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 }