From dba549382b8ff6daa63626d23b52ba3e47ba1c98 Mon Sep 17 00:00:00 2001 From: Marco Martin Date: Tue, 28 Feb 2012 19:22:59 +0100 Subject: [PATCH] coronabase, a pure qobject corona --- CMakeLists.txt | 1 + applet.h | 2 + containment.h | 1 + coronabase.cpp | 805 +++++++++++++++++++++++++++++++++++++++++ coronabase.h | 438 ++++++++++++++++++++++ private/coronabase_p.h | 72 ++++ 6 files changed, 1319 insertions(+) create mode 100644 coronabase.cpp create mode 100644 coronabase.h create mode 100644 private/coronabase_p.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 73585fc79..a08fd0d8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ set(plasma_LIB_SRCS configloader.cpp containmentactions.cpp containmentactionspluginsconfig.cpp + coronabase.cpp datacontainer.cpp dataengine.cpp dataenginemanager.cpp diff --git a/applet.h b/applet.h index d6aab20ba..d4201b8f8 100644 --- a/applet.h +++ b/applet.h @@ -1014,6 +1014,8 @@ class PLASMA_EXPORT Applet : public QGraphicsWidget //Corona needs to access setFailedToLaunch and init friend class Corona; friend class CoronaPrivate; + friend class CoronaBase; + friend class CoronaBasePrivate; friend class Containment; friend class ContainmentPrivate; friend class AppletScript; diff --git a/containment.h b/containment.h index 10d57f910..b935d6bff 100644 --- a/containment.h +++ b/containment.h @@ -624,6 +624,7 @@ Q_SIGNALS: friend class Applet; friend class AppletPrivate; friend class CoronaPrivate; + friend class CoronaBasePrivate; friend class ContainmentPrivate; friend class ContainmentActions; friend class PopupApplet; diff --git a/coronabase.cpp b/coronabase.cpp new file mode 100644 index 000000000..863e01c74 --- /dev/null +++ b/coronabase.cpp @@ -0,0 +1,805 @@ +/* + * Copyright 2007 Matt Broadstone + * Copyright 2007-2011 Aaron Seigo + * Copyright 2007 Riccardo Iaconelli + * Copyright (c) 2009 Chani Armitage + * + * 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 "coronabase.h" +#include "private/coronabase_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "abstractdialogmanager.h" +#include "abstracttoolbox.h" +#include "containment.h" +#include "containmentactionspluginsconfig.h" +#include "pluginloader.h" +#include "private/applet_p.h" +#include "private/containment_p.h" +#include "tooltipmanager.h" +#include "view.h" + +using namespace Plasma; + +namespace Plasma +{ + +bool CoronaBasePrivate::s_positioningContainments = false; + +CoronaBase::CoronaBase(QObject *parent) + : QObject(parent), + d(new CoronaBasePrivate(this)) +{ +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Corona ctor start"; +#endif + d->init(); + //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel))); +} + +CoronaBase::~CoronaBase() +{ + KConfigGroup trans(KGlobal::config(), "PlasmaTransientsConfig"); + trans.deleteGroup(); + + delete d; +} + +void CoronaBase::setAppletMimeType(const QString &type) +{ + d->mimetype = type; +} + +QString CoronaBase::appletMimeType() +{ + return d->mimetype; +} + +void CoronaBase::setDefaultContainmentPlugin(const QString &name) +{ + // we could check if it is in: + // Containment::listContainments().contains(name) || + // Containment::listContainments(QString(), KGlobal::mainComponent().componentName()).contains(name) + // but that seems like overkill + d->defaultContainmentPlugin = name; +} + +QString CoronaBase::defaultContainmentPlugin() const +{ + return d->defaultContainmentPlugin; +} + +void CoronaBase::saveLayout(const QString &configName) const +{ + KSharedConfigPtr c; + + if (configName.isEmpty() || configName == d->configName) { + c = config(); + } else { + c = KSharedConfig::openConfig(configName, KConfig::SimpleConfig); + } + + d->saveLayout(c); +} + +void CoronaBase::exportLayout(KConfigGroup &config, QList containments) +{ + foreach (const QString &group, config.groupList()) { + KConfigGroup cg(&config, group); + cg.deleteGroup(); + } + + //temporarily unlock so that removal works + ImmutabilityType oldImm = immutability(); + d->immutability = Mutable; + + KConfigGroup dest(&config, "Containments"); + KConfigGroup dummy; + foreach (Plasma::Containment *c, containments) { + c->save(dummy); + c->config().reparent(&dest); + + //ensure the containment is unlocked + //this is done directly because we have to bypass any SystemImmutable checks + c->Applet::d->immutability = Mutable; + foreach (Applet *a, c->applets()) { + a->d->immutability = Mutable; + } + + c->destroy(false); + } + + //restore immutability + d->immutability = oldImm; + + config.sync(); +} + +void CoronaBase::requestConfigSync() +{ + // constant controlling how long between requesting a configuration sync + // and one happening should occur. currently 10 seconds + static const int CONFIG_SYNC_TIMEOUT = 10000; + + // TODO: should we check into our immutability before doing this? + + //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT + // after the first time this is called. not much of a heuristic for save points, but + // it should at least compress these activities a bit and provide a way for applet + // authors to ween themselves from the sync() disease. A more interesting/dynamic + // algorithm for determining when to actually sync() to disk might be better, though. + if (!d->configSyncTimer.isActive()) { + d->configSyncTimer.start(CONFIG_SYNC_TIMEOUT); + } +} + +void CoronaBase::requireConfigSync() +{ + d->syncConfig(); +} + +void CoronaBase::initializeLayout(const QString &configName) +{ + clearContainments(); + loadLayout(configName); + + if (d->containments.isEmpty()) { + loadDefaultLayout(); + if (!d->containments.isEmpty()) { + requestConfigSync(); + } + } + + if (config()->isImmutable()) { + setImmutability(SystemImmutable); + } else { + KConfigGroup coronaConfig(config(), "General"); + setImmutability((ImmutabilityType)coronaConfig.readEntry("immutability", (int)Mutable)); + } +} + +void CoronaBase::layoutContainments() +{ + if (CoronaBasePrivate::s_positioningContainments) { + return; + } + + CoronaBasePrivate::s_positioningContainments = true; + + //TODO: we should avoid running this too often; consider compressing requests + // with a timer. + QList c = containments(); + QMutableListIterator it(c); + + while (it.hasNext()) { + Containment *containment = it.next(); + if (containment->containmentType() == Containment::PanelContainment || + containment->containmentType() == Containment::CustomPanelContainment) { + // weed out all containments we don't care about at all + // e.g. Panels and ourself + it.remove(); + continue; + } + } + + if (c.isEmpty()) { + CoronaBasePrivate::s_positioningContainments = false; + return; + } + + int column = 0; + int x = 0; + int y = 0; + int rowHeight = 0; + + it.toFront(); + while (it.hasNext()) { + Containment *containment = it.next(); + containment->setPos(x, y); + //kDebug() << ++count << "setting to" << x << y; + + int height = containment->size().height(); + if (height > rowHeight) { + rowHeight = height; + } + + ++column; + + if (column == CONTAINMENT_COLUMNS) { + column = 0; + x = 0; + y += rowHeight + INTER_CONTAINMENT_MARGIN + TOOLBOX_MARGIN; + rowHeight = 0; + } else { + x += containment->size().width() + INTER_CONTAINMENT_MARGIN; + } + //kDebug() << "column: " << column << "; x " << x << "; y" << y << "; width was" + // << containment->size().width(); + } + + CoronaBasePrivate::s_positioningContainments = false; +} + + +void CoronaBase::loadLayout(const QString &configName) +{ + if (!configName.isEmpty() && configName != d->configName) { + // if we have a new config name passed in, then use that as the config file for this Corona + d->config = 0; + d->configName = configName; + } + + KConfigGroup conf(config(), QString()); + d->importLayout(conf, false); +} + +QList CoronaBase::importLayout(const KConfigGroup &conf) +{ + return d->importLayout(conf, true); +} + +Containment *CoronaBase::containmentForScreen(int screen, int desktop) const +{ + foreach (Containment *containment, d->containments) { + if (containment->screen() == screen && + (desktop < 0 || containment->desktop() == desktop) && + (containment->containmentType() == Containment::DesktopContainment || + containment->containmentType() == Containment::CustomContainment)) { + return containment; + } + } + + return 0; +} + +Containment *CoronaBase::containmentForScreen(int screen, int desktop, + const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs) +{ + Containment *containment = containmentForScreen(screen, desktop); + if (!containment && !defaultPluginIfNonExistent.isEmpty()) { + // screen requests are allowed to bypass immutability + if (screen >= 0 && screen < numScreens() && + desktop >= -1 && desktop < KWindowSystem::numberOfDesktops()) { + containment = d->addContainment(defaultPluginIfNonExistent, defaultArgs, 0, false); + if (containment) { + containment->setScreen(screen, desktop); + } + } + } + + return containment; +} + +QList CoronaBase::containments() const +{ + return d->containments; +} + +void CoronaBase::clearContainments() +{ + foreach (Containment *containment, d->containments) { + containment->clearApplets(); + } +} + +KSharedConfigPtr CoronaBase::config() const +{ + if (!d->config) { + d->config = KSharedConfig::openConfig(d->configName, KConfig::SimpleConfig); + } + + return d->config; +} + +Containment *CoronaBase::addContainment(const QString &name, const QVariantList &args) +{ + if (d->immutability == Mutable) { + return d->addContainment(name, args, 0, false); + } + + return 0; +} + +Containment *CoronaBase::addContainmentDelayed(const QString &name, const QVariantList &args) +{ + if (d->immutability == Mutable) { + return d->addContainment(name, args, 0, true); + } + + return 0; +} + +int CoronaBase::numScreens() const +{ + return 1; +} + +QRect CoronaBase::screenGeometry(int id) const +{ + //This is unreliable, give better implementations in subclasses + return qApp->desktop()->screenGeometry(id); +} + +QRegion CoronaBase::availableScreenRegion(int id) const +{ + return QRegion(screenGeometry(id)); +} + +void CoronaBase::loadDefaultLayout() +{ +} + +void CoronaBase::setPreferredToolBoxPlugin(const Containment::Type type, const QString &plugin) +{ + d->toolBoxPlugins[type] = plugin; + //TODO: react to plugin changes on the fly? still don't see the use case (maybe for laptops that become tablets?) +} + +QString CoronaBase::preferredToolBoxPlugin(const Containment::Type type) const +{ + return d->toolBoxPlugins.value(type); +} + +ImmutabilityType CoronaBase::immutability() const +{ + return d->immutability; +} + +void CoronaBase::setImmutability(const ImmutabilityType immutable) +{ + if (d->immutability == immutable || d->immutability == SystemImmutable) { + return; + } + +#ifndef NDEBUG + kDebug() << "setting immutability to" << immutable; +#endif + d->immutability = immutable; + d->updateContainmentImmutability(); + //tell non-containments that might care (like plasmaapp or a custom corona) + emit immutabilityChanged(immutable); + + //update our actions + QAction *action = d->actions.action("lock widgets"); + if (action) { + if (d->immutability == SystemImmutable) { + action->setEnabled(false); + action->setVisible(false); + } else { + bool unlocked = d->immutability == Mutable; + action->setText(unlocked ? i18n("Lock Widgets") : i18n("Unlock Widgets")); + action->setIcon(KIcon(unlocked ? "object-locked" : "object-unlocked")); + action->setEnabled(true); + action->setVisible(true); + } + } + + if (d->immutability != SystemImmutable) { + KConfigGroup cg(config(), "General"); + + // we call the dptr member directly for locked since isImmutable() + // also checks kiosk and parent containers + cg.writeEntry("immutability", (int)d->immutability); + requestConfigSync(); + } +} + +QList CoronaBase::freeEdges(int screen) const +{ + QList freeEdges; + freeEdges << Plasma::TopEdge << Plasma::BottomEdge + << Plasma::LeftEdge << Plasma::RightEdge; + + foreach (Containment *containment, containments()) { + if (containment->screen() == screen && + freeEdges.contains(containment->location())) { + freeEdges.removeAll(containment->location()); + } + } + + return freeEdges; +} + +QAction *CoronaBase::action(QString name) const +{ + return d->actions.action(name); +} + +void CoronaBase::addAction(QString name, QAction *action) +{ + d->actions.addAction(name, action); +} + +KAction* CoronaBase::addAction(QString name) +{ + return d->actions.addAction(name); +} + +QList CoronaBase::actions() const +{ + return d->actions.actions(); +} + +void CoronaBase::enableAction(const QString &name, bool enable) +{ + QAction *action = d->actions.action(name); + if (action) { + action->setEnabled(enable); + action->setVisible(enable); + } +} + +void CoronaBase::updateShortcuts() +{ + QMutableListIterator > it(d->actionCollections); + while (it.hasNext()) { + it.next(); + KActionCollection *collection = it.value().data(); + if (!collection) { + // get rid of KActionCollections that have been deleted behind our backs + it.remove(); + continue; + } + + collection->readSettings(); + if (d->shortcutsDlg) { + d->shortcutsDlg.data()->addCollection(collection); + } + } +} + +void CoronaBase::addShortcuts(KActionCollection *newShortcuts) +{ + d->actionCollections << newShortcuts; + if (d->shortcutsDlg) { + d->shortcutsDlg.data()->addCollection(newShortcuts); + } +} + +void CoronaBase::setContainmentActionsDefaults(Containment::Type containmentType, const ContainmentActionsPluginsConfig &config) +{ + d->containmentActionsDefaults.insert(containmentType, config); +} + +ContainmentActionsPluginsConfig CoronaBase::containmentActionsDefaults(Containment::Type containmentType) +{ + return d->containmentActionsDefaults.value(containmentType); +} + +void CoronaBase::setDialogManager(AbstractDialogManager *dialogManager) +{ + d->dialogManager = dialogManager; +} + +AbstractDialogManager *CoronaBase::dialogManager() +{ + return d->dialogManager.data(); +} + +CoronaBasePrivate::CoronaBasePrivate(CoronaBase *corona) + : q(corona), + immutability(Mutable), + mimetype("text/x-plasmoidservicename"), + defaultContainmentPlugin("desktop"), + config(0), + actions(corona) +{ + if (KGlobal::hasMainComponent()) { + configName = KGlobal::mainComponent().componentName() + "-appletsrc"; + } else { + configName = "plasma-appletsrc"; + } +} + +CoronaBasePrivate::~CoronaBasePrivate() +{ + qDeleteAll(containments); +} + +void CoronaBasePrivate::init() +{ + configSyncTimer.setSingleShot(true); + QObject::connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig())); + + //some common actions + actions.setConfigGroup("Shortcuts"); + + KAction *lockAction = actions.addAction("lock widgets"); + QObject::connect(lockAction, SIGNAL(triggered(bool)), q, SLOT(toggleImmutability())); + lockAction->setText(i18n("Lock Widgets")); + lockAction->setAutoRepeat(true); + lockAction->setIcon(KIcon("object-locked")); + lockAction->setData(AbstractToolBox::ControlTool); + lockAction->setShortcut(KShortcut("alt+d, l")); + lockAction->setShortcutContext(Qt::ApplicationShortcut); + + //FIXME this doesn't really belong here. desktop KCM maybe? + //but should the shortcuts be per-app or really-global? + //I don't know how to make kactioncollections use plasmarc + KAction *action = actions.addAction("configure shortcuts"); + QObject::connect(action, SIGNAL(triggered()), q, SLOT(showShortcutConfig())); + action->setText(i18n("Shortcut Settings")); + action->setIcon(KIcon("configure-shortcuts")); + action->setAutoRepeat(false); + action->setData(AbstractToolBox::ConfigureTool); + //action->setShortcut(KShortcut("ctrl+h")); + action->setShortcutContext(Qt::ApplicationShortcut); + + //fake containment/applet actions + KActionCollection *containmentActions = AppletPrivate::defaultActions(q); //containment has to start with applet stuff + ContainmentPrivate::addDefaultActions(containmentActions); //now it's really containment + actionCollections << &actions << AppletPrivate::defaultActions(q) << containmentActions; + q->updateShortcuts(); +} + +void CoronaBasePrivate::showShortcutConfig() +{ + //show a kshortcutsdialog with the actions + KShortcutsDialog *dlg = shortcutsDlg.data(); + if (!dlg) { + dlg = new KShortcutsDialog(); + dlg->setModal(false); + dlg->setAttribute(Qt::WA_DeleteOnClose, true); + QObject::connect(dlg, SIGNAL(saved()), q, SIGNAL(shortcutsChanged())); + + dlg->addCollection(&actions); + QMutableListIterator > it(actionCollections); + while (it.hasNext()) { + it.next(); + KActionCollection *collection = it.value().data(); + if (!collection) { + // get rid of KActionCollections that have been deleted behind our backs + it.remove(); + continue; + } + + dlg->addCollection(collection); + } + } + + KWindowSystem::setOnDesktop(dlg->winId(), KWindowSystem::currentDesktop()); + dlg->configure(); + dlg->raise(); +} + +void CoronaBasePrivate::toggleImmutability() +{ + if (immutability == Mutable) { + q->setImmutability(UserImmutable); + } else { + q->setImmutability(Mutable); + } +} + +void CoronaBasePrivate::saveLayout(KSharedConfigPtr cg) const +{ + KConfigGroup containmentsGroup(cg, "Containments"); + foreach (const Containment *containment, containments) { + QString cid = QString::number(containment->id()); + KConfigGroup containmentConfig(&containmentsGroup, cid); + containment->save(containmentConfig); + } +} + +void CoronaBasePrivate::updateContainmentImmutability() +{ + foreach (Containment *c, containments) { + // we need to tell each containment that immutability has been altered + c->updateConstraints(ImmutableConstraint); + } +} + +void CoronaBasePrivate::containmentDestroyed(QObject *obj) +{ + // we do a static_cast here since it really isn't an Containment by this + // point anymore since we are in the qobject dtor. we don't actually + // try and do anything with it, we just need the value of the pointer + // so this unsafe looking code is actually just fine. + Containment* containment = static_cast(obj); + int index = containments.indexOf(containment); + + if (index > -1) { + containments.removeAt(index); + q->requestConfigSync(); + } + } + +void CoronaBasePrivate::syncConfig() +{ + q->config()->sync(); + emit q->configSynced(); +} + +Containment *CoronaBasePrivate::addContainment(const QString &name, const QVariantList &args, uint id, bool delayedInit) +{ + QString pluginName = name; + Containment *containment = 0; + Applet *applet = 0; + + //kDebug() << "Loading" << name << args << id; + + if (pluginName.isEmpty() || pluginName == "default") { + // default to the desktop containment + pluginName = defaultContainmentPlugin; + } + + bool loadingNull = pluginName == "null"; + if (!loadingNull) { + applet = PluginLoader::self()->loadApplet(pluginName, id, args); + containment = dynamic_cast(applet); + } + + if (!containment) { + if (!loadingNull) { +#ifndef NDEBUG + kDebug() << "loading of containment" << name << "failed."; +#endif + } + + // in case we got a non-Containment from Applet::loadApplet or + // a null containment was requested + if (applet) { + // the applet probably doesn't know what's hit it, so let's pretend it can be + // initialized to make assumptions in the applet's dtor safer + applet->init(); + delete applet; + } + applet = containment = new Containment(0, 0, id); + + if (loadingNull) { + containment->setDrawWallpaper(false); + } else { + containment->setFailedToLaunch(false); + } + + // we want to provide something and don't care about the failure to launch + containment->setFormFactor(Plasma::Planar); + } + + // if this is a new containment, we need to ensure that there are no stale + // configuration data around + if (id == 0) { + KConfigGroup conf(q->config(), "Containments"); + conf = KConfigGroup(&conf, QString::number(containment->id())); + conf.deleteGroup(); + } + + applet->d->isContainment = true; + applet->d->setIsContainment(true, true); + containments.append(containment); + + if (!delayedInit) { + containment->init(); + KConfigGroup cg = containment->config(); + containment->restore(cg); + containment->updateConstraints(Plasma::StartupCompletedConstraint); + containment->save(cg); + q->requestConfigSync(); + containment->flushPendingConstraintsEvents(); + } + + QObject::connect(containment, SIGNAL(destroyed(QObject*)), + q, SLOT(containmentDestroyed(QObject*))); + QObject::connect(containment, SIGNAL(configNeedsSaving()), + q, SLOT(requestConfigSync())); + QObject::connect(containment, SIGNAL(releaseVisualFocus()), + q, SIGNAL(releaseVisualFocus())); + QObject::connect(containment, SIGNAL(screenChanged(int,int,Plasma::Containment*)), + q, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*))); + + if (!delayedInit) { + emit q->containmentAdded(containment); + } + + return containment; +} + +QList CoronaBasePrivate::importLayout(const KConfigGroup &conf, bool mergeConfig) +{ + if (!conf.isValid()) { + return QList(); + } + + QList newContainments; + QSet containmentsIds; + + foreach (Containment *containment, containments) { + containmentsIds.insert(containment->id()); + } + + KConfigGroup containmentsGroup(&conf, "Containments"); + + foreach (const QString &group, containmentsGroup.groupList()) { + KConfigGroup containmentConfig(&containmentsGroup, group); + + if (containmentConfig.entryMap().isEmpty()) { + continue; + } + + uint cid = group.toUInt(); + if (containmentsIds.contains(cid)) { + cid = ++AppletPrivate::s_maxAppletId; + } else if (cid > AppletPrivate::s_maxAppletId) { + AppletPrivate::s_maxAppletId = cid; + } + + if (mergeConfig) { + KConfigGroup realConf(q->config(), "Containments"); + realConf = KConfigGroup(&realConf, QString::number(cid)); + // in case something was there before us + realConf.deleteGroup(); + containmentConfig.copyTo(&realConf); + } + + //kDebug() << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group; +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Adding Containment" << containmentConfig.readEntry("plugin", QString()); +#endif + Containment *c = addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(), cid, true); + if (!c) { + continue; + } + + newContainments.append(c); + containmentsIds.insert(c->id()); + + c->init(); +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Init Containment" << c->pluginName(); +#endif + c->restore(containmentConfig); +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Restored Containment" << c->pluginName(); +#endif + } + + foreach (Containment *containment, newContainments) { + containment->updateConstraints(Plasma::StartupCompletedConstraint); + containment->d->initApplets(); + emit q->containmentAdded(containment); +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment" << containment->name(); +#endif + } + + return newContainments; +} + +} // namespace Plasma + + + +#include "moc_coronabase.cpp" diff --git a/coronabase.h b/coronabase.h new file mode 100644 index 000000000..ed5691594 --- /dev/null +++ b/coronabase.h @@ -0,0 +1,438 @@ +/* + * Copyright 2007 Aaron Seigo + * Copyright 2007 Matt Broadstone + * Copyright 2012 Marco MArtin + * + * 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. + */ + +#ifndef PLASMA_CORONABASE_H +#define PLASMA_CORONABASE_H + +#include + +#include +#include +#include + +class QGraphicsGridLayout; +class QAction; + +class KAction; + +namespace Plasma +{ + +class CoronaBasePrivate; +class ContainmentActionsPluginsConfig; +class AbstractDialogManager; + +/** + * @class CoronaBase plasma/CoronaBase.h + * + * @short A QGraphicsScene for Plasma::Applets + */ +class PLASMA_EXPORT CoronaBase : public QObject +{ + Q_OBJECT + +//typedef QHash > layouts; + +public: + explicit CoronaBase(QObject * parent = 0); + ~CoronaBase(); + + /** + * Sets the mimetype of Drag/Drop items. Default is + * text/x-plasmoidservicename + */ + void setAppletMimeType(const QString &mimetype); + + /** + * The current mime type of Drag/Drop items. + */ + QString appletMimeType(); + + /** + * @return the default containment plugin type + * @since 4.7 + */ + QString defaultContainmentPlugin() const; + + /** + * @return all containments on this CoronaBase + */ + QList containments() const; + + /** + * Clear the CoronaBase from all applets. + */ + void clearContainments(); + + /** + * Returns the config file used to store the configuration for this CoronaBase + */ + KSharedConfig::Ptr config() const; + + /** + * Adds a Containment to the CoronaBase + * + * @param name the plugin name for the containment, as given by + * KPluginInfo::pluginName(). If an empty string is passed in, the default + * containment plugin will be used (usually DesktopContainment). If the + * string literal "null" is passed in, then no plugin will be loaded and + * a simple Containment object will be created instead. + * @param args argument list to pass to the containment + * + * @return a pointer to the containment on success, or 0 on failure. Failure can be + * caused by too restrictive of an Immutability type, as containments cannot be added + * when widgets are locked, or if the requested containment plugin can not be located + * or successfully loaded. + */ + Containment *addContainment(const QString &name, const QVariantList &args = QVariantList()); + + /** + * Loads a containment with delayed initialization, primarily useful + * for implementations of loadDefaultLayout. The caller is responsible + * for all initializating, saving and notification of a new containment. + * + * @param name the plugin name for the containment, as given by + * KPluginInfo::pluginName(). If an empty string is passed in, the defalt + * containment plugin will be used (usually DesktopContainment). If the + * string literal "null" is passed in, then no plugin will be loaded and + * a simple Containment object will be created instead. + * @param args argument list to pass to the containment + * + * @return a pointer to the containment on success, or 0 on failure. Failure can + * be caused by the Immutability type being too restrictive, as containments can't be added + * when widgets are locked, or if the requested containment plugin can not be located + * or successfully loaded. + * @see addContainment + **/ + Containment *addContainmentDelayed(const QString &name, + const QVariantList &args = QVariantList()); + + /** + * Returns the Containment, if any, for a given physical screen and desktop + * + * @param screen number of the physical screen to locate + * @param desktop the virtual desktop) to locate; if < 0 then it will + * simply return the first Containment associated with screen + */ + Containment *containmentForScreen(int screen, int desktop = -1) const; + + /** + * Returns the Containment for a given physical screen and desktop, creating one + * if none exists + * + * @param screen number of the physical screen to locate + * @param desktop the virtual desktop) to locate; if < 0 then it will + * simply return the first Containment associated with screen + * @param defaultPluginIfNonExistent the plugin to load by default; "null" is an empty + * Containment and "default" creates the default plugin + * @param defaultArgs optional arguments to pass in when creating a Containment if needed + * @since 4.6 + */ + Containment *containmentForScreen(int screen, int desktop, + const QString &defaultPluginIfNonExistent, + const QVariantList &defaultArgs = QVariantList()); + + /** + * Returns the number of screens available to plasma. + * Subclasses should override this method as the default + * implementation returns a meaningless value. + */ + virtual int numScreens() const; + + /** + * Returns the geometry of a given screen. + * Valid screen ids are 0 to numScreen()-1, or -1 for the full desktop geometry. + * Subclasses should override this method as the default + * implementation returns a meaningless value. + */ + virtual QRect screenGeometry(int id) const; + + /** + * Returns the available region for a given screen. + * The available region excludes panels and similar windows. + * Valid screen ids are 0 to numScreens()-1. + * By default this method returns a rectangular region + * equal to screenGeometry(id); subclasses that need another + * behavior should override this method. + */ + virtual QRegion availableScreenRegion(int id) const; + + /** + * This method is useful in order to retrieve the list of available + * screen edges for panel type containments. + * @param screen the id of the screen to look for free edges. + * @returns a list of free edges not filled with panel type containments. + */ + QList freeEdges(int screen) const; + + /** + * Returns the QAction with the given name from our collection + */ + QAction *action(QString name) const; + + /** + * Adds the action to our collection under the given name + */ + void addAction(QString name, QAction *action); + + /** + * Returns all the actions in our collection + */ + QList actions() const; + + /** + * convenience function - enables or disables an action by name + * + * @param name the name of the action in our collection + * @param enable true to enable, false to disable + */ + void enableAction(const QString &name, bool enable); + + /** + * @since 4.3 + * Updates keyboard shortcuts for all the CoronaBase's actions. + * If you've added actions to the CoronaBase you'll need to + * call this for them to be configurable. + */ + void updateShortcuts(); + + /** + * @since 4.3 + * Adds a set of actions to the shortcut config dialog. + * don't use this on actions in the CoronaBase's own actioncollection, + * those are handled automatically. this is for stuff outside of that. + */ + void addShortcuts(KActionCollection *newShortcuts); + + /** + * @since 4.3 + * Creates an action in our collection under the given name + * @return the new action + * FIXME I'm wrapping so much of kactioncollection API now, maybe I should just expose the + * collection itself :P + */ + KAction* addAction(QString name); + + /** + * @since 4.4 + * Sets the default containmentactions plugins for the given containment type + */ + void setContainmentActionsDefaults(Containment::Type containmentType, const ContainmentActionsPluginsConfig &config); + + /** + * @since 4.4 + * Returns the default containmentactions plugins for the given containment type + */ + ContainmentActionsPluginsConfig containmentActionsDefaults(Containment::Type containmentType); + + /** + * @param the AbstractDialogManager implementaion + * + * @since 4.5 + */ + void setDialogManager(AbstractDialogManager *manager); + + /** + * @return the AbstractDialogManager that will show dialogs used by applets, like configuration dialogs + * + * @since 4.5 + */ + AbstractDialogManager *dialogManager(); + + /** + * Returns the name of the preferred plugin to be used as containment toolboxes. + * CustomContainments and CustomPanelContainments can still override it as their liking. It's also not guaranteed that the plugin will actually exist. + * + * @param type the containment type of which we want to know the associated toolbox plugin + * @since 4.6 + */ + QString preferredToolBoxPlugin(const Containment::Type type) const; + + /** + * Imports an applet layout from a config file. The results will be added to the + * current set of Containments. + * + * @param config the name of the config file to load from, + * or the default config file if QString() + * @return the list of containments that were loaded + * @since 4.6 + */ + QList importLayout(const KConfigGroup &config); + + /** + * Exports a set of containments to a config file. + * + * @param config the config group to save to + * @param containments the list of containments to save + * @since 4.6 + */ + void exportLayout(KConfigGroup &config, QList containments); + +public Q_SLOTS: + /** + * Initializes the layout from a config file. This will first clear any existing + * Containments, load a layout from the requested configuration file, request the + * default layout if needed and update immutability. + * + * @param config the name of the config file to load from, + * or the default config file if QString() + */ + void initializeLayout(const QString &config = QString()); + + /** + * Load applet layout from a config file. The results will be added to the + * current set of Containments. + * + * @param config the name of the config file to load from, + * or the default config file if QString() + */ + void loadLayout(const QString &config = QString()); + + /** + * Save applets layout to file + * @param config the file to save to, or the default config file if QString() + */ + void saveLayout(const QString &config = QString()) const; + + /** + * @return The type of immutability of this CoronaBase + */ + ImmutabilityType immutability() const; + + /** + * Sets the immutability type for this CoronaBase (not immutable, + * user immutable or system immutable) + * @param immutable the new immutability type of this applet + */ + void setImmutability(const ImmutabilityType immutable); + + /** + * Schedules a flush-to-disk synchronization of the configuration state + * at the next convenient moment. + */ + void requestConfigSync(); + + /** + * Schedules a time sensitive flush-to-disk synchronization of the + * configuration state. Since this method does not provide any sort of + * event compression, it should only be used when an *immediate* disk + * sync is *absolutely* required. Otherwise, use @see requestConfigSync() + * which does do event compression. + */ + void requireConfigSync(); + + /** + * @since 4.5 + * Layout the containments on this CoronaBase. The default implementation + * organizes them in a grid-like view, but subclasses can reimplement + * this slot to provide their own layout. + */ + virtual void layoutContainments(); + +Q_SIGNALS: + /** + * This signal indicates a new containment has been added to + * the CoronaBase + */ + void containmentAdded(Plasma::Containment *containment); + + /** + * This signal indicates that a containment has been newly + * associated (or dissociated) with a physical screen. + * + * @param wasScreen the screen it was associated with + * @param isScreen the screen it is now associated with + * @param containment the containment switching screens + */ + void screenOwnerChanged(int wasScreen, int isScreen, Plasma::Containment *containment); + + /** + * This signal indicates that an application launch, window + * creation or window focus event was triggered. This is used, for instance, + * to ensure that the Dashboard view in Plasma hides when such an event is + * triggered by an item it is displaying. + */ + void releaseVisualFocus(); + + /** + * This signal indicates that the configuration file was flushed to disc. + */ + void configSynced(); + + /** + * This signal inicates that a change in available screen goemetry occurred. + */ + void availableScreenRegionChanged(); + + /** + * emitted when immutability changes. + * this is for use by things that don't get contraints events, like plasmaapp. + * it's NOT for containments or applets or any of the other stuff on the scene. + * if your code's not in shells/ it probably shouldn't be using it. + */ + void immutabilityChanged(Plasma::ImmutabilityType immutability); + + /** + * @since 4.3 + * emitted when the user changes keyboard shortcut settings + * connect to this if you've put some extra shortcuts in your app + * that are NOT in CoronaBase's actioncollection. + * if your code's not in shells/ it probably shouldn't be using this function. + * @see addShortcuts + */ + void shortcutsChanged(); + +protected: + /** + * Loads the default (system wide) layout for this user + **/ + virtual void loadDefaultLayout(); + + /** + * @return The preferred toolbox plugin name for a given containment type. + * @param type the containment type of which we want to know the preferred toolbox plugin. + * @param plugin the toolbox plugin name + * @since 4.6 + */ + void setPreferredToolBoxPlugin(const Containment::Type type, const QString &plugin); + + /** + * Sets the default containment plugin to try and load + * @since 4.7 + */ + void setDefaultContainmentPlugin(const QString &name); + +private: + CoronaBasePrivate *const d; + + Q_PRIVATE_SLOT(d, void containmentDestroyed(QObject*)) + Q_PRIVATE_SLOT(d, void syncConfig()) + Q_PRIVATE_SLOT(d, void toggleImmutability()) + Q_PRIVATE_SLOT(d, void showShortcutConfig()) + + friend class CoronaBasePrivate; + friend class View; +}; + +} // namespace Plasma + +#endif + diff --git a/private/coronabase_p.h b/private/coronabase_p.h new file mode 100644 index 000000000..b5f2796fb --- /dev/null +++ b/private/coronabase_p.h @@ -0,0 +1,72 @@ +/* + * Copyright 2007-2011 Aaron Seigo + * + * 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. + */ + +#ifndef PLASMA_CORONA_P_H +#define PLASMA_CORONA_P_H + +#include + +#include + +class KShortcutsDialog; + +namespace Plasma +{ + +class Containment; + +class CoronaBasePrivate +{ +public: + CoronaBasePrivate(CoronaBase *corona); + ~CoronaBasePrivate(); + + void init(); + void showShortcutConfig(); + void toggleImmutability(); + void saveLayout(KSharedConfigPtr cg) const; + void updateContainmentImmutability(); + void containmentDestroyed(QObject *obj); + void syncConfig(); + Containment *addContainment(const QString &name, const QVariantList &args, uint id, bool delayedInit); + void offscreenWidgetDestroyed(QObject *); + QList importLayout(const KConfigGroup &conf, bool mergeConfig); + + static bool s_positioningContainments; + + CoronaBase *q; + ImmutabilityType immutability; + QString mimetype; + QString configName; + QString defaultContainmentPlugin; + KSharedConfigPtr config; + QTimer configSyncTimer; + QList containments; + QHash offscreenWidgets; + KActionCollection actions; + QMap containmentActionsDefaults; + QWeakPointer shortcutsDlg; + QWeakPointer dialogManager; + QHash toolBoxPlugins; + QList > actionCollections; +}; + +} + +#endif