File indexing completed on 2024-05-19 04:29:02

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 #include "kis_node_model.h"
0008 
0009 #include <iostream>
0010 
0011 #include <QMimeData>
0012 #include <QBuffer>
0013 #include <QPointer>
0014 
0015 #include <KoColorSpaceConstants.h>
0016 #include <KoCompositeOpRegistry.h>
0017 
0018 #include <klocalizedstring.h>
0019 
0020 #include "kis_mimedata.h"
0021 #include <kis_debug.h>
0022 #include <kis_node.h>
0023 #include <kis_node_progress_proxy.h>
0024 #include <kis_image.h>
0025 #include <kis_selection.h>
0026 #include <kis_selection_mask.h>
0027 #include <kis_undo_adapter.h>
0028 #include <commands/kis_node_property_list_command.h>
0029 #include <kis_paint_layer.h>
0030 #include <kis_group_layer.h>
0031 #include <kis_projection_leaf.h>
0032 #include <kis_shape_controller.h>
0033 
0034 #include "kis_dummies_facade_base.h"
0035 #include "kis_node_dummies_graph.h"
0036 #include "kis_model_index_converter.h"
0037 #include "kis_model_index_converter_show_all.h"
0038 #include "kis_node_selection_adapter.h"
0039 #include "kis_node_insertion_adapter.h"
0040 #include "kis_node_manager.h"
0041 #include <KisSelectionActionsAdapter.h>
0042 #include <KisNodeDisplayModeAdapter.h>
0043 
0044 #include "kis_config.h"
0045 #include "kis_config_notifier.h"
0046 #include "kis_signal_auto_connection.h"
0047 #include "kis_signal_compressor.h"
0048 #include "KisLayerThumbnailCache.h"
0049 
0050 
0051 struct KisNodeModel::Private
0052 {
0053     Private() : updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) {}
0054 
0055     KisImageWSP image;
0056     KisShapeController *shapeController = 0;
0057     KisNodeSelectionAdapter *nodeSelectionAdapter = 0;
0058     KisNodeInsertionAdapter *nodeInsertionAdapter = 0;
0059     KisSelectionActionsAdapter *selectionActionsAdapter = 0;
0060     KisNodeDisplayModeAdapter *nodeDisplayModeAdapter = 0;
0061     KisNodeManager *nodeManager = 0;
0062 
0063     KisSignalAutoConnectionsStore nodeDisplayModeAdapterConnections;
0064 
0065     QList<KisNodeDummy*> updateQueue;
0066     KisSignalCompressor updateCompressor;
0067 
0068     KisModelIndexConverterBase *indexConverter = 0;
0069     QPointer<KisDummiesFacadeBase> dummiesFacade = 0;
0070     bool needFinishRemoveRows = false;
0071     bool needFinishInsertRows = false;
0072     bool showRootLayer = false;
0073     bool showGlobalSelection = false;
0074     int dummyColumns {0};
0075     QPersistentModelIndex activeNodeIndex;
0076 
0077     QPointer<KisNodeDummy> parentOfRemovedNode = 0;
0078 
0079     QSet<quintptr> dropEnabled;
0080 
0081     KisLayerThumbnailCache thumbnalCache;
0082 };
0083 
0084 KisNodeModel::KisNodeModel(QObject * parent, int clonedColumns)
0085         : QAbstractItemModel(parent)
0086         , m_d(new Private)
0087 {
0088     m_d->dummyColumns = qMax(0, clonedColumns);
0089     connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(processUpdateQueue()));
0090     connect(&m_d->thumbnalCache, SIGNAL(sigLayerThumbnailUpdated(KisNodeSP)), SLOT(slotLayerThumbnailUpdated(KisNodeSP)));
0091 }
0092 
0093 KisNodeModel::~KisNodeModel()
0094 {
0095     delete m_d->indexConverter;
0096     delete m_d;
0097 }
0098 
0099 KisNodeSP KisNodeModel::nodeFromIndex(const QModelIndex &index) const
0100 {
0101     Q_ASSERT(index.isValid());
0102 
0103     KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
0104     if (dummy) {
0105         return dummy->node();
0106     }
0107     return 0;
0108 }
0109 
0110 QModelIndex KisNodeModel::indexFromNode(KisNodeSP node) const
0111 {
0112     KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node);
0113     if(dummy)
0114         return m_d->indexConverter->indexFromDummy(dummy);
0115     return QModelIndex();
0116 }
0117 
0118 bool KisNodeModel::belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade)
0119 {
0120     KisNodeSP isolatedRoot = image->isolationRootNode();
0121     if (!isolatedRoot) return true;
0122 
0123     KisNodeDummy *isolatedRootDummy =
0124         dummiesFacade->dummyForNode(isolatedRoot);
0125     KisNodeDummy *dummy =
0126         dummiesFacade->dummyForNode(node);
0127 
0128     while (dummy) {
0129         if (dummy == isolatedRootDummy) {
0130             return true;
0131         }
0132         dummy = dummy->parent();
0133     }
0134 
0135     return false;
0136 }
0137 
0138 bool KisNodeModel::belongsToIsolatedGroup(KisNodeSP node) const
0139 {
0140     return belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade);
0141 }
0142 
0143 void KisNodeModel::resetIndexConverter()
0144 {
0145     delete m_d->indexConverter;
0146     m_d->indexConverter = 0;
0147 
0148     if(m_d->dummiesFacade) {
0149         m_d->indexConverter = createIndexConverter();
0150     }
0151 }
0152 
0153 KisModelIndexConverterBase *KisNodeModel::createIndexConverter()
0154 {
0155     if(m_d->showRootLayer) {
0156         return new KisModelIndexConverterShowAll(m_d->dummiesFacade, this);
0157     } else {
0158         return new KisModelIndexConverter(m_d->dummiesFacade, this, m_d->showGlobalSelection);
0159     }
0160 }
0161 
0162 void KisNodeModel::regenerateItems(KisNodeDummy *dummy)
0163 {
0164     const QModelIndex &index = m_d->indexConverter->indexFromDummy(dummy);
0165     emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(m_d->dummyColumns));
0166 
0167     dummy = dummy->firstChild();
0168     while (dummy) {
0169         regenerateItems(dummy);
0170         dummy = dummy->nextSibling();
0171     }
0172 }
0173 
0174 void KisNodeModel::slotIsolatedModeChanged()
0175 {
0176     KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy();
0177     if (!rootDummy) return;
0178 
0179     regenerateItems(rootDummy);
0180 }
0181 
0182 bool KisNodeModel::showGlobalSelection() const
0183 {
0184     return m_d->nodeDisplayModeAdapter ?
0185         m_d->nodeDisplayModeAdapter->showGlobalSelectionMask() :
0186         false;
0187 }
0188 
0189 void KisNodeModel::setPreferredThumnalSize(int preferredSize) const
0190 {
0191     m_d->thumbnalCache.setMaxSize(preferredSize);
0192 }
0193 
0194 void KisNodeModel::setShowGlobalSelection(bool value)
0195 {
0196     if (m_d->nodeDisplayModeAdapter) {
0197         m_d->nodeDisplayModeAdapter->setShowGlobalSelectionMask(value);
0198     }
0199 }
0200 
0201 void KisNodeModel::slotNodeDisplayModeChanged(bool showRootNode, bool showGlobalSelectionMask)
0202 {
0203     const bool oldShowRootLayer = m_d->showRootLayer;
0204     const bool oldShowGlobalSelection = m_d->showGlobalSelection;
0205     m_d->showRootLayer = showRootNode;
0206     m_d->showGlobalSelection = showGlobalSelectionMask;
0207 
0208     if (m_d->showRootLayer != oldShowRootLayer || m_d->showGlobalSelection != oldShowGlobalSelection) {
0209         resetIndexConverter();
0210         beginResetModel();
0211         endResetModel();
0212     }
0213 }
0214 
0215 void KisNodeModel::progressPercentageChanged(int, const KisNodeSP node)
0216 {
0217     if(!m_d->dummiesFacade) return;
0218 
0219     // Need to check here as the node might already be removed, but there might
0220     // still be some signals arriving from another thread
0221     if (m_d->dummiesFacade->hasDummyForNode(node)) {
0222         QModelIndex index = indexFromNode(node);
0223 
0224         emit dataChanged(index, index);
0225     }
0226 }
0227 
0228 void KisNodeModel::slotLayerThumbnailUpdated(KisNodeSP node)
0229 {
0230     QModelIndex index = indexFromNode(node);
0231     if (!index.isValid()) return;
0232 
0233     emit dataChanged(index, index);
0234 }
0235 
0236 KisModelIndexConverterBase * KisNodeModel::indexConverter() const
0237 {
0238     return m_d->indexConverter;
0239 }
0240 
0241 KisDummiesFacadeBase *KisNodeModel::dummiesFacade() const
0242 {
0243     return m_d->dummiesFacade;
0244 }
0245 
0246 void KisNodeModel::connectDummy(KisNodeDummy *dummy, bool needConnect)
0247 {
0248     KisNodeSP node = dummy->node();
0249     if (!node) {
0250         qWarning() << "Dummy node has no node!" << dummy << dummy->node();
0251         return;
0252     }
0253     KisNodeProgressProxy *progressProxy = node->nodeProgressProxy();
0254     if(progressProxy) {
0255         if(needConnect) {
0256             connect(progressProxy, SIGNAL(percentageChanged(int,KisNodeSP)),
0257                     SLOT(progressPercentageChanged(int,KisNodeSP)));
0258         } else {
0259             progressProxy->disconnect(this);
0260         }
0261     }
0262 }
0263 
0264 void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect)
0265 {
0266     connectDummy(dummy, needConnect);
0267 
0268     dummy = dummy->firstChild();
0269     while(dummy) {
0270         connectDummies(dummy, needConnect);
0271         dummy = dummy->nextSibling();
0272     }
0273 }
0274 
0275 void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade,
0276                                     KisImageWSP image,
0277                                     KisShapeController *shapeController,
0278                                     KisSelectionActionsAdapter *selectionActionsAdapter,
0279                                     KisNodeManager *nodeManager)
0280 {
0281     QPointer<KisDummiesFacadeBase> oldDummiesFacade(m_d->dummiesFacade);
0282     KisShapeController  *oldShapeController = m_d->shapeController;
0283 
0284     m_d->shapeController = shapeController;
0285     m_d->nodeManager = nodeManager;
0286     m_d->nodeSelectionAdapter = nodeManager ? nodeManager->nodeSelectionAdapter() : nullptr;
0287     m_d->nodeInsertionAdapter = nodeManager ? nodeManager->nodeInsertionAdapter() : nullptr;
0288     m_d->selectionActionsAdapter = selectionActionsAdapter;
0289 
0290     m_d->nodeDisplayModeAdapterConnections.clear();
0291     m_d->nodeDisplayModeAdapter = nodeManager ? nodeManager->nodeDisplayModeAdapter() : nullptr;
0292     if (m_d->nodeDisplayModeAdapter) {
0293         m_d->nodeDisplayModeAdapterConnections.addConnection(
0294             m_d->nodeDisplayModeAdapter, SIGNAL(sigNodeDisplayModeChanged(bool,bool)),
0295             this, SLOT(slotNodeDisplayModeChanged(bool,bool)));
0296 
0297         // cold initialization
0298         m_d->showGlobalSelection = m_d->nodeDisplayModeAdapter->showGlobalSelectionMask();
0299         m_d->showRootLayer = false;
0300     }
0301 
0302     if (oldDummiesFacade && m_d->image) {
0303         m_d->image->disconnect(this);
0304         oldDummiesFacade->disconnect(this);
0305         KisNodeDummy *oldRootDummy = m_d->dummiesFacade->rootDummy();
0306         if (oldRootDummy) {
0307             connectDummies(oldRootDummy, false);
0308         }
0309     }
0310 
0311     m_d->image = image;
0312     m_d->dummiesFacade = dummiesFacade;
0313     m_d->parentOfRemovedNode = 0;
0314     m_d->thumbnalCache.setImage(image);
0315     resetIndexConverter();
0316 
0317     if (m_d->dummiesFacade) {
0318         KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy();
0319         if (rootDummy) {
0320             connectDummies(rootDummy, true);
0321         }
0322 
0323         connect(m_d->dummiesFacade, SIGNAL(sigBeginInsertDummy(KisNodeDummy*,int,QString)),
0324                 SLOT(slotBeginInsertDummy(KisNodeDummy*,int,QString)));
0325         connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)),
0326                 SLOT(slotEndInsertDummy(KisNodeDummy*)));
0327         connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)),
0328                 SLOT(slotBeginRemoveDummy(KisNodeDummy*)));
0329         connect(m_d->dummiesFacade, SIGNAL(sigEndRemoveDummy()),
0330                 SLOT(slotEndRemoveDummy()));
0331 
0332         connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)),
0333                 SLOT(slotDummyChanged(KisNodeDummy*)));
0334 
0335         if (m_d->image.isValid()) {
0336             connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), SLOT(slotIsolatedModeChanged()));
0337         }
0338     }
0339 
0340     if (m_d->dummiesFacade != oldDummiesFacade || m_d->shapeController != oldShapeController) {
0341         beginResetModel();
0342         endResetModel();
0343     }
0344 }
0345 
0346 void KisNodeModel::setIdleTaskManager(KisIdleTasksManager *idleTasksManager)
0347 {
0348     m_d->thumbnalCache.setIdleTaskManager(idleTasksManager);
0349 }
0350 
0351 void KisNodeModel::slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType)
0352 {
0353     int row = 0;
0354     QModelIndex parentIndex;
0355 
0356     bool willAdd =
0357         m_d->indexConverter->indexFromAddedDummy(parent, index,
0358                                                  metaObjectType,
0359                                                  parentIndex, row);
0360 
0361     if(willAdd) {
0362         beginInsertRows(parentIndex, row, row);
0363         m_d->needFinishInsertRows = true;
0364     }
0365 }
0366 
0367 void KisNodeModel::slotEndInsertDummy(KisNodeDummy *dummy)
0368 {
0369     if(m_d->needFinishInsertRows) {
0370         connectDummy(dummy, true);
0371         endInsertRows();
0372         m_d->needFinishInsertRows = false;
0373     }
0374 
0375     m_d->thumbnalCache.notifyNodeAdded(dummy->node());
0376 }
0377 
0378 void KisNodeModel::slotBeginRemoveDummy(KisNodeDummy *dummy)
0379 {
0380     if (!dummy) return;
0381 
0382     // FIXME: is it really what we want?
0383     m_d->updateCompressor.stop();
0384     m_d->updateQueue.clear();
0385 
0386     m_d->parentOfRemovedNode = dummy->parent();
0387 
0388     QModelIndex parentIndex;
0389     if (m_d->parentOfRemovedNode) {
0390         parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
0391     }
0392 
0393     QModelIndex itemIndex = m_d->indexConverter->indexFromDummy(dummy);
0394 
0395     if (itemIndex.isValid()) {
0396         connectDummy(dummy, false);
0397         emit sigBeforeBeginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row());
0398         beginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row());
0399         m_d->needFinishRemoveRows = true;
0400     }
0401 
0402     m_d->thumbnalCache.notifyNodeRemoved(dummy->node());
0403 }
0404 
0405 void KisNodeModel::slotEndRemoveDummy()
0406 {
0407     if(m_d->needFinishRemoveRows) {
0408         endRemoveRows();
0409         m_d->needFinishRemoveRows = false;
0410     }
0411 }
0412 
0413 void KisNodeModel::slotDummyChanged(KisNodeDummy *dummy)
0414 {
0415     if (!m_d->updateQueue.contains(dummy)) {
0416         m_d->updateQueue.append(dummy);
0417     }
0418     m_d->updateCompressor.start();
0419 }
0420 
0421 void addChangedIndex(const QModelIndex &idx, QSet<QModelIndex> *indexes)
0422 {
0423     if (!idx.isValid() || indexes->contains(idx)) return;
0424 
0425     indexes->insert(idx);
0426 
0427     const int rowCount = idx.model()->rowCount(idx);
0428     for (int i = 0; i < rowCount; i++) {
0429         addChangedIndex(idx.model()->index(i, 0, idx), indexes);
0430     }
0431 }
0432 
0433 
0434 void KisNodeModel::processUpdateQueue()
0435 {
0436     QSet<QModelIndex> indexes;
0437 
0438     Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) {
0439         QModelIndex index = m_d->indexConverter->indexFromDummy(dummy);
0440         addChangedIndex(index, &indexes);
0441     }
0442 
0443     Q_FOREACH (const QModelIndex &index, indexes) {
0444         emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(m_d->dummyColumns));
0445     }
0446 
0447     m_d->updateQueue.clear();
0448 }
0449 
0450 QModelIndex KisNodeModel::index(int row, int col, const QModelIndex &parent) const
0451 {
0452     if(!m_d->dummiesFacade || !hasIndex(row, col, parent)) return QModelIndex();
0453 
0454     QModelIndex itemIndex;
0455 
0456     KisNodeDummy *dummy = m_d->indexConverter->dummyFromRow(row, parent);
0457     if(dummy) {
0458         itemIndex = m_d->indexConverter->indexFromDummy(dummy);
0459     }
0460 
0461     if (itemIndex.isValid() && itemIndex.column() != col) {
0462         itemIndex = createIndex(itemIndex.row(), col, itemIndex.internalPointer());
0463     }
0464 
0465     return itemIndex;
0466 }
0467 
0468 int KisNodeModel::rowCount(const QModelIndex &parent) const
0469 {
0470     if(!m_d->dummiesFacade) return 0;
0471     if (parent.column() > 0) {
0472         return 0;
0473     }
0474     return m_d->indexConverter->rowCount(parent);
0475 }
0476 
0477 int KisNodeModel::columnCount(const QModelIndex &parent) const
0478 {
0479     if (parent.column() > 0) {
0480         return 0;
0481     }
0482     return 1 + m_d->dummyColumns;
0483 }
0484 
0485 QModelIndex KisNodeModel::parent(const QModelIndex &index) const
0486 {
0487     if(!m_d->dummiesFacade || !index.isValid()) return QModelIndex();
0488 
0489     KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index);
0490     KisNodeDummy *parentDummy = dummy->parent();
0491 
0492     QModelIndex parentIndex;
0493 
0494     if(parentDummy) {
0495         parentIndex = m_d->indexConverter->indexFromDummy(parentDummy);
0496     }
0497 
0498     return parentIndex;
0499 }
0500 
0501 QModelIndex KisNodeModel::sibling(int row, int column, const QModelIndex &idx) const
0502 {
0503     // if it's just a different clone column, there's no need to lookup anything
0504     if (row == idx.row()) {
0505         if (column == idx.column()) {
0506             return idx;
0507         }
0508         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(idx.model() == this, QModelIndex());
0509         return createIndex(row, column, idx.internalPointer());
0510     }
0511     return index(row, column, parent(idx));
0512 }
0513 
0514 QVariant KisNodeModel::data(const QModelIndex &index, int role) const
0515 {
0516     if (!m_d->dummiesFacade || !index.isValid() || !m_d->image.isValid()) return QVariant();
0517 
0518     KisNodeSP node = nodeFromIndex(index);
0519 
0520     switch (role) {
0521     case Qt::DisplayRole: return node->name();
0522     case Qt::DecorationRole: return node->icon();
0523     case Qt::EditRole: return node->name();
0524     case Qt::SizeHintRole: return m_d->image->size(); // FIXME
0525     case Qt::ForegroundRole:
0526         return belongsToIsolatedGroup(node) &&
0527             !node->projectionLeaf()->isDroppedNode() ? QVariant() : QVariant(QColor(Qt::gray));
0528     case Qt::FontRole: {
0529         QFont baseFont;
0530         if (node->projectionLeaf()->isDroppedNode()) {
0531             baseFont.setStrikeOut(true);
0532         }
0533         if (m_d->activeNodeIndex == index) {
0534             baseFont.setBold(true);
0535         }
0536         return baseFont;
0537     }
0538     case KisNodeModel::PropertiesRole: return QVariant::fromValue(node->sectionModelProperties());
0539     case KisNodeModel::AspectRatioRole: return double(m_d->image->width()) / m_d->image->height();
0540     case KisNodeModel::ProgressRole: {
0541         KisNodeProgressProxy *proxy = node->nodeProgressProxy();
0542         return proxy ? proxy->percentage() : -1;
0543     }
0544     case KisNodeModel::ActiveRole: {
0545         return m_d->activeNodeIndex == index;
0546     }
0547     case KisNodeModel::ShouldGrayOutRole: {
0548         return !node->visible(true);
0549     }
0550     case KisNodeModel::ColorLabelIndexRole: {
0551         return node->colorLabelIndex();
0552     }
0553     case KisNodeModel::DropReasonRole: {
0554         QString result;
0555         KisProjectionLeaf::NodeDropReason reason = node->projectionLeaf()->dropReason();
0556 
0557         if (reason == KisProjectionLeaf::DropPassThroughMask) {
0558             result = i18nc("@info:tooltip", "Disabled: masks on pass-through groups are not supported!");
0559         } else if (reason == KisProjectionLeaf::DropPassThroughClone) {
0560             result = i18nc("@info:tooltip", "Disabled: cloning pass-through groups is not supported!");
0561         }
0562 
0563         return result;
0564     }
0565     case KisNodeModel::IsAnimatedRole: {
0566         return node->isAnimated();
0567     }
0568     case KisNodeModel::InfoTextRole: {
0569         // These layer types' opacity and blending modes cannot be changed,
0570         // so there's little point in showing them
0571         if (node->inherits("KisFilterMask") ||
0572             node->inherits("KisTransparencyMask") ||
0573             node->inherits("KisTransformMask") ||
0574             node->inherits("KisSelectionMask")) {
0575             return "";
0576         }
0577         const KisConfig::LayerInfoTextStyle infoTextStyle = KisConfig(true).layerInfoTextStyle();
0578         const int opacity = round(node->opacity() * 100.0 / 255);
0579         const QString opacityString = QString::number(opacity);
0580         const QString compositeOpId = node->compositeOpId();
0581         QString compositeOpDesc = "null";
0582         // make sure the compositeOp exists to avoid crashing on specific layer undo
0583         if (node->compositeOp()) {
0584             compositeOpDesc = node->compositeOp()->description();
0585         }
0586         QString defaultOpId = COMPOSITE_OVER;   // "normal";
0587         if (node->inherits("KisAdjustmentLayer")) {
0588             defaultOpId = COMPOSITE_COPY;
0589         }
0590         else if (node->inherits("KisColorizeMask")) {
0591             defaultOpId = COMPOSITE_BEHIND;
0592         }
0593         QString infoText = "";
0594         if (infoTextStyle == KisConfig::LayerInfoTextStyle::INFOTEXT_DETAILED ||
0595                 !(opacity == 100 && compositeOpId == defaultOpId)) {
0596             if (infoTextStyle == KisConfig::LayerInfoTextStyle::INFOTEXT_SIMPLE) {
0597                 if (opacity == 100) {
0598                     return QString(compositeOpDesc);
0599                 }
0600                 if (compositeOpId == defaultOpId) {
0601                     return i18nc("%1 is the percent value, % is the percent sign", "%1%", opacityString);
0602                 }
0603             }
0604             infoText = i18nc("%1 is the percent value, % is the percent sign", "%1% %2", opacityString, compositeOpDesc);
0605         }
0606         return infoText;
0607     }
0608     default:
0609 
0610         /**
0611          * The dummies are removed from the model asynchronously to the image operations,
0612          * therefore we should make sure that `node->graphListener()` is still valid and
0613          * this node is still present in the node graph.
0614          */
0615         if (role >= int(KisNodeModel::BeginThumbnailRole) &&
0616             belongsToIsolatedGroup(node) &&
0617             node->graphListener()) {
0618 
0619             /**
0620              * WARNING: there is still a possible theoretical race condition if the node is
0621              * removed from the image right here. We consider that as "improbable" atm.
0622              */
0623 
0624             const int maxSize = role - int(KisNodeModel::BeginThumbnailRole);
0625 
0626             if (maxSize == m_d->thumbnalCache.maxSize()) {
0627                 return m_d->thumbnalCache.thumbnail(node);
0628             } else {
0629                 return node->createThumbnail(maxSize, maxSize, Qt::KeepAspectRatio);
0630             }
0631         } else {
0632             return QVariant();
0633         }
0634     }
0635 
0636     return QVariant();
0637 }
0638 
0639 Qt::ItemFlags KisNodeModel::flags(const QModelIndex &index) const
0640 {
0641     if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled;
0642 
0643     Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsEditable;
0644     // currently dummy columns are neither selectable nor drag&drop enabled
0645     if (index.column() == 0) {
0646         flags |=  Qt::ItemIsDragEnabled | Qt::ItemIsSelectable;
0647         if (m_d->dropEnabled.contains(index.internalId())) {
0648             flags |= Qt::ItemIsDropEnabled;
0649         }
0650 
0651     }
0652 
0653     return flags;
0654 }
0655 
0656 bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role)
0657 {
0658     if (role == KisNodeModel::DropEnabled) {
0659         const QMimeData *mimeData = static_cast<const QMimeData*>(value.value<void*>());
0660         setDropEnabled(mimeData);
0661         return true;
0662     }
0663 
0664     if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) {
0665         QModelIndex parentIndex;
0666         if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) {
0667             parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode);
0668             m_d->parentOfRemovedNode = 0;
0669         }
0670 
0671         KisNodeSP activatedNode;
0672 
0673         if (index.isValid() && value.toBool()) {
0674             activatedNode = nodeFromIndex(index);
0675         }
0676         else if (parentIndex.isValid() && value.toBool()) {
0677             activatedNode = nodeFromIndex(parentIndex);
0678         }
0679         else {
0680             activatedNode = 0;
0681         }
0682 
0683         QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex();
0684         if (role == KisNodeModel::ActiveRole && value.toBool() &&
0685             m_d->activeNodeIndex == newActiveNode) {
0686 
0687             return true;
0688         }
0689 
0690         m_d->activeNodeIndex = newActiveNode;
0691 
0692         if (m_d->nodeSelectionAdapter) {
0693             m_d->nodeSelectionAdapter->setActiveNode(activatedNode);
0694         }
0695 
0696         if (role == KisNodeModel::AlternateActiveRole) {
0697             emit toggleIsolateActiveNode();
0698         }
0699 
0700         emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(m_d->dummyColumns));
0701         return true;
0702     }
0703 
0704     if(!m_d->dummiesFacade || !index.isValid()) return false;
0705 
0706     bool result = true;
0707     bool shouldUpdate = true;
0708     bool shouldUpdateRecursively = false;
0709     KisNodeSP node = nodeFromIndex(index);
0710 
0711     switch (role) {
0712     case Qt::DisplayRole:
0713     case Qt::EditRole:
0714         m_d->nodeManager->setNodeName(node, value.toString());
0715         break;
0716     case KisNodeModel::PropertiesRole:
0717         {
0718             // don't record undo/redo for visibility, locked or alpha locked changes
0719             KisBaseNode::PropertyList proplist = value.value<KisBaseNode::PropertyList>();
0720             m_d->nodeManager->trySetNodeProperties(node, m_d->image, proplist);
0721             shouldUpdateRecursively = true;
0722 
0723             break;
0724         }
0725     case KisNodeModel::SelectOpaqueRole:
0726         if (node && m_d->selectionActionsAdapter) {
0727             SelectionAction action = SelectionAction(value.toInt());
0728             m_d->selectionActionsAdapter->selectOpaqueOnNode(node, action);
0729         }
0730         shouldUpdate = false;
0731         break;
0732     default:
0733         result = false;
0734     }
0735 
0736     if (result && shouldUpdate) {
0737         if (shouldUpdateRecursively) {
0738             QSet<QModelIndex> indexes;
0739             addChangedIndex(index, &indexes);
0740             Q_FOREACH (const QModelIndex &idx, indexes) {
0741                 emit dataChanged(idx.siblingAtColumn(0), idx.siblingAtColumn(m_d->dummyColumns));
0742             }
0743         } else {
0744             emit dataChanged(index.siblingAtColumn(0), index.siblingAtColumn(m_d->dummyColumns));
0745         }
0746     }
0747 
0748     return result;
0749 }
0750 
0751 Qt::DropActions KisNodeModel::supportedDragActions() const
0752 {
0753     return Qt::CopyAction | Qt::MoveAction;
0754 }
0755 
0756 Qt::DropActions KisNodeModel::supportedDropActions() const
0757 {
0758     return Qt::MoveAction | Qt::CopyAction;
0759 }
0760 
0761 bool KisNodeModel::hasDummiesFacade()
0762 {
0763     return m_d->dummiesFacade != 0;
0764 }
0765 
0766 QStringList KisNodeModel::mimeTypes() const
0767 {
0768     QStringList types;
0769     types << QLatin1String("application/x-krita-node-internal-pointer");
0770     types << QLatin1String("application/x-qt-image");
0771     types << QLatin1String("application/x-color");
0772     types << QLatin1String("krita/x-colorsetentry");
0773     return types;
0774 }
0775 
0776 QMimeData * KisNodeModel::mimeData(const QModelIndexList &indexes) const
0777 {
0778     bool hasLockedLayer = false;
0779     KisNodeList nodes;
0780     Q_FOREACH (const QModelIndex &idx, indexes) {
0781         // Although clone columns should not be selectable, make sure we only use column 0,
0782         // because nodeFromIndex doesn't like duplicate list entries.
0783         if (idx.column() != 0) {
0784             continue;
0785         }
0786 
0787         KisNodeSP node = nodeFromIndex(idx);
0788 
0789         nodes << node;
0790         hasLockedLayer |= !node->isEditable(false);
0791     }
0792 
0793     return KisMimeData::mimeForLayers(nodes, m_d->image, hasLockedLayer);
0794 }
0795 
0796 bool KisNodeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
0797 {
0798     Q_UNUSED(column);
0799 
0800     bool copyNode = (action == Qt::CopyAction);
0801 
0802     KisNodeDummy *parentDummy = 0;
0803     KisNodeDummy *aboveThisDummy = 0;
0804 
0805     parentDummy = parent.isValid() ?
0806         m_d->indexConverter->dummyFromIndex(parent) :
0807         m_d->dummiesFacade->rootDummy();
0808 
0809     if (row == -1) {
0810         aboveThisDummy = parent.isValid() ? parentDummy->lastChild() : 0;
0811     }
0812     else {
0813         aboveThisDummy = row < m_d->indexConverter->rowCount(parent) ? m_d->indexConverter->dummyFromRow(row, parent) : 0;
0814     }
0815 
0816     return KisMimeData::insertMimeLayers(data,
0817                                          m_d->image,
0818                                          m_d->shapeController,
0819                                          parentDummy,
0820                                          aboveThisDummy,
0821                                          copyNode,
0822                                          m_d->nodeInsertionAdapter);
0823 }
0824 
0825 bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const {
0826     if (parent.isValid()) {
0827         // drop occurred on an item. always return true as returning false will mess up
0828         // QT5's drag handling (see KisNodeModel::setDropEnabled).
0829         return true;
0830     } else {
0831         return QAbstractItemModel::canDropMimeData(data, action, row, column, parent);
0832     }
0833 }
0834 
0835 void KisNodeModel::setDropEnabled(const QMimeData *data) {
0836     // what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5
0837     // will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by
0838     // canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData()
0839     // later on that an "onto" drag is not allowed, QT will display an drop indicator for
0840     // insertion, but not perform any drop when the mouse is released.
0841 
0842     // the only robust implementation seems to set all flags correctly, which is done here.
0843 
0844     bool copyNode = false;
0845     KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode);
0846     m_d->dropEnabled.clear();
0847     updateDropEnabled(nodes);
0848 }
0849 
0850 void KisNodeModel::updateDropEnabled(const QList<KisNodeSP> &nodes, QModelIndex parent) {
0851     for (int r = 0; r < rowCount(parent); r++) {
0852         QModelIndex idx = index(r, 0, parent);
0853 
0854         KisNodeSP target = nodeFromIndex(idx);
0855 
0856         bool dropEnabled = true;
0857         Q_FOREACH (const KisNodeSP &node, nodes) {
0858             if (!target->allowAsChild(node) || !target->isEditable(false)) {
0859                 dropEnabled = false;
0860                 break;
0861             }
0862         }
0863         if (dropEnabled) {
0864             m_d->dropEnabled.insert(idx.internalId());
0865         }
0866         emit dataChanged(idx, idx); // indicate to QT that flags() have changed
0867 
0868         if (hasChildren(idx)) {
0869             updateDropEnabled(nodes, idx);
0870         }
0871     }
0872 }