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