Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / libs / plasma / corona.cpp
blob033d5f10830c053a928fb27ec6efe9838bfee078
1 /*
2 * Copyright 2007 Matt Broadstone <mbroadst@gmail.com>
3 * Copyright 2007 Aaron Seigo <aseigo@kde.org>
4 * Copyright 2007 Riccardo Iaconelli <riccardo@kde.org>
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.
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
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.
22 #include "corona.h"
24 #include <QApplication>
25 #include <QDesktopWidget>
26 #include <QGraphicsLayout>
27 #include <QGraphicsSceneDragDropEvent>
28 #include <QMimeData>
29 #include <QTimer>
31 #include <KDebug>
32 #include <KGlobal>
33 #include <KLocale>
34 #include <KMimeType>
35 #include <KWindowSystem>
37 #include "containment.h"
38 #include "dataengine.h"
39 #include "phase.h"
40 #include "widgets/icon.h"
42 using namespace Plasma;
44 namespace Plasma
47 // constant controling how long between requesting a configuration sync
48 // and one happening should occur. currently 2 minutes.
49 const int CONFIG_SYNC_TIMEOUT = 120000;
51 class Corona::Private
53 public:
54 Private()
55 : immutable(false),
56 kioskImmutable(false),
57 mimetype("text/x-plasmoidservicename"),
58 config(0)
60 if (KGlobal::hasMainComponent()) {
61 configName = KGlobal::mainComponent().componentName() + "-appletsrc";
62 } else {
63 configName = "plasma-appletsrc";
67 ~Private()
69 qDeleteAll(containments);
72 void init(Corona* q)
74 configSyncTimer.setSingleShot(true);
75 connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig()));
76 QObject::connect(QApplication::desktop(), SIGNAL(resized(int)), q, SLOT(screenResized(int)));
79 void saveApplets(KSharedConfigPtr cg) const
81 KConfigGroup containmentsGroup(cg, "Containments");
82 foreach (const Containment *containment, containments) {
83 QString cid = QString::number(containment->id());
84 KConfigGroup containmentConfig(&containmentsGroup, cid);
85 containment->saveConstraints(&containmentConfig);
86 containment->save(&containmentConfig);
87 KConfigGroup applets(&containmentConfig, "Applets");
88 foreach (const Applet* applet, containment->applets()) {
89 KConfigGroup appletConfig(&applets, QString::number(applet->id()));
90 applet->save(&appletConfig);
95 void updateContainmentImmutability()
97 foreach (Containment *c, containments) {
98 // we need to tell each containment that immutability has been altered
99 // TODO: should we tell the applets too?
100 c->updateConstraints(ImmutableConstraint);
104 bool immutable;
105 bool kioskImmutable;
106 QString mimetype;
107 QString configName;
108 KSharedConfigPtr config;
109 QTimer configSyncTimer;
110 QList<Containment*> containments;
113 Corona::Corona(QObject *parent)
114 : QGraphicsScene(parent),
115 d(new Private)
117 d->init(this);
118 //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
121 Corona::Corona(const QRectF & sceneRect, QObject * parent )
122 : QGraphicsScene(sceneRect, parent),
123 d(new Private)
125 d->init(this);
126 //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
129 Corona::Corona(qreal x, qreal y, qreal width, qreal height, QObject * parent)
130 : QGraphicsScene(x, y, width, height, parent),
131 d(new Private)
133 d->init(this);
134 //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
137 Corona::~Corona()
139 KConfigGroup cg(config(), "General");
141 // we call the dptr member directly for locked since isImmutable()
142 // also checks kiosk and parent containers
143 cg.writeEntry("locked", d->immutable);
144 delete d;
147 QRectF Corona::maxSizeHint() const
149 //FIXME: this is a bit of a naive implementation, do you think? =)
150 // we should factor in how much space we actually have left!
151 return sceneRect();
154 void Corona::setAppletMimeType(const QString& type)
156 d->mimetype = type;
159 QString Corona::appletMimeType()
161 return d->mimetype;
164 void Corona::saveApplets(const QString &configName) const
166 KSharedConfigPtr c;
168 if (configName.isEmpty() || configName == d->configName) {
169 c = config();
170 } else {
171 c = KSharedConfig::openConfig(configName);
174 d->saveApplets(c);
177 void Corona::scheduleConfigSync() const
179 //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT
180 // after the first time this is called. not much of a heuristic for save points, but
181 // it should at least compress these activities a bit and provide a way for applet
182 // authors to ween themselves from the sync() disease. A more interesting/dynamic
183 // algorithm for determining when to actually sync() to disk might be better, though.
184 if (!d->configSyncTimer.isActive()) {
185 d->configSyncTimer.start(CONFIG_SYNC_TIMEOUT);
189 bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
191 QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft();
192 QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft();
193 if (p1.x() != p2.x()) {
194 return p1.x() < p2.x();
196 return p1.y() < p2.y();
199 void Corona::loadApplets(const QString& configName)
201 clearApplets();
202 KSharedConfigPtr c;
204 if (configName.isEmpty() || configName == d->configName) {
205 c = config();
206 } else {
207 c = KSharedConfig::openConfig(configName);
210 KConfigGroup containments(config(), "Containments");
212 foreach (const QString& group, containments.groupList()) {
213 KConfigGroup containmentConfig(&containments, group);
215 if (containmentConfig.entryMap().isEmpty()) {
216 continue;
219 int cid = group.toUInt();
220 // kDebug() << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group;
221 Containment *c = addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(),
222 cid, true);
223 if (!c) {
224 continue;
227 addItem(c);
228 c->init();
229 c->loadConstraints(&containmentConfig);
230 c->flushUpdatedConstraints();
231 // kDebug() << "Containment" << c->id() << "geometry is" << c->geometry().toRect() << "config'd with" << containmentConfig.name();
232 KConfigGroup applets(&containmentConfig, "Applets");
234 // Sort the applet configs in order of geometry to ensure that applets
235 // are added from left to right or top to bottom for a panel containment
236 QList<KConfigGroup> appletConfigs;
237 foreach (const QString &appletGroup, applets.groupList()) {
238 // kDebug() << "reading from applet group" << appletGroup;
239 KConfigGroup appletConfig(&applets, appletGroup);
240 appletConfigs.append(appletConfig);
242 qSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
244 foreach (KConfigGroup appletConfig, appletConfigs) {
245 int appId = appletConfig.name().toUInt();
246 // kDebug() << "the name is" << appletConfig.name();
247 QString plugin = appletConfig.readEntry("plugin", QString());
249 if (plugin.isEmpty()) {
250 continue;
253 Applet *applet = c->addApplet(plugin, QVariantList(), appId, appletConfig.readEntry("geometry", QRectF()), true);
254 applet->restore(&appletConfig);
257 updateToolboxPositions();
258 if (d->containments.count() < 1) {
259 loadDefaultSetup();
260 } else {
261 foreach (Containment* containment, d->containments) {
262 QString cid = QString::number(containment->id());
263 KConfigGroup containmentConfig(&containments, cid);
265 foreach(Applet* applet, containment->applets()) {
266 applet->init();
269 containment->updateConstraints(Plasma::StartupCompletedConstraint);
270 containment->flushUpdatedConstraints();
273 // quick sanity check to ensure we have containments for each screen!
274 int numScreens = QApplication::desktop()->numScreens();
275 for (int i = 0; i < numScreens; ++i) {
276 if (!containmentForScreen(i)) {
277 //TODO: should we look for containments that aren't asigned but already exist?
278 Containment* c = addContainment("desktop");
279 c->setScreen(i);
280 c->setFormFactor(Plasma::Planar);
281 c->flushUpdatedConstraints();
286 d->kioskImmutable = config()->isImmutable();
287 if (d->kioskImmutable) {
288 d->updateContainmentImmutability();
291 KConfigGroup coronaConfig(config(), "General");
292 setImmutable(coronaConfig.readEntry("locked", false));
295 void Corona::loadDefaultSetup()
297 //FIXME: implement support for system-wide defaults
298 QDesktopWidget *desktop = QApplication::desktop();
299 int numScreens = desktop->numScreens();
300 kDebug() << "number of screens is" << numScreens;
301 int topLeftScreen = 0;
302 QPoint topLeftCorner = desktop->screenGeometry(0).topLeft();
304 // create a containment for each screen
305 for (int i = 0; i < numScreens; ++i) {
306 QRect g = desktop->screenGeometry(i);
307 kDebug() << " screen " << i << "geometry is" << g;
308 Containment* c = addContainment("desktop");
309 c->setScreen(i);
310 c->setFormFactor(Plasma::Planar);
311 c->flushUpdatedConstraints();
313 if (g.x() <= topLeftCorner.x() && g.y() >= topLeftCorner.y()) {
314 topLeftCorner = g.topLeft();
315 topLeftScreen = i;
319 // make a panel at the bottom
320 Containment* panel = addContainment("panel");
321 panel->setScreen(topLeftScreen);
322 panel->setLocation(Plasma::BottomEdge);
324 // some default applets to get a usable UI
325 panel->addApplet("launcher");
326 panel->addApplet("tasks");
327 panel->addApplet("pager");
328 panel->addApplet("systemtray");
329 panel->addApplet("notifier");
330 panel->addApplet("digital-clock");
332 // trigger an instant layout so we immediately have a proper geometry rather than waiting around
333 // for the event loop
334 panel->flushUpdatedConstraints();
335 if (panel->layout()) {
336 panel->layout()->invalidate();
340 * a little snip that adds another panel, this time to the left
342 panel = addContainment("panel");
343 panel->setScreen(topLeftScreen);
344 //TODO: but .. *where* on the left edge?
345 panel->setLocation(Plasma::LeftEdge);
348 // in case something goes bad during runtime, let's at least save this to disk soonish
349 scheduleConfigSync();
352 Containment* Corona::containmentForScreen(int screen) const
354 foreach (Containment* containment, d->containments) {
355 if (containment->screen() == screen &&
356 containment->containmentType() == Containment::DesktopContainment) {
357 return containment;
361 return 0;
364 QList<Containment*> Corona::containments() const
366 return d->containments;
369 void Corona::clearApplets()
371 foreach (Containment* containment, d->containments) {
372 containment->clearApplets();
376 KSharedConfigPtr Corona::config() const
378 if (!d->config) {
379 d->config = KSharedConfig::openConfig(d->configName);
382 return d->config;
385 void Corona::updateToolboxPositions()
387 foreach (Containment *c, d->containments) {
388 if (c->containmentType() == Containment::DesktopContainment) {
389 c->repositionToolbox();
394 Containment* Corona::addContainment(const QString& name, const QVariantList& args, uint id, bool delayedInit)
396 QString pluginName = name;
397 Containment* containment = 0;
398 Applet* applet = 0;
400 //kDebug() << "Loading" << name << args << id;
402 if (pluginName.isEmpty()) {
403 // default to the desktop containment
404 pluginName = "desktop";
405 } else if (pluginName != "null") {
406 applet = Applet::load(pluginName, id, args);
407 containment = dynamic_cast<Containment*>(applet);
410 if (!containment) {
411 kDebug() << "loading of containment" << name << "failed.";
413 // in case we got a non-Containment from Applet::loadApplet or a null containment was requested
414 delete applet;
415 containment = new Containment;
417 // we want to provide something and don't care about the failure to launch
418 containment->setFailedToLaunch(false);
419 containment->setFormFactor(Plasma::Planar);
422 containment->setIsContainment(true);
424 if (!delayedInit) {
425 addItem(containment);
426 containment->init();
427 containment->updateConstraints(Plasma::StartupCompletedConstraint);
430 d->containments.append(containment);
431 connect(containment, SIGNAL(destroyed(QObject*)),
432 this, SLOT(containmentDestroyed(QObject*)));
433 connect(containment, SIGNAL(launchActivated()),
434 SIGNAL(launchActivated()));
435 connect(containment, SIGNAL(configNeedsSaving()),
436 SLOT(scheduleConfigSync()));
438 return containment;
441 void Corona::destroyContainment(Containment *c)
443 if (!d->containments.contains(c)) {
444 return;
447 d->containments.removeAll(c);
448 c->config().deleteGroup();
449 c->deleteLater();
452 void Corona::dragEnterEvent( QGraphicsSceneDragDropEvent *event)
454 // kDebug() << "Corona::dragEnterEvent(QGraphicsSceneDragDropEvent* event)";
455 if (event->mimeData()->hasFormat(d->mimetype) ||
456 KUrl::List::canDecode(event->mimeData())) {
457 event->acceptProposedAction();
458 //TODO Create the applet, move to mouse position then send the
459 // following event to lock it to the mouse
460 //QMouseEvent event(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, event->mouseButtons(), 0);
461 //QApplication::sendEvent(this, &event);
464 event->accept();
465 //TODO Allow dragging an applet from another Corona into this one while
466 // keeping its settings etc.
469 void Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
471 // kDebug() << "Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)";
472 //TODO If an established Applet is dragged out of the Corona, remove it and
473 // create a QDrag type thing to keep the Applet's settings
475 QGraphicsScene::dragLeaveEvent(event);
478 void Corona::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
480 QGraphicsScene::dragMoveEvent(event);
482 event->accept();
483 //kDebug() << "Corona::dragMoveEvent(QDragMoveEvent* event)";
486 void Corona::containmentDestroyed(QObject* obj)
488 // we do a static_cast here since it really isn't an Containment by this
489 // point anymore since we are in the qobject dtor. we don't actually
490 // try and do anything with it, we just need the value of the pointer
491 // so this unsafe looking code is actually just fine.
492 Containment* containment = static_cast<Plasma::Containment*>(obj);
493 int index = d->containments.indexOf(containment);
495 if (index > -1) {
496 d->containments.removeAt(index);
500 void Corona::screenResized(int screen)
502 bool desktopFound = false;
503 foreach (Containment *c, d->containments) {
504 if (c->screen() == screen) {
505 // trigger a relayout
506 c->setScreen(screen);
507 desktopFound = desktopFound || c->containmentType() == Containment::DesktopContainment;
511 if (desktopFound) {
512 return;
515 // a new screen appeared. neat.
516 // FIXME: apparently QDesktopWidget doesn't do the Right Thing when a new screen is plugged in
517 // at runtime. seems it gets confused and thinks it's all one big screen? need to
518 // fix this upstream
519 Containment* c = addContainment("desktop");
520 c->setScreen(screen);
521 c->setFormFactor(Plasma::Planar);
522 emit newScreen(screen);
525 void Corona::syncConfig()
527 config()->sync();
530 bool Corona::isImmutable() const
532 return d->kioskImmutable || d->immutable;
535 bool Corona::isKioskImmutable() const
537 return d->kioskImmutable;
540 void Corona::setImmutable(bool immutable)
542 if (d->immutable == immutable ||
543 (!immutable && d->kioskImmutable)) {
544 return;
547 kDebug() << "setting immutability to" << immutable;
548 d->immutable = immutable;
549 d->updateContainmentImmutability();
552 } // namespace Plasma
554 #include "corona.moc"