~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kdm/kfrontend/themer/kdmpixmap.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Copyright (C) 2003 by Unai Garro <ugarro@users.sourceforge.net>
 
3
 *  Copyright (C) 2004 by Enrico Ros <rosenric@dei.unipd.it>
 
4
 *  Copyright (C) 2004 by Stephan Kulow <coolo@kde.org>
 
5
 *  Copyright (C) 2004 by Oswald Buddenhagen <ossi@kde.org>
 
6
 *
 
7
 *  This program is free software; you can redistribute it and/or modify
 
8
 *  it under the terms of the GNU General Public License as published by
 
9
 *  the Free Software Foundation; either version 2 of the License, or
 
10
 *  (at your option) any later version.
 
11
 *
 
12
 *  This program is distributed in the hope that it will be useful,
 
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
 *  GNU General Public License for more details.
 
16
 *
 
17
 *  You should have received a copy of the GNU General Public License
 
18
 *  along with this program; if not, write to the Free Software
 
19
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
20
 */
 
21
 
 
22
#include "kdmpixmap.h"
 
23
#include "kdmthemer.h"
 
24
 
 
25
#include <kstandarddirs.h>
 
26
 
 
27
#include <QDirIterator>
 
28
#include <QPainter>
 
29
#include <QSignalMapper>
 
30
#include <QSvgRenderer>
 
31
 
 
32
#include <math.h>
 
33
 
 
34
KdmPixmap::KdmPixmap(QObject *parent, const QDomNode &node)
 
35
    : KdmItem(parent, node)
 
36
    , qsm(0)
 
37
{
 
38
    itemType = "pixmap";
 
39
    if (!isVisible())
 
40
        return;
 
41
 
 
42
    // Set default values for pixmap (note: strings are already Null)
 
43
    pixmap.normal.tint.setRgb(0xFFFFFF);
 
44
    pixmap.normal.present = true;
 
45
 
 
46
    // Read PIXMAP TAGS
 
47
    QDomNodeList childList = node.childNodes();
 
48
    for (int nod = 0; nod < childList.count(); nod++) {
 
49
        QDomNode child = childList.item(nod);
 
50
        QDomElement el = child.toElement();
 
51
        QString tagName = el.tagName();
 
52
 
 
53
        if (tagName == "normal") {
 
54
            definePixmap(el, pixmap.normal);
 
55
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.normal.tint);
 
56
        } else if (tagName == "active") {
 
57
            pixmap.active.present = true;
 
58
            definePixmap(el, pixmap.active);
 
59
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.active.tint);
 
60
        } else if (tagName == "prelight") {
 
61
            pixmap.prelight.present = true;
 
62
            definePixmap(el, pixmap.prelight);
 
63
            parseColor(el.attribute("tint", "#ffffff"), el.attribute("alpha", "1.0"), pixmap.prelight.tint);
 
64
        }
 
65
    }
 
66
}
 
67
 
 
68
void
 
69
KdmPixmap::definePixmap(const QDomElement &el, PixmapStruct::PixmapClass &pClass)
 
70
{
 
71
    QString fileName = el.attribute("file");
 
72
    if (!fileName.isEmpty()) {
 
73
        if (fileName.at(0) != '/')
 
74
            fileName.prepend(themer()->baseDir() + '/');
 
75
        pClass.fullpath = fileName;
 
76
    } else {
 
77
        fileName = el.attribute("wallpaper");
 
78
        if (fileName.isEmpty())
 
79
            return;
 
80
        QString xf = KStandardDirs::locate("wallpaper", fileName + "/contents/images/");
 
81
        if (!xf.isEmpty()) {
 
82
            pClass.package = true;
 
83
        } else {
 
84
            xf = KStandardDirs::locate("wallpaper", fileName);
 
85
            if (xf.isEmpty()) {
 
86
                kWarning() << "Cannot find wallpaper" << fileName;
 
87
                return;
 
88
            }
 
89
        }
 
90
        pClass.fullpath = xf;
 
91
    }
 
92
 
 
93
    QString aspect = el.attribute("scalemode", "free");
 
94
    pClass.aspectMode =
 
95
            (aspect == "fit") ? Qt::KeepAspectRatio :
 
96
            (aspect == "crop") ? Qt::KeepAspectRatioByExpanding :
 
97
            Qt::IgnoreAspectRatio;
 
98
 
 
99
    pClass.svgImage = fileName.endsWith(".svg") || fileName.endsWith(".svgz");
 
100
    if (pClass.svgImage)
 
101
        pClass.svgElement = el.attribute("element");
 
102
}
 
