diff -r 7516d6d86cf5 -r ed14f46c0e55 src/hbcore/image/hbiconloader.cpp --- a/src/hbcore/image/hbiconloader.cpp Mon Oct 04 17:49:30 2010 +0300 +++ b/src/hbcore/image/hbiconloader.cpp Mon Oct 18 18:23:13 2010 +0300 @@ -28,6 +28,7 @@ #include "hbicontheme_p.h" #include "hblayoutdirectionnotifier_p.h" #include "hbinstance.h" +#include "hbinstance_p.h" #include "hbiconanimation_p.h" #include "hbiconanimator.h" #include "hbiconanimator_p.h" @@ -40,10 +41,10 @@ #include "hbimagetraces_p.h" #include "hbmemoryutils_p.h" #include "hbpixmapiconimpl_p.h" +#include "hbiconimplcreator_p.h" #include "hbiconsource_p.h" #include "hbthemeindex_p.h" #include "hbthemecommon_p.h" -#include "hbiconimplcreator_p.h" #include #include #include @@ -56,46 +57,58 @@ #include #include #include +#include +#include +#include #ifdef HB_NVG_CS_ICON #include "hbeglstate_p.h" +#include "hbnvgrasterizer_p.h" #endif -#define HB_ICONIMPL_CACHE -/* - * Client side caching of sgimage icon required, - * as sgimage lite cannot be opened multiple times - */ -#ifdef HB_SGIMAGE_ICON -#ifndef HB_ICONIMPL_CACHE -#define HB_ICONIMPL_CACHE -#endif -#endif - -// Just an experimental implementation.. Disable if necessary. -#define ENABLE_EXPERIMENTAL_RESIZE_BOOST__ - // SVG animation is currently disabled because of bugs in QT's svg engine #undef HB_SVG_ANIMATION +//#define HB_ICON_CACHE_DEBUG + +// Icon name without extension static const char *s_unknown = "unknown"; -// Icon name without extension /*! \class HbIconLoader - \brief HbIconLoader loads icons according to the Freedesktop Icon Theme Specification + \brief HbIconLoader loads and caches vector and raster icons + either via the Theme Server or directly from files/resources. \internal */ // Allocated dynamically so it can be deleted before the application object is destroyed. -// Deleting it later causes segmentation fault. +// Deleting it later causes segmentation fault so Q_GLOBAL_STATIC cannot be used. static HbIconLoader *theLoader = 0; +static bool loaderDestroyed = false; + +// The max consumption for the icons held by the cachekeeper, assuming that each +// icon is 32bpp. Note that the cachekeeper's content is cleared also when +// losing visibility and when switching rendering mode. +const int MAX_KEEPALIVE_CACHE_SIZE_BYTES = 1024 * 1024; // 1 MB + +// Icons with size above a certain limit are always ignored by the cachekeeper. +const int MAX_KEEPALIVE_ITEM_SIZE_BYTES = MAX_KEEPALIVE_CACHE_SIZE_BYTES / 2; -#ifdef HB_ICONIMPL_CACHE -static QHash iconImplCache; -#endif +static void cleanupLoader() +{ + if (theLoader) { + delete theLoader; + theLoader = 0; + } +} + +class HbLocalLoaderThread : public QThread +{ +public: + void run(); +}; class HbIconLoaderPrivate { @@ -122,17 +135,15 @@ bool isLayoutMirrored(); void setLayoutMirrored(bool mirrored); -#ifdef HB_ICONIMPL_CACHE QByteArray createCacheKeyFrom(const QString &iconName, const QSizeF &size, Qt::AspectRatioMode aspectRatioMode, QIcon::Mode mode, bool mirrored, - const QColor &color, - HbRenderingMode renderMode); -#endif + const QColor &color); -public: + void addItemToCache(const QByteArray &cacheKey, HbIconImpl *iconImpl); + QString storedTheme; int sourceResolution; @@ -147,7 +158,6 @@ HbIconSource *lastIconSource; -private: enum { Unknown = 0, NotMirrored = 1, @@ -158,8 +168,76 @@ * Flipped icons are used in the mirrored layout. */ int layoutMirrored; + + struct AsyncParams { + HbIconLoader::HbAsyncIconLoaderCallback mCallback; + HbIconLoadingParams mLdParams; + void *mParam; + }; + + QList mActiveAsyncRequests; + + HbLocalLoaderThread mLocalLoaderThread; + HbLocalIconLoader *mLocalLoader; + QMutex mLocalLoadMutex; + QMutex mIconSourceMutex; + friend class HbLocalLoaderThread; + + /* + * Client side caching of sgimage icon required, as sgimage lite cannot be + * opened multiple times + * + * It is also beneficial for performance, because it reduces IPC in certain + * cases. + * + * Note that by default this is not a permanent cache, i.e. when an iconimpl's + * refcount reaches zero it is removed from the cache. This means that the + * cache is beneficial for having the same icon rendered by two or more HbIcons + * at the same time (very typical in some itemview (e.g. list widget) cases), + * but it would not benefit a load-unload-load scenario because the icon is + * removed from the cache during the unload when there are no references + * anymore. + * + * However the cachekeeper below will change this behavior, preventing refcounts + * reaching zero in unLoadIcon(), so this cache may contain also icons that are + * not really in use and are only referenced by the cachekeeper. This is + * required for further reduction of IPC calls. + */ + QHash iconImplCache; + + // The global cachekeeper instance will hold references to icons that would + // normally be unloaded (i.e. mIcons will contain icons with refcount 1). + // + // Icons get added from unLoadIcon(), meaning that if the cachekeeper decides to + // hold a reference then the icon is not really unloaded (and thus stays in + // iconImplCache). + // + // When the icon gets referenced due to a cache hit, in loadIcon() and other + // places, the icon is removed from the cachekeeper. + class CacheKeeper { + public: + CacheKeeper(HbIconLoaderPrivate *p) : mConsumption(0), mIconLoaderPrivate(p) { } + void ref(HbIconImpl *icon); + void unref(HbIconImpl *icon); + void clear(); + private: + void del(HbIconImpl *icon, bool sendUnloadReq); + QList mIcons; + int mConsumption; + HbIconLoaderPrivate *mIconLoaderPrivate; + friend class HbIconLoaderPrivate; + }; + + CacheKeeper cacheKeeper; }; +void HbLocalLoaderThread::run() +{ + setPriority(QThread::LowPriority); + exec(); + delete HbIconLoaderPrivate::global()->mLocalLoader; +} + HbIconLoaderPrivate::HbIconLoaderPrivate() : storedTheme(HbTheme::instance()->name()), sourceResolution(144), // This is about the resolution of a Nokia N95 8GB @@ -168,13 +246,34 @@ animationManager(HbIconAnimationManager::global()), animationLoading(false), lastIconSource(0), - layoutMirrored(Unknown) + layoutMirrored(Unknown), + mLocalLoadMutex(QMutex::Recursive), + mIconSourceMutex(QMutex::Recursive), + cacheKeeper(this) { + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + mLocalLoader = new HbLocalIconLoader; + mLocalLoader->moveToThread(&mLocalLoaderThread); + mLocalLoaderThread.start(); } HbIconLoaderPrivate::~HbIconLoaderPrivate() { + // quit() cannot be called directly on mLocalLoaderThread because + // there is a chance that start() has not yet executed. + QMetaObject::invokeMethod(mLocalLoader, "doQuit", Qt::QueuedConnection); + mLocalLoaderThread.wait(); delete lastIconSource; + qDeleteAll(mActiveAsyncRequests); + cacheKeeper.clear(); + // There may be icons in iconImplCache at this point and they are not + // necessarily leftovers so they must not be destroyed. Depending on how the + // app is implemented there is a (small) chance that the HbIconLoader is + // destroyed before the destructors of icon engines or framedrawers are run + // so it must be left up to them to correctly unref all icons. + iconImplCache.clear(); } HbIconLoaderPrivate *HbIconLoaderPrivate::global() @@ -221,7 +320,9 @@ return suffix; } -QString HbIconLoaderPrivate::findSharedResourceHelper(const QString &resourceName, bool mirrored, bool &mirroredIconFound, Hb::ResourceType itemType, bool useThemeIndex) +QString HbIconLoaderPrivate::findSharedResourceHelper(const QString &resourceName, bool mirrored, + bool &mirroredIconFound, Hb::ResourceType itemType, + bool useThemeIndex) { Q_UNUSED(useThemeIndex) Q_UNUSED(itemType) @@ -234,7 +335,7 @@ HbThemeIndexResource resource(resourceName); if (resource.isValid()) { if (mirrored) { - return resource.fullMirroredFileName(); + return resource.fullMirroredFileName(mirroredIconFound); } else { return resource.fullFileName(); } @@ -443,16 +544,14 @@ layoutMirrored = mirrored ? Mirrored : NotMirrored; } -#ifdef HB_ICONIMPL_CACHE QByteArray HbIconLoaderPrivate::createCacheKeyFrom(const QString &iconName, - const QSizeF &size, - Qt::AspectRatioMode aspectRatioMode, - QIcon::Mode mode, - bool mirrored, - const QColor &color, - HbRenderingMode renderMode) + const QSizeF &size, + Qt::AspectRatioMode aspectRatioMode, + QIcon::Mode mode, + bool mirrored, + const QColor &color) { - static const int paramArraySize = 8; + static const int paramArraySize = 7; // This uses QByteArray to improve performance compared to QString. // It allows appending stuff with less heap allocations and conversions compared to using QString. @@ -478,8 +577,11 @@ temp[6] = 0; } - // Append render mode when creating cache key - temp[7] = renderMode; + // The rendering mode should not be included in the cache key to prevent + // confusion when the requested and the received rendering modes are + // different (i.e. to make life simple). Having a cache hit is always + // preferable to anything else. + cacheKey.append((char *)&(temp[0]), sizeof(int)*paramArraySize); const QChar *iconNamePtr = iconName.constData(); @@ -491,48 +593,88 @@ return cacheKey; } -#endif + +inline HbThemeClient::IconReqInfo paramsToReqInfo(const HbIconLoadingParams ¶ms) +{ + HbThemeClient::IconReqInfo reqInfo; + reqInfo.iconPath = params.iconFileName; + reqInfo.size = params.size; + reqInfo.aspectRatioMode = params.aspectRatioMode; + reqInfo.mode = params.mode; + reqInfo.mirrored = (params.mirrored && !params.mirroredIconFound); + reqInfo.options = params.options; + reqInfo.color = params.color; + reqInfo.renderMode = params.renderMode; + return reqInfo; +} + +inline HbThemeClient::IconReqInfo iconImplToReqInfo(HbIconImpl *icon) +{ + HbThemeClient::IconReqInfo reqInfo; + reqInfo.iconPath = icon->iconFileName(); + reqInfo.size = icon->keySize(); + reqInfo.aspectRatioMode = icon->iconAspectRatioMode(); + reqInfo.mode = icon->iconMode(); + reqInfo.mirrored = icon->isMirrored(); + reqInfo.color = icon->color(); + reqInfo.renderMode = icon->iconRenderingMode(); + return reqInfo; +} HbIconLoader::HbIconLoader(const QString &appName, QObject *parent) : QObject(parent) { setObjectName(appName); - d = new HbIconLoaderPrivate(); + d = new HbIconLoaderPrivate; + connect(d->mLocalLoader, SIGNAL(ready(HbIconLoadingParams, void*)), SLOT(localLoadReady(HbIconLoadingParams, void*))); // Set default rendering mode to EHWRendering renderMode = EHWRendering; - // Delete the icon loader when the application is destroyed. - connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(destroy())); + // Delete the icon loader during QCoreApplication destruction. Relying on + // aboutToQuit() would cause destruction to happen too early, with Q_GLOBAL_STATIC it + // would be too late. Recreation is forbidden so HbIconLoader::global() will return 0 + // after destroying the application instance but at least in a typical application the + // loader will still be available e.g. in widget (and thus graphics widget, hb view, + // etc.) destructors. + qAddPostRoutine(cleanupLoader); connect(HbLayoutDirectionNotifier::instance(), SIGNAL(layoutDirectionChangeStarted()), this, SLOT(updateLayoutDirection())); -#ifdef HB_TOOL_INTERFACE - // This enables partial theme updates. - connect(&hbInstance->theme()->d_ptr->iconTheme, SIGNAL(iconsUpdated(QStringList)), this, SLOT(themeChange(QStringList))); -#endif + HbTheme *theme = hbInstance->theme(); + connect(&theme->d_ptr->iconTheme, SIGNAL(iconsUpdated(QStringList)), SLOT(themeChange(QStringList))); + connect(theme, SIGNAL(changeFinished()), SLOT(themeChangeFinished())); } HbIconLoader::~HbIconLoader() { delete d; + loaderDestroyed = true; } HbIconLoader *HbIconLoader::global() { // Allocated dynamically so it can be deleted before the application object is destroyed. // Deleting it later causes segmentation fault. - if (!theLoader) { + // Once destroyed, creating the loader again must not be allowed (e.g. because there + // may not be a QApplication instance anymore at this stage). It is normal to + // return null pointer in this case, it can happen only during app shutdown. If anybody + // is using HbIconLoader::global() from destructors, they have to be prepared for the + // null ptr result too. + if (!theLoader && !loaderDestroyed) { theLoader = new HbIconLoader; } - + if (!theLoader) { + qWarning("HbIconLoader instance not present, returning null."); + } return theLoader; } QSizeF HbIconLoader::defaultSize(const QString &iconName, const QString &appName, IconLoaderOptions options) { Q_UNUSED(appName) + QMutexLocker locker(&d->mIconSourceMutex); QSizeF size; // Populate parameters needed for getting the default size @@ -624,7 +766,7 @@ } } - foreach (const QString &suffix, suffixList) { + foreach(const QString & suffix, suffixList) { bool dummy = false; QString nameWithSuffix = iconName; @@ -678,15 +820,34 @@ void HbIconLoader::themeChange(const QStringList &updatedFiles) { - foreach(HbFrameDrawerPrivate * frameDrawer, this->frameDrawerInstanceList) frameDrawer->themeChange(updatedFiles); + // For icons, the content is dropped in HbIconEngine. + // For framedrawers, HbFrameItem notifies the framedrawer when the theme changes. + // This below is only needed to support partial theme updates for framedrawers in external tools. + // (standalone framedrawers do not update automatically, except in tools) +#ifdef HB_TOOL_INTERFACE + foreach(HbFrameDrawerPrivate * frameDrawer, frameDrawerInstanceList) { + frameDrawer->themeChange(updatedFiles); + } +#else + Q_UNUSED(updatedFiles); +#endif +} + +void HbIconLoader::themeChangeFinished() +{ +#ifdef HB_ICON_CACHE_DEBUG + qDebug("HbIconLoader::themeChangeFinished: dropping unused icons"); +#endif + // We need to drop unused icons to prevent reusing them now that the theme is different. + // Doing it in themeChange() would not be right, it would be too early, + // because unloading would make the dropped icons unused (but referenced) so + // we would end up with reusing the old graphics. This here is safe. + d->cacheKeeper.clear(); } void HbIconLoader::destroy() { - if (theLoader) { - delete theLoader; - theLoader = 0; - } + cleanupLoader(); } void HbIconLoader::updateLayoutDirection() @@ -698,16 +859,16 @@ // directionality must be updated in the icon loader before that. // Thus, there are these separate signals. QList allWindows = hbInstance->allMainWindows(); - HbMainWindow *primaryWindow = allWindows.value(0); - - d->setLayoutMirrored(primaryWindow->layoutDirection() == Qt::RightToLeft); + if (allWindows.count()) { + HbMainWindow *primaryWindow = allWindows.value(0); + d->setLayoutMirrored(primaryWindow->layoutDirection() == Qt::RightToLeft); + } } void HbIconLoader::handleForegroundLost() { #if defined(HB_SGIMAGE_ICON) || defined(HB_NVG_CS_ICON) - // Remove SGImage /NVG type of icons - freeGpuIconData(); + freeIconData(); // delete the VGImage HbEglStates *eglStateInstance = HbEglStates::global(); eglStateInstance->handleForegroundLost(); @@ -716,18 +877,36 @@ #endif } +void HbIconLoaderPrivate::addItemToCache(const QByteArray &cacheKey, HbIconImpl *iconImpl) +{ +#ifdef HB_ICON_CACHE_DEBUG + if (iconImplCache.contains(cacheKey)) { + qWarning() << "HbIconLoader::addItemToCache: Possible leak: Icon with same key for" + << iconImpl->iconFileName() << "is already in cache"; + } +#endif + + iconImplCache.insert(cacheKey, iconImpl); + +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader::addItemToCache: added" << iconImpl->iconFileName() + << "cache item count now" << iconImplCache.count(); +#endif +} + /*! - * Removes the IconImpl entry from the client side cache + * Removes the iconimpl entry from the client side cache */ void HbIconLoader::removeItemInCache(HbIconImpl *iconImpl) { -#ifdef HB_ICONIMPL_CACHE if (iconImpl) { - iconImplCache.remove(iconImplCache.key(iconImpl)); + if (d->iconImplCache.remove(d->iconImplCache.key(iconImpl)) > 0) { +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader::removeItemInCache: Removed" + << iconImpl->iconFileName() << iconImpl->keySize(); +#endif + } } -#else - Q_UNUSED(iconImpl); -#endif } /*! @@ -737,19 +916,41 @@ void HbIconLoader::freeGpuIconData() { #if defined(HB_SGIMAGE_ICON) || defined(HB_NVG_CS_ICON) + d->cacheKeeper.clear(); // unref all unused icons + for (int i = 0; i < iconEngineList.count(); i++) { + HbIconEngine *engine = iconEngineList.at(i); + if (engine->iconFormatType() == SGIMAGE || engine->iconFormatType() == NVG) { + engine->resetIconImpl(); + } + } + for (int i = 0; i < frameDrawerInstanceList.count(); i++) { + HbFrameDrawerPrivate *fd = frameDrawerInstanceList.at(i); + if (fd->iconFormatType() == SGIMAGE || fd->iconFormatType() == NVG) { + fd->resetMaskableIcon(); + } + } +#endif +} + +/*! + * Cleans up (deletes) the HbIconImpl instances at the client side + * It also resets the engine's iconImpl and MaskableIcon's iconImpl + */ +void HbIconLoader::freeIconData() +{ + d->cacheKeeper.clear(); // unref all unused icons for (int i = 0; i < iconEngineList.count(); i++) { HbIconEngine *engine = iconEngineList.at(i); engine->resetIconImpl(); } for (int i = 0; i < frameDrawerInstanceList.count(); i++) { HbFrameDrawerPrivate *fd = frameDrawerInstanceList.at(i); - if ((fd->iconFormatType() == SGIMAGE) || (fd->iconFormatType() == NVG)) { - fd->resetMaskableIcon(); - } + fd->resetMaskableIcon(); + } -#endif } + /*! \internal @@ -864,7 +1065,7 @@ params.size, params.aspectRatioMode, QIcon::Normal, - params.options, + params.options | DoNotCache, 0, params.color); @@ -877,9 +1078,9 @@ // Return the first frame in the canvas pixmap if (frameList.count()) { - params.canvasPixmap = frameList.at(0).pixmap; + params.image = frameList.at(0).pixmap.toImage(); // Mirroring is already done when loading the frame. - // Mode is handled for canvasPixmap in the end of this function. + // Mode is handled for the image at the end of this function. params.mirroringHandled = true; } @@ -895,7 +1096,7 @@ } // Take default size from the first frame - QSizeF renderSize = QSizeF(params.canvasPixmap.size()); + QSizeF renderSize = QSizeF(params.image.size()); if (!params.isDefaultSize) { renderSize.scale(params.size, params.aspectRatioMode); @@ -942,35 +1143,103 @@ */ HbIconImpl *HbIconLoader::getIconFromServer(HbIconLoadingParams ¶ms) { - HbIconImpl *icon = 0; - #ifdef HB_ICON_TRACES qDebug() << "HbIconLoader::getIconFromServer: req to server for" << params.iconFileName; #endif + HbSharedIconInfo iconInfo; iconInfo.type = INVALID_FORMAT; - //Initiate an IPC to themeserver to get the icon-data from the server via themeclient. - iconInfo = HbThemeClient::global()->getSharedIconInfo( - params.iconFileName, - params.size, - params.aspectRatioMode, - params.mode, - (params.mirrored && !params.mirroredIconFound), - params.options, - params.color, - params.renderMode); + HbThemeClient *themeClient = HbThemeClient::global(); + // Initiate an IPC to themeserver to get the icon-data from the server via themeclient. + iconInfo = themeClient->getSharedIconInfo(paramsToReqInfo(params)); + HbIconImpl *icon = finishGetIconFromServer(iconInfo, params); +#ifdef HB_ICON_TRACES + qDebug() << "Image from server: " << params.iconFileName << " offset = " << iconInfo.pixmapData.offset << "icon ptr" << (int) icon; +#endif + + return icon; +} + +HbIconImpl *HbIconLoader::finishGetIconFromServer(HbSharedIconInfo &iconInfo, HbIconLoadingParams ¶ms) +{ //Creates HbIconImpl instance based on the type of data returned by themeserver. //HbIconImpl thus created could be any one of the following impl-types: //1. HbSgImageIconImpl //2. HbNvgIconImpl //3. HbPixmapIconImpl - icon = HbIconImplCreator::createIconImpl(iconInfo, params); + HbIconImpl *icon = HbIconImplCreator::createIconImpl(iconInfo, params); + + if (icon && icon->initialize() == HbIconImpl::ErrorLowGraphicsMemory) { + // initialisation failed due to low graphics memory, in sgimage icon case + // try creating pixmap based icon + + // unload the created GPU icon + unLoadIcon(icon, false, true); + icon = 0; + + // create a pixmap based icon + HbThemeClient *themeClient = HbThemeClient::global(); + HbThemeClient::IconReqInfo reqInfo = paramsToReqInfo(params); + reqInfo.renderMode = ESWRendering; + iconInfo = themeClient->getSharedIconInfo(reqInfo); + icon = HbIconImplCreator::createIconImpl(iconInfo, params); + // no need call initialize of this icon + } + + return icon; +} -#ifdef HB_ICON_TRACES - qDebug() << "Image from server: " << params.iconFileName << " offset = " << iconInfo.pixmapData.offset << "icon ptr" << (int) icon; -#endif - return icon; +bool HbIconLoader::asyncCallback(const HbSharedIconInfo &info, void *param) +{ + HbIconLoader *self = theLoader; + HbIconLoaderPrivate::AsyncParams *p = static_cast(param); + HbSharedIconInfo iconInfo = info; + if (!self->d->mActiveAsyncRequests.contains(p)) { + // 'p' is destroyed already, this happens when canceling the loadIcon + // request. Stop here and return false so the themeclient will issue an + // unload request. + return false; + } + bool result = true; + // The icon may have been loaded via another request meanwhile so check the cache again. + HbIconImpl *icon = self->lookupInCache(p->mLdParams, 0); + if (icon) { + // Found in the local iconimpl cache so use that and roll back, i.e. do + // an unload for the data we just got (this is necessary to maintain + // proper remote refcounts). + result = false; + } else { + icon = self->finishGetIconFromServer(iconInfo, p->mLdParams); + if (!icon) { + self->loadLocal(p->mLdParams, self->formatFromPath(p->mLdParams.iconFileName)); + icon = self->finishLocal(p->mLdParams); + } + if (icon) { + self->cacheIcon(p->mLdParams, icon, 0); + } + } + if (p->mCallback) { + p->mCallback(icon, p->mParam, false); + } + self->d->mActiveAsyncRequests.removeOne(p); + delete p; + return result; +} + +void HbIconLoader::getIconFromServerAsync(HbIconLoadingParams ¶ms, + HbAsyncIconLoaderCallback callback, + void *callbackParam) +{ + HbThemeClient *themeClient = HbThemeClient::global(); + HbIconLoaderPrivate::AsyncParams *p = new HbIconLoaderPrivate::AsyncParams; + p->mCallback = callback; + p->mLdParams = params; + p->mParam = callbackParam; + d->mActiveAsyncRequests.append(p); + themeClient->getSharedIconInfo(paramsToReqInfo(params), + asyncCallback, + p); } void HbIconLoader::loadSvgIcon(HbIconLoadingParams ¶ms) @@ -1028,12 +1297,10 @@ #endif // HB_SVG_ANIMATION - QPixmap &pm = params.canvasPixmap; - - pm = QPixmap(renderSize.toSize()); - pm.fill(Qt::transparent); + params.image = QImage(renderSize.toSize(), QImage::Format_ARGB32_Premultiplied); + params.image.fill(QColor(Qt::transparent).rgba()); QPainter painter; - painter.begin(&pm); + painter.begin(¶ms.image); svgRenderer->render(&painter, QRectF(QPointF(), renderSize.toSize())); painter.end(); } @@ -1071,13 +1338,10 @@ sy = renderSize.height() / picSize.height(); } - QPixmap &pm = params.canvasPixmap; - - pm = QPixmap(renderSize.toSize()); - pm.fill(Qt::transparent); - + params.image = QImage(renderSize.toSize(), QImage::Format_ARGB32_Premultiplied); + params.image.fill(QColor(Qt::transparent).rgba()); QPainter painter; - painter.begin(&pm); + painter.begin(¶ms.image); if (scale) { painter.scale(sx, sy); } @@ -1140,7 +1404,7 @@ // Get the first frame if (animationCreated) { - params.canvasPixmap = params.animator->d->animation->currentFrame(); + params.image = params.animator->d->animation->currentFrame().toImage(); // Mirroring and mode are handled in HbIconAnimationImage::currentFrame() params.mirroringHandled = true; params.modeHandled = true; @@ -1149,9 +1413,7 @@ if (imgRenderer->size() != scaledSize) { imgRenderer->setScaledSize(scaledSize); } - - QImage img = imgRenderer->read(); - params.canvasPixmap = QPixmap::fromImage(img); + params.image = imgRenderer->read(); } source->releaseImageReader(); @@ -1161,39 +1423,75 @@ { HbIconSource *source = getIconSource(params.iconFileName, format); - QPixmap &pm = params.canvasPixmap; - // Render bitmap graphics onto pixmap - pm = *source->pixmap(); + // Render bitmap graphics into an image. We have to use QImage because this + // code may run outside the gui (main) thread. + params.image = *source->image(); - if (!pm.isNull()) { -#ifdef ENABLE_EXPERIMENTAL_RESIZE_BOOST__ - // This test implementation improves resize speed up to 5 times.. + if (!params.image.isNull()) { + // This implementation improves resize speed up to 5 times. if (!params.isDefaultSize && !params.size.isEmpty()) { // Smooth scaling is very expensive (size^2). Therefore we reduce the size // to 1.5 of the destination size and using fast transformation. // Therefore we speed up but don't loose quality.. - if (pm.size().width() > (4 * params.size.toSize().width())) { + if (params.image.size().width() > (4 * params.size.toSize().width())) { // Improve scaling speed by add an intermediate fast transformation.. QSize intermediate_size = QSize(params.size.toSize().width() * 2, params.size.toSize().height() * 2); - pm = pm.scaled( - intermediate_size, - params.aspectRatioMode, - Qt::FastTransformation); // Cheap operation! + params.image = params.image.scaled( + intermediate_size, + params.aspectRatioMode, + Qt::FastTransformation); // Cheap operation } -#endif // ENABLE_EXPERIMENTAL_RESIZE_BOOST__ - pm = pm.scaled( - params.size.toSize(), - params.aspectRatioMode, - Qt::SmoothTransformation); // Expensive operation! - -#ifdef ENABLE_EXPERIMENTAL_RESIZE_BOOST__ + params.image = params.image.scaled( + params.size.toSize(), + params.aspectRatioMode, + Qt::SmoothTransformation); // Expensive operation } -#endif } // Delete original pixmap if its size is large - source->deletePixmapIfLargerThan(PIXMAP_SIZE_LIMIT); + source->deleteImageIfLargerThan(IMAGE_SIZE_LIMIT); +} + +void HbIconLoader::loadNvgIcon(HbIconLoadingParams ¶ms ) +{ +#ifdef HB_NVG_CS_ICON + HbIconSource *source = getIconSource(params.iconFileName, "NVG"); + if (!source) { + return; + } + + HbNvgRasterizer * nvgRasterizer = HbNvgRasterizer::global(); + QByteArray *sourceByteArray = source->byteArray(); + if( !sourceByteArray ) { + return; + } + + QByteArray nvgArray = *sourceByteArray; + QSizeF renderSize = source->defaultSize(); + if (!params.isDefaultSize) { + renderSize.scale(params.size, params.aspectRatioMode); + } else if (params.options.testFlag(ResolutionCorrected)) { + applyResolutionCorrection(renderSize); + } + + QSize iconSize = renderSize.toSize(); + QImage image(iconSize, QImage::Format_ARGB32_Premultiplied); + QImage::Format imageFormat = image.format(); + int stride = image.bytesPerLine(); + void * rasterizedData = image.bits(); + + bool success = nvgRasterizer->rasterize(nvgArray, iconSize, + params.aspectRatioMode, + rasterizedData, stride,imageFormat); + if (success) { + params.image = image; + } + +#else + Q_UNUSED(params) +#endif + } /*! @@ -1215,6 +1513,7 @@ #if defined(HB_SGIMAGE_ICON) || defined(HB_NVG_CS_ICON) if (newRenderMode != renderMode) { + d->cacheKeeper.clear(); // unref all unused icons if (newRenderMode == ESWRendering) { // switching from HW to SW mode freeGpuIconData(); @@ -1235,6 +1534,38 @@ } } +// Note that this is not the same as HbThemeUtils::isLogicalName. +// A non-logical name can still indicate content that must go through themeserver. +inline bool isLocalContent(const QString &iconName) +{ + // Check if we have a simple file or embedded resource, given with full + // or relative path. A filename like "x.png" is also local content + // because logical icon names must never contain an extension. + bool localContent = iconName.startsWith(':') + || iconName.contains('/') || iconName.contains('\\') + || (iconName.length() > 1 && iconName.at(1) == ':') + || (iconName.contains('.') && !iconName.startsWith("qtg_", Qt::CaseInsensitive)); + + return localContent; +} + +inline bool serverUseAllowed(const QString &iconName, HbIconLoader::IconLoaderOptions options) +{ + bool allowServer = !options.testFlag(HbIconLoader::DoNotCache); + + // No local loading for NVG... The server must still be used. + if (iconName.endsWith(".nvg", Qt::CaseInsensitive)) { + allowServer = true; + } + // We have no choice but to assume that theme graphics (logical names) are + // NVG in order to keep it working with DoNotCache. + if (!iconName.contains('.') && !iconName.contains('/') && !iconName.contains('\\')) { + allowServer = true; + } + + return allowServer; +} + /*! * \fn HbIconImpl* HbIconLoader::loadIcon() * @@ -1255,26 +1586,21 @@ QIcon::Mode mode, IconLoaderOptions options, HbIconAnimator *animator, - const QColor &color) + const QColor &color, + HbAsyncIconLoaderCallback callback, + void *callbackParam) { #ifdef HB_ICON_TRACES - QString debugString = "HbIconLoader::loadIcon START - "; - debugString.append(iconName); - debugString.append(" @ "); - if (size.isNull()) { - debugString.append("DEFAULT SIZE"); - } else { - debugString.append(QString::number(size.width())); - debugString.append('x'); - debugString.append(QString::number(size.height())); - } - qDebug() << debugString; + qDebug() << "loadIcon" << iconName << size; #endif Q_UNUSED(type) HbIconImpl *icon = 0; if (!size.isValid()) { + if (callback) { + callback(0, callbackParam, true); + } return 0; } @@ -1323,28 +1649,16 @@ } // Step 2: There was no animation definition, try get icon from server + QByteArray cacheKey; if (!params.animationCreated) { - -#ifdef HB_ICONIMPL_CACHE - QByteArray cacheKey = d->createCacheKeyFrom(params.iconName, - params.size, - params.aspectRatioMode, - params.mode, - params.mirrored, - params.color, - params.renderMode); - //look up in the local iconImplCache. - //If found return the ptr directly - if (iconImplCache.contains(cacheKey)) { - HbIconImpl *ptr = iconImplCache.value(cacheKey); - ptr->incrementRefCount(); -#ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::loadIcon(): " << "Cache hit in iconImplCache for" << params.iconName << params.size.height() << "X" << params.size.width() ; - qDebug() << "HbIconLoader::loadIcon(): Client RefCount now = " << ptr->refCount(); -#endif - return ptr; + // First check in the local iconimpl cache. + HbIconImpl *cachedIcon = lookupInCache(params, &cacheKey); + if (cachedIcon) { + if (callback) { + callback(cachedIcon, callbackParam, true); + } + return cachedIcon; } -#endif // Resolve used icon filename. It uses themeindex for themed icons. params.iconFileName = resolveIconFileName(params); @@ -1358,40 +1672,40 @@ #ifdef HB_ICON_TRACES qDebug() << "HbIconLoader::loadIcon (empty icon) END"; #endif - icon = new HbPixmapIconImpl(params.canvasPixmap); + icon = new HbPixmapIconImpl(QPixmap()); + if (callback) { + callback(icon, callbackParam, true); + } return icon; } -#ifdef Q_OS_SYMBIAN - // Check whether icon is in a private directory which cannot be accessed by the theme server - bool privateDirectory = isInPrivateDirectory(iconName); -#endif // Q_OS_SYMBIAN - QString format = formatFromPath(params.iconFileName); -// Theme server on desktop was found very slow (probably due to IPC with QLocalServer/QLocalSocket). -// disabling icon sharing via theme server until theme server performance on desktop is improved #ifdef Q_OS_SYMBIAN GET_MEMORY_MANAGER(HbMemoryManager::SharedMemory) // Try to take data from server if parameters don't prevent it - if (!options.testFlag(DoNotCache) - && format != "MNG" - && format != "GIF" - && !iconName.startsWith(':') // not using server for app's own resources (iconName is a logical name for theme elements) - && !privateDirectory // server cannot load from protected private dir - && manager) { + if (serverUseAllowed(iconName, options) + // Use the server only for theme graphics. + // For local files, i.e. anything that is not a single logical name, use local loading. + && !isLocalContent(iconName) + && format != "MNG" + && format != "GIF" + && manager) { - //Initiate an IPC to themeserver to get the icon-data from the server. + // Initiate an IPC to themeserver to get the icon-data from the server. + + if (callback) { + getIconFromServerAsync(params, callback, callbackParam); + return 0; + } + icon = getIconFromServer(params); + // No check for DoNotCache here. If we decided to use the server regardless of + // the flag then there's a chance that we have to cache (in case of SgImage + // for example) for proper operation, no matter what. if (icon) { -#ifdef HB_ICONIMPL_CACHE - iconImplCache.insert(cacheKey, icon); -#ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::loadIcon(): " << params.iconName << " inserted into impl-cache, ref-count now = " << icon->refCount(); -#endif - -#endif + cacheIcon(params, icon, &cacheKey); return icon; } @@ -1399,52 +1713,177 @@ #endif // Q_OS_SYMBIAN // Step 3: Finally fall back to loading icon locally in the client side - if (!icon) { - if (format == "SVG") { - loadSvgIcon(params); - } else if (format == "PIC") { - loadPictureIcon(params); - } else if (format == "MNG" || format == "GIF") { - loadAnimatedIcon(params, format); - } else { - loadPixmapIcon(params, format); - } + if (callback) { + loadLocalAsync(params, format, callback, callbackParam); + return 0; } + loadLocal(params, format); } + icon = finishLocal(params); + if (!params.animationCreated && icon && !options.testFlag(DoNotCache)) { + cacheIcon(params, icon, &cacheKey); + } + + if (callback) { + callback(icon, callbackParam, true); + } + +#ifdef HB_ICON_TRACES + qDebug() << "HbIconLoader::loadIcon END"; +#endif + + return icon; +} + +HbIconImpl *HbIconLoader::lookupInCache(const HbIconLoadingParams ¶ms, QByteArray *outCacheKey) +{ + // Stop right away for resolution corrected icons, these may get false cache + // hits. The use of such icons should be very rare anyway. + if (params.options.testFlag(ResolutionCorrected)) { + return 0; + } + + QByteArray cacheKey = d->createCacheKeyFrom(params.iconName, + params.size, + params.aspectRatioMode, + params.mode, + params.mirrored, + params.color); + if (outCacheKey) { + *outCacheKey = cacheKey; + } + if (d->iconImplCache.contains(cacheKey)) { + HbIconImpl *icon = d->iconImplCache.value(cacheKey); + icon->incrementRefCount(); + d->cacheKeeper.unref(icon); +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader::lookupInCache: Cache hit for" << params.iconName << params.size + << "Client refcount now" << icon->refCount(); +#endif + return icon; + } + return 0; +} + +void HbIconLoader::loadLocal(HbIconLoadingParams ¶ms, const QString &format) +{ + QMutexLocker loadLocker(&d->mLocalLoadMutex); + QMutexLocker iconSourceLocker(&d->mIconSourceMutex); + if (format == "SVG") { + loadSvgIcon(params); + } else if(format == "NVG") { + //support for client side rendering of nvg icons + loadNvgIcon(params); + } else if (format == "PIC") { + loadPictureIcon(params); + } else if (format == "MNG" || format == "GIF") { + loadAnimatedIcon(params, format); + } else { + loadPixmapIcon(params, format); + } +} + +void HbIconLoader::loadLocalAsync(const HbIconLoadingParams ¶ms, const QString &format, + HbAsyncIconLoaderCallback callback, void *callbackParam) +{ + HbIconLoaderPrivate::AsyncParams *p = new HbIconLoaderPrivate::AsyncParams; + p->mCallback = callback; + p->mLdParams = params; + p->mParam = callbackParam; + d->mActiveAsyncRequests.append(p); + QMetaObject::invokeMethod(d->mLocalLoader, "load", + Qt::QueuedConnection, + Q_ARG(HbIconLoadingParams, params), + Q_ARG(QString, format), + Q_ARG(void *, p)); +} + +HbIconImpl *HbIconLoader::finishLocal(HbIconLoadingParams ¶ms) +{ + // This is called on the main thread so QPixmap can be used. + QPixmap pm = QPixmap::fromImage(params.image); + if (!params.mirroringHandled) { // Apply mirroring if required if (params.mirrored && !params.mirroredIconFound) { QTransform t; t.scale(-1, 1); - params.canvasPixmap = params.canvasPixmap.transformed(t); + pm = pm.transformed(t); + } + } + + if (params.color.isValid()) { + if (!pm.isNull()) { + QPixmap mask = pm.alphaChannel(); + pm.fill(params.color); + pm.setAlphaChannel(mask); } } if (!params.modeHandled) { // Apply mode - if (mode != QIcon::Normal) { + if (params.mode != QIcon::Normal) { QStyleOption opt(0); opt.palette = QApplication::palette(); - params.canvasPixmap = QApplication::style()->generatedIconPixmap(mode, params.canvasPixmap, &opt); + pm = QApplication::style()->generatedIconPixmap(params.mode, pm, &opt); } } - if ((params.color.isValid()) && (params.mode != QIcon::Disabled)) { - QPixmap &pm = params.canvasPixmap; - if (!pm.isNull()) { - QPixmap mask = pm.alphaChannel(); - pm.fill(color); - pm.setAlphaChannel(mask); + return HbIconImplCreator::createIconImpl(pm, params); +} + +void HbIconLoader::cacheIcon(const HbIconLoadingParams ¶ms, HbIconImpl *icon, QByteArray *existingCacheKey) +{ + QByteArray cacheKey; + if (existingCacheKey) { + cacheKey = *existingCacheKey; + } else { + cacheKey = d->createCacheKeyFrom(params.iconName, + params.size, + params.aspectRatioMode, + params.mode, + params.mirrored, + params.color); + } + d->addItemToCache(cacheKey, icon); + +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader:cacheIcon: " << params.iconName + << "inserted into local cache, client refcount now" << icon->refCount(); +#endif +} + +/*! + Cancels an outstanding loadIcon request. + + \a callback must match the callback parameter of a previous loadIcon call. + All loadIcon requests using the same callback will be canceled and the + callback will not be invoked. + + Has no effect if no matching loadIcon request is active. + + If \a callbackParam is not 0 then it is used in the matching too so only + loadIcon requests where both \a callback and \a callbackParam match will be + canceled. If \a callbackParam is 0 then only \a callback is used in the + matching. + */ +void HbIconLoader::cancelLoadIcon(HbAsyncIconLoaderCallback callback, void *callbackParam) +{ + // The callback used with the themeclient api is always the same, but the + // AsyncParams pointer in the custom parameter is different for each request + // so it can be used for identification. + foreach (HbIconLoaderPrivate::AsyncParams *p, d->mActiveAsyncRequests) { + if (p->mCallback == callback && (!callbackParam || callbackParam == p->mParam)) { + // Cancel the remote request. It will have no effect in case of + // local content. This is fine because for the local threaded loader + // the removal from mActiveAsyncRequests is enough. + HbThemeClient::global()->cancelGetSharedIconInfo(asyncCallback, p); + d->mActiveAsyncRequests.removeOne(p); + delete p; + return; } } - -#ifdef HB_ICON_TRACES - qDebug() << "HbIconLoader::loadIcon END"; -#endif - - icon = new HbPixmapIconImpl(params.canvasPixmap, params.iconFileName); - return icon; } /*! @@ -1468,6 +1907,8 @@ const QColor &color) { Q_UNUSED(color); + Q_UNUSED(multiPieceImpls); + HbIconImpl *icon = 0; if (listOfIcons.count() == 0) { return icon; @@ -1482,26 +1923,24 @@ // We don't want to get the consolidated icon for only NVG build, ie. without SGImage lite support. // Consolidated icon will be created for NVG with SGImage lite support. // and when NVG is not available. -#if defined(HB_ICONIMPL_CACHE) - - QByteArray cacheKey = d->createCacheKeyFrom(multiPartIconData.multiPartIconId, - size, - aspectRatioMode, - mode, - mirrored, - color, - renderMode); + QByteArray cacheKey = d->createCacheKeyFrom( + multiPartIconData.multiPartIconId, + size, + aspectRatioMode, + mode, + mirrored, + color); //If consolidated icon found in the client's cache, increment ref-count and return - if (iconImplCache.contains(cacheKey)) { - HbIconImpl *ptr = iconImplCache.value(cacheKey); + if (d->iconImplCache.contains(cacheKey)) { + HbIconImpl *ptr = d->iconImplCache.value(cacheKey); ptr->incrementRefCount(); + d->cacheKeeper.unref(ptr); #ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::loadMultiPieceIcon()" << "Cache hit in iconImplCache " << multiPartIconData.multiPartIconId << size.height() << "X" << size.width() ; - qDebug() << "HbIconLoader::loadMultiPieceIcon : Client RefCount now = " << ptr->refCount(); + qDebug() << "HbIconLoader::loadMultiPieceIcon: Cache hit" << multiPartIconData.multiPartIconId + << size << "Client refcount now" << ptr->refCount(); #endif return ptr; } -#endif QStringList iconPathList; @@ -1544,65 +1983,178 @@ params.mirrored = mirrored; params.mirroredIconFound = mirroredIconFound; - //Creating HbIconImpl for the consolidated icon-data returned from themeserver + // Creating HbIconImpl for the consolidated icon-data returned from themeserver. icon = HbIconImplCreator::createIconImpl(iconInfo, params); if (icon) { -#ifdef HB_ICONIMPL_CACHE - iconImplCache.insert(cacheKey, icon); -#ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::loadMultiPieceIcon(): " << params.iconName << " inserted into impl-cache, ref-count now = " << icon->refCount(); -#endif - -#endif icon->setMultiPieceIcon(); } - return icon; } else { - //themeserver wasn't successful in stitching of consolidated icon - multiPieceImpls.clear(); - int count = iconPathList.count(); - QVector sizeList; - for (int i = 0; i < count; i++) { - sizeList << multiPartIconData.pixmapSizes[i]; + //Consolidated (stitched) icon could not be loaded on themeserver side, taking + //fallback path for creating the consolidated icon on the client side. + icon = createLocalConsolidatedIcon(multiPartIconData, + iconPathList, + size, + aspectRatioMode, + mode, + options, + color); + } + + if (icon) { + // Not yet in local cache (was checked before the server request) so insert. + d->addItemToCache(cacheKey, icon); +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader::loadMultiPieceIcon: " << multiPartIconData.multiPartIconId + << " inserted into local cache, client refcount now" << icon->refCount(); +#endif + } + return icon; +} + +HbIconImpl * HbIconLoader::createLocalConsolidatedIcon(const HbMultiPartSizeData &multiPartIconData, + const QStringList & iconPathList, + const QSizeF &consolidatedSize, + Qt::AspectRatioMode aspectRatioMode, + QIcon::Mode mode, + const IconLoaderOptions & options, + const QColor &color) +{ + // load the icons in to QImage + HbIconLoadingParams params; + params.purpose = HbIconLoader::AnyPurpose; + params.aspectRatioMode = aspectRatioMode; + params.mode = mode; + params.color = color; + params.animator = 0; + params.mirrored = options.testFlag(HorizontallyMirrored); + params.mirroredIconFound = false; + params.mirroringHandled = false; + params.modeHandled = false; + params.renderMode = renderMode; + params.canCache = false; + params.animationCreated = false; + + QStringList::const_iterator iterFiles = iconPathList.begin(); + QStringList::const_iterator iterFilesEnd = iconPathList.end(); + + QImage finalImage(consolidatedSize.toSize(), QImage::Format_ARGB32_Premultiplied); + finalImage.fill(QColor(Qt::transparent).rgba()); + QPainter painter(&finalImage); + + for (int i=0; iterFiles != iterFilesEnd; ++iterFiles, ++i) { + + // Do not paint if image target rect is null + if (!multiPartIconData.targets[i].isNull()) { + + // Populate icon loading parameters + params.iconFileName = *iterFiles; + params.size = multiPartIconData.pixmapSizes[i]; + params.options = options; + params.isDefaultSize = params.size.isNull(); + + QString format = formatFromPath(params.iconFileName); + loadLocal(params, format); + painter.drawImage(multiPartIconData.targets[i].topLeft(), + params.image); + params.image = QImage(); + } -#ifdef Q_OS_SYMBIAN - //Since the consolidated icon-creation failed on themeserver, request loading of individual - //frame-items in a single IPC request to themeserver - getMultiIconImplFromServer(iconPathList, sizeList, - aspectRatioMode, - mode, - mirrored, - mirroredIconFound, - options, - color, - HbIconLoader::AnyType, - HbIconLoader::AnyPurpose, - multiPieceImpls, - renderMode); -#else - //For OS other than Symbian, call HbIconLoader::loadIcon to individually load icons - for (int i = 0; i < count; i++) { - HbIconImpl *impl = loadIcon(iconPathList[i], HbIconLoader::AnyType, - HbIconLoader::AnyPurpose, - sizeList.at(i), - Qt::IgnoreAspectRatio, - QIcon::Normal, - (options | DoNotCache)); - impl->setMultiPieceIcon(); - if (impl) { - multiPieceImpls.append(impl); + } + painter.end(); + + params.image = finalImage; + + return finishLocal(params); +} + +inline int iconImplConsumption(HbIconImpl *icon) +{ + QSize sz = icon->keySize().toSize(); + return sz.width() * sz.height() * 4; +} + +void HbIconLoaderPrivate::CacheKeeper::ref(HbIconImpl *icon) +{ + int consumption = iconImplConsumption(icon); + // Never hold something more than once and do not ref anything when icons + // are unloaded immediately when becoming hidden. Ignore also icons that are + // too large and those which had not been inserted into iconImplCache + // (e.g. icons that were created with the DoNotCache option). + if (!mIcons.contains(icon) + && !HbInstancePrivate::d_ptr()->mDropHiddenIconData + && consumption < MAX_KEEPALIVE_ITEM_SIZE_BYTES + && !mIconLoaderPrivate->iconImplCache.key(icon).isEmpty()) + { + icon->incrementRefCount(); + mIcons.append(icon); + mConsumption += consumption; +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "CacheKeeper::ref: Accepted" << icon->iconFileName() + << icon->keySize() << consumption + << "Total consumption now" << mConsumption; +#endif + // Now do some housekeeping. + while (mConsumption > MAX_KEEPALIVE_CACHE_SIZE_BYTES) { + HbIconImpl *oldest = mIcons.first(); + unref(oldest); + if (oldest->refCount() == 0 && oldest != icon) { + del(oldest, true); } } - -#endif - - return icon; } } -// Initiates an IPC call to the ThemeServer to unload ( decrement ref count ) the icon -void HbIconLoader::unLoadIcon(HbIconImpl *icon, bool unloadedByServer) +void HbIconLoaderPrivate::CacheKeeper::unref(HbIconImpl *icon) +{ + if (mIcons.contains(icon)) { +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "CacheKeeper::unref: Releasing" << icon->iconFileName() << icon->keySize(); +#endif + mIcons.removeOne(icon); + mConsumption -= iconImplConsumption(icon); + icon->decrementRefCount(); + } +} + +void HbIconLoaderPrivate::CacheKeeper::clear() +{ + // Get rid of all unused icons in the iconimplcache, regardless of + // the icons' rendering mode. Note that the list may contain non-sgimage + // iconimpls too that are created on the server, and unlike sgimage the + // unload request for these can never be skipped. + QVector unloadList; + foreach (HbIconImpl *icon, mIcons) { + icon->decrementRefCount(); + if (icon->refCount() == 0) { + if (icon->isCreatedOnServer()) { + unloadList.append(iconImplToReqInfo(icon)); + } + del(icon, false); // no unload request, do it at once at the end + } + } + mConsumption = 0; + mIcons.clear(); + // Now do batch unloading, to reduce IPC. + if (!unloadList.isEmpty()) { + HbThemeClient::global()->batchUnloadIcon(unloadList); + } +} + +void HbIconLoaderPrivate::CacheKeeper::del(HbIconImpl *icon, bool sendUnloadReq) +{ + HbIconLoader::global()->removeItemInCache(icon); + if (sendUnloadReq && icon->isCreatedOnServer()) { + HbThemeClient::global()->unloadIcon(iconImplToReqInfo(icon)); + } + icon->dispose(); +} + +// Initiates an IPC call to the ThemeServer to unload (or at least decrement the +// server-side refcount of) the icon, unless the icon unload was initiated by +// the server itself. Also, if the local refcount reaches zero, the icon is +// removed from the local cache. +void HbIconLoader::unLoadIcon(HbIconImpl *icon, bool unloadedByServer, bool noKeep) { if (!icon) { return; @@ -1610,26 +2162,22 @@ icon->decrementRefCount(); - if (icon->refCount() == 0 && icon->isCreatedOnServer()) { + if (icon->refCount() == 0) { if (!unloadedByServer) { - HbThemeClient::global()->unloadIcon(icon->iconFileName(), - icon->keySize(), - icon->iconAspectRatioMode(), - icon->iconMode(), - icon->isMirrored(), - icon->color(), - icon->iconRenderingMode() - ); + // Offer the icon to the cacheKeeper first. + if (!noKeep) { + d->cacheKeeper.ref(icon); + } + // If it was accepted then the refcount was increased so stop here. + if (icon->refCount() > 0) { + return; + } + // Otherwise continue with unloading. + if (icon->isCreatedOnServer()) { + HbThemeClient::global()->unloadIcon(iconImplToReqInfo(icon)); + } } -#ifdef HB_ICONIMPL_CACHE - int rem = iconImplCache.remove(iconImplCache.key(icon)); - if (rem > 0) { -#ifdef HB_ICON_TRACES - qDebug() << "Removed from HbIconImpl Cache " << rem << icon->iconFileName() << icon->keySize().height() << "X" << icon->keySize().width() ; -#endif - } -#endif - + removeItemInCache(icon); } } @@ -1652,131 +2200,6 @@ } /*! - * \fn void HbIconLoader::getMultiIconImplFromServer() - * - * This function is responsible for loading individual pieces of a multi-piece icon. - * This gets called if the consolidated icon-creation process on themeserver has failed. - * This function initiates a single IPC to themeserver in which it sends out icon-parameters - * for each of the frame-items and gets back a list of HbSharedIconInfo corresponding to - * individual pieces. - * - */ -void HbIconLoader::getMultiIconImplFromServer(QStringList &multiPartIconList, - QVector &sizeList, - Qt::AspectRatioMode aspectRatioMode, - QIcon::Mode mode, - bool mirrored, - bool mirroredIconFound, - HbIconLoader::IconLoaderOptions options, - const QColor &color, - HbIconLoader::IconDataType type, - HbIconLoader::Purpose, - QVector & iconImplList, - HbRenderingMode currRenderMode) -{ - Q_UNUSED(type); - QVector posList; -#ifdef HB_ICONIMPL_CACHE - // search the client cache first before asking the server - for (int i = 0; i < multiPartIconList.count(); i++) { - QByteArray cacheKey = d->createCacheKeyFrom(multiPartIconList[i], - sizeList[i], - aspectRatioMode, - mode, - mirrored, - color, - currRenderMode); - //look up in the local iconImplCache. - //If found return the ptr directly - HbIconImpl *ptr = 0; - if (iconImplCache.contains(cacheKey)) { - ptr = iconImplCache.value(cacheKey); - // if a specific frame-item is found in local impl-cache, - // increment the ref count and remove the entry from the list that needs to be sent to server. - ptr->incrementRefCount(); -#ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::getMultiIconImplFromServer()" << "Cache hit in iconImplCache "; - qDebug() << "HbIconLoader::getMultiIconImplFromServer : Client RefCount now = " << ptr->refCount(); -#endif - iconImplList.append(ptr); - multiPartIconList.replace(i, QString("")); - } else { - posList << i; - } - } - for (int i = 0; i < multiPartIconList.count(); i++) { - - if (multiPartIconList[i].isEmpty()) { - multiPartIconList.removeAt(i); - sizeList.remove(i); - i--; - } - } -#endif - // If client-side cache is not enabled, ask server for all the pieces' information - int count = multiPartIconList.count(); - if (count > 0) { - HbSharedIconInfoList iconInfoList = HbThemeClient::global()->getMultiIconInfo(multiPartIconList, sizeList, - aspectRatioMode, mode, mirrored, options, color, currRenderMode); - - HbIconImpl *impl = 0; - - HbIconLoadingParams params; - - params.aspectRatioMode = aspectRatioMode; - params.mode = mode; - params.mirrored = mirrored; - params.mirroredIconFound = mirroredIconFound; - - - for (int i = 0; i < count; i++) { - if (iconInfoList.icon[i].type != INVALID_FORMAT) { - params.iconFileName = multiPartIconList[i]; - params.size = sizeList.at(i); - - impl = HbIconImplCreator::createIconImpl(iconInfoList.icon[i], params); - -#ifdef HB_ICONIMPL_CACHE - QByteArray cacheKey = d->createCacheKeyFrom(multiPartIconList[i], - sizeList.at(i) , - aspectRatioMode, - mode, - mirrored, - color, - currRenderMode); - iconImplCache.insert(cacheKey, impl); -#ifdef HB_ICON_CACHE_DEBUG - qDebug() << "HbIconLoader::getMultiIconImplFromServer(): " << params.iconName << " inserted into impl-cache, ref-count now = " << impl->refCount(); -#endif - -#endif - } else { - //If for some reason individual frame-item's loading in themeserver fails, use HbIconLoader::loadIcon() - // as a fallback option to load it. - impl = loadIcon(multiPartIconList[i], HbIconLoader::AnyType, - HbIconLoader::AnyPurpose, - sizeList.at(i), - Qt::IgnoreAspectRatio, - QIcon::Normal, - (options | DoNotCache)); - } - - if (impl) { - impl->setMultiPieceIcon(); -#ifdef HB_ICONIMPL_CACHE - if (posList.count() > 0) { - iconImplList.insert(posList.front(), impl); - posList.pop_front(); - } -#else - iconImplList.append(impl); -#endif - } - } - } -} - -/*! * HbIconLoader::unLoadMultiIcon * * This function initiates a single IPC to unload each of the frame-items in a multi-piece icon. @@ -1790,18 +2213,18 @@ // then send to server for unload. foreach(HbIconImpl * impl, multiPieceImpls) { impl->decrementRefCount(); - if (impl->refCount() == 0 && impl->isCreatedOnServer()) { -#ifdef HB_ICONIMPL_CACHE - int rem = iconImplCache.remove(iconImplCache.key(impl)); - if (rem > 0) { -#ifdef HB_ICON_TRACES - qDebug() << "HbIconLoader::unLoadMultiIcon :Removed from HbIconImpl Cache " << rem << impl->iconFileName() << impl->keySize().height() << "X" << impl->keySize().width() ; + if (impl->refCount() == 0) { + if (d->iconImplCache.remove(d->iconImplCache.key(impl)) > 0) { +#ifdef HB_ICON_CACHE_DEBUG + qDebug() << "HbIconLoader::unLoadMultiIcon: Removed from cache" + << impl->iconFileName() << impl->keySize(); #endif } -#endif - // List of icons to be unloaded. - iconNameList << impl->iconFileName(); - sizeList << impl->keySize(); + if (impl->isCreatedOnServer()) { + // List of icons to be unloaded. + iconNameList << impl->iconFileName(); + sizeList << impl->keySize(); + } } } @@ -1819,20 +2242,64 @@ bool HbIconLoader::isInPrivateDirectory(const QString &filename) { - Q_UNUSED(filename); bool isPrivate = false; - + #ifdef Q_OS_SYMBIAN if (filename.length() > 11) { // Private dir starts with e.g. "z:/private/" if (filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\') && - (filename[10] == '/' || filename[10] == '\\') && filename.mid(3, 7).compare("private"), Qt::CaseInsensitive) { + (filename[10] == '/' || filename[10] == '\\') && filename.mid(3, 7).compare("private"), Qt::CaseInsensitive) { isPrivate = true; } } +#else + Q_UNUSED(filename); #endif return isPrivate; } +void HbIconLoader::localLoadReady(const HbIconLoadingParams &loadParams, void *reqParams) +{ + // This code is running on the main thread. + HbIconLoaderPrivate::AsyncParams *p = static_cast(reqParams); + // Stop right away if canceled. + if (!d->mActiveAsyncRequests.contains(p)) { + return; + } + // Do not use d->mLdParams, it is not up-to-date. + HbIconLoadingParams params = loadParams; + HbIconImpl *icon = finishLocal(params); + if (icon && !params.options.testFlag(DoNotCache)) { + cacheIcon(params, icon, 0); + } + if (p->mCallback) { + p->mCallback(icon, p->mParam, false); + } + d->mActiveAsyncRequests.removeOne(p); + delete p; +} + +void HbLocalIconLoader::load(const HbIconLoadingParams ¶ms, const QString &format, void *reqParams) +{ + // This is running on a separate thread (d->mLocalLoaderThread) and since + // the HbLocalIconLoader instance is moved to that thread, the requests from + // the main thread are delivered using queued connection so slots/signals + // provide easy inter-thread communication in this case. + HbIconLoaderPrivate::AsyncParams *p = static_cast(reqParams); + // Stop right away if canceled. + if (!HbIconLoaderPrivate::global()->mActiveAsyncRequests.contains(p)) { + return; + } + HbIconLoadingParams loadParams = params; + HbIconLoader::global()->loadLocal(loadParams, format); + emit ready(loadParams, reqParams); +} + +void HbLocalIconLoader::doQuit() +{ + QThread::currentThread()->quit(); +} + + // End of File