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