Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtabbar.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "private/qlayoutengine_p.h"
6#if QT_CONFIG(itemviews)
7#include "qabstractitemdelegate.h"
8#endif
9#include "qapplication.h"
10#include "qevent.h"
11#include "qpainter.h"
12#include "qstyle.h"
13#include "qstyleoption.h"
14#include "qstylepainter.h"
15#if QT_CONFIG(tabwidget)
16#include "qtabwidget.h"
17#endif
18#if QT_CONFIG(tooltip)
19#include "qtooltip.h"
20#endif
21#if QT_CONFIG(whatsthis)
22#include "qwhatsthis.h"
23#endif
24#include "private/qtextengine_p.h"
25#if QT_CONFIG(accessibility)
26#include "qaccessible.h"
27#endif
28#ifdef Q_OS_MACOS
29#include <qpa/qplatformnativeinterface.h>
30#endif
31#include "qmainwindow.h"
32#include "private/qmainwindowlayout_p.h"
33
34#include "qdebug.h"
35#include "private/qapplication_p.h"
36#include "private/qtabbar_p.h"
37
39
40using namespace Qt::StringLiterals;
41using namespace std::chrono_literals;
42
43namespace {
44class CloseButton : public QAbstractButton
45{
46 Q_OBJECT
47
48public:
49 explicit CloseButton(QWidget *parent = nullptr);
50
51 QSize sizeHint() const override;
52 QSize minimumSizeHint() const override
53 { return sizeHint(); }
54 void enterEvent(QEnterEvent *event) override;
55 void leaveEvent(QEvent *event) override;
56 void paintEvent(QPaintEvent *event) override;
57
58 void setParentClipRect(const QRect &clipRect)
59 {
60 m_parentClipRect = clipRect;
61 }
62
63protected:
64 QRect m_parentClipRect;
65};
66}
67
68QMovableTabWidget::QMovableTabWidget(QWidget *parent)
69 : QWidget(parent)
70{
71}
72
73void QMovableTabWidget::setPixmap(const QPixmap &pixmap)
74{
75 m_pixmap = pixmap;
76 update();
77}
78
79void QMovableTabWidget::paintEvent(QPaintEvent *e)
80{
81 Q_UNUSED(e);
82 QPainter p(this);
83 p.drawPixmap(0, 0, m_pixmap);
84}
85
86void QTabBarPrivate::updateMacBorderMetrics()
87{
88#if defined(Q_OS_MACOS)
89 Q_Q(QTabBar);
90
91 auto *mainWindow = qobject_cast<const QMainWindow*>(q->window());
92 if (!mainWindow)
93 return;
94
95 // Extend the unified title and toolbar area to cover the tab bar iff
96 // 1) the tab bar is in document mode
97 // 2) the tab bar is directly below an "unified" area.
98 // The extending itself is done in the Mac style,
99 // this function registers geometry and visibility state for the tab bar.
100
101 // Calculate geometry
102 int upper, lower;
103 if (documentMode) {
104 QPoint windowPos = q->mapTo(q->window(), QPoint(0,0));
105 upper = windowPos.y();
106 int tabStripHeight = q->tabSizeHint(0).height();
107 int pixelTweak = -3;
108 lower = upper + tabStripHeight + pixelTweak;
109 } else {
110 upper = 0;
111 lower = 0;
112 }
113
114 extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window);
115 auto *mainWindowLayout = qt_mainwindow_layout(mainWindow);
116 mainWindowLayout->registerUnifiedToolBarArea(q, upper, lower);
117 mainWindowLayout->setUnifiedToolBarAreaEnabled(q, q->isVisible());
118#endif
119}
120
121/*!
122 \internal
123 This is basically QTabBar::initStyleOption() but
124 without the expensive QFontMetrics::elidedText() call.
125*/
126
127void QTabBarPrivate::initBasicStyleOption(QStyleOptionTab *option, int tabIndex) const
128{
129 Q_Q(const QTabBar);
130 const int totalTabs = tabList.size();
131
132 if (!option || (tabIndex < 0 || tabIndex >= totalTabs))
133 return;
134
135 const QTabBarPrivate::Tab &tab = *tabList.at(tabIndex);
136 option->initFrom(q);
137 option->state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
138 option->rect = q->tabRect(tabIndex);
139 const bool isCurrent = tabIndex == currentIndex;
140 option->row = 0;
141 if (tabIndex == pressedIndex)
142 option->state |= QStyle::State_Sunken;
143 if (isCurrent)
144 option->state |= QStyle::State_Selected;
145 if (isCurrent && q->hasFocus())
146 option->state |= QStyle::State_HasFocus;
147 if (!tab.enabled)
148 option->state &= ~QStyle::State_Enabled;
149 if (q->isActiveWindow())
150 option->state |= QStyle::State_Active;
151 if (!dragInProgress && option->rect == hoverRect)
152 option->state |= QStyle::State_MouseOver;
153 option->shape = shape;
154 option->text = tab.text;
155
156 if (tab.textColor.isValid())
157 option->palette.setColor(q->foregroundRole(), tab.textColor);
158 option->icon = tab.icon;
159 option->iconSize = q->iconSize(); // Will get the default value then.
160
161 option->leftButtonSize = tab.leftWidget ? tab.leftWidget->size() : QSize();
162 option->rightButtonSize = tab.rightWidget ? tab.rightWidget->size() : QSize();
163 option->documentMode = documentMode;
164
165 if (tabIndex > 0 && tabIndex - 1 == currentIndex)
166 option->selectedPosition = QStyleOptionTab::PreviousIsSelected;
167 else if (tabIndex + 1 < totalTabs && tabIndex + 1 == currentIndex)
168 option->selectedPosition = QStyleOptionTab::NextIsSelected;
169 else
170 option->selectedPosition = QStyleOptionTab::NotAdjacent;
171
172 const bool paintBeginning = (tabIndex == firstVisible) || (dragInProgress && tabIndex == pressedIndex + 1);
173 const bool paintEnd = (tabIndex == lastVisible) || (dragInProgress && tabIndex == pressedIndex - 1);
174 if (paintBeginning) {
175 if (paintEnd)
176 option->position = QStyleOptionTab::OnlyOneTab;
177 else
178 option->position = QStyleOptionTab::Beginning;
179 } else if (paintEnd) {
180 option->position = QStyleOptionTab::End;
181 } else {
182 option->position = QStyleOptionTab::Middle;
183 }
184
185#if QT_CONFIG(tabwidget)
186 if (const QTabWidget *tw = qobject_cast<const QTabWidget *>(q->parentWidget())) {
187 option->features |= QStyleOptionTab::HasFrame;
188 if (tw->cornerWidget(Qt::TopLeftCorner) || tw->cornerWidget(Qt::BottomLeftCorner))
189 option->cornerWidgets |= QStyleOptionTab::LeftCornerWidget;
190 if (tw->cornerWidget(Qt::TopRightCorner) || tw->cornerWidget(Qt::BottomRightCorner))
191 option->cornerWidgets |= QStyleOptionTab::RightCornerWidget;
192 }
193#endif
194 if (tab.measuringMinimum)
195 option->features |= QStyleOptionTab::MinimumSizeHint;
196 option->tabIndex = tabIndex;
197}
198
199/*!
200 Initialize \a option with the values from the tab at \a tabIndex. This method
201 is useful for subclasses when they need a QStyleOptionTab,
202 but don't want to fill in all the information themselves.
203
204 \sa QStyleOption::initFrom(), QTabWidget::initStyleOption()
205*/
206void QTabBar::initStyleOption(QStyleOptionTab *option, int tabIndex) const
207{
208 Q_D(const QTabBar);
209 d->initBasicStyleOption(option, tabIndex);
210
211 QRect textRect = style()->subElementRect(QStyle::SE_TabBarTabText, option, this);
212 option->text = fontMetrics().elidedText(option->text, d->elideMode, textRect.width(),
213 Qt::TextShowMnemonic);
214}
215
216/*!
217 \class QTabBar
218 \brief The QTabBar class provides a tab bar, e.g. for use in tabbed dialogs.
219
220 \ingroup basicwidgets
221 \inmodule QtWidgets
222
223 QTabBar is straightforward to use; it draws the tabs using one of
224 the predefined \l{QTabBar::Shape}{shapes}, and emits a
225 signal when a tab is selected. It can be subclassed to tailor the
226 look and feel. Qt also provides a ready-made \l{QTabWidget}.
227
228 Each tab has a tabText(), an optional tabIcon(), an optional
229 tabToolTip(), optional tabWhatsThis() and optional tabData().
230 The tabs's attributes can be changed with setTabText(), setTabIcon(),
231 setTabToolTip(), setTabWhatsThis and setTabData(). Each tabs can be
232 enabled or disabled individually with setTabEnabled().
233
234 Each tab can display text in a distinct color. The current text color
235 for a tab can be found with the tabTextColor() function. Set the text
236 color for a particular tab with setTabTextColor().
237
238 Tabs are added using addTab(), or inserted at particular positions
239 using insertTab(). The total number of tabs is given by
240 count(). Tabs can be removed from the tab bar with
241 removeTab(). Combining removeTab() and insertTab() allows you to
242 move tabs to different positions.
243
244 The \l shape property defines the tabs' appearance. The choice of
245 shape is a matter of taste, although tab dialogs (for preferences
246 and similar) invariably use \l RoundedNorth.
247 Tab controls in windows other than dialogs almost
248 always use either \l RoundedSouth or \l TriangularSouth. Many
249 spreadsheets and other tab controls in which all the pages are
250 essentially similar use \l TriangularSouth, whereas \l
251 RoundedSouth is used mostly when the pages are different (e.g. a
252 multi-page tool palette). The default in QTabBar is \l
253 RoundedNorth.
254
255 The most important part of QTabBar's API is the currentChanged()
256 signal. This is emitted whenever the current tab changes (even at
257 startup, when the current tab changes from 'none'). There is also
258 a slot, setCurrentIndex(), which can be used to select a tab
259 programmatically. The function currentIndex() returns the index of
260 the current tab, \l count holds the number of tabs.
261
262 QTabBar creates automatic mnemonic keys in the manner of QAbstractButton;
263 e.g. if a tab's label is "\&Graphics", Alt+G becomes a shortcut
264 key for switching to that tab.
265
266 The following virtual functions may need to be reimplemented in
267 order to tailor the look and feel or store extra data with each
268 tab:
269
270 \list
271 \li tabSizeHint() calcuates the size of a tab.
272 \li tabInserted() notifies that a new tab was added.
273 \li tabRemoved() notifies that a tab was removed.
274 \li tabLayoutChange() notifies that the tabs have been re-laid out.
275 \li paintEvent() paints all tabs.
276 \endlist
277
278 For subclasses, you might also need the tabRect() functions which
279 returns the visual geometry of a single tab.
280
281 \table 100%
282 \row \li \inlineimage {fusion-tabbar.png} {Tab bar with three tabs}
283 \li A tab bar shown in the \l{Qt Widget Gallery}{Fusion widget style}.
284 \row \li \inlineimage {fusion-tabbar-truncated.png} {Truncated tab bar}
285 \li A truncated tab bar shown in the Fusion widget style.
286 \endtable
287
288 \sa QTabWidget
289*/
290
291/*!
292 \enum QTabBar::Shape
293
294 This enum type lists the built-in shapes supported by QTabBar. Treat these
295 as hints as some styles may not render some of the shapes. However,
296 position should be honored.
297
298 \value RoundedNorth The normal rounded look above the pages
299
300 \value RoundedSouth The normal rounded look below the pages
301
302 \value RoundedWest The normal rounded look on the left side of the pages
303
304 \value RoundedEast The normal rounded look on the right side the pages
305
306 \value TriangularNorth Triangular tabs above the pages.
307
308 \value TriangularSouth Triangular tabs similar to those used in
309 the Excel spreadsheet, for example
310
311 \value TriangularWest Triangular tabs on the left of the pages.
312
313 \value TriangularEast Triangular tabs on the right of the pages.
314*/
315
316/*!
317 \fn void QTabBar::currentChanged(int index)
318
319 This signal is emitted when the tab bar's current tab changes. The
320 new current has the given \a index, or -1 if there isn't a new one
321 (for example, if there are no tab in the QTabBar)
322*/
323
324/*!
325 \fn void QTabBar::tabCloseRequested(int index)
326 \since 4.5
327
328 This signal is emitted when the close button on a tab is clicked.
329 The \a index is the index that should be removed.
330
331 \sa setTabsClosable()
332*/
333
334/*!
335 \fn void QTabBar::tabMoved(int from, int to)
336 \since 4.5
337
338 This signal is emitted when the tab has moved the tab
339 at index position \a from to index position \a to.
340
341 note: QTabWidget will automatically move the page when
342 this signal is emitted from its tab bar.
343
344 \sa moveTab()
345*/
346
347/*!
348 \fn void QTabBar::tabBarClicked(int index)
349
350 This signal is emitted when user clicks on a tab at an \a index.
351
352 \a index is the index of a clicked tab, or -1 if no tab is under the cursor.
353
354 \since 5.2
355*/
356
357/*!
358 \fn void QTabBar::tabBarDoubleClicked(int index)
359
360 This signal is emitted when the user double clicks on a tab at \a index.
361
362 \a index refers to the tab clicked, or -1 if no tab is under the cursor.
363
364 \since 5.2
365*/
366
367void QTabBarPrivate::init()
368{
369 Q_Q(QTabBar);
370 leftB = new QToolButton(q);
371 leftB->setObjectName(u"ScrollLeftButton"_s);
372 leftB->setAutoRepeat(true);
373 QObjectPrivate::connect(leftB, &QToolButton::clicked,
374 this, &QTabBarPrivate::scrollTabs);
375 leftB->hide();
376 rightB = new QToolButton(q);
377 rightB->setObjectName(u"ScrollRightButton"_s);
378 rightB->setAutoRepeat(true);
379 QObjectPrivate::connect(rightB, &QToolButton::clicked,
380 this, &QTabBarPrivate::scrollTabs);
381 rightB->hide();
382#ifdef QT_KEYPAD_NAVIGATION
383 if (QApplicationPrivate::keypadNavigationEnabled()) {
384 leftB->setFocusPolicy(Qt::NoFocus);
385 rightB->setFocusPolicy(Qt::NoFocus);
386 q->setFocusPolicy(Qt::NoFocus);
387 } else
388#endif
389 q->setFocusPolicy(Qt::TabFocus);
390
391#if QT_CONFIG(accessibility)
392 leftB->setAccessibleName(QTabBar::tr("Scroll Left"));
393 rightB->setAccessibleName(QTabBar::tr("Scroll Right"));
394#endif
395 q->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
396 elideMode = Qt::TextElideMode(q->style()->styleHint(QStyle::SH_TabBar_ElideMode, nullptr, q));
397 useScrollButtons = !q->style()->styleHint(QStyle::SH_TabBar_PreferNoArrows, nullptr, q);
398}
399
400int QTabBarPrivate::indexAtPos(const QPoint &p) const
401{
402 Q_Q(const QTabBar);
403 if (q->tabRect(currentIndex).contains(p))
404 return currentIndex;
405 for (int i = 0; i < tabList.size(); ++i)
406 if (tabList.at(i)->enabled && q->tabRect(i).contains(p))
407 return i;
408 return -1;
409}
410
411void QTabBarPrivate::layoutTabs()
412{
413 Q_Q(QTabBar);
414 layoutDirty = false;
415 QSize size = q->size();
416 int last, available;
417 int maxExtent;
418 bool vertTabs = verticalTabs(shape);
419 int tabChainIndex = 0;
420 int hiddenTabs = 0;
421 const auto *style = q->style();
422
423 Qt::Alignment tabAlignment = Qt::Alignment(style->styleHint(QStyle::SH_TabBar_Alignment, nullptr, q));
424 QList<QLayoutStruct> tabChain(tabList.size() + 2);
425
426 // We put an empty item at the front and back and set its expansive attribute
427 // depending on tabAlignment and expanding.
428 tabChain[tabChainIndex].init();
429 tabChain[tabChainIndex].expansive = (!expanding)
430 && (tabAlignment != Qt::AlignLeft)
431 && (tabAlignment != Qt::AlignJustify);
432 tabChain[tabChainIndex].empty = true;
433 ++tabChainIndex;
434
435 // We now go through our list of tabs and set the minimum size and the size hint
436 // This will allow us to elide text if necessary. Since we don't set
437 // a maximum size, tabs will EXPAND to fill up the empty space.
438 // Since tab widget is rather *ahem* strict about keeping the geometry of the
439 // tab bar to its absolute minimum, this won't bleed through, but will show up
440 // if you use tab bar on its own (a.k.a. not a bug, but a feature).
441 // Update: if expanding is false, we DO set a maximum size to prevent the tabs
442 // being wider than necessary.
443 if (!vertTabs) {
444 int minx = 0;
445 int x = 0;
446 int maxHeight = 0;
447 for (int i = 0; i < tabList.size(); ++i) {
448 const auto tab = tabList.at(i);
449 if (!tab->visible) {
450 ++hiddenTabs;
451 continue;
452 }
453 QSize sz = q->tabSizeHint(i);
454 tab->maxRect = QRect(x, 0, sz.width(), sz.height());
455 x += sz.width();
456 maxHeight = qMax(maxHeight, sz.height());
457 sz = q->minimumTabSizeHint(i);
458 tab->minRect = QRect(minx, 0, sz.width(), sz.height());
459 minx += sz.width();
460 tabChain[tabChainIndex].init();
461 tabChain[tabChainIndex].sizeHint = tab->maxRect.width();
462 tabChain[tabChainIndex].minimumSize = sz.width();
463 tabChain[tabChainIndex].empty = false;
464 tabChain[tabChainIndex].expansive = true;
465
466 if (!expanding)
467 tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint;
468 ++tabChainIndex;
469 }
470
471 last = minx;
472 available = size.width();
473 maxExtent = maxHeight;
474 } else {
475 int miny = 0;
476 int y = 0;
477 int maxWidth = 0;
478 for (int i = 0; i < tabList.size(); ++i) {
479 auto tab = tabList.at(i);
480 if (!tab->visible) {
481 ++hiddenTabs;
482 continue;
483 }
484 QSize sz = q->tabSizeHint(i);
485 tab->maxRect = QRect(0, y, sz.width(), sz.height());
486 y += sz.height();
487 maxWidth = qMax(maxWidth, sz.width());
488 sz = q->minimumTabSizeHint(i);
489 tab->minRect = QRect(0, miny, sz.width(), sz.height());
490 miny += sz.height();
491 tabChain[tabChainIndex].init();
492 tabChain[tabChainIndex].sizeHint = tab->maxRect.height();
493 tabChain[tabChainIndex].minimumSize = sz.height();
494 tabChain[tabChainIndex].empty = false;
495 tabChain[tabChainIndex].expansive = true;
496
497 if (!expanding)
498 tabChain[tabChainIndex].maximumSize = tabChain[tabChainIndex].sizeHint;
499 ++tabChainIndex;
500 }
501
502 last = miny;
503 available = size.height();
504 maxExtent = maxWidth;
505 }
506
507 // Mirror our front item.
508 tabChain[tabChainIndex].init();
509 tabChain[tabChainIndex].expansive = (!expanding)
510 && (tabAlignment != Qt::AlignRight)
511 && (tabAlignment != Qt::AlignJustify);
512 tabChain[tabChainIndex].empty = true;
513 Q_ASSERT(tabChainIndex == tabChain.size() - 1 - hiddenTabs); // add an assert just to make sure.
514
515 // Do the calculation
516 qGeomCalc(tabChain, 0, tabChain.size(), 0, qMax(available, last), 0);
517
518 // Use the results
519 hiddenTabs = 0;
520 for (int i = 0; i < tabList.size(); ++i) {
521 auto tab = tabList.at(i);
522 if (!tab->visible) {
523 tab->rect = QRect();
524 ++hiddenTabs;
525 continue;
526 }
527 const QLayoutStruct &lstruct = tabChain.at(i + 1 - hiddenTabs);
528 if (!vertTabs)
529 tab->rect.setRect(lstruct.pos, 0, lstruct.size, maxExtent);
530 else
531 tab->rect.setRect(0, lstruct.pos, maxExtent, lstruct.size);
532 }
533
534 if (useScrollButtons && tabList.size() && last > available) {
535 const QRect scrollRect = normalizedScrollRect(0);
536
537 Q_Q(QTabBar);
538 QStyleOption opt;
539 opt.initFrom(q);
540 QRect scrollButtonLeftRect = style->subElementRect(QStyle::SE_TabBarScrollLeftButton, &opt, q);
541 QRect scrollButtonRightRect = style->subElementRect(QStyle::SE_TabBarScrollRightButton, &opt, q);
542 int scrollButtonWidth = style->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, &opt, q);
543
544 const auto setIconOr = [](QToolButton *tb, const QIcon &icon, Qt::ArrowType arrowType) {
545 if (icon.isNull())
546 tb->setArrowType(arrowType);
547 else
548 tb->setIcon(icon);
549 };
550 // Normally SE_TabBarScrollLeftButton should have the same width as PM_TabBarScrollButtonWidth.
551 // But if that is not the case, we set the actual button width to PM_TabBarScrollButtonWidth, and
552 // use the extra space from SE_TabBarScrollLeftButton as margins towards the tabs.
553 if (vertTabs) {
554 scrollButtonLeftRect.setHeight(scrollButtonWidth);
555 scrollButtonRightRect.setY(scrollButtonRightRect.bottom() + 1 - scrollButtonWidth);
556 scrollButtonRightRect.setHeight(scrollButtonWidth);
557 const auto upIcon = style->standardIcon(QStyle::SP_TabScrollUpButton, &opt, q);
558 const auto downIcon = style->standardIcon(QStyle::SP_TabScrollDownButton, &opt, q);
559 setIconOr(leftB, upIcon, Qt::UpArrow);
560 setIconOr(rightB, downIcon, Qt::DownArrow);
561 } else if (q->layoutDirection() == Qt::RightToLeft) {
562 scrollButtonRightRect.setWidth(scrollButtonWidth);
563 scrollButtonLeftRect.setX(scrollButtonLeftRect.right() + 1 - scrollButtonWidth);
564 scrollButtonLeftRect.setWidth(scrollButtonWidth);
565 const auto leftIcon = style->standardIcon(QStyle::SP_TabScrollLeftButton, &opt, q);
566 const auto rightIcon = style->standardIcon(QStyle::SP_TabScrollRightButton, &opt, q);
567 setIconOr(leftB, rightIcon, Qt::RightArrow);
568 setIconOr(rightB, leftIcon, Qt::LeftArrow);
569 } else {
570 scrollButtonLeftRect.setWidth(scrollButtonWidth);
571 scrollButtonRightRect.setX(scrollButtonRightRect.right() + 1 - scrollButtonWidth);
572 scrollButtonRightRect.setWidth(scrollButtonWidth);
573 const auto leftIcon = style->standardIcon(QStyle::SP_TabScrollLeftButton, &opt, q);
574 const auto rightIcon = style->standardIcon(QStyle::SP_TabScrollRightButton, &opt, q);
575 setIconOr(leftB, leftIcon, Qt::LeftArrow);
576 setIconOr(rightB, rightIcon, Qt::RightArrow);
577 }
578
579 leftB->setGeometry(scrollButtonLeftRect);
580 leftB->setEnabled(false);
581 leftB->show();
582
583 rightB->setGeometry(scrollButtonRightRect);
584 rightB->setEnabled(last + scrollRect.left() > scrollRect.x() + scrollRect.width());
585 rightB->show();
586 } else {
587 rightB->hide();
588 leftB->hide();
589 }
590
591 layoutWidgets();
592 q->tabLayoutChange();
593}
594
595QRect QTabBarPrivate::scrollRect(int index) const
596{
597 const QRect r = normalizedScrollRect(index);
598 return verticalTabs(shape) ? QRect(QPoint(r.y(), r.x()), r.size().transposed()) : r;
599}
600
601QRect QTabBarPrivate::normalizedScrollRect(int index) const
602{
603 // "Normalized scroll rect" means return the free space on the tab bar
604 // that doesn't overlap with scroll buttons or tear indicators, and
605 // always return the rect as horizontal Qt::LeftToRight, even if the
606 // tab bar itself is in a different orientation.
607
608 Q_Q(const QTabBar);
609 // If scrollbuttons are not visible, then there's no tear either, and
610 // the entire widget is the scroll rect.
611 if (leftB->isHidden())
612 return verticalTabs(shape) ? q->rect().transposed() : q->rect();
613
614 QStyleOptionTab opt;
615 q->initStyleOption(&opt, currentIndex);
616 opt.rect = q->rect();
617
618 const auto style = q->style();
619 QRect scrollButtonLeftRect = style->subElementRect(QStyle::SE_TabBarScrollLeftButton, &opt, q);
620 QRect scrollButtonRightRect = style->subElementRect(QStyle::SE_TabBarScrollRightButton, &opt, q);
621 QRect tearLeftRect = style->subElementRect(QStyle::SE_TabBarTearIndicatorLeft, &opt, q);
622 QRect tearRightRect = style->subElementRect(QStyle::SE_TabBarTearIndicatorRight, &opt, q);
623
624 if (verticalTabs(shape)) {
625 int topEdge, bottomEdge;
626 bool leftButtonIsOnTop = scrollButtonLeftRect.y() < q->height() / 2;
627 bool rightButtonIsOnTop = scrollButtonRightRect.y() < q->height() / 2;
628
629 if (leftButtonIsOnTop && rightButtonIsOnTop) {
630 topEdge = scrollButtonRightRect.bottom() + 1;
631 bottomEdge = q->height();
632 } else if (!leftButtonIsOnTop && !rightButtonIsOnTop) {
633 topEdge = 0;
634 bottomEdge = scrollButtonLeftRect.top();
635 } else {
636 topEdge = scrollButtonLeftRect.bottom() + 1;
637 bottomEdge = scrollButtonRightRect.top();
638 }
639
640 const auto lastTab = lastVisibleTab();
641 if (!lastTab)
642 return {};
643 bool tearTopVisible = index != 0 && topEdge != -scrollOffset;
644 bool tearBottomVisible = index != tabList.size() - 1 && bottomEdge != lastTab->rect.bottom() + 1 - scrollOffset;
645 if (tearTopVisible && !tearLeftRect.isNull())
646 topEdge = tearLeftRect.bottom() + 1;
647 if (tearBottomVisible && !tearRightRect.isNull())
648 bottomEdge = tearRightRect.top();
649
650 return QRect(topEdge, 0, bottomEdge - topEdge, q->height());
651 } else {
652 if (q->layoutDirection() == Qt::RightToLeft) {
653 scrollButtonLeftRect = QStyle::visualRect(Qt::RightToLeft, q->rect(), scrollButtonLeftRect);
654 scrollButtonRightRect = QStyle::visualRect(Qt::RightToLeft, q->rect(), scrollButtonRightRect);
655 tearLeftRect = QStyle::visualRect(Qt::RightToLeft, q->rect(), tearLeftRect);
656 tearRightRect = QStyle::visualRect(Qt::RightToLeft, q->rect(), tearRightRect);
657 }
658
659 int leftEdge, rightEdge;
660 bool leftButtonIsOnLeftSide = scrollButtonLeftRect.x() < q->width() / 2;
661 bool rightButtonIsOnLeftSide = scrollButtonRightRect.x() < q->width() / 2;
662
663 if (leftButtonIsOnLeftSide && rightButtonIsOnLeftSide) {
664 leftEdge = scrollButtonRightRect.right() + 1;
665 rightEdge = q->width();
666 } else if (!leftButtonIsOnLeftSide && !rightButtonIsOnLeftSide) {
667 leftEdge = 0;
668 rightEdge = scrollButtonLeftRect.left();
669 } else {
670 leftEdge = scrollButtonLeftRect.right() + 1;
671 rightEdge = scrollButtonRightRect.left();
672 }
673
674 const auto lastTab = lastVisibleTab();
675 if (!lastTab)
676 return {};
677 bool tearLeftVisible = index != 0 && leftEdge != -scrollOffset;
678 bool tearRightVisible = index != tabList.size() - 1 && rightEdge != lastTab->rect.right() + 1 - scrollOffset;
679 if (tearLeftVisible && !tearLeftRect.isNull())
680 leftEdge = tearLeftRect.right() + 1;
681 if (tearRightVisible && !tearRightRect.isNull())
682 rightEdge = tearRightRect.left();
683
684 return QRect(leftEdge, 0, rightEdge - leftEdge, q->height());
685 }
686}
687
688int QTabBarPrivate::hoveredTabIndex() const
689{
690 if (dragInProgress)
691 return currentIndex;
692 if (hoverIndex >= 0)
693 return hoverIndex;
694 return -1;
695}
696
697void QTabBarPrivate::makeVisible(int index)
698{
699 Q_Q(QTabBar);
700 if (!validIndex(index))
701 return;
702
703 const auto lastTab = lastVisibleTab();
704 const QRect tabRect = tabList.at(index)->rect;
705 const int oldScrollOffset = scrollOffset;
706 const bool horiz = !verticalTabs(shape);
707 const int available = horiz ? q->width() : q->height();
708 const int tabStart = horiz ? tabRect.left() : tabRect.top();
709 const int tabEnd = horiz ? tabRect.right() : tabRect.bottom();
710 const int lastTabEnd = lastTab ? (horiz ? lastTab->rect.right() : lastTab->rect.bottom()) : 0;
711 const QRect scrollRect = normalizedScrollRect(index);
712 const QRect entireScrollRect = normalizedScrollRect(0); // ignore tears
713 const int scrolledTabBarStart = qMax(1, scrollRect.left() + scrollOffset);
714 const int scrolledTabBarEnd = qMin(lastTabEnd - 1, scrollRect.right() + scrollOffset);
715
716 if (available >= lastTabEnd) {
717 // the entire tabbar fits, reset scroll
718 scrollOffset = 0;
719 } else if (tabStart < scrolledTabBarStart) {
720 // Tab is outside on the left, so scroll left.
721 scrollOffset = tabStart - scrollRect.left();
722 } else if (tabEnd > scrolledTabBarEnd) {
723 // Tab is outside on the right, so scroll right.
724 scrollOffset = qMax(0, tabEnd - scrollRect.right());
725 } else if (scrollOffset + entireScrollRect.width() > lastTabEnd + 1) {
726 // fill any free space on the right without overshooting
727 scrollOffset = qMax(0, lastTabEnd - entireScrollRect.width() + 1);
728 }
729
730 leftB->setEnabled(scrollOffset > -scrollRect.left());
731 rightB->setEnabled(scrollOffset < lastTabEnd - scrollRect.right());
732
733 if (oldScrollOffset != scrollOffset) {
734 q->update();
735 layoutWidgets();
736 }
737}
738
739void QTabBarPrivate::killSwitchTabTimer()
740{
741 switchTabTimer.stop();
742 switchTabCurrentIndex = -1;
743}
744
745void QTabBarPrivate::layoutTab(int index)
746{
747 Q_Q(QTabBar);
748 Q_ASSERT(index >= 0);
749
750 const Tab *tab = tabList.at(index);
751 bool vertical = verticalTabs(shape);
752 if (!(tab->leftWidget || tab->rightWidget))
753 return;
754
755 QStyleOptionTab opt;
756 q->initStyleOption(&opt, index);
757 if (tab->leftWidget) {
758 QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabLeftButton, &opt, q);
759 QPoint p = rect.topLeft();
760 if (tab->dragOffset != 0) {
761 if (vertical)
762 p.setY(p.y() + tab->dragOffset);
763 else
764 p.setX(p.x() + tab->dragOffset);
765 }
766 tab->leftWidget->move(p);
767 }
768 if (tab->rightWidget) {
769 QRect rect = q->style()->subElementRect(QStyle::SE_TabBarTabRightButton, &opt, q);
770 QPoint p = rect.topLeft();
771 if (tab->dragOffset != 0) {
772 if (vertical)
773 p.setY(p.y() + tab->dragOffset);
774 else
775 p.setX(p.x() + tab->dragOffset);
776 }
777 tab->rightWidget->move(p);
778 }
779}
780
781void QTabBarPrivate::layoutWidgets(int start)
782{
783 Q_Q(QTabBar);
784 for (int i = start; i < q->count(); ++i) {
785 layoutTab(i);
786 }
787}
788
789void QTabBarPrivate::autoHideTabs()
790{
791 Q_Q(QTabBar);
792
793 if (autoHide)
794 q->setVisible(q->count() > 1);
795}
796
797void QTabBarPrivate::closeTab()
798{
799 Q_Q(QTabBar);
800 QObject *object = q->sender();
801 int tabToClose = -1;
802 QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)q->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, q);
803 for (int i = 0; i < tabList.size(); ++i) {
804 if (closeSide == QTabBar::LeftSide) {
805 if (tabList.at(i)->leftWidget == object) {
806 tabToClose = i;
807 break;
808 }
809 } else {
810 if (tabList.at(i)->rightWidget == object) {
811 tabToClose = i;
812 break;
813 }
814 }
815 }
816 if (tabToClose != -1)
817 emit q->tabCloseRequested(tabToClose);
818}
819
820void QTabBarPrivate::scrollTabs()
821{
822 Q_Q(QTabBar);
823 const QObject *sender = q->sender();
824 const bool horizontal = !verticalTabs(shape);
825 const QRect scrollRect = normalizedScrollRect().translated(scrollOffset, 0);
826
827 if (sender == leftB) {
828 for (qsizetype i = tabList.size() - 1; i >= 0; --i) {
829 const auto *tab = tabList.at(i);
830 if (!tab->visible)
831 continue;
832 int start = horizontal ? tab->rect.left() : tab->rect.top();
833 if (start < scrollRect.left()) {
834 makeVisible(i);
835 return;
836 }
837 }
838 } else if (sender == rightB) {
839 for (qsizetype i = 0; i < tabList.size(); ++i) {
840 const auto *tab = tabList.at(i);
841 if (!tab->visible)
842 continue;
843 const auto &tabRect = tab->rect;
844 int start = horizontal ? tabRect.left() : tabRect.top();
845 int end = horizontal ? tabRect.right() : tabRect.bottom();
846 if (end > scrollRect.right() && start > scrollRect.left()) {
847 makeVisible(i);
848 return;
849 }
850 }
851 }
852}
853
854void QTabBarPrivate::refresh()
855{
856 Q_Q(QTabBar);
857
858 // be safe in case a subclass is also handling move with the tabs
859 if (pressedIndex != -1
860 && movable
861 && mouseButtons == Qt::NoButton) {
862 moveTabFinished(pressedIndex);
863 if (!validIndex(pressedIndex))
864 pressedIndex = -1;
865 }
866
867 if (!q->isVisible()) {
868 layoutDirty = true;
869 } else {
870 layoutTabs();
871 makeVisible(currentIndex);
872 q->update();
873 q->updateGeometry();
874 }
875}
876
877/*!
878 Creates a new tab bar with the given \a parent.
879*/
880QTabBar::QTabBar(QWidget* parent)
881 :QWidget(*new QTabBarPrivate, parent, { })
882{
883 Q_D(QTabBar);
884 d->init();
885}
886
887
888/*!
889 Destroys the tab bar.
890*/
891QTabBar::~QTabBar()
892{
893}
894
895/*!
896 \property QTabBar::shape
897 \brief the shape of the tabs in the tab bar
898
899 Possible values for this property are described by the Shape enum.
900*/
901
902
903QTabBar::Shape QTabBar::shape() const
904{
905 Q_D(const QTabBar);
906 return d->shape;
907}
908
909void QTabBar::setShape(Shape shape)
910{
911 Q_D(QTabBar);
912 if (d->shape == shape)
913 return;
914 d->shape = shape;
915 d->refresh();
916}
917
918/*!
919 \property QTabBar::drawBase
920 \brief defines whether or not tab bar should draw its base.
921
922 If true then QTabBar draws a base in relation to the styles overlap.
923 Otherwise only the tabs are drawn.
924
925 \sa QStyle::pixelMetric(), QStyle::PM_TabBarBaseOverlap, QStyleOptionTabBarBase
926*/
927
928void QTabBar::setDrawBase(bool drawBase)
929{
930 Q_D(QTabBar);
931 if (d->drawBase == drawBase)
932 return;
933 d->drawBase = drawBase;
934 update();
935}
936
937bool QTabBar::drawBase() const
938{
939 Q_D(const QTabBar);
940 return d->drawBase;
941}
942
943/*!
944 Adds a new tab with text \a text. Returns the new
945 tab's index.
946*/
947int QTabBar::addTab(const QString &text)
948{
949 return insertTab(-1, text);
950}
951
952/*!
953 \overload
954
955 Adds a new tab with icon \a icon and text \a
956 text. Returns the new tab's index.
957*/
958int QTabBar::addTab(const QIcon& icon, const QString &text)
959{
960 return insertTab(-1, icon, text);
961}
962
963/*!
964 Inserts a new tab with text \a text at position \a index. If \a
965 index is out of range, the new tab is appended. Returns the new
966 tab's index.
967*/
968int QTabBar::insertTab(int index, const QString &text)
969{
970 return insertTab(index, QIcon(), text);
971}
972
973/*!\overload
974
975 Inserts a new tab with icon \a icon and text \a text at position
976 \a index. If \a index is out of range, the new tab is
977 appended. Returns the new tab's index.
978
979 If the QTabBar was empty before this function is called, the inserted tab
980 becomes the current tab.
981
982 Inserting a new tab at an index less than or equal to the current index
983 will increment the current index, but keep the current tab.
984*/
985int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
986{
987 Q_D(QTabBar);
988 if (!d->validIndex(index)) {
989 index = d->tabList.size();
990 d->tabList.append(new QTabBarPrivate::Tab(icon, text));
991 } else {
992 d->tabList.insert(index, new QTabBarPrivate::Tab(icon, text));
993 }
994#ifndef QT_NO_SHORTCUT
995 d->tabList.at(index)->shortcutId = grabShortcut(QKeySequence::mnemonic(text));
996#endif
997 d->firstVisible = qMax(qMin(index, d->firstVisible), 0);
998 d->refresh();
999 if (d->tabList.size() == 1)
1000 setCurrentIndex(index);
1001 else if (index <= d->currentIndex)
1002 ++d->currentIndex;
1003
1004 if (index <= d->lastVisible)
1005 ++d->lastVisible;
1006 else
1007 d->lastVisible = index;
1008
1009 if (d->closeButtonOnTabs) {
1010 QStyleOptionTab opt;
1011 initStyleOption(&opt, index);
1012 ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, this);
1013 QAbstractButton *closeButton = new CloseButton(this);
1014 QObjectPrivate::connect(closeButton, &CloseButton::clicked,
1015 d, &QTabBarPrivate::closeTab);
1016 setTabButton(index, closeSide, closeButton);
1017 }
1018
1019 for (const auto tab : std::as_const(d->tabList)) {
1020 if (tab->lastTab >= index)
1021 ++tab->lastTab;
1022 }
1023
1024 if (isVisible() && tabAt(d->mousePosition) == index) {
1025 if (d->scrollRect(index).contains(d->mousePosition)) {
1026 d->hoverIndex = index;
1027 d->hoverRect = tabRect(index);
1028 } else {
1029 d->hoverIndex = -1;
1030 d->hoverRect = QRect();
1031 }
1032 }
1033
1034 tabInserted(index);
1035 d->autoHideTabs();
1036 return index;
1037}
1038
1039
1040/*!
1041 Removes the tab at position \a index.
1042
1043 \sa SelectionBehavior
1044 */
1045void QTabBar::removeTab(int index)
1046{
1047 Q_D(QTabBar);
1048 if (d->validIndex(index)) {
1049 auto removedTab = d->tabList.at(index);
1050 if (d->dragInProgress)
1051 d->moveTabFinished(d->pressedIndex);
1052
1053#ifndef QT_NO_SHORTCUT
1054 releaseShortcut(d->tabList.at(index)->shortcutId);
1055#endif
1056 if (removedTab->leftWidget) {
1057 removedTab->leftWidget->hide();
1058 removedTab->leftWidget->deleteLater();
1059 removedTab->leftWidget = nullptr;
1060 }
1061 if (removedTab->rightWidget) {
1062 removedTab->rightWidget->hide();
1063 removedTab->rightWidget->deleteLater();
1064 removedTab->rightWidget = nullptr;
1065 }
1066
1067 int newIndex = removedTab->lastTab;
1068 d->tabList.removeAt(index);
1069 delete removedTab;
1070 for (auto tab : std::as_const(d->tabList)) {
1071 if (tab->lastTab == index)
1072 tab->lastTab = -1;
1073 if (tab->lastTab > index)
1074 --tab->lastTab;
1075 }
1076
1077 d->calculateFirstLastVisible(index, false, true);
1078
1079 if (index == d->currentIndex) {
1080 // The current tab is going away, in order to make sure
1081 // we emit that "current has changed", we need to reset this
1082 // around.
1083 d->currentIndex = -1;
1084 if (d->tabList.size() > 0) {
1085 switch(d->selectionBehaviorOnRemove) {
1086 case SelectPreviousTab:
1087 if (newIndex > index)
1088 newIndex--;
1089 if (d->validIndex(newIndex) && d->tabList.at(newIndex)->visible)
1090 break;
1091 Q_FALLTHROUGH();
1092 case SelectRightTab:
1093 newIndex = qBound(d->firstVisible, index, d->lastVisible);
1094 break;
1095 case SelectLeftTab:
1096 newIndex = qBound(d->firstVisible, index-1, d->lastVisible);
1097 break;
1098 default:
1099 break;
1100 }
1101
1102 if (d->validIndex(newIndex)) {
1103 // don't loose newIndex's old through setCurrentIndex
1104 int bump = d->tabList.at(newIndex)->lastTab;
1105 setCurrentIndex(newIndex);
1106 d->tabList.at(newIndex)->lastTab = bump;
1107 } else {
1108 // we had a valid current index, but there are no visible tabs left
1109 emit currentChanged(-1);
1110 }
1111 } else {
1112 emit currentChanged(-1);
1113 }
1114 } else if (index < d->currentIndex) {
1115 setCurrentIndex(d->currentIndex - 1);
1116 }
1117 d->refresh();
1118 d->autoHideTabs();
1119 if (d->hoverRect.isValid()) {
1120 update(d->hoverRect);
1121 d->hoverIndex = tabAt(d->mousePosition);
1122 if (d->validIndex(d->hoverIndex)
1123 && d->scrollRect(d->hoverIndex).contains(d->mousePosition)) {
1124 d->hoverRect = tabRect(d->hoverIndex);
1125 update(d->hoverRect);
1126 } else {
1127 d->hoverRect = QRect();
1128 d->hoverIndex = -1;
1129 }
1130 }
1131 tabRemoved(index);
1132 }
1133}
1134
1135
1136/*!
1137 Returns \c true if the tab at position \a index is enabled; otherwise
1138 returns \c false.
1139*/
1140bool QTabBar::isTabEnabled(int index) const
1141{
1142 Q_D(const QTabBar);
1143 if (const QTabBarPrivate::Tab *tab = d->at(index))
1144 return tab->enabled;
1145 return false;
1146}
1147
1148/*!
1149 If \a enabled is true then the tab at position \a index is
1150 enabled; otherwise the item at position \a index is disabled.
1151*/
1152void QTabBar::setTabEnabled(int index, bool enabled)
1153{
1154 Q_D(QTabBar);
1155 if (QTabBarPrivate::Tab *tab = d->at(index)) {
1156 tab->enabled = enabled;
1157#ifndef QT_NO_SHORTCUT
1158 setShortcutEnabled(tab->shortcutId, enabled);
1159#endif
1160 update();
1161 if (!enabled && index == d->currentIndex)
1162 setCurrentIndex(d->selectNewCurrentIndexFrom(index+1));
1163 else if (enabled && !isTabVisible(d->currentIndex))
1164 setCurrentIndex(d->selectNewCurrentIndexFrom(index));
1165 }
1166}
1167
1168
1169/*!
1170 Returns true if the tab at position \a index is visible; otherwise
1171 returns false.
1172 \since 5.15
1173*/
1174bool QTabBar::isTabVisible(int index) const
1175{
1176 Q_D(const QTabBar);
1177 if (d->validIndex(index))
1178 return d->tabList.at(index)->visible;
1179 return false;
1180}
1181
1182/*!
1183 If \a visible is true, make the tab at position \a index visible,
1184 otherwise make it hidden.
1185 \since 5.15
1186*/
1187void QTabBar::setTabVisible(int index, bool visible)
1188{
1189 Q_D(QTabBar);
1190 if (QTabBarPrivate::Tab *tab = d->at(index)) {
1191 d->layoutDirty = (visible != tab->visible);
1192 if (!d->layoutDirty)
1193 return;
1194 tab->visible = visible;
1195 if (tab->leftWidget)
1196 tab->leftWidget->setVisible(visible);
1197 if (tab->rightWidget)
1198 tab->rightWidget->setVisible(visible);
1199#ifndef QT_NO_SHORTCUT
1200 setShortcutEnabled(tab->shortcutId, visible);
1201#endif
1202 d->calculateFirstLastVisible(index, visible, false);
1203 if (!visible && index == d->currentIndex) {
1204 const int newindex = d->selectNewCurrentIndexFrom(index+1);
1205 setCurrentIndex(newindex);
1206 }
1207 update();
1208 }
1209}
1210
1211
1212/*!
1213 Returns the text of the tab at position \a index, or an empty
1214 string if \a index is out of range.
1215*/
1216QString QTabBar::tabText(int index) const
1217{
1218 Q_D(const QTabBar);
1219 if (const QTabBarPrivate::Tab *tab = d->at(index))
1220 return tab->text;
1221 return QString();
1222}
1223
1224/*!
1225 Sets the text of the tab at position \a index to \a text.
1226*/
1227void QTabBar::setTabText(int index, const QString &text)
1228{
1229 Q_D(QTabBar);
1230 if (QTabBarPrivate::Tab *tab = d->at(index)) {
1231 d->textSizes.remove(tab->text);
1232 tab->text = text;
1233#ifndef QT_NO_SHORTCUT
1234 releaseShortcut(tab->shortcutId);
1235 tab->shortcutId = grabShortcut(QKeySequence::mnemonic(text));
1236 setShortcutEnabled(tab->shortcutId, tab->enabled);
1237#endif
1238 d->refresh();
1239 }
1240}
1241
1242/*!
1243 Returns the text color of the tab with the given \a index, or a invalid
1244 color if \a index is out of range.
1245
1246 \sa setTabTextColor()
1247*/
1248QColor QTabBar::tabTextColor(int index) const
1249{
1250 Q_D(const QTabBar);
1251 if (const QTabBarPrivate::Tab *tab = d->at(index))
1252 return tab->textColor;
1253 return QColor();
1254}
1255
1256/*!
1257 Sets the color of the text in the tab with the given \a index to the specified \a color.
1258
1259 If an invalid color is specified, the tab will use the QTabBar foreground role instead.
1260
1261 \sa tabTextColor()
1262*/
1263void QTabBar::setTabTextColor(int index, const QColor &color)
1264{
1265 Q_D(QTabBar);
1266 if (QTabBarPrivate::Tab *tab = d->at(index)) {
1267 tab->textColor = color;
1268 update(tabRect(index));
1269 }
1270}
1271
1272/*!
1273 Returns the icon of the tab at position \a index, or a null icon
1274 if \a index is out of range.
1275*/
1276QIcon QTabBar::tabIcon(int index) const
1277{
1278 Q_D(const QTabBar);
1279 if (const QTabBarPrivate::Tab *tab = d->at(index))
1280 return tab->icon;
1281 return QIcon();
1282}
1283
1284/*!
1285 Sets the icon of the tab at position \a index to \a icon.
1286*/
1287void QTabBar::setTabIcon(int index, const QIcon & icon)
1288{
1289 Q_D(QTabBar);
1290 if (QTabBarPrivate::Tab *tab = d->at(index)) {
1291 bool simpleIconChange = (!icon.isNull() && !tab->icon.isNull());
1292 tab->icon = icon;
1293 if (simpleIconChange)
1294 update(tabRect(index));
1295 else
1296 d->refresh();
1297 }
1298}
1299
1300#if QT_CONFIG(tooltip)
1301/*!
1302 Sets the tool tip of the tab at position \a index to \a tip.
1303*/
1304void QTabBar::setTabToolTip(int index, const QString & tip)
1305{
1306 Q_D(QTabBar);
1307 if (QTabBarPrivate::Tab *tab = d->at(index))
1308 tab->toolTip = tip;
1309}
1310
1311/*!
1312 Returns the tool tip of the tab at position \a index, or an empty
1313 string if \a index is out of range.
1314*/
1315QString QTabBar::tabToolTip(int index) const
1316{
1317 Q_D(const QTabBar);
1318 if (const QTabBarPrivate::Tab *tab = d->at(index))
1319 return tab->toolTip;
1320 return QString();
1321}
1322#endif // QT_CONFIG(tooltip)
1323
1324#if QT_CONFIG(whatsthis)
1325/*!
1326 \since 4.1
1327
1328 Sets the What's This help text of the tab at position \a index
1329 to \a text.
1330*/
1331void QTabBar::setTabWhatsThis(int index, const QString &text)
1332{
1333 Q_D(QTabBar);
1334 if (QTabBarPrivate::Tab *tab = d->at(index))
1335 tab->whatsThis = text;
1336}
1337
1338/*!
1339 \since 4.1
1340
1341 Returns the What's This help text of the tab at position \a index,
1342 or an empty string if \a index is out of range.
1343*/
1344QString QTabBar::tabWhatsThis(int index) const
1345{
1346 Q_D(const QTabBar);
1347 if (const QTabBarPrivate::Tab *tab = d->at(index))
1348 return tab->whatsThis;
1349 return QString();
1350}
1351
1352#endif // QT_CONFIG(whatsthis)
1353
1354/*!
1355 Sets the data of the tab at position \a index to \a data.
1356*/
1357void QTabBar::setTabData(int index, const QVariant & data)
1358{
1359 Q_D(QTabBar);
1360 if (QTabBarPrivate::Tab *tab = d->at(index))
1361 tab->data = data;
1362}
1363
1364/*!
1365 Returns the data of the tab at position \a index, or a null
1366 variant if \a index is out of range.
1367*/
1368QVariant QTabBar::tabData(int index) const
1369{
1370 Q_D(const QTabBar);
1371 if (const QTabBarPrivate::Tab *tab = d->at(index))
1372 return tab->data;
1373 return QVariant();
1374}
1375
1376/*!
1377 Returns the visual rectangle of the tab at position \a
1378 index, or a null rectangle if \a index is hidden, or out of range.
1379*/
1380QRect QTabBar::tabRect(int index) const
1381{
1382 Q_D(const QTabBar);
1383 if (const QTabBarPrivate::Tab *tab = d->at(index)) {
1384 if (d->layoutDirty)
1385 const_cast<QTabBarPrivate*>(d)->layoutTabs();
1386 if (!tab->visible)
1387 return QRect();
1388 QRect r = tab->rect;
1389 if (verticalTabs(d->shape))
1390 r.translate(0, -d->scrollOffset);
1391 else
1392 r.translate(-d->scrollOffset, 0);
1393 if (!verticalTabs(d->shape))
1394 r = QStyle::visualRect(layoutDirection(), rect(), r);
1395 return r;
1396 }
1397 return QRect();
1398}
1399
1400/*!
1401 \since 4.3
1402 Returns the index of the tab that covers \a position or -1 if no
1403 tab covers \a position;
1404*/
1405
1406int QTabBar::tabAt(const QPoint &position) const
1407{
1408 Q_D(const QTabBar);
1409 if (d->validIndex(d->currentIndex)
1410 && tabRect(d->currentIndex).contains(position)) {
1411 return d->currentIndex;
1412 }
1413 const int max = d->tabList.size();
1414 for (int i = 0; i < max; ++i) {
1415 if (tabRect(i).contains(position)) {
1416 return i;
1417 }
1418 }
1419 return -1;
1420}
1421
1422/*!
1423 \property QTabBar::currentIndex
1424 \brief the index of the tab bar's visible tab
1425
1426 The current index is -1 if there is no current tab.
1427*/
1428
1429int QTabBar::currentIndex() const
1430{
1431 Q_D(const QTabBar);
1432 if (d->validIndex(d->currentIndex))
1433 return d->currentIndex;
1434 return -1;
1435}
1436
1437
1438void QTabBar::setCurrentIndex(int index)
1439{
1440 Q_D(QTabBar);
1441 if (d->dragInProgress && d->pressedIndex != -1)
1442 return;
1443 if (d->currentIndex == index)
1444 return;
1445
1446 int oldIndex = d->currentIndex;
1447 if (auto tab = d->at(index)) {
1448 d->currentIndex = index;
1449 // If the size hint depends on whether the tab is selected (for instance a style
1450 // sheet rule that sets a bold font on the 'selected' tab) then we need to
1451 // re-layout the entire tab bar. To minimize the cost, do that only if the
1452 // size hint changes for the tab that becomes the current tab (the old current tab
1453 // will most certainly do the same). QTBUG-6905
1454 if (tabRect(index).size() != tabSizeHint(index))
1455 d->layoutTabs();
1456 update();
1457 if (!isVisible())
1458 d->layoutDirty = true;
1459 else
1460 d->makeVisible(index);
1461 if (d->validIndex(oldIndex)) {
1462 tab->lastTab = oldIndex;
1463 d->layoutTab(oldIndex);
1464 }
1465 d->layoutTab(index);
1466#if QT_CONFIG(accessibility)
1467 if (QAccessible::isActive()) {
1468 if (hasFocus()) {
1469 QAccessibleEvent focusEvent(this, QAccessible::Focus);
1470 focusEvent.setChild(index);
1471 QAccessible::updateAccessibility(&focusEvent);
1472 }
1473 QAccessibleEvent selectionEvent(this, QAccessible::Selection);
1474 selectionEvent.setChild(index);
1475 QAccessible::updateAccessibility(&selectionEvent);
1476 }
1477#endif
1478 emit currentChanged(index);
1479 }
1480}
1481
1482/*!
1483 \property QTabBar::iconSize
1484 \brief The size for icons in the tab bar
1485 \since 4.1
1486
1487 The default value is style-dependent. \c iconSize is a maximum
1488 size; icons that are smaller are not scaled up.
1489
1490 \sa QTabWidget::iconSize
1491*/
1492QSize QTabBar::iconSize() const
1493{
1494 Q_D(const QTabBar);
1495 if (d->iconSize.isValid())
1496 return d->iconSize;
1497 int iconExtent = style()->pixelMetric(QStyle::PM_TabBarIconSize, nullptr, this);
1498 return QSize(iconExtent, iconExtent);
1499
1500}
1501
1502void QTabBar::setIconSize(const QSize &size)
1503{
1504 Q_D(QTabBar);
1505 d->iconSize = size;
1506 d->layoutDirty = true;
1507 update();
1508 updateGeometry();
1509}
1510
1511/*!
1512 \property QTabBar::count
1513 \brief the number of tabs in the tab bar
1514*/
1515
1516int QTabBar::count() const
1517{
1518 Q_D(const QTabBar);
1519 return d->tabList.size();
1520}
1521
1522
1523/*!\reimp
1524 */
1525QSize QTabBar::sizeHint() const
1526{
1527 Q_D(const QTabBar);
1528 if (d->layoutDirty)
1529 const_cast<QTabBarPrivate*>(d)->layoutTabs();
1530 QRect r;
1531 for (const auto tab : d->tabList) {
1532 if (tab->visible)
1533 r = r.united(tab->maxRect);
1534 }
1535 return r.size();
1536}
1537
1538/*!\reimp
1539 */
1540QSize QTabBar::minimumSizeHint() const
1541{
1542 Q_D(const QTabBar);
1543 if (d->layoutDirty)
1544 const_cast<QTabBarPrivate*>(d)->layoutTabs();
1545 if (!d->useScrollButtons) {
1546 QRect r;
1547 for (const auto tab : d->tabList) {
1548 if (tab->visible)
1549 r = r.united(tab->minRect);
1550 }
1551 return r.size();
1552 }
1553 if (verticalTabs(d->shape))
1554 return QSize(sizeHint().width(), d->rightB->sizeHint().height() * 2 + 75);
1555 else
1556 return QSize(d->rightB->sizeHint().width() * 2 + 75, sizeHint().height());
1557}
1558
1559// Compute the most-elided possible text, for minimumSizeHint
1560static QString computeElidedText(Qt::TextElideMode mode, const QString &text)
1561{
1562 if (text.size() <= 3)
1563 return text;
1564
1565 static const auto Ellipses = "..."_L1;
1566 QString ret;
1567 switch (mode) {
1568 case Qt::ElideRight:
1569 ret = QStringView{text}.left(2) + Ellipses;
1570 break;
1571 case Qt::ElideMiddle:
1572 ret = QStringView{text}.left(1) + Ellipses + QStringView{text}.right(1);
1573 break;
1574 case Qt::ElideLeft:
1575 ret = Ellipses + QStringView{text}.right(2);
1576 break;
1577 case Qt::ElideNone:
1578 ret = text;
1579 break;
1580 }
1581 return ret;
1582}
1583
1584/*!
1585 Returns the minimum tab size hint for the tab at position \a index.
1586 \since 5.0
1587*/
1588
1589QSize QTabBar::minimumTabSizeHint(int index) const
1590{
1591 Q_D(const QTabBar);
1592 QTabBarPrivate::Tab *tab = d->tabList.at(index);
1593 QString oldText = tab->text;
1594 tab->text = computeElidedText(d->elideMode, oldText);
1595 tab->measuringMinimum = true;
1596 QSize size = tabSizeHint(index);
1597 tab->text = oldText;
1598 tab->measuringMinimum = false;
1599 return size;
1600}
1601
1602/*!
1603 Returns the size hint for the tab at position \a index.
1604*/
1605QSize QTabBar::tabSizeHint(int index) const
1606{
1607 //Note: this must match with the computations in QCommonStylePrivate::tabLayout
1608 Q_D(const QTabBar);
1609 if (const QTabBarPrivate::Tab *tab = d->at(index)) {
1610 QStyleOptionTab opt;
1611 d->initBasicStyleOption(&opt, index);
1612 opt.text = tab->text;
1613 QSize iconSize = tab->icon.isNull() ? QSize(0, 0) : opt.iconSize;
1614 int hframe = style()->pixelMetric(QStyle::PM_TabBarTabHSpace, &opt, this);
1615 int vframe = style()->pixelMetric(QStyle::PM_TabBarTabVSpace, &opt, this);
1616 const QFontMetrics fm = fontMetrics();
1617
1618 int maxWidgetHeight = qMax(opt.leftButtonSize.height(), opt.rightButtonSize.height());
1619 int maxWidgetWidth = qMax(opt.leftButtonSize.width(), opt.rightButtonSize.width());
1620
1621 int widgetWidth = 0;
1622 int widgetHeight = 0;
1623 int padding = 0;
1624 if (!opt.leftButtonSize.isEmpty()) {
1625 padding += 4;
1626 widgetWidth += opt.leftButtonSize.width();
1627 widgetHeight += opt.leftButtonSize.height();
1628 }
1629 if (!opt.rightButtonSize.isEmpty()) {
1630 padding += 4;
1631 widgetWidth += opt.rightButtonSize.width();
1632 widgetHeight += opt.rightButtonSize.height();
1633 }
1634 if (!opt.icon.isNull())
1635 padding += 4;
1636
1637 QHash<QString, QSize>::iterator it = d->textSizes.find(tab->text);
1638 if (it == d->textSizes.end())
1639 it = d->textSizes.insert(tab->text, fm.size(Qt::TextShowMnemonic, tab->text));
1640 const int textWidth = it.value().width();
1641 QSize csz;
1642 if (verticalTabs(d->shape)) {
1643 csz = QSize( qMax(maxWidgetWidth, qMax(fm.height(), iconSize.height())) + vframe,
1644 textWidth + iconSize.width() + hframe + widgetHeight + padding);
1645 } else {
1646 csz = QSize(textWidth + iconSize.width() + hframe + widgetWidth + padding,
1647 qMax(maxWidgetHeight, qMax(fm.height(), iconSize.height())) + vframe);
1648 }
1649
1650 QSize retSize = style()->sizeFromContents(QStyle::CT_TabBarTab, &opt, csz, this);
1651 return retSize;
1652 }
1653 return QSize();
1654}
1655
1656/*!
1657 This virtual handler is called after a new tab was added or
1658 inserted at position \a index.
1659
1660 \sa tabRemoved()
1661 */
1662void QTabBar::tabInserted(int index)
1663{
1664 Q_UNUSED(index);
1665}
1666
1667/*!
1668 This virtual handler is called after a tab was removed from
1669 position \a index.
1670
1671 \sa tabInserted()
1672 */
1673void QTabBar::tabRemoved(int index)
1674{
1675 Q_UNUSED(index);
1676}
1677
1678/*!
1679 This virtual handler is called whenever the tab layout changes.
1680
1681 \sa tabRect()
1682 */
1683void QTabBar::tabLayoutChange()
1684{
1685}
1686
1687
1688/*!\reimp
1689 */
1690void QTabBar::showEvent(QShowEvent *)
1691{
1692 Q_D(QTabBar);
1693 if (d->layoutDirty)
1694 d->refresh();
1695 if (!d->validIndex(d->currentIndex))
1696 setCurrentIndex(0);
1697 else
1698 d->makeVisible(d->currentIndex);
1699 d->updateMacBorderMetrics();
1700}
1701
1702/*!\reimp
1703 */
1704void QTabBar::hideEvent(QHideEvent *)
1705{
1706 Q_D(QTabBar);
1707 d->updateMacBorderMetrics();
1708}
1709
1710/*!\reimp
1711 */
1712bool QTabBar::event(QEvent *event)
1713{
1714 Q_D(QTabBar);
1715 switch (event->type()) {
1716 case QEvent::HoverMove:
1717 case QEvent::HoverEnter: {
1718 QHoverEvent *he = static_cast<QHoverEvent *>(event);
1719 d->mousePosition = he->position().toPoint();
1720 const auto sr = d->scrollRect();
1721 const auto oldHoverRect = d->hoverRect & sr;
1722 if (!oldHoverRect.contains(d->mousePosition)) {
1723 if (d->hoverRect.isValid())
1724 update(d->hoverRect);
1725 d->hoverIndex = tabAt(d->mousePosition);
1726 if (d->validIndex(d->hoverIndex) && sr.contains(d->mousePosition)) {
1727 d->hoverRect = tabRect(d->hoverIndex);
1728 update(d->hoverRect);
1729 } else {
1730 d->hoverRect = QRect();
1731 d->hoverIndex = -1;
1732 }
1733 }
1734 return true;
1735 }
1736 case QEvent::HoverLeave: {
1737 d->mousePosition = {-1, -1};
1738 if (d->hoverRect.isValid())
1739 update(d->hoverRect);
1740 d->hoverIndex = -1;
1741 d->hoverRect = QRect();
1742#if QT_CONFIG(wheelevent)
1743 d->accumulatedAngleDelta = QPoint();
1744#endif
1745 return true;
1746 }
1747#if QT_CONFIG(tooltip)
1748 case QEvent::ToolTip:
1749 if (const QTabBarPrivate::Tab *tab = d->at(tabAt(static_cast<QHelpEvent*>(event)->pos()))) {
1750 if (!tab->toolTip.isEmpty()) {
1751 QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), tab->toolTip, this);
1752 return true;
1753 }
1754 }
1755 break;
1756#endif // QT_CONFIG(tooltip)
1757#if QT_CONFIG(whatsthis)
1758 case QEvent::QEvent::QueryWhatsThis: {
1759 const QTabBarPrivate::Tab *tab = d->at(d->indexAtPos(static_cast<QHelpEvent*>(event)->pos()));
1760 if (!tab || tab->whatsThis.isEmpty())
1761 event->ignore();
1762 return true;
1763 }
1764 case QEvent::WhatsThis:
1765 if (const QTabBarPrivate::Tab *tab = d->at(d->indexAtPos(static_cast<QHelpEvent*>(event)->pos()))) {
1766 if (!tab->whatsThis.isEmpty()) {
1767 QWhatsThis::showText(static_cast<QHelpEvent*>(event)->globalPos(),
1768 tab->whatsThis, this);
1769 return true;
1770 }
1771 }
1772 break;
1773#endif // QT_CONFIG(whatsthis)
1774#ifndef QT_NO_SHORTCUT
1775
1776 case QEvent::Shortcut: {
1777 QShortcutEvent *se = static_cast<QShortcutEvent *>(event);
1778 for (int i = 0; i < d->tabList.size(); ++i) {
1779 const QTabBarPrivate::Tab *tab = d->tabList.at(i);
1780 if (tab->shortcutId == se->shortcutId()) {
1781 setCurrentIndex(i);
1782 return true;
1783 }
1784 }
1785 }
1786 break;
1787#endif
1788 case QEvent::Move:
1789 d->updateMacBorderMetrics();
1790 break;
1791#if QT_CONFIG(draganddrop)
1792
1793 case QEvent::DragEnter:
1794 if (d->changeCurrentOnDrag)
1795 event->accept();
1796 break;
1797 case QEvent::DragMove:
1798 if (d->changeCurrentOnDrag) {
1799 const int tabIndex = tabAt(static_cast<QDragMoveEvent *>(event)->position().toPoint());
1800 if (isTabEnabled(tabIndex) && d->switchTabCurrentIndex != tabIndex) {
1801 d->switchTabCurrentIndex = tabIndex;
1802 d->switchTabTimer.start(
1803 style()->styleHint(QStyle::SH_TabBar_ChangeCurrentDelay, nullptr, this) * 1ms, this);
1804 }
1805 event->ignore();
1806 }
1807 break;
1808 case QEvent::DragLeave:
1809 case QEvent::Drop:
1810 d->killSwitchTabTimer();
1811 event->ignore();
1812 break;
1813#endif
1814 case QEvent::MouseButtonPress:
1815 case QEvent::MouseButtonRelease:
1816 case QEvent::MouseMove:
1817 d->mousePosition = static_cast<QMouseEvent *>(event)->position().toPoint();
1818 d->mouseButtons = static_cast<QMouseEvent *>(event)->buttons();
1819 break;
1820 default:
1821 break;
1822 }
1823
1824 return QWidget::event(event);
1825}
1826
1827/*!\reimp
1828 */
1829void QTabBar::resizeEvent(QResizeEvent *)
1830{
1831 Q_D(QTabBar);
1832 if (d->layoutDirty)
1833 updateGeometry();
1834
1835 // when resizing, we want to keep the scroll offset as much as possible
1836 d->layoutTabs();
1837
1838 d->makeVisible(d->currentIndex);
1839}
1840
1841/*!\reimp
1842 */
1843void QTabBar::paintEvent(QPaintEvent *)
1844{
1845 Q_D(QTabBar);
1846
1847 QStyleOptionTabBarBase optTabBase;
1848 QTabBarPrivate::initStyleBaseOption(&optTabBase, this, size());
1849
1850 QStylePainter p(this);
1851 int selected = -1;
1852 int cutLeft = -1;
1853 int cutRight = -1;
1854 bool vertical = verticalTabs(d->shape);
1855 QStyleOptionTab cutTabLeft;
1856 QStyleOptionTab cutTabRight;
1857 selected = d->currentIndex;
1858 if (d->dragInProgress)
1859 selected = d->pressedIndex;
1860 const QRect scrollRect = d->scrollRect();
1861 const QRect normalizedScrollRect = d->normalizedScrollRect();
1862
1863 for (int i = 0; i < d->tabList.size(); ++i)
1864 optTabBase.tabBarRect |= tabRect(i);
1865
1866 optTabBase.selectedTabRect = tabRect(selected);
1867
1868 if (d->drawBase)
1869 p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
1870
1871 // the buttons might be semi-transparent or not fill their rect, but we don't
1872 // want the tab underneath to shine through, so clip the button area; QTBUG-50866
1873 if (d->leftB->isVisible() || d->rightB->isVisible()) {
1874 QStyleOption opt;
1875 opt.initFrom(this);
1876 QRegion buttonRegion;
1877 if (d->leftB->isVisible()) {
1878 const auto r = style()->subElementRect(QStyle::SE_TabBarScrollLeftButton, &opt, this);
1879 buttonRegion |= r;
1880 }
1881 if (d->rightB->isVisible()) {
1882 const auto r = style()->subElementRect(QStyle::SE_TabBarScrollRightButton, &opt, this);
1883 buttonRegion |= r;
1884 }
1885 if (!buttonRegion.isEmpty())
1886 p.setClipRegion(QRegion(rect()) - buttonRegion);
1887 }
1888
1889 for (int i = 0; i < d->tabList.size(); ++i) {
1890 const auto tab = d->tabList.at(i);
1891 if (!tab->visible)
1892 continue;
1893 for (const auto side : { QTabBar::LeftSide, QTabBar::RightSide }) {
1894 if (auto closeButton = qobject_cast<CloseButton *>(tabButton(i, side)))
1895 closeButton->setParentClipRect(scrollRect);
1896 }
1897 QStyleOptionTab tabOption;
1898 initStyleOption(&tabOption, i);
1899 if (tab->dragOffset != 0) {
1900 if (vertical) {
1901 tabOption.rect.moveTop(tabOption.rect.y() + tab->dragOffset);
1902 } else {
1903 tabOption.rect.moveLeft(tabOption.rect.x() + tab->dragOffset);
1904 }
1905 }
1906 if (!(tabOption.state & QStyle::State_Enabled)) {
1907 tabOption.palette.setCurrentColorGroup(QPalette::Disabled);
1908 }
1909
1910 // If this tab is partially obscured, make a note of it so that we can pass the information
1911 // along when we draw the tear.
1912 QRect tabRect = tab->rect;
1913 int tabStart = vertical ? tabRect.top() : tabRect.left();
1914 int tabEnd = vertical ? tabRect.bottom() : tabRect.right();
1915 if (tabStart < normalizedScrollRect.left() + d->scrollOffset) {
1916 cutLeft = i;
1917 cutTabLeft = tabOption;
1918 } else if (tabEnd > normalizedScrollRect.right() + d->scrollOffset) {
1919 cutRight = i;
1920 cutTabRight = tabOption;
1921 }
1922
1923 // Don't bother drawing a tab if the entire tab is outside of the visible tab bar.
1924 if ((!vertical && (tabOption.rect.right() < 0 || tabOption.rect.left() > width()))
1925 || (vertical && (tabOption.rect.bottom() < 0 || tabOption.rect.top() > height())))
1926 continue;
1927
1928 optTabBase.tabBarRect |= tabOption.rect;
1929 if (i == selected)
1930 continue;
1931
1932 p.drawControl(QStyle::CE_TabBarTab, tabOption);
1933 }
1934
1935 // Draw the selected tab last to get it "on top"
1936 if (selected >= 0) {
1937 QStyleOptionTab tabOption;
1938 const auto tab = d->tabList.at(selected);
1939 initStyleOption(&tabOption, selected);
1940
1941 if (tab->dragOffset != 0) {
1942 // if the drag offset is != 0, a move is in progress (drag or animation)
1943 // => set the tab position to Moving to preserve the rect
1944 tabOption.position = QStyleOptionTab::TabPosition::Moving;
1945
1946 if (vertical)
1947 tabOption.rect.moveTop(tabOption.rect.y() + tab->dragOffset);
1948 else
1949 tabOption.rect.moveLeft(tabOption.rect.x() + tab->dragOffset);
1950 }
1951
1952 // Calculate the rect of a moving tab
1953 const int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
1954 const QRect &movingRect = verticalTabs(d->shape)
1955 ? tabOption.rect.adjusted(0, -taboverlap, 0, taboverlap)
1956 : tabOption.rect.adjusted(-taboverlap, 0, taboverlap, 0);
1957
1958 // If a drag is in process, set the moving tab's geometry here
1959 // (in an animation, it is already set)
1960 if (d->dragInProgress)
1961 d->movingTab->setGeometry(movingRect);
1962
1963 p.drawControl(QStyle::CE_TabBarTab, tabOption);
1964 }
1965
1966 // Only draw the tear indicator if necessary. Most of the time we don't need too.
1967 if (d->leftB->isVisible() && cutLeft >= 0) {
1968 cutTabLeft.rect = rect();
1969 cutTabLeft.rect = style()->subElementRect(QStyle::SE_TabBarTearIndicatorLeft, &cutTabLeft, this);
1970 p.drawPrimitive(QStyle::PE_IndicatorTabTearLeft, cutTabLeft);
1971 }
1972
1973 if (d->rightB->isVisible() && cutRight >= 0) {
1974 cutTabRight.rect = rect();
1975 cutTabRight.rect = style()->subElementRect(QStyle::SE_TabBarTearIndicatorRight, &cutTabRight, this);
1976 p.drawPrimitive(QStyle::PE_IndicatorTabTearRight, cutTabRight);
1977 }
1978}
1979
1980/*
1981 When index changes visibility, we have to find first & last visible indexes.
1982 If remove is set, we force both
1983 */
1984void QTabBarPrivate::calculateFirstLastVisible(int index, bool visible, bool remove)
1985{
1986 if (visible) {
1987 firstVisible = qMin(index, firstVisible);
1988 lastVisible = qMax(index, lastVisible);
1989 } else {
1990 if (remove || (index == firstVisible)) {
1991 firstVisible = -1;
1992 for (int i = 0; i < tabList.size(); ++i) {
1993 if (tabList.at(i)->visible) {
1994 firstVisible = i;
1995 break;
1996 }
1997 }
1998 }
1999 if (remove || (index == lastVisible)) {
2000 lastVisible = -1;
2001 for (int i = tabList.size() - 1; i >= 0; --i) {
2002 if (tabList.at(i)->visible) {
2003 lastVisible = i;
2004 break;
2005 }
2006 }
2007 }
2008 }
2009}
2010
2011/*
2012 Selects the new current index starting at "fromIndex". If "fromIndex" is visible we're done.
2013 Else it tries any index AFTER fromIndex, then any BEFORE fromIndex and, if everything fails,
2014 it returns -1 indicating that no index is available
2015 */
2016int QTabBarPrivate::selectNewCurrentIndexFrom(int fromIndex)
2017{
2018 int newindex = -1;
2019 for (int i = fromIndex; i < tabList.size(); ++i) {
2020 if (at(i)->visible && at(i)->enabled) {
2021 newindex = i;
2022 break;
2023 }
2024 }
2025 if (newindex < 0) {
2026 for (int i = fromIndex-1; i > -1; --i) {
2027 if (at(i)->visible && at(i)->enabled) {
2028 newindex = i;
2029 break;
2030 }
2031 }
2032 }
2033
2034 return newindex;
2035}
2036
2037/*
2038 Given that index at position from moved to position to where return where index goes.
2039 */
2040int QTabBarPrivate::calculateNewPosition(int from, int to, int index) const
2041{
2042 if (index == from)
2043 return to;
2044
2045 int start = qMin(from, to);
2046 int end = qMax(from, to);
2047 if (index >= start && index <= end)
2048 index += (from < to) ? -1 : 1;
2049 return index;
2050}
2051
2052/*!
2053 Moves the item at index position \a from to index position \a to.
2054 \since 4.5
2055
2056 \sa tabMoved(), tabLayoutChange()
2057 */
2058void QTabBar::moveTab(int from, int to)
2059{
2060 Q_D(QTabBar);
2061 if (from == to
2062 || !d->validIndex(from)
2063 || !d->validIndex(to))
2064 return;
2065
2066 auto &fromTab = *d->tabList.at(from);
2067 auto &toTab = *d->tabList.at(to);
2068
2069 bool vertical = verticalTabs(d->shape);
2070 int oldPressedPosition = 0;
2071 if (d->pressedIndex != -1) {
2072 // Record the position of the pressed tab before reordering the tabs.
2073 oldPressedPosition = vertical ? d->tabList.at(d->pressedIndex)->rect.y()
2074 : d->tabList.at(d->pressedIndex)->rect.x();
2075 }
2076
2077 // Update the locations of the tabs first
2078 int start = qMin(from, to);
2079 int end = qMax(from, to);
2080 int width = vertical ? fromTab.rect.height() : fromTab.rect.width();
2081 if (from < to)
2082 width *= -1;
2083 bool rtl = isRightToLeft();
2084 for (int i = start; i <= end; ++i) {
2085 if (i == from)
2086 continue;
2087 auto &tab = *d->tabList.at(i);
2088 if (vertical)
2089 tab.rect.moveTop(tab.rect.y() + width);
2090 else
2091 tab.rect.moveLeft(tab.rect.x() + width);
2092 int direction = -1;
2093 if (rtl && !vertical)
2094 direction *= -1;
2095 if (tab.dragOffset != 0)
2096 tab.dragOffset += (direction * width);
2097 }
2098
2099 if (vertical) {
2100 if (from < to)
2101 fromTab.rect.moveTop(toTab.rect.bottom() + 1);
2102 else
2103 fromTab.rect.moveTop(toTab.rect.top() - width);
2104 } else {
2105 if (from < to)
2106 fromTab.rect.moveLeft(toTab.rect.right() + 1);
2107 else
2108 fromTab.rect.moveLeft(toTab.rect.left() - width);
2109 }
2110
2111 // Move the actual data structures
2112 d->tabList.move(from, to);
2113
2114 // update lastTab locations
2115 for (const auto tab : std::as_const(d->tabList))
2116 tab->lastTab = d->calculateNewPosition(from, to, tab->lastTab);
2117
2118 // update external variables
2119 int previousIndex = d->currentIndex;
2120 d->currentIndex = d->calculateNewPosition(from, to, d->currentIndex);
2121
2122 // If we are in the middle of a drag update the dragStartPosition
2123 if (d->pressedIndex != -1) {
2124 d->pressedIndex = d->calculateNewPosition(from, to, d->pressedIndex);
2125 const auto pressedTab = d->tabList.at(d->pressedIndex);
2126 int newPressedPosition = vertical ? pressedTab->rect.top() : pressedTab->rect.left();
2127 int diff = oldPressedPosition - newPressedPosition;
2128 if (isRightToLeft() && !vertical)
2129 diff *= -1;
2130 if (vertical)
2131 d->dragStartPosition.setY(d->dragStartPosition.y() - diff);
2132 else
2133 d->dragStartPosition.setX(d->dragStartPosition.x() - diff);
2134 }
2135
2136 d->layoutWidgets(start);
2137 update();
2138 emit tabMoved(from, to);
2139 if (previousIndex != d->currentIndex)
2140 emit currentChanged(d->currentIndex);
2141 emit tabLayoutChange();
2142}
2143
2144void QTabBarPrivate::slide(int from, int to)
2145{
2146 Q_Q(QTabBar);
2147 if (from == to
2148 || !validIndex(from)
2149 || !validIndex(to))
2150 return;
2151 bool vertical = verticalTabs(shape);
2152 int preLocation = vertical ? q->tabRect(from).y() : q->tabRect(from).x();
2153 q->setUpdatesEnabled(false);
2154 q->moveTab(from, to);
2155 q->setUpdatesEnabled(true);
2156 int postLocation = vertical ? q->tabRect(to).y() : q->tabRect(to).x();
2157 int length = postLocation - preLocation;
2158 tabList.at(to)->dragOffset -= length;
2159 tabList.at(to)->startAnimation(this, ANIMATION_DURATION);
2160}
2161
2162void QTabBarPrivate::moveTab(int index, int offset)
2163{
2164 if (!validIndex(index))
2165 return;
2166 tabList.at(index)->dragOffset = offset;
2167 layoutTab(index); // Make buttons follow tab
2168 q_func()->update();
2169}
2170
2171/*!\reimp
2172*/
2173void QTabBar::mousePressEvent(QMouseEvent *event)
2174{
2175 Q_D(QTabBar);
2176
2177 const QPoint pos = event->position().toPoint();
2178 const bool isEventInCornerButtons = (!d->leftB->isHidden() && d->leftB->geometry().contains(pos))
2179 || (!d->rightB->isHidden() && d->rightB->geometry().contains(pos));
2180 if (!isEventInCornerButtons) {
2181 const int index = d->indexAtPos(pos);
2182 emit tabBarClicked(index);
2183 }
2184
2185 if (event->button() != Qt::LeftButton) {
2186 event->ignore();
2187 return;
2188 }
2189 // Be safe!
2190 if (d->pressedIndex != -1 && d->movable)
2191 d->moveTabFinished(d->pressedIndex);
2192
2193 d->pressedIndex = d->indexAtPos(event->position().toPoint());
2194
2195 if (d->validIndex(d->pressedIndex)) {
2196 QStyleOptionTabBarBase optTabBase;
2197 optTabBase.initFrom(this);
2198 optTabBase.documentMode = d->documentMode;
2199 if (event->type() == style()->styleHint(QStyle::SH_TabBar_SelectMouseType, &optTabBase, this))
2200 setCurrentIndex(d->pressedIndex);
2201 else
2202 repaint(tabRect(d->pressedIndex));
2203 if (d->movable) {
2204 d->dragStartPosition = event->position().toPoint();
2205 }
2206 }
2207}
2208
2209/*!\reimp
2210 */
2211void QTabBar::mouseMoveEvent(QMouseEvent *event)
2212{
2213 Q_D(QTabBar);
2214 if (d->movable) {
2215 // Be safe!
2216 if (d->pressedIndex != -1
2217 && event->buttons() == Qt::NoButton)
2218 d->moveTabFinished(d->pressedIndex);
2219
2220 // Start drag
2221 if (!d->dragInProgress && d->pressedIndex != -1) {
2222 if ((event->position().toPoint() - d->dragStartPosition).manhattanLength() > QApplication::startDragDistance()) {
2223 d->dragInProgress = true;
2224 d->setupMovableTab();
2225 }
2226 }
2227
2228 if (event->buttons() == Qt::LeftButton
2229 && d->dragInProgress
2230 && d->validIndex(d->pressedIndex)) {
2231 bool vertical = verticalTabs(d->shape);
2232 int dragDistance;
2233 if (vertical) {
2234 dragDistance = (event->position().toPoint().y() - d->dragStartPosition.y());
2235 } else {
2236 dragDistance = (event->position().toPoint().x() - d->dragStartPosition.x());
2237 }
2238 d->tabList.at(d->pressedIndex)->dragOffset = dragDistance;
2239
2240 QRect startingRect = tabRect(d->pressedIndex);
2241 if (vertical)
2242 startingRect.moveTop(startingRect.y() + dragDistance);
2243 else
2244 startingRect.moveLeft(startingRect.x() + dragDistance);
2245
2246 int overIndex;
2247 if (dragDistance < 0)
2248 overIndex = tabAt(startingRect.topLeft());
2249 else
2250 overIndex = tabAt(startingRect.topRight());
2251
2252 if (overIndex != d->pressedIndex && overIndex != -1) {
2253 int offset = 1;
2254 if (isRightToLeft() && !vertical)
2255 offset *= -1;
2256 if (dragDistance < 0) {
2257 dragDistance *= -1;
2258 offset *= -1;
2259 }
2260 for (int i = d->pressedIndex;
2261 offset > 0 ? i < overIndex : i > overIndex;
2262 i += offset) {
2263 QRect overIndexRect = tabRect(overIndex);
2264 int needsToBeOver = (vertical ? overIndexRect.height() : overIndexRect.width()) / 2;
2265 if (dragDistance > needsToBeOver)
2266 d->slide(i + offset, d->pressedIndex);
2267 }
2268 }
2269 // Buttons needs to follow the dragged tab
2270 if (d->pressedIndex != -1)
2271 d->layoutTab(d->pressedIndex);
2272
2273 update();
2274 }
2275 }
2276
2277 if (event->buttons() != Qt::LeftButton) {
2278 event->ignore();
2279 return;
2280 }
2281}
2282
2283void QTabBarPrivate::setupMovableTab()
2284{
2285 Q_Q(QTabBar);
2286 if (!movingTab)
2287 movingTab = new QMovableTabWidget(q);
2288
2289 int taboverlap = q->style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr ,q);
2290 QRect grabRect = q->tabRect(pressedIndex);
2291 if (verticalTabs(shape))
2292 grabRect.adjust(0, -taboverlap, 0, taboverlap);
2293 else
2294 grabRect.adjust(-taboverlap, 0, taboverlap, 0);
2295
2296 QPixmap grabImage(grabRect.size() * q->devicePixelRatio());
2297 grabImage.setDevicePixelRatio(q->devicePixelRatio());
2298 grabImage.fill(Qt::transparent);
2299 QStylePainter p(&grabImage, q);
2300
2301 QStyleOptionTab tab;
2302 q->initStyleOption(&tab, pressedIndex);
2303 tab.position = QStyleOptionTab::Moving;
2304 if (verticalTabs(shape))
2305 tab.rect.moveTopLeft(QPoint(0, taboverlap));
2306 else
2307 tab.rect.moveTopLeft(QPoint(taboverlap, 0));
2308 p.drawControl(QStyle::CE_TabBarTab, tab);
2309 p.end();
2310
2311 movingTab->setPixmap(grabImage);
2312 movingTab->setGeometry(grabRect);
2313 movingTab->raise();
2314
2315 // Re-arrange widget order to avoid overlaps
2316 const auto &pressedTab = *tabList.at(pressedIndex);
2317 if (pressedTab.leftWidget)
2318 pressedTab.leftWidget->raise();
2319 if (pressedTab.rightWidget)
2320 pressedTab.rightWidget->raise();
2321 if (leftB)
2322 leftB->raise();
2323 if (rightB)
2324 rightB->raise();
2325 movingTab->setVisible(true);
2326}
2327
2328void QTabBarPrivate::moveTabFinished(int index)
2329{
2330 Q_Q(QTabBar);
2331 bool cleanup = (pressedIndex == index) || (pressedIndex == -1) || !validIndex(index);
2332 bool allAnimationsFinished = true;
2333#if QT_CONFIG(animation)
2334 for (const auto tab : std::as_const(tabList)) {
2335 if (tab->animation && tab->animation->state() == QAbstractAnimation::Running) {
2336 allAnimationsFinished = false;
2337 break;
2338 }
2339 }
2340#endif // animation
2341 if (allAnimationsFinished && cleanup) {
2342 if (movingTab)
2343 movingTab->setVisible(false); // We might not get a mouse release
2344 for (auto tab : std::as_const(tabList)) {
2345 tab->dragOffset = 0;
2346 }
2347 if (pressedIndex != -1 && movable) {
2348 pressedIndex = -1;
2349 dragInProgress = false;
2350 dragStartPosition = QPoint();
2351 }
2352 layoutWidgets();
2353 } else {
2354 if (!validIndex(index))
2355 return;
2356 tabList.at(index)->dragOffset = 0;
2357 }
2358 q->update();
2359}
2360
2361/*!\reimp
2362*/
2363void QTabBar::mouseReleaseEvent(QMouseEvent *event)
2364{
2365 Q_D(QTabBar);
2366
2367 if (d->closeButtonOnTabs && event->button() == Qt::MiddleButton) {
2368 const int index = tabAt(event->pos());
2369 if (index != -1) {
2370 emit tabCloseRequested(index);
2371 return;
2372 }
2373 }
2374
2375 if (event->button() != Qt::LeftButton) {
2376 event->ignore();
2377 return;
2378 }
2379
2380 if (d->movable && d->dragInProgress && d->validIndex(d->pressedIndex)) {
2381 int length = d->tabList.at(d->pressedIndex)->dragOffset;
2382 int width = verticalTabs(d->shape)
2383 ? tabRect(d->pressedIndex).height()
2384 : tabRect(d->pressedIndex).width();
2385 int duration = qMin(ANIMATION_DURATION,
2386 (qAbs(length) * ANIMATION_DURATION) / width);
2387 d->tabList.at(d->pressedIndex)->startAnimation(d, duration);
2388 d->dragInProgress = false;
2389 d->movingTab->setVisible(false);
2390 d->dragStartPosition = QPoint();
2391 }
2392
2393 // mouse release event might happen outside the tab, so keep the pressed index
2394 int oldPressedIndex = d->pressedIndex;
2395 int i = d->indexAtPos(event->position().toPoint()) == d->pressedIndex ? d->pressedIndex : -1;
2396 d->pressedIndex = -1;
2397 QStyleOptionTabBarBase optTabBase;
2398 optTabBase.initFrom(this);
2399 optTabBase.documentMode = d->documentMode;
2400 const bool selectOnRelease =
2401 (style()->styleHint(QStyle::SH_TabBar_SelectMouseType, &optTabBase, this) == QEvent::MouseButtonRelease);
2402 if (selectOnRelease)
2403 setCurrentIndex(i);
2404 if (d->validIndex(oldPressedIndex))
2405 update(tabRect(oldPressedIndex));
2406}
2407
2408/*!\reimp
2409 */
2410void QTabBar::mouseDoubleClickEvent(QMouseEvent *event)
2411{
2412 Q_D(QTabBar);
2413 const QPoint pos = event->position().toPoint();
2414 const bool isEventInCornerButtons = (!d->leftB->isHidden() && d->leftB->geometry().contains(pos))
2415 || (!d->rightB->isHidden() && d->rightB->geometry().contains(pos));
2416 if (!isEventInCornerButtons)
2417 emit tabBarDoubleClicked(tabAt(pos));
2418
2419 mousePressEvent(event);
2420}
2421
2422/*!\reimp
2423 */
2424void QTabBar::keyPressEvent(QKeyEvent *event)
2425{
2426 Q_D(QTabBar);
2427 if (event->key() != Qt::Key_Left && event->key() != Qt::Key_Right) {
2428 event->ignore();
2429 return;
2430 }
2431 int offset = event->key() == (isRightToLeft() ? Qt::Key_Right : Qt::Key_Left) ? -1 : 1;
2432 d->setCurrentNextEnabledIndex(offset);
2433}
2434
2435/*!\reimp
2436 */
2437#if QT_CONFIG(wheelevent)
2438void QTabBar::wheelEvent(QWheelEvent *event)
2439{
2440 Q_D(QTabBar);
2441 if (style()->styleHint(QStyle::SH_TabBar_AllowWheelScrolling, nullptr, this)) {
2442 const bool wheelVertical = qAbs(event->angleDelta().y()) > qAbs(event->angleDelta().x());
2443 const bool tabsVertical = verticalTabs(d->shape);
2444 if (event->device()->capabilities().testFlag(QInputDevice::Capability::PixelScroll)) {
2445 // For wheels/touch pads with pixel precision, scroll the tab bar if
2446 // it has the right orientation.
2447 int delta = 0;
2448 if (tabsVertical == wheelVertical)
2449 delta = wheelVertical ? event->pixelDelta().y() : event->pixelDelta().x();
2450 if (layoutDirection() == Qt::RightToLeft)
2451 delta = -delta;
2452 if (delta && d->validIndex(d->lastVisible)) {
2453 const int oldScrollOffset = d->scrollOffset;
2454 const QRect lastTabRect = d->tabList.at(d->lastVisible)->rect;
2455 const QRect scrollRect = d->normalizedScrollRect(d->lastVisible);
2456 int scrollRectExtent = scrollRect.right();
2457 if (!d->leftB->isVisible())
2458 scrollRectExtent += tabsVertical ? d->leftB->height() : d->leftB->width();
2459 if (!d->rightB->isVisible())
2460 scrollRectExtent += tabsVertical ? d->rightB->height() : d->rightB->width();
2461
2462 const QRect scrollRect0 = d->normalizedScrollRect(0);
2463 const int minScrollOffset = -1 * scrollRect0.left();
2464 const int maxScrollOffset = qMax((tabsVertical ?
2465 lastTabRect.bottom() :
2466 lastTabRect.right()) - scrollRectExtent, 0);
2467 d->scrollOffset = qBound(minScrollOffset, d->scrollOffset - delta, maxScrollOffset);
2468 d->leftB->setEnabled(d->scrollOffset > -scrollRect.left());
2469 d->rightB->setEnabled(maxScrollOffset > d->scrollOffset);
2470 if (oldScrollOffset != d->scrollOffset) {
2471 event->accept();
2472 d->layoutTabs();
2473 update();
2474 return;
2475 }
2476 }
2477 } else {
2478 d->accumulatedAngleDelta += event->angleDelta();
2479 const int xSteps = d->accumulatedAngleDelta.x() / QWheelEvent::DefaultDeltasPerStep;
2480 const int ySteps = d->accumulatedAngleDelta.y() / QWheelEvent::DefaultDeltasPerStep;
2481 int offset = 0;
2482 if (xSteps > 0 || ySteps > 0) {
2483 offset = -1;
2484 d->accumulatedAngleDelta = QPoint();
2485 } else if (xSteps < 0 || ySteps < 0) {
2486 offset = 1;
2487 d->accumulatedAngleDelta = QPoint();
2488 }
2489 const int oldCurrentIndex = d->currentIndex;
2490 d->setCurrentNextEnabledIndex(offset);
2491 if (oldCurrentIndex != d->currentIndex) {
2492 event->accept();
2493 return;
2494 }
2495 }
2496 QWidget::wheelEvent(event);
2497 }
2498}
2499#endif // QT_CONFIG(wheelevent)
2500
2501void QTabBarPrivate::setCurrentNextEnabledIndex(int offset)
2502{
2503 Q_Q(QTabBar);
2504 for (int index = currentIndex + offset; validIndex(index); index += offset) {
2505 if (tabList.at(index)->enabled && tabList.at(index)->visible) {
2506 q->setCurrentIndex(index);
2507 break;
2508 }
2509 }
2510}
2511
2512/*!\reimp
2513 */
2514void QTabBar::changeEvent(QEvent *event)
2515{
2516 Q_D(QTabBar);
2517 switch (event->type()) {
2518 case QEvent::StyleChange:
2519 if (!d->elideModeSetByUser)
2520 d->elideMode = Qt::TextElideMode(style()->styleHint(QStyle::SH_TabBar_ElideMode, nullptr, this));
2521 if (!d->useScrollButtonsSetByUser)
2522 d->useScrollButtons = !style()->styleHint(QStyle::SH_TabBar_PreferNoArrows, nullptr, this);
2523 Q_FALLTHROUGH();
2524 case QEvent::FontChange:
2525 d->textSizes.clear();
2526 d->refresh();
2527 break;
2528 default:
2529 break;
2530 }
2531
2532 QWidget::changeEvent(event);
2533}
2534
2535/*!
2536 \reimp
2537*/
2538void QTabBar::timerEvent(QTimerEvent *event)
2539{
2540 Q_D(QTabBar);
2541 if (event->id() == d->switchTabTimer.id()) {
2542 d->switchTabTimer.stop();
2543 setCurrentIndex(d->switchTabCurrentIndex);
2544 d->switchTabCurrentIndex = -1;
2545 }
2546 QWidget::timerEvent(event);
2547}
2548
2549/*!
2550 \property QTabBar::elideMode
2551 \brief how to elide text in the tab bar
2552 \since 4.2
2553
2554 This property controls how items are elided when there is not
2555 enough space to show them for a given tab bar size.
2556
2557 By default the value is style-dependent.
2558
2559 \sa QTabWidget::elideMode, usesScrollButtons, QStyle::SH_TabBar_ElideMode
2560*/
2561
2562Qt::TextElideMode QTabBar::elideMode() const
2563{
2564 Q_D(const QTabBar);
2565 return d->elideMode;
2566}
2567
2568void QTabBar::setElideMode(Qt::TextElideMode mode)
2569{
2570 Q_D(QTabBar);
2571 d->elideMode = mode;
2572 d->elideModeSetByUser = true;
2573 d->textSizes.clear();
2574 d->refresh();
2575}
2576
2577/*!
2578 \property QTabBar::usesScrollButtons
2579 \brief Whether or not a tab bar should use buttons to scroll tabs when it
2580 has many tabs.
2581 \since 4.2
2582
2583 When there are too many tabs in a tab bar for its size, the tab bar can either choose
2584 to expand its size or to add buttons that allow you to scroll through the tabs.
2585
2586 By default the value is style-dependent.
2587
2588 \sa elideMode, QTabWidget::usesScrollButtons, QStyle::SH_TabBar_PreferNoArrows
2589*/
2590bool QTabBar::usesScrollButtons() const
2591{
2592 return d_func()->useScrollButtons;
2593}
2594
2595void QTabBar::setUsesScrollButtons(bool useButtons)
2596{
2597 Q_D(QTabBar);
2598 d->useScrollButtonsSetByUser = true;
2599 if (d->useScrollButtons == useButtons)
2600 return;
2601 d->useScrollButtons = useButtons;
2602 d->refresh();
2603}
2604
2605/*!
2606 \property QTabBar::tabsClosable
2607 \brief Whether or not a tab bar should place close buttons on each tab
2608 \since 4.5
2609
2610 When tabsClosable is set to true a close button will appear on the tab on
2611 either the left or right hand side depending upon the style. When the button
2612 is clicked directly, or a mouse middle-click is received anywhere in the tab,
2613 the signal tabCloseRequested will be emitted.
2614
2615 By default the value is false.
2616
2617 \sa setTabButton(), tabRemoved()
2618*/
2619
2620bool QTabBar::tabsClosable() const
2621{
2622 Q_D(const QTabBar);
2623 return d->closeButtonOnTabs;
2624}
2625
2626void QTabBar::setTabsClosable(bool closable)
2627{
2628 Q_D(QTabBar);
2629 if (d->closeButtonOnTabs == closable)
2630 return;
2631 d->closeButtonOnTabs = closable;
2632 ButtonPosition closeSide = (ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, this);
2633 if (!closable) {
2634 for (auto tab : std::as_const(d->tabList)) {
2635 if (closeSide == LeftSide && tab->leftWidget) {
2636 tab->leftWidget->deleteLater();
2637 tab->leftWidget = nullptr;
2638 }
2639 if (closeSide == RightSide && tab->rightWidget) {
2640 tab->rightWidget->deleteLater();
2641 tab->rightWidget = nullptr;
2642 }
2643 }
2644 } else {
2645 bool newButtons = false;
2646 for (int i = 0; i < d->tabList.size(); ++i) {
2647 if (tabButton(i, closeSide))
2648 continue;
2649 newButtons = true;
2650 QAbstractButton *closeButton = new CloseButton(this);
2651 QObjectPrivate::connect(closeButton, &CloseButton::clicked,
2652 d, &QTabBarPrivate::closeTab);
2653 setTabButton(i, closeSide, closeButton);
2654 }
2655 if (newButtons)
2656 d->layoutTabs();
2657 }
2658 update();
2659}
2660
2661/*!
2662 \enum QTabBar::ButtonPosition
2663 \since 4.5
2664
2665 This enum type lists the location of the widget on a tab.
2666
2667 \value LeftSide Left side of the tab.
2668
2669 \value RightSide Right side of the tab.
2670
2671*/
2672
2673/*!
2674 \enum QTabBar::SelectionBehavior
2675 \since 4.5
2676
2677 This enum type lists the behavior of QTabBar when a tab is removed
2678 and the tab being removed is also the current tab.
2679
2680 \value SelectLeftTab Select the tab to the left of the one being removed.
2681
2682 \value SelectRightTab Select the tab to the right of the one being removed.
2683
2684 \value SelectPreviousTab Select the previously selected tab.
2685
2686*/
2687
2688/*!
2689 \property QTabBar::selectionBehaviorOnRemove
2690 \brief What tab should be set as current when removeTab is called if
2691 the removed tab is also the current tab.
2692 \since 4.5
2693
2694 By default the value is SelectRightTab.
2695
2696 \sa removeTab()
2697*/
2698
2699
2700QTabBar::SelectionBehavior QTabBar::selectionBehaviorOnRemove() const
2701{
2702 Q_D(const QTabBar);
2703 return d->selectionBehaviorOnRemove;
2704}
2705
2706void QTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior)
2707{
2708 Q_D(QTabBar);
2709 d->selectionBehaviorOnRemove = behavior;
2710}
2711
2712/*!
2713 \property QTabBar::expanding
2714 \brief When expanding is true QTabBar will expand the tabs to use the empty space.
2715 \since 4.5
2716
2717 By default the value is true.
2718
2719 \sa QTabWidget::documentMode
2720*/
2721
2722bool QTabBar::expanding() const
2723{
2724 Q_D(const QTabBar);
2725 return d->expanding;
2726}
2727
2728void QTabBar::setExpanding(bool enabled)
2729{
2730 Q_D(QTabBar);
2731 if (d->expanding == enabled)
2732 return;
2733 d->expanding = enabled;
2734 d->layoutTabs();
2735}
2736
2737/*!
2738 \property QTabBar::movable
2739 \brief This property holds whether the user can move the tabs
2740 within the tabbar area.
2741
2742 \since 4.5
2743
2744 By default, this property is \c false;
2745*/
2746
2747bool QTabBar::isMovable() const
2748{
2749 Q_D(const QTabBar);
2750 return d->movable;
2751}
2752
2753void QTabBar::setMovable(bool movable)
2754{
2755 Q_D(QTabBar);
2756 d->movable = movable;
2757}
2758
2759
2760/*!
2761 \property QTabBar::documentMode
2762 \brief Whether or not the tab bar is rendered in a mode suitable for the main window.
2763 \since 4.5
2764
2765 This property is used as a hint for styles to draw the tabs in a different
2766 way then they would normally look in a tab widget. On \macos this will
2767 look similar to the tabs in Safari or Sierra's Terminal.app.
2768
2769 \sa QTabWidget::documentMode
2770*/
2771bool QTabBar::documentMode() const
2772{
2773 return d_func()->documentMode;
2774}
2775
2776void QTabBar::setDocumentMode(bool enabled)
2777{
2778 Q_D(QTabBar);
2779
2780 d->documentMode = enabled;
2781 d->updateMacBorderMetrics();
2782}
2783
2784/*!
2785 \property QTabBar::autoHide
2786 \brief If true, the tab bar is automatically hidden when it contains less
2787 than 2 tabs.
2788 \since 5.4
2789
2790 By default, this property is false.
2791
2792 \sa QWidget::visible
2793*/
2794
2795bool QTabBar::autoHide() const
2796{
2797 Q_D(const QTabBar);
2798 return d->autoHide;
2799}
2800
2801void QTabBar::setAutoHide(bool hide)
2802{
2803 Q_D(QTabBar);
2804 if (d->autoHide == hide)
2805 return;
2806
2807 d->autoHide = hide;
2808 if (hide)
2809 d->autoHideTabs();
2810 else
2811 setVisible(true);
2812}
2813
2814/*!
2815 \property QTabBar::changeCurrentOnDrag
2816 \brief If true, then the current tab is automatically changed when dragging
2817 over the tabbar.
2818 \since 5.4
2819
2820 \note You should also set acceptDrops property to true to make this feature
2821 work.
2822
2823 By default, this property is false.
2824*/
2825
2826bool QTabBar::changeCurrentOnDrag() const
2827{
2828 Q_D(const QTabBar);
2829 return d->changeCurrentOnDrag;
2830}
2831
2832void QTabBar::setChangeCurrentOnDrag(bool change)
2833{
2834 Q_D(QTabBar);
2835 d->changeCurrentOnDrag = change;
2836 if (!change)
2837 d->killSwitchTabTimer();
2838}
2839
2840/*!
2841 Sets \a widget on the tab \a index. The widget is placed
2842 on the left or right hand side depending on the \a position.
2843 \since 4.5
2844
2845 Any previously set widget in \a position is hidden. Setting \a widget
2846 to \nullptr will hide the current widget at \a position.
2847
2848 The tab bar will take ownership of the widget and so all widgets set here
2849 will be deleted by the tab bar when it is destroyed unless you separately
2850 reparent the widget after setting some other widget (or \nullptr).
2851
2852 \sa tabsClosable()
2853 */
2854void QTabBar::setTabButton(int index, ButtonPosition position, QWidget *widget)
2855{
2856 Q_D(QTabBar);
2857 if (index < 0 || index >= d->tabList.size())
2858 return;
2859 if (widget) {
2860 widget->setParent(this);
2861 // make sure our left and right widgets stay on top
2862 widget->lower();
2863 widget->show();
2864 }
2865 auto &tab = *d->tabList.at(index);
2866 if (position == LeftSide) {
2867 if (tab.leftWidget)
2868 tab.leftWidget->hide();
2869 tab.leftWidget = widget;
2870 } else {
2871 if (tab.rightWidget)
2872 tab.rightWidget->hide();
2873 tab.rightWidget = widget;
2874 }
2875 d->layoutTabs();
2876 d->refresh();
2877 update();
2878}
2879
2880/*!
2881 Returns the widget set a tab \a index and \a position or \nullptr
2882 if one is not set.
2883 */
2884QWidget *QTabBar::tabButton(int index, ButtonPosition position) const
2885{
2886 Q_D(const QTabBar);
2887 if (const auto tab = d->at(index)) {
2888 return position == LeftSide ? tab->leftWidget
2889 : tab->rightWidget;
2890 }
2891 return nullptr;
2892}
2893
2894#if QT_CONFIG(accessibility)
2895/*!
2896 Sets the accessibleName of the tab at position \a index to \a name.
2897*/
2898void QTabBar::setAccessibleTabName(int index, const QString &name)
2899{
2900 Q_D(QTabBar);
2901 if (QTabBarPrivate::Tab *tab = d->at(index)) {
2902 tab->accessibleName = name;
2903 QAccessibleEvent event(this, QAccessible::NameChanged);
2904 event.setChild(index);
2905 QAccessible::updateAccessibility(&event);
2906 }
2907}
2908
2909/*!
2910 Returns the accessibleName of the tab at position \a index, or an empty
2911 string if \a index is out of range.
2912*/
2913QString QTabBar::accessibleTabName(int index) const
2914{
2915 Q_D(const QTabBar);
2916 if (const QTabBarPrivate::Tab *tab = d->at(index))
2917 return tab->accessibleName;
2918 return QString();
2919}
2920#endif // QT_CONFIG(accessibility)
2921
2922CloseButton::CloseButton(QWidget *parent)
2923 : QAbstractButton(parent)
2924{
2925 setFocusPolicy(Qt::NoFocus);
2926#ifndef QT_NO_CURSOR
2927 setCursor(Qt::ArrowCursor);
2928#endif
2929#if QT_CONFIG(tooltip)
2930 setToolTip(tr("Close Tab"));
2931#endif
2932 resize(sizeHint());
2933}
2934
2935QSize CloseButton::sizeHint() const
2936{
2937 ensurePolished();
2938 int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
2939 int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
2940 return QSize(width, height);
2941}
2942
2943void CloseButton::enterEvent(QEnterEvent *event)
2944{
2945 if (isEnabled())
2946 update();
2947 QAbstractButton::enterEvent(event);
2948}
2949
2950void CloseButton::leaveEvent(QEvent *event)
2951{
2952 if (isEnabled())
2953 update();
2954 QAbstractButton::leaveEvent(event);
2955}
2956
2957void CloseButton::paintEvent(QPaintEvent *)
2958{
2959 QPainter p(this);
2960 QStyleOption opt;
2961 opt.initFrom(this);
2962 opt.state |= QStyle::State_AutoRaise;
2963 if (isEnabled() && underMouse() && !isChecked() && !isDown())
2964 opt.state |= QStyle::State_Raised;
2965 if (isChecked())
2966 opt.state |= QStyle::State_On;
2967 if (isDown())
2968 opt.state |= QStyle::State_Sunken;
2969
2970 if (const QTabBar *tb = qobject_cast<const QTabBar *>(parent())) {
2971 int index = tb->currentIndex();
2972 QTabBar::ButtonPosition position = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tb);
2973 if (tb->tabButton(index, position) == this)
2974 opt.state |= QStyle::State_Selected;
2975 }
2976
2977 if (m_parentClipRect.isValid()) {
2978 auto tl = mapFromParent(m_parentClipRect.topLeft());
2979 auto br = mapFromParent(m_parentClipRect.bottomRight());
2980 p.setClipRect(QRect(tl, br));
2981 }
2982 style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
2983}
2984
2985#if QT_CONFIG(animation)
2986void QTabBarPrivate::Tab::TabBarAnimation::updateCurrentValue(const QVariant &current)
2987{
2988 priv->moveTab(priv->tabList.indexOf(tab), current.toInt());
2989}
2990
2991void QTabBarPrivate::Tab::TabBarAnimation::updateState(QAbstractAnimation::State newState, QAbstractAnimation::State)
2992{
2993 if (newState == Stopped) priv->moveTabFinished(priv->tabList.indexOf(tab));
2994}
2995#endif
2996
2997QT_END_NAMESPACE
2998
2999#include "moc_qtabbar.cpp"
3000#include "qtabbar.moc"
friend class QWidget
Definition qpainter.h:432
Combined button and popup list for selecting options.
static QString computeElidedText(Qt::TextElideMode mode, const QString &text)
Definition qtabbar.cpp:1560