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"