diff --git a/gui/include/DragDropHelper.h b/gui/include/DragDropHelper.h new file mode 100644 index 0000000..d6b6284 --- /dev/null +++ b/gui/include/DragDropHelper.h @@ -0,0 +1,67 @@ +#ifndef DRAGDROPHELPER_H +#define 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: + DragDropHelper(); + ~DragDropHelper(); + + static const QString MIME_TYPE_GRAPH; + static const QString MIME_TYPE_ZONE; + + 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 // DRAGDROPHELPER_H diff --git a/gui/include/SqpApplication.h b/gui/include/SqpApplication.h index a23bd7f..d7049ba 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..5f9e9bd --- /dev/null +++ b/gui/include/Visualization/VisualizationDragDropContainer.h @@ -0,0 +1,45 @@ +#ifndef VISUALIZATIONDRAGDROPCONTAINER_H +#define 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 // VISUALIZATIONDRAGDROPCONTAINER_H diff --git a/gui/include/Visualization/VisualizationDragWidget.h b/gui/include/Visualization/VisualizationDragWidget.h new file mode 100644 index 0000000..46e038f --- /dev/null +++ b/gui/include/Visualization/VisualizationDragWidget.h @@ -0,0 +1,30 @@ +#ifndef VISUALIZATIONDRAGWIDGET_H +#define 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 // VISUALIZATIONDRAGWIDGET_H diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index 98a1c1d..c8f0adc 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..49a1769 100644 --- a/gui/include/Visualization/VisualizationTabWidget.h +++ b/gui/include/Visualization/VisualizationTabWidget.h @@ -7,6 +7,7 @@ #include #include +#include Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationTabWidget) @@ -27,14 +28,32 @@ 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 +71,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..a2dac14 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 @@ -19,29 +20,55 @@ class VisualizationZoneWidget; 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 +82,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..b327352 --- /dev/null +++ b/gui/src/DragDropHelper.cpp @@ -0,0 +1,248 @@ +#include "DragDropHelper.h" +#include "Visualization/VisualizationDragWidget.h" +#include "SqpApplication.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) + { + auto w = static_cast(obj); + 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) + { + auto w = static_cast(obj); + 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..1187cc9 100644 --- a/gui/src/SqpApplication.cpp +++ b/gui/src/SqpApplication.cpp @@ -8,6 +8,7 @@ #include #include #include +#include Q_LOGGING_CATEGORY(LOG_SqpApplication, "SqpApplication") @@ -18,7 +19,8 @@ public: m_NetworkController{std::make_unique()}, m_TimeController{std::make_unique()}, m_VariableController{std::make_unique()}, - m_VisualizationController{std::make_unique()} + m_VisualizationController{std::make_unique()}, + m_DragDropHelper{std::make_unique()} { // /////////////////////////////// // // Connections between controllers // @@ -82,6 +84,8 @@ public: QThread m_NetworkControllerThread; QThread m_VariableControllerThread; QThread m_VisualizationControllerThread; + + std::unique_ptr m_DragDropHelper; }; @@ -148,3 +152,8 @@ VisualizationController &SqpApplication::visualizationController() noexcept { return *impl->m_VisualizationController; } + +DragDropHelper &SqpApplication::dragDropHelper() noexcept +{ + return *impl->m_DragDropHelper; +} diff --git a/gui/src/Visualization/VisualizationDragDropContainer.cpp b/gui/src/Visualization/VisualizationDragDropContainer.cpp new file mode 100644 index 0000000..8d199b9 --- /dev/null +++ b/gui/src/Visualization/VisualizationDragDropContainer.cpp @@ -0,0 +1,312 @@ +#include "Visualization/VisualizationDragDropContainer.h" +#include "Visualization/VisualizationDragWidget.h" +#include "SqpApplication.h" +#include "DragDropHelper.h" + +#include +#include +#include + +#include + +struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate { + + QVBoxLayout* m_layout; + QStringList m_acceptedMimeTypes; + QStringList m_mergeAllowedMimeTypes; + + explicit VisualizationDragDropContainerPrivate(QWidget* widget) + { + m_layout = new QVBoxLayout(widget); + m_layout->setContentsMargins(0,0,0,0); + } + + bool acceptMimeData(const QMimeData* data) const + { + for (const auto& type : m_acceptedMimeTypes) + { + if (data->hasFormat(type)) + return true; + } + + return false; + } + + bool allowMergeMimeData(const QMimeData* data) const + { + for (const auto& type : m_mergeAllowedMimeTypes) + { + if (data->hasFormat(type)) + return true; + } + + return false; + } + + bool hasPlaceHolder() const + { + return sqpApp->dragDropHelper().placeHolder().parentWidget() == m_layout->parentWidget(); + } + + VisualizationDragWidget* getChildDragWidgetAt(QWidget* parent, const QPoint &pos) const + { + VisualizationDragWidget* dragWidget = nullptr; + + for (auto child : parent->children()) + { + auto widget = qobject_cast(child); + if (widget && widget->isVisible()) + { + if (widget->frameGeometry().contains(pos)) + { + dragWidget = widget; + break; + } + } + } + + return dragWidget; + } + + bool cursorIsInContainer(QWidget* container) const + { + auto adustNum = 18; //to be safe, in case of scrollbar on the side + auto containerRect = QRect(QPoint(), container->contentsRect().size()).adjusted(adustNum, adustNum, -adustNum, -adustNum); + qDebug() << containerRect << container->mapFromGlobal(QCursor::pos()); + return containerRect.contains(container->mapFromGlobal(QCursor::pos())); + } + +}; + +VisualizationDragDropContainer::VisualizationDragDropContainer(QWidget *parent) + : QWidget{parent}, impl{spimpl::make_unique_impl(this)} +{ + setAcceptDrops(true); +} + +void VisualizationDragDropContainer::addDragWidget(VisualizationDragWidget *dragWidget) +{ + impl->m_layout->addWidget(dragWidget); + disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr); + connect(dragWidget, &VisualizationDragWidget::dragDetected, this, &VisualizationDragDropContainer::startDrag); +} + +void VisualizationDragDropContainer::insertDragWidget(int index, VisualizationDragWidget *dragWidget) +{ + impl->m_layout->insertWidget(index, dragWidget); + disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr); + connect(dragWidget, &VisualizationDragWidget::dragDetected, this, &VisualizationDragDropContainer::startDrag); +} + +void VisualizationDragDropContainer::setAcceptedMimeTypes(const QStringList &mimeTypes) +{ + impl->m_acceptedMimeTypes = mimeTypes; +} + +void VisualizationDragDropContainer::setMergeAllowedMimeTypes(const QStringList &mimeTypes) +{ + impl->m_mergeAllowedMimeTypes = mimeTypes; +} + +int VisualizationDragDropContainer::countDragWidget() const +{ + auto nbGraph = 0; + for (auto child : children()) + { + auto widget = qobject_cast(child); + if (widget) + { + nbGraph += 1; + } + } + + return nbGraph; +} + +void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidget, const QPoint &dragPosition) +{ + auto& helper = sqpApp->dragDropHelper(); + + //Note: The management of the drag object is done by Qt + auto *drag = new QDrag{dragWidget}; + drag->setHotSpot(dragPosition); + + auto mimeData = dragWidget->mimeData(); + drag->setMimeData(mimeData); + + auto pixmap = QPixmap(dragWidget->size()); + dragWidget->render(&pixmap); + drag->setPixmap(pixmap); + + auto image = pixmap.toImage(); + mimeData->setImageData(image); + mimeData->setUrls({helper.imageTemporaryUrl(image)}); + + if (impl->m_layout->indexOf(dragWidget) >= 0) + { + helper.setCurrentDragWidget(dragWidget); + + if (impl->cursorIsInContainer(this)) + { + auto dragWidgetIndex = impl->m_layout->indexOf(dragWidget); + helper.insertPlaceHolder(impl->m_layout, dragWidgetIndex); + dragWidget->setVisible(false); + } + } + + //Note: The exec() is blocking on windows but not on linux and macOS + drag->exec(Qt::MoveAction | Qt::CopyAction); +} + +void VisualizationDragDropContainer::dragEnterEvent(QDragEnterEvent *event) +{ + if (impl->acceptMimeData(event->mimeData())) + { + event->acceptProposedAction(); + + auto& helper = sqpApp->dragDropHelper(); + + if (!impl->hasPlaceHolder()) + { + auto dragWidget = helper.getCurrentDragWidget(); + auto parentWidget = qobject_cast(dragWidget->parentWidget()); + if (parentWidget) + { + dragWidget->setVisible(false); + } + + auto dragWidgetHovered = impl->getChildDragWidgetAt(this, event->pos()); + + if (dragWidgetHovered) + { + auto hoveredWidgetIndex = impl->m_layout->indexOf(dragWidgetHovered); + auto dragWidgetIndex = impl->m_layout->indexOf(helper.getCurrentDragWidget()); + if (dragWidgetIndex >= 0 && dragWidgetIndex <= hoveredWidgetIndex) + hoveredWidgetIndex += 1; //Correction of the index if the drop occurs in the same container + + helper.insertPlaceHolder(impl->m_layout, hoveredWidgetIndex); + } + else + { + helper.insertPlaceHolder(impl->m_layout, 0); + } + } + } + else + event->ignore(); + + QWidget::dragEnterEvent(event); +} + +void VisualizationDragDropContainer::dragLeaveEvent(QDragLeaveEvent *event) +{ + Q_UNUSED(event); + + auto& helper = sqpApp->dragDropHelper(); + + if (!impl->cursorIsInContainer(this)) + { + helper.removePlaceHolder(); + + bool isInternal = true; + if (isInternal) + { + //Only if the drag is strated from the visualization + //Show the drag widget at its original place + //So the drag widget doesn't stay hidden if the drop occurs outside the visualization drop zone + //(It is not possible to catch a drop event outside of the application) + + auto dragWidget = sqpApp->dragDropHelper().getCurrentDragWidget(); + if (dragWidget) + { + dragWidget->setVisible(true); + } + } + } + + QWidget::dragLeaveEvent(event); +} + +void VisualizationDragDropContainer::dragMoveEvent(QDragMoveEvent *event) +{ + if (impl->acceptMimeData(event->mimeData())) + { + auto dragWidgetHovered = impl->getChildDragWidgetAt(this, event->pos()); + if (dragWidgetHovered) + { + auto canMerge = impl->allowMergeMimeData(event->mimeData()); + + auto nbDragWidget = countDragWidget(); + if (nbDragWidget > 0) + { + auto graphHeight = size().height() / nbDragWidget; + auto dropIndex = floor(event->pos().y() / graphHeight); + auto zoneSize = qMin(graphHeight / 3.0, 150.0); + + auto isOnTop = event->pos().y() < dropIndex * graphHeight + zoneSize; + auto isOnBottom = event->pos().y() > (dropIndex + 1) * graphHeight - zoneSize; + + auto& helper = sqpApp->dragDropHelper(); + auto placeHolderIndex = impl->m_layout->indexOf(&(helper.placeHolder())); + + if (isOnTop || isOnBottom) + { + if (isOnBottom) + dropIndex += 1; + + auto dragWidgetIndex = impl->m_layout->indexOf(helper.getCurrentDragWidget()); + if (dragWidgetIndex >= 0 && dragWidgetIndex <= dropIndex) + dropIndex += 1; //Correction of the index if the drop occurs in the same container + + if (dropIndex != placeHolderIndex) + { + helper.insertPlaceHolder(impl->m_layout, dropIndex); + } + } + else if (canMerge) + { + //drop on the middle -> merge + if (impl->hasPlaceHolder()) + { + helper.removePlaceHolder(); + } + } + } + } + } + else + event->ignore(); + + QWidget::dragMoveEvent(event); +} + +void VisualizationDragDropContainer::dropEvent(QDropEvent *event) +{ + if (impl->acceptMimeData(event->mimeData())) + { + auto dragWidget = sqpApp->dragDropHelper().getCurrentDragWidget(); + if (impl->hasPlaceHolder() && dragWidget) + { + auto& helper = sqpApp->dragDropHelper(); + + auto droppedIndex = impl->m_layout->indexOf(&helper.placeHolder()); + + auto dragWidgetIndex = impl->m_layout->indexOf(dragWidget); + if (dragWidgetIndex >= 0 && dragWidgetIndex < droppedIndex) + droppedIndex -= 1; //Correction of the index if the drop occurs in the same container + + dragWidget->setVisible(true); + dragWidget->setStyleSheet(""); + + event->acceptProposedAction(); + + helper.removePlaceHolder(); + + emit dropOccured(droppedIndex, event->mimeData()); + } + } + else + event->ignore(); + + QWidget::dropEvent(event); +} diff --git a/gui/src/Visualization/VisualizationDragWidget.cpp b/gui/src/Visualization/VisualizationDragWidget.cpp new file mode 100644 index 0000000..0784704 --- /dev/null +++ b/gui/src/Visualization/VisualizationDragWidget.cpp @@ -0,0 +1,50 @@ +#include "Visualization/VisualizationDragWidget.h" +#include "Visualization/VisualizationDragDropContainer.h" + +#include +#include + +struct VisualizationDragWidget::VisualizationDragWidgetPrivate { + + QPoint m_dragStartPosition; + bool m_dragStartPositionValid = false; + + explicit VisualizationDragWidgetPrivate() + { + } +}; + +VisualizationDragWidget::VisualizationDragWidget(QWidget* parent) + : QWidget{parent}, impl{spimpl::make_unique_impl()} +{ + +} + +void VisualizationDragWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + impl->m_dragStartPosition = event->pos(); + + impl->m_dragStartPositionValid = isDragAllowed(); + + QWidget::mousePressEvent(event); +} + +void VisualizationDragWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (!impl->m_dragStartPositionValid || !isDragAllowed()) + return; + + if (!(event->buttons() & Qt::LeftButton)) + return; + + if (!event->modifiers().testFlag(Qt::AltModifier)) + return; + + if ((event->pos() - impl->m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()) + return; + + emit dragDetected(this, impl->m_dragStartPosition); + + QWidget::mouseMoveEvent(event); +} diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 50d0562..a0f598e 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -3,12 +3,14 @@ #include "Visualization/VisualizationDefs.h" #include "Visualization/VisualizationGraphHelper.h" #include "Visualization/VisualizationGraphRenderingDelegate.h" +#include "Visualization/VisualizationZoneWidget.h" #include "ui_VisualizationGraphWidget.h" #include #include #include #include +#include #include #include @@ -47,7 +49,7 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { }; VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent) - : QWidget{parent}, + : VisualizationDragWidget{parent}, ui{new Ui::VisualizationGraphWidget}, impl{spimpl::make_unique_impl(name)} { @@ -92,6 +94,17 @@ VisualizationGraphWidget::~VisualizationGraphWidget() delete ui; } +VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept +{ + auto parent = parentWidget(); + do + { + parent = parent->parentWidget(); + } while (parent != nullptr && !qobject_cast(parent)); + + return qobject_cast(parent); +} + void VisualizationGraphWidget::enableAcquisition(bool enable) { impl->m_DoAcquisition = enable; @@ -152,6 +165,17 @@ void VisualizationGraphWidget::removeVariable(std::shared_ptr variable ui->widget->replot(); } +QList> VisualizationGraphWidget::variables() const +{ + auto variables = QList>{}; + for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap); it != std::cend(impl->m_VariableToPlotMultiMap); ++it) + { + variables << it->first; + } + + return variables; +} + void VisualizationGraphWidget::setYRange(const SqpRange &range) { ui->widget->yAxis->setRange(range.m_TStart, range.m_TEnd); @@ -206,6 +230,19 @@ QString VisualizationGraphWidget::name() const return impl->m_Name; } +QMimeData *VisualizationGraphWidget::mimeData() const +{ + auto *mimeData = new QMimeData; + mimeData->setData(DragDropHelper::MIME_TYPE_GRAPH, QByteArray()); + + return mimeData; +} + +bool VisualizationGraphWidget::isDragAllowed() const +{ + return true; +} + void VisualizationGraphWidget::closeEvent(QCloseEvent *event) { Q_UNUSED(event); @@ -284,6 +321,8 @@ void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept { // Handles plot rendering when mouse is moving impl->m_RenderingDelegate->onMouseMove(event); + + VisualizationDragWidget::mouseMoveEvent(event); } void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept @@ -307,6 +346,10 @@ void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept { impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier); + + plot().setInteraction(QCP::iRangeDrag, !event->modifiers().testFlag(Qt::AltModifier)); + + VisualizationDragWidget::mousePressEvent(event); } void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept diff --git a/gui/src/Visualization/VisualizationTabWidget.cpp b/gui/src/Visualization/VisualizationTabWidget.cpp index 84751e9..148fbcb 100644 --- a/gui/src/Visualization/VisualizationTabWidget.cpp +++ b/gui/src/Visualization/VisualizationTabWidget.cpp @@ -3,6 +3,12 @@ #include "ui_VisualizationTabWidget.h" #include "Visualization/VisualizationZoneWidget.h" +#include "Visualization/VisualizationGraphWidget.h" + +#include "Variable/VariableController.h" + +#include "SqpApplication.h" +#include "DragDropHelper.h" Q_LOGGING_CATEGORY(LOG_VisualizationTabWidget, "VisualizationTabWidget") @@ -55,27 +61,49 @@ VisualizationTabWidget::VisualizationTabWidget(const QString &name, QWidget *par { ui->setupUi(this); + ui->dragDropContainer->setAcceptedMimeTypes({DragDropHelper::MIME_TYPE_GRAPH, DragDropHelper::MIME_TYPE_ZONE}); + connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccured, this, &VisualizationTabWidget::dropMimeData); + sqpApp->dragDropHelper().addDragDropScrollArea(ui->scrollArea); + // Widget is deleted when closed setAttribute(Qt::WA_DeleteOnClose); } VisualizationTabWidget::~VisualizationTabWidget() { + sqpApp->dragDropHelper().removeDragDropScrollArea(ui->scrollArea); delete ui; } void VisualizationTabWidget::addZone(VisualizationZoneWidget *zoneWidget) { - tabLayout().addWidget(zoneWidget); + ui->dragDropContainer->addDragWidget(zoneWidget); +} + +void VisualizationTabWidget::insertZone(int index, VisualizationZoneWidget *zoneWidget) +{ + ui->dragDropContainer->insertDragWidget(index, zoneWidget); } VisualizationZoneWidget *VisualizationTabWidget::createZone(std::shared_ptr variable) { - auto zoneWidget = new VisualizationZoneWidget{defaultZoneName(tabLayout()), this}; - this->addZone(zoneWidget); + return createZone({variable}, -1); +} + +VisualizationZoneWidget *VisualizationTabWidget::createZone(const QList > &variables, int index) +{ + auto zoneWidget = createEmptyZone(index); // Creates a new graph into the zone - zoneWidget->createGraph(variable); + zoneWidget->createGraph(variables, index); + + return zoneWidget; +} + +VisualizationZoneWidget *VisualizationTabWidget::createEmptyZone(int index) +{ + auto zoneWidget = new VisualizationZoneWidget{defaultZoneName(*ui->dragDropContainer->layout()), this}; + this->insertZone(index, zoneWidget); return zoneWidget; } @@ -125,5 +153,67 @@ void VisualizationTabWidget::closeEvent(QCloseEvent *event) QLayout &VisualizationTabWidget::tabLayout() const noexcept { - return *ui->scrollAreaWidgetContents->layout(); + return *ui->dragDropContainer->layout(); +} + +void VisualizationTabWidget::dropMimeData(int index, const QMimeData *mimeData) +{ + auto& helper = sqpApp->dragDropHelper(); + if (mimeData->hasFormat(DragDropHelper::MIME_TYPE_GRAPH)) + { + auto graphWidget = static_cast(helper.getCurrentDragWidget()); + auto parentDragDropContainer = qobject_cast(graphWidget->parentWidget()); + Q_ASSERT(parentDragDropContainer); + + auto nbGraph = parentDragDropContainer->countDragWidget(); + + const auto& variables = graphWidget->variables(); + + if (!variables.isEmpty()) + { + //Abort the requests for the variables (if any) + //Commented, because it's not sure if it's needed or not + //for (const auto& var : variables) + //{ + // sqpApp->variableController().onAbortProgressRequested(var); + //} + + if (nbGraph == 1) + { + //This is the only graph in the previous zone, close the zone + graphWidget->parentZoneWidget()->close(); + } + else + { + //Close the graph + graphWidget->close(); + } + + createZone(variables, index); + } + else + { + //The graph is empty, create an empty zone and move the graph inside + + auto parentZoneWidget = graphWidget->parentZoneWidget(); + + parentDragDropContainer->layout()->removeWidget(graphWidget); + + auto zoneWidget = createEmptyZone(index); + zoneWidget->addGraph(graphWidget); + + //Close the old zone if it was the only graph inside + if (nbGraph == 1) + parentZoneWidget->close(); + } + } + else if (mimeData->hasFormat(DragDropHelper::MIME_TYPE_ZONE)) + { + //Simple move of the zone, no variable operation associated + auto zoneWidget = static_cast(helper.getCurrentDragWidget()); + auto parentDragDropContainer = zoneWidget->parentWidget(); + parentDragDropContainer->layout()->removeWidget(zoneWidget); + + ui->dragDropContainer->insertDragWidget(index, zoneWidget); + } } diff --git a/gui/src/Visualization/VisualizationZoneWidget.cpp b/gui/src/Visualization/VisualizationZoneWidget.cpp index 7d1998a..3cb3499 100644 --- a/gui/src/Visualization/VisualizationZoneWidget.cpp +++ b/gui/src/Visualization/VisualizationZoneWidget.cpp @@ -11,8 +11,11 @@ #include #include +#include #include +#include + Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget") namespace { @@ -66,7 +69,7 @@ struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate { }; VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent) - : QWidget{parent}, + : VisualizationDragWidget{parent}, ui{new Ui::VisualizationZoneWidget}, impl{spimpl::make_unique_impl()} { @@ -74,6 +77,9 @@ VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *p ui->zoneNameLabel->setText(name); + ui->dragDropContainer->setAcceptedMimeTypes({DragDropHelper::MIME_TYPE_GRAPH}); + connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccured, this, &VisualizationZoneWidget::dropMimeData); + // 'Close' options : widget is deleted when closed setAttribute(Qt::WA_DeleteOnClose); connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close); @@ -94,13 +100,26 @@ void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget) // Synchronize new graph with others in the zone impl->m_Synchronizer->addGraph(*graphWidget); - ui->visualizationZoneFrame->layout()->addWidget(graphWidget); + ui->dragDropContainer->addDragWidget(graphWidget); +} + +void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget) +{ + // Synchronize new graph with others in the zone + impl->m_Synchronizer->addGraph(*graphWidget); + + ui->dragDropContainer->insertDragWidget(index, graphWidget); } VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr variable) { + return createGraph(variable, -1); +} + +VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr variable, int index) +{ auto graphWidget = new VisualizationGraphWidget{ - defaultGraphName(*ui->visualizationZoneFrame->layout()), this}; + defaultGraphName(*ui->dragDropContainer->layout()), this}; // Set graph properties @@ -113,7 +132,7 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrvisualizationZoneFrame->layout(); + auto frameLayout = ui->dragDropContainer->layout(); for (auto i = 0; i < frameLayout->count(); ++i) { auto graphChild = dynamic_cast(frameLayout->itemAt(i)->widget()); @@ -203,11 +222,11 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrvisualizationZoneFrame->layout(); + auto layout = ui->dragDropContainer->layout(); if (layout->count() > 0) { // Case of a new graph in a existant zone if (auto visualizationGraphWidget - = dynamic_cast(layout->itemAt(0)->widget())) { + = dynamic_cast(layout->itemAt(0)->widget())) { range = visualizationGraphWidget->graphRange(); } } @@ -216,7 +235,7 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrrange(); } - this->addGraph(graphWidget); + this->insertGraph(index, graphWidget); graphWidget->addVariable(variable, range); @@ -224,7 +243,7 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrdataSeries()) { dataSeries->lockRead(); auto valuesBounds - = dataSeries->valuesBounds(variable->range().m_TStart, variable->range().m_TEnd); + = dataSeries->valuesBounds(variable->range().m_TStart, variable->range().m_TEnd); auto end = dataSeries->cend(); if (valuesBounds.first != end && valuesBounds.second != end) { auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; }; @@ -240,6 +259,20 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr > variables, int index) +{ + if (variables.isEmpty()) + return nullptr; + + auto graphWidget = createGraph(variables.first(), index); + for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) + { + graphWidget->addVariable(*variableIt, graphWidget->graphRange()); + } + + return graphWidget; +} + void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { @@ -248,7 +281,7 @@ void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor) // Apply visitor to graph children: widgets different from graphs are not visited (no // action) processGraphs( - *ui->visualizationZoneFrame->layout(), + *ui->dragDropContainer->layout(), [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); }); visitor->visitLeave(this); @@ -276,10 +309,23 @@ QString VisualizationZoneWidget::name() const return ui->zoneNameLabel->text(); } +QMimeData *VisualizationZoneWidget::mimeData() const +{ + auto *mimeData = new QMimeData; + mimeData->setData(DragDropHelper::MIME_TYPE_ZONE, QByteArray()); + + return mimeData; +} + +bool VisualizationZoneWidget::isDragAllowed() const +{ + return true; +} + void VisualizationZoneWidget::closeEvent(QCloseEvent *event) { // Closes graphs in the zone - processGraphs(*ui->visualizationZoneFrame->layout(), + processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); }); // Delete synchronization group from variable controller @@ -302,3 +348,66 @@ void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr, variable), Q_ARG(QUuid, impl->m_SynchronisationGroupId)); } + +void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData) +{ + auto& helper = sqpApp->dragDropHelper(); + if (mimeData->hasFormat(DragDropHelper::MIME_TYPE_GRAPH)) + { + auto graphWidget = static_cast(helper.getCurrentDragWidget()); + auto parentDragDropContainer = qobject_cast(graphWidget->parentWidget()); + Q_ASSERT(parentDragDropContainer); + + const auto& variables = graphWidget->variables(); + + if (parentDragDropContainer != ui->dragDropContainer && !variables.isEmpty()) + { + //The drop didn't occur in the same zone + + //Abort the requests for the variables (if any) + //Commented, because it's not sure if it's needed or not + //for (const auto& var : variables) + //{ + // sqpApp->variableController().onAbortProgressRequested(var); + //} + + auto previousParentZoneWidget = graphWidget->parentZoneWidget(); + auto nbGraph = parentDragDropContainer->countDragWidget(); + if (nbGraph == 1) + { + //This is the only graph in the previous zone, close the zone + previousParentZoneWidget->close(); + } + else + { + //Close the graph + graphWidget->close(); + } + + //Creates the new graph in the zone + createGraph(variables, index); + } + else + { + //The drop occurred in the same zone or the graph is empty + //Simple move of the graph, no variable operation associated + parentDragDropContainer->layout()->removeWidget(graphWidget); + + if (variables.isEmpty() && parentDragDropContainer != ui->dragDropContainer) + { + // The graph is empty and dropped in a different zone. + // Take the range of the first graph in the zone (if existing). + auto layout = ui->dragDropContainer->layout(); + if (layout->count() > 0) + { + if (auto visualizationGraphWidget = qobject_cast(layout->itemAt(0)->widget())) + { + graphWidget->setGraphRange(visualizationGraphWidget->graphRange()); + } + } + } + + ui->dragDropContainer->insertDragWidget(index, graphWidget); + } + } +} diff --git a/gui/ui/Visualization/VisualizationTabWidget.ui b/gui/ui/Visualization/VisualizationTabWidget.ui index 60b3e1c..afdd13e 100644 --- a/gui/ui/Visualization/VisualizationTabWidget.ui +++ b/gui/ui/Visualization/VisualizationTabWidget.ui @@ -62,12 +62,23 @@ 0 + + + + + + VisualizationDragDropContainer + QWidget +
Visualization/VisualizationDragDropContainer.h
+ 1 +
+
diff --git a/gui/ui/Visualization/VisualizationZoneWidget.ui b/gui/ui/Visualization/VisualizationZoneWidget.ui index 79d6be0..9746ca6 100644 --- a/gui/ui/Visualization/VisualizationZoneWidget.ui +++ b/gui/ui/Visualization/VisualizationZoneWidget.ui @@ -107,11 +107,22 @@ 0 + + + + + + VisualizationDragDropContainer + QWidget +
Visualization/VisualizationDragDropContainer.h
+ 1 +
+