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