File indexing completed on 2024-12-22 04:10:02
0001 /* 0002 * SPDX-FileCopyrightText: 2009 Cyrille Berger <cberger@cberger.net> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <klocalizedstring.h> 0008 #include "kis_node.h" 0009 #include "kis_layer.h" 0010 #include "kis_image.h" 0011 #include "kis_selection_mask.h" 0012 #include "kis_paint_layer.h" 0013 #include "commands/kis_node_property_list_command.h" 0014 #include "kis_undo_adapter.h" 0015 #include "kis_layer_properties_icons.h" 0016 #include "kis_command_ids.h" 0017 0018 // HACK! please refactor out! 0019 #include "kis_simple_stroke_strategy.h" 0020 #include "kis_abstract_projection_plane.h" 0021 0022 namespace { 0023 0024 QSet<QString> changedProperties(const KisBaseNode::PropertyList &before, 0025 const KisBaseNode::PropertyList &after) 0026 { 0027 QSet<QString> changedIds; 0028 0029 auto valueForId = [] (const QString &id, const KisBaseNode::PropertyList &list) { 0030 QVariant value; 0031 Q_FOREACH (const KisBaseNode::Property &prop, list) { 0032 if (prop.id == id) { 0033 value = prop.state; 0034 break; 0035 } 0036 0037 } 0038 return value; 0039 }; 0040 0041 /// we expect that neither of the lists has duplicated values, 0042 /// therefore we can just iterate over the bigger list 0043 const KisBaseNode::PropertyList &list1 = before.size() >= after.size() ? before : after; 0044 const KisBaseNode::PropertyList &list2 = before.size() >= after.size() ? after : before; 0045 0046 Q_FOREACH (const KisBaseNode::Property &prop, list1) { 0047 if (prop.state != valueForId(prop.id, list2)) { 0048 changedIds.insert(prop.id); 0049 } 0050 } 0051 0052 return changedIds; 0053 } 0054 0055 } 0056 0057 0058 KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList) 0059 : KisNodeCommand(kundo2_i18n("Property Changes"), node), 0060 m_newPropertyList(newPropertyList), 0061 m_oldPropertyList(node->sectionModelProperties()) 0062 /** 0063 * TODO instead of "Property Changes" check which property 0064 * has been changed and display either lock/unlock, visible/hidden 0065 * or "Property Changes" (this require new strings) 0066 */ 0067 { 0068 } 0069 0070 void KisNodePropertyListCommand::redo() 0071 { 0072 const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); 0073 const QSet<QString> changed = changedProperties(propsBefore, m_newPropertyList); 0074 if (changed.isEmpty()) return; 0075 0076 const QRect oldExtent = m_node->projectionPlane()->tightUserVisibleBounds(); 0077 m_node->setSectionModelProperties(m_newPropertyList); 0078 0079 if (!propsWithNoUpdates().contains(changed)) { 0080 doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->projectionPlane()->tightUserVisibleBounds()); 0081 } 0082 } 0083 0084 void KisNodePropertyListCommand::undo() 0085 { 0086 const KisBaseNode::PropertyList propsBefore = m_node->sectionModelProperties(); 0087 const QSet<QString> changed = changedProperties(propsBefore, m_oldPropertyList); 0088 if (changed.isEmpty()) return; 0089 0090 const QRect oldExtent = m_node->projectionPlane()->tightUserVisibleBounds(); 0091 m_node->setSectionModelProperties(m_oldPropertyList); 0092 0093 if (!propsWithNoUpdates().contains(changed)) { 0094 doUpdate(propsBefore, m_node->sectionModelProperties(), oldExtent | m_node->projectionPlane()->tightUserVisibleBounds()); 0095 } 0096 } 0097 0098 int KisNodePropertyListCommand::id() const 0099 { 0100 return KisCommandUtils::NodePropertyListCommandId; 0101 } 0102 0103 bool KisNodePropertyListCommand::mergeWith(const KUndo2Command *command) 0104 { 0105 const KisNodePropertyListCommand *other = 0106 dynamic_cast<const KisNodePropertyListCommand*>(command); 0107 0108 if (other && other->m_node == m_node && 0109 (changedProperties(m_oldPropertyList, m_newPropertyList).isEmpty() || 0110 changedProperties(m_oldPropertyList, m_newPropertyList) == 0111 changedProperties(other->m_oldPropertyList, other->m_newPropertyList))) { 0112 0113 KIS_SAFE_ASSERT_RECOVER_NOOP(m_newPropertyList == other->m_oldPropertyList); 0114 m_newPropertyList = other->m_newPropertyList; 0115 return true; 0116 } 0117 0118 return false; 0119 } 0120 0121 bool KisNodePropertyListCommand::canMergeWith(const KUndo2Command *command) const 0122 { 0123 const KisNodePropertyListCommand *other = 0124 dynamic_cast<const KisNodePropertyListCommand*>(command); 0125 0126 return other && other->m_node == m_node && 0127 (changedProperties(m_oldPropertyList, m_newPropertyList).isEmpty() || 0128 changedProperties(m_oldPropertyList, m_newPropertyList) == 0129 changedProperties(other->m_oldPropertyList, other->m_newPropertyList)); 0130 } 0131 0132 bool KisNodePropertyListCommand::canAnnihilateWith(const KUndo2Command *command) const 0133 { 0134 const KisNodePropertyListCommand *other = 0135 dynamic_cast<const KisNodePropertyListCommand*>(command); 0136 0137 return other && other->m_node == m_node && 0138 changedProperties(m_oldPropertyList, other->m_newPropertyList).isEmpty(); 0139 } 0140 0141 bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList, 0142 const KisBaseNode::PropertyList &newPropertyList) 0143 { 0144 return changedProperties(oldPropertyList, newPropertyList).contains(KisLayerPropertiesIcons::onionSkins.id()); 0145 } 0146 0147 0148 void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList, 0149 const KisBaseNode::PropertyList &newPropertyList, 0150 const QRect &totalUpdateExtent) 0151 { 0152 /** 0153 * Sometimes the node might refuse to change the property, e.g. needs-update for colorize 0154 * mask. In this case we should avoid issuing the update and set-modified call. 0155 */ 0156 if (oldPropertyList == newPropertyList) { 0157 return; 0158 } 0159 0160 bool oldPassThroughValue = false; 0161 bool newPassThroughValue = false; 0162 0163 bool oldVisibilityValue = false; 0164 bool newVisibilityValue = false; 0165 0166 Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) { 0167 if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { 0168 oldPassThroughValue = prop.state.toBool(); 0169 } 0170 if (prop.id == KisLayerPropertiesIcons::visible.id()) { 0171 oldVisibilityValue = prop.state.toBool(); 0172 } 0173 } 0174 0175 Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) { 0176 if (prop.id == KisLayerPropertiesIcons::passThrough.id()) { 0177 newPassThroughValue = prop.state.toBool(); 0178 } 0179 if (prop.id == KisLayerPropertiesIcons::visible.id()) { 0180 newVisibilityValue = prop.state.toBool(); 0181 } 0182 } 0183 0184 if (oldPassThroughValue && !newPassThroughValue) { 0185 KisLayerSP layer(qobject_cast<KisLayer*>(m_node.data())); 0186 KisImageSP image = layer->image().toStrongRef(); 0187 if (image) { 0188 image->refreshGraphAsync(layer); 0189 } 0190 } else if ((m_node->parent() && !oldPassThroughValue && newPassThroughValue) || 0191 (oldPassThroughValue && newPassThroughValue && 0192 !oldVisibilityValue && newVisibilityValue)) { 0193 0194 KisLayerSP layer(qobject_cast<KisLayer*>(m_node->parent().data())); 0195 KisImageSP image = layer->image().toStrongRef(); 0196 if (image) { 0197 image->refreshGraphAsync(layer); 0198 } 0199 } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) { 0200 m_node->setDirtyDontResetAnimationCache(totalUpdateExtent); 0201 } else { 0202 m_node->setDirty(totalUpdateExtent); // TODO check if visibility was actually changed or not 0203 } 0204 } 0205 0206 const QSet<QString>& KisNodePropertyListCommand::propsWithNoUpdates() 0207 { 0208 static const QSet<QString> noUpdates { 0209 KisLayerPropertiesIcons::locked.id(), 0210 KisLayerPropertiesIcons::alphaLocked.id(), 0211 KisLayerPropertiesIcons::selectionActive.id(), 0212 KisLayerPropertiesIcons::colorLabelIndex.id(), 0213 KisLayerPropertiesIcons::colorizeNeedsUpdate.id(), 0214 KisLayerPropertiesIcons::openFileLayerFile.id(), 0215 KisLayerPropertiesIcons::layerError.id() 0216 }; 0217 return noUpdates; 0218 } 0219 0220 void KisNodePropertyListCommand::setNodePropertiesAutoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist) 0221 { 0222 const QSet<QString> properties = changedProperties(node->sectionModelProperties(), proplist); 0223 0224 const bool undo = !properties.isEmpty() && 0225 properties != QSet<QString>{KisLayerPropertiesIcons::colorizeNeedsUpdate.id()} && 0226 properties != QSet<QString>{KisLayerPropertiesIcons::openFileLayerFile.id()}; 0227 0228 QScopedPointer<KUndo2Command> cmd(new KisNodePropertyListCommand(node, proplist)); 0229 0230 if (undo) { 0231 image->undoAdapter()->addCommand(cmd.take()); 0232 } 0233 else { 0234 /** 0235 * HACK ALERT! 0236 * 0237 * Here we start a fake legacy stroke, so that all the LoD planes would 0238 * be invalidated. Ideally, we should refactor this method and avoid 0239 * resetting LoD planes when node visibility changes, Instead there should 0240 * be two commands executes: LoD agnostic one (which sets the properties 0241 * themselves), and two LoD-specific update commands: one for lodN and 0242 * another one for lod0. 0243 */ 0244 0245 struct SimpleLodResettingStroke : public KisSimpleStrokeStrategy { 0246 SimpleLodResettingStroke(KUndo2Command *cmd) 0247 : KisSimpleStrokeStrategy(QLatin1String("SimpleLodResettingStroke")), 0248 m_cmd(cmd) 0249 { 0250 setClearsRedoOnStart(false); 0251 this->enableJob(JOB_INIT, true); 0252 } 0253 0254 void initStrokeCallback() override { 0255 m_cmd->redo(); 0256 // NOTE: we don't emit imageModified signal here because this 0257 // branch is only taken for the stasis changes, that do not 0258 // change actual image representation. 0259 } 0260 0261 private: 0262 QScopedPointer<KUndo2Command> m_cmd; 0263 }; 0264 0265 KisStrokeId strokeId = image->startStroke(new SimpleLodResettingStroke(cmd.take())); 0266 image->endStroke(strokeId); 0267 } 0268 0269 }