diff -r 000000000000 -r 1918ee327afb src/gui/image/qpixmapcache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/image/qpixmapcache.cpp Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,631 @@ +/**************************************************************************** +** +** 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 QtGui 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 "qpixmapcache.h" +#include "qobject.h" +#include "qdebug.h" +#include "qpixmapcache_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QPixmapCache + + \brief The QPixmapCache class provides an application-wide cache for pixmaps. + + \ingroup painting + + This class is a tool for optimized drawing with QPixmap. You can + use it to store temporary pixmaps that are expensive to generate + without using more storage space than cacheLimit(). Use insert() + to insert pixmaps, find() to find them, and clear() to empty the + cache. + + QPixmapCache contains no member data, only static functions to + access the global pixmap cache. It creates an internal QCache + object for caching the pixmaps. + + The cache associates a pixmap with a user-provided string as a key, + or with a QPixmapCache::Key that the cache generates. + Using QPixmapCache::Key for keys is faster than using strings. The string API is + very convenient for complex keys but the QPixmapCache::Key API will be very + efficient and convenient for a one-to-one object-to-pixmap mapping \mdash in + this case, you can store the keys as members of an object. + + If two pixmaps are inserted into the cache using equal keys then the + last pixmap will replace the first pixmap in the cache. This follows the + behavior of the QHash and QCache classes. + + The cache becomes full when the total size of all pixmaps in the + cache exceeds cacheLimit(). The initial cache limit is + 2048 KB (2 MB) on embedded platforms, 10240 KB (10 MB) on desktop + platforms; you can change this by calling setCacheLimit() with the + required value. + A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of + memory. + + The \e{Qt Quarterly} article + \l{http://qt.nokia.com/doc/qq/qq12-qpixmapcache.html}{Optimizing + with QPixmapCache} explains how to use QPixmapCache to speed up + applications by caching the results of painting. + + \sa QCache, QPixmap +*/ + +#if defined(Q_OS_SYMBIAN) +static int cache_limit = 1024; // 1048 KB cache limit for symbian +#elif defined(Q_WS_QWS) || defined(Q_WS_WINCE) +static int cache_limit = 2048; // 2048 KB cache limit for embedded +#else +static int cache_limit = 10240; // 10 MB cache limit for desktop +#endif + +/*! + \class QPixmapCache::Key + \brief The QPixmapCache::Key class can be used for efficient access + to the QPixmapCache. + \since 4.6 + + Use QPixmapCache::insert() to receive an instance of Key generated + by the pixmap cache. You can store the key in your own objects for + a very efficient one-to-one object-to-pixmap mapping. +*/ + +/*! + Constructs an empty Key object. +*/ +QPixmapCache::Key::Key() : d(0) +{ +} + +/*! + \internal + Constructs a copy of \a other. +*/ +QPixmapCache::Key::Key(const Key &other) +{ + if (other.d) + ++(other.d->ref); + d = other.d; +} + +/*! + Destroys the key. +*/ +QPixmapCache::Key::~Key() +{ + if (d && --(d->ref) == 0) + delete d; +} + +/*! + \internal + + Returns true if this key is the same as the given \a key; otherwise returns + false. +*/ +bool QPixmapCache::Key::operator ==(const Key &key) const +{ + return (d == key.d); +} + +/*! + \fn bool QPixmapCache::Key::operator !=(const Key &key) const + \internal +*/ + +/*! + \internal +*/ +QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other) +{ + if (d != other.d) { + if (other.d) + ++(other.d->ref); + if (d && --(d->ref) == 0) + delete d; + d = other.d; + } + return *this; +} + +class QPMCache : public QObject, public QCache +{ + Q_OBJECT +public: + QPMCache(); + ~QPMCache(); + + void timerEvent(QTimerEvent *); + bool insert(const QString& key, const QPixmap &pixmap, int cost); + QPixmapCache::Key insert(const QPixmap &pixmap, int cost); + bool replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost); + bool remove(const QString &key); + bool remove(const QPixmapCache::Key &key); + + void resizeKeyArray(int size); + QPixmapCache::Key createKey(); + void releaseKey(const QPixmapCache::Key &key); + void clear(); + + QPixmap *object(const QString &key) const; + QPixmap *object(const QPixmapCache::Key &key) const; + + static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key) + {return key.d;} + + static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key); + +private: + int *keyArray; + int theid; + int ps; + int keyArraySize; + int freeKey; + QHash cacheKeys; + bool t; +}; + +QT_BEGIN_INCLUDE_NAMESPACE +#include "qpixmapcache.moc" +QT_END_INCLUDE_NAMESPACE + +uint qHash(const QPixmapCache::Key &k) +{ + return qHash(QPMCache::get(k)->key); +} + +QPMCache::QPMCache() + : QObject(0), + QCache(cache_limit * 1024), + keyArray(0), theid(0), ps(0), keyArraySize(0), freeKey(0), t(false) +{ +} +QPMCache::~QPMCache() +{ + clear(); + free(keyArray); +} + +/* + This is supposed to cut the cache size down by about 25% in a + minute once the application becomes idle, to let any inserted pixmap + remain in the cache for some time before it becomes a candidate for + cleaning-up, and to not cut down the size of the cache while the + cache is in active use. + + When the last pixmap has been deleted from the cache, kill the + timer so Qt won't keep the CPU from going into sleep mode. +*/ +void QPMCache::timerEvent(QTimerEvent *) +{ + int mc = maxCost(); + bool nt = totalCost() == ps; + setMaxCost(nt ? totalCost() * 3 / 4 : totalCost() -1); + setMaxCost(mc); + ps = totalCost(); + + QHash::iterator it = cacheKeys.begin(); + while (it != cacheKeys.end()) { + if (!contains(it.value())) { + releaseKey(it.value()); + it = cacheKeys.erase(it); + } else { + ++it; + } + } + + if (!size()) { + killTimer(theid); + theid = 0; + } else if (nt != t) { + killTimer(theid); + theid = startTimer(nt ? 10000 : 30000); + t = nt; + } +} + +QPixmap *QPMCache::object(const QString &key) const +{ + QPixmapCache::Key cacheKey = cacheKeys.value(key); + if (!cacheKey.d || !cacheKey.d->isValid) { + const_cast(this)->cacheKeys.remove(key); + return 0; + } + QPixmap *ptr = QCache::object(cacheKey); + //We didn't find the pixmap in the cache, the key is not valid anymore + if (!ptr) { + const_cast(this)->cacheKeys.remove(key); + } + return ptr; +} + +QPixmap *QPMCache::object(const QPixmapCache::Key &key) const +{ + Q_ASSERT(key.d->isValid); + QPixmap *ptr = QCache::object(key); + //We didn't find the pixmap in the cache, the key is not valid anymore + if (!ptr) + const_cast(this)->releaseKey(key); + return ptr; +} + +bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost) +{ + QPixmapCache::Key cacheKey; + QPixmapCache::Key oldCacheKey = cacheKeys.value(key); + //If for the same key we add already a pixmap we should delete it + if (oldCacheKey.d) { + QCache::remove(oldCacheKey); + cacheKeys.remove(key); + } + + //we create a new key the old one has been removed + cacheKey = createKey(); + + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + cacheKeys.insert(key, cacheKey); + if (!theid) { + theid = startTimer(30000); + t = false; + } + } else { + //Insertion failed we released the new allocated key + releaseKey(cacheKey); + } + return success; +} + +QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost) +{ + QPixmapCache::Key cacheKey = createKey(); + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + if (!theid) { + theid = startTimer(30000); + t = false; + } + } else { + //Insertion failed we released the key and return an invalid one + releaseKey(cacheKey); + } + return cacheKey; +} + +bool QPMCache::replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost) +{ + Q_ASSERT(key.d->isValid); + //If for the same key we had already an entry so we should delete the pixmap and use the new one + QCache::remove(key); + + QPixmapCache::Key cacheKey = createKey(); + + bool success = QCache::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost); + if (success) { + if(!theid) { + theid = startTimer(30000); + t = false; + } + const_cast(key) = cacheKey; + } else { + //Insertion failed we released the key + releaseKey(cacheKey); + } + return success; +} + +bool QPMCache::remove(const QString &key) +{ + QPixmapCache::Key cacheKey = cacheKeys.value(key); + //The key was not in the cache + if (!cacheKey.d) + return false; + cacheKeys.remove(key); + return QCache::remove(cacheKey); +} + +bool QPMCache::remove(const QPixmapCache::Key &key) +{ + return QCache::remove(key); +} + +void QPMCache::resizeKeyArray(int size) +{ + if (size <= keyArraySize || size == 0) + return; + keyArray = q_check_ptr(reinterpret_cast(realloc(keyArray, + size * sizeof(int)))); + for (int i = keyArraySize; i != size; ++i) + keyArray[i] = i + 1; + keyArraySize = size; +} + +QPixmapCache::Key QPMCache::createKey() +{ + if (freeKey == keyArraySize) + resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2); + int id = freeKey; + freeKey = keyArray[id]; + QPixmapCache::Key key; + QPixmapCache::KeyData *d = QPMCache::getKeyData(&key); + d->key = ++id; + return key; +} + +void QPMCache::releaseKey(const QPixmapCache::Key &key) +{ + if (key.d->key > keyArraySize || key.d->key <= 0) + return; + key.d->key--; + keyArray[key.d->key] = freeKey; + freeKey = key.d->key; + key.d->isValid = false; + key.d->key = 0; +} + +void QPMCache::clear() +{ + free(keyArray); + keyArray = 0; + freeKey = 0; + keyArraySize = 0; + //Mark all keys as invalid + QList keys = QCache::keys(); + for (int i = 0; i < keys.size(); ++i) + keys.at(i).d->isValid = false; + QCache::clear(); +} + +QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key) +{ + if (!key->d) + key->d = new QPixmapCache::KeyData; + return key->d; +} + +Q_GLOBAL_STATIC(QPMCache, pm_cache) + +QPixmapCacheEntry::~QPixmapCacheEntry() +{ + pm_cache()->releaseKey(key); +} + +/*! + \obsolete + \overload + + Returns the pixmap associated with the \a key in the cache, or + null if there is no such pixmap. + + \warning If valid, you should copy the pixmap immediately (this is + fast). Subsequent insertions into the cache could cause the + pointer to become invalid. For this reason, we recommend you use + bool find(const QString&, QPixmap*) instead. + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 0 +*/ + +QPixmap *QPixmapCache::find(const QString &key) +{ + return pm_cache()->object(key); +} + + +/*! + \obsolete + + Use bool find(const QString&, QPixmap*) instead. +*/ + +bool QPixmapCache::find(const QString &key, QPixmap& pixmap) +{ + return find(key, &pixmap); +} + +/*! + Looks for a cached pixmap associated with the given \a key in the cache. + If the pixmap is found, the function sets \a pixmap to that pixmap and + returns true; otherwise it leaves \a pixmap alone and returns false. + + \since 4.6 + + Example: + \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 1 +*/ + +bool QPixmapCache::find(const QString &key, QPixmap* pixmap) +{ + QPixmap *ptr = pm_cache()->object(key); + if (ptr && pixmap) + *pixmap = *ptr; + return ptr != 0; +} + +/*! + Looks for a cached pixmap associated with the given \a key in the cache. + If the pixmap is found, the function sets \a pixmap to that pixmap and + returns true; otherwise it leaves \a pixmap alone and returns false. If + the pixmap is not found, it means that the \a key is no longer valid, + so it will be released for the next insertion. + + \since 4.6 +*/ +bool QPixmapCache::find(const Key &key, QPixmap* pixmap) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return false; + QPixmap *ptr = pm_cache()->object(key); + if (ptr && pixmap) + *pixmap = *ptr; + return ptr != 0; +} + +/*! + Inserts a copy of the pixmap \a pixmap associated with the \a key into + the cache. + + All pixmaps inserted by the Qt library have a key starting with + "$qt", so your own pixmap keys should never begin "$qt". + + When a pixmap is inserted and the cache is about to exceed its + limit, it removes pixmaps until there is enough room for the + pixmap to be inserted. + + The oldest pixmaps (least recently accessed in the cache) are + deleted when more space is needed. + + The function returns true if the object was inserted into the + cache; otherwise it returns false. + + \sa setCacheLimit() +*/ + +bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap) +{ + return pm_cache()->insert(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Inserts a copy of the given \a pixmap into the cache and returns a key + that can be used to retrieve it. + + When a pixmap is inserted and the cache is about to exceed its + limit, it removes pixmaps until there is enough room for the + pixmap to be inserted. + + The oldest pixmaps (least recently accessed in the cache) are + deleted when more space is needed. + + \sa setCacheLimit(), replace() + + \since 4.6 +*/ +QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap) +{ + return pm_cache()->insert(pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Replaces the pixmap associated with the given \a key with the \a pixmap + specified. Returns true if the \a pixmap has been correctly inserted into + the cache; otherwise returns false. + + \sa setCacheLimit(), insert() + + \since 4.6 +*/ +bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return false; + return pm_cache()->replace(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8); +} + +/*! + Returns the cache limit (in kilobytes). + + The default cache limit is 2048 KB on embedded platforms, 10240 KB on + desktop platforms. + + \sa setCacheLimit() +*/ + +int QPixmapCache::cacheLimit() +{ + return cache_limit; +} + +/*! + Sets the cache limit to \a n kilobytes. + + The default setting is 2048 KB on embedded platforms, 10240 KB on + desktop platforms. + + \sa cacheLimit() +*/ + +void QPixmapCache::setCacheLimit(int n) +{ + cache_limit = n; + pm_cache()->setMaxCost(1024 * cache_limit); +} + +/*! + Removes the pixmap associated with \a key from the cache. +*/ +void QPixmapCache::remove(const QString &key) +{ + pm_cache()->remove(key); +} + +/*! + Removes the pixmap associated with \a key from the cache and releases + the key for a future insertion. + + \since 4.6 +*/ +void QPixmapCache::remove(const Key &key) +{ + //The key is not valid anymore, a flush happened before probably + if (!key.d || !key.d->isValid) + return; + pm_cache()->remove(key); +} + +/*! + Removes all pixmaps from the cache. +*/ + +void QPixmapCache::clear() +{ + QT_TRY { + pm_cache()->clear(); + } QT_CATCH(const std::bad_alloc &) { + // if we ran out of memory during pm_cache(), it's no leak, + // so just ignore it. + } +} + +QT_END_NAMESPACE