widgetexplorer.cpp 14.7 KB
Newer Older
1
/*
2
3
4
5
6
7
    SPDX-FileCopyrightText: 2007 Ivan Cukic <ivan.cukic+kde@gmail.com>
    SPDX-FileCopyrightText: 2009 Ana Cecília Martins <anaceciliamb@gmail.com>
    SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/
8
9
10

#include "widgetexplorer.h"

Marco Martin's avatar
Marco Martin committed
11
#include <QQmlContext>
Alexander Lohnau's avatar
Alexander Lohnau committed
12
#include <QQmlEngine>
13
14
#include <QQmlExpression>
#include <QQmlProperty>
Marco Martin's avatar
Marco Martin committed
15

16
#include <KAuthorized>
17
#include <KLocalizedString>
18
#include <KNewStuff3/KNS3/QtQuickDialogWrapper>
19
#include <KWindowSystem>
20

21
22
#include <Plasma/Applet>
#include <Plasma/Containment>
Alexander Lohnau's avatar
Alexander Lohnau committed
23
#include <Plasma/Corona>
24
#include <Plasma/PluginLoader>
25
#include <QStandardPaths>
26

27
28
#include <KActivities/Consumer>

29
30
#include <KPackage/Package>
#include <KPackage/PackageLoader>
Alexander Lohnau's avatar
Alexander Lohnau committed
31
#include <KPackage/PackageStructure>
32

Alexander Lohnau's avatar
Alexander Lohnau committed
33
#include "config-workspace.h"
34
#include "kcategorizeditemsviewmodels_p.h"
35
#include "openwidgetassistant_p.h"
36

37
using namespace KActivities;
38
using namespace KCategorizedItemsViewModels;
39
using namespace Plasma;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

WidgetAction::WidgetAction(QObject *parent)
    : QAction(parent)
{
}

WidgetAction::WidgetAction(const QIcon &icon, const QString &text, QObject *parent)
    : QAction(icon, text, parent)
{
}

class WidgetExplorerPrivate
{
public:
    WidgetExplorerPrivate(WidgetExplorer *w)
Alexander Lohnau's avatar
Alexander Lohnau committed
55
56
57
58
59
        : q(w)
        , containment(nullptr)
        , itemModel(w)
        , filterModel(w)
        , activitiesConsumer(new KActivities::Consumer())
60
    {
61
62
63
        QObject::connect(activitiesConsumer.data(), &Consumer::currentActivityChanged, q, [this] {
            initRunningApplets();
        });
64
65
66
67
    }

    void initFilters();
    void initRunningApplets();
68
69
    void screenAdded(int screen);
    void screenRemoved(int screen);
70
71
    void containmentDestroyed();

72
    void addContainment(Containment *containment);
73
    void removeContainment(Containment *containment);
74

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    /**
     * Tracks a new running applet
     */
    void appletAdded(Plasma::Applet *applet);

    /**
     * A running applet is no more
     */
    void appletRemoved(Plasma::Applet *applet);

    WidgetExplorer *q;
    QString application;
    Plasma::Containment *containment;

    QHash<QString, int> runningApplets; // applet name => count
Alexander Lohnau's avatar
Alexander Lohnau committed
90
91
    // extra hash so we can look up the names of deleted applets
    QHash<Plasma::Applet *, QString> appletNames;
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
92
    QPointer<Plasma::OpenWidgetAssistant> openAssistant;
93
    KPackage::Package *package;
94
95
96

    PlasmaAppletItemModel itemModel;
    KCategorizedItemsViewModels::DefaultFilterModel filterModel;
Eike Hein's avatar
Eike Hein committed
97
    bool showSpecialFilters = true;
98
    DefaultItemFilterProxyModel filterItemModel;
99
    static QPointer<KNS3::QtQuickDialogWrapper> newStuffDialog;
100
101

    QScopedPointer<KActivities::Consumer> activitiesConsumer;
102
103
};

104
105
QPointer<KNS3::QtQuickDialogWrapper> WidgetExplorerPrivate::newStuffDialog;

