#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); }