103
 
 
104
QString
 
105
KdmPixmap::findBestPixmap(const QString &dir, const QString &pat,
 
106
                          const QRect &area, Qt::AspectRatioMode aspectMode)
 
107
{
 
108
    int tw = area.width(), th = area.height();
 
109
    float tar = 1.0 * tw / th;
 
110
    float tpn = tw * th;
 
111
    QString best;
 
112
    float bestPenalty = 0;
 
113
    QRegExp rx(QRegExp::escape(dir) + pat);
 
114
    QDirIterator dit(dir);
 
115
    while (dit.hasNext()) {
 
116
        QString fn = dit.next();
 
117
        if (rx.exactMatch(fn)) {
 
118
            int w = rx.cap(1).toInt(), h = rx.cap(2).toInt();
 
119
            // This algorithm considers need for zooming and distortion of
 
120
            // aspect ratio / discarded pixels / pixels left free. The weighting
 
121
            // gives good results in the tested cases, but is pretty arbitrary
 
122
            // by all practical means.
 
123
            float ar = 1.0 * w / h;
 
124
            float pn = w * h;
 
125
            float rawAspect = ((tar > ar) ? tar / ar : ar / tar);
 
126
            float penalty = rawAspect - 1;
 
127
            if (aspectMode != Qt::IgnoreAspectRatio) {
 
128
                bool exp = (aspectMode == Qt::KeepAspectRatioByExpanding);
 
129
                // Give an advantage to non-zooming cases.
 
130
                if ((w == tw && (h > th) == exp) || (h == th && (w > tw) == exp))
 
131
                    goto skipSize;
 
132
                else
 
133
                    penalty *= 5;
 
134
                // Dropped pixels don't contribute to the input area.
 
135
                if (exp)
 
136
                    pn /= rawAspect;
 
137
            } else {
 
138
                // This mode does not preserve aspect ratio, so give an
 
139
                // advantage to pics with a better ratio to start with.
 
140
                penalty *= 10;
 
141
            }
 
142
            // Too small is worse than too big - within limits.
 
143
            penalty += sqrt((tpn > pn) ? (tpn / pn - 1) * 2 : pn / tpn - 1);
 
144
            skipSize:
 
145
            if (best.isEmpty() || penalty < bestPenalty) {
 
146
                best = fn;
 
147
                bestPenalty = penalty;
 
148
            }
 
149
        }
 
150
    }
 
151
    return best;
 
152
}
 
153
 
 
154
bool
 
155
KdmPixmap::loadPixmap(PixmapStruct::PixmapClass &pClass)
 
156
{
 
157
    if (!pClass.image.isNull())
 
158
        return true;
 
159
    if (pClass.fullpath.isEmpty())
 
160
        return false;
 
161
    QString fn;
 
162
    if (pClass.package) {
 
163
        // Always find best fit from package.
 
164
        fn = findBestPixmap(pClass.fullpath, "(\\d+)x(\\d+)\\.[^.]+",
 
165
                            area.isValid() ? area : QRect(0, 0, 1600, 1200),
 
166
                            pClass.aspectMode);
 
167
    } else {
 
168
        if (area.isValid()) {
 
169
            if (QFile::exists(pClass.fullpath)) {
 
170
                // If base file exists, use only a perfect match.
 
171
                int dot = pClass.fullpath.lastIndexOf('.');
 
172
                fn = pClass.fullpath.left(dot);
 
173
                fn += QString("-%1x%2").arg(area.width()).arg(area.height());
 
174
                fn += pClass.fullpath.mid(dot);
 
175
                if (!QFile::exists(fn))
 
176
                    fn = pClass.fullpath;
 
177
            } else {
 
178
                // Otherwise find best match.
 
179
                int sep = pClass.fullpath.lastIndexOf('/');
 
180
                int dot = pClass.fullpath.lastIndexOf('.');
 
181
                if (dot < sep)
 
182
                    dot = pClass.fullpath.length();
 
183
                QString f = QRegExp::escape(pClass.fullpath.mid(sep + 1, dot - sep - 1));
 
184
                f += "-(\\d+)x(\\d+)";
 
185
                f += QRegExp::escape(pClass.fullpath.mid(dot));
 
186
                fn = findBestPixmap(pClass.fullpath.left(sep + 1), f, area,
 
187
                                    pClass.aspectMode);
 
188
            }
 
189
        } else {
 
190
            fn = pClass.fullpath;
 
191
        }
 
192
    }
 
193
    if (!pClass.image.load(fn)) {
 
194
        kWarning() << "failed to load" << fn;
 
195
        pClass.fullpath.clear();
 
196
        return false;
 
197
    }
 
198
    if (pClass.image.format() != QImage::Format_ARGB32)
 
199
        pClass.image = pClass.image.convertToFormat(QImage::Format_ARGB32);
 
200
    applyTint(pClass, pClass.image);
 
201
    return true;
 
202
}
 
