Preserve aspect ratio when setting Image.sourceSize
[profile/ivi/qtdeclarative.git] / src / quick / util / qdeclarativepixmapcache.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qdeclarativepixmapcache_p.h"
43 #include <qdeclarativenetworkaccessmanagerfactory.h>
44 #include <qdeclarativeimageprovider.h>
45
46 #include <qdeclarativeengine.h>
47 #include <private/qdeclarativeglobal_p.h>
48 #include <private/qdeclarativeengine_p.h>
49
50 #include <QtQuick/private/qsgtexture_p.h>
51 #include <QtQuick/private/qsgcontext_p.h>
52
53 #include <QCoreApplication>
54 #include <QImageReader>
55 #include <QHash>
56 #include <QNetworkReply>
57 #include <QPixmapCache>
58 #include <QFile>
59 #include <QThread>
60 #include <QMutex>
61 #include <QMutexLocker>
62 #include <QWaitCondition>
63 #include <QBuffer>
64 #include <QWaitCondition>
65 #include <QtCore/qdebug.h>
66 #include <private/qobject_p.h>
67 #include <QSslError>
68
69 #define IMAGEREQUEST_MAX_REQUEST_COUNT       8
70 #define IMAGEREQUEST_MAX_REDIRECT_RECURSION 16
71 #define CACHE_EXPIRE_TIME 30
72 #define CACHE_REMOVAL_FRACTION 4
73
74 QT_BEGIN_NAMESPACE
75
76 // The cache limit describes the maximum "junk" in the cache.
77 static int cache_limit = 2048 * 1024; // 2048 KB cache limit for embedded in qpixmapcache.cpp
78
79 QSGTexture *QDeclarativeDefaultTextureFactory::createTexture(QQuickCanvas *) const
80 {
81     QSGPlainTexture *t = new QSGPlainTexture();
82     t->setImage(im);
83     return t;
84 }
85
86 class QDeclarativePixmapReader;
87 class QDeclarativePixmapData;
88 class QDeclarativePixmapReply : public QObject
89 {
90     Q_OBJECT
91 public:
92     enum ReadError { NoError, Loading, Decoding };
93
94     QDeclarativePixmapReply(QDeclarativePixmapData *);
95     ~QDeclarativePixmapReply();
96
97     QDeclarativePixmapData *data;
98     QDeclarativeEngine *engineForReader; // always access reader inside readerMutex
99     QSize requestSize;
100     QUrl url;
101
102     bool loading;
103     int redirectCount;
104
105     class Event : public QEvent {
106     public:
107         Event(ReadError, const QString &, const QSize &, QDeclarativeTextureFactory *factory, const QImage &image);
108
109         ReadError error;
110         QString errorString;
111         QSize implicitSize;
112         QImage image;
113         QDeclarativeTextureFactory *textureFactory;
114     };
115     void postReply(ReadError, const QString &, const QSize &, const QImage &image);
116     void postReply(ReadError, const QString &, const QSize &, QDeclarativeTextureFactory *factory, const QImage &image);
117
118
119 Q_SIGNALS:
120     void finished();
121     void downloadProgress(qint64, qint64);
122
123 protected:
124     bool event(QEvent *event);
125
126 private:
127     Q_DISABLE_COPY(QDeclarativePixmapReply)
128
129 public:
130     static int finishedIndex;
131     static int downloadProgressIndex;
132 };
133
134 class QDeclarativePixmapReaderThreadObject : public QObject {
135     Q_OBJECT
136 public:
137     QDeclarativePixmapReaderThreadObject(QDeclarativePixmapReader *);
138     void processJobs();
139     virtual bool event(QEvent *e);
140 private slots:
141     void networkRequestDone();
142 private:
143     QDeclarativePixmapReader *reader;
144 };
145
146 class QDeclarativePixmapData;
147 class QDeclarativePixmapReader : public QThread
148 {
149     Q_OBJECT
150 public:
151     QDeclarativePixmapReader(QDeclarativeEngine *eng);
152     ~QDeclarativePixmapReader();
153
154     QDeclarativePixmapReply *getImage(QDeclarativePixmapData *);
155     void cancel(QDeclarativePixmapReply *rep);
156
157     static QDeclarativePixmapReader *instance(QDeclarativeEngine *engine);
158     static QDeclarativePixmapReader *existingInstance(QDeclarativeEngine *engine);
159
160 protected:
161     void run();
162
163 private:
164     friend class QDeclarativePixmapReaderThreadObject;
165     void processJobs();
166     void processJob(QDeclarativePixmapReply *, const QUrl &, const QSize &);
167     void networkRequestDone(QNetworkReply *);
168
169     QList<QDeclarativePixmapReply*> jobs;
170     QList<QDeclarativePixmapReply*> cancelled;
171     QDeclarativeEngine *engine;
172     QObject *eventLoopQuitHack;
173
174     QMutex mutex;
175     QDeclarativePixmapReaderThreadObject *threadObject;
176     QWaitCondition waitCondition;
177
178     QNetworkAccessManager *networkAccessManager();
179     QNetworkAccessManager *accessManager;
180
181     QHash<QNetworkReply*,QDeclarativePixmapReply*> replies;
182
183     static int replyDownloadProgress;
184     static int replyFinished;
185     static int downloadProgress;
186     static int threadNetworkRequestDone;
187     static QHash<QDeclarativeEngine *,QDeclarativePixmapReader*> readers;
188 public:
189     static QMutex readerMutex;
190 };
191
192 class QDeclarativePixmapData
193 {
194 public:
195     QDeclarativePixmapData(QDeclarativePixmap *pixmap, const QUrl &u, const QSize &s, const QString &e)
196     : refCount(1), inCache(false), pixmapStatus(QDeclarativePixmap::Error), 
197       url(u), errorString(e), requestSize(s), textureFactory(0), reply(0), prevUnreferenced(0),
198       prevUnreferencedPtr(0), nextUnreferenced(0)
199     {
200         declarativePixmaps.insert(pixmap);
201     }
202
203     QDeclarativePixmapData(QDeclarativePixmap *pixmap, const QUrl &u, const QSize &r)
204     : refCount(1), inCache(false), pixmapStatus(QDeclarativePixmap::Loading), 
205       url(u), requestSize(r), textureFactory(0), reply(0), prevUnreferenced(0), prevUnreferencedPtr(0),
206       nextUnreferenced(0)
207     {
208         declarativePixmaps.insert(pixmap);
209     }
210
211     QDeclarativePixmapData(QDeclarativePixmap *pixmap, const QUrl &u, const QImage &p, const QSize &s, const QSize &r)
212     : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QDeclarativePixmap::Ready),
213       url(u), image(p), implicitSize(s), requestSize(r), textureFactory(new QDeclarativeDefaultTextureFactory(p)), reply(0), prevUnreferenced(0),
214       prevUnreferencedPtr(0), nextUnreferenced(0)
215     {
216         declarativePixmaps.insert(pixmap);
217     }
218
219     QDeclarativePixmapData(QDeclarativePixmap *pixmap, const QUrl &u, QDeclarativeTextureFactory *factory, const QImage &p, const QSize &s, const QSize &r)
220     : refCount(1), inCache(false), privatePixmap(false), pixmapStatus(QDeclarativePixmap::Ready),
221       url(u), image(p), implicitSize(s), requestSize(r), textureFactory(factory), reply(0), prevUnreferenced(0),
222       prevUnreferencedPtr(0), nextUnreferenced(0)
223     {
224         declarativePixmaps.insert(pixmap);
225     }
226
227     QDeclarativePixmapData(QDeclarativePixmap *pixmap, const QImage &p)
228     : refCount(1), inCache(false), privatePixmap(true), pixmapStatus(QDeclarativePixmap::Ready),
229       image(p), implicitSize(p.size()), requestSize(p.size()), textureFactory(new QDeclarativeDefaultTextureFactory(p)), reply(0), prevUnreferenced(0),
230       prevUnreferencedPtr(0), nextUnreferenced(0)
231     {
232         declarativePixmaps.insert(pixmap);
233     }
234
235     ~QDeclarativePixmapData()
236     {
237         while (!declarativePixmaps.isEmpty()) {
238             QDeclarativePixmap *referencer = declarativePixmaps.first();
239             declarativePixmaps.remove(referencer);
240             referencer->d = 0;
241         }
242         delete textureFactory;
243     }
244
245     int cost() const;
246     void addref();
247     void release();
248     void addToCache();
249     void removeFromCache();
250
251     uint refCount;
252
253     bool inCache:1;
254     bool privatePixmap:1;
255     
256     QDeclarativePixmap::Status pixmapStatus;
257     QUrl url;
258     QString errorString;
259     QImage image;
260     QSize implicitSize;
261     QSize requestSize;
262
263     QDeclarativeTextureFactory *textureFactory;
264
265     QIntrusiveList<QDeclarativePixmap, &QDeclarativePixmap::dataListNode> declarativePixmaps;
266     QDeclarativePixmapReply *reply;
267
268     QDeclarativePixmapData *prevUnreferenced;
269     QDeclarativePixmapData**prevUnreferencedPtr;
270     QDeclarativePixmapData *nextUnreferenced;
271 };
272
273 int QDeclarativePixmapReply::finishedIndex = -1;
274 int QDeclarativePixmapReply::downloadProgressIndex = -1;
275
276 // XXX
277 QHash<QDeclarativeEngine *,QDeclarativePixmapReader*> QDeclarativePixmapReader::readers;
278 QMutex QDeclarativePixmapReader::readerMutex;
279
280 int QDeclarativePixmapReader::replyDownloadProgress = -1;
281 int QDeclarativePixmapReader::replyFinished = -1;
282 int QDeclarativePixmapReader::downloadProgress = -1;
283 int QDeclarativePixmapReader::threadNetworkRequestDone = -1;
284
285
286 void QDeclarativePixmapReply::postReply(ReadError error, const QString &errorString,
287                                         const QSize &implicitSize, const QImage &image)
288 {
289     loading = false;
290     QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, new QDeclarativeDefaultTextureFactory(image), image));
291 }
292
293 void QDeclarativePixmapReply::postReply(ReadError error, const QString &errorString,
294                                         const QSize &implicitSize, QDeclarativeTextureFactory *factory,
295                                         const QImage &image)
296 {
297     loading = false;
298     QCoreApplication::postEvent(this, new Event(error, errorString, implicitSize, factory, image));
299 }
300
301 QDeclarativePixmapReply::Event::Event(ReadError e, const QString &s, const QSize &iSize, QDeclarativeTextureFactory *factory, const QImage &i)
302     : QEvent(QEvent::User), error(e), errorString(s), implicitSize(iSize), image(i), textureFactory(factory)
303 {
304 }
305
306 QNetworkAccessManager *QDeclarativePixmapReader::networkAccessManager()
307 {
308     if (!accessManager) {
309         Q_ASSERT(threadObject);
310         accessManager = QDeclarativeEnginePrivate::get(engine)->createNetworkAccessManager(threadObject);
311     }
312     return accessManager;
313 }
314
315 static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, 
316                       const QSize &requestSize)
317 {
318     QImageReader imgio(dev);
319
320     bool force_scale = false;
321     if (url.path().endsWith(QLatin1String(".svg"),Qt::CaseInsensitive)) {
322         imgio.setFormat("svg"); // QSvgPlugin::capabilities bug QTBUG-9053
323         force_scale = true;
324     }
325
326     if (requestSize.width() > 0 || requestSize.height() > 0) {
327         QSize s = imgio.size();
328         qreal ratio = 0.0;
329         if (requestSize.width() && (force_scale || requestSize.width() < s.width())) {
330             ratio = qreal(requestSize.width())/s.width();
331         }
332         if (requestSize.height() && (force_scale || requestSize.height() < s.height())) {
333             qreal hr = qreal(requestSize.height())/s.height();
334             if (ratio == 0.0 || hr < ratio)
335                 ratio = hr;
336         }
337         if (ratio > 0.0) {
338             s.setHeight(qRound(s.height() * ratio));
339             s.setWidth(qRound(s.width() * ratio));
340             imgio.setScaledSize(s);
341         }
342     }
343
344     if (impsize)
345         *impsize = imgio.size();
346
347     if (imgio.read(image)) {
348         if (impsize && impsize->width() < 0)
349             *impsize = image->size();
350         return true;
351     } else {
352         if (errorString)
353             *errorString = QDeclarativePixmap::tr("Error decoding: %1: %2").arg(url.toString())
354                                 .arg(imgio.errorString());
355         return false;
356     }
357 }
358
359 QDeclarativePixmapReader::QDeclarativePixmapReader(QDeclarativeEngine *eng)
360 : QThread(eng), engine(eng), threadObject(0), accessManager(0)
361 {
362     eventLoopQuitHack = new QObject;
363     eventLoopQuitHack->moveToThread(this);
364     connect(eventLoopQuitHack, SIGNAL(destroyed(QObject*)), SLOT(quit()), Qt::DirectConnection);
365     start(QThread::LowestPriority);
366 }
367
368 QDeclarativePixmapReader::~QDeclarativePixmapReader()
369 {
370     readerMutex.lock();
371     readers.remove(engine);
372     readerMutex.unlock();
373
374     mutex.lock();
375     // manually cancel all outstanding jobs.
376     foreach (QDeclarativePixmapReply *reply, jobs) {
377         delete reply;
378     }
379     jobs.clear();
380     QList<QDeclarativePixmapReply*> activeJobs = replies.values();
381     foreach (QDeclarativePixmapReply *reply, activeJobs) {
382         if (reply->loading) {
383             cancelled.append(reply);
384             reply->data = 0;
385         }
386     }
387     if (threadObject) threadObject->processJobs();
388     mutex.unlock();
389
390     eventLoopQuitHack->deleteLater();
391     wait();
392 }
393
394 void QDeclarativePixmapReader::networkRequestDone(QNetworkReply *reply)
395 {
396     QDeclarativePixmapReply *job = replies.take(reply);
397
398     if (job) {
399         job->redirectCount++;
400         if (job->redirectCount < IMAGEREQUEST_MAX_REDIRECT_RECURSION) {
401             QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
402             if (redirect.isValid()) {
403                 QUrl url = reply->url().resolved(redirect.toUrl());
404                 QNetworkRequest req(url);
405                 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
406
407                 reply->deleteLater();
408                 reply = networkAccessManager()->get(req);
409
410                 QMetaObject::connect(reply, replyDownloadProgress, job, downloadProgress);
411                 QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
412
413                 replies.insert(reply, job);
414                 return;
415             }
416         }
417
418         QImage image;
419         QDeclarativePixmapReply::ReadError error = QDeclarativePixmapReply::NoError;
420         QString errorString;
421         QSize readSize;
422         if (reply->error()) {
423             error = QDeclarativePixmapReply::Loading;
424             errorString = reply->errorString();
425         } else {
426             QByteArray all = reply->readAll();
427             QBuffer buff(&all);
428             buff.open(QIODevice::ReadOnly);
429             if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, job->requestSize))
430                 error = QDeclarativePixmapReply::Decoding;
431        }
432         // send completion event to the QDeclarativePixmapReply
433         mutex.lock();
434         if (!cancelled.contains(job)) {
435             QDeclarativeTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
436             if (factory)
437                 job->postReply(error, errorString, readSize, factory, image);
438             else
439                 job->postReply(error, errorString, readSize, image);
440         }
441         mutex.unlock();
442     }
443     reply->deleteLater();
444
445     // kick off event loop again incase we have dropped below max request count
446     threadObject->processJobs();
447 }
448
449 QDeclarativePixmapReaderThreadObject::QDeclarativePixmapReaderThreadObject(QDeclarativePixmapReader *i)
450 : reader(i)
451 {
452 }
453
454 void QDeclarativePixmapReaderThreadObject::processJobs() 
455
456     QCoreApplication::postEvent(this, new QEvent(QEvent::User)); 
457 }
458
459 bool QDeclarativePixmapReaderThreadObject::event(QEvent *e) 
460 {
461     if (e->type() == QEvent::User) { 
462         reader->processJobs(); 
463         return true; 
464     } else { 
465         return QObject::event(e);
466     }
467 }
468
469 void QDeclarativePixmapReaderThreadObject::networkRequestDone()
470 {
471     QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
472     reader->networkRequestDone(reply);
473 }
474
475 void QDeclarativePixmapReader::processJobs()
476 {
477     QMutexLocker locker(&mutex);
478
479     while (true) {
480         if (cancelled.isEmpty() && (jobs.isEmpty() || replies.count() >= IMAGEREQUEST_MAX_REQUEST_COUNT)) 
481             return; // Nothing else to do
482
483         // Clean cancelled jobs
484         if (cancelled.count()) {
485             for (int i = 0; i < cancelled.count(); ++i) {
486                 QDeclarativePixmapReply *job = cancelled.at(i);
487                 QNetworkReply *reply = replies.key(job, 0);
488                 if (reply && reply->isRunning()) {
489                     // cancel any jobs already started
490                     replies.remove(reply);
491                     reply->close();
492                 }
493                 // deleteLater, since not owned by this thread
494                 job->deleteLater();
495             }
496             cancelled.clear();
497         }
498
499         if (!jobs.isEmpty() && replies.count() < IMAGEREQUEST_MAX_REQUEST_COUNT) {
500             QDeclarativePixmapReply *runningJob = jobs.takeLast();
501             runningJob->loading = true;
502
503             QUrl url = runningJob->url;
504             QSize requestSize = runningJob->requestSize;
505             locker.unlock();
506             processJob(runningJob, url, requestSize);
507             locker.relock();
508         }
509     }
510 }
511
512 void QDeclarativePixmapReader::processJob(QDeclarativePixmapReply *runningJob, const QUrl &url, 
513                                           const QSize &requestSize)
514 {
515     // fetch
516     if (url.scheme() == QLatin1String("image")) {
517         // Use QmlImageProvider
518         QSize readSize;
519         QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine);
520         QDeclarativeImageProvider::ImageType imageType = ep->getImageProviderType(url);
521         if (imageType == QDeclarativeImageProvider::Invalid) {
522             QDeclarativePixmapReply::ReadError errorCode = QDeclarativePixmapReply::Loading;
523             QString errorStr = QDeclarativePixmap::tr("Invalid image provider: %1").arg(url.toString());
524             QImage image;
525             mutex.lock();
526             if (!cancelled.contains(runningJob))
527                 runningJob->postReply(errorCode, errorStr, readSize, image);
528             mutex.unlock();
529         } else if (imageType == QDeclarativeImageProvider::Image) {
530             QImage image = ep->getImageFromProvider(url, &readSize, requestSize);
531             QDeclarativePixmapReply::ReadError errorCode = QDeclarativePixmapReply::NoError;
532             QString errorStr;
533             if (image.isNull()) {
534                 errorCode = QDeclarativePixmapReply::Loading;
535                 errorStr = QDeclarativePixmap::tr("Failed to get image from provider: %1").arg(url.toString());
536             }
537             mutex.lock();
538             if (!cancelled.contains(runningJob)) {
539                 QDeclarativeTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
540                 if (factory)
541                     runningJob->postReply(errorCode, errorStr, readSize, factory, image);
542                 else
543                     runningJob->postReply(errorCode, errorStr, readSize, image);
544             }
545
546             mutex.unlock();
547         } else {
548             QDeclarativeTextureFactory *t = ep->getTextureFromProvider(url, &readSize, requestSize);
549             QDeclarativePixmapReply::ReadError errorCode = QDeclarativePixmapReply::NoError;
550             QString errorStr;
551             if (!t) {
552                 errorCode = QDeclarativePixmapReply::Loading;
553                 errorStr = QDeclarativePixmap::tr("Failed to get texture from provider: %1").arg(url.toString());
554             }
555             mutex.lock();
556             if (!cancelled.contains(runningJob))
557                 runningJob->postReply(errorCode, errorStr, readSize, t, QImage());
558             mutex.unlock();
559
560         }
561
562     } else {
563         QString lf = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
564         if (!lf.isEmpty()) {
565             // Image is local - load/decode immediately
566             QImage image;
567             QDeclarativePixmapReply::ReadError errorCode = QDeclarativePixmapReply::NoError;
568             QString errorStr;
569             QFile f(lf);
570             QSize readSize;
571             if (f.open(QIODevice::ReadOnly)) {
572                 if (!readImage(url, &f, &image, &errorStr, &readSize, requestSize))
573                     errorCode = QDeclarativePixmapReply::Loading;
574             } else {
575                 errorStr = QDeclarativePixmap::tr("Cannot open: %1").arg(url.toString());
576                 errorCode = QDeclarativePixmapReply::Loading;
577             }
578             mutex.lock();
579             if (!cancelled.contains(runningJob)) {
580                 QDeclarativeTextureFactory *factory = QSGContext::createTextureFactoryFromImage(image);
581                 if (factory)
582                     runningJob->postReply(errorCode, errorStr, readSize, factory, image);
583                 else
584                     runningJob->postReply(errorCode, errorStr, readSize, image);
585             }
586             mutex.unlock();
587         } else {
588             // Network resource
589             QNetworkRequest req(url);
590             req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
591             QNetworkReply *reply = networkAccessManager()->get(req);
592
593             QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress);
594             QMetaObject::connect(reply, replyFinished, threadObject, threadNetworkRequestDone);
595
596             replies.insert(reply, runningJob);
597         }
598     }
599 }
600
601 QDeclarativePixmapReader *QDeclarativePixmapReader::instance(QDeclarativeEngine *engine)
602 {
603     // XXX NOTE: must be called within readerMutex locking.
604     QDeclarativePixmapReader *reader = readers.value(engine);
605     if (!reader) {
606         reader = new QDeclarativePixmapReader(engine);
607         readers.insert(engine, reader);
608     }
609
610     return reader;
611 }
612
613 QDeclarativePixmapReader *QDeclarativePixmapReader::existingInstance(QDeclarativeEngine *engine)
614 {
615     // XXX NOTE: must be called within readerMutex locking.
616     return readers.value(engine, 0);
617 }
618
619 QDeclarativePixmapReply *QDeclarativePixmapReader::getImage(QDeclarativePixmapData *data)
620 {
621     mutex.lock();
622     QDeclarativePixmapReply *reply = new QDeclarativePixmapReply(data);
623     reply->engineForReader = engine;
624     jobs.append(reply);
625     // XXX 
626     if (threadObject) threadObject->processJobs();
627     mutex.unlock();
628     return reply;
629 }
630
631 void QDeclarativePixmapReader::cancel(QDeclarativePixmapReply *reply)
632 {
633     mutex.lock();
634     if (reply->loading) {
635         cancelled.append(reply);
636         reply->data = 0;
637         // XXX 
638         if (threadObject) threadObject->processJobs();
639     } else {
640         jobs.removeAll(reply);
641         delete reply;
642     }
643     mutex.unlock();
644 }
645
646 void QDeclarativePixmapReader::run()
647 {
648     if (replyDownloadProgress == -1) {
649         const QMetaObject *nr = &QNetworkReply::staticMetaObject;
650         const QMetaObject *pr = &QDeclarativePixmapReply::staticMetaObject;
651         const QMetaObject *ir = &QDeclarativePixmapReaderThreadObject::staticMetaObject;
652         replyDownloadProgress = nr->indexOfSignal("downloadProgress(qint64,qint64)");
653         replyFinished = nr->indexOfSignal("finished()");
654         downloadProgress = pr->indexOfSignal("downloadProgress(qint64,qint64)");
655         threadNetworkRequestDone = ir->indexOfSlot("networkRequestDone()");
656     }
657
658     mutex.lock();
659     threadObject = new QDeclarativePixmapReaderThreadObject(this);
660     mutex.unlock();
661
662     processJobs();
663     exec();
664
665     delete threadObject;
666     threadObject = 0;
667 }
668
669 class QDeclarativePixmapKey
670 {
671 public:
672     const QUrl *url;
673     const QSize *size;
674 };
675
676 inline bool operator==(const QDeclarativePixmapKey &lhs, const QDeclarativePixmapKey &rhs)
677 {
678     return *lhs.size == *rhs.size && *lhs.url == *rhs.url;
679 }
680
681 inline uint qHash(const QDeclarativePixmapKey &key)
682 {
683     return qHash(*key.url) ^ key.size->width() ^ key.size->height();
684 }
685
686 class QSGContext;
687
688 class QDeclarativePixmapStore : public QObject
689 {
690     Q_OBJECT
691 public:
692     QDeclarativePixmapStore();
693     ~QDeclarativePixmapStore();
694
695     void unreferencePixmap(QDeclarativePixmapData *);
696     void referencePixmap(QDeclarativePixmapData *);
697
698 protected:
699     virtual void timerEvent(QTimerEvent *);
700
701 public:
702     QHash<QDeclarativePixmapKey, QDeclarativePixmapData *> m_cache;
703
704 private:
705     void shrinkCache(int remove);
706
707     QDeclarativePixmapData *m_unreferencedPixmaps;
708     QDeclarativePixmapData *m_lastUnreferencedPixmap;
709
710     int m_unreferencedCost;
711     int m_timerId;
712     bool m_destroying;
713 };
714 Q_GLOBAL_STATIC(QDeclarativePixmapStore, pixmapStore);
715
716
717 QDeclarativePixmapStore::QDeclarativePixmapStore()
718     : m_unreferencedPixmaps(0), m_lastUnreferencedPixmap(0), m_unreferencedCost(0), m_timerId(-1), m_destroying(false)
719 {
720 }
721
722 QDeclarativePixmapStore::~QDeclarativePixmapStore()
723 {
724     m_destroying = true;
725
726     int leakedPixmaps = 0;
727     QList<QDeclarativePixmapData*> cachedData = m_cache.values();
728
729     // Prevent unreferencePixmap() from assuming it needs to kick
730     // off the cache expiry timer, as we're shrinking the cache
731     // manually below after releasing all the pixmaps.
732     m_timerId = -2;
733
734     // unreference all (leaked) pixmaps
735     foreach (QDeclarativePixmapData* pixmap, cachedData) {
736         int currRefCount = pixmap->refCount;
737         if (currRefCount) {
738             leakedPixmaps++;
739             while (currRefCount > 0) {
740                 pixmap->release();
741                 currRefCount--;
742             }
743         }
744     }
745
746     // free all unreferenced pixmaps
747     while (m_lastUnreferencedPixmap) {
748         shrinkCache(20);
749     }
750
751     if (leakedPixmaps)
752         qDebug("Number of leaked pixmaps: %i", leakedPixmaps);
753 }
754
755 void QDeclarativePixmapStore::unreferencePixmap(QDeclarativePixmapData *data)
756 {
757     Q_ASSERT(data->prevUnreferenced == 0);
758     Q_ASSERT(data->prevUnreferencedPtr == 0);
759     Q_ASSERT(data->nextUnreferenced == 0);
760
761     data->nextUnreferenced = m_unreferencedPixmaps;
762     data->prevUnreferencedPtr = &m_unreferencedPixmaps;
763
764     m_unreferencedPixmaps = data;
765     if (m_unreferencedPixmaps->nextUnreferenced) {
766         m_unreferencedPixmaps->nextUnreferenced->prevUnreferenced = m_unreferencedPixmaps;
767         m_unreferencedPixmaps->nextUnreferenced->prevUnreferencedPtr = &m_unreferencedPixmaps->nextUnreferenced;
768     }
769
770     if (!m_lastUnreferencedPixmap)
771         m_lastUnreferencedPixmap = data;
772
773     m_unreferencedCost += data->cost();
774
775     shrinkCache(-1); // Shrink the cache incase it has become larger than cache_limit
776
777     if (m_timerId == -1 && m_unreferencedPixmaps && !m_destroying)
778         m_timerId = startTimer(CACHE_EXPIRE_TIME * 1000);
779 }
780
781 void QDeclarativePixmapStore::referencePixmap(QDeclarativePixmapData *data)
782 {
783     Q_ASSERT(data->prevUnreferencedPtr);
784
785     *data->prevUnreferencedPtr = data->nextUnreferenced;
786     if (data->nextUnreferenced) { 
787         data->nextUnreferenced->prevUnreferencedPtr = data->prevUnreferencedPtr;
788         data->nextUnreferenced->prevUnreferenced = data->prevUnreferenced;
789     }
790     if (m_lastUnreferencedPixmap == data)
791         m_lastUnreferencedPixmap = data->prevUnreferenced;
792
793     data->nextUnreferenced = 0;
794     data->prevUnreferencedPtr = 0;
795     data->prevUnreferenced = 0;
796
797     m_unreferencedCost -= data->cost();
798 }
799
800 void QDeclarativePixmapStore::shrinkCache(int remove)
801 {
802     while ((remove > 0 || m_unreferencedCost > cache_limit) && m_lastUnreferencedPixmap) {
803         QDeclarativePixmapData *data = m_lastUnreferencedPixmap;
804         Q_ASSERT(data->nextUnreferenced == 0);
805
806         *data->prevUnreferencedPtr = 0;
807         m_lastUnreferencedPixmap = data->prevUnreferenced;
808         data->prevUnreferencedPtr = 0;
809         data->prevUnreferenced = 0;
810
811         remove -= data->cost();
812         m_unreferencedCost -= data->cost();
813         data->removeFromCache();
814         delete data;
815     }
816 }
817
818 void QDeclarativePixmapStore::timerEvent(QTimerEvent *)
819 {
820     int removalCost = m_unreferencedCost / CACHE_REMOVAL_FRACTION;
821
822     shrinkCache(removalCost);
823
824     if (m_unreferencedPixmaps == 0) {
825         killTimer(m_timerId);
826         m_timerId = -1;
827     }
828 }
829
830 QDeclarativePixmapReply::QDeclarativePixmapReply(QDeclarativePixmapData *d)
831 : data(d), engineForReader(0), requestSize(d->requestSize), url(d->url), loading(false), redirectCount(0)
832 {
833     if (finishedIndex == -1) {
834         finishedIndex = QDeclarativePixmapReply::staticMetaObject.indexOfSignal("finished()");
835         downloadProgressIndex = QDeclarativePixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)");
836     }
837 }
838
839 QDeclarativePixmapReply::~QDeclarativePixmapReply()
840 {
841 }
842
843 bool QDeclarativePixmapReply::event(QEvent *event)
844 {
845     if (event->type() == QEvent::User) {
846
847         if (data) {
848             Event *de = static_cast<Event *>(event);
849             data->pixmapStatus = (de->error == NoError) ? QDeclarativePixmap::Ready : QDeclarativePixmap::Error;
850
851             if (data->pixmapStatus == QDeclarativePixmap::Ready) {
852                 if (de->textureFactory) {
853                     data->textureFactory = de->textureFactory;
854                 }
855                 data->image = de->image;
856                 data->implicitSize = de->implicitSize;
857             } else {
858                 data->errorString = de->errorString;
859                 data->removeFromCache(); // We don't continue to cache error'd pixmaps
860             }
861
862             data->reply = 0;
863             emit finished();
864         }
865
866         delete this;
867         return true;
868     } else {
869         return QObject::event(event);
870     }
871 }
872
873 int QDeclarativePixmapData::cost() const
874 {
875     if (textureFactory)
876         return textureFactory->textureByteCount();
877     return image.byteCount();
878 }
879
880 void QDeclarativePixmapData::addref()
881 {
882     ++refCount;
883     if (prevUnreferencedPtr) 
884         pixmapStore()->referencePixmap(this);
885 }
886
887 void QDeclarativePixmapData::release()
888 {
889     Q_ASSERT(refCount > 0);
890     --refCount;
891     if (refCount == 0) {
892         if (reply) {
893             QDeclarativePixmapReply *cancelReply = reply;
894             reply->data = 0;
895             reply = 0;
896             QDeclarativePixmapReader::readerMutex.lock();
897             QDeclarativePixmapReader *reader = QDeclarativePixmapReader::existingInstance(cancelReply->engineForReader);
898             if (reader)
899                 reader->cancel(cancelReply);
900             QDeclarativePixmapReader::readerMutex.unlock();
901         }
902
903         if (pixmapStatus == QDeclarativePixmap::Ready) {
904             pixmapStore()->unreferencePixmap(this);
905         } else {
906             removeFromCache();
907             delete this;
908         }
909     }
910 }
911
912 void QDeclarativePixmapData::addToCache()
913 {
914     if (!inCache) {
915         QDeclarativePixmapKey key = { &url, &requestSize };
916         pixmapStore()->m_cache.insert(key, this);
917         inCache = true;
918     }
919 }
920
921 void QDeclarativePixmapData::removeFromCache()
922 {
923     if (inCache) {
924         QDeclarativePixmapKey key = { &url, &requestSize };
925         pixmapStore()->m_cache.remove(key);
926         inCache = false;
927     }
928 }
929
930 static QDeclarativePixmapData* createPixmapDataSync(QDeclarativePixmap *declarativePixmap, QDeclarativeEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
931 {
932     if (url.scheme() == QLatin1String("image")) {
933         QSize readSize;
934         QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine);
935         QDeclarativeImageProvider::ImageType imageType = ep->getImageProviderType(url);
936
937         switch (imageType) {
938             case QDeclarativeImageProvider::Invalid:
939                 return new QDeclarativePixmapData(declarativePixmap, url, requestSize,
940                     QDeclarativePixmap::tr("Invalid image provider: %1").arg(url.toString()));
941             case QDeclarativeImageProvider::Texture:
942             {
943                 QDeclarativeTextureFactory *texture = ep->getTextureFromProvider(url, &readSize, requestSize);
944                 if (texture) {
945                     *ok = true;
946                     return new QDeclarativePixmapData(declarativePixmap, url, texture, QImage(), readSize, requestSize);
947                 }
948             }
949
950             case QDeclarativeImageProvider::Image:
951             {
952                 QImage image = ep->getImageFromProvider(url, &readSize, requestSize);
953                 if (!image.isNull()) {
954                     *ok = true;
955                     return new QDeclarativePixmapData(declarativePixmap, url, image, readSize, requestSize);
956                 }
957             }
958             case QDeclarativeImageProvider::Pixmap:
959             {
960                 QPixmap pixmap = ep->getPixmapFromProvider(url, &readSize, requestSize);
961                 if (!pixmap.isNull()) {
962                     *ok = true;
963                     return new QDeclarativePixmapData(declarativePixmap, url, pixmap.toImage(), readSize, requestSize);
964                 }
965             }
966         }
967
968         // provider has bad image type, or provider returned null image
969         return new QDeclarativePixmapData(declarativePixmap, url, requestSize,
970             QDeclarativePixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
971     }
972
973     QString localFile = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
974     if (localFile.isEmpty()) 
975         return 0;
976
977     QFile f(localFile);
978     QSize readSize;
979     QString errorString;
980
981     if (f.open(QIODevice::ReadOnly)) {
982         QImage image;
983
984         if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
985             *ok = true;
986             return new QDeclarativePixmapData(declarativePixmap, url, image, readSize, requestSize);
987         }
988         errorString = QDeclarativePixmap::tr("Invalid image data: %1").arg(url.toString());
989
990     } else {
991         errorString = QDeclarativePixmap::tr("Cannot open: %1").arg(url.toString());
992     }
993     return new QDeclarativePixmapData(declarativePixmap, url, requestSize, errorString);
994 }
995
996
997 struct QDeclarativePixmapNull {
998     QUrl url;
999     QImage image;
1000     QSize size;
1001 };
1002 Q_GLOBAL_STATIC(QDeclarativePixmapNull, nullPixmap);
1003
1004 QDeclarativePixmap::QDeclarativePixmap()
1005 : d(0)
1006 {
1007 }
1008
1009 QDeclarativePixmap::QDeclarativePixmap(QDeclarativeEngine *engine, const QUrl &url)
1010 : d(0)
1011 {
1012     load(engine, url);
1013 }
1014
1015 QDeclarativePixmap::QDeclarativePixmap(QDeclarativeEngine *engine, const QUrl &url, const QSize &size)
1016 : d(0)
1017 {
1018     load(engine, url, size);
1019 }
1020
1021 QDeclarativePixmap::~QDeclarativePixmap()
1022 {
1023     if (d) {
1024         d->declarativePixmaps.remove(this);
1025         d->release();
1026         d = 0;
1027     }
1028 }
1029
1030 bool QDeclarativePixmap::isNull() const
1031 {
1032     return d == 0;
1033 }
1034
1035 bool QDeclarativePixmap::isReady() const
1036 {
1037     return status() == Ready;
1038 }
1039
1040 bool QDeclarativePixmap::isError() const
1041 {
1042     return status() == Error;
1043 }
1044
1045 bool QDeclarativePixmap::isLoading() const
1046 {
1047     return status() == Loading;
1048 }
1049
1050 QString QDeclarativePixmap::error() const
1051 {
1052     if (d)
1053         return d->errorString;
1054     else
1055         return QString();
1056 }
1057
1058 QDeclarativePixmap::Status QDeclarativePixmap::status() const
1059 {
1060     if (d)
1061         return d->pixmapStatus;
1062     else
1063         return Null;
1064 }
1065
1066 const QUrl &QDeclarativePixmap::url() const
1067 {
1068     if (d)
1069         return d->url;
1070     else
1071         return nullPixmap()->url;
1072 }
1073
1074 const QSize &QDeclarativePixmap::implicitSize() const
1075 {
1076     if (d) 
1077         return d->implicitSize;
1078     else
1079         return nullPixmap()->size;
1080 }
1081
1082 const QSize &QDeclarativePixmap::requestSize() const
1083 {
1084     if (d)
1085         return d->requestSize;
1086     else
1087         return nullPixmap()->size;
1088 }
1089
1090 QDeclarativeTextureFactory *QDeclarativePixmap::textureFactory() const
1091 {
1092     if (d)
1093         return d->textureFactory;
1094
1095     return 0;
1096 }
1097
1098 const QImage &QDeclarativePixmap::image() const
1099 {
1100     if (d) 
1101         return d->image;
1102     else
1103         return nullPixmap()->image;
1104 }
1105
1106 void QDeclarativePixmap::setImage(const QImage &p)
1107 {
1108     clear();
1109
1110     if (!p.isNull())
1111         d = new QDeclarativePixmapData(this, p);
1112 }
1113
1114 int QDeclarativePixmap::width() const
1115 {
1116     if (d) 
1117         return d->textureFactory ? d->textureFactory->textureSize().width() : d->image.width();
1118     else
1119         return 0;
1120 }
1121
1122 int QDeclarativePixmap::height() const
1123 {
1124     if (d) 
1125         return d->textureFactory ? d->textureFactory->textureSize().height() : d->image.height();
1126     else
1127         return 0;
1128 }
1129
1130 QRect QDeclarativePixmap::rect() const
1131 {
1132     if (d)
1133         return d->textureFactory ? QRect(QPoint(), d->textureFactory->textureSize()) : d->image.rect();
1134     else
1135         return QRect();
1136 }
1137
1138 void QDeclarativePixmap::load(QDeclarativeEngine *engine, const QUrl &url)
1139 {
1140     load(engine, url, QSize(), QDeclarativePixmap::Cache);
1141 }
1142
1143 void QDeclarativePixmap::load(QDeclarativeEngine *engine, const QUrl &url, QDeclarativePixmap::Options options)
1144 {
1145     load(engine, url, QSize(), options);
1146 }
1147
1148 void QDeclarativePixmap::load(QDeclarativeEngine *engine, const QUrl &url, const QSize &size)
1149 {
1150     load(engine, url, size, QDeclarativePixmap::Cache);
1151 }
1152
1153 void QDeclarativePixmap::load(QDeclarativeEngine *engine, const QUrl &url, const QSize &requestSize, QDeclarativePixmap::Options options)
1154 {
1155     if (d) {
1156         d->declarativePixmaps.remove(this);
1157         d->release();
1158         d = 0;
1159     }
1160
1161     QDeclarativePixmapKey key = { &url, &requestSize };
1162     QDeclarativePixmapStore *store = pixmapStore();
1163
1164     QHash<QDeclarativePixmapKey, QDeclarativePixmapData *>::Iterator iter = store->m_cache.find(key);
1165
1166     if (iter == store->m_cache.end()) {
1167         if (options & QDeclarativePixmap::Asynchronous) {
1168             // pixmaps can only be loaded synchronously
1169             if (url.scheme() == QLatin1String("image") 
1170                     && QDeclarativeEnginePrivate::get(engine)->getImageProviderType(url) == QDeclarativeImageProvider::Pixmap) {
1171                 options &= ~QDeclarativePixmap::Asynchronous;
1172             }
1173         }
1174
1175         if (!(options & QDeclarativePixmap::Asynchronous)) {
1176             bool ok = false;
1177             d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1178             if (ok) {
1179                 if (options & QDeclarativePixmap::Cache)
1180                     d->addToCache();
1181                 return;
1182             }
1183             if (d)  // loadable, but encountered error while loading
1184                 return;
1185         } 
1186
1187         if (!engine)
1188             return;
1189
1190         d = new QDeclarativePixmapData(this, url, requestSize);
1191         if (options & QDeclarativePixmap::Cache)
1192             d->addToCache();
1193
1194         QDeclarativePixmapReader::readerMutex.lock();
1195         d->reply = QDeclarativePixmapReader::instance(engine)->getImage(d);
1196         QDeclarativePixmapReader::readerMutex.unlock();
1197     } else {
1198         d = *iter;
1199         d->addref();
1200         d->declarativePixmaps.insert(this);
1201     }
1202 }
1203
1204 void QDeclarativePixmap::clear()
1205 {
1206     if (d) {
1207         d->declarativePixmaps.remove(this);
1208         d->release();
1209         d = 0;
1210     }
1211 }
1212
1213 void QDeclarativePixmap::clear(QObject *obj)
1214 {
1215     if (d) {
1216         if (d->reply) 
1217             QObject::disconnect(d->reply, 0, obj, 0);
1218         d->declarativePixmaps.remove(this);
1219         d->release();
1220         d = 0;
1221     }
1222 }
1223
1224 bool QDeclarativePixmap::connectFinished(QObject *object, const char *method)
1225 {
1226     if (!d || !d->reply) {
1227         qWarning("QDeclarativePixmap: connectFinished() called when not loading.");
1228         return false;
1229     }
1230
1231     return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1232 }
1233
1234 bool QDeclarativePixmap::connectFinished(QObject *object, int method)
1235 {
1236     if (!d || !d->reply) {
1237         qWarning("QDeclarativePixmap: connectFinished() called when not loading.");
1238         return false;
1239     }
1240
1241     return QMetaObject::connect(d->reply, QDeclarativePixmapReply::finishedIndex, object, method);
1242 }
1243
1244 bool QDeclarativePixmap::connectDownloadProgress(QObject *object, const char *method)
1245 {
1246     if (!d || !d->reply) {
1247         qWarning("QDeclarativePixmap: connectDownloadProgress() called when not loading.");
1248         return false;
1249     }
1250
1251     return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1252 }
1253
1254 bool QDeclarativePixmap::connectDownloadProgress(QObject *object, int method)
1255 {
1256     if (!d || !d->reply) {
1257         qWarning("QDeclarativePixmap: connectDownloadProgress() called when not loading.");
1258         return false;
1259     }
1260
1261     return QMetaObject::connect(d->reply, QDeclarativePixmapReply::downloadProgressIndex, object, method);
1262 }
1263
1264 QT_END_NAMESPACE
1265
1266 #include <qdeclarativepixmapcache.moc>