106
107
void WidgetExplorerPrivate::initFilters()
{
Eike Hein's avatar
Eike Hein committed
108
    filterModel.clear();
109

Alexander Lohnau's avatar
Alexander Lohnau committed
110
    filterModel.addFilter(i18n("All Widgets"), KCategorizedItemsViewModels::Filter(), QIcon::fromTheme(QStringLiteral("plasma")));
111

112
    if (showSpecialFilters) {
Eike Hein's avatar
Eike Hein committed
113
114
        // Filters: Special
        filterModel.addFilter(i18n("Running"),
Alexander Lohnau's avatar
Alexander Lohnau committed
115
116
                              KCategorizedItemsViewModels::Filter(QStringLiteral("running"), true),
                              QIcon::fromTheme(QStringLiteral("dialog-ok")));
117

Eike Hein's avatar
Eike Hein committed
118
        filterModel.addFilter(i18n("Uninstallable"),
Alexander Lohnau's avatar
Alexander Lohnau committed
119
120
                              KCategorizedItemsViewModels::Filter(QStringLiteral("local"), true),
                              QIcon::fromTheme(QStringLiteral("edit-delete")));
Eike Hein's avatar
Eike Hein committed
121
122
123

        filterModel.addSeparator(i18n("Categories:"));
    }
124
125

    typedef QPair<QString, QString> catPair;
Alexander Lohnau's avatar
Alexander Lohnau committed
126
    QMap<QString, catPair> categories;
127
    QSet<QString> existingCategories = itemModel.categories();
128
    QStringList cats;
129
    const QList<KPluginMetaData> list = PluginLoader::self()->listAppletMetaData(QString());
130

Alexander Lohnau's avatar
Alexander Lohnau committed
131
    for (auto &plugin : list) {
132
        if (!plugin.isValid()) {
Marco Martin's avatar
Marco Martin committed
133
134
            continue;
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
135
        if (plugin.rawData().value("NoDisplay").toBool() || plugin.category() == QLatin1String("Containments") || plugin.category().isEmpty()) {
136
137
138
            // we don't want to show the hidden category
            continue;
        }
139
        const QString c = plugin.category();
140
141
142
143
        if (-1 == cats.indexOf(c)) {
            cats << c;
        }
    }
144
    for (const QString &category : qAsConst(cats)) {
145
146
        const QString lowerCaseCat = category.toLower();
        if (existingCategories.contains(lowerCaseCat)) {
147
            const QString trans = i18nd("libplasma5", category.toLocal8Bit());
148
149
150
151
            categories.insert(trans.toLower(), qMakePair(trans, lowerCaseCat));
        }
    }

152
    for (const catPair &category : qAsConst(categories)) {
Alexander Lohnau's avatar
Alexander Lohnau committed
153
        filterModel.addFilter(category.first, KCategorizedItemsViewModels::Filter(QStringLiteral("category"), category.second));
154
155
156
    }
}

Eike Hein's avatar
Eike Hein committed
157
158
159
160
161
162
void WidgetExplorer::classBegin()
{
}

void WidgetExplorer::componentComplete()
{
163
    d->itemModel.setStartupCompleted(true);
Eike Hein's avatar
Eike Hein committed
164
165
166
    setApplication();
    d->initRunningApplets();
}
167
168
169
170
171
172
173
174
175
176
177

QObject *WidgetExplorer::widgetsModel() const
{
    return &d->filterItemModel;
}

QObject *WidgetExplorer::filterModel() const
{
    return &d->filterModel;
}

Eike Hein's avatar
Eike Hein committed
178
179
180
181
182
183
184
185
186
187
bool WidgetExplorer::showSpecialFilters() const
{
    return d->showSpecialFilters;
}

void WidgetExplorer::setShowSpecialFilters(bool show)
{
    if (d->showSpecialFilters != show) {
        d->showSpecialFilters = show;
        d->initFilters();
Laurent Montel's avatar
Laurent Montel committed
188
        Q_EMIT showSpecialFiltersChanged();
Eike Hein's avatar
Eike Hein committed
189
190
191
    }
}

Alexander Lohnau's avatar
Alexander Lohnau committed
192
QList<QObject *> WidgetExplorer::widgetsMenuActions()
193
{
Alexander Lohnau's avatar
Alexander Lohnau committed
194
    QList<QObject *> actionList;
195

196
197
    WidgetAction *action = nullptr;

198
    if (KAuthorized::authorize(KAuthorized::GHNS)) {
Alexander Lohnau's avatar
Alexander Lohnau committed
199
        action = new WidgetAction(QIcon::fromTheme(QStringLiteral("internet-services")), i18n("Download New Plasma Widgets"), this);
200
201
202
        connect(action, &QAction::triggered, this, &WidgetExplorer::downloadWidgets);
        actionList << action;
    }
203
204
205
206
207

    action = new WidgetAction(this);
    action->setSeparator(true);
    actionList << action;

208
    action = new WidgetAction(QIcon::fromTheme(QStringLiteral("package-x-generic")), i18n("Install Widget From Local File…"), this);
209
    QObject::connect(action, &QAction::triggered, this, &WidgetExplorer::openWidgetFile);
210
211
212
213
214
215
216
    actionList << action;

    return actionList;
}

void WidgetExplorerPrivate::initRunningApplets()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
217
    // get applets from corona, count them, send results to model
218
219
220
221
222
223
    if (!containment) {
        return;
    }

    Plasma::Corona *c = containment->corona();

Alexander Lohnau's avatar
Alexander Lohnau committed
224
225
    // we've tried our best to get a corona
    // we don't want just one containment, we want them all
226
    if (!c) {
227
        qWarning() << "WidgetExplorer failed to find corona";
228
229
230
231
        return;
    }
    appletNames.clear();
    runningApplets.clear();
232

Alexander Lohnau's avatar
Alexander Lohnau committed
233
234
235
236
237
238
    QObject::connect(c, &Plasma::Corona::screenAdded, q, [this](int screen) {
        screenAdded(screen);
    });
    QObject::connect(c, &Plasma::Corona::screenRemoved, q, [this](int screen) {
        screenRemoved(screen);
    });
239

Alexander Lohnau's avatar
Alexander Lohnau committed
240
    const QList<Containment *> containments = c->containments();
241
    for (Containment *containment : containments) {
Alexander Lohnau's avatar
Alexander Lohnau committed
242
        if (containment->containmentType() == Plasma::Types::DesktopContainment && containment->activity() != activitiesConsumer->currentActivity()) {
243
244
            continue;
        }
245
246
247
        if (containment->screen() != -1) {
            addContainment(containment);
        }
248
249
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
250
    // qDebug() << runningApplets;
251
252
253
    itemModel.setRunningApplets(runningApplets);
}

254
255
void WidgetExplorerPrivate::screenAdded(int screen)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
256
    const QList<Containment *> containments = containment->corona()->containments();
257
258
259
260
261
262
263
264
265
266
    for (auto c : containments) {
        if (c->screen() == screen) {
            addContainment(c);
        }
    }
    itemModel.setRunningApplets(runningApplets);
}

