diff --git a/gui/include/DragDropHelper.h b/gui/include/DragDropHelper.h new file mode 100644 index 0000000..95a9c45 --- /dev/null +++ b/gui/include/DragDropHelper.h @@ -0,0 +1,65 @@ +#ifndef SCIQLOP_DRAGDROPHELPER_H +#define SCIQLOP_DRAGDROPHELPER_H + +#include +#include + +class QVBoxLayout; +class QScrollArea; +class VisualizationDragWidget; +class QMimeData; + +/** + * @brief Event filter class which manage the scroll of QScrollArea during a drag&drop operation. + * @note A QScrollArea inside an other QScrollArea is not fully supported. + */ +class DragDropScroller : public QObject { + Q_OBJECT + +public: + DragDropScroller(QObject *parent = nullptr); + + void addScrollArea(QScrollArea *scrollArea); + void removeScrollArea(QScrollArea *scrollArea); + +protected: + bool eventFilter(QObject *obj, QEvent *event); + +private: + class DragDropScrollerPrivate; + spimpl::unique_impl_ptr impl; + +private slots: + void onTimer(); +}; + +/** + * @brief Helper class for drag&drop operations. + */ +class DragDropHelper { +public: + static const QString MIME_TYPE_GRAPH; + static const QString MIME_TYPE_ZONE; + + DragDropHelper(); + virtual ~DragDropHelper(); + + void setCurrentDragWidget(VisualizationDragWidget *dragWidget); + VisualizationDragWidget *getCurrentDragWidget() const; + + QWidget &placeHolder() const; + void insertPlaceHolder(QVBoxLayout *layout, int index); + void removePlaceHolder(); + bool isPlaceHolderSet() const; + + void addDragDropScrollArea(QScrollArea *scrollArea); + void removeDragDropScrollArea(QScrollArea *scrollArea); + + QUrl imageTemporaryUrl(const QImage &image) const; + +private: + class DragDropHelperPrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_DRAGDROPHELPER_H diff --git a/gui/include/SqpApplication.h b/gui/include/SqpApplication.h index a23bd7f..1027e29 100644 --- a/gui/include/SqpApplication.h +++ b/gui/include/SqpApplication.h @@ -20,6 +20,7 @@ class NetworkController; class TimeController; class VariableController; class VisualizationController; +class DragDropHelper; /** * @brief The SqpApplication class aims to make the link between SciQlop @@ -44,6 +45,9 @@ public: VariableController &variableController() noexcept; VisualizationController &visualizationController() noexcept; + /// Accessors for the differents sciqlop helpers + DragDropHelper &dragDropHelper() noexcept; + private: class SqpApplicationPrivate; spimpl::unique_impl_ptr impl; diff --git a/gui/include/Visualization/VisualizationDragDropContainer.h b/gui/include/Visualization/VisualizationDragDropContainer.h new file mode 100644 index 0000000..d8460de --- /dev/null +++ b/gui/include/Visualization/VisualizationDragDropContainer.h @@ -0,0 +1,42 @@ +#ifndef SCIQLOP_VISUALIZATIONDRAGDROPCONTAINER_H +#define SCIQLOP_VISUALIZATIONDRAGDROPCONTAINER_H + +#include +#include +#include +#include + +class VisualizationDragWidget; + +class VisualizationDragDropContainer : public QWidget { + Q_OBJECT + +signals: + void dropOccured(int dropIndex, const QMimeData *mimeData); + +public: + VisualizationDragDropContainer(QWidget *parent = nullptr); + + void addDragWidget(VisualizationDragWidget *dragWidget); + void insertDragWidget(int index, VisualizationDragWidget *dragWidget); + + void setAcceptedMimeTypes(const QStringList &mimeTypes); + void setMergeAllowedMimeTypes(const QStringList &mimeTypes); + + int countDragWidget() const; + +protected: + void dragEnterEvent(QDragEnterEvent *event); + void dragLeaveEvent(QDragLeaveEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dropEvent(QDropEvent *event); + +private: + class VisualizationDragDropContainerPrivate; + spimpl::unique_impl_ptr impl; + +private slots: + void startDrag(VisualizationDragWidget *dragWidget, const QPoint &dragPosition); +}; + +#endif // SCIQLOP_VISUALIZATIONDRAGDROPCONTAINER_H diff --git a/gui/include/Visualization/VisualizationDragWidget.h b/gui/include/Visualization/VisualizationDragWidget.h new file mode 100644 index 0000000..3552cef --- /dev/null +++ b/gui/include/Visualization/VisualizationDragWidget.h @@ -0,0 +1,29 @@ +#ifndef SCIQLOP_VISUALIZATIONDRAGWIDGET_H +#define SCIQLOP_VISUALIZATIONDRAGWIDGET_H + +#include +#include +#include + +class VisualizationDragWidget : public QWidget { + Q_OBJECT + +public: + VisualizationDragWidget(QWidget *parent = nullptr); + + virtual QMimeData *mimeData() const = 0; + virtual bool isDragAllowed() const = 0; + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + +private: + class VisualizationDragWidgetPrivate; + spimpl::unique_impl_ptr impl; + +signals: + void dragDetected(VisualizationDragWidget *dragWidget, const QPoint &dragPosition); +}; + +#endif // SCIQLOP_VISUALIZATIONDRAGWIDGET_H diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index 98a1c1d..b0e85c7 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -2,6 +2,7 @@ #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H #include "Visualization/IVisualizationWidget.h" +#include "Visualization/VisualizationDragWidget.h" #include #include @@ -16,12 +17,13 @@ class QCPRange; class QCustomPlot; class SqpRange; class Variable; +class VisualizationZoneWidget; namespace Ui { class VisualizationGraphWidget; } // namespace Ui -class VisualizationGraphWidget : public QWidget, public IVisualizationWidget { +class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget { Q_OBJECT friend class QCustomPlotSynchronizer; @@ -31,6 +33,8 @@ public: explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0); virtual ~VisualizationGraphWidget(); + VisualizationZoneWidget *parentZoneWidget() const noexcept; + /// If acquisition isn't enable, requestDataLoading signal cannot be emit void enableAcquisition(bool enable); @@ -39,6 +43,9 @@ public: /// Removes a variable from the graph void removeVariable(std::shared_ptr variable) noexcept; + /// Returns the list of all variables used in the graph + QList > variables() const; + void setYRange(const SqpRange &range); SqpRange graphRange() const noexcept; void setGraphRange(const SqpRange &range); @@ -49,6 +56,9 @@ public: bool contains(const Variable &variable) const override; QString name() const override; + // VisualisationDragWidget + QMimeData *mimeData() const override; + bool isDragAllowed() const override; signals: void synchronize(const SqpRange &range, const SqpRange &oldRange); diff --git a/gui/include/Visualization/VisualizationTabWidget.h b/gui/include/Visualization/VisualizationTabWidget.h index c3623e2..4df7113 100644 --- a/gui/include/Visualization/VisualizationTabWidget.h +++ b/gui/include/Visualization/VisualizationTabWidget.h @@ -6,6 +6,7 @@ #include #include +#include #include Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationTabWidget) @@ -27,14 +28,33 @@ public: /// Add a zone widget void addZone(VisualizationZoneWidget *zoneWidget); + void insertZone(int index, VisualizationZoneWidget *zoneWidget); + /** * Creates a zone using a variable. The variable will be displayed in a new graph of the new - * zone. + * zone. The zone is added at the end. * @param variable the variable for which to create the zone * @return the pointer to the created zone */ VisualizationZoneWidget *createZone(std::shared_ptr variable); + /** + * Creates a zone using a list of variables. The variables will be displayed in a new graph of + * the new zone. The zone is inserted at the specified index. + * @param variables the variables for which to create the zone + * @param index The index where the zone should be inserted in the layout + * @return the pointer to the created zone + */ + VisualizationZoneWidget *createZone(const QList > &variables, + int index); + + /** + * Creates a zone which is empty (no variables). The zone is inserted at the specified index. + * @param index The index where the zone should be inserted in the layout + * @return the pointer to the created zone + */ + VisualizationZoneWidget *createEmptyZone(int index); + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; @@ -52,6 +72,9 @@ private: class VisualizationTabWidgetPrivate; spimpl::unique_impl_ptr impl; + +private slots: + void dropMimeData(int index, const QMimeData *mimeData); }; #endif // SCIQLOP_VISUALIZATIONTABWIDGET_H diff --git a/gui/include/Visualization/VisualizationZoneWidget.h b/gui/include/Visualization/VisualizationZoneWidget.h index 02aa6ba..3700489 100644 --- a/gui/include/Visualization/VisualizationZoneWidget.h +++ b/gui/include/Visualization/VisualizationZoneWidget.h @@ -2,6 +2,7 @@ #define SCIQLOP_VISUALIZATIONZONEWIDGET_H #include "Visualization/IVisualizationWidget.h" +#include "Visualization/VisualizationDragWidget.h" #include #include @@ -14,34 +15,61 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationZoneWidget) namespace Ui { class VisualizationZoneWidget; -} // Ui +} // namespace Ui class Variable; class VisualizationGraphWidget; -class VisualizationZoneWidget : public QWidget, public IVisualizationWidget { +class VisualizationZoneWidget : public VisualizationDragWidget, public IVisualizationWidget { Q_OBJECT public: explicit VisualizationZoneWidget(const QString &name = {}, QWidget *parent = 0); virtual ~VisualizationZoneWidget(); - /// Add a graph widget + /// Adds a graph widget void addGraph(VisualizationGraphWidget *graphWidget); + /// Inserts a graph widget + void insertGraph(int index, VisualizationGraphWidget *graphWidget); + /** * Creates a graph using a variable. The variable will be displayed in the new graph. + * The graph is added at the end. * @param variable the variable for which to create the graph * @return the pointer to the created graph */ VisualizationGraphWidget *createGraph(std::shared_ptr variable); + /** + * Creates a graph using a variable. The variable will be displayed in the new graph. + * The graph is inserted at the specified index. + * @param variable the variable for which to create the graph + * @param index The index where the graph should be inserted in the layout + * @return the pointer to the created graph + */ + VisualizationGraphWidget *createGraph(std::shared_ptr variable, int index); + + /** + * Creates a graph using a list of variables. The variables will be displayed in the new graph. + * The graph is inserted at the specified index. + * @param variables List of variables to be added to the graph + * @param index The index where the graph should be inserted in the layout + * @return the pointer to the created graph + */ + VisualizationGraphWidget *createGraph(const QList > variables, + int index); + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; bool contains(const Variable &variable) const override; QString name() const override; + // VisualisationDragWidget + QMimeData *mimeData() const override; + bool isDragAllowed() const override; + protected: void closeEvent(QCloseEvent *event) override; @@ -55,6 +83,8 @@ private slots: void onVariableAdded(std::shared_ptr variable); /// Slot called when a variable is about to be removed from a graph contained in the zone void onVariableAboutToBeRemoved(std::shared_ptr variable); + + void dropMimeData(int index, const QMimeData *mimeData); }; #endif // SCIQLOP_VISUALIZATIONZONEWIDGET_H diff --git a/gui/src/DragDropHelper.cpp b/gui/src/DragDropHelper.cpp new file mode 100644 index 0000000..9db14fe --- /dev/null +++ b/gui/src/DragDropHelper.cpp @@ -0,0 +1,230 @@ +#include "DragDropHelper.h" +#include "SqpApplication.h" +#include "Visualization/VisualizationDragWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +const QString DragDropHelper::MIME_TYPE_GRAPH = "scqlop/graph"; +const QString DragDropHelper::MIME_TYPE_ZONE = "scqlop/zone"; + +const int SCROLL_SPEED = 5; +const int SCROLL_ZONE_SIZE = 50; + +struct DragDropScroller::DragDropScrollerPrivate { + + QList m_ScrollAreas; + QScrollArea *m_CurrentScrollArea = nullptr; + std::unique_ptr m_Timer = nullptr; + + + enum class ScrollDirection { up, down, unknown }; + ScrollDirection m_Direction = ScrollDirection::unknown; + + explicit DragDropScrollerPrivate() : m_Timer{std::make_unique()} + { + m_Timer->setInterval(0); + } +}; + +DragDropScroller::DragDropScroller(QObject *parent) + : QObject{parent}, impl{spimpl::make_unique_impl()} +{ + connect(impl->m_Timer.get(), &QTimer::timeout, this, &DragDropScroller::onTimer); +} + +void DragDropScroller::addScrollArea(QScrollArea *scrollArea) +{ + impl->m_ScrollAreas << scrollArea; + scrollArea->viewport()->setAcceptDrops(true); +} + +void DragDropScroller::removeScrollArea(QScrollArea *scrollArea) +{ + impl->m_ScrollAreas.removeAll(scrollArea); + scrollArea->viewport()->setAcceptDrops(false); +} + +bool DragDropScroller::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::DragMove) { + auto w = static_cast(obj); + + if (impl->m_CurrentScrollArea && impl->m_CurrentScrollArea->isAncestorOf(w)) { + auto moveEvent = static_cast(event); + + auto pos = moveEvent->pos(); + if (impl->m_CurrentScrollArea->viewport() != w) { + auto globalPos = w->mapToGlobal(moveEvent->pos()); + pos = impl->m_CurrentScrollArea->viewport()->mapFromGlobal(globalPos); + } + + auto isInTopZone = pos.y() > impl->m_CurrentScrollArea->viewport()->size().height() + - SCROLL_ZONE_SIZE; + auto isInBottomZone = pos.y() < SCROLL_ZONE_SIZE; + + if (!isInTopZone && !isInBottomZone) { + impl->m_Direction = DragDropScrollerPrivate::ScrollDirection::unknown; + impl->m_Timer->stop(); + } + else if (!impl->m_Timer->isActive()) { + impl->m_Direction = isInTopZone ? DragDropScrollerPrivate::ScrollDirection::up + : DragDropScrollerPrivate::ScrollDirection::down; + impl->m_Timer->start(); + } + } + } + else if (event->type() == QEvent::DragEnter) { + auto w = static_cast(obj); + + for (auto scrollArea : impl->m_ScrollAreas) { + if (impl->m_CurrentScrollArea != scrollArea && scrollArea->isAncestorOf(w)) { + auto enterEvent = static_cast(event); + enterEvent->acceptProposedAction(); + enterEvent->setDropAction(Qt::IgnoreAction); + impl->m_CurrentScrollArea = scrollArea; + break; + } + } + } + else if (event->type() == QEvent::DragLeave) { + if (impl->m_CurrentScrollArea) { + if (!QRect(QPoint(), impl->m_CurrentScrollArea->size()) + .contains(impl->m_CurrentScrollArea->mapFromGlobal(QCursor::pos()))) { + impl->m_CurrentScrollArea = nullptr; + impl->m_Direction = DragDropScrollerPrivate::ScrollDirection::unknown; + impl->m_Timer->stop(); + } + } + } + else if (event->type() == QEvent::Drop) { + if (impl->m_CurrentScrollArea) { + impl->m_CurrentScrollArea = nullptr; + impl->m_Direction = DragDropScrollerPrivate::ScrollDirection::unknown; + impl->m_Timer->stop(); + } + } + + return false; +} + +void DragDropScroller::onTimer() +{ + if (impl->m_CurrentScrollArea) { + auto mvt = 0; + switch (impl->m_Direction) { + case DragDropScrollerPrivate::ScrollDirection::up: + mvt = SCROLL_SPEED; + break; + case DragDropScrollerPrivate::ScrollDirection::down: + mvt = -SCROLL_SPEED; + break; + default: + break; + } + + impl->m_CurrentScrollArea->verticalScrollBar()->setValue( + impl->m_CurrentScrollArea->verticalScrollBar()->value() + mvt); + } +} + +struct DragDropHelper::DragDropHelperPrivate { + + VisualizationDragWidget *m_CurrentDragWidget = nullptr; + std::unique_ptr m_PlaceHolder = nullptr; + std::unique_ptr m_DragDropScroller = 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. + + explicit DragDropHelperPrivate() + : m_PlaceHolder{std::make_unique()}, + m_DragDropScroller{std::make_unique()} + { + m_PlaceHolder->setStyleSheet("background-color: #BBD5EE; border:2px solid #2A7FD4"); + sqpApp->installEventFilter(m_DragDropScroller.get()); + + + m_ImageTempUrl = QDir::temp().absoluteFilePath("Scqlop_graph.png"); + } + + void preparePlaceHolder() const + { + if (m_CurrentDragWidget) { + m_PlaceHolder->setMinimumSize(m_CurrentDragWidget->size()); + m_PlaceHolder->setSizePolicy(m_CurrentDragWidget->sizePolicy()); + } + else { + m_PlaceHolder->setMinimumSize(200, 200); + } + } +}; + + +DragDropHelper::DragDropHelper() : impl{spimpl::make_unique_impl()} +{ +} + +DragDropHelper::~DragDropHelper() +{ + QFile::remove(impl->m_ImageTempUrl); +} + +void DragDropHelper::setCurrentDragWidget(VisualizationDragWidget *dragWidget) +{ + impl->m_CurrentDragWidget = dragWidget; +} + +VisualizationDragWidget *DragDropHelper::getCurrentDragWidget() const +{ + return impl->m_CurrentDragWidget; +} + + +QWidget &DragDropHelper::placeHolder() const +{ + return *impl->m_PlaceHolder; +} + +void DragDropHelper::insertPlaceHolder(QVBoxLayout *layout, int index) +{ + removePlaceHolder(); + impl->preparePlaceHolder(); + layout->insertWidget(index, impl->m_PlaceHolder.get()); + impl->m_PlaceHolder->show(); +} + +void DragDropHelper::removePlaceHolder() +{ + auto parentWidget = impl->m_PlaceHolder->parentWidget(); + if (parentWidget) { + parentWidget->layout()->removeWidget(impl->m_PlaceHolder.get()); + impl->m_PlaceHolder->setParent(nullptr); + impl->m_PlaceHolder->hide(); + } +} + +bool DragDropHelper::isPlaceHolderSet() const +{ + return impl->m_PlaceHolder->parentWidget(); +} + +void DragDropHelper::addDragDropScrollArea(QScrollArea *scrollArea) +{ + impl->m_DragDropScroller->addScrollArea(scrollArea); +} + +void DragDropHelper::removeDragDropScrollArea(QScrollArea *scrollArea) +{ + impl->m_DragDropScroller->removeScrollArea(scrollArea); +} + +QUrl DragDropHelper::imageTemporaryUrl(const QImage &image) const +{ + image.save(impl->m_ImageTempUrl); + return QUrl::fromLocalFile(impl->m_ImageTempUrl); +} diff --git a/gui/src/SqpApplication.cpp b/gui/src/SqpApplication.cpp index 4968eb0..09ee43b 100644 --- a/gui/src/SqpApplication.cpp +++ b/gui/src/SqpApplication.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include