grantleethememanager.cpp 13.3 KB
Newer Older
1
/*
Laurent Montel's avatar
Laurent Montel committed
2
  SPDX-FileCopyrightText: 2013-2022 Laurent Montel <montel@kde.org>
3

4
  SPDX-License-Identifier: LGPL-2.1-or-later
5
6
7
8
9
10
*/

#include "grantleethememanager.h"
#include "grantleetheme_p.h"

#include <KActionCollection>
Laurent Montel's avatar
Laurent Montel committed
11
12
13
14
#include <KActionMenu>
#include <KAuthorized>
#include <KConfigGroup>
#include <KDirWatch>
15
#include <KLocalizedString>
16
17
#include <knewstuffcore_version.h>
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
18
#include <KNS3/QtQuickDialogWrapper>
19
20
21
#else
#include <KNSWidgets/Action>
#endif
Laurent Montel's avatar
Laurent Montel committed
22
23
#include <KSharedConfig>
#include <KToggleAction>
24
25
#include <QAction>
#include <QIcon>
26
#include <QPointer>
27

Laurent Montel's avatar
Laurent Montel committed
28
#include <QActionGroup>
29
30
31
32
#include <QDir>
#include <QDirIterator>
#include <QStandardPaths>

33
34
35
namespace GrantleeTheme
{
class ThemeManagerPrivate
36
37
{
public:
38
    ThemeManagerPrivate(const QString &type, const QString &desktopFileName, KActionCollection *ac, const QString &relativePath, ThemeManager *qq)
Laurent Montel's avatar
Laurent Montel committed
39
40
41
42
        : applicationType(type)
        , defaultDesktopFileName(desktopFileName)
        , actionCollection(ac)
        , q(qq)
43
44
45
    {
        watch = new KDirWatch(q);
        initThemesDirectories(relativePath);
46
        if (KAuthorized::authorize(QStringLiteral("ghns"))) {
47
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
48
49
            downloadThemesAction = new QAction(i18n("Download New Themes..."), q);
            downloadThemesAction->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")));
50
51
52
            q->connect(downloadThemesAction, &QAction::triggered, q, [this]() {
                slotDownloadHeaderThemes();
            });
53
54
55
            if (actionCollection) {
                actionCollection->addAction(QStringLiteral("download_header_themes"), downloadThemesAction);
            }
56
57
#else
            downloadThemesAction = new KNSWidgets::Action(i18n("Download new Templates..."), QString(), q);
Laurent Montel's avatar
Laurent Montel committed
58
59
60
            if (actionCollection) {
                actionCollection->addAction(QStringLiteral("download_header_themes"), downloadThemesAction);
            }
61
#endif
62
63
            separatorAction = new QAction(q);
            separatorAction->setSeparator(true);
64
65
        }

Laurent Montel's avatar
Laurent Montel committed
66
67
68
        q->connect(watch, &KDirWatch::dirty, q, [this]() {
            directoryChanged();
        });
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
        updateThemesPath(true);

        // Migrate the old configuration format that only support mail and addressbook
        // theming to the new generic format
        KSharedConfig::Ptr config = KSharedConfig::openConfig();
        if (config->hasGroup(QStringLiteral("GrantleeTheme"))) {
            const KConfigGroup group = config->group(QStringLiteral("GrantleeTheme"));
            const QString mailTheme = group.readEntry(QStringLiteral("grantleeMailThemeName"));
            const QString addressbookTheme = group.readEntry(QStringLiteral("grantleeAddressBookThemeName"));

            config->group(QStringLiteral("mail")).writeEntry(QStringLiteral("themeName"), mailTheme);
            config->group(QStringLiteral("addressbook")).writeEntry(QStringLiteral("themeName"), addressbookTheme);

            config->deleteGroup(QStringLiteral("GrantleeTheme"));
        }
    }

86
    ~ThemeManagerPrivate()
87
    {
88
        removeActions();
89
90
91
92
93
        themes.clear();
    }

    void slotDownloadHeaderThemes()
    {
94
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
95
        KNS3::QtQuickDialogWrapper(downloadConfigFileName).exec();
96
#endif
97
98
99
100
101
102
103
104
105
106
107
108
109
    }

    void directoryChanged()
    {
        updateThemesPath();
        updateActionList();
        Q_EMIT q->updateThemes();
    }

    void updateThemesPath(bool init = false)
    {
        if (!init) {
            if (!themesDirectories.isEmpty()) {
110
                for (const QString &directory : std::as_const(themesDirectories)) {
111
112
113
114
115
116
117
118
119
120
                    watch->removeDir(directory);
                }
            } else {
                return;
            }
        }

        // clear all previous theme information
        themes.clear();

121
        for (const QString &directory : std::as_const(themesDirectories)) {
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
            QDirIterator dirIt(directory, QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot);
            QStringList alreadyLoadedThemeName;
            while (dirIt.hasNext()) {
                dirIt.next();
                const QString dirName = dirIt.fileName();
                GrantleeTheme::Theme theme = q->loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
                if (theme.isValid()) {
                    QString themeName = theme.name();
                    if (alreadyLoadedThemeName.contains(themeName)) {
                        int i = 2;
                        const QString originalName(theme.name());
                        while (alreadyLoadedThemeName.contains(themeName)) {
                            themeName = originalName + QStringLiteral(" (%1)").arg(i);
                            ++i;
                        }
                        theme.d->name = themeName;
                    }
                    alreadyLoadedThemeName << themeName;
140
141
142
143
144
145
                    auto it = themes.find(dirName);
                    if (it != themes.end()) {
                        (*it).addThemePath(dirIt.filePath());
                    } else {
                        themes.insert(dirName, theme);
                    }
146
147
148
149
150
151
152
153
154
                }
            }
            watch->addDir(directory);
        }

        Q_EMIT q->themesChanged();
        watch->startScan();
    }

155
    void removeActions()
156
157
158
159
    {
        if (!actionGroup || !menu) {
            return;
        }
160
        for (KToggleAction *action : std::as_const(themesActionList)) {
161
            actionGroup->removeAction(action);
Laurent Montel's avatar
Laurent Montel committed
162
            menu->removeAction(action);
163
164
165
166
            if (actionCollection) {
                actionCollection->removeAction(action);
            }
        }
167
168
169
170
        if (separatorAction) {
            menu->removeAction(separatorAction);
            menu->removeAction(downloadThemesAction);
        }
171
        themesActionList.clear();
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    }

    void updateActionList()
    {
        if (!actionGroup || !menu) {
            return;
        }
        QString themeActivated;

        QAction *selectedAction = actionGroup->checkedAction();
        if (selectedAction) {
            themeActivated = selectedAction->data().toString();
        }

        removeActions();
187
188
189
190
191
192

        bool themeActivatedFound = false;
        QMapIterator<QString, GrantleeTheme::Theme> i(themes);
        while (i.hasNext()) {
            i.next();
            GrantleeTheme::Theme theme = i.value();
Laurent Montel's avatar
Laurent Montel committed
193
            auto act = new KToggleAction(theme.name(), q);
194
195
196
197
198
199
200
201
202
            act->setToolTip(theme.description());
            act->setData(theme.dirName());
            if (theme.dirName() == themeActivated) {
                act->setChecked(true);
                themeActivatedFound = true;
            }
            themesActionList.append(act);
            actionGroup->addAction(act);
            menu->addAction(act);
Laurent Montel's avatar
Laurent Montel committed
203
204
205
            q->connect(act, &KToggleAction::triggered, q, [this]() {
                slotThemeSelected();
            });
206
207
208
        }
        if (!themeActivatedFound) {
            if (!themesActionList.isEmpty() && !themeActivated.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
209
                // Activate first item if we removed theme.
210
211
212
213
214
                KToggleAction *act = themesActionList.at(0);
                act->setChecked(true);
                selectTheme(act);
            }
        }
215
216
217
218
        if (separatorAction) {
            menu->addAction(separatorAction);
            menu->addAction(downloadThemesAction);
        }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    }

    void selectTheme(KToggleAction *act)
    {
        if (act) {
            KSharedConfig::Ptr config = KSharedConfig::openConfig();
            KConfigGroup group = config->group(applicationType);
            group.writeEntry(QStringLiteral("themeName"), act->data().toString());
            config->sync();
        }
    }

    void slotThemeSelected()
    {
        if (q->sender()) {
234
            auto act = qobject_cast<KToggleAction *>(q->sender());
235
236
237
238
239
240
241
242
243
244
245
246
            selectTheme(act);
            Q_EMIT q->grantleeThemeSelected();
        }
    }

    KToggleAction *actionForTheme()
    {
        const KSharedConfig::Ptr config = KSharedConfig::openConfig();
        const KConfigGroup group = config->group(applicationType);
        const QString themeName = group.readEntry(QStringLiteral("themeName"), QStringLiteral("default"));

        if (themeName.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
247
            return nullptr;
248
        }
249
        for (KToggleAction *act : std::as_const(themesActionList)) {
250
251
252
253
            if (act->data().toString() == themeName) {
                return static_cast<KToggleAction *>(act);
            }
        }
Laurent Montel's avatar
Laurent Montel committed
254
        return nullptr;
255
256
257
258
259
260
    }

    void initThemesDirectories(const QString &themesRelativePath)
    {
        if (!themesRelativePath.isEmpty()) {
            themesDirectories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, themesRelativePath, QStandardPaths::LocateDirectory);
261
262
            const QString localDirectory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + themesRelativePath;
            themesDirectories.append(localDirectory);
263
264
        }
    }
Laurent Montel's avatar
Laurent Montel committed
265

266
267
    QString applicationType;
    QString defaultDesktopFileName;
268
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
269
    QString downloadConfigFileName;
270
#endif
271
272
    QStringList themesDirectories;
    QMap<QString, GrantleeTheme::Theme> themes;
Laurent Montel's avatar
Laurent Montel committed
273
    QVector<KToggleAction *> themesActionList;
Laurent Montel's avatar
Laurent Montel committed
274
275
276
    KDirWatch *watch = nullptr;
    QActionGroup *actionGroup = nullptr;
    KActionMenu *menu = nullptr;
Laurent Montel's avatar
Laurent Montel committed
277
    KActionCollection *const actionCollection;
278
    QAction *separatorAction = nullptr;
279
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
280
    QAction *downloadThemesAction = nullptr;
281
282
283
#else
    KNSWidgets::Action *downloadThemesAction = nullptr;
#endif
Laurent Montel's avatar
Laurent Montel committed
284
    ThemeManager *const q;
285
};
286
287
288
}

