File indexing completed on 2024-05-12 04:58:23

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2013-2014 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
0004 * Copyright (C) 2014-2018 David Rosca <nowrep@gmail.com>
0005 *
0006 * This program is free software: you can redistribute it and/or modify
0007 * it under the terms of the GNU General Public License as published by
0008 * the Free Software Foundation, either version 3 of the License, or
0009 * (at your option) any later version.
0010 *
0011 * This program is distributed in the hope that it will be useful,
0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 * GNU General Public License for more details.
0015 *
0016 * You should have received a copy of the GNU General Public License
0017 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0018 * ============================================================ */
0019 #include "combotabbar.h"
0020 #include "toolbutton.h"
0021 #include "tabicon.h"
0022 #include "mainapplication.h"
0023 #include "proxystyle.h"
0024 #include "qzsettings.h"
0025 #include "qztools.h"
0026 
0027 #include <QIcon>
0028 #include <QHBoxLayout>
0029 #include <QStylePainter>
0030 #include <QStyleOptionTab>
0031 #include <QStyleOptionTabBarBase>
0032 #include <QPropertyAnimation>
0033 #include <QScrollArea>
0034 #include <QTimer>
0035 #include <QMouseEvent>
0036 #include <QApplication>
0037 #include <QToolTip>
0038 #include <QtGuiVersion>
0039 
0040 class QMovableTabWidget : public QWidget
0041 {
0042 public:
0043     QPixmap m_pixmap;
0044 };
0045 
0046 ComboTabBar::ComboTabBar(QWidget* parent)
0047     : QWidget(parent)
0048     , m_mainTabBar(nullptr)
0049     , m_pinnedTabBar(nullptr)
0050     , m_mainBarOverFlowed(false)
0051     , m_lastAppliedOverflow(false)
0052     , m_usesScrollButtons(false)
0053     , m_blockCurrentChangedSignal(false)
0054 {
0055     QObject::setObjectName(QSL("tabbarwidget"));
0056 
0057     m_mainTabBar = new TabBarHelper(/*isPinnedTabBar*/ false, this);
0058     m_pinnedTabBar = new TabBarHelper(/*isPinnedTabBar*/ true, this);
0059     m_mainTabBarWidget = new TabBarScrollWidget(m_mainTabBar, this);
0060     m_pinnedTabBarWidget = new TabBarScrollWidget(m_pinnedTabBar, this);
0061 
0062     m_mainTabBar->setScrollArea(m_mainTabBarWidget->scrollArea());
0063     m_pinnedTabBar->setScrollArea(m_pinnedTabBarWidget->scrollArea());
0064 
0065     connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
0066     connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
0067     connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
0068     connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
0069     connect(this, SIGNAL(overFlowChanged(bool)), m_mainTabBarWidget, SLOT(overFlowChanged(bool)));
0070 
0071     m_mainTabBar->setActiveTabBar(true);
0072     m_pinnedTabBar->setTabsClosable(false);
0073 
0074     m_leftLayout = new QHBoxLayout;
0075     m_leftLayout->setSpacing(0);
0076     m_leftLayout->setContentsMargins(0, 0, 0, 0);
0077     m_leftContainer = new QWidget(this);
0078     m_leftContainer->setLayout(m_leftLayout);
0079 
0080     m_rightLayout = new QHBoxLayout;
0081     m_rightLayout->setSpacing(0);
0082     m_rightLayout->setContentsMargins(0, 0, 0, 0);
0083     m_rightContainer = new QWidget(this);
0084     m_rightContainer->setLayout(m_rightLayout);
0085 
0086     m_mainLayout = new QHBoxLayout;
0087     m_mainLayout->setSpacing(0);
0088     m_mainLayout->setContentsMargins(0, 0, 0, 0);
0089     m_mainLayout->addWidget(m_leftContainer);
0090     m_mainLayout->addWidget(m_pinnedTabBarWidget);
0091     m_mainLayout->addWidget(m_mainTabBarWidget);
0092     m_mainLayout->addWidget(m_rightContainer);
0093     setLayout(m_mainLayout);
0094 
0095     connect(m_mainTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
0096     connect(m_mainTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
0097     connect(m_mainTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);
0098 
0099     connect(m_pinnedTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
0100     connect(m_pinnedTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
0101     connect(m_pinnedTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);
0102 
0103     setAutoFillBackground(false);
0104     m_mainTabBar->setAutoFillBackground(false);
0105     m_pinnedTabBar->setAutoFillBackground(false);
0106 
0107     m_mainTabBar->installEventFilter(this);
0108     m_pinnedTabBar->installEventFilter(this);
0109     m_leftContainer->installEventFilter(this);
0110     m_rightContainer->installEventFilter(this);
0111     m_mainTabBarWidget->installEventFilter(this);
0112     m_pinnedTabBarWidget->installEventFilter(this);
0113 }
0114 
0115 int ComboTabBar::addTab(const QString &text)
0116 {
0117     return insertTab(-1, text);
0118 }
0119 
0120 int ComboTabBar::addTab(const QIcon &icon, const QString &text)
0121 {
0122     return insertTab(-1, icon, text);
0123 }
0124 
0125 int ComboTabBar::insertTab(int index, const QString &text)
0126 {
0127     return insertTab(index, QIcon(), text);
0128 }
0129 
0130 int ComboTabBar::insertTab(int index, const QIcon &icon, const QString &text, bool pinned)
0131 {
0132     if (pinned) {
0133         index = m_pinnedTabBar->insertTab(index, icon, text);
0134     }
0135     else {
0136         index = m_mainTabBar->insertTab(index - pinnedTabsCount(), icon, text);
0137 
0138         if (tabsClosable()) {
0139             QWidget* closeButton = m_mainTabBar->tabButton(index, closeButtonPosition());
0140             if ((closeButton && closeButton->objectName() != QLatin1String("combotabbar_tabs_close_button")) || !closeButton) {
0141                 // insert our close button
0142                 insertCloseButton(index + pinnedTabsCount());
0143                 if (closeButton) {
0144                     closeButton->deleteLater();
0145                 }
0146             }
0147         }
0148 
0149         index += pinnedTabsCount();
0150     }
0151 
0152     updatePinnedTabBarVisibility();
0153     tabInserted(index);
0154     setMinimumWidths();
0155 
0156     return index;
0157 }
0158 
0159 void ComboTabBar::removeTab(int index)
0160 {
0161     if (validIndex(index)) {
0162         setUpdatesEnabled(false);
0163 
0164         localTabBar(index)->removeTab(toLocalIndex(index));
0165         updatePinnedTabBarVisibility();
0166         tabRemoved(index);
0167         setMinimumWidths();
0168 
0169         setUpdatesEnabled(true);
0170         updateTabBars();
0171     }
0172 }
0173 
0174 void ComboTabBar::moveTab(int from, int to)
0175 {
0176     if (from >= pinnedTabsCount() && to >= pinnedTabsCount()) {
0177         m_mainTabBar->moveTab(from - pinnedTabsCount(), to - pinnedTabsCount());
0178     }
0179     else if (from < pinnedTabsCount() && to < pinnedTabsCount()) {
0180         m_pinnedTabBar->moveTab(from, to);
0181     }
0182 }
0183 
0184 bool ComboTabBar::isTabEnabled(int index) const
0185 {
0186     return localTabBar(index)->isTabEnabled(toLocalIndex(index));
0187 }
0188 
0189 void ComboTabBar::setTabEnabled(int index, bool enabled)
0190 {
0191     localTabBar(index)->setTabEnabled(toLocalIndex(index), enabled);
0192 }
0193 
0194 QColor ComboTabBar::tabTextColor(int index) const
0195 {
0196     return localTabBar(index)->tabTextColor(toLocalIndex(index));
0197 }
0198 
0199 void ComboTabBar::setTabTextColor(int index, const QColor &color)
0200 {
0201     localTabBar(index)->setTabTextColor(toLocalIndex(index), color);
0202 }
0203 
0204 QRect ComboTabBar::tabRect(int index) const
0205 {
0206     return mapFromLocalTabRect(localTabBar(index)->tabRect(toLocalIndex(index)), localTabBar(index));
0207 }
0208 
0209 QRect ComboTabBar::draggedTabRect() const
0210 {
0211     const QRect r = m_pinnedTabBar->draggedTabRect();
0212     if (r.isValid()) {
0213         return mapFromLocalTabRect(r, m_pinnedTabBar);
0214     }
0215     return mapFromLocalTabRect(m_mainTabBar->draggedTabRect(), m_mainTabBar);
0216 }
0217 
0218 QPixmap ComboTabBar::tabPixmap(int index) const
0219 {
0220     return localTabBar(index)->tabPixmap(toLocalIndex(index));
0221 }
0222 
0223 int ComboTabBar::tabAt(const QPoint &pos) const
0224 {
0225     QWidget* w = QApplication::widgetAt(mapToGlobal(pos));
0226     if (!qobject_cast<TabBarHelper*>(w) && !qobject_cast<TabIcon*>(w) && !qobject_cast<CloseButton*>(w))
0227         return -1;
0228 
0229     if (m_pinnedTabBarWidget->geometry().contains(pos)) {
0230         return m_pinnedTabBarWidget->tabAt(m_pinnedTabBarWidget->mapFromParent(pos));
0231     } else if (m_mainTabBarWidget->geometry().contains(pos)) {
0232         int index = m_mainTabBarWidget->tabAt(m_mainTabBarWidget->mapFromParent(pos));
0233         if (index != -1)
0234             index += pinnedTabsCount();
0235         return index;
0236     }
0237 
0238     return -1;
0239 }
0240 
0241 bool ComboTabBar::emptyArea(const QPoint &pos) const
0242 {
0243     if (tabAt(pos) != -1)
0244         return false;
0245 
0246     return qobject_cast<TabBarHelper*>(QApplication::widgetAt(mapToGlobal(pos)));
0247 }
0248 
0249 int ComboTabBar::mainTabBarCurrentIndex() const
0250 {
0251     return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
0252 }
0253 
0254 int ComboTabBar::currentIndex() const
0255 {
0256     if (m_pinnedTabBar->isActiveTabBar()) {
0257         return m_pinnedTabBar->currentIndex();
0258     }
0259     else {
0260         return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
0261     }
0262 }
0263 
0264 void ComboTabBar::setCurrentIndex(int index)
0265 {
0266     return localTabBar(index)->setCurrentIndex(toLocalIndex(index));
0267 }
0268 
0269 void ComboTabBar::slotCurrentChanged(int index)
0270 {
0271     if (m_blockCurrentChangedSignal) {
0272         return;
0273     }
0274 
0275     if (sender() == m_pinnedTabBar) {
0276         if (index == -1 && m_mainTabBar->count() > 0) {
0277             m_mainTabBar->setActiveTabBar(true);
0278             m_pinnedTabBar->setActiveTabBar(false);
0279             Q_EMIT currentChanged(pinnedTabsCount());
0280         }
0281         else {
0282             m_pinnedTabBar->setActiveTabBar(true);
0283             m_mainTabBar->setActiveTabBar(false);
0284             Q_EMIT currentChanged(index);
0285         }
0286     }
0287     else {
0288         if (index == -1 && pinnedTabsCount() > 0) {
0289             m_pinnedTabBar->setActiveTabBar(true);
0290             m_mainTabBar->setActiveTabBar(false);
0291             Q_EMIT currentChanged(pinnedTabsCount() - 1);
0292         }
0293         else {
0294             m_mainTabBar->setActiveTabBar(true);
0295             m_pinnedTabBar->setActiveTabBar(false);
0296             Q_EMIT currentChanged(index + pinnedTabsCount());
0297         }
0298     }
0299 }
0300 
0301 void ComboTabBar::slotTabCloseRequested(int index)
0302 {
0303     if (sender() == m_pinnedTabBar) {
0304         Q_EMIT tabCloseRequested(index);
0305     }
0306     else {
0307         Q_EMIT tabCloseRequested(index + pinnedTabsCount());
0308     }
0309 }
0310 
0311 void ComboTabBar::slotTabMoved(int from, int to)
0312 {
0313     if (sender() == m_pinnedTabBar) {
0314         Q_EMIT tabMoved(from, to);
0315     }
0316     else {
0317         Q_EMIT tabMoved(from + pinnedTabsCount(), to + pinnedTabsCount());
0318     }
0319 }
0320 
0321 void ComboTabBar::closeTabFromButton()
0322 {
0323     QWidget* button = qobject_cast<QWidget*>(sender());
0324 
0325     int tabToClose = -1;
0326 
0327     for (int i = 0; i < m_mainTabBar->count(); ++i) {
0328         if (m_mainTabBar->tabButton(i, closeButtonPosition()) == button) {
0329             tabToClose = i;
0330             break;
0331         }
0332     }
0333 
0334     if (tabToClose != -1) {
0335         Q_EMIT tabCloseRequested(tabToClose + pinnedTabsCount());
0336     }
0337 }
0338 
0339 void ComboTabBar::updateTabBars()
0340 {
0341     m_mainTabBar->update();
0342     m_pinnedTabBar->update();
0343 }
0344 
0345 void ComboTabBar::emitOverFlowChanged()
0346 {
0347     if (m_mainBarOverFlowed != m_lastAppliedOverflow) {
0348         Q_EMIT overFlowChanged(m_mainBarOverFlowed);
0349         m_lastAppliedOverflow = m_mainBarOverFlowed;
0350     }
0351 }
0352 
0353 int ComboTabBar::count() const
0354 {
0355     return pinnedTabsCount() + m_mainTabBar->count();
0356 }
0357 
0358 void ComboTabBar::setDrawBase(bool drawTheBase)
0359 {
0360     m_mainTabBar->setDrawBase(drawTheBase);
0361     m_pinnedTabBar->setDrawBase(drawTheBase);
0362 }
0363 
0364 bool ComboTabBar::drawBase() const
0365 {
0366     return m_mainTabBar->drawBase();
0367 }
0368 
0369 Qt::TextElideMode ComboTabBar::elideMode() const
0370 {
0371     return m_mainTabBar->elideMode();
0372 }
0373 
0374 void ComboTabBar::setElideMode(Qt::TextElideMode elide)
0375 {
0376     m_mainTabBar->setElideMode(elide);
0377     m_pinnedTabBar->setElideMode(elide);
0378 }
0379 
0380 QString ComboTabBar::tabText(int index) const
0381 {
0382     return localTabBar(index)->tabText(toLocalIndex(index));
0383 }
0384 
0385 void ComboTabBar::setTabText(int index, const QString &text)
0386 {
0387     localTabBar(index)->setTabText(toLocalIndex(index), text);
0388 }
0389 
0390 void ComboTabBar::setTabToolTip(int index, const QString &tip)
0391 {
0392     localTabBar(index)->setTabToolTip(toLocalIndex(index), tip);
0393 }
0394 
0395 QString ComboTabBar::tabToolTip(int index) const
0396 {
0397     return localTabBar(index)->tabToolTip(toLocalIndex(index));
0398 }
0399 
0400 bool ComboTabBar::tabsClosable() const
0401 {
0402     return m_mainTabBar->tabsClosable();
0403 }
0404 
0405 void ComboTabBar::setTabsClosable(bool closable)
0406 {
0407     if (closable == tabsClosable()) {
0408         return;
0409     }
0410 
0411     if (closable) {
0412         // insert our close button
0413         for (int i = 0; i < m_mainTabBar->count(); ++i) {
0414             QWidget* closeButton = m_mainTabBar->tabButton(i, closeButtonPosition());
0415             if (closeButton) {
0416                 if (closeButton->objectName() == QLatin1String("combotabbar_tabs_close_button")) {
0417                     continue;
0418                 }
0419             }
0420 
0421             insertCloseButton(i + pinnedTabsCount());
0422             if (closeButton) {
0423                 closeButton->deleteLater();
0424             }
0425         }
0426     }
0427     m_mainTabBar->setTabsClosable(closable);
0428 }
0429 
0430 void ComboTabBar::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
0431 {
0432     if (widget)
0433         widget->setMinimumSize(closeButtonSize());
0434     localTabBar(index)->setTabButton(toLocalIndex(index), position, widget);
0435 }
0436 
0437 QWidget* ComboTabBar::tabButton(int index, QTabBar::ButtonPosition position) const
0438 {
0439     return localTabBar(index)->tabButton(toLocalIndex(index), position);
0440 }
0441 
0442 QTabBar::SelectionBehavior ComboTabBar::selectionBehaviorOnRemove() const
0443 {
0444     return m_mainTabBar->selectionBehaviorOnRemove();
0445 }
0446 
0447 void ComboTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior)
0448 {
0449     m_mainTabBar->setSelectionBehaviorOnRemove(behavior);
0450     m_pinnedTabBar->setSelectionBehaviorOnRemove(behavior);
0451 }
0452 
0453 bool ComboTabBar::expanding() const
0454 {
0455     return m_mainTabBar->expanding();
0456 }
0457 
0458 void ComboTabBar::setExpanding(bool enabled)
0459 {
0460     m_mainTabBar->setExpanding(enabled);
0461     m_pinnedTabBar->setExpanding(enabled);
0462 }
0463 
0464 bool ComboTabBar::isMovable() const
0465 {
0466     return m_mainTabBar->isMovable();
0467 }
0468 
0469 void ComboTabBar::setMovable(bool movable)
0470 {
0471     m_mainTabBar->setMovable(movable);
0472     m_pinnedTabBar->setMovable(movable);
0473 }
0474 
0475 bool ComboTabBar::documentMode() const
0476 {
0477     return m_mainTabBar->documentMode();
0478 }
0479 
0480 void ComboTabBar::setDocumentMode(bool set)
0481 {
0482     m_mainTabBar->setDocumentMode(set);
0483     m_pinnedTabBar->setDocumentMode(set);
0484 }
0485 
0486 int ComboTabBar::pinnedTabsCount() const
0487 {
0488     return m_pinnedTabBar->count();
0489 }
0490 
0491 int ComboTabBar::normalTabsCount() const
0492 {
0493     return m_mainTabBar->count();
0494 }
0495 
0496 bool ComboTabBar::isPinned(int index) const
0497 {
0498     return index >= 0 && index < pinnedTabsCount();
0499 }
0500 
0501 void ComboTabBar::setFocusPolicy(Qt::FocusPolicy policy)
0502 {
0503     QWidget::setFocusPolicy(policy);
0504     m_mainTabBar->setFocusPolicy(policy);
0505     m_pinnedTabBar->setFocusPolicy(policy);
0506 }
0507 
0508 void ComboTabBar::setObjectName(const QString &name)
0509 {
0510     m_mainTabBar->setObjectName(name);
0511     m_pinnedTabBar->setObjectName(name);
0512 }
0513 
0514 void ComboTabBar::setMouseTracking(bool enable)
0515 {
0516     m_mainTabBarWidget->scrollArea()->setMouseTracking(enable);
0517     m_mainTabBarWidget->setMouseTracking(enable);
0518     m_mainTabBar->setMouseTracking(enable);
0519 
0520     m_pinnedTabBarWidget->scrollArea()->setMouseTracking(enable);
0521     m_pinnedTabBarWidget->setMouseTracking(enable);
0522     m_pinnedTabBar->setMouseTracking(enable);
0523 
0524     QWidget::setMouseTracking(enable);
0525 }
0526 
0527 void ComboTabBar::setUpLayout()
0528 {
0529     int height = qMax(m_mainTabBar->height(), m_pinnedTabBar->height());
0530 
0531     if (height < 1) {
0532         height = qMax(m_mainTabBar->sizeHint().height(), m_pinnedTabBar->sizeHint().height());
0533     }
0534 
0535     // We need to setup heights even before m_mainTabBar->height() has correct value
0536     // So lets just set minimum 5px height
0537     height = qMax(5, height);
0538 
0539     setFixedHeight(height);
0540     m_leftContainer->setFixedHeight(height);
0541     m_rightContainer->setFixedHeight(height);
0542     m_mainTabBarWidget->setUpLayout();
0543     m_pinnedTabBarWidget->setUpLayout();
0544 
0545     setMinimumWidths();
0546 
0547     if (isVisible() && height > 5) {
0548         // ComboTabBar is now visible, we can sync heights of both tabbars
0549         m_mainTabBar->setFixedHeight(height);
0550         m_pinnedTabBar->setFixedHeight(height);
0551     }
0552 }
0553 
0554 void ComboTabBar::insertCloseButton(int index)
0555 {
0556     index -= pinnedTabsCount();
0557     if (index < 0) {
0558         return;
0559     }
0560 
0561     QAbstractButton* closeButton = new CloseButton(this);
0562     closeButton->setFixedSize(closeButtonSize());
0563     closeButton->setToolTip(m_closeButtonsToolTip);
0564     connect(closeButton, &QAbstractButton::clicked, this, &ComboTabBar::closeTabFromButton);
0565     m_mainTabBar->setTabButton(index, closeButtonPosition(), closeButton);
0566 }
0567 
0568 void ComboTabBar::setCloseButtonsToolTip(const QString &tip)
0569 {
0570     m_closeButtonsToolTip = tip;
0571 }
0572 
0573 int ComboTabBar::mainTabBarWidth() const
0574 {
0575     return m_mainTabBar->width();
0576 }
0577 
0578 int ComboTabBar::pinTabBarWidth() const
0579 {
0580     return m_pinnedTabBarWidget->isHidden() ? 0 : m_pinnedTabBarWidget->width();
0581 }
0582 
0583 bool ComboTabBar::event(QEvent *event)
0584 {
0585     const bool res = QWidget::event(event);
0586 
0587     switch (event->type()) {
0588     case QEvent::ToolTip:
0589         if (!isDragInProgress() && !isScrollInProgress()) {
0590             int index = tabAt(mapFromGlobal(QCursor::pos()));
0591             if (index >= 0)
0592                 QToolTip::showText(QCursor::pos(), tabToolTip(index));
0593         }
0594         break;
0595 
0596     case QEvent::Resize:
0597         ensureVisible();
0598         break;
0599 
0600     case QEvent::Show:
0601         if (!event->spontaneous())
0602             QTimer::singleShot(0, this, &ComboTabBar::setUpLayout);
0603         break;
0604 
0605     case QEvent::Enter:
0606     case QEvent::Leave:
0607         // Make sure tabs are painted with correct mouseover state
0608         QTimer::singleShot(100, this, &ComboTabBar::updateTabBars);
0609         break;
0610 
0611     default:
0612         break;
0613     }
0614 
0615     return res;
0616 }
0617 
0618 void ComboTabBar::wheelEvent(QWheelEvent* event)
0619 {
0620     event->accept();
0621 
0622     if (qzSettings->alwaysSwitchTabsWithWheel || (!m_mainTabBarWidget->isOverflowed() && !m_pinnedTabBarWidget->isOverflowed())) {
0623         m_wheelHelper.processEvent(event);
0624         while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) {
0625             switch (direction) {
0626             case WheelHelper::WheelUp:
0627             case WheelHelper::WheelLeft:
0628                 setCurrentNextEnabledIndex(-1);
0629                 break;
0630 
0631             case WheelHelper::WheelDown:
0632             case WheelHelper::WheelRight:
0633                 setCurrentNextEnabledIndex(1);
0634                 break;
0635 
0636             default:
0637                 break;
0638             }
0639         }
0640         return;
0641     }
0642 
0643     if (m_mainTabBarWidget->underMouse()) {
0644         if (m_mainTabBarWidget->isOverflowed()) {
0645             m_mainTabBarWidget->scrollByWheel(event);
0646         }
0647         else if (m_pinnedTabBarWidget->isOverflowed()) {
0648             m_pinnedTabBarWidget->scrollByWheel(event);
0649         }
0650     }
0651     else if (m_pinnedTabBarWidget->underMouse()) {
0652         if (m_pinnedTabBarWidget->isOverflowed()) {
0653             m_pinnedTabBarWidget->scrollByWheel(event);
0654         }
0655         else if (m_mainTabBarWidget->isOverflowed()) {
0656             m_mainTabBarWidget->scrollByWheel(event);
0657         }
0658     }
0659 }
0660 
0661 bool ComboTabBar::eventFilter(QObject* obj, QEvent* ev)
0662 {
0663     if (obj == m_mainTabBar && ev->type() == QEvent::Resize) {
0664         auto* event = static_cast<QResizeEvent*>(ev);
0665         if (event->oldSize().height() != event->size().height()) {
0666             setUpLayout();
0667         }
0668     }
0669 
0670     // Handle wheel events exclusively in ComboTabBar
0671     if (ev->type() == QEvent::Wheel) {
0672         wheelEvent(static_cast<QWheelEvent*>(ev));
0673         return true;
0674     }
0675 
0676     return QWidget::eventFilter(obj, ev);
0677 }
0678 
0679 void ComboTabBar::paintEvent(QPaintEvent* ev)
0680 {
0681     Q_UNUSED(ev);
0682 
0683     // This is needed to apply style sheets
0684     QStyleOption option;
0685     option.initFrom(this);
0686     QPainter p(this);
0687     style()->drawPrimitive(QStyle::PE_Widget, &option, &p, this);
0688 
0689 #ifndef Q_OS_MACOS
0690     // Draw tabbar base even on parts of ComboTabBar that are not directly QTabBar
0691     QStyleOptionTabBarBase opt;
0692     TabBarHelper::initStyleBaseOption(&opt, m_mainTabBar, size());
0693 
0694     // Left container
0695     opt.rect.setX(m_leftContainer->x());
0696     opt.rect.setWidth(m_leftContainer->width());
0697     style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
0698 
0699     // Right container
0700     opt.rect.setX(m_rightContainer->x());
0701     opt.rect.setWidth(m_rightContainer->width());
0702     style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
0703 
0704     if (m_mainBarOverFlowed) {
0705         const int scrollButtonWidth = m_mainTabBarWidget->scrollButtonsWidth();
0706 
0707         // Left scroll button
0708         opt.rect.setX(m_mainTabBarWidget->x());
0709         opt.rect.setWidth(scrollButtonWidth);
0710         style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
0711 
0712         // Right scroll button
0713         opt.rect.setX(m_mainTabBarWidget->x() + m_mainTabBarWidget->width() - scrollButtonWidth);
0714         opt.rect.setWidth(scrollButtonWidth);
0715         style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
0716     }
0717 
0718     // Draw base even when main tabbar is empty
0719     if (normalTabsCount() == 0) {
0720         opt.rect.setX(m_mainTabBarWidget->x());
0721         opt.rect.setWidth(m_mainTabBarWidget->width());
0722         style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
0723     }
0724 #endif
0725 }
0726 
0727 int ComboTabBar::comboTabBarPixelMetric(ComboTabBar::SizeType sizeType) const
0728 {
0729     switch (sizeType) {
0730     case ExtraReservedWidth:
0731         return 0;
0732 
0733     case NormalTabMaximumWidth:
0734         return 150;
0735 
0736     case ActiveTabMinimumWidth:
0737     case NormalTabMinimumWidth:
0738     case OverflowedTabWidth:
0739         return 100;
0740 
0741     case PinnedTabWidth:
0742         return 30;
0743 
0744     default:
0745         break;
0746     }
0747 
0748     return -1;
0749 }
0750 
0751 QTabBar::ButtonPosition ComboTabBar::iconButtonPosition() const
0752 {
0753     return (closeButtonPosition() == QTabBar::RightSide ? QTabBar::LeftSide : QTabBar::RightSide);
0754 }
0755 
0756 QTabBar::ButtonPosition ComboTabBar::closeButtonPosition() const
0757 {
0758     return (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, m_mainTabBar);
0759 }
0760 
0761 QSize ComboTabBar::iconButtonSize() const
0762 {
0763     QSize s = closeButtonSize();
0764     s.setWidth(qMax(16, s.width()));
0765     s.setHeight(qMax(16, s.height()));
0766     return s;
0767 }
0768 
0769 QSize ComboTabBar::closeButtonSize() const
0770 {
0771     int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
0772     int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
0773     return QSize(width, height);
0774 }
0775 
0776 bool ComboTabBar::validIndex(int index) const
0777 {
0778     return (index >= 0 && index < count());
0779 }
0780 
0781 void ComboTabBar::setCurrentNextEnabledIndex(int offset)
0782 {
0783     for (int index = currentIndex() + offset; validIndex(index); index += offset) {
0784         if (isTabEnabled(index)) {
0785             setCurrentIndex(index);
0786             break;
0787         }
0788     }
0789 }
0790 
0791 bool ComboTabBar::usesScrollButtons() const
0792 {
0793     return m_mainTabBarWidget->usesScrollButtons();
0794 }
0795 
0796 void ComboTabBar::setUsesScrollButtons(bool useButtons)
0797 {
0798     m_mainTabBarWidget->setUsesScrollButtons(useButtons);
0799 }
0800 
0801 void ComboTabBar::showDropIndicator(int index, DropIndicatorPosition position)
0802 {
0803     clearDropIndicator();
0804     localTabBar(index)->showDropIndicator(toLocalIndex(index), position);
0805 }
0806 
0807 void ComboTabBar::clearDropIndicator()
0808 {
0809     m_mainTabBar->clearDropIndicator();
0810     m_pinnedTabBar->clearDropIndicator();
0811 }
0812 
0813 bool ComboTabBar::isDragInProgress() const
0814 {
0815     return m_mainTabBar->isDragInProgress() || m_pinnedTabBar->isDragInProgress();
0816 }
0817 
0818 bool ComboTabBar::isScrollInProgress() const
0819 {
0820     return m_mainTabBarWidget->scrollBar()->isScrolling() || m_pinnedTabBarWidget->scrollBar()->isScrolling();
0821 }
0822 
0823 bool ComboTabBar::isMainBarOverflowed() const
0824 {
0825     return m_mainBarOverFlowed;
0826 }
0827 
0828 int ComboTabBar::cornerWidth(Qt::Corner corner) const
0829 {
0830     if (corner == Qt::TopLeftCorner) {
0831         return m_leftContainer->width();
0832     }
0833     else if (corner == Qt::TopRightCorner) {
0834         return m_rightContainer->width();
0835     }
0836 
0837     qFatal("ComboTabBar::cornerWidth Only TopLeft and TopRight corners are implemented!");
0838     return -1;
0839 }
0840 
0841 void ComboTabBar::addCornerWidget(QWidget* widget, Qt::Corner corner)
0842 {
0843     if (corner == Qt::TopLeftCorner) {
0844         m_leftLayout->addWidget(widget);
0845     }
0846     else if (corner == Qt::TopRightCorner) {
0847         m_rightLayout->addWidget(widget);
0848     }
0849     else {
0850         qFatal("ComboTabBar::addCornerWidget Only TopLeft and TopRight corners are implemented!");
0851     }
0852 }
0853 
0854 // static
0855 int ComboTabBar::slideAnimationDuration()
0856 {
0857     // taken from qtabbar_p.h
0858     return 250;
0859 }
0860 
0861 void ComboTabBar::ensureVisible(int index, int xmargin)
0862 {
0863     if (index == -1) {
0864         index = currentIndex();
0865     }
0866 
0867     if (index < pinnedTabsCount()) {
0868         if (xmargin == -1) {
0869             xmargin = qMax(20, comboTabBarPixelMetric(PinnedTabWidth));
0870         }
0871         m_pinnedTabBarWidget->ensureVisible(index, xmargin);
0872     }
0873     else {
0874         if (xmargin == -1) {
0875             xmargin = comboTabBarPixelMetric(OverflowedTabWidth);
0876         }
0877         index -= pinnedTabsCount();
0878         m_mainTabBarWidget->ensureVisible(index, xmargin);
0879     }
0880 }
0881 
0882 QSize ComboTabBar::tabSizeHint(int index, bool fast) const
0883 {
0884     Q_UNUSED(fast)
0885 
0886     return localTabBar(index)->baseClassTabSizeHint(toLocalIndex(index));
0887 }
0888 
0889 void ComboTabBar::tabInserted(int index)
0890 {
0891     Q_UNUSED(index)
0892 }
0893 
0894 void ComboTabBar::tabRemoved(int index)
0895 {
0896     Q_UNUSED(index)
0897 }
0898 
0899 TabBarHelper* ComboTabBar::mainTabBar() const
0900 {
0901     return m_mainTabBar;
0902 }
0903 
0904 TabBarHelper* ComboTabBar::localTabBar(int index) const
0905 {
0906     if (index < 0 || index >= pinnedTabsCount()) {
0907         return m_mainTabBar;
0908     }
0909     else {
0910         return m_pinnedTabBar;
0911     }
0912 }
0913 
0914 int ComboTabBar::toLocalIndex(int globalIndex) const
0915 {
0916     if (globalIndex < 0) {
0917         return -1;
0918     }
0919 
0920     if (globalIndex >= pinnedTabsCount()) {
0921         return globalIndex - pinnedTabsCount();
0922     }
0923     else {
0924         return globalIndex;
0925     }
0926 }
0927 
0928 QRect ComboTabBar::mapFromLocalTabRect(const QRect &rect, QWidget *tabBar) const
0929 {
0930     if (!rect.isValid()) {
0931         return rect;
0932     }
0933 
0934     QRect r = rect;
0935 
0936     if (tabBar == m_mainTabBar) {
0937         r.moveLeft(r.x() + mapFromGlobal(m_mainTabBar->mapToGlobal(QPoint(0, 0))).x());
0938         QRect widgetRect = m_mainTabBarWidget->scrollArea()->viewport()->rect();
0939         widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_mainTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
0940         r = r.intersected(widgetRect);
0941     } else {
0942         r.moveLeft(r.x() + mapFromGlobal(m_pinnedTabBar->mapToGlobal(QPoint(0, 0))).x());
0943         QRect widgetRect = m_pinnedTabBarWidget->scrollArea()->viewport()->rect();
0944         widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_pinnedTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
0945         r = r.intersected(widgetRect);
0946     }
0947 
0948     return r;
0949 }
0950 
0951 void ComboTabBar::updatePinnedTabBarVisibility()
0952 {
0953     m_pinnedTabBarWidget->setVisible(pinnedTabsCount() > 0);
0954 }
0955 
0956 void ComboTabBar::setMinimumWidths()
0957 {
0958     if (!isVisible() || comboTabBarPixelMetric(PinnedTabWidth) < 0) {
0959         return;
0960     }
0961 
0962     const int tabBarsSpacing = 3; // To distinguish tabbars
0963     int pinnedTabBarWidth = pinnedTabsCount() * comboTabBarPixelMetric(PinnedTabWidth);
0964     m_pinnedTabBar->setMinimumWidth(pinnedTabBarWidth);
0965     m_pinnedTabBarWidget->setFixedWidth(pinnedTabBarWidth + tabBarsSpacing);
0966 
0967     // Width that is needed by main tabbar
0968     int mainTabBarWidth = comboTabBarPixelMetric(NormalTabMinimumWidth) * (m_mainTabBar->count() - 1) +
0969                           comboTabBarPixelMetric(ActiveTabMinimumWidth) +
0970                           comboTabBarPixelMetric(ExtraReservedWidth);
0971 
0972     // This is the full width that would be needed for the tabbar (including pinned tabbar and corner widgets)
0973     int realTabBarWidth = mainTabBarWidth + m_pinnedTabBarWidget->width() +
0974                           cornerWidth(Qt::TopLeftCorner) +
0975                           cornerWidth(Qt::TopRightCorner);
0976 
0977     // Does it fit in our widget?
0978     if (realTabBarWidth <= width()) {
0979         if (m_mainBarOverFlowed) {
0980             m_mainBarOverFlowed = false;
0981             QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged);
0982         }
0983 
0984         m_mainTabBar->useFastTabSizeHint(false);
0985         m_mainTabBar->setMinimumWidth(mainTabBarWidth);
0986     }
0987     else {
0988         if (!m_mainBarOverFlowed) {
0989             m_mainBarOverFlowed = true;
0990             QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged);
0991         }
0992 
0993         // All tabs have now same width, we can use fast tabSizeHint
0994         m_mainTabBar->useFastTabSizeHint(true);
0995         m_mainTabBar->setMinimumWidth(m_mainTabBar->count() * comboTabBarPixelMetric(OverflowedTabWidth));
0996     }
0997 }
0998 
0999 
1000 TabBarHelper::TabBarHelper(bool isPinnedTabBar, ComboTabBar* comboTabBar)
1001     : QTabBar(comboTabBar)
1002     , m_comboTabBar(comboTabBar)
1003     , m_scrollArea(nullptr)
1004     , m_pressedIndex(-1)
1005     , m_dragInProgress(false)
1006     , m_activeTabBar(false)
1007     , m_isPinnedTabBar(isPinnedTabBar)
1008     , m_useFastTabSizeHint(false)
1009 {
1010 }
1011 
1012 int TabBarHelper::tabPadding() const
1013 {
1014     return m_tabPadding;
1015 }
1016 
1017 void TabBarHelper::setTabPadding(int padding)
1018 {
1019     m_tabPadding = padding;
1020 }
1021 
1022 QColor TabBarHelper::baseColor() const
1023 {
1024     return m_baseColor;
1025 }
1026 
1027 void TabBarHelper::setBaseColor(const QColor &color)
1028 {
1029     m_baseColor = color;
1030 }
1031 
1032 void TabBarHelper::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
1033 {
1034     QTabBar::setTabButton(index, position, widget);
1035 }
1036 
1037 QSize TabBarHelper::tabSizeHint(int index) const
1038 {
1039     if (this == m_comboTabBar->mainTabBar()) {
1040         index += m_comboTabBar->pinnedTabsCount();
1041     }
1042     return m_comboTabBar->tabSizeHint(index, m_useFastTabSizeHint);
1043 }
1044 
1045 QSize TabBarHelper::baseClassTabSizeHint(int index) const
1046 {
1047     return QTabBar::tabSizeHint(index);
1048 }
1049 
1050 QRect TabBarHelper::draggedTabRect() const
1051 {
1052     if (!m_dragInProgress) {
1053         return {};
1054     }
1055 
1056     QStyleOptionTab tab;
1057     initStyleOption(&tab, m_pressedIndex);
1058 
1059     const int tabDragOffset = dragOffset(&tab, m_pressedIndex);
1060     if (tabDragOffset != 0) {
1061         tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
1062     }
1063     return tab.rect;
1064 }
1065 
1066 QPixmap TabBarHelper::tabPixmap(int index) const
1067 {
1068     QStyleOptionTab tab;
1069     initStyleOption(&tab, index);
1070 
1071     tab.state &= ~QStyle::State_MouseOver;
1072     tab.position = QStyleOptionTab::OnlyOneTab;
1073     tab.leftButtonSize = QSize();
1074     tab.rightButtonSize = QSize();
1075 
1076     QWidget *iconButton = tabButton(index, m_comboTabBar->iconButtonPosition());
1077     QWidget *closeButton = tabButton(index, m_comboTabBar->closeButtonPosition());
1078 
1079     if (iconButton) {
1080         const QPixmap pix = iconButton->grab();
1081         if (!pix.isNull()) {
1082             tab.icon = pix;
1083             tab.iconSize = pix.size() / pix.devicePixelRatioF();
1084         }
1085     }
1086 
1087     if (closeButton) {
1088         const int width = tab.fontMetrics.horizontalAdvance(tab.text) + closeButton->width();
1089         tab.text = tab.fontMetrics.elidedText(tabText(index), Qt::ElideRight, width);
1090     }
1091 
1092     QPixmap out(tab.rect.size() * devicePixelRatioF());
1093     out.setDevicePixelRatio(devicePixelRatioF());
1094     out.fill(Qt::transparent);
1095     tab.rect = QRect(QPoint(0, 0), tab.rect.size());
1096 
1097     QPainter p(&out);
1098     style()->drawControl(QStyle::CE_TabBarTab, &tab, &p, this);
1099     p.end();
1100 
1101     return out;
1102 }
1103 
1104 bool TabBarHelper::isActiveTabBar()
1105 {
1106     return m_activeTabBar;
1107 }
1108 
1109 void TabBarHelper::setActiveTabBar(bool activate)
1110 {
1111     if (m_activeTabBar != activate) {
1112         m_activeTabBar = activate;
1113 
1114         // If the last tab in a tabbar is closed, the selection jumps to the other
1115         // tabbar. The stacked widget automatically selects the next tab, which is
1116         // either the last tab in pinned tabbar or the first one in main tabbar.
1117 
1118         if (!m_activeTabBar) {
1119             m_comboTabBar->m_blockCurrentChangedSignal = true;
1120             setCurrentIndex(m_isPinnedTabBar ? count() - 1 : 0);
1121             m_comboTabBar->m_blockCurrentChangedSignal = false;
1122         }
1123 
1124         update();
1125     }
1126 }
1127 
1128 void TabBarHelper::removeTab(int index)
1129 {
1130     // Removing tab in inactive tabbar will change current index and thus
1131     // changing active tabbar, which is really not wanted.
1132     // Also removing tab will cause a duplicate call to ComboTabBar::slotCurrentChanged()
1133     m_comboTabBar->m_blockCurrentChangedSignal = true;
1134 
1135     QTabBar::removeTab(index);
1136 
1137     m_comboTabBar->m_blockCurrentChangedSignal = false;
1138 }
1139 
1140 void TabBarHelper::setScrollArea(QScrollArea* scrollArea)
1141 {
1142     m_scrollArea = scrollArea;
1143 }
1144 
1145 void TabBarHelper::useFastTabSizeHint(bool enabled)
1146 {
1147     m_useFastTabSizeHint = enabled;
1148 }
1149 
1150 void TabBarHelper::showDropIndicator(int index, ComboTabBar::DropIndicatorPosition position)
1151 {
1152     m_dropIndicatorIndex = index;
1153     m_dropIndicatorPosition = position;
1154     update();
1155 }
1156 
1157 void TabBarHelper::clearDropIndicator()
1158 {
1159     m_dropIndicatorIndex = -1;
1160     update();
1161 }
1162 
1163 bool TabBarHelper::isDisplayedOnViewPort(int globalLeft, int globalRight)
1164 {
1165     bool isVisible = true;
1166 
1167     if (m_scrollArea) {
1168         if (globalRight < m_scrollArea->viewport()->mapToGlobal(QPoint(0, 0)).x() ||
1169             globalLeft > m_scrollArea->viewport()->mapToGlobal(m_scrollArea->viewport()->rect().topRight()).x()
1170            ) {
1171             isVisible = false;
1172         }
1173     }
1174 
1175     return isVisible;
1176 }
1177 
1178 bool TabBarHelper::isDragInProgress() const
1179 {
1180     return m_dragInProgress;
1181 }
1182 
1183 void TabBarHelper::setCurrentIndex(int index)
1184 {
1185     if (index == currentIndex() && !m_activeTabBar) {
1186         Q_EMIT currentChanged(currentIndex());
1187     }
1188 
1189     QTabBar::setCurrentIndex(index);
1190 }
1191 
1192 bool TabBarHelper::event(QEvent* ev)
1193 {
1194     switch (ev->type()) {
1195     case QEvent::ToolTip:
1196         ev->ignore();
1197         return false;
1198 
1199     default:
1200         break;
1201     }
1202 
1203     QTabBar::event(ev);
1204     ev->ignore();
1205     return false;
1206 }
1207 
1208 // Hack to get dragOffset from QTabBar internals
1209 int TabBarHelper::dragOffset(QStyleOptionTab *option, int tabIndex) const
1210 {
1211     QRect rect;
1212     QWidget *button = tabButton(tabIndex, QTabBar::LeftSide);
1213     if (button) {
1214         rect = style()->subElementRect(QStyle::SE_TabBarTabLeftButton, option, this);
1215     }
1216     if (!rect.isValid()) {
1217         button = tabButton(tabIndex, QTabBar::RightSide);
1218         rect = style()->subElementRect(QStyle::SE_TabBarTabRightButton, option, this);
1219     }
1220     if (!button || !rect.isValid()) {
1221         return 0;
1222     }
1223     return button->pos().x() - rect.topLeft().x();
1224 }
1225 
1226 // Taken from qtabbar.cpp
1227 void TabBarHelper::initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar* tabbar, QSize size)
1228 {
1229     QStyleOptionTab tabOverlap;
1230     tabOverlap.shape = tabbar->shape();
1231     int overlap = tabbar->style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, tabbar);
1232     QWidget* theParent = tabbar->parentWidget();
1233     optTabBase->initFrom(tabbar);
1234     optTabBase->shape = tabbar->shape();
1235     optTabBase->documentMode = tabbar->documentMode();
1236     if (theParent && overlap > 0) {
1237         QRect rect;
1238         switch (tabOverlap.shape) {
1239         case QTabBar::RoundedNorth:
1240         case QTabBar::TriangularNorth:
1241             rect.setRect(0, size.height() - overlap, size.width(), overlap);
1242             break;
1243         case QTabBar::RoundedSouth:
1244         case QTabBar::TriangularSouth:
1245             rect.setRect(0, 0, size.width(), overlap);
1246             break;
1247         case QTabBar::RoundedEast:
1248         case QTabBar::TriangularEast:
1249             rect.setRect(0, 0, overlap, size.height());
1250             break;
1251         case QTabBar::RoundedWest:
1252         case QTabBar::TriangularWest:
1253             rect.setRect(size.width() - overlap, 0, overlap, size.height());
1254             break;
1255         }
1256         optTabBase->rect = rect;
1257     }
1258 }
1259 
1260 // Adapted from qtabbar.cpp
1261 // Note: doesn't support vertical tabs
1262 void TabBarHelper::paintEvent(QPaintEvent *)
1263 {
1264     QStyleOptionTabBarBase optTabBase;
1265     initStyleBaseOption(&optTabBase, this, size());
1266 
1267     QStylePainter p(this);
1268     int selected = currentIndex();
1269 
1270     for (int i = 0; i < count(); ++i) {
1271         optTabBase.tabBarRect |= tabRect(i);
1272     }
1273 
1274     if (m_activeTabBar) {
1275         optTabBase.selectedTabRect = tabRect(selected);
1276     }
1277 
1278     if (drawBase()) {
1279         p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
1280     }
1281 
1282     const QPoint cursorPos = QCursor::pos();
1283     int indexUnderMouse = isDisplayedOnViewPort(cursorPos.x(), cursorPos.x()) ? tabAt(mapFromGlobal(cursorPos)) : -1;
1284 
1285     for (int i = 0; i < count(); ++i) {
1286         if (i == selected) {
1287             continue;
1288         }
1289 
1290         QStyleOptionTab tab;
1291         initStyleOption(&tab, i);
1292 
1293         const int tabDragOffset = dragOffset(&tab, i);
1294         if (tabDragOffset != 0) {
1295             tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
1296         }
1297 
1298         // Don't bother drawing a tab if the entire tab is outside of the visible tab bar.
1299         if (!isDisplayedOnViewPort(mapToGlobal(tab.rect.topLeft()).x(), mapToGlobal(tab.rect.topRight()).x())) {
1300             continue;
1301         }
1302 
1303         if (!m_activeTabBar) {
1304             tab.selectedPosition = QStyleOptionTab::NotAdjacent;
1305         }
1306 
1307         if (!(tab.state & QStyle::State_Enabled)) {
1308             tab.palette.setCurrentColorGroup(QPalette::Disabled);
1309         }
1310 
1311         // Update mouseover state when scrolling
1312         if (!m_dragInProgress && i == indexUnderMouse) {
1313             tab.state |= QStyle::State_MouseOver;
1314         } else {
1315             tab.state &= ~QStyle::State_MouseOver;
1316         }
1317 
1318         p.drawControl(QStyle::CE_TabBarTab, tab);
1319     }
1320 
1321     // Draw the selected tab last to get it "on top"
1322     if (selected >= 0) {
1323         QStyleOptionTab tab;
1324         initStyleOption(&tab, selected);
1325 
1326         const int tabDragOffset = dragOffset(&tab, selected);
1327         if (tabDragOffset != 0) {
1328             tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
1329         }
1330 
1331         // Update mouseover state when scrolling
1332         if (selected == indexUnderMouse) {
1333             tab.state |= QStyle::State_MouseOver;
1334         } else {
1335             tab.state &= ~QStyle::State_MouseOver;
1336         }
1337 
1338         if (!m_activeTabBar) {
1339             // If this is inactive tab, we still need to draw selected tab outside the tabbar
1340             // Some themes (eg. Oxygen) draws line under tabs with selected tab
1341             // Let's just move it outside rect(), it appears to work
1342             QStyleOptionTab tb = tab;
1343             tb.rect.moveRight((rect().x() + rect().width()) * 2);
1344             p.drawControl(QStyle::CE_TabBarTab, tb);
1345 
1346             // Draw the tab without selected state
1347             tab.state = tab.state & ~QStyle::State_Selected;
1348         }
1349 
1350         if (!m_movingTab || !m_movingTab->isVisible()) {
1351             p.drawControl(QStyle::CE_TabBarTab, tab);
1352         } else {
1353             int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
1354             m_movingTab->setGeometry(tab.rect.adjusted(-taboverlap, 0, taboverlap, 0));
1355 
1356             QRect grabRect = tabRect(selected);
1357             grabRect.adjust(-taboverlap, 0, taboverlap, 0);
1358             QPixmap grabImage(grabRect.size() * devicePixelRatioF());
1359             grabImage.setDevicePixelRatio(devicePixelRatioF());
1360             grabImage.fill(Qt::transparent);
1361             QStylePainter p(&grabImage, this);
1362             if (tabDragOffset != 0) {
1363                 tab.position = QStyleOptionTab::OnlyOneTab;
1364             }
1365             tab.rect.moveTopLeft(QPoint(taboverlap, 0));
1366             p.drawControl(QStyle::CE_TabBarTab, tab);
1367             m_movingTab->m_pixmap = grabImage;
1368             m_movingTab->update();
1369         }
1370     }
1371 
1372     // Draw drop indicator
1373     if (m_dropIndicatorIndex != -1) {
1374         const QRect tr = tabRect(m_dropIndicatorIndex);
1375         QRect r;
1376         if (m_dropIndicatorPosition == ComboTabBar::BeforeTab) {
1377             r = QRect(qMax(0, tr.left() - 1), tr.top(), 3, tr.height());
1378         } else {
1379             const int rightOffset = m_dropIndicatorIndex == count() - 1 ? -2 : 0;
1380             r = QRect(tr.right() + rightOffset, tr.top(), 3, tr.height());
1381         }
1382         QzTools::paintDropIndicator(this, r);
1383     }
1384 }
1385 
1386 void TabBarHelper::mousePressEvent(QMouseEvent* event)
1387 {
1388     event->ignore();
1389     if (event->buttons() == Qt::LeftButton) {
1390         m_pressedIndex = tabAt(event->position().toPoint());
1391         if (m_pressedIndex != -1) {
1392             m_dragStartPosition = event->position().toPoint();
1393             // virtualize selecting tab by click
1394             if (m_pressedIndex == currentIndex() && !m_activeTabBar) {
1395                 Q_EMIT currentChanged(currentIndex());
1396             }
1397         }
1398     }
1399 
1400     QTabBar::mousePressEvent(event);
1401 }
1402 
1403 void TabBarHelper::mouseMoveEvent(QMouseEvent *event)
1404 {
1405     if (!m_dragInProgress && m_pressedIndex != -1) {
1406         if ((event->position().toPoint() - m_dragStartPosition).manhattanLength() > QApplication::startDragDistance()) {
1407             m_dragInProgress = true;
1408         }
1409     }
1410 
1411     QTabBar::mouseMoveEvent(event);
1412 
1413     // Hack to find QMovableTabWidget
1414     if (m_dragInProgress && !m_movingTab) {
1415         const auto objects = children();
1416         const int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
1417         QRect grabRect = tabRect(currentIndex());
1418         grabRect.adjust(-taboverlap, 0, taboverlap, 0);
1419         for (QObject *object : objects) {
1420             QWidget *widget = qobject_cast<QWidget*>(object);
1421             if (widget && widget->geometry() == grabRect) {
1422                 m_movingTab = static_cast<QMovableTabWidget*>(widget);
1423                 break;
1424             }
1425         }
1426     }
1427 
1428     // Don't allow to move tabs outside of tabbar
1429     if (m_dragInProgress && m_movingTab) {
1430         // FIXME: This doesn't work at all with RTL...
1431         if (isRightToLeft()) {
1432             return;
1433         }
1434         QRect r = tabRect(m_pressedIndex);
1435         r.moveLeft(r.x() + (event->position().toPoint().x() - m_dragStartPosition.x()));
1436         bool sendEvent = false;
1437         int diff = r.topRight().x() - tabRect(count() - 1).topRight().x();
1438         if (diff > 0) {
1439             sendEvent = true;
1440         } else {
1441             diff = r.topLeft().x() - tabRect(0).topLeft().x();
1442             if (diff < 0) {
1443                 sendEvent = true;
1444             }
1445         }
1446         if (sendEvent) {
1447             QPoint pos = event->position().toPoint();
1448             pos.setX(pos.x() - diff);
1449             QMouseEvent ev(event->type(), pos, event->globalPosition(), event->button(), event->buttons(), event->modifiers());
1450             QTabBar::mouseMoveEvent(&ev);
1451         }
1452     }
1453 }
1454 
1455 void TabBarHelper::mouseReleaseEvent(QMouseEvent* event)
1456 {
1457     event->ignore();
1458 
1459     if (event->button() == Qt::LeftButton) {
1460         m_pressedIndex = -1;
1461         m_dragInProgress = false;
1462         m_dragStartPosition = QPoint();
1463     }
1464 
1465     QTabBar::mouseReleaseEvent(event);
1466 
1467     update();
1468 }
1469 
1470 void TabBarHelper::initStyleOption(QStyleOptionTab* option, int tabIndex) const
1471 {
1472     QTabBar::initStyleOption(option, tabIndex);
1473 
1474     // Workaround zero padding when tabs are styled using style sheets
1475     if (m_tabPadding) {
1476         const QRect textRect = style()->subElementRect(QStyle::SE_TabBarTabText, option, this);
1477         const int width = textRect.width() - 2 * m_tabPadding;
1478         option->text = option->fontMetrics.elidedText(tabText(tabIndex), elideMode(), width, Qt::TextShowMnemonic);
1479     }
1480 
1481     // Bespin doesn't highlight current tab when there is only one tab in tabbar
1482     static int isBespin = -1;
1483 
1484     if (isBespin == -1)
1485         isBespin = mApp->styleName() == QL1S("bespin");
1486 
1487     if (!isBespin)
1488         return;
1489 
1490     int index = m_isPinnedTabBar ? tabIndex : m_comboTabBar->pinnedTabsCount() + tabIndex;
1491 
1492     if (m_comboTabBar->count() > 1) {
1493         if (index == 0)
1494             option->position = QStyleOptionTab::Beginning;
1495         else if (index == m_comboTabBar->count() - 1)
1496             option->position = QStyleOptionTab::End;
1497         else
1498             option->position = QStyleOptionTab::Middle;
1499     }
1500     else {
1501         option->position = QStyleOptionTab::OnlyOneTab;
1502     }
1503 }
1504 
1505 
1506 TabScrollBar::TabScrollBar(QWidget* parent)
1507     : QScrollBar(Qt::Horizontal, parent)
1508 {
1509     m_animation = new QPropertyAnimation(this, "value", this);
1510 }
1511 
1512 TabScrollBar::~TabScrollBar()
1513 = default;
1514 
1515 bool TabScrollBar::isScrolling() const
1516 {
1517     return m_animation->state() == QPropertyAnimation::Running;
1518 }
1519 
1520 void TabScrollBar::animateToValue(int to, QEasingCurve::Type type)
1521 {
1522     to = qBound(minimum(), to, maximum());
1523     int length = qAbs(to - value());
1524     int duration = qMin(1500, 200 + length / 2);
1525 
1526     m_animation->stop();
1527     m_animation->setEasingCurve(type);
1528     m_animation->setDuration(duration);
1529     m_animation->setStartValue(value());
1530     m_animation->setEndValue(to);
1531     m_animation->start();
1532 }
1533 
1534 
1535 TabBarScrollWidget::TabBarScrollWidget(QTabBar* tabBar, QWidget* parent)
1536     : QWidget(parent)
1537     , m_tabBar(tabBar)
1538     , m_usesScrollButtons(false)
1539     , m_totalVerticalDeltas(0)
1540 {
1541     m_scrollArea = new QScrollArea(this);
1542     m_scrollArea->setFocusPolicy(Qt::NoFocus);
1543     m_scrollArea->setFrameStyle(QFrame::NoFrame);
1544     m_scrollArea->setWidgetResizable(true);
1545     m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1546     m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1547 
1548     m_scrollBar = new TabScrollBar(m_scrollArea);
1549     m_scrollArea->setHorizontalScrollBar(m_scrollBar);
1550     m_scrollArea->setWidget(m_tabBar);
1551 
1552     m_leftScrollButton = new ToolButton(this);
1553     m_leftScrollButton->setFocusPolicy(Qt::NoFocus);
1554     m_leftScrollButton->setAutoRaise(true);
1555     m_leftScrollButton->setObjectName("tabbar-button-left");
1556     m_leftScrollButton->setAutoRepeat(true);
1557     m_leftScrollButton->setAutoRepeatDelay(200);
1558     m_leftScrollButton->setAutoRepeatInterval(200);
1559     connect(m_leftScrollButton, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart);
1560     connect(m_leftScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::scrollToLeftEdge);
1561     connect(m_leftScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible()));
1562 
1563     m_rightScrollButton = new ToolButton(this);
1564     m_rightScrollButton->setFocusPolicy(Qt::NoFocus);
1565     m_rightScrollButton->setAutoRaise(true);
1566     m_rightScrollButton->setObjectName("tabbar-button-right");
1567     m_rightScrollButton->setAutoRepeat(true);
1568     m_rightScrollButton->setAutoRepeatDelay(200);
1569     m_rightScrollButton->setAutoRepeatInterval(200);
1570     connect(m_rightScrollButton, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart);
1571     connect(m_rightScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::scrollToRightEdge);
1572     connect(m_rightScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible()));
1573 
1574     auto* hLayout = new QHBoxLayout;
1575     hLayout->setSpacing(0);
1576     hLayout->setContentsMargins(0, 0, 0, 0);
1577     hLayout->addWidget(m_leftScrollButton);
1578     hLayout->addWidget(m_scrollArea);
1579     hLayout->addWidget(m_rightScrollButton);
1580     setLayout(hLayout);
1581 
1582     m_scrollArea->viewport()->setAutoFillBackground(false);
1583     connect(m_scrollBar, &QAbstractSlider::valueChanged, this, &TabBarScrollWidget::updateScrollButtonsState);
1584 
1585     updateScrollButtonsState();
1586     overFlowChanged(false);
1587 }
1588 
1589 QTabBar* TabBarScrollWidget::tabBar()
1590 {
1591     return m_tabBar;
1592 }
1593 
1594 QScrollArea* TabBarScrollWidget::scrollArea()
1595 {
1596     return m_scrollArea;
1597 }
1598 
1599 TabScrollBar* TabBarScrollWidget::scrollBar()
1600 {
1601     return m_scrollBar;
1602 }
1603 
1604 void TabBarScrollWidget::ensureVisible(int index, int xmargin)
1605 {
1606     if (index == -1) {
1607         index = m_tabBar->currentIndex();
1608     }
1609 
1610     if (index < 0 || index >= m_tabBar->count()) {
1611         return;
1612     }
1613     xmargin = qMin(xmargin, m_scrollArea->viewport()->width() / 2);
1614 
1615     // Qt Bug? the following lines were taken from QScrollArea::ensureVisible() and
1616     // then were fixed. The original version caculates wrong values in RTL layouts.
1617     const QRect logicalTabRect = QStyle::visualRect(m_tabBar->layoutDirection(), m_tabBar->rect(), m_tabBar->tabRect(index));
1618     int logicalX = QStyle::visualPos(Qt::LeftToRight, m_scrollArea->viewport()->rect(), logicalTabRect.center()).x();
1619 
1620     if (logicalX - xmargin < m_scrollBar->value()) {
1621         m_scrollBar->animateToValue(qMax(0, logicalX - xmargin));
1622     }
1623     else if (logicalX > m_scrollBar->value() + m_scrollArea->viewport()->width() - xmargin) {
1624         m_scrollBar->animateToValue(qMin(logicalX - m_scrollArea->viewport()->width() + xmargin,
1625                                          m_scrollBar->maximum()));
1626     }
1627 }
1628 
1629 void TabBarScrollWidget::scrollToLeft(int n, QEasingCurve::Type type)
1630 {
1631     n = qMax(1, n);
1632     m_scrollBar->animateToValue(m_scrollBar->value() - n * m_scrollBar->singleStep(), type);
1633 }
1634 
1635 void TabBarScrollWidget::scrollToRight(int n, QEasingCurve::Type type)
1636 {
1637     n = qMax(1, n);
1638     m_scrollBar->animateToValue(m_scrollBar->value() + n * m_scrollBar->singleStep(), type);
1639 }
1640 
1641 void TabBarScrollWidget::scrollToLeftEdge()
1642 {
1643     m_scrollBar->animateToValue(m_scrollBar->minimum());
1644 }
1645 
1646 void TabBarScrollWidget::scrollToRightEdge()
1647 {
1648     m_scrollBar->animateToValue(m_scrollBar->maximum());
1649 }
1650 
1651 void TabBarScrollWidget::setUpLayout()
1652 {
1653     const int height = m_tabBar->height();
1654 
1655     setFixedHeight(height);
1656 }
1657 
1658 void TabBarScrollWidget::updateScrollButtonsState()
1659 {
1660     m_leftScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->minimum());
1661     m_rightScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->maximum());
1662 }
1663 
1664 void TabBarScrollWidget::overFlowChanged(bool overflowed)
1665 {
1666     bool showScrollButtons = overflowed && m_usesScrollButtons;
1667 
1668     m_leftScrollButton->setVisible(showScrollButtons);
1669     m_rightScrollButton->setVisible(showScrollButtons);
1670 }
1671 
1672 void TabBarScrollWidget::scrollStart()
1673 {
1674     bool ctrlModifier = QApplication::keyboardModifiers() & Qt::ControlModifier;
1675 
1676     if (sender() == m_leftScrollButton) {
1677         if (ctrlModifier) {
1678             scrollToLeftEdge();
1679         }
1680         else {
1681             scrollToLeft(5, QEasingCurve::Linear);
1682         }
1683     }
1684     else if (sender() == m_rightScrollButton) {
1685         if (ctrlModifier) {
1686             scrollToRightEdge();
1687         }
1688         else {
1689             scrollToRight(5, QEasingCurve::Linear);
1690         }
1691     }
1692 }
1693 
1694 void TabBarScrollWidget::scrollByWheel(QWheelEvent* event)
1695 {
1696     event->accept();
1697 
1698     // Process horizontal wheel scrolling first
1699     // Slower scrolling for horizontal wheel scrolling
1700     if (event->angleDelta().x() > 0) {
1701         scrollToLeft();
1702     }
1703     else if (event->angleDelta().x() < 0) {
1704         scrollToRight();
1705     }
1706 
1707     auto verticalDelta = event->angleDelta().y();
1708     if (verticalDelta == 0) {
1709         return;
1710     }
1711 
1712     // Check if vertical direction has changed from last time
1713     if (m_totalVerticalDeltas * verticalDelta < 0) {
1714         m_totalVerticalDeltas = 0;
1715     }
1716 
1717     m_totalVerticalDeltas += verticalDelta;
1718 
1719     // Faster scrolling with control modifier
1720     if (event->modifiers() == Qt::ControlModifier) {
1721         if (verticalDelta > 0) {
1722             scrollToLeft(10);
1723         }
1724         else if (verticalDelta < 0) {
1725             scrollToRight(10);
1726         }
1727         return;
1728     }
1729 
1730     // Fast scrolling with just wheel scroll
1731     int factor = qMax(qRound(m_scrollBar->pageStep() / 1.5), m_scrollBar->singleStep());
1732     if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
1733         factor = m_scrollBar->pageStep();
1734     }
1735 
1736     int offset = (m_totalVerticalDeltas / 120) * factor;
1737     if (offset != 0) {
1738         if (isRightToLeft()) {
1739             m_scrollBar->animateToValue(m_scrollBar->value() + offset);
1740         }
1741         else {
1742             m_scrollBar->animateToValue(m_scrollBar->value() - offset);
1743         }
1744 
1745         m_totalVerticalDeltas -= (offset / factor) * 120;
1746     }
1747 }
1748 
1749 int TabBarScrollWidget::scrollButtonsWidth() const
1750 {
1751     // Assumes both buttons have the same width
1752     return m_leftScrollButton->width();
1753 }
1754 
1755 bool TabBarScrollWidget::usesScrollButtons() const
1756 {
1757     return m_usesScrollButtons;
1758 }
1759 
1760 void TabBarScrollWidget::setUsesScrollButtons(bool useButtons)
1761 {
1762     if (useButtons != m_usesScrollButtons) {
1763         m_usesScrollButtons = useButtons;
1764         updateScrollButtonsState();
1765         m_tabBar->setElideMode(m_tabBar->elideMode());
1766     }
1767 }
1768 
1769 bool TabBarScrollWidget::isOverflowed() const
1770 {
1771     return m_tabBar->count() > 0 && m_scrollBar->minimum() != m_scrollBar->maximum();
1772 }
1773 
1774 int TabBarScrollWidget::tabAt(const QPoint &pos) const
1775 {
1776     if (m_leftScrollButton->isVisible() && (m_leftScrollButton->rect().contains(pos) ||
1777                                             m_rightScrollButton->rect().contains(pos))) {
1778         return -1;
1779     }
1780 
1781     return m_tabBar->tabAt(m_tabBar->mapFromGlobal(mapToGlobal(pos)));
1782 }
1783 
1784 void TabBarScrollWidget::mouseMoveEvent(QMouseEvent* event)
1785 {
1786     event->ignore();
1787 }
1788 
1789 void TabBarScrollWidget::resizeEvent(QResizeEvent* event)
1790 {
1791     QWidget::resizeEvent(event);
1792 
1793     updateScrollButtonsState();
1794 }
1795 
1796 
1797 CloseButton::CloseButton(QWidget* parent)
1798     : QAbstractButton(parent)
1799 {
1800     setObjectName("combotabbar_tabs_close_button");
1801     setFocusPolicy(Qt::NoFocus);
1802     setCursor(Qt::ArrowCursor);
1803     resize(sizeHint());
1804 }
1805 
1806 QSize CloseButton::sizeHint() const
1807 {
1808     ensurePolished();
1809     int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
1810     int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
1811     return QSize(width, height);
1812 }
1813 
1814 void CloseButton::enterEvent(QEnterEvent* event)
1815 {
1816     if (isEnabled()) {
1817         update();
1818     }
1819 
1820     QAbstractButton::enterEvent(event);
1821 }
1822 
1823 void CloseButton::leaveEvent(QEvent* event)
1824 {
1825     if (isEnabled()) {
1826         update();
1827     }
1828 
1829     QAbstractButton::leaveEvent(event);
1830 }
1831 
1832 void CloseButton::paintEvent(QPaintEvent*)
1833 {
1834     QPainter p(this);
1835     QStyleOption opt;
1836     opt.initFrom(this);
1837     opt.state |= QStyle::State_AutoRaise;
1838 
1839     // update raised state on scrolling
1840     bool isUnderMouse = rect().contains(mapFromGlobal(QCursor::pos()));
1841 
1842     if (isEnabled() && isUnderMouse && !isChecked() && !isDown()) {
1843         opt.state |= QStyle::State_Raised;
1844     }
1845     if (isChecked()) {
1846         opt.state |= QStyle::State_On;
1847     }
1848     if (isDown()) {
1849         opt.state |= QStyle::State_Sunken;
1850     }
1851 
1852     if (auto* tb = qobject_cast<TabBarHelper*>(parent())) {
1853         int index = tb->currentIndex();
1854         auto closeSide = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tb);
1855         if (tb->tabButton(index, closeSide) == this && tb->isActiveTabBar()) {
1856             opt.state |= QStyle::State_Selected;
1857         }
1858     }
1859 
1860     style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
1861 }