diff options
author | Thiago Macieira <thiago.macieira@nokia.com> | 2010-02-13 15:24:56 (GMT) |
---|---|---|
committer | Thiago Macieira <thiago.macieira@nokia.com> | 2010-02-13 15:24:56 (GMT) |
commit | dcb2678f39345b66c5303e74c156654a8d13fe83 (patch) | |
tree | d2c7f2c9de464b1908316b94c07cc8ece831ad00 /src/declarative/util/qmlpixmapcache.cpp | |
parent | 03a55630cfccf7f6ed01d865c541e07afb25c96e (diff) | |
parent | 6944a72cd26a5e3611ebd305ec665bc4c0fcee12 (diff) | |
download | Qt-dcb2678f39345b66c5303e74c156654a8d13fe83.zip Qt-dcb2678f39345b66c5303e74c156654a8d13fe83.tar.gz Qt-dcb2678f39345b66c5303e74c156654a8d13fe83.tar.bz2 |
Merge remote branch 'origin/master' into qt-master-from-4.6
Conflicts:
tests/auto/qlineedit/tst_qlineedit.cpp
tests/benchmarks/benchmarks.pro
Diffstat (limited to 'src/declarative/util/qmlpixmapcache.cpp')
-rw-r--r-- | src/declarative/util/qmlpixmapcache.cpp | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/src/declarative/util/qmlpixmapcache.cpp b/src/declarative/util/qmlpixmapcache.cpp new file mode 100644 index 0000000..c03b5df --- /dev/null +++ b/src/declarative/util/qmlpixmapcache.cpp @@ -0,0 +1,605 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmlpixmapcache_p.h" +#include "qmlnetworkaccessmanagerfactory.h" + +#include "qfxperf_p_p.h" + +#include <qmlengine.h> +#include <private/qmlglobal_p.h> + +#include <QCoreApplication> +#include <QImageReader> +#include <QHash> +#include <QNetworkReply> +#include <QPixmapCache> +#include <QFile> +#include <QThread> +#include <QMutex> +#include <QWaitCondition> +#include <QtCore/qdebug.h> +#include <private/qobject_p.h> +#include <QSslError> + +#ifdef Q_OS_LINUX +#include <pthread.h> +#include <linux/sched.h> +#endif + +// Maximum number of simultaneous image requests to send. +static const int maxImageRequestCount = 8; + +QT_BEGIN_NAMESPACE + +#if (QT_VERSION < QT_VERSION_CHECK(4, 7, 0)) +inline uint qHash(const QUrl &uri) +{ + return qHash(uri.toEncoded(QUrl::FormattingOption(0x100))); +} +#endif + +class QmlImageReaderEvent : public QEvent +{ +public: + enum ReadError { NoError, Loading, Decoding }; + + QmlImageReaderEvent(QmlImageReaderEvent::ReadError err, const QString &errStr, QImage &img) + : QEvent(QEvent::User), error(err), errorString(errStr), image(img) {} + + ReadError error; + QString errorString; + QImage image; +}; + +class QmlImageRequestHandler; +class QmlImageReader : public QThread +{ + Q_OBJECT +public: + QmlImageReader(QmlEngine *eng); + ~QmlImageReader(); + + QmlPixmapReply *getImage(const QUrl &url); + void cancel(QmlPixmapReply *rep); + + static QmlImageReader *instance(QmlEngine *engine); + +protected: + void run(); + +private: + QList<QmlPixmapReply*> jobs; + QList<QmlPixmapReply*> cancelled; + QmlEngine *engine; + QmlImageRequestHandler *handler; + QMutex mutex; + + static QHash<QmlEngine *,QmlImageReader*> readers; + static QMutex readerMutex; + friend class QmlImageRequestHandler; +}; + +QHash<QmlEngine *,QmlImageReader*> QmlImageReader::readers; +QMutex QmlImageReader::readerMutex; + + +class QmlImageRequestHandler : public QObject +{ + Q_OBJECT +public: + QmlImageRequestHandler(QmlImageReader *read, QmlEngine *eng) + : QObject(), accessManager(0), engine(eng), reader(read) + { + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + } + + QmlPixmapReply *getImage(const QUrl &url); + void cancel(QmlPixmapReply *reply); + +protected: + bool event(QEvent *event); + +private slots: + void networkRequestDone(); + +private: + QNetworkAccessManager *networkAccessManager() { + if (!accessManager) { + if (engine && engine->networkAccessManagerFactory()) { + accessManager = engine->networkAccessManagerFactory()->create(this); + } else { + accessManager = new QNetworkAccessManager(this); + } + } + return accessManager; + } + + QHash<QNetworkReply*,QmlPixmapReply*> replies; + QNetworkAccessManager *accessManager; + QmlEngine *engine; + QmlImageReader *reader; +}; + +//=========================================================================== + +bool QmlImageRequestHandler::event(QEvent *event) +{ + if (event->type() == QEvent::User) { + static int replyDownloadProgress = -1; + static int replyFinished = -1; + static int downloadProgress = -1; + static int thisNetworkRequestDone = -1; + + if (replyDownloadProgress == -1) { + replyDownloadProgress = QNetworkReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); + replyFinished = QNetworkReply::staticMetaObject.indexOfSignal("finished()"); + downloadProgress = QmlPixmapReply::staticMetaObject.indexOfSignal("downloadProgress(qint64,qint64)"); + thisNetworkRequestDone = QmlImageRequestHandler::staticMetaObject.indexOfSlot("networkRequestDone()"); + } + + while (1) { + reader->mutex.lock(); + + if (reader->cancelled.count()) { + for (int i = 0; i < reader->cancelled.count(); ++i) { + QmlPixmapReply *job = reader->cancelled.at(i); + QNetworkReply *reply = replies.key(job, 0); + if (reply && reply->isRunning()) { + replies.remove(reply); + reply->close(); + job->release(true); + } + } + reader->cancelled.clear(); + } + + if (!reader->jobs.count() || replies.count() > maxImageRequestCount) { + reader->mutex.unlock(); + break; + } + + QmlPixmapReply *runningJob = reader->jobs.takeFirst(); + runningJob->addRef(); + runningJob->setLoading(); + QUrl url = runningJob->url(); + reader->mutex.unlock(); + + // fetch + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + QNetworkReply *reply = networkAccessManager()->get(req); + + QMetaObject::connect(reply, replyDownloadProgress, runningJob, downloadProgress); + QMetaObject::connect(reply, replyFinished, this, thisNetworkRequestDone); + + replies.insert(reply, runningJob); + } + return true; + } + + return QObject::event(event); +} + +void QmlImageRequestHandler::networkRequestDone() +{ + QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); + QmlPixmapReply *job = replies.take(reply); + if (job) { + QImage image; + QmlImageReaderEvent::ReadError error; + QString errorString; + if (reply->error()) { + error = QmlImageReaderEvent::Loading; + errorString = reply->errorString(); + } else { + QImageReader imgio(reply); + if (imgio.read(&image)) { + error = QmlImageReaderEvent::NoError; + } else { + errorString = QLatin1String("Error decoding: ") + reply->url().toString() + + QLatin1String(" \"") + imgio.errorString() + QLatin1String("\""); + error = QmlImageReaderEvent::Decoding; + } + } + // send completion event to the QmlPixmapReply + QCoreApplication::postEvent(job, new QmlImageReaderEvent(error, errorString, image)); + } + // kick off event loop again if we have dropped below max request count + if (replies.count() == maxImageRequestCount) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + reply->deleteLater(); +} + +//=========================================================================== + +QmlImageReader::QmlImageReader(QmlEngine *eng) + : QThread(eng), engine(eng), handler(0) +{ + start(QThread::LowPriority); +} + +QmlImageReader::~QmlImageReader() +{ + quit(); + wait(); + readerMutex.lock(); + readers.remove(engine); + readerMutex.unlock(); + delete handler; +} + +QmlImageReader *QmlImageReader::instance(QmlEngine *engine) +{ + readerMutex.lock(); + QmlImageReader *reader = readers.value(engine); + if (!reader) { + reader = new QmlImageReader(engine); + readers.insert(engine, reader); + } + readerMutex.unlock(); + + return reader; +} + +QmlPixmapReply *QmlImageReader::getImage(const QUrl &url) +{ + mutex.lock(); + QmlPixmapReply *reply = new QmlPixmapReply(this, url); + jobs.append(reply); + if (jobs.count() == 1 && handler) + QCoreApplication::postEvent(handler, new QEvent(QEvent::User)); + mutex.unlock(); + return reply; +} + +void QmlImageReader::cancel(QmlPixmapReply *reply) +{ + mutex.lock(); + if (reply->isLoading()) { + // Already requested. Add to cancel list to be cancelled in reader thread. + cancelled.append(reply); + if (cancelled.count() == 1 && handler) + QCoreApplication::postEvent(handler, new QEvent(QEvent::User)); + } else { + // Not yet processed - just remove from waiting list + QList<QmlPixmapReply*>::iterator it = jobs.begin(); + while (it != jobs.end()) { + QmlPixmapReply *job = *it; + if (job == reply) { + jobs.erase(it); + break; + } + ++it; + } + } + mutex.unlock(); +} + +void QmlImageReader::run() +{ +#if defined(Q_OS_LINUX) && defined(SCHED_IDLE) + struct sched_param param; + int policy; + + pthread_getschedparam(pthread_self(), &policy, ¶m); + pthread_setschedparam(pthread_self(), SCHED_IDLE, ¶m); +#endif + + handler = new QmlImageRequestHandler(this, engine); + + exec(); +} + +//=========================================================================== + +static bool readImage(QIODevice *dev, QPixmap *pixmap, QString &errorString) +{ + QImageReader imgio(dev); + +//#define QT_TEST_SCALED_SIZE +#ifdef QT_TEST_SCALED_SIZE + /* + Some mechanism is needed for loading images at a limited size, especially + for remote images. Loading only thumbnails of remote progressive JPEG + images can be efficient. (Qt jpeg handler does not do so currently) + */ + + QSize limit(60,60); + QSize sz = imgio.size(); + if (sz.width() > limit.width() || sz.height() > limit.height()) { + sz.scale(limit,Qt::KeepAspectRatio); + imgio.setScaledSize(sz); + } +#endif + + QImage img; + if (imgio.read(&img)) { +#ifdef QT_TEST_SCALED_SIZE + if (!sz.isValid()) + img = img.scaled(limit,Qt::KeepAspectRatio); +#endif + *pixmap = QPixmap::fromImage(img); + return true; + } else { + errorString = imgio.errorString(); + return false; + } +} + +/*! + \internal + \class QmlPixmapCache + \brief Enacapsultes a pixmap for QmlGraphics items. + + This class is NOT reentrant. + */ + +static QString toLocalFileOrQrc(const QUrl& url) +{ + QString r = url.toLocalFile(); + if (r.isEmpty() && url.scheme() == QLatin1String("qrc")) + r = QLatin1Char(':') + url.path(); + return r; +} + +typedef QHash<QUrl, QmlPixmapReply *> QmlPixmapReplyHash; +Q_GLOBAL_STATIC(QmlPixmapReplyHash, qmlActivePixmapReplies); + +class QmlPixmapReplyPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QmlPixmapReply) + +public: + QmlPixmapReplyPrivate(QmlImageReader *r, const QUrl &u) + : QObjectPrivate(), refCount(1), url(u), status(QmlPixmapReply::Loading), loading(false), reader(r) { + } + + int refCount; + QUrl url; + QPixmap pixmap; // ensure reference to pixmap so QPixmapCache does not discard + QmlPixmapReply::Status status; + bool loading; + QmlImageReader *reader; +}; + + +QmlPixmapReply::QmlPixmapReply(QmlImageReader *reader, const QUrl &url) + : QObject(*new QmlPixmapReplyPrivate(reader, url), 0) +{ +} + +QmlPixmapReply::~QmlPixmapReply() +{ +} + +const QUrl &QmlPixmapReply::url() const +{ + Q_D(const QmlPixmapReply); + return d->url; +} + +bool QmlPixmapReply::event(QEvent *event) +{ + Q_D(QmlPixmapReply); + if (event->type() == QEvent::User) { + d->loading = false; + if (!release(true)) { + QmlImageReaderEvent *de = static_cast<QmlImageReaderEvent*>(event); + d->status = (de->error == QmlImageReaderEvent::NoError) ? Ready : Error; + if (d->status == Ready) + d->pixmap = QPixmap::fromImage(de->image); + else + qWarning() << de->errorString; + QByteArray key = d->url.toEncoded(QUrl::FormattingOption(0x100)); + QString strKey = QString::fromLatin1(key.constData(), key.count()); + QPixmapCache::insert(strKey, d->pixmap); // note: may fail (returns false) + emit finished(); + } + return true; + } + + return QObject::event(event); +} + +QmlPixmapReply::Status QmlPixmapReply::status() const +{ + Q_D(const QmlPixmapReply); + return d->status; +} + +bool QmlPixmapReply::isLoading() const +{ + Q_D(const QmlPixmapReply); + return d->loading; +} + +void QmlPixmapReply::setLoading() +{ + Q_D(QmlPixmapReply); + d->loading = true; +} + +void QmlPixmapReply::addRef() +{ + Q_D(QmlPixmapReply); + ++d->refCount; +} + +bool QmlPixmapReply::release(bool defer) +{ + Q_D(QmlPixmapReply); + Q_ASSERT(d->refCount > 0); + --d->refCount; + if (d->refCount == 0) { + qmlActivePixmapReplies()->remove(d->url); + if (d->status == Loading && !d->loading) + d->reader->cancel(this); + if (defer) + deleteLater(); + else + delete this; + return true; + } else if (d->refCount == 1 && d->loading) { + // The only reference left is the reader thread. + qmlActivePixmapReplies()->remove(d->url); + d->reader->cancel(this); + } + + return false; +} + +/*! + Finds the cached pixmap corresponding to \a url. + If the image is a network resource and has not yet + been retrieved and cached, request() must be called. + + Returns Ready, or Error if the image has been retrieved, + otherwise the current retrieval status. +*/ +QmlPixmapReply::Status QmlPixmapCache::get(const QUrl& url, QPixmap *pixmap) +{ + QmlPixmapReply::Status status = QmlPixmapReply::Unrequested; + +#ifndef QT_NO_LOCALFILE_OPTIMIZED_QML + QString lf = toLocalFileOrQrc(url); + if (!lf.isEmpty()) { + status = QmlPixmapReply::Ready; + if (!QPixmapCache::find(lf,pixmap)) { + QFile f(lf); + if (f.open(QIODevice::ReadOnly)) { + QString errorString; + if (!readImage(&f, pixmap, errorString)) { + errorString = QLatin1String("Error decoding: ") + url.toString() + + QLatin1String(" \"") + errorString + QLatin1String("\""); + qWarning() << errorString; + *pixmap = QPixmap(); + status = QmlPixmapReply::Error; + } + } else { + qWarning() << "Cannot open" << url; + *pixmap = QPixmap(); + status = QmlPixmapReply::Error; + } + if (status == QmlPixmapReply::Ready) + QPixmapCache::insert(lf, *pixmap); + } + return status; + } +#endif + + QByteArray key = url.toEncoded(QUrl::FormattingOption(0x100)); + QString strKey = QString::fromLatin1(key.constData(), key.count()); + QmlPixmapReplyHash::Iterator iter = qmlActivePixmapReplies()->find(url); + if (iter != qmlActivePixmapReplies()->end() && (*iter)->status() == QmlPixmapReply::Ready) { + // Must check this, since QPixmapCache::insert may have failed. + *pixmap = (*iter)->d_func()->pixmap; + status = (*iter)->status(); + (*iter)->release(); + } else if (QPixmapCache::find(strKey, pixmap)) { + if (iter != qmlActivePixmapReplies()->end()) { + status = (*iter)->status(); + (*iter)->release(); + } else { + status = pixmap->isNull() ? QmlPixmapReply::Error : QmlPixmapReply::Ready; + } + } else if (iter != qmlActivePixmapReplies()->end()) { + status = QmlPixmapReply::Loading; + } + + return status; +} + +/*! + Starts a network request to load \a url. + + Returns a QmlPixmapReply. Caller should connect to QmlPixmapReply::finished() + and call get() when the image is available. + + The returned QmlPixmapReply will be deleted when all request() calls are + matched by a corresponding get() call. +*/ +QmlPixmapReply *QmlPixmapCache::request(QmlEngine *engine, const QUrl &url) +{ + QmlPixmapReplyHash::Iterator iter = qmlActivePixmapReplies()->find(url); + if (iter == qmlActivePixmapReplies()->end()) { + QmlImageReader *reader = QmlImageReader::instance(engine); + QmlPixmapReply *item = reader->getImage(url); + iter = qmlActivePixmapReplies()->insert(url, item); + } else { + (*iter)->addRef(); + } + + return (*iter); +} + +/*! + Cancels a previous call to request(). + + May also cancel loading (eg. if no other pending request). + + Any connections from the QmlPixmapReply returned by request() to \a obj will be + disconnected. +*/ +void QmlPixmapCache::cancel(const QUrl& url, QObject *obj) +{ + QmlPixmapReplyHash::Iterator iter = qmlActivePixmapReplies()->find(url); + if (iter == qmlActivePixmapReplies()->end()) + return; + + QmlPixmapReply *reply = *iter; + if (obj) + QObject::disconnect(reply, 0, obj, 0); + reply->release(); +} + +/*! + This function is mainly for test verification. It returns the number of + requests that are still unfinished. +*/ +int QmlPixmapCache::pendingRequests() +{ + return qmlActivePixmapReplies()->count(); +} + +#include <qmlpixmapcache.moc> + +QT_END_NAMESPACE |