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.
24 #include <QApplication>
25 #include <QDesktopWidget>
26 #include <QGraphicsLayout>
27 #include <QGraphicsSceneDragDropEvent>
35 #include <KWindowSystem>
37 #include "containment.h"
38 #include "dataengine.h"
40 #include "widgets/icon.h"
42 using 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;
56 kioskImmutable(false),
57 mimetype("text/x-plasmoidservicename"),
60 if (KGlobal::hasMainComponent()) {
61 configName
= KGlobal::mainComponent().componentName() + "-appletsrc";
63 configName
= "plasma-appletsrc";
69 qDeleteAll(containments
);
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
);
108 KSharedConfigPtr config
;
109 QTimer configSyncTimer
;
110 QList
<Containment
*> containments
;
113 Corona::Corona(QObject
*parent
)
114 : QGraphicsScene(parent
),
118 //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
121 Corona::Corona(const QRectF
& sceneRect
, QObject
* parent
)
122 : QGraphicsScene(sceneRect
, parent
),
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
),
134 //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
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
);
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!
154 void Corona::setAppletMimeType(const QString
& type
)
159 QString
Corona::appletMimeType()
164 void Corona::saveApplets(const QString
&configName
) const
168 if (configName
.isEmpty() || configName
== d
->configName
) {
171 c
= KSharedConfig::openConfig(configName
);
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
)
204 if (configName
.isEmpty() || configName
== d
->configName
) {
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()) {
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(),
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()) {
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) {
261 foreach (Containment
* containment
, d
->containments
) {
262 QString cid
= QString::number(containment
->id());
263 KConfigGroup
containmentConfig(&containments
, cid
);
265 foreach(Applet
* applet
, containment
->applets()) {
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");
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");
310 c
->setFormFactor(Plasma::Planar
);
311 c
->flushUpdatedConstraints();
313 if (g
.x() <= topLeftCorner
.x() && g
.y() >= topLeftCorner
.y()) {
314 topLeftCorner
= g
.topLeft();
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
) {
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
379 d
->config
= KSharedConfig::openConfig(d
->configName
);
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;
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
);
411 kDebug() << "loading of containment" << name
<< "failed.";
413 // in case we got a non-Containment from Applet::loadApplet or a null containment was requested
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);
425 addItem(containment
);
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()));
441 void Corona::destroyContainment(Containment
*c
)
443 if (!d
->containments
.contains(c
)) {
447 d
->containments
.removeAll(c
);
448 c
->config().deleteGroup();
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);
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
);
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
);
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
;
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
519 Containment
* c
= addContainment("desktop");
520 c
->setScreen(screen
);
521 c
->setFormFactor(Plasma::Planar
);
522 emit
newScreen(screen
);
525 void Corona::syncConfig()
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
)) {
547 kDebug() << "setting immutability to" << immutable
;
548 d
->immutable
= immutable
;
549 d
->updateContainmentImmutability();
552 } // namespace Plasma
554 #include "corona.moc"