File indexing completed on 2023-09-24 04:14:55

0001 /*
0002     SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2008 Ménard Alexis <darktears31@gmail.com>
0004     SPDX-FileCopyrightText: 2009 Chani Armitage <chani@kde.org>
0005     SPDX-FileCopyrightText: 2012 Marco Martin <notmart@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 #include "containment.h"
0011 #include "private/containment_p.h"
0012 
0013 #include "config-plasma.h"
0014 
0015 #include <QClipboard>
0016 #include <QContextMenuEvent>
0017 #include <QDebug>
0018 #include <QFile>
0019 #include <QMimeData>
0020 #include <QMimeDatabase>
0021 #include <QPainter>
0022 #include <QTemporaryFile>
0023 
0024 #include <KAuthorized>
0025 #include <KConfigLoader>
0026 #include <KConfigSkeleton>
0027 #include <KLocalizedString>
0028 
0029 #include "containmentactions.h"
0030 #include "corona.h"
0031 #include "debug_p.h"
0032 #include "pluginloader.h"
0033 
0034 #include "private/applet_p.h"
0035 
0036 #include "plasma/plasma.h"
0037 
0038 namespace Plasma
0039 {
0040 Containment::Containment(QObject *parentObject, const KPluginMetaData &data, const QVariantList &args)
0041     : Applet(parentObject, data, args)
0042     , d(new ContainmentPrivate(this))
0043 {
0044     // WARNING: do not access config() OR globalConfig() in this method!
0045     //          that requires a scene, which is not available at this point
0046     setHasConfigurationInterface(true);
0047 }
0048 
0049 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 86)
0050 Containment::Containment(QObject *parent, const QString &serviceId, uint containmentId)
0051     : Applet(parent, serviceId, containmentId)
0052     , d(new ContainmentPrivate(this))
0053 {
0054     // WARNING: do not access config() OR globalConfig() in this method!
0055     //          that requires a scene, which is not available at this point
0056     setContainmentType(Types::CustomContainment);
0057     setHasConfigurationInterface(true);
0058 }
0059 
0060 Containment::Containment(QObject *parent, const QVariantList &args)
0061     : Applet(parent, args)
0062     , d(new ContainmentPrivate(this))
0063 {
0064     // WARNING: do not access config() OR globalConfig() in this method!
0065     //          that requires a scene, which is not available at this point
0066     setHasConfigurationInterface(true);
0067 }
0068 
0069 Containment::Containment(const KPluginMetaData &md, uint appletId)
0070     : Applet(md, nullptr, appletId)
0071     , d(new ContainmentPrivate(this))
0072 {
0073     // WARNING: do not access config() OR globalConfig() in this method!
0074     //          that requires a scene, which is not available at this point
0075     setHasConfigurationInterface(true);
0076 }
0077 #endif
0078 
0079 Containment::~Containment()
0080 {
0081     qDeleteAll(d->localActionPlugins);
0082     delete d;
0083 }
0084 
0085 void Containment::init()
0086 {
0087     Applet::init();
0088     static_cast<Applet *>(this)->d->setupScripting();
0089 
0090     if (d->type == Types::NoContainmentType) {
0091         // setContainmentType(Plasma::Types::DesktopContainment);
0092         // Try to determine the containment type. It must be done as soon as possible
0093         QString type = pluginMetaData().value(QStringLiteral("X-Plasma-ContainmentType"));
0094 
0095         if (type == QLatin1String("Panel")) {
0096             setContainmentType(Plasma::Types::PanelContainment);
0097         } else if (type == QLatin1String("Custom")) {
0098             setContainmentType(Plasma::Types::CustomContainment);
0099         } else if (type == QLatin1String("CustomPanel")) {
0100             setContainmentType(Plasma::Types::CustomPanelContainment);
0101             // default to desktop
0102         } else {
0103             setContainmentType(Plasma::Types::DesktopContainment);
0104         }
0105     }
0106 
0107     // connect actions
0108     ContainmentPrivate::addDefaultActions(actions(), this);
0109     bool unlocked = immutability() == Types::Mutable;
0110 
0111     // fix the text of the actions that need title()
0112     // btw, do we really want to use title() when it's a desktopcontainment?
0113     QAction *closeApplet = actions()->action(QStringLiteral("remove"));
0114     if (closeApplet) {
0115         closeApplet->setText(i18nc("%1 is the name of the applet", "Remove %1", title()));
0116     }
0117 
0118     QAction *configAction = actions()->action(QStringLiteral("configure"));
0119     if (configAction) {
0120         if (d->type == Types::PanelContainment || d->type == Types::CustomPanelContainment) {
0121             configAction->setText(i18n("Enter Edit Mode"));
0122             configAction->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
0123         } else {
0124             configAction->setText(i18nc("%1 is the name of the applet", "Configure %1...", title()));
0125         }
0126     }
0127 
0128     QAction *appletBrowserAction = actions()->action(QStringLiteral("add widgets"));
0129     if (appletBrowserAction) {
0130         appletBrowserAction->setVisible(unlocked);
0131         appletBrowserAction->setEnabled(unlocked);
0132         connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
0133     }
0134 
0135     if (immutability() != Types::SystemImmutable && corona()) {
0136         QAction *lockDesktopAction = corona()->actions()->action(QStringLiteral("lock widgets"));
0137         // keep a pointer so nobody notices it moved to corona
0138         if (lockDesktopAction) {
0139             actions()->addAction(QStringLiteral("lock widgets"), lockDesktopAction);
0140         }
0141     }
0142 
0143     // HACK: this is valid only in the systray case
0144     connect(this, &Containment::configureRequested, this, [=](Plasma::Applet *a) {
0145         if (Plasma::Applet *p = qobject_cast<Plasma::Applet *>(parent())) {
0146             Q_EMIT p->containment()->configureRequested(a);
0147         }
0148     });
0149 }
0150 
0151 // helper function for sorting the list of applets
0152 bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
0153 {
0154     int i1 = c1.readEntry("id", 0);
0155     int i2 = c2.readEntry("id", 0);
0156 
0157     return (i1 < i2);
0158 }
0159 
0160 void Containment::restore(KConfigGroup &group)
0161 {
0162     /*
0163     #ifndef NDEBUG
0164     // qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!initConstraints" << group.name() << d->type;
0165     // qCDebug(LOG_PLASMA) << "    location:" << group.readEntry("location", (int)d->location);
0166     // qCDebug(LOG_PLASMA) << "    geom:" << group.readEntry("geometry", geometry());
0167     // qCDebug(LOG_PLASMA) << "    formfactor:" << group.readEntry("formfactor", (int)d->formFactor);
0168     // qCDebug(LOG_PLASMA) << "    screen:" << group.readEntry("screen", d->screen);
0169     #endif
0170     */
0171     setLocation((Plasma::Types::Location)group.readEntry("location", (int)d->location));
0172     setFormFactor((Plasma::Types::FormFactor)group.readEntry("formfactor", (int)d->formFactor));
0173     d->lastScreen = group.readEntry("lastScreen", d->lastScreen);
0174 
0175     setWallpaper(group.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper));
0176 
0177     d->activityId = group.readEntry("activityId", QString());
0178 
0179     flushPendingConstraintsEvents();
0180     restoreContents(group);
0181     setImmutability((Types::ImmutabilityType)group.readEntry("immutability", (int)Types::Mutable));
0182 
0183     if (isContainment() && KAuthorized::authorize(QStringLiteral("plasma/containment_actions"))) {
0184         KConfigGroup cfg = KConfigGroup(corona()->config(), "ActionPlugins");
0185         cfg = KConfigGroup(&cfg, QString::number(containmentType()));
0186 
0187         // qCDebug(LOG_PLASMA) << cfg.keyList();
0188         if (cfg.exists()) {
0189             const auto keyList = cfg.keyList();
0190             for (const QString &key : keyList) {
0191                 // qCDebug(LOG_PLASMA) << "loading" << key;
0192                 setContainmentActions(key, cfg.readEntry(key, QString()));
0193             }
0194         } else { // shell defaults
0195             KConfigGroup defaultActionsCfg;
0196 
0197             switch (d->type) {
0198             case Plasma::Types::PanelContainment:
0199                 /* fall through*/
0200             case Plasma::Types::CustomPanelContainment:
0201                 defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Panel");
0202                 break;
0203             case Plasma::Types::DesktopContainment:
0204                 defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Desktop");
0205                 break;
0206             default:
0207                 // for any other type of containment, there are no defaults
0208                 break;
0209             }
0210             if (defaultActionsCfg.isValid()) {
0211                 defaultActionsCfg = KConfigGroup(&defaultActionsCfg, "ContainmentActions");
0212                 const auto keyList = defaultActionsCfg.keyList();
0213                 for (const QString &key : keyList) {
0214                     setContainmentActions(key, defaultActionsCfg.readEntry(key, QString()));
0215                 }
0216             }
0217         }
0218     }
0219     Applet::restore(group);
0220 }
0221 
0222 void Containment::save(KConfigGroup &g) const
0223 {
0224     if (Applet::d->transient) {
0225         return;
0226     }
0227 
0228     KConfigGroup group = g;
0229     if (!group.isValid()) {
0230         group = config();
0231     }
0232 
0233     // locking is saved in Applet::save
0234     Applet::save(group);
0235 
0236     //     group.writeEntry("screen", d->screen);
0237     group.writeEntry("lastScreen", d->lastScreen);
0238     group.writeEntry("formfactor", (int)d->formFactor);
0239     group.writeEntry("location", (int)d->location);
0240     group.writeEntry("activityId", d->activityId);
0241 
0242     group.writeEntry("wallpaperplugin", d->wallpaper);
0243 
0244     saveContents(group);
0245 }
0246 
0247 void Containment::saveContents(KConfigGroup &group) const
0248 {
0249     KConfigGroup applets(&group, "Applets");
0250     for (const Applet *applet : std::as_const(d->applets)) {
0251         KConfigGroup appletConfig(&applets, QString::number(applet->id()));
0252         applet->save(appletConfig);
0253     }
0254 }
0255 
0256 void Containment::restoreContents(KConfigGroup &group)
0257 {
0258     KConfigGroup applets(&group, "Applets");
0259 
0260     // restore the applets ordered by id
0261     QStringList groups = applets.groupList();
0262     std::sort(groups.begin(), groups.end());
0263 
0264     // Sort the applet configs in order of geometry to ensure that applets
0265     // are added from left to right or top to bottom for a panel containment
0266     QList<KConfigGroup> appletConfigs;
0267     for (const QString &appletGroup : std::as_const(groups)) {
0268         // qCDebug(LOG_PLASMA) << "reading from applet group" << appletGroup;
0269         KConfigGroup appletConfig(&applets, appletGroup);
0270         appletConfigs.append(appletConfig);
0271     }
0272     std::stable_sort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
0273 
0274     QMutableListIterator<KConfigGroup> it(appletConfigs);
0275     while (it.hasNext()) {
0276         KConfigGroup &appletConfig = it.next();
0277         if (appletConfig.readEntry(QStringLiteral("transient"), false)) {
0278             appletConfig.deleteGroup();
0279             continue;
0280         }
0281         int appId = appletConfig.name().toUInt();
0282         QString plugin = appletConfig.readEntry("plugin", QString());
0283 
0284         if (plugin.isEmpty()) {
0285             continue;
0286         }
0287 
0288         d->createApplet(plugin, QVariantList(), appId);
0289     }
0290 
0291     // if there are no applets, none of them is "loading"
0292     if (Containment::applets().isEmpty()) {
0293         d->appletsUiReady = true;
0294     }
0295     const auto lstApplets = Containment::applets();
0296     for (Applet *applet : lstApplets) {
0297         if (!applet->pluginMetaData().isValid()) {
0298             applet->updateConstraints(Plasma::Types::UiReadyConstraint);
0299         }
0300     }
0301 }
0302 
0303 Plasma::Types::ContainmentType Containment::containmentType() const
0304 {
0305     return d->type;
0306 }
0307 
0308 void Containment::setContainmentType(Plasma::Types::ContainmentType type)
0309 {
0310     if (d->type == type) {
0311         return;
0312     }
0313 
0314     d->type = type;
0315     Q_EMIT containmentTypeChanged();
0316 }
0317 
0318 Corona *Containment::corona() const
0319 {
0320     if (Plasma::Corona *corona = qobject_cast<Corona *>(parent())) {
0321         return corona;
0322         // case in which this containment is child of an applet, hello systray :)
0323     } else {
0324         Plasma::Applet *parentApplet = qobject_cast<Plasma::Applet *>(parent());
0325         if (parentApplet && parentApplet->containment()) {
0326             return parentApplet->containment()->corona();
0327         }
0328     }
0329 
0330     return nullptr;
0331 }
0332 
0333 void Containment::setFormFactor(Types::FormFactor formFactor)
0334 {
0335     if (d->formFactor == formFactor) {
0336         return;
0337     }
0338 
0339     // qCDebug(LOG_PLASMA) << "switching FF to " << formFactor;
0340     d->formFactor = formFactor;
0341 
0342     updateConstraints(Plasma::Types::FormFactorConstraint);
0343 
0344     KConfigGroup c = config();
0345     c.writeEntry("formfactor", (int)formFactor);
0346     Q_EMIT configNeedsSaving();
0347     Q_EMIT formFactorChanged(formFactor);
0348 }
0349 
0350 void Containment::setContainmentDisplayHints(Types::ContainmentDisplayHints hints)
0351 {
0352     if (d->containmentDisplayHints == hints) {
0353         return;
0354     }
0355 
0356     d->containmentDisplayHints = hints;
0357     Q_EMIT containmentDisplayHintsChanged(hints);
0358 }
0359 
0360 void Containment::setLocation(Types::Location location)
0361 {
0362     if (d->location == location) {
0363         return;
0364     }
0365 
0366     d->location = location;
0367 
0368     for (Applet *applet : std::as_const(d->applets)) {
0369         applet->updateConstraints(Plasma::Types::LocationConstraint);
0370     }
0371 
0372     updateConstraints(Plasma::Types::LocationConstraint);
0373 
0374     KConfigGroup c = config();
0375     c.writeEntry("location", (int)location);
0376     Q_EMIT configNeedsSaving();
0377     Q_EMIT locationChanged(location);
0378 }
0379 
0380 Applet *Containment::createApplet(const QString &name, const QVariantList &args)
0381 {
0382     Plasma::Applet *applet = d->createApplet(name, args);
0383     if (applet) {
0384         Q_EMIT appletCreated(applet);
0385     }
0386     return applet;
0387 }
0388 
0389 void Containment::addApplet(Applet *applet)
0390 {
0391     if (!applet) {
0392 #ifndef NDEBUG
0393         // qCDebug(LOG_PLASMA) << "adding null applet!?!";
0394 #endif
0395         return;
0396     }
0397 
0398     if (immutability() != Types::Mutable && !applet->property("org.kde.plasma:force-create").toBool()) {
0399         return;
0400     }
0401 
0402 #ifndef NDEBUG
0403     if (d->applets.contains(applet)) {
0404         // qCDebug(LOG_PLASMA) << "already have this applet!";
0405     }
0406 #endif
0407 
0408     Containment *currentContainment = applet->containment();
0409 
0410     if (currentContainment && currentContainment != this) {
0411         Q_EMIT currentContainment->appletRemoved(applet);
0412 
0413         disconnect(applet, nullptr, currentContainment, nullptr);
0414         connect(currentContainment, nullptr, applet, nullptr);
0415         KConfigGroup oldConfig = applet->config();
0416         currentContainment->d->applets.removeAll(applet);
0417         applet->setParent(this);
0418 
0419         // now move the old config to the new location
0420         // FIXME: this doesn't seem to get the actual main config group containing plugin=, etc
0421         KConfigGroup c = config().group("Applets").group(QString::number(applet->id()));
0422         oldConfig.reparent(&c);
0423         applet->d->resetConfigurationObject();
0424 
0425         disconnect(applet, &Applet::activated, currentContainment, &Applet::activated);
0426         // change the group to its configloader, if any
0427         // FIXME: this is very, very brutal
0428         if (applet->configScheme()) {
0429             const QString oldGroupPrefix = QStringLiteral("Containments") + QString::number(currentContainment->id()) + QStringLiteral("Applets");
0430             const QString newGroupPrefix = QStringLiteral("Containments") + QString::number(id()) + QStringLiteral("Applets");
0431 
0432             applet->configScheme()->setCurrentGroup(applet->configScheme()->currentGroup().replace(0, oldGroupPrefix.length(), newGroupPrefix));
0433 
0434             const auto items = applet->configScheme()->items();
0435             for (KConfigSkeletonItem *item : items) {
0436                 item->setGroup(item->group().replace(0, oldGroupPrefix.length(), newGroupPrefix));
0437             }
0438         }
0439     } else {
0440         applet->setParent(this);
0441     }
0442 
0443     // make sure the applets are sorted by id
0444     auto position = std::lower_bound(d->applets.begin(), d->applets.end(), applet, [](Plasma::Applet *a1, Plasma::Applet *a2) {
0445         return a1->id() < a2->id();
0446     });
0447     d->applets.insert(position, applet);
0448 
0449     if (!d->uiReady) {
0450         d->loadingApplets << applet;
0451     }
0452 
0453     connect(applet, &Applet::configNeedsSaving, this, &Applet::configNeedsSaving);
0454     connect(applet, SIGNAL(appletDeleted(Plasma::Applet *)), this, SLOT(appletDeleted(Plasma::Applet *)));
0455     connect(applet, SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(checkStatus(Plasma::Types::ItemStatus)));
0456     connect(applet, &Applet::activated, this, &Applet::activated);
0457     connect(this, &Containment::containmentDisplayHintsChanged, applet, &Applet::containmentDisplayHintsChanged);
0458 
0459     if (!currentContainment) {
0460         const bool isNew = applet->d->mainConfigGroup()->entryMap().isEmpty();
0461 
0462         if (!isNew) {
0463             applet->restore(*applet->d->mainConfigGroup());
0464         }
0465 
0466         applet->init();
0467         applet->d->setupScripting();
0468 
0469         if (isNew) {
0470             applet->save(*applet->d->mainConfigGroup());
0471             Q_EMIT configNeedsSaving();
0472         }
0473         // FIXME: an on-appear animation would be nice to have again
0474     }
0475 
0476     applet->updateConstraints(Plasma::Types::AllConstraints);
0477     applet->flushPendingConstraintsEvents();
0478 
0479     Q_EMIT appletAdded(applet);
0480 
0481     if (!currentContainment) {
0482         applet->updateConstraints(Plasma::Types::StartupCompletedConstraint);
0483         applet->flushPendingConstraintsEvents();
0484     }
0485 
0486     applet->d->scheduleModificationNotification();
0487 }
0488 
0489 QList<Applet *> Containment::applets() const
0490 {
0491     return d->applets;
0492 }
0493 
0494 int Containment::screen() const
0495 {
0496     Q_ASSERT(corona());
0497     if (Corona *c = corona()) {
0498         return c->screenForContainment(this);
0499     } else {
0500         return -1;
0501     }
0502 }
0503 
0504 int Containment::lastScreen() const
0505 {
0506     return d->lastScreen;
0507 }
0508 
0509 void Containment::setWallpaper(const QString &pluginName)
0510 {
0511     if (pluginName != d->wallpaper) {
0512         d->wallpaper = pluginName;
0513 
0514         KConfigGroup cfg = config();
0515         cfg.writeEntry("wallpaperplugin", d->wallpaper);
0516         Q_EMIT configNeedsSaving();
0517         Q_EMIT wallpaperChanged();
0518     }
0519 }
0520 
0521 QString Containment::wallpaper() const
0522 {
0523     return d->wallpaper;
0524 }
0525 
0526 void Containment::setContainmentActions(const QString &trigger, const QString &pluginName)
0527 {
0528     KConfigGroup cfg = d->containmentActionsConfig();
0529     ContainmentActions *plugin = nullptr;
0530 
0531     plugin = containmentActions().value(trigger);
0532     if (plugin && plugin->metadata().pluginId() != pluginName) {
0533         containmentActions().remove(trigger);
0534         delete plugin;
0535         plugin = nullptr;
0536     }
0537 
0538     if (pluginName.isEmpty()) {
0539         cfg.deleteEntry(trigger);
0540     } else if (plugin) {
0541         // it already existed, just reload config
0542         plugin->setContainment(this); // to be safe
0543         // FIXME make a truly unique config group
0544         KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
0545         plugin->restore(pluginConfig);
0546 
0547     } else {
0548         plugin = PluginLoader::self()->loadContainmentActions(this, pluginName);
0549 
0550         if (plugin) {
0551             cfg.writeEntry(trigger, pluginName);
0552             containmentActions().insert(trigger, plugin);
0553             plugin->setContainment(this);
0554             KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
0555             plugin->restore(pluginConfig);
0556         } else {
0557             // bad plugin... gets removed. is this a feature or a bug?
0558             cfg.deleteEntry(trigger);
0559         }
0560     }
0561 
0562     Q_EMIT configNeedsSaving();
0563 }
0564 
0565 QHash<QString, ContainmentActions *> &Containment::containmentActions()
0566 {
0567     return d->localActionPlugins;
0568 }
0569 
0570 bool Containment::isUiReady() const
0571 {
0572     return d->uiReady && d->appletsUiReady && Applet::d->started;
0573 }
0574 
0575 void Containment::setActivity(const QString &activityId)
0576 {
0577     if (activityId.isEmpty() || d->activityId == activityId) {
0578         return;
0579     }
0580 
0581     d->activityId = activityId;
0582     KConfigGroup c = config();
0583     c.writeEntry("activityId", activityId);
0584 
0585     Q_EMIT configNeedsSaving();
0586     Q_EMIT activityChanged(activityId);
0587 }
0588 
0589 QString Containment::activity() const
0590 {
0591     return d->activityId;
0592 }
0593 
0594 void Containment::reactToScreenChange()
0595 {
0596     int newScreen = screen();
0597 
0598     if (newScreen >= 0) {
0599         d->lastScreen = newScreen;
0600         KConfigGroup c = config();
0601         c.writeEntry("lastScreen", d->lastScreen);
0602         Q_EMIT configNeedsSaving();
0603     }
0604 
0605     Q_EMIT screenChanged(newScreen);
0606 }
0607 
0608 } // Plasma namespace
0609 
0610 #include "moc_containment.cpp"