/* * Copyright 2007 by Aaron Seigo * Copyright 2008 by Ménard Alexis * Copyright 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 "containment.h" #include "private/containment_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/jobclasses.h" // for KIO::JobFlags #include "kio/job.h" #include "kio/scheduler.h" #include "animator.h" #include "context.h" #include "containmentactions.h" #include "containmentactionspluginsconfig.h" #include "corona.h" #include "extender.h" #include "extenderitem.h" #include "svg.h" #include "wallpaper.h" #include "remote/accessappletjob.h" #include "remote/accessmanager.h" #include "private/applet_p.h" #include "private/applethandle_p.h" #include "private/containmentactionspluginsconfig_p.h" #include "private/desktoptoolbox_p.h" #include "private/extenderitemmimedata_p.h" #include "private/extenderapplet_p.h" #include "private/paneltoolbox_p.h" #include "plasma/plasma.h" #include "animations/animation.h" namespace Plasma { bool ContainmentPrivate::s_positioningPanels = false; static const char defaultWallpaper[] = "image"; static const char defaultWallpaperMode[] = "SingleImage"; Containment::StyleOption::StyleOption() : QStyleOptionGraphicsItem(), view(0) { version = Version; type = Type; } Containment::StyleOption::StyleOption(const Containment::StyleOption & other) : QStyleOptionGraphicsItem(other), view(other.view) { version = Version; type = Type; } Containment::StyleOption::StyleOption(const QStyleOptionGraphicsItem &other) : QStyleOptionGraphicsItem(other), view(0) { version = Version; type = Type; } Containment::Containment(QGraphicsItem *parent, const QString &serviceId, uint containmentId) : Applet(parent, serviceId, containmentId), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setPos(0, 0); setBackgroundHints(NoBackground); setContainmentType(CustomContainment); setHasConfigurationInterface(false); } Containment::Containment(QObject *parent, const QVariantList &args) : Applet(parent, args), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setPos(0, 0); setBackgroundHints(NoBackground); setHasConfigurationInterface(false); } Containment::Containment(const QString &packagePath, uint appletId, const QVariantList &args) : Plasma::Applet(packagePath, appletId, args), d(new ContainmentPrivate(this)) { // WARNING: do not access config() OR globalConfig() in this method! // that requires a scene, which is not available at this point setPos(0, 0); setBackgroundHints(NoBackground); setHasConfigurationInterface(false); } Containment::~Containment() { delete d; // Applet touches our dptr if we are a containment and is the superclass (think of dtors) // so we reset this as we exit the building Applet::d->isContainment = false; } void Containment::init() { Applet::init(); if (!isContainment()) { return; } setCacheMode(NoCache); setFlag(QGraphicsItem::ItemIsMovable, false); setFlag(QGraphicsItem::ItemClipsChildrenToShape, false); setAcceptDrops(true); setAcceptsHoverEvents(true); if (d->type == NoContainmentType) { setContainmentType(DesktopContainment); } //connect actions ContainmentPrivate::addDefaultActions(d->actions(), this); bool unlocked = immutability() == Mutable; //fix the text of the actions that need name() //btw, do we really want to use name() when it's a desktopcontainment? QAction *closeApplet = action("remove"); if (closeApplet) { closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name())); } QAction *configAction = action("configure"); if (configAction) { configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", name())); } QAction *appletBrowserAction = action("add widgets"); if (appletBrowserAction) { appletBrowserAction->setVisible(unlocked); appletBrowserAction->setEnabled(unlocked); connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets())); } QAction *act = action("next applet"); if (act) { connect(act, SIGNAL(triggered()), this, SLOT(focusNextApplet())); } act = action("previous applet"); if (act) { connect(act, SIGNAL(triggered()), this, SLOT(focusPreviousApplet())); } if (immutability() != SystemImmutable && corona()) { QAction *lockDesktopAction = corona()->action("lock widgets"); //keep a pointer so nobody notices it moved to corona if (lockDesktopAction) { d->actions()->addAction("lock widgets", lockDesktopAction); } } if (d->type != PanelContainment && d->type != CustomPanelContainment) { if (corona()) { //FIXME this is just here because of the darn keyboard shortcut :/ act = corona()->action("manage activities"); if (act) { d->actions()->addAction("manage activities", act); } //a stupid hack to make this one's keyboard shortcut work act = corona()->action("configure shortcuts"); if (act) { d->actions()->addAction("configure shortcuts", act); } } if (d->type == DesktopContainment && d->toolBox) { d->toolBox.data()->addTool(action("add widgets")); //TODO: do we need some way to allow this be overridden? // it's always available because shells rely on this // to offer their own custom configuration as well QAction *configureContainment = action("configure"); if (configureContainment) { d->toolBox.data()->addTool(configureContainment); } } //Set a default wallpaper the first time the containment is created, //for instance from the toolbox by the user if (d->drawWallpaper) { setDrawWallpaper(true); } } //containmentactionss, from config or defaults KConfigGroup cfg = config(); cfg = KConfigGroup(&cfg, "ActionPlugins"); if (cfg.exists()) { foreach (const QString &key, cfg.keyList()) { setContainmentActions(key, cfg.readEntry(key, QString())); } } else if (corona()){ //we need to be very careful here to not write anything //because we have a group, and so the defaults will get merged instead of overwritten //when copyTo is used (which happens right before restore() is called) ContainmentActionsPluginsConfig conf = corona()->containmentActionsDefaults(d->type); //steal the data directly, for efficiency QHash defaults = conf.d->plugins; for (QHash::const_iterator it = defaults.constBegin(), end = defaults.constEnd(); it != end; ++it) { ContainmentActions *plugin = ContainmentActions::load(this, it.value()); if (plugin) { d->actionPlugins.insert(it.key(), plugin); } } } } void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c) { actions->setConfigGroup("Shortcuts-Containment"); //adjust applet actions KAction *appAction = qobject_cast(actions->action("remove")); appAction->setShortcut(KShortcut("alt+d, alt+r")); if (c && c->d->isPanelContainment()) { appAction->setText(i18n("Remove this Panel")); } else { appAction->setText(i18n("Remove this Activity")); } appAction = qobject_cast(actions->action("configure")); if (appAction) { appAction->setShortcut(KShortcut("alt+d, alt+s")); appAction->setText(i18n("Activity Settings")); } //add our own actions KAction *appletBrowserAction = actions->addAction("add widgets"); appletBrowserAction->setAutoRepeat(false); appletBrowserAction->setText(i18n("Add Widgets...")); appletBrowserAction->setIcon(KIcon("list-add")); appletBrowserAction->setShortcut(KShortcut("alt+d, a")); appletBrowserAction->setData(AbstractToolBox::AddTool); KAction *action = actions->addAction("next applet"); action->setText(i18n("Next Widget")); //no icon action->setShortcut(KShortcut("alt+d, n")); action->setData(AbstractToolBox::ControlTool); action = actions->addAction("previous applet"); action->setText(i18n("Previous Widget")); //no icon action->setShortcut(KShortcut("alt+d, p")); action->setData(AbstractToolBox::ControlTool); } // helper function for sorting the list of applets bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2) { QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft(); QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft(); if (!qFuzzyCompare(p1.x(), p2.x())) { if (QApplication::layoutDirection() == Qt::RightToLeft) { return p1.x() > p2.x(); } return p1.x() < p2.x(); } return qFuzzyCompare(p1.y(), p2.y()) || p1.y() < p2.y(); } void Containment::restore(KConfigGroup &group) { /*kDebug() << "!!!!!!!!!!!!initConstraints" << group.name() << d->type; kDebug() << " location:" << group.readEntry("location", (int)d->location); kDebug() << " geom:" << group.readEntry("geometry", geometry()); kDebug() << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor); kDebug() << " screen:" << group.readEntry("screen", d->screen);*/ if (!isContainment()) { Applet::restore(group); return; } QRectF geo = group.readEntry("geometry", geometry()); //override max/min //this ensures panels are set to their saved size even when they have max & min set to prevent //resizing if (geo.size() != geo.size().boundedTo(maximumSize())) { setMaximumSize(maximumSize().expandedTo(geo.size())); } if (geo.size() != geo.size().expandedTo(minimumSize())) { setMinimumSize(minimumSize().boundedTo(geo.size())); } resize(geo.size()); //are we an offscreen containment? if (containmentType() != PanelContainment && containmentType() != CustomPanelContainment && geo.right() < 0) { corona()->addOffscreenWidget(this); } setLocation((Plasma::Location)group.readEntry("location", (int)d->location)); setFormFactor((Plasma::FormFactor)group.readEntry("formfactor", (int)d->formFactor)); //kDebug() << "setScreen from restore"; d->lastScreen = group.readEntry("lastScreen", d->lastScreen); d->lastDesktop = group.readEntry("lastDesktop", d->lastDesktop); setScreen(group.readEntry("screen", d->screen), group.readEntry("desktop", d->desktop)); QString activityId = group.readEntry("activityId", QString()); if (!activityId.isEmpty()) { d->context()->setCurrentActivityId(activityId); } setActivity(group.readEntry("activity", QString())); flushPendingConstraintsEvents(); restoreContents(group); setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); InternalToolBox *internalToolBox = qobject_cast(d->toolBox.data()); if (internalToolBox) { internalToolBox->restore(group); } KConfigGroup cfg(&group, "ActionPlugins"); kDebug() << cfg.keyList(); if (cfg.exists()) { //clear default containmentactionss qDeleteAll(d->actionPlugins); d->actionPlugins.clear(); //load the right configactions foreach (const QString &key, cfg.keyList()) { kDebug() << "loading" << key; setContainmentActions(key, cfg.readEntry(key, QString())); } } /* kDebug() << "Containment" << id() << "screen" << screen() << "geometry is" << geometry() << "wallpaper" << ((d->wallpaper) ? d->wallpaper->pluginName() : QString()) << "wallpaper mode" << wallpaperMode() << "config entries" << group.entryMap(); */ } void Containment::save(KConfigGroup &g) const { if (Applet::d->transient) { return; } KConfigGroup group = g; if (!group.isValid()) { group = config(); } // locking is saved in Applet::save Applet::save(group); if (!isContainment()) { return; } group.writeEntry("screen", d->screen); group.writeEntry("lastScreen", d->lastScreen); group.writeEntry("desktop", d->desktop); group.writeEntry("lastDesktop", d->lastDesktop); group.writeEntry("formfactor", (int)d->formFactor); group.writeEntry("location", (int)d->location); group.writeEntry("activity", d->context()->currentActivity()); group.writeEntry("activityId", d->context()->currentActivityId()); InternalToolBox *toolBox = qobject_cast(d->toolBox.data()); if (toolBox) { toolBox->save(group); } if (d->wallpaper) { group.writeEntry("wallpaperplugin", d->wallpaper->pluginName()); group.writeEntry("wallpaperpluginmode", d->wallpaper->renderingMode().name()); if (d->wallpaper->isInitialized()) { KConfigGroup wallpaperConfig(&group, "Wallpaper"); wallpaperConfig = KConfigGroup(&wallpaperConfig, d->wallpaper->pluginName()); d->wallpaper->save(wallpaperConfig); } } saveContents(group); } void Containment::saveContents(KConfigGroup &group) const { KConfigGroup applets(&group, "Applets"); foreach (const Applet *applet, d->applets) { KConfigGroup appletConfig(&applets, QString::number(applet->id())); applet->save(appletConfig); } } void ContainmentPrivate::initApplets() { foreach (Applet *applet, applets) { applet->restore(*applet->d->mainConfigGroup()); applet->init(); kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name(); } q->flushPendingConstraintsEvents(); foreach (Applet *applet, applets) { applet->flushPendingConstraintsEvents(); } kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name(); } void Containment::restoreContents(KConfigGroup &group) { KConfigGroup applets(&group, "Applets"); // Sort the applet configs in order of geometry to ensure that applets // are added from left to right or top to bottom for a panel containment QList appletConfigs; foreach (const QString &appletGroup, applets.groupList()) { //kDebug() << "reading from applet group" << appletGroup; KConfigGroup appletConfig(&applets, appletGroup); appletConfigs.append(appletConfig); } qStableSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan); QMutableListIterator it(appletConfigs); while (it.hasNext()) { KConfigGroup &appletConfig = it.next(); int appId = appletConfig.name().toUInt(); QString plugin = appletConfig.readEntry("plugin", QString()); if (plugin.isEmpty()) { continue; } d->addApplet(plugin, QVariantList(), appletConfig.readEntry("geometry", QRectF()), appId, true); } } Containment::Type Containment::containmentType() const { return d->type; } void Containment::setContainmentType(Containment::Type type) { if (d->type == type) { return; } delete d->toolBox.data(); d->type = type; d->checkContainmentFurniture(); } void ContainmentPrivate::checkContainmentFurniture() { if (q->isContainment() && (type == Containment::DesktopContainment || type == Containment::PanelContainment)) { createToolBox(); } } Corona *Containment::corona() const { return dynamic_cast(scene()); } void Containment::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { event->ignore(); if (d->wallpaper && d->wallpaper->isInitialized()) { QGraphicsItem *item = scene()->itemAt(event->scenePos()); if (item == this) { d->wallpaper->mouseMoveEvent(event); } } if (!event->isAccepted()) { event->accept(); Applet::mouseMoveEvent(event); } } void Containment::mousePressEvent(QGraphicsSceneMouseEvent *event) { event->ignore(); if (d->appletAt(event->scenePos())) { return; //no unexpected click-throughs } QGraphicsItem *item = scene()->itemAt(event->scenePos()); if (event->button() == Qt::RightButton && event->modifiers() == Qt::NoModifier && item != this) { //fake a contextmenuevent in case something in the containment plugin is expecting it //we do this because the click is sent around as a mousepress before a contextmenu event, //folderview only handles it as a contextmenu event, but if folderview isn't handling it //then we need to handle it as a mousepress *not* a contextmenuevent. //unfortunately this makes is possible for badly-behaved containments to eat rightclicks QGraphicsSceneContextMenuEvent contextEvent(QEvent::GraphicsSceneContextMenu); contextEvent.setReason(QGraphicsSceneContextMenuEvent::Mouse); contextEvent.setPos(event->pos()); contextEvent.setScenePos(event->scenePos()); contextEvent.setScreenPos(event->screenPos()); contextEvent.setModifiers(event->modifiers()); contextEvent.setWidget(event->widget()); scene()->sendEvent(item, &contextEvent); if (contextEvent.isAccepted()) { event->accept(); return; } } if (d->wallpaper && d->wallpaper->isInitialized() && !event->isAccepted()) { d->wallpaper->mousePressEvent(event); } if (event->isAccepted()) { setFocus(Qt::MouseFocusReason); } else { QString trigger = ContainmentActions::eventToString(event); if (d->actionPlugins.contains(trigger)) { if (d->prepareContainmentActions(trigger, event->screenPos())) { d->actionPlugins.value(trigger)->contextEvent(event); } event->accept(); return; } Applet::mousePressEvent(event); } } void Containment::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { event->ignore(); if (d->appletAt(event->scenePos())) { return; //no unexpected click-throughs } QString trigger = ContainmentActions::eventToString(event); if (d->wallpaper && d->wallpaper->isInitialized()) { d->wallpaper->mouseReleaseEvent(event); } if (!event->isAccepted() && isContainment()) { if (d->actionPlugins.contains(trigger)) { if (d->prepareContainmentActions(trigger, event->screenPos())) { d->actionPlugins.value(trigger)->contextEvent(event); } event->accept(); return; } event->accept(); Applet::mouseReleaseEvent(event); } } void Containment::showDropZone(const QPoint pos) { Q_UNUSED(pos) //Base implementation does nothing, don't put code here } void Containment::showContextMenu(const QPointF &containmentPos, const QPoint &screenPos) { d->showContextMenu(mapToScene(containmentPos), screenPos, false, false); } void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { //kDebug() << "let's see if we manage to get a context menu here, huh"; if (!isContainment() || !scene() || !KAuthorized::authorizeKAction("plasma/containment_context_menu")) { Applet::contextMenuEvent(event); return; } if (d->showContextMenu(event->scenePos(), event->screenPos(), true, event->reason() == QGraphicsSceneContextMenuEvent::Mouse)) { event->accept(); } else { Applet::contextMenuEvent(event); } } void ContainmentPrivate::containmentActions(KMenu &desktopMenu) { if (static_cast(q->scene())->immutability() != Mutable && !KAuthorized::authorizeKAction("plasma/containment_actions")) { //kDebug() << "immutability"; return; } QString trigger = "RightButton;NoModifier"; //get base context actions ContainmentActions *plugin = actionPlugins.value(trigger); if (plugin) { if (!plugin->isInitialized()) { KConfigGroup cfg = q->config(); cfg = KConfigGroup(&cfg, "ActionPlugins"); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } if (plugin->configurationRequired()) { desktopMenu.addTitle(i18n("This menu needs to be configured")); desktopMenu.addAction(q->action("configure")); } else { QList actions = plugin->contextualActions(); if (actions.isEmpty()) { //it probably didn't bother implementing the function. give the user a chance to set //a better plugin. //note that if the user sets no-plugin this won't happen... //FIXME maybe the behaviour could be better if (type == Containment::DesktopContainment) { desktopMenu.addAction(q->action("configure")); } } else { //yay! desktopMenu.addActions(actions); } } } } void ContainmentPrivate::appletActions(KMenu &desktopMenu, Applet *applet, bool includeApplet) { if (includeApplet) { foreach (QAction *action, applet->contextualActions()) { if (action) { desktopMenu.addAction(action); } } } QAction *configureApplet = applet->d->actions->action("configure"); if (configureApplet && configureApplet->isEnabled()) { desktopMenu.addAction(configureApplet); } QAction *runAssociatedApplication = applet->d->actions->action("run associated application"); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu.addAction(runAssociatedApplication); } KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); containmentActions(*containmentMenu); if (!containmentMenu->isEmpty()) { int enabled = 0; //count number of real actions foreach (const QAction *action, containmentMenu->actions()) { if (action->isVisible() && !action->isSeparator()) { ++enabled; } } if (enabled) { //if there is only one, don't create a submenu if (enabled < 2) { foreach (QAction *action, containmentMenu->actions()) { desktopMenu.addAction(action); } } else { desktopMenu.addMenu(containmentMenu); } } } if (q->immutability() == Mutable) { if (!desktopMenu.isEmpty()) { desktopMenu.addSeparator(); } QAction *closeApplet = applet->d->actions->action("remove"); if (closeApplet) { desktopMenu.addAction(closeApplet); } } } Applet* ContainmentPrivate::appletAt(const QPointF &point) { Applet *applet = 0; QGraphicsItem *item = q->scene()->itemAt(point); if (item == q) { item = 0; } while (item) { if (item->isWidget()) { applet = qobject_cast(static_cast(item)); if (applet) { if (applet->isContainment()) { applet = 0; } break; } } AppletHandle *handle = dynamic_cast(item); if (handle) { //pretend it was on the applet applet = handle->applet(); break; } item = item->parentItem(); } return applet; } bool ContainmentPrivate::showContextMenu(const QPointF &point, const QPoint &screenPos, bool includeApplet, bool isMouseEvent) { Applet *applet = appletAt(point); KMenu desktopMenu; //kDebug() << "context menu event " << (QObject*)applet; if (applet) { appletActions(desktopMenu, applet, includeApplet); } else if (isMouseEvent) { return false; //fall through to plugin/wallpaper stuff } else { containmentActions(desktopMenu); } if (!desktopMenu.isEmpty()) { //kDebug() << "executing at" << screenPos; QPoint pos = screenPos; if (applet && isPanelContainment()) { desktopMenu.adjustSize(); pos = applet->popupPosition(desktopMenu.size()); if (formFactor == Vertical) { if (pos.y() + desktopMenu.height() < screenPos.y()) { pos.setY(screenPos.y()); } } else if (formFactor == Horizontal) { if (pos.x() + desktopMenu.width() < screenPos.x()) { pos.setX(screenPos.x()); } } } desktopMenu.exec(pos); return true; } return false; } bool ContainmentPrivate::showAppletContextMenu(Applet *applet, const QPoint &screenPos) { KMenu desktopMenu; appletActions(desktopMenu, applet, true); if (!desktopMenu.isEmpty()) { desktopMenu.exec(screenPos); return true; } return false; } void Containment::setFormFactor(FormFactor formFactor) { if (d->formFactor == formFactor) { return; } //kDebug() << "switching FF to " << formFactor; d->formFactor = formFactor; if (isContainment() && (d->type == PanelContainment || d->type == CustomPanelContainment)) { // we are a panel and we have chaged our orientation d->positionPanel(true); } InternalToolBox *toolBox = qobject_cast(d->toolBox.data()); if (toolBox) { if (d->formFactor == Vertical) { toolBox->setCorner(InternalToolBox::Bottom); //defaults to horizontal } else if (QApplication::layoutDirection() == Qt::RightToLeft) { toolBox->setCorner(InternalToolBox::Left); } else { toolBox->setCorner(InternalToolBox::Right); } } updateConstraints(Plasma::FormFactorConstraint); KConfigGroup c = config(); c.writeEntry("formfactor", (int)formFactor); emit configNeedsSaving(); } void Containment::setLocation(Location location) { if (d->location == location) { return; } bool emitGeomChange = false; if ((location == TopEdge || location == BottomEdge) && (d->location == TopEdge || d->location == BottomEdge)) { emitGeomChange = true; } if ((location == RightEdge || location == LeftEdge) && (d->location == RightEdge || d->location == LeftEdge)) { emitGeomChange = true; } d->location = location; foreach (Applet *applet, d->applets) { applet->updateConstraints(Plasma::LocationConstraint); } if (emitGeomChange) { // our geometry on the scene will not actually change, // but for the purposes of views it has emit geometryChanged(); } updateConstraints(Plasma::LocationConstraint); KConfigGroup c = config(); c.writeEntry("location", (int)location); emit configNeedsSaving(); } void Containment::addSiblingContainment() { emit addSiblingContainment(this); } void Containment::clearApplets() { foreach (Applet *applet, d->applets) { applet->d->cleanUpAndDelete(); } d->applets.clear(); } Applet *Containment::addApplet(const QString &name, const QVariantList &args, const QRectF &appletGeometry) { return d->addApplet(name, args, appletGeometry); } void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit) { if (!isContainment() || (!delayInit && immutability() != Mutable)) { return; } if (!applet) { kDebug() << "adding null applet!?!"; return; } if (d->applets.contains(applet)) { kDebug() << "already have this applet!"; } Containment *currentContainment = applet->containment(); if (d->type == PanelContainment) { //panels don't want backgrounds, which is important when setting geometry setBackgroundHints(NoBackground); } if (currentContainment && currentContainment != this) { emit currentContainment->appletRemoved(applet); if (currentContainment->d->focusedApplet == applet) { currentContainment->d->focusedApplet = 0; } disconnect(applet, 0, currentContainment, 0); applet->removeSceneEventFilter(currentContainment); KConfigGroup oldConfig = applet->config(); currentContainment->d->applets.removeAll(applet); if (currentContainment->d->handles.contains(applet)) { currentContainment->d->handles.remove(applet); } applet->setParentItem(this); // now move the old config to the new location //FIXME: this doesn't seem to get the actual main config group containing plugin=, etc KConfigGroup c = config().group("Applets").group(QString::number(applet->id())); oldConfig.reparent(&c); applet->d->resetConfigurationObject(); disconnect(applet, SIGNAL(activate()), currentContainment, SIGNAL(activate())); } else { applet->setParentItem(this); } d->applets << applet; connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus())); connect(applet, SIGNAL(appletDestroyed(Plasma::Applet*)), this, SLOT(appletDestroyed(Plasma::Applet*))); connect(applet, SIGNAL(newStatus(Plasma::ItemStatus)), this, SLOT(checkStatus(Plasma::ItemStatus))); connect(applet, SIGNAL(activate()), this, SIGNAL(activate())); if (pos != QPointF(-1, -1)) { applet->setPos(pos); } if (delayInit || currentContainment) { if (d->type == DesktopContainment) { applet->installSceneEventFilter(this); //applet->setWindowFlags(Qt::Window); } } else { applet->restore(*applet->d->mainConfigGroup()); applet->init(); Plasma::Animation *anim = Plasma::Animator::create(Plasma::Animator::AppearAnimation); if (anim) { connect(anim, SIGNAL(finished()), this, SLOT(appletAppearAnimationComplete())); anim->start(QAbstractAnimation::DeleteWhenStopped); } else { d->appletAppeared(applet); } } applet->setFlag(QGraphicsItem::ItemIsMovable, true); applet->updateConstraints(Plasma::AllConstraints); if (!delayInit) { applet->flushPendingConstraintsEvents(); } emit appletAdded(applet, pos); if (!currentContainment) { applet->updateConstraints(Plasma::StartupCompletedConstraint); if (!delayInit) { applet->flushPendingConstraintsEvents(); } } if (!delayInit) { applet->d->scheduleModificationNotification(); } } Applet::List Containment::applets() const { return d->applets; } void Containment::setScreen(int newScreen, int newDesktop) { // What we want to do in here is: // * claim the screen as our own // * signal whatever may be watching this containment about the switch // * if we are a full screen containment, then: // * resize to match the screen if we're that kind of containment // * kick other full-screen containments off this screen // * if we had a screen, then give our screen to the containment // we kick out // // a screen of -1 means no associated screen. Q_ASSERT(corona()); int numScreens = corona()->numScreens(); if (newScreen < -1) { newScreen = -1; } // -1 == All desktops if (newDesktop < -1 || newDesktop > KWindowSystem::numberOfDesktops() - 1) { newDesktop = -1; } //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << d->type; Containment *swapScreensWith(0); if (d->type == DesktopContainment || d->type >= CustomContainment) { // we want to listen to changes in work area if our screen changes if (d->screen < 0 && newScreen > -1) { connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(positionToolBox()), Qt::UniqueConnection); } else if (newScreen < 0) { disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(positionToolBox())); } if (newScreen > -1 && corona()) { // sanity check to make sure someone else doesn't have this screen already! Containment *currently = corona()->containmentForScreen(newScreen, newDesktop); if (currently && currently != this) { kDebug() << "currently is on screen" << currently->screen() << "desktop" << currently->desktop() << "and is" << currently->activity() << (QObject*)currently << "i'm" << (QObject*)this; //kDebug() << "setScreen due to swap"; //make the view completely forget about us emit screenChanged(d->screen, -1, this); currently->setScreen(-1, newDesktop); swapScreensWith = currently; } } } if (newScreen < numScreens && newScreen > -1 && (d->type == DesktopContainment || d->type >= CustomContainment)) { resize(corona()->screenGeometry(newScreen).size()); } int oldDesktop = d->desktop; d->desktop = newDesktop; int oldScreen = d->screen; d->screen = newScreen; updateConstraints(Plasma::ScreenConstraint); if (oldScreen != newScreen || oldDesktop != newDesktop) { emit screenChanged(oldScreen, newScreen, this); KConfigGroup c = config(); c.writeEntry("screen", d->screen); c.writeEntry("desktop", d->desktop); if (newScreen != -1) { d->lastScreen = newScreen; d->lastDesktop = newDesktop; c.writeEntry("lastScreen", d->lastScreen); c.writeEntry("lastDesktop", d->lastDesktop); } emit configNeedsSaving(); } if (swapScreensWith) { //kDebug() << "setScreen due to swap, part 2"; swapScreensWith->setScreen(oldScreen, oldDesktop); } d->checkRemoveAction(); if (newScreen >= 0) { emit activate(); } } int Containment::screen() const { return d->screen; } int Containment::lastScreen() const { return d->lastScreen; } int Containment::desktop() const { return d->desktop; } int Containment::lastDesktop() const { return d->lastDesktop; } KPluginInfo::List Containment::listContainments(const QString &category, const QString &parentApp) { return listContainmentsOfType(QString(), category, parentApp); } KPluginInfo::List Containment::listContainmentsOfType(const QString &type, const QString &category, const QString &parentApp) { QString constraint; if (parentApp.isEmpty()) { constraint.append("(not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '')"); } else { constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); } if (!type.isEmpty()) { if (!constraint.isEmpty()) { constraint.append(" and "); } constraint.append("'").append(type).append("' ~in [X-Plasma-ContainmentCategories]"); } if (!category.isEmpty()) { if (!constraint.isEmpty()) { constraint.append(" and "); } constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'"); if (category == "Miscellaneous") { constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')"); } } KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); //kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches"; return KPluginInfo::fromServices(offers); } KPluginInfo::List Containment::listContainmentsForMimetype(const QString &mimetype) { const QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); //kDebug() << mimetype << constraint; const KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); return KPluginInfo::fromServices(offers); } QStringList Containment::listContainmentTypes() { KPluginInfo::List containmentInfos = listContainments(); QSet types; foreach (const KPluginInfo &containmentInfo, containmentInfos) { QStringList theseTypes = containmentInfo.service()->property("X-Plasma-ContainmentCategories").toStringList(); foreach (const QString &type, theseTypes) { types.insert(type); } } return types.toList(); } void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { //kDebug() << immutability() << Mutable << (immutability() == Mutable); event->setAccepted(immutability() == Mutable && (event->mimeData()->hasFormat(static_cast(scene())->appletMimeType()) || KUrl::List::canDecode(event->mimeData()) || event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType()))); if (!event->isAccepted()) { // check to see if we have an applet that accepts the format. QStringList formats = event->mimeData()->formats(); foreach (const QString &format, formats) { KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(format); if (!appletList.isEmpty()) { event->setAccepted(true); break; } } if (!event->isAccepted()) { foreach (const QString &format, formats) { KPluginInfo::List wallpaperList = Wallpaper::listWallpaperInfoForMimetype(format); if (!wallpaperList.isEmpty()) { event->setAccepted(true); break; } } } } if (event->isAccepted()) { if (d->dropZoneStarted) { showDropZone(event->pos().toPoint()); } else { if (!d->showDropZoneDelayTimer) { d->showDropZoneDelayTimer = new QTimer(this); d->showDropZoneDelayTimer->setInterval(300); d->showDropZoneDelayTimer->setSingleShot(true); connect(d->showDropZoneDelayTimer, SIGNAL(timeout()), this, SLOT(showDropZoneDelayed())); } d->dropPoints.insert(0, event->pos()); d->showDropZoneDelayTimer->start(); } } } void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { //kDebug() << event->pos() << size().height() << size().width(); if (d->showDropZoneDelayTimer) { d->showDropZoneDelayTimer->stop(); } if (event->pos().y() < 1 || event->pos().y() > size().height() || event->pos().x() < 1 || event->pos().x() > size().width()) { showDropZone(QPoint()); d->dropZoneStarted = false; } } void ContainmentPrivate::showDropZoneDelayed() { dropZoneStarted = true; q->showDropZone(dropPoints.value(0).toPoint()); dropPoints.remove(0); } void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { QGraphicsItem *item = scene()->itemAt(event->scenePos()); event->setAccepted(item == this || item == d->toolBox.data() || !item); //kDebug() << event->isAccepted() << d->showDropZoneDelayTimer->isActive(); if (!event->isAccepted()) { if (d->showDropZoneDelayTimer) { d->showDropZoneDelayTimer->stop(); } } else if (!d->showDropZoneDelayTimer->isActive() && immutability() == Plasma::Mutable) { showDropZone(event->pos().toPoint()); } } void Containment::dropEvent(QGraphicsSceneDragDropEvent *event) { if (isContainment()) { d->dropData(event->scenePos(), event->screenPos(), event); } else { Applet::dropEvent(event); } } void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent) { if (q->immutability() != Mutable) { return; } QPointF pos = q->mapFromScene(scenePos); const QMimeData *mimeData = 0; if (dropEvent) { mimeData = dropEvent->mimeData(); } else { QClipboard *clipboard = QApplication::clipboard(); mimeData = clipboard->mimeData(QClipboard::Selection); //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection? } if (!mimeData) { //Selection is either empty or not supported on this OS kDebug() << "no mime data"; return; } //kDebug() << event->mimeData()->text(); QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString()); if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) { QString data = mimeData->data(appletMimetype); const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); foreach (const QString &appletName, appletNames) { //kDebug() << "doing" << appletName; QRectF geom(pos, QSize(0, 0)); q->addApplet(appletName, QVariantList(), geom); } if (dropEvent) { dropEvent->acceptProposedAction(); } } else if (mimeData->hasFormat(ExtenderItemMimeData::mimeType())) { kDebug() << "mimetype plasma/extenderitem is dropped, creating internal:extender"; //Handle dropping extenderitems. const ExtenderItemMimeData *extenderData = qobject_cast(mimeData); if (extenderData) { ExtenderItem *item = extenderData->extenderItem(); QRectF geometry(pos - extenderData->pointerOffset(), item->size()); kDebug() << "desired geometry: " << geometry; Applet *applet = qobject_cast(item->extender() ? item->extender()->applet() : 0); if (applet) { qreal left, top, right, bottom; applet->getContentsMargins(&left, &top, &right, &bottom); applet->setPos(geometry.topLeft() - QPointF(int(left), int(top))); applet->show(); } else { applet = q->addApplet("internal:extender", QVariantList(), geometry); } item->setExtender(applet->extender()); } } else if (KUrl::List::canDecode(mimeData)) { //TODO: collect the mimetypes of available script engines and offer // to create widgets out of the matching URLs, if any const KUrl::List urls = KUrl::List::fromMimeData(mimeData); foreach (const KUrl &url, urls) { if (AccessManager::supportedProtocols().contains(url.protocol())) { AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url); if (dropEvent) { dropPoints[job] = dropEvent->pos(); } else { dropPoints[job] = scenePos; } QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)), q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*))); } else { KMimeType::Ptr mime = KMimeType::findByUrl(url); QString mimeName = mime->name(); QRectF geom(pos, QSize()); QVariantList args; args << url.url(); kDebug() << "can decode" << mimeName << args; // It may be a directory or a file, let's stat KIO::JobFlags flags = KIO::HideProgressInfo; KIO::MimetypeJob *job = KIO::mimetype(url, flags); if (dropEvent) { dropPoints[job] = dropEvent->pos(); } else { dropPoints[job] = scenePos; } QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*))); QObject::connect(job, SIGNAL(mimetype(KIO::Job *, const QString&)), q, SLOT(mimeTypeRetrieved(KIO::Job *, const QString&))); KMenu *choices = new KMenu("Content dropped"); choices->addAction(KIcon("process-working"), i18n("Fetching file type...")); if (dropEvent) { choices->popup(dropEvent->screenPos()); } else { choices->popup(screenPos); } dropMenus[job] = choices; } } if (dropEvent) { dropEvent->acceptProposedAction(); } } else { QStringList formats = mimeData->formats(); QHash seenPlugins; QHash pluginFormats; foreach (const QString &format, formats) { KPluginInfo::List plugins = Applet::listAppletInfoForMimetype(format); foreach (const KPluginInfo &plugin, plugins) { if (seenPlugins.contains(plugin.pluginName())) { continue; } seenPlugins.insert(plugin.pluginName(), plugin); pluginFormats.insert(plugin.pluginName(), format); } } //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); QString selectedPlugin; if (seenPlugins.isEmpty()) { // do nothing } else if (seenPlugins.count() == 1) { selectedPlugin = seenPlugins.constBegin().key(); } else { KMenu choices; QHash actionsToPlugins; foreach (const KPluginInfo &info, seenPlugins) { QAction *action; if (!info.icon().isEmpty()) { action = choices.addAction(KIcon(info.icon()), info.name()); } else { action = choices.addAction(info.name()); } actionsToPlugins.insert(action, info.pluginName()); } QAction *choice = choices.exec(screenPos); if (choice) { selectedPlugin = actionsToPlugins[choice]; } } if (!selectedPlugin.isEmpty()) { if (!dropEvent) { // since we may have entered an event loop up above with the menu, // the clipboard item may no longer be valid, as QClipboard resets // the object behind the back of the application with a zero timer // so we fetch it again here QClipboard *clipboard = QApplication::clipboard(); mimeData = clipboard->mimeData(QClipboard::Selection); } KTemporaryFile tempFile; if (mimeData && tempFile.open()) { //TODO: what should we do with files after the applet is done with them?? tempFile.setAutoRemove(false); { QDataStream stream(&tempFile); QByteArray data = mimeData->data(pluginFormats[selectedPlugin]); stream.writeRawData(data, data.size()); } QRectF geom(pos, QSize()); QVariantList args; args << tempFile.fileName(); kDebug() << args; tempFile.close(); q->addApplet(selectedPlugin, args, geom); } } } } void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job) { QObject::disconnect(job, 0, q, 0); dropPoints.remove(job); KMenu *choices = dropMenus.take(job); if (choices) { delete choices; } job->kill(); } void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job) { QPointF pos = dropPoints.take(job); if (job->error()) { //TODO: nice user visible error handling (knotification probably?) kDebug() << "remote applet access failed: " << job->errorText(); return; } if (!job->applet()) { kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero"; return; } q->addApplet(job->applet(), pos); } void ContainmentPrivate::dropJobResult(KJob *job) { KIO::TransferJob* tjob = dynamic_cast(job); if (!tjob) { kDebug() << "job is not a KIO::TransferJob, won't handle the drop..."; clearDataForMimeJob(tjob); return; } if (job->error()) { kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString(); } // We call mimetypeRetrieved since there might be other mechanisms // for finding suitable applets. Cleanup happens there as well. mimeTypeRetrieved(qobject_cast(job), QString()); } void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype) { kDebug() << "Mimetype Job returns." << mimetype; KIO::TransferJob* tjob = dynamic_cast(job); if (!tjob) { kDebug() << "job should be a TransferJob, but isn't"; clearDataForMimeJob(job); return; } KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url()); if (mimetype.isEmpty() && !appletList.count()) { clearDataForMimeJob(job); kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")"; return; } else { QPointF posi; // will be overwritten with the event's position if (dropPoints.keys().contains(tjob)) { posi = dropPoints[tjob]; kDebug() << "Received a suitable dropEvent at" << posi; } else { kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; clearDataForMimeJob(job); return; } KMenu *choices = dropMenus.value(tjob); if (!choices) { kDebug() << "Bailing out. No QMenu found for this job."; clearDataForMimeJob(job); return; } QVariantList args; args << tjob->url().url() << mimetype; kDebug() << "Creating menu for:" << mimetype << posi << args; appletList << Applet::listAppletInfoForMimetype(mimetype); KPluginInfo::List wallpaperList; if (q->drawWallpaper()) { wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimetype); } if (!appletList.isEmpty() || !wallpaperList.isEmpty()) { choices->clear(); QHash actionsToApplets; choices->addTitle(i18n("Widgets")); foreach (const KPluginInfo &info, appletList) { kDebug() << info.name(); QAction *action; if (!info.icon().isEmpty()) { action = choices->addAction(KIcon(info.icon()), info.name()); } else { action = choices->addAction(info.name()); } actionsToApplets.insert(action, info.pluginName()); kDebug() << info.pluginName(); } actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon"); QHash actionsToWallpapers; if (!wallpaperList.isEmpty()) { choices->addTitle(i18n("Wallpaper")); QMap sorted; foreach (const KPluginInfo &info, appletList) { sorted.insert(info.name(), info); } foreach (const KPluginInfo &info, wallpaperList) { QAction *action; if (!info.icon().isEmpty()) { action = choices->addAction(KIcon(info.icon()), info.name()); } else { action = choices->addAction(info.name()); } actionsToWallpapers.insert(action, info.pluginName()); } } QAction *choice = choices->exec(); if (choice) { // Put the job on hold so it can be recycled to fetch the actual content, // which is to be expected when something's dropped onto the desktop and // an applet is to be created with this URL if (!mimetype.isEmpty() && !tjob->error()) { tjob->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); } QString plugin = actionsToApplets.value(choice); if (plugin.isEmpty()) { //set wallpapery stuff plugin = actionsToWallpapers.value(choice); if (!wallpaper || plugin != wallpaper->pluginName()) { kDebug() << "Wallpaper dropped:" << tjob->url(); q->setWallpaper(plugin); } if (wallpaper) { kDebug() << "Wallpaper dropped:" << tjob->url(); emit wallpaper->urlDropped(tjob->url()); } } else { addApplet(actionsToApplets[choice], args, QRectF(posi, QSize())); } clearDataForMimeJob(job); return; } } else { // we can at least create an icon as a link to the URL addApplet("icon", args, QRectF(posi, QSize())); } } clearDataForMimeJob(job); } const QGraphicsItem *Containment::toolBoxItem() const { return d->toolBox.data(); } void Containment::setToolBox(AbstractToolBox *toolBox) { if (d->toolBox.data()) { d->toolBox.data()->deleteLater(); } d->toolBox = toolBox; } AbstractToolBox *Containment::toolBox() const { return d->toolBox.data(); } void Containment::resizeEvent(QGraphicsSceneResizeEvent *event) { Applet::resizeEvent(event); if (isContainment()) { if (d->isPanelContainment()) { d->positionPanel(); } else if (corona()) { QMetaObject::invokeMethod(corona(), "layoutContainments"); } if (d->wallpaper) { d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); } } } void Containment::keyPressEvent(QKeyEvent *event) { //kDebug() << "keyPressEvent with" << event->key() // << "and hoping and wishing for a" << Qt::Key_Tab; if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) { if (!d->applets.isEmpty()) { kDebug() << "let's give focus to...." << (QObject*)d->applets.first(); d->applets.first()->setFocus(Qt::TabFocusReason); } } } void Containment::wheelEvent(QGraphicsSceneWheelEvent *event) { event->ignore(); if (d->appletAt(event->scenePos())) { return; //no unexpected click-throughs } if (d->wallpaper && d->wallpaper->isInitialized()) { QGraphicsItem *item = scene()->itemAt(event->scenePos()); if (item == this) { event->ignore(); d->wallpaper->wheelEvent(event); if (event->isAccepted()) { return; } } } QString trigger = ContainmentActions::eventToString(event); if (d->actionPlugins.contains(trigger)) { if (d->prepareContainmentActions(trigger, event->screenPos())) { d->actionPlugins.value(trigger)->contextEvent(event); } event->accept(); return; } event->ignore(); Applet::wheelEvent(event); } bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event) { Applet *applet = qgraphicsitem_cast(watched); // Otherwise we're watching something we shouldn't be... Q_ASSERT(applet != 0); if (!d->applets.contains(applet)) { return false; } //kDebug() << "got sceneEvent"; switch (event->type()) { case QEvent::GraphicsSceneHoverEnter: //kDebug() << "got hoverenterEvent" << immutability() << " " << applet->immutability(); if (immutability() == Mutable && applet->immutability() == Mutable) { QGraphicsSceneHoverEvent *he = static_cast(event); if (d->handles.contains(applet)) { AppletHandle *handle = d->handles.value(applet); if (handle) { handle->setHoverPos(he->pos()); } } else { //kDebug() << "generated applet handle"; AppletHandle *handle = new AppletHandle(this, applet, he->pos()); d->handles[applet] = handle; connect(handle, SIGNAL(disappearDone(AppletHandle*)), this, SLOT(handleDisappeared(AppletHandle*))); connect(applet, SIGNAL(geometryChanged()), handle, SLOT(appletResized())); } } break; case QEvent::GraphicsSceneHoverMove: if (immutability() == Mutable && applet->immutability() == Mutable) { QGraphicsSceneHoverEvent *he = static_cast(event); if (d->handles.contains(applet)) { AppletHandle *handle = d->handles.value(applet); if (handle) { handle->setHoverPos(he->pos()); } } } break; default: break; } return false; } QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) { //FIXME if the applet is moved to another containment we need to unfocus it if (isContainment() && (change == QGraphicsItem::ItemSceneHasChanged || change == QGraphicsItem::ItemPositionHasChanged)) { switch (d->type) { case PanelContainment: case CustomPanelContainment: d->positionPanel(); break; default: if (corona()) { QMetaObject::invokeMethod(corona(), "layoutContainments"); } break; } } return Applet::itemChange(change, value); } void Containment::enableAction(const QString &name, bool enable) { QAction *action = this->action(name); if (action) { action->setEnabled(enable); action->setVisible(enable); } } void Containment::addToolBoxAction(QAction *action) { d->createToolBox(); d->toolBox.data()->addTool(action); } void Containment::removeToolBoxAction(QAction *action) { if (d->toolBox) { d->toolBox.data()->removeTool(action); } } void Containment::setToolBoxOpen(bool open) { if (open) { openToolBox(); } else { closeToolBox(); } } bool Containment::isToolBoxOpen() const { return (d->toolBox && d->toolBox.data()->isShowing()); } void Containment::openToolBox() { if (d->toolBox && !d->toolBox.data()->isShowing()) { d->toolBox.data()->setShowing(true); emit toolBoxVisibilityChanged(true); } } void Containment::closeToolBox() { if (d->toolBox && d->toolBox.data()->isShowing()) { d->toolBox.data()->setShowing(false); emit toolBoxVisibilityChanged(false); } } void Containment::addAssociatedWidget(QWidget *widget) { Applet::addAssociatedWidget(widget); if (d->focusedApplet) { d->focusedApplet->addAssociatedWidget(widget); } foreach (const Applet *applet, d->applets) { if (applet->d->activationAction) { widget->addAction(applet->d->activationAction); } } } void Containment::removeAssociatedWidget(QWidget *widget) { Applet::removeAssociatedWidget(widget); if (d->focusedApplet) { d->focusedApplet->removeAssociatedWidget(widget); } foreach (const Applet *applet, d->applets) { if (applet->d->activationAction) { widget->removeAction(applet->d->activationAction); } } } void Containment::setDrawWallpaper(bool drawWallpaper) { d->drawWallpaper = drawWallpaper; if (drawWallpaper) { KConfigGroup cfg = config(); const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper); const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode); setWallpaper(wallpaper, mode); } else { delete d->wallpaper; d->wallpaper = 0; } } bool Containment::drawWallpaper() { return d->drawWallpaper; } void Containment::setWallpaper(const QString &pluginName, const QString &mode) { KConfigGroup cfg = config(); bool newPlugin = true; bool newMode = true; if (d->drawWallpaper) { if (d->wallpaper) { // we have a wallpaper, so let's decide whether we need to swap it out if (d->wallpaper->pluginName() != pluginName) { delete d->wallpaper; d->wallpaper = 0; } else { // it's the same plugin, so let's save its state now so when // we call restore later on we're safe newMode = d->wallpaper->renderingMode().name() != mode; newPlugin = false; } } if (!pluginName.isEmpty() && !d->wallpaper) { d->wallpaper = Plasma::Wallpaper::load(pluginName); } if (d->wallpaper) { d->wallpaper->setParent(this); d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); d->wallpaper->setRenderingMode(mode); if (newPlugin) { cfg.writeEntry("wallpaperplugin", pluginName); } if (d->wallpaper->isInitialized()) { KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper"); wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName); d->wallpaper->restore(wallpaperConfig); } if (newMode) { cfg.writeEntry("wallpaperpluginmode", mode); } } update(); } if (!d->wallpaper) { cfg.deleteEntry("wallpaperplugin"); cfg.deleteEntry("wallpaperpluginmode"); } if (newPlugin || newMode) { if (newPlugin && d->wallpaper) { connect(d->wallpaper, SIGNAL(configureRequested()), this, SLOT(requestConfiguration())); connect(d->wallpaper, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); } emit configNeedsSaving(); } } Plasma::Wallpaper *Containment::wallpaper() const { return d->wallpaper; } void Containment::setContainmentActions(const QString &trigger, const QString &pluginName) { KConfigGroup cfg = config(); cfg = KConfigGroup(&cfg, "ActionPlugins"); bool everSaved = cfg.exists(); ContainmentActions *plugin = 0; if (d->actionPlugins.contains(trigger)) { plugin = d->actionPlugins.value(trigger); if (plugin->pluginName() != pluginName) { d->actionPlugins.remove(trigger); delete plugin; plugin=0; } } if (pluginName.isEmpty()) { cfg.deleteEntry(trigger); } else if (plugin) { //it already existed, just reload config if (plugin->isInitialized()) { //FIXME make a truly unique config group KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } } else { plugin = ContainmentActions::load(this, pluginName); if (plugin) { cfg.writeEntry(trigger, pluginName); d->actionPlugins.insert(trigger, plugin); } else { //bad plugin... gets removed. is this a feature or a bug? cfg.deleteEntry(trigger); } } if (!everSaved) { //ensure all our defaults are written out //the disadvantage of using a group... for (QHash::const_iterator it = d->actionPlugins.constBegin(), end = d->actionPlugins.constEnd(); it != end; ++it) { cfg.writeEntry(it.key(), it.value()->pluginName()); } } emit configNeedsSaving(); } QStringList Containment::containmentActionsTriggers() { return d->actionPlugins.keys(); } QString Containment::containmentActions(const QString &trigger) { ContainmentActions *c = d->actionPlugins.value(trigger); return c ? c->pluginName() : QString(); } void Containment::setActivity(const QString &activity) { Context *context = d->context(); if (context->currentActivity() != activity) { context->setCurrentActivity(activity); } } void ContainmentPrivate::onContextChanged(Plasma::Context *con) { foreach (Applet *a, applets) { a->updateConstraints(ContextConstraint); } KConfigGroup c = q->config(); QString act = con->currentActivityId(); //save anything that's been set (boy I hope this avoids overwriting things) //FIXME of course if the user sets the name to an empty string we have a bug //but once we get context retrieving the name as soon as the id is set, this issue should go away if (!act.isEmpty()) { c.writeEntry("activityId", act); } act = con->currentActivity(); if (!act.isEmpty()) { c.writeEntry("activity", act); } if (toolBox) { toolBox.data()->update(); } emit q->configNeedsSaving(); emit q->contextChanged(con); } QString Containment::activity() const { return d->context()->currentActivity(); } Context *Containment::context() const { return d->context(); } Context *ContainmentPrivate::context() { if (!con) { con = new Context(q); q->connect(con, SIGNAL(changed(Plasma::Context*)), q, SLOT(onContextChanged(Plasma::Context*))); } return con; } KActionCollection* ContainmentPrivate::actions() { return static_cast(q)->d->actions; } void ContainmentPrivate::focusApplet(Plasma::Applet *applet) { if (focusedApplet == applet) { return; } QList widgets = actions()->associatedWidgets(); if (focusedApplet) { foreach (QWidget *w, widgets) { focusedApplet->removeAssociatedWidget(w); } } if (applet && applets.contains(applet)) { //kDebug() << "switching to" << applet->name(); focusedApplet = applet; foreach (QWidget *w, widgets) { focusedApplet->addAssociatedWidget(w); } if (!focusedApplet->hasFocus()) { focusedApplet->setFocus(Qt::ShortcutFocusReason); } } else { focusedApplet = 0; } } void Containment::focusNextApplet() { if (d->applets.isEmpty()) { return; } int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0; if (index >= d->applets.size()) { index = 0; } kDebug() << "index" << index; d->focusApplet(d->applets.at(index)); } void Containment::focusPreviousApplet() { if (d->applets.isEmpty()) { return; } int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1; if (index < 0) { index = d->applets.size() - 1; } kDebug() << "index" << index; d->focusApplet(d->applets.at(index)); } void Containment::destroy() { destroy(true); } void Containment::showConfigurationInterface() { Applet::showConfigurationInterface(); } void ContainmentPrivate::requestConfiguration() { emit q->configureRequested(q); } void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus) { //kDebug() << "================== "<< appletStatus << q->status(); if (appletStatus == q->status()) { emit q->newStatus(appletStatus); return; } if (appletStatus < q->status()) { // check to see if any other applet has a higher status, and stick with that // if we do foreach (Applet *applet, applets) { if (applet->status() > appletStatus) { appletStatus = applet->status(); } } } q->setStatus(appletStatus); } void Containment::destroy(bool confirm) { if (immutability() != Mutable || Applet::d->transient) { return; } if (isContainment()) { //FIXME maybe that %1 should be the containment type not the name //FIXME: should not be blocking if (!confirm || KMessageBox::warningContinueCancel( view(), i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()), i18nc("@title:window %1 is the name of the containment", "Remove %1", name()), KStandardGuiItem::remove()) == KMessageBox::Continue) { //clearApplets(); Applet::destroy(); } } else { Applet::destroy(); } } void ContainmentPrivate::createToolBox() { if (!toolBox) { if (isPanelContainment()) { PanelToolBox *pt = new PanelToolBox(q); toolBox = pt; pt->setSize(KIconLoader::SizeSmallMedium); pt->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall)); if (q->immutability() != Mutable) { pt->hide(); } } else { DesktopToolBox *dt = new DesktopToolBox(q); toolBox = dt; dt->setSize(KIconLoader::SizeSmallMedium); dt->setIconSize(QSize(KIconLoader::SizeSmall, KIconLoader::SizeSmall)); } if (toolBox) { QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility())); InternalToolBox *internalToolBox = qobject_cast(toolBox.data()); if (internalToolBox) { internalToolBox->restore(); positionToolBox(); } } } } void ContainmentPrivate::positionToolBox() { InternalToolBox *internalToolBox = qobject_cast(toolBox.data()); if (internalToolBox) { internalToolBox->updateToolBox(); internalToolBox->reposition(); } } void ContainmentPrivate::updateToolBoxVisibility() { emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing()); } void ContainmentPrivate::triggerShowAddWidgets() { emit q->showAddWidgetsInterface(QPointF()); } void ContainmentPrivate::handleDisappeared(AppletHandle *handle) { if (handles.contains(handle->applet())) { handles.remove(handle->applet()); handle->detachApplet(); QGraphicsScene *scene = q->scene(); if (scene && handle->scene() == scene) { scene->removeItem(handle); } handle->deleteLater(); } } void ContainmentPrivate::checkRemoveAction() { q->enableAction("remove", q->immutability() == Mutable); } void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) { if (!q->isContainment()) { return; } //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; if (constraints & Plasma::ImmutableConstraint) { //update actions checkRemoveAction(); bool unlocked = q->immutability() == Mutable; q->setAcceptDrops(unlocked); q->enableAction("add widgets", unlocked); // tell the applets too foreach (Applet *a, applets) { a->setImmutability(q->immutability()); a->updateConstraints(ImmutableConstraint); } if (toolBox) { if (isPanelContainment()) { toolBox.data()->setVisible(unlocked); } else { InternalToolBox *internalToolBox = qobject_cast(toolBox.data()); if (internalToolBox) { internalToolBox->setIsMovable(unlocked); } } } //clear handles on lock if (!unlocked) { QMap h = handles; handles.clear(); foreach (AppletHandle *handle, h) { handle->disconnect(q); if (q->scene()) { q->scene()->removeItem(handle); } handle->deleteLater(); } } } // pass on the constraints that are relevant here Constraints appletConstraints = NoConstraint; if (constraints & FormFactorConstraint) { appletConstraints |= FormFactorConstraint; } if (constraints & ScreenConstraint) { appletConstraints |= ScreenConstraint; } if (appletConstraints != NoConstraint) { foreach (Applet *applet, applets) { applet->updateConstraints(appletConstraints); } } if (toolBox && (constraints & Plasma::SizeConstraint || constraints & Plasma::FormFactorConstraint || constraints & Plasma::ScreenConstraint || constraints & Plasma::StartupCompletedConstraint)) { //kDebug() << "Positioning toolbox"; positionToolBox(); } if (toolBox && constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) { toolBox.data()->addTool(q->action("remove")); checkRemoveAction(); } } Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, const QRectF &appletGeometry, uint id, bool delayInit) { if (!q->isContainment()) { return 0; } if (!delayInit && q->immutability() != Mutable) { kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; return 0; } QGraphicsView *v = q->view(); if (v) { v->setCursor(Qt::BusyCursor); } Applet *applet = Applet::load(name, id, args); if (v) { v->unsetCursor(); } if (!applet) { kDebug() << "Applet" << name << "could not be loaded."; applet = new Applet(0, QString(), id); applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); } //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); q->addApplet(applet, appletGeometry.topLeft(), delayInit); return applet; } bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const { foreach (Applet *applet, applets) { if (applet != ignoredApplet && applet->geometry().intersects(region)) { return false; } } return true; } void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet) { applets.removeAll(applet); if (focusedApplet == applet) { focusedApplet = 0; } if (handles.contains(applet)) { AppletHandle *handle = handles.value(applet); handles.remove(applet); if (q->scene()) { q->scene()->removeItem(handle); } } emit q->appletRemoved(applet); emit q->configNeedsSaving(); } void ContainmentPrivate::appletAppearAnimationComplete() { Animation *anim = qobject_cast(q->sender()); if (anim) { Applet *applet = qobject_cast(anim->targetWidget()); if (applet) { appletAppeared(applet); } } } void ContainmentPrivate::appletAppeared(Applet *applet) { kDebug() << type << Containment::DesktopContainment; if (type == Containment::DesktopContainment) { applet->installSceneEventFilter(q); } KConfigGroup *cg = applet->d->mainConfigGroup(); applet->save(*cg); emit q->configNeedsSaving(); } void ContainmentPrivate::positionPanel(bool force) { if (!q->scene()) { kDebug() << "no scene yet"; return; } // already positioning the panel - avoid infinite loops if (ContainmentPrivate::s_positioningPanels) { return; } // we position panels in negative coordinates, and stack all horizontal // and all vertical panels with each other. const QPointF p = q->pos(); if (!force && p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && q->scene()->collidingItems(q).isEmpty()) { // already positioned and not running into any other panels return; } QPointF newPos = preferredPanelPos(q->corona()); if (p != newPos) { ContainmentPrivate::s_positioningPanels = true; q->setPos(newPos); ContainmentPrivate::s_positioningPanels = false; } } bool ContainmentPrivate::isPanelContainment() const { return type == Containment::PanelContainment || type == Containment::CustomPanelContainment; } QPointF ContainmentPrivate::preferredPos(Corona *corona) const { Q_ASSERT(corona); if (isPanelContainment()) { //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona); return preferredPanelPos(corona); } QPointF pos(0, 0); QTransform t; while (QGraphicsItem *i = corona->itemAt(pos, t)) { pos.setX(i->scenePos().x() + i->boundingRect().width() + 10); } //kDebug() << "not a panel, put it at" << pos; return pos; } QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const { Q_ASSERT(corona); //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here bool horiz = formFactor == Plasma::Horizontal; qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; qreal lastHeight = 0; // this should be ok for small numbers of panels, but if we ever end // up managing hundreds of them, this simplistic alogrithm will // likely be too slow. foreach (const Containment *other, corona->containments()) { if (other == q || !other->d->isPanelContainment() || horiz != (other->formFactor() == Plasma::Horizontal)) { // only line up with panels of the same orientation continue; } if (horiz) { qreal y = other->pos().y(); if (y < bottom) { lastHeight = other->size().height(); bottom = y; } } else { qreal width = other->size().width(); qreal x = other->pos().x() + width; if (x > bottom) { lastHeight = width; bottom = x + lastHeight; } } } // give a space equal to the height again of the last item so there is // room to grow. QPointF newPos; if (horiz) { bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; //TODO: fix x position for non-flush-left panels kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); newPos = QPointF(0, bottom - q->size().height()); } else { bottom += lastHeight + INTER_CONTAINMENT_MARGIN; //TODO: fix y position for non-flush-top panels kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); } return newPos; } bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos) { ContainmentActions *plugin = actionPlugins.value(trigger); if (!plugin->isInitialized()) { KConfigGroup cfg = q->config(); cfg = KConfigGroup(&cfg, "ActionPlugins"); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } if (plugin->configurationRequired()) { KMenu menu; menu.addTitle(i18n("This plugin needs to be configured")); menu.addAction(q->action("configure")); menu.exec(screenPos); return false; } return true; } } // Plasma namespace #include "containment.moc"