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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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