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 }