de4fa09a6d
setFailedToLaunch has a weird asymmetric api: now launchErrorMessage is a string property. failedToLaunch remains and is internally set true the first time a message is set (is not posssible to set it back to false) the message is in turn used by the scriptengine for the user visible error message. It is done in 2 places: if the applet package is invalid (applet not found) the message is created by the containment if the applet package is valid, but there is a parse error in the qml file the error message is generated by the scriptengine of the applet itself. I'm still really not happy about how the error ui is generated.
553 lines
18 KiB
C++
553 lines
18 KiB
C++
/*
|
|
* Copyright 2005 by Aaron Seigo <aseigo@kde.org>
|
|
* Copyright 2007 by Riccardo Iaconelli <riccardo@kde.org>
|
|
* Copyright 2008 by Ménard Alexis <darktears31@gmail.com>
|
|
* Copyright (c) 2009 Chani Armitage <chani@kde.org>
|
|
* Copyright 2012 by Marco Martin <mart@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 "private/applet_p.h"
|
|
|
|
#include <config-plasma.h>
|
|
|
|
#include <QFile>
|
|
#include <QHostInfo>
|
|
#include <QVBoxLayout>
|
|
#include <qstandardpaths.h>
|
|
|
|
#include <kaction.h>
|
|
#include <kdebug.h>
|
|
#include <kiconloader.h>
|
|
#include <klocale.h>
|
|
#include <klocalizedstring.h>
|
|
#include <kkeysequencewidget.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kglobal.h>
|
|
|
|
#include "containment.h"
|
|
#include "corona.h"
|
|
#include "pluginloader.h"
|
|
#include "scripting/scriptengine.h"
|
|
#include "scripting/appletscript.h"
|
|
#include "private/containment_p.h"
|
|
|
|
namespace Plasma
|
|
{
|
|
|
|
AppletPrivate::AppletPrivate(KService::Ptr service, const KPluginInfo *info, int uniqueID, Applet *applet)
|
|
: appletId(uniqueID),
|
|
q(applet),
|
|
immutability(Mutable),
|
|
appletDescription(info ? *info : KPluginInfo(service)),
|
|
mainConfig(0),
|
|
pendingConstraints(NoConstraint),
|
|
script(0),
|
|
package(0),
|
|
configLoader(0),
|
|
actions(AppletPrivate::defaultActions(applet)),
|
|
activationAction(0),
|
|
itemStatus(UnknownStatus),
|
|
modificationsTimer(0),
|
|
hasConfigurationInterface(false),
|
|
failed(false),
|
|
isContainment(false),
|
|
transient(false),
|
|
needsConfig(false),
|
|
started(false)
|
|
{
|
|
if (appletId == 0) {
|
|
appletId = ++s_maxAppletId;
|
|
} else if (appletId > s_maxAppletId) {
|
|
s_maxAppletId = appletId;
|
|
}
|
|
}
|
|
|
|
AppletPrivate::~AppletPrivate()
|
|
{
|
|
if (activationAction && activationAction->isGlobalShortcutEnabled()) {
|
|
//kDebug() << "reseting global action for" << q->title() << activationAction->objectName();
|
|
activationAction->forgetGlobalShortcut();
|
|
}
|
|
|
|
delete script;
|
|
script = 0;
|
|
delete package;
|
|
package = 0;
|
|
delete configLoader;
|
|
configLoader = 0;
|
|
delete mainConfig;
|
|
mainConfig = 0;
|
|
delete modificationsTimer;
|
|
}
|
|
|
|
void AppletPrivate::init(const QString &packagePath)
|
|
{
|
|
// WARNING: do not access config() OR globalConfig() in this method!
|
|
// that requires a scene, which is not available at this point
|
|
|
|
q->setHasConfigurationInterface(true); //FIXME why not default it to true in the constructor?
|
|
|
|
QAction *closeApplet = actions->action("remove");
|
|
if (closeApplet) {
|
|
closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", q->title()));
|
|
}
|
|
|
|
QAction *configAction = actions->action("configure");
|
|
if (configAction) {
|
|
configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", q->title()));
|
|
}
|
|
|
|
QObject::connect(q, SIGNAL(activate()), q, SLOT(setFocus()));
|
|
if (!appletDescription.isValid()) {
|
|
#ifndef NDEBUG
|
|
kDebug() << "Check your constructor! "
|
|
<< "You probably want to be passing in a Service::Ptr "
|
|
<< "or a QVariantList with a valid storageid as arg[0].";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
QString api = appletDescription.property("X-Plasma-API").toString();
|
|
|
|
// we have a scripted plasmoid
|
|
if (api.isEmpty()) {
|
|
q->setLaunchErrorMessage(i18n("The %2 widget did not define which ScriptEngine to use.", appletDescription.name()));
|
|
return;
|
|
}
|
|
|
|
|
|
const QString path = packagePath.isEmpty() ? appletDescription.pluginName() : packagePath;
|
|
package = new Package(PluginLoader::self()->loadPackage("Plasma/Applet", api));
|
|
package->setPath(path);
|
|
|
|
if (!package->isValid()) {
|
|
delete package;
|
|
package = 0;
|
|
q->setLaunchErrorMessage(i18nc("Package file, name of the widget",
|
|
"Could not open the %1 package required for the %2 widget.",
|
|
appletDescription.pluginName(), appletDescription.name()));
|
|
return;
|
|
}
|
|
|
|
// create the package and see if we have something real
|
|
//kDebug() << "trying for" << path;
|
|
|
|
// now we try and set up the script engine.
|
|
// it will be parented to this applet and so will get
|
|
// deleted when the applet does
|
|
|
|
script = Plasma::loadScriptEngine(api, q);
|
|
|
|
if (!script) {
|
|
delete package;
|
|
package = 0;
|
|
q->setLaunchErrorMessage(
|
|
i18nc("API or programming language the widget was written in, name of the widget",
|
|
"Could not create a %1 ScriptEngine for the %2 widget.",
|
|
api, appletDescription.name()));
|
|
}
|
|
}
|
|
|
|
void AppletPrivate::cleanUpAndDelete()
|
|
{
|
|
// reimplemented in the UI specific library
|
|
}
|
|
|
|
void AppletPrivate::showConfigurationRequiredMessage(bool show, const QString &reason)
|
|
{
|
|
// reimplemented in the UI specific library
|
|
Q_UNUSED(show)
|
|
Q_UNUSED(reason)
|
|
}
|
|
|
|
void AppletPrivate::globalShortcutChanged()
|
|
{
|
|
if (!activationAction) {
|
|
return;
|
|
}
|
|
|
|
KConfigGroup shortcutConfig(mainConfigGroup(), "Shortcuts");
|
|
shortcutConfig.writeEntry("global", activationAction->globalShortcut().toString());
|
|
scheduleModificationNotification();
|
|
//kDebug() << "after" << shortcut.primary() << d->activationAction->globalShortcut().primary();
|
|
}
|
|
|
|
KActionCollection* AppletPrivate::defaultActions(QObject *parent)
|
|
{
|
|
KActionCollection *actions = new KActionCollection(parent);
|
|
actions->setConfigGroup("Shortcuts-Applet");
|
|
|
|
KAction *configAction = actions->addAction("configure");
|
|
configAction->setAutoRepeat(false);
|
|
configAction->setText(i18n("Widget Settings"));
|
|
configAction->setIcon(KDE::icon("configure"));
|
|
configAction->setShortcut(KShortcut("alt+d, s"));
|
|
configAction->setData(Containment::ConfigureTool);
|
|
|
|
KAction *closeApplet = actions->addAction("remove");
|
|
closeApplet->setAutoRepeat(false);
|
|
closeApplet->setText(i18n("Remove this Widget"));
|
|
closeApplet->setIcon(KDE::icon("edit-delete"));
|
|
closeApplet->setShortcut(KShortcut("alt+d, r"));
|
|
closeApplet->setData(Containment::DestructiveTool);
|
|
|
|
KAction *runAssociatedApplication = actions->addAction("run associated application");
|
|
runAssociatedApplication->setAutoRepeat(false);
|
|
runAssociatedApplication->setText(i18n("Run the Associated Application"));
|
|
runAssociatedApplication->setIcon(KDE::icon("system-run"));
|
|
runAssociatedApplication->setShortcut(KShortcut("alt+d, t"));
|
|
runAssociatedApplication->setVisible(false);
|
|
runAssociatedApplication->setEnabled(false);
|
|
runAssociatedApplication->setData(Containment::ControlTool);
|
|
|
|
return actions;
|
|
}
|
|
|
|
QString AppletPrivate::configDialogId() const
|
|
{
|
|
return QString("%1settings%2").arg(appletId).arg(q->title());
|
|
}
|
|
|
|
QString AppletPrivate::configWindowTitle() const
|
|
{
|
|
return i18nc("@title:window", "%1 Settings", q->title());
|
|
}
|
|
|
|
QSet<QString> AppletPrivate::knownCategories()
|
|
{
|
|
// this is to trick the tranlsation tools into making the correct
|
|
// strings for translation
|
|
QSet<QString> categories = s_customCategories;
|
|
categories << QString(I18N_NOOP("Accessibility")).toLower()
|
|
<< QString(I18N_NOOP("Application Launchers")).toLower()
|
|
<< QString(I18N_NOOP("Astronomy")).toLower()
|
|
<< QString(I18N_NOOP("Date and Time")).toLower()
|
|
<< QString(I18N_NOOP("Development Tools")).toLower()
|
|
<< QString(I18N_NOOP("Education")).toLower()
|
|
<< QString(I18N_NOOP("Environment and Weather")).toLower()
|
|
<< QString(I18N_NOOP("Examples")).toLower()
|
|
<< QString(I18N_NOOP("File System")).toLower()
|
|
<< QString(I18N_NOOP("Fun and Games")).toLower()
|
|
<< QString(I18N_NOOP("Graphics")).toLower()
|
|
<< QString(I18N_NOOP("Language")).toLower()
|
|
<< QString(I18N_NOOP("Mapping")).toLower()
|
|
<< QString(I18N_NOOP("Miscellaneous")).toLower()
|
|
<< QString(I18N_NOOP("Multimedia")).toLower()
|
|
<< QString(I18N_NOOP("Online Services")).toLower()
|
|
<< QString(I18N_NOOP("Productivity")).toLower()
|
|
<< QString(I18N_NOOP("System Information")).toLower()
|
|
<< QString(I18N_NOOP("Utilities")).toLower()
|
|
<< QString(I18N_NOOP("Windows and Tasks")).toLower();
|
|
return categories;
|
|
}
|
|
|
|
KConfigDialog *AppletPrivate::generateGenericConfigDialog()
|
|
{
|
|
KConfigSkeleton *nullManager = new KConfigSkeleton(0);
|
|
KConfigDialog *dialog = new AppletConfigDialog(0, configDialogId(), nullManager);
|
|
nullManager->setParent(dialog);
|
|
dialog->setFaceType(KPageDialog::Auto);
|
|
dialog->setWindowTitle(configWindowTitle());
|
|
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
q->createConfigurationInterface(dialog);
|
|
dialog->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);
|
|
QObject::connect(dialog->button(QDialogButtonBox::Apply), SIGNAL(clicked()), q, SLOT(configDialogFinished()));
|
|
QObject::connect(dialog->button(QDialogButtonBox::Ok), SIGNAL(clicked()), q, SLOT(configDialogFinished()));
|
|
return dialog;
|
|
}
|
|
|
|
void AppletPrivate::addStandardConfigurationPages(KConfigDialog *dialog)
|
|
{
|
|
addGlobalShortcutsPage(dialog);
|
|
}
|
|
|
|
void AppletPrivate::addGlobalShortcutsPage(KConfigDialog *dialog)
|
|
{
|
|
#if !PLASMA_NO_GLOBAL_SHORTCUTS
|
|
if (isContainment) {
|
|
return;
|
|
}
|
|
|
|
QWidget *page = new QWidget;
|
|
QVBoxLayout *layout = new QVBoxLayout(page);
|
|
|
|
if (!shortcutEditor) {
|
|
shortcutEditor = new KKeySequenceWidget(page);
|
|
QObject::connect(shortcutEditor.data(), SIGNAL(keySequenceChanged(QKeySequence)), dialog, SLOT(settingsModified()));
|
|
}
|
|
|
|
shortcutEditor.data()->setKeySequence(q->globalShortcut().primary());
|
|
layout->addWidget(shortcutEditor.data());
|
|
layout->addStretch();
|
|
dialog->addPage(page, i18n("Keyboard Shortcut"), "preferences-desktop-keyboard");
|
|
|
|
QObject::connect(dialog, SIGNAL(applyClicked()), q, SLOT(configDialogFinished()), Qt::UniqueConnection);
|
|
QObject::connect(dialog, SIGNAL(okClicked()), q, SLOT(configDialogFinished()), Qt::UniqueConnection);
|
|
#endif
|
|
}
|
|
|
|
void AppletPrivate::configDialogFinished()
|
|
{
|
|
if (shortcutEditor) {
|
|
QKeySequence sequence = shortcutEditor.data()->keySequence();
|
|
if (sequence != q->globalShortcut().primary()) {
|
|
q->setGlobalShortcut(KShortcut(sequence));
|
|
emit q->configNeedsSaving();
|
|
}
|
|
}
|
|
|
|
if (!configLoader) {
|
|
// the config loader will trigger this for us, so we don't need to.
|
|
propagateConfigChanged();
|
|
if (KConfigDialog *dialog = qobject_cast<KConfigDialog *>(q->sender())) {
|
|
dialog->button(QDialogButtonBox::Apply)->setEnabled(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AppletPrivate::updateShortcuts()
|
|
{
|
|
if (isContainment) {
|
|
//a horrible hack to avoid clobbering corona settings
|
|
//we pull them out, then read, then put them back
|
|
QList<QString> names;
|
|
QList<QAction*> qactions;
|
|
names << "add sibling containment" << "configure shortcuts" << "lock widgets";
|
|
foreach (const QString &name, names) {
|
|
QAction *a = actions->action(name);
|
|
actions->takeAction(a); //FIXME this is stupid, KActionCollection needs a takeAction(QString) method
|
|
qactions << a;
|
|
}
|
|
|
|
actions->readSettings();
|
|
|
|
for (int i = 0; i < names.size(); ++i) {
|
|
QAction *a = qactions.at(i);
|
|
if (a) {
|
|
actions->addAction(names.at(i), a);
|
|
}
|
|
}
|
|
} else {
|
|
actions->readSettings();
|
|
}
|
|
}
|
|
|
|
void AppletPrivate::propagateConfigChanged()
|
|
{
|
|
if (isContainment) {
|
|
Containment *c = qobject_cast<Containment *>(q);
|
|
if (c) {
|
|
c->d->configChanged();
|
|
}
|
|
}
|
|
|
|
q->configChanged();
|
|
}
|
|
|
|
QString AppletPrivate::parentAppConstraint(const QString &parentApp)
|
|
{
|
|
if (parentApp.isEmpty()) {
|
|
QCoreApplication *app = QCoreApplication::instance();
|
|
if (!app) {
|
|
return QString();
|
|
}
|
|
|
|
return QString("((not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '') or [X-KDE-ParentApp] == '%1')")
|
|
.arg(app->applicationName());
|
|
}
|
|
|
|
return QString("[X-KDE-ParentApp] == '%1'").arg(parentApp);
|
|
}
|
|
|
|
void AppletPrivate::setIsContainment(bool nowIsContainment, bool forceUpdate)
|
|
{
|
|
if (isContainment == nowIsContainment && !forceUpdate) {
|
|
return;
|
|
}
|
|
|
|
isContainment = nowIsContainment;
|
|
//FIXME I do not like this function.
|
|
//currently it's only called before ctmt/applet init, with (true,true), and I'm going to assume it stays that way.
|
|
//if someone calls it at some other time it'll cause headaches. :P
|
|
|
|
delete mainConfig;
|
|
mainConfig = 0;
|
|
|
|
Containment *c = q->containment();
|
|
if (c) {
|
|
c->d->checkContainmentFurniture();
|
|
}
|
|
}
|
|
|
|
// put all setup routines for script here. at this point we can assume that
|
|
// package exists and that we have a script engine
|
|
void AppletPrivate::setupScriptSupport()
|
|
{
|
|
if (!package) {
|
|
return;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
kDebug() << "setting up script support, package is in" << package->path()
|
|
<< ", main script is" << package->filePath("mainscript");
|
|
#endif
|
|
|
|
const QString translationsPath = package->filePath("translations");
|
|
if (!translationsPath.isEmpty()) {
|
|
KGlobal::dirs()->addResourceDir("locale", translationsPath);
|
|
KLocalizedString::insertCatalog(appletDescription.pluginName());
|
|
}
|
|
|
|
const QString xmlPath = package->filePath("mainconfigxml");
|
|
if (!xmlPath.isEmpty()) {
|
|
QFile file(xmlPath);
|
|
KConfigGroup config = q->config();
|
|
configLoader = new ConfigLoader(&config, &file);
|
|
QObject::connect(configLoader, SIGNAL(configChanged()), q, SLOT(propagateConfigChanged()));
|
|
}
|
|
|
|
if (!package->filePath("mainconfigui").isEmpty()) {
|
|
q->setHasConfigurationInterface(true);
|
|
}
|
|
}
|
|
|
|
QString AppletPrivate::globalName() const
|
|
{
|
|
if (!appletDescription.isValid()) {
|
|
return QString();
|
|
}
|
|
|
|
return appletDescription.service()->library();
|
|
}
|
|
|
|
QString AppletPrivate::instanceName()
|
|
{
|
|
if (!appletDescription.isValid()) {
|
|
return QString();
|
|
}
|
|
|
|
return appletDescription.service()->library() + QString::number(appletId);
|
|
}
|
|
|
|
void AppletPrivate::scheduleConstraintsUpdate(Plasma::Constraints c)
|
|
{
|
|
// Don't start up a timer if we're just starting up
|
|
// flushPendingConstraints will be called by Corona
|
|
if (started && !constraintsTimer.isActive() && !(c & Plasma::StartupCompletedConstraint)) {
|
|
constraintsTimer.start(0, q);
|
|
}
|
|
|
|
if (c & Plasma::StartupCompletedConstraint) {
|
|
started = true;
|
|
}
|
|
|
|
pendingConstraints |= c;
|
|
}
|
|
|
|
void AppletPrivate::scheduleModificationNotification()
|
|
{
|
|
// modificationsTimer is not allocated until we get our notice of being started
|
|
if (modificationsTimer) {
|
|
// schedule a save
|
|
if (modificationsTimer->isActive()) {
|
|
modificationsTimer->stop();
|
|
}
|
|
|
|
modificationsTimer->start(1000, q);
|
|
}
|
|
}
|
|
|
|
KConfigGroup *AppletPrivate::mainConfigGroup()
|
|
{
|
|
if (mainConfig) {
|
|
return mainConfig;
|
|
}
|
|
|
|
if (isContainment) {
|
|
Corona *corona = static_cast<Containment*>(q)->corona();
|
|
KConfigGroup containmentConfig;
|
|
//kDebug() << "got a corona, baby?" << (QObject*)corona << (QObject*)q;
|
|
|
|
if (corona) {
|
|
containmentConfig = KConfigGroup(corona->config(), "Containments");
|
|
} else {
|
|
containmentConfig = KConfigGroup(KSharedConfig::openConfig(), "Containments");
|
|
}
|
|
|
|
mainConfig = new KConfigGroup(&containmentConfig, QString::number(appletId));
|
|
} else {
|
|
KConfigGroup appletConfig;
|
|
|
|
Containment *c = q->containment();
|
|
Applet *parentApplet = qobject_cast<Applet *>(q->parent());
|
|
if (parentApplet && parentApplet != static_cast<Applet *>(c)) {
|
|
// this applet is nested inside another applet! use it's config
|
|
// as the parent group in the config
|
|
appletConfig = parentApplet->config();
|
|
appletConfig = KConfigGroup(&appletConfig, "Applets");
|
|
} else if (c) {
|
|
// applet directly in a Containment, as usual
|
|
appletConfig = c->config();
|
|
appletConfig = KConfigGroup(&appletConfig, "Applets");
|
|
} else {
|
|
kWarning() << "requesting config for" << q->title() << "without a containment!";
|
|
appletConfig = KConfigGroup(KSharedConfig::openConfig(), "Applets");
|
|
}
|
|
|
|
mainConfig = new KConfigGroup(&appletConfig, QString::number(appletId));
|
|
}
|
|
|
|
return mainConfig;
|
|
}
|
|
|
|
QString AppletPrivate::visibleFailureText(const QString &reason)
|
|
{
|
|
QString text;
|
|
|
|
if (reason.isEmpty()) {
|
|
text = i18n("This object could not be created.");
|
|
} else {
|
|
text = i18n("This object could not be created for the following reason:<p><b>%1</b></p>", reason);
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
void AppletPrivate::resetConfigurationObject()
|
|
{
|
|
// make sure mainConfigGroup exists in all cases
|
|
mainConfigGroup();
|
|
|
|
mainConfig->deleteGroup();
|
|
delete mainConfig;
|
|
mainConfig = 0;
|
|
|
|
if (!q->containment()) {
|
|
return;
|
|
}
|
|
Corona * corona = q->containment()->corona();
|
|
if (corona) {
|
|
corona->requireConfigSync();
|
|
}
|
|
}
|
|
|
|
uint AppletPrivate::s_maxAppletId = 0;
|
|
QSet<QString> AppletPrivate::s_customCategories;
|
|
|
|
} //namespace Plasma
|