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/containmentactionspluginsconfig_p.h" 00071 #include "private/extenderitemmimedata_p.h" 00072 #include "private/extenderapplet_p.h" 00073 #include "private/wallpaper_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 qobject_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 if (!applet->d->failed) { 00691 QAction *configureApplet = applet->d->actions->action("configure"); 00692 if (configureApplet && configureApplet->isEnabled()) { 00693 desktopMenu.addAction(configureApplet); 00694 } 00695 00696 QAction *runAssociatedApplication = applet->d->actions->action("run associated application"); 00697 if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { 00698 desktopMenu.addAction(runAssociatedApplication); 00699 } 00700 } 00701 00702 KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); 00703 addContainmentActions(*containmentMenu, event); 00704 if (!containmentMenu->isEmpty()) { 00705 int enabled = 0; 00706 //count number of real actions 00707 QListIterator<QAction *> actionsIt(containmentMenu->actions()); 00708 while (enabled < 3 && actionsIt.hasNext()) { 00709 QAction *action = actionsIt.next(); 00710 if (action->isVisible() && !action->isSeparator()) { 00711 ++enabled; 00712 } 00713 } 00714 00715 if (enabled) { 00716 //if there is only one, don't create a submenu 00717 if (enabled < 2) { 00718 foreach (QAction *action, containmentMenu->actions()) { 00719 if (action->isVisible() && !action->isSeparator()) { 00720 desktopMenu.addAction(action); 00721 } 00722 } 00723 } else { 00724 desktopMenu.addMenu(containmentMenu); 00725 } 00726 } 00727 } 00728 00729 if (q->immutability() == Mutable) { 00730 QAction *closeApplet = applet->d->actions->action("remove"); 00731 //kDebug() << "checking for removal" << closeApplet; 00732 if (closeApplet) { 00733 if (!desktopMenu.isEmpty()) { 00734 desktopMenu.addSeparator(); 00735 } 00736 00737 //kDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); 00738 desktopMenu.addAction(closeApplet); 00739 } 00740 } 00741 } 00742 00743 Applet* ContainmentPrivate::appletAt(const QPointF &point) 00744 { 00745 Applet *applet = 0; 00746 00747 QGraphicsItem *item = q->scene()->itemAt(point); 00748 if (item == q) { 00749 item = 0; 00750 } 00751 00752 while (item) { 00753 if (item->isWidget()) { 00754 applet = qobject_cast<Applet*>(static_cast<QGraphicsWidget*>(item)); 00755 if (applet) { 00756 if (applet->isContainment()) { 00757 applet = 0; 00758 } 00759 break; 00760 } 00761 } 00762 AppletHandle *handle = dynamic_cast<AppletHandle*>(item); 00763 if (handle) { 00764 //pretend it was on the applet 00765 applet = handle->applet(); 00766 break; 00767 } 00768 item = item->parentItem(); 00769 } 00770 return applet; 00771 } 00772 00773 void Containment::setFormFactor(FormFactor formFactor) 00774 { 00775 if (d->formFactor == formFactor) { 00776 return; 00777 } 00778 00779 //kDebug() << "switching FF to " << formFactor; 00780 d->formFactor = formFactor; 00781 00782 if (isContainment() && 00783 (d->type == PanelContainment || d->type == CustomPanelContainment)) { 00784 // we are a panel and we have chaged our orientation 00785 d->positionPanel(true); 00786 } 00787 00788 QMetaObject::invokeMethod(d->toolBox.data(), "reposition"); 00789 00790 updateConstraints(Plasma::FormFactorConstraint); 00791 00792 KConfigGroup c = config(); 00793 c.writeEntry("formfactor", (int)formFactor); 00794 emit configNeedsSaving(); 00795 } 00796 00797 void Containment::setLocation(Location location) 00798 { 00799 if (d->location == location) { 00800 return; 00801 } 00802 00803 bool emitGeomChange = false; 00804 00805 if ((location == TopEdge || location == BottomEdge) && 00806 (d->location == TopEdge || d->location == BottomEdge)) { 00807 emitGeomChange = true; 00808 } 00809 00810 if ((location == RightEdge || location == LeftEdge) && 00811 (d->location == RightEdge || d->location == LeftEdge)) { 00812 emitGeomChange = true; 00813 } 00814 00815 d->location = location; 00816 00817 foreach (Applet *applet, d->applets) { 00818 applet->updateConstraints(Plasma::LocationConstraint); 00819 } 00820 00821 if (emitGeomChange) { 00822 // our geometry on the scene will not actually change, 00823 // but for the purposes of views it has 00824 emit geometryChanged(); 00825 } 00826 00827 updateConstraints(Plasma::LocationConstraint); 00828 00829 KConfigGroup c = config(); 00830 c.writeEntry("location", (int)location); 00831 emit configNeedsSaving(); 00832 } 00833 00834 void Containment::addSiblingContainment() 00835 { 00836 emit addSiblingContainment(this); 00837 } 00838 00839 void Containment::clearApplets() 00840 { 00841 foreach (Applet *applet, d->applets) { 00842 applet->d->cleanUpAndDelete(); 00843 } 00844 00845 d->applets.clear(); 00846 } 00847 00848 Applet *Containment::addApplet(const QString &name, const QVariantList &args, 00849 const QRectF &appletGeometry) 00850 { 00851 return d->addApplet(name, args, appletGeometry); 00852 } 00853 00854 void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit) 00855 { 00856 if (!isContainment() || (!delayInit && immutability() != Mutable)) { 00857 return; 00858 } 00859 00860 if (!applet) { 00861 kDebug() << "adding null applet!?!"; 00862 return; 00863 } 00864 00865 if (d->applets.contains(applet)) { 00866 kDebug() << "already have this applet!"; 00867 } 00868 00869 Containment *currentContainment = applet->containment(); 00870 00871 if (d->type == PanelContainment) { 00872 //panels don't want backgrounds, which is important when setting geometry 00873 setBackgroundHints(NoBackground); 00874 } 00875 00876 if (currentContainment && currentContainment != this) { 00877 emit currentContainment->appletRemoved(applet); 00878 if (currentContainment->d->focusedApplet == applet) { 00879 currentContainment->d->focusedApplet = 0; 00880 } 00881 00882 disconnect(applet, 0, currentContainment, 0); 00883 KConfigGroup oldConfig = applet->config(); 00884 currentContainment->d->applets.removeAll(applet); 00885 applet->setParentItem(this); 00886 applet->setParent(this); 00887 00888 // now move the old config to the new location 00889 //FIXME: this doesn't seem to get the actual main config group containing plugin=, etc 00890 KConfigGroup c = config().group("Applets").group(QString::number(applet->id())); 00891 oldConfig.reparent(&c); 00892 applet->d->resetConfigurationObject(); 00893 00894 disconnect(applet, SIGNAL(activate()), currentContainment, SIGNAL(activate())); 00895 } else { 00896 applet->setParentItem(this); 00897 applet->setParent(this); 00898 } 00899 00900 d->applets << applet; 00901 00902 connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); 00903 connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus())); 00904 connect(applet, SIGNAL(appletDestroyed(Plasma::Applet*)), this, SLOT(appletDestroyed(Plasma::Applet*))); 00905 connect(applet, SIGNAL(newStatus(Plasma::ItemStatus)), this, SLOT(checkStatus(Plasma::ItemStatus))); 00906 connect(applet, SIGNAL(activate()), this, SIGNAL(activate())); 00907 00908 if (pos != QPointF(-1, -1)) { 00909 applet->setPos(pos); 00910 } 00911 00912 if (!delayInit && !currentContainment) { 00913 applet->restore(*applet->d->mainConfigGroup()); 00914 applet->init(); 00915 Plasma::Animation *anim = Plasma::Animator::create(Plasma::Animator::AppearAnimation); 00916 if (anim) { 00917 connect(anim, SIGNAL(finished()), this, SLOT(appletAppearAnimationComplete())); 00918 anim->setTargetWidget(applet); 00919 //FIXME: small hack until we have proper js anim support; allows 'zoom' to work in the 00920 //'right' direction for appearance 00921 anim->setDirection(QAbstractAnimation::Backward); 00922 anim->start(QAbstractAnimation::DeleteWhenStopped); 00923 } else { 00924 d->appletAppeared(applet); 00925 } 00926 } 00927 00928 applet->setFlag(QGraphicsItem::ItemIsMovable, true); 00929 applet->updateConstraints(Plasma::AllConstraints); 00930 if (!delayInit) { 00931 applet->flushPendingConstraintsEvents(); 00932 } 00933 emit appletAdded(applet, pos); 00934 00935 if (!currentContainment) { 00936 applet->updateConstraints(Plasma::StartupCompletedConstraint); 00937 if (!delayInit) { 00938 applet->flushPendingConstraintsEvents(); 00939 } 00940 } 00941 00942 if (!delayInit) { 00943 applet->d->scheduleModificationNotification(); 00944 } 00945 } 00946 00947 Applet::List Containment::applets() const 00948 { 00949 return d->applets; 00950 } 00951 00952 void Containment::setScreen(int newScreen, int newDesktop) 00953 { 00954 d->setScreen(newScreen, newDesktop); 00955 } 00956 00957 void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops) 00958 { 00959 // What we want to do in here is: 00960 // * claim the screen as our own 00961 // * signal whatever may be watching this containment about the switch 00962 // * if we are a full screen containment, then: 00963 // * resize to match the screen if we're that kind of containment 00964 // * kick other full-screen containments off this screen 00965 // * if we had a screen, then give our screen to the containment 00966 // we kick out 00967 // 00968 // a screen of -1 means no associated screen. 00969 Corona *corona = q->corona(); 00970 Q_ASSERT(corona); 00971 00972 //if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen 00973 if (corona->offscreenWidgets().contains(q)) { 00974 return; 00975 } 00976 00977 int numScreens = corona->numScreens(); 00978 if (newScreen < -1) { 00979 newScreen = -1; 00980 } 00981 00982 // -1 == All desktops 00983 if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) { 00984 newDesktop = -1; 00985 } 00986 00987 //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type; 00988 00989 Containment *swapScreensWith(0); 00990 if (type == Containment::DesktopContainment || type >= Containment::CustomContainment) { 00991 // we want to listen to changes in work area if our screen changes 00992 if (toolBox) { 00993 if (screen < 0 && newScreen > -1) { 00994 QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection); 00995 } else if (newScreen < 0) { 00996 QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition())); 00997 } 00998 } 00999 01000 if (newScreen > -1) { 01001 // sanity check to make sure someone else doesn't have this screen already! 01002 Containment *currently = corona->containmentForScreen(newScreen, newDesktop); 01003 if (currently && currently != q) { 01004 kDebug() << "currently is on screen" << currently->screen() 01005 << "desktop" << currently->desktop() 01006 << "and is" << currently->activity() 01007 << (QObject*)currently << "i'm" << (QObject*)q; 01008 currently->setScreen(-1, currently->desktop()); 01009 swapScreensWith = currently; 01010 } 01011 } 01012 } 01013 01014 if (newScreen < numScreens && newScreen > -1 && 01015 (type == Containment::DesktopContainment || type >= Containment::CustomContainment)) { 01016 q->resize(corona->screenGeometry(newScreen).size()); 01017 } 01018 01019 int oldDesktop = desktop; 01020 desktop = newDesktop; 01021 01022 int oldScreen = screen; 01023 screen = newScreen; 01024 01025 q->updateConstraints(Plasma::ScreenConstraint); 01026 01027 if (oldScreen != newScreen || oldDesktop != newDesktop) { 01028 /* 01029 kDebug() << "going to signal change for" << q 01030 << ", old screen & desktop:" << oldScreen << oldDesktop 01031 << ", new:" << screen << desktop; 01032 */ 01033 KConfigGroup c = q->config(); 01034 c.writeEntry("screen", screen); 01035 c.writeEntry("desktop", desktop); 01036 if (newScreen != -1) { 01037 lastScreen = newScreen; 01038 lastDesktop = newDesktop; 01039 c.writeEntry("lastScreen", lastScreen); 01040 c.writeEntry("lastDesktop", lastDesktop); 01041 } 01042 emit q->configNeedsSaving(); 01043 emit q->screenChanged(oldScreen, newScreen, q); 01044 } 01045 01046 if (swapScreensWith) { 01047 //kDebug() << "setScreen due to swap, part 2"; 01048 swapScreensWith->setScreen(oldScreen, oldDesktop); 01049 } 01050 01051 checkRemoveAction(); 01052 01053 if (newScreen >= 0) { 01054 emit q->activate(); 01055 } 01056 } 01057 01058 int Containment::screen() const 01059 { 01060 return d->screen; 01061 } 01062 01063 int Containment::lastScreen() const 01064 { 01065 return d->lastScreen; 01066 } 01067 01068 int Containment::desktop() const 01069 { 01070 return d->desktop; 01071 } 01072 01073 int Containment::lastDesktop() const 01074 { 01075 return d->lastDesktop; 01076 } 01077 01078 KPluginInfo::List Containment::listContainments(const QString &category, 01079 const QString &parentApp) 01080 { 01081 return listContainmentsOfType(QString(), category, parentApp); 01082 } 01083 01084 01085 KPluginInfo::List Containment::listContainmentsOfType(const QString &type, 01086 const QString &category, 01087 const QString &parentApp) 01088 { 01089 QString constraint; 01090 01091 if (parentApp.isEmpty()) { 01092 constraint.append("(not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '')"); 01093 } else { 01094 constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'"); 01095 } 01096 01097 if (!type.isEmpty()) { 01098 if (!constraint.isEmpty()) { 01099 constraint.append(" and "); 01100 } 01101 01102 constraint.append("'").append(type).append("' ~in [X-Plasma-ContainmentCategories]"); 01103 } 01104 01105 if (!category.isEmpty()) { 01106 if (!constraint.isEmpty()) { 01107 constraint.append(" and "); 01108 } 01109 01110 constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'"); 01111 if (category == "Miscellaneous") { 01112 constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')"); 01113 } 01114 } 01115 01116 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); 01117 //kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches"; 01118 return KPluginInfo::fromServices(offers); 01119 } 01120 01121 KPluginInfo::List Containment::listContainmentsForMimetype(const QString &mimetype) 01122 { 01123 const QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); 01124 //kDebug() << mimetype << constraint; 01125 const KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint); 01126 return KPluginInfo::fromServices(offers); 01127 } 01128 01129 QStringList Containment::listContainmentTypes() 01130 { 01131 KPluginInfo::List containmentInfos = listContainments(); 01132 QSet<QString> types; 01133 01134 foreach (const KPluginInfo &containmentInfo, containmentInfos) { 01135 QStringList theseTypes = containmentInfo.service()->property("X-Plasma-ContainmentCategories").toStringList(); 01136 foreach (const QString &type, theseTypes) { 01137 types.insert(type); 01138 } 01139 } 01140 01141 return types.toList(); 01142 } 01143 01144 void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event) 01145 { 01146 //kDebug() << immutability() << Mutable << (immutability() == Mutable); 01147 event->setAccepted(immutability() == Mutable && 01148 (event->mimeData()->hasFormat(static_cast<Corona*>(scene())->appletMimeType()) || 01149 KUrl::List::canDecode(event->mimeData()) || 01150 event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType()))); 01151 01152 if (!event->isAccepted()) { 01153 // check to see if we have an applet that accepts the format. 01154 QStringList formats = event->mimeData()->formats(); 01155 01156 foreach (const QString &format, formats) { 01157 KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(format); 01158 if (!appletList.isEmpty()) { 01159 event->setAccepted(true); 01160 break; 01161 } 01162 } 01163 01164 if (!event->isAccepted()) { 01165 foreach (const QString &format, formats) { 01166 KPluginInfo::List wallpaperList = Wallpaper::listWallpaperInfoForMimetype(format); 01167 if (!wallpaperList.isEmpty()) { 01168 event->setAccepted(true); 01169 break; 01170 } 01171 } 01172 } 01173 } 01174 01175 if (event->isAccepted()) { 01176 if (d->dropZoneStarted) { 01177 showDropZone(event->pos().toPoint()); 01178 } else { 01179 if (!d->showDropZoneDelayTimer) { 01180 d->showDropZoneDelayTimer = new QTimer(this); 01181 d->showDropZoneDelayTimer->setInterval(300); 01182 d->showDropZoneDelayTimer->setSingleShot(true); 01183 connect(d->showDropZoneDelayTimer, SIGNAL(timeout()), this, SLOT(showDropZoneDelayed())); 01184 } 01185 01186 d->dropPoints.insert(0, event->pos()); 01187 d->showDropZoneDelayTimer->start(); 01188 } 01189 } 01190 } 01191 01192 void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) 01193 { 01194 //kDebug() << event->pos() << size().height() << size().width(); 01195 if (d->showDropZoneDelayTimer) { 01196 d->showDropZoneDelayTimer->stop(); 01197 } 01198 01199 if (event->pos().y() < 1 || event->pos().y() > size().height() || 01200 event->pos().x() < 1 || event->pos().x() > size().width()) { 01201 showDropZone(QPoint()); 01202 d->dropZoneStarted = false; 01203 } 01204 } 01205 01206 void ContainmentPrivate::showDropZoneDelayed() 01207 { 01208 dropZoneStarted = true; 01209 q->showDropZone(dropPoints.value(0).toPoint()); 01210 dropPoints.remove(0); 01211 } 01212 01213 void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event) 01214 { 01215 QGraphicsItem *item = scene()->itemAt(event->scenePos()); 01216 event->setAccepted(item == this || item == d->toolBox.data() || !item); 01217 //kDebug() << event->isAccepted() << d->showDropZoneDelayTimer->isActive(); 01218 if (!event->isAccepted()) { 01219 if (d->showDropZoneDelayTimer) { 01220 d->showDropZoneDelayTimer->stop(); 01221 } 01222 } else if (!d->showDropZoneDelayTimer->isActive() && immutability() == Plasma::Mutable) { 01223 showDropZone(event->pos().toPoint()); 01224 } 01225 } 01226 01227 void Containment::dropEvent(QGraphicsSceneDragDropEvent *event) 01228 { 01229 if (isContainment()) { 01230 d->dropData(event->scenePos(), event->screenPos(), event); 01231 } else { 01232 Applet::dropEvent(event); 01233 } 01234 } 01235 01236 void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent) 01237 { 01238 if (q->immutability() != Mutable) { 01239 return; 01240 } 01241 01242 QPointF pos = q->mapFromScene(scenePos); 01243 const QMimeData *mimeData = 0; 01244 01245 if (dropEvent) { 01246 mimeData = dropEvent->mimeData(); 01247 } else { 01248 QClipboard *clipboard = QApplication::clipboard(); 01249 mimeData = clipboard->mimeData(QClipboard::Selection); 01250 //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection? 01251 } 01252 01253 if (!mimeData) { 01254 //Selection is either empty or not supported on this OS 01255 kDebug() << "no mime data"; 01256 return; 01257 } 01258 01259 //kDebug() << event->mimeData()->text(); 01260 01261 QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString()); 01262 01263 if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) { 01264 QString data = mimeData->data(appletMimetype); 01265 const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); 01266 foreach (const QString &appletName, appletNames) { 01267 //kDebug() << "doing" << appletName; 01268 QRectF geom(pos, QSize(0, 0)); 01269 q->addApplet(appletName, QVariantList(), geom); 01270 } 01271 if (dropEvent) { 01272 dropEvent->acceptProposedAction(); 01273 } 01274 } else if (mimeData->hasFormat(ExtenderItemMimeData::mimeType())) { 01275 kDebug() << "mimetype plasma/extenderitem is dropped, creating internal:extender"; 01276 //Handle dropping extenderitems. 01277 const ExtenderItemMimeData *extenderData = qobject_cast<const ExtenderItemMimeData*>(mimeData); 01278 if (extenderData) { 01279 ExtenderItem *item = extenderData->extenderItem(); 01280 QRectF geometry(pos - extenderData->pointerOffset(), item->size()); 01281 kDebug() << "desired geometry: " << geometry; 01282 Applet *applet = qobject_cast<ExtenderApplet *>(item->extender() ? item->extender()->applet() : 0); 01283 if (applet) { 01284 qreal left, top, right, bottom; 01285 applet->getContentsMargins(&left, &top, &right, &bottom); 01286 applet->setPos(geometry.topLeft() - QPointF(int(left), int(top))); 01287 applet->show(); 01288 } else { 01289 applet = addApplet("internal:extender", QVariantList(), geometry, 0, true); 01290 applet->hide(); 01291 applet->init(); 01292 appletAppeared(applet); 01293 applet->flushPendingConstraintsEvents(); 01294 applet->d->scheduleModificationNotification(); 01295 applet->adjustSize(); 01296 applet->show(); 01297 } 01298 item->setExtender(applet->extender()); 01299 } 01300 } else if (KUrl::List::canDecode(mimeData)) { 01301 //TODO: collect the mimetypes of available script engines and offer 01302 // to create widgets out of the matching URLs, if any 01303 const KUrl::List urls = KUrl::List::fromMimeData(mimeData); 01304 foreach (const KUrl &url, urls) { 01305 if (AccessManager::supportedProtocols().contains(url.protocol())) { 01306 AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url); 01307 if (dropEvent) { 01308 dropPoints[job] = dropEvent->pos(); 01309 } else { 01310 dropPoints[job] = scenePos; 01311 } 01312 QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)), 01313 q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*))); 01314 } 01315 #ifndef PLASMA_NO_KIO 01316 else { 01317 KMimeType::Ptr mime = KMimeType::findByUrl(url); 01318 QString mimeName = mime->name(); 01319 QRectF geom(pos, QSize()); 01320 QVariantList args; 01321 args << url.url(); 01322 kDebug() << "can decode" << mimeName << args; 01323 01324 // It may be a directory or a file, let's stat 01325 KIO::JobFlags flags = KIO::HideProgressInfo; 01326 KIO::MimetypeJob *job = KIO::mimetype(url, flags); 01327 if (dropEvent) { 01328 dropPoints[job] = dropEvent->pos(); 01329 } else { 01330 dropPoints[job] = scenePos; 01331 } 01332 01333 QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*))); 01334 QObject::connect(job, SIGNAL(mimetype(KIO::Job *, const QString&)), 01335 q, SLOT(mimeTypeRetrieved(KIO::Job *, const QString&))); 01336 01337 KMenu *choices = new KMenu("Content dropped"); 01338 choices->addAction(KIcon("process-working"), i18n("Fetching file type...")); 01339 if (dropEvent) { 01340 choices->popup(dropEvent->screenPos()); 01341 } else { 01342 choices->popup(screenPos); 01343 } 01344 01345 dropMenus[job] = choices; 01346 } 01347 #endif 01348 } 01349 01350 if (dropEvent) { 01351 dropEvent->acceptProposedAction(); 01352 } 01353 } else { 01354 QStringList formats = mimeData->formats(); 01355 QHash<QString, KPluginInfo> seenPlugins; 01356 QHash<QString, QString> pluginFormats; 01357 01358 foreach (const QString &format, formats) { 01359 KPluginInfo::List plugins = Applet::listAppletInfoForMimetype(format); 01360 01361 foreach (const KPluginInfo &plugin, plugins) { 01362 if (seenPlugins.contains(plugin.pluginName())) { 01363 continue; 01364 } 01365 01366 seenPlugins.insert(plugin.pluginName(), plugin); 01367 pluginFormats.insert(plugin.pluginName(), format); 01368 } 01369 } 01370 //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); 01371 01372 QString selectedPlugin; 01373 01374 if (seenPlugins.isEmpty()) { 01375 // do nothing 01376 } else if (seenPlugins.count() == 1) { 01377 selectedPlugin = seenPlugins.constBegin().key(); 01378 } else { 01379 KMenu choices; 01380 QHash<QAction *, QString> actionsToPlugins; 01381 foreach (const KPluginInfo &info, seenPlugins) { 01382 QAction *action; 01383 if (!info.icon().isEmpty()) { 01384 action = choices.addAction(KIcon(info.icon()), info.name()); 01385 } else { 01386 action = choices.addAction(info.name()); 01387 } 01388 01389 actionsToPlugins.insert(action, info.pluginName()); 01390 } 01391 01392 QAction *choice = choices.exec(screenPos); 01393 if (choice) { 01394 selectedPlugin = actionsToPlugins[choice]; 01395 } 01396 } 01397 01398 if (!selectedPlugin.isEmpty()) { 01399 if (!dropEvent) { 01400 // since we may have entered an event loop up above with the menu, 01401 // the clipboard item may no longer be valid, as QClipboard resets 01402 // the object behind the back of the application with a zero timer 01403 // so we fetch it again here 01404 QClipboard *clipboard = QApplication::clipboard(); 01405 mimeData = clipboard->mimeData(QClipboard::Selection); 01406 } 01407 01408 KTemporaryFile tempFile; 01409 if (mimeData && tempFile.open()) { 01410 //TODO: what should we do with files after the applet is done with them?? 01411 tempFile.setAutoRemove(false); 01412 01413 { 01414 QDataStream stream(&tempFile); 01415 QByteArray data = mimeData->data(pluginFormats[selectedPlugin]); 01416 stream.writeRawData(data, data.size()); 01417 } 01418 01419 QRectF geom(pos, QSize()); 01420 QVariantList args; 01421 args << tempFile.fileName(); 01422 kDebug() << args; 01423 tempFile.close(); 01424 01425 q->addApplet(selectedPlugin, args, geom); 01426 } 01427 } 01428 } 01429 } 01430 01431 void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job) 01432 { 01433 #ifndef PLASMA_NO_KIO 01434 QObject::disconnect(job, 0, q, 0); 01435 dropPoints.remove(job); 01436 KMenu *choices = dropMenus.take(job); 01437 delete choices; 01438 job->kill(); 01439 #endif // PLASMA_NO_KIO 01440 } 01441 01442 void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job) 01443 { 01444 QPointF pos = dropPoints.take(job); 01445 if (job->error()) { 01446 //TODO: nice user visible error handling (knotification probably?) 01447 kDebug() << "remote applet access failed: " << job->errorText(); 01448 return; 01449 } 01450 01451 if (!job->applet()) { 01452 kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero"; 01453 return; 01454 } 01455 01456 q->addApplet(job->applet(), pos); 01457 } 01458 01459 void ContainmentPrivate::dropJobResult(KJob *job) 01460 { 01461 #ifndef PLASMA_NO_KIO 01462 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job); 01463 if (!tjob) { 01464 kDebug() << "job is not a KIO::TransferJob, won't handle the drop..."; 01465 clearDataForMimeJob(tjob); 01466 return; 01467 } 01468 if (job->error()) { 01469 kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString(); 01470 } 01471 // We call mimetypeRetrieved since there might be other mechanisms 01472 // for finding suitable applets. Cleanup happens there as well. 01473 mimeTypeRetrieved(qobject_cast<KIO::Job *>(job), QString()); 01474 #endif // PLASMA_NO_KIO 01475 } 01476 01477 void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype) 01478 { 01479 #ifndef PLASMA_NO_KIO 01480 kDebug() << "Mimetype Job returns." << mimetype; 01481 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job); 01482 if (!tjob) { 01483 kDebug() << "job should be a TransferJob, but isn't"; 01484 clearDataForMimeJob(job); 01485 return; 01486 } 01487 KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url()); 01488 if (mimetype.isEmpty() && !appletList.count()) { 01489 clearDataForMimeJob(job); 01490 kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")"; 01491 return; 01492 } else { 01493 01494 QPointF posi; // will be overwritten with the event's position 01495 if (dropPoints.keys().contains(tjob)) { 01496 posi = dropPoints[tjob]; 01497 kDebug() << "Received a suitable dropEvent at" << posi; 01498 } else { 01499 kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; 01500 clearDataForMimeJob(job); 01501 return; 01502 } 01503 01504 KMenu *choices = dropMenus.value(tjob); 01505 if (!choices) { 01506 kDebug() << "Bailing out. No QMenu found for this job."; 01507 clearDataForMimeJob(job); 01508 return; 01509 } 01510 01511 QVariantList args; 01512 args << tjob->url().url() << mimetype; 01513 01514 kDebug() << "Creating menu for:" << mimetype << posi << args; 01515 01516 appletList << Applet::listAppletInfoForMimetype(mimetype); 01517 KPluginInfo::List wallpaperList; 01518 if (drawWallpaper) { 01519 if (wallpaper && wallpaper->supportsMimetype(mimetype)) { 01520 wallpaperList << wallpaper->d->wallpaperDescription; 01521 } else { 01522 wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimetype); 01523 } 01524 } 01525 01526 if (!appletList.isEmpty() || !wallpaperList.isEmpty()) { 01527 choices->clear(); 01528 QHash<QAction *, QString> actionsToApplets; 01529 choices->addTitle(i18n("Widgets")); 01530 foreach (const KPluginInfo &info, appletList) { 01531 kDebug() << info.name(); 01532 QAction *action; 01533 if (!info.icon().isEmpty()) { 01534 action = choices->addAction(KIcon(info.icon()), info.name()); 01535 } else { 01536 action = choices->addAction(info.name()); 01537 } 01538 01539 actionsToApplets.insert(action, info.pluginName()); 01540 kDebug() << info.pluginName(); 01541 } 01542 actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon"); 01543 01544 QHash<QAction *, QString> actionsToWallpapers; 01545 if (!wallpaperList.isEmpty()) { 01546 choices->addTitle(i18n("Wallpaper")); 01547 01548 QMap<QString, KPluginInfo> sorted; 01549 foreach (const KPluginInfo &info, appletList) { 01550 sorted.insert(info.name(), info); 01551 } 01552 01553 foreach (const KPluginInfo &info, wallpaperList) { 01554 QAction *action; 01555 if (!info.icon().isEmpty()) { 01556 action = choices->addAction(KIcon(info.icon()), info.name()); 01557 } else { 01558 action = choices->addAction(info.name()); 01559 } 01560 01561 actionsToWallpapers.insert(action, info.pluginName()); 01562 } 01563 } 01564 01565 QAction *choice = choices->exec(); 01566 if (choice) { 01567 // Put the job on hold so it can be recycled to fetch the actual content, 01568 // which is to be expected when something's dropped onto the desktop and 01569 // an applet is to be created with this URL 01570 if (!mimetype.isEmpty() && !tjob->error()) { 01571 tjob->putOnHold(); 01572 KIO::Scheduler::publishSlaveOnHold(); 01573 } 01574 QString plugin = actionsToApplets.value(choice); 01575 if (plugin.isEmpty()) { 01576 //set wallpapery stuff 01577 plugin = actionsToWallpapers.value(choice); 01578 if (!wallpaper || plugin != wallpaper->pluginName()) { 01579 kDebug() << "Wallpaper dropped:" << tjob->url(); 01580 q->setWallpaper(plugin); 01581 } 01582 01583 if (wallpaper) { 01584 kDebug() << "Wallpaper dropped:" << tjob->url(); 01585 wallpaper->setUrls(KUrl::List() << tjob->url()); 01586 } 01587 } else { 01588 addApplet(actionsToApplets[choice], args, QRectF(posi, QSize())); 01589 } 01590 01591 clearDataForMimeJob(job); 01592 return; 01593 } 01594 } else { 01595 // we can at least create an icon as a link to the URL 01596 addApplet("icon", args, QRectF(posi, QSize())); 01597 } 01598 } 01599 01600 clearDataForMimeJob(job); 01601 #endif // PLASMA_NO_KIO 01602 } 01603 01604 #ifndef KDE_NO_DEPRECATED 01605 const QGraphicsItem *Containment::toolBoxItem() const 01606 { 01607 return d->toolBox.data(); 01608 } 01609 #endif 01610 01611 void Containment::setToolBox(AbstractToolBox *toolBox) 01612 { 01613 if (d->toolBox.data()) { 01614 d->toolBox.data()->deleteLater(); 01615 } 01616 d->toolBox = toolBox; 01617 } 01618 01619 AbstractToolBox *Containment::toolBox() const 01620 { 01621 return d->toolBox.data(); 01622 } 01623 01624 void Containment::resizeEvent(QGraphicsSceneResizeEvent *event) 01625 { 01626 Applet::resizeEvent(event); 01627 01628 if (isContainment()) { 01629 if (d->isPanelContainment()) { 01630 d->positionPanel(); 01631 } else if (corona()) { 01632 QMetaObject::invokeMethod(corona(), "layoutContainments"); 01633 } 01634 01635 if (d->wallpaper) { 01636 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); 01637 } 01638 } 01639 } 01640 01641 void Containment::keyPressEvent(QKeyEvent *event) 01642 { 01643 //kDebug() << "keyPressEvent with" << event->key() 01644 // << "and hoping and wishing for a" << Qt::Key_Tab; 01645 if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) { 01646 if (!d->applets.isEmpty()) { 01647 kDebug() << "let's give focus to...." << (QObject*)d->applets.first(); 01648 d->applets.first()->setFocus(Qt::TabFocusReason); 01649 } 01650 } 01651 } 01652 01653 void Containment::wheelEvent(QGraphicsSceneWheelEvent *event) 01654 { 01655 event->ignore(); 01656 if (d->appletAt(event->scenePos())) { 01657 return; //no unexpected click-throughs 01658 } 01659 01660 if (d->wallpaper && d->wallpaper->isInitialized()) { 01661 QGraphicsItem *item = scene()->itemAt(event->scenePos()); 01662 if (item == this) { 01663 event->ignore(); 01664 d->wallpaper->wheelEvent(event); 01665 01666 if (event->isAccepted()) { 01667 return; 01668 } 01669 } 01670 } 01671 01672 QString trigger = ContainmentActions::eventToString(event); 01673 01674 if (d->prepareContainmentActions(trigger, event->screenPos())) { 01675 d->actionPlugins()->value(trigger)->contextEvent(event); 01676 event->accept(); 01677 } else { 01678 event->ignore(); 01679 Applet::wheelEvent(event); 01680 } 01681 } 01682 01683 bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event) 01684 { 01685 return Applet::sceneEventFilter(watched, event); 01686 } 01687 01688 QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value) 01689 { 01690 //FIXME if the applet is moved to another containment we need to unfocus it 01691 01692 if (isContainment() && 01693 (change == QGraphicsItem::ItemSceneHasChanged || 01694 change == QGraphicsItem::ItemPositionHasChanged)) { 01695 switch (d->type) { 01696 case PanelContainment: 01697 case CustomPanelContainment: 01698 d->positionPanel(); 01699 break; 01700 default: 01701 if (corona()) { 01702 QMetaObject::invokeMethod(corona(), "layoutContainments"); 01703 } 01704 break; 01705 } 01706 } 01707 01708 return Applet::itemChange(change, value); 01709 } 01710 01711 void Containment::enableAction(const QString &name, bool enable) 01712 { 01713 QAction *action = this->action(name); 01714 if (action) { 01715 action->setEnabled(enable); 01716 action->setVisible(enable); 01717 } 01718 } 01719 01720 void Containment::addToolBoxAction(QAction *action) 01721 { 01722 d->createToolBox(); 01723 if (d->toolBox) { 01724 d->toolBox.data()->addTool(action); 01725 } 01726 } 01727 01728 void Containment::removeToolBoxAction(QAction *action) 01729 { 01730 if (d->toolBox) { 01731 d->toolBox.data()->removeTool(action); 01732 } 01733 } 01734 01735 void Containment::setToolBoxOpen(bool open) 01736 { 01737 if (open) { 01738 openToolBox(); 01739 } else { 01740 closeToolBox(); 01741 } 01742 } 01743 01744 bool Containment::isToolBoxOpen() const 01745 { 01746 return (d->toolBox && d->toolBox.data()->isShowing()); 01747 } 01748 01749 void Containment::openToolBox() 01750 { 01751 if (d->toolBox && !d->toolBox.data()->isShowing()) { 01752 d->toolBox.data()->setShowing(true); 01753 emit toolBoxVisibilityChanged(true); 01754 } 01755 } 01756 01757 void Containment::closeToolBox() 01758 { 01759 if (d->toolBox && d->toolBox.data()->isShowing()) { 01760 d->toolBox.data()->setShowing(false); 01761 emit toolBoxVisibilityChanged(false); 01762 } 01763 } 01764 01765 void Containment::addAssociatedWidget(QWidget *widget) 01766 { 01767 Applet::addAssociatedWidget(widget); 01768 if (d->focusedApplet) { 01769 d->focusedApplet->addAssociatedWidget(widget); 01770 } 01771 01772 foreach (const Applet *applet, d->applets) { 01773 if (applet->d->activationAction) { 01774 widget->addAction(applet->d->activationAction); 01775 } 01776 } 01777 } 01778 01779 void Containment::removeAssociatedWidget(QWidget *widget) 01780 { 01781 Applet::removeAssociatedWidget(widget); 01782 if (d->focusedApplet) { 01783 d->focusedApplet->removeAssociatedWidget(widget); 01784 } 01785 01786 foreach (const Applet *applet, d->applets) { 01787 if (applet->d->activationAction) { 01788 widget->removeAction(applet->d->activationAction); 01789 } 01790 } 01791 } 01792 01793 void Containment::setDrawWallpaper(bool drawWallpaper) 01794 { 01795 d->drawWallpaper = drawWallpaper; 01796 if (drawWallpaper) { 01797 KConfigGroup cfg = config(); 01798 const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper); 01799 const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode); 01800 setWallpaper(wallpaper, mode); 01801 } else { 01802 delete d->wallpaper; 01803 d->wallpaper = 0; 01804 } 01805 } 01806 01807 bool Containment::drawWallpaper() 01808 { 01809 return d->drawWallpaper; 01810 } 01811 01812 void Containment::setWallpaper(const QString &pluginName, const QString &mode) 01813 { 01814 KConfigGroup cfg = config(); 01815 bool newPlugin = true; 01816 bool newMode = true; 01817 01818 if (d->drawWallpaper) { 01819 if (d->wallpaper) { 01820 // we have a wallpaper, so let's decide whether we need to swap it out 01821 if (d->wallpaper->pluginName() != pluginName) { 01822 delete d->wallpaper; 01823 d->wallpaper = 0; 01824 } else { 01825 // it's the same plugin, so let's save its state now so when 01826 // we call restore later on we're safe 01827 newMode = d->wallpaper->renderingMode().name() != mode; 01828 newPlugin = false; 01829 } 01830 } 01831 01832 if (!pluginName.isEmpty() && !d->wallpaper) { 01833 d->wallpaper = Plasma::Wallpaper::load(pluginName); 01834 } 01835 01836 if (d->wallpaper) { 01837 d->wallpaper->setParent(this); 01838 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size())); 01839 d->wallpaper->setRenderingMode(mode); 01840 01841 if (newPlugin) { 01842 cfg.writeEntry("wallpaperplugin", pluginName); 01843 } 01844 01845 if (d->wallpaper->isInitialized()) { 01846 KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper"); 01847 wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName); 01848 d->wallpaper->restore(wallpaperConfig); 01849 } 01850 01851 if (newMode) { 01852 cfg.writeEntry("wallpaperpluginmode", mode); 01853 } 01854 } 01855 01856 update(); 01857 } 01858 01859 if (!d->wallpaper) { 01860 cfg.deleteEntry("wallpaperplugin"); 01861 cfg.deleteEntry("wallpaperpluginmode"); 01862 } 01863 01864 if (newPlugin || newMode) { 01865 if (newPlugin && d->wallpaper) { 01866 connect(d->wallpaper, SIGNAL(configureRequested()), this, SLOT(requestConfiguration())); 01867 connect(d->wallpaper, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving())); 01868 } 01869 01870 emit configNeedsSaving(); 01871 } 01872 } 01873 01874 Plasma::Wallpaper *Containment::wallpaper() const 01875 { 01876 return d->wallpaper; 01877 } 01878 01879 void Containment::setContainmentActions(const QString &trigger, const QString &pluginName) 01880 { 01881 KConfigGroup cfg = containmentActionsConfig(); 01882 ContainmentActions *plugin = 0; 01883 01884 if (d->actionPlugins()->contains(trigger)) { 01885 plugin = d->actionPlugins()->value(trigger); 01886 if (plugin->pluginName() != pluginName) { 01887 d->actionPlugins()->remove(trigger); 01888 delete plugin; 01889 plugin=0; 01890 } 01891 } 01892 if (pluginName.isEmpty()) { 01893 cfg.deleteEntry(trigger); 01894 } else if (plugin) { 01895 //it already existed, just reload config 01896 if (plugin->isInitialized()) { 01897 plugin->setContainment(this); //to be safe 01898 //FIXME make a truly unique config group 01899 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); 01900 plugin->restore(pluginConfig); 01901 } 01902 } else { 01903 switch (d->containmentActionsSource) { 01904 case ContainmentPrivate::Activity: 01905 //FIXME 01906 case ContainmentPrivate::Local: 01907 plugin = ContainmentActions::load(this, pluginName); 01908 break; 01909 default: 01910 plugin = ContainmentActions::load(0, pluginName); 01911 } 01912 if (plugin) { 01913 cfg.writeEntry(trigger, pluginName); 01914 d->actionPlugins()->insert(trigger, plugin); 01915 } else { 01916 //bad plugin... gets removed. is this a feature or a bug? 01917 cfg.deleteEntry(trigger); 01918 } 01919 } 01920 01921 emit configNeedsSaving(); 01922 } 01923 01924 QStringList Containment::containmentActionsTriggers() 01925 { 01926 return d->actionPlugins()->keys(); 01927 } 01928 01929 QString Containment::containmentActions(const QString &trigger) 01930 { 01931 ContainmentActions *c = d->actionPlugins()->value(trigger); 01932 return c ? c->pluginName() : QString(); 01933 } 01934 01935 void Containment::setActivity(const QString &activity) 01936 { 01937 Context *context = d->context(); 01938 if (context->currentActivity() != activity) { 01939 context->setCurrentActivity(activity); 01940 } 01941 } 01942 01943 void ContainmentPrivate::onContextChanged(Plasma::Context *con) 01944 { 01945 foreach (Applet *a, applets) { 01946 a->updateConstraints(ContextConstraint); 01947 } 01948 01949 KConfigGroup c = q->config(); 01950 QString act = con->currentActivityId(); 01951 01952 //save anything that's been set (boy I hope this avoids overwriting things) 01953 //FIXME of course if the user sets the name to an empty string we have a bug 01954 //but once we get context retrieving the name as soon as the id is set, this issue should go away 01955 if (!act.isEmpty()) { 01956 c.writeEntry("activityId", act); 01957 } 01958 act = con->currentActivity(); 01959 if (!act.isEmpty()) { 01960 c.writeEntry("activity", act); 01961 } 01962 01963 if (toolBox) { 01964 toolBox.data()->update(); 01965 } 01966 emit q->configNeedsSaving(); 01967 emit q->contextChanged(con); 01968 } 01969 01970 QString Containment::activity() const 01971 { 01972 return d->context()->currentActivity(); 01973 } 01974 01975 Context *Containment::context() const 01976 { 01977 return d->context(); 01978 } 01979 01980 Context *ContainmentPrivate::context() 01981 { 01982 if (!con) { 01983 con = new Context(q); 01984 q->connect(con, SIGNAL(changed(Plasma::Context*)), 01985 q, SLOT(onContextChanged(Plasma::Context*))); 01986 } 01987 01988 return con; 01989 } 01990 01991 KActionCollection* ContainmentPrivate::actions() 01992 { 01993 return static_cast<Applet*>(q)->d->actions; 01994 } 01995 01996 void ContainmentPrivate::focusApplet(Plasma::Applet *applet) 01997 { 01998 if (focusedApplet == applet) { 01999 return; 02000 } 02001 02002 QList<QWidget *> widgets = actions()->associatedWidgets(); 02003 if (focusedApplet) { 02004 foreach (QWidget *w, widgets) { 02005 focusedApplet->removeAssociatedWidget(w); 02006 } 02007 } 02008 02009 if (applet && applets.contains(applet)) { 02010 //kDebug() << "switching to" << applet->name(); 02011 focusedApplet = applet; 02012 foreach (QWidget *w, widgets) { 02013 focusedApplet->addAssociatedWidget(w); 02014 } 02015 02016 if (!focusedApplet->hasFocus()) { 02017 focusedApplet->setFocus(Qt::ShortcutFocusReason); 02018 } 02019 } else { 02020 focusedApplet = 0; 02021 } 02022 } 02023 02024 void Containment::focusNextApplet() 02025 { 02026 if (d->applets.isEmpty()) { 02027 return; 02028 } 02029 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0; 02030 if (index >= d->applets.size()) { 02031 index = 0; 02032 } 02033 kDebug() << "index" << index; 02034 d->focusApplet(d->applets.at(index)); 02035 } 02036 02037 void Containment::focusPreviousApplet() 02038 { 02039 if (d->applets.isEmpty()) { 02040 return; 02041 } 02042 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1; 02043 if (index < 0) { 02044 index = d->applets.size() - 1; 02045 } 02046 kDebug() << "index" << index; 02047 d->focusApplet(d->applets.at(index)); 02048 } 02049 02050 void Containment::destroy() 02051 { 02052 destroy(true); 02053 } 02054 02055 void Containment::showConfigurationInterface() 02056 { 02057 Applet::showConfigurationInterface(); 02058 } 02059 02060 void Containment::configChanged() 02061 { 02062 } 02063 02064 void ContainmentPrivate::configChanged() 02065 { 02066 if (drawWallpaper) { 02067 KConfigGroup group = q->config(); 02068 q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), 02069 group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); 02070 } 02071 } 02072 02073 void ContainmentPrivate::requestConfiguration() 02074 { 02075 emit q->configureRequested(q); 02076 } 02077 02078 void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus) 02079 { 02080 //kDebug() << "================== "<< appletStatus << q->status(); 02081 if (appletStatus == q->status()) { 02082 emit q->newStatus(appletStatus); 02083 return; 02084 } 02085 02086 if (appletStatus < q->status()) { 02087 // check to see if any other applet has a higher status, and stick with that 02088 // if we do 02089 foreach (Applet *applet, applets) { 02090 if (applet->status() > appletStatus) { 02091 appletStatus = applet->status(); 02092 } 02093 } 02094 } 02095 02096 q->setStatus(appletStatus); 02097 } 02098 02099 void Containment::destroy(bool confirm) 02100 { 02101 if (immutability() != Mutable || Applet::d->transient) { 02102 return; 02103 } 02104 02105 if (isContainment() && confirm) { 02106 //FIXME: should not be blocking 02107 const QString title = i18nc("@title:window %1 is the name of the containment", "Remove %1", name()); 02108 KGuiItem remove = KStandardGuiItem::remove(); 02109 remove.setText(title); 02110 if (KMessageBox::warningContinueCancel(view(), 02111 i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()), 02112 title, remove) != KMessageBox::Continue) { 02113 return; 02114 } 02115 } 02116 02117 Applet::destroy(); 02118 } 02119 02120 void ContainmentPrivate::createToolBox() 02121 { 02122 if (!toolBox && KAuthorized::authorizeKAction("plasma/containment_context_menu")) { 02123 toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q); 02124 02125 if (toolBox) { 02126 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); 02127 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility())); 02128 02129 positionToolBox(); 02130 } 02131 } 02132 } 02133 02134 void ContainmentPrivate::positionToolBox() 02135 { 02136 QMetaObject::invokeMethod(toolBox.data(), "reposition"); 02137 } 02138 02139 void ContainmentPrivate::updateToolBoxVisibility() 02140 { 02141 emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing()); 02142 } 02143 02144 void ContainmentPrivate::triggerShowAddWidgets() 02145 { 02146 emit q->showAddWidgetsInterface(QPointF()); 02147 } 02148 02149 void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) 02150 { 02151 if (!q->isContainment()) { 02152 return; 02153 } 02154 02155 //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; 02156 if (constraints & Plasma::ImmutableConstraint) { 02157 //update actions 02158 checkRemoveAction(); 02159 const bool unlocked = q->immutability() == Mutable; 02160 q->setAcceptDrops(unlocked); 02161 q->enableAction("add widgets", unlocked); 02162 02163 // tell the applets too 02164 foreach (Applet *a, applets) { 02165 a->setImmutability(q->immutability()); 02166 a->updateConstraints(ImmutableConstraint); 02167 } 02168 } 02169 02170 // pass on the constraints that are relevant here 02171 Constraints appletConstraints = NoConstraint; 02172 if (constraints & FormFactorConstraint) { 02173 appletConstraints |= FormFactorConstraint; 02174 } 02175 02176 if (constraints & ScreenConstraint) { 02177 appletConstraints |= ScreenConstraint; 02178 } 02179 02180 if (appletConstraints != NoConstraint) { 02181 foreach (Applet *applet, applets) { 02182 applet->updateConstraints(appletConstraints); 02183 } 02184 } 02185 02186 if (toolBox && (constraints & Plasma::SizeConstraint || 02187 constraints & Plasma::FormFactorConstraint || 02188 constraints & Plasma::ScreenConstraint || 02189 constraints & Plasma::StartupCompletedConstraint)) { 02190 //kDebug() << "Positioning toolbox"; 02191 positionToolBox(); 02192 } 02193 02194 if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) { 02195 q->addToolBoxAction(q->action("remove")); 02196 checkRemoveAction(); 02197 } 02198 } 02199 02200 Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, 02201 const QRectF &appletGeometry, uint id, bool delayInit) 02202 { 02203 if (!q->isContainment()) { 02204 return 0; 02205 } 02206 02207 if (!delayInit && q->immutability() != Mutable) { 02208 kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; 02209 return 0; 02210 } 02211 02212 QGraphicsView *v = q->view(); 02213 if (v) { 02214 v->setCursor(Qt::BusyCursor); 02215 } 02216 02217 Applet *applet = Applet::load(name, id, args); 02218 if (v) { 02219 v->unsetCursor(); 02220 } 02221 02222 if (!applet) { 02223 kDebug() << "Applet" << name << "could not be loaded."; 02224 applet = new Applet(0, QString(), id); 02225 applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); 02226 } 02227 02228 //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); 02229 02230 q->addApplet(applet, appletGeometry.topLeft(), delayInit); 02231 return applet; 02232 } 02233 02234 bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const 02235 { 02236 foreach (Applet *applet, applets) { 02237 if (applet != ignoredApplet && applet->geometry().intersects(region)) { 02238 return false; 02239 } 02240 } 02241 return true; 02242 } 02243 02244 void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet) 02245 { 02246 applets.removeAll(applet); 02247 if (focusedApplet == applet) { 02248 focusedApplet = 0; 02249 } 02250 02251 emit q->appletRemoved(applet); 02252 emit q->configNeedsSaving(); 02253 } 02254 02255 void ContainmentPrivate::appletAppearAnimationComplete() 02256 { 02257 Animation *anim = qobject_cast<Animation *>(q->sender()); 02258 if (anim) { 02259 Applet *applet = qobject_cast<Applet*>(anim->targetWidget()); 02260 if (applet) { 02261 appletAppeared(applet); 02262 } 02263 } 02264 } 02265 02266 void ContainmentPrivate::appletAppeared(Applet *applet) 02267 { 02268 //kDebug() << type << Containment::DesktopContainment; 02269 KConfigGroup *cg = applet->d->mainConfigGroup(); 02270 applet->save(*cg); 02271 emit q->configNeedsSaving(); 02272 } 02273 02274 void ContainmentPrivate::positionPanel(bool force) 02275 { 02276 if (!q->scene()) { 02277 kDebug() << "no scene yet"; 02278 return; 02279 } 02280 02281 // already positioning the panel - avoid infinite loops 02282 if (ContainmentPrivate::s_positioningPanels) { 02283 return; 02284 } 02285 02286 // we position panels in negative coordinates, and stack all horizontal 02287 // and all vertical panels with each other. 02288 02289 02290 const QPointF p = q->pos(); 02291 02292 if (!force && 02293 p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && 02294 q->scene()->collidingItems(q).isEmpty()) { 02295 // already positioned and not running into any other panels 02296 return; 02297 } 02298 02299 02300 QPointF newPos = preferredPanelPos(q->corona()); 02301 if (p != newPos) { 02302 ContainmentPrivate::s_positioningPanels = true; 02303 q->setPos(newPos); 02304 ContainmentPrivate::s_positioningPanels = false; 02305 } 02306 } 02307 02308 bool ContainmentPrivate::isPanelContainment() const 02309 { 02310 return type == Containment::PanelContainment || type == Containment::CustomPanelContainment; 02311 } 02312 02313 QPointF ContainmentPrivate::preferredPos(Corona *corona) const 02314 { 02315 Q_ASSERT(corona); 02316 02317 if (isPanelContainment()) { 02318 //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona); 02319 return preferredPanelPos(corona); 02320 } 02321 02322 QPointF pos(0, 0); 02323 QTransform t; 02324 while (QGraphicsItem *i = corona->itemAt(pos, t)) { 02325 pos.setX(i->scenePos().x() + i->boundingRect().width() + 10); 02326 } 02327 02328 //kDebug() << "not a panel, put it at" << pos; 02329 return pos; 02330 } 02331 02332 QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const 02333 { 02334 Q_ASSERT(corona); 02335 02336 //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here 02337 bool horiz = formFactor == Plasma::Horizontal; 02338 qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; 02339 qreal lastHeight = 0; 02340 02341 // this should be ok for small numbers of panels, but if we ever end 02342 // up managing hundreds of them, this simplistic alogrithm will 02343 // likely be too slow. 02344 foreach (const Containment *other, corona->containments()) { 02345 if (other == q || 02346 !other->d->isPanelContainment() || 02347 horiz != (other->formFactor() == Plasma::Horizontal)) { 02348 // only line up with panels of the same orientation 02349 continue; 02350 } 02351 02352 if (horiz) { 02353 qreal y = other->pos().y(); 02354 if (y < bottom) { 02355 lastHeight = other->size().height(); 02356 bottom = y; 02357 } 02358 } else { 02359 qreal width = other->size().width(); 02360 qreal x = other->pos().x() + width; 02361 if (x > bottom) { 02362 lastHeight = width; 02363 bottom = x + lastHeight; 02364 } 02365 } 02366 } 02367 02368 // give a space equal to the height again of the last item so there is 02369 // room to grow. 02370 QPointF newPos; 02371 if (horiz) { 02372 bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; 02373 //TODO: fix x position for non-flush-left panels 02374 kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); 02375 newPos = QPointF(0, bottom - q->size().height()); 02376 } else { 02377 bottom += lastHeight + INTER_CONTAINMENT_MARGIN; 02378 //TODO: fix y position for non-flush-top panels 02379 kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); 02380 newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); 02381 } 02382 02383 return newPos; 02384 } 02385 02386 02387 bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu) 02388 { 02389 ContainmentActions *plugin = actionPlugins()->value(trigger); 02390 if (!plugin) { 02391 return false; 02392 } 02393 plugin->setContainment(q); 02394 02395 if (!plugin->isInitialized()) { 02396 KConfigGroup cfg = q->containmentActionsConfig(); 02397 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); 02398 plugin->restore(pluginConfig); 02399 } 02400 02401 if (plugin->configurationRequired()) { 02402 KMenu *localMenu = menu ? menu : new KMenu(); 02403 02404 localMenu->addTitle(i18n("This plugin needs to be configured")); 02405 localMenu->addAction(q->action("configure")); 02406 02407 if (!menu) { 02408 localMenu->exec(screenPos); 02409 delete localMenu; 02410 } 02411 02412 return false; 02413 } else if (menu) { 02414 QList<QAction*> actions = plugin->contextualActions(); 02415 if (actions.isEmpty()) { 02416 //it probably didn't bother implementing the function. give the user a chance to set 02417 //a better plugin. note that if the user sets no-plugin this won't happen... 02418 if (!isPanelContainment() && q->action("configure")) { 02419 menu->addAction(q->action("configure")); 02420 } 02421 } else { 02422 menu->addActions(actions); 02423 } 02424 } 02425 02426 return true; 02427 } 02428 02429 KConfigGroup Containment::containmentActionsConfig() 02430 { 02431 KConfigGroup cfg; 02432 switch (d->containmentActionsSource) { 02433 case ContainmentPrivate::Local: 02434 cfg = config(); 02435 cfg = KConfigGroup(&cfg, "ActionPlugins"); 02436 break; 02437 case ContainmentPrivate::Activity: 02438 cfg = KConfigGroup(corona()->config(), "Activities"); 02439 cfg = KConfigGroup(&cfg, d->context()->currentActivityId()); 02440 cfg = KConfigGroup(&cfg, "ActionPlugins"); 02441 break; 02442 default: 02443 cfg = KConfigGroup(corona()->config(), "ActionPlugins"); 02444 } 02445 return cfg; 02446 } 02447 02448 QHash<QString, ContainmentActions*> * ContainmentPrivate::actionPlugins() 02449 { 02450 switch (containmentActionsSource) { 02451 case Activity: 02452 //FIXME 02453 case Local: 02454 return &localActionPlugins; 02455 default: 02456 return &globalActionPlugins; 02457 } 02458 } 02459 02460 } // Plasma namespace 02461 02462 #include "containment.moc" 02463
KDE 4.7 API Reference