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