DragDropTabSwitcher.cpp
180 lines
| 6.1 KiB
| text/x-c
|
CppLexer
r886 | #include "DragAndDrop/DragDropTabSwitcher.h" | |||
#include <QAbstractButton> | ||||
#include <QDragEnterEvent> | ||||
#include <QDragMoveEvent> | ||||
#include <QTimer> | ||||
#include "SqpApplication.h" | ||||
Q_LOGGING_CATEGORY(LOG_DragDropTabSwitcher, "DragDropTabSwitcher") | ||||
const int CHANGE_TAB_INTERVAL = 400; // time necessary over a tab to accept the switch | ||||
const int SCROLL_BUTTON_AUTO_CLICK_INTERVAL | ||||
= 500; // time between 2 auto clicks on a scroll button of the tab bar | ||||
struct DragDropTabSwitcher::DragDropTabSwitcherPrivate { | ||||
QList<QTabBar *> m_TabBarList; | ||||
QTabBar *m_CurrentTabBar = nullptr; | ||||
int m_HoveredTabIndex = -1; | ||||
std::unique_ptr<QTimer> m_TabSwitchTimer = nullptr; | ||||
QAbstractButton *m_HoveredScrollButton = nullptr; | ||||
std::unique_ptr<QTimer> m_ScrollButtonsTimer = nullptr; | ||||
explicit DragDropTabSwitcherPrivate() | ||||
: m_TabSwitchTimer{std::make_unique<QTimer>()}, | ||||
m_ScrollButtonsTimer{std::make_unique<QTimer>()} | ||||
{ | ||||
m_TabSwitchTimer->setSingleShot(true); | ||||
m_TabSwitchTimer->setInterval(CHANGE_TAB_INTERVAL); | ||||
QObject::connect(m_TabSwitchTimer.get(), &QTimer::timeout, [this]() { | ||||
if (m_CurrentTabBar) { | ||||
m_CurrentTabBar->setCurrentIndex(m_HoveredTabIndex); | ||||
} | ||||
else { | ||||
qCWarning(LOG_DragDropTabSwitcher()) << "DragDropTabSwitcherPrivate::timeout: " | ||||
"Cannot select a new tab: unknown current " | ||||
"tab bar."; | ||||
} | ||||
}); | ||||
m_ScrollButtonsTimer->setInterval(SCROLL_BUTTON_AUTO_CLICK_INTERVAL); | ||||
QObject::connect(m_ScrollButtonsTimer.get(), &QTimer::timeout, [this]() { | ||||
if (m_HoveredScrollButton) { | ||||
m_HoveredScrollButton->animateClick(); | ||||
} | ||||
else { | ||||
qCWarning(LOG_DragDropTabSwitcher()) | ||||
<< "DragDropTabSwitcherPrivate::timeoutScroll: " | ||||
"Unknown scroll button"; | ||||
} | ||||
}); | ||||
} | ||||
bool isScrollTabButton(QAbstractButton *button, QTabBar *tabBar) | ||||
{ | ||||
auto isNextOrPreviousTabButton = true; | ||||
if (tabBar->isAncestorOf(button)) { | ||||
for (auto i = 0; i < tabBar->count(); ++i) { | ||||
if (tabBar->tabButton(i, QTabBar::RightSide) == button | ||||
|| tabBar->tabButton(i, QTabBar::LeftSide) == button) { | ||||
isNextOrPreviousTabButton = false; | ||||
break; | ||||
} | ||||
} | ||||
} | ||||
else { | ||||
isNextOrPreviousTabButton = false; | ||||
} | ||||
return isNextOrPreviousTabButton; | ||||
} | ||||
QAbstractButton *tabScrollButtonAt(const QPoint &pos, QTabBar *tabBar) | ||||
{ | ||||
auto globalPos = tabBar->mapToGlobal(pos); | ||||
auto widgetUnderMouse = sqpApp->widgetAt(globalPos); | ||||
if (auto btn = qobject_cast<QAbstractButton *>(widgetUnderMouse)) { | ||||
if (isScrollTabButton(btn, tabBar)) { | ||||
return btn; | ||||
} | ||||
} | ||||
return nullptr; | ||||
} | ||||
}; | ||||
DragDropTabSwitcher::DragDropTabSwitcher(QObject *parent) | ||||
: QObject(parent), impl{spimpl::make_unique_impl<DragDropTabSwitcherPrivate>()} | ||||
{ | ||||
} | ||||
void DragDropTabSwitcher::addTabBar(QTabBar *tabBar) | ||||
{ | ||||
impl->m_TabBarList << tabBar; | ||||
tabBar->setAcceptDrops(true); | ||||
} | ||||
void DragDropTabSwitcher::removeTabBar(QTabBar *tabBar) | ||||
{ | ||||
impl->m_TabBarList.removeAll(tabBar); | ||||
tabBar->setAcceptDrops(false); | ||||
} | ||||
bool DragDropTabSwitcher::eventFilter(QObject *obj, QEvent *event) | ||||
{ | ||||
if (event->type() == QEvent::DragMove) { | ||||
if (impl->m_CurrentTabBar) { | ||||
QWidget *w = static_cast<QWidget *>(obj); | ||||
if (!impl->m_CurrentTabBar->isAncestorOf(w)) { | ||||
return false; | ||||
} | ||||
auto moveEvent = static_cast<QDragMoveEvent *>(event); | ||||
auto scrollButton = impl->tabScrollButtonAt(moveEvent->pos(), impl->m_CurrentTabBar); | ||||
if (!scrollButton) { | ||||
auto tabIndex = impl->m_CurrentTabBar->tabAt(moveEvent->pos()); | ||||
if (tabIndex >= 0 && tabIndex != impl->m_CurrentTabBar->currentIndex()) { | ||||
// The mouse is over an unselected tab | ||||
if (!impl->m_TabSwitchTimer->isActive() | ||||
|| tabIndex != impl->m_HoveredTabIndex) { | ||||
impl->m_HoveredTabIndex = tabIndex; | ||||
impl->m_TabSwitchTimer->start(); | ||||
} | ||||
else { | ||||
// do nothing, timer already running | ||||
} | ||||
} | ||||
else { | ||||
impl->m_TabSwitchTimer->stop(); | ||||
} | ||||
impl->m_ScrollButtonsTimer->stop(); | ||||
} | ||||
else { | ||||
// The mouse is over a scroll button | ||||
// click it in a loop with a timer | ||||
if (!impl->m_ScrollButtonsTimer->isActive() | ||||
|| impl->m_HoveredScrollButton != scrollButton) { | ||||
impl->m_HoveredScrollButton = scrollButton; | ||||
impl->m_ScrollButtonsTimer->start(); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
else if (event->type() == QEvent::DragEnter) { | ||||
QWidget *w = static_cast<QWidget *>(obj); | ||||
for (auto tabBar : impl->m_TabBarList) { | ||||
if (w == tabBar) { | ||||
auto enterEvent = static_cast<QDragEnterEvent *>(event); | ||||
enterEvent->acceptProposedAction(); | ||||
enterEvent->setDropAction(Qt::IgnoreAction); | ||||
impl->m_CurrentTabBar = tabBar; | ||||
break; | ||||
} | ||||
} | ||||
} | ||||
else if (event->type() == QEvent::DragLeave || event->type() == QEvent::Drop) { | ||||
if (impl->m_CurrentTabBar) { | ||||
impl->m_HoveredTabIndex = -1; | ||||
impl->m_TabSwitchTimer->stop(); | ||||
impl->m_CurrentTabBar = nullptr; | ||||
impl->m_ScrollButtonsTimer->stop(); | ||||
} | ||||
} | ||||
return false; | ||||
} | ||||