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 */