VisualizationDragDropContainer.cpp
554 lines
| 17.7 KiB
| text/x-c
|
CppLexer
r1397 | #include "Visualization/VisualizationDragDropContainer.h" | |||
#include "DragAndDrop/DragDropGuiController.h" | ||||
#include "SqpApplication.h" | ||||
#include "Visualization/VisualizationDragWidget.h" | ||||
#include "Common/VisualizationDef.h" | ||||
#include <QDrag> | ||||
#include <QDragEnterEvent> | ||||
#include <QVBoxLayout> | ||||
#include <cmath> | ||||
#include <memory> | ||||
Q_LOGGING_CATEGORY(LOG_VisualizationDragDropContainer, "VisualizationDragDropContainer") | ||||
auto DRAGGED_MINIATURE_WIDTH = 200; // in pixels | ||||
struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate | ||||
{ | ||||
QVBoxLayout* m_Layout; | ||||
QHash<QString, VisualizationDragDropContainer::DropBehavior> 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<VisualizationDragWidget*>(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<VisualizationDragWidget*>(child)) | ||||
{ | ||||
if (!onlyVisible || qobject_cast<VisualizationDragWidget*>(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<VisualizationDragDropContainerPrivate>(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<VisualizationDragDropContainer*>(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())) | ||||
{ | ||||
event->acceptProposedAction(); | ||||
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 = graphHeight / 4.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 | ||||
{ | ||||
qCInfo(LOG_VisualizationDragDropContainer()) | ||||
<< tr("VisualizationDragDropContainer::findPlaceHolderPosition, no widget " | ||||
"found in the " | ||||
"container"); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
// the mouse is hover the placeHolder | ||||
// Do nothing | ||||
} | ||||
return true; | ||||
} | ||||