1
mirror of https://invent.kde.org/network/falkon.git synced 2024-09-23 02:32:10 +02:00
falkonOfficial/src/lib/tabwidget/combotabbar.cpp
David Rosca e4c2194177
ComboTabBar: Force close button on right side
This mainly applies to macOS where close buttons are normally on left side,
but native macOS tabbar doesn't use icons and it looks wrong with icons.
2018-02-16 12:04:29 +01:00

1869 lines
53 KiB
C++

/* ============================================================
* Falkon - Qt web browser
* Copyright (C) 2013-2014 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com>
* Copyright (C) 2014-2018 David Rosca <nowrep@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "combotabbar.h"
#include "toolbutton.h"
#include "tabicon.h"
#include "mainapplication.h"
#include "proxystyle.h"
#include "qzsettings.h"
#include <QIcon>
#include <QHBoxLayout>
#include <QStylePainter>
#include <QStyleOptionTabV3>
#include <QStyleOptionTabBarBaseV2>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QTimer>
#include <QTabBar>
#include <QMouseEvent>
#include <QApplication>
#include <QToolTip>
class QMovableTabWidget : public QWidget
{
public:
QPixmap m_pixmap;
};
ComboTabBar::ComboTabBar(QWidget* parent)
: QWidget(parent)
, m_mainTabBar(0)
, m_pinnedTabBar(0)
, m_mainBarOverFlowed(false)
, m_lastAppliedOverflow(false)
, m_usesScrollButtons(false)
, m_blockCurrentChangedSignal(false)
{
QObject::setObjectName(QSL("tabbarwidget"));
m_mainTabBar = new TabBarHelper(/*isPinnedTabBar*/ false, this);
m_pinnedTabBar = new TabBarHelper(/*isPinnedTabBar*/ true, this);
m_mainTabBarWidget = new TabBarScrollWidget(m_mainTabBar, this);
m_pinnedTabBarWidget = new TabBarScrollWidget(m_pinnedTabBar, this);
m_mainTabBar->setScrollArea(m_mainTabBarWidget->scrollArea());
m_pinnedTabBar->setScrollArea(m_pinnedTabBarWidget->scrollArea());
connect(m_mainTabBarWidget->scrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(setMinimumWidths()));
connect(m_mainTabBarWidget->scrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrollBarValueChanged(int)));
connect(m_pinnedTabBarWidget->scrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(setMinimumWidths()));
connect(m_pinnedTabBarWidget->scrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrollBarValueChanged(int)));
connect(this, SIGNAL(overFlowChanged(bool)), m_mainTabBarWidget, SLOT(overFlowChanged(bool)));
m_mainTabBar->setActiveTabBar(true);
m_pinnedTabBar->setTabsClosable(false);
m_leftLayout = new QHBoxLayout;
m_leftLayout->setSpacing(0);
m_leftLayout->setContentsMargins(0, 0, 0, 0);
m_leftContainer = new QWidget(this);
m_leftContainer->setLayout(m_leftLayout);
m_rightLayout = new QHBoxLayout;
m_rightLayout->setSpacing(0);
m_rightLayout->setContentsMargins(0, 0, 0, 0);
m_rightContainer = new QWidget(this);
m_rightContainer->setLayout(m_rightLayout);
m_mainLayout = new QHBoxLayout;
m_mainLayout->setSpacing(0);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->addWidget(m_leftContainer);
m_mainLayout->addWidget(m_pinnedTabBarWidget);
m_mainLayout->addWidget(m_mainTabBarWidget);
m_mainLayout->addWidget(m_rightContainer);
setLayout(m_mainLayout);
connect(m_mainTabBar, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentChanged(int)));
connect(m_mainTabBar, SIGNAL(tabCloseRequested(int)), this, SLOT(slotTabCloseRequested(int)));
connect(m_mainTabBar, SIGNAL(tabMoved(int,int)), this, SLOT(slotTabMoved(int,int)));
connect(m_pinnedTabBar, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentChanged(int)));
connect(m_pinnedTabBar, SIGNAL(tabCloseRequested(int)), this, SLOT(slotTabCloseRequested(int)));
connect(m_pinnedTabBar, SIGNAL(tabMoved(int,int)), this, SLOT(slotTabMoved(int,int)));
setAutoFillBackground(false);
m_mainTabBar->setAutoFillBackground(false);
m_pinnedTabBar->setAutoFillBackground(false);
m_mainTabBar->installEventFilter(this);
m_pinnedTabBar->installEventFilter(this);
m_leftContainer->installEventFilter(this);
m_rightContainer->installEventFilter(this);
m_mainTabBarWidget->installEventFilter(this);
m_pinnedTabBarWidget->installEventFilter(this);
}
int ComboTabBar::addTab(const QString &text)
{
return insertTab(-1, text);
}
int ComboTabBar::addTab(const QIcon &icon, const QString &text)
{
return insertTab(-1, icon, text);
}
int ComboTabBar::insertTab(int index, const QString &text)
{
return insertTab(index, QIcon(), text);
}
int ComboTabBar::insertTab(int index, const QIcon &icon, const QString &text, bool pinned)
{
if (pinned) {
index = m_pinnedTabBar->insertTab(index, icon, text);
}
else {
index = m_mainTabBar->insertTab(index - pinnedTabsCount(), icon, text);
if (tabsClosable()) {
QWidget* closeButton = m_mainTabBar->tabButton(index, closeButtonPosition());
if ((closeButton && closeButton->objectName() != QLatin1String("combotabbar_tabs_close_button")) || !closeButton) {
// insert our close button
insertCloseButton(index + pinnedTabsCount());
if (closeButton) {
closeButton->deleteLater();
}
}
}
index += pinnedTabsCount();
}
updatePinnedTabBarVisibility();
tabInserted(index);
setMinimumWidths();
return index;
}
void ComboTabBar::removeTab(int index)
{
if (validIndex(index)) {
setUpdatesEnabled(false);
localTabBar(index)->removeTab(toLocalIndex(index));
updatePinnedTabBarVisibility();
tabRemoved(index);
setMinimumWidths();
setUpdatesEnabled(true);
updateTabBars();
}
}
void ComboTabBar::moveTab(int from, int to)
{
if (from >= pinnedTabsCount() && to >= pinnedTabsCount()) {
m_mainTabBar->moveTab(from - pinnedTabsCount(), to - pinnedTabsCount());
}
else if (from < pinnedTabsCount() && to < pinnedTabsCount()) {
m_pinnedTabBar->moveTab(from, to);
}
}
bool ComboTabBar::isTabEnabled(int index) const
{
return localTabBar(index)->isTabEnabled(toLocalIndex(index));
}
void ComboTabBar::setTabEnabled(int index, bool enabled)
{
localTabBar(index)->setTabEnabled(toLocalIndex(index), enabled);
}
QColor ComboTabBar::tabTextColor(int index) const
{
return localTabBar(index)->tabTextColor(toLocalIndex(index));
}
void ComboTabBar::setTabTextColor(int index, const QColor &color)
{
localTabBar(index)->setTabTextColor(toLocalIndex(index), color);
}
QRect ComboTabBar::tabRect(int index) const
{
return mapFromLocalTabRect(localTabBar(index)->tabRect(toLocalIndex(index)), localTabBar(index));
}
QRect ComboTabBar::draggedTabRect() const
{
const QRect r = m_pinnedTabBar->draggedTabRect();
if (r.isValid()) {
return mapFromLocalTabRect(r, m_pinnedTabBar);
}
return mapFromLocalTabRect(m_mainTabBar->draggedTabRect(), m_mainTabBar);
}
QPixmap ComboTabBar::tabPixmap(int index) const
{
return localTabBar(index)->tabPixmap(toLocalIndex(index));
}
int ComboTabBar::tabAt(const QPoint &pos) const
{
QWidget* w = QApplication::widgetAt(mapToGlobal(pos));
if (!qobject_cast<TabBarHelper*>(w) && !qobject_cast<TabIcon*>(w) && !qobject_cast<CloseButton*>(w))
return -1;
if (m_pinnedTabBarWidget->geometry().contains(pos)) {
return m_pinnedTabBarWidget->tabAt(m_pinnedTabBarWidget->mapFromParent(pos));
} else if (m_mainTabBarWidget->geometry().contains(pos)) {
int index = m_mainTabBarWidget->tabAt(m_mainTabBarWidget->mapFromParent(pos));
if (index != -1)
index += pinnedTabsCount();
return index;
}
return -1;
}
bool ComboTabBar::emptyArea(const QPoint &pos) const
{
if (tabAt(pos) != -1)
return false;
return qobject_cast<TabBarHelper*>(QApplication::widgetAt(mapToGlobal(pos)));
}
int ComboTabBar::mainTabBarCurrentIndex() const
{
return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
}
int ComboTabBar::currentIndex() const
{
if (m_pinnedTabBar->isActiveTabBar()) {
return m_pinnedTabBar->currentIndex();
}
else {
return (m_mainTabBar->currentIndex() == -1 ? -1 : pinnedTabsCount() + m_mainTabBar->currentIndex());
}
}
void ComboTabBar::setCurrentIndex(int index)
{
return localTabBar(index)->setCurrentIndex(toLocalIndex(index));
}
void ComboTabBar::slotCurrentChanged(int index)
{
if (m_blockCurrentChangedSignal) {
return;
}
if (sender() == m_pinnedTabBar) {
if (index == -1 && m_mainTabBar->count() > 0) {
m_mainTabBar->setActiveTabBar(true);
m_pinnedTabBar->setActiveTabBar(false);
emit currentChanged(pinnedTabsCount());
}
else {
m_pinnedTabBar->setActiveTabBar(true);
m_mainTabBar->setActiveTabBar(false);
emit currentChanged(index);
}
}
else {
if (index == -1 && pinnedTabsCount() > 0) {
m_pinnedTabBar->setActiveTabBar(true);
m_mainTabBar->setActiveTabBar(false);
emit currentChanged(pinnedTabsCount() - 1);
}
else {
m_mainTabBar->setActiveTabBar(true);
m_pinnedTabBar->setActiveTabBar(false);
emit currentChanged(index + pinnedTabsCount());
}
}
}
void ComboTabBar::slotTabCloseRequested(int index)
{
if (sender() == m_pinnedTabBar) {
emit tabCloseRequested(index);
}
else {
emit tabCloseRequested(index + pinnedTabsCount());
}
}
void ComboTabBar::slotTabMoved(int from, int to)
{
if (sender() == m_pinnedTabBar) {
emit tabMoved(from, to);
}
else {
emit tabMoved(from + pinnedTabsCount(), to + pinnedTabsCount());
}
}
void ComboTabBar::closeTabFromButton()
{
QWidget* button = qobject_cast<QWidget*>(sender());
int tabToClose = -1;
for (int i = 0; i < m_mainTabBar->count(); ++i) {
if (m_mainTabBar->tabButton(i, closeButtonPosition()) == button) {
tabToClose = i;
break;
}
}
if (tabToClose != -1) {
emit tabCloseRequested(tabToClose + pinnedTabsCount());
}
}
void ComboTabBar::updateTabBars()
{
m_mainTabBar->update();
m_pinnedTabBar->update();
}
void ComboTabBar::emitOverFlowChanged()
{
if (m_mainBarOverFlowed != m_lastAppliedOverflow) {
emit overFlowChanged(m_mainBarOverFlowed);
m_lastAppliedOverflow = m_mainBarOverFlowed;
}
}
int ComboTabBar::count() const
{
return pinnedTabsCount() + m_mainTabBar->count();
}
void ComboTabBar::setDrawBase(bool drawTheBase)
{
m_mainTabBar->setDrawBase(drawTheBase);
m_pinnedTabBar->setDrawBase(drawTheBase);
}
bool ComboTabBar::drawBase() const
{
return m_mainTabBar->drawBase();
}
Qt::TextElideMode ComboTabBar::elideMode() const
{
return m_mainTabBar->elideMode();
}
void ComboTabBar::setElideMode(Qt::TextElideMode elide)
{
m_mainTabBar->setElideMode(elide);
m_pinnedTabBar->setElideMode(elide);
}
QString ComboTabBar::tabText(int index) const
{
return localTabBar(index)->tabText(toLocalIndex(index));
}
void ComboTabBar::setTabText(int index, const QString &text)
{
localTabBar(index)->setTabText(toLocalIndex(index), text);
}
void ComboTabBar::setTabToolTip(int index, const QString &tip)
{
localTabBar(index)->setTabToolTip(toLocalIndex(index), tip);
}
QString ComboTabBar::tabToolTip(int index) const
{
return localTabBar(index)->tabToolTip(toLocalIndex(index));
}
bool ComboTabBar::tabsClosable() const
{
return m_mainTabBar->tabsClosable();
}
void ComboTabBar::setTabsClosable(bool closable)
{
if (closable == tabsClosable()) {
return;
}
if (closable) {
// insert our close button
for (int i = 0; i < m_mainTabBar->count(); ++i) {
QWidget* closeButton = m_mainTabBar->tabButton(i, closeButtonPosition());
if (closeButton) {
if (closeButton->objectName() == QLatin1String("combotabbar_tabs_close_button")) {
continue;
}
}
insertCloseButton(i + pinnedTabsCount());
if (closeButton) {
closeButton->deleteLater();
}
}
}
m_mainTabBar->setTabsClosable(closable);
}
void ComboTabBar::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
{
if (widget)
widget->setMinimumSize(closeButtonSize());
localTabBar(index)->setTabButton(toLocalIndex(index), position, widget);
}
QWidget* ComboTabBar::tabButton(int index, QTabBar::ButtonPosition position) const
{
return localTabBar(index)->tabButton(toLocalIndex(index), position);
}
QTabBar::SelectionBehavior ComboTabBar::selectionBehaviorOnRemove() const
{
return m_mainTabBar->selectionBehaviorOnRemove();
}
void ComboTabBar::setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior behavior)
{
m_mainTabBar->setSelectionBehaviorOnRemove(behavior);
m_pinnedTabBar->setSelectionBehaviorOnRemove(behavior);
}
bool ComboTabBar::expanding() const
{
return m_mainTabBar->expanding();
}
void ComboTabBar::setExpanding(bool enabled)
{
m_mainTabBar->setExpanding(enabled);
m_pinnedTabBar->setExpanding(enabled);
}
bool ComboTabBar::isMovable() const
{
return m_mainTabBar->isMovable();
}
void ComboTabBar::setMovable(bool movable)
{
m_mainTabBar->setMovable(movable);
m_pinnedTabBar->setMovable(movable);
}
bool ComboTabBar::documentMode() const
{
return m_mainTabBar->documentMode();
}
void ComboTabBar::setDocumentMode(bool set)
{
m_mainTabBar->setDocumentMode(set);
m_pinnedTabBar->setDocumentMode(set);
}
int ComboTabBar::pinnedTabsCount() const
{
return m_pinnedTabBar->count();
}
int ComboTabBar::normalTabsCount() const
{
return m_mainTabBar->count();
}
bool ComboTabBar::isPinned(int index) const
{
return index >= 0 && index < pinnedTabsCount();
}
void ComboTabBar::setFocusPolicy(Qt::FocusPolicy policy)
{
QWidget::setFocusPolicy(policy);
m_mainTabBar->setFocusPolicy(policy);
m_pinnedTabBar->setFocusPolicy(policy);
}
void ComboTabBar::setObjectName(const QString &name)
{
m_mainTabBar->setObjectName(name);
m_pinnedTabBar->setObjectName(name);
}
void ComboTabBar::setMouseTracking(bool enable)
{
m_mainTabBarWidget->scrollArea()->setMouseTracking(enable);
m_mainTabBarWidget->setMouseTracking(enable);
m_mainTabBar->setMouseTracking(enable);
m_pinnedTabBarWidget->scrollArea()->setMouseTracking(enable);
m_pinnedTabBarWidget->setMouseTracking(enable);
m_pinnedTabBar->setMouseTracking(enable);
QWidget::setMouseTracking(enable);
}
void ComboTabBar::setUpLayout()
{
int height = qMax(m_mainTabBar->height(), m_pinnedTabBar->height());
if (height < 1) {
height = qMax(m_mainTabBar->sizeHint().height(), m_pinnedTabBar->sizeHint().height());
}
// Workaround for Oxygen theme. For some reason, QTabBar::height() returns bigger
// height than it actually should.
if (mApp->styleName() == QLatin1String("oxygen")) {
height -= 4;
}
// We need to setup heights even before m_mainTabBar->height() has correct value
// So lets just set minimum 5px height
height = qMax(5, height);
setFixedHeight(height);
m_leftContainer->setFixedHeight(height);
m_rightContainer->setFixedHeight(height);
m_mainTabBarWidget->setUpLayout();
m_pinnedTabBarWidget->setUpLayout();
setMinimumWidths();
if (isVisible() && height > 5) {
// ComboTabBar is now visible, we can sync heights of both tabbars
m_mainTabBar->setFixedHeight(height);
m_pinnedTabBar->setFixedHeight(height);
}
}
void ComboTabBar::insertCloseButton(int index)
{
index -= pinnedTabsCount();
if (index < 0) {
return;
}
QAbstractButton* closeButton = new CloseButton(this);
closeButton->setFixedSize(closeButtonSize());
closeButton->setToolTip(m_closeButtonsToolTip);
connect(closeButton, SIGNAL(clicked()), this, SLOT(closeTabFromButton()));
m_mainTabBar->setTabButton(index, closeButtonPosition(), closeButton);
}
void ComboTabBar::setCloseButtonsToolTip(const QString &tip)
{
m_closeButtonsToolTip = tip;
}
int ComboTabBar::mainTabBarWidth() const
{
return m_mainTabBar->width();
}
int ComboTabBar::pinTabBarWidth() const
{
return m_pinnedTabBarWidget->isHidden() ? 0 : m_pinnedTabBarWidget->width();
}
bool ComboTabBar::event(QEvent *event)
{
const bool res = QWidget::event(event);
switch (event->type()) {
case QEvent::ToolTip:
if (!isDragInProgress() && !isScrollInProgress()) {
int index = tabAt(mapFromGlobal(QCursor::pos()));
if (index >= 0)
QToolTip::showText(QCursor::pos(), tabToolTip(index));
}
break;
case QEvent::Resize:
ensureVisible();
break;
case QEvent::Show:
if (!event->spontaneous())
QTimer::singleShot(0, this, &ComboTabBar::setUpLayout);
break;
case QEvent::Enter:
case QEvent::Leave:
// Make sure tabs are painted with correct mouseover state
QTimer::singleShot(100, this, &ComboTabBar::updateTabBars);
break;
default:
break;
}
return res;
}
void ComboTabBar::wheelEvent(QWheelEvent* event)
{
event->accept();
if (qzSettings->alwaysSwitchTabsWithWheel || (!m_mainTabBarWidget->isOverflowed() && !m_pinnedTabBarWidget->isOverflowed())) {
m_wheelHelper.processEvent(event);
while (WheelHelper::Direction direction = m_wheelHelper.takeDirection()) {
switch (direction) {
case WheelHelper::WheelUp:
case WheelHelper::WheelLeft:
setCurrentNextEnabledIndex(-1);
break;
case WheelHelper::WheelDown:
case WheelHelper::WheelRight:
setCurrentNextEnabledIndex(1);
break;
default:
break;
}
}
return;
}
if (m_mainTabBarWidget->underMouse()) {
if (m_mainTabBarWidget->isOverflowed()) {
m_mainTabBarWidget->scrollByWheel(event);
}
else if (m_pinnedTabBarWidget->isOverflowed()) {
m_pinnedTabBarWidget->scrollByWheel(event);
}
}
else if (m_pinnedTabBarWidget->underMouse()) {
if (m_pinnedTabBarWidget->isOverflowed()) {
m_pinnedTabBarWidget->scrollByWheel(event);
}
else if (m_mainTabBarWidget->isOverflowed()) {
m_mainTabBarWidget->scrollByWheel(event);
}
}
}
bool ComboTabBar::eventFilter(QObject* obj, QEvent* ev)
{
if (obj == m_mainTabBar && ev->type() == QEvent::Resize) {
QResizeEvent* event = static_cast<QResizeEvent*>(ev);
if (event->oldSize().height() != event->size().height()) {
setUpLayout();
}
}
// Handle wheel events exclusively in ComboTabBar
if (ev->type() == QEvent::Wheel) {
wheelEvent(static_cast<QWheelEvent*>(ev));
return true;
}
return QWidget::eventFilter(obj, ev);
}
void ComboTabBar::paintEvent(QPaintEvent* ev)
{
Q_UNUSED(ev);
// This is needed to apply style sheets
QStyleOption option;
option.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &option, &p, this);
#ifndef Q_OS_MACOS
// Draw tabbar base even on parts of ComboTabBar that are not directly QTabBar
QStyleOptionTabBarBase opt;
TabBarHelper::initStyleBaseOption(&opt, m_mainTabBar, size());
// Left container
opt.rect.setX(m_leftContainer->x());
opt.rect.setWidth(m_leftContainer->width());
style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
// Right container
opt.rect.setX(m_rightContainer->x());
opt.rect.setWidth(m_rightContainer->width());
style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
if (m_mainBarOverFlowed) {
const int scrollButtonWidth = m_mainTabBarWidget->scrollButtonsWidth();
// Left scroll button
opt.rect.setX(m_mainTabBarWidget->x());
opt.rect.setWidth(scrollButtonWidth);
style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
// Right scroll button
opt.rect.setX(m_mainTabBarWidget->x() + m_mainTabBarWidget->width() - scrollButtonWidth);
opt.rect.setWidth(scrollButtonWidth);
style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
}
// Draw base even when main tabbar is empty
if (normalTabsCount() == 0) {
opt.rect.setX(m_mainTabBarWidget->x());
opt.rect.setWidth(m_mainTabBarWidget->width());
style()->drawPrimitive(QStyle::PE_FrameTabBarBase, &opt, &p);
}
#endif
}
int ComboTabBar::comboTabBarPixelMetric(ComboTabBar::SizeType sizeType) const
{
switch (sizeType) {
case ExtraReservedWidth:
return 0;
case NormalTabMaximumWidth:
return 150;
case ActiveTabMinimumWidth:
case NormalTabMinimumWidth:
case OverflowedTabWidth:
return 100;
case PinnedTabWidth:
return 30;
default:
break;
}
return -1;
}
QTabBar::ButtonPosition ComboTabBar::iconButtonPosition() const
{
return (closeButtonPosition() == QTabBar::RightSide ? QTabBar::LeftSide : QTabBar::RightSide);
}
QTabBar::ButtonPosition ComboTabBar::closeButtonPosition() const
{
return (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, m_mainTabBar);
}
QSize ComboTabBar::iconButtonSize() const
{
QSize s = closeButtonSize();
s.setWidth(qMax(16, s.width()));
s.setHeight(qMax(16, s.height()));
return s;
}
QSize ComboTabBar::closeButtonSize() const
{
int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this);
int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this);
return QSize(width, height);
}
bool ComboTabBar::validIndex(int index) const
{
return (index >= 0 && index < count());
}
void ComboTabBar::setCurrentNextEnabledIndex(int offset)
{
for (int index = currentIndex() + offset; validIndex(index); index += offset) {
if (isTabEnabled(index)) {
setCurrentIndex(index);
break;
}
}
}
bool ComboTabBar::usesScrollButtons() const
{
return m_mainTabBarWidget->usesScrollButtons();
}
void ComboTabBar::setUsesScrollButtons(bool useButtons)
{
m_mainTabBarWidget->setUsesScrollButtons(useButtons);
}
void ComboTabBar::showDropIndicator(int index, DropIndicatorPosition position)
{
clearDropIndicator();
localTabBar(index)->showDropIndicator(toLocalIndex(index), position);
}
void ComboTabBar::clearDropIndicator()
{
m_mainTabBar->clearDropIndicator();
m_pinnedTabBar->clearDropIndicator();
}
bool ComboTabBar::isDragInProgress() const
{
return m_mainTabBar->isDragInProgress() || m_pinnedTabBar->isDragInProgress();
}
bool ComboTabBar::isScrollInProgress() const
{
return m_mainTabBarWidget->scrollBar()->isScrolling() || m_pinnedTabBarWidget->scrollBar()->isScrolling();
}
bool ComboTabBar::isMainBarOverflowed() const
{
return m_mainBarOverFlowed;
}
int ComboTabBar::cornerWidth(Qt::Corner corner) const
{
if (corner == Qt::TopLeftCorner) {
return m_leftContainer->width();
}
else if (corner == Qt::TopRightCorner) {
return m_rightContainer->width();
}
qFatal("ComboTabBar::cornerWidth Only TopLeft and TopRight corners are implemented!");
return -1;
}
void ComboTabBar::addCornerWidget(QWidget* widget, Qt::Corner corner)
{
if (corner == Qt::TopLeftCorner) {
m_leftLayout->addWidget(widget);
}
else if (corner == Qt::TopRightCorner) {
m_rightLayout->addWidget(widget);
}
else {
qFatal("ComboTabBar::addCornerWidget Only TopLeft and TopRight corners are implemented!");
}
}
// static
int ComboTabBar::slideAnimationDuration()
{
// taken from qtabbar_p.h
return 250;
}
void ComboTabBar::ensureVisible(int index, int xmargin)
{
if (index == -1) {
index = currentIndex();
}
if (index < pinnedTabsCount()) {
if (xmargin == -1) {
xmargin = qMax(20, comboTabBarPixelMetric(PinnedTabWidth));
}
m_pinnedTabBarWidget->ensureVisible(index, xmargin);
}
else {
if (xmargin == -1) {
xmargin = comboTabBarPixelMetric(OverflowedTabWidth);
}
index -= pinnedTabsCount();
m_mainTabBarWidget->ensureVisible(index, xmargin);
}
}
QSize ComboTabBar::tabSizeHint(int index, bool fast) const
{
Q_UNUSED(fast)
return localTabBar(index)->baseClassTabSizeHint(toLocalIndex(index));
}
void ComboTabBar::tabInserted(int index)
{
Q_UNUSED(index)
}
void ComboTabBar::tabRemoved(int index)
{
Q_UNUSED(index)
}
TabBarHelper* ComboTabBar::mainTabBar() const
{
return m_mainTabBar;
}
TabBarHelper* ComboTabBar::localTabBar(int index) const
{
if (index < 0 || index >= pinnedTabsCount()) {
return m_mainTabBar;
}
else {
return m_pinnedTabBar;
}
}
int ComboTabBar::toLocalIndex(int globalIndex) const
{
if (globalIndex < 0) {
return -1;
}
if (globalIndex >= pinnedTabsCount()) {
return globalIndex - pinnedTabsCount();
}
else {
return globalIndex;
}
}
QRect ComboTabBar::mapFromLocalTabRect(const QRect &rect, QWidget *tabBar) const
{
if (!rect.isValid()) {
return rect;
}
QRect r = rect;
if (tabBar == m_mainTabBar) {
r.moveLeft(r.x() + mapFromGlobal(m_mainTabBar->mapToGlobal(QPoint(0, 0))).x());
QRect widgetRect = m_mainTabBarWidget->scrollArea()->viewport()->rect();
widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_mainTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
r = r.intersected(widgetRect);
} else {
r.moveLeft(r.x() + mapFromGlobal(m_pinnedTabBar->mapToGlobal(QPoint(0, 0))).x());
QRect widgetRect = m_pinnedTabBarWidget->scrollArea()->viewport()->rect();
widgetRect.moveLeft(widgetRect.x() + mapFromGlobal(m_pinnedTabBarWidget->scrollArea()->viewport()->mapToGlobal(QPoint(0, 0))).x());
r = r.intersected(widgetRect);
}
return r;
}
void ComboTabBar::updatePinnedTabBarVisibility()
{
m_pinnedTabBarWidget->setVisible(pinnedTabsCount() > 0);
}
void ComboTabBar::setMinimumWidths()
{
if (!isVisible() || comboTabBarPixelMetric(PinnedTabWidth) < 0) {
return;
}
const int tabBarsSpacing = 3; // To distinguish tabbars
int pinnedTabBarWidth = pinnedTabsCount() * comboTabBarPixelMetric(PinnedTabWidth);
m_pinnedTabBar->setMinimumWidth(pinnedTabBarWidth);
m_pinnedTabBarWidget->setFixedWidth(pinnedTabBarWidth + tabBarsSpacing);
// Width that is needed by main tabbar
int mainTabBarWidth = comboTabBarPixelMetric(NormalTabMinimumWidth) * (m_mainTabBar->count() - 1) +
comboTabBarPixelMetric(ActiveTabMinimumWidth) +
comboTabBarPixelMetric(ExtraReservedWidth);
// This is the full width that would be needed for the tabbar (including pinned tabbar and corner widgets)
int realTabBarWidth = mainTabBarWidth + m_pinnedTabBarWidget->width() +
cornerWidth(Qt::TopLeftCorner) +
cornerWidth(Qt::TopRightCorner);
// Does it fit in our widget?
if (realTabBarWidth <= width()) {
if (m_mainBarOverFlowed) {
m_mainBarOverFlowed = false;
QTimer::singleShot(0, this, SLOT(emitOverFlowChanged()));
}
m_mainTabBar->useFastTabSizeHint(false);
m_mainTabBar->setMinimumWidth(mainTabBarWidth);
}
else {
if (!m_mainBarOverFlowed) {
m_mainBarOverFlowed = true;
QTimer::singleShot(0, this, SLOT(emitOverFlowChanged()));
}
// All tabs have now same width, we can use fast tabSizeHint
m_mainTabBar->useFastTabSizeHint(true);
m_mainTabBar->setMinimumWidth(m_mainTabBar->count() * comboTabBarPixelMetric(OverflowedTabWidth));
}
}
TabBarHelper::TabBarHelper(bool isPinnedTabBar, ComboTabBar* comboTabBar)
: QTabBar(comboTabBar)
, m_comboTabBar(comboTabBar)
, m_scrollArea(0)
, m_pressedIndex(-1)
, m_dragInProgress(false)
, m_activeTabBar(false)
, m_isPinnedTabBar(isPinnedTabBar)
, m_useFastTabSizeHint(false)
{
}
int TabBarHelper::tabPadding() const
{
return m_tabPadding;
}
void TabBarHelper::setTabPadding(int padding)
{
m_tabPadding = padding;
}
QColor TabBarHelper::baseColor() const
{
return m_baseColor;
}
void TabBarHelper::setBaseColor(const QColor &color)
{
m_baseColor = color;
}
void TabBarHelper::setTabButton(int index, QTabBar::ButtonPosition position, QWidget* widget)
{
QTabBar::setTabButton(index, position, widget);
}
QSize TabBarHelper::tabSizeHint(int index) const
{
if (this == m_comboTabBar->mainTabBar()) {
index += m_comboTabBar->pinnedTabsCount();
}
return m_comboTabBar->tabSizeHint(index, m_useFastTabSizeHint);
}
QSize TabBarHelper::baseClassTabSizeHint(int index) const
{
return QTabBar::tabSizeHint(index);
}
QRect TabBarHelper::draggedTabRect() const
{
if (!m_dragInProgress) {
return QRect();
}
QStyleOptionTab tab;
initStyleOption(&tab, m_pressedIndex);
const int tabDragOffset = dragOffset(&tab, m_pressedIndex);
if (tabDragOffset != 0) {
tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
}
return tab.rect;
}
QPixmap TabBarHelper::tabPixmap(int index) const
{
QStyleOptionTab tab;
initStyleOption(&tab, index);
tab.state &= ~QStyle::State_MouseOver;
tab.position = QStyleOptionTab::OnlyOneTab;
tab.leftButtonSize = QSize();
tab.rightButtonSize = QSize();
QWidget *iconButton = tabButton(index, m_comboTabBar->iconButtonPosition());
QWidget *closeButton = tabButton(index, m_comboTabBar->closeButtonPosition());
if (iconButton) {
const QPixmap pix = iconButton->grab();
if (!pix.isNull()) {
tab.icon = pix;
tab.iconSize = pix.size() / pix.devicePixelRatioF();
}
}
if (closeButton) {
const int width = tab.fontMetrics.width(tab.text) + closeButton->width();
tab.text = tab.fontMetrics.elidedText(tabText(index), Qt::ElideRight, width);
}
QPixmap out(tab.rect.size() * devicePixelRatioF());
out.setDevicePixelRatio(devicePixelRatioF());
out.fill(Qt::transparent);
tab.rect = QRect(QPoint(0, 0), tab.rect.size());
QPainter p(&out);
style()->drawControl(QStyle::CE_TabBarTab, &tab, &p, this);
p.end();
return out;
}
bool TabBarHelper::isActiveTabBar()
{
return m_activeTabBar;
}
void TabBarHelper::setActiveTabBar(bool activate)
{
if (m_activeTabBar != activate) {
m_activeTabBar = activate;
// If the last tab in a tabbar is closed, the selection jumps to the other
// tabbar. The stacked widget automatically selects the next tab, which is
// either the last tab in pinned tabbar or the first one in main tabbar.
if (!m_activeTabBar) {
m_comboTabBar->m_blockCurrentChangedSignal = true;
setCurrentIndex(m_isPinnedTabBar ? count() - 1 : 0);
m_comboTabBar->m_blockCurrentChangedSignal = false;
}
update();
}
}
void TabBarHelper::removeTab(int index)
{
// Removing tab in inactive tabbar will change current index and thus
// changing active tabbar, which is really not wanted.
// Also removing tab will cause a duplicate call to ComboTabBar::slotCurrentChanged()
m_comboTabBar->m_blockCurrentChangedSignal = true;
QTabBar::removeTab(index);
m_comboTabBar->m_blockCurrentChangedSignal = false;
}
void TabBarHelper::setScrollArea(QScrollArea* scrollArea)
{
m_scrollArea = scrollArea;
}
void TabBarHelper::useFastTabSizeHint(bool enabled)
{
m_useFastTabSizeHint = enabled;
}
void TabBarHelper::showDropIndicator(int index, ComboTabBar::DropIndicatorPosition position)
{
m_dropIndicatorIndex = index;
m_dropIndicatorPosition = position;
update();
}
void TabBarHelper::clearDropIndicator()
{
m_dropIndicatorIndex = -1;
update();
}
bool TabBarHelper::isDisplayedOnViewPort(int globalLeft, int globalRight)
{
bool isVisible = true;
if (m_scrollArea) {
if (globalRight < m_scrollArea->viewport()->mapToGlobal(QPoint(0, 0)).x() ||
globalLeft > m_scrollArea->viewport()->mapToGlobal(m_scrollArea->viewport()->rect().topRight()).x()
) {
isVisible = false;
}
}
return isVisible;
}
bool TabBarHelper::isDragInProgress() const
{
return m_dragInProgress;
}
void TabBarHelper::setCurrentIndex(int index)
{
if (index == currentIndex() && !m_activeTabBar) {
emit currentChanged(currentIndex());
}
QTabBar::setCurrentIndex(index);
}
bool TabBarHelper::event(QEvent* ev)
{
switch (ev->type()) {
case QEvent::ToolTip:
ev->ignore();
return false;
default:
break;
}
QTabBar::event(ev);
ev->ignore();
return false;
}
// Hack to get dragOffset from QTabBar internals
int TabBarHelper::dragOffset(QStyleOptionTab *option, int tabIndex) const
{
QRect rect;
QWidget *button = tabButton(tabIndex, QTabBar::LeftSide);
if (button) {
rect = style()->subElementRect(QStyle::SE_TabBarTabLeftButton, option, this);
}
if (!rect.isValid()) {
button = tabButton(tabIndex, QTabBar::RightSide);
rect = style()->subElementRect(QStyle::SE_TabBarTabRightButton, option, this);
}
if (!button || !rect.isValid()) {
return 0;
}
return button->pos().x() - rect.topLeft().x();
}
// Taken from qtabbar.cpp
void TabBarHelper::initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar* tabbar, QSize size)
{
QStyleOptionTab tabOverlap;
tabOverlap.shape = tabbar->shape();
int overlap = tabbar->style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, tabbar);
QWidget* theParent = tabbar->parentWidget();
optTabBase->init(tabbar);
optTabBase->shape = tabbar->shape();
optTabBase->documentMode = tabbar->documentMode();
if (theParent && overlap > 0) {
QRect rect;
switch (tabOverlap.shape) {
case QTabBar::RoundedNorth:
case QTabBar::TriangularNorth:
rect.setRect(0, size.height() - overlap, size.width(), overlap);
break;
case QTabBar::RoundedSouth:
case QTabBar::TriangularSouth:
rect.setRect(0, 0, size.width(), overlap);
break;
case QTabBar::RoundedEast:
case QTabBar::TriangularEast:
rect.setRect(0, 0, overlap, size.height());
break;
case QTabBar::RoundedWest:
case QTabBar::TriangularWest:
rect.setRect(size.width() - overlap, 0, overlap, size.height());
break;
}
optTabBase->rect = rect;
}
}
// Adapted from qtabbar.cpp
// Note: doesn't support vertical tabs
void TabBarHelper::paintEvent(QPaintEvent *)
{
QStyleOptionTabBarBase optTabBase;
initStyleBaseOption(&optTabBase, this, size());
QStylePainter p(this);
int selected = currentIndex();
for (int i = 0; i < count(); ++i) {
optTabBase.tabBarRect |= tabRect(i);
}
if (m_activeTabBar) {
optTabBase.selectedTabRect = tabRect(selected);
}
if (drawBase()) {
p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase);
}
const QPoint cursorPos = QCursor::pos();
int indexUnderMouse = isDisplayedOnViewPort(cursorPos.x(), cursorPos.x()) ? tabAt(mapFromGlobal(cursorPos)) : -1;
for (int i = 0; i < count(); ++i) {
if (i == selected) {
continue;
}
QStyleOptionTab tab;
initStyleOption(&tab, i);
const int tabDragOffset = dragOffset(&tab, i);
if (tabDragOffset != 0) {
tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
}
// Don't bother drawing a tab if the entire tab is outside of the visible tab bar.
if (!isDisplayedOnViewPort(mapToGlobal(tab.rect.topLeft()).x(), mapToGlobal(tab.rect.topRight()).x())) {
continue;
}
if (!m_activeTabBar) {
tab.selectedPosition = QStyleOptionTab::NotAdjacent;
}
if (!(tab.state & QStyle::State_Enabled)) {
tab.palette.setCurrentColorGroup(QPalette::Disabled);
}
// Update mouseover state when scrolling
if (!m_dragInProgress && i == indexUnderMouse) {
tab.state |= QStyle::State_MouseOver;
} else {
tab.state &= ~QStyle::State_MouseOver;
}
p.drawControl(QStyle::CE_TabBarTab, tab);
}
// Draw the selected tab last to get it "on top"
if (selected >= 0) {
QStyleOptionTab tab;
initStyleOption(&tab, selected);
const int tabDragOffset = dragOffset(&tab, selected);
if (tabDragOffset != 0) {
tab.rect.moveLeft(tab.rect.x() + tabDragOffset);
}
// Update mouseover state when scrolling
if (selected == indexUnderMouse) {
tab.state |= QStyle::State_MouseOver;
} else {
tab.state &= ~QStyle::State_MouseOver;
}
if (!m_activeTabBar) {
// If this is inactive tab, we still need to draw selected tab outside the tabbar
// Some themes (eg. Oxygen) draws line under tabs with selected tab
// Let's just move it outside rect(), it appears to work
QStyleOptionTab tb = tab;
tb.rect.moveRight((rect().x() + rect().width()) * 2);
p.drawControl(QStyle::CE_TabBarTab, tb);
// Draw the tab without selected state
tab.state = tab.state & ~QStyle::State_Selected;
}
if (!m_movingTab || !m_movingTab->isVisible()) {
p.drawControl(QStyle::CE_TabBarTab, tab);
} else {
int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
m_movingTab->setGeometry(tab.rect.adjusted(-taboverlap, 0, taboverlap, 0));
QRect grabRect = tabRect(selected);
grabRect.adjust(-taboverlap, 0, taboverlap, 0);
QPixmap grabImage(grabRect.size() * devicePixelRatioF());
grabImage.setDevicePixelRatio(devicePixelRatioF());
grabImage.fill(Qt::transparent);
QStylePainter p(&grabImage, this);
p.initFrom(this);
if (tabDragOffset != 0) {
tab.position = QStyleOptionTab::OnlyOneTab;
}
tab.rect.moveTopLeft(QPoint(taboverlap, 0));
p.drawControl(QStyle::CE_TabBarTab, tab);
m_movingTab->m_pixmap = grabImage;
m_movingTab->update();
}
}
// Draw drop indicator
if (m_dropIndicatorIndex != -1) {
const QRect tr = tabRect(m_dropIndicatorIndex);
QRect r;
if (m_dropIndicatorPosition == ComboTabBar::BeforeTab) {
r = QRect(qMax(0, tr.left() - 1), tr.top(), 3, tr.height());
} else {
const int rightOffset = m_dropIndicatorIndex == count() - 1 ? -2 : 0;
r = QRect(tr.right() + rightOffset, tr.top(), 3, tr.height());
}
// Modified code from KFilePlacesView
QColor color = palette().brush(QPalette::Normal, QPalette::Highlight).color();
const int x = (r.left() + r.right()) / 2;
const int thickness = qRound(r.width() / 2.0);
int alpha = 255;
const int alphaDec = alpha / (thickness + 1);
for (int i = 0; i < thickness; i++) {
color.setAlpha(alpha);
alpha -= alphaDec;
p.setPen(color);
p.drawLine(x - i, r.top(), x - i, r.bottom());
p.drawLine(x + i, r.top(), x + i, r.bottom());
}
}
}
void TabBarHelper::mousePressEvent(QMouseEvent* event)
{
event->ignore();
if (event->buttons() == Qt::LeftButton) {
m_pressedIndex = tabAt(event->pos());
if (m_pressedIndex != -1) {
m_dragStartPosition = event->pos();
// virtualize selecting tab by click
if (m_pressedIndex == currentIndex() && !m_activeTabBar) {
emit currentChanged(currentIndex());
}
}
}
QTabBar::mousePressEvent(event);
}
void TabBarHelper::mouseMoveEvent(QMouseEvent *event)
{
if (!m_dragInProgress && m_pressedIndex != -1) {
if ((event->pos() - m_dragStartPosition).manhattanLength() > QApplication::startDragDistance()) {
m_dragInProgress = true;
}
}
QTabBar::mouseMoveEvent(event);
// Hack to find QMovableTabWidget
if (m_dragInProgress && !m_movingTab) {
const auto objects = children();
const int taboverlap = style()->pixelMetric(QStyle::PM_TabBarTabOverlap, nullptr, this);
QRect grabRect = tabRect(currentIndex());
grabRect.adjust(-taboverlap, 0, taboverlap, 0);
for (QObject *object : objects) {
QWidget *widget = qobject_cast<QWidget*>(object);
if (widget && widget->geometry() == grabRect) {
m_movingTab = static_cast<QMovableTabWidget*>(widget);
break;
}
}
}
// Don't allow to move tabs outside of tabbar
if (m_dragInProgress && m_movingTab) {
// FIXME: This doesn't work at all with RTL...
if (isRightToLeft()) {
return;
}
QRect r = tabRect(m_pressedIndex);
r.moveLeft(r.x() + (event->pos().x() - m_dragStartPosition.x()));
bool sendEvent = false;
int diff = r.topRight().x() - tabRect(count() - 1).topRight().x();
if (diff > 0) {
sendEvent = true;
} else {
diff = r.topLeft().x() - tabRect(0).topLeft().x();
if (diff < 0) {
sendEvent = true;
}
}
if (sendEvent) {
QPoint pos = event->pos();
pos.setX(pos.x() - diff);
QMouseEvent ev(event->type(), pos, event->button(), event->buttons(), event->modifiers());
QTabBar::mouseMoveEvent(&ev);
}
}
}
void TabBarHelper::mouseReleaseEvent(QMouseEvent* event)
{
event->ignore();
if (event->button() == Qt::LeftButton) {
m_pressedIndex = -1;
m_dragInProgress = false;
m_dragStartPosition = QPoint();
}
QTabBar::mouseReleaseEvent(event);
update();
}
void TabBarHelper::initStyleOption(QStyleOptionTab* option, int tabIndex) const
{
QTabBar::initStyleOption(option, tabIndex);
// Workaround zero padding when tabs are styled using style sheets
if (m_tabPadding) {
const QRect textRect = style()->subElementRect(QStyle::SE_TabBarTabText, option, this);
const int width = textRect.width() - 2 * m_tabPadding;
option->text = option->fontMetrics.elidedText(tabText(tabIndex), elideMode(), width, Qt::TextShowMnemonic);
}
// Bespin doesn't highlight current tab when there is only one tab in tabbar
static int isBespin = -1;
if (isBespin == -1)
isBespin = mApp->styleName() == QL1S("bespin");
if (!isBespin)
return;
int index = m_isPinnedTabBar ? tabIndex : m_comboTabBar->pinnedTabsCount() + tabIndex;
if (m_comboTabBar->count() > 1) {
if (index == 0)
option->position = QStyleOptionTab::Beginning;
else if (index == m_comboTabBar->count() - 1)
option->position = QStyleOptionTab::End;
else
option->position = QStyleOptionTab::Middle;
}
else {
option->position = QStyleOptionTab::OnlyOneTab;
}
}
TabScrollBar::TabScrollBar(QWidget* parent)
: QScrollBar(Qt::Horizontal, parent)
{
m_animation = new QPropertyAnimation(this, "value", this);
}
TabScrollBar::~TabScrollBar()
{
}
bool TabScrollBar::isScrolling() const
{
return m_animation->state() == QPropertyAnimation::Running;
}
void TabScrollBar::animateToValue(int to, QEasingCurve::Type type)
{
to = qBound(minimum(), to, maximum());
int lenght = qAbs(to - value());
int duration = qMin(1500, 200 + lenght / 2);
m_animation->stop();
m_animation->setEasingCurve(type);
m_animation->setDuration(duration);
m_animation->setStartValue(value());
m_animation->setEndValue(to);
m_animation->start();
}
TabBarScrollWidget::TabBarScrollWidget(QTabBar* tabBar, QWidget* parent)
: QWidget(parent)
, m_tabBar(tabBar)
, m_usesScrollButtons(false)
, m_totalDeltas(0)
{
m_scrollArea = new QScrollArea(this);
m_scrollArea->setFocusPolicy(Qt::NoFocus);
m_scrollArea->setFrameStyle(QFrame::NoFrame);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_scrollBar = new TabScrollBar(m_scrollArea);
m_scrollArea->setHorizontalScrollBar(m_scrollBar);
m_scrollArea->setWidget(m_tabBar);
m_leftScrollButton = new ToolButton(this);
m_leftScrollButton->setFocusPolicy(Qt::NoFocus);
m_leftScrollButton->setAutoRaise(true);
m_leftScrollButton->setObjectName("tabbar-button-left");
m_leftScrollButton->setAutoRepeat(true);
m_leftScrollButton->setAutoRepeatDelay(200);
m_leftScrollButton->setAutoRepeatInterval(200);
connect(m_leftScrollButton, SIGNAL(pressed()), this, SLOT(scrollStart()));
connect(m_leftScrollButton, SIGNAL(doubleClicked()), this, SLOT(scrollToLeftEdge()));
connect(m_leftScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible()));
m_rightScrollButton = new ToolButton(this);
m_rightScrollButton->setFocusPolicy(Qt::NoFocus);
m_rightScrollButton->setAutoRaise(true);
m_rightScrollButton->setObjectName("tabbar-button-right");
m_rightScrollButton->setAutoRepeat(true);
m_rightScrollButton->setAutoRepeatDelay(200);
m_rightScrollButton->setAutoRepeatInterval(200);
connect(m_rightScrollButton, SIGNAL(pressed()), this, SLOT(scrollStart()));
connect(m_rightScrollButton, SIGNAL(doubleClicked()), this, SLOT(scrollToRightEdge()));
connect(m_rightScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible()));
QHBoxLayout* hLayout = new QHBoxLayout;
hLayout->setSpacing(0);
hLayout->setContentsMargins(0, 0, 0, 0);
hLayout->addWidget(m_leftScrollButton);
hLayout->addWidget(m_scrollArea);
hLayout->addWidget(m_rightScrollButton);
setLayout(hLayout);
m_scrollArea->viewport()->setAutoFillBackground(false);
connect(m_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(updateScrollButtonsState()));
updateScrollButtonsState();
overFlowChanged(false);
}
QTabBar* TabBarScrollWidget::tabBar()
{
return m_tabBar;
}
QScrollArea* TabBarScrollWidget::scrollArea()
{
return m_scrollArea;
}
TabScrollBar* TabBarScrollWidget::scrollBar()
{
return m_scrollBar;
}
void TabBarScrollWidget::ensureVisible(int index, int xmargin)
{
if (index == -1) {
index = m_tabBar->currentIndex();
}
if (index < 0 || index >= m_tabBar->count()) {
return;
}
xmargin = qMin(xmargin, m_scrollArea->viewport()->width() / 2);
// Qt Bug? the following lines were taken from QScrollArea::ensureVisible() and
// then were fixed. The original version caculates wrong values in RTL layouts.
const QRect logicalTabRect = QStyle::visualRect(m_tabBar->layoutDirection(), m_tabBar->rect(), m_tabBar->tabRect(index));
int logicalX = QStyle::visualPos(Qt::LeftToRight, m_scrollArea->viewport()->rect(), logicalTabRect.center()).x();
if (logicalX - xmargin < m_scrollBar->value()) {
m_scrollBar->animateToValue(qMax(0, logicalX - xmargin));
}
else if (logicalX > m_scrollBar->value() + m_scrollArea->viewport()->width() - xmargin) {
m_scrollBar->animateToValue(qMin(logicalX - m_scrollArea->viewport()->width() + xmargin,
m_scrollBar->maximum()));
}
}
void TabBarScrollWidget::scrollToLeft(int n, QEasingCurve::Type type)
{
n = qMax(1, n);
m_scrollBar->animateToValue(m_scrollBar->value() - n * m_scrollBar->singleStep(), type);
}
void TabBarScrollWidget::scrollToRight(int n, QEasingCurve::Type type)
{
n = qMax(1, n);
m_scrollBar->animateToValue(m_scrollBar->value() + n * m_scrollBar->singleStep(), type);
}
void TabBarScrollWidget::scrollToLeftEdge()
{
m_scrollBar->animateToValue(m_scrollBar->minimum());
}
void TabBarScrollWidget::scrollToRightEdge()
{
m_scrollBar->animateToValue(m_scrollBar->maximum());
}
void TabBarScrollWidget::setUpLayout()
{
const int height = m_tabBar->height();
setFixedHeight(height);
}
void TabBarScrollWidget::updateScrollButtonsState()
{
m_leftScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->minimum());
m_rightScrollButton->setEnabled(m_scrollBar->value() != m_scrollBar->maximum());
}
void TabBarScrollWidget::overFlowChanged(bool overflowed)
{
bool showScrollButtons = overflowed && m_usesScrollButtons;
m_leftScrollButton->setVisible(showScrollButtons);
m_rightScrollButton->setVisible(showScrollButtons);
}
void TabBarScrollWidget::scrollStart()
{
bool ctrlModifier = QApplication::keyboardModifiers() & Qt::ControlModifier;
if (sender() == m_leftScrollButton) {
if (ctrlModifier) {
scrollToLeftEdge();
}
else {
scrollToLeft(5, QEasingCurve::Linear);
}
}
else if (sender() == m_rightScrollButton) {
if (ctrlModifier) {
scrollToRightEdge();
}
else {
scrollToRight(5, QEasingCurve::Linear);
}
}
}
void TabBarScrollWidget::scrollByWheel(QWheelEvent* event)
{
event->accept();
// Check if direction has changed from last time
if (m_totalDeltas * event->delta() < 0) {
m_totalDeltas = 0;
}
m_totalDeltas += event->delta();
// Slower scrolling for horizontal wheel scrolling
if (event->orientation() == Qt::Horizontal) {
if (event->delta() > 0) {
scrollToLeft();
}
else if (event->delta() < 0) {
scrollToRight();
}
return;
}
// Faster scrolling with control modifier
if (event->orientation() == Qt::Vertical && event->modifiers() == Qt::ControlModifier) {
if (event->delta() > 0) {
scrollToLeft(10);
}
else if (event->delta() < 0) {
scrollToRight(10);
}
return;
}
// Fast scrolling with just wheel scroll
int factor = qMax(qRound(m_scrollBar->pageStep() / 1.5), m_scrollBar->singleStep());
if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
factor = m_scrollBar->pageStep();
}
int offset = (m_totalDeltas / 120) * factor;
if (offset != 0) {
if (isRightToLeft()) {
m_scrollBar->animateToValue(m_scrollBar->value() + offset);
}
else {
m_scrollBar->animateToValue(m_scrollBar->value() - offset);
}
m_totalDeltas -= (offset / factor) * 120;
}
}
int TabBarScrollWidget::scrollButtonsWidth() const
{
// Assumes both buttons have the same width
return m_leftScrollButton->width();
}
bool TabBarScrollWidget::usesScrollButtons() const
{
return m_usesScrollButtons;
}
void TabBarScrollWidget::setUsesScrollButtons(bool useButtons)
{
if (useButtons != m_usesScrollButtons) {
m_usesScrollButtons = useButtons;
updateScrollButtonsState();
m_tabBar->setElideMode(m_tabBar->elideMode());
}
}
bool TabBarScrollWidget::isOverflowed() const
{
return m_tabBar->count() > 0 && m_scrollBar->minimum() != m_scrollBar->maximum();
}
int TabBarScrollWidget::tabAt(const QPoint &pos) const
{
if (m_leftScrollButton->isVisible() && (m_leftScrollButton->rect().contains(pos) ||
m_rightScrollButton->rect().contains(pos))) {
return -1;
}
return m_tabBar->tabAt(m_tabBar->mapFromGlobal(mapToGlobal(pos)));
}
void TabBarScrollWidget::mouseMoveEvent(QMouseEvent* event)
{
event->ignore();
}
void TabBarScrollWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateScrollButtonsState();
}
CloseButton::CloseButton(QWidget* parent)
: QAbstractButton(parent)
{
setObjectName("combotabbar_tabs_close_button");
setFocusPolicy(Qt::NoFocus);
setCursor(Qt::ArrowCursor);
}
void CloseButton::enterEvent(QEvent* event)
{
if (isEnabled()) {
update();
}
QAbstractButton::enterEvent(event);
}
void CloseButton::leaveEvent(QEvent* event)
{
if (isEnabled()) {
update();
}
QAbstractButton::leaveEvent(event);
}
void CloseButton::paintEvent(QPaintEvent*)
{
QPainter p(this);
QStyleOption opt;
opt.init(this);
opt.state |= QStyle::State_AutoRaise;
// update raised state on scrolling
bool isUnderMouse = rect().contains(mapFromGlobal(QCursor::pos()));
if (isEnabled() && isUnderMouse && !isChecked() && !isDown()) {
opt.state |= QStyle::State_Raised;
}
if (isChecked()) {
opt.state |= QStyle::State_On;
}
if (isDown()) {
opt.state |= QStyle::State_Sunken;
}
if (TabBarHelper* tb = qobject_cast<TabBarHelper*>(parent())) {
int index = tb->currentIndex();
QTabBar::ButtonPosition closeSide = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb);
if (tb->tabButton(index, closeSide) == this && tb->isActiveTabBar()) {
opt.state |= QStyle::State_Selected;
}
}
style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
}