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