File indexing completed on 2024-05-12 05:36:50
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(QMetaType(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 qsizetype objectCount(QQmlListProperty<PageDataObject> *list) 0051 { 0052 return static_cast<PageDataObject *>(list->object)->children().count(); 0053 } 0054 PageDataObject *objectAt(QQmlListProperty<PageDataObject> *list, qsizetype index) 0055 { 0056 return static_cast<PageDataObject *>(list->object)->children().at(index); 0057 } 0058 0059 PageDataObject::PageDataObject(const KSharedConfig::Ptr &config, QObject *parent) 0060 : QQmlPropertyMap(this, parent) 0061 , m_config(config) 0062 { 0063 m_childrenProperty = QQmlListProperty<PageDataObject>(this, nullptr, &objectCount, &objectAt); 0064 connect(this, &PageDataObject::valueChanged, this, &PageDataObject::markDirty); 0065 } 0066 0067 QQmlListProperty<PageDataObject> PageDataObject::childrenProperty() const 0068 { 0069 return m_childrenProperty; 0070 } 0071 0072 QList<PageDataObject *> PageDataObject::children() const 0073 { 0074 return m_children; 0075 } 0076 0077 PageDataObject *PageDataObject::insertChild(int index, const QVariantMap &properties) 0078 { 0079 if (index < 0) { 0080 return nullptr; 0081 } 0082 0083 if (index >= m_children.size()) { 0084 index = m_children.size(); 0085 } 0086 0087 auto child = new PageDataObject(m_config, this); 0088 for (auto itr = properties.begin(); itr != properties.end(); ++itr) { 0089 QString key = itr.key(); 0090 if (key == QLatin1String("Title")) { 0091 key = QStringLiteral("title"); 0092 } 0093 child->insert(key, itr.value()); 0094 } 0095 m_children.insert(index, child); 0096 child->markDirty(); 0097 0098 updateNames(); 0099 0100 connect(child, &PageDataObject::dirtyChanged, this, [this, child]() { 0101 if (child->dirty()) { 0102 markDirty(); 0103 } 0104 }); 0105 0106 markDirty(); 0107 0108 Q_EMIT childInserted(index); 0109 Q_EMIT childrenChanged(); 0110 0111 return child; 0112 } 0113 0114 void PageDataObject::removeChild(int index) 0115 { 0116 if (index < 0 || index >= m_children.size()) { 0117 return; 0118 } 0119 0120 auto child = m_children.at(index); 0121 m_children.remove(index); 0122 0123 child->disconnect(this); 0124 child->deleteLater(); 0125 0126 updateNames(); 0127 0128 markDirty(); 0129 0130 Q_EMIT childRemoved(index); 0131 Q_EMIT childrenChanged(); 0132 } 0133 0134 void PageDataObject::moveChild(int from, int to) 0135 { 0136 if (from < 0 || to < 0 || from >= m_children.size() || to >= m_children.size()) { 0137 return; 0138 } 0139 0140 auto movingChild = m_children.at(from); 0141 m_children.remove(from); 0142 m_children.insert(to, movingChild); 0143 0144 updateNames(); 0145 0146 markDirty(); 0147 0148 Q_EMIT childMoved(from, to); 0149 Q_EMIT childrenChanged(); 0150 } 0151 0152 PageDataObject *PageDataObject::childAt(int index) const 0153 { 0154 if (index < 0 || index >= m_children.size()) { 0155 return nullptr; 0156 } 0157 0158 return m_children.at(index); 0159 } 0160 0161 int PageDataObject::childCount() const 0162 { 0163 return m_children.size(); 0164 } 0165 0166 KSharedConfig::Ptr PageDataObject::config() const 0167 { 0168 return m_config; 0169 } 0170 0171 bool PageDataObject::resetPage() 0172 { 0173 reset(); 0174 0175 m_config->markAsClean(); 0176 m_config->reparseConfiguration(); 0177 return load(*m_config, QStringLiteral("page")); 0178 } 0179 0180 bool PageDataObject::savePage() 0181 { 0182 auto result = save(*m_config, QStringLiteral("page")); 0183 if (result) { 0184 return m_config->sync(); 0185 } 0186 return false; 0187 } 0188 0189 void PageDataObject::saveAs(const QUrl &destination) 0190 { 0191 auto copiedPage = m_config->copyTo(destination.toLocalFile()); 0192 // KConfig passes the ownership of the returned config to us, the destructor of it will write it to the disk 0193 delete copiedPage; 0194 } 0195 0196 bool PageDataObject::load(const KConfigBase &config, const QString &groupName) 0197 { 0198 auto group = config.group(groupName); 0199 0200 if (!m_children.isEmpty()) { 0201 qDeleteAll(m_children); 0202 m_children.clear(); 0203 } 0204 0205 if (isGroupEmpty(group)) { 0206 return false; 0207 } 0208 0209 const auto entries = group.entryMap(); 0210 for (auto itr = entries.begin(); itr != entries.end(); ++itr) { 0211 auto variant = QVariant::fromValue(itr.value()); 0212 static const std::array<QMetaType::Type, 5> types{ 0213 QMetaType::Double, 0214 QMetaType::Int, 0215 QMetaType::QDateTime, 0216 QMetaType::Bool, 0217 QMetaType::QString, 0218 }; 0219 for (auto type : types) { 0220 auto value = converted(variant, type); 0221 if (value.isValid()) { 0222 // We want titles translatable 0223 // While most config keys are lowerCase, KDE's translation system only deals with UpperCamelCase 0224 // We abstract in this class to keep the rest of plasma-systemmonitor code consistent 0225 QString key = itr.key(); 0226 if (key == QLatin1String("Title")) { 0227 key = QStringLiteral("title"); 0228 } 0229 insert(key, value); 0230 break; 0231 } 0232 } 0233 } 0234 0235 auto groups = group.groupList(); 0236 groups.sort(); 0237 for (const auto &groupName : std::as_const(groups)) { 0238 auto object = new PageDataObject{m_config, this}; 0239 if (object->load(group, groupName)) { 0240 m_children.append(object); 0241 connect(object, &PageDataObject::dirtyChanged, this, [this, object]() { 0242 if (object->dirty()) { 0243 markDirty(); 0244 } 0245 }); 0246 } else { 0247 delete object; 0248 } 0249 } 0250 0251 markClean(); 0252 Q_EMIT childrenChanged(); 0253 Q_EMIT loaded(); 0254 return true; 0255 } 0256 0257 bool PageDataObject::save(KConfigBase &config, const QString &groupName, const QStringList &ignoreProperties) 0258 { 0259 if (!m_dirty && config.hasGroup(groupName)) { 0260 return false; 0261 } 0262 0263 auto group = config.group(groupName); 0264 0265 const auto names = keys(); 0266 for (const auto &name : names) { 0267 if (ignoreProperties.contains(name)) { 0268 continue; 0269 } else { 0270 QString key = name; 0271 if (name == QLatin1String("title")) { 0272 key = QStringLiteral("Title"); 0273 } 0274 group.writeEntry(key, value(name)); 0275 } 0276 } 0277 0278 auto groupNames = group.groupList(); 0279 for (auto child : std::as_const(m_children)) { 0280 auto name = child->value(QStringLiteral("name")).toString(); 0281 groupNames.removeOne(name); 0282 child->save(group, name); 0283 } 0284 0285 for (const auto &name : std::as_const(groupNames)) { 0286 group.deleteGroup(name); 0287 } 0288 0289 markClean(); 0290 Q_EMIT saved(); 0291 return true; 0292 } 0293 0294 void PageDataObject::reset() 0295 { 0296 markClean(); 0297 0298 if (m_faceLoader) { 0299 m_faceLoader->reset(); 0300 } 0301 0302 for (auto child : std::as_const(m_children)) { 0303 child->reset(); 0304 } 0305 } 0306 0307 bool PageDataObject::dirty() const 0308 { 0309 return m_dirty; 0310 } 0311 0312 void PageDataObject::markDirty() 0313 { 0314 if (m_dirty) { 0315 return; 0316 } 0317 0318 m_dirty = true; 0319 Q_EMIT dirtyChanged(); 0320 } 0321 0322 void PageDataObject::markClean() 0323 { 0324 if (!m_dirty) { 0325 return; 0326 } 0327 0328 m_dirty = false; 0329 Q_EMIT dirtyChanged(); 0330 } 0331 0332 void PageDataObject::updateNames() 0333 { 0334 for (auto i = 0; i < m_children.size(); ++i) { 0335 auto name = m_children.at(i)->value(QStringLiteral("name")).toString(); 0336 name = QStringLiteral("%1-%2").arg(name.left(name.lastIndexOf('-'))).arg(i); 0337 m_children.at(i)->insert(QStringLiteral("name"), name); 0338 } 0339 } 0340 0341 FaceLoader *PageDataObject::faceLoader() 0342 { 0343 return m_faceLoader; 0344 } 0345 0346 void PageDataObject::setFaceLoader(FaceLoader *faceLoader) 0347 { 0348 m_faceLoader = faceLoader; 0349 } 0350 0351 bool PageDataObject::isGroupEmpty(const KConfigGroup &group) 0352 { 0353 if (group.entryMap().size() != 0) { 0354 return false; 0355 } 0356 0357 if (group.groupList().size() == 0) { 0358 return true; 0359 } 0360 0361 const auto groups = group.groupList(); 0362 for (const auto &subGroup : groups) { 0363 if (!isGroupEmpty(group.group(subGroup))) { 0364 return false; 0365 } 0366 } 0367 0368 return true; 0369 } 0370 0371 QString PageDataObject::fileName() const 0372 { 0373 return m_config->name(); 0374 } 0375 0376 #include "moc_PageDataObject.cpp"