1 | /* |
2 | * Copyright 2008 by Aaron Seigo <aseigo@kde.org> |
3 | * Copyright 2008 by Petri Damsten <damu@iki.fi> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU Library General Public License as |
7 | * published by the Free Software Foundation; either version 2, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this program; if not, write to the |
17 | * Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #include "wallpaper.h" |
22 | |
23 | #include "config-plasma.h" |
24 | |
25 | #include <QColor> |
26 | #include <QFile> |
27 | #include <QFileInfo> |
28 | #include <QImage> |
29 | #include <QAction> |
30 | #include <QQueue> |
31 | #include <QTimer> |
32 | #include <QRunnable> |
33 | #include <QThreadPool> |
34 | |
35 | #include <kdebug.h> |
36 | #include <kglobal.h> |
37 | #include <kservicetypetrader.h> |
38 | #include <kstandarddirs.h> |
39 | |
40 | #ifndef PLASMA_NO_KIO |
41 | #include <kio/job.h> |
42 | #endif |
43 | |
44 | #include <version.h> |
45 | |
46 | #include "plasma/package.h" |
47 | #include "plasma/private/dataengineconsumer_p.h" |
48 | #include "plasma/private/packages_p.h" |
49 | #include "plasma/private/wallpaper_p.h" |
50 | |
51 | namespace Plasma |
52 | { |
53 | |
54 | class SaveImageThread : public QRunnable |
55 | { |
56 | QImage m_image; |
57 | QString m_filePath; |
58 | |
59 | public: |
60 | SaveImageThread(const QImage &image, const QString &filePath) |
61 | { |
62 | m_image = image; |
63 | m_filePath = filePath; |
64 | } |
65 | |
66 | void run() |
67 | { |
68 | m_image.save(m_filePath); |
69 | } |
70 | }; |
71 | |
72 | LoadImageThread::LoadImageThread(const QString &filePath) |
73 | { |
74 | m_filePath = filePath; |
75 | } |
76 | |
77 | void LoadImageThread::run() |
78 | { |
79 | QImage image; |
80 | image.load(m_filePath); |
81 | emit done(image); |
82 | } |
83 | |
84 | class WallpaperWithPaint : public Wallpaper |
85 | { |
86 | public: |
87 | WallpaperWithPaint(QObject *parent, const QVariantList &args) |
88 | : Wallpaper(parent, args) |
89 | { |
90 | } |
91 | |
92 | virtual void paint(QPainter *painter, const QRectF &exposedRect) |
93 | { |
94 | if (d->script) { |
95 | d->script->paint(painter, exposedRect); |
96 | } |
97 | } |
98 | }; |
99 | |
100 | PackageStructure::Ptr WallpaperPrivate::s_packageStructure(0); |
101 | |
102 | Wallpaper::Wallpaper(QObject * parentObject) |
103 | : d(new WallpaperPrivate(KService::serviceByStorageId(QString()), this)) |
104 | { |
105 | setParent(parentObject); |
106 | } |
107 | |
108 | Wallpaper::Wallpaper(QObject *parentObject, const QVariantList &args) |
109 | : d(new WallpaperPrivate(KService::serviceByStorageId(args.count() > 0 ? |
110 | args[0].toString() : QString()), this)) |
111 | { |
112 | // now remove first item since those are managed by Wallpaper and subclasses shouldn't |
113 | // need to worry about them. yes, it violates the constness of this var, but it lets us add |
114 | // or remove items later while applets can just pretend that their args always start at 0 |
115 | QVariantList &mutableArgs = const_cast<QVariantList &>(args); |
116 | if (!mutableArgs.isEmpty()) { |
117 | mutableArgs.removeFirst(); |
118 | } |
119 | |
120 | setParent(parentObject); |
121 | } |
122 | |
123 | Wallpaper::~Wallpaper() |
124 | { |
125 | delete d; |
126 | } |
127 | |
128 | void Wallpaper::addUrls(const KUrl::List &urls) |
129 | { |
130 | if (d->script) { |
131 | d->script->setUrls(urls); |
132 | } else { |
133 | // provide compatibility with urlDropped |
134 | foreach (const KUrl &url, urls) { |
135 | emit urlDropped(url); |
136 | } |
137 | } |
138 | } |
139 | |
140 | void Wallpaper::setUrls(const KUrl::List &urls) |
141 | { |
142 | if (!d->initialized) { |
143 | d->pendingUrls = urls; |
144 | } else if (d->script) { |
145 | d->script->setUrls(urls); |
146 | } else { |
147 | QMetaObject::invokeMethod(this, "addUrls" , Q_ARG(KUrl::List, urls)); |
148 | } |
149 | } |
150 | |
151 | KPluginInfo::List Wallpaper::listWallpaperInfo(const QString &formFactor) |
152 | { |
153 | QString constraint; |
154 | if (!formFactor.isEmpty()) { |
155 | constraint.append("[X-Plasma-FormFactors] ~~ '" ).append(formFactor).append("'" ); |
156 | } |
157 | |
158 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper" , constraint); |
159 | return KPluginInfo::fromServices(offers); |
160 | } |
161 | |
162 | KPluginInfo::List Wallpaper::listWallpaperInfoForMimetype(const QString &mimetype, const QString &formFactor) |
163 | { |
164 | QString constraint = QString("'%1' in [X-Plasma-DropMimeTypes]" ).arg(mimetype); |
165 | if (!formFactor.isEmpty()) { |
166 | constraint.append("[X-Plasma-FormFactors] ~~ '" ).append(formFactor).append("'" ); |
167 | } |
168 | |
169 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper" , constraint); |
170 | kDebug() << offers.count() << constraint; |
171 | return KPluginInfo::fromServices(offers); |
172 | } |
173 | |
174 | bool Wallpaper::supportsMimetype(const QString &mimetype) const |
175 | { |
176 | return d->wallpaperDescription.isValid() && |
177 | d->wallpaperDescription.service()->hasMimeType(mimetype); |
178 | } |
179 | |
180 | Wallpaper *Wallpaper::load(const QString &wallpaperName, const QVariantList &args) |
181 | { |
182 | if (wallpaperName.isEmpty()) { |
183 | return 0; |
184 | } |
185 | |
186 | QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'" ).arg(wallpaperName); |
187 | KService::List offers = KServiceTypeTrader::self()->query("Plasma/Wallpaper" , constraint); |
188 | |
189 | if (offers.isEmpty()) { |
190 | kDebug() << "offers is empty for " << wallpaperName; |
191 | return 0; |
192 | } |
193 | |
194 | KService::Ptr offer = offers.first(); |
195 | QVariantList allArgs; |
196 | allArgs << offer->storageId() << args; |
197 | |
198 | if (!offer->property("X-Plasma-API" ).toString().isEmpty()) { |
199 | kDebug() << "we have a script using the" |
200 | << offer->property("X-Plasma-API" ).toString() << "API" ; |
201 | return new WallpaperWithPaint(0, allArgs); |
202 | } |
203 | |
204 | KPluginLoader plugin(*offer); |
205 | |
206 | if (!Plasma::isPluginVersionCompatible(plugin.pluginVersion())) { |
207 | return 0; |
208 | } |
209 | |
210 | QString error; |
211 | Wallpaper *wallpaper = offer->createInstance<Plasma::Wallpaper>(0, allArgs, &error); |
212 | |
213 | if (!wallpaper) { |
214 | kDebug() << "Couldn't load wallpaper \"" << wallpaperName << "\"! reason given: " << error; |
215 | } |
216 | |
217 | return wallpaper; |
218 | } |
219 | |
220 | Wallpaper *Wallpaper::load(const KPluginInfo &info, const QVariantList &args) |
221 | { |
222 | if (!info.isValid()) { |
223 | return 0; |
224 | } |
225 | return load(info.pluginName(), args); |
226 | } |
227 | |
228 | PackageStructure::Ptr Wallpaper::packageStructure(Wallpaper *paper) |
229 | { |
230 | if (paper) { |
231 | PackageStructure::Ptr package(new WallpaperPackage(paper)); |
232 | return package; |
233 | } |
234 | |
235 | if (!WallpaperPrivate::s_packageStructure) { |
236 | WallpaperPrivate::s_packageStructure = new WallpaperPackage(); |
237 | } |
238 | |
239 | return WallpaperPrivate::s_packageStructure; |
240 | } |
241 | |
242 | QString Wallpaper::name() const |
243 | { |
244 | if (!d->wallpaperDescription.isValid()) { |
245 | return i18n("Unknown Wallpaper" ); |
246 | } |
247 | |
248 | return d->wallpaperDescription.name(); |
249 | } |
250 | |
251 | QString Wallpaper::icon() const |
252 | { |
253 | if (!d->wallpaperDescription.isValid()) { |
254 | return QString(); |
255 | } |
256 | |
257 | return d->wallpaperDescription.icon(); |
258 | } |
259 | |
260 | QString Wallpaper::pluginName() const |
261 | { |
262 | if (!d->wallpaperDescription.isValid()) { |
263 | return QString(); |
264 | } |
265 | |
266 | return d->wallpaperDescription.pluginName(); |
267 | } |
268 | |
269 | KServiceAction Wallpaper::renderingMode() const |
270 | { |
271 | return d->mode; |
272 | } |
273 | |
274 | QList<KServiceAction> Wallpaper::listRenderingModes() const |
275 | { |
276 | if (!d->wallpaperDescription.isValid()) { |
277 | return QList<KServiceAction>(); |
278 | } |
279 | |
280 | return d->wallpaperDescription.service()->actions(); |
281 | } |
282 | |
283 | QRectF Wallpaper::boundingRect() const |
284 | { |
285 | return d->boundingRect; |
286 | } |
287 | |
288 | bool Wallpaper::isInitialized() const |
289 | { |
290 | return d->initialized; |
291 | } |
292 | |
293 | void Wallpaper::setBoundingRect(const QRectF &boundingRect) |
294 | { |
295 | d->boundingRect = boundingRect; |
296 | |
297 | if (d->targetSize != boundingRect.size()) { |
298 | d->targetSize = boundingRect.size(); |
299 | emit renderHintsChanged(); |
300 | } |
301 | } |
302 | |
303 | void Wallpaper::setRenderingMode(const QString &mode) |
304 | { |
305 | if (d->mode.name() == mode) { |
306 | return; |
307 | } |
308 | |
309 | d->mode = KServiceAction(); |
310 | if (!mode.isEmpty()) { |
311 | QList<KServiceAction> modes = listRenderingModes(); |
312 | |
313 | foreach (const KServiceAction &action, modes) { |
314 | if (action.name() == mode) { |
315 | d->mode = action; |
316 | break; |
317 | } |
318 | } |
319 | } |
320 | } |
321 | |
322 | void Wallpaper::restore(const KConfigGroup &config) |
323 | { |
324 | init(config); |
325 | d->initialized = true; |
326 | if (!d->pendingUrls.isEmpty()) { |
327 | setUrls(d->pendingUrls); |
328 | d->pendingUrls.clear(); |
329 | } |
330 | } |
331 | |
332 | void Wallpaper::init(const KConfigGroup &config) |
333 | { |
334 | if (d->script) { |
335 | d->initScript(); |
336 | d->script->initWallpaper(config); |
337 | } |
338 | } |
339 | |
340 | void Wallpaper::save(KConfigGroup &config) |
341 | { |
342 | if (d->script) { |
343 | d->script->save(config); |
344 | } |
345 | } |
346 | |
347 | QWidget *Wallpaper::createConfigurationInterface(QWidget *parent) |
348 | { |
349 | if (d->script) { |
350 | return d->script->createConfigurationInterface(parent); |
351 | } else { |
352 | return 0; |
353 | } |
354 | } |
355 | |
356 | void Wallpaper::mouseMoveEvent(QGraphicsSceneMouseEvent *event) |
357 | { |
358 | if (d->script) { |
359 | return d->script->mouseMoveEvent(event); |
360 | } |
361 | } |
362 | |
363 | void Wallpaper::mousePressEvent(QGraphicsSceneMouseEvent *event) |
364 | { |
365 | if (d->script) { |
366 | return d->script->mousePressEvent(event); |
367 | } |
368 | } |
369 | |
370 | void Wallpaper::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
371 | { |
372 | if (d->script) { |
373 | return d->script->mouseReleaseEvent(event); |
374 | } |
375 | } |
376 | |
377 | void Wallpaper::wheelEvent(QGraphicsSceneWheelEvent *event) |
378 | { |
379 | if (d->script) { |
380 | return d->script->wheelEvent(event); |
381 | } |
382 | } |
383 | |
384 | DataEngine *Wallpaper::dataEngine(const QString &name) const |
385 | { |
386 | return d->dataEngine(name); |
387 | } |
388 | |
389 | bool Wallpaper::configurationRequired() const |
390 | { |
391 | return d->needsConfig; |
392 | } |
393 | |
394 | void Wallpaper::setConfigurationRequired(bool needsConfig, const QString &reason) |
395 | { |
396 | //TODO: implement something for reason. first, we need to decide where/how |
397 | // to communicate it to the user |
398 | Q_UNUSED(reason) |
399 | |
400 | if (d->needsConfig == needsConfig) { |
401 | return; |
402 | } |
403 | |
404 | d->needsConfig = needsConfig; |
405 | emit configurationRequired(needsConfig); |
406 | } |
407 | |
408 | bool Wallpaper::isUsingRenderingCache() const |
409 | { |
410 | return d->cacheRendering; |
411 | } |
412 | |
413 | void Wallpaper::setUsingRenderingCache(bool useCache) |
414 | { |
415 | d->cacheRendering = useCache; |
416 | } |
417 | |
418 | void Wallpaper::setResizeMethodHint(Wallpaper::ResizeMethod resizeMethod) |
419 | { |
420 | const ResizeMethod method = qBound(ScaledResize, resizeMethod, LastResizeMethod); |
421 | if (method != d->lastResizeMethod) { |
422 | d->lastResizeMethod = method; |
423 | emit renderHintsChanged(); |
424 | } |
425 | } |
426 | |
427 | Wallpaper::ResizeMethod Wallpaper::resizeMethodHint() const |
428 | { |
429 | return d->lastResizeMethod; |
430 | } |
431 | |
432 | void Wallpaper::setTargetSizeHint(const QSizeF &targetSize) |
433 | { |
434 | if (targetSize != d->targetSize) { |
435 | d->targetSize = targetSize; |
436 | emit renderHintsChanged(); |
437 | } |
438 | } |
439 | |
440 | QSizeF Wallpaper::targetSizeHint() const |
441 | { |
442 | return d->targetSize; |
443 | } |
444 | |
445 | void Wallpaper::render(const QImage &image, const QSize &size, |
446 | Wallpaper::ResizeMethod resizeMethod, const QColor &color) |
447 | { |
448 | if (image.isNull()) { |
449 | return; |
450 | } |
451 | |
452 | d->renderWallpaper(QString(), image, size, resizeMethod, color); |
453 | } |
454 | |
455 | void Wallpaper::render(const QString &sourceImagePath, const QSize &size, |
456 | Wallpaper::ResizeMethod resizeMethod, const QColor &color) |
457 | { |
458 | if (sourceImagePath.isEmpty() || !QFile::exists(sourceImagePath)) { |
459 | //kDebug() << "failed on:" << sourceImagePath; |
460 | return; |
461 | } |
462 | |
463 | d->renderWallpaper(sourceImagePath, QImage(), size, resizeMethod, color); |
464 | } |
465 | |
466 | void WallpaperPrivate::renderWallpaper(const QString &sourceImagePath, const QImage &image, const QSize &size, |
467 | Wallpaper::ResizeMethod resizeMethod, const QColor &color) |
468 | { |
469 | resizeMethod = qBound(Wallpaper::ScaledResize, resizeMethod, Wallpaper::LastResizeMethod); |
470 | if (lastResizeMethod != resizeMethod) { |
471 | lastResizeMethod = resizeMethod; |
472 | emit q->renderHintsChanged(); |
473 | } |
474 | |
475 | if (cacheRendering) { |
476 | QFileInfo info(sourceImagePath); |
477 | QString cache = cacheKey(sourceImagePath, size, resizeMethod, color); |
478 | if (findInCache(cache, info.lastModified().toTime_t())) { |
479 | return; |
480 | } |
481 | } |
482 | |
483 | WallpaperRenderRequest request; |
484 | renderToken = request.token; |
485 | request.requester = q; |
486 | request.providedImage = image; |
487 | request.file = sourceImagePath; |
488 | request.size = size; |
489 | request.resizeMethod = resizeMethod; |
490 | request.color = color; |
491 | WallpaperRenderThread::render(request); |
492 | //kDebug() << "rendering" << sourceImagePath << ", token is" << d->renderToken; |
493 | } |
494 | |
495 | WallpaperPrivate::WallpaperPrivate(KService::Ptr service, Wallpaper *wallpaper) : |
496 | q(wallpaper), |
497 | wallpaperDescription(service), |
498 | package(0), |
499 | renderToken(-1), |
500 | lastResizeMethod(Wallpaper::ScaledResize), |
501 | script(0), |
502 | cacheRendering(false), |
503 | initialized(false), |
504 | needsConfig(false), |
505 | scriptInitialized(false), |
506 | previewing(false), |
507 | needsPreviewDuringConfiguration(false) |
508 | { |
509 | if (wallpaperDescription.isValid()) { |
510 | QString api = wallpaperDescription.property("X-Plasma-API" ).toString(); |
511 | |
512 | if (!api.isEmpty()) { |
513 | const QString path = KStandardDirs::locate("data" , |
514 | "plasma/wallpapers/" + wallpaperDescription.pluginName() + '/'); |
515 | PackageStructure::Ptr structure = |
516 | Plasma::packageStructure(api, Plasma::WallpaperComponent); |
517 | structure->setPath(path); |
518 | package = new Package(path, structure); |
519 | |
520 | script = Plasma::loadScriptEngine(api, q); |
521 | if (!script) { |
522 | kDebug() << "Could not create a" << api << "ScriptEngine for the" |
523 | << wallpaperDescription.name() << "Wallpaper." ; |
524 | delete package; |
525 | package = 0; |
526 | } |
527 | } |
528 | } |
529 | } |
530 | |
531 | QString WallpaperPrivate::cacheKey(const QString &sourceImagePath, const QSize &size, |
532 | int resizeMethod, const QColor &color) const |
533 | { |
534 | const QString id = QString("%5_%3_%4_%1x%2" ) |
535 | .arg(size.width()).arg(size.height()).arg(color.name()) |
536 | .arg(resizeMethod).arg(sourceImagePath); |
537 | return id; |
538 | } |
539 | |
540 | QString WallpaperPrivate::cachePath(const QString &key) const |
541 | { |
542 | return KGlobal::dirs()->locateLocal("cache" , "plasma-wallpapers/" + key + ".png" ); |
543 | } |
544 | |
545 | void WallpaperPrivate::newRenderCompleted(const WallpaperRenderRequest &request, const QImage &image) |
546 | { |
547 | kDebug() << request.token << renderToken; |
548 | if (request.token != renderToken) { |
549 | //kDebug() << "render token mismatch" << token << renderToken; |
550 | return; |
551 | } |
552 | |
553 | if (cacheRendering) { |
554 | q->insertIntoCache(cacheKey(request.file, request.size, request.resizeMethod, request.color), image); |
555 | } |
556 | |
557 | //kDebug() << "rendering complete!"; |
558 | emit q->renderCompleted(image); |
559 | } |
560 | |
561 | // put all setup routines for script here. at this point we can assume that |
562 | // package exists and that we have a script engine |
563 | void WallpaperPrivate::setupScriptSupport() |
564 | { |
565 | Q_ASSERT(package); |
566 | kDebug() << "setting up script support, package is in" << package->path() |
567 | << "which is a" << package->structure()->type() << "package" |
568 | << ", main script is" << package->filePath("mainscript" ); |
569 | |
570 | QString translationsPath = package->filePath("translations" ); |
571 | if (!translationsPath.isEmpty()) { |
572 | //FIXME: we should _probably_ use a KComponentData to segregate the applets |
573 | // from each other; but I want to get the basics working first :) |
574 | KGlobal::dirs()->addResourceDir("locale" , translationsPath); |
575 | KGlobal::locale()->insertCatalog(package->metadata().pluginName()); |
576 | } |
577 | } |
578 | |
579 | void WallpaperPrivate::initScript() |
580 | { |
581 | if (script && !scriptInitialized) { |
582 | setupScriptSupport(); |
583 | script->init(); |
584 | scriptInitialized = true; |
585 | } |
586 | } |
587 | |
588 | bool WallpaperPrivate::findInCache(const QString &key, unsigned int lastModified) |
589 | { |
590 | if (cacheRendering) { |
591 | QString cache = cachePath(key); |
592 | if (QFile::exists(cache)) { |
593 | if (lastModified > 0) { |
594 | QFileInfo info(cache); |
595 | if (info.lastModified().toTime_t() < lastModified) { |
596 | return false; |
597 | } |
598 | } |
599 | |
600 | LoadImageThread *loadImageT = new LoadImageThread(cache); |
601 | q->connect(loadImageT, SIGNAL(done(QImage)), q, SIGNAL(renderCompleted(QImage))); |
602 | QThreadPool::globalInstance()->start(loadImageT); |
603 | |
604 | return true; |
605 | } |
606 | } |
607 | |
608 | return false; |
609 | } |
610 | |
611 | bool Wallpaper::findInCache(const QString &key, QImage &image, unsigned int lastModified) |
612 | { |
613 | if (d->cacheRendering) { |
614 | QString cache = d->cachePath(key); |
615 | if (QFile::exists(cache)) { |
616 | if (lastModified > 0) { |
617 | QFileInfo info(cache); |
618 | if (info.lastModified().toTime_t() < lastModified) { |
619 | return false; |
620 | } |
621 | } |
622 | |
623 | image.load(cache); |
624 | return true; |
625 | } |
626 | } |
627 | |
628 | return false; |
629 | } |
630 | |
631 | void Wallpaper::insertIntoCache(const QString& key, const QImage &image) |
632 | { |
633 | //TODO: cache limits? |
634 | if (key.isEmpty()) { |
635 | return; |
636 | } |
637 | |
638 | if (d->cacheRendering) { |
639 | if (image.isNull()) { |
640 | #ifndef PLASMA_NO_KIO |
641 | KIO::file_delete(d->cachePath(key)); |
642 | #else |
643 | QFile f(d->cachePath(key)); |
644 | f.remove(); |
645 | #endif |
646 | } else { |
647 | QThreadPool::globalInstance()->start(new SaveImageThread(image, d->cachePath(key))); |
648 | } |
649 | } |
650 | } |
651 | |
652 | QList<QAction*> Wallpaper::contextualActions() const |
653 | { |
654 | return contextActions; |
655 | } |
656 | |
657 | void Wallpaper::setContextualActions(const QList<QAction*> &actions) |
658 | { |
659 | contextActions = actions; |
660 | } |
661 | |
662 | bool Wallpaper::isPreviewing() const |
663 | { |
664 | return d->previewing; |
665 | } |
666 | |
667 | void Wallpaper::setPreviewing(bool previewing) |
668 | { |
669 | d->previewing = previewing; |
670 | } |
671 | |
672 | bool Wallpaper::needsPreviewDuringConfiguration() const |
673 | { |
674 | return d->needsPreviewDuringConfiguration; |
675 | } |
676 | |
677 | void Wallpaper::setPreviewDuringConfiguration(const bool preview) |
678 | { |
679 | d->needsPreviewDuringConfiguration = preview; |
680 | } |
681 | |
682 | const Package *Wallpaper::package() const |
683 | { |
684 | return d->package; |
685 | } |
686 | |
687 | } // Plasma namespace |
688 | |
689 | #include "wallpaper.moc" |
690 | #include "private/wallpaper_p.moc" |
691 | |