Falkon Develop
Cross-platform Qt-based web browser
combotabbar.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2013-2014 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
4* Copyright (C) 2014-2018 David Rosca <nowrep@gmail.com>
5*
6* This program is free software: you can redistribute it and/or modify
7* it under the terms of the GNU General Public License as published by
8* the Free Software Foundation, either version 3 of the License, or
9* (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <http://www.gnu.org/licenses/>.
18* ============================================================ */
19#include "combotabbar.h"
20#include "toolbutton.h"
21#include "tabicon.h"
22#include "mainapplication.h"
23#include "proxystyle.h"
24#include "qzsettings.h"
25#include "qztools.h"
26
27#include <QIcon>
28#include <QHBoxLayout>
29#include <QStylePainter>
30#include <QStyleOptionTab>
31#include <QStyleOptionTabBarBase>
32#include <QPropertyAnimation>
33#include <QScrollArea>
34#include <QTimer>
35#include <QMouseEvent>
36#include <QApplication>
37#include <QToolTip>
38#include <QtGuiVersion>
39
40class QMovableTabWidget : public QWidget
41{
42public:
43 QPixmap m_pixmap;
44};
45
47 : QWidget(parent)
48 , m_mainTabBar(nullptr)
49 , m_pinnedTabBar(nullptr)
50 , m_mainBarOverFlowed(false)
51 , m_lastAppliedOverflow(false)
52 , m_usesScrollButtons(false)
53 , m_blockCurrentChangedSignal(false)
54{
55 QObject::setObjectName(QSL("tabbarwidget"));
56
57 m_mainTabBar = new TabBarHelper(/*isPinnedTabBar*/ false, this);
58 m_pinnedTabBar = new TabBarHelper(/*isPinnedTabBar*/ true, this);
59 m_mainTabBarWidget = new TabBarScrollWidget(m_mainTabBar, this);
60 m_pinnedTabBarWidget = new TabBarScrollWidget(m_pinnedTabBar, this);
61
62 m_mainTabBar->setScrollArea(m_mainTabBarWidget->scrollArea());
63 m_pinnedTabBar->setScrollArea(m_pinnedTabBarWidget->scrollArea());
64
65 connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
66 connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
67 connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
68 connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
69 connect(this, SIGNAL(overFlowChanged(bool)), m_mainTabBarWidget, SLOT(overFlowChanged(bool)));
70
71 m_mainTabBar->setActiveTabBar(true);
72 m_pinnedTabBar->setTabsClosable(false);
73
74 m_leftLayout = new QHBoxLayout;
75 m_leftLayout->setSpacing(0);
76 m_leftLayout->setContentsMargins(0, 0, 0, 0);
77 m_leftContainer = new QWidget(this);
78 m_leftContainer->setLayout(m_leftLayout);
79
80 m_rightLayout = new QHBoxLayout;
81 m_rightLayout->setSpacing(0);
82 m_rightLayout->setContentsMargins(0, 0, 0, 0);
83 m_rightContainer = new QWidget(this);
84 m_rightContainer->setLayout(m_rightLayout);
85
86 m_mainLayout = new QHBoxLayout;
87 m_mainLayout->setSpacing(0);
88 m_mainLayout->setContentsMargins(0, 0, 0, 0);
89 m_mainLayout->addWidget(m_leftContainer);
90 m_mainLayout->addWidget(m_pinnedTabBarWidget);
91 m_mainLayout->addWidget(m_mainTabBarWidget);
92 m_mainLayout->addWidget(m_rightContainer);
93 setLayout(m_mainLayout);
94
95 connect(m_mainTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
96 connect(m_mainTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
97 connect(m_mainTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);
98
99 connect(m_pinnedTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
100 connect(m_pinnedTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
101 connect(m_pinnedTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);
102
103 setAutoFillBackground(false);
104 m_mainTabBar->setAutoFillBackground(false);
105 m_pinnedTabBar->setAutoFillBackground(false);
106
107 m_mainTabBar->installEventFilter(this);
108 m_pinnedTabBar->installEventFilter(this);
109 m_leftContainer->installEventFilter(this);
110 m_rightContainer->installEventFilter(this);
111 m_mainTabBarWidget->installEventFilter(this);
112 m_pinnedTabBarWidget->installEventFilter(this);
113}
114
115int ComboTabBar::addTab(const QString &text)
116{
117 return insertTab(-1, text);
118}
119
120int ComboTabBar::addTab(const QIcon &icon, const QString &text)
121{
122 return insertTab(-1, icon, text);
123}
124
125int ComboTabBar::insertTab(int index, const QString &text)
126{
127 return insertTab(index, QIcon(), text);
128}
129
130int ComboTabBar::insertTab(int index, const QIcon &icon, const QString &text, bool pinned)
131{
132 if (pinned) {
133 index = m_pinnedTabBar->insertTab(index, icon, text);
134 }
135 else {
136 index = m_mainTabBar->insertTab(index - pinnedTabsCount(), icon, text);
137
138 if (tabsClosable()) {
139 QWidget* closeButton = m_mainTabBar->tabButton(index, closeButtonPosition());
140 if ((closeButton && closeButton->objectName() != QLatin1String("combotabbar_tabs_close_button")) || !closeButton) {
141 // insert our close button
143 if (closeButton) {
144 closeButton->deleteLater();
145 }
146 }
147 }
148
149 index += pinnedTabsCount();
150 }
151
152 updatePinnedTabBarVisibility();
153 tabInserted(index);
154 setMinimumWidths();
155
156 return index;
157}
158
160{
161 if (validIndex(index)) {
162 setUpdatesEnabled(false);
163
164 localTabBar(index)->removeTab(toLocalIndex(index));
165 updatePinnedTabBarVisibility();
166 tabRemoved(index);
167 setMinimumWidths();
168
169 setUpdatesEnabled(true);
170 updateTabBars();
171 }
172}
173
174void ComboTabBar::moveTab(int from, int to)
175{
176 if (from >= pinnedTabsCount() && to >= pinnedTabsCount()) {
177 m_mainTabBar->moveTab(from - pinnedTabsCount(), to - pinnedTabsCount());
178 }
179 else if (from < pinnedTabsCount() && to < pinnedTabsCount()) {
180 m_pinnedTabBar->moveTab(from, to);
181 }
182}
183
184bool ComboTabBar::isTabEnabled(int index) const
185{
186 return localTabBar(index)->isTabEnabled(toLocalIndex(index));
187}
188
189void ComboTabBar::setTabEnabled(int index, bool enabled)
190{
191 localTabBar(index)->setTabEnabled(toLocalIndex(index), enabled);
192}
193
194QColor ComboTabBar::tabTextColor(int index) const
195{
196 return localTabBar(index)->tabTextColor(toLocalIndex(index));
197}
198
199void ComboTabBar::setTabTextColor(int index, const QColor &color)
200{
201 localTabBar(index)->setTabTextColor(toLocalIndex(index), color);
202}
203
204QRect ComboTabBar::tabRect(int index) const
205{
206 return mapFromLocalTabRect(localTabBar(index)->tabRect(toLocalIndex(index)), localTabBar(index));
207}
208
210{
211 const QRect r = m_pinnedTabBar->draggedTabRect();
212 if (r.isValid()) {
213 return mapFromLocalTabRect(r, m_pinnedTabBar);
214 }
215 return mapFromLocalTabRect(m_mainTabBar->draggedTabRect(), m_mainTabBar);
216}
217
218QPixmap ComboTabBar::tabPixmap(int index) const
219{
220 return localTabBar(index)->tabPixmap(toLocalIndex(index));
221}
222
223int ComboTabBar::tabAt(const QPoint &pos) const
224{
225 QWidget* w = QApplication::widgetAt(mapToGlobal(pos));
226 if (!qobject_cast<TabBarHelper*>(w) && !qobject_cast<TabIcon*>(w) && !qobject_cast<CloseButton*>(w))
227 return -1;
228
229 if (m_pinnedTabBarWidget->geometry().contains(pos)) {
230 return m_pinnedTabBarWidget->tabAt(m_pinnedTabBarWidget->mapFromParent(pos));
231 } else if (m_mainTabBarWidget->geometry().contains(pos)) {
232 int index = m_mainTabBarWidget->tabAt(m_mainTabBarWidget->mapFromParent(pos));
233 if (index != -1)
234 index += pinnedTabsCount();
235 return index;
236 }
237
238 return -1;
239}
240
241bool ComboTabBar::emptyArea(const QPoint &pos) const
242{
243 if (tabAt(pos) != -1)
244 return false;
245
246 return qobject_cast<TabBarHelper*>(QApplication::widgetAt(mapToGlobal(pos)));
247}
248
250{
251 return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
252}
253
255{
256 if (m_pinnedTabBar->isActiveTabBar()) {
257 return m_pinnedTabBar->currentIndex();
258 }
259 else {
260 return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
261 }
262}
263
265{
266 return localTabBar(index)->setCurrentIndex(toLocalIndex(index));
267}
268
269void ComboTabBar::slotCurrentChanged(int index)
270{
271 if (m_blockCurrentChangedSignal) {
272 return;
273 }
274
275 if (sender() == m_pinnedTabBar) {
276 if (index == -1 && m_mainTabBar->count() > 0) {
277 m_mainTabBar->setActiveTabBar(true);
278 m_pinnedTabBar->setActiveTabBar(false);
280 }
281 else {
282 m_pinnedTabBar->setActiveTabBar(true);
283 m_mainTabBar->setActiveTabBar(false);
284 Q_EMIT currentChanged(index);
285 }
286 }
287 else {
288 if (index == -1 && pinnedTabsCount() > 0) {
289 m_pinnedTabBar->setActiveTabBar(true);
290 m_mainTabBar->setActiveTabBar(false);
291 Q_EMIT currentChanged(pinnedTabsCount() - 1);
292 }
293 else {
294 m_mainTabBar->setActiveTabBar(true);
295 m_pinnedTabBar->setActiveTabBar(false);
296 Q_EMIT currentChanged(index + pinnedTabsCount());
297 }
298 }
299}
300
301void ComboTabBar::slotTabCloseRequested(int index)
302{
303 if (sender() == m_pinnedTabBar) {
304 Q_EMIT tabCloseRequested(index);
305 }
306 else {
307 Q_EMIT tabCloseRequested(index + pinnedTabsCount());
308 }
309}
310
311void ComboTabBar::slotTabMoved(int from, int to)
312{
313 if (sender() == m_pinnedTabBar) {
314 Q_EMIT tabMoved(from, to);
315 }
316 else {
317 Q_EMIT tabMoved(from + pinnedTabsCount(), to + pinnedTabsCount());
318 }
319}
320
321void ComboTabBar::closeTabFromButton()
322{
323 QWidget* button = qobject_cast<QWidget*>(sender());
324
325 int tabToClose = -1;
326
327 for (int i = 0; i < m_mainTabBar->count(); ++i) {
328 if (m_mainTabBar->tabButton(i, closeButtonPosition()) == button) {
329 tabToClose = i;
330 break;
331 }
332 }
333
334 if (tabToClose != -1) {
335 Q_EMIT tabCloseRequested(tabToClose + pinnedTabsCount());
336 }
337}
338
339void ComboTabBar::updateTabBars()
340{
341 m_mainTabBar->update();
342 m_pinnedTabBar->update();
343}
344
345void ComboTabBar::emitOverFlowChanged()
346{
347 if (m_mainBarOverFlowed != m_lastAppliedOverflow) {
348 Q_EMIT overFlowChanged(m_mainBarOverFlowed);
349 m_lastAppliedOverflow = m_mainBarOverFlowed;
350 }
351}
352
354{
355 return pinnedTabsCount() + m_mainTabBar->count();
356}
357
358void ComboTabBar::setDrawBase(bool drawTheBase)
359{
360 m_mainTabBar->setDrawBase(drawTheBase);
361 m_pinnedTabBar->setDrawBase(drawTheBase);
362}
363
365{
366 return m_mainTabBar->drawBase();
367}
368
369Qt::TextElideMode ComboTabBar::elideMode() const
370{
371 return m_mainTabBar->elideMode();
372}
373
374void ComboTabBar::setElideMode(Qt::TextElideMode elide)
375{
376 m_mainTabBar->setElideMode(elide);
377 m_pinnedTabBar->setElideMode(elide);
378}
379
380QString ComboTabBar::tabText(int index) const
381{
382 return localTabBar(index)->tabText(toLocalIndex(index));
383}
384
385void ComboTabBar::setTabText(int index, const QString &text)
386{
387 localTabBar(index)->setTabText(toLocalIndex(index), text);
388}
389
390void ComboTabBar::setTabToolTip(int index, const QString &tip)
391{
392 localTabBar(index)->setTabToolTip(toLocalIndex(index), tip);
393}
394
395QString ComboTabBar::tabToolTip(int index) const
396{
397 return localTabBar(index)->tabToolTip(toLocalIndex(index));
398}
399
401{
402 return m_mainTabBar->tabsClosable();
403}
404
406{
407 if (closable == tabsClosable()) {
408 return;
409 }
410
411 if (closable) {
412 // insert our close button
413 for (int i = 0; i < m_mainTabBar->count(); ++i) {
414 QWidget* closeButton = m_mainTabBar->tabButton(i, closeButtonPosition());
415 if (closeButton) {
416 if (closeButton->objectName() == QLatin1String("combotabbar_tabs_close_button")) {
417 continue;
418 }
419 }
420
422 if (closeButton) {
423 closeButton->deleteLater();
424 }
425 }
426 }
427 m_mainTabBar->setTabsClosable(closable);
428}
429
430void ComboTabBar::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
431{
432 if (widget)
433 widget->setMinimumSize(closeButtonSize());
434 localTabBar(index)->setTabButton(toLocalIndex(index), position, widget);
435}
436
437QWidget* ComboTabBar::tabButton(int index, QTabBar::ButtonPosition position) const
438{
439 return localTabBar(index)->tabButton(toLocalIndex(index), position);
440}
441
442QTabBar::SelectionBehavior ComboTabBar::selectionBehaviorOnRemove() const
443{
444 return m_mainTabBar->selectionBehaviorOnRemove();
445}
446
447void ComboTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior)
448{
449 m_mainTabBar->setSelectionBehaviorOnRemove(behavior);
450 m_pinnedTabBar->setSelectionBehaviorOnRemove(behavior);
451}
452
454{
455 return m_mainTabBar->expanding();
456}
457
459{
460 m_mainTabBar->setExpanding(enabled);
461 m_pinnedTabBar->setExpanding(enabled);
462}
463
465{
466 return m_mainTabBar->isMovable();
467}
468
469void ComboTabBar::setMovable(bool movable)
470{
471 m_mainTabBar->setMovable(movable);
472 m_pinnedTabBar->setMovable(movable);
473}
474
476{
477 return m_mainTabBar->documentMode();
478}
479
481{
482 m_mainTabBar->setDocumentMode(set);
483 m_pinnedTabBar->setDocumentMode(set);
484}
485
487{
488 return m_pinnedTabBar->count();
489}
490
492{
493 return m_mainTabBar->count();
494}
495
496bool ComboTabBar::isPinned(int index) const
497{
498 return index >= 0 && index < pinnedTabsCount();
499}
500
501void ComboTabBar::setFocusPolicy(Qt::FocusPolicy policy)
502{
503 QWidget::setFocusPolicy(policy);
504 m_mainTabBar->setFocusPolicy(policy);
505 m_pinnedTabBar->setFocusPolicy(policy);
506}
507
508void ComboTabBar::setObjectName(const QString &name)
509{
510 m_mainTabBar->setObjectName(name);
511 m_pinnedTabBar->setObjectName(name);
512}
513
515{
516 m_mainTabBarWidget->scrollArea()->setMouseTracking(enable);
517 m_mainTabBarWidget->setMouseTracking(enable);
518 m_mainTabBar->setMouseTracking(enable);
519
520 m_pinnedTabBarWidget->scrollArea()->setMouseTracking(enable);
521 m_pinnedTabBarWidget->setMouseTracking(enable);
522 m_pinnedTabBar->setMouseTracking(enable);
523
524 QWidget::setMouseTracking(enable);
525}
526
528{
529 int height = qMax(m_mainTabBar->height(), m_pinnedTabBar->height());
530
531 if (height < 1) {
532 height = qMax(m_mainTabBar->sizeHint().height(), m_pinnedTabBar->sizeHint().height());
533 }
534
535 // We need to setup heights even before m_mainTabBar->height() has correct value
536 // So lets just set minimum 5px height
537 height = qMax(5, height);
538
539 setFixedHeight(height);
540 m_leftContainer->setFixedHeight(height);
541 m_rightContainer->setFixedHeight(height);
542 m_mainTabBarWidget->setUpLayout();
543 m_pinnedTabBarWidget->setUpLayout();
544
545 setMinimumWidths();
546
547 if (isVisible() && height > 5) {
548 // ComboTabBar is now visible, we can sync heights of both tabbars
549 m_mainTabBar->setFixedHeight(height);
550 m_pinnedTabBar->setFixedHeight(height);
551 }
552}
553
555{
556 index -= pinnedTabsCount();
557 if (index < 0) {
558 return;
559 }
560
561 QAbstractButton* closeButton = new CloseButton(this);
562 closeButton->setFixedSize(closeButtonSize());
563 closeButton->setToolTip(m_closeButtonsToolTip);
564 connect(closeButton, &QAbstractButton::clicked, this, &ComboTabBar::closeTabFromButton);
565 m_mainTabBar->setTabButton(index, closeButtonPosition(), closeButton);
566}
567
569{
570 m_closeButtonsToolTip = tip;
571}
572
574{
575 return m_mainTabBar->width();
576}
577
579{
580 return m_pinnedTabBarWidget->isHidden() ? 0 : m_pinnedTabBarWidget->width();
581}
582
583bool ComboTabBar::event(QEvent *event)
584{
585 const bool res = QWidget::event(event);
586
587 switch (event->type()) {
588 case QEvent::ToolTip:
590 int index = tabAt(mapFromGlobal(QCursor::pos()));
591 if (index >= 0)
592 QToolTip::showText(QCursor::pos(), tabToolTip(index));
593 }
594 break;
595
596 case QEvent::Resize:
598 break;
599
600 case QEvent::Show:
601 if (!event->spontaneous())
602 QTimer::singleShot(0, this, &ComboTabBar::setUpLayout);
603 break;
604
605 case QEvent::Enter:
606 case QEvent::Leave:
607 // Make sure tabs are painted with correct mouseover state
608 QTimer::singleShot(100, this, &ComboTabBar::updateTabBars);
609 break;
610
611 default:
612 break;
613 }
614
615 return res;
616}
617
618void ComboTabBar::wheelEvent(QWheelEvent* event)
619{
620 event->accept();
621
622 if (qzSettings->alwaysSwitchTabsWithWheel || (!m_mainTabBarWidget->isOverflowed() && !m_pinnedTabBarWidget->isOverflowed())) {
623 m_wheelHelper.processEvent(event);
624 while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) {
625 switch (direction) {
629 break;
630
634 break;
635
636 default:
637 break;
638 }
639 }
640 return;
641 }
642
643 if (m_mainTabBarWidget->underMouse()) {
644 if (m_mainTabBarWidget->isOverflowed()) {
645 m_mainTabBarWidget->scrollByWheel(event);
646 }
647 else if (m_pinnedTabBarWidget->isOverflowed()) {
648 m_pinnedTabBarWidget->scrollByWheel(event);
649 }
650 }
651 else if (m_pinnedTabBarWidget->underMouse()) {
652 if (m_pinnedTabBarWidget->isOverflowed()) {
653 m_pinnedTabBarWidget->scrollByWheel(event);
654 }
655 else if (m_mainTabBarWidget->isOverflowed()) {
656 m_mainTabBarWidget->scrollByWheel(event);
657 }
658 }
659}
660
661bool ComboTabBar::eventFilter(QObject* obj, QEvent* ev)
662{
663 if (obj == m_mainTabBar && ev->type() == QEvent::Resize) {
664 auto* event = static_cast<QResizeEvent*>(ev);
665 if (event->oldSize().height() != event->size().height()) {
666 setUpLayout();
667 }
668 }
669
670 // Handle wheel events exclusively in ComboTabBar
671 if (ev->type() == QEvent::Wheel) {
672 wheelEvent(static_cast<QWheelEvent*>(ev));
673 return true;
674 }
675
676 return QWidget::eventFilter(obj, ev);
677}
678
679void ComboTabBar::paintEvent(QPaintEvent* ev)
680{
681 Q_UNUSED(ev);
682
683 // This is needed to apply style sheets
684 QStyleOption option;
685 option.initFrom(this);
686 QPainter p(this);
687 style()->drawPrimitive(QStyle::PE_Widget, &option, &p, this);
688
689#ifndef Q_OS_MACOS
690 // Draw tabbar base even on parts of ComboTabBar that are not directly QTabBar
691 QStyleOptionTabBarBase opt;
692 TabBarHelper::initStyleBaseOption(&opt, m_mainTabBar, size());
693
694 // Left container
695 opt.rect.setX(m_leftContainer->x());
696 opt.rect.setWidth(m_leftContainer->width());
697 style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
698
699 // Right container
700 opt.rect.setX(m_rightContainer->x());
701 opt.rect.setWidth(m_rightContainer->width());
702 style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
703
704 if (m_mainBarOverFlowed) {
705 const int scrollButtonWidth = m_mainTabBarWidget->scrollButtonsWidth();
706
707 // Left scroll button
708 opt.rect.setX(m_mainTabBarWidget->x());
709 opt.rect.setWidth(scrollButtonWidth);
710 style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
711
712 // Right scroll button
713 opt.rect.setX(m_mainTabBarWidget->x() + m_mainTabBarWidget->width() - scrollButtonWidth);
714 opt.rect.setWidth(scrollButtonWidth);
715 style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
716 }
717
718 // Draw base even when main tabbar is empty
719 if (normalTabsCount() == 0) {
720 opt.rect.setX(m_mainTabBarWidget->x());
721 opt.rect.setWidth(m_mainTabBarWidget->width());
722 style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
723 }
724#endif
725}
726
728{
729 switch (sizeType) {
731 return 0;
732
734 return 150;
735
739 return 100;
740
741 case PinnedTabWidth:
742 return 30;
743
744 default:
745 break;
746 }
747
748 return -1;
749}
750
751QTabBar::ButtonPosition ComboTabBar::iconButtonPosition() const
752{
753 return (closeButtonPosition() == QTabBar::RightSide ? QTabBar::LeftSide : QTabBar::RightSide);
754}
755
756QTabBar::ButtonPosition ComboTabBar::closeButtonPosition() const
757{
758 return (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, m_mainTabBar);
759}
760
762{
763 QSize s = closeButtonSize();
764 s.setWidth(qMax(16, s.width()));
765 s.setHeight(qMax(16, s.height()));
766 return s;
767}
768
770{
771 int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
772 int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
773 return QSize(width, height);
774}
775
776bool ComboTabBar::validIndex(int index) const
777{
778 return (index >= 0 && index < count());
779}
780
782{
783 for (int index = currentIndex() + offset; validIndex(index); index += offset) {
784 if (isTabEnabled(index)) {
785 setCurrentIndex(index);
786 break;
787 }
788 }
789}
790
792{
793 return m_mainTabBarWidget->usesScrollButtons();
794}
795
797{
798 m_mainTabBarWidget->setUsesScrollButtons(useButtons);
799}
800
802{
804 localTabBar(index)->showDropIndicator(toLocalIndex(index), position);
805}
806
808{
809 m_mainTabBar->clearDropIndicator();
810 m_pinnedTabBar->clearDropIndicator();
811}
812
814{
815 return m_mainTabBar->isDragInProgress() || m_pinnedTabBar->isDragInProgress();
816}
817
819{
820 return m_mainTabBarWidget->scrollBar()->isScrolling() || m_pinnedTabBarWidget->scrollBar()->isScrolling();
821}
822
824{
825 return m_mainBarOverFlowed;
826}
827
828int ComboTabBar::cornerWidth(Qt::Corner corner) const
829{
830 if (corner == Qt::TopLeftCorner) {
831 return m_leftContainer->width();
832 }
833 else if (corner == Qt::TopRightCorner) {
834 return m_rightContainer->width();
835 }
836
837 qFatal("ComboTabBar::cornerWidth Only TopLeft and TopRight corners are implemented!");
838 return -1;
839}
840
841void ComboTabBar::addCornerWidget(QWidget* widget, Qt::Corner corner)
842{
843 if (corner == Qt::TopLeftCorner) {
844 m_leftLayout->addWidget(widget);
845 }
846 else if (corner == Qt::TopRightCorner) {
847 m_rightLayout->addWidget(widget);
848 }
849 else {
850 qFatal("ComboTabBar::addCornerWidget Only TopLeft and TopRight corners are implemented!");
851 }
852}
853
854// static
856{
857 // taken from qtabbar_p.h
858 return 250;
859}
860
861void ComboTabBar::ensureVisible(int index, int xmargin)
862{
863 if (index == -1) {
864 index = currentIndex();
865 }
866
867 if (index < pinnedTabsCount()) {
868 if (xmargin == -1) {
869 xmargin = qMax(20, comboTabBarPixelMetric(PinnedTabWidth));
870 }
871 m_pinnedTabBarWidget->ensureVisible(index, xmargin);
872 }
873 else {
874 if (xmargin == -1) {
876 }
877 index -= pinnedTabsCount();
878 m_mainTabBarWidget->ensureVisible(index, xmargin);
879 }
880}
881
882QSize ComboTabBar::tabSizeHint(int index, bool fast) const
883{
884 Q_UNUSED(fast)
885
886 return localTabBar(index)->baseClassTabSizeHint(toLocalIndex(index));
887}
888
890{
891 Q_UNUSED(index)
892}
893
895{
896 Q_UNUSED(index)
897}
898
899TabBarHelper* ComboTabBar::mainTabBar() const
900{
901 return m_mainTabBar;
902}
903
904TabBarHelper* ComboTabBar::localTabBar(int index) const
905{
906 if (index < 0 || index >= pinnedTabsCount()) {
907 return m_mainTabBar;
908 }
909 else {
910 return m_pinnedTabBar;
911 }
912}
913
914int ComboTabBar::toLocalIndex(int globalIndex) const
915{
916 if (globalIndex < 0) {
917 return -1;
918 }
919
920 if (globalIndex >= pinnedTabsCount()) {
921 return globalIndex - pinnedTabsCount();
922 }
923 else {
924 return globalIndex;
925 }
926}
927
928QRect ComboTabBar::mapFromLocalTabRect(const QRect &rect, QWidget *tabBar) const
929{
930 if (!rect.isValid()) {
931 return rect;
932 }
933
934 QRect r = rect;
935
936 if (tabBar == m_mainTabBar) {
937 r.moveLeft(r.x() + mapFromGlobal(m_mainTabBar->mapToGlobal(QPoint(0, 0))).x());
938 QRect widgetRect = m_mainTabBarWidget->scrollArea()->viewport()->rect();
939 widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_mainTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
940 r = r.intersected(widgetRect);
941 } else {
942 r.moveLeft(r.x() + mapFromGlobal(m_pinnedTabBar->mapToGlobal(QPoint(0, 0))).x());
943 QRect widgetRect = m_pinnedTabBarWidget->scrollArea()->viewport()->rect();
944 widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_pinnedTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
945 r = r.intersected(widgetRect);
946 }
947
948 return r;
949}
950
951void ComboTabBar::updatePinnedTabBarVisibility()
952{
953 m_pinnedTabBarWidget->setVisible(pinnedTabsCount() > 0);
954}
955
956void ComboTabBar::setMinimumWidths()
957{
958 if (!isVisible() || comboTabBarPixelMetric(PinnedTabWidth) < 0) {
959 return;
960 }
961
962 const int tabBarsSpacing = 3; // To distinguish tabbars
963 int pinnedTabBarWidth = pinnedTabsCount() * comboTabBarPixelMetric(PinnedTabWidth);
964 m_pinnedTabBar->setMinimumWidth(pinnedTabBarWidth);
965 m_pinnedTabBarWidget->setFixedWidth(pinnedTabBarWidth + tabBarsSpacing);
966
967 // Width that is needed by main tabbar
968 int mainTabBarWidth = comboTabBarPixelMetric(NormalTabMinimumWidth) * (m_mainTabBar->count() - 1) +
971
972 // This is the full width that would be needed for the tabbar (including pinned tabbar and corner widgets)
973 int realTabBarWidth = mainTabBarWidth + m_pinnedTabBarWidget->width() +
974 cornerWidth(Qt::TopLeftCorner) +
975 cornerWidth(Qt::TopRightCorner);
976
977 // Does it fit in our widget?
978 if (realTabBarWidth <= width()) {
979 if (m_mainBarOverFlowed) {
980 m_mainBarOverFlowed = false;
981 QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged);
982 }
983
984 m_mainTabBar->useFastTabSizeHint(false);
985 m_mainTabBar->setMinimumWidth(mainTabBarWidth);
986 }
987 else {
988 if (!m_mainBarOverFlowed) {
989 m_mainBarOverFlowed = true;
990 QTimer::singleShot(0, this, &ComboTabBar::emitOverFlowChanged);
991 }
992
993 // All tabs have now same width, we can use fast tabSizeHint
994 m_mainTabBar->useFastTabSizeHint(true);
995 m_mainTabBar->setMinimumWidth(m_mainTabBar->count() * comboTabBarPixelMetric(OverflowedTabWidth));
996 }
997}
998
999
1000TabBarHelper::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
1013{
1014 return m_tabPadding;
1015}
1016
1018{
1019 m_tabPadding = padding;
1020}
1021
1023{
1024 return m_baseColor;
1025}
1026
1027void TabBarHelper::setBaseColor(const QColor &color)
1028{
1029 m_baseColor = color;
1030}
1031
1032void TabBarHelper::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
1033{
1034 QTabBar::setTabButton(index, position, widget);
1035}
1036
1037QSize 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
1046{
1047 return QTabBar::tabSizeHint(index);
1048}
1049
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
1066QPixmap 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
1105{
1106 return m_activeTabBar;
1107}
1108
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
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
1140void TabBarHelper::setScrollArea(QScrollArea* scrollArea)
1141{
1142 m_scrollArea = scrollArea;
1143}
1144
1146{
1147 m_useFastTabSizeHint = enabled;
1148}
1149
1151{
1152 m_dropIndicatorIndex = index;
1153 m_dropIndicatorPosition = position;
1154 update();
1155}
1156
1158{
1159 m_dropIndicatorIndex = -1;
1160 update();
1161}
1162
1163bool 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
1179{
1180 return m_dragInProgress;
1181}
1182
1184{
1185 if (index == currentIndex() && !m_activeTabBar) {
1186 Q_EMIT currentChanged(currentIndex());
1187 }
1188
1189 QTabBar::setCurrentIndex(index);
1190}
1191
1192bool 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
1209int 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
1227void 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
1262void 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 }
1383 }
1384}
1385
1386void 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
1403void 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
1455void 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
1470void 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
1507 : QScrollBar(Qt::Horizontal, parent)
1508{
1509 m_animation = new QPropertyAnimation(this, "value", this);
1510}
1511
1513= default;
1514
1516{
1517 return m_animation->state() == QPropertyAnimation::Running;
1518}
1519
1520void 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
1535TabBarScrollWidget::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
1590{
1591 return m_tabBar;
1592}
1593
1595{
1596 return m_scrollArea;
1597}
1598
1600{
1601 return m_scrollBar;
1602}
1603
1604void 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
1629void 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
1635void 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
1642{
1643 m_scrollBar->animateToValue(m_scrollBar->minimum());
1644}
1645
1647{
1648 m_scrollBar->animateToValue(m_scrollBar->maximum());
1649}
1650
1652{
1653 const int height = m_tabBar->height();
1654
1655 setFixedHeight(height);
1656}
1657
1658void 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
1664void TabBarScrollWidget::overFlowChanged(bool overflowed)
1665{
1666 bool showScrollButtons = overflowed && m_usesScrollButtons;
1667
1668 m_leftScrollButton->setVisible(showScrollButtons);
1669 m_rightScrollButton->setVisible(showScrollButtons);
1670}
1671
1672void TabBarScrollWidget::scrollStart()
1673{
1674 bool ctrlModifier = QApplication::keyboardModifiers() & Qt::ControlModifier;
1675
1676 if (sender() == m_leftScrollButton) {
1677 if (ctrlModifier) {
1679 }
1680 else {
1681 scrollToLeft(5, QEasingCurve::Linear);
1682 }
1683 }
1684 else if (sender() == m_rightScrollButton) {
1685 if (ctrlModifier) {
1687 }
1688 else {
1689 scrollToRight(5, QEasingCurve::Linear);
1690 }
1691 }
1692}
1693
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
1750{
1751 // Assumes both buttons have the same width
1752 return m_leftScrollButton->width();
1753}
1754
1756{
1757 return m_usesScrollButtons;
1758}
1759
1761{
1762 if (useButtons != m_usesScrollButtons) {
1763 m_usesScrollButtons = useButtons;
1764 updateScrollButtonsState();
1765 m_tabBar->setElideMode(m_tabBar->elideMode());
1766 }
1767}
1768
1770{
1771 return m_tabBar->count() > 0 && m_scrollBar->minimum() != m_scrollBar->maximum();
1772}
1773
1774int 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
1784void TabBarScrollWidget::mouseMoveEvent(QMouseEvent* event)
1785{
1786 event->ignore();
1787}
1788
1789void TabBarScrollWidget::resizeEvent(QResizeEvent* event)
1790{
1791 QWidget::resizeEvent(event);
1792
1793 updateScrollButtonsState();
1794}
1795
1796
1798 : QAbstractButton(parent)
1799{
1800 setObjectName("combotabbar_tabs_close_button");
1801 setFocusPolicy(Qt::NoFocus);
1802 setCursor(Qt::ArrowCursor);
1803 resize(sizeHint());
1804}
1805
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
1814void CloseButton::enterEvent(QEnterEvent* event)
1815{
1816 if (isEnabled()) {
1817 update();
1818 }
1819
1820 QAbstractButton::enterEvent(event);
1821}
1822
1823void CloseButton::leaveEvent(QEvent* event)
1824{
1825 if (isEnabled()) {
1826 update();
1827 }
1828
1829 QAbstractButton::leaveEvent(event);
1830}
1831
1832void 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}
void leaveEvent(QEvent *event) override
void paintEvent(QPaintEvent *event) override
QSize sizeHint() const override
void enterEvent(QEnterEvent *event) override
CloseButton(QWidget *parent=nullptr)
bool validIndex(int index) const
virtual int comboTabBarPixelMetric(SizeType sizeType) const
int addTab(const QString &text)
int tabAt(const QPoint &pos) const
QTabBar::ButtonPosition iconButtonPosition() const
void setMovable(bool movable)
void tabCloseRequested(int index)
bool isDragInProgress() const
bool documentMode() const
@ OverflowedTabWidth
Definition: combotabbar.h:51
@ ActiveTabMinimumWidth
Definition: combotabbar.h:48
@ NormalTabMaximumWidth
Definition: combotabbar.h:50
@ ExtraReservedWidth
Definition: combotabbar.h:52
@ NormalTabMinimumWidth
Definition: combotabbar.h:49
QColor tabTextColor(int index) const
QSize closeButtonSize() const
virtual void tabRemoved(int index)
int insertTab(int index, const QString &text)
int normalTabsCount() const
void setMouseTracking(bool enable)
friend class TabBarHelper
Definition: combotabbar.h:223
QString tabText(int index) const
bool eventFilter(QObject *obj, QEvent *ev) override
void clearDropIndicator()
void addCornerWidget(QWidget *widget, Qt::Corner corner)
void moveTab(int from, int to)
bool drawBase() const
int mainTabBarCurrentIndex() const
QTabBar::SelectionBehavior selectionBehaviorOnRemove() const
void showDropIndicator(int index, DropIndicatorPosition position)
void removeTab(int index)
QString tabToolTip(int index) const
void scrollBarValueChanged(int value)
QPixmap tabPixmap(int index) const
bool isPinned(int index) const
void setExpanding(bool enabled)
void setElideMode(Qt::TextElideMode elide)
QWidget * tabButton(int index, QTabBar::ButtonPosition position) const
QRect tabRect(int index) const
QSize iconButtonSize() const
void setTabTextColor(int index, const QColor &color)
void ensureVisible(int index=-1, int xmargin=-1)
QTabBar::ButtonPosition closeButtonPosition() const
int currentIndex
Definition: combotabbar.h:42
void paintEvent(QPaintEvent *ev) override
int pinnedTabsCount() const
QRect draggedTabRect() const
bool isMovable() const
void setTabButton(int index, QTabBar::ButtonPosition position, QWidget *widget)
bool expanding() const
bool isMainBarOverflowed() const
void currentChanged(int index)
void setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior)
void setDrawBase(bool drawTheBase)
int pinTabBarWidth() const
int cornerWidth(Qt::Corner corner) const
void overFlowChanged(bool overFlow)
void setFocusPolicy(Qt::FocusPolicy policy)
void tabMoved(int from, int to)
void setTabToolTip(int index, const QString &tip)
ComboTabBar(QWidget *parent=nullptr)
Definition: combotabbar.cpp:46
void setUpLayout()
bool usesScrollButtons() const
virtual void tabInserted(int index)
virtual QSize tabSizeHint(int index, bool fast=false) const
void setCloseButtonsToolTip(const QString &tip)
bool event(QEvent *event) override
void setTabText(int index, const QString &text)
bool isTabEnabled(int index) const
void wheelEvent(QWheelEvent *event) override
int mainTabBarWidth() const
void setTabsClosable(bool closable)
void setObjectName(const QString &name)
bool emptyArea(const QPoint &pos) const
void setUsesScrollButtons(bool useButtons)
bool tabsClosable() const
void setTabEnabled(int index, bool enabled)
void setDocumentMode(bool set)
void insertCloseButton(int index)
Qt::TextElideMode elideMode() const
bool isScrollInProgress() const
void setCurrentNextEnabledIndex(int offset)
void setCurrentIndex(int index)
static int slideAnimationDuration()
static void paintDropIndicator(QWidget *widget, const QRect &r)
Definition: qztools.cpp:1023
QSize baseClassTabSizeHint(int index) const
void setScrollArea(QScrollArea *scrollArea)
void setBaseColor(const QColor &color)
static void initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar *tabbar, QSize size)
bool isDisplayedOnViewPort(int globalLeft, int globalRight)
void setTabButton(int index, QTabBar::ButtonPosition position, QWidget *widget)
void useFastTabSizeHint(bool enabled)
void showDropIndicator(int index, ComboTabBar::DropIndicatorPosition position)
QColor baseColor
Definition: combotabbar.h:231
QPixmap tabPixmap(int index) const
bool isDragInProgress() const
void setActiveTabBar(bool activate)
void setTabPadding(int padding)
bool isActiveTabBar()
void clearDropIndicator()
QRect draggedTabRect() const
QSize tabSizeHint(int index) const override
void removeTab(int index)
void setCurrentIndex(int index)
TabBarHelper(bool isPinnedTabBar, ComboTabBar *comboTabBar)
void ensureVisible(int index=-1, int xmargin=132)
void setUsesScrollButtons(bool useButtons)
bool isOverflowed() const
TabScrollBar * scrollBar()
QScrollArea * scrollArea()
int scrollButtonsWidth() const
void scrollToLeft(int n=5, QEasingCurve::Type type=QEasingCurve::OutQuad)
bool usesScrollButtons() const
void scrollByWheel(QWheelEvent *event)
TabBarScrollWidget(QTabBar *tabBar, QWidget *parent=nullptr)
void scrollToRight(int n=5, QEasingCurve::Type type=QEasingCurve::OutQuad)
int tabAt(const QPoint &pos) const
void animateToValue(int to, QEasingCurve::Type type=QEasingCurve::OutQuad)
TabScrollBar(QWidget *parent=nullptr)
bool isScrolling() const
void doubleClicked()
Direction takeDirection()
Definition: wheelhelper.cpp:74
void processEvent(QWheelEvent *event)
Definition: wheelhelper.cpp:31
#define mApp
int value(const QColor &c)
Definition: colors.cpp:238
i
Definition: i18n.py:23
#define QL1S(x)
Definition: qzcommon.h:44
#define QSL(x)
Definition: qzcommon.h:40
#define qzSettings
Definition: qzsettings.h:69