File indexing completed on 2024-05-12 17:08:28
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "PageDataObject.h" 0008 0009 #include <array> 0010 0011 #include <QDebug> 0012 #include <QRegularExpression> 0013 #include <QUrl> 0014 0015 #include <KConfig> 0016 #include <KConfigGroup> 0017 0018 #include "FaceLoader.h" 0019 0020 QVariant converted(const QVariant &variant, QMetaType::Type type) 0021 { 0022 auto result = variant; 0023 0024 if (variant.toString().isEmpty()) { 0025 // Empty strings should be treated as just that, empty strings. 0026 // However, QVariant is a bit over eager to convert empty strings 0027 // to bool false. So explicitly ignore them here. 0028 return QVariant{}; 0029 } 0030 0031 if (!result.convert(type)) { 0032 return QVariant{}; 0033 } 0034 0035 if (type == QMetaType::Bool) { 0036 if (result.toBool()) { 0037 // Unfortunately, QVariant regards any string that is not "no" or "false" as true. 0038 // So we have to do our own parsing to check if the string matches "yes" or "true". 0039 static const QRegularExpression boolTrueExpression(QStringLiteral("^[yY][eE][sS]|[tT][rR][uU][eE]$")); 0040 auto match = boolTrueExpression.match(variant.toString()); 0041 if (!match.hasMatch()) { 0042 return QVariant{}; 0043 } 0044 } 0045 } 0046 0047 return result; 0048 } 0049 0050 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0051 int objectCount(QQmlListProperty<PageDataObject> *list) 0052 #else 0053 qsizetype objectCount(QQmlListProperty<PageDataObject> *list) 0054 #endif 0055 { 0056 return static_cast<PageDataObject *>(list->object)->children().count(); 0057 } 0058 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0059 PageDataObject *objectAt(QQmlListProperty<PageDataObject> *list, int index) 0060 #else 0061 PageDataObject *objectAt(QQmlListProperty<PageDataObject> *list, qsizetype index) 0062 #endif 0063 { 0064 return static_cast<PageDataObject *>(list->object)->children().at(index); 0065 } 0066 0067 PageDataObject::PageDataObject(const KSharedConfig::Ptr &config, QObject *parent) 0068 : QQmlPropertyMap(this, parent) 0069 , m_config(config) 0070 { 0071 m_childrenProperty = QQmlListProperty<PageDataObject>(this, nullptr, &objectCount, &objectAt); 0072 connect(this, &PageDataObject::valueChanged, this, &PageDataObject::markDirty); 0073 } 0074 0075 QQmlListProperty<PageDataObject> PageDataObject::childrenProperty() const 0076 { 0077 return m_childrenProperty; 0078 } 0079 0080 QVector<PageDataObject *> PageDataObject::children() const 0081 { 0082 return m_children; 0083 } 0084 0085 PageDataObject *PageDataObject::insertChild(int index, const QVariantMap &properties) 0086 { 0087 if (index < 0) { 0088 return nullptr; 0089 } 0090 0091 if (index >= m_children.size()) { 0092 index = m_children.size(); 0093 } 0094 0095 auto child = new PageDataObject(m_config, this); 0096 for (auto itr = properties.begin(); itr != properties.end(); ++itr) { 0097 QString key = itr.key(); 0098 if (key == QLatin1String("Title")) { 0099 key = QStringLiteral("title"); 0100 } 0101 child->insert(key, itr.value()); 0102 } 0103 m_children.insert(index, child); 0104 child->markDirty(); 0105 0106 updateNames(); 0107 0108 connect(child, &PageDataObject::dirtyChanged, this, [this, child]() { 0109 if (child->dirty()) { 0110 markDirty(); 0111 } 0112 }); 0113 0114 markDirty(); 0115 0116 Q_EMIT childInserted(index); 0117 Q_EMIT childrenChanged(); 0118 0119 return child; 0120 } 0121 0122 void PageDataObject::removeChild(int index) 0123 { 0124 if (index < 0 || index >= m_children.size()) { 0125 return; 0126 } 0127 0128 auto child = m_children.at(index); 0129 m_children.remove(index); 0130 0131 child->disconnect(this); 0132 child->deleteLater(); 0133 0134 updateNames(); 0135 0136 markDirty(); 0137 0138 Q_EMIT childRemoved(index); 0139 Q_EMIT childrenChanged(); 0140 } 0141 0142 void PageDataObject::moveChild(int from, int to) 0143 { 0144 if (from < 0 || to < 0 || from >= m_children.size() || to >= m_children.size()) { 0145 return; 0146 } 0147 0148 auto movingChild = m_children.at(from); 0149 m_children.remove(from); 0150 m_children.insert(to, movingChild); 0151 0152 updateNames(); 0153 0154 markDirty(); 0155 0156 Q_EMIT childMoved(from, to); 0157 Q_EMIT childrenChanged(); 0158 } 0159 0160 PageDataObject *PageDataObject::childAt(int index) const 0161 { 0162 if (index < 0 || index >= m_children.size()) { 0163 return nullptr; 0164 } 0165 0166 return m_children.at(index); 0167 } 0168 0169 int PageDataObject::childCount() const 0170 { 0171 return m_children.size(); 0172 } 0173 0174 KSharedConfig::Ptr PageDataObject::config() const 0175 { 0176 return m_config; 0177 } 0178 0179 bool PageDataObject::resetPage() 0180 { 0181 reset(); 0182 0183 m_config->markAsClean(); 0184 m_config->reparseConfiguration(); 0185 return load(*m_config, QStringLiteral("page")); 0186 } 0187 0188 bool PageDataObject::savePage() 0189 { 0190 auto result = save(*m_config, QStringLiteral("page")); 0191 if (result) { 0192 return m_config->sync(); 0193 } 0194 return false; 0195 } 0196 0197 void PageDataObject::saveAs(const QUrl &destination) 0198 { 0199 auto copiedPage = m_config->copyTo(destination.toLocalFile()); 0200 // KConfig passes the ownership of the returned config to us, the destructor of it will write it to the disk 0201 delete copiedPage; 0202 } 0203 0204 bool PageDataObject::load(const KConfigBase &config, const QString &groupName) 0205 { 0206 auto group = config.group(groupName); 0207 0208 if (!m_children.isEmpty()) { 0209 qDeleteAll(m_children); 0210 m_children.clear(); 0211 } 0212 0213 if (isGroupEmpty(group)) { 0214 return false; 0215 } 0216 0217 const auto entries = group.entryMap(); 0218 for (auto itr = entries.begin(); itr != entries.end(); ++itr) { 0219 auto variant = QVariant::fromValue(itr.value()); 0220 static const std::array<QMetaType::Type, 5> types{ 0221 QMetaType::Double, 0222 QMetaType::Int, 0223 QMetaType::QDateTime, 0224 QMetaType::Bool, 0225 QMetaType::QString, 0226 }; 0227 for (auto type : types) { 0228 auto value = converted(variant, type); 0229 if (value.isValid()) { 0230 // We want titles translatable 0231 // While most config keys are lowerCase, KDE's translation system only deals with UpperCamelCase 0232 // We abstract in this class to keep the rest of plasma-systemmonitor code consistent 0233 QString key = itr.key(); 0234 if (key == QLatin1String("Title")) { 0235 key = QStringLiteral("title"); 0236 } 0237 insert(key, value); 0238 break; 0239 } 0240 } 0241 } 0242 0243 auto groups = group.groupList(); 0244 groups.sort(); 0245 for (const auto &groupName : std::as_const(groups)) { 0246 auto object = new PageDataObject{m_config, this}; 0247 if (object->load(group, groupName)) { 0248 m_children.append(object); 0249 connect(object, &PageDataObject::dirtyChanged, this, [this, object]() { 0250 if (object->dirty()) { 0251 markDirty(); 0252 } 0253 }); 0254 } else { 0255 delete object; 0256 } 0257 } 0258 0259 markClean(); 0260 Q_EMIT childrenChanged(); 0261 Q_EMIT loaded(); 0262 return true; 0263 } 0264 0265 bool PageDataObject::save(KConfigBase &config, const QString &groupName, const QStringList &ignoreProperties) 0266 { 0267 if (!m_dirty && config.hasGroup(groupName)) { 0268 return false; 0269 } 0270 0271 auto group = config.group(groupName); 0272 0273 const auto names = keys(); 0274 for (const auto &name : names) { 0275 if (ignoreProperties.contains(name)) { 0276 continue; 0277 } else { 0278 QString key = name; 0279 if (name == QLatin1String("title")) { 0280 key = QStringLiteral("Title"); 0281 } 0282 group.writeEntry(key, value(name)); 0283 } 0284 } 0285 0286 auto groupNames = group.groupList(); 0287 for (auto child : std::as_const(m_children)) { 0288 auto name = child->value(QStringLiteral("name")).toString(); 0289 groupNames.removeOne(name); 0290 child->save(group, name); 0291 } 0292 0293 for (const auto &name : std::as_const(groupNames)) { 0294 group.deleteGroup(name); 0295 } 0296 0297 markClean(); 0298 Q_EMIT saved(); 0299 return true; 0300 } 0301 0302 void PageDataObject::reset() 0303 { 0304 markClean(); 0305 0306 if (m_faceLoader) { 0307 m_faceLoader->reset(); 0308 } 0309 0310 for (auto child : std::as_const(m_children)) { 0311 child->reset(); 0312 } 0313 } 0314 0315 bool PageDataObject::dirty() const 0316 { 0317 return m_dirty; 0318 } 0319 0320 void PageDataObject::markDirty() 0321 { 0322 if (m_dirty) { 0323 return; 0324 } 0325 0326 m_dirty = true; 0327 Q_EMIT dirtyChanged(); 0328 } 0329 0330 void PageDataObject::markClean() 0331 { 0332 if (!m_dirty) { 0333 return; 0334 } 0335 0336 m_dirty = false; 0337 Q_EMIT dirtyChanged(); 0338 } 0339 0340 void PageDataObject::updateNames() 0341 { 0342 for (auto i = 0; i < m_children.size(); ++i) { 0343 auto name = m_children.at(i)->value(QStringLiteral("name")).toString(); 0344 name = QStringLiteral("%1-%2").arg(name.left(name.lastIndexOf('-'))).arg(i); 0345 m_children.at(i)->insert(QStringLiteral("name"), name); 0346 } 0347 } 0348 0349 FaceLoader *PageDataObject::faceLoader() 0350 { 0351 return m_faceLoader; 0352 } 0353 0354 void PageDataObject::setFaceLoader(FaceLoader *faceLoader) 0355 { 0356 m_faceLoader = faceLoader; 0357 } 0358 0359 bool PageDataObject::isGroupEmpty(const KConfigGroup &group) 0360 { 0361 if (group.entryMap().size() != 0) { 0362 return false; 0363 } 0364 0365 if (group.groupList().size() == 0) { 0366 return true; 0367 } 0368 0369 const auto groups = group.groupList(); 0370 for (const auto &subGroup : groups) { 0371 if (!isGroupEmpty(group.group(subGroup))) { 0372 return false; 0373 } 0374 } 0375 0376 return true; 0377 } 0378 0379 QString PageDataObject::fileName() const 0380 { 0381 return m_config->name(); 0382 }