From 86c6f684b23457ce66e9afeb50ff30f38dec9cbf Mon Sep 17 00:00:00 2001 From: vuurvlieg Date: Fri, 29 Mar 2024 22:44:03 +0100 Subject: [PATCH] Add untracked move action --- src/gui/entry/EntryModel.cpp | 2 +- src/gui/group/GroupModel.cpp | 31 ++++++++++---- src/gui/group/GroupView.cpp | 79 +++++++++++++++++++++++++++++++++--- src/gui/group/GroupView.h | 4 ++ 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/gui/entry/EntryModel.cpp b/src/gui/entry/EntryModel.cpp index 234a08ba7..e45c8c1ee 100644 --- a/src/gui/entry/EntryModel.cpp +++ b/src/gui/entry/EntryModel.cpp @@ -447,7 +447,7 @@ Qt::DropActions EntryModel::supportedDropActions() const Qt::DropActions EntryModel::supportedDragActions() const { - return (Qt::MoveAction | Qt::CopyAction); + return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction; } Qt::ItemFlags EntryModel::flags(const QModelIndex& modelIndex) const diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index 436660f91..c4c703cbd 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -180,7 +180,7 @@ Group* GroupModel::groupFromIndex(const QModelIndex& index) const Qt::DropActions GroupModel::supportedDropActions() const { - return Qt::MoveAction | Qt::CopyAction; + return Qt::MoveAction | Qt::CopyAction | Qt::LinkAction; } Qt::ItemFlags GroupModel::flags(const QModelIndex& modelIndex) const @@ -206,7 +206,7 @@ bool GroupModel::dropMimeData(const QMimeData* data, return true; } - if (!data || (action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) { + if (!data || !(action & (Qt::MoveAction | Qt::CopyAction | Qt::LinkAction)) || !parent.isValid()) { return false; } @@ -261,11 +261,20 @@ bool GroupModel::dropMimeData(const QMimeData* data, targetDb->metadata()->copyCustomIcons(group->customIconsRecursive(), sourceDb->metadata()); if (action == Qt::MoveAction) { - // For cross-db moves use a clone with new UUID but original CreationTime + // -- Tracked move + // A clone with new UUID but original CreationTime group = dragGroup->clone(Entry::CloneFlags(Entry::CloneCopy & ~Entry::CloneResetCreationTime), Group::CloneFlags(Group::CloneCopy & ~Group::CloneResetCreationTime)); - // Remove the original from the sourceDb to allow this change to sync to other dbs + // Original UUID is marked as deleted to remove it from dbs that merge with this one delete dragGroup; + } else if (action == Qt::LinkAction) { + // -- Untracked move + QList deletedObjects(sourceDb->deletedObjects()); + // Exact copy of original + group = dragGroup->clone(Entry::CloneExactCopy, Group::CloneExactCopy); + delete dragGroup; + // Unmark UUID(s) as deleted by restoring the previous list + sourceDb->setDeletedObjects(deletedObjects); } else { group = dragGroup->clone(Entry::CloneCopy); } @@ -302,12 +311,20 @@ bool GroupModel::dropMimeData(const QMimeData* data, if (sourceDb != targetDb) { targetDb->metadata()->copyCustomIcon(entry->iconUuid(), sourceDb->metadata()); - // Reset the UUID when moving across db boundary if (action == Qt::MoveAction) { - // For cross-db moves use a clone with new UUID but original CreationTime + // -- Tracked move + // A clone with new UUID but original CreationTime entry = dragEntry->clone(Entry::CloneFlags(Entry::CloneCopy & ~Entry::CloneResetCreationTime)); - // Remove the original from the sourceDb to allow this change to sync to other dbs + // Original UUID is marked as deleted to remove it from dbs that merge with this one delete dragEntry; + } else if (action == Qt::LinkAction) { + // -- Untracked move + QList deletedObjects(sourceDb->deletedObjects()); + // Exact copy of original + entry = dragEntry->clone(Entry::CloneExactCopy); + delete dragEntry; + // Unmark UUID as deleted by restoring the previous list + sourceDb->setDeletedObjects(deletedObjects); } else { entry = dragEntry->clone(Entry::CloneCopy); } diff --git a/src/gui/group/GroupView.cpp b/src/gui/group/GroupView.cpp index 5ffcc5d94..b2ac0dd97 100644 --- a/src/gui/group/GroupView.cpp +++ b/src/gui/group/GroupView.cpp @@ -24,11 +24,15 @@ #include "core/Config.h" #include "core/Group.h" #include "gui/group/GroupModel.h" +#include "gui/entry/EntryView.h" +#include "gui/DatabaseWidget.h" GroupView::GroupView(Database* db, QWidget* parent) : QTreeView(parent) , m_model(new GroupModel(db, this)) , m_updatingExpanded(false) + , m_isDragEventSrcFromOtherDb(false) + , m_lastAcceptedDropAction(Qt::IgnoreAction) { QTreeView::setModel(m_model); setHeaderHidden(true); @@ -73,20 +77,83 @@ void GroupView::changeDatabase(const QSharedPointer& newDb) m_model->changeDatabase(newDb.data()); } -void GroupView::dragMoveEvent(QDragMoveEvent* event) +void GroupView::dragEnterEvent(QDragEnterEvent *event) { - if (event->keyboardModifiers() & Qt::ControlModifier) { - event->setDropAction(Qt::CopyAction); - } else { - event->setDropAction(Qt::MoveAction); + event->ignore(); // default to ignore + + auto const eventSource = event->source(); + // ignore events from other processes + if (!eventSource) { + return; } + // ignore events with unsupported mime-types + auto supportedFormats = m_model->mimeTypes().toSet(); + if (!supportedFormats.intersects(event->mimeData()->formats().toSet())) { + return; + } + + auto firstAncestorOfTypeDatabaseWidget = [](QObject* object) -> DatabaseWidget* { + if (object) { + for (auto parent = object->parent(); parent; parent = parent->parent()) { + if (auto dbWidget = qobject_cast(parent)) { + return dbWidget; + } + } + } + return nullptr; + }; + + m_isDragEventSrcFromOtherDb = false; + if (GroupView* view = qobject_cast(eventSource)) { + m_isDragEventSrcFromOtherDb = view != this; + } else if (EntryView* view = qobject_cast(eventSource)) { + auto targetDbWidget = firstAncestorOfTypeDatabaseWidget(this); + auto sourceDbWidget = firstAncestorOfTypeDatabaseWidget(view); + m_isDragEventSrcFromOtherDb = sourceDbWidget != targetDbWidget; + } + + QTreeView::dragEnterEvent(event); +} + +void GroupView::dragMoveEvent(QDragMoveEvent* event) +{ QTreeView::dragMoveEvent(event); + if (!event->isAccepted()) { + return; + } + // entries may only be dropped on groups - if (event->isAccepted() && event->mimeData()->hasFormat("application/x-keepassx-entry") + if (event->mimeData()->hasFormat("application/x-keepassx-entry") && (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) { event->ignore(); + return; + } + + // figure out which dropaction should be used + Qt::DropAction dropAction = Qt::MoveAction; + if (event->keyboardModifiers() & Qt::ControlModifier) { + dropAction = Qt::CopyAction; + } else if (event->keyboardModifiers() & Qt::AltModifier) { + dropAction = m_isDragEventSrcFromOtherDb ? Qt::LinkAction : Qt::IgnoreAction; + } + + if (dropAction != Qt::IgnoreAction && event->possibleActions() & dropAction) { + event->setDropAction(dropAction); + m_lastAcceptedDropAction = event->dropAction(); + } else { + event->ignore(); + } +} + +void GroupView::dropEvent(QDropEvent* event) +{ + if (m_lastAcceptedDropAction != Qt::IgnoreAction) { + event->setDropAction(m_lastAcceptedDropAction); + QTreeView::dropEvent(event); + } else { + event->ignore(); } } diff --git a/src/gui/group/GroupView.h b/src/gui/group/GroupView.h index aa4fd85de..d0c23e777 100644 --- a/src/gui/group/GroupView.h +++ b/src/gui/group/GroupView.h @@ -48,7 +48,9 @@ private slots: void contextMenuShortcutPressed(); protected: + void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; void focusInEvent(QFocusEvent* event) override; private: @@ -56,6 +58,8 @@ private: GroupModel* const m_model; bool m_updatingExpanded; + bool m_isDragEventSrcFromOtherDb; + Qt::DropAction m_lastAcceptedDropAction; }; #endif // KEEPASSX_GROUPVIEW_H