VisualizationDragDropContainer.cpp
453 lines
| 16.1 KiB
| text/x-c
|
CppLexer
r838 | #include "Visualization/VisualizationDragDropContainer.h" | |||
r884 | #include "DragAndDrop/DragDropHelper.h" | |||
r844 | #include "SqpApplication.h" | |||
#include "Visualization/VisualizationDragWidget.h" | ||||
r838 | ||||
r851 | #include "Common/VisualizationDef.h" | |||
r838 | #include <QDrag> | |||
#include <QDragEnterEvent> | ||||
r844 | #include <QVBoxLayout> | |||
r838 | ||||
r845 | #include <cmath> | |||
r838 | #include <memory> | |||
r850 | Q_LOGGING_CATEGORY(LOG_VisualizationDragDropContainer, "VisualizationDragDropContainer") | |||
r838 | struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate { | |||
r844 | QVBoxLayout *m_Layout; | |||
r879 | QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes; | |||
r881 | QString m_PlaceHolderText; | |||
DragDropHelper::PlaceHolderType m_PlaceHolderType = DragDropHelper::PlaceHolderType::Graph; | ||||
r879 | ||||
r850 | VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun | |||
= [](auto mimeData) { return true; }; | ||||
r879 | ||||
r873 | int m_MinContainerHeight = 0; | |||
r838 | ||||
r844 | explicit VisualizationDragDropContainerPrivate(QWidget *widget) | |||
r838 | { | |||
r844 | m_Layout = new QVBoxLayout(widget); | |||
m_Layout->setContentsMargins(0, 0, 0, 0); | ||||
r838 | } | |||
r844 | bool acceptMimeData(const QMimeData *data) const | |||
r838 | { | |||
r879 | for (const auto &type : m_AcceptedMimeTypes.keys()) { | |||
r850 | if (data->hasFormat(type) && m_AcceptMimeDataFun(data)) { | |||
r838 | return true; | |||
r844 | } | |||
r838 | } | |||
return false; | ||||
} | ||||
r879 | bool allowMergeForMimeData(const QMimeData *data) const | |||
r838 | { | |||
r879 | bool 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)) { | ||||
r838 | return true; | |||
r844 | } | |||
r838 | } | |||
return false; | ||||
} | ||||
bool hasPlaceHolder() const | ||||
{ | ||||
r844 | return sqpApp->dragDropHelper().placeHolder().parentWidget() == m_Layout->parentWidget(); | |||
r838 | } | |||
r873 | VisualizationDragWidget *getChildDragWidgetAt(const QWidget *parent, const QPoint &pos) const | |||
r838 | { | |||
r844 | VisualizationDragWidget *dragWidget = nullptr; | |||
for (auto child : parent->children()) { | ||||
auto widget = qobject_cast<VisualizationDragWidget *>(child); | ||||
if (widget && widget->isVisible()) { | ||||
if (widget->frameGeometry().contains(pos)) { | ||||
r838 | dragWidget = widget; | |||
break; | ||||
} | ||||
} | ||||
} | ||||
return dragWidget; | ||||
} | ||||
r844 | bool cursorIsInContainer(QWidget *container) const | |||
r838 | { | |||
r874 | return container->isAncestorOf(sqpApp->widgetAt(QCursor::pos())); | |||
r838 | } | |||
r873 | ||||
r880 | int countDragWidget(const QWidget *parent, bool onlyVisible = false) const | |||
r873 | { | |||
auto nbGraph = 0; | ||||
for (auto child : parent->children()) { | ||||
if (qobject_cast<VisualizationDragWidget *>(child)) { | ||||
r880 | if (!onlyVisible || qobject_cast<VisualizationDragWidget *>(child)->isVisible()) { | |||
nbGraph += 1; | ||||
} | ||||
r873 | } | |||
} | ||||
return nbGraph; | ||||
} | ||||
r879 | void findPlaceHolderPosition(const QPoint &pos, bool canInsert, bool canMerge, | |||
r873 | const VisualizationDragDropContainer *container); | |||
r838 | }; | |||
VisualizationDragDropContainer::VisualizationDragDropContainer(QWidget *parent) | ||||
r880 | : QFrame{parent}, | |||
r844 | impl{spimpl::make_unique_impl<VisualizationDragDropContainerPrivate>(this)} | |||
r838 | { | |||
setAcceptDrops(true); | ||||
} | ||||
void VisualizationDragDropContainer::addDragWidget(VisualizationDragWidget *dragWidget) | ||||
{ | ||||
r844 | impl->m_Layout->addWidget(dragWidget); | |||
r838 | disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr); | |||
r844 | connect(dragWidget, &VisualizationDragWidget::dragDetected, this, | |||
&VisualizationDragDropContainer::startDrag); | ||||
r838 | } | |||
r844 | void VisualizationDragDropContainer::insertDragWidget(int index, | |||
VisualizationDragWidget *dragWidget) | ||||
r838 | { | |||
r844 | impl->m_Layout->insertWidget(index, dragWidget); | |||
r838 | disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr); | |||
r844 | connect(dragWidget, &VisualizationDragWidget::dragDetected, this, | |||
&VisualizationDragDropContainer::startDrag); | ||||
r838 | } | |||
r879 | void VisualizationDragDropContainer::addAcceptedMimeType( | |||
const QString &mimeType, VisualizationDragDropContainer::DropBehavior behavior) | ||||
r838 | { | |||
r879 | impl->m_AcceptedMimeTypes[mimeType] = behavior; | |||
r838 | } | |||
int VisualizationDragDropContainer::countDragWidget() const | ||||
{ | ||||
r873 | return impl->countDragWidget(this); | |||
r838 | } | |||
r850 | void VisualizationDragDropContainer::setAcceptMimeDataFunction( | |||
VisualizationDragDropContainer::AcceptMimeDataFunction fun) | ||||
{ | ||||
impl->m_AcceptMimeDataFun = fun; | ||||
} | ||||
r881 | void VisualizationDragDropContainer::setPlaceHolderType(DragDropHelper::PlaceHolderType type, | |||
const QString &placeHolderText) | ||||
{ | ||||
impl->m_PlaceHolderType = type; | ||||
impl->m_PlaceHolderText = placeHolderText; | ||||
} | ||||
r844 | void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidget, | |||
const QPoint &dragPosition) | ||||
r838 | { | |||
r844 | auto &helper = sqpApp->dragDropHelper(); | |||
r850 | helper.resetDragAndDrop(); | |||
r838 | ||||
r844 | // Note: The management of the drag object is done by Qt | |||
r846 | auto drag = new QDrag{dragWidget}; | |||
r838 | 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)}); | ||||
r844 | if (impl->m_Layout->indexOf(dragWidget) >= 0) { | |||
r838 | helper.setCurrentDragWidget(dragWidget); | |||
r844 | if (impl->cursorIsInContainer(this)) { | |||
auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget); | ||||
r881 | helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType, | |||
impl->m_PlaceHolderText); | ||||
r838 | dragWidget->setVisible(false); | |||
} | ||||
r874 | else { | |||
// The drag starts directly outside the drop zone | ||||
// do not add the placeHolder | ||||
} | ||||
r838 | ||||
r850 | // Note: The exec() is blocking on windows but not on linux and macOS | |||
drag->exec(Qt::MoveAction | Qt::CopyAction); | ||||
} | ||||
else { | ||||
qCWarning(LOG_VisualizationDragDropContainer()) | ||||
<< tr("VisualizationDragDropContainer::startDrag, drag aborted, the specified " | ||||
"VisualizationDragWidget is not found in this container."); | ||||
} | ||||
r838 | } | |||
void VisualizationDragDropContainer::dragEnterEvent(QDragEnterEvent *event) | ||||
{ | ||||
r844 | if (impl->acceptMimeData(event->mimeData())) { | |||
r838 | event->acceptProposedAction(); | |||
r844 | auto &helper = sqpApp->dragDropHelper(); | |||
r838 | ||||
r844 | if (!impl->hasPlaceHolder()) { | |||
r838 | auto dragWidget = helper.getCurrentDragWidget(); | |||
r850 | ||||
if (dragWidget) { | ||||
// If the drag&drop is internal to the visualization, entering the container hide | ||||
r873 | // the dragWidget which was made visible by the dragLeaveEvent | |||
r850 | auto parentWidget | |||
= qobject_cast<VisualizationDragDropContainer *>(dragWidget->parentWidget()); | ||||
if (parentWidget) { | ||||
dragWidget->setVisible(false); | ||||
} | ||||
r838 | } | |||
r879 | auto canMerge = impl->allowMergeForMimeData(event->mimeData()); | |||
auto canInsert = impl->allowInsertForMimeData(event->mimeData()); | ||||
impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this); | ||||
r838 | } | |||
r850 | else { | |||
// do nothing | ||||
} | ||||
r838 | } | |||
r846 | else { | |||
r838 | event->ignore(); | |||
r846 | } | |||
r838 | ||||
QWidget::dragEnterEvent(event); | ||||
} | ||||
void VisualizationDragDropContainer::dragLeaveEvent(QDragLeaveEvent *event) | ||||
{ | ||||
Q_UNUSED(event); | ||||
r844 | auto &helper = sqpApp->dragDropHelper(); | |||
r838 | ||||
r844 | if (!impl->cursorIsInContainer(this)) { | |||
r838 | helper.removePlaceHolder(); | |||
r873 | helper.setHightlightedDragWidget(nullptr); | |||
impl->m_MinContainerHeight = 0; | ||||
r838 | ||||
r850 | 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 | ||||
r844 | // 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) | ||||
r838 | ||||
r844 | if (dragWidget) { | |||
r838 | dragWidget->setVisible(true); | |||
} | ||||
} | ||||
} | ||||
r850 | 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. | ||||
} | ||||
r838 | ||||
QWidget::dragLeaveEvent(event); | ||||
} | ||||
void VisualizationDragDropContainer::dragMoveEvent(QDragMoveEvent *event) | ||||
{ | ||||
r844 | if (impl->acceptMimeData(event->mimeData())) { | |||
r879 | auto canMerge = impl->allowMergeForMimeData(event->mimeData()); | |||
auto canInsert = impl->allowInsertForMimeData(event->mimeData()); | ||||
impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this); | ||||
r838 | } | |||
r846 | else { | |||
r838 | event->ignore(); | |||
r846 | } | |||
r838 | ||||
QWidget::dragMoveEvent(event); | ||||
} | ||||
void VisualizationDragDropContainer::dropEvent(QDropEvent *event) | ||||
{ | ||||
r875 | auto &helper = sqpApp->dragDropHelper(); | |||
r844 | if (impl->acceptMimeData(event->mimeData())) { | |||
r875 | auto dragWidget = helper.getCurrentDragWidget(); | |||
r850 | if (impl->hasPlaceHolder()) { | |||
r875 | // drop where the placeHolder is located | |||
r838 | ||||
r879 | auto canInsert = impl->allowInsertForMimeData(event->mimeData()); | |||
if (canInsert) { | ||||
auto droppedIndex = impl->m_Layout->indexOf(&helper.placeHolder()); | ||||
r838 | ||||
r879 | 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; | ||||
} | ||||
r838 | ||||
r879 | dragWidget->setVisible(true); | |||
} | ||||
r838 | ||||
r879 | event->acceptProposedAction(); | |||
r838 | ||||
r879 | helper.removePlaceHolder(); | |||
r838 | ||||
r879 | emit dropOccuredInContainer(droppedIndex, event->mimeData()); | |||
} | ||||
else { | ||||
qCWarning(LOG_VisualizationDragDropContainer()) << tr( | ||||
"VisualizationDragDropContainer::dropEvent, dropping on the placeHolder, but " | ||||
"the insertion is forbidden."); | ||||
Q_ASSERT(false); | ||||
} | ||||
r838 | } | |||
r875 | else if (helper.getHightlightedDragWidget()) { | |||
// drop on the highlighted widget | ||||
r879 | auto canMerge = impl->allowMergeForMimeData(event->mimeData()); | |||
r875 | 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); | ||||
} | ||||
r850 | } | |||
r838 | } | |||
r846 | else { | |||
r838 | event->ignore(); | |||
r846 | } | |||
r838 | ||||
r873 | sqpApp->dragDropHelper().setHightlightedDragWidget(nullptr); | |||
impl->m_MinContainerHeight = 0; | ||||
r838 | QWidget::dropEvent(event); | |||
} | ||||
r873 | ||||
void VisualizationDragDropContainer::VisualizationDragDropContainerPrivate::findPlaceHolderPosition( | ||||
r879 | const QPoint &pos, bool canInsert, bool canMerge, | |||
const VisualizationDragDropContainer *container) | ||||
r873 | { | |||
auto &helper = sqpApp->dragDropHelper(); | ||||
r880 | auto absPos = container->mapToGlobal(pos); | |||
auto isOnPlaceHolder = sqpApp->widgetAt(absPos) == &(helper.placeHolder()); | ||||
if (countDragWidget(container, true) == 0) { | ||||
// Drop on an empty container, just add the placeHolder at the top | ||||
r881 | helper.insertPlaceHolder(m_Layout, 0, m_PlaceHolderType, m_PlaceHolderText); | |||
r880 | } | |||
else if (!isOnPlaceHolder) { | ||||
r873 | 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())); | ||||
r880 | auto dragWidgetHovered = getChildDragWidgetAt(container, pos); | |||
r879 | if (canInsert && (isOnTop || isOnBottom || !canMerge)) { | |||
r873 | if (isOnBottom) { | |||
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) { | ||||
r881 | helper.insertPlaceHolder(m_Layout, dropIndex, m_PlaceHolderType, | |||
m_PlaceHolderText); | ||||
r873 | } | |||
helper.setHightlightedDragWidget(nullptr); | ||||
} | ||||
r880 | else if (canMerge && dragWidgetHovered) { | |||
r873 | // drop on the middle -> merge | |||
if (hasPlaceHolder()) { | ||||
helper.removePlaceHolder(); | ||||
} | ||||
helper.setHightlightedDragWidget(dragWidgetHovered); | ||||
} | ||||
r879 | else { | |||
r881 | qCWarning(LOG_VisualizationDragDropContainer()) | |||
<< tr("VisualizationDragDropContainer::findPlaceHolderPosition, no valid drop " | ||||
"action."); | ||||
r879 | Q_ASSERT(false); | |||
} | ||||
r873 | } | |||
else { | ||||
r880 | qCWarning(LOG_VisualizationDragDropContainer()) | |||
r881 | << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no widget " | |||
"found in the " | ||||
"container"); | ||||
r873 | } | |||
} | ||||
else { | ||||
r880 | // the mouse is hover the placeHolder | |||
r873 | // Do nothing | |||
} | ||||
} | ||||