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"