File indexing completed on 2024-05-19 04:26:31

0001 /*
0002  *  SPDX-FileCopyrightText: 2006 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2007, 2010 Cyrille Berger <cberger@cberger.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_properties_configuration.h"
0009 
0010 
0011 #include <kis_debug.h>
0012 #include <QDomDocument>
0013 #include <QString>
0014 
0015 #include "kis_image.h"
0016 #include "kis_transaction.h"
0017 #include "kis_undo_adapter.h"
0018 #include "kis_painter.h"
0019 #include "kis_selection.h"
0020 #include "KoID.h"
0021 #include "kis_types.h"
0022 #include <KoColor.h>
0023 #include <KoColorModelStandardIds.h>
0024 #include <KoColorSpaceRegistry.h>
0025 
0026 struct Q_DECL_HIDDEN KisPropertiesConfiguration::Private {
0027     QMap<QString, QVariant> properties;
0028     QSet<QString> notSavedProperties;
0029 };
0030 
0031 KisPropertiesConfiguration::KisPropertiesConfiguration() : d(new Private)
0032 {
0033 }
0034 
0035 KisPropertiesConfiguration::~KisPropertiesConfiguration()
0036 {
0037     delete d;
0038 }
0039 
0040 KisPropertiesConfiguration::KisPropertiesConfiguration(const KisPropertiesConfiguration& rhs)
0041     : KisSerializableConfiguration(rhs)
0042     , d(new Private(*rhs.d))
0043 {
0044 }
0045 
0046 KisPropertiesConfiguration &KisPropertiesConfiguration::operator=(const KisPropertiesConfiguration &rhs)
0047 {
0048     if (&rhs != this) {
0049         *d = *rhs.d;
0050     }
0051 
0052     return *this;
0053 }
0054 
0055 bool KisPropertiesConfiguration::fromXML(const QString & xml, bool clear)
0056 {
0057     if (clear) {
0058         clearProperties();
0059     }
0060 
0061     QDomDocument doc;
0062     bool retval = doc.setContent(xml);
0063     if (retval) {
0064         QDomElement e = doc.documentElement();
0065         fromXML(e);
0066     }
0067     return retval;
0068 }
0069 
0070 void KisPropertiesConfiguration::fromXML(const QDomElement &root)
0071 {
0072     QDomElement e;
0073     for (e = root.firstChildElement("param"); !e.isNull(); e = e.nextSiblingElement("param")) {
0074         QString name = e.attribute("name");
0075         QString value = e.text();
0076 
0077         // Older versions didn't have a "type" parameter,
0078         // so fall back to the old behavior if it's missing.
0079         if (!e.hasAttribute("type")) {
0080             d->properties[name] = QVariant(value);
0081         } else if (e.attribute("type") == "bytearray") {
0082             d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1()));
0083         } else {
0084             d->properties[name] = value;
0085         }
0086     }
0087 }
0088 
0089 void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
0090 {
0091     QMap<QString, QVariant>::ConstIterator it;
0092     for (it = d->properties.constBegin(); it != d->properties.constEnd(); ++it) {
0093         if (d->notSavedProperties.contains(it.key())) {
0094             continue;
0095         }
0096 
0097         QDomElement e = doc.createElement("param");
0098         e.setAttribute("name", QString(it.key().toLatin1()));
0099         QString type = "string";
0100         QVariant v = it.value();
0101         QDomText text;
0102         if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KisCubicCurve>()) {
0103             text = doc.createCDATASection(v.value<KisCubicCurve>().toString());
0104         } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KoColor>()) {
0105             QDomDocument cdataDoc = QDomDocument("color");
0106             QDomElement cdataRoot = cdataDoc.createElement("color");
0107             cdataDoc.appendChild(cdataRoot);
0108             v.value<KoColor>().toXML(cdataDoc, cdataRoot);
0109             text = cdataDoc.createCDATASection(cdataDoc.toString());
0110             type = "color";
0111         } else if(v.type() == QVariant::String ) {
0112             text = doc.createCDATASection(v.toString());  // XXX: Unittest this!
0113             type = "string";
0114         } else if(v.type() == QVariant::ByteArray ) {
0115             text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data
0116             type = "bytearray";
0117         } else {
0118             text = doc.createTextNode(v.toString());
0119             type = "internal";
0120         }
0121         e.setAttribute("type", type);
0122         e.appendChild(text);
0123         root.appendChild(e);
0124     }
0125 }
0126 
0127 QString KisPropertiesConfiguration::toXML() const
0128 {
0129     QDomDocument doc = QDomDocument("params");
0130     QDomElement root = doc.createElement("params");
0131     doc.appendChild(root);
0132     toXML(doc, root);
0133     return doc.toString();
0134 }
0135 
0136 
0137 bool KisPropertiesConfiguration::hasProperty(const QString& name) const
0138 {
0139     return d->properties.contains(name);
0140 }
0141 
0142 void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value)
0143 {
0144     if (d->properties.find(name) == d->properties.end()) {
0145         d->properties.insert(name, value);
0146     } else {
0147         d->properties[name] = value;
0148     }
0149 }
0150 
0151 bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const
0152 {
0153     if (d->properties.constFind(name) == d->properties.constEnd()) {
0154         return false;
0155     } else {
0156         value = d->properties.value(name);
0157         return true;
0158     }
0159 }
0160 
0161 QVariant KisPropertiesConfiguration::getProperty(const QString & name) const
0162 {
0163     return d->properties.value(name, QVariant());
0164 }
0165 
0166 
0167 int KisPropertiesConfiguration::getInt(const QString & name, int def) const
0168 {
0169     QVariant v = getProperty(name);
0170     if (v.isValid())
0171         return v.toInt();
0172     else
0173         return def;
0174 
0175 }
0176 
0177 double KisPropertiesConfiguration::getDouble(const QString & name, double def) const
0178 {
0179     QVariant v = getProperty(name);
0180     if (v.isValid())
0181         return v.toDouble();
0182     else
0183         return def;
0184 }
0185 
0186 float KisPropertiesConfiguration::getFloat(const QString & name, float def) const
0187 {
0188     QVariant v = getProperty(name);
0189     if (v.isValid())
0190         return (float)v.toDouble();
0191     else
0192         return def;
0193 }
0194 
0195 
0196 bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const
0197 {
0198     QVariant v = getProperty(name);
0199     if (v.isValid())
0200         return v.toBool();
0201     else
0202         return def;
0203 }
0204 
0205 QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const
0206 {
0207     QVariant v = getProperty(name);
0208     if (v.isValid())
0209         return v.toString();
0210     else
0211         return def;
0212 }
0213 
0214 KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const
0215 {
0216     QVariant v = getProperty(name);
0217     if (v.isValid()) {
0218         if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KisCubicCurve>()) {
0219             return v.value<KisCubicCurve>();
0220         } else {
0221             return KisCubicCurve(v.toString());
0222         }
0223     } else
0224         return curve;
0225 }
0226 
0227 KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const
0228 {
0229     QVariant v = getProperty(name);
0230 
0231     if (v.isValid()) {
0232         switch(v.type()) {
0233         case QVariant::UserType:
0234         {
0235             if (v.userType() == qMetaTypeId<KoColor>()) {
0236                 return v.value<KoColor>();
0237             }
0238             break;
0239         }
0240         case QVariant::String:
0241         {
0242             QDomDocument doc;
0243             if (doc.setContent(v.toString())) {
0244                 QDomElement e = doc.documentElement().firstChild().toElement();
0245                 bool ok;
0246                 KoColor c = KoColor::fromXML(e, Integer16BitsColorDepthID.id(), &ok);
0247                 if (ok) {
0248                     return c;
0249                 }
0250             }
0251             else {
0252                 QColor c(v.toString());
0253                 if (c.isValid()) {
0254                     KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0255                     return kc;
0256                 }
0257             }
0258             break;
0259         }
0260         case QVariant::Color:
0261         {
0262             QColor c = v.value<QColor>();
0263             KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0264             return kc;
0265         }
0266         case QVariant::Int:
0267         {
0268             QColor c(v.toInt());
0269             if (c.isValid()) {
0270                 KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0271                 return kc;
0272             }
0273             break;
0274         }
0275         default:
0276             ;
0277         }
0278     }
0279     return color;
0280 }
0281 
0282 void KisPropertiesConfiguration::dump() const
0283 {
0284     QMap<QString, QVariant>::ConstIterator it;
0285     for (it = d->properties.constBegin(); it != d->properties.constEnd(); ++it) {
0286         if (it->type() == QVariant::ByteArray) {
0287             QByteArray ba = it->toByteArray();
0288 
0289             if (ba.size() > 32) {
0290                 qDebug() << it.key() << " = " << QString("...skipped total %1 bytes...").arg(ba.size()) << it.value().typeName();
0291             } else {
0292                 qDebug() << it.key() << " = " << it.value() << it.value().typeName();
0293             }
0294         } else {
0295             qDebug() << it.key() << " = " << it.value() << it.value().typeName();
0296         }
0297     }
0298 
0299 }
0300 
0301 void KisPropertiesConfiguration::clearProperties()
0302 {
0303     d->properties.clear();
0304 }
0305 
0306 void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name)
0307 {
0308     d->notSavedProperties.insert(name);
0309 }
0310 
0311 QMap<QString, QVariant> KisPropertiesConfiguration::getProperties() const
0312 {
0313     return d->properties;
0314 }
0315 
0316 void KisPropertiesConfiguration::removeProperty(const QString & name)
0317 {
0318     d->properties.remove(name);
0319 }
0320 
0321 QList<QString> KisPropertiesConfiguration::getPropertiesKeys() const
0322 {
0323     return d->properties.keys();
0324 }
0325 
0326 void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfiguration *config) const
0327 {
0328     const int prefixSize = prefix.size();
0329 
0330     const QList<QString> keys = getPropertiesKeys();
0331     Q_FOREACH (const QString &key, keys) {
0332         if (key.startsWith(prefix)) {
0333             config->setProperty(key.mid(prefixSize), getProperty(key));
0334         }
0335     }
0336 
0337     QString fullPrefix;
0338     const QString parentPrefix = getString(extractedPrefixKey());
0339     if (!parentPrefix.isEmpty()) {
0340         fullPrefix = parentPrefix + "/" + prefix;
0341     } else {
0342         fullPrefix = prefix;
0343     }
0344 
0345     config->setProperty(extractedPrefixKey(), fullPrefix);
0346     config->setPropertyNotSaved(extractedPrefixKey());
0347 }
0348 
0349 void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfigurationSP config) const
0350 {
0351     getPrefixedProperties(prefix, config.data());
0352 }
0353 
0354 void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfiguration *config)
0355 {
0356     const QList<QString> keys = config->getPropertiesKeys();
0357     Q_FOREACH (const QString &key, keys) {
0358         this->setProperty(prefix + key, config->getProperty(key));
0359     }
0360 }
0361 
0362 void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfigurationSP config)
0363 {
0364     setPrefixedProperties(prefix, config.data());
0365 }
0366 
0367 QString KisPropertiesConfiguration::extractedPrefixKey()
0368 {
0369     static const QString key = "__extractedFromPrefix";
0370     return key;
0371 }
0372 
0373 QString KisPropertiesConfiguration::escapeString(const QString &string)
0374 {
0375     QString result = string;
0376     result.replace(";", "\\;");
0377     result.replace("]", "\\]");
0378     result.replace(">", "\\>");
0379     return result;
0380 }
0381 
0382 QString KisPropertiesConfiguration::unescapeString(const QString &string)
0383 {
0384     QString result = string;
0385     result.replace("\\;", ";");
0386     result.replace("\\]", "]");
0387     result.replace("\\>", ">");
0388     return result;
0389 }
0390 
0391 void KisPropertiesConfiguration::setProperty(const QString &name, const QStringList &value)
0392 {
0393     QStringList escapedList;
0394     escapedList.reserve(value.size());
0395 
0396     Q_FOREACH (const QString &str, value) {
0397         escapedList << escapeString(str);
0398     }
0399 
0400     setProperty(name, escapedList.join(';'));
0401 }
0402 
0403 QStringList KisPropertiesConfiguration::getStringList(const QString &name, const QStringList &defaultValue) const
0404 {
0405     if (!hasProperty(name)) return defaultValue;
0406 
0407     const QString joined = getString(name);
0408 
0409     QStringList result;
0410 
0411     int afterLastMatch = -1;
0412     for (int i = 0; i < joined.size(); i++) {
0413         const bool lastChunk = i == joined.size() - 1;
0414         const bool matchedSplitter = joined[i] == ';' && (i == 0 || joined[i - 1] != '\\');
0415 
0416         if (lastChunk || matchedSplitter) {
0417             result << unescapeString(joined.mid(afterLastMatch, i - afterLastMatch + int(lastChunk && !matchedSplitter)));
0418             afterLastMatch = i + 1;
0419         }
0420 
0421         if (lastChunk && matchedSplitter) {
0422             result << QString();
0423         }
0424     }
0425 
0426     return result;
0427 }
0428 
0429 QStringList KisPropertiesConfiguration::getPropertyLazy(const QString &name, const QStringList &defaultValue) const
0430 {
0431     return getStringList(name, defaultValue);
0432 }
0433 
0434 bool KisPropertiesConfiguration::compareTo(const KisPropertiesConfiguration* rhs) const
0435 {
0436     if (rhs == nullptr)
0437         return false;
0438 
0439     for(const auto& propertyName: getPropertiesKeys()) {
0440         if (getProperty(propertyName) != rhs->getProperty(propertyName))
0441             return false;
0442     }
0443 
0444     return true;
0445 }
0446 
0447 // --- factory ---
0448 
0449 struct Q_DECL_HIDDEN KisPropertiesConfigurationFactory::Private {
0450 };
0451 
0452 KisPropertiesConfigurationFactory::KisPropertiesConfigurationFactory() : d(new Private)
0453 {
0454 }
0455 
0456 KisPropertiesConfigurationFactory::~KisPropertiesConfigurationFactory()
0457 {
0458     delete d;
0459 }
0460 
0461 KisSerializableConfigurationSP KisPropertiesConfigurationFactory::createDefault()
0462 {
0463     return new KisPropertiesConfiguration();
0464 }
0465 
0466 KisSerializableConfigurationSP KisPropertiesConfigurationFactory::create(const QDomElement& e)
0467 {
0468     KisPropertiesConfigurationSP pc = new KisPropertiesConfiguration();
0469     pc->fromXML(e);
0470     return pc;
0471 }
0472