thememodel.cpp 10.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 *   Copyright 2015 Marco Martin <mart@kde.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as
 *   published by the Free Software Foundation; either version 2, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "thememodel.h"
21
#include "themelistmodel.h"
Marco Martin's avatar
Marco Martin committed
22
#include "coloreditor.h"
23
24
25
26
27
28
29
#include <QDebug>
#include <QByteArray>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QIcon>
#include <QStandardPaths>
30

Marco Martin's avatar
Marco Martin committed
31
32
33
34
35
#include <QXmlSimpleReader>
#include <QXmlDefaultHandler>
#include <QXmlInputSource>

#include <KCompressionDevice>
36
37
#include <KProcess>
#include <KIO/Job>
Marco Martin's avatar
Marco Martin committed
38
#include <KRun>
39
40
41

#include <Plasma/Theme>

Marco Martin's avatar
Marco Martin committed
42
43
44
45
46
class IconsParserHandler : public QXmlDefaultHandler
{
public:
    IconsParserHandler();
    bool startElement(const QString &namespaceURI, const QString &localName,
47
                      const QString &qName, const QXmlAttributes &atts) override;
Marco Martin's avatar
Marco Martin committed
48
    QStringList m_ids;
Marco Martin's avatar
Marco Martin committed
49
    QStringList m_prefixes;
Marco Martin's avatar
Marco Martin committed
50
51
52
53
54
55
56
57
58
};

IconsParserHandler::IconsParserHandler()
    : QXmlDefaultHandler()
{}

bool IconsParserHandler::startElement(const QString &namespaceURI, const QString &localName,
                      const QString &qName, const QXmlAttributes &atts)
{
Marco Martin's avatar
Marco Martin committed
59
60
61
62
    Q_UNUSED(namespaceURI)
    Q_UNUSED(localName)
    Q_UNUSED(qName)

Marco Martin's avatar
Marco Martin committed
63
64
65
66
67
68
69
    const QString id = atts.value("id");
    //qWarning() << "Start Element:"<<id;

    if (!id.isEmpty() && !id.contains(QRegExp("\\d\\d$")) &&
        id != "base" && !id.contains("layer")) {
        m_ids<<id;
    }
Marco Martin's avatar
Marco Martin committed
70
71
72
73
    if (id.endsWith("-center") && !id.contains("hint-")) {
        //remove -center
        m_prefixes << id.mid(0, id.length() - 7);
    }
Marco Martin's avatar
Marco Martin committed
74
75
    return true;
}
76

77
ThemeModel::ThemeModel(const KPackage::Package &package, QObject *parent)
78
    : QAbstractListModel(parent),
Marco Martin's avatar
Marco Martin committed
79
80
      m_theme(new Plasma::Theme),
      m_themeName(QStringLiteral("default")),
81
      m_package(package),
Marco Martin's avatar
Marco Martin committed
82
83
      m_themeListModel(new ThemeListModel(this)),
      m_colorEditor(new ColorEditor(this))      
84
{
85
    m_theme->setUseGlobalSettings(false);
86
87
    m_theme->setThemeName(m_themeName);

88
    m_roleNames.insert(ImagePath, "imagePath");
Marco Martin's avatar
Marco Martin committed
89
90
    m_roleNames.insert(Description, "description");
    m_roleNames.insert(Delegate, "delegate");
91
    m_roleNames.insert(UsesFallback, "usesFallback");
Marco Martin's avatar
Marco Martin committed
92
    m_roleNames.insert(SvgAbsolutePath, "svgAbsolutePath");
93
    m_roleNames.insert(IsWritable, "isWritable");
Marco Martin's avatar
Marco Martin committed
94
    m_roleNames.insert(IconElements, "iconElements");
Marco Martin's avatar
Marco Martin committed
95
    m_roleNames.insert(FrameSvgPrefixes, "frameSvgPrefixes");
96
97
98
99
100
101
102
103

    load();
}

ThemeModel::~ThemeModel()
{
}

104
105
106
107
108
ThemeListModel *ThemeModel::themeList()
{
    return m_themeListModel;
}

Marco Martin's avatar
Marco Martin committed
109
110
111
112
113
ColorEditor *ThemeModel::colorEditor()
{
    return m_colorEditor;
}

114
115
116
117
118
119
120
121
QHash<int, QByteArray> ThemeModel::roleNames() const
{
    return m_roleNames;
}

int ThemeModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
Marco Martin's avatar
Marco Martin committed
122
    return m_jsonDoc.array().size();
123
124
125
126
}