using namespace GrantleeTheme;
289

Laurent Montel's avatar
Laurent Montel committed
290
291
292
293
294
ThemeManager::ThemeManager(const QString &applicationType,
                           const QString &defaultDesktopFileName,
                           KActionCollection *actionCollection,
                           const QString &path,
                           QObject *parent)
295
    : QObject(parent)
296
    , d(new ThemeManagerPrivate(applicationType, defaultDesktopFileName, actionCollection, path, this))
297
298
299
{
}

300
ThemeManager::~ThemeManager() = default;
301
302
303
304
305
306
307
308
309

QMap<QString, GrantleeTheme::Theme> ThemeManager::themes() const
{
    return d->themes;
}

void ThemeManager::setActionGroup(QActionGroup *actionGroup)
{
    if (d->actionGroup != actionGroup) {
310
        d->removeActions();
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
        d->actionGroup = actionGroup;
        d->updateActionList();
    }
}

KToggleAction *ThemeManager::actionForTheme()
{
    return d->actionForTheme();
}

void ThemeManager::setThemeMenu(KActionMenu *menu)
{
    if (d->menu != menu) {
        d->menu = menu;
        d->updateActionList();
    }
}

QStringList ThemeManager::displayExtraVariables(const QString &themename) const
{
    QMapIterator<QString, GrantleeTheme::Theme> i(d->themes);
    while (i.hasNext()) {
        i.next();
        if (i.value().dirName() == themename) {
            return i.value().displayExtraVariables();
        }
    }
Laurent Montel's avatar
Laurent Montel committed
338
    return {};
339
340
341
342
343
344
345
346
347
}

