/* * Copyright 2005 by Aaron Seigo * Copyright 2007 by Riccardo Iaconelli * Copyright 2008 by Ménard Alexis * 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 "applet.h" #include "private/applet_p.h" #include "config-plasma.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef PLASMA_NO_KUTILS #include #include #else #include #endif #ifndef PLASMA_NO_SOLID #include #endif #include "abstracttoolbox.h" #include "authorizationrule.h" #include "configloader.h" #include "containment.h" #include "corona.h" #include "dataenginemanager.h" #include "dialog.h" #include "package.h" #include "plasma.h" #include "scripting/appletscript.h" #include "svg.h" #include "framesvg.h" #include "popupapplet.h" #include "private/applethandle_p.h" #include "private/framesvg_p.h" #include "remote/authorizationmanager.h" #include "remote/authorizationmanager_p.h" #include "theme.h" #include "view.h" #include "widgets/iconwidget.h" #include "widgets/label.h" #include "tooltipmanager.h" #include "wallpaper.h" #include "paintutils.h" #include "abstractdialogmanager.h" #include "pluginloader.h" #include "private/associatedapplicationmanager_p.h" #include "private/containment_p.h" #include "private/package_p.h" #include "private/packages_p.h" #include "private/plasmoidservice_p.h" #include "private/popupapplet_p.h" #include "private/remotedataengine_p.h" #include "private/service_p.h" #include "ui_publish.h" namespace Plasma { Applet::Applet(const KPluginInfo &info, QGraphicsItem *parent, uint appletId) : QGraphicsWidget(parent), d(new AppletPrivate(KService::Ptr(), &info, appletId, this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point d->init(); } Applet::Applet(QGraphicsItem *parent, const QString &serviceID, uint appletId) : QGraphicsWidget(parent), d(new AppletPrivate(KService::serviceByStorageId(serviceID), 0, appletId, this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point d->init(); } Applet::Applet(QGraphicsItem *parent, const QString &serviceID, uint appletId, const QVariantList &args) : QGraphicsWidget(parent), d(new AppletPrivate(KService::serviceByStorageId(serviceID), 0, appletId, this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point QVariantList &mutableArgs = const_cast(args); if (!mutableArgs.isEmpty()) { mutableArgs.removeFirst(); if (!mutableArgs.isEmpty()) { mutableArgs.removeFirst(); } } d->args = mutableArgs; d->init(); } Applet::Applet(QObject *parentObject, const QVariantList &args) : QGraphicsWidget(0), d(new AppletPrivate( KService::serviceByStorageId(args.count() > 0 ? args[0].toString() : QString()), 0, args.count() > 1 ? args[1].toInt() : 0, this)) { // now remove those first two items since those are managed by Applet and subclasses shouldn't // need to worry about them. yes, it violates the constness of this var, but it lets us add // or remove items later while applets can just pretend that their args always start at 0 QVariantList &mutableArgs = const_cast(args); if (!mutableArgs.isEmpty()) { mutableArgs.removeFirst(); if (!mutableArgs.isEmpty()) { mutableArgs.removeFirst(); } } d->args = mutableArgs; setParent(parentObject); // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point d->init(); // the brain damage seen in the initialization list is due to the // inflexibility of KService::createInstance } Applet::Applet(const QString &packagePath, uint appletId, const QVariantList &args) : QGraphicsWidget(0), d(new AppletPrivate(KService::Ptr(new KService(packagePath + "/metadata.desktop")), 0, appletId, this)) { Q_UNUSED(args) // FIXME? d->init(packagePath); } Applet::~Applet() { //let people know that i will die emit appletDestroyed(this); // clean up our config dialog, if any delete KConfigDialog::exists(d->configDialogId()); delete d; } void Applet::init() { setFlag(ItemIsMovable, true); if (d->script) { d->setupScriptSupport(); if (!d->script->init() && !d->failed) { setFailedToLaunch(true, i18n("Script initialization failed")); } } } uint Applet::id() const { return d->appletId; } void Applet::save(KConfigGroup &g) const { if (d->transient) { return; } KConfigGroup group = g; if (!group.isValid()) { group = *d->mainConfigGroup(); } //kDebug() << "saving to" << group.name(); // we call the dptr member directly for locked since isImmutable() // also checks kiosk and parent containers group.writeEntry("immutability", (int)d->immutability); group.writeEntry("plugin", pluginName()); group.writeEntry("geometry", geometry()); group.writeEntry("zvalue", zValue()); if (!d->started) { return; } //kDebug() << pluginName() << "geometry is" << geometry() // << "pos is" << pos() << "bounding rect is" << boundingRect(); if (transform() == QTransform()) { group.deleteEntry("transform"); } else { QList m; QTransform t = transform(); m << t.m11() << t.m12() << t.m13() << t.m21() << t.m22() << t.m23() << t.m31() << t.m32() << t.m33(); group.writeEntry("transform", m); //group.writeEntry("transform", transformToString(transform())); } KConfigGroup appletConfigGroup(&group, "Configuration"); saveState(appletConfigGroup); if (d->configLoader) { // we're saving so we know its changed, we don't need or want the configChanged // signal bubbling up at this point due to that disconnect(d->configLoader, SIGNAL(configChanged()), this, SLOT(propagateConfigChanged())); d->configLoader->writeConfig(); connect(d->configLoader, SIGNAL(configChanged()), this, SLOT(propagateConfigChanged())); } } void Applet::restore(KConfigGroup &group) { QList m = group.readEntry("transform", QList()); if (m.count() == 9) { QTransform t(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]); setTransform(t); } qreal z = group.readEntry("zvalue", 0); if (z >= AppletPrivate::s_maxZValue) { AppletPrivate::s_maxZValue = z; } if (z > 0) { setZValue(z); } setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); QRectF geom = group.readEntry("geometry", QRectF()); if (geom.isValid()) { setGeometry(geom); } KConfigGroup shortcutConfig(&group, "Shortcuts"); QString shortcutText = shortcutConfig.readEntryUntranslated("global", QString()); if (!shortcutText.isEmpty()) { setGlobalShortcut(KShortcut(shortcutText)); /* #ifndef NDEBUG kDebug() << "got global shortcut for" << name() << "of" << QKeySequence(shortcutText); #endif #ifndef NDEBUG kDebug() << "set to" << d->activationAction->objectName() #endif << d->activationAction->globalShortcut().primary(); */ } // local shortcut, if any //TODO: implement; the shortcut will need to be registered with the containment /* #include "accessmanager.h" #include "private/plasmoidservice_p.h" #include "authorizationmanager.h" #include "authorizationmanager.h" shortcutText = shortcutConfig.readEntryUntranslated("local", QString()); if (!shortcutText.isEmpty()) { //TODO: implement; the shortcut } */ } void AppletPrivate::setFocus() { //kDebug() << "setting focus"; q->setFocus(Qt::ShortcutFocusReason); } void Applet::setFailedToLaunch(bool failed, const QString &reason) { d->failed = failed; d->updateFailedToLaunch(reason); } void Applet::saveState(KConfigGroup &group) const { if (d->script) { emit d->script->saveState(group); } if (group.config()->name() != config().config()->name()) { // we're being saved to a different file! // let's just copy the current values in our configuration over KConfigGroup c = config(); c.copyTo(&group); } } KConfigGroup Applet::config() const { if (d->transient) { return KConfigGroup(KGlobal::config(), "PlasmaTransientsConfig"); } if (d->isContainment) { return *(d->mainConfigGroup()); } return KConfigGroup(d->mainConfigGroup(), "Configuration"); } KConfigGroup Applet::globalConfig() const { KConfigGroup globalAppletConfig; QString group = isContainment() ? "ContainmentGlobals" : "AppletGlobals"; Corona *corona = qobject_cast(scene()); if (corona) { KSharedConfig::Ptr coronaConfig = corona->config(); globalAppletConfig = KConfigGroup(coronaConfig, group); } else { globalAppletConfig = KConfigGroup(KGlobal::config(), group); } return KConfigGroup(&globalAppletConfig, d->globalName()); } void Applet::destroy() { if (immutability() != Mutable || d->transient || !d->started) { return; //don't double delete } d->transient = true; //FIXME: an animation on leave if !isContainment() would be good again .. which should be handled by the containment class d->cleanUpAndDelete(); } bool Applet::destroyed() const { return d->transient; } void AppletPrivate::selectItemToDestroy() { //FIXME: this will not work nicely with multiple screens and being zoomed out! if (isContainment) { QGraphicsView *view = q->view(); if (view && view->transform().isScaling() && q->scene()->focusItem() != q) { QGraphicsItem *focus = q->scene()->focusItem(); if (focus) { Containment *toDestroy = dynamic_cast(focus->topLevelItem()); if (toDestroy) { toDestroy->destroy(); return; } } } } q->destroy(); } void AppletPrivate::updateRect(const QRectF &rect) { q->update(rect); } void AppletPrivate::cleanUpAndDelete() { //kDebug() << "???????????????? DESTROYING APPLET" << q->name() << q->scene() << " ???????????????????????????"; QGraphicsWidget *parent = dynamic_cast(q->parentItem()); //it probably won't matter, but right now if there are applethandles, *they* are the parent. //not the containment. //is the applet in a containment and does the containment have a layout? //if yes, we remove the applet in the layout if (parent && parent->layout()) { QGraphicsLayout *l = parent->layout(); for (int i = 0; i < l->count(); ++i) { if (q == l->itemAt(i)) { l->removeAt(i); break; } } } if (configLoader) { configLoader->setDefaults(); } resetConfigurationObject(); if (q->scene()) { if (isContainment) { // prematurely emit our destruction if we are a Containment, // giving Corona a chance to remove this Containment from its collection emit q->QObject::destroyed(q); } q->scene()->removeItem(q); } q->deleteLater(); } ConfigLoader *Applet::configScheme() const { return d->configLoader; } DataEngine *Applet::dataEngine(const QString &name) const { if (d->remoteLocation.isEmpty()) { return d->dataEngine(name); } else { return d->remoteDataEngine(d->remoteLocation, name); } } Package Applet::package() const { return d->package ? *d->package : Package(); } QGraphicsView *Applet::view() const { // It's assumed that we won't be visible on more than one view here. // Anything that actually needs view() should only really care about // one of them anyway though. if (!scene()) { return 0; } QGraphicsView *found = 0; QGraphicsView *possibleFind = 0; //kDebug() << "looking through" << scene()->views().count() << "views"; foreach (QGraphicsView *view, scene()->views()) { //kDebug() << " checking" << view << view->sceneRect() // << "against" << sceneBoundingRect() << scenePos(); if (view->sceneRect().intersects(sceneBoundingRect()) || view->sceneRect().contains(scenePos())) { //kDebug() << " found something!" << view->isActiveWindow(); if (view->isActiveWindow()) { found = view; } else { possibleFind = view; } } } return found ? found : possibleFind; } QRectF Applet::mapFromView(const QGraphicsView *view, const QRect &rect) const { // Why is this adjustment needed? Qt calculation error? return mapFromScene(view->mapToScene(rect)).boundingRect().adjusted(0, 0, 1, 1); } QRect Applet::mapToView(const QGraphicsView *view, const QRectF &rect) const { // Why is this adjustment needed? Qt calculation error? return view->mapFromScene(mapToScene(rect)).boundingRect().adjusted(0, 0, -1, -1); } QPoint Applet::popupPosition(const QSize &s) const { return popupPosition(s, Qt::AlignLeft); } QPoint Applet::popupPosition(const QSize &s, Qt::AlignmentFlag alignment) const { Corona * corona = qobject_cast(scene()); Q_ASSERT(corona); return corona->popupPosition(this, s, alignment); } void Applet::updateConstraints(Plasma::Constraints constraints) { d->scheduleConstraintsUpdate(constraints); } void Applet::constraintsEvent(Plasma::Constraints constraints) { //NOTE: do NOT put any code in here that reacts to constraints updates // as it will not get called for any applet that reimplements constraintsEvent // without calling the Applet:: version as well, which it shouldn't need to. // INSTEAD put such code into flushPendingConstraintsEvents Q_UNUSED(constraints) //kDebug() << constraints << "constraints are FormFactor: " << formFactor() // << ", Location: " << location(); if (d->script) { d->script->constraintsEvent(constraints); } } void Applet::setBusy(bool busy) { d->setBusy(busy); } bool Applet::isBusy() const { return d->isBusy(); } QString Applet::name() const { if (d->isContainment) { const Containment *c = qobject_cast(this); if (c && c->d->isPanelContainment()) { return i18n("Panel"); } else if (!d->appletDescription.isValid()) { return i18n("Unknown"); } else { return d->appletDescription.name(); } } else if (!d->appletDescription.isValid()) { return i18n("Unknown Widget"); } return d->appletDescription.name(); } QFont Applet::font() const { return QApplication::font(); } QString Applet::icon() const { if (!d->appletDescription.isValid()) { return QString(); } return d->appletDescription.icon(); } QString Applet::pluginName() const { if (!d->appletDescription.isValid()) { return QString(); } return d->appletDescription.pluginName(); } bool Applet::shouldConserveResources() const { #ifndef PLASMA_NO_SOLID return Solid::PowerManagement::appShouldConserveResources(); #else return true; #endif } QString Applet::category() const { if (!d->appletDescription.isValid()) { return i18nc("misc category", "Miscellaneous"); } return d->appletDescription.category(); } QString Applet::category(const KPluginInfo &applet) { return applet.property("X-KDE-PluginInfo-Category").toString(); } QString Applet::category(const QString &appletName) { if (appletName.isEmpty()) { return QString(); } const QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(appletName); KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); if (offers.isEmpty()) { return QString(); } return offers.first()->property("X-KDE-PluginInfo-Category").toString(); } ImmutabilityType Applet::immutability() const { // if this object is itself system immutable, then just return that; it's the most // restrictive setting possible and will override anything that might be happening above it // in the Corona->Containment->Applet hierarchy if (d->transient || (d->mainConfig && d->mainConfig->isImmutable())) { return SystemImmutable; } //Returning the more strict immutability between the applet immutability, Containment and Corona ImmutabilityType upperImmutability = Mutable; Containment *cont = d->isContainment ? 0 : containment(); if (cont) { upperImmutability = cont->immutability(); } else if (Corona *corona = qobject_cast(scene())) { upperImmutability = corona->immutability(); } if (upperImmutability != Mutable) { // it's either system or user immutable, and we already check for local system immutability, // so upperImmutability is guaranteed to be as or more severe as this object's immutability return upperImmutability; } else { return d->immutability; } } void Applet::setImmutability(const ImmutabilityType immutable) { if (d->immutability == immutable || immutable == Plasma::SystemImmutable) { // we do not store system immutability in d->immutability since that gets saved // out to the config file; instead, we check with // the config group itself for this information at all times. this differs from // corona, where SystemImmutability is stored in d->immutability. return; } d->immutability = immutable; updateConstraints(ImmutableConstraint); } BackgroundHints Applet::backgroundHints() const { return d->backgroundHints; } void Applet::setBackgroundHints(const Plasma::BackgroundHints hints) { if (d->backgroundHints == hints) { return; } d->backgroundHints = hints; d->preferredBackgroundHints = hints; //Draw the standard background? if ((hints & StandardBackground) || (hints & TranslucentBackground)) { if (!d->background) { d->background = new Plasma::FrameSvg(this); QObject::connect(d->background, SIGNAL(repaintNeeded()), this, SLOT(themeChanged())); } if ((hints & TranslucentBackground) && Plasma::Theme::defaultTheme()->currentThemeHasImage("widgets/translucentbackground")) { d->background->setImagePath("widgets/translucentbackground"); } else { d->background->setImagePath("widgets/background"); } d->background->setEnabledBorders(Plasma::FrameSvg::AllBorders); qreal left, top, right, bottom; d->background->getMargins(left, top, right, bottom); setContentsMargins(left, right, top, bottom); QSizeF fitSize(left + right, top + bottom); d->background->resizeFrame(boundingRect().size()); //if the background has an "overlay" element decide a random position for it and then save it so it's consistent across plasma starts if (d->background->hasElement("overlay")) { QSize overlaySize = d->background->elementSize("overlay"); //position is in the boundaries overlaySize.width()*2, overlaySize.height() qsrand(id()); d->background->d->overlayPos.rx() = - (overlaySize.width() /2) + (overlaySize.width() /4) * (qrand() % (4 + 1)); d->background->d->overlayPos.ry() = (- (overlaySize.height() /2) + (overlaySize.height() /4) * (qrand() % (4 + 1)))/2; } } else if (d->background) { qreal left, top, right, bottom; d->background->getMargins(left, top, right, bottom); delete d->background; d->background = 0; setContentsMargins(0, 0, 0, 0); } update(); } bool Applet::hasFailedToLaunch() const { return d->failed; } bool Applet::configurationRequired() const { return d->needsConfig; } void Applet::setConfigurationRequired(bool needsConfig, const QString &reason) { if (d->needsConfig == needsConfig) { return; } d->needsConfig = needsConfig; d->showConfigurationRequiredMessage(needsConfig, reason); } void Applet::showMessage(const QIcon &icon, const QString &message, const MessageButtons buttons) { d->showMessage(icon, message, buttons); } QVariantList Applet::startupArguments() const { return d->args; } ItemStatus Applet::status() const { return d->itemStatus; } void Applet::setStatus(const ItemStatus status) { d->itemStatus = status; emit newStatus(status); } void Applet::flushPendingConstraintsEvents() { if (d->pendingConstraints == NoConstraint) { return; } if (d->constraintsTimer.isActive()) { d->constraintsTimer.stop(); } //kDebug() << "fushing constraints: " << d->pendingConstraints << "!!!!!!!!!!!!!!!!!!!!!!!!!!!"; Plasma::Constraints c = d->pendingConstraints; d->pendingConstraints = NoConstraint; if (c & Plasma::StartupCompletedConstraint) { //common actions bool unlocked = immutability() == Mutable; QAction *closeApplet = d->actions->action("remove"); if (closeApplet) { closeApplet->setEnabled(unlocked); closeApplet->setVisible(unlocked); connect(closeApplet, SIGNAL(triggered(bool)), this, SLOT(selectItemToDestroy()), Qt::UniqueConnection); } QAction *configAction = d->actions->action("configure"); if (configAction) { if (d->isContainment) { connect(configAction, SIGNAL(triggered(bool)), this, SLOT(requestConfiguration()), Qt::UniqueConnection); } else { connect(configAction, SIGNAL(triggered(bool)), this, SLOT(showConfigurationInterface()), Qt::UniqueConnection); } if (d->hasConfigurationInterface) { bool canConfig = unlocked || KAuthorized::authorize("plasma/allow_configure_when_locked"); configAction->setVisible(canConfig); configAction->setEnabled(canConfig); } } QAction *runAssociatedApplication = d->actions->action("run associated application"); if (runAssociatedApplication) { connect(runAssociatedApplication, SIGNAL(triggered(bool)), this, SLOT(runAssociatedApplication()), Qt::UniqueConnection); } d->updateShortcuts(); Corona * corona = qobject_cast(scene()); if (corona) { connect(corona, SIGNAL(shortcutsChanged()), this, SLOT(updateShortcuts()), Qt::UniqueConnection); } } if (c & Plasma::ImmutableConstraint) { bool unlocked = immutability() == Mutable; QAction *action = d->actions->action("remove"); if (action) { action->setVisible(unlocked); action->setEnabled(unlocked); } action = d->actions->action("configure"); if (action && d->hasConfigurationInterface) { bool canConfig = unlocked || KAuthorized::authorize("plasma/allow_configure_when_locked"); action->setVisible(canConfig); action->setEnabled(canConfig); } if (!unlocked && d->handle) { AppletHandle *h = d->handle.data(); disconnect(this); QGraphicsScene *s = scene(); if (s && h->scene() == s) { s->removeItem(h); } h->deleteLater(); } emit immutabilityChanged(immutability()); } if (c & Plasma::SizeConstraint) { d->positionMessageOverlay(); if (d->started && layout()) { layout()->updateGeometry(); } } if (c & Plasma::FormFactorConstraint) { FormFactor f = formFactor(); if (!d->isContainment && f != Vertical && f != Horizontal) { setBackgroundHints(d->preferredBackgroundHints); } else { BackgroundHints hints = d->preferredBackgroundHints; setBackgroundHints(NoBackground); d->preferredBackgroundHints = hints; } if (d->failed) { if (f == Vertical || f == Horizontal) { QGraphicsLayoutItem *item = layout()->itemAt(1); layout()->removeAt(1); delete item; } } // avoid putting rotated applets in panels if (f == Vertical || f == Horizontal) { QTransform at; at.rotateRadians(0); setTransform(at); } //was a size saved for a particular form factor? if (d->sizeForFormFactor.contains(f)) { resize(d->sizeForFormFactor.value(f)); } } if (!size().isEmpty() && ((c & Plasma::StartupCompletedConstraint) || (c & Plasma::SizeConstraint && !(c & Plasma::FormFactorConstraint)))) { d->sizeForFormFactor[formFactor()] = size(); } if (c & Plasma::SizeConstraint || c & Plasma::FormFactorConstraint) { if (aspectRatioMode() == Plasma::Square || aspectRatioMode() == Plasma::ConstrainedSquare) { // enforce square size in panels //save the old size policy. since ignored doesn't (yet) have a valid use case in containments, use it as special unset value if (d->preferredSizePolicy == QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)) { d->preferredSizePolicy = sizePolicy(); } if (formFactor() == Horizontal) { setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding)); } else if (formFactor() == Vertical) { setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); } else if (d->preferredSizePolicy != QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)) { setSizePolicy(d->preferredSizePolicy); } } updateGeometry(); } // now take care of constraints in special subclasses: Contaiment and PopupApplet Containment* containment = qobject_cast(this); if (d->isContainment && containment) { containment->d->containmentConstraintsEvent(c); } PopupApplet* popup = qobject_cast(this); if (popup) { popup->d->popupConstraintsEvent(c); } // pass the constraint on to the actual subclass constraintsEvent(c); if (c & StartupCompletedConstraint) { // start up is done, we can now go do a mod timer if (d->modificationsTimer) { if (d->modificationsTimer->isActive()) { d->modificationsTimer->stop(); } } else { d->modificationsTimer = new QBasicTimer; } } } int Applet::type() const { return Type; } QList Applet::contextualActions() { //kDebug() << "empty context actions"; return d->script ? d->script->contextualActions() : QList(); } QAction *Applet::action(QString name) const { return d->actions->action(name); } void Applet::addAction(QString name, QAction *action) { d->actions->addAction(name, action); } void Applet::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { if (!d->started) { //kDebug() << "not started"; return; } if (transform().isRotating()) { painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->setRenderHint(QPainter::Antialiasing); } if (d->background && formFactor() != Plasma::Vertical && formFactor() != Plasma::Horizontal) { //kDebug() << "option rect is" << option->rect; d->background->paintFrame(painter); } if (d->failed) { //kDebug() << "failed!"; return; } qreal left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); QRect contentsRect = QRectF(QPointF(0, 0), boundingRect().size()).adjusted(left, top, -right, -bottom).toRect(); if (widget && d->isContainment) { // note that the widget we get is actually the viewport of the view, not the view itself View* v = qobject_cast(widget->parent()); Containment* c = qobject_cast(this); if (!v || v->isWallpaperEnabled()) { // paint the wallpaper if (c && c->drawWallpaper() && c->wallpaper()) { Wallpaper *w = c->wallpaper(); if (!w->isInitialized()) { // delayed paper initialization KConfigGroup wallpaperConfig = c->config(); wallpaperConfig = KConfigGroup(&wallpaperConfig, "Wallpaper"); wallpaperConfig = KConfigGroup(&wallpaperConfig, w->pluginName()); w->restore(wallpaperConfig); disconnect(w, SIGNAL(update(const QRectF&)), this, SLOT(updateRect(const QRectF&))); connect(w, SIGNAL(update(const QRectF&)), this, SLOT(updateRect(const QRectF&))); } painter->save(); c->wallpaper()->paint(painter, option->exposedRect); painter->restore(); } // .. and now paint the actual containment interface, but with // a Containment style option based on the one we get // the view must be assigned only if its containment is actually our own Containment::StyleOption coption(*option); if (v && v->containment() == containment()) { coption.view = v; } paintInterface(painter, &coption, contentsRect); } } else { //kDebug() << "paint interface of" << (QObject*) this; // paint the applet's interface paintInterface(painter, option, contentsRect); } } void Applet::paintInterface(QPainter *painter, const QStyleOptionGraphicsItem *option, const QRect &contentsRect) { if (d->script) { d->script->paintInterface(painter, option, contentsRect); } else { //kDebug() << "Applet::paintInterface() default impl"; } } FormFactor Applet::formFactor() const { Containment *c = containment(); QGraphicsWidget *pw = qobject_cast(parent()); if (!pw) { pw = dynamic_cast(parentItem()); } Plasma::Applet *parentApplet = qobject_cast(pw); //assumption: this loop is usually is -really- short or doesn't run at all while (!parentApplet && pw && pw->parentWidget()) { QGraphicsWidget *parentWidget = qobject_cast(pw->parent()); if (!parentWidget) { parentWidget = dynamic_cast(pw->parentItem()); } pw = parentWidget; parentApplet = qobject_cast(pw); } const PopupApplet *pa = dynamic_cast(this); //if the applet is in a widget that isn't a containment //try to retrieve the formFactor from the parent size //we can't use our own sizeHint here because it needs formFactor, so endless recursion. // a popupapplet can always be constrained. // a normal applet should to but //FIXME: not always constrained to not break systemmonitor if (parentApplet && parentApplet != c && c != this && (pa || layout())) { if (pa || (parentApplet->size().height() < layout()->effectiveSizeHint(Qt::MinimumSize).height())) { return Plasma::Horizontal; } else if (pa || (parentApplet->size().width() < layout()->effectiveSizeHint(Qt::MinimumSize).width())) { return Plasma::Vertical; } return parentApplet->formFactor(); } return c ? c->d->formFactor : Plasma::Planar; } Containment *Applet::containment() const { if (d->isContainment) { Containment *c = qobject_cast(const_cast(this)); if (c) { return c; } } QGraphicsItem *parent = parentItem(); Containment *c = 0; while (parent) { Containment *possibleC = dynamic_cast(parent); if (possibleC && possibleC->Applet::d->isContainment) { c = possibleC; break; } parent = parent->parentItem(); } if (!c) { //if the applet is an offscreen widget its parentItem will be 0, while its parent //will be its parentWidget, so here we check the QObject hierarchy. QObject *objParent = this->parent(); while (objParent) { Containment *possibleC = qobject_cast(objParent); if (possibleC && possibleC->Applet::d->isContainment) { c = possibleC; break; } objParent = objParent->parent(); } } return c; } void Applet::setGlobalShortcut(const KShortcut &shortcut) { if (!d->activationAction) { d->activationAction = new KAction(this); d->activationAction->setText(i18n("Activate %1 Widget", name())); d->activationAction->setObjectName(QString("activate widget %1").arg(id())); // NO I18N connect(d->activationAction, SIGNAL(triggered()), this, SIGNAL(activate())); connect(d->activationAction, SIGNAL(globalShortcutChanged(QKeySequence)), this, SLOT(globalShortcutChanged())); QList widgets = d->actions->associatedWidgets(); foreach (QWidget *w, widgets) { w->addAction(d->activationAction); } } else if (d->activationAction->globalShortcut() == shortcut) { return; } //kDebug() << "before" << shortcut.primary() << d->activationAction->globalShortcut().primary(); d->activationAction->setGlobalShortcut( shortcut, KAction::ShortcutTypes(KAction::ActiveShortcut | KAction::DefaultShortcut), KAction::NoAutoloading); d->globalShortcutChanged(); } void AppletPrivate::showConfigurationRequiredMessage(bool show, const QString &reason) { // reimplemented in the UI specific library Q_UNUSED(show) Q_UNUSED(reason) } void AppletPrivate::showMessage(const QIcon &icon, const QString &message, const MessageButtons buttons) { // reimplemented in the UI specific library Q_UNUSED(icon) Q_UNUSED(message) Q_UNUSED(buttons) } void AppletPrivate::positionMessageOverlay() { // reimplemented in the UI specific library } void AppletPrivate::setBusy(bool busy) { // reimplemented in the UI specific library Q_UNUSED(busy) } bool AppletPrivate::isBusy() const { // reimplemented in the UI specific library return false; } void AppletPrivate::updateFailedToLaunch(const QString &reason) { // reimplemented in the UI specific library 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(); } KShortcut Applet::globalShortcut() const { if (d->activationAction) { return d->activationAction->globalShortcut(); } return KShortcut(); } bool Applet::isPopupShowing() const { return false; } void Applet::addAssociatedWidget(QWidget *widget) { d->actions->addAssociatedWidget(widget); } void Applet::removeAssociatedWidget(QWidget *widget) { d->actions->removeAssociatedWidget(widget); } Location Applet::location() const { Containment *c = containment(); return c ? c->d->location : Plasma::Desktop; } Plasma::AspectRatioMode Applet::aspectRatioMode() const { return d->aspectRatioMode; } void Applet::setAspectRatioMode(Plasma::AspectRatioMode mode) { PopupApplet *popup = qobject_cast(this); if (popup && popup->d->dialogPtr) { popup->d->dialogPtr.data()->setAspectRatioMode(mode); popup->d->savedAspectRatio = mode; } d->aspectRatioMode = mode; } void Applet::registerAsDragHandle(QGraphicsItem *item) { if (!item || d->registeredAsDragHandle.contains(item)) { return; } d->registeredAsDragHandle.insert(item); item->installSceneEventFilter(this); } void Applet::unregisterAsDragHandle(QGraphicsItem *item) { if (!item) { return; } if (d->registeredAsDragHandle.remove(item)) { if (item != this) { item->removeSceneEventFilter(this); } } } bool Applet::isRegisteredAsDragHandle(QGraphicsItem *item) { return d->registeredAsDragHandle.contains(item); } bool Applet::hasConfigurationInterface() const { return d->hasConfigurationInterface; } void Applet::publish(AnnouncementMethods methods, const QString &resourceName) { if (!d->remotingService) { d->remotingService = new PlasmoidService(this); } const QString resName = resourceName.isEmpty() ? i18nc("%1 is the name of a plasmoid, %2 the name of the machine that plasmoid is published on", "%1 on %2", name(), QHostInfo::localHostName()) : resourceName; #ifndef NDEBUG kDebug() << "publishing package under name " << resName; #endif if (d->package && d->package->isValid()) { d->remotingService->d->publish(methods, resName, d->package->metadata()); } else if (!d->package && d->appletDescription.isValid()) { d->remotingService->d->publish(methods, resName, d->appletDescription); } else { delete d->remotingService; d->remotingService = 0; #ifndef NDEBUG kDebug() << "Can not publish invalid applets."; #endif } } void Applet::unpublish() { if (d->remotingService) { d->remotingService->d->unpublish(); } } bool Applet::isPublished() const { return d->remotingService && d->remotingService->d->isPublished(); } void Applet::setHasConfigurationInterface(bool hasInterface) { if (hasInterface == d->hasConfigurationInterface) { return; } QAction *configAction = d->actions->action("configure"); if (configAction) { bool enable = hasInterface; if (enable) { const bool unlocked = immutability() == Mutable; enable = unlocked || KAuthorized::authorize("plasma/allow_configure_when_locked"); } configAction->setEnabled(enable); } d->hasConfigurationInterface = hasInterface; } 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(KIcon("configure")); configAction->setShortcut(KShortcut("alt+d, s")); configAction->setData(AbstractToolBox::ConfigureTool); KAction *closeApplet = actions->addAction("remove"); closeApplet->setAutoRepeat(false); closeApplet->setText(i18n("Remove this Widget")); closeApplet->setIcon(KIcon("edit-delete")); closeApplet->setShortcut(KShortcut("alt+d, r")); closeApplet->setData(AbstractToolBox::DestructiveTool); KAction *runAssociatedApplication = actions->addAction("run associated application"); runAssociatedApplication->setAutoRepeat(false); runAssociatedApplication->setText(i18n("Run the Associated Application")); runAssociatedApplication->setIcon(KIcon("system-run")); runAssociatedApplication->setShortcut(KShortcut("alt+d, t")); runAssociatedApplication->setVisible(false); runAssociatedApplication->setEnabled(false); runAssociatedApplication->setData(AbstractToolBox::ControlTool); return actions; } bool Applet::sceneEventFilter(QGraphicsItem *watched, QEvent *event) { if (watched == this) { switch (event->type()) { case QEvent::GraphicsSceneHoverEnter: //kDebug() << "got hoverenterEvent" << immutability() << " " << immutability(); if (immutability() == Mutable) { QGraphicsSceneHoverEvent *he = static_cast(event); if (d->handle) { d->handle.data()->setHoverPos(he->pos()); } else { //kDebug() << "generated applet handle"; AppletHandle *handle = new AppletHandle(containment(), this, he->pos()); connect(handle, SIGNAL(disappearDone(AppletHandle*)), this, SLOT(handleDisappeared(AppletHandle*))); connect(this, SIGNAL(geometryChanged()), handle, SLOT(appletResized())); d->handle = handle; } } break; case QEvent::GraphicsSceneHoverMove: if (d->handle && !d->handle.data()->shown() && immutability() == Mutable) { QGraphicsSceneHoverEvent *he = static_cast(event); d->handle.data()->setHoverPos(he->pos()); } break; default: break; } } switch (event->type()) { case QEvent::GraphicsSceneMouseMove: case QEvent::GraphicsSceneMousePress: case QEvent::GraphicsSceneMouseRelease: { // don't move when the containment is not mutable, // in the rare case the containment doesn't exists consider it as mutable if ((flags() & ItemIsMovable) && d->registeredAsDragHandle.contains(watched)) { Containment *c = containment(); if (!c || c->immutability() == Mutable) { scene()->sendEvent(this, event); return false; } } break; } default: break; } return QGraphicsItem::sceneEventFilter(watched, event); } void Applet::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (immutability() == Mutable && formFactor() == Plasma::Planar && (flags() & ItemIsMovable)) { QGraphicsWidget::mouseMoveEvent(event); } } void Applet::focusInEvent(QFocusEvent *event) { if (!isContainment() && containment()) { //focusing an applet may trigger this event again, but we won't be here more than twice containment()->d->focusApplet(this); } QGraphicsWidget::focusInEvent(event); } void Applet::resizeEvent(QGraphicsSceneResizeEvent *event) { QGraphicsWidget::resizeEvent(event); if (d->background) { d->background->resizeFrame(boundingRect().size()); } updateConstraints(Plasma::SizeConstraint); d->scheduleModificationNotification(); emit geometryChanged(); } bool Applet::isUserConfiguring() const { return KConfigDialog::exists(d->configDialogId()); } void Applet::showConfigurationInterface() { if (!hasConfigurationInterface()) { return; } if (immutability() != Mutable && !KAuthorized::authorize("plasma/allow_configure_when_locked")) { return; } KConfigDialog *dlg = KConfigDialog::exists(d->configDialogId()); if (dlg) { KWindowSystem::setOnDesktop(dlg->winId(), KWindowSystem::currentDesktop()); dlg->show(); KWindowSystem::activateWindow(dlg->winId()); return; } d->publishUI.publishCheckbox = 0; if (d->package) { KConfigDialog *dialog = 0; const QString uiFile = d->package->filePath("mainconfigui"); KDesktopFile df(d->package->path() + "/metadata.desktop"); const QStringList kcmPlugins = df.desktopGroup().readEntry("X-Plasma-ConfigPlugins", QStringList()); if (!uiFile.isEmpty() || !kcmPlugins.isEmpty()) { KConfigSkeleton *configLoader = d->configLoader ? d->configLoader : new KConfigSkeleton(0); dialog = new AppletConfigDialog(0, d->configDialogId(), configLoader); if (!d->configLoader) { // delete the temporary when this dialog is done configLoader->setParent(dialog); } dialog->setWindowTitle(d->configWindowTitle()); dialog->setAttribute(Qt::WA_DeleteOnClose, true); bool hasPages = false; QFile f(uiFile); QUiLoader loader; QWidget *w = loader.load(&f); if (w) { dialog->addPage(w, i18n("Settings"), icon(), i18n("%1 Settings", name())); hasPages = true; } foreach (const QString &kcm, kcmPlugins) { #ifndef PLASMA_NO_KUTILS KCModuleProxy *module = new KCModuleProxy(kcm); if (module->realModule()) { connect(module, SIGNAL(changed(bool)), dialog, SLOT(settingsModified(bool))); dialog->addPage(module, module->moduleInfo().moduleName(), module->moduleInfo().icon()); hasPages = true; } else { delete module; } #else KService::Ptr service = KService::serviceByStorageId(kcm); if (service) { QString error; KCModule *module = service->createInstance(dialog, QVariantList(), &error); if (module) { connect(module, SIGNAL(changed(bool)), dialog, SLOT(settingsModified(bool))); dialog->addPage(module, service->name(), service->icon()); hasPages = true; } else { #ifndef NDEBUG kDebug() << "failed to load kcm" << kcm << "for" << name(); #endif } } #endif } if (hasPages) { d->addGlobalShortcutsPage(dialog); d->addPublishPage(dialog); dialog->show(); } else { delete dialog; dialog = 0; } } if (!dialog && d->script) { d->script->showConfigurationInterface(); } } else if (d->script) { d->script->showConfigurationInterface(); } else { KConfigDialog *dialog = d->generateGenericConfigDialog(); d->addStandardConfigurationPages(dialog); showConfigurationInterface(dialog); } emit releaseVisualFocus(); } void Applet::showConfigurationInterface(QWidget *widget) { if (!containment() || !containment()->corona() || !containment()->corona()->dialogManager()) { widget->show(); return; } QMetaObject::invokeMethod(containment()->corona()->dialogManager(), "showDialog", Q_ARG(QWidget *, widget), Q_ARG(Plasma::Applet *, this)); } QString AppletPrivate::configDialogId() const { return QString("%1settings%2").arg(appletId).arg(q->name()); } QString AppletPrivate::configWindowTitle() const { return i18nc("@title:window", "%1 Settings", q->name()); } QSet AppletPrivate::knownCategories() { // this is to trick the tranlsation tools into making the correct // strings for translation QSet 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->showButton(KDialog::Default, false); dialog->showButton(KDialog::Help, false); QObject::connect(dialog, SIGNAL(applyClicked()), q, SLOT(configDialogFinished())); QObject::connect(dialog, SIGNAL(okClicked()), q, SLOT(configDialogFinished())); return dialog; } void AppletPrivate::addStandardConfigurationPages(KConfigDialog *dialog) { addGlobalShortcutsPage(dialog); addPublishPage(dialog); } void AppletPrivate::addGlobalShortcutsPage(KConfigDialog *dialog) { #ifndef 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::addPublishPage(KConfigDialog *dialog) { #ifdef ENABLE_REMOTE_WIDGETS QWidget *page = new QWidget; publishUI.setupUi(page); publishUI.publishCheckbox->setChecked(q->isPublished()); QObject::connect(publishUI.publishCheckbox, SIGNAL(clicked(bool)), dialog, SLOT(settingsModified())); publishUI.allUsersCheckbox->setEnabled(q->isPublished()); QObject::connect(publishUI.allUsersCheckbox, SIGNAL(clicked(bool)), dialog, SLOT(settingsModified())); QString resourceName = i18nc("%1 is the name of a plasmoid, %2 the name of the machine that plasmoid is published on", "%1 on %2", q->name(), QHostInfo::localHostName()); if (AuthorizationManager::self()->d->matchingRule(resourceName, Credentials())) { publishUI.allUsersCheckbox->setChecked(true); } else { publishUI.allUsersCheckbox->setChecked(false); } q->connect(publishUI.publishCheckbox, SIGNAL(stateChanged(int)), q, SLOT(publishCheckboxStateChanged(int))); dialog->addPage(page, i18n("Share"), "applications-internet"); #endif } void AppletPrivate::publishCheckboxStateChanged(int state) { if (state == Qt::Checked) { publishUI.allUsersCheckbox->setEnabled(true); } else { publishUI.allUsersCheckbox->setEnabled(false); } } void AppletPrivate::configDialogFinished() { if (shortcutEditor) { QKeySequence sequence = shortcutEditor.data()->keySequence(); if (sequence != q->globalShortcut().primary()) { q->setGlobalShortcut(KShortcut(sequence)); emit q->configNeedsSaving(); } } #ifdef ENABLE_REMOTE_WIDGETS if (KConfigDialog::exists(configDialogId()) && publishUI.publishCheckbox) { q->config().writeEntry("Share", publishUI.publishCheckbox->isChecked()); if (publishUI.publishCheckbox->isChecked()) { QString resourceName = i18nc("%1 is the name of a plasmoid, %2 the name of the machine that plasmoid is published on", "%1 on %2", q->name(), QHostInfo::localHostName()); q->publish(Plasma::ZeroconfAnnouncement, resourceName); if (publishUI.allUsersCheckbox->isChecked()) { if (!AuthorizationManager::self()->d->matchingRule(resourceName, Credentials())) { AuthorizationRule *rule = new AuthorizationRule(resourceName, ""); rule->setPolicy(AuthorizationRule::Allow); rule->setTargets(AuthorizationRule::AllUsers); AuthorizationManager::self()->d->rules.append(rule); } } else { AuthorizationRule *matchingRule = AuthorizationManager::self()->d->matchingRule(resourceName, Credentials()); if (matchingRule) { AuthorizationManager::self()->d->rules.removeAll(matchingRule); } } } else { q->unpublish(); } } #endif if (!configLoader) { // the config loader will trigger this for us, so we don't need to. propagateConfigChanged(); if (KConfigDialog *dialog = qobject_cast(q->sender())) { dialog->enableButton(KDialog::Apply, 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 names; QList 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(q); if (c) { c->d->configChanged(); } } q->configChanged(); } void Applet::configChanged() { if (d->script) { if (d->configLoader) { d->configLoader->readConfig(); } d->script->configChanged(); } } void Applet::createConfigurationInterface(KConfigDialog *parent) { Q_UNUSED(parent) // virtual method reimplemented by subclasses. // do not put anything here ... } bool Applet::hasAuthorization(const QString &constraint) const { KConfigGroup constraintGroup(KGlobal::config(), "Constraints"); return constraintGroup.readEntry(constraint, true); } void Applet::setAssociatedApplication(const QString &string) { AssociatedApplicationManager::self()->setApplication(this, string); QAction *runAssociatedApplication = d->actions->action("run associated application"); if (runAssociatedApplication) { bool valid = AssociatedApplicationManager::self()->appletHasValidAssociatedApplication(this); valid = valid && hasAuthorization("LaunchApp"); //obey security! runAssociatedApplication->setVisible(valid); runAssociatedApplication->setEnabled(valid); } } void Applet::setAssociatedApplicationUrls(const KUrl::List &urls) { AssociatedApplicationManager::self()->setUrls(this, urls); QAction *runAssociatedApplication = d->actions->action("run associated application"); if (runAssociatedApplication) { bool valid = AssociatedApplicationManager::self()->appletHasValidAssociatedApplication(this); valid = valid && hasAuthorization("LaunchApp"); //obey security! runAssociatedApplication->setVisible(valid); runAssociatedApplication->setEnabled(valid); } } QString Applet::associatedApplication() const { return AssociatedApplicationManager::self()->application(this); } KUrl::List Applet::associatedApplicationUrls() const { return AssociatedApplicationManager::self()->urls(this); } void Applet::runAssociatedApplication() { if (hasAuthorization("LaunchApp")) { AssociatedApplicationManager::self()->run(this); } } bool Applet::hasValidAssociatedApplication() const { return AssociatedApplicationManager::self()->appletHasValidAssociatedApplication(this); } void AppletPrivate::filterOffers(QList &offers) { KConfigGroup constraintGroup(KGlobal::config(), "Constraints"); foreach (const QString &key, constraintGroup.keyList()) { //kDebug() << "security constraint" << key; if (constraintGroup.readEntry(key, true)) { continue; } //ugh. a qlist of ksharedptr QMutableListIterator it(offers); while (it.hasNext()) { KService::Ptr p = it.next(); QString prop = QString("X-Plasma-Requires-").append(key); QVariant req = p->property(prop, QVariant::String); //valid values: Required/Optional/Unused QString reqValue; if (req.isValid()) { reqValue = req.toString(); } else if (p->property("X-Plasma-API").toString().toLower() == "javascript") { //TODO: be able to check whether or not a script engine provides "controled" //bindings; for now we just give a pass to the qscript ones reqValue = "Unused"; } if (!(reqValue == "Optional" || reqValue == "Unused")) { //if (reqValue == "Required") { it.remove(); } } } } QString AppletPrivate::parentAppConstraint(const QString &parentApp) { if (parentApp.isEmpty()) { return QString("((not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '') or [X-KDE-ParentApp] == '%1')") .arg(KGlobal::mainComponent().aboutData()->appName()); } return QString("[X-KDE-ParentApp] == '%1'").arg(parentApp); } KPluginInfo::List Applet::listAppletInfo(const QString &category, const QString &parentApp) { return PluginLoader::self()->listAppletInfo(category, parentApp); } KPluginInfo::List Applet::listAppletInfoForMimeType(const QString &mimeType) { QString constraint = AppletPrivate::parentAppConstraint(); constraint.append(QString(" and '%1' in [X-Plasma-DropMimeTypes]").arg(mimeType)); //kDebug() << "listAppletInfoForMimetype with" << mimeType << constraint; KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); AppletPrivate::filterOffers(offers); return KPluginInfo::fromServices(offers); } KPluginInfo::List Applet::listAppletInfoForUrl(const QUrl &url) { QString constraint = AppletPrivate::parentAppConstraint(); constraint.append(" and exist [X-Plasma-DropUrlPatterns]"); KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); AppletPrivate::filterOffers(offers); KPluginInfo::List allApplets = KPluginInfo::fromServices(offers); KPluginInfo::List filtered; foreach (const KPluginInfo &info, allApplets) { QStringList urlPatterns = info.property("X-Plasma-DropUrlPatterns").toStringList(); foreach (const QString &glob, urlPatterns) { QRegExp rx(glob); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(url.toString())) { #ifndef NDEBUG kDebug() << info.name() << "matches" << glob << url; #endif filtered << info; } } } return filtered; } QStringList Applet::listCategories(const QString &parentApp, bool visibleOnly) { QString constraint = AppletPrivate::parentAppConstraint(parentApp); constraint.append(" and exist [X-KDE-PluginInfo-Category]"); KConfigGroup group(KGlobal::config(), "General"); const QStringList excluded = group.readEntry("ExcludeCategories", QStringList()); foreach (const QString &category, excluded) { constraint.append(" and [X-KDE-PluginInfo-Category] != '").append(category).append("'"); } KService::List offers = KServiceTypeTrader::self()->query("Plasma/Applet", constraint); AppletPrivate::filterOffers(offers); QStringList categories; QSet known = AppletPrivate::knownCategories(); foreach (const KService::Ptr &applet, offers) { QString appletCategory = applet->property("X-KDE-PluginInfo-Category").toString(); if (visibleOnly && applet->noDisplay()) { // we don't want to show the hidden category continue; } //kDebug() << " and we have " << appletCategory; if (!appletCategory.isEmpty() && !known.contains(appletCategory.toLower())) { #ifndef NDEBUG kDebug() << "Unknown category: " << applet->name() << "says it is in the" << appletCategory << "category which is unknown to us"; #endif appletCategory.clear(); } if (appletCategory.isEmpty()) { if (!categories.contains(i18nc("misc category", "Miscellaneous"))) { categories << i18nc("misc category", "Miscellaneous"); } } else if (!categories.contains(appletCategory)) { categories << appletCategory; } } categories.sort(); return categories; } void Applet::setCustomCategories(const QStringList &categories) { AppletPrivate::s_customCategories = QSet::fromList(categories); } QStringList Applet::customCategories() { return AppletPrivate::s_customCategories.toList(); } Applet *Applet::loadPlasmoid(const QString &path, uint appletId, const QVariantList &args) { if (QFile::exists(path + "/metadata.desktop")) { KService service(path + "/metadata.desktop"); const QStringList &types = service.serviceTypes(); if (types.contains("Plasma/Containment")) { return new Containment(path, appletId, args); } else if (types.contains("Plasma/PopupApplet")) { return new PopupApplet(path, appletId, args); } else { return new Applet(path, appletId, args); } } return 0; } QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value) { QVariant ret = QGraphicsWidget::itemChange(change, value); //kDebug() << change; switch (change) { case ItemSceneHasChanged: { Corona *newCorona = qobject_cast(qvariant_cast(value)); if (newCorona && newCorona->immutability() != Mutable) { updateConstraints(ImmutableConstraint); } } break; case ItemParentChange: if (!d->isContainment) { Containment *c = containment(); if (d->mainConfig && !c) { kWarning() << "Configuration object was requested prior to init(), which is too early. " "Please fix this item:" << parentItem() << value.value() << name(); Applet *newC = dynamic_cast(value.value()); if (newC) { // if this is an applet, and we've just been assigned to our first containment, // but the applet did something stupid like ask for the config() object prior to // this happening (e.g. inits ctor) then let's repair that situation for them. KConfigGroup *old = d->mainConfig; KConfigGroup appletConfig = newC->config(); appletConfig = KConfigGroup(&appletConfig, "Applets"); d->mainConfig = new KConfigGroup(&appletConfig, QString::number(d->appletId)); old->copyTo(d->mainConfig); old->deleteGroup(); delete old; } } } break; case ItemParentHasChanged: { if (isContainment()) { removeSceneEventFilter(this); } else { Containment *c = containment(); if (c && c->containmentType() == Containment::DesktopContainment) { installSceneEventFilter(this); } else { removeSceneEventFilter(this); } } } break; case ItemPositionHasChanged: emit geometryChanged(); // fall through! case ItemTransformHasChanged: d->scheduleModificationNotification(); break; default: break; }; return ret; } QPainterPath Applet::shape() const { if (d->script) { return d->script->shape(); } return QGraphicsWidget::shape(); } QSizeF Applet::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const { QSizeF hint = QGraphicsWidget::sizeHint(which, constraint); const FormFactor ff = formFactor(); // in panels make sure that the contents won't exit from the panel if (which == Qt::MinimumSize) { if (ff == Horizontal) { hint.setHeight(0); } else if (ff == Vertical) { hint.setWidth(0); } } // enforce a square size in panels if (d->aspectRatioMode == Plasma::Square) { if (ff == Horizontal) { hint.setWidth(size().height()); } else if (ff == Vertical) { hint.setHeight(size().width()); } } else if (d->aspectRatioMode == Plasma::ConstrainedSquare) { //enforce a size not wider than tall if (ff == Horizontal) { hint.setWidth(size().height()); //enforce a size not taller than wide } else if (ff == Vertical && (which == Qt::MaximumSize || size().width() <= KIconLoader::SizeLarge)) { hint.setHeight(size().width()); } } return hint; } void Applet::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { Q_UNUSED(event) } void Applet::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { Q_UNUSED(event) } void Applet::timerEvent(QTimerEvent *event) { if (d->transient) { d->constraintsTimer.stop(); if (d->modificationsTimer) { d->modificationsTimer->stop(); } return; } if (event->timerId() == d->constraintsTimer.timerId()) { d->constraintsTimer.stop(); // Don't flushPendingConstraints if we're just starting up // flushPendingConstraints will be called by Corona if(!(d->pendingConstraints & Plasma::StartupCompletedConstraint)) { flushPendingConstraintsEvents(); } } else if (d->modificationsTimer && event->timerId() == d->modificationsTimer->timerId()) { d->modificationsTimer->stop(); // invalid group, will result in save using the default group KConfigGroup cg; save(cg); emit configNeedsSaving(); } } QRect Applet::screenRect() const { QGraphicsView *v = view(); if (v) { QPointF bottomRight = pos(); bottomRight.rx() += size().width(); bottomRight.ry() += size().height(); QPoint tL = v->mapToGlobal(v->mapFromScene(pos())); QPoint bR = v->mapToGlobal(v->mapFromScene(bottomRight)); return QRect(QPoint(tL.x(), tL.y()), QSize(bR.x() - tL.x(), bR.y() - tL.y())); } //The applet doesn't have a view on it. //So a screenRect isn't relevant. return QRect(QPoint(0, 0), QSize(0, 0)); } void Applet::raise() { setZValue(++AppletPrivate::s_maxZValue); } void Applet::lower() { setZValue(--AppletPrivate::s_minZValue); } 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(); } } bool Applet::isContainment() const { return d->isContainment; } // PRIVATE CLASS IMPLEMENTATION AppletPrivate::AppletPrivate(KService::Ptr service, const KPluginInfo *info, int uniqueID, Applet *applet) : appletId(uniqueID), q(applet), remotingService(0), preferredBackgroundHints(StandardBackground), backgroundHints(NoBackground), aspectRatioMode(Plasma::KeepAspectRatio), immutability(Mutable), appletDescription(info ? *info : KPluginInfo(service)), background(0), mainConfig(0), pendingConstraints(NoConstraint), script(0), package(0), configLoader(0), actions(AppletPrivate::defaultActions(applet)), activationAction(0), itemStatus(UnknownStatus), preferredSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored), 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->name() << 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->setCacheMode(QGraphicsItem::DeviceCoordinateCache); q->setAcceptsHoverEvents(true); q->setFlag(QGraphicsItem::ItemIsFocusable, true); q->setFocusPolicy(Qt::ClickFocus); // FIXME: adding here because nothing seems to be doing it in QGraphicsView, // but it doesn't actually work anyways =/ q->setLayoutDirection(qApp->layoutDirection()); //set a default size before any saved settings are read QSize size(200, 200); q->setBackgroundHints(DefaultBackground); 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->name())); } QAction *configAction = actions->action("configure"); if (configAction) { configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", q->name())); } 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 q->resize(size); return; } QVariant s = appletDescription.property("X-Plasma-DefaultSize"); if (s.isValid()) { size = s.toSize(); } //kDebug() << "size" << size; q->resize(size); QString api = appletDescription.property("X-Plasma-API").toString(); // we have a scripted plasmoid if (api.isEmpty()) { q->setFailedToLaunch(true, i18n("The %2 widget did not define which ScriptEngine to use.", appletDescription.name())); return; } package = new Package(PluginLoader::self()->loadPackage("Plasma/Applet", api)); // find where the Package is QString path = packagePath; if (path.isEmpty()) { const QString subPath = package->defaultPackageRoot() + appletDescription.pluginName() + '/'; path = KStandardDirs::locate("data", subPath + "metadata.desktop"); if (path.isEmpty()) { path = KStandardDirs::locate("data", subPath); } else { path.remove(QString("metadata.desktop")); } } else if (!path.endsWith('/')) { path.append('/'); } if (path.isEmpty()) { delete package; package = 0; q->setFailedToLaunch(true, i18nc("Package file, name of the widget", "Could not locate the %1 package required for the %2 widget.", appletDescription.pluginName(), appletDescription.name())); return; } package->setPath(path); if (!package->isValid()) { delete package; package = 0; q->setFailedToLaunch(true, 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->setFailedToLaunch(true, 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())); } } // 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); KGlobal::locale()->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; } bool newGroup = false; if (isContainment) { Corona *corona = qobject_cast(q->scene()); KConfigGroup containmentConfig; //kDebug() << "got a corona, baby?" << (QObject*)corona << (QObject*)q; if (corona) { containmentConfig = KConfigGroup(corona->config(), "Containments"); } else { containmentConfig = KConfigGroup(KGlobal::config(), "Containments"); } if (package && !containmentConfig.hasGroup(QString::number(appletId))) { newGroup = true; } mainConfig = new KConfigGroup(&containmentConfig, QString::number(appletId)); } else { KConfigGroup appletConfig; Containment *c = q->containment(); Applet *parentApplet = qobject_cast(q->parent()); if (parentApplet && parentApplet != static_cast(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->name() << "without a containment!"; appletConfig = KConfigGroup(KGlobal::config(), "Applets"); } if (package && !appletConfig.hasGroup(QString::number(appletId))) { newGroup = true; } mainConfig = new KConfigGroup(&appletConfig, QString::number(appletId)); } if (newGroup) { //see if we have a default configuration in our package const QString defaultConfigFile = package->filePath("defaultconfig"); if (!defaultConfigFile.isEmpty()) { #ifndef NDEBUG kDebug() << "copying default config: " << package->filePath("defaultconfig"); #endif KConfigGroup defaultConfig(KSharedConfig::openConfig(defaultConfigFile)->group("Configuration")); defaultConfig.copyTo(mainConfig); } } 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:

%1

", reason); } return text; } void AppletPrivate::themeChanged() { if (background) { //do again the translucent background fallback q->setBackgroundHints(backgroundHints); qreal left; qreal right; qreal top; qreal bottom; background->getMargins(left, top, right, bottom); q->setContentsMargins(left, right, top, bottom); } q->update(); } void AppletPrivate::resetConfigurationObject() { // make sure mainConfigGroup exists in all cases mainConfigGroup(); mainConfig->deleteGroup(); delete mainConfig; mainConfig = 0; Corona * corona = qobject_cast(q->scene()); if (corona) { corona->requireConfigSync(); } } void AppletPrivate::handleDisappeared(AppletHandle *h) { if (h == handle.data()) { h->detachApplet(); QGraphicsScene *scene = q->scene(); if (scene && h->scene() == scene) { scene->removeItem(h); } h->deleteLater(); } } void ContainmentPrivate::checkRemoveAction() { q->enableAction("remove", q->immutability() == Mutable); } uint AppletPrivate::s_maxAppletId = 0; int AppletPrivate::s_maxZValue = 0; int AppletPrivate::s_minZValue = 0; QSet AppletPrivate::s_customCategories; } // Plasma namespace #include "moc_applet_p.cpp"