File indexing completed on 2024-05-12 15:58:38

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     QStringList 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& e)
0071 {
0072     QDomNode n = e.firstChild();
0073 
0074     while (!n.isNull()) {
0075         // We don't nest elements in filter configuration. For now...
0076         QDomElement e = n.toElement();
0077         if (!e.isNull()) {
0078             if (e.tagName() == "param") {
0079                 // If the file contains the new type parameter introduced in Krita act on it
0080                 // Else invoke old behaviour
0081                 if(e.attributes().contains("type"))
0082                 {
0083                     QString type = e.attribute("type");
0084                     QString name = e.attribute("name");
0085                     QString value = e.text();
0086 
0087                     if (type == "bytearray") {
0088                         d->properties[name] = QVariant(QByteArray::fromBase64(value.toLatin1()));
0089                     }
0090                     else {
0091                         d->properties[name] = value;
0092                     }
0093                 }
0094                 else {
0095                     d->properties[e.attribute("name")] = QVariant(e.text());
0096                 }
0097             }
0098         }
0099         n = n.nextSibling();
0100     }
0101     //dump();
0102 }
0103 
0104 void KisPropertiesConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
0105 {
0106     QMap<QString, QVariant>::ConstIterator it;
0107     for (it = d->properties.constBegin(); it != d->properties.constEnd(); ++it) {
0108         if (d->notSavedProperties.contains(it.key())) {
0109             continue;
0110         }
0111 
0112         QDomElement e = doc.createElement("param");
0113         e.setAttribute("name", QString(it.key().toLatin1()));
0114         QString type = "string";
0115         QVariant v = it.value();
0116         QDomText text;
0117         if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KisCubicCurve>()) {
0118             text = doc.createCDATASection(v.value<KisCubicCurve>().toString());
0119         } else if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KoColor>()) {
0120             QDomDocument cdataDoc = QDomDocument("color");
0121             QDomElement cdataRoot = cdataDoc.createElement("color");
0122             cdataDoc.appendChild(cdataRoot);
0123             v.value<KoColor>().toXML(cdataDoc, cdataRoot);
0124             text = cdataDoc.createCDATASection(cdataDoc.toString());
0125             type = "color";
0126         } else if(v.type() == QVariant::String ) {
0127             text = doc.createCDATASection(v.toString());  // XXX: Unittest this!
0128             type = "string";
0129         } else if(v.type() == QVariant::ByteArray ) {
0130             text = doc.createTextNode(QString::fromLatin1(v.toByteArray().toBase64())); // Arbitrary Data
0131             type = "bytearray";
0132         } else {
0133             text = doc.createTextNode(v.toString());
0134             type = "internal";
0135         }
0136         e.setAttribute("type", type);
0137         e.appendChild(text);
0138         root.appendChild(e);
0139     }
0140 }
0141 
0142 QString KisPropertiesConfiguration::toXML() const
0143 {
0144     QDomDocument doc = QDomDocument("params");
0145     QDomElement root = doc.createElement("params");
0146     doc.appendChild(root);
0147     toXML(doc, root);
0148     return doc.toString();
0149 }
0150 
0151 
0152 bool KisPropertiesConfiguration::hasProperty(const QString& name) const
0153 {
0154     return d->properties.contains(name);
0155 }
0156 
0157 void KisPropertiesConfiguration::setProperty(const QString & name, const QVariant & value)
0158 {
0159     if (d->properties.find(name) == d->properties.end()) {
0160         d->properties.insert(name, value);
0161     } else {
0162         d->properties[name] = value;
0163     }
0164 }
0165 
0166 bool KisPropertiesConfiguration::getProperty(const QString & name, QVariant & value) const
0167 {
0168     if (d->properties.constFind(name) == d->properties.constEnd()) {
0169         return false;
0170     } else {
0171         value = d->properties.value(name);
0172         return true;
0173     }
0174 }
0175 
0176 QVariant KisPropertiesConfiguration::getProperty(const QString & name) const
0177 {
0178     return d->properties.value(name, QVariant());
0179 }
0180 
0181 
0182 int KisPropertiesConfiguration::getInt(const QString & name, int def) const
0183 {
0184     QVariant v = getProperty(name);
0185     if (v.isValid())
0186         return v.toInt();
0187     else
0188         return def;
0189 
0190 }
0191 
0192 double KisPropertiesConfiguration::getDouble(const QString & name, double def) const
0193 {
0194     QVariant v = getProperty(name);
0195     if (v.isValid())
0196         return v.toDouble();
0197     else
0198         return def;
0199 }
0200 
0201 float KisPropertiesConfiguration::getFloat(const QString & name, float def) const
0202 {
0203     QVariant v = getProperty(name);
0204     if (v.isValid())
0205         return (float)v.toDouble();
0206     else
0207         return def;
0208 }
0209 
0210 
0211 bool KisPropertiesConfiguration::getBool(const QString & name, bool def) const
0212 {
0213     QVariant v = getProperty(name);
0214     if (v.isValid())
0215         return v.toBool();
0216     else
0217         return def;
0218 }
0219 
0220 QString KisPropertiesConfiguration::getString(const QString & name, const QString & def) const
0221 {
0222     QVariant v = getProperty(name);
0223     if (v.isValid())
0224         return v.toString();
0225     else
0226         return def;
0227 }
0228 
0229 KisCubicCurve KisPropertiesConfiguration::getCubicCurve(const QString & name, const KisCubicCurve & curve) const
0230 {
0231     QVariant v = getProperty(name);
0232     if (v.isValid()) {
0233         if (v.type() == QVariant::UserType && v.userType() == qMetaTypeId<KisCubicCurve>()) {
0234             return v.value<KisCubicCurve>();
0235         } else {
0236             KisCubicCurve c;
0237             c.fromString(v.toString());
0238             return c;
0239         }
0240     } else
0241         return curve;
0242 }
0243 
0244 KoColor KisPropertiesConfiguration::getColor(const QString& name, const KoColor& color) const
0245 {
0246     QVariant v = getProperty(name);
0247 
0248     if (v.isValid()) {
0249         switch(v.type()) {
0250         case QVariant::UserType:
0251         {
0252             if (v.userType() == qMetaTypeId<KoColor>()) {
0253                 return v.value<KoColor>();
0254             }
0255             break;
0256         }
0257         case QVariant::String:
0258         {
0259             QDomDocument doc;
0260             if (doc.setContent(v.toString())) {
0261                 QDomElement e = doc.documentElement().firstChild().toElement();
0262                 bool ok;
0263                 KoColor c = KoColor::fromXML(e, Integer16BitsColorDepthID.id(), &ok);
0264                 if (ok) {
0265                     return c;
0266                 }
0267             }
0268             else {
0269                 QColor c(v.toString());
0270                 if (c.isValid()) {
0271                     KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0272                     return kc;
0273                 }
0274             }
0275             break;
0276         }
0277         case QVariant::Color:
0278         {
0279             QColor c = v.value<QColor>();
0280             KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0281             return kc;
0282         }
0283         case QVariant::Int:
0284         {
0285             QColor c(v.toInt());
0286             if (c.isValid()) {
0287                 KoColor kc(c, KoColorSpaceRegistry::instance()->rgb8());
0288                 return kc;
0289             }
0290             break;
0291         }
0292         default:
0293             ;
0294         }
0295     }
0296     return color;
0297 }
0298 
0299 void KisPropertiesConfiguration::dump() const
0300 {
0301     QMap<QString, QVariant>::ConstIterator it;
0302     for (it = d->properties.constBegin(); it != d->properties.constEnd(); ++it) {
0303         if (it->type() == QVariant::ByteArray) {
0304             QByteArray ba = it->toByteArray();
0305 
0306             if (ba.size() > 32) {
0307                 qDebug() << it.key() << " = " << QString("...skipped total %1 bytes...").arg(ba.size()) << it.value().typeName();
0308             } else {
0309                 qDebug() << it.key() << " = " << it.value() << it.value().typeName();
0310             }
0311         } else {
0312             qDebug() << it.key() << " = " << it.value() << it.value().typeName();
0313         }
0314     }
0315 
0316 }
0317 
0318 void KisPropertiesConfiguration::clearProperties()
0319 {
0320     d->properties.clear();
0321 }
0322 
0323 void KisPropertiesConfiguration::setPropertyNotSaved(const QString& name)
0324 {
0325     d->notSavedProperties.append(name);
0326 }
0327 
0328 QMap<QString, QVariant> KisPropertiesConfiguration::getProperties() const
0329 {
0330     return d->properties;
0331 }
0332 
0333 void KisPropertiesConfiguration::removeProperty(const QString & name)
0334 {
0335     d->properties.remove(name);
0336 }
0337 
0338 QList<QString> KisPropertiesConfiguration::getPropertiesKeys() const
0339 {
0340     return d->properties.keys();
0341 }
0342 
0343 void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfiguration *config) const
0344 {
0345     const int prefixSize = prefix.size();
0346 
0347     const QList<QString> keys = getPropertiesKeys();
0348     Q_FOREACH (const QString &key, keys) {
0349         if (key.startsWith(prefix)) {
0350             config->setProperty(key.mid(prefixSize), getProperty(key));
0351         }
0352     }
0353 }
0354 
0355 void KisPropertiesConfiguration::getPrefixedProperties(const QString &prefix, KisPropertiesConfigurationSP config) const
0356 {
0357     getPrefixedProperties(prefix, config.data());
0358 }
0359 
0360 void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfiguration *config)
0361 {
0362     const QList<QString> keys = config->getPropertiesKeys();
0363     Q_FOREACH (const QString &key, keys) {
0364         this->setProperty(prefix + key, config->getProperty(key));
0365     }
0366 }
0367 
0368 void KisPropertiesConfiguration::setPrefixedProperties(const QString &prefix, const KisPropertiesConfigurationSP config)
0369 {
0370     setPrefixedProperties(prefix, config.data());
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