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 QtGui module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include "qpixmapcache.h"
43
#include "qobject.h"
44
#include "qdebug.h"
45
#include "qpixmapcache_p.h"
46
47
QT_BEGIN_NAMESPACE
48
49
/*!
50
    \class QPixmapCache
51
52
    \brief The QPixmapCache class provides an application-wide cache for pixmaps.
53
54
    \ingroup painting
55
56
    This class is a tool for optimized drawing with QPixmap. You can
57
    use it to store temporary pixmaps that are expensive to generate
58
    without using more storage space than cacheLimit(). Use insert()
59
    to insert pixmaps, find() to find them, and clear() to empty the
60
    cache.
61
62
    QPixmapCache contains no member data, only static functions to
63
    access the global pixmap cache. It creates an internal QCache
64
    object for caching the pixmaps.
65
66
    The cache associates a pixmap with a user-provided string as a key,
67
    or with a QPixmapCache::Key that the cache generates.
68
    Using QPixmapCache::Key for keys is faster than using strings. The string API is
69
    very convenient for complex keys but the QPixmapCache::Key API will be very
70
    efficient and convenient for a one-to-one object-to-pixmap mapping \mdash in
71
    this case, you can store the keys as members of an object.
72
73
    If two pixmaps are inserted into the cache using equal keys then the
74
    last pixmap will replace the first pixmap in the cache. This follows the
75
    behavior of the QHash and QCache classes.
76
77
    The cache becomes full when the total size of all pixmaps in the
78
    cache exceeds cacheLimit(). The initial cache limit is
79
    2048 KB (2 MB) on embedded platforms, 10240 KB (10 MB) on desktop
80
    platforms; you can change this by calling setCacheLimit() with the
81
    required value.
82
    A pixmap takes roughly (\e{width} * \e{height} * \e{depth})/8 bytes of
83
    memory.
84
85
    The \e{Qt Quarterly} article
86
    \l{http://qt.nokia.com/doc/qq/qq12-qpixmapcache.html}{Optimizing
87
    with QPixmapCache} explains how to use QPixmapCache to speed up
88
    applications by caching the results of painting.
89
90
    \sa QCache, QPixmap
91
*/
92
93
#if defined(Q_OS_SYMBIAN)
94
static int cache_limit = 1024; // 1048 KB cache limit for symbian
95
#elif defined(Q_WS_QWS) || defined(Q_WS_WINCE)
96
static int cache_limit = 2048; // 2048 KB cache limit for embedded
97
#else
98
static int cache_limit = 10240; // 10 MB cache limit for desktop
99
#endif
100
101
/*!
102
    \class QPixmapCache::Key
103
    \brief The QPixmapCache::Key class can be used for efficient access
104
    to the QPixmapCache.
105
    \since 4.6
106
107
    Use QPixmapCache::insert() to receive an instance of Key generated
108
    by the pixmap cache. You can store the key in your own objects for
109
    a very efficient one-to-one object-to-pixmap mapping.
110
*/
111
112
/*!
113
    Constructs an empty Key object.
114
*/
115
QPixmapCache::Key::Key() : d(0)
116
{
117
}
118
119
/*!
120
   \internal
121
    Constructs a copy of \a other.
122
*/
123
QPixmapCache::Key::Key(const Key &other)
124
{
125
    if (other.d)
126
        ++(other.d->ref);
127
    d = other.d;
128
}
129
130
/*!
131
    Destroys the key.
132
*/
133
QPixmapCache::Key::~Key()
134
{
135
    if (d && --(d->ref) == 0)
136
        delete d;
137
}
138
139
/*!
140
    \internal
141
142
    Returns true if this key is the same as the given \a key; otherwise returns
143
    false.
144
*/
145
bool QPixmapCache::Key::operator ==(const Key &key) const
146
{
147
    return (d == key.d);
148
}
149
150
/*!
151
    \fn bool QPixmapCache::Key::operator !=(const Key &key) const
152
    \internal
153
*/
154
155
/*!
156
    \internal
157
*/
158
QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other)
159
{
160
    if (d != other.d) {
161
        if (other.d)
162
            ++(other.d->ref);
163
        if (d && --(d->ref) == 0)
164
            delete d;
165
        d = other.d;
166
    }
167
    return *this;
168
}
169
170
class QPMCache : public QObject, public QCache<QPixmapCache::Key, QPixmapCacheEntry>
171
{
172
    Q_OBJECT
173
public:
174
    QPMCache();
175
    ~QPMCache();
176
177
    void timerEvent(QTimerEvent *);
178
    bool insert(const QString& key, const QPixmap &pixmap, int cost);
179
    QPixmapCache::Key insert(const QPixmap &pixmap, int cost);
180
    bool replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost);
