QQuickCanvas renames
[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             pixmapStore()->unreferencePixmap(this);
906         } else {
907             removeFromCache();
908             delete this;
909         }
910     }
911 }
912
913 void QQuickPixmapData::addToCache()
914 {
915     if (!inCache) {
916         QQuickPixmapKey key = { &url, &requestSize };
917         pixmapStore()->m_cache.insert(key, this);
918         inCache = true;
919     }
920 }
921
922 void QQuickPixmapData::removeFromCache()
923 {
924     if (inCache) {
925         QQuickPixmapKey key = { &url, &requestSize };
926         pixmapStore()->m_cache.remove(key);
927         inCache = false;
928     }
929 }
930
931 static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, bool *ok)
932 {
933     if (url.scheme() == QLatin1String("image")) {
934         QSize readSize;
935
936         QQuickImageProvider::ImageType imageType = QQuickImageProvider::Invalid;
937         QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)));
938         if (provider)
939             imageType = provider->imageType();
940
941         switch (imageType) {
942             case QQuickImageProvider::Invalid:
943                 return new QQuickPixmapData(declarativePixmap, url, requestSize,
944                     QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
945             case QQuickImageProvider::Texture:
946             {
947                 QQuickTextureFactory *texture = provider->requestTexture(imageId(url), &readSize, requestSize);
948                 if (texture) {
949                     *ok = true;
950                     return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize);
951                 }
952             }
953
954             case QQuickImageProvider::Image:
955             {
956                 QImage image = provider->requestImage(imageId(url), &readSize, requestSize);
957                 if (!image.isNull()) {
958                     *ok = true;
959                     return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
960                 }
961             }
962             case QQuickImageProvider::Pixmap:
963             {
964                 QPixmap pixmap = provider->requestPixmap(imageId(url), &readSize, requestSize);
965                 if (!pixmap.isNull()) {
966                     *ok = true;
967                     return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(pixmap.toImage()), readSize, requestSize);
968                 }
969             }
970         }
971
972         // provider has bad image type, or provider returned null image
973         return new QQuickPixmapData(declarativePixmap, url, requestSize,
974             QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
975     }
976
977     QString localFile = QQmlFile::urlToLocalFileOrQrc(url);
978     if (localFile.isEmpty()) 
979         return 0;
980
981     QFile f(localFile);
982     QSize readSize;
983     QString errorString;
984
985     if (f.open(QIODevice::ReadOnly)) {
986         QImage image;
987
988         if (readImage(url, &f, &image, &errorString, &readSize, requestSize)) {
989             *ok = true;
990             return new QQuickPixmapData(declarativePixmap, url, textureFactoryForImage(image), readSize, requestSize);
991         }
992         errorString = QQuickPixmap::tr("Invalid image data: %1").arg(url.toString());
993
994     } else {
995         errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
996     }
997     return new QQuickPixmapData(declarativePixmap, url, requestSize, errorString);
998 }
999
1000
1001 struct QQuickPixmapNull {
1002     QUrl url;
1003     QSize size;
1004 };
1005 Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
1006
1007 QQuickPixmap::QQuickPixmap()
1008 : d(0)
1009 {
1010 }
1011
1012 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
1013 : d(0)
1014 {
1015     load(engine, url);
1016 }
1017
1018 QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
1019 : d(0)
1020 {
1021     load(engine, url, size);
1022 }
1023
1024 QQuickPixmap::~QQuickPixmap()
1025 {
1026     if (d) {
1027         d->declarativePixmaps.remove(this);
1028         d->release();
1029         d = 0;
1030     }
1031 }
1032
1033 bool QQuickPixmap::isNull() const
1034 {
1035     return d == 0;
1036 }
1037
1038 bool QQuickPixmap::isReady() const
1039 {
1040     return status() == Ready;
1041 }
1042
1043 bool QQuickPixmap::isError() const
1044 {
1045     return status() == Error;
1046 }
1047
1048 bool QQuickPixmap::isLoading() const
1049 {
1050     return status() == Loading;
1051 }
1052
1053 QString QQuickPixmap::error() const
1054 {
1055     if (d)
1056         return d->errorString;
1057     else
1058         return QString();
1059 }
1060
1061 QQuickPixmap::Status QQuickPixmap::status() const
1062 {
1063     if (d)
1064         return d->pixmapStatus;
1065     else
1066         return Null;
1067 }
1068
1069 const QUrl &QQuickPixmap::url() const
1070 {
1071     if (d)
1072         return d->url;
1073     else
1074         return nullPixmap()->url;
1075 }
1076
1077 const QSize &QQuickPixmap::implicitSize() const
1078 {
1079     if (d) 
1080         return d->implicitSize;
1081     else
1082         return nullPixmap()->size;
1083 }
1084
1085 const QSize &QQuickPixmap::requestSize() const
1086 {
1087     if (d)
1088         return d->requestSize;
1089     else
1090         return nullPixmap()->size;
1091 }
1092
1093 QQuickTextureFactory *QQuickPixmap::textureFactory() const
1094 {
1095     if (d)
1096         return d->textureFactory;
1097
1098     return 0;
1099 }
1100
1101 QImage QQuickPixmap::image() const
1102 {
1103     if (d && d->textureFactory)
1104         return d->textureFactory->image();
1105     return QImage();
1106 }
1107
1108 void QQuickPixmap::setImage(const QImage &p)
1109 {
1110     clear();
1111
1112     if (!p.isNull())
1113         d = new QQuickPixmapData(this, textureFactoryForImage(p));
1114 }
1115
1116 int QQuickPixmap::width() const
1117 {
1118     if (d && d->textureFactory)
1119         return d->textureFactory->textureSize().width();
1120     else
1121         return 0;
1122 }
1123
1124 int QQuickPixmap::height() const
1125 {
1126     if (d && d->textureFactory)
1127         return d->textureFactory->textureSize().height();
1128     else
1129         return 0;
1130 }
1131
1132 QRect QQuickPixmap::rect() const
1133 {
1134     if (d && d->textureFactory)
1135         return QRect(QPoint(), d->textureFactory->textureSize());
1136     else
1137         return QRect();
1138 }
1139
1140 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
1141 {
1142     load(engine, url, QSize(), QQuickPixmap::Cache);
1143 }
1144
1145 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
1146 {
1147     load(engine, url, QSize(), options);
1148 }
1149
1150 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
1151 {
1152     load(engine, url, size, QQuickPixmap::Cache);
1153 }
1154
1155 void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
1156 {
1157     if (d) {
1158         d->declarativePixmaps.remove(this);
1159         d->release();
1160         d = 0;
1161     }
1162
1163     QQuickPixmapKey key = { &url, &requestSize };
1164     QQuickPixmapStore *store = pixmapStore();
1165
1166     QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.find(key);
1167
1168     if (iter == store->m_cache.end()) {
1169         if (url.scheme() == QLatin1String("image")) {
1170             if (QQuickImageProvider *provider = static_cast<QQuickImageProvider *>(engine->imageProvider(imageProviderId(url)))) {
1171                 if (provider->imageType() == QQuickImageProvider::Pixmap) {
1172                     // pixmaps can only be loaded synchronously
1173                     options &= ~QQuickPixmap::Asynchronous;
1174                 } else if (provider->flags() & QQuickImageProvider::ForceAsynchronousImageLoading) {
1175                     options |= QQuickPixmap::Asynchronous;
1176                 }
1177             }
1178         }
1179
1180         if (!(options & QQuickPixmap::Asynchronous)) {
1181             bool ok = false;
1182             d = createPixmapDataSync(this, engine, url, requestSize, &ok);
1183             if (ok) {
1184                 if (options & QQuickPixmap::Cache)
1185                     d->addToCache();
1186                 return;
1187             }
1188             if (d)  // loadable, but encountered error while loading
1189                 return;
1190         } 
1191
1192         if (!engine)
1193             return;
1194
1195         d = new QQuickPixmapData(this, url, requestSize);
1196         if (options & QQuickPixmap::Cache)
1197             d->addToCache();
1198
1199         QQuickPixmapReader::readerMutex.lock();
1200         d->reply = QQuickPixmapReader::instance(engine)->getImage(d);
1201         QQuickPixmapReader::readerMutex.unlock();
1202     } else {
1203         d = *iter;
1204         d->addref();
1205         d->declarativePixmaps.insert(this);
1206     }
1207 }
1208
1209 void QQuickPixmap::clear()
1210 {
1211     if (d) {
1212         d->declarativePixmaps.remove(this);
1213         d->release();
1214         d = 0;
1215     }
1216 }
1217
1218 void QQuickPixmap::clear(QObject *obj)
1219 {
1220     if (d) {
1221         if (d->reply) 
1222             QObject::disconnect(d->reply, 0, obj, 0);
1223         d->declarativePixmaps.remove(this);
1224         d->release();
1225         d = 0;
1226     }
1227 }
1228
1229 bool QQuickPixmap::connectFinished(QObject *object, const char *method)
1230 {
1231     if (!d || !d->reply) {
1232         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1233         return false;
1234     }
1235
1236     return QObject::connect(d->reply, SIGNAL(finished()), object, method);
1237 }
1238
1239 bool QQuickPixmap::connectFinished(QObject *object, int method)
1240 {
1241     if (!d || !d->reply) {
1242         qWarning("QQuickPixmap: connectFinished() called when not loading.");
1243         return false;
1244     }
1245
1246     return QMetaObject::connect(d->reply, QQuickPixmapReply::finishedIndex, object, method);
1247 }
1248
1249 bool QQuickPixmap::connectDownloadProgress(QObject *object, const char *method)
1250 {
1251     if (!d || !d->reply) {
1252         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1253         return false;
1254     }
1255
1256     return QObject::connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), object, method);
1257 }
1258
1259 bool QQuickPixmap::connectDownloadProgress(QObject *object, int method)
1260 {
1261     if (!d || !d->reply) {
1262         qWarning("QQuickPixmap: connectDownloadProgress() called when not loading.");
1263         return false;
1264     }
1265
1266     return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method);
1267 }
1268
1269 QT_END_NAMESPACE
1270
1271 #include <qquickpixmapcache.moc>