Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / kcontrol / kdm / background / bgrender.cpp
blob89c0a506a166ce9928c6db562e0b0862c09758e4
1 /* vi: ts=8 sts=4 sw=4
2 * kate: space-indent on; tab-width 8; indent-width 4; indent-mode cstyle;
4 * This file is part of the KDE project, module kdesktop.
5 * Copyright (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl>
7 * You can Freely distribute this program under the GNU Library General
8 * Public License. See the file "COPYING.LIB" for the exact licensing terms.
9 */
10 #include "bgrender.h"
12 #include <fixx11h.h>
13 #include <config-workspace.h>
15 #include <time.h>
16 #include <stdlib.h>
17 #include <utime.h>
19 #include <QTimer>
20 #include <QPainter>
21 #include <QImage>
22 #include <QFileInfo>
23 #include <QDir>
24 #include <QDesktopWidget>
25 #include <QPaintEngine>
26 #include <QHash>
27 #include <QDateTime>
28 #include <QPixmap>
30 #include <kapplication.h>
31 #include <kdebug.h>
32 #include <kstandarddirs.h>
33 #include <qimageblitz.h>
34 #include <k3process.h>
35 #include <ktemporaryfile.h>
36 #include <kcursor.h>
37 #include <kfilemetainfo.h>
38 #include <kconfig.h>
39 #include <kconfiggroup.h>
40 #include <ksvgrenderer.h>
42 #include "bgdefaults.h"
44 #include <X11/Xlib.h>
46 #include <QX11Info>
48 /**** KBackgroundRenderer ****/
51 KBackgroundRenderer::KBackgroundRenderer(int desk, int screen, bool drawBackgroundPerScreen, const KSharedConfigPtr &config, bool kdmMode)
52 : KBackgroundSettings(desk, screen, drawBackgroundPerScreen, config, kdmMode)
54 m_State = 0;
55 m_isBusyCursor = false;
56 m_enableBusyCursor = false;
57 m_pDirs = KGlobal::dirs();
58 m_rSize = m_Size = drawBackgroundPerScreen ?
59 QApplication::desktop()->screenGeometry(screen).size() : QApplication::desktop()->size();
60 m_pProc = 0L;
61 m_Tempfile = 0L;
62 m_bPreview = false;
63 m_Cached = false;
64 m_TilingEnabled = false;
66 m_pTimer = new QTimer(this);
67 m_pTimer->setSingleShot(true);
68 connect(m_pTimer, SIGNAL(timeout()), SLOT(render()));
72 KBackgroundRenderer::~KBackgroundRenderer()
74 cleanup();
75 delete m_Tempfile;
76 m_Tempfile = 0;
80 void KBackgroundRenderer::setSize(const QSize &size)
82 m_rSize = m_Size = size;
86 * Re-configure because the desktop has been resized.
88 void KBackgroundRenderer::desktopResized()
90 m_State = 0;
91 m_rSize = drawBackgroundPerScreen() ?
92 QApplication::desktop()->screenGeometry(screen()).size() : QApplication::desktop()->size();
93 if( !m_bPreview )
94 m_Size = m_rSize;
98 void KBackgroundRenderer::tile(QImage& dest, const QRect &_rect, const QImage& src)
100 QRect rect = _rect;
101 rect &= dest.rect();
103 int x, y;
104 int h = rect.height(), w = rect.width();
105 int offx = rect.x(), offy = rect.y();
106 int sw = src.width(), sh = src.height();
108 for (y=offy; y<offy+h; y++)
109 for (x=offx; x<offx+w; x++)
110 dest.setPixel(x, y, src.pixel(x%sw, y%sh));
115 * Build a command line to run the program.
118 QString KBackgroundRenderer::buildCommand()
120 QString num;
121 int pos = 0;
123 QString cmd;
124 if (m_bPreview)
125 cmd = previewCommand();
126 else
127 cmd = command();
129 if (cmd.isEmpty())
130 return QString();
132 while ((pos = cmd.indexOf('%', pos)) != -1) {
134 if (pos == (int) (cmd.length() - 1))
135 break;
137 switch (cmd.at(pos+1).toLatin1()) {
138 case 'f':
139 createTempFile();
140 cmd.replace(pos, 2, K3ShellProcess::quote(m_Tempfile->fileName()));
141 pos += m_Tempfile->fileName().length() - 2;
142 break;
144 case 'x':
145 num.setNum(m_Size.width());
146 cmd.replace(pos, 2, num);
147 pos += num.length() - 2;
148 break;
150 case 'y':
151 num.setNum(m_Size.height());
152 cmd.replace(pos, 2, num);
153 pos += num.length() - 2;
154 break;
156 case '%':
157 cmd.replace(pos, 2, "%");
158 pos--;
159 break;
160 default:
161 ++pos; // avoid infinite loop
162 break;
166 return cmd;
171 * Create a background tile. If the background mode is `Program',
172 * this is asynchronous.
174 int KBackgroundRenderer::doBackground(bool quit)
176 if (m_State & BackgroundDone)
177 return Done;
178 int bgmode = backgroundMode();
180 if (!enabled())
181 bgmode= Flat;
183 if (quit) {
184 if (bgmode == Program && m_pProc)
185 m_pProc->kill();
186 return Done;
189 int retval = Done;
190 QString file;
192 static unsigned int tileWidth = 0;
193 static unsigned int tileHeight = 0;
194 if( tileWidth == 0 )
196 int tile_val = QPixmap::defaultDepth() >= 24 ? 1 : 2;
197 // some dithering may be needed even with bpb==15/16, so don't use tileWidth==1
198 // for them
199 // with tileWidth>2, repainting the desktop causes nasty effect (XFree86 4.1.0 )
200 if( XQueryBestTile( QX11Info::display(), QX11Info::appRootWindow(), tile_val, tile_val,
201 &tileWidth, &tileHeight ) != Success )
202 tileWidth = tileHeight = tile_val; // some defaults
204 switch (bgmode) {
206 case Flat:
207 // this can be tiled correctly without problems
208 m_Background = QImage( tileWidth, tileHeight, QImage::Format_RGB32 );
209 m_Background.fill(colorA().rgb());
210 break;
212 case Pattern:
214 if (pattern().isEmpty())
215 break;
216 file = m_pDirs->findResource("dtop_pattern", pattern());
217 if (file.isEmpty())
218 break;
220 m_Background.load(file);
221 if (m_Background.isNull())
222 break;
223 int w = m_Background.width();
224 int h = m_Background.height();
225 if ((w > m_Size.width()) || (h > m_Size.height())) {
226 w = qMin(w, m_Size.width());
227 h = qMin(h, m_Size.height());
228 m_Background = m_Background.copy(0, 0, w, h);
230 Blitz::flatten(m_Background, colorA(), colorB());
231 break;
233 case Program:
234 if (m_State & BackgroundStarted)
235 break;
236 m_State |= BackgroundStarted;
237 createTempFile();
239 file = buildCommand();
240 if (file.isEmpty())
241 break;
243 delete m_pProc;
244 m_pProc = new K3ShellProcess;
245 *m_pProc << file;
246 connect(m_pProc, SIGNAL(processExited(K3Process *)),
247 SLOT(slotBackgroundDone(K3Process *)));
248 m_pProc->start(K3ShellProcess::NotifyOnExit);
249 retval = Wait;
250 break;
252 case HorizontalGradient:
254 QSize size = m_Size;
255 // on <16bpp displays the gradient sucks when tiled because of dithering
256 if( canTile())
257 size.setHeight( tileHeight );
258 m_Background = Blitz::gradient(size, colorA(), colorB(),
259 Blitz::HorizontalGradient);
260 break;
262 case VerticalGradient:
264 QSize size = m_Size;
265 // on <16bpp displays the gradient sucks when tiled because of dithering
266 if( canTile())
267 size.setWidth( tileWidth );
268 m_Background = Blitz::gradient(size, colorA(), colorB(),
269 Blitz::VerticalGradient);
270 break;
272 case PyramidGradient:
273 m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
274 Blitz::PyramidGradient);
275 break;
277 case PipeCrossGradient:
278 m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
279 Blitz::PipeCrossGradient);
280 break;
282 case EllipticGradient:
283 m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
284 Blitz::EllipticGradient);
285 break;
288 if (retval == Done)
289 m_State |= BackgroundDone;
291 return retval;
295 int KBackgroundRenderer::doWallpaper(bool quit)
297 if (m_State & WallpaperDone)
298 return Done;
300 if (quit)
301 // currently no asynch. wallpapers
302 return Done;
304 int wpmode= enabled()?wallpaperMode():NoWallpaper;
306 m_Wallpaper = QImage();
307 if (wpmode != NoWallpaper) {
308 wp_load:
309 if (currentWallpaper().isEmpty()) {
310 wpmode = NoWallpaper;
311 goto wp_out;
313 QString file = m_pDirs->findResource("wallpaper", currentWallpaper());
314 if (file.isEmpty()) {
315 wpmode = NoWallpaper;
316 goto wp_out;
319 // _Don't_ use KMimeType, as it relies on ksycoca which we really
320 // don't want in krootimage (kdm context).
321 //if ( KMimeType::findByPath( file )->is( "image/svg+xml" ) ) {
322 if (file.endsWith(".svg") || file.endsWith(".svgz")) {
324 // Special stuff for SVG icons
326 //FIXME
327 //ksvgiconloader doesn't seem to let us find out the
328 //ratio of width to height so for the most part we just
329 //assume it's a square
330 int svgWidth;
331 int svgHeight;
332 switch (wpmode)
334 case Centred:
335 case CentredAutoFit:
336 svgHeight = (int)(m_Size.height() * 0.8);
337 svgWidth = svgHeight;
338 break;
339 case Tiled:
340 case CenterTiled:
341 svgHeight = (int)(m_Size.height() * 0.5);
342 svgWidth = svgHeight;
343 break;
344 case Scaled:
345 svgHeight = m_Size.height();
346 svgWidth = m_Size.width();
347 break;
348 case CentredMaxpect:
349 case ScaleAndCrop:
350 case TiledMaxpect:
351 svgHeight = m_Size.height();
352 svgWidth = svgHeight;
353 break;
354 case NoWallpaper:
355 default:
356 kWarning() << "unknown diagram type" ;
357 svgHeight = m_Size.height();
358 svgWidth = svgHeight;
359 break;
361 //FIXME hack due to strangeness with
362 //background control modules
363 if ( svgHeight < 200 ) {
364 svgHeight *= 6;
365 svgWidth *= 6;
368 KSvgRenderer renderer(file);
369 if (renderer.isValid()) {
370 m_Wallpaper = QImage(svgWidth, svgHeight, QImage::Format_ARGB32_Premultiplied);
371 m_Wallpaper.fill(0);
372 QPainter p(&m_Wallpaper);
373 renderer.render(&p);
375 } else {
376 m_Wallpaper.load(file);
378 if (m_Wallpaper.isNull()) {
379 kWarning() << "failed to load wallpaper " << file ;
380 if (discardCurrentWallpaper())
381 goto wp_load;
382 wpmode = NoWallpaper;
383 goto wp_out;
385 m_Wallpaper = m_Wallpaper.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
387 // If we're previewing, scale the wallpaper down to make the preview
388 // look more like the real desktop.
389 if (m_bPreview) {
390 int xs = m_Wallpaper.width() * m_Size.width() / m_rSize.width();
391 int ys = m_Wallpaper.height() * m_Size.height() / m_rSize.height();
392 if ((xs < 1) || (ys < 1))
394 xs = ys = 1;
396 if( m_WallpaperRect.size() != QSize( xs, ys ))
397 m_Wallpaper = m_Wallpaper.scaled(xs, ys, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
400 // HACK: Use KFileMetaInfo only when we have a KApplication
401 // KFileMetaInfo needs ksycoca and so on, but this code is
402 // used also in krootimage (which in turn is used by kdm).
403 if (kapp) {
404 KFileMetaInfo metaInfo(file);
405 if (metaInfo.isValid() && metaInfo.item("Orientation").isValid()) {
406 switch (metaInfo.item("Orientation").value().toInt()) {
407 case 2:
408 // Flipped horizontally
409 m_Wallpaper.mirrored(true, false);
410 break;
411 case 3:
412 // Rotated 180 degrees
413 m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(180));
414 break;
415 case 4:
416 // Flipped vertically
417 m_Wallpaper = m_Wallpaper.mirrored(false, true);
418 break;
419 case 5:
420 // Rotated 90 degrees & flipped horizontally
421 m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(true, false);
422 break;
423 case 6:
424 // Rotated 90 degrees
425 m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90));
426 break;
427 case 7:
428 // Rotated 90 degrees & flipped vertically
429 m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(false, true);
430 break;
431 case 8:
432 // Rotated 270 degrees
433 m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(270));
434 break;
435 case 1:
436 default:
437 // Normal or invalid orientation
438 break;
443 wp_out:
445 if (m_Background.isNull()) {
446 m_Background = QImage(8, 8, QImage::Format_RGB32);
447 m_Background.fill(colorA().rgb());
450 int retval = Done;
452 // desktop width/height
453 int w = m_Size.width();
454 int h = m_Size.height();
456 // wallpaper width/height
457 int ww = m_Wallpaper.width();
458 int wh = m_Wallpaper.height();
460 // to be filled destination rectangle; may exceed desktop!
461 m_WallpaperRect = QRect();
463 switch (wpmode)
465 case NoWallpaper:
466 break;
467 case Centred:
468 m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
469 break;
470 case Tiled:
471 m_WallpaperRect.setRect(0, 0, w, h);
472 break;
473 case CenterTiled:
474 m_WallpaperRect.setCoords(-ww + ((w - ww) / 2) % ww, -wh + ((h - wh) / 2) % wh, w-1, h-1);
475 break;
476 case Scaled:
477 ww = w;
478 wh = h;
479 if( m_WallpaperRect.size() != QSize( w, h ))
480 m_Wallpaper = m_Wallpaper.scaled( w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
481 m_WallpaperRect.setRect(0, 0, w, h);
482 break;
483 case CentredAutoFit:
484 if( ww <= w && wh <= h ) {
485 m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); // like Centred
486 break;
488 // fall through
489 case CentredMaxpect:
491 double sx = (double) w / ww;
492 double sy = (double) h / wh;
493 if (sx > sy) {
494 ww = (int)(sy * ww);
495 wh = h;
496 } else {
497 wh = (int)(sx * wh);
498 ww = w;
500 if( m_WallpaperRect.size() != QSize( ww, wh ))
501 m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
502 m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
503 break;
505 case TiledMaxpect:
507 double sx = (double) w / ww;
508 double sy = (double) h / wh;
509 if (sx > sy) {
510 ww = (int)(sy * ww);
511 wh = h;
512 } else {
513 wh = (int)(sx * wh);
514 ww = w;
516 if( m_WallpaperRect.size() != QSize( ww, wh ))
517 m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
518 m_WallpaperRect.setRect(0, 0, w, h);
519 break;
521 case ScaleAndCrop:
523 double sx = (double) w / ww;
524 double sy = (double) h / wh;
525 if (sx > sy) {
526 //Case 1: x needs bigger scaling. Lets increase x and leave part of y offscreen
527 ww = w;
528 wh=(int)(sx * wh);
529 } else {
530 //Case 2: y needs bigger scaling. Lets increase y and leave part of x offscreen
531 wh = h;
532 ww = (int)(sy*ww);
534 if( m_WallpaperRect.size() != QSize( ww, wh ))
535 m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
536 m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2,w, h);
537 break;
541 wallpaperBlend();
543 if (retval == Done)
544 m_State |= WallpaperDone;
546 return retval;
549 bool KBackgroundRenderer::canTile() const
551 return m_TilingEnabled && optimize();
554 void KBackgroundRenderer::wallpaperBlend()
556 if( !enabled() || wallpaperMode() == NoWallpaper
557 || (blendMode() == NoBlending &&
558 ( QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::Antialiasing)
559 || !m_Wallpaper.hasAlphaChannel()))) {
560 fastWallpaperBlend();
562 else {
563 fullWallpaperBlend();
567 // works only for NoBlending and no alpha in wallpaper
568 // but is much faster than QImage fidling
569 void KBackgroundRenderer::fastWallpaperBlend()
571 m_Image = QImage();
572 // copy background to m_pPixmap
573 if( !enabled() || (wallpaperMode() == NoWallpaper && canTile())) {
574 // if there's no wallpaper, no need to tile the pixmap to the size of desktop, as X does
575 // that automatically and using a smaller pixmap should save some memory
576 m_Pixmap = QPixmap::fromImage( m_Background );
577 return;
579 else if( wallpaperMode() == Tiled && !m_Wallpaper.hasAlphaChannel() && canTile() && !m_bPreview ) {
580 // tiles will be tiled by X automatically
581 m_Pixmap = QPixmap::fromImage( m_Wallpaper );
582 return;
584 else if( m_WallpaperRect.contains( QRect( QPoint( 0, 0 ), m_Size ))
585 && !m_Wallpaper.hasAlphaChannel()) // wallpaper covers all and no blending
586 m_Pixmap = QPixmap( m_Size );
587 else if (m_Background.size() == m_Size)
588 m_Pixmap = QPixmap::fromImage( m_Background );
589 else {
590 m_Pixmap = QPixmap( m_Size );
591 QPainter p( &m_Pixmap );
592 QPixmap pm = QPixmap::fromImage( m_Background );
593 p.drawTiledPixmap( 0, 0, m_Size.width(), m_Size.height(), pm );
596 // paint/alpha-blend wallpaper to destination rectangle of m_pPixmap
597 if (m_WallpaperRect.isValid()) {
598 QPixmap wp_pixmap = QPixmap::fromImage( m_Wallpaper );
599 QPainter pa( &m_Pixmap );
600 int ww = m_Wallpaper.width();
601 int wh = m_Wallpaper.height();
602 for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
603 for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
604 pa.drawPixmap( x, y, wp_pixmap );
611 void KBackgroundRenderer::fullWallpaperBlend()
613 m_Pixmap = QPixmap();
615 // desktop width/height
616 int w = m_Size.width();
617 int h = m_Size.height();
619 // copy background to m_pImage
620 if (m_Background.size() == m_Size) {
621 m_Image = m_Background.copy();
623 if (m_Image.depth() < 32) {
624 m_Image = m_Image.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
626 } else {
627 m_Image = QImage(w, h, QImage::Format_RGB32);
628 tile(m_Image, QRect(0, 0, w, h), m_Background);
631 // blend wallpaper to destination rectangle of m_pImage
632 if (m_WallpaperRect.isValid())
634 int blendFactor = 100;
635 if (blendMode() == FlatBlending)
636 blendFactor = (blendBalance()+200)/4;
637 int ww = m_Wallpaper.width();
638 int wh = m_Wallpaper.height();
639 for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
640 for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
641 blend(m_Image, QRect(x, y, ww, wh), m_Wallpaper,
642 QPoint(-qMin(x, 0), -qMin(y, 0)), blendFactor);
648 // blend whole desktop
649 if ( wallpaperMode() != NoWallpaper) {
650 int bal = blendBalance();
652 switch( blendMode() ) {
653 /* TODO disabled for now
654 case HorizontalBlending:
655 Blitz::blend( m_Image, m_Background,
656 Blitz::HorizontalGradient,
657 bal, 100 );
658 break;
660 case VerticalBlending:
661 Blitz::blend( m_Image, m_Background,
662 Blitz::VerticalGradient,
663 100, bal );
664 break;
666 case PyramidBlending:
667 Blitz::blend( m_Image, m_Background,
668 Blitz::PyramidGradient,
669 bal, bal );
670 break;
672 case PipeCrossBlending:
673 Blitz::blend( m_Image, m_Background,
674 Blitz::PipeCrossGradient,
675 bal, bal );
676 break;
678 case EllipticBlending:
679 Blitz::blend( m_Image, m_Background,
680 Blitz::EllipticGradient,
681 bal, bal );
682 break;
685 case IntensityBlending:
686 Blitz::modulate( m_Image, m_Background, reverseBlending(),
687 Blitz::Intensity, bal, Blitz::All );
688 break;
690 case SaturateBlending:
691 Blitz::modulate( m_Image, m_Background, reverseBlending(),
692 Blitz::Saturation, bal, Blitz::Brightness );
693 break;
695 case ContrastBlending:
696 Blitz::modulate( m_Image, m_Background, reverseBlending(),
697 Blitz::Contrast, bal, Blitz::All );
698 break;
700 case HueShiftBlending:
701 Blitz::modulate( m_Image, m_Background, reverseBlending(),
702 Blitz::HueShift, bal, Blitz::Brightness );
703 break;
705 case FlatBlending:
706 // Already handled
707 break;
712 /* Alpha blend an area from <src> with offset <soffs> to rectangle <dr> of <dst>
713 * Default offset is QPoint(0, 0).
714 * blendfactor = [0, 100%]
716 void KBackgroundRenderer::blend(QImage& dst, const QRect &_dr, const QImage& src, const QPoint &soffs, int blendFactor)
718 QRect dr = _dr;
719 int x, y, a;
720 dr &= dst.rect();
722 for (y = 0; y < dr.height(); y++) {
723 if (dst.scanLine(dr.y() + y) && src.scanLine(soffs.y() + y)) {
724 QRgb *b;
725 const QRgb *d;
726 for (x = 0; x < dr.width(); x++) {
727 b = reinterpret_cast<QRgb*>(dst.scanLine(dr.y() + y)
728 + (dr.x() + x) * sizeof(QRgb));
729 d = reinterpret_cast<const QRgb*>(src.scanLine(soffs.y() + y)
730 + (soffs.x() + x) * sizeof(QRgb));
731 a = (qAlpha(*d) * blendFactor) / 100;
732 *b = qRgb(qRed(*b) - (((qRed(*b) - qRed(*d)) * a) >> 8),
733 qGreen(*b) - (((qGreen(*b) - qGreen(*d)) * a) >> 8),
734 qBlue(*b) - (((qBlue(*b) - qBlue(*d)) * a) >> 8));
742 void KBackgroundRenderer::slotBackgroundDone(K3Process *process)
744 Q_ASSERT(process == m_pProc);
745 m_State |= BackgroundDone;
747 if (m_pProc->normalExit() && !m_pProc->exitStatus()) {
748 m_Background.load(m_Tempfile->fileName());
749 m_State |= BackgroundDone;
752 delete m_Tempfile; m_Tempfile = 0;
753 m_pTimer->start(0);
754 setBusyCursor(false);
760 * Starts the rendering process.
762 void KBackgroundRenderer::start(bool enableBusyCursor)
764 m_enableBusyCursor = enableBusyCursor;
765 setBusyCursor(true);
767 m_Cached = false;
769 m_State = Rendering;
770 m_pTimer->start(0);
775 * This slot is connected to a timer event. It is called repeatedly until
776 * the rendering is done.
778 void KBackgroundRenderer::render()
780 setBusyCursor(true);
781 if (!(m_State & Rendering))
782 return;
784 if( !(m_State & InitCheck)) {
785 QString f = cacheFileName();
786 if( useCacheFile()) {
787 QString w = m_pDirs->findResource("wallpaper", currentWallpaper());
788 QFileInfo wi( w );
789 QFileInfo fi( f );
790 if( wi.lastModified().isValid() && fi.lastModified().isValid()
791 && wi.lastModified() < fi.lastModified()) {
792 QImage im;
793 if( im.load( f, "PNG" )) {
794 m_Image = im;
795 m_Pixmap = QPixmap::fromImage( m_Image );
796 m_Cached = true;
797 m_State |= InitCheck | BackgroundDone | WallpaperDone;
801 m_pTimer->start(0);
802 m_State |= InitCheck;
803 return;
806 int ret;
808 if (!(m_State & BackgroundDone)) {
809 ret = doBackground();
810 if (ret != Wait)
811 m_pTimer->start(0);
812 return;
815 // No async wallpaper
816 doWallpaper();
818 done();
819 setBusyCursor(false);
824 * Rendering is finished.
826 void KBackgroundRenderer::done()
828 setBusyCursor(false);
829 m_State |= AllDone;
830 emit imageDone(desk(), screen());
831 if(backgroundMode() == Program && m_pProc &&
832 m_pProc->normalExit() && m_pProc->exitStatus()) {
833 emit programFailure(desk(), m_pProc->exitStatus());
834 } else if(backgroundMode() == Program && m_pProc &&
835 !m_pProc->normalExit()) {
836 emit programFailure(desk(), -1);
837 } else if(backgroundMode() == Program) {
838 emit programSuccess(desk());
843 * This function toggles a busy cursor on and off, for use in rendering.
844 * It is useful because of the ASYNC nature of the rendering - it is hard
845 * to make sure we don't set the busy cursor twice, but only restore
846 * once.
848 void KBackgroundRenderer::setBusyCursor(bool isBusy) {
849 if(m_isBusyCursor == isBusy)
850 return;
851 if (isBusy && !m_enableBusyCursor)
852 return;
853 m_isBusyCursor = isBusy;
854 if(isBusy)
855 QApplication::setOverrideCursor( QCursor(Qt::BusyCursor) );
856 else
857 QApplication::restoreOverrideCursor();
861 * Stop the rendering.
863 void KBackgroundRenderer::stop()
865 if (!(m_State & Rendering))
866 return;
868 doBackground(true);
869 doWallpaper(true);
870 m_State = 0;
875 * Cleanup after rendering.
877 void KBackgroundRenderer::cleanup()
879 setBusyCursor(false);
880 m_Background = QImage();
881 m_Image = QImage();
882 m_Pixmap = QPixmap();
883 m_Wallpaper = QImage();
884 delete m_pProc; m_pProc = 0L;
885 m_State = 0;
886 m_WallpaperRect = QRect();
887 m_Cached = false;
891 void KBackgroundRenderer::setPreview(const QSize &size)
893 if (size.isNull())
894 m_bPreview = false;
895 else {
896 m_bPreview = true;
897 m_Size = size;
902 QPixmap KBackgroundRenderer::pixmap()
904 if (m_State & AllDone) {
905 if( m_Pixmap.isNull())
906 m_Pixmap = QPixmap::fromImage( m_Image );
907 return m_Pixmap;
909 return QPixmap();
912 QImage KBackgroundRenderer::image()
914 if (m_State & AllDone) {
915 if( m_Image.isNull())
916 fullWallpaperBlend(); // create from m_Pixmap
917 return m_Image;
919 return QImage();
923 void KBackgroundRenderer::load(int desk, int screen, bool drawBackgroundPerScreen, bool reparseConfig)
925 if (m_State & Rendering)
926 stop();
928 cleanup();
929 m_bPreview = false;
930 m_Size = m_rSize;
932 KBackgroundSettings::load(desk, screen, drawBackgroundPerScreen, reparseConfig);
935 void KBackgroundRenderer::createTempFile()
937 if( !m_Tempfile ){
938 m_Tempfile = new KTemporaryFile();
939 m_Tempfile->open();
943 QString KBackgroundRenderer::cacheFileName()
945 QString f = fingerprint();
946 f.replace ( ':', '_' ); // avoid characters that shouldn't be in filenames
947 f.replace ( '/', '#' );
948 f = KStandardDirs::locateLocal( "cache", QString( "background/%1x%2_%3.png" )
949 .arg( m_Size.width()).arg( m_Size.height()).arg( f ));
950 return f;
953 bool KBackgroundRenderer::useCacheFile() const
955 if( !enabled())
956 return false;
957 if( backgroundMode() == Program )
958 return false; // don't cache these at all
959 if( wallpaperMode() == NoWallpaper )
960 return false; // generating only background patterns should be always faster
961 QString file = currentWallpaper();
962 if( file.endsWith(".svg") || file.endsWith(".svgz"))
963 return true; // cache these, they can be bloody slow
964 switch( backgroundMode())
966 case NoWallpaper:
967 case Centred:
968 case Tiled:
969 case CenterTiled:
970 return false; // these don't need scaling
971 case CentredMaxpect:
972 case TiledMaxpect:
973 case Scaled:
974 case CentredAutoFit:
975 case ScaleAndCrop:
976 default:
977 return true;
981 void KBackgroundRenderer::saveCacheFile()
983 if( !( m_State & AllDone ))
984 return;
985 if( !useCacheFile())
986 return;
987 if( m_Image.isNull())
988 fullWallpaperBlend(); // generate from m_Pixmap
989 QString f = cacheFileName();
990 if( KStandardDirs::exists( f ) || m_Cached )
991 utime( QFile::encodeName( f ), NULL );
992 else {
993 m_Image.save( f, "PNG" );
994 // remove old entries from the cache
995 QDir dir( KStandardDirs::locateLocal( "cache", "background/" ));
996 const QFileInfoList list = dir.entryInfoList( QStringList() << "*.png", QDir::Files, QDir::Time | QDir::Reversed );
997 if( !list.isEmpty()) {
998 int size = 0;
999 Q_FOREACH( QFileInfo info, list )
1000 size += info.size();
1001 Q_FOREACH( QFileInfo info, list ) {
1002 if( size < 8 * 1024 * 1024 )
1003 break;
1004 // keep everything newer than 10 minutes if the total size is less than 50M (just in case)
1005 if( size < 50 * 1024 * 1024
1006 && ( time_t ) info.lastModified().toTime_t() >= time( NULL ) - 10 * 60 )
1007 break;
1008 size -= info.size();
1009 QFile::remove( info.absoluteFilePath());
1015 //BEGIN class KVirtualBGRenderer
1016 KVirtualBGRenderer::KVirtualBGRenderer(int desk, const KSharedConfigPtr &config, bool kdmMode)
1018 m_pPixmap = 0l;
1019 m_desk = desk;
1020 m_numRenderers = 0;
1021 m_scaleX = 1;
1022 m_scaleY = 1;
1023 m_kdmMode = kdmMode;
1025 // The following code is borrowed from KBackgroundSettings::KBackgroundSettings
1026 if (!config) {
1027 int screen_number = 0;
1028 if (QX11Info::display())
1029 screen_number = DefaultScreen(QX11Info::display());
1030 QString configname;
1031 if (screen_number == 0)
1032 configname = "kdesktoprc";
1033 else
1034 configname.sprintf("kdesktop-screen-%drc", screen_number);
1036 m_pConfig = KSharedConfig::openConfig(configname, KConfig::NoGlobals);
1037 } else {
1038 m_pConfig = config;
1041 initRenderers();
1042 m_size = QApplication::desktop()->size();
1045 KVirtualBGRenderer::~KVirtualBGRenderer()
1047 for (unsigned i=0; i<m_numRenderers; ++i)
1048 delete m_renderer[i];
1050 delete m_pPixmap;
1054 KBackgroundRenderer * KVirtualBGRenderer::renderer(unsigned screen)
1056 return m_renderer[screen];
1060 QPixmap KVirtualBGRenderer::pixmap()
1062 if (m_numRenderers == 1)
1063 return m_renderer[0]->pixmap();
1065 return *m_pPixmap;
1069 bool KVirtualBGRenderer::needProgramUpdate()
1071 for (unsigned i=0; i<m_numRenderers; ++i)
1073 if ( m_renderer[i]->backgroundMode() == KBackgroundSettings::Program &&
1074 m_renderer[i]->KBackgroundProgram::needUpdate() )
1075 return true;
1077 return false;
1081 void KVirtualBGRenderer::programUpdate()
1083 for (unsigned i=0; i<m_numRenderers; ++i)
1085 if ( m_renderer[i]->backgroundMode() == KBackgroundSettings::Program &&
1086 m_renderer[i]->KBackgroundProgram::needUpdate() )
1088 m_renderer[i]->KBackgroundProgram::update();
1094 bool KVirtualBGRenderer::needWallpaperChange()
1096 for (unsigned i=0; i<m_numRenderers; ++i)
1098 if ( m_renderer[i]->needWallpaperChange() )
1099 return true;
1101 return false;
1105 void KVirtualBGRenderer::changeWallpaper()
1107 for (unsigned i=0; i<m_numRenderers; ++i)
1109 m_renderer[i]->changeWallpaper();
1114 int KVirtualBGRenderer::hash()
1116 QString fp;
1117 for (unsigned i=0; i<m_numRenderers; ++i)
1119 fp += m_renderer[i]->fingerprint();
1121 int h = qHash(fp);
1122 kDebug() << " fp=\""<<fp<<"\" h="<<h;
1123 return qHash(fp);
1127 bool KVirtualBGRenderer::isActive()
1129 for (unsigned i=0; i<m_numRenderers; ++i)
1131 if ( m_renderer[i]->isActive() )
1132 return true;
1134 return false;
1138 void KVirtualBGRenderer::setEnabled(bool enable)
1140 for (unsigned i=0; i<m_numRenderers; ++i)
1141 m_renderer[i]->setEnabled(enable);
1145 void KVirtualBGRenderer::desktopResized()
1147 m_size = QApplication::desktop()->size();
1149 if (m_pPixmap)
1151 delete m_pPixmap;
1152 m_pPixmap = new QPixmap(m_size);
1153 m_pPixmap->fill(Qt::black);
1156 for (unsigned i=0; i<m_numRenderers; ++i)
1157 m_renderer[i]->desktopResized();
1161 void KVirtualBGRenderer::setPreview(const QSize & size)
1163 if (m_size == size)
1164 return;
1166 m_size = size;
1168 if (m_pPixmap)
1169 *m_pPixmap = QPixmap(m_size);
1171 // Scaling factors
1172 m_scaleX = float(m_size.width()) / float(QApplication::desktop()->size().width());
1173 m_scaleY = float(m_size.height()) / float(QApplication::desktop()->size().height());
1175 // Scale renderers appropriately
1176 for (unsigned i=0; i<m_renderer.size(); ++i)
1178 QSize unscaledRendererSize = renderSize(i);
1180 m_renderer[i]->setPreview( QSize(
1181 int(unscaledRendererSize.width() * m_scaleX),
1182 int(unscaledRendererSize.height() * m_scaleY) ) );
1187 QSize KVirtualBGRenderer::renderSize(int screen)
1189 return m_bDrawBackgroundPerScreen ?
1190 QApplication::desktop()->screenGeometry(screen).size() : QApplication::desktop()->size();
1194 void KVirtualBGRenderer::initRenderers()
1196 KConfigGroup cg(m_pConfig, "Background Common");
1197 m_bDrawBackgroundPerScreen = cg.readEntry( QString("DrawBackgroundPerScreen_%1").arg(m_desk), _defDrawBackgroundPerScreen );
1199 m_bCommonScreen = cg.readEntry("CommonScreen", _defCommonScreen);
1201 m_numRenderers = m_bDrawBackgroundPerScreen ? QApplication::desktop()->numScreens() : 1;
1203 m_bFinished.resize(m_numRenderers);
1204 m_bFinished.fill(false);
1206 if (m_numRenderers == m_renderer.size())
1207 return;
1209 for (unsigned i=0; i<m_renderer.size(); ++i)
1210 delete m_renderer[i];
1212 m_renderer.resize(m_numRenderers);
1213 for (unsigned i=0; i<m_numRenderers; ++i)
1215 int eScreen = m_bCommonScreen ? 0 : i;
1216 KBackgroundRenderer * r = new KBackgroundRenderer( m_desk, eScreen, m_bDrawBackgroundPerScreen, m_pConfig, m_kdmMode );
1217 m_renderer.insert( i, r );
1218 r->setSize(renderSize(i));
1219 connect( r, SIGNAL(imageDone(int,int)), this, SLOT(screenDone(int,int)) );
1224 void KVirtualBGRenderer::load(int desk, bool reparseConfig)
1226 m_desk = desk;
1228 m_bCommonScreen = m_pConfig->group("Background Common").readEntry("CommonScreen", _defCommonScreen);
1230 initRenderers();
1232 for (unsigned i=0; i<m_numRenderers; ++i)
1234 unsigned eScreen = m_bCommonScreen ? 0 : i;
1235 m_renderer[i]->load(desk, eScreen, m_bDrawBackgroundPerScreen, reparseConfig);
1240 void KVirtualBGRenderer::screenDone(int _desk, int _screen)
1242 Q_UNUSED(_desk);
1243 Q_UNUSED(_screen);
1245 const KBackgroundRenderer * sender = dynamic_cast<const KBackgroundRenderer*>(this->sender());
1246 int screen = m_renderer.find(sender);
1247 if (screen == -1)
1248 //??
1249 return;
1251 m_bFinished[screen] = true;
1254 if (m_pPixmap)
1256 // There's more than one renderer, so we are drawing each output to our own pixmap
1258 QRect overallGeometry;
1259 for (int i=0; i < QApplication::desktop()->numScreens(); ++i)
1260 overallGeometry |= QApplication::desktop()->screenGeometry(i);
1262 QPoint drawPos = QApplication::desktop()->screenGeometry(screen).topLeft() - overallGeometry.topLeft();
1263 drawPos.setX( int(drawPos.x() * m_scaleX) );
1264 drawPos.setY( int(drawPos.y() * m_scaleY) );
1266 QPixmap source = m_renderer[screen]->pixmap();
1267 QSize renderSize = this->renderSize(screen);
1268 renderSize.setWidth( int(renderSize.width() * m_scaleX) );
1269 renderSize.setHeight( int(renderSize.height() * m_scaleY) );
1271 QPainter p(m_pPixmap);
1273 if (renderSize == source.size())
1274 p.drawPixmap( drawPos, source );
1276 else
1277 p.drawTiledPixmap( drawPos.x(), drawPos.y(), renderSize.width(), renderSize.height(), source );
1279 p.end();
1282 for (int i=0; i<m_bFinished.size(); ++i)
1284 if (!m_bFinished[i])
1285 return;
1288 emit imageDone(m_desk);
1292 void KVirtualBGRenderer::start()
1294 if (m_pPixmap)
1296 delete m_pPixmap;
1297 m_pPixmap = 0l;
1300 if (m_numRenderers > 1)
1302 m_pPixmap = new QPixmap(m_size);
1303 // If are screen sizes do not properly tile the overall virtual screen
1304 // size, then we want the untiled parts to be black for use in desktop
1305 // previews, etc
1306 m_pPixmap->fill(Qt::black);
1309 m_bFinished.fill(false);
1310 for (unsigned i=0; i<m_numRenderers; ++i)
1311 m_renderer[i]->start();
1315 void KVirtualBGRenderer::stop()
1317 for (unsigned i=0; i<m_numRenderers; ++i)
1318 m_renderer[i]->stop();
1322 void KVirtualBGRenderer::cleanup()
1324 m_bFinished.fill(false);
1326 for (unsigned i=0; i<m_numRenderers; ++i)
1327 m_renderer[i]->cleanup();
1329 delete m_pPixmap;
1330 m_pPixmap = 0l;
1333 void KVirtualBGRenderer::saveCacheFile()
1335 for (unsigned i=0; i<m_numRenderers; ++i)
1336 m_renderer[i]->saveCacheFile();
1339 void KVirtualBGRenderer::enableTiling( bool enable )
1341 for (unsigned i=0; i<m_numRenderers; ++i)
1342 m_renderer[i]->enableTiling( enable );
1345 //END class KVirtualBGRenderer
1348 #include "bgrender.moc"