#include "Visualization/VisualizationDragDropContainer.h" #include "DragAndDrop/DragDropGuiController.h" #include "SqpApplication.h" #include "Visualization/VisualizationDragWidget.h" #include "Common/VisualizationDef.h" #include #include #include #include #include Q_LOGGING_CATEGORY(LOG_VisualizationDragDropContainer, "VisualizationDragDropContainer") auto DRAGGED_MINIATURE_WIDTH = 200; // in pixels struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate { QVBoxLayout *m_Layout; QHash m_AcceptedMimeTypes; QString m_PlaceHolderText; DragDropGuiController::PlaceHolderType m_PlaceHolderType; VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun = [](auto mimeData) { return true; }; VisualizationDragDropContainer::AcceptDragWidgetFunction m_AcceptDragWidgetFun = [](auto dragWidget, auto mimeData) { return true; }; int m_MinContainerHeight = 0; explicit VisualizationDragDropContainerPrivate(QWidget *widget) : m_PlaceHolderType(DragDropGuiController::PlaceHolderType::Graph) { m_Layout = new QVBoxLayout(widget); m_Layout->setContentsMargins(0, 0, 0, 0); } bool acceptMimeData(const QMimeData *data) const { auto accepted = false; for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd(); ++it) { const auto &type = it.key(); const auto &behavior = it.value(); if (data->hasFormat(type)) { if (behavior != DropBehavior::Forbidden) { accepted = true; } else { accepted = false; break; } } } if (accepted) { accepted = m_AcceptMimeDataFun(data); } return accepted; } bool allowMergeForMimeData(const QMimeData *data) const { auto result = false; for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd(); ++it) { if (data->hasFormat(it.key()) && (it.value() == VisualizationDragDropContainer::DropBehavior::Merged || it.value() == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) { result = true; } else if (data->hasFormat(it.key()) && it.value() == VisualizationDragDropContainer::DropBehavior::Inserted) { // Merge is forbidden if the mime data contain an acceptable type which cannot be // merged result = false; break; } } return result; } bool allowInsertForMimeData(const QMimeData *data) const { for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd(); ++it) { if (data->hasFormat(it.key()) && (it.value() == VisualizationDragDropContainer::DropBehavior::Inserted || it.value() == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) { return true; } } return false; } bool hasPlaceHolder() const { return sqpApp->dragDropGuiController().placeHolder().parentWidget() == m_Layout->parentWidget(); } VisualizationDragWidget *getChildDragWidgetAt(const 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 widgetUnderMouse = sqpApp->widgetAt(QCursor::pos()); return container->isAncestorOf(widgetUnderMouse) && widgetUnderMouse != container && sqpApp->dragDropGuiController().placeHolder().isAncestorOf(widgetUnderMouse); } int countDragWidget(const QWidget *parent, bool onlyVisible = false) const { auto nbGraph = 0; for (auto child : parent->children()) { if (qobject_cast(child)) { if (!onlyVisible || qobject_cast(child)->isVisible()) { nbGraph += 1; } } } return nbGraph; } bool findPlaceHolderPosition(const QPoint &pos, const QMimeData *mimeData, bool canInsert, bool canMerge, const VisualizationDragDropContainer *container); }; VisualizationDragDropContainer::VisualizationDragDropContainer(QWidget *parent) : QFrame{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::setMimeType( const QString &mimeType, VisualizationDragDropContainer::DropBehavior behavior) { impl->m_AcceptedMimeTypes[mimeType] = behavior; } int VisualizationDragDropContainer::countDragWidget() const { return impl->countDragWidget(this); } void VisualizationDragDropContainer::setAcceptMimeDataFunction( VisualizationDragDropContainer::AcceptMimeDataFunction fun) { impl->m_AcceptMimeDataFun = fun; } void VisualizationDragDropContainer::setAcceptDragWidgetFunction( VisualizationDragDropContainer::AcceptDragWidgetFunction fun) { impl->m_AcceptDragWidgetFun = fun; } void VisualizationDragDropContainer::setPlaceHolderType(DragDropGuiController::PlaceHolderType type, const QString &placeHolderText) { impl->m_PlaceHolderType = type; impl->m_PlaceHolderText = placeHolderText; } void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidget, const QPoint &dragPosition) { auto &helper = sqpApp->dragDropGuiController(); helper.resetDragAndDrop(); // Note: The management of the drag object is done by Qt auto drag = new QDrag{dragWidget}; auto mimeData = dragWidget->mimeData(dragPosition); drag->setMimeData(mimeData); auto pixmap = dragWidget->customDragPixmap(dragPosition); if (pixmap.isNull()) { pixmap = QPixmap{dragWidget->size()}; dragWidget->render(&pixmap); } drag->setPixmap(pixmap.scaled(DRAGGED_MINIATURE_WIDTH, DRAGGED_MINIATURE_WIDTH, Qt::KeepAspectRatio, Qt::SmoothTransformation)); auto image = pixmap.toImage(); mimeData->setImageData(image); mimeData->setUrls({helper.imageTemporaryUrl(image)}); if (impl->m_Layout->indexOf(dragWidget) >= 0) { if (impl->acceptMimeData(mimeData) && impl->allowInsertForMimeData(mimeData)) { helper.setCurrentDragWidget(dragWidget); if (impl->cursorIsInContainer(this)) { auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget); helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType, impl->m_PlaceHolderText); dragWidget->setVisible(false); } else { // The drag starts directly outside the drop zone // do not add the placeHolder } } drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction); helper.doCloseWidgets(); } else { qCWarning(LOG_VisualizationDragDropContainer()) << tr("VisualizationDragDropContainer::startDrag, drag aborted, the specified " "VisualizationDragWidget is not found in this container."); } } void VisualizationDragDropContainer::dragEnterEvent(QDragEnterEvent *event) { if (impl->acceptMimeData(event->mimeData())) { event->acceptProposedAction(); auto &helper = sqpApp->dragDropGuiController(); if (!impl->hasPlaceHolder()) { auto dragWidget = helper.getCurrentDragWidget(); if (dragWidget) { // If the drag&drop is internal to the visualization, entering the container hide // the dragWidget which was made visible by the dragLeaveEvent auto parentWidget = qobject_cast(dragWidget->parentWidget()); if (parentWidget) { dragWidget->setVisible(false); } } auto canMerge = impl->allowMergeForMimeData(event->mimeData()); auto canInsert = impl->allowInsertForMimeData(event->mimeData()); if (!impl->findPlaceHolderPosition(event->pos(), event->mimeData(), canInsert, canMerge, this)) { event->ignore(); } } else { // do nothing } } else { event->ignore(); } QWidget::dragEnterEvent(event); } void VisualizationDragDropContainer::dragLeaveEvent(QDragLeaveEvent *event) { Q_UNUSED(event); auto &helper = sqpApp->dragDropGuiController(); if (!impl->cursorIsInContainer(this)) { helper.removePlaceHolder(); helper.setHightlightedDragWidget(nullptr); impl->m_MinContainerHeight = 0; auto dragWidget = helper.getCurrentDragWidget(); if (dragWidget) { // dragWidget has a value only if the drag is started from the visualization // In that case, shows 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) if (dragWidget) { dragWidget->setVisible(true); } } } else { // Leave event probably received for a child widget. // Do nothing. // Note: The DragLeave event, doesn't have any mean to determine who sent it. } QWidget::dragLeaveEvent(event); } void VisualizationDragDropContainer::dragMoveEvent(QDragMoveEvent *event) { if (impl->acceptMimeData(event->mimeData())) { auto canMerge = impl->allowMergeForMimeData(event->mimeData()); auto canInsert = impl->allowInsertForMimeData(event->mimeData()); impl->findPlaceHolderPosition(event->pos(), event->mimeData(), canInsert, canMerge, this); } else { event->ignore(); } QWidget::dragMoveEvent(event); } void VisualizationDragDropContainer::dropEvent(QDropEvent *event) { auto &helper = sqpApp->dragDropGuiController(); if (impl->acceptMimeData(event->mimeData())) { auto dragWidget = helper.getCurrentDragWidget(); if (impl->hasPlaceHolder()) { // drop where the placeHolder is located auto canInsert = impl->allowInsertForMimeData(event->mimeData()); if (canInsert) { auto droppedIndex = impl->m_Layout->indexOf(&helper.placeHolder()); if (dragWidget) { auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget); if (dragWidgetIndex >= 0 && dragWidgetIndex < droppedIndex) { // Correction of the index if the drop occurs in the same container // and if the drag is started from the visualization (in that case, the // dragWidget is hidden) droppedIndex -= 1; } dragWidget->setVisible(true); } event->acceptProposedAction(); helper.removePlaceHolder(); emit dropOccuredInContainer(droppedIndex, event->mimeData()); } else { qCWarning(LOG_VisualizationDragDropContainer()) << tr( "VisualizationDragDropContainer::dropEvent, dropping on the placeHolder, but " "the insertion is forbidden."); Q_ASSERT(false); } } else if (helper.getHightlightedDragWidget()) { // drop on the highlighted widget auto canMerge = impl->allowMergeForMimeData(event->mimeData()); if (canMerge) { event->acceptProposedAction(); emit dropOccuredOnWidget(helper.getHightlightedDragWidget(), event->mimeData()); } else { qCWarning(LOG_VisualizationDragDropContainer()) << tr("VisualizationDragDropContainer::dropEvent, dropping on a widget, but " "the merge is forbidden."); Q_ASSERT(false); } } } else { event->ignore(); } sqpApp->dragDropGuiController().setHightlightedDragWidget(nullptr); impl->m_MinContainerHeight = 0; QWidget::dropEvent(event); } bool VisualizationDragDropContainer::VisualizationDragDropContainerPrivate::findPlaceHolderPosition( const QPoint &pos, const QMimeData *mimeData, bool canInsert, bool canMerge, const VisualizationDragDropContainer *container) { auto &helper = sqpApp->dragDropGuiController(); auto absPos = container->mapToGlobal(pos); auto isOnPlaceHolder = helper.placeHolder().isAncestorOf(sqpApp->widgetAt(absPos)); if (countDragWidget(container, true) == 0) { // Drop on an empty container, just add the placeHolder at the top helper.insertPlaceHolder(m_Layout, 0, m_PlaceHolderType, m_PlaceHolderText); } else if (!isOnPlaceHolder) { auto nbDragWidget = countDragWidget(container); if (nbDragWidget > 0) { if (m_MinContainerHeight == 0) { m_MinContainerHeight = container->size().height(); } m_MinContainerHeight = qMin(m_MinContainerHeight, container->size().height()); auto graphHeight = qMax(m_MinContainerHeight / nbDragWidget, GRAPH_MINIMUM_HEIGHT); auto posY = pos.y(); auto dropIndex = floor(posY / graphHeight); auto zoneSize = qMin(graphHeight / 4.0, 75.0); auto isOnTop = posY < dropIndex * graphHeight + zoneSize; auto isOnBottom = posY > (dropIndex + 1) * graphHeight - zoneSize; auto placeHolderIndex = m_Layout->indexOf(&(helper.placeHolder())); auto dragWidgetHovered = getChildDragWidgetAt(container, pos); auto acceptMerge = m_AcceptDragWidgetFun(dragWidgetHovered, mimeData); if (canInsert && (isOnTop || isOnBottom || !canMerge || !acceptMerge)) { if (posY > (dropIndex + 1) * graphHeight - graphHeight / 2.0) { dropIndex += 1; } if (helper.getCurrentDragWidget()) { auto dragWidgetIndex = m_Layout->indexOf(helper.getCurrentDragWidget()); if (dragWidgetIndex >= 0 && dragWidgetIndex <= dropIndex) { // Correction of the index if the drop occurs in the same container // and if the drag is started from the visualization (in that case, the // dragWidget is hidden) dropIndex += 1; } } if (dropIndex != placeHolderIndex) { helper.insertPlaceHolder(m_Layout, dropIndex, m_PlaceHolderType, m_PlaceHolderText); } helper.setHightlightedDragWidget(nullptr); } else if (canMerge && dragWidgetHovered) { // drop on the middle -> merge if (hasPlaceHolder()) { helper.removePlaceHolder(); } helper.setHightlightedDragWidget(dragWidgetHovered); } else { qCWarning(LOG_VisualizationDragDropContainer()) << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no valid drop " "action."); } } else { qCWarning(LOG_VisualizationDragDropContainer()) << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no widget " "found in the " "container"); } } else { // the mouse is hover the placeHolder // Do nothing } return true; }