/* ============================================================
* 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 "qztools.h"

#include <QIcon>
#include <QHBoxLayout>
#include <QStylePainter>
#include <QStyleOptionTab>
#include <QStyleOptionTabBarBase>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QTimer>
#include <QMouseEvent>
#include <QApplication>
#include <QToolTip>
#include <QtGuiVersion>

class QMovableTabWidget : public QWidget
{
public:
    QPixmap m_pixmap;
};

ComboTabBar::ComboTabBar(QWidget* parent)
    : QWidget(parent)
    , m_mainTabBar(nullptr)
    , m_pinnedTabBar(nullptr)
    , 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(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
    connect(m_mainTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
    connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::rangeChanged, this, &ComboTabBar::setMinimumWidths);
    connect(m_pinnedTabBarWidget->scrollBar(), &QAbstractSlider::valueChanged, this, &ComboTabBar::scrollBarValueChanged);
    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, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
    connect(m_mainTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
    connect(m_mainTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);

    connect(m_pinnedTabBar, &QTabBar::currentChanged, this, &ComboTabBar::slotCurrentChanged);
    connect(m_pinnedTabBar, &QTabBar::tabCloseRequested, this, &ComboTabBar::slotTabCloseRequested);
    connect(m_pinnedTabBar, &QTabBar::tabMoved, this, &ComboTabBar::slotTabMoved);

    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);
            Q_EMIT currentChanged(pinnedTabsCount());
        }
        else {
            m_pinnedTabBar->setActiveTabBar(true);
            m_mainTabBar->setActiveTabBar(false);
            Q_EMIT currentChanged(index);
        }
    }
    else {
        if (index == -1 && pinnedTabsCount() > 0) {
            m_pinnedTabBar->setActiveTabBar(true);
            m_mainTabBar->setActiveTabBar(false);
            Q_EMIT currentChanged(pinnedTabsCount() - 1);
        }
        else {
            m_mainTabBar->setActiveTabBar(true);
            m_pinnedTabBar->setActiveTabBar(false);
            Q_EMIT currentChanged(index + pinnedTabsCount());
        }
    }
}

void ComboTabBar::slotTabCloseRequested(int index)
{
    if (sender() == m_pinnedTabBar) {
        Q_EMIT tabCloseRequested(index);
    }
    else {
        Q_EMIT tabCloseRequested(index + pinnedTabsCount());
    }
}

void ComboTabBar::slotTabMoved(int from, int to)
{
    if (sender() == m_pinnedTabBar) {
        Q_EMIT tabMoved(from, to);
    }
    else {
        Q_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) {
        Q_EMIT tabCloseRequested(tabToClose + pinnedTabsCount());
    }
}

void ComboTabBar::updateTabBars()
{
    m_mainTabBar->update();
    m_pinnedTabBar->update();
}

void ComboTabBar::emitOverFlowChanged()
{
    if (m_mainBarOverFlowed != m_lastAppliedOverflow) {
        Q_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());
    }

    // 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, &QAbstractButton::clicked, this, &ComboTabBar::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), this);
        }
        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) {
        auto* 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.initFrom(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, nullptr, 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, nullptr, this);
    int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, 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, &ComboTabBar::emitOverFlowChanged);
        }

        m_mainTabBar->useFastTabSizeHint(false);
        m_mainTabBar->setMinimumWidth(mainTabBarWidth);
    }
    else {
        if (!m_mainBarOverFlowed) {
            m_mainBarOverFlowed = true;
            QTimer::singleShot(0, this, &ComboTabBar::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(nullptr)
    , 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 {};
    }

    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.horizontalAdvance(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) {
        Q_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->initFrom(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);
            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());
        }
        QzTools::paintDropIndicator(this, r);
    }
}

void TabBarHelper::mousePressEvent(QMouseEvent* event)
{
    event->ignore();
    if (event->buttons() == Qt::LeftButton) {
        m_pressedIndex = tabAt(event->position().toPoint());
        if (m_pressedIndex != -1) {
            m_dragStartPosition = event->position().toPoint();
            // virtualize selecting tab by click
            if (m_pressedIndex == currentIndex() && !m_activeTabBar) {
                Q_EMIT currentChanged(currentIndex());
            }
        }
    }

    QTabBar::mousePressEvent(event);
}

void TabBarHelper::mouseMoveEvent(QMouseEvent *event)
{
    if (!m_dragInProgress && m_pressedIndex != -1) {
        if ((event->position().toPoint() - 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->position().toPoint().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->position().toPoint();
            pos.setX(pos.x() - diff);
            QMouseEvent ev(event->type(), pos, event->globalPosition(), 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()
= default;

bool TabScrollBar::isScrolling() const
{
    return m_animation->state() == QPropertyAnimation::Running;
}

void TabScrollBar::animateToValue(int to, QEasingCurve::Type type)
{
    to = qBound(minimum(), to, maximum());
    int length = qAbs(to - value());
    int duration = qMin(1500, 200 + length / 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_totalVerticalDeltas(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, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart);
    connect(m_leftScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::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, &QAbstractButton::pressed, this, &TabBarScrollWidget::scrollStart);
    connect(m_rightScrollButton, &ToolButton::doubleClicked, this, &TabBarScrollWidget::scrollToRightEdge);
    connect(m_rightScrollButton, SIGNAL(middleMouseClicked()), this, SLOT(ensureVisible()));

    auto* 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, &QAbstractSlider::valueChanged, this, &TabBarScrollWidget::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();

    // Process horizontal wheel scrolling first
    // Slower scrolling for horizontal wheel scrolling
    if (event->angleDelta().x() > 0) {
        scrollToLeft();
    }
    else if (event->angleDelta().x() < 0) {
        scrollToRight();
    }

    auto verticalDelta = event->angleDelta().y();
    if (verticalDelta == 0) {
        return;
    }

    // Check if vertical direction has changed from last time
    if (m_totalVerticalDeltas * verticalDelta < 0) {
        m_totalVerticalDeltas = 0;
    }

    m_totalVerticalDeltas += verticalDelta;

    // Faster scrolling with control modifier
    if (event->modifiers() == Qt::ControlModifier) {
        if (verticalDelta > 0) {
            scrollToLeft(10);
        }
        else if (verticalDelta < 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_totalVerticalDeltas / 120) * factor;
    if (offset != 0) {
        if (isRightToLeft()) {
            m_scrollBar->animateToValue(m_scrollBar->value() + offset);
        }
        else {
            m_scrollBar->animateToValue(m_scrollBar->value() - offset);
        }

        m_totalVerticalDeltas -= (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);
    resize(sizeHint());
}

QSize CloseButton::sizeHint() const
{
    ensurePolished();
    int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
    int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
    return QSize(width, height);
}

void CloseButton::enterEvent(QEnterEvent* 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.initFrom(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 (auto* tb = qobject_cast<TabBarHelper*>(parent())) {
        int index = tb->currentIndex();
        auto closeSide = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tb);
        if (tb->tabButton(index, closeSide) == this && tb->isActiveTabBar()) {
            opt.state |= QStyle::State_Selected;
        }
    }

    style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
}