void WidgetExplorerPrivate::screenRemoved(int screen)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
267
268
    const QList<Containment *> containments = containment->corona()->containments();
    for (auto c : containments) {
269
270
271
272
273
274
275
        if (c->lastScreen() == screen) {
            removeContainment(c);
        }
    }
    itemModel.setRunningApplets(runningApplets);
}

276
277
void WidgetExplorerPrivate::addContainment(Containment *containment)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
278
279
    QObject::connect(containment, SIGNAL(appletAdded(Plasma::Applet *)), q, SLOT(appletAdded(Plasma::Applet *)));
    QObject::connect(containment, SIGNAL(appletRemoved(Plasma::Applet *)), q, SLOT(appletRemoved(Plasma::Applet *)));
280
281

    foreach (Applet *applet, containment->applets()) {
282
        if (applet->pluginMetaData().isValid()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
283
            Containment *childContainment = applet->property("containment").value<Containment *>();
284
285
286
            if (childContainment) {
                addContainment(childContainment);
            }
287
            runningApplets[applet->pluginMetaData().pluginId()]++;
288
        } else {
289
            qDebug() << "Invalid plugin metadata. :(";
290
291
292
293
        }
    }
}

294
295
296
void WidgetExplorerPrivate::removeContainment(Plasma::Containment *containment)
{
    containment->disconnect(q);
Alexander Lohnau's avatar
Alexander Lohnau committed
297
    const QList<Applet *> applets = containment->applets();
298
299
    for (auto applet : applets) {
        if (applet->pluginMetaData().isValid()) {
Alexander Lohnau's avatar
Alexander Lohnau committed
300
            Containment *childContainment = applet->property("containment").value<Containment *>();
301
302
303
304
305
306
307
308
            if (childContainment) {
                removeContainment(childContainment);
            }
            runningApplets[applet->pluginMetaData().pluginId()]--;
        }
    }
}