QVariant ThemeModel::data(const QModelIndex &index, int role) const
{
Marco Martin's avatar
Marco Martin committed
127
    if (!index.isValid() || index.row() < 0 || index.row() > m_jsonDoc.array().size()) {
128
129
130
        return QVariant();
    }

Marco Martin's avatar
Marco Martin committed
131
132
    const QVariantMap value = m_jsonDoc.array().at(index.row()).toObject().toVariantMap();

133
134
    switch (role) {
    case ImagePath:
Marco Martin's avatar
Marco Martin committed
135
        return value.value("imagePath");
Marco Martin's avatar
Marco Martin committed
136
137
138
139
    case Description:
        return value.value("description");
    case Delegate:
        return value.value("delegate");
140
141
    case UsesFallback:
        return !m_theme->currentThemeHasImage(value.value("imagePath").toString());
142
143
144
145
146
147
148
    case SvgAbsolutePath: {
        QString path = m_theme->imagePath(value.value("imagePath").toString());
        if (!value.value("imagePath").toString().contains("translucent")) {
             path = path.replace("translucent/", "");
        }
        return path;
    }
149
150
    case IsWritable:
        return QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName);
Marco Martin's avatar
Marco Martin committed
151
152
153
    case IconElements:
    case FrameSvgPrefixes:
    {
Marco Martin's avatar
Marco Martin committed
154
155
156
157
158
159
160
161
162
163
164
165
166
167
        QString path = m_theme->imagePath(value.value("imagePath").toString());
        if (!value.value("imagePath").toString().contains("translucent")) {
             path = path.replace("translucent/", "");
        }
        KCompressionDevice file(path, KCompressionDevice::GZip);
        if (!file.open(QIODevice::ReadOnly)) {
            return QVariant();
        }

        QXmlSimpleReader reader;
        IconsParserHandler handler;
        reader.setContentHandler(&handler);
        QXmlInputSource source(&file);
        reader.parse(&source);
168

Marco Martin's avatar
Marco Martin committed
169
170
171
172
173
        if (role == IconElements) {
            return handler.m_ids;
        } else {
            return handler.m_prefixes;
        }
Marco Martin's avatar
Marco Martin committed
174
    }
175
176
177
178
179
180
181
182
183
184
    }

    return QVariant();
}




void ThemeModel::load()
{
Marco Martin's avatar
Marco Martin committed
185
    beginResetModel();
186
    qDebug() << "Loading theme description file" << m_package.filePath("data", "themeDescription.json");
Marco Martin's avatar
Marco Martin committed
187

188
    QFile jsonFile(m_package.filePath("data", "themeDescription.json"));
Marco Martin's avatar
Marco Martin committed
189
190
191
192
193
194
195
196
    jsonFile.open(QIODevice::ReadOnly);

    QJsonParseError error;
    m_jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &error);

    if (error.error != QJsonParseError::NoError) {
        qWarning() << "Error parsing Json" << error.errorString();
    }
197

Marco Martin's avatar
Marco Martin committed
198
    endResetModel();
199
200
201
202
}

QString ThemeModel::theme() const
{
Marco Martin's avatar
Marco Martin committed
203
    return m_themeName;
204
205
}

Marco Martin's avatar
Marco Martin committed
206
207
208
209
210
211
212
213
214
215
QString ThemeModel::author() const
{
    return m_theme->pluginInfo().author();
}

QString ThemeModel::email() const
{
    return m_theme->pluginInfo().email();
}

Marco Martin's avatar
Marco Martin committed
216
217
218
219
220
QString ThemeModel::license() const
{
    return m_theme->pluginInfo().license();
}

Marco Martin's avatar
Marco Martin committed
221
222
223
224
225
QString ThemeModel::website() const
{
    return m_theme->pluginInfo().website();
}

226
227
void ThemeModel::setTheme(const QString& theme)
{
Marco Martin's avatar
Marco Martin committed
228
    if (theme == m_themeName) {
229
230
231
        return;
    }

Marco Martin's avatar
Marco Martin committed
232
    m_themeName = theme;
233
    m_theme->setThemeName(theme);
234
    load();
Marco Martin's avatar
Marco Martin committed
235
    m_colorEditor->setTheme(theme);
236
237
238
    emit themeChanged();
}

239
240
241
242
243
244
245
246
247
248
249
250
251
void ThemeModel::editElement(const QString& imagePath)
{
    QString file = m_theme->imagePath(imagePath);
    if (!file.contains("translucent")) {
            file = file.replace("translucent/", "");
    }

    QString finalFile;

    if (m_theme->currentThemeHasImage(imagePath)) {
        finalFile = file;
    } else {
        finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + m_themeName + "/" + imagePath + ".svgz";
252
253
254
        const QString dirPath = QFileInfo(finalFile).absoluteDir().absolutePath();
        KIO::mkdir(QUrl::fromLocalFile(dirPath))->exec();

255
256
257
258
259
260
261
262
263
264
265
        KIO::FileCopyJob *job = KIO::file_copy( QUrl::fromLocalFile(file), QUrl::fromLocalFile(finalFile) );
        if (!job->exec()) {
            qWarning() << "Error copying" << file << "to" << finalFile;
        }
    }

    //QProcess::startDetached("inkscape", QStringList() << finalFile);
    KProcess *process = new KProcess();
    //TODO: don't use the script to not depend from bash/linux?
    process->setProgram("bash", QStringList() << m_package.filePath("scripts", "openInEditor.sh") << finalFile);

266
    connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &ThemeModel::processFinished);
