Plasma
wallpaper.cpp
Go to the documentation of this file.
00001 /* 00002 * Copyright 2008 by Aaron Seigo <aseigo@kde.org> 00003 * Copyright 2008 by Petri Damsten <damu@iki.fi> 00004 * 00005 * This program is free software; you can redistribute it and/or modify 00006 * it under the terms of the GNU Library General Public License as 00007 * published by the Free Software Foundation; either version 2, or 00008 * (at your option) any later version. 00009 * 00010 * This program is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 * GNU General Public License for more details 00014 * 00015 * You should have received a copy of the GNU Library General Public 00016 * License along with this program; if not, write to the 00017 * Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "wallpaper.h" 00022 00023 #include "config-plasma.h" 00024 00025 #include <QColor> 00026 #include <QFile> 00027 #include <QFileInfo> 00028 #include <QImage> 00029 #include <QAction> 00030 #include <QQueue> 00031 #include <QTimer> 00032 #include <QRunnable> 00033 #include <QThreadPool> 00034 00035 #include <kdebug.h> 00036 #include <kglobal.h> 00037 #include <kservicetypetrader.h> 00038 #include <kstandarddirs.h> 00039 00040 #ifndef PLASMA_NO_KIO 00041 #include <kio/job.h> 00042 #endif 00043 00044 #ifndef PLASMA_NO_SOLID 00045 #include <solid/device.h> 00046 #include <solid/deviceinterface.h> 00047 #endif 00048 00049 #include <version.h> 00050 00051 #include "plasma/package.h" 00052 #include "plasma/private/dataengineconsumer_p.h" 00053 #include "plasma/private/packages_p.h" 00054 #include "plasma/private/wallpaper_p.h" 00055 00056 namespace Plasma 00057 { 00058 00059 class SaveImageThread : public QRunnable 00060 { 00061 QImage m_image; 00062 QString m_filePath; 00063 00064 public: 00065 SaveImageThread(const QImage &image, const QString &filePath) 00066 { 00067 m_image = image; 00068 m_filePath = filePath; 00069 } 00070 00071 void run() 00072 { 00073 m_image.save(m_filePath); 00074 } 00075 }; 00076 00077 LoadImageThread::LoadImageThread(const QString &filePath) 00078 { 00079 m_filePath = filePath; 00080 } 00081 00082 void LoadImageThread::run() 00083 { 00084 QImage image; 00085 image.load(m_filePath); 00086 emit done(image); 00087 } 00088 00089 class WallpaperWithPaint : public Wallpaper 00090 { 00091 public: 00092 WallpaperWithPaint(QObject *parent, const QVariantList &args) 00093 : Wallpaper(parent, args) 00094 { 00095 } 00096 00097 virtual void paint(QPainter *painter, const QRectF &exposedRect) 00098 { 00099 if (d->script) { 00100 d->script->paint(painter, exposedRect); 00101 } 00102 } 00103 }; 00104 00105 QList<WallpaperRenderThread *> WallpaperPrivate::s_renderers; 00106 QQueue<WallpaperPrivate::RenderRequest> WallpaperPrivate::s_renderQueue; 00107 PackageStructure::Ptr WallpaperPrivate::s_packageStructure(0); 00108 00109 Wallpaper::Wallpaper(QObject * parentObject) 00110 : d(new WallpaperPrivate(KService::serviceByStorageId(QString()), this)) 00111 { 00112 setParent(parentObject); 00113 } 00114 00115 Wallpaper::Wallpaper(QObject *parentObject, const QVariantList &args) 00116 : d(new WallpaperPrivate(KService::serviceByStorageId(args.count() > 0 ? 00117 args[0].toString() : QString()), this)) 00118 { 00119 // now remove first item since those are managed by Wallpaper and subclasses shouldn't 00120 // need to worry about them. yes, it violates the constness of this var, but it lets us add 00121 // or remove items later while applets can just pretend that their args always start at 0 00122 QVariantList &mutableArgs = const_cast<QVariantList &>(args); 00123 if (!mutableArgs.isEmpty()) { 00124 mutableArgs.removeFirst(); 00125 } 00126 00127 setParent(parentObject); 00128 } 00129 00130 Wallpaper::~Wallpaper() 00131 { 00132 delete d; 00133 } 00134 00135 KPluginInfo::List Wallpaper::listWallpaperInfo(const QString &formFactor) 00136 { 00137 QString constraint; 00138 if (!formFactor.isEmpty()) { 00139 constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); 00140 } 00141 00142 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint); 00143 return KPluginInfo::fromServices(offers); 00144 } 00145 00146 KPluginInfo::List Wallpaper::listWallpaperInfoForMimetype(const QString &mimetype, const QString &formFactor) 00147 { 00148 QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]").arg(mimetype); 00149 if (!formFactor.isEmpty()) { 00150 constraint.append("[X-Plasma-FormFactors] ~~ '").append(formFactor).append("'"); 00151 } 00152 00153 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint); 00154 kDebug() << offers.count() << constraint; 00155 return KPluginInfo::fromServices(offers); 00156 } 00157 00158 Wallpaper *Wallpaper::load(const QString &wallpaperName, const QVariantList &args) 00159 { 00160 if (wallpaperName.isEmpty()) { 00161 return 0; 00162 } 00163 00164 QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(wallpaperName); 00165 KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper", constraint); 00166 00167 if (offers.isEmpty()) { 00168 kDebug() << "offers is empty for " << wallpaperName; 00169 return 0; 00170 } 00171 00172 KService::Ptr offer = offers.first(); 00173 QVariantList allArgs; 00174 allArgs << offer->storageId() << args; 00175 00176 if (!offer->property("X-Plasma-API").toString().isEmpty()) { 00177 kDebug() << "we have a script using the" 00178 << offer->property("X-Plasma-API").toString() << "API"; 00179 return new WallpaperWithPaint(0, allArgs); 00180 } 00181 00182 KPluginLoader plugin(*offer); 00183 00184 if (!Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { 00185 return 0; 00186 } 00187 00188 QString error; 00189 Wallpaper *wallpaper = offer->createInstance<Plasma::Wallpaper>(0, allArgs, &error); 00190 00191 if (!wallpaper) { 00192 kDebug() << "Couldn't load wallpaper \"" << wallpaperName << "\"! reason given: " << error; 00193 } 00194 00195 return wallpaper; 00196 } 00197 00198 Wallpaper *Wallpaper::load(const KPluginInfo &info, const QVariantList &args) 00199 { 00200 if (!info.isValid()) { 00201 return 0; 00202 } 00203 return load(info.pluginName(), args); 00204 } 00205 00206 PackageStructure::Ptr Wallpaper::packageStructure(Wallpaper *paper) 00207 { 00208 if (paper) { 00209 PackageStructure::Ptr package(new WallpaperPackage(paper)); 00210 return package; 00211 } 00212 00213 if (!WallpaperPrivate::s_packageStructure) { 00214 WallpaperPrivate::s_packageStructure = new WallpaperPackage(); 00215 } 00216 00217 return WallpaperPrivate::s_packageStructure; 00218 } 00219 00220 QString Wallpaper::name() const 00221 { 00222 if (!d->wallpaperDescription.isValid()) { 00223 return i18n("Unknown Wallpaper"); 00224 } 00225 00226 return d->wallpaperDescription.name(); 00227 } 00228 00229 QString Wallpaper::icon() const 00230 { 00231 if (!d->wallpaperDescription.isValid()) { 00232 return QString(); 00233 } 00234 00235 return d->wallpaperDescription.icon(); 00236 } 00237 00238 QString Wallpaper::pluginName() const 00239 { 00240 if (!d->wallpaperDescription.isValid()) { 00241 return QString(); 00242 } 00243 00244 return d->wallpaperDescription.pluginName(); 00245 } 00246 00247 KServiceAction Wallpaper::renderingMode() const 00248 { 00249 return d->mode; 00250 } 00251 00252 QList<KServiceAction> Wallpaper::listRenderingModes() const 00253 { 00254 if (!d->wallpaperDescription.isValid()) { 00255 return QList<KServiceAction>(); 00256 } 00257 00258 return d->wallpaperDescription.service()->actions(); 00259 } 00260 00261 QRectF Wallpaper::boundingRect() const 00262 { 00263 return d->boundingRect; 00264 } 00265 00266 bool Wallpaper::isInitialized() const 00267 { 00268 return d->initialized; 00269 } 00270 00271 void Wallpaper::setBoundingRect(const QRectF &boundingRect) 00272 { 00273 QSizeF oldBoundingRectSize = d->boundingRect.size(); 00274 d->boundingRect = boundingRect; 00275 00276 if (!d->targetSize.isValid() || d->targetSize == oldBoundingRectSize) { 00277 d->targetSize = boundingRect.size(); 00278 emit renderHintsChanged(); 00279 } 00280 } 00281 00282 void Wallpaper::setRenderingMode(const QString &mode) 00283 { 00284 if (d->mode.name() == mode) { 00285 return; 00286 } 00287 00288 d->mode = KServiceAction(); 00289 if (!mode.isEmpty()) { 00290 QList<KServiceAction> modes = listRenderingModes(); 00291 00292 foreach (const KServiceAction &action, modes) { 00293 if (action.name() == mode) { 00294 d->mode = action; 00295 break; 00296 } 00297 } 00298 } 00299 } 00300 00301 void Wallpaper::restore(const KConfigGroup &config) 00302 { 00303 init(config); 00304 d->initialized = true; 00305 } 00306 00307 void Wallpaper::init(const KConfigGroup &config) 00308 { 00309 if (d->script) { 00310 d->initScript(); 00311 d->script->initWallpaper(config); 00312 } 00313 } 00314 00315 void Wallpaper::save(KConfigGroup &config) 00316 { 00317 if (d->script) { 00318 d->script->save(config); 00319 } 00320 } 00321 00322 QWidget *Wallpaper::createConfigurationInterface(QWidget *parent) 00323 { 00324 if (d->script) { 00325 return d->script->createConfigurationInterface(parent); 00326 } else { 00327 return 0; 00328 } 00329 } 00330 00331 void Wallpaper::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 00332 { 00333 if (d->script) { 00334 return d->script->mouseMoveEvent(event); 00335 } 00336 } 00337 00338 void Wallpaper::mousePressEvent(QGraphicsSceneMouseEvent *event) 00339 { 00340 if (d->script) { 00341 return d->script->mousePressEvent(event); 00342 } 00343 } 00344 00345 void Wallpaper::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) 00346 { 00347 if (d->script) { 00348 return d->script->mouseReleaseEvent(event); 00349 } 00350 } 00351 00352 void Wallpaper::wheelEvent(QGraphicsSceneWheelEvent *event) 00353 { 00354 if (d->script) { 00355 return d->script->wheelEvent(event); 00356 } 00357 } 00358 00359 DataEngine *Wallpaper::dataEngine(const QString &name) const 00360 { 00361 return d->dataEngine(name); 00362 } 00363 00364 bool Wallpaper::configurationRequired() const 00365 { 00366 return d->needsConfig; 00367 } 00368 00369 void Wallpaper::setConfigurationRequired(bool needsConfig, const QString &reason) 00370 { 00371 //TODO: implement something for reason. first, we need to decide where/how 00372 // to communicate it to the user 00373 Q_UNUSED(reason) 00374 00375 if (d->needsConfig == needsConfig) { 00376 return; 00377 } 00378 00379 d->needsConfig = needsConfig; 00380 emit configurationRequired(needsConfig); 00381 } 00382 00383 bool Wallpaper::isUsingRenderingCache() const 00384 { 00385 return d->cacheRendering; 00386 } 00387 00388 void Wallpaper::setUsingRenderingCache(bool useCache) 00389 { 00390 d->cacheRendering = useCache; 00391 } 00392 00393 void Wallpaper::setResizeMethodHint(Wallpaper::ResizeMethod resizeMethod) 00394 { 00395 d->lastResizeMethod = resizeMethod; 00396 emit renderHintsChanged(); 00397 } 00398 00399 void Wallpaper::setTargetSizeHint(const QSizeF &targetSize) 00400 { 00401 d->targetSize = targetSize; 00402 emit renderHintsChanged(); 00403 } 00404 00405 void Wallpaper::render(const QString &sourceImagePath, const QSize &size, 00406 Wallpaper::ResizeMethod resizeMethod, const QColor &color) 00407 { 00408 if (sourceImagePath.isEmpty() || !QFile::exists(sourceImagePath)) { 00409 //kDebug() << "failed on:" << sourceImagePath; 00410 return; 00411 } 00412 00413 if (d->lastResizeMethod != resizeMethod) { 00414 d->lastResizeMethod = resizeMethod; 00415 emit renderHintsChanged(); 00416 } 00417 00418 if (d->cacheRendering) { 00419 QFileInfo info(sourceImagePath); 00420 QString cache = d->cacheKey(sourceImagePath, size, resizeMethod, color); 00421 if (d->findInCache(cache, info.lastModified().toTime_t())) { 00422 return; 00423 } 00424 } 00425 00426 for (int i = 0; i < WallpaperPrivate::s_renderers.size(); i++) { 00427 if (d->renderToken == WallpaperPrivate::s_renderers[i]->currentToken()) { 00428 d->renderToken = WallpaperPrivate::s_renderers[i]->render(sourceImagePath, size, resizeMethod, color); 00429 return; 00430 } 00431 } 00432 00433 #ifndef PLASMA_NO_SOLID 00434 const int numProcs = Solid::Device::listFromType(Solid::DeviceInterface::Processor).count(); 00435 #else 00436 const int numProcs = 1; 00437 #endif 00438 if (WallpaperPrivate::s_renderers.size() < qMax(numProcs, 1)) { 00439 WallpaperRenderThread *renderThread = new WallpaperRenderThread(); 00440 WallpaperPrivate::s_renderers.append(renderThread); 00441 d->renderToken = renderThread->render(sourceImagePath, size, resizeMethod, color); 00442 connect(renderThread, SIGNAL(done(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor)), 00443 this, SLOT(newRenderCompleted(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor)), Qt::UniqueConnection); 00444 } else { 00445 WallpaperPrivate::RenderRequest request; 00446 request.parent = this; 00447 request.file = sourceImagePath; 00448 request.size = size; 00449 request.resizeMethod = resizeMethod; 00450 request.color = color; 00451 00452 for (int i = 0; i < WallpaperPrivate::s_renderQueue.size(); i++) { 00453 if (WallpaperPrivate::s_renderQueue[i].parent.data() == this){ 00454 WallpaperPrivate::s_renderQueue[i] = request; 00455 return; 00456 } 00457 } 00458 WallpaperPrivate::s_renderQueue.append(request); 00459 } 00460 //kDebug() << "rendering" << sourceImagePath << ", token is" << d->renderToken; 00461 } 00462 00463 WallpaperPrivate::WallpaperPrivate(KService::Ptr service, Wallpaper *wallpaper) : 00464 q(wallpaper), 00465 wallpaperDescription(service), 00466 package(0), 00467 renderToken(-1), 00468 lastResizeMethod(Wallpaper::ScaledResize), 00469 script(0), 00470 cacheRendering(false), 00471 initialized(false), 00472 needsConfig(false), 00473 scriptInitialized(false), 00474 previewing(false), 00475 needsPreviewDuringConfiguration(false) 00476 { 00477 if (wallpaperDescription.isValid()) { 00478 QString api = wallpaperDescription.property("X-Plasma-API").toString(); 00479 00480 if (!api.isEmpty()) { 00481 const QString path = KStandardDirs::locate("data", 00482 "plasma/wallpapers/" + wallpaperDescription.pluginName() + '/'); 00483 PackageStructure::Ptr structure = 00484 Plasma::packageStructure(api, Plasma::WallpaperComponent); 00485 structure->setPath(path); 00486 package = new Package(path, structure); 00487 00488 script = Plasma::loadScriptEngine(api, q); 00489 if (!script) { 00490 kDebug() << "Could not create a" << api << "ScriptEngine for the" 00491 << wallpaperDescription.name() << "Wallpaper."; 00492 delete package; 00493 package = 0; 00494 } 00495 } 00496 } 00497 } 00498 00499 QString WallpaperPrivate::cacheKey(const QString &sourceImagePath, const QSize &size, 00500 int resizeMethod, const QColor &color) const 00501 { 00502 const QString id = QString("%5_%3_%4_%1x%2") 00503 .arg(size.width()).arg(size.height()).arg(color.name()) 00504 .arg(resizeMethod).arg(sourceImagePath); 00505 return id; 00506 } 00507 00508 QString WallpaperPrivate::cachePath(const QString &key) const 00509 { 00510 return KGlobal::dirs()->locateLocal("cache", "plasma-wallpapers/" + key + ".png"); 00511 } 00512 00513 void WallpaperPrivate::newRenderCompleted(WallpaperRenderThread *currentRenderer, int token, const QImage &image, 00514 const QString &sourceImagePath, const QSize &size, 00515 int resizeMethod, const QColor &color) 00516 { 00517 q->disconnect(currentRenderer, SIGNAL(done(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor)), 00518 q, SLOT(newRenderCompleted(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor))); 00519 00520 if (!s_renderQueue.isEmpty()) { 00521 while (!s_renderQueue.isEmpty()) { 00522 WallpaperPrivate::RenderRequest request = s_renderQueue.dequeue(); 00523 00524 if (!request.parent) { 00525 continue; 00526 } 00527 00528 currentRenderer->wait(); 00529 request.parent.data()->d->renderToken = currentRenderer->render(request.file, request.size, request.resizeMethod, request.color); 00530 QObject::connect(currentRenderer, SIGNAL(done(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor)), 00531 request.parent.data(), SLOT(newRenderCompleted(WallpaperRenderThread*,int,QImage,QString,QSize,int,QColor)), Qt::UniqueConnection); 00532 00533 break; 00534 } 00535 } else { 00536 for (int i = 0; i < s_renderers.size(); i++) { 00537 if (s_renderers[i] == currentRenderer){ 00538 s_renderers.removeAt(i); 00539 } 00540 } 00541 currentRenderer->deleteLater(); 00542 currentRenderer = 0; 00543 } 00544 00545 if (token != renderToken) { 00546 //kDebug() << "render token mismatch" << token << renderToken; 00547 return; 00548 } 00549 00550 if (cacheRendering) { 00551 q->insertIntoCache(cacheKey(sourceImagePath, size, resizeMethod, color), image); 00552 } 00553 00554 //kDebug() << "rendering complete!"; 00555 emit q->renderCompleted(image); 00556 } 00557 00558 // put all setup routines for script here. at this point we can assume that 00559 // package exists and that we have a script engine 00560 void WallpaperPrivate::setupScriptSupport() 00561 { 00562 Q_ASSERT(package); 00563 kDebug() << "setting up script support, package is in" << package->path() 00564 << "which is a" << package->structure()->type() << "package" 00565 << ", main script is" << package->filePath("mainscript"); 00566 00567 QString translationsPath = package->filePath("translations"); 00568 if (!translationsPath.isEmpty()) { 00569 //FIXME: we should _probably_ use a KComponentData to segregate the applets 00570 // from each other; but I want to get the basics working first :) 00571 KGlobal::dirs()->addResourceDir("locale", translationsPath); 00572 KGlobal::locale()->insertCatalog(package->metadata().pluginName()); 00573 } 00574 } 00575 00576 void WallpaperPrivate::initScript() 00577 { 00578 if (script && !scriptInitialized) { 00579 setupScriptSupport(); 00580 script->init(); 00581 scriptInitialized = true; 00582 } 00583 } 00584 00585 bool WallpaperPrivate::findInCache(const QString &key, unsigned int lastModified) 00586 { 00587 if (cacheRendering) { 00588 QString cache = cachePath(key); 00589 if (QFile::exists(cache)) { 00590 if (lastModified > 0) { 00591 QFileInfo info(cache); 00592 if (info.lastModified().toTime_t() < lastModified) { 00593 return false; 00594 } 00595 } 00596 00597 LoadImageThread *loadImageT = new LoadImageThread(cache); 00598 q->connect(loadImageT, SIGNAL(done(const QImage&)), q, SIGNAL(renderCompleted(const QImage&))); 00599 QThreadPool::globalInstance()->start(loadImageT); 00600 00601 return true; 00602 } 00603 } 00604 00605 return false; 00606 } 00607 00608 bool Wallpaper::findInCache(const QString &key, QImage &image, unsigned int lastModified) 00609 { 00610 if (d->cacheRendering) { 00611 QString cache = d->cachePath(key); 00612 if (QFile::exists(cache)) { 00613 if (lastModified > 0) { 00614 QFileInfo info(cache); 00615 if (info.lastModified().toTime_t() < lastModified) { 00616 return false; 00617 } 00618 } 00619 00620 image.load(cache); 00621 return true; 00622 } 00623 } 00624 00625 return false; 00626 } 00627 00628 void Wallpaper::insertIntoCache(const QString& key, const QImage &image) 00629 { 00630 //TODO: cache limits? 00631 if (key.isEmpty()) { 00632 return; 00633 } 00634 00635 if (d->cacheRendering) { 00636 if (image.isNull()) { 00637 #ifndef PLASMA_NO_KIO 00638 KIO::file_delete(d->cachePath(key)); 00639 #else 00640 QFile f(d->cachePath(key)); 00641 f.remove(); 00642 #endif 00643 } else { 00644 QThreadPool::globalInstance()->start(new SaveImageThread(image, d->cachePath(key))); 00645 } 00646 } 00647 } 00648 00649 QList<QAction*> Wallpaper::contextualActions() const 00650 { 00651 return contextActions; 00652 } 00653 00654 void Wallpaper::setContextualActions(const QList<QAction*> &actions) 00655 { 00656 contextActions = actions; 00657 } 00658 00659 bool Wallpaper::isPreviewing() const 00660 { 00661 return d->previewing; 00662 } 00663 00664 void Wallpaper::setPreviewing(bool previewing) 00665 { 00666 d->previewing = previewing; 00667 } 00668 00669 bool Wallpaper::needsPreviewDuringConfiguration() const 00670 { 00671 return d->needsPreviewDuringConfiguration; 00672 } 00673 00674 void Wallpaper::setPreviewDuringConfiguration(const bool preview) 00675 { 00676 d->needsPreviewDuringConfiguration = preview; 00677 } 00678 00679 const Package *Wallpaper::package() const 00680 { 00681 return d->package; 00682 } 00683 00684 } // Plasma namespace 00685 00686 #include "wallpaper.moc" 00687 #include "private/wallpaper_p.moc"
KDE 4.6 API Reference