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