181
    bool remove(const QString &key);
182
    bool remove(const QPixmapCache::Key &key);
183
184
    void resizeKeyArray(int size);
185
    QPixmapCache::Key createKey();
186
    void releaseKey(const QPixmapCache::Key &key);
187
    void clear();
188
189
    QPixmap *object(const QString &key) const;
190
    QPixmap *object(const QPixmapCache::Key &key) const;
191
192
    static inline QPixmapCache::KeyData *get(const QPixmapCache::Key &key)
193
    {return key.d;}
194
195
    static QPixmapCache::KeyData* getKeyData(QPixmapCache::Key *key);
196
197
private:
198
    int *keyArray;
199
    int theid;
200
    int ps;
201
    int keyArraySize;
202
    int freeKey;
203
    QHash<QString, QPixmapCache::Key> cacheKeys;
204
    bool t;
205
};
206
207
QT_BEGIN_INCLUDE_NAMESPACE
208
#include "qpixmapcache.moc"
209
QT_END_INCLUDE_NAMESPACE
210
211
uint qHash(const QPixmapCache::Key &k)
212
{
213
    return qHash(QPMCache::get(k)->key);
214
}
215
216
QPMCache::QPMCache()
217
    : QObject(0),
218
      QCache<QPixmapCache::Key, QPixmapCacheEntry>(cache_limit * 1024),
219
      keyArray(0), theid(0), ps(0), keyArraySize(0), freeKey(0), t(false)
