From 28af007d266351a916475faf55a05c3358228087 2017-11-06 15:58:21 From: Thibaud Rabillard Date: 2017-11-06 15:58:21 Subject: [PATCH] New event filter class to manage the tab switching of a tabBar during a drag&drop --- diff --git a/gui/include/DragAndDrop/DragDropHelper.h b/gui/include/DragAndDrop/DragDropHelper.h index 2c116f7..c557cb5 100644 --- a/gui/include/DragAndDrop/DragDropHelper.h +++ b/gui/include/DragAndDrop/DragDropHelper.h @@ -7,6 +7,7 @@ class QVBoxLayout; class QScrollArea; +class QTabBar; class VisualizationDragWidget; class VisualizationDragDropContainer; class QMimeData; @@ -54,6 +55,9 @@ public: void addDragDropScrollArea(QScrollArea *scrollArea); void removeDragDropScrollArea(QScrollArea *scrollArea); + void addDragDropTabBar(QTabBar *tabBar); + void removeDragDropTabBar(QTabBar *tabBar); + QUrl imageTemporaryUrl(const QImage &image) const; void setHightlightedDragWidget(VisualizationDragWidget *dragWidget); diff --git a/gui/include/DragAndDrop/DragDropTabSwitcher.h b/gui/include/DragAndDrop/DragDropTabSwitcher.h new file mode 100644 index 0000000..1665d46 --- /dev/null +++ b/gui/include/DragAndDrop/DragDropTabSwitcher.h @@ -0,0 +1,29 @@ +#ifndef SCIQLOP_DRAGDROPTABSWITCHER_H +#define SCIQLOP_DRAGDROPTABSWITCHER_H + +#include + +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(LOG_DragDropTabSwitcher) + +class DragDropTabSwitcher : public QObject { + Q_OBJECT + +public: + DragDropTabSwitcher(QObject *parent = nullptr); + + void addTabBar(QTabBar *tabBar); + void removeTabBar(QTabBar *tabBar); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private: + class DragDropTabSwitcherPrivate; + spimpl::unique_impl_ptr impl; +}; + + +#endif // SCIQLOP_DRAGDROPTABSWITCHER_H diff --git a/gui/meson.build b/gui/meson.build index 2c6d92d..6520ba3 100644 --- a/gui/meson.build +++ b/gui/meson.build @@ -8,6 +8,7 @@ gui_moc_headers = [ 'include/SqpApplication.h', 'include/DragAndDrop/DragDropHelper.h', 'include/DragAndDrop/DragDropScroller.h', + 'include/DragAndDrop/DragDropTabSwitcher.h', 'include/TimeWidget/TimeWidget.h', 'include/Variable/VariableInspectorWidget.h', 'include/Variable/VariableInspectorTableView.h', @@ -46,6 +47,7 @@ gui_sources = [ 'src/SqpApplication.cpp', 'src/DragAndDrop/DragDropHelper.cpp', 'src/DragAndDrop/DragDropScroller.cpp', + 'src/DragAndDrop/DragDropTabSwitcher.cpp', 'src/Common/ColorUtils.cpp', 'src/Common/VisualizationDef.cpp', 'src/DataSource/DataSourceTreeWidgetItem.cpp', diff --git a/gui/src/DragAndDrop/DragDropHelper.cpp b/gui/src/DragAndDrop/DragDropHelper.cpp index dd95d42..dd2b000 100644 --- a/gui/src/DragAndDrop/DragDropHelper.cpp +++ b/gui/src/DragAndDrop/DragDropHelper.cpp @@ -1,5 +1,6 @@ #include "DragAndDrop/DragDropHelper.h" #include "DragAndDrop/DragDropScroller.h" +#include "DragAndDrop/DragDropTabSwitcher.h" #include "SqpApplication.h" #include "Visualization/VisualizationDragDropContainer.h" #include "Visualization/VisualizationDragWidget.h" @@ -18,7 +19,7 @@ #include -Q_LOGGING_CATEGORY(LOG_DragDropHelper, "DragDrophelper") +Q_LOGGING_CATEGORY(LOG_DragDropHelper, "DragDropHelper") struct DragDropHelper::DragDropHelperPrivate { @@ -28,6 +29,7 @@ struct DragDropHelper::DragDropHelperPrivate { QLabel *m_PlaceHolderLabel; QWidget *m_PlaceBackground; std::unique_ptr m_DragDropScroller = nullptr; + std::unique_ptr m_DragDropTabSwitcher = nullptr; QString m_ImageTempUrl; // Temporary file for image url generated by the drag & drop. Not using // QTemporaryFile to have a name which is not generated. @@ -38,7 +40,8 @@ struct DragDropHelper::DragDropHelperPrivate { explicit DragDropHelperPrivate() : m_PlaceHolder{std::make_unique()}, - m_DragDropScroller{std::make_unique()} + m_DragDropScroller{std::make_unique()}, + m_DragDropTabSwitcher{std::make_unique()} { auto layout = new QVBoxLayout{m_PlaceHolder.get()}; @@ -54,6 +57,7 @@ struct DragDropHelper::DragDropHelperPrivate { layout->addWidget(m_PlaceBackground); sqpApp->installEventFilter(m_DragDropScroller.get()); + sqpApp->installEventFilter(m_DragDropTabSwitcher.get()); m_ImageTempUrl = QDir::temp().absoluteFilePath("Sciqlop_graph.png"); } @@ -165,6 +169,16 @@ void DragDropHelper::removeDragDropScrollArea(QScrollArea *scrollArea) impl->m_DragDropScroller->removeScrollArea(scrollArea); } +void DragDropHelper::addDragDropTabBar(QTabBar *tabBar) +{ + impl->m_DragDropTabSwitcher->addTabBar(tabBar); +} + +void DragDropHelper::removeDragDropTabBar(QTabBar *tabBar) +{ + impl->m_DragDropTabSwitcher->removeTabBar(tabBar); +} + QUrl DragDropHelper::imageTemporaryUrl(const QImage &image) const { image.save(impl->m_ImageTempUrl); diff --git a/gui/src/DragAndDrop/DragDropTabSwitcher.cpp b/gui/src/DragAndDrop/DragDropTabSwitcher.cpp new file mode 100644 index 0000000..ce42b3a --- /dev/null +++ b/gui/src/DragAndDrop/DragDropTabSwitcher.cpp @@ -0,0 +1,180 @@ +#include "DragAndDrop/DragDropTabSwitcher.h" + +#include +#include +#include +#include + +#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 m_TabBarList; + QTabBar *m_CurrentTabBar = nullptr; + + int m_HoveredTabIndex = -1; + std::unique_ptr m_TabSwitchTimer = nullptr; + + QAbstractButton *m_HoveredScrollButton = nullptr; + std::unique_ptr m_ScrollButtonsTimer = nullptr; + + explicit DragDropTabSwitcherPrivate() + : m_TabSwitchTimer{std::make_unique()}, + m_ScrollButtonsTimer{std::make_unique()} + { + 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(widgetUnderMouse)) { + + if (isScrollTabButton(btn, tabBar)) { + return btn; + } + } + + return nullptr; + } +}; + +DragDropTabSwitcher::DragDropTabSwitcher(QObject *parent) + : QObject(parent), impl{spimpl::make_unique_impl()} +{ +} + +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(obj); + if (!impl->m_CurrentTabBar->isAncestorOf(w)) { + return false; + } + + auto moveEvent = static_cast(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(obj); + + for (auto tabBar : impl->m_TabBarList) { + if (w == tabBar) { + auto enterEvent = static_cast(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; +} diff --git a/gui/src/Visualization/VisualizationWidget.cpp b/gui/src/Visualization/VisualizationWidget.cpp index b215461..39f3370 100644 --- a/gui/src/Visualization/VisualizationWidget.cpp +++ b/gui/src/Visualization/VisualizationWidget.cpp @@ -11,6 +11,9 @@ #include "ui_VisualizationWidget.h" +#include "DragAndDrop/DragDropHelper.h" +#include "SqpApplication.h" + #include Q_LOGGING_CATEGORY(LOG_VisualizationWidget, "VisualizationWidget") @@ -67,12 +70,15 @@ VisualizationWidget::VisualizationWidget(QWidget *parent) connect(addTabViewButton, &QToolButton::clicked, addTabView); connect(ui->tabWidget, &QTabWidget::tabCloseRequested, removeTabView); + sqpApp->dragDropHelper().addDragDropTabBar(ui->tabWidget->tabBar()); + // Adds default tab addTabView(); } VisualizationWidget::~VisualizationWidget() { + sqpApp->dragDropHelper().removeDragDropTabBar(ui->tabWidget->tabBar()); delete ui; }