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 }