diff --git a/src/presentation/querytreemodelbase.cpp b/src/presentation/querytreemodelbase.cpp --- a/src/presentation/querytreemodelbase.cpp +++ b/src/presentation/querytreemodelbase.cpp @@ -25,10 +25,13 @@ #include "querytreemodelbase.h" +#include #include #include +Q_DECLARE_METATYPE(QModelIndexList) + using namespace Presentation; QueryTreeNodeBase::QueryTreeNodeBase(QueryTreeNodeBase *parent, QueryTreeModelBase *model) @@ -197,20 +200,37 @@ if (row != -1 || column != -1) return false; + // If that's not holding that mime type we can't do the cycle checking + // this is relevant only for internal drag and drop anyway + if (data->hasFormat("application/x-zanshin-indexes")) { + const auto indexes = data->property("indexes").value(); + foreach (const auto &index, indexes) { + auto p = parent; + while (p.isValid()) { + if (p == index) // Oops, we found a cycle (one of the indexes is parent of the drop point) + return false; + p = p.parent(); + } + } + } + return nodeFromIndex(parent)->dropMimeData(data, action); } QMimeData *QueryTreeModelBase::mimeData(const QModelIndexList &indexes) const { if (indexes.isEmpty()) return Q_NULLPTR; - return createMimeData(indexes); + auto data = createMimeData(indexes); + data->setData("application/x-zanshin-indexes", "indexes"); + data->setProperty("indexes", QVariant::fromValue(indexes)); + return data; } QStringList QueryTreeModelBase::mimeTypes() const { - return QAbstractItemModel::mimeTypes() << "application/x-zanshin-object"; + return QAbstractItemModel::mimeTypes() << "application/x-zanshin-object" << "application/x-zanshin-indexes"; } QueryTreeNodeBase *QueryTreeModelBase::nodeFromIndex(const QModelIndex &index) const diff --git a/tests/units/presentation/querytreemodeltest.cpp b/tests/units/presentation/querytreemodeltest.cpp --- a/tests/units/presentation/querytreemodeltest.cpp +++ b/tests/units/presentation/querytreemodeltest.cpp @@ -874,6 +874,75 @@ QCOMPARE(colorSeen, parent.data(Presentation::QueryTreeModelBase::ObjectRole).value()); } } + + void shouldPreventCyclesByDragAndDrop() + { + // GIVEN + bool dropCalled = false; + const QMimeData *droppedData = Q_NULLPTR; + + auto topProvider = Domain::QueryResultProvider::Ptr::create(); + topProvider->append("1"); + topProvider->append("2"); + topProvider->append("3"); + + auto firstLevelProvider = Domain::QueryResultProvider::Ptr::create(); + firstLevelProvider->append("2.1"); + firstLevelProvider->append("2.2"); + firstLevelProvider->append("2.3"); + + auto secondLevelProvider = Domain::QueryResultProvider::Ptr::create(); + secondLevelProvider->append("2.1.1"); + secondLevelProvider->append("2.1.2"); + secondLevelProvider->append("2.1.3"); + + auto queryGenerator = [&] (const QString &string) { + if (string.isEmpty()) + return Domain::QueryResult::create(topProvider); + else if (string == "2") + return Domain::QueryResult::create(firstLevelProvider); + else if (string == "2.1") + return Domain::QueryResult::create(secondLevelProvider); + else + return Domain::QueryResult::Ptr(); + }; + auto flagsFunction = [] (const QString &) { + return Qt::NoItemFlags; + }; + auto dataFunction = [] (const QString &, int) { + return QVariant(); + }; + auto setDataFunction = [] (const QString &, const QVariant &, int) { + return false; + }; + auto dropFunction = [&] (const QMimeData *, Qt::DropAction, const QString &) { + dropCalled = true; + return false; + }; + auto dragFunction = [] (const QStringList &strings) -> QMimeData* { + auto data = new QMimeData; + data->setData("application/x-zanshin-object", "object"); + data->setProperty("objects", QVariant::fromValue(strings)); + return data; + }; + + Presentation::QueryTreeModel model(queryGenerator, flagsFunction, + dataFunction, setDataFunction, + dropFunction, dragFunction); + new ModelTest(&model); + + const auto indexes = QModelIndexList() << model.index(0, 0) + << model.index(1, 0) + << model.index(1, 0, model.index(1, 0)); + + // WHEN + auto data = model.mimeData(indexes); + const auto parent = model.index(1, 0, model.index(0, 0, model.index(1, 0))); + model.dropMimeData(data, Qt::MoveAction, -1, -1, parent); + + // THEN + QVERIFY(!dropCalled); + } }; QTEST_MAIN(QueryTreeModelTest)