src/gui/image/qpixmapcache.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /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<QPixmapCache::Key, QPixmapCacheEntry>
+{
+    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<QString, QPixmapCache::Key> 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<QPixmapCache::Key, QPixmapCacheEntry>(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<QString, QPixmapCache::Key>::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<QPMCache *>(this)->cacheKeys.remove(key);
+        return 0;
+    }
+    QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(cacheKey);
+     //We didn't find the pixmap in the cache, the key is not valid anymore
+    if (!ptr) {
+        const_cast<QPMCache *>(this)->cacheKeys.remove(key);
+    }
+    return ptr;
+}
+
+QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
+{
+    Q_ASSERT(key.d->isValid);
+    QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(key);
+    //We didn't find the pixmap in the cache, the key is not valid anymore
+    if (!ptr)
+        const_cast<QPMCache *>(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<QPixmapCache::Key, QPixmapCacheEntry>::remove(oldCacheKey);
+        cacheKeys.remove(key);
+    }
+
+    //we create a new key the old one has been removed
+    cacheKey = createKey();
+
+    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::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<QPixmapCache::Key, QPixmapCacheEntry>::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<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
+
+    QPixmapCache::Key cacheKey = createKey();
+
+    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
+    if (success) {
+        if(!theid) {
+            theid = startTimer(30000);
+            t = false;
+        }
+        const_cast<QPixmapCache::Key&>(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<QPixmapCache::Key, QPixmapCacheEntry>::remove(cacheKey);
+}
+
+bool QPMCache::remove(const QPixmapCache::Key &key)
+{
+    return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
+}
+
+void QPMCache::resizeKeyArray(int size)
+{
+    if (size <= keyArraySize || size == 0)
+        return;
+    keyArray = q_check_ptr(reinterpret_cast<int *>(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<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
+    for (int i = 0; i < keys.size(); ++i)
+        keys.at(i).d->isValid = false;
+    QCache<QPixmapCache::Key, QPixmapCacheEntry>::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