diff --git a/libs/ui/KisNodeView.cpp b/libs/ui/KisNodeView.cpp --- a/libs/ui/KisNodeView.cpp +++ b/libs/ui/KisNodeView.cpp @@ -522,6 +522,11 @@ void KisNodeView::dragEnterEvent(QDragEnterEvent *ev) { DRAG_WHILE_DRAG_WORKAROUND_START(); + + QVariant data = qVariantFromValue( + static_cast(const_cast(ev->mimeData()))); + model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled); + QTreeView::dragEnterEvent(ev); } diff --git a/libs/ui/kis_mimedata.h b/libs/ui/kis_mimedata.h --- a/libs/ui/kis_mimedata.h +++ b/libs/ui/kis_mimedata.h @@ -78,6 +78,12 @@ KisImageWSP image, KisShapeController *shapeController); + static KisNodeList loadNodesFast( + const QMimeData *data, + KisImageSP image, + KisShapeController *shapeController, + bool ©Node); + private: /** * Try load the node, which belongs to the same Krita instance, diff --git a/libs/ui/kis_mimedata.cpp b/libs/ui/kis_mimedata.cpp --- a/libs/ui/kis_mimedata.cpp +++ b/libs/ui/kis_mimedata.cpp @@ -327,7 +327,10 @@ KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); device->convertFromQImage(qimage, 0); - nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device); + + if (image) { + nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device); + } alwaysRecenter = true; } @@ -402,13 +405,11 @@ return result; } -bool KisMimeData::insertMimeLayers(const QMimeData *data, - KisImageSP image, - KisShapeController *shapeController, - KisNodeDummy *parentDummy, - KisNodeDummy *aboveThisDummy, - bool copyNode, - KisNodeInsertionAdapter *nodeInsertionAdapter) +KisNodeList KisMimeData::loadNodesFast( + const QMimeData *data, + KisImageSP image, + KisShapeController *shapeController, + bool ©Node) { QList nodes = KisMimeData::tryLoadInternalNodes(data, @@ -429,6 +430,19 @@ copyNode = true; } + return nodes; +} + +bool KisMimeData::insertMimeLayers(const QMimeData *data, + KisImageSP image, + KisShapeController *shapeController, + KisNodeDummy *parentDummy, + KisNodeDummy *aboveThisDummy, + bool copyNode, + KisNodeInsertionAdapter *nodeInsertionAdapter) +{ + QList nodes = loadNodesFast(data, image, shapeController, copyNode /* IN-OUT */); + if (nodes.isEmpty()) return false; bool result = true; diff --git a/libs/ui/kis_node_model.h b/libs/ui/kis_node_model.h --- a/libs/ui/kis_node_model.h +++ b/libs/ui/kis_node_model.h @@ -80,6 +80,10 @@ // An index of a color label associated with the node ColorLabelIndexRole, + // Instruct this model to update all its items' Qt::ItemIsDropEnabled flags in order to + // reflect if the item allows an "onto" drop of the given QMimeData*. + DropEnabled, + /// This is to ensure that we can extend the data role in the future, since it's not possible to add a role after BeginThumbnailRole (due to "Hack") ReservedRole = 99, @@ -119,6 +123,7 @@ QStringList mimeTypes() const override; QMimeData* mimeData(const QModelIndexList & indexes) const override; bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; + bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; Qt::DropActions supportedDragActions() const override; Qt::DropActions supportedDropActions() const override; bool hasDummiesFacade(); @@ -158,6 +163,9 @@ void regenerateItems(KisNodeDummy *dummy); bool belongsToIsolatedGroup(KisNodeSP node) const; + + void setDropEnabled(const QMimeData *data); + void updateDropEnabled(const QList &nodes, QModelIndex parent = QModelIndex()); private: diff --git a/libs/ui/kis_node_model.cpp b/libs/ui/kis_node_model.cpp --- a/libs/ui/kis_node_model.cpp +++ b/libs/ui/kis_node_model.cpp @@ -72,6 +72,8 @@ QPersistentModelIndex activeNodeIndex; QPointer parentOfRemovedNode = 0; + + QSet dropEnabled; }; KisNodeModel::KisNodeModel(QObject * parent) @@ -496,12 +498,20 @@ { if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled; - Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled; + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; + if (m_d->dropEnabled.contains(index.internalId())) { + flags |= Qt::ItemIsDropEnabled; + } return flags; } bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role == KisNodeModel::DropEnabled) { + const QMimeData *mimeData = static_cast(value.value()); + setDropEnabled(mimeData); + return true; + } if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) { QModelIndex parentIndex; @@ -644,3 +654,51 @@ m_d->nodeInsertionAdapter); } +bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { + if (parent.isValid()) { + // drop occured on an item. always return true as returning false will mess up + // QT5's drag handling (see KisNodeModel::setDropEnabled). + return true; + } else { + return QAbstractItemModel::canDropMimeData(data, action, row, column, parent); + } +} + +void KisNodeModel::setDropEnabled(const QMimeData *data) { + // what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5 + // will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by + // canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData() + // later on that an "onto" drag is not allowed, QT will display an drop indicator for + // insertion, but not perform any drop when the mouse is released. + + // the only robust implementation seems to set all flags correctly, which is done here. + + bool copyNode = false; + KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode); + m_d->dropEnabled.clear(); + updateDropEnabled(nodes); +} + +void KisNodeModel::updateDropEnabled(const QList &nodes, QModelIndex parent) { + for (int r = 0; r < rowCount(parent); r++) { + QModelIndex idx = index(r, 0, parent); + + KisNodeSP target = nodeFromIndex(idx); + + bool dropEnabled = true; + Q_FOREACH (const KisNodeSP &node, nodes) { + if (!target->allowAsChild(node)) { + dropEnabled = false; + break; + } + } + if (dropEnabled) { + m_d->dropEnabled.insert(idx.internalId()); + } + emit dataChanged(idx, idx); // indicate to QT that flags() have changed + + if (hasChildren(idx)) { + updateDropEnabled(nodes, idx); + } + } +}