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

source code of qtbase/src/widgets/widgets/qtabbar.cpp