267
268
269
270
271
272
273
    process->start();
}

void ThemeModel::processFinished()
{
    /*We increment the microversion of the theme: keeps track and will force the cache to be
      discarded in order to reload immediately the graphics*/
274
    const QString metadataPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("plasma/desktoptheme/") % m_themeName % QLatin1String("/metadata.desktop")));
275
276
277
    KConfig c(metadataPath);
    KConfigGroup cg(&c, "Desktop Entry");

Marco Martin's avatar
Marco Martin committed
278
279
    QStringList version = cg.readEntry("X-KDE-PluginInfo-Version", "0.0").split('.');
    if (version.length() < 2) {
280
        version << QLatin1String("0");
Marco Martin's avatar
Marco Martin committed
281
282
    }
    if (version.length() < 3) {
283
        version << QLatin1String("0");
Marco Martin's avatar
Marco Martin committed
284
    }
285

286
    cg.writeEntry("X-KDE-PluginInfo-Version", QString(version.first() + QLatin1String(".") + version[1] + QLatin1String(".") + QString::number(version.last().toInt() + 1)));
287
288
289
    cg.sync();
}

Marco Martin's avatar
Marco Martin committed
290
void ThemeModel::editThemeMetaData(const QString& name, const QString& author, const QString& email, const QString &license, const QString& website)
Marco Martin's avatar
Marco Martin committed
291
{
292
293
    QString compactName = name.toLower();
    compactName.replace(' ', QString());
294
    const QString metadataPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1String("/plasma/desktoptheme/") % compactName % QLatin1String("/metadata.desktop"));
Marco Martin's avatar
Marco Martin committed
295
296
297
298
299
300
301
302
303
    KConfig c(metadataPath);

    KConfigGroup cg(&c, "Desktop Entry");
    cg.writeEntry("X-KDE-PluginInfo-Name", name);
    cg.writeEntry("X-KDE-PluginInfo-Author", author);
    cg.writeEntry("X-KDE-PluginInfo-Email", email);
    cg.writeEntry("X-KDE-PluginInfo-Website", website);
    cg.writeEntry("X-KDE-PluginInfo-Category", "Plasma Theme");
    cg.writeEntry("X-KDE-PluginInfo-Depends", "plasmashell");
Marco Martin's avatar
Marco Martin committed
304
    cg.writeEntry("X-KDE-PluginInfo-License", license);
Marco Martin's avatar
Marco Martin committed
305
306
307
308
309
310
    cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true");
    cg.writeEntry("X-Plasma-API", "5.0");
    cg.writeEntry("X-KDE-PluginInfo-Version", "0.1");
    cg.sync();

    KConfigGroup cg2(&c, "ContrastEffect");
311
312
313
314
    cg2.writeEntry("enabled", "true");
    cg2.writeEntry("contrast", "0.2");
    cg2.writeEntry("intensity", "2.0");
    cg2.writeEntry("saturation", "1.7");
Marco Martin's avatar
Marco Martin committed
315
    cg2.sync();
Marco Martin's avatar
Marco Martin committed
316
317
}

Marco Martin's avatar
Marco Martin committed
318
void ThemeModel::createNewTheme(const QString& name, const QString& author, const QString& email, const QString &license, const QString& website)
Marco Martin's avatar
Marco Martin committed
319
{
Marco Martin's avatar
Marco Martin committed
320
    editThemeMetaData(name, author, email, license, website);
321

Marco Martin's avatar
Marco Martin committed
322
    QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "/plasma/desktoptheme/default/colors");
323

324
325
326
    QString compactName = name.toLower();
    compactName.replace(' ', QString());
    QString finalFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/desktoptheme/" + compactName + "/colors";
327
328
329
330
331
332
333

    KIO::FileCopyJob *job = KIO::file_copy( QUrl::fromLocalFile(file), QUrl::fromLocalFile(finalFile) );
    if (!job->exec()) {
        qWarning() << "Error copying" << file << "to" << finalFile;
    }


334
    m_themeListModel->reload();
Marco Martin's avatar
Marco Martin committed
335
336
}

337
QString ThemeModel::themeFolder()
Marco Martin's avatar
Marco Martin committed
338
{
339
    return QStandardPaths::locate(QStandardPaths::GenericDataLocation, + "plasma/desktoptheme/" + m_themeName, QStandardPaths::LocateDirectory);
Marco Martin's avatar
Marco Martin committed
340
341
}

342
#include "moc_thememodel.cpp"