203
 
 
204
bool
 
205
KdmPixmap::loadSvg(PixmapStruct::PixmapClass &pClass)
 
206
{
 
207
    if (pClass.svgRenderer)
 
208
        return true;
 
209
    if (pClass.fullpath.isEmpty())
 
210
        return false;
 
211
    pClass.svgRenderer = new QSvgRenderer(pClass.fullpath, this);
 
212
    if (!pClass.svgRenderer->isValid()) {
 
213
        delete pClass.svgRenderer;
 
214
        pClass.svgRenderer = 0;
 
215
        kWarning() << "failed to load " << pClass.fullpath ;
 
216
        pClass.fullpath.clear();
 
217
        return false;
 
218
    }
 
219
    if (pClass.svgRenderer->animated()) {
 
220
        if (!qsm) {
 
221
            qsm = new QSignalMapper(this);
 
222
            connect(qsm, SIGNAL(mapped(int)), SLOT(slotAnimate(int)));
 
223
        }
 
224
        qsm->setMapping(pClass.svgRenderer, state); // assuming we only load the current state
 
225
        connect(pClass.svgRenderer, SIGNAL(repaintNeeded()), qsm, SLOT(map()));
 
226
    }
 
227
    pClass.svgSizeHint = pClass.svgElement.isEmpty() ?
 
228
        pClass.svgRenderer->defaultSize() :
 
229
        pClass.svgRenderer->boundsOnElement(pClass.svgElement).size().toSize();
 
230
    return true;
 
231
}
 
232
 
 
233
QSize
 
234
KdmPixmap::sizeHint()
 
235
{
 
236
    // use the pixmap size as the size hint
 
237
    if (!pixmap.normal.svgImage) {
 
238
        if (loadPixmap(pixmap.normal))
 
239
            return pixmap.normal.image.size();
 
240
    } else {
 
241
        if (loadSvg(pixmap.normal))
 
242
            return pixmap.normal.svgSizeHint;
 
243
    }
 
244
    return KdmItem::sizeHint();
 
245
}
 
246
 
 
247
void
 
248
KdmPixmap::setGeometry(QStack<QSize> &parentSizes, const QRect &newGeometry, bool force)
 
249
{
 
250
    KdmItem::setGeometry(parentSizes, newGeometry, force);
 
251
    pixmap.active.targetArea = QRect();
 
252
    pixmap.prelight.targetArea = QRect();
 
253
    pixmap.normal.targetArea = QRect();
 
254
}
 
255
 
 
256
bool
 
257
KdmPixmap::calcTargetArea(PixmapStruct::PixmapClass &pClass, const QSize &sh)
 
258
{
 
259
    QSize sz = sh;
 
260
    sz.scale(area.size(), pClass.aspectMode);
 
261
    pClass.targetArea.setSize(sz);
 
262
    pClass.targetArea.moveCenter(area.center());
 
263
    return pClass.targetArea.size() != pClass.readyPixmap.size();
 
264
}
 
265
 
 
266
void
 
267
KdmPixmap::drawContents(QPainter *p, const QRect &r)
 
