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

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