GrantleeTheme::Theme ThemeManager::theme(const QString &themeName)
{
    return d->themes.value(themeName);
}

void ThemeManager::setDownloadNewStuffConfigFile(const QString &configFileName)
{
348
#if KNEWSTUFFCORE_VERSION < QT_VERSION_CHECK(5, 94, 0)
349
    d->downloadConfigFileName = configFileName;
350
351
352
#else
    d->downloadThemesAction->setConfigFile(configFileName);
#endif
353
354
}

Laurent Montel's avatar
Laurent Montel committed
355
QString ThemeManager::pathFromThemes(const QString &themesRelativePath, const QString &themeName, const QString &defaultDesktopFileName)
356
357
358
359
360
{
    QStringList themesDirectories;
    if (!themesRelativePath.isEmpty()) {
        themesDirectories = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, themesRelativePath, QStandardPaths::LocateDirectory);
        if (themesDirectories.count() < 2) {
Laurent Montel's avatar
Laurent Montel committed
361
            // Make sure to add local directory
362
363
364
365
366
            const QString localDirectory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + themesRelativePath;
            if (!themesDirectories.contains(localDirectory)) {
                themesDirectories.append(localDirectory);
            }
        }
367
        for (const QString &directory : std::as_const(themesDirectories)) {
368
369
370
371
372
373
374
375
376
377
378
379
380
            QDirIterator dirIt(directory, QStringList(), QDir::AllDirs | QDir::NoDotAndDotDot);
            while (dirIt.hasNext()) {
                dirIt.next();
                const QString dirName = dirIt.fileName();
                GrantleeTheme::Theme theme = loadTheme(dirIt.filePath(), dirName, defaultDesktopFileName);
                if (theme.isValid()) {
                    if (dirName == themeName) {
                        return theme.absolutePath();
                    }
                }
            }
        }
    }
Laurent Montel's avatar
Laurent Montel committed
381
    return {};
382
383
}

Laurent Montel's avatar
Laurent Montel committed
384
GrantleeTheme::Theme ThemeManager::loadTheme(const QString &themePath, const QString &dirName, const QString &defaultDesktopFileName)
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
{
    const GrantleeTheme::Theme theme(themePath, dirName, defaultDesktopFileName);
    return theme;
}

QString ThemeManager::configuredThemeName() const
{
    return configuredThemeName(d->applicationType);
}

QString ThemeManager::configuredThemeName(const QString &themeType)
{
    const KSharedConfig::Ptr config = KSharedConfig::openConfig();
    const KConfigGroup grp = config->group(themeType);
    return grp.readEntry(QStringLiteral("themeName"));
}

#include "moc_grantleethememanager.cpp"