Plasma
containment.cpp
Go to the documentation of this file.
00001 /* 00002 * Copyright 2007 by Aaron Seigo <aseigo@kde.org> 00003 * Copyright 2008 by Ménard Alexis <darktears31@gmail.com> 00004 * Copyright 2009 Chani Armitage <chani@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU Library General Public License as 00008 * published by the Free Software Foundation; either version 2, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details 00015 * 00016 * You should have received a copy of the GNU Library General Public 00017 * License along with this program; if not, write to the 00018 * Free Software Foundation, Inc., 00019 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00020 */ 00021 00022 #include "containment.h" 00023 #include "private/containment_p.h" 00024 00025 #include "config-plasma.h" 00026 00027 #include <QApplication> 00028 #include <QClipboard> 00029 #include <QFile> 00030 #include <QGraphicsSceneContextMenuEvent> 00031 #include <QGraphicsView> 00032 #include <QMimeData> 00033 #include <QPainter> 00034 #include <QStyleOptionGraphicsItem> 00035 #include <QGraphicsLayout> 00036 #include <QGraphicsLinearLayout> 00037 00038 #include <kaction.h> 00039 #include <kauthorized.h> 00040 #include <kicon.h> 00041 #include <kmenu.h> 00042 #include <kmessagebox.h> 00043 #include <kmimetype.h> 00044 #include <kservicetypetrader.h> 00045 #include <kstandarddirs.h> 00046 #include <ktemporaryfile.h> 00047 #include <kwindowsystem.h> 00048 00049 #ifndef PLASMA_NO_KIO 00050 #include "kio/jobclasses.h" // for KIO::JobFlags 00051 #include "kio/job.h" 00052 #include "kio/scheduler.h" 00053 #endif 00054 00055 #include "abstracttoolbox.h" 00056 #include "animator.h" 00057 #include "context.h" 00058 #include "containmentactions.h" 00059 #include "containmentactionspluginsconfig.h" 00060 #include "corona.h" 00061 #include "extender.h" 00062 #include "extenderitem.h" 00063 #include "svg.h" 00064 #include "wallpaper.h" 00065 00066 #include "remote/accessappletjob.h" 00067 #include "remote/accessmanager.h" 00068 00069 #include "private/applet_p.h" 00070 #include "private/applethandle_p.h" 00071 #include "private/containmentactionspluginsconfig_p.h" 00072 #include "private/extenderitemmimedata_p.h" 00073 #include "private/extenderapplet_p.h" 00074 00075 #include "plasma/plasma.h" 00076 #include "animations/animation.h" 00077 00078 namespace Plasma 00079 { 00080 00081 bool ContainmentPrivate::s_positioningPanels = false; 00082 QHash<QString, ContainmentActions*> ContainmentPrivate::globalActionPlugins; 00083 static const char defaultWallpaper[] = "image"; 00084 static const char defaultWallpaperMode[] = "SingleImage"; 00085 00086 Containment::StyleOption::StyleOption() 00087 : QStyleOptionGraphicsItem(), 00088 view(0) 00089 { 00090 version = Version; 00091 type = Type; 00092 } 00093 00094 Containment::StyleOption::StyleOption(const Containment::StyleOption & other) 00095 : QStyleOptionGraphicsItem(other), 00096 view(other.view) 00097 { 00098 version = Version; 00099 type = Type; 00100 } 00101 00102 Containment::StyleOption::StyleOption(const QStyleOptionGraphicsItem &other) 00103 : QStyleOptionGraphicsItem(other), 00104 view(0) 00105 { 00106 version = Version; 00107 type = Type; 00108 } 00109 00110 Containment::Containment(QGraphicsItem *parent, 00111 const QString &serviceId, 00112 uint containmentId) 00113 : Applet(parent, serviceId, containmentId), 00114 d(new ContainmentPrivate(this)) 00115 { 00116 // WARNING: do not access config() OR globalConfig() in this method! 00117 // that requires a scene, which is not available at this point 00118 setPos(0, 0); 00119 setBackgroundHints(NoBackground); 00120 setContainmentType(CustomContainment); 00121 setHasConfigurationInterface(false); 00122 } 00123 00124 Containment::Containment(QObject *parent, const QVariantList &args) 00125 : Applet(parent, args), 00126 d(new ContainmentPrivate(this)) 00127 { 00128 // WARNING: do not access config() OR globalConfig() in this method! 00129 // that requires a scene, which is not available at this point 00130 setPos(0, 0); 00131 setBackgroundHints(NoBackground); 00132 setHasConfigurationInterface(false); 00133 } 00134 00135 Containment::Containment(const QString &packagePath, uint appletId, const QVariantList &args) 00136 : Plasma::Applet(packagePath, appletId, args), 00137 d(new ContainmentPrivate(this)) 00138 { 00139 // WARNING: do not access config() OR globalConfig() in this method! 00140 // that requires a scene, which is not available at this point 00141 setPos(0, 0); 00142 setBackgroundHints(NoBackground); 00143 setHasConfigurationInterface(false); 00144 } 00145 00146 Containment::~Containment() 00147 { 00148 delete d; 00149 // Applet touches our dptr if we are a containment and is the superclass (think of dtors) 00150 // so we reset this as we exit the building 00151 Applet::d->isContainment = false; 00152 } 00153 00154 void Containment::init() 00155 { 00156 Applet::init(); 00157 if (!isContainment()) { 00158 return; 00159 } 00160 00161 setCacheMode(NoCache); 00162 setFlag(QGraphicsItem::ItemIsMovable, false); 00163 setFlag(QGraphicsItem::ItemClipsChildrenToShape, false); 00164 setAcceptDrops(true); 00165 setAcceptsHoverEvents(true); 00166 00167 if (d->type == NoContainmentType) { 00168 setContainmentType(DesktopContainment); 00169 } 00170 00171 //connect actions 00172 ContainmentPrivate::addDefaultActions(d->actions(), this); 00173 bool unlocked = immutability() == Mutable; 00174 00175 //fix the text of the actions that need name() 00176 //btw, do we really want to use name() when it's a desktopcontainment? 00177 QAction *closeApplet = action("remove"); 00178 if (closeApplet) { 00179 closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name())); 00180 } 00181 00182 QAction *configAction = action("configure"); 00183 if (configAction) { 00184 configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", name())); 00185 } 00186 00187 QAction *appletBrowserAction = action("add widgets"); 00188 if (appletBrowserAction) { 00189 appletBrowserAction->setVisible(unlocked); 00190 appletBrowserAction->setEnabled(unlocked); 00191 connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets())); 00192 } 00193 00194 QAction *act = action("next applet"); 00195 if (act) { 00196 connect(act, SIGNAL(triggered()), this, SLOT(focusNextApplet())); 00197 } 00198 00199 act = action("previous applet"); 00200 if (act) { 00201 connect(act, SIGNAL(triggered()), this, SLOT(focusPreviousApplet())); 00202 } 00203 00204 if (immutability() != SystemImmutable && corona()) { 00205 QAction *lockDesktopAction = corona()->action("lock widgets"); 00206 //keep a pointer so nobody notices it moved to corona 00207 if (lockDesktopAction) { 00208 d->actions()->addAction("lock widgets", lockDesktopAction); 00209 } 00210 } 00211 if (d->type != PanelContainment && d->type != CustomPanelContainment) { 00212 if (corona()) { 00213 //FIXME this is just here because of the darn keyboard shortcut :/ 00214 act = corona()->action("manage activities"); 00215 if (act) { 00216 d->actions()->addAction("manage activities", act); 00217 } 00218 //a stupid hack to make this one's keyboard shortcut work 00219 act = corona()->action("configure shortcuts"); 00220 if (act) { 00221 d->actions()->addAction("configure shortcuts", act); 00222 } 00223 } 00224 00225 if (d->type == DesktopContainment) { 00226 addToolBoxAction(action("add widgets")); 00227 00228 //TODO: do we need some way to allow this be overridden? 00229 // it's always available because shells rely on this 00230 // to offer their own custom configuration as well 00231 QAction *configureContainment = action("configure"); 00232 if (configureContainment) { 00233 addToolBoxAction(configureContainment); 00234 } 00235 } 00236 } 00237 } 00238 00239 void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c) 00240 { 00241 actions->setConfigGroup("Shortcuts-Containment"); 00242 00243 //adjust applet actions 00244 KAction *appAction = qobject_cast<KAction*>(actions->action("remove")); 00245 appAction->setShortcut(KShortcut("alt+d, alt+r")); 00246 if (c && c->d->isPanelContainment()) { 00247 appAction->setText(i18n("Remove this Panel")); 00248 } else { 00249 appAction->setText(i18n("Remove this Activity")); 00250 } 00251 00252 appAction = qobject_cast<KAction*>(actions->action("configure")); 00253 if (appAction) { 00254 appAction->setShortcut(KShortcut("alt+d, alt+s")); 00255 appAction->setText(i18n("Activity Settings")); 00256 } 00257 00258 //add our own actions 00259 KAction *appletBrowserAction = actions->addAction("add widgets"); 00260 appletBrowserAction->setAutoRepeat(false); 00261 appletBrowserAction->setText(i18n("Add Widgets...")); 00262 appletBrowserAction->setIcon(KIcon("list-add")); 00263 appletBrowserAction->setShortcut(KShortcut("alt+d, a")); 00264 appletBrowserAction->setData(AbstractToolBox::AddTool); 00265 00266 KAction *action = actions->addAction("next applet"); 00267 action->setText(i18n("Next Widget")); 00268 //no icon 00269 action->setShortcut(KShortcut("alt+d, n")); 00270 action->setData(AbstractToolBox::ControlTool); 00271 00272 action = actions->addAction("previous applet"); 00273 action->setText(i18n("Previous Widget")); 00274 //no icon 00275 action->setShortcut(KShortcut("alt+d, p")); 00276 action->setData(AbstractToolBox::ControlTool); 00277 } 00278 00279 // helper function for sorting the list of applets 00280 bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2) 00281 { 00282 QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft(); 00283 QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft(); 00284 00285 if (!qFuzzyCompare(p1.x(), p2.x())) { 00286 if (QApplication::layoutDirection() == Qt::RightToLeft) { 00287 return p1.x() > p2.x(); 00288 } 00289 00290 return p1.x() < p2.x(); 00291 } 00292 00293 return qFuzzyCompare(p1.y(), p2.y()) || p1.y() < p2.y(); 00294 } 00295 00296 void Containment::restore(KConfigGroup &group) 00297 { 00298 /*kDebug() << "!!!!!!!!!!!!initConstraints" << group.name() << d->type; 00299 kDebug() << " location:" << group.readEntry("location", (int)d->location); 00300 kDebug() << " geom:" << group.readEntry("geometry", geometry()); 00301 kDebug() << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor); 00302 kDebug() << " screen:" << group.readEntry("screen", d->screen);*/ 00303 if (!isContainment()) { 00304 Applet::restore(group); 00305 return; 00306 } 00307 00308 QRectF geo = group.readEntry("geometry", geometry()); 00309 //override max/min 00310 //this ensures panels are set to their saved size even when they have max & min set to prevent 00311 //resizing 00312 if (geo.size() != geo.size().boundedTo(maximumSize())) { 00313 setMaximumSize(maximumSize().expandedTo(geo.size())); 00314 } 00315 00316 if (geo.size() != geo.size().expandedTo(minimumSize())) { 00317 setMinimumSize(minimumSize().boundedTo(geo.size())); 00318 } 00319 00320 00321 resize(geo.size()); 00322 //are we an offscreen containment? 00323 if (containmentType() != PanelContainment && containmentType() != CustomPanelContainment && geo.right() < 0) { 00324 corona()->addOffscreenWidget(this); 00325 } 00326 00327 setLocation((Plasma::Location)group.readEntry("location", (int)d->location)); 00328 setFormFactor((Plasma::FormFactor)group.readEntry("formfactor", (int)d->formFactor)); 00329 //kDebug() << "setScreen from restore"; 00330 d->lastScreen = group.readEntry("lastScreen", d->lastScreen); 00331 d->lastDesktop = group.readEntry("lastDesktop", d->lastDesktop); 00332 d->setScreen(group.readEntry("screen", d->screen), group.readEntry("desktop", d->desktop), false); 00333 QString activityId = group.readEntry("activityId", QString()); 00334 if (!activityId.isEmpty()) { 00335 d->context()->setCurrentActivityId(activityId); 00336 } 00337 setActivity(group.readEntry("activity", QString())); 00338 00339 flushPendingConstraintsEvents(); 00340 restoreContents(group); 00341 setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); 00342 00343 setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), 00344 group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); 00345 00346 QMetaObject::invokeMethod(d->toolBox.data(), "restore", Q_ARG(KConfigGroup, group)); 00347 00348 KConfigGroup cfg; 00349 if (containmentType() == PanelContainment || containmentType() == CustomPanelContainment) { 00350 //don't let global desktop actions conflict with panels 00351 //this also prevents panels from sharing config with each other 00352 //but the panels aren't configurable anyways, and I doubt that'll change. 00353 d->containmentActionsSource = ContainmentPrivate::Local; 00354 cfg = KConfigGroup(&group, "ActionPlugins"); 00355 } else { 00356 QString source = group.readEntry("ActionPluginsSource", QString()); 00357 if (source == "Global") { 00358 cfg = KConfigGroup(corona()->config(), "ActionPlugins"); 00359 d->containmentActionsSource = ContainmentPrivate::Global; 00360 } else if (source == "Activity") { 00361 cfg = KConfigGroup(corona()->config(), "Activities"); 00362 cfg = KConfigGroup(&cfg, activityId); 00363 cfg = KConfigGroup(&cfg, "ActionPlugins"); 00364 d->containmentActionsSource = ContainmentPrivate::Activity; 00365 } else if (source == "Local") { 00366 cfg = group; 00367 d->containmentActionsSource = ContainmentPrivate::Local; 00368 } else { 00369 //default to global 00370 //but, if there is no global config, try copying it from local. 00371 cfg = KConfigGroup(corona()->config(), "ActionPlugins"); 00372 if (!cfg.exists()) { 00373 cfg = KConfigGroup(&group, "ActionPlugins"); 00374 } 00375 d->containmentActionsSource = ContainmentPrivate::Global; 00376 group.writeEntry("ActionPluginsSource", "Global"); 00377 } 00378 } 00379 //kDebug() << cfg.keyList(); 00380 if (cfg.exists()) { 00381 foreach (const QString &key, cfg.keyList()) { 00382 //kDebug() << "loading" << key; 00383 setContainmentActions(key, cfg.readEntry(key, QString())); 00384 } 00385 } else { //shell defaults 00386 ContainmentActionsPluginsConfig conf = corona()->containmentActionsDefaults(d->type); 00387 //steal the data directly, for efficiency 00388 QHash<QString,QString> defaults = conf.d->plugins; 00389 for (QHash<QString,QString>::const_iterator it = defaults.constBegin(), 00390 end = defaults.constEnd(); it != end; ++it) { 00391 setContainmentActions(it.key(), it.value()); 00392 } 00393 } 00394 00395 /* 00396 kDebug() << "Containment" << id() << 00397 "screen" << screen() << 00398 "geometry is" << geometry() << 00399 "wallpaper" << ((d->wallpaper) ? d->wallpaper->pluginName() : QString()) << 00400 "wallpaper mode" << wallpaperMode() << 00401 "config entries" << group.entryMap(); 00402 */ 00403 } 00404 00405 void Containment::save(KConfigGroup &g) const 00406 { 00407 if (Applet::d->transient) { 00408 return; 00409 } 00410 00411 KConfigGroup group = g; 00412 if (!group.isValid()) { 00413 group = config(); 00414 } 00415 00416 // locking is saved in Applet::save 00417 Applet::save(group); 00418 00419 if (!isContainment()) { 00420 return; 00421 } 00422 00423 group.writeEntry("screen", d->screen); 00424 group.writeEntry("lastScreen", d->lastScreen); 00425 group.writeEntry("desktop", d->desktop); 00426 group.writeEntry("lastDesktop", d->lastDesktop); 00427 group.writeEntry("formfactor", (int)d->formFactor); 00428 group.writeEntry("location", (int)d->location); 00429 group.writeEntry("activity", d->context()->currentActivity()); 00430 group.writeEntry("activityId", d->context()->currentActivityId()); 00431 00432 00433 QMetaObject::invokeMethod(d->toolBox.data(), "save", Q_ARG(KConfigGroup, group)); 00434 00435 00436 if (d->wallpaper) { 00437 group.writeEntry("wallpaperplugin", d->wallpaper->pluginName()); 00438 group.writeEntry("wallpaperpluginmode", d->wallpaper->renderingMode().name()); 00439 00440 if (d->wallpaper->isInitialized()) { 00441 KConfigGroup wallpaperConfig(&group, "Wallpaper"); 00442 wallpaperConfig = KConfigGroup(&wallpaperConfig, d->wallpaper->pluginName()); 00443 d->wallpaper->save(wallpaperConfig); 00444 } 00445 } 00446 00447 saveContents(group); 00448 } 00449 00450 void Containment::saveContents(KConfigGroup &group) const 00451 { 00452 KConfigGroup applets(&group, "Applets"); 00453 foreach (const Applet *applet, d->applets) { 00454 KConfigGroup appletConfig(&applets, QString::number(applet->id())); 00455 applet->save(appletConfig); 00456 } 00457 } 00458 00459 void ContainmentPrivate::initApplets() 00460 { 00461 foreach (Applet *applet, applets) { 00462 applet->restore(*applet->d->mainConfigGroup()); 00463 applet->init(); 00464 kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name(); 00465 } 00466 00467 q->flushPendingConstraintsEvents(); 00468 00469 foreach (Applet *applet, applets) { 00470 applet->flushPendingConstraintsEvents(); 00471 } 00472 00473 kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name(); 00474 } 00475 00476 void Containment::restoreContents(KConfigGroup &group) 00477 { 00478 KConfigGroup applets(&group, "Applets"); 00479 00480 // Sort the applet configs in order of geometry to ensure that applets 00481 // are added from left to right or top to bottom for a panel containment 00482 QList<KConfigGroup> appletConfigs; 00483 foreach (const QString &appletGroup, applets.groupList()) { 00484 //kDebug() << "reading from applet group" << appletGroup; 00485 KConfigGroup appletConfig(&applets, appletGroup); 00486 appletConfigs.append(appletConfig); 00487 } 00488 qStableSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan); 00489 00490 QMutableListIterator<KConfigGroup> it(appletConfigs); 00491 while (it.hasNext()) { 00492 KConfigGroup &appletConfig = it.next(); 00493 int appId = appletConfig.name().toUInt(); 00494 QString plugin = appletConfig.readEntry("plugin", QString()); 00495 00496 if (plugin.isEmpty()) { 00497 continue; 00498 } 00499 00500 d->addApplet(plugin, QVariantList(), appletConfig.readEntry("geometry", QRectF()), appId, true); 00501 } 00502 } 00503 00504 Containment::Type Containment::containmentType() const 00505 { 00506 return d->type; 00507 } 00508 00509 void Containment::setContainmentType(Containment::Type type) 00510 { 00511 if (d->type == type) { 00512 return; 00513 } 00514 00515 delete d->toolBox.data(); 00516 d->type = type; 00517 d->checkContainmentFurniture(); 00518 } 00519 00520 void ContainmentPrivate::checkContainmentFurniture() 00521 { 00522 if (q->isContainment() && 00523 (type == Containment::DesktopContainment || type == Containment::PanelContainment)) { 00524 createToolBox(); 00525 } 00526 } 00527 00528 Corona *Containment::corona() const 00529 { 00530 return dynamic_cast<Corona*>(scene()); 00531 } 00532 00533 void Containment::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 00534 { 00535 event->ignore(); 00536 if (d->wallpaper && d->wallpaper->isInitialized()) { 00537 QGraphicsItem *item = scene()->itemAt(event->scenePos()); 00538 if (item == this) { 00539 d->wallpaper->mouseMoveEvent(event); 00540 } 00541 } 00542 00543 if (!event->isAccepted()) { 00544 event->accept(); 00545 Applet::mouseMoveEvent(event); 00546 } 00547 } 00548 00549 void Containment::mousePressEvent(QGraphicsSceneMouseEvent *event) 00550 { 00551 event->ignore(); 00552 if (d->appletAt(event->scenePos())) { 00553 return; //no unexpected click-throughs 00554 } 00555 00556 if (d->wallpaper && d->wallpaper->isInitialized() && !event->isAccepted()) { 00557 d->wallpaper->mousePressEvent(event); 00558 } 00559 00560 if (event->isAccepted()) { 00561 setFocus(Qt::MouseFocusReason); 00562 } else if (event->button() == Qt::RightButton && event->modifiers() == Qt::NoModifier) { 00563 // we'll catch this in the context menu even 00564 Applet::mousePressEvent(event); 00565 } else { 00566 QString trigger = ContainmentActions::eventToString(event); 00567 if (d->prepareContainmentActions(trigger, event->screenPos())) { 00568 d->actionPlugins()->value(trigger)->contextEvent(event); 00569 } 00570 00571 if (!event->isAccepted()) { 00572 Applet::mousePressEvent(event); 00573 } 00574 } 00575 } 00576 00577 void Containment::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) 00578 { 00579 event->ignore(); 00580 if (d->appletAt(event->scenePos())) { 00581 return; //no unexpected click-throughs 00582 } 00583 00584 QString trigger = ContainmentActions::eventToString(event); 00585 00586 if (d->wallpaper && d->wallpaper->isInitialized()) { 00587 d->wallpaper->mouseReleaseEvent(event); 00588 } 00589 00590 if (!event->isAccepted() && isContainment()) { 00591 if (d->prepareContainmentActions(trigger, event->screenPos())) { 00592 d->actionPlugins()->value(trigger)->contextEvent(event); 00593 } 00594 00595 event->accept(); 00596 Applet::mouseReleaseEvent(event); 00597 } 00598 } 00599 00600 void Containment::showDropZone(const QPoint pos) 00601 { 00602 Q_UNUSED(pos) 00603 //Base implementation does nothing, don't put code here 00604 } 00605 00606 void Containment::showContextMenu(const QPointF &containmentPos, const QPoint &screenPos) 00607 { 00608 //kDebug() << containmentPos << screenPos; 00609 QGraphicsSceneContextMenuEvent gvevent; 00610 gvevent.setScreenPos(screenPos); 00611 gvevent.setScenePos(mapToScene(containmentPos)); 00612 gvevent.setPos(containmentPos); 00613 gvevent.setReason(QGraphicsSceneContextMenuEvent::Mouse); 00614 gvevent.setWidget(view()); 00615 contextMenuEvent(&gvevent); 00616 } 00617 00618 void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) 00619 { 00620 if (!isContainment() || !KAuthorized::authorizeKAction("plasma/containment_context_menu")) { 00621 Applet::contextMenuEvent(event); 00622 return; 00623 } 00624 00625 KMenu desktopMenu; 00626 Applet *applet = d->appletAt(event->scenePos()); 00627 //kDebug() << "context menu event " << (QObject*)applet; 00628 00629 if (applet) { 00630 d->addAppletActions(desktopMenu, applet, event); 00631 } else { 00632 d->addContainmentActions(desktopMenu, event); 00633 } 00634 00635 //kDebug() << "executing at" << screenPos; 00636 QMenu *menu = &desktopMenu; 00637 //kDebug() << "showing menu, actions" << desktopMenu.actions().size() << desktopMenu.actions().first()->menu(); 00638 if (desktopMenu.actions().size() == 1 && desktopMenu.actions().first()->menu()) { 00639 // we have a menu with a single top level menu; just show that top level menu instad. 00640 menu = desktopMenu.actions().first()->menu(); 00641 } 00642 00643 if (!menu->isEmpty()) { 00644 QPoint pos = event->screenPos(); 00645 if (applet && d->isPanelContainment()) { 00646 menu->adjustSize(); 00647 pos = applet->popupPosition(menu->size()); 00648 if (event->reason() == QGraphicsSceneContextMenuEvent::Mouse) { 00649 // if the menu pops up way away from the mouse press, then move it 00650 // to the mouse press 00651 if (d->formFactor == Vertical) { 00652 if (pos.y() + menu->height() < event->screenPos().y()) { 00653 pos.setY(event->screenPos().y()); 00654 } 00655 } else if (d->formFactor == Horizontal) { 00656 if (pos.x() + menu->width() < event->screenPos().x()) { 00657 pos.setX(event->screenPos().x()); 00658 } 00659 } 00660 } 00661 } 00662 00663 menu->exec(pos); 00664 event->accept(); 00665 } else { 00666 Applet::contextMenuEvent(event); 00667 } 00668 } 00669 00670 void ContainmentPrivate::addContainmentActions(KMenu &desktopMenu, QEvent *event) 00671 { 00672 if (static_cast<Corona*>(q->scene())->immutability() != Mutable && 00673 !KAuthorized::authorizeKAction("plasma/containment_actions")) { 00674 //kDebug() << "immutability"; 00675 return; 00676 } 00677 00678 const QString trigger = ContainmentActions::eventToString(event); 00679 prepareContainmentActions(trigger, QPoint(), &desktopMenu); 00680 } 00681 00682 void ContainmentPrivate::addAppletActions(KMenu &desktopMenu, Applet *applet, QEvent *event) 00683 { 00684 foreach (QAction *action, applet->contextualActions()) { 00685 if (action) { 00686 desktopMenu.addAction(action); 00687 } 00688 } 00689 00690 QAction *configureApplet = applet->d->actions->action("configure"); 00691 if (configureApplet && configureApplet->isEnabled()) { 00692 desktopMenu.addAction(configureApplet); 00693 } 00694 00695 QAction *runAssociatedApplication = applet->d->actions->action("run associated application"); 00696 if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { 00697 desktopMenu.addAction(runAssociatedApplication); 00698 } 00699 00700 KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); 00701 addContainmentActions(*containmentMenu, event); 00702 if (!containmentMenu->isEmpty()) { 00703 int enabled = 0; 00704 //count number of real actions 00705 QListIterator<QAction *> actionsIt(containmentMenu->actions()); 00706 while (enabled < 3 && actionsIt.hasNext()) { 00707 QAction *action = actionsIt.next(); 00708 if (action->isVisible() && !action->isSeparator()) { 00709 ++enabled; 00710 } 00711 } 00712 00713 if (enabled) { 00714 //if there is only one, don't create a submenu 00715 if (enabled < 2) { 00716 foreach (QAction *action, containmentMenu->actions()) { 00717 if (action->isVisible() && !action->isSeparator()) { 00718 desktopMenu.addAction(action); 00719 } 00720 } 00721 } else { 00722 desktopMenu.addMenu(containmentMenu); 00723 } 00724 } 00725 } 00726 00727 if (q->immutability() == Mutable) { 00728 QAction *closeApplet = applet->d->actions->action("remove"); 00729 kDebug() << "checking for removal" << closeApplet; 00730 if (closeApplet) { 00731 if (!desktopMenu.isEmpty()) { 00732 desktopMenu.addSeparator(); 00733 } 00734 00735 kDebug() << "boo yah, adding it!" << closeApplet->isEnabled() << closeApplet->isVisible(); 00736 desktopMenu.addAction(closeApplet); 00737 } 00738 } 00739 } 00740 00741 Applet* ContainmentPrivate::appletAt(const QPointF &point) 00742 { 00743 Applet *applet = 0; 00744 00745 QGraphicsItem *item = q->scene()->itemAt(point); 00746 if (item == q) { 00747 item = 0; 00748 } 00749 00750 while (item) { 00751 if (item->isWidget()) { 00752 applet = qobject_cast<Applet*>(static_cast<QGraphicsWidget*>(item)); 00753 if (applet) { 00754 if (applet->isContainment()) { 00755 applet = 0; 00756 } 00757 break; 00758 } 00759 } 00760 AppletHandle *handle = dynamic_cast<AppletHandle*>(item); 00761 if (handle) { 00762 //pretend it was on the applet 00763 applet = handle->applet(); 00764 break; 00765 } 00766 item = item->parentItem(); 00767 } 00768 return applet; 00769 } 00770 00771 void Containment::setFormFactor(FormFactor formFactor) 00772 { 00773 if (d->formFactor == formFactor) { 00774 return; 00775 } 00776 00777 //kDebug() << "switching FF to " << formFactor; 00778 d->formFactor = formFactor; 00779 00780 if (isContainment() && 00781 (d->type == PanelContainment || d->type == CustomPanelContainment)) { 00782 // we are a panel and we have chaged our orientation 00783 d->positionPanel(true); 00784 } 00785 00786 QMetaObject::invokeMethod(d->toolBox.data(), "reposition"); 00787 00788 updateConstraints(Plasma::FormFactorConstraint); 00789 00790 KConfigGroup c = config(); 00791 c.writeEntry("formfactor", (int)formFactor); 00792 emit configNeedsSaving(); 00793 } 00794 00795 void Containment::setLocation(Location location) 00796 { 00797 if (d->location == location) { 00798 return; 00799 } 00800 00801 bool emitGeomChange = false; 00802 00803 if ((location == TopEdge || location == BottomEdge) && 00804 (d->location == TopEdge || d->location == BottomEdge)) { 00805 emitGeomChange = true; 00806 } 00807 00808 if ((location == RightEdge || location == LeftEdge) && 00809 (d->location == RightEdge || d->location == LeftEdge)) { 00810 emitGeomChange = true; 00811 } 00812 00813 d->location = location; 00814 00815 foreach (Applet *applet, d->applets) { 00816 applet->updateConstraints(Plasma::LocationConstraint); 00817 } 00818 00819 if (emitGeomChange) { 00820 // our geometry on the scene will not actually change, 00821 // but for the purposes of views it has 00822 emit geometryChanged(); 00823 } 00824 00825 updateConstraints(Plasma::LocationConstraint); 00826 00827 KConfigGroup c = config(); 00828 c.writeEntry("location", (int)location); 00829 emit configNeedsSaving(); 00830 } 00831 00832 void Containment::addSiblingContainment() 00833 { 00834 emit addSiblingContainment(this); 00835 } 00836 00837 void Containment::clearApplets() 00838 { 00839 foreach (Applet *applet, d->applets) { 00840 applet->d->cleanUpAndDelete(); 00841 } 00842 00843 d->applets.clear(); 00844 } 00845 00846 Applet *Containment::addApplet(const QString &name, const QVariantList &args, 00847 const QRectF &appletGeometry) 00848 { 00849 return d->addApplet(name, args, appletGeometry); 00850 } 00851 00852 void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit) 00853 { 00854 if (!isContainment() || (!delayInit && immutability() != Mutable)) { 00855 return; 00856 } 00857 00858 if (!applet) { 00859 kDebug() << "adding null applet!?!"; 00860 return; 00861 } 00862 00863 if (d->applets.contains(applet)) { 00864 kDebug() << "already have this applet!"; 00865 } 00866 00867 Containment *currentContainment = applet->containment(); 00868 00869 if (d->type == PanelContainment) { 00870 //panels don't want backgrounds, which is important when setting geometry 00871 setBackgroundHints(NoBackground); 00872 } 00873 00874 if (currentContainment && currentContainment != this) { 00875 emit currentContainment->appletRemoved(applet); 00876 if (currentContainment->d->focusedApplet == applet) { 00877 currentContainment->d->focusedApplet = 0; 00878 } 00879 00880 disconnect(applet, 0, currentContainment, 0); 00881 applet->removeSceneEventFilter(currentContainment); 00882 KConfigGroup oldConfig = applet->config(); 00883 currentContainment->d->applets.removeAll(applet); 00884 if (currentContainment->d->handles.contains(applet)) { 00885 currentContainment->d->handles.remove(applet); 00886 } 00887 applet->setParentItem(this); 00888 applet->setParent(this); 00889 00890 // now move the old config to the new location 00891 //FIXME: this doesn't seem to get the actual main config group containing plugin=, etc 00892 KConfigGroup c = config().group("Applets").group(QString::number(applet->id())); 00893 oldConfig.reparent(&c); 00894 applet->d->resetConfigurationObject(); 00895 00896 disconnect(applet, SIGNAL(activate()), currentContainment, SIGNAL(activate())); 00897 } else { 00898 applet->setParentItem(this); 00899 applet->setParent(this); 00900 } 00901 00902 d->applets << applet; 00903 00904 connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); 00905 connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus())); 00906 connect(applet, SIGNAL(appletDestroyed(Plasma::Applet*)), this, SLOT(appletDestroyed(Plasma::Applet*))); 00907 connect(applet, SIGNAL(newStatus(Plasma::ItemStatus)), this, SLOT(checkStatus(Plasma::ItemStatus))); 00908 connect(applet, SIGNAL(activate()), this, SIGNAL(activate())); 00909 00910 if (pos != QPointF(-1, -1)) { 00911 applet->setPos(pos); 00912 } 00913 00914 if (delayInit || currentContainment) { 00915 if (d->type == DesktopContainment) { 00916 applet->installSceneEventFilter(this); 00917 //applet->setWindowFlags(Qt::Window); 00918 } 00919 } else { 00920 applet->restore(*applet->d->mainConfigGroup()); 00921 applet->init(); 00922 Plasma::Animation *anim = Plasma::Animator::create(Plasma::Animator::AppearAnimation); 00923 if (anim) { 00924 connect(anim, SIGNAL(finished()), this, SLOT(appletAppearAnimationComplete())); 00925 anim->setTargetWidget(applet); 00926 //FIXME: small hack until we have proper js anim support; allows 'zoom' to work in the 00927 //'right' direction for appearance 00928 anim->setDirection(QAbstractAnimation::Backward); 00929 anim->start(QAbstractAnimation::DeleteWhenStopped); 00930 } else { 00931 d->appletAppeared(applet); 00932 } 00933 } 00934 00935 applet->setFlag(QGraphicsItem::ItemIsMovable, true); 00936 applet->updateConstraints(Plasma::AllConstraints); 00937 if (!delayInit) { 00938 applet->flushPendingConstraintsEvents(); 00939 } 00940 emit appletAdded(applet, pos); 00941 00942 if (!currentContainment) { 00943 applet->updateConstraints(Plasma::StartupCompletedConstraint); 00944 if (!delayInit) { 00945 applet->flushPendingConstraintsEvents(); 00946 } 00947 } 00948 00949 if (!delayInit) { 00950 applet->d->scheduleModificationNotification(); 00951 } 00952 } 00953 00954 Applet::List Containment::applets() const 00955 { 00956 return d->applets; 00957 } 00958 00959 void Containment::setScreen(int newScreen, int newDesktop) 00960 { 00961 d->setScreen(newScreen, newDesktop); 00962 } 00963 00964 void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops) 00965 { 00966 // What we want to do in here is: 00967 // * claim the screen as our own 00968 // * signal whatever may be watching this containment about the switch 00969 // * if we are a full screen containment, then: 00970 // * resize to match the screen if we're that kind of containment 00971 // * kick other full-screen containments off this screen 00972 // * if we had a screen, then give our screen to the containment 00973 // we kick out 00974 // 00975 // a screen of -1 means no associated screen. 00976 Corona *corona = q->corona(); 00977 Q_ASSERT(corona); 00978 00979 //if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen 00980 if (corona->offscreenWidgets().contains(q)) { 00981 return; 00982 } 00983 00984 int numScreens = corona->numScreens(); 00985 if (newScreen < -1) { 00986 newScreen = -1; 00987 } 00988 00989 // -1 == All desktops 00990 if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) { 00991 newDesktop = -1; 00992 } 00993 00994 //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type; 00995 00996 Containment *swapScreensWith(0); 00997 if (type == Containment::DesktopContainment || type >= Containment::CustomContainment) { 00998 // we want to listen to changes in work area if our screen changes 00999 if (toolBox) { 01000 if (screen < 0 && newScreen > -1) { 01001 QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection); 01002 } else if (newScreen < 0) { 01003 QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition())); 01004 } 01005 } 01006 01007 if (newScreen > -1) { 01008 // sanity check to make sure someone else doesn't have this screen already! 01009 Containment *currently = corona->containmentForScreen(newScreen, newDesktop); 01010 if (currently && currently != q) { 01011 kDebug() << "currently is on screen" << currently->screen() 01012 << "desktop" << currently->desktop() 01013 << "and is" << currently->activity() 01014 << (QObject*)currently << "i'm" << (QObject*)q; 01015 currently->setScreen(-1, currently->desktop()); 01016 swapScreensWith = currently; 01017 } 01018 } 01019 } 01020 01021 if (newScreen < numScreens && newScreen > -1 && 01022 (type == Containment::DesktopContainment || type >= Containment::CustomContainment)) { 01023 q->resize(corona->screenGeometry(newScreen).size()); 01024 } 01025 01026 int oldDesktop = desktop; 01027 desktop = newDesktop; 01028 01029 int oldScreen = screen; 01030 screen = newScreen; 01031 01032 q->updateConstraints(Plasma::ScreenConstraint); 01033 01034 if (oldScreen != newScreen || oldDesktop != newDesktop) { 01035 /* 01036 kDebug() << "going to signal change for" << q 01037 << ", old screen & desktop:" << oldScreen << oldDesktop 01038 << ", new:" << screen << desktop; 01039 */ 01040 KConfigGroup c = q->config(); 01041 c.writeEntry("screen", screen); 01042 c.writeEntry("desktop", desktop); 01043 if (newScreen != -1) { 01044 lastScreen = newScreen; 01045 lastDesktop = newDesktop; 01046 c.writeEntry("lastScreen", lastScreen); 01047 c.writeEntry("lastDesktop", lastDesktop); 01048 } 01049 emit q->configNeedsSaving(); 01050 emit q->screenChanged(oldScreen, newScreen, q); 01051 } 01052 01053 if (swapScreensWith) { 01054 //kDebug() << "setScreen due to swap, part 2"; 01055 swapScreensWith->setScreen(oldScreen, oldDesktop); 01056 } 01057 01058 checkRemoveAction(); 01059 01060 if (newScreen >= 0) { 01061 emit q->activate(); 01062 } 01063 } 01064 01065 int Containment::screen() const 01066 { 01067 return d->screen; 01068 } 01069 01070 int Containment::lastScreen() const 01071 { 01072 return d->lastScreen; 01073 } 01074 01075 int Containment::desktop() const 01076 { 01077 return d->desktop; 01078 } 01079 01080 int Containment::lastDesktop() const 01081 { 01082 return d->lastDesktop; 01083 } 01084 01085 KPluginInfo::List Containment::listContainments(const QString &category, 01086 const QString &parentApp) 01087 { 01088 return listContainmentsOfType(QString(), category, parentApp); 01089 } 01090 01091 01092 KPluginInfo::List Containment::listContainmentsOfType(const QString &type, 01093 const QString &category, 01094 const QString &parentApp) 01095 { 01096 QString constraint; 01097 01098 if (parentApp.isEmpty()) { 01099 constraint.append("(not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '')"); 01100 } else { 01101 constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); 01102 } 01103 01104 if (!type.isEmpty()) { 01105 if (!constraint.isEmpty()) { 01106 constraint.append(" and "); 01107 } 01108 01109 constraint.append("'").append(type).append("' ~in [X-Plasma-ContainmentCategories]"); 01110 } 01111 01112 if (!category.isEmpty()) { 01113 if (!constraint.isEmpty()) { 01114 constraint.append(" and "); 01115 } 01116 01117 constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'"); 01118 if (category == "Miscellaneous") { 01119 constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')"); 01120 } 01121 } 01122 01123 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); 01124 //kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches"; 01125 return KPluginInfo::fromServices(offers); 01126 } 01127 01128 KPluginInfo::List Containment::listContainmentsForMimetype(const QString &mimetype) 01129 { 01130 const QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); 01131 //kDebug() << mimetype << constraint; 01132 const KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); 01133 return KPluginInfo::fromServices(offers); 01134 } 01135 01136 QStringList Containment::listContainmentTypes() 01137 { 01138 KPluginInfo::List containmentInfos = listContainments(); 01139 QSet<QString> types; 01140 01141 foreach (const KPluginInfo &containmentInfo, containmentInfos) { 01142 QStringList theseTypes = containmentInfo.service()->property("X-Plasma-ContainmentCategories").toStringList(); 01143 foreach (const QString &type, theseTypes) { 01144 types.insert(type); 01145 } 01146 } 01147 01148 return types.toList(); 01149 } 01150 01151 void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event) 01152 { 01153 //kDebug() << immutability() << Mutable << (immutability() == Mutable); 01154 event->setAccepted(immutability() == Mutable && 01155 (event->mimeData()->hasFormat(static_cast<Corona*>(scene())->appletMimeType()) || 01156 KUrl::List::canDecode(event->mimeData()) || 01157 event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType()))); 01158 01159 if (!event->isAccepted()) { 01160 // check to see if we have an applet that accepts the format. 01161 QStringList formats = event->mimeData()->formats(); 01162 01163 foreach (const QString &format, formats) { 01164 KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(format); 01165 if (!appletList.isEmpty()) { 01166 event->setAccepted(true); 01167 break; 01168 } 01169 } 01170 01171 if (!event->isAccepted()) { 01172 foreach (const QString &format, formats) { 01173 KPluginInfo::List wallpaperList = Wallpaper::listWallpaperInfoForMimetype(format); 01174 if (!wallpaperList.isEmpty()) { 01175 event->setAccepted(true); 01176 break; 01177 } 01178 } 01179 } 01180 } 01181 01182 if (event->isAccepted()) { 01183 if (d->dropZoneStarted) { 01184 showDropZone(event->pos().toPoint()); 01185 } else { 01186 if (!d->showDropZoneDelayTimer) { 01187 d->showDropZoneDelayTimer = new QTimer(this); 01188 d->showDropZoneDelayTimer->setInterval(300); 01189 d->showDropZoneDelayTimer->setSingleShot(true); 01190 connect(d->showDropZoneDelayTimer, SIGNAL(timeout()), this, SLOT(showDropZoneDelayed())); 01191 } 01192 01193 d->dropPoints.insert(0, event->pos()); 01194 d->showDropZoneDelayTimer->start(); 01195 } 01196 } 01197 } 01198 01199 void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) 01200 { 01201 //kDebug() << event->pos() << size().height() << size().width(); 01202 if (d->showDropZoneDelayTimer) { 01203 d->showDropZoneDelayTimer->stop(); 01204 } 01205 01206 if (event->pos().y() < 1 || event->pos().y() > size().height() || 01207 event->pos().x() < 1 || event->pos().x() > size().width()) { 01208 showDropZone(QPoint()); 01209 d->dropZoneStarted = false; 01210 } 01211 } 01212 01213 void ContainmentPrivate::showDropZoneDelayed() 01214 { 01215 dropZoneStarted = true; 01216 q->showDropZone(dropPoints.value(0).toPoint()); 01217 dropPoints.remove(0); 01218 } 01219 01220 void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event) 01221 { 01222 QGraphicsItem *item = scene()->itemAt(event->scenePos()); 01223 event->setAccepted(item == this || item == d->toolBox.data() || !item); 01224 //kDebug() << event->isAccepted() << d->showDropZoneDelayTimer->isActive(); 01225 if (!event->isAccepted()) { 01226 if (d->showDropZoneDelayTimer) { 01227 d->showDropZoneDelayTimer->stop(); 01228 } 01229 } else if (!d->showDropZoneDelayTimer->isActive() && immutability() == Plasma::Mutable) { 01230 showDropZone(event->pos().toPoint()); 01231 } 01232 } 01233 01234 void Containment::dropEvent(QGraphicsSceneDragDropEvent *event) 01235 { 01236 if (isContainment()) { 01237 d->dropData(event->scenePos(), event->screenPos(), event); 01238 } else { 01239 Applet::dropEvent(event); 01240 } 01241 } 01242 01243 void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent) 01244 { 01245 if (q->immutability() != Mutable) { 01246 return; 01247 } 01248 01249 QPointF pos = q->mapFromScene(scenePos); 01250 const QMimeData *mimeData = 0; 01251 01252 if (dropEvent) { 01253 mimeData = dropEvent->mimeData(); 01254 } else { 01255 QClipboard *clipboard = QApplication::clipboard(); 01256 mimeData = clipboard->mimeData(QClipboard::Selection); 01257 //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection? 01258 } 01259 01260 if (!mimeData) { 01261 //Selection is either empty or not supported on this OS 01262 kDebug() << "no mime data"; 01263 return; 01264 } 01265 01266 //kDebug() << event->mimeData()->text(); 01267 01268 QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString()); 01269 01270 if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) { 01271 QString data = mimeData->data(appletMimetype); 01272 const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); 01273 foreach (const QString &appletName, appletNames) { 01274 //kDebug() << "doing" << appletName; 01275 QRectF geom(pos, QSize(0, 0)); 01276 q->addApplet(appletName, QVariantList(), geom); 01277 } 01278 if (dropEvent) { 01279 dropEvent->acceptProposedAction(); 01280 } 01281 } else if (mimeData->hasFormat(ExtenderItemMimeData::mimeType())) { 01282 kDebug() << "mimetype plasma/extenderitem is dropped, creating internal:extender"; 01283 //Handle dropping extenderitems. 01284 const ExtenderItemMimeData *extenderData = qobject_cast<const ExtenderItemMimeData*>(mimeData); 01285 if (extenderData) { 01286 ExtenderItem *item = extenderData->extenderItem(); 01287 QRectF geometry(pos - extenderData->pointerOffset(), item->size()); 01288 kDebug() << "desired geometry: " << geometry; 01289 Applet *applet = qobject_cast<ExtenderApplet *>(item->extender() ? item->extender()->applet() : 0); 01290 if (applet) { 01291 qreal left, top, right, bottom; 01292 applet->getContentsMargins(&left, &top, &right, &bottom); 01293 applet->setPos(geometry.topLeft() - QPointF(int(left), int(top))); 01294 applet->show(); 01295 } else { 01296 applet = addApplet("internal:extender", QVariantList(), geometry, 0, true); 01297 applet->hide(); 01298 applet->init(); 01299 appletAppeared(applet); 01300 applet->flushPendingConstraintsEvents(); 01301 applet->d->scheduleModificationNotification(); 01302 applet->adjustSize(); 01303 applet->show(); 01304 } 01305 item->setExtender(applet->extender()); 01306 } 01307 } else if (KUrl::List::canDecode(mimeData)) { 01308 //TODO: collect the mimetypes of available script engines and offer 01309 // to create widgets out of the matching URLs, if any 01310 const KUrl::List urls = KUrl::List::fromMimeData(mimeData); 01311 foreach (const KUrl &url, urls) { 01312 if (AccessManager::supportedProtocols().contains(url.protocol())) { 01313 AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url); 01314 if (dropEvent) { 01315 dropPoints[job] = dropEvent->pos(); 01316 } else { 01317 dropPoints[job] = scenePos; 01318 } 01319 QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)), 01320 q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*))); 01321 } 01322 #ifndef PLASMA_NO_KIO 01323 else { 01324 KMimeType::Ptr mime = KMimeType::findByUrl(url); 01325 QString mimeName = mime->name(); 01326 QRectF geom(pos, QSize()); 01327 QVariantList args; 01328 args << url.url(); 01329 kDebug() << "can decode" << mimeName << args; 01330 01331 // It may be a directory or a file, let's stat 01332 KIO::JobFlags flags = KIO::HideProgressInfo; 01333 KIO::MimetypeJob *job = KIO::mimetype(url, flags); 01334 if (dropEvent) { 01335 dropPoints[job] = dropEvent->pos(); 01336 } else { 01337 dropPoints[job] = scenePos; 01338 } 01339 01340 QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*))); 01341 QObject::connect(job, SIGNAL(mimetype(KIO::Job *, const QString&)), 01342 q, SLOT(mimeTypeRetrieved(KIO::Job *, const QString&))); 01343 01344 KMenu *choices = new KMenu("Content dropped"); 01345 choices->addAction(KIcon("process-working"), i18n("Fetching file type...")); 01346 if (dropEvent) { 01347 choices->popup(dropEvent->screenPos()); 01348 } else { 01349 choices->popup(screenPos); 01350 } 01351 01352 dropMenus[job] = choices; 01353 } 01354 #endif 01355 } 01356 01357 if (dropEvent) { 01358 dropEvent->acceptProposedAction(); 01359 } 01360 } else { 01361 QStringList formats = mimeData->formats(); 01362 QHash<QString, KPluginInfo> seenPlugins; 01363 QHash<QString, QString> pluginFormats; 01364 01365 foreach (const QString &format, formats) { 01366 KPluginInfo::List plugins = Applet::listAppletInfoForMimetype(format); 01367 01368 foreach (const KPluginInfo &plugin, plugins) { 01369 if (seenPlugins.contains(plugin.pluginName())) { 01370 continue; 01371 } 01372 01373 seenPlugins.insert(plugin.pluginName(), plugin); 01374 pluginFormats.insert(plugin.pluginName(), format); 01375 } 01376 } 01377 //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); 01378 01379 QString selectedPlugin; 01380 01381 if (seenPlugins.isEmpty()) { 01382 // do nothing 01383 } else if (seenPlugins.count() == 1) { 01384 selectedPlugin = seenPlugins.constBegin().key(); 01385 } else { 01386 KMenu choices; 01387 QHash<QAction *, QString> actionsToPlugins; 01388 foreach (const KPluginInfo &info, seenPlugins) { 01389 QAction *action; 01390 if (!info.icon().isEmpty()) { 01391 action = choices.addAction(KIcon(info.icon()), info.name()); 01392 } else { 01393 action = choices.addAction(info.name()); 01394 } 01395 01396 actionsToPlugins.insert(action, info.pluginName()); 01397 } 01398 01399 QAction *choice = choices.exec(screenPos); 01400 if (choice) { 01401 selectedPlugin = actionsToPlugins[choice]; 01402 } 01403 } 01404 01405 if (!selectedPlugin.isEmpty()) { 01406 if (!dropEvent) { 01407 // since we may have entered an event loop up above with the menu, 01408 // the clipboard item may no longer be valid, as QClipboard resets 01409 // the object behind the back of the application with a zero timer 01410 // so we fetch it again here 01411 QClipboard *clipboard = QApplication::clipboard(); 01412 mimeData = clipboard->mimeData(QClipboard::Selection); 01413 } 01414 01415 KTemporaryFile tempFile; 01416 if (mimeData && tempFile.open()) { 01417 //TODO: what should we do with files after the applet is done with them?? 01418 tempFile.setAutoRemove(false); 01419 01420 { 01421 QDataStream stream(&tempFile); 01422 QByteArray data = mimeData->data(pluginFormats[selectedPlugin]); 01423 stream.writeRawData(data, data.size()); 01424 } 01425 01426 QRectF geom(pos, QSize()); 01427 QVariantList args; 01428 args << tempFile.fileName(); 01429 kDebug() << args; 01430 tempFile.close(); 01431 01432 q->addApplet(selectedPlugin, args, geom); 01433 } 01434 } 01435 } 01436 } 01437 01438 void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job) 01439 { 01440 #ifndef PLASMA_NO_KIO 01441 QObject::disconnect(job, 0, q, 0); 01442 dropPoints.remove(job); 01443 KMenu *choices = dropMenus.take(job); 01444 delete choices; 01445 job->kill(); 01446 #endif // PLASMA_NO_KIO 01447 } 01448 01449 void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job) 01450 { 01451 QPointF pos = dropPoints.take(job); 01452 if (job->error()) { 01453 //TODO: nice user visible error handling (knotification probably?) 01454 kDebug() << "remote applet access failed: " << job->errorText(); 01455 return; 01456 } 01457 01458 if (!job->applet()) { 01459 kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero"; 01460 return; 01461 } 01462 01463 q->addApplet(job->applet(), pos); 01464 } 01465 01466 void ContainmentPrivate::dropJobResult(KJob *job) 01467 { 01468 #ifndef PLASMA_NO_KIO 01469 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job); 01470 if (!tjob) { 01471 kDebug() << "job is not a KIO::TransferJob, won't handle the drop..."; 01472 clearDataForMimeJob(tjob); 01473 return; 01474 } 01475 if (job->error()) { 01476 kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString(); 01477 } 01478 // We call mimetypeRetrieved since there might be other mechanisms 01479 // for finding suitable applets. Cleanup happens there as well. 01480 mimeTypeRetrieved(qobject_cast<KIO::Job *>(job), QString()); 01481 #endif // PLASMA_NO_KIO 01482 } 01483 01484 void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype) 01485 { 01486 #ifndef PLASMA_NO_KIO 01487 kDebug() << "Mimetype Job returns." << mimetype; 01488 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job); 01489 if (!tjob) { 01490 kDebug() << "job should be a TransferJob, but isn't"; 01491 clearDataForMimeJob(job); 01492 return; 01493 } 01494 KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url()); 01495 if (mimetype.isEmpty() && !appletList.count()) { 01496 clearDataForMimeJob(job); 01497 kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")"; 01498 return; 01499 } else { 01500 01501 QPointF posi; // will be overwritten with the event's position 01502 if (dropPoints.keys().contains(tjob)) { 01503 posi = dropPoints[tjob]; 01504 kDebug() << "Received a suitable dropEvent at" << posi; 01505 } else { 01506 kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; 01507 clearDataForMimeJob(job); 01508 return; 01509 } 01510 01511 KMenu *choices = dropMenus.value(tjob); 01512 if (!choices) { 01513 kDebug() << "Bailing out. No QMenu found for this job."; 01514 clearDataForMimeJob(job); 01515 return; 01516 } 01517 01518 QVariantList args; 01519 args << tjob->url().url() << mimetype; 01520 01521 kDebug() << "Creating menu for:" << mimetype << posi << args; 01522 01523 appletList << Applet::listAppletInfoForMimetype(mimetype); 01524 KPluginInfo::List wallpaperList; 01525 if (q->drawWallpaper()) { 01526 wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimetype); 01527 } 01528 01529 if (!appletList.isEmpty() || !wallpaperList.isEmpty()) { 01530 choices->clear(); 01531 QHash<QAction *, QString> actionsToApplets; 01532 choices->addTitle(i18n("Widgets")); 01533 foreach (const KPluginInfo &info, appletList) { 01534 kDebug() << info.name(); 01535 QAction *action; 01536 if (!info.icon().isEmpty()) { 01537 action = choices->addAction(KIcon(info.icon()), info.name()); 01538 } else { 01539 action = choices->addAction(info.name()); 01540 } 01541 01542 actionsToApplets.insert(action, info.pluginName()); 01543 kDebug() << info.pluginName(); 01544 } 01545 actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon"); 01546 01547 QHash<QAction *, QString> actionsToWallpapers; 01548 if (!wallpaperList.isEmpty()) { 01549 choices->addTitle(i18n("Wallpaper")); 01550 01551 QMap<QString, KPluginInfo> sorted; 01552 foreach (const KPluginInfo &info, appletList) { 01553 sorted.insert(info.name(), info); 01554 } 01555 01556 foreach (const KPluginInfo &info, wallpaperList) { 01557 QAction *action; 01558 if (!info.icon().isEmpty()) { 01559 action = choices->addAction(KIcon(info.icon()), info.name()); 01560 } else { 01561 action = choices->addAction(info.name()); 01562 } 01563 01564 actionsToWallpapers.insert(action, info.pluginName()); 01565 } 01566 } 01567 01568 QAction *choice = choices->exec(); 01569 if (choice) { 01570 // Put the job on hold so it can be recycled to fetch the actual content, 01571 // which is to be expected when something's dropped onto the desktop and 01572 // an applet is to be created with this URL 01573 if (!mimetype.isEmpty() && !tjob->error()) { 01574 tjob->putOnHold(); 01575 KIO::Scheduler::publishSlaveOnHold(); 01576 } 01577 QString plugin = actionsToApplets.value(choice); 01578 if (plugin.isEmpty()) { 01579 //set wallpapery stuff 01580 plugin = actionsToWallpapers.value(choice); 01581 if (!wallpaper || plugin != wallpaper->pluginName()) { 01582 kDebug() << "Wallpaper dropped:" << tjob->url(); 01583 q->setWallpaper(plugin); 01584 } 01585 01586 if (wallpaper) { 01587 kDebug() << "Wallpaper dropped:" << tjob->url(); 01588 emit wallpaper->urlDropped(tjob->url()); 01589 } 01590 } else { 01591 addApplet(actionsToApplets[choice], args, QRectF(posi, QSize())); 01592 } 01593 01594 clearDataForMimeJob(job); 01595 return; 01596 } 01597 } else { 01598 // we can at least create an icon as a link to the URL 01599 addApplet("icon", args, QRectF(posi, QSize())); 01600 } 01601 } 01602 01603 clearDataForMimeJob(job); 01604 #endif // PLASMA_NO_KIO 01605 } 01606 01607 #ifndef KDE_NO_DEPRECATED 01608 const QGraphicsItem *Containment::toolBoxItem() const 01609 { 01610 return d->toolBox.data(); 01611 } 01612 #endif 01613 01614 void Containment::setToolBox(AbstractToolBox *toolBox) 01615 { 01616 if (d->toolBox.data()) { 01617 d->toolBox.data()->deleteLater(); 01618 } 01619 d->toolBox = toolBox; 01620 } 01621 01622 AbstractToolBox *Containment::toolBox() const 01623 { 01624 return d->toolBox.data(); 01625 } 01626 01627 void Containment::resizeEvent(QGraphicsSceneResizeEvent *event) 01628 { 01629 Applet::resizeEvent(event); 01630 01631 if (isContainment()) { 01632 if (d->isPanelContainment()) { 01633 d->positionPanel(); 01634 } else if (corona()) { 01635 QMetaObject::invokeMethod(corona(), "layoutContainments"); 01636 } 01637 01638 if (d->wallpaper) { 01639 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); 01640 } 01641 } 01642 } 01643 01644 void Containment::keyPressEvent(QKeyEvent *event) 01645 { 01646 //kDebug() << "keyPressEvent with" << event->key() 01647 // << "and hoping and wishing for a" << Qt::Key_Tab; 01648 if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) { 01649 if (!d->applets.isEmpty()) { 01650 kDebug() << "let's give focus to...." << (QObject*)d->applets.first(); 01651 d->applets.first()->setFocus(Qt::TabFocusReason); 01652 } 01653 } 01654 } 01655 01656 void Containment::wheelEvent(QGraphicsSceneWheelEvent *event) 01657 { 01658 event->ignore(); 01659 if (d->appletAt(event->scenePos())) { 01660 return; //no unexpected click-throughs 01661 } 01662 01663 if (d->wallpaper && d->wallpaper->isInitialized()) { 01664 QGraphicsItem *item = scene()->itemAt(event->scenePos()); 01665 if (item == this) { 01666 event->ignore(); 01667 d->wallpaper->wheelEvent(event); 01668 01669 if (event->isAccepted()) { 01670 return; 01671 } 01672 } 01673 } 01674 01675 QString trigger = ContainmentActions::eventToString(event); 01676 01677 if (d->prepareContainmentActions(trigger, event->screenPos())) { 01678 d->actionPlugins()->value(trigger)->contextEvent(event); 01679 event->accept(); 01680 } else { 01681 event->ignore(); 01682 Applet::wheelEvent(event); 01683 } 01684 } 01685 01686 bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event) 01687 { 01688 Applet *applet = qgraphicsitem_cast<Applet*>(watched); 01689 01690 // Otherwise we're watching something we shouldn't be... 01691 Q_ASSERT(applet != 0); 01692 if (!d->applets.contains(applet)) { 01693 return false; 01694 } 01695 01696 //kDebug() << "got sceneEvent"; 01697 switch (event->type()) { 01698 case QEvent::GraphicsSceneHoverEnter: 01699 //kDebug() << "got hoverenterEvent" << immutability() << " " << applet->immutability(); 01700 if (immutability() == Mutable && applet->immutability() == Mutable) { 01701 QGraphicsSceneHoverEvent *he = static_cast<QGraphicsSceneHoverEvent*>(event); 01702 if (d->handles.contains(applet)) { 01703 AppletHandle *handle = d->handles.value(applet); 01704 if (handle) { 01705 handle->setHoverPos(he->pos()); 01706 } 01707 } else { 01708 //kDebug() << "generated applet handle"; 01709 AppletHandle *handle = new AppletHandle(this, applet, he->pos()); 01710 d->handles[applet] = handle; 01711 connect(handle, SIGNAL(disappearDone(AppletHandle*)), 01712 this, SLOT(handleDisappeared(AppletHandle*))); 01713 connect(applet, SIGNAL(geometryChanged()), 01714 handle, SLOT(appletResized())); 01715 } 01716 } 01717 break; 01718 case QEvent::GraphicsSceneHoverMove: 01719 if (immutability() == Mutable && applet->immutability() == Mutable) { 01720 QGraphicsSceneHoverEvent *he = static_cast<QGraphicsSceneHoverEvent*>(event); 01721 if (d->handles.contains(applet)) { 01722 AppletHandle *handle = d->handles.value(applet); 01723 if (handle) { 01724 handle->setHoverPos(he->pos()); 01725 } 01726 } 01727 } 01728 break; 01729 default: 01730 break; 01731 } 01732 01733 return false; 01734 } 01735 01736 QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) 01737 { 01738 //FIXME if the applet is moved to another containment we need to unfocus it 01739 01740 if (isContainment() && 01741 (change == QGraphicsItem::ItemSceneHasChanged || 01742 change == QGraphicsItem::ItemPositionHasChanged)) { 01743 switch (d->type) { 01744 case PanelContainment: 01745 case CustomPanelContainment: 01746 d->positionPanel(); 01747 break; 01748 default: 01749 if (corona()) { 01750 QMetaObject::invokeMethod(corona(), "layoutContainments"); 01751 } 01752 break; 01753 } 01754 } 01755 01756 return Applet::itemChange(change, value); 01757 } 01758 01759 void Containment::enableAction(const QString &name, bool enable) 01760 { 01761 QAction *action = this->action(name); 01762 if (action) { 01763 action->setEnabled(enable); 01764 action->setVisible(enable); 01765 } 01766 } 01767 01768 void Containment::addToolBoxAction(QAction *action) 01769 { 01770 d->createToolBox(); 01771 if (d->toolBox) { 01772 d->toolBox.data()->addTool(action); 01773 } 01774 } 01775 01776 void Containment::removeToolBoxAction(QAction *action) 01777 { 01778 if (d->toolBox) { 01779 d->toolBox.data()->removeTool(action); 01780 } 01781 } 01782 01783 void Containment::setToolBoxOpen(bool open) 01784 { 01785 if (open) { 01786 openToolBox(); 01787 } else { 01788 closeToolBox(); 01789 } 01790 } 01791 01792 bool Containment::isToolBoxOpen() const 01793 { 01794 return (d->toolBox && d->toolBox.data()->isShowing()); 01795 } 01796 01797 void Containment::openToolBox() 01798 { 01799 if (d->toolBox && !d->toolBox.data()->isShowing()) { 01800 d->toolBox.data()->setShowing(true); 01801 emit toolBoxVisibilityChanged(true); 01802 } 01803 } 01804 01805 void Containment::closeToolBox() 01806 { 01807 if (d->toolBox && d->toolBox.data()->isShowing()) { 01808 d->toolBox.data()->setShowing(false); 01809 emit toolBoxVisibilityChanged(false); 01810 } 01811 } 01812 01813 void Containment::addAssociatedWidget(QWidget *widget) 01814 { 01815 Applet::addAssociatedWidget(widget); 01816 if (d->focusedApplet) { 01817 d->focusedApplet->addAssociatedWidget(widget); 01818 } 01819 01820 foreach (const Applet *applet, d->applets) { 01821 if (applet->d->activationAction) { 01822 widget->addAction(applet->d->activationAction); 01823 } 01824 } 01825 } 01826 01827 void Containment::removeAssociatedWidget(QWidget *widget) 01828 { 01829 Applet::removeAssociatedWidget(widget); 01830 if (d->focusedApplet) { 01831 d->focusedApplet->removeAssociatedWidget(widget); 01832 } 01833 01834 foreach (const Applet *applet, d->applets) { 01835 if (applet->d->activationAction) { 01836 widget->removeAction(applet->d->activationAction); 01837 } 01838 } 01839 } 01840 01841 void Containment::setDrawWallpaper(bool drawWallpaper) 01842 { 01843 d->drawWallpaper = drawWallpaper; 01844 if (drawWallpaper) { 01845 KConfigGroup cfg = config(); 01846 const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper); 01847 const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode); 01848 setWallpaper(wallpaper, mode); 01849 } else { 01850 delete d->wallpaper; 01851 d->wallpaper = 0; 01852 } 01853 } 01854 01855 bool Containment::drawWallpaper() 01856 { 01857 return d->drawWallpaper; 01858 } 01859 01860 void Containment::setWallpaper(const QString &pluginName, const QString &mode) 01861 { 01862 KConfigGroup cfg = config(); 01863 bool newPlugin = true; 01864 bool newMode = true; 01865 01866 if (d->drawWallpaper) { 01867 if (d->wallpaper) { 01868 // we have a wallpaper, so let's decide whether we need to swap it out 01869 if (d->wallpaper->pluginName() != pluginName) { 01870 delete d->wallpaper; 01871 d->wallpaper = 0; 01872 } else { 01873 // it's the same plugin, so let's save its state now so when 01874 // we call restore later on we're safe 01875 newMode = d->wallpaper->renderingMode().name() != mode; 01876 newPlugin = false; 01877 } 01878 } 01879 01880 if (!pluginName.isEmpty() && !d->wallpaper) { 01881 d->wallpaper = Plasma::Wallpaper::load(pluginName); 01882 } 01883 01884 if (d->wallpaper) { 01885 d->wallpaper->setParent(this); 01886 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); 01887 d->wallpaper->setRenderingMode(mode); 01888 01889 if (newPlugin) { 01890 cfg.writeEntry("wallpaperplugin", pluginName); 01891 } 01892 01893 if (d->wallpaper->isInitialized()) { 01894 KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper"); 01895 wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName); 01896 d->wallpaper->restore(wallpaperConfig); 01897 } 01898 01899 if (newMode) { 01900 cfg.writeEntry("wallpaperpluginmode", mode); 01901 } 01902 } 01903 01904 update(); 01905 } 01906 01907 if (!d->wallpaper) { 01908 cfg.deleteEntry("wallpaperplugin"); 01909 cfg.deleteEntry("wallpaperpluginmode"); 01910 } 01911 01912 if (newPlugin || newMode) { 01913 if (newPlugin && d->wallpaper) { 01914 connect(d->wallpaper, SIGNAL(configureRequested()), this, SLOT(requestConfiguration())); 01915 connect(d->wallpaper, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); 01916 } 01917 01918 emit configNeedsSaving(); 01919 } 01920 } 01921 01922 Plasma::Wallpaper *Containment::wallpaper() const 01923 { 01924 return d->wallpaper; 01925 } 01926 01927 void Containment::setContainmentActions(const QString &trigger, const QString &pluginName) 01928 { 01929 KConfigGroup cfg = containmentActionsConfig(); 01930 ContainmentActions *plugin = 0; 01931 01932 if (d->actionPlugins()->contains(trigger)) { 01933 plugin = d->actionPlugins()->value(trigger); 01934 if (plugin->pluginName() != pluginName) { 01935 d->actionPlugins()->remove(trigger); 01936 delete plugin; 01937 plugin=0; 01938 } 01939 } 01940 if (pluginName.isEmpty()) { 01941 cfg.deleteEntry(trigger); 01942 } else if (plugin) { 01943 //it already existed, just reload config 01944 if (plugin->isInitialized()) { 01945 plugin->setContainment(this); //to be safe 01946 //FIXME make a truly unique config group 01947 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); 01948 plugin->restore(pluginConfig); 01949 } 01950 } else { 01951 switch (d->containmentActionsSource) { 01952 case ContainmentPrivate::Activity: 01953 //FIXME 01954 case ContainmentPrivate::Local: 01955 plugin = ContainmentActions::load(this, pluginName); 01956 break; 01957 default: 01958 plugin = ContainmentActions::load(0, pluginName); 01959 } 01960 if (plugin) { 01961 cfg.writeEntry(trigger, pluginName); 01962 d->actionPlugins()->insert(trigger, plugin); 01963 } else { 01964 //bad plugin... gets removed. is this a feature or a bug? 01965 cfg.deleteEntry(trigger); 01966 } 01967 } 01968 01969 emit configNeedsSaving(); 01970 } 01971 01972 QStringList Containment::containmentActionsTriggers() 01973 { 01974 return d->actionPlugins()->keys(); 01975 } 01976 01977 QString Containment::containmentActions(const QString &trigger) 01978 { 01979 ContainmentActions *c = d->actionPlugins()->value(trigger); 01980 return c ? c->pluginName() : QString(); 01981 } 01982 01983 void Containment::setActivity(const QString &activity) 01984 { 01985 Context *context = d->context(); 01986 if (context->currentActivity() != activity) { 01987 context->setCurrentActivity(activity); 01988 } 01989 } 01990 01991 void ContainmentPrivate::onContextChanged(Plasma::Context *con) 01992 { 01993 foreach (Applet *a, applets) { 01994 a->updateConstraints(ContextConstraint); 01995 } 01996 01997 KConfigGroup c = q->config(); 01998 QString act = con->currentActivityId(); 01999 02000 //save anything that's been set (boy I hope this avoids overwriting things) 02001 //FIXME of course if the user sets the name to an empty string we have a bug 02002 //but once we get context retrieving the name as soon as the id is set, this issue should go away 02003 if (!act.isEmpty()) { 02004 c.writeEntry("activityId", act); 02005 } 02006 act = con->currentActivity(); 02007 if (!act.isEmpty()) { 02008 c.writeEntry("activity", act); 02009 } 02010 02011 if (toolBox) { 02012 toolBox.data()->update(); 02013 } 02014 emit q->configNeedsSaving(); 02015 emit q->contextChanged(con); 02016 } 02017 02018 QString Containment::activity() const 02019 { 02020 return d->context()->currentActivity(); 02021 } 02022 02023 Context *Containment::context() const 02024 { 02025 return d->context(); 02026 } 02027 02028 Context *ContainmentPrivate::context() 02029 { 02030 if (!con) { 02031 con = new Context(q); 02032 q->connect(con, SIGNAL(changed(Plasma::Context*)), 02033 q, SLOT(onContextChanged(Plasma::Context*))); 02034 } 02035 02036 return con; 02037 } 02038 02039 KActionCollection* ContainmentPrivate::actions() 02040 { 02041 return static_cast<Applet*>(q)->d->actions; 02042 } 02043 02044 void ContainmentPrivate::focusApplet(Plasma::Applet *applet) 02045 { 02046 if (focusedApplet == applet) { 02047 return; 02048 } 02049 02050 QList<QWidget *> widgets = actions()->associatedWidgets(); 02051 if (focusedApplet) { 02052 foreach (QWidget *w, widgets) { 02053 focusedApplet->removeAssociatedWidget(w); 02054 } 02055 } 02056 02057 if (applet && applets.contains(applet)) { 02058 //kDebug() << "switching to" << applet->name(); 02059 focusedApplet = applet; 02060 foreach (QWidget *w, widgets) { 02061 focusedApplet->addAssociatedWidget(w); 02062 } 02063 02064 if (!focusedApplet->hasFocus()) { 02065 focusedApplet->setFocus(Qt::ShortcutFocusReason); 02066 } 02067 } else { 02068 focusedApplet = 0; 02069 } 02070 } 02071 02072 void Containment::focusNextApplet() 02073 { 02074 if (d->applets.isEmpty()) { 02075 return; 02076 } 02077 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0; 02078 if (index >= d->applets.size()) { 02079 index = 0; 02080 } 02081 kDebug() << "index" << index; 02082 d->focusApplet(d->applets.at(index)); 02083 } 02084 02085 void Containment::focusPreviousApplet() 02086 { 02087 if (d->applets.isEmpty()) { 02088 return; 02089 } 02090 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1; 02091 if (index < 0) { 02092 index = d->applets.size() - 1; 02093 } 02094 kDebug() << "index" << index; 02095 d->focusApplet(d->applets.at(index)); 02096 } 02097 02098 void Containment::destroy() 02099 { 02100 destroy(true); 02101 } 02102 02103 void Containment::showConfigurationInterface() 02104 { 02105 Applet::showConfigurationInterface(); 02106 } 02107 02108 void Containment::configChanged() 02109 { 02110 if (d->drawWallpaper) { 02111 KConfigGroup group = config(); 02112 setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), 02113 group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); 02114 } 02115 02116 Applet::configChanged(); 02117 } 02118 02119 void ContainmentPrivate::requestConfiguration() 02120 { 02121 emit q->configureRequested(q); 02122 } 02123 02124 void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus) 02125 { 02126 //kDebug() << "================== "<< appletStatus << q->status(); 02127 if (appletStatus == q->status()) { 02128 emit q->newStatus(appletStatus); 02129 return; 02130 } 02131 02132 if (appletStatus < q->status()) { 02133 // check to see if any other applet has a higher status, and stick with that 02134 // if we do 02135 foreach (Applet *applet, applets) { 02136 if (applet->status() > appletStatus) { 02137 appletStatus = applet->status(); 02138 } 02139 } 02140 } 02141 02142 q->setStatus(appletStatus); 02143 } 02144 02145 void Containment::destroy(bool confirm) 02146 { 02147 if (immutability() != Mutable || Applet::d->transient) { 02148 return; 02149 } 02150 02151 if (isContainment()) { 02152 //FIXME maybe that %1 should be the containment type not the name 02153 //FIXME: should not be blocking 02154 if (!confirm || 02155 KMessageBox::warningContinueCancel( 02156 view(), 02157 i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()), 02158 i18nc("@title:window %1 is the name of the containment", "Remove %1", name()), KStandardGuiItem::remove()) == KMessageBox::Continue) { 02159 //clearApplets(); 02160 Applet::destroy(); 02161 } 02162 } else { 02163 Applet::destroy(); 02164 } 02165 } 02166 02167 void ContainmentPrivate::createToolBox() 02168 { 02169 if (!toolBox) { 02170 toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q); 02171 02172 if (toolBox) { 02173 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); 02174 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility())); 02175 02176 positionToolBox(); 02177 } 02178 } 02179 } 02180 02181 void ContainmentPrivate::positionToolBox() 02182 { 02183 QMetaObject::invokeMethod(toolBox.data(), "reposition"); 02184 } 02185 02186 void ContainmentPrivate::updateToolBoxVisibility() 02187 { 02188 emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing()); 02189 } 02190 02191 void ContainmentPrivate::triggerShowAddWidgets() 02192 { 02193 emit q->showAddWidgetsInterface(QPointF()); 02194 } 02195 02196 void ContainmentPrivate::handleDisappeared(AppletHandle *handle) 02197 { 02198 if (handles.contains(handle->applet())) { 02199 handles.remove(handle->applet()); 02200 handle->detachApplet(); 02201 QGraphicsScene *scene = q->scene(); 02202 if (scene && handle->scene() == scene) { 02203 scene->removeItem(handle); 02204 } 02205 handle->deleteLater(); 02206 } 02207 } 02208 02209 void ContainmentPrivate::checkRemoveAction() 02210 { 02211 q->enableAction("remove", q->immutability() == Mutable); 02212 } 02213 02214 void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) 02215 { 02216 if (!q->isContainment()) { 02217 return; 02218 } 02219 02220 //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; 02221 if (constraints & Plasma::ImmutableConstraint) { 02222 //update actions 02223 checkRemoveAction(); 02224 const bool unlocked = q->immutability() == Mutable; 02225 q->setAcceptDrops(unlocked); 02226 q->enableAction("add widgets", unlocked); 02227 02228 // tell the applets too 02229 foreach (Applet *a, applets) { 02230 a->setImmutability(q->immutability()); 02231 a->updateConstraints(ImmutableConstraint); 02232 } 02233 02234 //clear handles on lock 02235 if (!unlocked) { 02236 QMap<Applet*, AppletHandle*> h = handles; 02237 handles.clear(); 02238 02239 foreach (AppletHandle *handle, h) { 02240 handle->disconnect(q); 02241 02242 if (q->scene()) { 02243 q->scene()->removeItem(handle); 02244 } 02245 02246 handle->deleteLater(); 02247 } 02248 } 02249 } 02250 02251 // pass on the constraints that are relevant here 02252 Constraints appletConstraints = NoConstraint; 02253 if (constraints & FormFactorConstraint) { 02254 appletConstraints |= FormFactorConstraint; 02255 } 02256 02257 if (constraints & ScreenConstraint) { 02258 appletConstraints |= ScreenConstraint; 02259 } 02260 02261 if (appletConstraints != NoConstraint) { 02262 foreach (Applet *applet, applets) { 02263 applet->updateConstraints(appletConstraints); 02264 } 02265 } 02266 02267 if (toolBox && (constraints & Plasma::SizeConstraint || 02268 constraints & Plasma::FormFactorConstraint || 02269 constraints & Plasma::ScreenConstraint || 02270 constraints & Plasma::StartupCompletedConstraint)) { 02271 //kDebug() << "Positioning toolbox"; 02272 positionToolBox(); 02273 } 02274 02275 if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) { 02276 q->addToolBoxAction(q->action("remove")); 02277 checkRemoveAction(); 02278 } 02279 } 02280 02281 Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, 02282 const QRectF &appletGeometry, uint id, bool delayInit) 02283 { 02284 if (!q->isContainment()) { 02285 return 0; 02286 } 02287 02288 if (!delayInit && q->immutability() != Mutable) { 02289 kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; 02290 return 0; 02291 } 02292 02293 QGraphicsView *v = q->view(); 02294 if (v) { 02295 v->setCursor(Qt::BusyCursor); 02296 } 02297 02298 Applet *applet = Applet::load(name, id, args); 02299 if (v) { 02300 v->unsetCursor(); 02301 } 02302 02303 if (!applet) { 02304 kDebug() << "Applet" << name << "could not be loaded."; 02305 applet = new Applet(0, QString(), id); 02306 applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); 02307 } 02308 02309 //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); 02310 02311 q->addApplet(applet, appletGeometry.topLeft(), delayInit); 02312 return applet; 02313 } 02314 02315 bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const 02316 { 02317 foreach (Applet *applet, applets) { 02318 if (applet != ignoredApplet && applet->geometry().intersects(region)) { 02319 return false; 02320 } 02321 } 02322 return true; 02323 } 02324 02325 void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet) 02326 { 02327 applets.removeAll(applet); 02328 if (focusedApplet == applet) { 02329 focusedApplet = 0; 02330 } 02331 02332 if (handles.contains(applet)) { 02333 handles.remove(applet); 02334 } 02335 02336 emit q->appletRemoved(applet); 02337 emit q->configNeedsSaving(); 02338 } 02339 02340 void ContainmentPrivate::appletAppearAnimationComplete() 02341 { 02342 Animation *anim = qobject_cast<Animation *>(q->sender()); 02343 if (anim) { 02344 Applet *applet = qobject_cast<Applet*>(anim->targetWidget()); 02345 if (applet) { 02346 appletAppeared(applet); 02347 } 02348 } 02349 } 02350 02351 void ContainmentPrivate::appletAppeared(Applet *applet) 02352 { 02353 kDebug() << type << Containment::DesktopContainment; 02354 if (type == Containment::DesktopContainment) { 02355 applet->installSceneEventFilter(q); 02356 } 02357 02358 KConfigGroup *cg = applet->d->mainConfigGroup(); 02359 applet->save(*cg); 02360 emit q->configNeedsSaving(); 02361 } 02362 02363 void ContainmentPrivate::positionPanel(bool force) 02364 { 02365 if (!q->scene()) { 02366 kDebug() << "no scene yet"; 02367 return; 02368 } 02369 02370 // already positioning the panel - avoid infinite loops 02371 if (ContainmentPrivate::s_positioningPanels) { 02372 return; 02373 } 02374 02375 // we position panels in negative coordinates, and stack all horizontal 02376 // and all vertical panels with each other. 02377 02378 02379 const QPointF p = q->pos(); 02380 02381 if (!force && 02382 p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && 02383 q->scene()->collidingItems(q).isEmpty()) { 02384 // already positioned and not running into any other panels 02385 return; 02386 } 02387 02388 02389 QPointF newPos = preferredPanelPos(q->corona()); 02390 if (p != newPos) { 02391 ContainmentPrivate::s_positioningPanels = true; 02392 q->setPos(newPos); 02393 ContainmentPrivate::s_positioningPanels = false; 02394 } 02395 } 02396 02397 bool ContainmentPrivate::isPanelContainment() const 02398 { 02399 return type == Containment::PanelContainment || type == Containment::CustomPanelContainment; 02400 } 02401 02402 QPointF ContainmentPrivate::preferredPos(Corona *corona) const 02403 { 02404 Q_ASSERT(corona); 02405 02406 if (isPanelContainment()) { 02407 //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona); 02408 return preferredPanelPos(corona); 02409 } 02410 02411 QPointF pos(0, 0); 02412 QTransform t; 02413 while (QGraphicsItem *i = corona->itemAt(pos, t)) { 02414 pos.setX(i->scenePos().x() + i->boundingRect().width() + 10); 02415 } 02416 02417 //kDebug() << "not a panel, put it at" << pos; 02418 return pos; 02419 } 02420 02421 QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const 02422 { 02423 Q_ASSERT(corona); 02424 02425 //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here 02426 bool horiz = formFactor == Plasma::Horizontal; 02427 qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; 02428 qreal lastHeight = 0; 02429 02430 // this should be ok for small numbers of panels, but if we ever end 02431 // up managing hundreds of them, this simplistic alogrithm will 02432 // likely be too slow. 02433 foreach (const Containment *other, corona->containments()) { 02434 if (other == q || 02435 !other->d->isPanelContainment() || 02436 horiz != (other->formFactor() == Plasma::Horizontal)) { 02437 // only line up with panels of the same orientation 02438 continue; 02439 } 02440 02441 if (horiz) { 02442 qreal y = other->pos().y(); 02443 if (y < bottom) { 02444 lastHeight = other->size().height(); 02445 bottom = y; 02446 } 02447 } else { 02448 qreal width = other->size().width(); 02449 qreal x = other->pos().x() + width; 02450 if (x > bottom) { 02451 lastHeight = width; 02452 bottom = x + lastHeight; 02453 } 02454 } 02455 } 02456 02457 // give a space equal to the height again of the last item so there is 02458 // room to grow. 02459 QPointF newPos; 02460 if (horiz) { 02461 bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; 02462 //TODO: fix x position for non-flush-left panels 02463 kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); 02464 newPos = QPointF(0, bottom - q->size().height()); 02465 } else { 02466 bottom += lastHeight + INTER_CONTAINMENT_MARGIN; 02467 //TODO: fix y position for non-flush-top panels 02468 kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); 02469 newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); 02470 } 02471 02472 return newPos; 02473 } 02474 02475 02476 bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu) 02477 { 02478 ContainmentActions *plugin = actionPlugins()->value(trigger); 02479 if (!plugin) { 02480 return false; 02481 } 02482 plugin->setContainment(q); 02483 02484 if (!plugin->isInitialized()) { 02485 KConfigGroup cfg = q->containmentActionsConfig(); 02486 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); 02487 plugin->restore(pluginConfig); 02488 } 02489 02490 if (plugin->configurationRequired()) { 02491 KMenu *localMenu = menu ? menu : new KMenu(); 02492 02493 localMenu->addTitle(i18n("This plugin needs to be configured")); 02494 localMenu->addAction(q->action("configure")); 02495 02496 if (!menu) { 02497 localMenu->exec(screenPos); 02498 delete localMenu; 02499 } 02500 02501 return false; 02502 } else if (menu) { 02503 QList<QAction*> actions = plugin->contextualActions(); 02504 if (actions.isEmpty()) { 02505 //it probably didn't bother implementing the function. give the user a chance to set 02506 //a better plugin. note that if the user sets no-plugin this won't happen... 02507 if (!isPanelContainment() && q->action("configure")) { 02508 menu->addAction(q->action("configure")); 02509 } 02510 } else { 02511 menu->addActions(actions); 02512 } 02513 } 02514 02515 return true; 02516 } 02517 02518 KConfigGroup Containment::containmentActionsConfig() 02519 { 02520 KConfigGroup cfg; 02521 switch (d->containmentActionsSource) { 02522 case ContainmentPrivate::Local: 02523 cfg = config(); 02524 cfg = KConfigGroup(&cfg, "ActionPlugins"); 02525 break; 02526 case ContainmentPrivate::Activity: 02527 cfg = KConfigGroup(corona()->config(), "Activities"); 02528 cfg = KConfigGroup(&cfg, d->context()->currentActivityId()); 02529 cfg = KConfigGroup(&cfg, "ActionPlugins"); 02530 break; 02531 default: 02532 cfg = KConfigGroup(corona()->config(), "ActionPlugins"); 02533 } 02534 return cfg; 02535 } 02536 02537 QHash<QString, ContainmentActions*> * ContainmentPrivate::actionPlugins() 02538 { 02539 switch (containmentActionsSource) { 02540 case Activity: 02541 //FIXME 02542 case Local: 02543 return &localActionPlugins; 02544 default: 02545 return &globalActionPlugins; 02546 } 02547 } 02548 02549 } // Plasma namespace 02550 02551 #include "containment.moc" 02552
KDE 4.6 API Reference