268
{
 
269
    PixmapStruct::PixmapClass &pClass = getCurClass();
 
270
 
 
271
    if (pClass.targetArea.isEmpty()) {
 
272
        QImage scaledImage;
 
273
 
 
274
        if (pClass.svgImage) {
 
275
            if (loadSvg(pClass)) {
 
276
                if (!calcTargetArea(pClass, pClass.svgSizeHint))
 
277
                    goto noop;
 
278
                scaledImage = QImage(pClass.targetArea.size(), QImage::Format_ARGB32);
 
279
                scaledImage.fill(0);
 
280
                QPainter pa(&scaledImage);
 
281
                if (pClass.svgElement.isEmpty())
 
282
                    pClass.svgRenderer->render(&pa);
 
283
                else
 
284
                    pClass.svgRenderer->render(&pa, pClass.svgElement);
 
285
                applyTint(pClass, scaledImage);
 
286
            }
 
287
        } else {
 
288
            if (loadPixmap(pClass)) {
 
289
                if (!calcTargetArea(pClass, pClass.image.size()))
 
290
                    goto noop;
 
291
                scaledImage =
 
292
                    (area.size() != pClass.image.size()) ?
 
293
                        pClass.image.scaled(pClass.targetArea.size(),
 
294
                                            Qt::IgnoreAspectRatio, Qt::SmoothTransformation) :
 
295
                        pClass.image;
 
296
            }
 
297
        }
 
298
 
 
299
        if (scaledImage.isNull()) {
 
300
            p->fillRect(r, Qt::black);
 
301
            return;
 
302
        }
 
303
 
 
304
        pClass.readyPixmap = QPixmap::fromImage(scaledImage);
 
305
    }
 
306
  noop:
 
307
    QRect tr = r.intersected(pClass.targetArea);
 
308
    p->drawPixmap(tr.topLeft(), pClass.readyPixmap,
 
309
                  QRect(tr.topLeft() - pClass.targetArea.topLeft(), tr.size()));
 
310
}
 
311
 
 
312
void
 
313
KdmPixmap::applyTint(PixmapStruct::PixmapClass &pClass, QImage &img)
 
314
{
 
315
    if (pClass.tint.rgba() == 0xFFFFFFFF)
 
316
        return;
 
317
 
 
318
    int w = img.width();
 
319
    int h = img.height();
 
320
    int tint_red = pClass.tint.red();
 
321
    int tint_green = pClass.tint.green();
 
322
    int tint_blue = pClass.tint.blue();
 
323
    int tint_alpha = pClass.tint.alpha();
 
324
 
 
325
    for (int y = 0; y < h; ++y) {
 
326
        QRgb *ls = (QRgb *)img.scanLine(y);
 
327
        for (int x = 0; x < w; ++x) {
 
328
            QRgb l = ls[x];
 
329
            int r = qRed(l) * tint_red / 255;
 
330
            int g = qGreen(l) * tint_green / 255;
 
331
            int b = qBlue(l) * tint_blue / 255;
 
332
            int a = qAlpha(l) * tint_alpha / 255;
 
333
            ls[x] = qRgba(r, g, b, a);
 
334
        }
 
335
    }
 
336
}
 
337
 
 
338
KdmPixmap::PixmapStruct::PixmapClass &
 
339
KdmPixmap::getClass(ItemState sts)
 
340
{
 
341
    return
 
342
        (sts == Sprelight && pixmap.prelight.present) ?
 
343
            pixmap.prelight :
 
344
            (sts == Sactive && pixmap.active.present) ?
 
345
                pixmap.active :
 
346
                pixmap.normal;
 
347
}
 
348
 
 
349
void
 
350
KdmPixmap::slotAnimate(int sts)
 
351
{
 
352
    PixmapStruct::PixmapClass &pClass = getClass(ItemState(sts));
 
353
    pClass.readyPixmap = QPixmap();
 
354
    if (&pClass == &getCurClass())
 
355
        needUpdate();
 
356
}
 
357
 
 
358
void
 
359
KdmPixmap::statusChanged(bool descend)
 
360
{
 
361
    KdmItem::statusChanged(descend);
 
362
    if (!pixmap.active.present && !pixmap.prelight.present)
 
363
        return;
 
364
    if ((state == Sprelight && !pixmap.prelight.present) ||
 
365
        (state == Sactive && !pixmap.active.present))
 
366
        return;
 
367
    needUpdate();
 
368
}
 
369
 
 
370
#include "kdmpixmap.moc"