309
310
void WidgetExplorerPrivate::containmentDestroyed()
{
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
311
    containment = nullptr;
312
313
314
315
}

void WidgetExplorerPrivate::appletAdded(Plasma::Applet *applet)
{
316
    if (!applet->pluginMetaData().isValid()) {
317
318
        return;
    }
319
    QString name = applet->pluginMetaData().pluginId();
320
321
322
323
324
325
326
327

    runningApplets[name]++;
    appletNames.insert(applet, name);
    itemModel.setRunningApplets(name, runningApplets[name]);
}

void WidgetExplorerPrivate::appletRemoved(Plasma::Applet *applet)
{
328
    QString name = appletNames.take(applet);
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343

    int count = 0;
    if (runningApplets.contains(name)) {
        count = runningApplets[name] - 1;

        if (count < 1) {
            runningApplets.remove(name);
        } else {
            runningApplets[name] = count;
        }
    }

    itemModel.setRunningApplets(name, count);
}

Alexander Lohnau's avatar
Alexander Lohnau committed
344
// WidgetExplorer
345

346
WidgetExplorer::WidgetExplorer(QObject *parent)
Alexander Lohnau's avatar
Alexander Lohnau committed
347
348
    : QObject(parent)
    , d(new WidgetExplorerPrivate(this))
349
{
Marco Martin's avatar
Marco Martin committed
350
351
352
353
    d->filterItemModel.setSortCaseSensitivity(Qt::CaseInsensitive);
    d->filterItemModel.setDynamicSortFilter(true);
    d->filterItemModel.setSourceModel(&d->itemModel);
    d->filterItemModel.sort(0);
354
355
356
357
}

WidgetExplorer::~WidgetExplorer()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
358
    delete d;
359
360
}

Marco Martin's avatar
Marco Martin committed
361
void WidgetExplorer::setApplication(const QString &app)
362
{
Marco Martin's avatar
Marco Martin committed
363
    if (d->application == app && !app.isEmpty()) {
Marco Martin's avatar
Marco Martin committed
364
365
366
        return;
    }

367
368
369
370
371
    d->application = app;
    d->itemModel.setApplication(app);
    d->initFilters();

    d->itemModel.setRunningApplets(d->runningApplets);
Laurent Montel's avatar
Laurent Montel committed
372
    Q_EMIT applicationChanged();
373
374
375
376
377
378
379
}

QString WidgetExplorer::application()
{
    return d->application;
}

380
381
382
383
384
385
386
387
388
389
390
391
QStringList WidgetExplorer::provides() const
{
    return d->itemModel.provides();
}

void WidgetExplorer::setProvides(const QStringList &provides)
{
    if (d->itemModel.provides() == provides) {
        return;
    }

    d->itemModel.setProvides(provides);
Laurent Montel's avatar
Laurent Montel committed
392
    Q_EMIT providesChanged();
393
394
}

395
396
397
398
399
400
401
402
403
404
void WidgetExplorer::setContainment(Plasma::Containment *containment)
{
    if (d->containment != containment) {
        if (d->containment) {
            d->containment->disconnect(this);
        }

        d->containment = containment;

        if (d->containment) {
Alexander Lohnau's avatar
Alexander Lohnau committed
405
            connect(d->containment, SIGNAL(destroyed(QObject *)), this, SLOT(containmentDestroyed()));
406
            connect(d->containment, &Applet::immutabilityChanged, this, &WidgetExplorer::immutabilityChanged);
407
408
409
        }

        d->initRunningApplets();
Laurent Montel's avatar
Laurent Montel committed
410
        Q_EMIT containmentChanged();
411
412
413
414
415
416
417
418
419
420
421
422
423
424
    }
}

