plasma-framework/src/plasma/containment.cpp
2018-10-22 21:05:29 +03:00

599 lines
19 KiB
C++

/*
* Copyright 2007 by Aaron Seigo <aseigo@kde.org>
* Copyright 2008 by Ménard Alexis <darktears31@gmail.com>
* Copyright 2009 Chani Armitage <chani@kde.org>
* Copyright 2012 Marco Martin <notmart@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 "containment.h"
#include "private/containment_p.h"
#include "config-plasma.h"
#include <QClipboard>
#include <QDebug>
#include <QFile>
#include <QContextMenuEvent>
#include <QMimeData>
#include <QPainter>
#include <qtemporaryfile.h>
#include <qmimedatabase.h>
#include <kauthorized.h>
#include <klocalizedstring.h>
#include <KConfigSkeleton>
#include <KConfigLoader>
#include "containmentactions.h"
#include "corona.h"
#include "pluginloader.h"
#include "debug_p.h"
#include "private/applet_p.h"
#include "plasma/plasma.h"
namespace Plasma
{
Containment::Containment(QObject *parent,
const QString &serviceId,
uint containmentId)
: Applet(parent, serviceId, containmentId),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setContainmentType(Types::CustomContainment);
setHasConfigurationInterface(true);
}
Containment::Containment(QObject *parent, const QVariantList &args)
: Applet(parent, args),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setHasConfigurationInterface(true);
}
Containment::Containment(const KPluginMetaData &md, uint appletId)
: Applet(md, nullptr, appletId),
d(new ContainmentPrivate(this))
{
// WARNING: do not access config() OR globalConfig() in this method!
// that requires a scene, which is not available at this point
setHasConfigurationInterface(true);
}
Containment::~Containment()
{
qDeleteAll(d->localActionPlugins);
delete d;
}
void Containment::init()
{
Applet::init();
static_cast<Applet *>(this)->d->setupScripting();
if (d->type == Types::NoContainmentType) {
//setContainmentType(Plasma::Types::DesktopContainment);
//Try to determine the containment type. It must be done as soon as possible
QString type = pluginMetaData().value(QStringLiteral("X-Plasma-ContainmentType"));
if (type == QLatin1String("Panel")) {
setContainmentType(Plasma::Types::PanelContainment);
} else if (type == QLatin1String("Custom")) {
setContainmentType(Plasma::Types::CustomContainment);
} else if (type == QLatin1String("CustomPanel")) {
setContainmentType(Plasma::Types::CustomPanelContainment);
//default to desktop
} else {
setContainmentType(Plasma::Types::DesktopContainment);
}
}
//connect actions
ContainmentPrivate::addDefaultActions(actions(), this);
bool unlocked = immutability() == Types::Mutable;
//fix the text of the actions that need title()
//btw, do we really want to use title() when it's a desktopcontainment?
QAction *closeApplet = actions()->action(QStringLiteral("remove"));
if (closeApplet) {
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove %1", title()));
}
QAction *configAction = actions()->action(QStringLiteral("configure"));
if (configAction) {
configAction->setText(i18nc("%1 is the name of the applet", "Configure %1...", title()));
}
QAction *appletBrowserAction = actions()->action(QStringLiteral("add widgets"));
if (appletBrowserAction) {
appletBrowserAction->setVisible(unlocked);
appletBrowserAction->setEnabled(unlocked);
connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
}
if (immutability() != Types::SystemImmutable && corona()) {
QAction *lockDesktopAction = corona()->actions()->action(QStringLiteral("lock widgets"));
//keep a pointer so nobody notices it moved to corona
if (lockDesktopAction) {
actions()->addAction(QStringLiteral("lock widgets"), lockDesktopAction);
}
}
//HACK: this is valid only in the systray case
connect(this, &Containment::configureRequested, this, [=] (Plasma::Applet *a) {
if (Plasma::Applet *p = qobject_cast<Plasma::Applet *>(parent())) {
emit p->containment()->configureRequested(a);
}
});
}
// helper function for sorting the list of applets
bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
{
int i1 = c1.readEntry("id", 0);
int i2 = c2.readEntry("id", 0);
return (i1 < i2);
}
void Containment::restore(KConfigGroup &group)
{
/*
#ifndef NDEBUG
// qCDebug(LOG_PLASMA) << "!!!!!!!!!!!!initConstraints" << group.name() << d->type;
// qCDebug(LOG_PLASMA) << " location:" << group.readEntry("location", (int)d->location);
// qCDebug(LOG_PLASMA) << " geom:" << group.readEntry("geometry", geometry());
// qCDebug(LOG_PLASMA) << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor);
// qCDebug(LOG_PLASMA) << " screen:" << group.readEntry("screen", d->screen);
#endif
*/
setLocation((Plasma::Types::Location)group.readEntry("location", (int)d->location));
setFormFactor((Plasma::Types::FormFactor)group.readEntry("formfactor", (int)d->formFactor));
d->lastScreen = group.readEntry("lastScreen", d->lastScreen);
setWallpaper(group.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper));
d->activityId = group.readEntry("activityId", QString());
flushPendingConstraintsEvents();
restoreContents(group);
setImmutability((Types::ImmutabilityType)group.readEntry("immutability", (int)Types::Mutable));
if (isContainment() && KAuthorized::authorize(QStringLiteral("plasma/containment_actions"))) {
KConfigGroup cfg = KConfigGroup(corona()->config(), "ActionPlugins");
cfg = KConfigGroup(&cfg, QString::number(containmentType()));
//qCDebug(LOG_PLASMA) << cfg.keyList();
if (cfg.exists()) {
foreach (const QString &key, cfg.keyList()) {
//qCDebug(LOG_PLASMA) << "loading" << key;
setContainmentActions(key, cfg.readEntry(key, QString()));
}
} else { //shell defaults
KConfigGroup defaultActionsCfg;
switch (d->type) {
case Plasma::Types::PanelContainment:
/* fall through*/
case Plasma::Types::CustomPanelContainment:
defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Panel");
break;
case Plasma::Types::DesktopContainment:
defaultActionsCfg = KConfigGroup(KSharedConfig::openConfig(corona()->kPackage().filePath("defaults")), "Desktop");
break;
default:
//for any other type of containment, there are no defaults
break;
}
if (defaultActionsCfg.isValid()) {
defaultActionsCfg = KConfigGroup(&defaultActionsCfg, "ContainmentActions");
foreach (const QString &key, defaultActionsCfg.keyList()) {
setContainmentActions(key, defaultActionsCfg.readEntry(key, QString()));
}
}
}
}
/*
#ifndef NDEBUG
// qCDebug(LOG_PLASMA) << "Containment" << id() <<
#endif
"screen" << screen() <<
"geometry is" << geometry() <<
"config entries" << group.entryMap();
*/
}
void Containment::save(KConfigGroup &g) const
{
if (Applet::d->transient) {
return;
}
KConfigGroup group = g;
if (!group.isValid()) {
group = config();
}
// locking is saved in Applet::save
Applet::save(group);
// group.writeEntry("screen", d->screen);
group.writeEntry("lastScreen", d->lastScreen);
group.writeEntry("formfactor", (int)d->formFactor);
group.writeEntry("location", (int)d->location);
group.writeEntry("activityId", d->activityId);
group.writeEntry("wallpaperplugin", d->wallpaper);
saveContents(group);
}
void Containment::saveContents(KConfigGroup &group) const
{
KConfigGroup applets(&group, "Applets");
foreach (const Applet *applet, d->applets) {
KConfigGroup appletConfig(&applets, QString::number(applet->id()));
applet->save(appletConfig);
}
}
void Containment::restoreContents(KConfigGroup &group)
{
KConfigGroup applets(&group, "Applets");
//restore the applets ordered by id
QStringList groups = applets.groupList();
qSort(groups.begin(), groups.end());
// Sort the applet configs in order of geometry to ensure that applets
// are added from left to right or top to bottom for a panel containment
QList<KConfigGroup> appletConfigs;
foreach (const QString &appletGroup, groups) {
//qCDebug(LOG_PLASMA) << "reading from applet group" << appletGroup;
KConfigGroup appletConfig(&applets, appletGroup);
appletConfigs.append(appletConfig);
}
qStableSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
QMutableListIterator<KConfigGroup> it(appletConfigs);
while (it.hasNext()) {
KConfigGroup &appletConfig = it.next();
int appId = appletConfig.name().toUInt();
QString plugin = appletConfig.readEntry("plugin", QString());
if (plugin.isEmpty()) {
continue;
}
d->createApplet(plugin, QVariantList(), appId);
}
//if there are no applets, none of them is "loading"
if (Containment::applets().isEmpty()) {
d->appletsUiReady = true;
}
foreach (Applet *applet, Containment::applets()) {
if (!applet->pluginMetaData().isValid()) {
applet->updateConstraints(Plasma::Types::UiReadyConstraint);
}
}
}
Plasma::Types::ContainmentType Containment::containmentType() const
{
return d->type;
}
void Containment::setContainmentType(Plasma::Types::ContainmentType type)
{
if (d->type == type) {
return;
}
d->type = type;
emit containmentTypeChanged();
}
Corona *Containment::corona() const
{
if(Plasma::Corona* corona = qobject_cast<Corona *>(parent())) {
return corona;
//case in which this containment is child of an applet, hello systray :)
} else {
Plasma::Applet *parentApplet = qobject_cast<Plasma::Applet *>(parent());
if (parentApplet && parentApplet->containment()) {
return parentApplet->containment()->corona();
}
}
return nullptr;
}
void Containment::setFormFactor(Types::FormFactor formFactor)
{
if (d->formFactor == formFactor) {
return;
}
//qCDebug(LOG_PLASMA) << "switching FF to " << formFactor;
d->formFactor = formFactor;
updateConstraints(Plasma::Types::FormFactorConstraint);
KConfigGroup c = config();
c.writeEntry("formfactor", (int)formFactor);
emit configNeedsSaving();
emit formFactorChanged(formFactor);
}
void Containment::setLocation(Types::Location location)
{
if (d->location == location) {
return;
}
d->location = location;
foreach (Applet *applet, d->applets) {
applet->updateConstraints(Plasma::Types::LocationConstraint);
}
updateConstraints(Plasma::Types::LocationConstraint);
KConfigGroup c = config();
c.writeEntry("location", (int)location);
emit configNeedsSaving();
emit locationChanged(location);
}
Applet *Containment::createApplet(const QString &name, const QVariantList &args)
{
Plasma::Applet *applet = d->createApplet(name, args);
if (applet) {
emit appletCreated(applet);
}
return applet;
}
void Containment::addApplet(Applet *applet)
{
if (!applet) {
#ifndef NDEBUG
// qCDebug(LOG_PLASMA) << "adding null applet!?!";
#endif
return;
}
if (immutability() != Types::Mutable && !applet->property("org.kde.plasma:force-create").toBool()) {
return;
}
#ifndef NDEBUG
if (d->applets.contains(applet)) {
// qCDebug(LOG_PLASMA) << "already have this applet!";
}
#endif
Containment *currentContainment = applet->containment();
if (currentContainment && currentContainment != this) {
emit currentContainment->appletRemoved(applet);
disconnect(applet, nullptr, currentContainment, nullptr);
KConfigGroup oldConfig = applet->config();
currentContainment->d->applets.removeAll(applet);
applet->setParent(this);
// now move the old config to the new location
//FIXME: this doesn't seem to get the actual main config group containing plugin=, etc
KConfigGroup c = config().group("Applets").group(QString::number(applet->id()));
oldConfig.reparent(&c);
applet->d->resetConfigurationObject();
disconnect(applet, SIGNAL(activated()), currentContainment, SIGNAL(activated()));
//change the group to its configloader, if any
//FIXME: this is very, very brutal
if (applet->configScheme()) {
const QString oldGroupPrefix = QStringLiteral("Containments") + QString::number(currentContainment->id()) + QStringLiteral("Applets");
const QString newGroupPrefix = QStringLiteral("Containments") + QString::number(id()) + QStringLiteral("Applets");
applet->configScheme()->setCurrentGroup(applet->configScheme()->currentGroup().replace(0, oldGroupPrefix.length(), newGroupPrefix));
foreach (KConfigSkeletonItem *item, applet->configScheme()->items()) {
item->setGroup(item->group().replace(0, oldGroupPrefix.length(), newGroupPrefix));
}
}
} else {
applet->setParent(this);
}
//make sure the applets are sorted by id
auto position = std::lower_bound(d->applets.begin(), d->applets.end(), applet, [](Plasma::Applet *a1, Plasma::Applet *a2) {
return a1->id() < a2->id();
});
d->applets.insert(position, applet);
if (!d->uiReady) {
d->loadingApplets << applet;
}
connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving()));
connect(applet, SIGNAL(appletDeleted(Plasma::Applet*)), this, SLOT(appletDeleted(Plasma::Applet*)));
connect(applet, SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(checkStatus(Plasma::Types::ItemStatus)));
connect(applet, SIGNAL(activated()), this, SIGNAL(activated()));
if (!currentContainment) {
const bool isNew = applet->d->mainConfigGroup()->entryMap().isEmpty();
if (!isNew) {
applet->restore(*applet->d->mainConfigGroup());
}
applet->init();
applet->d->setupScripting();
if (isNew) {
applet->save(*applet->d->mainConfigGroup());
emit configNeedsSaving();
}
//FIXME: an on-appear animation would be nice to have again
}
applet->updateConstraints(Plasma::Types::AllConstraints);
applet->flushPendingConstraintsEvents();
emit appletAdded(applet);
if (!currentContainment) {
applet->updateConstraints(Plasma::Types::StartupCompletedConstraint);
applet->flushPendingConstraintsEvents();
}
applet->d->scheduleModificationNotification();
}
QList<Applet *> Containment::applets() const
{
return d->applets;
}
int Containment::screen() const
{
Q_ASSERT(corona());
if (Corona* c = corona()) {
return c->screenForContainment(this);
} else {
return -1;
}
}
int Containment::lastScreen() const
{
return d->lastScreen;
}
void Containment::setWallpaper(const QString &pluginName)
{
if (pluginName != d->wallpaper) {
d->wallpaper = pluginName;
KConfigGroup cfg = config();
cfg.writeEntry("wallpaperplugin", d->wallpaper);
emit configNeedsSaving();
emit wallpaperChanged();
}
}
QString Containment::wallpaper() const
{
return d->wallpaper;
}
void Containment::setContainmentActions(const QString &trigger, const QString &pluginName)
{
KConfigGroup cfg = d->containmentActionsConfig();
ContainmentActions *plugin = nullptr;
plugin = containmentActions().value(trigger);
if (plugin && plugin->pluginInfo().pluginName() != pluginName) {
containmentActions().remove(trigger);
delete plugin;
plugin = nullptr;
}
if (pluginName.isEmpty()) {
cfg.deleteEntry(trigger);
} else if (plugin) {
//it already existed, just reload config
plugin->setContainment(this); //to be safe
//FIXME make a truly unique config group
KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
plugin->restore(pluginConfig);
} else {
plugin = PluginLoader::self()->loadContainmentActions(this, pluginName);
if (plugin) {
cfg.writeEntry(trigger, pluginName);
containmentActions().insert(trigger, plugin);
plugin->setContainment(this);
KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
plugin->restore(pluginConfig);
} else {
//bad plugin... gets removed. is this a feature or a bug?
cfg.deleteEntry(trigger);
}
}
emit configNeedsSaving();
}
QHash<QString, ContainmentActions *> &Containment::containmentActions()
{
return d->localActionPlugins;
}
bool Containment::isUiReady() const
{
return d->uiReady && d->appletsUiReady && Applet::d->started;
}
void Containment::setActivity(const QString &activityId)
{
if (activityId.isEmpty() || d->activityId == activityId) {
return;
}
d->activityId = activityId;
KConfigGroup c = config();
c.writeEntry("activityId", activityId);
emit configNeedsSaving();
emit activityChanged(activityId);
}
QString Containment::activity() const
{
return d->activityId;
}
void Containment::reactToScreenChange()
{
int newScreen = screen();
if (newScreen >= 0) {
d->lastScreen = newScreen;
KConfigGroup c = config();
c.writeEntry("lastScreen", d->lastScreen);
emit configNeedsSaving();
}
emit screenChanged(newScreen);
}
} // Plasma namespace
#include "moc_containment.cpp"