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