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/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); +}