Containment *WidgetExplorer::containment() const
{
    return d->containment;
}

Plasma::Corona *WidgetExplorer::corona() const
{
    if (d->containment) {
        return d->containment->corona();
    }

Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
425
    return nullptr;
426
427
428
429
}

void WidgetExplorer::addApplet(const QString &pluginName)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
430
    const QString p = PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/" + pluginName;
Sebastian Kügler's avatar
Sebastian Kügler committed
431
432
433
434
435
    qWarning() << "-------->  load applet: " << pluginName << " relpath: " << p;

    QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory);

    qDebug() << " .. pathes: " << dirs;
436
437
438
439
    if (!dirs.count()) {
        qWarning() << "Failed to find plasmoid path for " << pluginName;
        return;
    }
Sebastian Kügler's avatar
Sebastian Kügler committed
440

441
442
    if (d->containment) {
        d->containment->createApplet(dirs.first());
Sebastian Kügler's avatar
Sebastian Kügler committed
443
    }
444
445
}

Marco Martin's avatar
Marco Martin committed
446
void WidgetExplorer::immutabilityChanged(Plasma::Types::ImmutabilityType type)
447
{
448
    if (type != Plasma::Types::Mutable) {
Laurent Montel's avatar
Laurent Montel committed
449
        Q_EMIT shouldClose();
450
451
452
    }
}

453
void WidgetExplorer::downloadWidgets()
454
{
455
456
457
    if (d->newStuffDialog.isNull()) {
        d->newStuffDialog = new KNS3::QtQuickDialogWrapper(QStringLiteral("plasmoids.knsrc"));
        connect(d->newStuffDialog, &KNS3::QtQuickDialogWrapper::closed, d->newStuffDialog, &QObject::deleteLater);
458

459
        d->newStuffDialog->open();
460
461
    }

Laurent Montel's avatar
Laurent Montel committed
462
    Q_EMIT shouldClose();
463
464
465
466
467
468
}

void WidgetExplorer::openWidgetFile()
{
    Plasma::OpenWidgetAssistant *assistant = d->openAssistant.data();
    if (!assistant) {
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
469
        assistant = new Plasma::OpenWidgetAssistant(nullptr);
470
471
472
473
474
475
476
477
        d->openAssistant = assistant;
    }

    KWindowSystem::setOnDesktop(assistant->winId(), KWindowSystem::currentDesktop());
    assistant->setAttribute(Qt::WA_DeleteOnClose, true);
    assistant->show();
    assistant->raise();
    assistant->setFocus();
478

Laurent Montel's avatar
Laurent Montel committed
479
    Q_EMIT shouldClose();
480
481
482
483
}

void WidgetExplorer::uninstall(const QString &pluginName)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
484
485
    static const QString packageRoot =
        QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/";
Marco Martin's avatar
Marco Martin committed
486

487
488
489
    KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Applet"));

    KPackage::Package pkg(structure);
Marco Martin's avatar
Marco Martin committed
490
    pkg.uninstall(pluginName, packageRoot);
491

Alexander Lohnau's avatar
Alexander Lohnau committed
492
    // FIXME: moreefficient way rather a linear scan?
493
494
495
496
497
498
499
    for (int i = 0; i < d->itemModel.rowCount(); ++i) {
        QStandardItem *item = d->itemModel.item(i);
        if (item->data(PlasmaAppletItemModel::PluginNameRole).toString() == pluginName) {
            d->itemModel.takeRow(i);
            break;
        }
    }
500
501
502
503
504

    // now remove all instances of that applet
    if (corona()) {
        const auto &containments = corona()->containments();

Alexander Lohnau's avatar
Alexander Lohnau committed
505
        for (Containment *c : containments) {
506
507
            const auto &applets = c->applets();

508
            for (Applet *applet : applets) {
509
                const auto &appletInfo = applet->pluginMetaData();
510

511
                if (appletInfo.isValid() && appletInfo.pluginId() == pluginName) {
512
513
514
515
516
                    applet->destroy();
                }
            }
        }
    }
517
518
}

Sebastian Kügler's avatar
Sebastian Kügler committed
519
#include "moc_widgetexplorer.cpp"