File indexing completed on 2024-05-12 17:08:27
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "thememodel.h" 0008 #include "coloreditor.h" 0009 #include "themelistmodel.h" 0010 #include <QByteArray> 0011 #include <QDebug> 0012 #include <QDir> 0013 #include <QDirIterator> 0014 #include <QFile> 0015 #include <QIcon> 0016 #include <QStandardPaths> 0017 0018 #include <QXmlDefaultHandler> 0019 #include <QXmlInputSource> 0020 #include <QXmlSimpleReader> 0021 0022 #include <KAboutData> 0023 #include <KCompressionDevice> 0024 #include <KConfigGroup> 0025 #include <KIO/Job> 0026 #include <KProcess> 0027 #include <KRun> 0028 0029 #include <Plasma/Theme> 0030 0031 class IconsParserHandler : public QXmlDefaultHandler 0032 { 0033 public: 0034 IconsParserHandler(); 0035 bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) override; 0036 QStringList m_ids; 0037 QStringList m_prefixes; 0038 }; 0039 0040 IconsParserHandler::IconsParserHandler() 0041 : QXmlDefaultHandler() 0042 { 0043 } 0044 0045 bool IconsParserHandler::startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts) 0046 { 0047 Q_UNUSED(namespaceURI) 0048 Q_UNUSED(localName) 0049 Q_UNUSED(qName) 0050 0051 const QString id = atts.value("id"); 0052 // qWarning() << "Start Element:"<<id; 0053 0054 if (!id.isEmpty() && !id.contains(QRegularExpression("\\d\\d$")) && id != "base" && !id.contains("layer")) { 0055 m_ids << id; 0056 } 0057 if (id.endsWith(QLatin1String("-center")) && !id.contains("hint-")) { 0058 // remove -center 0059 m_prefixes << id.mid(0, id.length() - 7); 0060 } 0061 return true; 0062 } 0063 0064 ThemeModel::ThemeModel(const KPackage::Package &package, QObject *parent) 0065 : QAbstractListModel(parent) 0066 , m_theme(new Plasma::Theme) 0067 , m_themeName(QStringLiteral("default")) 0068 , m_package(package) 0069 , m_themeListModel(new ThemeListModel(this)) 0070 , m_colorEditor(new ColorEditor(this)) 0071 { 0072 m_theme->setUseGlobalSettings(false); 0073 m_theme->setThemeName(m_themeName); 0074 0075 m_roleNames.insert(ImagePath, "imagePath"); 0076 m_roleNames.insert(Description, "description"); 0077 m_roleNames.insert(Delegate, "delegate"); 0078 m_roleNames.insert(UsesFallback, "usesFallback"); 0079 m_roleNames.insert(SvgAbsolutePath, "svgAbsolutePath"); 0080 m_roleNames.insert(IsWritable, "isWritable"); 0081 m_roleNames.insert(IconElements, "iconElements"); 0082 m_roleNames.insert(FrameSvgPrefixes, "frameSvgPrefixes"); 0083 0084 load(); 0085 } 0086 0087 ThemeModel::~ThemeModel() 0088 { 0089 } 0090 0091 ThemeListModel *ThemeModel::themeList() 0092 { 0093 return m_themeListModel; 0094 } 0095 0096 ColorEditor *ThemeModel::colorEditor() 0097 { 0098 return m_colorEditor; 0099 } 0100 0101 QHash<int, QByteArray> ThemeModel::roleNames() const 0102 { 0103 return m_roleNames; 0104 } 0105 0106 int ThemeModel::rowCount(const QModelIndex &parent) const 0107 { 0108 Q_UNUSED(parent) 0109 return m_jsonDoc.array().size(); 0110 } 0111 0112 QVariant ThemeModel::data(const QModelIndex &index, int role) const 0113 { 0114 if (!index.isValid() || index.row() < 0 || index.row() > m_jsonDoc.array().size()) { 0115 return QVariant(); 0116 } 0117 0118 const QVariantMap value = m_jsonDoc.array().at(index.row()).toObject().toVariantMap(); 0119 0120 switch (role) { 0121 case ImagePath: 0122 return value.value("imagePath"); 0123 case Description: 0124 return value.value("description"); 0125 case Delegate: 0126 return value.value("delegate"); 0127 case UsesFallback: 0128 return !m_theme->currentThemeHasImage(value.value("imagePath").toString()); 0129 case SvgAbsolutePath: { 0130 QString path = m_theme->imagePath(value.value("imagePath").toString()); 0131 if (!value.value("imagePath").toString().contains("translucent")) { 0132 path = path.replace("translucent/", ""); 0133 } 0134 return path; 0135 } 0136 case IsWritable: 0137 return QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName); 0138 case IconElements: 0139 case FrameSvgPrefixes: { 0140 QString path = m_theme->imagePath(value.value("imagePath").toString()); 0141 if (!value.value("imagePath").toString().contains("translucent")) { 0142 path = path.replace("translucent/", ""); 0143 } 0144 KCompressionDevice file(path, KCompressionDevice::GZip); 0145 if (!file.open(QIODevice::ReadOnly)) { 0146 return QVariant(); 0147 } 0148 0149 QXmlSimpleReader reader; 0150 IconsParserHandler handler; 0151 reader.setContentHandler(&handler); 0152 QXmlInputSource source(&file); 0153 reader.parse(&source); 0154 0155 if (role == IconElements) { 0156 return handler.m_ids; 0157 } else { 0158 return handler.m_prefixes; 0159 } 0160 } 0161 } 0162 0163 return QVariant(); 0164 } 0165 0166 void ThemeModel::load() 0167 { 0168 beginResetModel(); 0169 qDebug() << "Loading theme description file" << m_package.filePath("data", "themeDescription.json"); 0170 0171 QFile jsonFile(m_package.filePath("data", "themeDescription.json")); 0172 jsonFile.open(QIODevice::ReadOnly); 0173 0174 QJsonParseError error; 0175 m_jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &error); 0176 0177 if (error.error != QJsonParseError::NoError) { 0178 qWarning() << "Error parsing Json" << error.errorString(); 0179 } 0180 0181 endResetModel(); 0182 } 0183 0184 QString ThemeModel::theme() const 0185 { 0186 return m_themeName; 0187 } 0188 0189 QString ThemeModel::author() const 0190 { 0191 const QList<KAboutPerson> authors = m_theme->metadata().authors(); 0192 return authors.isEmpty() ? authors.at(0).name() : QString(); 0193 } 0194 0195 QString ThemeModel::email() const 0196 { 0197 const QList<KAboutPerson> authors = m_theme->metadata().authors(); 0198 return authors.isEmpty() ? authors.at(0).emailAddress() : QString(); 0199 } 0200 0201 QString ThemeModel::license() const 0202 { 0203 return m_theme->metadata().license(); 0204 } 0205 0206 QString ThemeModel::website() const 0207 { 0208 return m_theme->metadata().website(); 0209 } 0210 0211 void ThemeModel::setTheme(const QString &theme) 0212 { 0213 if (theme == m_themeName) { 0214 return; 0215 } 0216 0217 m_themeName = theme; 0218 m_theme->setThemeName(theme); 0219 load(); 0220 m_colorEditor->setTheme(theme); 0221 emit themeChanged(); 0222 } 0223 0224 void ThemeModel::editElement(const QString &imagePath) 0225 { 0226 QString file = m_theme->imagePath(imagePath); 0227 if (!file.contains("translucent")) { 0228 file = file.replace("translucent/", ""); 0229 } 0230 0231 QString finalFile; 0232 0233 if (m_theme->currentThemeHasImage(imagePath)) { 0234 finalFile = file; 0235 } else { 0236 finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName + "/" + imagePath + ".svgz"; 0237 const QString dirPath = QFileInfo(finalFile).absoluteDir().absolutePath(); 0238 KIO::mkdir(QUrl::fromLocalFile(dirPath))->exec(); 0239 0240 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file), QUrl::fromLocalFile(finalFile)); 0241 if (!job->exec()) { 0242 qWarning() << "Error copying" << file << "to" << finalFile; 0243 } 0244 } 0245 0246 // QProcess::startDetached("inkscape", QStringList() << finalFile); 0247 KProcess *process = new KProcess(); 0248 // TODO: don't use the script to not depend from bash/linux? 0249 process->setProgram("bash", QStringList() << m_package.filePath("scripts", "openInEditor.sh") << finalFile); 0250 0251 connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &ThemeModel::processFinished); 0252 process->start(); 0253 } 0254 0255 void ThemeModel::processFinished() 0256 { 0257 /*We increment the microversion of the theme: keeps track and will force the cache to be 0258 discarded in order to reload immediately the graphics*/ 0259 const QString metadataPath( 0260 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("plasma/desktoptheme/") % m_themeName % QLatin1String("/metadata.desktop"))); 0261 KConfig c(metadataPath); 0262 KConfigGroup cg(&c, "Desktop Entry"); 0263 0264 QStringList version = cg.readEntry("X-KDE-PluginInfo-Version", "0.0").split('.'); 0265 if (version.length() < 2) { 0266 version << QLatin1String("0"); 0267 } 0268 if (version.length() < 3) { 0269 version << QLatin1String("0"); 0270 } 0271 0272 cg.writeEntry("X-KDE-PluginInfo-Version", 0273 QString(version.first() + QLatin1String(".") + version[1] + QLatin1String(".") + QString::number(version.last().toInt() + 1))); 0274 cg.sync(); 0275 } 0276 0277 void ThemeModel::editThemeMetaData(const QString &name, const QString &author, const QString &email, const QString &license, const QString &website) 0278 { 0279 QString compactName = name.toLower(); 0280 compactName.replace(' ', QString()); 0281 const QString metadataPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1String("/plasma/desktoptheme/") % compactName 0282 % QLatin1String("/metadata.desktop")); 0283 KConfig c(metadataPath); 0284 0285 KConfigGroup cg(&c, "Desktop Entry"); 0286 cg.writeEntry("X-KDE-PluginInfo-Name", name); 0287 cg.writeEntry("X-KDE-PluginInfo-Author", author); 0288 cg.writeEntry("X-KDE-PluginInfo-Email", email); 0289 cg.writeEntry("X-KDE-PluginInfo-Website", website); 0290 cg.writeEntry("X-KDE-PluginInfo-Category", "Plasma Theme"); 0291 cg.writeEntry("X-KDE-PluginInfo-License", license); 0292 cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true"); 0293 cg.writeEntry("X-Plasma-API", "5.0"); 0294 cg.writeEntry("X-KDE-PluginInfo-Version", "0.1"); 0295 cg.sync(); 0296 0297 KConfigGroup cg2(&c, "ContrastEffect"); 0298 cg2.writeEntry("enabled", "true"); 0299 cg2.writeEntry("contrast", "0.2"); 0300 cg2.writeEntry("intensity", "2.0"); 0301 cg2.writeEntry("saturation", "1.7"); 0302 cg2.sync(); 0303 } 0304 0305 void ThemeModel::createNewTheme(const QString &name, const QString &author, const QString &email, const QString &license, const QString &website) 0306 { 0307 editThemeMetaData(name, author, email, license, website); 0308 0309 QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, +"/plasma/desktoptheme/default/colors"); 0310 0311 QString compactName = name.toLower(); 0312 compactName.replace(' ', QString()); 0313 QString finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName + "/colors"; 0314 0315 KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file), QUrl::fromLocalFile(finalFile)); 0316 if (!job->exec()) { 0317 qWarning() << "Error copying" << file << "to" << finalFile; 0318 } 0319 0320 m_themeListModel->reload(); 0321 } 0322 0323 QString ThemeModel::themeFolder() 0324 { 0325 return QStandardPaths::locate(QStandardPaths::GenericDataLocation, +"plasma/desktoptheme/" + m_themeName, QStandardPaths::LocateDirectory); 0326 } 0327 0328 #include "moc_thememodel.cpp"