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