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 }