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