220
{
221
}
222
QPMCache::~QPMCache()
223
{
224
    clear();
225
    free(keyArray);
226
}
227
228
/*
229
  This is supposed to cut the cache size down by about 25% in a
230
  minute once the application becomes idle, to let any inserted pixmap
231
  remain in the cache for some time before it becomes a candidate for
232
  cleaning-up, and to not cut down the size of the cache while the
233
  cache is in active use.
234
235
  When the last pixmap has been deleted from the cache, kill the
236
  timer so Qt won't keep the CPU from going into sleep mode.
237
*/
238
void QPMCache::timerEvent(QTimerEvent *)
239
{
240
    int mc = maxCost();
241
    bool nt = totalCost() == ps;
242
    setMaxCost(nt ? totalCost() * 3 / 4 : totalCost() -1);
243
    setMaxCost(mc);
244
    ps = totalCost();
245
246
    QHash<QString, QPixmapCache::Key>::iterator it = cacheKeys.begin();
247
    while (it != cacheKeys.end()) {
248
        if (!contains(it.value())) {
249
            releaseKey(it.value());
250
            it = cacheKeys.erase(it);
251
        } else {
252
            ++it;
253
        }
254
    }
255
256
    if (!size()) {
257
        killTimer(theid);
258
        theid = 0;
259
    } else if (nt != t) {
260
        killTimer(theid);
261
        theid = startTimer(nt ? 10000 : 30000);
262
        t = nt;
263
    }
264
}
265
266
QPixmap *QPMCache::object(const QString &key) const
267
{
268
    QPixmapCache::Key cacheKey = cacheKeys.value(key);
269
    if (!cacheKey.d || !cacheKey.d->isValid) {
270
        const_cast<QPMCache *>(this)->cacheKeys.remove(key);
271
        return 0;
272
    }
273
    QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(cacheKey);
274
     //We didn't find the pixmap in the cache, the key is not valid anymore
275
    if (!ptr) {
276
        const_cast<QPMCache *>(this)->cacheKeys.remove(key);
277
    }
278
    return ptr;
279
}
280
281
QPixmap *QPMCache::object(const QPixmapCache::Key &key) const
282
{
283
    Q_ASSERT(key.d->isValid);
284
    QPixmap *ptr = QCache<QPixmapCache::Key, QPixmapCacheEntry>::object(key);
285
    //We didn't find the pixmap in the cache, the key is not valid anymore
286
    if (!ptr)
287
        const_cast<QPMCache *>(this)->releaseKey(key);
288
    return ptr;
289
}
290
291
bool QPMCache::insert(const QString& key, const QPixmap &pixmap, int cost)
292
{
293
    QPixmapCache::Key cacheKey;
294
    QPixmapCache::Key oldCacheKey = cacheKeys.value(key);
295
    //If for the same key we add already a pixmap we should delete it
296
    if (oldCacheKey.d) {
297
        QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(oldCacheKey);
298
        cacheKeys.remove(key);
299
    }
300
301
    //we create a new key the old one has been removed
302
    cacheKey = createKey();
303
304
    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
305
    if (success) {
306
        cacheKeys.insert(key, cacheKey);
307
        if (!theid) {
308
            theid = startTimer(30000);
309
            t = false;
310
        }
311
    } else {
312
        //Insertion failed we released the new allocated key
313
        releaseKey(cacheKey);
314
    }
315
    return success;
316
}
317
318
QPixmapCache::Key QPMCache::insert(const QPixmap &pixmap, int cost)
319
{
320
    QPixmapCache::Key cacheKey = createKey();
321
    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
322
    if (success) {
323
        if (!theid) {
324
            theid = startTimer(30000);
325
            t = false;
326
        }
327
    } else {
328
        //Insertion failed we released the key and return an invalid one
329
        releaseKey(cacheKey);
330
    }
331
    return cacheKey;
332
}
333
334
bool QPMCache::replace(const QPixmapCache::Key &key, const QPixmap &pixmap, int cost)
335
{
336
    Q_ASSERT(key.d->isValid);
337
    //If for the same key we had already an entry so we should delete the pixmap and use the new one
338
    QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
339
340
    QPixmapCache::Key cacheKey = createKey();
341
342
    bool success = QCache<QPixmapCache::Key, QPixmapCacheEntry>::insert(cacheKey, new QPixmapCacheEntry(cacheKey, pixmap), cost);
343
    if (success) {
344
        if(!theid) {
345
            theid = startTimer(30000);
346
            t = false;
347
        }
348
        const_cast<QPixmapCache::Key&>(key) = cacheKey;
349
    } else {
350
        //Insertion failed we released the key
351
        releaseKey(cacheKey);
352
    }
353
    return success;
354
}
355
356
bool QPMCache::remove(const QString &key)
357
{
358
    QPixmapCache::Key cacheKey = cacheKeys.value(key);
359
    //The key was not in the cache
360
    if (!cacheKey.d)
361
        return false;
362
    cacheKeys.remove(key);
363
    return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(cacheKey);
364
}
365
366
bool QPMCache::remove(const QPixmapCache::Key &key)
367
{
368
    return QCache<QPixmapCache::Key, QPixmapCacheEntry>::remove(key);
369
}
370
371
void QPMCache::resizeKeyArray(int size)
372
{
373
    if (size <= keyArraySize || size == 0)
374
        return;
375
    keyArray = q_check_ptr(reinterpret_cast<int *>(realloc(keyArray,
376
                    size * sizeof(int))));
377
    for (int i = keyArraySize; i != size; ++i)
378
        keyArray[i] = i + 1;
379
    keyArraySize = size;
380
}
381
382
QPixmapCache::Key QPMCache::createKey()
383
{
384
    if (freeKey == keyArraySize)
385
        resizeKeyArray(keyArraySize ? keyArraySize << 1 : 2);
386
    int id = freeKey;
387
    freeKey = keyArray[id];
388
    QPixmapCache::Key key;
389
    QPixmapCache::KeyData *d = QPMCache::getKeyData(&key);
390
    d->key = ++id;
391
    return key;
392
}
393
394
void QPMCache::releaseKey(const QPixmapCache::Key &key)
395
{
396
    if (key.d->key > keyArraySize || key.d->key <= 0)
397
        return;
398
    key.d->key--;
399
    keyArray[key.d->key] = freeKey;
400
    freeKey = key.d->key;
401
    key.d->isValid = false;
402
    key.d->key = 0;
403
}
404
405
void QPMCache::clear()
406
{
407
    free(keyArray);
408
    keyArray = 0;
409
    freeKey = 0;
410
    keyArraySize = 0;
411
    //Mark all keys as invalid
412
    QList<QPixmapCache::Key> keys = QCache<QPixmapCache::Key, QPixmapCacheEntry>::keys();
413
    for (int i = 0; i < keys.size(); ++i)
414
        keys.at(i).d->isValid = false;
415
    QCache<QPixmapCache::Key, QPixmapCacheEntry>::clear();
416
}
417
418
QPixmapCache::KeyData* QPMCache::getKeyData(QPixmapCache::Key *key)
419
{
420
    if (!key->d)
421
        key->d = new QPixmapCache::KeyData;
422
    return key->d;
423
}
424
425
Q_GLOBAL_STATIC(QPMCache, pm_cache)
426
427
int Q_AUTOTEST_EXPORT q_QPixmapCache_keyHashSize()
428
{
429
    return pm_cache()->size();
430
}
431
432
QPixmapCacheEntry::~QPixmapCacheEntry()
433
{
434
    pm_cache()->releaseKey(key);
435
}
436
437
/*!
438
    \obsolete
439
    \overload
440
441
    Returns the pixmap associated with the \a key in the cache, or
442
    null if there is no such pixmap.
443
444
    \warning If valid, you should copy the pixmap immediately (this is
445
    fast). Subsequent insertions into the cache could cause the
446
    pointer to become invalid. For this reason, we recommend you use
447
    bool find(const QString&, QPixmap*) instead.
448
449
    Example:
450
    \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 0
451
*/
452
453
QPixmap *QPixmapCache::find(const QString &key)
454
{
455
    return pm_cache()->object(key);
456
}
457
458
459
/*!
460
    \obsolete
461
462
    Use bool find(const QString&, QPixmap*) instead.
463
*/
464
465
bool QPixmapCache::find(const QString &key, QPixmap& pixmap)
466
{
467
    return find(key, &pixmap);
468
}
469
470
/*!
471
    Looks for a cached pixmap associated with the given \a key in the cache.
472
    If the pixmap is found, the function sets \a pixmap to that pixmap and
473
    returns true; otherwise it leaves \a pixmap alone and returns false.
474
475
    \since 4.6
476
477
    Example:
478
    \snippet doc/src/snippets/code/src_gui_image_qpixmapcache.cpp 1
479
*/
480
481
bool QPixmapCache::find(const QString &key, QPixmap* pixmap)
482
{
483
    QPixmap *ptr = pm_cache()->object(key);
484
    if (ptr && pixmap)
485
        *pixmap = *ptr;
486
    return ptr != 0;
487
}
488
489
/*!
490
    Looks for a cached pixmap associated with the given \a key in the cache.
491
    If the pixmap is found, the function sets \a pixmap to that pixmap and
492
    returns true; otherwise it leaves \a pixmap alone and returns false. If
493
    the pixmap is not found, it means that the \a key is no longer valid,
494
    so it will be released for the next insertion.
495
496
    \since 4.6
497
*/
498
bool QPixmapCache::find(const Key &key, QPixmap* pixmap)
499
{
500
    //The key is not valid anymore, a flush happened before probably
501
    if (!key.d || !key.d->isValid)
502
        return false;
503
    QPixmap *ptr = pm_cache()->object(key);
504
    if (ptr && pixmap)
505
        *pixmap = *ptr;
506
    return ptr != 0;
507
}
508
509
/*!
510
    Inserts a copy of the pixmap \a pixmap associated with the \a key into
511
    the cache.
512
513
    All pixmaps inserted by the Qt library have a key starting with
514
    "$qt", so your own pixmap keys should never begin "$qt".
515
516
    When a pixmap is inserted and the cache is about to exceed its
517
    limit, it removes pixmaps until there is enough room for the
518
    pixmap to be inserted.
519
520
    The oldest pixmaps (least recently accessed in the cache) are
521
    deleted when more space is needed.
522
523
    The function returns true if the object was inserted into the
524
    cache; otherwise it returns false.
525
526
    \sa setCacheLimit()
527
*/
528
529
bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
530
{
531
    return pm_cache()->insert(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8);
532
}
533
534
/*!
535
    Inserts a copy of the given \a pixmap into the cache and returns a key
536
    that can be used to retrieve it.
537
538
    When a pixmap is inserted and the cache is about to exceed its
539
    limit, it removes pixmaps until there is enough room for the
540
    pixmap to be inserted.
541
542
    The oldest pixmaps (least recently accessed in the cache) are
543
    deleted when more space is needed.
544
545
    \sa setCacheLimit(), replace()
546
547
    \since 4.6
548
*/
549
QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
550
{
551
    return pm_cache()->insert(pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8);
552
}
553
554
/*!
555
    Replaces the pixmap associated with the given \a key with the \a pixmap
556
    specified. Returns true if the \a pixmap has been correctly inserted into
557
    the cache; otherwise returns false.
558
559
    \sa setCacheLimit(), insert()
560
561
    \since 4.6
562
*/
563
bool QPixmapCache::replace(const Key &key, const QPixmap &pixmap)
564
{
565
    //The key is not valid anymore, a flush happened before probably
566
    if (!key.d || !key.d->isValid)
567
        return false;
568
    return pm_cache()->replace(key, pixmap, pixmap.width() * pixmap.height() * pixmap.depth() / 8);
569
}
570
571
/*!
572
    Returns the cache limit (in kilobytes).
573
574
    The default cache limit is 2048 KB on embedded platforms, 10240 KB on
575
    desktop platforms.
576
577
    \sa setCacheLimit()
578
*/
579
580
int QPixmapCache::cacheLimit()
581
{
582
    return cache_limit;
583
}
584
585
/*!
586
    Sets the cache limit to \a n kilobytes.
587
588
    The default setting is 2048 KB on embedded platforms, 10240 KB on
589
    desktop platforms.
590
591
    \sa cacheLimit()
592
*/
593
594
void QPixmapCache::setCacheLimit(int n)
595
{
596
    cache_limit = n;
597
    pm_cache()->setMaxCost(1024 * cache_limit);
598
}
599
600
/*!
601
  Removes the pixmap associated with \a key from the cache.
602
*/
603
void QPixmapCache::remove(const QString &key)
604
{
605
    pm_cache()->remove(key);
606
}
607
608
/*!
609
  Removes the pixmap associated with \a key from the cache and releases
610
  the key for a future insertion.
611
612
  \since 4.6
613
*/
614
void QPixmapCache::remove(const Key &key)
615
{
616
    //The key is not valid anymore, a flush happened before probably
617
    if (!key.d || !key.d->isValid)
618
        return;
619
    pm_cache()->remove(key);
620
}
621
622
/*!
623
    Removes all pixmaps from the cache.
624
*/
625
626
void QPixmapCache::clear()
627
{
628
    QT_TRY {
629
        pm_cache()->clear();
630
    } QT_CATCH(const std::bad_alloc &) {
631
        // if we ran out of memory during pm_cache(), it's no leak,
632
        // so just ignore it.
633
    }
634
}
635
636
QT_END_NAMESPACE