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