• Skip to content
  • Skip to link menu
KDE 4.0 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

libplasma

corona.cpp

Go to the documentation of this file.
00001 /*
00002  *   Copyright 2007 Matt Broadstone <mbroadst@gmail.com>
00003  *   Copyright 2007 Aaron Seigo <aseigo@kde.org>
00004  *   Copyright 2007 Riccardo Iaconelli <riccardo@kde.org>
00005  *
00006  *   This program is free software; you can redistribute it and/or modify
00007  *   it under the terms of the GNU Library General Public License as
00008  *   published by the Free Software Foundation; either version 2, or
00009  *   (at your option) any later version.
00010  *
00011  *   This program is distributed in the hope that it will be useful,
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *   GNU General Public License for more details
00015  *
00016  *   You should have received a copy of the GNU Library General Public
00017  *   License along with this program; if not, write to the
00018  *   Free Software Foundation, Inc.,
00019  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00020  */
00021 
00022 #include "corona.h"
00023 
00024 #include <QApplication>
00025 #include <QDesktopWidget>
00026 #include <QGraphicsSceneDragDropEvent>
00027 #include <QMimeData>
00028 #include <QUrl>
00029 #include <QGraphicsView>
00030 #include <QStringList>
00031 #include <QTimer>
00032 
00033 #include <KDebug>
00034 #include <KLocale>
00035 #include <KMimeType>
00036 #include <KWindowSystem>
00037 
00038 #include "containment.h"
00039 #include "dataengine.h"
00040 #include "phase.h"
00041 #include "layouts/layout.h"
00042 #include "widgets/icon.h"
00043 
00044 using namespace Plasma;
00045 
00046 namespace Plasma
00047 {
00048 
00049 // constant controling how long between requesting a configuration sync
00050 // and one happening should occur. currently 2 minutes.
00051 const int CONFIG_SYNC_TIMEOUT = 120000;
00052 
00053 class Corona::Private
00054 {
00055 public:
00056     Private()
00057         : immutable(false),
00058           kioskImmutable(false),
00059           mimetype("text/x-plasmoidservicename"),
00060           configName("plasma-appletsrc"),
00061           config(0)
00062     {
00063     }
00064 
00065     ~Private()
00066     {
00067         qDeleteAll(containments);
00068     }
00069 
00070     void init(Corona* q)
00071     {
00072         configSyncTimer.setSingleShot(true);
00073         connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig()));
00074         QObject::connect(QApplication::desktop(), SIGNAL(resized(int)), q, SLOT(screenResized(int)));
00075     }
00076 
00077     void saveApplets(KSharedConfigPtr cg) const
00078     {
00079         KConfigGroup containmentsGroup(cg, "Containments");
00080         foreach (const Containment *containment, containments) {
00081             QString cid = QString::number(containment->id());
00082             KConfigGroup containmentConfig(&containmentsGroup, cid);
00083             containment->saveConstraints(&containmentConfig);
00084             containment->save(&containmentConfig);
00085             KConfigGroup applets(&containmentConfig, "Applets");
00086             foreach (const Applet* applet, containment->applets()) {
00087                 KConfigGroup appletConfig(&applets, QString::number(applet->id()));
00088                 applet->save(&appletConfig);
00089             }
00090         }
00091     }
00092 
00093     void updateContainmentImmutability()
00094     {
00095         foreach (Containment *c, containments) {
00096             // we need to tell each containment that immutability has been altered
00097             // TODO: should we tell the applets too?
00098             c->updateConstraints(ImmutableConstraint);
00099         }
00100     }
00101 
00102     bool immutable;
00103     bool kioskImmutable;
00104     QString mimetype;
00105     QString configName;
00106     KSharedConfigPtr config;
00107     QTimer configSyncTimer;
00108     QList<Containment*> containments;
00109 };
00110 
00111 Corona::Corona(QObject *parent)
00112     : QGraphicsScene(parent),
00113       d(new Private)
00114 {
00115     d->init(this);
00116     //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
00117 }
00118 
00119 Corona::Corona(const QRectF & sceneRect, QObject * parent )
00120     : QGraphicsScene(sceneRect, parent),
00121       d(new Private)
00122 {
00123     d->init(this);
00124     //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
00125 }
00126 
00127 Corona::Corona(qreal x, qreal y, qreal width, qreal height, QObject * parent)
00128     : QGraphicsScene(x, y, width, height, parent),
00129       d(new Private)
00130 {
00131     d->init(this);
00132     //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel)));
00133 }
00134 
00135 Corona::~Corona()
00136 {
00137     KConfigGroup cg(config(), "General");
00138 
00139     // we call the dptr member directly for locked since isImmutable()
00140     // also checks kiosk and parent containers
00141     cg.writeEntry("locked", d->immutable);
00142     delete d;
00143 }
00144 
00145 QRectF Corona::maxSizeHint() const
00146 {
00147     //FIXME: this is a bit of a naive implementation, do you think? =)
00148     //       we should factor in how much space we actually have left!
00149     return sceneRect();
00150 }
00151 
00152 void Corona::setAppletMimeType(const QString& type)
00153 {
00154     d->mimetype = type;
00155 }
00156 
00157 QString Corona::appletMimeType()
00158 {
00159     return d->mimetype;
00160 }
00161 
00162 void Corona::saveApplets(const QString &config) const
00163 {
00164     KSharedConfigPtr cg = KSharedConfig::openConfig(config);
00165     d->saveApplets(cg);
00166 }
00167 
00168 void Corona::saveApplets() const
00169 {
00170     d->saveApplets(config());
00171     scheduleConfigSync();
00172 }
00173 
00174 void Corona::scheduleConfigSync() const
00175 {
00176     //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT
00177     //      after the first time this is called. not much of a heuristic for save points, but
00178     //      it should at least compress these activities a bit and provide a way for applet
00179     //      authors to ween themselves from the sync() disease. A more interesting/dynamic
00180     //      algorithm for determining when to actually sync() to disk might be better, though.
00181     if (!d->configSyncTimer.isActive()) {
00182         d->configSyncTimer.start(CONFIG_SYNC_TIMEOUT);
00183     }
00184 }
00185 
00186 bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2)
00187 {
00188     QPointF p1 = c1.readEntry("geometry", QRectF()).topLeft();
00189     QPointF p2 = c2.readEntry("geometry", QRectF()).topLeft();
00190     if (p1.x() != p2.x()) {
00191         return p1.x() < p2.x();
00192     }
00193     return p1.y() < p2.y();
00194 }
00195 
00196 void Corona::loadApplets(const QString& configName)
00197 {
00198     clearApplets();
00199     if (configName != d->configName) {
00200         d->configName = configName;
00201         d->config = 0;
00202     }
00203 
00204     KConfigGroup containments(config(), "Containments");
00205 
00206     foreach (const QString& group, containments.groupList()) {
00207         KConfigGroup containmentConfig(&containments, group);
00208 
00209         if (containmentConfig.entryMap().isEmpty()) {
00210             continue;
00211         }
00212 
00213         int cid = group.toUInt();
00214         //kDebug() << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group;
00215         Containment *c = addContainment(containmentConfig.readEntry("plugin", QString()), QVariantList(),
00216                                         cid, true);
00217         if (!c) {
00218             continue;
00219         }
00220 
00221         addItem(c);
00222         c->init();
00223         c->loadConstraints(&containmentConfig);
00224         c->flushUpdatedConstraints();
00225         //kDebug() << "Containment" << c->id() << "geometry is" << c->geometry().toRect() << "config'd with" << appletConfig.name();
00226         KConfigGroup applets(&containmentConfig, "Applets");
00227 
00228         // Sort the applet configs in order of geometry to ensure that applets
00229         // are added from left to right or top to bottom for a panel containment
00230         QList<KConfigGroup> appletConfigs;
00231         foreach (const QString &appletGroup, applets.groupList()) {
00232             //kDebug() << "reading from applet group" << appletGroup;
00233             KConfigGroup appletConfig(&applets, appletGroup);
00234             appletConfigs.append(appletConfig);
00235         }
00236         qSort(appletConfigs.begin(), appletConfigs.end(), appletConfigLessThan);
00237 
00238         foreach (KConfigGroup appletConfig, appletConfigs) {
00239             int appId = appletConfig.name().toUInt();
00240             //kDebug() << "the name is" << appletConfig.name();
00241             QString plugin = appletConfig.readEntry("plugin", QString());
00242 
00243             if (plugin.isEmpty()) {
00244                 continue;
00245             }
00246 
00247             Applet *applet = c->addApplet(plugin, QVariantList(), appId, appletConfig.readEntry("geometry", QRectF()), true);
00248 
00249             QList<qreal> m = appletConfig.readEntry("transform", QList<qreal>());
00250             if (m.count() == 9) {
00251                 QTransform t(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
00252                 applet->setTransform(t);
00253             }
00254          }
00255     }
00256 
00257     if (d->containments.count() < 1) {
00258         loadDefaultSetup();
00259     } else {
00260         foreach (Containment* containment, d->containments) {
00261             QString cid = QString::number(containment->id());
00262             KConfigGroup containmentConfig(&containments, cid);
00263 
00264             foreach(Applet* applet, containment->applets()) {
00265                 applet->init();
00266             }
00267 
00268             containment->updateConstraints(Plasma::StartupCompletedConstraint);
00269             containment->flushUpdatedConstraints();
00270         }
00271 
00272         // quick sanity check to ensure we have containments for each screen!
00273         int numScreens = QApplication::desktop()->numScreens();
00274         for (int i = 0; i < numScreens; ++i) {
00275             if (!containmentForScreen(i)) {
00276                 //TODO: should we look for containments that aren't asigned but already exist?
00277                 Containment* c = addContainment("desktop");
00278                 c->setScreen(i);
00279                 c->setFormFactor(Plasma::Planar);
00280                 c->flushUpdatedConstraints();
00281             }
00282         }
00283     }
00284 
00285     d->kioskImmutable = config()->isImmutable();
00286     if (d->kioskImmutable) {
00287         d->updateContainmentImmutability();
00288     }
00289 
00290     KConfigGroup coronaConfig(config(), "General");
00291     setImmutable(coronaConfig.readEntry("locked", false));
00292 }
00293 
00294 void Corona::loadApplets()
00295 {
00296     loadApplets(d->configName);
00297 }
00298 
00299 void Corona::loadDefaultSetup()
00300 {
00301     //FIXME: implement support for system-wide defaults
00302     QDesktopWidget *desktop = QApplication::desktop();
00303     int numScreens = desktop->numScreens();
00304     kDebug() << "number of screens is" << numScreens;
00305     int topLeftScreen = 0;
00306     QPoint topLeftCorner = desktop->screenGeometry(0).topLeft();
00307 
00308     // create a containment for each screen
00309     for (int i = 0; i < numScreens; ++i) {
00310         QRect g = desktop->screenGeometry(i);
00311         kDebug() << "     screen " << i << "geometry is" << g;
00312         Containment* c = addContainment("desktop");
00313         c->setScreen(i);
00314         c->setFormFactor(Plasma::Planar);
00315         c->flushUpdatedConstraints();
00316 
00317         if (g.x() <= topLeftCorner.x() && g.y() >= topLeftCorner.y()) {
00318             topLeftCorner = g.topLeft();
00319             topLeftScreen = i;
00320         }
00321     }
00322 
00323     // make a panel at the bottom
00324     Containment* panel = addContainment("panel");
00325     panel->setScreen(topLeftScreen);
00326     panel->setLocation(Plasma::BottomEdge);
00327 
00328     // some default applets to get a usable UI
00329     panel->addApplet("launcher");
00330     panel->addApplet("tasks");
00331     panel->addApplet("pager");
00332     panel->addApplet("systemtray");
00333     panel->addApplet("notifier");
00334     panel->addApplet("digital-clock");
00335 
00336     // trigger an instant layout so we immediately have a proper geometry rather than waiting around
00337     // for the event loop
00338     panel->flushUpdatedConstraints();
00339     if (panel->layout()) {
00340         panel->layout()->invalidate();
00341     }
00342 
00343     /*
00344      * a little snip that adds another panel, this time to the left
00345      *
00346     panel = addContainment("panel");
00347     panel->setScreen(topLeftScreen);
00348     //TODO: but .. *where* on the left edge?
00349     panel->setLocation(Plasma::LeftEdge);
00350     */
00351 
00352     // in case something goes bad during runtime, let's at least save this to disk soonish
00353     scheduleConfigSync();
00354 }
00355 
00356 Containment* Corona::containmentForScreen(int screen) const
00357 {
00358     foreach (Containment* containment, d->containments) {
00359         if (containment->screen() == screen &&
00360             containment->containmentType() == Containment::DesktopContainment) {
00361             return containment;
00362         }
00363     }
00364 
00365     return 0;
00366 }
00367 
00368 QList<Containment*> Corona::containments() const
00369 {
00370     return d->containments;
00371 }
00372 
00373 void Corona::clearApplets()
00374 {
00375     foreach (Containment* containment, d->containments) {
00376         containment->clearApplets();
00377     }
00378 }
00379 
00380 KSharedConfigPtr Corona::config() const
00381 {
00382     if (!d->config) {
00383         d->config = KSharedConfig::openConfig(d->configName);
00384     }
00385 
00386     return d->config;
00387 }
00388 
00389 Containment* Corona::addContainment(const QString& name, const QVariantList& args, uint id, bool delayedInit)
00390 {
00391     QString pluginName = name;
00392     Containment* containment = 0;
00393     Applet* applet = 0;
00394 
00395     //kDebug() << "Loading" << name << args << id;
00396 
00397     if (pluginName.isEmpty()) {
00398         // default to the desktop containment
00399         pluginName = "desktop";
00400     } else if (pluginName != "null") {
00401         applet = Applet::loadApplet(pluginName, id, args);
00402         containment = dynamic_cast<Containment*>(applet);
00403     }
00404 
00405     if (!containment) {
00406         kDebug() << "loading of containment" << name << "failed.";
00407 
00408         // in case we got a non-Containment from Applet::loadApplet or a null containment was requested
00409         delete applet;
00410         containment = new Containment;
00411 
00412         // we want to provide something and don't care about the failure to launch
00413         containment->setFailedToLaunch(false);
00414         containment->setFormFactor(Plasma::Planar);
00415     }
00416 
00417     containment->setIsContainment(true);
00418 
00419     if (!delayedInit) {
00420         addItem(containment);
00421         containment->init();
00422         containment->updateConstraints(Plasma::StartupCompletedConstraint);
00423     }
00424 
00425     d->containments.append(containment);
00426     connect(containment, SIGNAL(destroyed(QObject*)),
00427             this, SLOT(containmentDestroyed(QObject*)));
00428     connect(containment, SIGNAL(launchActivated()),
00429             SIGNAL(launchActivated()));
00430     connect(containment, SIGNAL(configNeedsSaving()),
00431             SLOT(scheduleConfigSync()));
00432 
00433     return containment;
00434 }
00435 
00436 void Corona::destroyContainment(Containment *c)
00437 {
00438     if (!d->containments.contains(c)) {
00439         return;
00440     }
00441 
00442     d->containments.removeAll(c);
00443     c->config().deleteGroup();
00444     c->deleteLater();
00445 }
00446 
00447 void Corona::dragEnterEvent( QGraphicsSceneDragDropEvent *event)
00448 {
00449 //    kDebug() << "Corona::dragEnterEvent(QGraphicsSceneDragDropEvent* event)";
00450     if (event->mimeData()->hasFormat(d->mimetype) ||
00451         KUrl::List::canDecode(event->mimeData())) {
00452         event->acceptProposedAction();
00453         //TODO Create the applet, move to mouse position then send the
00454         //     following event to lock it to the mouse
00455         //QMouseEvent event(QEvent::MouseButtonPress, event->pos(), Qt::LeftButton, event->mouseButtons(), 0);
00456         //QApplication::sendEvent(this, &event);
00457     }
00458 
00459     event->accept();
00460     //TODO Allow dragging an applet from another Corona into this one while
00461     //     keeping its settings etc.
00462 }
00463 
00464 void Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
00465 {
00466    // kDebug() << "Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)";
00467     //TODO If an established Applet is dragged out of the Corona, remove it and
00468     //     create a QDrag type thing to keep the Applet's settings
00469 
00470     QGraphicsScene::dragLeaveEvent(event);
00471 }
00472 
00473 void Corona::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
00474 {
00475     QGraphicsScene::dragMoveEvent(event);
00476 
00477     event->accept();
00478     //kDebug() << "Corona::dragMoveEvent(QDragMoveEvent* event)";
00479 }
00480 
00481 void Corona::containmentDestroyed(QObject* obj)
00482 {
00483     // we do a static_cast here since it really isn't an Containment by this
00484     // point anymore since we are in the qobject dtor. we don't actually
00485     // try and do anything with it, we just need the value of the pointer
00486     // so this unsafe looking code is actually just fine.
00487     Containment* containment = static_cast<Plasma::Containment*>(obj);
00488     int index = d->containments.indexOf(containment);
00489 
00490     if (index > -1) {
00491         d->containments.removeAt(index);
00492     }
00493 }
00494 
00495 void Corona::screenResized(int screen)
00496 {
00497     bool desktopFound = false;
00498     foreach (Containment *c, d->containments) {
00499         if (c->screen() == screen) {
00500             // trigger a relayout
00501             c->setScreen(screen);
00502             desktopFound = desktopFound || c->containmentType() == Containment::DesktopContainment;
00503         }
00504     }
00505 
00506     if (desktopFound) {
00507         return;
00508     }
00509 
00510     // a new screen appeared. neat.
00511     // FIXME: apparently QDesktopWidget doesn't do the Right Thing when a new screen is plugged in
00512     //        at runtime. seems it gets confused and thinks it's all one big screen? need to 
00513     //        fix this upstream
00514     Containment* c = addContainment("desktop");
00515     c->setScreen(screen);
00516     c->setFormFactor(Plasma::Planar);
00517     emit newScreen(screen);
00518 }
00519 
00520 void Corona::syncConfig()
00521 {
00522     config()->sync();
00523 }
00524 
00525 bool Corona::isImmutable() const
00526 {
00527     return d->kioskImmutable || d->immutable;
00528 }
00529 
00530 bool Corona::isKioskImmutable() const
00531 {
00532     return d->kioskImmutable;
00533 }
00534 
00535 void Corona::setImmutable(bool immutable)
00536 {
00537     if (d->immutable == immutable ||
00538         (!immutable && d->kioskImmutable)) {
00539         return;
00540     }
00541 
00542     kDebug() << "setting immutability to" << immutable;
00543     d->immutable = immutable;
00544     d->updateContainmentImmutability();
00545 }
00546 
00547 } // namespace Plasma
00548 
00549 #include "corona.moc"
00550 

libplasma

Skip menu "libplasma"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • KWin
  •   KWin Libraries
  • Libraries
  •   libkworkspace
  •   libplasma
  • Plasma
  •   Animators
  •   Applets
  •   Engines
  • Solid Modules
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal