1/*
2 * Copyright 2007 by Aaron Seigo <aseigo@kde.org>
3 * Copyright 2008 by Ménard Alexis <darktears31@gmail.com>
4 * Copyright 2009 Chani Armitage <chani@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Library General Public License as
8 * published by the Free Software Foundation; either version 2, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22#include "containment.h"
23#include "private/containment_p.h"
24
25#include "config-plasma.h"
26
27#include <QApplication>
28#include <QClipboard>
29#include <QFile>
30#include <QGraphicsSceneContextMenuEvent>
31#include <QGraphicsView>
32#include <QMimeData>
33#include <QPainter>
34#include <QStyleOptionGraphicsItem>
35#include <QGraphicsLayout>
36#include <QGraphicsLinearLayout>
37
38#include <kaction.h>
39#include <kauthorized.h>
40#include <kicon.h>
41#include <kmenu.h>
42#include <kmessagebox.h>
43#include <kmimetype.h>
44#include <kservicetypetrader.h>
45#include <kstandarddirs.h>
46#include <ktemporaryfile.h>
47#include <kwindowsystem.h>
48
49#ifndef PLASMA_NO_KIO
50#include "kio/jobclasses.h" // for KIO::JobFlags
51#include "kio/job.h"
52#include "kio/scheduler.h"
53#endif
54
55#include "abstracttoolbox.h"
56#include "animator.h"
57#include "context.h"
58#include "containmentactions.h"
59#include "containmentactionspluginsconfig.h"
60#include "corona.h"
61#include "extender.h"
62#include "extenderitem.h"
63#include "svg.h"
64#include "wallpaper.h"
65
66#include "remote/accessappletjob.h"
67#include "remote/accessmanager.h"
68
69#include "private/applet_p.h"
70#include "private/containmentactionspluginsconfig_p.h"
71#include "private/extenderitemmimedata_p.h"
72#include "private/extenderapplet_p.h"
73#include "private/wallpaper_p.h"
74
75#include "plasma/plasma.h"
76#include "animations/animation.h"
77
78namespace Plasma
79{
80
81bool ContainmentPrivate::s_positioningPanels = false;
82QHash<QString, ContainmentActions*> ContainmentPrivate::globalActionPlugins;
83static const char defaultWallpaper[] = "image";
84static const char defaultWallpaperMode[] = "SingleImage";
85
86Containment::StyleOption::StyleOption()
87 : QStyleOptionGraphicsItem(),
88 view(0)
89{
90 version = Version;
91 type = Type;
92}
93
94Containment::StyleOption::StyleOption(const Containment::StyleOption & other)
95 : QStyleOptionGraphicsItem(other),
96 view(other.view)
97{
98 version = Version;
99 type = Type;
100}
101
102Containment::StyleOption::StyleOption(const QStyleOptionGraphicsItem &other)
103 : QStyleOptionGraphicsItem(other),
104 view(0)
105{
106 version = Version;
107 type = Type;
108}
109
110Containment::Containment(QGraphicsItem *parent,
111 const QString &serviceId,
112 uint containmentId)
113 : Applet(parent, serviceId, containmentId),
114 d(new ContainmentPrivate(this))
115{
116 // WARNING: do not access config() OR globalConfig() in this method!
117 // that requires a scene, which is not available at this point
118 setPos(0, 0);
119 setBackgroundHints(NoBackground);
120 setContainmentType(CustomContainment);
121}
122
123Containment::Containment(QObject *parent, const QVariantList &args)
124 : Applet(parent, args),
125 d(new ContainmentPrivate(this))
126{
127 // WARNING: do not access config() OR globalConfig() in this method!
128 // that requires a scene, which is not available at this point
129 setPos(0, 0);
130 setBackgroundHints(NoBackground);
131}
132
133Containment::Containment(const QString &packagePath, uint appletId, const QVariantList &args)
134 : Plasma::Applet(packagePath, appletId, args),
135 d(new ContainmentPrivate(this))
136{
137 // WARNING: do not access config() OR globalConfig() in this method!
138 // that requires a scene, which is not available at this point
139 setPos(0, 0);
140 setBackgroundHints(NoBackground);
141}
142
143Containment::~Containment()
144{
145 // Applet touches our dptr if we are a containment and is the superclass (think of dtors)
146 // so we reset this as we exit the building
147 Applet::d->isContainment = false;
148 delete d;
149}
150
151void Containment::init()
152{
153 Applet::init();
154 if (!isContainment()) {
155 return;
156 }
157
158 setCacheMode(NoCache);
159 setFlag(QGraphicsItem::ItemIsMovable, false);
160 setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
161 setAcceptDrops(true);
162 setAcceptsHoverEvents(true);
163
164 if (d->type == NoContainmentType) {
165 setContainmentType(DesktopContainment);
166 }
167
168 //connect actions
169 ContainmentPrivate::addDefaultActions(d->actions(), this);
170 bool unlocked = immutability() == Mutable;
171
172 //fix the text of the actions that need name()
173 //btw, do we really want to use name() when it's a desktopcontainment?
174 QAction *closeApplet = action("remove");
175 if (closeApplet) {
176 closeApplet->setText(i18nc("%1 is the name of the applet", "Remove this %1", name()));
177 }
178
179 QAction *configAction = action("configure");
180 if (configAction) {
181 configAction->setText(i18nc("%1 is the name of the applet", "%1 Settings", name()));
182 }
183
184 QAction *appletBrowserAction = action("add widgets");
185 if (appletBrowserAction) {
186 appletBrowserAction->setVisible(unlocked);
187 appletBrowserAction->setEnabled(unlocked);
188 connect(appletBrowserAction, SIGNAL(triggered()), this, SLOT(triggerShowAddWidgets()));
189 }
190
191 QAction *act = action("next applet");
192 if (act) {
193 connect(act, SIGNAL(triggered()), this, SLOT(focusNextApplet()));
194 }
195
196 act = action("previous applet");
197 if (act) {
198 connect(act, SIGNAL(triggered()), this, SLOT(focusPreviousApplet()));
199 }
200
201 if (immutability() != SystemImmutable && corona()) {
202 QAction *lockDesktopAction = corona()->action("lock widgets");
203 //keep a pointer so nobody notices it moved to corona
204 if (lockDesktopAction) {
205 d->actions()->addAction("lock widgets", lockDesktopAction);
206 }
207 }
208 if (d->type != PanelContainment && d->type != CustomPanelContainment) {
209 if (corona()) {
210 //FIXME this is just here because of the darn keyboard shortcut :/
211 act = corona()->action("manage activities");
212 if (act) {
213 d->actions()->addAction("manage activities", act);
214 }
215 //a stupid hack to make this one's keyboard shortcut work
216 act = corona()->action("configure shortcuts");
217 if (act) {
218 d->actions()->addAction("configure shortcuts", act);
219 }
220 }
221
222 if (d->type == DesktopContainment) {
223 addToolBoxAction(action("add widgets"));
224
225 //TODO: do we need some way to allow this be overridden?
226 // it's always available because shells rely on this
227 // to offer their own custom configuration as well
228 QAction *configureContainment = action("configure");
229 if (configureContainment) {
230 addToolBoxAction(configureContainment);
231 }
232 }
233 }
234}
235
236void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c)
237{
238 actions->setConfigGroup("Shortcuts-Containment");
239
240 //adjust applet actions
241 KAction *appAction = qobject_cast<KAction*>(actions->action("remove"));
242 appAction->setShortcut(KShortcut("alt+d, alt+r"));
243 if (c && c->d->isPanelContainment()) {
244 appAction->setText(i18n("Remove this Panel"));
245 } else {
246 appAction->setText(i18n("Remove this Activity"));
247 }
248
249 appAction = qobject_cast<KAction*>(actions->action("configure"));
250 if (appAction) {
251 appAction->setShortcut(KShortcut("alt+d, alt+s"));
252 appAction->setText(i18n("Activity Settings"));
253 }
254
255 //add our own actions
256 KAction *appletBrowserAction = actions->addAction("add widgets");
257 appletBrowserAction->setAutoRepeat(false);
258 appletBrowserAction->setText(i18n("Add Widgets..."));
259 appletBrowserAction->setIcon(KIcon("list-add"));
260 appletBrowserAction->setShortcut(KShortcut("alt+d, a"));
261 appletBrowserAction->setData(AbstractToolBox::AddTool);
262
263 KAction *action = actions->addAction("next applet");
264 action->setText(i18n("Next Widget"));
265 //no icon
266 action->setShortcut(KShortcut("alt+d, n"));
267 action->setData(AbstractToolBox::ControlTool);
268
269 action = actions->addAction("previous applet");
270 action->setText(i18n("Previous Widget"));
271 //no icon
272 action->setShortcut(KShortcut("alt+d, p"));
273 action->setData(AbstractToolBox::ControlTool);
274}
275
276// helper function for sorting the list of applets
277bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
278{
279 QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft();
280 QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft();
281
282 if (!qFuzzyCompare(p1.x(), p2.x())) {
283 if (QApplication::layoutDirection() == Qt::RightToLeft) {
284 return p1.x() > p2.x();
285 }
286
287 return p1.x() < p2.x();
288 }
289
290 return qFuzzyCompare(p1.y(), p2.y()) || p1.y() < p2.y();
291}
292
293void Containment::restore(KConfigGroup &group)
294{
295 /*kDebug() << "!!!!!!!!!!!!initConstraints" << group.name() << d->type;
296 kDebug() << " location:" << group.readEntry("location", (int)d->location);
297 kDebug() << " geom:" << group.readEntry("geometry", geometry());
298 kDebug() << " formfactor:" << group.readEntry("formfactor", (int)d->formFactor);
299 kDebug() << " screen:" << group.readEntry("screen", d->screen);*/
300 if (!isContainment()) {
301 Applet::restore(group);
302 return;
303 }
304
305 QRectF geo = group.readEntry("geometry", geometry());
306 //override max/min
307 //this ensures panels are set to their saved size even when they have max & min set to prevent
308 //resizing
309 if (geo.size() != geo.size().boundedTo(maximumSize())) {
310 setMaximumSize(maximumSize().expandedTo(geo.size()));
311 }
312
313 if (geo.size() != geo.size().expandedTo(minimumSize())) {
314 setMinimumSize(minimumSize().boundedTo(geo.size()));
315 }
316
317
318 resize(geo.size());
319 //are we an offscreen containment?
320 if (containmentType() != PanelContainment && containmentType() != CustomPanelContainment && geo.right() < 0) {
321 corona()->addOffscreenWidget(this);
322 }
323
324 setLocation((Plasma::Location)group.readEntry("location", (int)d->location));
325 setFormFactor((Plasma::FormFactor)group.readEntry("formfactor", (int)d->formFactor));
326 //kDebug() << "setScreen from restore";
327 d->lastScreen = group.readEntry("lastScreen", d->lastScreen);
328 d->lastDesktop = group.readEntry("lastDesktop", d->lastDesktop);
329 d->setScreen(group.readEntry("screen", d->screen), group.readEntry("desktop", d->desktop), false);
330 QString activityId = group.readEntry("activityId", QString());
331 if (!activityId.isEmpty()) {
332 d->context()->setCurrentActivityId(activityId);
333 }
334 setActivity(group.readEntry("activity", QString()));
335
336 flushPendingConstraintsEvents();
337 restoreContents(group);
338 setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable));
339
340 setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper),
341 group.readEntry("wallpaperpluginmode", defaultWallpaperMode));
342
343 QMetaObject::invokeMethod(d->toolBox.data(), "restore", Q_ARG(KConfigGroup, group));
344
345 KConfigGroup cfg;
346 if (containmentType() == PanelContainment || containmentType() == CustomPanelContainment) {
347 //don't let global desktop actions conflict with panels
348 //this also prevents panels from sharing config with each other
349 //but the panels aren't configurable anyways, and I doubt that'll change.
350 d->containmentActionsSource = ContainmentPrivate::Local;
351 cfg = KConfigGroup(&group, "ActionPlugins");
352 } else {
353 QString source = group.readEntry("ActionPluginsSource", QString());
354 if (source == "Global") {
355 cfg = KConfigGroup(corona()->config(), "ActionPlugins");
356 d->containmentActionsSource = ContainmentPrivate::Global;
357 } else if (source == "Activity") {
358 cfg = KConfigGroup(corona()->config(), "Activities");
359 cfg = KConfigGroup(&cfg, activityId);
360 cfg = KConfigGroup(&cfg, "ActionPlugins");
361 d->containmentActionsSource = ContainmentPrivate::Activity;
362 } else if (source == "Local") {
363 cfg = group;
364 d->containmentActionsSource = ContainmentPrivate::Local;
365 } else {
366 //default to global
367 //but, if there is no global config, try copying it from local.
368 cfg = KConfigGroup(corona()->config(), "ActionPlugins");
369 if (!cfg.exists()) {
370 cfg = KConfigGroup(&group, "ActionPlugins");
371 }
372 d->containmentActionsSource = ContainmentPrivate::Global;
373 group.writeEntry("ActionPluginsSource", "Global");
374 }
375 }
376 //kDebug() << cfg.keyList();
377 if (cfg.exists()) {
378 foreach (const QString &key, cfg.keyList()) {
379 //kDebug() << "loading" << key;
380 setContainmentActions(key, cfg.readEntry(key, QString()));
381 }
382 } else { //shell defaults
383 ContainmentActionsPluginsConfig conf = corona()->containmentActionsDefaults(d->type);
384 //steal the data directly, for efficiency
385 QHash<QString,QString> defaults = conf.d->plugins;
386 for (QHash<QString,QString>::const_iterator it = defaults.constBegin(),
387 end = defaults.constEnd(); it != end; ++it) {
388 setContainmentActions(it.key(), it.value());
389 }
390 }
391
392 /*
393 kDebug() << "Containment" << id() <<
394 "screen" << screen() <<
395 "geometry is" << geometry() <<
396 "wallpaper" << ((d->wallpaper) ? d->wallpaper->pluginName() : QString()) <<
397 "wallpaper mode" << wallpaperMode() <<
398 "config entries" << group.entryMap();
399 */
400}
401
402void Containment::save(KConfigGroup &g) const
403{
404 if (Applet::d->transient) {
405 return;
406 }
407
408 KConfigGroup group = g;
409 if (!group.isValid()) {
410 group = config();
411 }
412
413 // locking is saved in Applet::save
414 Applet::save(group);
415
416 if (!isContainment()) {
417 return;
418 }
419
420 group.writeEntry("screen", d->screen);
421 group.writeEntry("lastScreen", d->lastScreen);
422 group.writeEntry("desktop", d->desktop);
423 group.writeEntry("lastDesktop", d->lastDesktop);
424 group.writeEntry("formfactor", (int)d->formFactor);
425 group.writeEntry("location", (int)d->location);
426 group.writeEntry("activity", d->context()->currentActivity());
427 group.writeEntry("activityId", d->context()->currentActivityId());
428
429
430 QMetaObject::invokeMethod(d->toolBox.data(), "save", Q_ARG(KConfigGroup, group));
431
432
433 if (d->wallpaper) {
434 group.writeEntry("wallpaperplugin", d->wallpaper->pluginName());
435 group.writeEntry("wallpaperpluginmode", d->wallpaper->renderingMode().name());
436
437 if (d->wallpaper->isInitialized()) {
438 KConfigGroup wallpaperConfig(&group, "Wallpaper");
439 wallpaperConfig = KConfigGroup(&wallpaperConfig, d->wallpaper->pluginName());
440 d->wallpaper->save(wallpaperConfig);
441 }
442 }
443
444 saveContents(group);
445}
446
447void Containment::saveContents(KConfigGroup &group) const
448{
449 KConfigGroup applets(&group, "Applets");
450 foreach (const Applet *applet, d->applets) {
451 KConfigGroup appletConfig(&applets, QString::number(applet->id()));
452 applet->save(appletConfig);
453 }
454}
455
456void ContainmentPrivate::initApplets()
457{
458 foreach (Applet *applet, applets) {
459 applet->restore(*applet->d->mainConfigGroup());
460 applet->init();
461 kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name();
462 }
463
464 q->flushPendingConstraintsEvents();
465
466 foreach (Applet *applet, applets) {
467 applet->flushPendingConstraintsEvents();
468 }
469
470 kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name();
471}
472
473void Containment::restoreContents(KConfigGroup &group)
474{
475 KConfigGroup applets(&group, "Applets");
476
477 // Sort the applet configs in order of geometry to ensure that applets
478 // are added from left to right or top to bottom for a panel containment
479 QList<KConfigGroup> appletConfigs;
480 foreach (const QString &appletGroup, applets.groupList()) {
481 //kDebug() << "reading from applet group" << appletGroup;
482 KConfigGroup appletConfig(&applets, appletGroup);
483 appletConfigs.append(appletConfig);
484 }
485 qStableSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
486
487 QMutableListIterator<KConfigGroup> it(appletConfigs);
488 while (it.hasNext()) {
489 KConfigGroup &appletConfig = it.next();
490 int appId = appletConfig.name().toUInt();
491 QString plugin = appletConfig.readEntry("plugin", QString());
492
493 if (plugin.isEmpty()) {
494 continue;
495 }
496
497 d->addApplet(plugin, QVariantList(), appletConfig.readEntry("geometry", QRectF()), appId, true);
498 }
499}
500
501Containment::Type Containment::containmentType() const
502{
503 return d->type;
504}
505
506void Containment::setContainmentType(Containment::Type type)
507{
508 if (d->type == type) {
509 return;
510 }
511
512 delete d->toolBox.data();
513 d->type = type;
514 d->checkContainmentFurniture();
515}
516
517void ContainmentPrivate::checkContainmentFurniture()
518{
519 if (q->isContainment() &&
520 (type == Containment::DesktopContainment || type == Containment::PanelContainment)) {
521 createToolBox();
522 }
523}
524
525Corona *Containment::corona() const
526{
527 return qobject_cast<Corona*>(scene());
528}
529
530void Containment::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
531{
532 event->ignore();
533 if (d->wallpaper && d->wallpaper->isInitialized()) {
534 QGraphicsItem *item = scene()->itemAt(event->scenePos());
535 if (item == this) {
536 d->wallpaper->mouseMoveEvent(event);
537 }
538 }
539
540 if (!event->isAccepted()) {
541 event->accept();
542 Applet::mouseMoveEvent(event);
543 }
544}
545
546void Containment::mousePressEvent(QGraphicsSceneMouseEvent *event)
547{
548 //close a toolbox if exists, to emulate qmenu behavior
549 if (d->toolBox) {
550 d->toolBox.data()->setShowing(false);
551 }
552 event->ignore();
553 if (d->appletAt(event->scenePos())) {
554 return; //no unexpected click-throughs
555 }
556
557 if (d->wallpaper && d->wallpaper->isInitialized() && !event->isAccepted()) {
558 d->wallpaper->mousePressEvent(event);
559 }
560
561 if (event->isAccepted()) {
562 setFocus(Qt::MouseFocusReason);
563 } else if (event->button() == Qt::RightButton && event->modifiers() == Qt::NoModifier) {
564 // we'll catch this in the context menu even
565 Applet::mousePressEvent(event);
566 } else {
567 QString trigger = ContainmentActions::eventToString(event);
568 if (d->prepareContainmentActions(trigger, event->screenPos())) {
569 d->actionPlugins()->value(trigger)->contextEvent(event);
570 }
571
572 if (!event->isAccepted()) {
573 Applet::mousePressEvent(event);
574 }
575 }
576}
577
578void Containment::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
579{
580 event->ignore();
581
582 if (d->appletAt(event->scenePos())) {
583 return; //no unexpected click-throughs
584 }
585
586 QString trigger = ContainmentActions::eventToString(event);
587
588 if (d->wallpaper && d->wallpaper->isInitialized()) {
589 d->wallpaper->mouseReleaseEvent(event);
590 }
591
592 if (!event->isAccepted() && isContainment()) {
593 if (d->prepareContainmentActions(trigger, event->screenPos())) {
594 d->actionPlugins()->value(trigger)->contextEvent(event);
595 }
596
597 event->accept();
598 Applet::mouseReleaseEvent(event);
599 }
600}
601
602void Containment::showDropZone(const QPoint pos)
603{
604 Q_UNUSED(pos)
605 //Base implementation does nothing, don't put code here
606}
607
608void Containment::showContextMenu(const QPointF &containmentPos, const QPoint &screenPos)
609{
610 //kDebug() << containmentPos << screenPos;
611 QGraphicsSceneContextMenuEvent gvevent;
612 gvevent.setScreenPos(screenPos);
613 gvevent.setScenePos(mapToScene(containmentPos));
614 gvevent.setPos(containmentPos);
615 gvevent.setReason(QGraphicsSceneContextMenuEvent::Mouse);
616 gvevent.setWidget(view());
617 contextMenuEvent(&gvevent);
618}
619
620void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
621{
622 if (!isContainment() || !KAuthorized::authorizeKAction("plasma/containment_context_menu")) {
623 Applet::contextMenuEvent(event);
624 return;
625 }
626
627 KMenu desktopMenu;
628 Applet *applet = d->appletAt(event->scenePos());
629 //kDebug() << "context menu event " << (QObject*)applet;
630
631 if (applet) {
632 d->addAppletActions(desktopMenu, applet, event);
633 } else {
634 d->addContainmentActions(desktopMenu, event);
635 }
636
637 //kDebug() << "executing at" << screenPos;
638 QMenu *menu = &desktopMenu;
639 //kDebug() << "showing menu, actions" << desktopMenu.actions().size() << desktopMenu.actions().first()->menu();
640 if (desktopMenu.actions().size() == 1 && desktopMenu.actions().first()->menu()) {
641 // we have a menu with a single top level menu; just show that top level menu instad.
642 menu = desktopMenu.actions().first()->menu();
643 }
644
645 if (!menu->isEmpty()) {
646 QPoint pos = event->screenPos();
647 if (applet && d->isPanelContainment()) {
648 menu->adjustSize();
649 pos = applet->popupPosition(menu->size());
650 if (event->reason() == QGraphicsSceneContextMenuEvent::Mouse) {
651 // if the menu pops up way away from the mouse press, then move it
652 // to the mouse press
653 if (d->formFactor == Vertical) {
654 if (pos.y() + menu->height() < event->screenPos().y()) {
655 pos.setY(event->screenPos().y());
656 }
657 } else if (d->formFactor == Horizontal) {
658 if (pos.x() + menu->width() < event->screenPos().x()) {
659 pos.setX(event->screenPos().x());
660 }
661 }
662 }
663 }
664
665 menu->exec(pos);
666 event->accept();
667 } else {
668 Applet::contextMenuEvent(event);
669 }
670}
671
672void ContainmentPrivate::addContainmentActions(KMenu &desktopMenu, QEvent *event)
673{
674 if (static_cast<Corona*>(q->scene())->immutability() != Mutable &&
675 !KAuthorized::authorizeKAction("plasma/containment_actions")) {
676 //kDebug() << "immutability";
677 return;
678 }
679
680 const QString trigger = ContainmentActions::eventToString(event);
681 prepareContainmentActions(trigger, QPoint(), &desktopMenu);
682}
683
684void ContainmentPrivate::addAppletActions(KMenu &desktopMenu, Applet *applet, QEvent *event)
685{
686 foreach (QAction *action, applet->contextualActions()) {
687 if (action) {
688 desktopMenu.addAction(action);
689 }
690 }
691
692 if (!applet->d->failed) {
693 QAction *configureApplet = applet->d->actions->action("configure");
694 if (configureApplet && configureApplet->isEnabled()) {
695 desktopMenu.addAction(configureApplet);
696 }
697
698 QAction *runAssociatedApplication = applet->d->actions->action("run associated application");
699 if (runAssociatedApplication && runAssociatedApplication->isEnabled()) {
700 desktopMenu.addAction(runAssociatedApplication);
701 }
702 }
703
704 KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu);
705 addContainmentActions(*containmentMenu, event);
706 if (!containmentMenu->isEmpty()) {
707 int enabled = 0;
708 //count number of real actions
709 QListIterator<QAction *> actionsIt(containmentMenu->actions());
710 while (enabled < 3 && actionsIt.hasNext()) {
711 QAction *action = actionsIt.next();
712 if (action->isVisible() && !action->isSeparator()) {
713 ++enabled;
714 }
715 }
716
717 if (enabled) {
718 //if there is only one, don't create a submenu
719 if (enabled < 2) {
720 foreach (QAction *action, containmentMenu->actions()) {
721 if (action->isVisible() && !action->isSeparator()) {
722 desktopMenu.addAction(action);
723 }
724 }
725 } else {
726 desktopMenu.addMenu(containmentMenu);
727 }
728 }
729 }
730
731 if (q->immutability() == Mutable &&
732 !q->property("hideCloseAppletInContextMenu").toBool()) {
733 QAction *closeApplet = applet->d->actions->action("remove");
734 //kDebug() << "checking for removal" << closeApplet;
735 if (closeApplet) {
736 if (!desktopMenu.isEmpty()) {
737 desktopMenu.addSeparator();
738 }
739
740 //kDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible();
741 desktopMenu.addAction(closeApplet);
742 }
743 }
744}
745
746Applet* ContainmentPrivate::appletAt(const QPointF &point)
747{
748 Applet *applet = 0;
749
750 QGraphicsItem *item = q->scene()->itemAt(point);
751 if (item == q) {
752 item = 0;
753 }
754
755 while (item) {
756 if (item->isWidget()) {
757 applet = qobject_cast<Applet*>(static_cast<QGraphicsWidget*>(item));
758 if (applet) {
759 if (applet->isContainment()) {
760 applet = 0;
761 }
762 break;
763 }
764 }
765 AppletHandle *handle = dynamic_cast<AppletHandle*>(item);
766 if (handle) {
767 //pretend it was on the applet
768 applet = handle->applet();
769 break;
770 }
771 item = item->parentItem();
772 }
773 return applet;
774}
775
776void Containment::setFormFactor(FormFactor formFactor)
777{
778 if (d->formFactor == formFactor) {
779 return;
780 }
781
782 //kDebug() << "switching FF to " << formFactor;
783 d->formFactor = formFactor;
784
785 if (isContainment() &&
786 (d->type == PanelContainment || d->type == CustomPanelContainment)) {
787 // we are a panel and we have chaged our orientation
788 d->positionPanel(true);
789 }
790
791 QMetaObject::invokeMethod(d->toolBox.data(), "reposition");
792
793 updateConstraints(Plasma::FormFactorConstraint);
794
795 KConfigGroup c = config();
796 c.writeEntry("formfactor", (int)formFactor);
797 emit configNeedsSaving();
798}
799
800void Containment::setLocation(Location location)
801{
802 if (d->location == location) {
803 return;
804 }
805
806 bool emitGeomChange = false;
807
808 if ((location == TopEdge || location == BottomEdge) &&
809 (d->location == TopEdge || d->location == BottomEdge)) {
810 emitGeomChange = true;
811 }
812
813 if ((location == RightEdge || location == LeftEdge) &&
814 (d->location == RightEdge || d->location == LeftEdge)) {
815 emitGeomChange = true;
816 }
817
818 d->location = location;
819
820 foreach (Applet *applet, d->applets) {
821 applet->updateConstraints(Plasma::LocationConstraint);
822 }
823
824 if (emitGeomChange) {
825 // our geometry on the scene will not actually change,
826 // but for the purposes of views it has
827 emit geometryChanged();
828 }
829
830 updateConstraints(Plasma::LocationConstraint);
831
832 KConfigGroup c = config();
833 c.writeEntry("location", (int)location);
834 emit configNeedsSaving();
835}
836
837void Containment::addSiblingContainment()
838{
839 emit addSiblingContainment(this);
840}
841
842void Containment::clearApplets()
843{
844 foreach (Applet *applet, d->applets) {
845 applet->d->cleanUpAndDelete();
846 }
847
848 d->applets.clear();
849}
850
851Applet *Containment::addApplet(const QString &name, const QVariantList &args,
852 const QRectF &appletGeometry)
853{
854 return d->addApplet(name, args, appletGeometry);
855}
856
857void Containment::addApplet(Applet *applet, const QPointF &pos, bool delayInit)
858{
859 if (!isContainment() || (!delayInit && immutability() != Mutable)) {
860 return;
861 }
862
863 if (!applet) {
864 kDebug() << "adding null applet!?!";
865 return;
866 }
867
868 if (d->applets.contains(applet)) {
869 kDebug() << "already have this applet!";
870 }
871
872 Containment *currentContainment = applet->containment();
873
874 if (d->type == PanelContainment) {
875 //panels don't want backgrounds, which is important when setting geometry
876 setBackgroundHints(NoBackground);
877 }
878
879 if (currentContainment && currentContainment != this) {
880 emit currentContainment->appletRemoved(applet);
881 if (currentContainment->d->focusedApplet == applet) {
882 currentContainment->d->focusedApplet = 0;
883 }
884
885 disconnect(applet, 0, currentContainment, 0);
886 KConfigGroup oldConfig = applet->config();
887 currentContainment->d->applets.removeAll(applet);
888 applet->setParentItem(this);
889 applet->setParent(this);
890
891 // now move the old config to the new location
892 //FIXME: this doesn't seem to get the actual main config group containing plugin=, etc
893 KConfigGroup c = config().group("Applets").group(QString::number(applet->id()));
894 oldConfig.reparent(&c);
895 applet->d->resetConfigurationObject();
896
897 disconnect(applet, SIGNAL(activate()), currentContainment, SIGNAL(activate()));
898 } else {
899 applet->setParentItem(this);
900 applet->setParent(this);
901 }
902
903 d->applets << applet;
904
905 connect(applet, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving()));
906 connect(applet, SIGNAL(releaseVisualFocus()), this, SIGNAL(releaseVisualFocus()));
907 connect(applet, SIGNAL(appletDestroyed(Plasma::Applet*)), this, SLOT(appletDestroyed(Plasma::Applet*)));
908 connect(applet, SIGNAL(newStatus(Plasma::ItemStatus)), this, SLOT(checkStatus(Plasma::ItemStatus)));
909 connect(applet, SIGNAL(activate()), this, SIGNAL(activate()));
910
911 if (pos != QPointF(-1, -1)) {
912 applet->setPos(pos);
913 }
914
915 if (!delayInit && !currentContainment) {
916 applet->restore(*applet->d->mainConfigGroup());
917 applet->init();
918 Plasma::Animation *anim = Plasma::Animator::create(Plasma::Animator::AppearAnimation);
919 if (anim) {
920 connect(anim, SIGNAL(finished()), this, SLOT(appletAppearAnimationComplete()));
921 anim->setTargetWidget(applet);
922 //FIXME: small hack until we have proper js anim support; allows 'zoom' to work in the
923 //'right' direction for appearance
924 anim->setDirection(QAbstractAnimation::Backward);
925 anim->start(QAbstractAnimation::DeleteWhenStopped);
926 } else {
927 d->appletAppeared(applet);
928 }
929 }
930
931 applet->setFlag(QGraphicsItem::ItemIsMovable, true);
932 applet->updateConstraints(Plasma::AllConstraints);
933 if (!delayInit) {
934 applet->flushPendingConstraintsEvents();
935 }
936 emit appletAdded(applet, pos);
937
938 if (!currentContainment) {
939 applet->updateConstraints(Plasma::StartupCompletedConstraint);
940 if (!delayInit) {
941 applet->flushPendingConstraintsEvents();
942 }
943 }
944
945 if (!delayInit) {
946 applet->d->scheduleModificationNotification();
947 }
948}
949
950Applet::List Containment::applets() const
951{
952 return d->applets;
953}
954
955void Containment::setScreen(int newScreen, int newDesktop)
956{
957 d->setScreen(newScreen, newDesktop);
958}
959
960void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops)
961{
962 // What we want to do in here is:
963 // * claim the screen as our own
964 // * signal whatever may be watching this containment about the switch
965 // * if we are a full screen containment, then:
966 // * resize to match the screen if we're that kind of containment
967 // * kick other full-screen containments off this screen
968 // * if we had a screen, then give our screen to the containment
969 // we kick out
970 //
971 // a screen of -1 means no associated screen.
972 Corona *corona = q->corona();
973 Q_ASSERT(corona);
974
975 //if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen
976 if (corona->offscreenWidgets().contains(q)) {
977 return;
978 }
979
980 int numScreens = corona->numScreens();
981 if (newScreen < -1) {
982 newScreen = -1;
983 }
984
985 // -1 == All desktops
986 if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) {
987 newDesktop = -1;
988 }
989
990 //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type;
991
992 Containment *swapScreensWith(0);
993 const bool isDesktopContainment = type == Containment::DesktopContainment ||
994 type == Containment::CustomContainment;
995 if (isDesktopContainment) {
996 // we want to listen to changes in work area if our screen changes
997 if (toolBox) {
998 if (screen < 0 && newScreen > -1) {
999 QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection);
1000 } else if (newScreen < 0) {
1001 QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()));
1002 }
1003 }
1004
1005 if (newScreen > -1) {
1006 // sanity check to make sure someone else doesn't have this screen already!
1007 Containment *currently = corona->containmentForScreen(newScreen, newDesktop);
1008 if (currently && currently != q) {
1009 kDebug() << "currently is on screen" << currently->screen()
1010 << "desktop" << currently->desktop()
1011 << "and is" << currently->activity()
1012 << (QObject*)currently << "i'm" << (QObject*)q;
1013 currently->setScreen(-1, currently->desktop());
1014 swapScreensWith = currently;
1015 }
1016 }
1017 }
1018
1019 if (newScreen < numScreens && newScreen > -1 && isDesktopContainment) {
1020 q->resize(corona->screenGeometry(newScreen).size());
1021 }
1022
1023 int oldDesktop = desktop;
1024 desktop = newDesktop;
1025
1026 int oldScreen = screen;
1027 screen = newScreen;
1028
1029 q->updateConstraints(Plasma::ScreenConstraint);
1030
1031 if (oldScreen != newScreen || oldDesktop != newDesktop) {
1032 /*
1033 kDebug() << "going to signal change for" << q
1034 << ", old screen & desktop:" << oldScreen << oldDesktop
1035 << ", new:" << screen << desktop;
1036 */
1037 KConfigGroup c = q->config();
1038 c.writeEntry("screen", screen);
1039 c.writeEntry("desktop", desktop);
1040 if (newScreen != -1) {
1041 lastScreen = newScreen;
1042 lastDesktop = newDesktop;
1043 c.writeEntry("lastScreen", lastScreen);
1044 c.writeEntry("lastDesktop", lastDesktop);
1045 }
1046 emit q->configNeedsSaving();
1047 emit q->screenChanged(oldScreen, newScreen, q);
1048 }
1049
1050 if (swapScreensWith) {
1051 //kDebug() << "setScreen due to swap, part 2";
1052 swapScreensWith->setScreen(oldScreen, oldDesktop);
1053 }
1054
1055 checkRemoveAction();
1056
1057 if (newScreen >= 0) {
1058 emit q->activate();
1059 }
1060}
1061
1062int Containment::screen() const
1063{
1064 return d->screen;
1065}
1066
1067int Containment::lastScreen() const
1068{
1069 return d->lastScreen;
1070}
1071
1072int Containment::desktop() const
1073{
1074 return d->desktop;
1075}
1076
1077int Containment::lastDesktop() const
1078{
1079 return d->lastDesktop;
1080}
1081
1082KPluginInfo::List Containment::listContainments(const QString &category,
1083 const QString &parentApp)
1084{
1085 return listContainmentsOfType(QString(), category, parentApp);
1086}
1087
1088
1089KPluginInfo::List Containment::listContainmentsOfType(const QString &type,
1090 const QString &category,
1091 const QString &parentApp)
1092{
1093 QString constraint;
1094
1095 if (parentApp.isEmpty()) {
1096 constraint.append("(not exist [X-KDE-ParentApp] or [X-KDE-ParentApp] == '')");
1097 } else {
1098 constraint.append("[X-KDE-ParentApp] == '").append(parentApp).append("'");
1099 }
1100
1101 if (!type.isEmpty()) {
1102 if (!constraint.isEmpty()) {
1103 constraint.append(" and ");
1104 }
1105
1106 constraint.append("'").append(type).append("' ~in [X-Plasma-ContainmentCategories]");
1107 }
1108
1109 if (!category.isEmpty()) {
1110 if (!constraint.isEmpty()) {
1111 constraint.append(" and ");
1112 }
1113
1114 constraint.append("[X-KDE-PluginInfo-Category] == '").append(category).append("'");
1115 if (category == "Miscellaneous") {
1116 constraint.append(" or (not exist [X-KDE-PluginInfo-Category] or [X-KDE-PluginInfo-Category] == '')");
1117 }
1118 }
1119
1120 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint);
1121 //kDebug() << "constraint was" << constraint << "which got us" << offers.count() << "matches";
1122 return KPluginInfo::fromServices(offers);
1123}
1124
1125KPluginInfo::List Containment::listContainmentsForMimetype(const QString &mimetype)
1126{
1127 const QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype);
1128 //kDebug() << mimetype << constraint;
1129 const KService::List offers = KServiceTypeTrader::self()->query("Plasma/Containment", constraint);
1130 return KPluginInfo::fromServices(offers);
1131}
1132
1133QStringList Containment::listContainmentTypes()
1134{
1135 KPluginInfo::List containmentInfos = listContainments();
1136 QSet<QString> types;
1137
1138 foreach (const KPluginInfo &containmentInfo, containmentInfos) {
1139 QStringList theseTypes = containmentInfo.service()->property("X-Plasma-ContainmentCategories").toStringList();
1140 foreach (const QString &type, theseTypes) {
1141 types.insert(type);
1142 }
1143 }
1144
1145 return types.toList();
1146}
1147
1148void Containment::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
1149{
1150 event->setAccepted(immutability() == Mutable &&
1151 (event->mimeData()->hasFormat(static_cast<Corona*>(scene())->appletMimeType()) ||
1152 KUrl::List::canDecode(event->mimeData()) ||
1153 event->mimeData()->hasFormat(ExtenderItemMimeData::mimeType())));
1154 //kDebug() << immutability() << Mutable << (immutability() == Mutable) << event->isAccepted();
1155
1156 if (!event->isAccepted()) {
1157 // check to see if we have an applet that accepts the format.
1158 QStringList formats = event->mimeData()->formats();
1159
1160 foreach (const QString &format, formats) {
1161 KPluginInfo::List appletList = Applet::listAppletInfoForMimetype(format);
1162 if (!appletList.isEmpty()) {
1163 event->setAccepted(true);
1164 break;
1165 }
1166 }
1167
1168 if (!event->isAccepted()) {
1169 foreach (const QString &format, formats) {
1170 KPluginInfo::List wallpaperList = Wallpaper::listWallpaperInfoForMimetype(format);
1171 if (!wallpaperList.isEmpty()) {
1172 event->setAccepted(true);
1173 break;
1174 }
1175 }
1176 }
1177 }
1178
1179 if (event->isAccepted()) {
1180 if (d->dropZoneStarted) {
1181 showDropZone(event->pos().toPoint());
1182 } else {
1183 if (!d->showDropZoneDelayTimer) {
1184 d->showDropZoneDelayTimer = new QTimer(this);
1185 d->showDropZoneDelayTimer->setSingleShot(true);
1186 connect(d->showDropZoneDelayTimer, SIGNAL(timeout()), this, SLOT(showDropZoneDelayed()));
1187 }
1188
1189 d->dropPoints.insert(0, event->pos());
1190 d->showDropZoneDelayTimer->start(300);
1191 }
1192 }
1193}
1194
1195void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
1196{
1197 if (event->pos().y() < 1 || event->pos().y() > size().height() ||
1198 event->pos().x() < 1 || event->pos().x() > size().width()) {
1199 if (d->showDropZoneDelayTimer) {
1200 d->showDropZoneDelayTimer->stop();
1201 }
1202
1203 showDropZone(QPoint());
1204 d->dropZoneStarted = false;
1205 }
1206}
1207
1208void ContainmentPrivate::showDropZoneDelayed()
1209{
1210 dropZoneStarted = true;
1211 q->showDropZone(dropPoints.value(0).toPoint());
1212 dropPoints.remove(0);
1213}
1214
1215void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
1216{
1217 //event->setAccepted(item == this || item == d->toolBox.data() || !item);
1218 //kDebug() << event->isAccepted() << d->showDropZoneDelayTimer->isActive();
1219 if (!event->isAccepted()) {
1220 if (d->showDropZoneDelayTimer) {
1221 d->showDropZoneDelayTimer->stop();
1222 }
1223 } else if ((!d->showDropZoneDelayTimer || !d->showDropZoneDelayTimer->isActive()) && immutability() == Plasma::Mutable) {
1224 showDropZone(event->pos().toPoint());
1225 }
1226}
1227
1228void Containment::dropEvent(QGraphicsSceneDragDropEvent *event)
1229{
1230 showDropZone(QPoint());
1231 d->dropZoneStarted = false;
1232 if (isContainment()) {
1233 d->dropData(event->scenePos(), event->screenPos(), event);
1234 } else {
1235 Applet::dropEvent(event);
1236 }
1237}
1238
1239void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent)
1240{
1241 if (q->immutability() != Mutable) {
1242 return;
1243 }
1244
1245 QPointF pos = q->mapFromScene(scenePos);
1246 const QMimeData *mimeData = 0;
1247
1248 if (dropEvent) {
1249 mimeData = dropEvent->mimeData();
1250 } else {
1251 QClipboard *clipboard = QApplication::clipboard();
1252 mimeData = clipboard->mimeData(QClipboard::Selection);
1253 //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection?
1254 }
1255
1256 if (!mimeData) {
1257 //Selection is either empty or not supported on this OS
1258 kDebug() << "no mime data";
1259 return;
1260 }
1261
1262 //kDebug() << event->mimeData()->text();
1263
1264 QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString());
1265
1266 if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) {
1267 QString data = mimeData->data(appletMimetype);
1268 const QStringList appletNames = data.split('\n', QString::SkipEmptyParts);
1269 foreach (const QString &appletName, appletNames) {
1270 //kDebug() << "doing" << appletName;
1271 QRectF geom(pos, QSize(0, 0));
1272 q->addApplet(appletName, QVariantList(), geom);
1273 }
1274 if (dropEvent) {
1275 dropEvent->acceptProposedAction();
1276 }
1277 } else if (mimeData->hasFormat(ExtenderItemMimeData::mimeType())) {
1278 kDebug() << "mimetype plasma/extenderitem is dropped, creating internal:extender";
1279 //Handle dropping extenderitems.
1280 const ExtenderItemMimeData *extenderData = qobject_cast<const ExtenderItemMimeData*>(mimeData);
1281 if (extenderData) {
1282 ExtenderItem *item = extenderData->extenderItem();
1283 QRectF geometry(pos - extenderData->pointerOffset(), item->size());
1284 kDebug() << "desired geometry: " << geometry;
1285 Applet *applet = qobject_cast<ExtenderApplet *>(item->extender() ? item->extender()->applet() : 0);
1286 if (applet) {
1287 qreal left, top, right, bottom;
1288 applet->getContentsMargins(&left, &top, &right, &bottom);
1289 applet->setPos(geometry.topLeft() - QPointF(int(left), int(top)));
1290 applet->show();
1291 } else {
1292 applet = addApplet("internal:extender", QVariantList(), geometry, 0, true);
1293 applet->hide();
1294 applet->init();
1295 appletAppeared(applet);
1296 applet->flushPendingConstraintsEvents();
1297 applet->d->scheduleModificationNotification();
1298 applet->adjustSize();
1299 applet->show();
1300 }
1301 item->setExtender(applet->extender());
1302 }
1303 } else if (KUrl::List::canDecode(mimeData)) {
1304 //TODO: collect the mimetypes of available script engines and offer
1305 // to create widgets out of the matching URLs, if any
1306 const KUrl::List urls = KUrl::List::fromMimeData(mimeData);
1307 foreach (const KUrl &url, urls) {
1308 if (AccessManager::supportedProtocols().contains(url.protocol())) {
1309 AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url);
1310 if (dropEvent) {
1311 dropPoints[job] = dropEvent->pos();
1312 } else {
1313 dropPoints[job] = scenePos;
1314 }
1315 QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)),
1316 q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*)));
1317 }
1318#ifndef PLASMA_NO_KIO
1319 else {
1320 KMimeType::Ptr mime = KMimeType::findByUrl(url);
1321 QString mimeName = mime->name();
1322 QRectF geom(pos, QSize());
1323 QVariantList args;
1324 args << url.url();
1325 kDebug() << "can decode" << mimeName << args;
1326
1327 // It may be a directory or a file, let's stat
1328 KIO::JobFlags flags = KIO::HideProgressInfo;
1329 KIO::MimetypeJob *job = KIO::mimetype(url, flags);
1330 if (dropEvent) {
1331 dropPoints[job] = dropEvent->pos();
1332 } else {
1333 dropPoints[job] = scenePos;
1334 }
1335
1336 QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*)));
1337 QObject::connect(job, SIGNAL(mimetype(KIO::Job*,QString)),
1338 q, SLOT(mimeTypeRetrieved(KIO::Job*,QString)));
1339
1340 KMenu *choices = new KMenu("Content dropped");
1341 choices->addAction(KIcon("process-working"), i18n("Fetching file type..."));
1342 if (dropEvent) {
1343 choices->popup(dropEvent->screenPos());
1344 } else {
1345 choices->popup(screenPos);
1346 }
1347
1348 dropMenus[job] = choices;
1349 }
1350#endif
1351 }
1352
1353 if (dropEvent) {
1354 dropEvent->acceptProposedAction();
1355 }
1356 } else {
1357 QStringList formats = mimeData->formats();
1358 QHash<QString, KPluginInfo> seenPlugins;
1359 QHash<QString, QString> pluginFormats;
1360
1361 foreach (const QString &format, formats) {
1362 KPluginInfo::List plugins = Applet::listAppletInfoForMimetype(format);
1363
1364 foreach (const KPluginInfo &plugin, plugins) {
1365 if (seenPlugins.contains(plugin.pluginName())) {
1366 continue;
1367 }
1368
1369 seenPlugins.insert(plugin.pluginName(), plugin);
1370 pluginFormats.insert(plugin.pluginName(), format);
1371 }
1372 }
1373 //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values();
1374
1375 QString selectedPlugin;
1376
1377 if (seenPlugins.isEmpty()) {
1378 // do nothing
1379 } else if (seenPlugins.count() == 1) {
1380 selectedPlugin = seenPlugins.constBegin().key();
1381 } else {
1382 KMenu choices;
1383 QHash<QAction *, QString> actionsToPlugins;
1384 foreach (const KPluginInfo &info, seenPlugins) {
1385 QAction *action;
1386 if (!info.icon().isEmpty()) {
1387 action = choices.addAction(KIcon(info.icon()), info.name());
1388 } else {
1389 action = choices.addAction(info.name());
1390 }
1391
1392 actionsToPlugins.insert(action, info.pluginName());
1393 }
1394
1395 QAction *choice = choices.exec(screenPos);
1396 if (choice) {
1397 selectedPlugin = actionsToPlugins[choice];
1398 }
1399 }
1400
1401 if (!selectedPlugin.isEmpty()) {
1402 if (!dropEvent) {
1403 // since we may have entered an event loop up above with the menu,
1404 // the clipboard item may no longer be valid, as QClipboard resets
1405 // the object behind the back of the application with a zero timer
1406 // so we fetch it again here
1407 QClipboard *clipboard = QApplication::clipboard();
1408 mimeData = clipboard->mimeData(QClipboard::Selection);
1409 }
1410
1411 KTemporaryFile tempFile;
1412 if (mimeData && tempFile.open()) {
1413 //TODO: what should we do with files after the applet is done with them??
1414 tempFile.setAutoRemove(false);
1415
1416 {
1417 QDataStream stream(&tempFile);
1418 QByteArray data = mimeData->data(pluginFormats[selectedPlugin]);
1419 stream.writeRawData(data, data.size());
1420 }
1421
1422 QRectF geom(pos, QSize());
1423 QVariantList args;
1424 args << tempFile.fileName();
1425 kDebug() << args;
1426 tempFile.close();
1427
1428 q->addApplet(selectedPlugin, args, geom);
1429 }
1430 }
1431 }
1432}
1433
1434void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job)
1435{
1436#ifndef PLASMA_NO_KIO
1437 QObject::disconnect(job, 0, q, 0);
1438 dropPoints.remove(job);
1439 KMenu *choices = dropMenus.take(job);
1440 delete choices;
1441 job->kill();
1442#endif // PLASMA_NO_KIO
1443}
1444
1445void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job)
1446{
1447 QPointF pos = dropPoints.take(job);
1448 if (job->error()) {
1449 //TODO: nice user visible error handling (knotification probably?)
1450 kDebug() << "remote applet access failed: " << job->errorText();
1451 return;
1452 }
1453
1454 if (!job->applet()) {
1455 kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero";
1456 return;
1457 }
1458
1459 q->addApplet(job->applet(), pos);
1460}
1461
1462void ContainmentPrivate::dropJobResult(KJob *job)
1463{
1464#ifndef PLASMA_NO_KIO
1465 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job);
1466 if (!tjob) {
1467 kDebug() << "job is not a KIO::TransferJob, won't handle the drop...";
1468 clearDataForMimeJob(tjob);
1469 return;
1470 }
1471 if (job->error()) {
1472 kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString();
1473 }
1474 // We call mimetypeRetrieved since there might be other mechanisms
1475 // for finding suitable applets. Cleanup happens there as well.
1476 mimeTypeRetrieved(qobject_cast<KIO::Job *>(job), QString());
1477#endif // PLASMA_NO_KIO
1478}
1479
1480void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype)
1481{
1482#ifndef PLASMA_NO_KIO
1483 kDebug() << "Mimetype Job returns." << mimetype;
1484 KIO::TransferJob* tjob = dynamic_cast<KIO::TransferJob*>(job);
1485 if (!tjob) {
1486 kDebug() << "job should be a TransferJob, but isn't";
1487 clearDataForMimeJob(job);
1488 return;
1489 }
1490 KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url());
1491 if (mimetype.isEmpty() && !appletList.count()) {
1492 clearDataForMimeJob(job);
1493 kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")";
1494 return;
1495 } else {
1496
1497 QPointF posi; // will be overwritten with the event's position
1498 if (dropPoints.keys().contains(tjob)) {
1499 posi = dropPoints[tjob];
1500 kDebug() << "Received a suitable dropEvent at" << posi;
1501 } else {
1502 kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob";
1503 clearDataForMimeJob(job);
1504 return;
1505 }
1506
1507 KMenu *choices = dropMenus.value(tjob);
1508 if (!choices) {
1509 kDebug() << "Bailing out. No QMenu found for this job.";
1510 clearDataForMimeJob(job);
1511 return;
1512 }
1513
1514 QVariantList args;
1515 args << tjob->url().url() << mimetype;
1516
1517 kDebug() << "Creating menu for:" << mimetype << posi << args;
1518
1519 appletList << Applet::listAppletInfoForMimetype(mimetype);
1520 KPluginInfo::List wallpaperList;
1521 if (drawWallpaper) {
1522 if (wallpaper && wallpaper->supportsMimetype(mimetype)) {
1523 wallpaperList << wallpaper->d->wallpaperDescription;
1524 } else {
1525 wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimetype);
1526 }
1527 }
1528
1529 if (!appletList.isEmpty() || !wallpaperList.isEmpty()) {
1530 choices->clear();
1531 QHash<QAction *, QString> actionsToApplets;
1532 choices->addTitle(i18n("Widgets"));
1533 foreach (const KPluginInfo &info, appletList) {
1534 kDebug() << info.name();
1535 QAction *action;
1536 if (!info.icon().isEmpty()) {
1537 action = choices->addAction(KIcon(info.icon()), info.name());
1538 } else {
1539 action = choices->addAction(info.name());
1540 }
1541
1542 actionsToApplets.insert(action, info.pluginName());
1543 kDebug() << info.pluginName();
1544 }
1545 actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon");
1546
1547 QHash<QAction *, QString> actionsToWallpapers;
1548 if (!wallpaperList.isEmpty()) {
1549 choices->addTitle(i18n("Wallpaper"));
1550
1551 QMap<QString, KPluginInfo> sorted;
1552 foreach (const KPluginInfo &info, appletList) {
1553 sorted.insert(info.name(), info);
1554 }
1555
1556 foreach (const KPluginInfo &info, wallpaperList) {
1557 QAction *action;
1558 if (!info.icon().isEmpty()) {
1559 action = choices->addAction(KIcon(info.icon()), info.name());
1560 } else {
1561 action = choices->addAction(info.name());
1562 }
1563
1564 actionsToWallpapers.insert(action, info.pluginName());
1565 }
1566 }
1567
1568 QAction *choice = choices->exec();
1569 if (choice) {
1570 // Put the job on hold so it can be recycled to fetch the actual content,
1571 // which is to be expected when something's dropped onto the desktop and
1572 // an applet is to be created with this URL
1573 if (!mimetype.isEmpty() && !tjob->error()) {
1574 tjob->putOnHold();
1575 KIO::Scheduler::publishSlaveOnHold();
1576 }
1577 QString plugin = actionsToApplets.value(choice);
1578 if (plugin.isEmpty()) {
1579 //set wallpapery stuff
1580 plugin = actionsToWallpapers.value(choice);
1581 if (!wallpaper || plugin != wallpaper->pluginName()) {
1582 kDebug() << "Wallpaper dropped:" << tjob->url();
1583 q->setWallpaper(plugin);
1584 }
1585
1586 if (wallpaper) {
1587 kDebug() << "Wallpaper dropped:" << tjob->url();
1588 wallpaper->setUrls(KUrl::List() << tjob->url());
1589 }
1590 } else {
1591 addApplet(actionsToApplets[choice], args, QRectF(posi, QSize()));
1592 }
1593
1594 clearDataForMimeJob(job);
1595 return;
1596 }
1597 } else {
1598 // we can at least create an icon as a link to the URL
1599 addApplet("icon", args, QRectF(posi, QSize()));
1600 }
1601 }
1602
1603 clearDataForMimeJob(job);
1604#endif // PLASMA_NO_KIO
1605}
1606
1607#ifndef KDE_NO_DEPRECATED
1608const QGraphicsItem *Containment::toolBoxItem() const
1609{
1610 return d->toolBox.data();
1611}
1612#endif
1613
1614void Containment::setToolBox(AbstractToolBox *toolBox)
1615{
1616 if (d->toolBox.data()) {
1617 d->toolBox.data()->deleteLater();
1618 }
1619 d->toolBox = toolBox;
1620}
1621
1622AbstractToolBox *Containment::toolBox() const
1623{
1624 return d->toolBox.data();
1625}
1626
1627void Containment::resizeEvent(QGraphicsSceneResizeEvent *event)
1628{
1629 Applet::resizeEvent(event);
1630
1631 if (isContainment()) {
1632 if (d->isPanelContainment()) {
1633 d->positionPanel();
1634 } else if (corona()) {
1635 QMetaObject::invokeMethod(corona(), "layoutContainments");
1636 }
1637
1638 if (d->wallpaper) {
1639 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size()));
1640 }
1641 }
1642}
1643
1644void Containment::keyPressEvent(QKeyEvent *event)
1645{
1646 //kDebug() << "keyPressEvent with" << event->key()
1647 // << "and hoping and wishing for a" << Qt::Key_Tab;
1648 if (event->key() == Qt::Key_Tab) { // && event->modifiers() == 0) {
1649 if (!d->applets.isEmpty()) {
1650 kDebug() << "let's give focus to...." << (QObject*)d->applets.first();
1651 d->applets.first()->setFocus(Qt::TabFocusReason);
1652 }
1653 }
1654}
1655
1656void Containment::wheelEvent(QGraphicsSceneWheelEvent *event)
1657{
1658 event->ignore();
1659 if (d->appletAt(event->scenePos())) {
1660 return; //no unexpected click-throughs
1661 }
1662
1663 if (d->wallpaper && d->wallpaper->isInitialized()) {
1664 QGraphicsItem *item = scene()->itemAt(event->scenePos());
1665 if (item == this) {
1666 event->ignore();
1667 d->wallpaper->wheelEvent(event);
1668
1669 if (event->isAccepted()) {
1670 return;
1671 }
1672 }
1673 }
1674
1675 QString trigger = ContainmentActions::eventToString(event);
1676
1677 if (d->prepareContainmentActions(trigger, event->screenPos())) {
1678 d->actionPlugins()->value(trigger)->contextEvent(event);
1679 event->accept();
1680 } else {
1681 event->ignore();
1682 Applet::wheelEvent(event);
1683 }
1684}
1685
1686bool Containment::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
1687{
1688 return Applet::sceneEventFilter(watched, event);
1689}
1690
1691QVariant Containment::itemChange(GraphicsItemChange change, const QVariant &value)
1692{
1693 //FIXME if the applet is moved to another containment we need to unfocus it
1694
1695 if (isContainment() &&
1696 (change == QGraphicsItem::ItemSceneHasChanged ||
1697 change == QGraphicsItem::ItemPositionHasChanged)) {
1698 switch (d->type) {
1699 case PanelContainment:
1700 case CustomPanelContainment:
1701 d->positionPanel();
1702 break;
1703 default:
1704 if (corona()) {
1705 QMetaObject::invokeMethod(corona(), "layoutContainments");
1706 }
1707 break;
1708 }
1709 }
1710
1711 return Applet::itemChange(change, value);
1712}
1713
1714void Containment::enableAction(const QString &name, bool enable)
1715{
1716 QAction *action = this->action(name);
1717 if (action) {
1718 action->setEnabled(enable);
1719 action->setVisible(enable);
1720 }
1721}
1722
1723void Containment::addToolBoxAction(QAction *action)
1724{
1725 d->createToolBox();
1726 if (d->toolBox) {
1727 d->toolBox.data()->addTool(action);
1728 }
1729}
1730
1731void Containment::removeToolBoxAction(QAction *action)
1732{
1733 if (d->toolBox) {
1734 d->toolBox.data()->removeTool(action);
1735 }
1736}
1737
1738void Containment::setToolBoxOpen(bool open)
1739{
1740 if (open) {
1741 openToolBox();
1742 } else {
1743 closeToolBox();
1744 }
1745}
1746
1747bool Containment::isToolBoxOpen() const
1748{
1749 return (d->toolBox && d->toolBox.data()->isShowing());
1750}
1751
1752void Containment::openToolBox()
1753{
1754 if (d->toolBox && !d->toolBox.data()->isShowing()) {
1755 d->toolBox.data()->setShowing(true);
1756 emit toolBoxVisibilityChanged(true);
1757 }
1758}
1759
1760void Containment::closeToolBox()
1761{
1762 if (d->toolBox && d->toolBox.data()->isShowing()) {
1763 d->toolBox.data()->setShowing(false);
1764 emit toolBoxVisibilityChanged(false);
1765 }
1766}
1767
1768void Containment::addAssociatedWidget(QWidget *widget)
1769{
1770 Applet::addAssociatedWidget(widget);
1771 if (d->focusedApplet) {
1772 d->focusedApplet->addAssociatedWidget(widget);
1773 }
1774
1775 foreach (const Applet *applet, d->applets) {
1776 if (applet->d->activationAction) {
1777 widget->addAction(applet->d->activationAction);
1778 }
1779 }
1780}
1781
1782void Containment::removeAssociatedWidget(QWidget *widget)
1783{
1784 Applet::removeAssociatedWidget(widget);
1785 if (d->focusedApplet) {
1786 d->focusedApplet->removeAssociatedWidget(widget);
1787 }
1788
1789 foreach (const Applet *applet, d->applets) {
1790 if (applet->d->activationAction) {
1791 widget->removeAction(applet->d->activationAction);
1792 }
1793 }
1794}
1795
1796void Containment::setDrawWallpaper(bool drawWallpaper)
1797{
1798 d->drawWallpaper = drawWallpaper;
1799 if (drawWallpaper) {
1800 KConfigGroup cfg = config();
1801 const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper);
1802 const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode);
1803 setWallpaper(wallpaper, mode);
1804 } else {
1805 delete d->wallpaper;
1806 d->wallpaper = 0;
1807 }
1808}
1809
1810bool Containment::drawWallpaper()
1811{
1812 return d->drawWallpaper;
1813}
1814
1815void Containment::setWallpaper(const QString &pluginName, const QString &mode)
1816{
1817 KConfigGroup cfg = config();
1818 bool newPlugin = true;
1819 bool newMode = true;
1820
1821 if (d->drawWallpaper) {
1822 if (d->wallpaper) {
1823 // we have a wallpaper, so let's decide whether we need to swap it out
1824 if (d->wallpaper->pluginName() != pluginName) {
1825 delete d->wallpaper;
1826 d->wallpaper = 0;
1827 } else {
1828 // it's the same plugin, so let's save its state now so when
1829 // we call restore later on we're safe
1830 newMode = d->wallpaper->renderingMode().name() != mode;
1831 newPlugin = false;
1832 }
1833 }
1834
1835 if (!pluginName.isEmpty() && !d->wallpaper) {
1836 d->wallpaper = Plasma::Wallpaper::load(pluginName);
1837 }
1838
1839 if (d->wallpaper) {
1840 d->wallpaper->setParent(this);
1841 d->wallpaper->setBoundingRect(QRectF(QPointF(0, 0), size()));
1842 d->wallpaper->setRenderingMode(mode);
1843
1844 if (newPlugin) {
1845 cfg.writeEntry("wallpaperplugin", pluginName);
1846 }
1847
1848 if (d->wallpaper->isInitialized()) {
1849 KConfigGroup wallpaperConfig = KConfigGroup(&cfg, "Wallpaper");
1850 wallpaperConfig = KConfigGroup(&wallpaperConfig, pluginName);
1851 d->wallpaper->restore(wallpaperConfig);
1852 }
1853
1854 if (newMode) {
1855 cfg.writeEntry("wallpaperpluginmode", mode);
1856 }
1857 }
1858
1859 update();
1860 }
1861
1862 if (!d->wallpaper) {
1863 cfg.deleteEntry("wallpaperplugin");
1864 cfg.deleteEntry("wallpaperpluginmode");
1865 }
1866
1867 if (newPlugin || newMode) {
1868 if (newPlugin && d->wallpaper) {
1869 connect(d->wallpaper, SIGNAL(configureRequested()), this, SLOT(requestConfiguration()));
1870 connect(d->wallpaper, SIGNAL(configNeedsSaving()), this, SIGNAL(configNeedsSaving()));
1871 }
1872
1873 emit configNeedsSaving();
1874 }
1875}
1876
1877Plasma::Wallpaper *Containment::wallpaper() const
1878{
1879 return d->wallpaper;
1880}
1881
1882void Containment::setContainmentActions(const QString &trigger, const QString &pluginName)
1883{
1884 KConfigGroup cfg = containmentActionsConfig();
1885 ContainmentActions *plugin = 0;
1886
1887 if (d->actionPlugins()->contains(trigger)) {
1888 plugin = d->actionPlugins()->value(trigger);
1889 if (plugin->pluginName() != pluginName) {
1890 d->actionPlugins()->remove(trigger);
1891 delete plugin;
1892 plugin=0;
1893 }
1894 }
1895 if (pluginName.isEmpty()) {
1896 cfg.deleteEntry(trigger);
1897 } else if (plugin) {
1898 //it already existed, just reload config
1899 if (plugin->isInitialized()) {
1900 plugin->setContainment(this); //to be safe
1901 //FIXME make a truly unique config group
1902 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
1903 plugin->restore(pluginConfig);
1904 }
1905 } else {
1906 switch (d->containmentActionsSource) {
1907 case ContainmentPrivate::Activity:
1908 //FIXME
1909 case ContainmentPrivate::Local:
1910 plugin = ContainmentActions::load(this, pluginName);
1911 break;
1912 default:
1913 plugin = ContainmentActions::load(0, pluginName);
1914 }
1915 if (plugin) {
1916 cfg.writeEntry(trigger, pluginName);
1917 d->actionPlugins()->insert(trigger, plugin);
1918 } else {
1919 //bad plugin... gets removed. is this a feature or a bug?
1920 cfg.deleteEntry(trigger);
1921 }
1922 }
1923
1924 emit configNeedsSaving();
1925}
1926
1927QStringList Containment::containmentActionsTriggers()
1928{
1929 return d->actionPlugins()->keys();
1930}
1931
1932QString Containment::containmentActions(const QString &trigger)
1933{
1934 ContainmentActions *c = d->actionPlugins()->value(trigger);
1935 return c ? c->pluginName() : QString();
1936}
1937
1938void Containment::setActivity(const QString &activity)
1939{
1940 Context *context = d->context();
1941 if (context->currentActivity() != activity) {
1942 context->setCurrentActivity(activity);
1943 }
1944}
1945
1946void ContainmentPrivate::onContextChanged(Plasma::Context *con)
1947{
1948 foreach (Applet *a, applets) {
1949 a->updateConstraints(ContextConstraint);
1950 }
1951
1952 KConfigGroup c = q->config();
1953 QString act = con->currentActivityId();
1954
1955 //save anything that's been set (boy I hope this avoids overwriting things)
1956 //FIXME of course if the user sets the name to an empty string we have a bug
1957 //but once we get context retrieving the name as soon as the id is set, this issue should go away
1958 if (!act.isEmpty()) {
1959 c.writeEntry("activityId", act);
1960 }
1961 act = con->currentActivity();
1962 if (!act.isEmpty()) {
1963 c.writeEntry("activity", act);
1964 }
1965
1966 if (toolBox) {
1967 toolBox.data()->update();
1968 }
1969 emit q->configNeedsSaving();
1970 emit q->contextChanged(con);
1971}
1972
1973QString Containment::activity() const
1974{
1975 return d->context()->currentActivity();
1976}
1977
1978Context *Containment::context() const
1979{
1980 return d->context();
1981}
1982
1983Context *ContainmentPrivate::context()
1984{
1985 if (!con) {
1986 con = new Context(q);
1987 q->connect(con, SIGNAL(changed(Plasma::Context*)),
1988 q, SLOT(onContextChanged(Plasma::Context*)));
1989 }
1990
1991 return con;
1992}
1993
1994KActionCollection* ContainmentPrivate::actions()
1995{
1996 return static_cast<Applet*>(q)->d->actions;
1997}
1998
1999void ContainmentPrivate::focusApplet(Plasma::Applet *applet)
2000{
2001 if (focusedApplet == applet) {
2002 return;
2003 }
2004
2005 QList<QWidget *> widgets = actions()->associatedWidgets();
2006 if (focusedApplet) {
2007 foreach (QWidget *w, widgets) {
2008 focusedApplet->removeAssociatedWidget(w);
2009 }
2010 }
2011
2012 if (applet && applets.contains(applet)) {
2013 //kDebug() << "switching to" << applet->name();
2014 focusedApplet = applet;
2015 foreach (QWidget *w, widgets) {
2016 focusedApplet->addAssociatedWidget(w);
2017 }
2018
2019 if (!focusedApplet->hasFocus()) {
2020 focusedApplet->setFocus(Qt::ShortcutFocusReason);
2021 }
2022 } else {
2023 focusedApplet = 0;
2024 }
2025}
2026
2027void Containment::focusNextApplet()
2028{
2029 if (d->applets.isEmpty()) {
2030 return;
2031 }
2032 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) + 1 : 0;
2033 if (index >= d->applets.size()) {
2034 index = 0;
2035 }
2036 kDebug() << "index" << index;
2037 d->focusApplet(d->applets.at(index));
2038}
2039
2040void Containment::focusPreviousApplet()
2041{
2042 if (d->applets.isEmpty()) {
2043 return;
2044 }
2045 int index = d->focusedApplet ? d->applets.indexOf(d->focusedApplet) - 1 : -1;
2046 if (index < 0) {
2047 index = d->applets.size() - 1;
2048 }
2049 kDebug() << "index" << index;
2050 d->focusApplet(d->applets.at(index));
2051}
2052
2053void Containment::destroy()
2054{
2055 destroy(true);
2056}
2057
2058void Containment::showConfigurationInterface()
2059{
2060 Applet::showConfigurationInterface();
2061}
2062
2063void Containment::configChanged()
2064{
2065 Applet::configChanged();
2066}
2067
2068void ContainmentPrivate::configChanged()
2069{
2070 if (drawWallpaper) {
2071 KConfigGroup group = q->config();
2072 q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper),
2073 group.readEntry("wallpaperpluginmode", defaultWallpaperMode));
2074 }
2075}
2076
2077void ContainmentPrivate::requestConfiguration()
2078{
2079 emit q->configureRequested(q);
2080}
2081
2082void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus)
2083{
2084 //kDebug() << "================== "<< appletStatus << q->status();
2085 if (appletStatus == q->status()) {
2086 emit q->newStatus(appletStatus);
2087 return;
2088 }
2089
2090 if (appletStatus < q->status()) {
2091 // check to see if any other applet has a higher status, and stick with that
2092 // if we do
2093 foreach (Applet *applet, applets) {
2094 if (applet->status() > appletStatus) {
2095 appletStatus = applet->status();
2096 }
2097 }
2098 }
2099
2100 q->setStatus(appletStatus);
2101}
2102
2103void Containment::destroy(bool confirm)
2104{
2105 if (immutability() != Mutable || Applet::d->transient) {
2106 return;
2107 }
2108
2109 if (isContainment() && confirm) {
2110 //FIXME: should not be blocking
2111 const QString title = i18nc("@title:window %1 is the name of the containment", "Remove %1", name());
2112 KGuiItem remove = KStandardGuiItem::remove();
2113 remove.setText(title);
2114 if (KMessageBox::warningContinueCancel(view(),
2115 i18nc("%1 is the name of the containment", "Do you really want to remove this %1?", name()),
2116 title, remove) != KMessageBox::Continue) {
2117 return;
2118 }
2119 }
2120
2121 Applet::destroy();
2122}
2123
2124void ContainmentPrivate::createToolBox()
2125{
2126 if (!toolBox && KAuthorized::authorizeKAction("plasma/containment_context_menu")) {
2127 toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q);
2128
2129 if (toolBox) {
2130 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled()));
2131 QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility()));
2132
2133 positionToolBox();
2134 }
2135 }
2136}
2137
2138void ContainmentPrivate::positionToolBox()
2139{
2140 QMetaObject::invokeMethod(toolBox.data(), "reposition");
2141}
2142
2143void ContainmentPrivate::updateToolBoxVisibility()
2144{
2145 emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing());
2146}
2147
2148void ContainmentPrivate::triggerShowAddWidgets()
2149{
2150 emit q->showAddWidgetsInterface(QPointF());
2151}
2152
2153void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints)
2154{
2155 if (!q->isContainment()) {
2156 return;
2157 }
2158
2159 //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox;
2160 if (constraints & Plasma::ImmutableConstraint) {
2161 //update actions
2162 checkRemoveAction();
2163 const bool unlocked = q->immutability() == Mutable;
2164 q->setAcceptDrops(unlocked);
2165 q->enableAction("add widgets", unlocked);
2166
2167 // tell the applets too
2168 foreach (Applet *a, applets) {
2169 a->setImmutability(q->immutability());
2170 a->updateConstraints(ImmutableConstraint);
2171 }
2172 }
2173
2174 // pass on the constraints that are relevant here
2175 Constraints appletConstraints = NoConstraint;
2176 if (constraints & FormFactorConstraint) {
2177 appletConstraints |= FormFactorConstraint;
2178 }
2179
2180 if (constraints & ScreenConstraint) {
2181 appletConstraints |= ScreenConstraint;
2182 }
2183
2184 if (appletConstraints != NoConstraint) {
2185 foreach (Applet *applet, applets) {
2186 applet->updateConstraints(appletConstraints);
2187 }
2188 }
2189
2190 if (toolBox && (constraints & Plasma::SizeConstraint ||
2191 constraints & Plasma::FormFactorConstraint ||
2192 constraints & Plasma::ScreenConstraint ||
2193 constraints & Plasma::StartupCompletedConstraint)) {
2194 //kDebug() << "Positioning toolbox";
2195 positionToolBox();
2196 }
2197
2198 if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) {
2199 q->addToolBoxAction(q->action("remove"));
2200 checkRemoveAction();
2201 }
2202}
2203
2204Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args,
2205 const QRectF &appletGeometry, uint id, bool delayInit)
2206{
2207 if (!q->isContainment()) {
2208 return 0;
2209 }
2210
2211 if (!delayInit && q->immutability() != Mutable) {
2212 kDebug() << "addApplet for" << name << "requested, but we're currently immutable!";
2213 return 0;
2214 }
2215
2216 QGraphicsView *v = q->view();
2217 if (v) {
2218 v->setCursor(Qt::BusyCursor);
2219 }
2220
2221 Applet *applet = Applet::load(name, id, args);
2222 if (v) {
2223 v->unsetCursor();
2224 }
2225
2226 if (!applet) {
2227 kDebug() << "Applet" << name << "could not be loaded.";
2228 applet = new Applet(0, QString(), id);
2229 applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name));
2230 }
2231
2232 //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry();
2233
2234 q->addApplet(applet, appletGeometry.topLeft(), delayInit);
2235 return applet;
2236}
2237
2238bool ContainmentPrivate::regionIsEmpty(const QRectF &region, Applet *ignoredApplet) const
2239{
2240 foreach (Applet *applet, applets) {
2241 if (applet != ignoredApplet && applet->geometry().intersects(region)) {
2242 return false;
2243 }
2244 }
2245 return true;
2246}
2247
2248void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet)
2249{
2250 applets.removeAll(applet);
2251 if (focusedApplet == applet) {
2252 focusedApplet = 0;
2253 }
2254
2255 emit q->appletRemoved(applet);
2256 emit q->configNeedsSaving();
2257}
2258
2259void ContainmentPrivate::appletAppearAnimationComplete()
2260{
2261 Animation *anim = qobject_cast<Animation *>(q->sender());
2262 if (anim) {
2263 Applet *applet = qobject_cast<Applet*>(anim->targetWidget());
2264 if (applet) {
2265 appletAppeared(applet);
2266 }
2267 }
2268}
2269
2270void ContainmentPrivate::appletAppeared(Applet *applet)
2271{
2272 //kDebug() << type << Containment::DesktopContainment;
2273 KConfigGroup *cg = applet->d->mainConfigGroup();
2274 applet->save(*cg);
2275 emit q->configNeedsSaving();
2276}
2277
2278void ContainmentPrivate::positionPanel(bool force)
2279{
2280 if (!q->scene()) {
2281 kDebug() << "no scene yet";
2282 return;
2283 }
2284
2285 // already positioning the panel - avoid infinite loops
2286 if (ContainmentPrivate::s_positioningPanels) {
2287 return;
2288 }
2289
2290 // we position panels in negative coordinates, and stack all horizontal
2291 // and all vertical panels with each other.
2292
2293
2294 const QPointF p = q->pos();
2295
2296 if (!force &&
2297 p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN &&
2298 q->scene()->collidingItems(q).isEmpty()) {
2299 // already positioned and not running into any other panels
2300 return;
2301 }
2302
2303
2304 QPointF newPos = preferredPanelPos(q->corona());
2305 if (p != newPos) {
2306 ContainmentPrivate::s_positioningPanels = true;
2307 q->setPos(newPos);
2308 ContainmentPrivate::s_positioningPanels = false;
2309 }
2310}
2311
2312bool ContainmentPrivate::isPanelContainment() const
2313{
2314 return type == Containment::PanelContainment || type == Containment::CustomPanelContainment;
2315}
2316
2317QPointF ContainmentPrivate::preferredPos(Corona *corona) const
2318{
2319 Q_ASSERT(corona);
2320
2321 if (isPanelContainment()) {
2322 //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona);
2323 return preferredPanelPos(corona);
2324 }
2325
2326 QPointF pos(0, 0);
2327 QTransform t;
2328 while (QGraphicsItem *i = corona->itemAt(pos, t)) {
2329 pos.setX(i->scenePos().x() + i->boundingRect().width() + 10);
2330 }
2331
2332 //kDebug() << "not a panel, put it at" << pos;
2333 return pos;
2334}
2335
2336QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const
2337{
2338 Q_ASSERT(corona);
2339
2340 //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here
2341 bool horiz = formFactor == Plasma::Horizontal;
2342 qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET;
2343 qreal lastHeight = 0;
2344
2345 // this should be ok for small numbers of panels, but if we ever end
2346 // up managing hundreds of them, this simplistic alogrithm will
2347 // likely be too slow.
2348 foreach (const Containment *other, corona->containments()) {
2349 if (other == q ||
2350 !other->d->isPanelContainment() ||
2351 horiz != (other->formFactor() == Plasma::Horizontal)) {
2352 // only line up with panels of the same orientation
2353 continue;
2354 }
2355
2356 if (horiz) {
2357 qreal y = other->pos().y();
2358 if (y < bottom) {
2359 lastHeight = other->size().height();
2360 bottom = y;
2361 }
2362 } else {
2363 qreal width = other->size().width();
2364 qreal x = other->pos().x() + width;
2365 if (x > bottom) {
2366 lastHeight = width;
2367 bottom = x + lastHeight;
2368 }
2369 }
2370 }
2371
2372 // give a space equal to the height again of the last item so there is
2373 // room to grow.
2374 QPointF newPos;
2375 if (horiz) {
2376 bottom -= lastHeight + INTER_CONTAINMENT_MARGIN;
2377 //TODO: fix x position for non-flush-left panels
2378 kDebug() << "moved to" << QPointF(0, bottom - q->size().height());
2379 newPos = QPointF(0, bottom - q->size().height());
2380 } else {
2381 bottom += lastHeight + INTER_CONTAINMENT_MARGIN;
2382 //TODO: fix y position for non-flush-top panels
2383 kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height());
2384 newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height());
2385 }
2386
2387 return newPos;
2388}
2389
2390
2391bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu)
2392{
2393 ContainmentActions *plugin = actionPlugins()->value(trigger);
2394 if (!plugin) {
2395 return false;
2396 }
2397 plugin->setContainment(q);
2398
2399 if (!plugin->isInitialized()) {
2400 KConfigGroup cfg = q->containmentActionsConfig();
2401 KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger);
2402 plugin->restore(pluginConfig);
2403 }
2404
2405 if (plugin->configurationRequired()) {
2406 KMenu *localMenu = menu ? menu : new KMenu();
2407
2408 localMenu->addTitle(i18n("This plugin needs to be configured"));
2409 localMenu->addAction(q->action("configure"));
2410
2411 if (!menu) {
2412 localMenu->exec(screenPos);
2413 delete localMenu;
2414 }
2415
2416 return false;
2417 } else if (menu) {
2418 QList<QAction*> actions = plugin->contextualActions();
2419 if (actions.isEmpty()) {
2420 //it probably didn't bother implementing the function. give the user a chance to set
2421 //a better plugin. note that if the user sets no-plugin this won't happen...
2422 if (!isPanelContainment() && q->action("configure")) {
2423 menu->addAction(q->action("configure"));
2424 }
2425 } else {
2426 menu->addActions(actions);
2427 }
2428 }
2429
2430 return true;
2431}
2432
2433KConfigGroup Containment::containmentActionsConfig()
2434{
2435 KConfigGroup cfg;
2436 switch (d->containmentActionsSource) {
2437 case ContainmentPrivate::Local:
2438 cfg = config();
2439 cfg = KConfigGroup(&cfg, "ActionPlugins");
2440 break;
2441 case ContainmentPrivate::Activity:
2442 cfg = KConfigGroup(corona()->config(), "Activities");
2443 cfg = KConfigGroup(&cfg, d->context()->currentActivityId());
2444 cfg = KConfigGroup(&cfg, "ActionPlugins");
2445 break;
2446 default:
2447 cfg = KConfigGroup(corona()->config(), "ActionPlugins");
2448 }
2449 return cfg;
2450}
2451
2452QHash<QString, ContainmentActions*> * ContainmentPrivate::actionPlugins()
2453{
2454 switch (containmentActionsSource) {
2455 case Activity:
2456 //FIXME
2457 case Local:
2458 return &localActionPlugins;
2459 default:
2460 return &globalActionPlugins;
2461 }
2462}
2463
2464} // Plasma namespace
2465
2466#include "containment.moc"
2467
2468