• Skip to content
  • Skip to link menu
KDE 4.0 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDEUI

kpixmapcache.cpp

Go to the documentation of this file.
00001 /*
00002  *
00003  * This file is part of the KDE project.
00004  * Copyright (C) 2007 Rivo Laks <rivolaks@hot.ee>
00005  *
00006  * This library is free software; you can redistribute it and/or
00007  * modify it under the terms of the GNU Library General Public
00008  * License version 2 as published by the Free Software Foundation.
00009  *
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Library General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Library General Public License
00016  * along with this library; see the file COPYING.LIB.  If not, write to
00017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include "kpixmapcache.h"
00022 
00023 #include <QtCore/QString>
00024 #include <QtGui/QPixmap>
00025 #include <QtCore/QFile>
00026 #include <QtCore/QDataStream>
00027 #include <QtCore/QFileInfo>
00028 #include <QtCore/QDateTime>
00029 #include <QtGui/QPixmapCache>
00030 #include <QtCore/QtGlobal>
00031 #include <QtGui/QPainter>
00032 #include <QtCore/QQueue>
00033 #include <QtCore/QThread>
00034 #include <QtCore/QTimer>
00035 #include <QtCore/QMutex>
00036 #include <QtCore/QMutexLocker>
00037 
00038 #include <kglobal.h>
00039 #include <kstandarddirs.h>
00040 #include <kdebug.h>
00041 #include <klockfile.h>
00042 #include <ksvgrenderer.h>
00043 #include <kdefakes.h>
00044 
00045 #include <config.h>
00046 
00047 #include <time.h>
00048 #include <unistd.h>
00049 #ifdef HAVE_SYS_MMAN_H
00050 #include <sys/mman.h>
00051 #endif
00052 
00053 
00054 //#define DISABLE_PIXMAPCACHE
00055 #if defined HAVE_MMAP && !defined Q_WS_WIN
00056 #define USE_MMAP
00057 #endif
00058 
00059 #define KPIXMAPCACHE_VERSION 0x000206
00060 
00061 namespace {
00062 
00063 class KPCLockFile
00064 {
00065 public:
00066     KPCLockFile(const QString& filename)
00067     {
00068         mValid = false;
00069         mLockFile = new KLockFile(filename);
00070         // Try to lock the file up to 5 times, waiting 5 ms between retries
00071         KLockFile::LockResult result;
00072         for (int i = 0; i < 5; i++) {
00073             result = mLockFile->lock(KLockFile::NoBlockFlag);
00074             if (result == KLockFile::LockOK) {
00075                 mValid = true;
00076                 break;
00077             }
00078             usleep(5*1000);
00079         }
00080         // Output error msg if locking failed
00081         if (!mValid) {
00082             kError() << "Failed to lock file" << filename << ", last result =" << result;
00083         }
00084     }
00085     ~KPCLockFile()
00086     {
00087         unlock();
00088         delete mLockFile;
00089     }
00090 
00091     void unlock()
00092     {
00093         if (mValid) {
00094             mLockFile->unlock();
00095             mValid = false;
00096         }
00097     }
00098 
00099     bool isValid() const  { return mValid; }
00100 
00101 private:
00102     bool mValid;
00103     KLockFile* mLockFile;
00104 };
00105 
00106 class KPCMemoryDevice : public QIODevice
00107 {
00108 public:
00109     KPCMemoryDevice(char* start, quint32* size, quint32 available);
00110     virtual ~KPCMemoryDevice();
00111 
00112     virtual qint64 size() const  { return *mSize; }
00113     void setSize(quint32 s)  { *mSize = s; }
00114     virtual bool seek(qint64 pos);
00115 
00116     static void setSizeEntryOffset(int o)  { mSizeEntryOffset = o; }
00117 
00118 protected:
00119     virtual qint64 readData(char* data, qint64 maxSize);
00120     virtual qint64 writeData(const char* data, qint64 maxSize);
00121 
00122 private:
00123     char* mMemory;
00124     quint32* mSize;
00125     quint32 mInitialSize;
00126     qint64 mAvailable;
00127     quint32 mPos;
00128 
00129     static int mSizeEntryOffset;  // Offset of file size entry
00130 };
00131 int KPCMemoryDevice::mSizeEntryOffset = 0;
00132 
00133 KPCMemoryDevice::KPCMemoryDevice(char* start, quint32* size, quint32 available) : QIODevice()
00134 {
00135     mMemory = start;
00136     mSize = size;
00137     mAvailable = available;
00138     mPos = 0;
00139 
00140     open(QIODevice::ReadWrite);
00141 
00142     // Load size
00143 #if 0
00144     // Can anyone tell why this isn't working?
00145     seek(mSizeEntryOffset);
00146     QDataStream stream(this);
00147     stream >> *mSize;
00148 #else
00149     // Load up-to-date size from the memory
00150     if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
00151         char buf[4];
00152         uchar* p = (uchar*)mSize;
00153         memcpy(buf, mMemory + mSizeEntryOffset, 4);
00154         *p++ = buf[3];
00155         *p++ = buf[2];
00156         *p++ = buf[1];
00157         *p   = buf[0];
00158     } else {
00159         *mSize = *((quint32*)(mMemory + mSizeEntryOffset));
00160     }
00161 #endif
00162 
00163     mInitialSize = *mSize;
00164 }
00165 
00166 KPCMemoryDevice::~KPCMemoryDevice()
00167 {
00168     if (*mSize != mInitialSize) {
00169         // Update file size
00170         seek(mSizeEntryOffset);
00171         QDataStream stream(this);
00172         stream << *mSize;
00173     }
00174 }
00175 
00176 bool KPCMemoryDevice::seek(qint64 pos)
00177 {
00178     if (pos < 0 || pos > *mSize) {
00179         return false;
00180     }
00181     mPos = pos;
00182     return QIODevice::seek(pos);
00183 }
00184 
00185 qint64 KPCMemoryDevice::readData(char* data, qint64 len)
00186 {
00187     len = qMin(len, qint64(*mSize) - mPos);
00188     if (len <= 0) {
00189         return 0;
00190     }
00191     memcpy(data, mMemory + mPos, len);
00192     mPos += len;
00193     return len;
00194 }
00195 
00196 qint64 KPCMemoryDevice::writeData(const char* data, qint64 len)
00197 {
00198     if (mPos + len > mAvailable) {
00199         kError() << "Overflow of" << mPos+len - mAvailable;
00200         return -1;
00201     }
00202     memcpy(mMemory + mPos, (uchar*)data, len);
00203     mPos += len;
00204     *mSize = qMax(*mSize, mPos);
00205     return len;
00206 }
00207 
00208 
00209 } // namespace
00210 
00211 
00212 class KPixmapCache::Private
00213 {
00214 public:
00215     Private(KPixmapCache* q);
00216 
00217     // Return device used to read from index or data file. The device is either
00218     //  QFile or KPCMemoryDevice (if mmap is used)
00219     QIODevice* indexDevice();
00220     QIODevice* dataDevice();
00221 
00222     // Unmmaps any currently mmapped files and then tries to (re)mmap the cache
00223     //  files. If mmapping is disabled then it does nothing
00224     bool mmapFiles();
00225     void unmmapFiles();
00226     // Marks the shared mmapped files as invalid so that all processes will
00227     //  reload the files
00228     void invalidateMmapFiles();
00229 
00230     int findOffset(const QString& key);
00231     int binarySearchKey(QDataStream& stream, const QString& key, int start);
00232     void writeIndexEntry(QDataStream& stream, const QString& key, int dataoffset);
00233 
00234     bool checkLockFile();
00235     bool checkFileVersion(const QString& filename);
00236     bool loadIndexHeader();
00237     bool loadDataHeader();
00238 
00239     bool removeEntries(int newsize);
00240     bool scheduleRemoveEntries(int newsize);
00241 
00242     void init();
00243     bool loadData(int offset, QPixmap& pix);
00244     int writeData(const QString& key, const QPixmap& pix);
00245     void writeIndex(const QString& key, int offset);
00246 
00247     // Prepends key's hash to the key. This makes comparisons and key
00248     //  lookups faster as the beginnings of the keys are more random
00249     QString indexKey(const QString& key);
00250 
00251 
00252     KPixmapCache* q;
00253 
00254     static const char* kpc_magic;
00255     static const int kpc_magic_len;
00256     static const int kpc_header_len;
00257     quint32 mHeaderSize;  // full size of the index header, including custom (subclass') header data
00258     quint32 mIndexRootOffset;  // offset of the first entry in index file
00259 
00260     QString mName;
00261     QString mIndexFile;
00262     QString mDataFile;
00263     QString mLockFileName;
00264     QMutex mMutex;
00265 
00266     quint32 mTimestamp;
00267     quint32 mCacheId;  // Unique id, will change when cache is recreated
00268     int mCacheLimit;
00269     RemoveStrategy mRemoveStrategy:4;
00270     bool mUseQPixmapCache:4;
00271 
00272     bool mInited:8;  // Whether init() has been called (it's called on-demand)
00273     bool mEnabled:8;   // whether it's possible to use the cache
00274     bool mValid:8;  // whether cache has been inited and is ready to be used
00275 
00276     // Holds info about mmapped file
00277     struct MmapInfo
00278     {
00279         MmapInfo()  { file = 0; memory = 0; }
00280         QFile* file;  // If this is not null, then the file is mmapped
00281         char* memory;
00282         quint32 size;  // Number of currently used bytes
00283         quint32 available;  // Number of available bytes (including those reserved for mmap)
00284     };
00285     MmapInfo mIndexMmapInfo;
00286     MmapInfo mDataMmapInfo;
00287     // Mmaps given file, growing it to newsize bytes.
00288     bool mmapFile(const QString& filename, MmapInfo* info, int newsize);
00289     void unmmapFile(MmapInfo* info);
00290 
00291 
00292     // Used by removeEntries()
00293     class KPixmapCacheEntry
00294     {
00295     public:
00296         KPixmapCacheEntry(int indexoffset_, const QString& key_, int dataoffset_,
00297                           int pos_, quint32 timesused_, quint32 lastused_)
00298         {
00299             indexoffset = indexoffset_;
00300             key = key_;
00301             dataoffset = dataoffset_;
00302             pos = pos_;
00303             timesused = timesused_;
00304             lastused = lastused_;
00305         }
00306 
00307         int indexoffset;
00308         QString key;
00309         int dataoffset;
00310 
00311         int pos;
00312         quint32 timesused;
00313         quint32 lastused;
00314     };
00315 
00316     // Various comparison functions for different removal strategies
00317     static bool compareEntriesByAge(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00318     {
00319         return a.pos > b.pos;
00320     }
00321     static bool compareEntriesByTimesUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00322     {
00323         return a.timesused > b.timesused;
00324     }
00325     static bool compareEntriesByLastUsed(const KPixmapCacheEntry& a, const KPixmapCacheEntry& b)
00326     {
00327         return a.lastused > b.lastused;
00328     }
00329 
00330     // Helper class to run the possibly expensive removeEntries() operation in
00331     //  the background thread
00332     class RemovalThread : public QThread
00333     {
00334     public:
00335         RemovalThread(KPixmapCache::Private* _d) : QThread()
00336         {
00337             d = _d;
00338             mRemovalScheduled = false;
00339         }
00340         ~RemovalThread()
00341         {
00342         }
00343 
00344         void scheduleRemoval(int newsize)
00345         {
00346             mNewSize = newsize;
00347             if (!mRemovalScheduled) {
00348                 QTimer::singleShot(5000, this, SLOT(start()));
00349                 mRemovalScheduled = true;
00350             }
00351         }
00352 
00353     protected:
00354         virtual void run()
00355         {
00356             mRemovalScheduled = false;
00357             kDebug(264) << "starting";
00358             d->removeEntries(mNewSize);
00359             kDebug(264) << "done";
00360         }
00361 
00362     private:
00363         bool mRemovalScheduled;
00364         int mNewSize;
00365         KPixmapCache::Private* d;
00366     };
00367     RemovalThread* mRemovalThread;
00368 };
00369 
00370 // Magic in the cache files
00371 const char* KPixmapCache::Private::kpc_magic = "KDE PIXMAP CACHE ";
00372 const int KPixmapCache::Private::kpc_magic_len = qstrlen(KPixmapCache::Private::kpc_magic);
00373 // Whole header is magic + version (4 bytes)
00374 const int KPixmapCache::Private::kpc_header_len = KPixmapCache::Private::kpc_magic_len + 4;
00375 
00376 
00377 KPixmapCache::Private::Private(KPixmapCache* _q)
00378 {
00379     q = _q;
00380     mRemovalThread = 0;
00381 
00382     KPCMemoryDevice::setSizeEntryOffset(kpc_header_len);  // ugly
00383 }
00384 
00385 bool KPixmapCache::Private::mmapFiles()
00386 {
00387 #ifdef USE_MMAP
00388     unmmapFiles();  // Noop if nothing has been mmapped
00389     if (!q->isValid()) {
00390         return false;
00391     }
00392 
00393     if (!mmapFile(mIndexFile, &mIndexMmapInfo, (int)(q->cacheLimit() * 0.4 + 100) * 1024)) {
00394         q->setValid(false);
00395         return false;
00396     }
00397     if (!mmapFile(mDataFile, &mDataMmapInfo, (int)(q->cacheLimit() * 1.5 + 500) * 1024)) {
00398         unmmapFile(&mIndexMmapInfo);
00399         q->setValid(false);
00400         return false;
00401     }
00402 
00403     return true;
00404 #else
00405     return false;
00406 #endif
00407 }
00408 
00409 void KPixmapCache::Private::unmmapFiles()
00410 {
00411     unmmapFile(&mIndexMmapInfo);
00412     unmmapFile(&mDataMmapInfo);
00413 }
00414 
00415 void KPixmapCache::Private::invalidateMmapFiles()
00416 {
00417     if (!q->isValid())
00418         return;
00419 #ifdef USE_MMAP
00420     // Set cache id to 0, this will force a reload the next time the files are used
00421     if (mIndexMmapInfo.file) {
00422         KPCMemoryDevice dev(mIndexMmapInfo.memory, &mIndexMmapInfo.size, mIndexMmapInfo.available);
00423         QDataStream stream(&dev);
00424         kDebug(264) << "Invalidating cache";
00425         dev.seek(kpc_header_len + sizeof(quint32));
00426         stream << (quint32)0;
00427     }
00428 #endif
00429 }
00430 
00431 bool KPixmapCache::Private::mmapFile(const QString& filename, MmapInfo* info, int newsize)
00432 {
00433 #ifdef USE_MMAP
00434     info->file = new QFile(filename);
00435     if (!info->file->open(QIODevice::ReadWrite)) {
00436         kDebug(264) << "Couldn't open" << filename;
00437         delete info->file;
00438         info->file = 0;
00439         return false;
00440     }
00441 
00442     if (!info->size) {
00443         info->size = info->file->size();
00444     }
00445     info->available = newsize;
00446     if (!info->file->resize(info->available)) {
00447         kError(264) << "Couldn't resize" << filename << "to" << newsize;
00448         delete info->file;
00449         info->file = 0;
00450         return false;
00451     }
00452 
00453     void* indexMem = mmap(0, info->available, PROT_READ | PROT_WRITE, MAP_SHARED, info->file->handle(), 0);
00454     if (indexMem == MAP_FAILED) {
00455         kError() << "mmap failed for" << filename;
00456         delete info->file;
00457         info->file = 0;
00458         return false;
00459     }
00460     info->memory = reinterpret_cast<char*>(indexMem);
00461 #ifdef HAVE_MADVISE
00462     madvise(info->memory, info->size, MADV_WILLNEED);
00463 #endif
00464 
00465     // Update our stored file size
00466     int size = info->size;
00467     KPCMemoryDevice dev(info->memory, &info->size, info->available);
00468     if (!info->size) {
00469         // Null size was read from the file. This means that we're the first to
00470         //  mmap it and so we have to write the correct size to the file.
00471         dev.setSize(size);
00472         // New size will be written to file in KPCMemoryDevice dtor
00473     }
00474 
00475     return true;
00476 #else
00477     return false;
00478 #endif
00479 }
00480 
00481 void KPixmapCache::Private::unmmapFile(MmapInfo* info)
00482 {
00483 #ifdef USE_MMAP
00484     if (info->file) {
00485         munmap(info->memory, info->available);
00486         delete info->file;
00487         info->file = 0;
00488     }
00489 #endif
00490 }
00491 
00492 QIODevice* KPixmapCache::Private::indexDevice()
00493 {
00494     QIODevice* device = 0;
00495 #ifdef USE_MMAP
00496     if (mIndexMmapInfo.file) {
00497         // Make sure the file still exists
00498         QFileInfo fi(mIndexFile);
00499         if (!fi.exists() || fi.size() != mIndexMmapInfo.available) {
00500             kDebug(264) << "File size has changed, re-initing";
00501             q->recreateCacheFiles();  // Also tries to re-init mmap
00502             if (!q->isValid()) {
00503                 return 0;
00504             } else {
00505                 return indexDevice();
00506             }
00507         }
00508 
00509         // Create the device
00510         device = new KPCMemoryDevice(mIndexMmapInfo.memory, &mIndexMmapInfo.size, mIndexMmapInfo.available);
00511     }
00512 #endif
00513     if (!device) {
00514         QFile* file = new QFile(mIndexFile);
00515         if (!file->exists() || file->size() < kpc_header_len) {
00516             q->recreateCacheFiles();
00517         }
00518         if (!file->open(QIODevice::ReadWrite)) {
00519             kDebug(264) << "Couldn't open index file" << mIndexFile;
00520             delete file;
00521             return 0;
00522         }
00523         device = file;
00524     }
00525     // Make sure the device is up-to-date
00526     QDataStream stream(device);
00527     device->seek(kpc_header_len + sizeof(quint32));
00528     quint32 cacheid;
00529     stream >> cacheid;
00530     if (cacheid != mCacheId) {
00531         kDebug(264) << "Cache has changed, reloading";
00532         delete device;
00533 
00534         init();
00535         if (!q->isValid()) {
00536             return 0;
00537         } else {
00538             return indexDevice();
00539         }
00540     }
00541 
00542     return device;
00543 }
00544 
00545 QIODevice* KPixmapCache::Private::dataDevice()
00546 {
00547 #ifdef USE_MMAP
00548     if (mDataMmapInfo.file) {
00549         // TODO: make sure the mmapped memory is up-to-date
00550         KPCMemoryDevice* device = new KPCMemoryDevice(mDataMmapInfo.memory, &mDataMmapInfo.size, mDataMmapInfo.available);
00551         return device;
00552     }
00553 #endif
00554     QFile* file = new QFile(mDataFile);
00555     if (!file->exists() || file->size() < kpc_header_len) {
00556         q->recreateCacheFiles();
00557         // Index file has also been recreated so we cannot continue with
00558         //  modifying the data file because it would make things inconsistent.
00559         delete file;
00560         return 0;
00561     }
00562     if (!file->open(QIODevice::ReadWrite)) {
00563         kDebug(264) << "Couldn't open data file" << mDataFile;
00564         delete file;
00565         return 0;
00566     }
00567     return file;
00568 }
00569 
00570 int KPixmapCache::Private::binarySearchKey(QDataStream& stream, const QString& key, int start)
00571 {
00572     stream.device()->seek(start);
00573 
00574     QString fkey;
00575     qint32 foffset;
00576     quint32 timesused, lastused;
00577     qint32 leftchild, rightchild;
00578     stream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
00579 
00580     if (key < fkey) {
00581         if (leftchild) {
00582             return binarySearchKey(stream, key, leftchild);
00583         }
00584     } else if (key == fkey) {
00585         return start;
00586     } else if (rightchild) {
00587         return binarySearchKey(stream, key, rightchild);
00588     }
00589 
00590     return start;
00591 }
00592 
00593 int KPixmapCache::Private::findOffset(const QString& key)
00594 {
00595     // Open device and datastream on it
00596     QIODevice* device = indexDevice();
00597     if (!device) {
00598         return -1;
00599     }
00600     device->seek(mIndexRootOffset);
00601     QDataStream stream(device);
00602 
00603     // If we're already at the end of the stream then the root node doesn't
00604     //  exist yet and there are no entries. Otherwise, do a binary search
00605     //  starting from the root node.
00606     if (!stream.atEnd()) {
00607         int nodeoffset = binarySearchKey(stream, key, mIndexRootOffset);
00608 
00609         // Load the found entry and check if it's the one we're looking for.
00610         device->seek(nodeoffset);
00611         QString fkey;
00612         stream >> fkey;
00613         if (fkey == key) {
00614             // Read offset and statistics
00615             qint32 foffset;
00616             quint32 timesused, lastused;
00617             stream >> foffset >> timesused;
00618             // Update statistics
00619             timesused++;
00620             lastused = ::time(0);
00621             stream.device()->seek(stream.device()->pos() - sizeof(quint32));
00622             stream << timesused << lastused;
00623             delete device;
00624             return foffset;
00625         }
00626     }
00627 
00628     // Nothing was found
00629     delete device;
00630     return -1;
00631 }
00632 
00633 bool KPixmapCache::Private::checkLockFile()
00634 {
00635     // For KLockFile we need to ensure the lock file doesn't exist.
00636     if (QFile::exists(mLockFileName)) {
00637         if (!QFile::remove(mLockFileName)) {
00638             kError() << "Couldn't remove lockfile" << mLockFileName;
00639             return false;
00640         }
00641     }
00642     return true;
00643 }
00644 
00645 bool KPixmapCache::Private::checkFileVersion(const QString& filename)
00646 {
00647     if (!mEnabled) {
00648         return false;
00649     }
00650 
00651     if (QFile::exists(filename)) {
00652         // File already exists, make sure it can be opened
00653         QFile f(filename);
00654         if (!f.open(QIODevice::ReadOnly)) {
00655             kError() << "Couldn't open file" << filename;
00656             return false;
00657         }
00658 
00659         QDataStream stream(&f);
00660 
00661         // Check header and version
00662         char buf[100];
00663         quint32 version;
00664         stream.readRawData(buf, kpc_magic_len);
00665         stream >> version;
00666         if (qstrncmp(buf, kpc_magic, kpc_magic_len) == 0) {
00667             if (version == KPIXMAPCACHE_VERSION) {
00668                 return true;
00669             } else if (version < KPIXMAPCACHE_VERSION) {
00670                 kDebug(264) << "File" << filename << "is outdated, will recreate...";
00671             } else {
00672                 // Don't recreate the cache if it has newer version to avoid
00673                 //  problems when upgrading kdelibs.
00674                 kDebug(264) << "File" << filename << "has newer version, disabling cache";
00675                 return false;
00676             }
00677         } else {
00678             kDebug(264) << "File" << filename << "is not KPixmapCache file, will recreate...";
00679         }
00680     }
00681 
00682     return q->recreateCacheFiles();
00683 }
00684 
00685 bool KPixmapCache::Private::loadDataHeader()
00686 {
00687     // Open file and datastream on it
00688     QFile file(mDataFile);
00689     if (!file.open(QIODevice::ReadOnly)) {
00690         return false;
00691     }
00692     file.seek(kpc_header_len);
00693 
00694     QDataStream stream(&file);
00695     stream >> mDataMmapInfo.size;
00696 
00697     return true;
00698 }
00699 
00700 bool KPixmapCache::Private::loadIndexHeader()
00701 {
00702     // Open file and datastream on it
00703     QFile file(mIndexFile);
00704     if (!file.open(QIODevice::ReadOnly)) {
00705         return false;
00706     }
00707     file.seek(kpc_header_len);
00708 
00709     QDataStream stream(&file);
00710     // Load file size
00711     stream >> mIndexMmapInfo.size;
00712     // Load cache id
00713     stream >> mCacheId;
00714     // Load timestamp
00715     stream >> mTimestamp;
00716 
00717     if (stream.status() != QDataStream::Ok) {
00718         kWarning() << "Failed to read index file's header";
00719         q->recreateCacheFiles();
00720         return false;
00721     }
00722 
00723     // Give custom implementations chance to load their headers
00724     if (!q->loadCustomIndexHeader(stream)) {
00725         return false;
00726     }
00727     mHeaderSize = stream.device()->pos();
00728 
00729     // Load root node pos.
00730     stream >> mIndexRootOffset;
00731 
00732     return true;
00733 }
00734 
00735 QString KPixmapCache::Private::indexKey(const QString& key)
00736 {
00737     QByteArray latin1 = key.toLatin1();
00738     return QString("%1%2").arg((ushort)qChecksum(latin1.data(), latin1.size()), 4, 16, QLatin1Char('0')).arg(key);
00739 }
00740 
00741 void KPixmapCache::Private::writeIndexEntry(QDataStream& stream, const QString& key, int dataoffset)
00742 {
00743     // New entry will be written to the end of the file
00744     qint32 offset = stream.device()->size();
00745     // Find parent index node for this node.
00746     int parentoffset = binarySearchKey(stream, key, mIndexRootOffset);
00747     if (parentoffset != stream.device()->size()) {
00748         // Check if this entry has the same key
00749         QString fkey;
00750         stream.device()->seek(parentoffset);
00751         stream >> fkey;
00752         if (key == fkey) {
00753             // We're overwriting an existing entry
00754             offset = parentoffset;
00755         }
00756     }
00757 
00758     stream.device()->seek(offset);
00759     // Write the data
00760     stream << key << (qint32)dataoffset;
00761     // Statistics (# of uses and last used timestamp)
00762     stream << (quint32)1 << (quint32)::time(0);
00763     // Write (empty) children offsets
00764     stream << (qint32)0 << (qint32)0;
00765 
00766     // If we created the root node or overwrote existing entry then the two
00767     //  offsets are equal and we're done. Otherwise set parent's child offset
00768     //  to correct value.
00769     if (parentoffset != offset) {
00770         stream.device()->seek(parentoffset);
00771         QString fkey;
00772         qint32 foffset, tmp;
00773         quint32 timesused, lastused;
00774         stream >> fkey >> foffset >> timesused >> lastused;
00775         if (key < fkey) {
00776             // New entry will be parent's left child
00777             stream << offset;
00778         } else {
00779             // New entry will be parent's right child
00780             stream >> tmp;
00781             stream << offset;
00782         }
00783     }
00784 }
00785 
00786 bool KPixmapCache::Private::scheduleRemoveEntries(int newsize)
00787 {
00788     if (!mRemovalThread) {
00789         mRemovalThread = new RemovalThread(this);
00790     }
00791     mRemovalThread->scheduleRemoval(newsize);
00792     return true;
00793 }
00794 
00795 bool KPixmapCache::Private::removeEntries(int newsize)
00796 {
00797     KPCLockFile lock(mLockFileName);
00798     if (!lock.isValid()) {
00799         kDebug(264) << "Couldn't lock cache" << mName;
00800         return false;
00801     }
00802     QMutexLocker mutexlocker(&mMutex);
00803 
00804     // Open old (current) files
00805     QFile indexfile(mIndexFile);
00806     if (!indexfile.open(QIODevice::ReadOnly)) {
00807         kDebug(264) << "Couldn't open old index file";
00808         return false;
00809     }
00810     QDataStream istream(&indexfile);
00811     QFile datafile(mDataFile);
00812     if (!datafile.open(QIODevice::ReadOnly)) {
00813         kDebug(264) << "Couldn't open old data file";
00814         return false;
00815     }
00816     if (datafile.size() <= newsize*1024) {
00817         kDebug(264) << "Cache size is already within limit (" << datafile.size() << " <= " << newsize*1024 << ")";
00818         return true;
00819     }
00820     QDataStream dstream(&datafile);
00821     // Open new files
00822     QFile newindexfile(mIndexFile + ".new");
00823     if (!newindexfile.open(QIODevice::ReadWrite)) {
00824         kDebug(264) << "Couldn't open new index file";
00825         return false;
00826     }
00827     QDataStream newistream(&newindexfile);
00828     QFile newdatafile(mDataFile + ".new");
00829     if (!newdatafile.open(QIODevice::WriteOnly)) {
00830         kDebug(264) << "Couldn't open new data file";
00831         return false;
00832     }
00833     QDataStream newdstream(&newdatafile);
00834 
00835     // Copy index file header
00836     char* header = new char[mHeaderSize];
00837     if (istream.readRawData(header, mHeaderSize) != (int)mHeaderSize) {
00838         kDebug(264) << "Couldn't read index header";
00839         delete [] header;
00840         return false;
00841     }
00842     newistream.writeRawData(header, mHeaderSize);
00843     // Write root index node offset
00844     newistream << (quint32)(mHeaderSize + sizeof(quint32));
00845     // Copy data file header
00846     int dataheaderlen = kpc_header_len + sizeof(quint32);
00847     // mHeaderSize is always bigger than dataheaderlen, so we needn't create
00848     //  new buffer
00849     if (dstream.readRawData(header, dataheaderlen) != dataheaderlen) {
00850         kDebug(264) << "Couldn't read data header";
00851         delete [] header;
00852         return false;
00853     }
00854     newdstream.writeRawData(header, dataheaderlen);
00855     delete [] header;
00856 
00857 
00858     // Load all entries
00859     QList<KPixmapCacheEntry> entries;
00860     // Do BFS to find all entries
00861     QQueue<int> open;
00862     open.enqueue(mIndexRootOffset);
00863     while (!open.isEmpty()) {
00864         int indexoffset = open.dequeue();
00865         indexfile.seek(indexoffset);
00866         QString fkey;
00867         qint32 foffset;
00868         quint32 timesused, lastused;
00869         qint32 leftchild, rightchild;
00870         istream >> fkey >> foffset >> timesused >> lastused >> leftchild >> rightchild;
00871         entries.append(KPixmapCacheEntry(indexoffset, fkey, foffset, entries.count(), timesused, lastused));
00872         if (leftchild) {
00873             open.enqueue(leftchild);
00874         }
00875         if (rightchild) {
00876             open.enqueue(rightchild);
00877         }
00878     }
00879 
00880 
00881     // Sort the entries according to RemoveStrategy. This moves the best
00882     //  entries to the beginning of the list
00883     if (q->removeEntryStrategy() == RemoveOldest) {
00884         qSort(entries.begin(), entries.end(), compareEntriesByAge);
00885     } else if (q->removeEntryStrategy() == RemoveSeldomUsed) {
00886         qSort(entries.begin(), entries.end(), compareEntriesByTimesUsed);
00887     } else {
00888         qSort(entries.begin(), entries.end(), compareEntriesByLastUsed);
00889     }
00890 
00891 
00892     // Write some entries to the new files
00893     int entrieswritten = 0;
00894     for (entrieswritten = 0; entrieswritten < entries.count(); entrieswritten++) {
00895         const KPixmapCacheEntry& entry = entries[entrieswritten];
00896         // Load data
00897         datafile.seek(entry.dataoffset);
00898         int entrysize = -datafile.pos();
00899         // We have some duplication here but this way we avoid uncompressing
00900         //  the data and constructing QPixmap which we don't really need.
00901         QString fkey;
00902         dstream >> fkey;
00903         qint32 format, w, h, bpl;
00904         dstream >> format >> w >> h >> bpl;
00905         QByteArray imgdatacompressed;
00906         dstream >> imgdatacompressed;
00907         // Load custom data as well. This will be stored by the subclass itself.
00908         if (!q->loadCustomData(dstream)) {
00909             return false;
00910         }
00911         // Find out size of this entry
00912         entrysize += datafile.pos();
00913 
00914         // Make sure we'll stay within size limit
00915         if (newdatafile.size() + entrysize > newsize*1024) {
00916             break;
00917         }
00918 
00919         // Now write the same data to the new file
00920         int newdataoffset = newdatafile.pos();
00921         newdstream << fkey;
00922         newdstream << format << w << h << bpl;
00923         newdstream << imgdatacompressed;
00924         q->writeCustomData(newdstream);
00925 
00926         // Finally, add the index entry
00927         writeIndexEntry(newistream, entry.key, newdataoffset);
00928     }
00929 
00930     // Set file size to 0 for mmap stuff
00931     newindexfile.seek(kpc_header_len);
00932     newistream << (quint32)0;
00933     newdatafile.seek(kpc_header_len);
00934     newdstream << (quint32)0;
00935 
00936     // Remove old files and rename the new ones
00937     indexfile.remove();
00938     datafile.remove();
00939     newindexfile.rename(mIndexFile);
00940     newdatafile.rename(mDataFile);
00941     invalidateMmapFiles();
00942 
00943     kDebug(264) << "Wrote back" << entrieswritten << "of" << entries.count() << "entries";
00944 
00945     return true;
00946 }
00947 
00948 
00949 
00950 
00951 KPixmapCache::KPixmapCache(const QString& name)
00952     :d(new Private(this))
00953 {
00954     d->mName = name;
00955     d->mUseQPixmapCache = true;
00956     d->mCacheLimit = 3 * 1024;
00957     d->mRemoveStrategy = RemoveLeastRecentlyUsed;
00958 
00959     // We cannot call init() here because the subclasses haven't been
00960     //  constructed yet and so their virtual methods cannot be used.
00961     d->mInited = false;
00962 }
00963 
00964 KPixmapCache::~KPixmapCache()
00965 {
00966     d->unmmapFiles();
00967     if (d->mRemovalThread) {
00968         d->mRemovalThread->wait();
00969         delete d->mRemovalThread;
00970     }
00971     delete d;
00972 }
00973 
00974 void KPixmapCache::Private::init()
00975 {
00976     mInited = true;
00977 
00978 #ifdef DISABLE_PIXMAPCACHE
00979     mValid = mEnabled = false;
00980 #else
00981     mValid = false;
00982 
00983     // Find locations of the files
00984     mIndexFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".index");
00985     mDataFile  = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".data");
00986     mLockFileName = KGlobal::dirs()->locateLocal("cache", "kpc/" + mName + ".lock");
00987 
00988     mEnabled = true;
00989     mEnabled &= checkLockFile();
00990     mEnabled &= checkFileVersion(mDataFile);
00991     mEnabled &= checkFileVersion(mIndexFile);
00992     if (!mEnabled) {
00993         kDebug(264) << "Pixmap cache" << mName << "is disabled";
00994     } else {
00995         // Cache is enabled, but check if it's ready for use
00996         loadDataHeader();
00997         q->setValid(loadIndexHeader());
00998         // Init mmap stuff if mmap is used
00999         mmapFiles();
01000     }
01001 #endif
01002 }
01003 
01004 void KPixmapCache::ensureInited() const
01005 {
01006     if (!d->mInited) {
01007         const_cast<KPixmapCache*>(this)->d->init();
01008     }
01009 }
01010 
01011 bool KPixmapCache::loadCustomIndexHeader(QDataStream&)
01012 {
01013     return true;
01014 }
01015 
01016 void KPixmapCache::writeCustomIndexHeader(QDataStream&)
01017 {
01018 }
01019 
01020 bool KPixmapCache::isEnabled() const
01021 {
01022     ensureInited();
01023     return d->mEnabled;
01024 }
01025 
01026 bool KPixmapCache::isValid() const
01027 {
01028     ensureInited();
01029     return d->mEnabled && d->mValid;
01030 }
01031 
01032 void KPixmapCache::setValid(bool valid)
01033 {
01034     ensureInited();
01035     d->mValid = valid;
01036 }
01037 
01038 unsigned int KPixmapCache::timestamp() const
01039 {
01040     ensureInited();
01041     return d->mTimestamp;
01042 }
01043 
01044 void KPixmapCache::setTimestamp(unsigned int ts)
01045 {
01046     ensureInited();
01047     d->mTimestamp = ts;
01048 
01049     // Write to file
01050     KPCLockFile lock(d->mLockFileName);
01051     if (!lock.isValid()) {
01052         // FIXME: now what?
01053         return;
01054     }
01055     QIODevice* device = d->indexDevice();
01056     if (!device) {
01057         return;
01058     }
01059     device->seek(d->kpc_header_len + 2*sizeof(quint32));
01060     QDataStream stream(device);
01061     stream << d->mTimestamp;
01062 
01063     delete device;
01064 }
01065 
01066 int KPixmapCache::size() const
01067 {
01068     ensureInited();
01069 #ifdef USE_MMAP
01070     if (d->mDataMmapInfo.file) {
01071         return d->mDataMmapInfo.size / 1024;
01072     }
01073 #endif
01074     return QFileInfo(d->mDataFile).size() / 1024;
01075 }
01076 
01077 void KPixmapCache::setUseQPixmapCache(bool use)
01078 {
01079     d->mUseQPixmapCache = use;
01080 }
01081 
01082 bool KPixmapCache::useQPixmapCache() const
01083 {
01084     return d->mUseQPixmapCache;
01085 }
01086 
01087 int KPixmapCache::cacheLimit() const
01088 {
01089     return d->mCacheLimit;
01090 }
01091 
01092 void KPixmapCache::setCacheLimit(int kbytes)
01093 {
01094     d->mCacheLimit = kbytes;
01095     // TODO: cleanup if size() > cacheLimit()
01096 }
01097 
01098 KPixmapCache::RemoveStrategy KPixmapCache::removeEntryStrategy() const
01099 {
01100     return d->mRemoveStrategy;
01101 }
01102 
01103 void KPixmapCache::setRemoveEntryStrategy(KPixmapCache::RemoveStrategy strategy)
01104 {
01105     d->mRemoveStrategy = strategy;
01106 }
01107 
01108 bool KPixmapCache::recreateCacheFiles()
01109 {
01110     if (!isEnabled()) {
01111         return false;
01112     }
01113 
01114     d->invalidateMmapFiles();
01115 
01116     d->mEnabled = false;
01117     // Create index file
01118     QFile indexfile(d->mIndexFile);
01119     if (!indexfile.open(QIODevice::WriteOnly)) {
01120         kError() << "Couldn't create index file" << d->mIndexFile;
01121         return false;
01122     }
01123     QDataStream istream(&indexfile);
01124     istream.writeRawData(d->kpc_magic, d->kpc_magic_len);
01125     istream << (quint32)KPIXMAPCACHE_VERSION;
01126     // Write invalid file size, valid one will be written later by mmap code
01127     d->mIndexMmapInfo.size = 0;
01128     istream << d->mIndexMmapInfo.size;
01129     // Write cache id
01130     d->mCacheId = ::time(0);
01131     istream << d->mCacheId;
01132     // Write default timestamp
01133     d->mTimestamp = ::time(0);
01134     istream << d->mTimestamp;
01135 
01136     // Create data file
01137     QFile datafile(d->mDataFile);
01138     if (!datafile.open(QIODevice::WriteOnly)) {
01139         kError() << "Couldn't create data file" << d->mDataFile;
01140         return false;
01141     }
01142     QDataStream dstream(&datafile);
01143     dstream.writeRawData(d->kpc_magic, d->kpc_magic_len);
01144     dstream << (quint32)KPIXMAPCACHE_VERSION;
01145     d->mDataMmapInfo.size = 0;
01146     dstream << d->mDataMmapInfo.size;
01147 
01148     setValid(true);
01149     writeCustomIndexHeader(istream);
01150     d->mHeaderSize = indexfile.pos();
01151 
01152     d->mIndexRootOffset = d->mHeaderSize + sizeof(quint32);
01153     istream << d->mIndexRootOffset;
01154 
01155     // Close the files and mmap them (if mmapping is used)
01156     indexfile.close();
01157     datafile.close();
01158     d->mEnabled = true;
01159     d->mmapFiles();
01160     return true;
01161 }
01162 
01163 void KPixmapCache::deleteCache(const QString& name)
01164 {
01165     QString indexFile = KGlobal::dirs()->locateLocal("cache", "kpc/" + name + ".index");
01166     QString dataFile  = KGlobal::dirs()->locateLocal("cache", "kpc/" + name + ".data");
01167     if (QFile::exists(indexFile)) {
01168         QFile::remove(indexFile);
01169     }
01170     if (QFile::exists(dataFile)) {
01171         QFile::remove(dataFile);
01172     }
01173     // No need to remove the lockfile
01174 }
01175 
01176 void KPixmapCache::discard()
01177 {
01178     deleteCache(d->mName);
01179     if (d->mUseQPixmapCache) {
01180         QPixmapCache::clear();
01181     }
01182     d->invalidateMmapFiles();
01183 
01184     d->mInited = false;
01185     d->init();
01186 }
01187 
01188 void KPixmapCache::removeEntries(int newsize)
01189 {
01190     if (!newsize) {
01191         newsize = cacheLimit();
01192     }
01193 
01194     d->removeEntries(newsize);
01195 }
01196 
01197 bool KPixmapCache::find(const QString& key, QPixmap& pix)
01198 {
01199     ensureInited();
01200     if (!isValid()) {
01201         return false;
01202     }
01203 
01204     //kDebug(264) << "key:" << key << ", use QPC:" << d->mUseQPixmapCache;
01205     // First try the QPixmapCache
01206     if (d->mUseQPixmapCache && QPixmapCache::find(key, pix)) {
01207         //kDebug(264) << "Found from QPC";
01208         return true;
01209     }
01210 
01211     KPCLockFile lock(d->mLockFileName);
01212     if (!lock.isValid()) {
01213         return false;
01214     }
01215 
01216     // Try to find the offset
01217     QString indexkey = d->indexKey(key);
01218     int offset = d->findOffset(indexkey);
01219     //kDebug(264) << "found offset" << offset;
01220     if (offset == -1) {
01221         return false;
01222     }
01223 
01224     // Load the data
01225     bool ret = d->loadData(offset, pix);
01226     if (ret && d->mUseQPixmapCache) {
01227         // This pixmap wasn't in QPC, put it there
01228         QPixmapCache::insert(key, pix);
01229     }
01230     return ret;
01231 }
01232 
01233 bool KPixmapCache::Private::loadData(int offset, QPixmap& pix)
01234 {
01235     // Open device and datastream on it
01236     QIODevice* device = dataDevice();
01237     if (!device) {
01238         return false;
01239     }
01240     //kDebug(264) << "Seeking to pos" << offset << "/" << file.size();
01241     if (!device->seek(offset)) {
01242         kError() << "Couldn't seek to pos" << offset;
01243         delete device;
01244         return false;
01245     }
01246     QDataStream stream(device);
01247 
01248     // Load
01249     QString fkey;
01250     stream >> fkey;
01251 
01252     // Load image info and compressed data
01253     qint32 format, w, h, bpl;
01254     stream >> format >> w >> h >> bpl;
01255     QByteArray imgdatacompressed;
01256     stream >> imgdatacompressed;
01257 
01258     // Uncompress the data and create the image
01259     // TODO: make sure this actually works. QImage ctor we use here seems to
01260     //  want 32-bit aligned data. QByteArray uses malloc() to allocate it's
01261     //  data, which _probably_ returns 32-bit aligned data.
01262     QByteArray imgdata = qUncompress(imgdatacompressed);
01263     if (!imgdata.isEmpty()) {
01264         QImage img((const uchar*)imgdata.constData(), w, h, bpl, (QImage::Format)format);
01265         img.bits();  // make deep copy since we don't want to keep imgdata around
01266         pix = QPixmap::fromImage(img);
01267     } else {
01268         pix = QPixmap(w, h);
01269     }
01270 
01271     if (!q->loadCustomData(stream)) {
01272         delete device;
01273         return false;
01274     }
01275 
01276     delete device;
01277     if (stream.status() != QDataStream::Ok) {
01278         kError() << "stream is bad :-(  status=" << stream.status();
01279         return false;
01280     }
01281 
01282     //kDebug(264) << "pixmap successfully loaded";
01283     return true;
01284 }
01285 
01286 bool KPixmapCache::loadCustomData(QDataStream&)
01287 {
01288     return true;
01289 }
01290 
01291 void KPixmapCache::insert(const QString& key, const QPixmap& pix)
01292 {
01293     ensureInited();
01294     if (!isValid()) {
01295         return;
01296     }
01297 
01298     //kDebug(264) << "key:" << key << ", size:" << pix.width() << "x" << pix.height();
01299     // Insert to QPixmapCache as well
01300     if (d->mUseQPixmapCache) {
01301         QPixmapCache::insert(key, pix);
01302     }
01303 
01304     KPCLockFile lock(d->mLockFileName);
01305     if (!lock.isValid()) {
01306         return;
01307     }
01308 
01309     // Insert to cache
01310     QString indexkey = d->indexKey(key);
01311     int offset = d->writeData(key, pix);
01312     //kDebug(264) << "data is at offset" << offset;
01313     if (offset == -1) {
01314         return;
01315     }
01316 
01317     d->writeIndex(indexkey, offset);
01318 
01319     // Make sure the cache size stays within limits
01320     if (size() > cacheLimit()) {
01321         lock.unlock();
01322         if (size() > (int)(cacheLimit() * 1.2)) {
01323             // Can't wait any longer, do it immediately
01324             d->removeEntries(int(cacheLimit() * 0.75));
01325         } else {
01326             d->scheduleRemoveEntries(int(cacheLimit() * 0.75));
01327         }
01328     }
01329 }
01330 
01331 int KPixmapCache::Private::writeData(const QString& key, const QPixmap& pix)
01332 {
01333     // Open device and datastream on it
01334     QIODevice* device = dataDevice();
01335     if (!device) {
01336         return -1;
01337     }
01338     int offset = device->size();
01339     device->seek(offset);
01340     QDataStream stream(device);
01341 
01342     // Write the data
01343     stream << key;
01344     // Write image info and compressed data
01345     QImage img = pix.toImage();
01346     QByteArray imgdatacompressed = qCompress(img.bits(), img.numBytes());
01347     stream << (qint32)img.format() << (qint32)img.width() << (qint32)img.height() << (qint32)img.bytesPerLine();
01348     stream << imgdatacompressed;
01349 
01350     q->writeCustomData(stream);
01351 
01352     delete device;
01353     return offset;
01354 }
01355 
01356 bool KPixmapCache::writeCustomData(QDataStream&)
01357 {
01358     return true;
01359 }
01360 
01361 void KPixmapCache::Private::writeIndex(const QString& key, int dataoffset)
01362 {
01363     // Open device and datastream on it
01364     QIODevice* device = indexDevice();
01365     if (!device) {
01366         return;
01367     }
01368     QDataStream stream(device);
01369 
01370     writeIndexEntry(stream, key, dataoffset);
01371     delete device;
01372 }
01373 
01374 QPixmap KPixmapCache::loadFromFile(const QString& filename)
01375 {
01376     QFileInfo fi(filename);
01377     if (!fi.exists()) {
01378         return QPixmap();
01379     } else if (fi.lastModified().toTime_t() > timestamp()) {
01380         // Cache is obsolete, will be regenerated
01381         discard();
01382     }
01383 
01384     QPixmap pix;
01385     QString key("file:" + filename);
01386     if (!find(key, pix)) {
01387         // It wasn't in the cache, so load it...
01388         pix = QPixmap(filename);
01389         if (pix.isNull()) {
01390             return pix;
01391         }
01392         // ... and put it there
01393         insert(key, pix);
01394     }
01395 
01396     return pix;
01397 }
01398 
01399 QPixmap KPixmapCache::loadFromSvg(const QString& filename, const QSize& size)
01400 {
01401     QFileInfo fi(filename);
01402     if (!fi.exists()) {
01403         return QPixmap();
01404     } else if (fi.lastModified().toTime_t() > timestamp()) {
01405         // Cache is obsolete, will be regenerated
01406         discard();
01407     }
01408 
01409     QPixmap pix;
01410     QString key = QString("file:%1_%2_%3").arg(filename).arg(size.width()).arg(size.height());
01411     if (!find(key, pix)) {
01412         // It wasn't in the cache, so load it...
01413         KSvgRenderer svg;
01414         if (!svg.load(filename)) {
01415             return pix;  // null pixmap
01416         } else {
01417             QSize pixSize = size.isValid() ? size : svg.defaultSize();
01418             pix = QPixmap(pixSize);
01419             pix.fill(Qt::transparent);
01420 
01421             QPainter p(&pix);
01422             svg.render(&p, QRectF(QPointF(), pixSize));
01423         }
01424 
01425         // ... and put it there
01426         insert(key, pix);
01427     }
01428 
01429     return pix;
01430 }
01431 

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   WTF
  • KJSEmbed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  •   core
  • Phonon
  •   Backend
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal