File indexing completed on 2024-05-12 16:01:34

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef __KIS_MULTINODE_PROPERTY_H
0008 #define __KIS_MULTINODE_PROPERTY_H
0009 
0010 #include <QObject>
0011 #include <QCheckBox>
0012 #include <QPointer>
0013 #include <QRegExp>
0014 #include <QBitArray>
0015 
0016 #include <kundo2command.h>
0017 #include <KoColorSpace.h>
0018 #include <KoChannelInfo.h>
0019 
0020 #include "kis_node.h"
0021 #include "kis_layer.h"
0022 #include "kis_layer_utils.h"
0023 
0024 #include "kritaui_export.h"
0025 
0026 class KisMultinodePropertyInterface;
0027 class MultinodePropertyBaseConnector;
0028 template <class PropertyAdapter> class MultinodePropertyBoolConnector;
0029 template <class PropertyAdapter> class KisMultinodeProperty;
0030 
0031 /******************************************************************/
0032 /*               Adapters                                         */
0033 /******************************************************************/
0034 
0035 struct BaseAdapter {
0036     static KisNodeList filterNodes(KisNodeList nodes) { return nodes; }
0037 
0038     void setNumNodes(int numNodes) { m_numNodes = numNodes; }
0039     int m_numNodes = 0;
0040 };
0041 
0042 struct CompositeOpAdapter : public BaseAdapter {
0043     typedef QString ValueType;
0044     typedef MultinodePropertyBaseConnector ConnectorType;
0045     static const bool forceIgnoreByDefault = false;
0046 
0047     static ValueType propForNode(KisNodeSP node) {
0048         return node->compositeOpId();
0049     }
0050 
0051     static void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0052         Q_UNUSED(index);
0053         node->setCompositeOpId(value);
0054     }
0055 };
0056 
0057 struct NameAdapter : public BaseAdapter {
0058     typedef QString ValueType;
0059     typedef MultinodePropertyBaseConnector ConnectorType;
0060     static const bool forceIgnoreByDefault = true;
0061 
0062     ValueType propForNode(KisNodeSP node) {
0063         return m_numNodes == 1 ? node->name() : stripName(node->name());
0064     }
0065 
0066     void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0067         QString name;
0068 
0069         if (index < 0 || m_numNodes == 1) {
0070             name = value;
0071         } else {
0072             name = QString("%1 %2").arg(stripName(value)).arg(index);
0073         }
0074 
0075         node->setName(name);
0076     }
0077 
0078 private:
0079     static QString stripName(QString name) {
0080         QRegExp rexp("^(.+) (\\d{1,3})$");
0081         int pos = rexp.indexIn(name);
0082         if (pos > -1) {
0083             name = rexp.cap(1);
0084         }
0085 
0086         return name;
0087     }
0088 };
0089 
0090 struct ColorLabelAdapter : public BaseAdapter {
0091     typedef int ValueType;
0092     typedef MultinodePropertyBaseConnector ConnectorType;
0093     static const bool forceIgnoreByDefault = false;
0094 
0095     static ValueType propForNode(KisNodeSP node) {
0096         return node->colorLabelIndex();
0097     }
0098 
0099     static void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0100         Q_UNUSED(index);
0101         node->setColorLabelIndex(value);
0102     }
0103 };
0104 
0105 struct OpacityAdapter : public BaseAdapter {
0106     typedef int ValueType;
0107     typedef MultinodePropertyBaseConnector ConnectorType;
0108     static const bool forceIgnoreByDefault = false;
0109 
0110     static ValueType propForNode(KisNodeSP node) {
0111         return qRound(node->opacity() / 255.0 * 100);
0112     }
0113 
0114     static void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0115         Q_UNUSED(index);
0116         node->setOpacity(qRound(value * 255.0 / 100));
0117     }
0118 };
0119 
0120 inline uint qHash(const KisBaseNode::Property &prop, uint seed = 0) {
0121     return qHash(prop.name, seed);
0122 }
0123 
0124 struct LayerPropertyAdapter : public BaseAdapter {
0125     typedef bool ValueType;
0126     typedef MultinodePropertyBoolConnector<LayerPropertyAdapter> ConnectorType;
0127     static const bool forceIgnoreByDefault = false;
0128 
0129     LayerPropertyAdapter(const QString &propName) : m_propName(propName) {}
0130 
0131     ValueType propForNode(KisNodeSP node) {
0132         KisBaseNode::PropertyList props = node->sectionModelProperties();
0133         Q_FOREACH (const KisBaseNode::Property &prop, props) {
0134             if (prop.name == m_propName) {
0135                 return prop.state.toBool();
0136             }
0137         }
0138 
0139         return false;
0140     }
0141 
0142     void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0143         Q_UNUSED(index);
0144         bool stateChanged = false;
0145 
0146         KisBaseNode::PropertyList props = node->sectionModelProperties();
0147         KisBaseNode::PropertyList::iterator it = props.begin();
0148         KisBaseNode::PropertyList::iterator end = props.end();
0149         for (; it != end; ++it) {
0150             if (it->name == m_propName) {
0151                 it->state = value;
0152                 stateChanged = true;
0153                 break;
0154             }
0155         }
0156 
0157         if (stateChanged) {
0158             node->setSectionModelProperties(props);
0159         }
0160     }
0161 
0162     QString name() const {
0163         return m_propName;
0164     }
0165 
0166     static KisBaseNode::PropertyList adaptersList(KisNodeList nodes) {
0167         QHash<QString, std::pair<KisBaseNode::Property, int>> adapters;
0168 
0169         Q_FOREACH (KisNodeSP node, nodes) {
0170             int sortingIndex = 0;
0171             KisBaseNode::PropertyList props = node->sectionModelProperties();
0172             Q_FOREACH (const KisBaseNode::Property &prop, props) {
0173                 if (prop.state.type() != QVariant::Bool) continue;
0174 
0175                 if (!adapters.contains(prop.id)) {
0176                     adapters.insert(prop.id, std::make_pair(prop, sortingIndex));
0177                 } else {
0178                     adapters[prop.id].second = qMin(adapters[prop.id].second, sortingIndex);
0179                 }
0180                 sortingIndex++;
0181             }
0182         }
0183 
0184         QMultiMap<int, KisBaseNode::Property> sortedAdapters;
0185         auto it = adapters.constBegin();
0186         auto end = adapters.constEnd();
0187         for (; it != end; ++it) {
0188             KisBaseNode::Property prop;
0189             int sortingIndex = 0;
0190             std::tie(prop, sortingIndex) = it.value();
0191 
0192             sortedAdapters.insert(sortingIndex, prop);
0193         }
0194 
0195         return sortedAdapters.values();
0196     }
0197 
0198 private:
0199     QString m_propName;
0200 };
0201 
0202 struct ChannelFlagAdapter : public BaseAdapter {
0203     typedef bool ValueType;
0204     typedef MultinodePropertyBoolConnector<ChannelFlagAdapter> ConnectorType;
0205     static const bool forceIgnoreByDefault = false;
0206 
0207     struct Property {
0208         Property(QString _name, int _channelIndex) : name(_name), channelIndex(_channelIndex) {}
0209         QString name;
0210         int channelIndex;
0211     };
0212     typedef QList<Property> PropertyList;
0213 
0214     ChannelFlagAdapter(const Property &prop) : m_prop(prop) {}
0215 
0216     ValueType propForNode(KisNodeSP node) {
0217         KisLayerSP layer = toLayer(node);
0218         Q_ASSERT(layer);
0219 
0220         QBitArray flags = layer->channelFlags();
0221         if (flags.isEmpty()) return true;
0222 
0223         return flags.testBit(m_prop.channelIndex);
0224     }
0225 
0226     void setPropForNode(KisNodeSP node, const ValueType &value, int index) {
0227         Q_UNUSED(index);
0228         KisLayerSP layer = toLayer(node);
0229         Q_ASSERT(layer);
0230 
0231         QBitArray flags = layer->channelFlags();
0232         if (flags.isEmpty()) {
0233             flags = QBitArray(layer->colorSpace()->channelCount(), true);
0234         }
0235 
0236         if (flags.testBit(m_prop.channelIndex) != value) {
0237             flags.setBit(m_prop.channelIndex, value);
0238             layer->setChannelFlags(flags);
0239         }
0240     }
0241 
0242     QString name() const {
0243         return m_prop.name;
0244     }
0245 
0246     static PropertyList adaptersList(KisNodeList nodes) {
0247         PropertyList props;
0248 
0249         {
0250             bool nodesDiffer = KisLayerUtils::checkNodesDiffer<const KoColorSpace*>(nodes, [](KisNodeSP node) { return node->colorSpace(); });
0251 
0252             if (nodesDiffer) {
0253                 return props;
0254             }
0255         }
0256 
0257 
0258         QList<KoChannelInfo*> channels = nodes.first()->colorSpace()->channels();
0259 
0260         int index = 0;
0261         Q_FOREACH (KoChannelInfo *info, channels) {
0262             props << Property(info->name(), index);
0263             index++;
0264         }
0265 
0266         return props;
0267     }
0268 
0269     static KisNodeList filterNodes(KisNodeList nodes) {
0270         KisNodeList filteredNodes;
0271         Q_FOREACH (KisNodeSP node, nodes) {
0272             if (toLayer(node)) {
0273                 filteredNodes << node;
0274             }
0275         }
0276         return filteredNodes;
0277     }
0278 private:
0279     static KisLayerSP toLayer(KisNodeSP node) {
0280         return qobject_cast<KisLayer*>(node.data());
0281     }
0282 private:
0283     Property m_prop;
0284 };
0285 
0286 /******************************************************************/
0287 /*               MultinodePropertyConnectorInterface              */
0288 /******************************************************************/
0289 
0290 class KRITAUI_EXPORT MultinodePropertyConnectorInterface : public QObject
0291 {
0292     Q_OBJECT
0293 public:
0294     ~MultinodePropertyConnectorInterface() override;
0295 
0296     /**
0297      * Public interface
0298      */
0299     virtual void connectIgnoreCheckBox(QCheckBox *ignoreBox) = 0;
0300     void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);
0301 
0302     /**
0303      * Clicking on this widget will automatically enable it,
0304      * setting "Ignored" property to false.
0305      *
0306      * Default implementation does nothing.
0307      */
0308     virtual void connectAutoEnableWidget(QWidget *widget);
0309 
0310 Q_SIGNALS:
0311     void sigValueChanged();
0312 
0313 protected Q_SLOTS:
0314     virtual void slotIgnoreCheckBoxChanged(int state) = 0;
0315 
0316 public:
0317     /**
0318      * Interface for KisMultinodeProperty's notifications
0319      */
0320     virtual void notifyValueChanged();
0321     virtual void notifyIgnoreChanged() = 0;
0322 };
0323 
0324 /******************************************************************/
0325 /*               MultinodePropertyBaseConnector                   */
0326 /******************************************************************/
0327 
0328 class KRITAUI_EXPORT MultinodePropertyBaseConnector : public MultinodePropertyConnectorInterface
0329 {
0330 public:
0331     MultinodePropertyBaseConnector(KisMultinodePropertyInterface *parent);
0332 
0333     void connectIgnoreCheckBox(QCheckBox *ignoreBox) override;
0334     void notifyIgnoreChanged() override;
0335 
0336     void connectAutoEnableWidget(QWidget *widget) override;
0337 
0338 protected:
0339     void slotIgnoreCheckBoxChanged(int state) override;
0340 
0341 private:
0342     QPointer<QCheckBox> m_ignoreBox;
0343     KisMultinodePropertyInterface *m_parent;
0344 };
0345 
0346 /******************************************************************/
0347 /*               MultinodePropertyBoolConnector                   */
0348 /******************************************************************/
0349 
0350 template <class PropertyAdapter>
0351 class MultinodePropertyBoolConnector : public MultinodePropertyConnectorInterface
0352 {
0353     typedef KisMultinodeProperty<PropertyAdapter> PropertyType;
0354 public:
0355     MultinodePropertyBoolConnector(PropertyType *parent)
0356         : m_parent(parent)
0357     {
0358     }
0359 
0360     void connectIgnoreCheckBox(QCheckBox *ignoreBox) override {
0361         m_ignoreBox = ignoreBox;
0362 
0363         if ((!m_parent->isIgnored() && !m_parent->savedValuesDiffer())
0364             || m_parent->haveTheOnlyNode()) {
0365 
0366             m_ignoreBox->setTristate(false);
0367         } else {
0368             m_ignoreBox->setTristate(true);
0369         }
0370         connect(m_ignoreBox, SIGNAL(stateChanged(int)), SLOT(slotIgnoreCheckBoxChanged(int)));
0371     }
0372 
0373     void notifyIgnoreChanged() override {
0374         // noop
0375     }
0376 
0377     void notifyValueChanged() override {
0378         if (m_ignoreBox) {
0379             Qt::CheckState newState =
0380                 m_parent->isIgnored() ? Qt::PartiallyChecked :
0381                 bool(m_parent->value()) ? Qt::Checked :
0382                 Qt::Unchecked;
0383 
0384             if (m_ignoreBox->checkState() != newState) {
0385                 m_ignoreBox->setCheckState(newState);
0386             }
0387         }
0388         MultinodePropertyConnectorInterface::notifyValueChanged();
0389     }
0390 protected:
0391     void slotIgnoreCheckBoxChanged(int state) override {
0392         if (state == Qt::PartiallyChecked) {
0393             m_parent->setIgnored(true);
0394         } else {
0395             m_parent->setIgnored(false);
0396             m_parent->setValue(bool(state == Qt::Checked));
0397         }
0398     }
0399 
0400 private:
0401     QPointer<QCheckBox> m_ignoreBox;
0402     PropertyType *m_parent;
0403 };
0404 
0405 /******************************************************************/
0406 /*               MultinodePropertyUndoCommand                     */
0407 /******************************************************************/
0408 
0409 template <class PropertyAdapter>
0410 class MultinodePropertyUndoCommand : public KUndo2Command
0411 {
0412 public:
0413     typedef typename PropertyAdapter::ValueType ValueType;
0414 public:
0415     MultinodePropertyUndoCommand(PropertyAdapter propAdapter,
0416                                  KisNodeList nodes,
0417                                  const QList<ValueType> &oldValues,
0418                                  ValueType newValue,
0419                                  KUndo2Command *parent = 0)
0420         : KUndo2Command(parent),
0421           m_propAdapter(propAdapter),
0422           m_nodes(nodes),
0423           m_oldValues(oldValues),
0424           m_newValue(newValue)
0425     {
0426     }
0427 
0428     void undo() override {
0429         int index = 0;
0430         Q_FOREACH (KisNodeSP node, m_nodes) {
0431             m_propAdapter.setPropForNode(node, m_oldValues[index], -1);
0432             index++;
0433         }
0434     }
0435 
0436     void redo() override {
0437         int index = 0;
0438         Q_FOREACH (KisNodeSP node, m_nodes) {
0439             m_propAdapter.setPropForNode(node, m_newValue, index);
0440             index++;
0441         }
0442     }
0443 
0444 private:
0445     PropertyAdapter m_propAdapter;
0446     KisNodeList m_nodes;
0447     QList<ValueType> m_oldValues;
0448     ValueType m_newValue;
0449 };
0450 
0451 /******************************************************************/
0452 /*               KisMultinodePropertyInterface                    */
0453 /******************************************************************/
0454 
0455 class KRITAUI_EXPORT KisMultinodePropertyInterface
0456 {
0457 public:
0458     KisMultinodePropertyInterface();
0459     virtual ~KisMultinodePropertyInterface();
0460 
0461     virtual void rereadCurrentValue() = 0;
0462 
0463     virtual void setIgnored(bool value) = 0;
0464     virtual bool isIgnored() const = 0;
0465 
0466     virtual bool savedValuesDiffer() const = 0;
0467     virtual bool haveTheOnlyNode() const = 0;
0468 
0469     virtual void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) = 0;
0470     virtual void connectIgnoreCheckBox(QCheckBox *ignoreBox) = 0;
0471 
0472     virtual void connectAutoEnableWidget(QWidget *widget) = 0;
0473 
0474     virtual KUndo2Command* createPostExecutionUndoCommand() = 0;
0475 };
0476 
0477 typedef QSharedPointer<KisMultinodePropertyInterface> KisMultinodePropertyInterfaceSP;
0478 
0479 /******************************************************************/
0480 /*               KisMultinodeProperty                             */
0481 /******************************************************************/
0482 
0483 template <class PropertyAdapter>
0484 class KisMultinodeProperty : public KisMultinodePropertyInterface
0485 {
0486 public:
0487     typedef typename PropertyAdapter::ValueType ValueType;
0488     typedef typename PropertyAdapter::ConnectorType ConnectorType;
0489 public:
0490     KisMultinodeProperty(KisNodeList nodes, PropertyAdapter adapter = PropertyAdapter())
0491         : m_nodes(PropertyAdapter::filterNodes(nodes)),
0492           m_savedValuesDiffer(false),
0493           m_propAdapter(adapter),
0494           m_connector(new ConnectorType(this))
0495     {
0496         Q_ASSERT(!m_nodes.isEmpty());
0497         m_propAdapter.setNumNodes(m_nodes.size());
0498 
0499         ValueType lastValue = m_propAdapter.propForNode(m_nodes.first());
0500         Q_FOREACH (KisNodeSP node, m_nodes) {
0501             ValueType value = m_propAdapter.propForNode(node);
0502             m_savedValues.append(value);
0503 
0504             if (value != lastValue) {
0505                 m_savedValuesDiffer = true;
0506             }
0507 
0508             lastValue = value;
0509         }
0510 
0511         m_isIgnored =
0512             m_nodes.size() > 1 && PropertyAdapter::forceIgnoreByDefault ?
0513             true : m_savedValuesDiffer;
0514 
0515         m_currentValue = defaultValue();
0516     }
0517     ~KisMultinodeProperty() override {}
0518 
0519     void rereadCurrentValue() override {
0520         if (m_isIgnored) return;
0521 
0522         ValueType lastValue = m_propAdapter.propForNode(m_nodes.first());
0523         Q_FOREACH (KisNodeSP node, m_nodes) {
0524             ValueType value = m_propAdapter.propForNode(node);
0525 
0526             if (value != lastValue) {
0527                 qWarning() << "WARNING: mutiprops: values differ after reread!";
0528             }
0529 
0530             lastValue = value;
0531         }
0532 
0533         if (lastValue != m_currentValue) {
0534             m_currentValue = lastValue;
0535             m_connector->notifyValueChanged();
0536         }
0537     }
0538 
0539     void setValue(const ValueType &value) {
0540         Q_ASSERT(!m_isIgnored);
0541         if (value == m_currentValue) return;
0542 
0543         int index = 0;
0544 
0545         Q_FOREACH (KisNodeSP node, m_nodes) {
0546             m_propAdapter.setPropForNode(node, value, index);
0547             index++;
0548         }
0549 
0550         m_currentValue = value;
0551         m_connector->notifyValueChanged();
0552     }
0553 
0554     ValueType value() const {
0555         return m_currentValue;
0556     }
0557 
0558     void setIgnored(bool value) override {
0559         if (value == m_isIgnored) return;
0560 
0561         m_isIgnored = value;
0562         if (m_isIgnored) {
0563             int index = 0;
0564             Q_FOREACH (KisNodeSP node, m_nodes) {
0565                 m_propAdapter.setPropForNode(node, m_savedValues[index], -1);
0566                 index++;
0567             }
0568             m_currentValue = defaultValue();
0569         } else {
0570             int index = 0;
0571             Q_FOREACH (KisNodeSP node, m_nodes) {
0572                 m_propAdapter.setPropForNode(node, m_currentValue, index);
0573                 index++;
0574             }
0575         }
0576 
0577         m_connector->notifyValueChanged();
0578         m_connector->notifyIgnoreChanged();
0579     }
0580 
0581     bool isIgnored() const override {
0582         return m_isIgnored;
0583     }
0584 
0585     KUndo2Command* createPostExecutionUndoCommand() override {
0586         KIS_ASSERT_RECOVER(!m_isIgnored) { return new KUndo2Command(); }
0587 
0588         return new MultinodePropertyUndoCommand<PropertyAdapter>(m_propAdapter, m_nodes,
0589                                                                  m_savedValues, m_currentValue);
0590     }
0591 
0592     // TODO: disconnect methods...
0593     void connectIgnoreCheckBox(QCheckBox *ignoreBox) override {
0594         m_connector->connectIgnoreCheckBox(ignoreBox);
0595     }
0596 
0597     void connectValueChangedSignal(const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) override {
0598         m_connector->connectValueChangedSignal(receiver, method, type);
0599     }
0600 
0601     void connectAutoEnableWidget(QWidget *widget) override {
0602         m_connector->connectAutoEnableWidget(widget);
0603     }
0604 
0605     /**
0606      * Interface for the connector
0607      */
0608 
0609     bool savedValuesDiffer() const override {
0610         return m_savedValuesDiffer;
0611     }
0612 
0613     bool haveTheOnlyNode() const override {
0614         return m_nodes.size() == 1;
0615     }
0616 
0617 private:
0618     ValueType defaultValue() const {
0619         return m_savedValues.first();
0620     }
0621 
0622 private:
0623     bool m_isIgnored;
0624     ValueType m_currentValue;
0625 
0626     KisNodeList m_nodes;
0627     QList<ValueType> m_savedValues;
0628 
0629     bool m_savedValuesDiffer;
0630     PropertyAdapter m_propAdapter;
0631     QScopedPointer<MultinodePropertyConnectorInterface> m_connector;
0632 };
0633 
0634 
0635 typedef KisMultinodeProperty<CompositeOpAdapter> KisMultinodeCompositeOpProperty;
0636 typedef KisMultinodeProperty<OpacityAdapter> KisMultinodeOpacityProperty;
0637 typedef KisMultinodeProperty<NameAdapter> KisMultinodeNameProperty;
0638 typedef KisMultinodeProperty<ColorLabelAdapter> KisMultinodeColorLabelProperty;
0639 
0640 #endif /* __KIS_MULTINODE_PROPERTY_H */