diff options
author | Jens Bache-Wiig <jbache@trolltech.com> | 2009-08-10 15:15:03 (GMT) |
---|---|---|
committer | Jens Bache-Wiig <jbache@trolltech.com> | 2009-08-10 15:39:20 (GMT) |
commit | 13a31fe82845f8b1f4d86919080d3b2a87c4d061 (patch) | |
tree | 6ec3b5c3bc0811abca5fc84d1aa094be30d4e8c4 /src/gui/image | |
parent | 35ef002453cbe9841e23a7d33064a51dbe99fb06 (diff) | |
download | Qt-13a31fe82845f8b1f4d86919080d3b2a87c4d061.zip Qt-13a31fe82845f8b1f4d86919080d3b2a87c4d061.tar.gz Qt-13a31fe82845f8b1f4d86919080d3b2a87c4d061.tar.bz2 |
Introducing icon theme support
Added some static functions to QIcon to support desktop
themes based on the freedesktop spec. It is not intended to
replace KIcon and the intention is to use it when available to
share icon cache between applications.
Applications currently using icon themes are Assistant, Designer
and the textedit demo.
Reviewed-by: ogoffart
Diffstat (limited to 'src/gui/image')
-rw-r--r-- | src/gui/image/image.pri | 3 | ||||
-rw-r--r-- | src/gui/image/qicon.cpp | 226 | ||||
-rw-r--r-- | src/gui/image/qicon.h | 10 | ||||
-rw-r--r-- | src/gui/image/qicon_p.h | 138 | ||||
-rw-r--r-- | src/gui/image/qiconloader.cpp | 599 | ||||
-rw-r--r-- | src/gui/image/qiconloader_p.h | 185 |
6 files changed, 1095 insertions, 66 deletions
diff --git a/src/gui/image/image.pri b/src/gui/image/image.pri index b9c36dc..baf2125 100644 --- a/src/gui/image/image.pri +++ b/src/gui/image/image.pri @@ -6,6 +6,8 @@ HEADERS += \ image/qbitmap.h \ image/qicon.h \ + image/qicon_p.h \ + image/qiconloader_p.h \ image/qiconengine.h \ image/qiconengineplugin.h \ image/qimage.h \ @@ -32,6 +34,7 @@ HEADERS += \ SOURCES += \ image/qbitmap.cpp \ image/qicon.cpp \ + image/qiconloader.cpp \ image/qimage.cpp \ image/qimageiohandler.cpp \ image/qimagereader.cpp \ diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index fa407c7..7a43514 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -40,9 +40,11 @@ ****************************************************************************/ #include "qicon.h" +#include "qicon_p.h" #include "qiconengine.h" #include "qiconengineplugin.h" #include "private/qfactoryloader_p.h" +#include "private/qiconloader_p.h" #include "qapplication.h" #include "qstyleoption.h" #include "qpainter.h" @@ -50,6 +52,7 @@ #include "qstyle.h" #include "qpixmapcache.h" #include "qvariant.h" +#include "qcache.h" #include "qdebug.h" #ifdef Q_WS_MAC @@ -57,6 +60,11 @@ #include <private/qt_cocoa_helpers_mac_p.h> #endif +#ifdef Q_WS_X11 +#include "private/qt_x11_p.h" +#include "private/qkde_p.h" +#endif + QT_BEGIN_NAMESPACE /*! @@ -94,73 +102,14 @@ QT_BEGIN_NAMESPACE static QBasicAtomicInt serialNumCounter = Q_BASIC_ATOMIC_INITIALIZER(1); -class QIconPrivate -{ -public: - QIconPrivate(): engine(0), ref(1), serialNum(serialNumCounter.fetchAndAddRelaxed(1)), detach_no(0), engine_version(2), v1RefCount(0) {} - - ~QIconPrivate() { - if (engine_version == 1) { - if (!v1RefCount->deref()) { - delete engine; - delete v1RefCount; - } - } else if (engine_version == 2) { - delete engine; - } - } - - QIconEngine *engine; - - QAtomicInt ref; - int serialNum; - int detach_no; - int engine_version; - - QAtomicInt *v1RefCount; -}; - - -struct QPixmapIconEngineEntry +QIconPrivate::QIconPrivate() + : engine(0), ref(1), + serialNum(serialNumCounter.fetchAndAddRelaxed(1)), + detach_no(0), + engine_version(2), + v1RefCount(0) { - QPixmapIconEngineEntry():mode(QIcon::Normal), state(QIcon::Off){} - QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) - :pixmap(pm), size(pm.size()), mode(m), state(s){} - QPixmapIconEngineEntry(const QString &file, const QSize &sz = QSize(), QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) - :fileName(file), size(sz), mode(m), state(s){} - QPixmap pixmap; - QString fileName; - QSize size; - QIcon::Mode mode; - QIcon::State state; - bool isNull() const {return (fileName.isEmpty() && pixmap.isNull()); } -}; - -class QPixmapIconEngine : public QIconEngineV2 { -public: - QPixmapIconEngine(); - QPixmapIconEngine(const QPixmapIconEngine &); - ~QPixmapIconEngine(); - void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); - QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); - QPixmapIconEngineEntry *bestMatch(const QSize &size, QIcon::Mode mode, QIcon::State state, bool sizeOnly); - QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); - void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state); - void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state); - - // v2 functions - QString key() const; - QIconEngineV2 *clone() const; - bool read(QDataStream &in); - bool write(QDataStream &out) const; - void virtual_hook(int id, void *data); - -private: - QPixmapIconEngineEntry *tryMatch(const QSize &size, QIcon::Mode mode, QIcon::State state); - QVector<QPixmapIconEngineEntry> pixmaps; - - friend QDataStream &operator<<(QDataStream &s, const QIcon &icon); -}; +} QPixmapIconEngine::QPixmapIconEngine() { @@ -918,6 +867,146 @@ QList<QSize> QIcon::availableSizes(Mode mode, State state) const return engine->availableSizes(mode, state); } +/*! + \since 4.6 + + Sets the search paths for icon themes. + \sa themeSearchPaths(), fromTheme() +*/ +void QIcon::setThemeSearchPaths(const QStringList &paths) +{ + QIconLoader::instance()->setThemeSearchPath(paths); +} + +/*! + \since 4.6 + + Returns the search paths for icon themes. + + The default value will depend on the platform: + + On X11, the search path will use the XDG_DATA_DIRS environment + variable if available. + + On Windows the search path defaults to [Application Directory]/icons + + On Mac the default search path will search in the + [Contents/Resources/icons] part of the application bundle. + + \sa setThemeSearchPaths(), fromName() +*/ +QStringList QIcon::themeSearchPaths() +{ + return QIconLoader::instance()->themeSearchPaths(); +} + +/*! + \since 4.6 + + Sets the current icon theme. + + The name should correspond to a directory name in the + current \ themeSearchPath() containing an index.theme + file describing it's contents.. + +*/ +void QIcon::setThemeName(const QString &path) +{ + QIconLoader::instance()->setThemeName(path); +} + +/*! + \since 4.6 + + Returns the name of the current icon theme. + + On X11, the current icon theme depends on your desktop + settings. On other platforms it is not set by default. + + \sa themeSearchPaths(), themeIcon(), fromTheme(), hasThemeIcon() +*/ +QString QIcon::themeName() +{ + return QIconLoader::instance()->themeName(); +} + +/*! + \since 4.6 + + Returns the QIcon corresponding to \a name in the current + icon theme. If no such icon is found in the current theme + \a fallback is return instead. + + To use an icon theme on Windows or Mac, you will need to + bundle a compliant theme with your application and make sure + it is located in your themeSarchPaths. + + The lastest version of the freedesktop icon specification and naming + spesification can be obtained here: + http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + + To fetch an icon from the current icon theme: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 3 + + Or if you want to provide a guaranteed fallback for platforms that + do not support theme icons, you can use the second argument: + + \snippet doc/src/snippets/code/src_gui_image_qicon.cpp 4 + + \note By default, only X11 will support themed icons. In order to + use themed icons on Mac and Windows, you will have to bundle a + compliant theme in one of your themeSearchPaths() and set the + appropriate themeName(). + + \sa themeName(), themeSearchPaths() +*/ +QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback) +{ + + static QCache <QString, QIcon> iconCache; + + QIcon icon; + +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_KDE) { + icon = QKde::kdeIcon(name); + if (!icon.isNull()) + return icon; + } +#endif + + if (iconCache.contains(name)) { + icon = *iconCache.object(name); + } else { + QIcon *cachedIcon = new QIcon(new QIconLoaderEngine(name)); + iconCache.insert(name, cachedIcon); + icon = *cachedIcon; + } + + if (icon.availableSizes().isEmpty()) + return fallback; + + return icon; +} + +/*! + \since 4.6 + + Returns true if there is an icon available for a \a name in the current + icon theme, otherwise returns false. + + \sa themeSearchPaths(), fromTheme() +*/ +bool QIcon::hasThemeIcon(const QString &name) +{ + QIcon icon = fromTheme(name); + + return !icon.isNull(); +} + + /***************************************************************************** QIcon stream functions *****************************************************************************/ @@ -989,6 +1078,11 @@ QDataStream &operator>>(QDataStream &s, QIcon &icon) QIconEngineV2 *engine = new QPixmapIconEngine; icon.d->engine = engine; engine->read(s); + } else if (key == QLatin1String("QIconLoaderEngine")) { + icon.d = new QIconPrivate; + QIconEngineV2 *engine = new QIconLoaderEngine(); + icon.d->engine = engine; + engine->read(s); #if !defined (QT_NO_LIBRARY) && !defined(QT_NO_SETTINGS) } else if (QIconEngineFactoryInterfaceV2 *factory = qobject_cast<QIconEngineFactoryInterfaceV2*>(loaderV2()->instance(key))) { if (QIconEngineV2 *engine= factory->create()) { diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h index c318e14..2a71717 100644 --- a/src/gui/image/qicon.h +++ b/src/gui/image/qicon.h @@ -97,6 +97,16 @@ public: QList<QSize> availableSizes(Mode mode = Normal, State state = Off) const; + static QIcon fromTheme(const QString &name, const QIcon &fallback = QIcon()); + static bool hasThemeIcon(const QString &name); + + static QStringList themeSearchPaths(); + static void setThemeSearchPaths(const QStringList &searchpath); + + static QString themeName(); + static void setThemeName(const QString &path); + + #ifdef QT3_SUPPORT enum Size { Small, Large, Automatic = Small }; static QT3_SUPPORT void setPixmapSize(Size which, const QSize &size); diff --git a/src/gui/image/qicon_p.h b/src/gui/image/qicon_p.h new file mode 100644 index 0000000..ccac4c3 --- /dev/null +++ b/src/gui/image/qicon_p.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QICON_P_H +#define QICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> +#include <QtCore/qsize.h> +#include <QtCore/qlist.h> +#include <QtGui/qpixmap.h> +#include <QtGui/qicon.h> +#include <QtGui/qiconengine.h> + +QT_BEGIN_NAMESPACE + +class QIconPrivate +{ +public: + QIconPrivate(); + + ~QIconPrivate() { + if (engine_version == 1) { + if (!v1RefCount->deref()) { + delete engine; + delete v1RefCount; + } + } else if (engine_version == 2) { + delete engine; + } + } + + QIconEngine *engine; + + QAtomicInt ref; + int serialNum; + int detach_no; + int engine_version; + + QAtomicInt *v1RefCount; +}; + + +struct QPixmapIconEngineEntry +{ + QPixmapIconEngineEntry():mode(QIcon::Normal), state(QIcon::Off){} + QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) + :pixmap(pm), size(pm.size()), mode(m), state(s){} + QPixmapIconEngineEntry(const QString &file, const QSize &sz = QSize(), QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off) + :fileName(file), size(sz), mode(m), state(s){} + QPixmap pixmap; + QString fileName; + QSize size; + QIcon::Mode mode; + QIcon::State state; + bool isNull() const {return (fileName.isEmpty() && pixmap.isNull()); } +}; + + + +class QPixmapIconEngine : public QIconEngineV2 { +public: + QPixmapIconEngine(); + QPixmapIconEngine(const QPixmapIconEngine &); + ~QPixmapIconEngine(); + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QPixmapIconEngineEntry *bestMatch(const QSize &size, QIcon::Mode mode, QIcon::State state, bool sizeOnly); + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state); + void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state); + + // v2 functions + QString key() const; + QIconEngineV2 *clone() const; + bool read(QDataStream &in); + bool write(QDataStream &out) const; + void virtual_hook(int id, void *data); + +private: + QPixmapIconEngineEntry *tryMatch(const QSize &size, QIcon::Mode mode, QIcon::State state); + QVector<QPixmapIconEngineEntry> pixmaps; + + friend QDataStream &operator<<(QDataStream &s, const QIcon &icon); + friend class QIconThemeEngine; +}; + +QT_END_NAMESPACE + +#endif // QICON_P_H diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp new file mode 100644 index 0000000..6bf8d3b --- /dev/null +++ b/src/gui/image/qiconloader.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://www.qtsoftware.com/contact. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <private/qiconloader_p.h> + +#include <private/qapplication_p.h> +#include <private/qicon_p.h> +#include <private/qkde_p.h> + +#include <QtGui/QIconEnginePlugin> +#include <QtGui/QPixmapCache> +#include <QtGui/QIconEngine> +#include <QtGui/QStyleOption> +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QDir> +#include <QtCore/QSettings> + +#ifdef Q_WS_MAC +#include <private/qt_cocoa_helpers_mac_p.h> +#endif + +#ifdef Q_WS_X11 +#include <private/qt_x11_p.h> +#include <private/gtksymbols_p.h> +#endif + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance) + +static QString systemThemeName() +{ + QString result; +#ifdef Q_WS_X11 + if (X11->desktopEnvironment == DE_GNOME) { + result = QGtk::getGConfString(QLatin1String("/desktop/gnome/interface/icon_theme"), + QLatin1String("gnome")); + } else if (X11->desktopEnvironment == DE_KDE) { + QString kdeDefault = X11->desktopVersion >= 4 ? + QString::fromLatin1("oxygen") : + QString::fromLatin1("crystalsvg"); + + QSettings settings(QKde::kdeHome() + + QLatin1String("/share/config/kdeglobals"), + QSettings::IniFormat); + + settings.beginGroup(QLatin1String("Icons")); + + result = settings.value(QLatin1String("Theme"), kdeDefault).toString(); + } +#endif + return result; +} + +static QString fallbackTheme() +{ + QString defaultTheme = systemThemeName(); + if (defaultTheme.isEmpty()) + defaultTheme = QLatin1String("hicolor"); + return defaultTheme; +} + +QIconLoader::QIconLoader() : + m_themeKey(1), m_supportsSvg(false) +{ + m_systemTheme = systemThemeName(); + + QFactoryLoader iconFactoryLoader(QIconEngineFactoryInterfaceV2_iid, + QLatin1String("/iconengines"), + Qt::CaseInsensitive); + if (iconFactoryLoader.keys().contains(QLatin1String("svg"))) + m_supportsSvg = true; +} + +QIconLoader *QIconLoader::instance() +{ + return iconLoaderInstance(); +} + +// Queries the system theme and invalidates existing +// icons if the theme has changed. +void QIconLoader::updateSystemTheme() +{ + // Only change if this is not explicitly set by the user + if (m_userTheme.isEmpty()) { + QString theme = systemThemeName(); + if (theme != m_systemTheme) { + m_systemTheme = theme; + invalidateKey(); + } + } +} + +void QIconLoader::setThemeName(const QString &themeName) +{ + m_userTheme = themeName; + invalidateKey(); +} + +void QIconLoader::setThemeSearchPath(const QStringList &searchPaths) +{ + m_iconDirs = searchPaths; + themeList.clear(); + invalidateKey(); +} + +QStringList QIconLoader::themeSearchPaths() const +{ + if (m_iconDirs.isEmpty()) { + +#if defined(Q_WS_X11) + + QString xdgDirString = QFile::decodeName(getenv("XDG_DATA_DIRS")); + if (xdgDirString.isEmpty()) + xdgDirString = QLatin1String("/usr/local/share/:/usr/share/"); + + QStringList xdgDirs = xdgDirString.split(QLatin1Char(':')); + + for (int i = 0 ; i < xdgDirs.size() ; ++i) { + QDir dir(xdgDirs[i]); + if (dir.exists()) + m_iconDirs.append(dir.path() + + QLatin1String("/icons")); + } + + if (X11->desktopEnvironment == DE_KDE) { + + m_iconDirs << QLatin1Char(':') + + QKde::kdeHome() + + QLatin1String("/share/icons"); + QStringList kdeDirs = + QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':')); + + for (int i = 0 ; i< kdeDirs.count() ; ++i) { + QDir dir(QLatin1Char(':') + kdeDirs.at(i) + + QLatin1String("/share/icons")); + if (dir.exists()) + m_iconDirs.append(dir.path()); + } + } + + // Add home directory first in search path + QDir homeDir(QDir::homePath() + QLatin1String("/.icons")); + if (homeDir.exists()) + m_iconDirs.prepend(homeDir.path()); + +#elif defined(Q_WS_WIN) + m_iconDirs.append(qApp->applicationDirPath() + + QLatin1String("/icons")); +#elif defined(Q_WS_MAC) + m_iconDirs.append(qApp->applicationDirPath() + + QLatin1String("/../Resources/icons")); +#endif + } + return m_iconDirs; +} + +QIconTheme::QIconTheme(const QString &themeName) + : m_valid(false) +{ + QFile themeIndex; + + QList <QIconDirInfo> keyList; + QStringList iconDirs = QIcon::themeSearchPaths(); + for ( int i = 0 ; i < iconDirs.size() ; ++i) { + QDir iconDir(iconDirs[i]); + QString themeDir = iconDir.path() + QLatin1Char('/') + themeName; + themeIndex.setFileName(themeDir + QLatin1String("/index.theme")); + if (themeIndex.exists()) { + m_contentDir = themeDir; + m_valid = true; + break; + } + } + + if (themeIndex.exists()) { + const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat); + QStringListIterator keyIterator(indexReader.allKeys()); + while (keyIterator.hasNext()) { + + const QString key = keyIterator.next(); + if (key.endsWith(QLatin1String("/Size"))) { + // Note the QSettings ini-format does not accept + // slashes in key names, hence we have to cheat + if (int size = indexReader.value(key).toInt()) { + QString directoryKey = key.left(key.size() - 5); + QIconDirInfo dirInfo(directoryKey); + dirInfo.size = size; + QString type = indexReader.value(directoryKey + + QLatin1String("/Type") + ).toString(); + + if (type == QLatin1String("Fixed")) + dirInfo.type = QIconDirInfo::Fixed; + else if (type == QLatin1String("Scalable")) + dirInfo.type = QIconDirInfo::Scalable; + else + dirInfo.type = QIconDirInfo::Threshold; + + dirInfo.threshold = indexReader.value(directoryKey + + QLatin1String("/Threshold"), + 2).toInt(); + + dirInfo.minSize = indexReader.value(directoryKey + + QLatin1String("/MinSize"), + size).toInt(); + + dirInfo.maxSize = indexReader.value(directoryKey + + QLatin1String("/MaxSize"), + size).toInt(); + m_keyList.append(dirInfo); + } + } + } + + // Parent themes provide fallbacks for missing icons + m_parents = indexReader.value( + QLatin1String("Icon Theme/Inherits")).toStringList(); + + // Ensure a default platform fallback for all themes + if (m_parents.isEmpty()) + m_parents.append(fallbackTheme()); + + // Ensure that all themes fall back to hicolor + if (!m_parents.isEmpty()) + m_parents.append(QLatin1String("hicolor")); + } +} + +QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const +{ + QThemeIconEntries entries; + Q_ASSERT(!themeName.isEmpty()); + + QPixmap pixmap; + + // Used to protect against potential recursions + visited << themeName; + + QIconTheme theme = themeList.value(themeName); + if (!theme.isValid()) { + theme = QIconTheme(themeName); + if (!theme.isValid()) + theme = fallbackTheme(); + + themeList.insert(themeName, theme); + } + + QString contentDir = theme.contentDir() + QLatin1Char('/'); + QList<QIconDirInfo> subDirs = theme.keyList(); + + const QString svgext(QLatin1String(".svg")); + const QString pngext(QLatin1String(".png")); + + // Add all relevant files + for (int i = 0; i < subDirs.size() ; ++i) { + const QIconDirInfo &dirInfo = subDirs.at(i); + QString subdir = dirInfo.path; + QDir currentDir(contentDir + subdir); + + if (dirInfo.type == QIconDirInfo::Scalable && m_supportsSvg && + currentDir.exists(iconName + svgext)) { + ScalableEntry *iconEntry = new ScalableEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + svgext); + entries.append(iconEntry); + + } else if (currentDir.exists(iconName + pngext)) { + PixmapEntry *iconEntry = new PixmapEntry; + iconEntry->dir = dirInfo; + iconEntry->filename = currentDir.filePath(iconName + pngext); + // Notice we ensure that pixmap entries allways come before + // scalable to preserve search order afterwards + entries.prepend(iconEntry); + } + } + + if (entries.isEmpty()) { + const QStringList parents = theme.parents(); + // Search recursively through inherited themes + for (int i = 0 ; i < parents.size() ; ++i) { + + const QString parentTheme = parents.at(i).trimmed(); + + if (!visited.contains(parentTheme)) // guard against recursion + entries = findIconHelper(parentTheme, iconName, visited); + + if (!entries.isEmpty()) // success + break; + } + } + return entries; +} + +QThemeIconEntries QIconLoader::loadIcon(const QString &name) const +{ + if (!themeName().isEmpty()) { + QStringList visited; + return findIconHelper(themeName(), name, visited); + } + + return QThemeIconEntries(); +} + + +// -------- Icon Loader Engine -------- // + + +QIconLoaderEngine::QIconLoaderEngine(const QString& iconName) + : m_iconName(iconName), m_key(0) +{ +} + +QIconLoaderEngine::~QIconLoaderEngine() +{ + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + Q_ASSERT(m_entries.size() == 0); +} + +QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other) + : QIconEngineV2(other), + m_iconName(other.m_iconName), + m_key(0) +{ +} + +QIconEngineV2 *QIconLoaderEngine::clone() const +{ + return new QIconLoaderEngine(*this); +} + +bool QIconLoaderEngine::read(QDataStream &in) { + in >> m_iconName; + return true; +} + +bool QIconLoaderEngine::write(QDataStream &out) const +{ + out << m_iconName; + return true; +} + +bool QIconLoaderEngine::hasIcon() const +{ + return !(m_entries.isEmpty()); +} + +// Lazily load the icon +void QIconLoaderEngine::ensureLoaded() +{ + if (!(iconLoaderInstance()->themeKey() == m_key)) { + + while (!m_entries.isEmpty()) + delete m_entries.takeLast(); + + Q_ASSERT(m_entries.size() == 0); + m_entries = iconLoaderInstance()->loadIcon(m_iconName); + m_key = iconLoaderInstance()->themeKey(); + } +} + +void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect, + QIcon::Mode mode, QIcon::State state) +{ + QSize pixmapSize = rect.size(); +#if defined(Q_WS_MAC) + pixmapSize *= qt_mac_get_scalefactor(); +#endif + painter->drawPixmap(rect, pixmap(pixmapSize, mode, state)); +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return dir.size == iconsize; + + } else if (dir.type == QIconDirInfo::Scalable) { + return dir.size <= dir.maxSize && + iconsize >= dir.minSize; + + } else if (dir.type == QIconDirInfo::Threshold) { + return iconsize >= dir.size - dir.threshold && + iconsize <= dir.size + dir.threshold; + } + + Q_ASSERT(1); // Not a valid value + return false; +} + +/* + * This algorithm is defined by the freedesktop spec: + * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + */ +static int directorySizeDistance(const QIconDirInfo &dir, int iconsize) +{ + if (dir.type == QIconDirInfo::Fixed) { + return qAbs(dir.size - iconsize); + + } else if (dir.type == QIconDirInfo::Scalable) { + if (iconsize < dir.minSize) + return dir.minSize - iconsize; + else if (iconsize > dir.maxSize) + return iconsize - dir.maxSize; + else + return 0; + + } else if (dir.type == QIconDirInfo::Threshold) { + if (iconsize < dir.size - dir.threshold) + return dir.minSize - iconsize; + else if (iconsize > dir.size + dir.threshold) + return iconsize - dir.maxSize; + else return 0; + } + + Q_ASSERT(1); // Not a valid value + return INT_MAX; +} + +QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size) +{ + int iconsize = qMin(size.width(), size.height()); + + // Note that m_entries are sorted so that png-files + // come first + + // Search for exact matches first + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + if (directoryMatchesSize(entry->dir, iconsize)) { + return entry; + } + } + + // Find the minimum distance icon + int minimalSize = INT_MAX; + QIconLoaderEngineEntry *closestMatch = 0; + for (int i = 0; i < m_entries.count(); ++i) { + QIconLoaderEngineEntry *entry = m_entries.at(i); + int distance = directorySizeDistance(entry->dir, iconsize); + if (distance < minimalSize) { + minimalSize = distance; + closestMatch = entry; + } + } + return closestMatch; +} + +/* + * Returns the actual icon size. For scalable svg's this is equivalent + * to the requested size. Otherwise the closest match is returned. + * + * todo: the spec is a bit fuzzy in this area, but we should probably + * allow scaling down pixmap icons as well. + * + */ +QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) { + const QIconDirInfo &dir = entry->dir; + if (dir.type == QIconDirInfo::Scalable) + return size; + else + return QSize(dir.size, dir.size); + } + return QIconEngineV2::actualSize(size, mode, state); +} + +QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + // Ensure that basePixmap is lazily initialized before generating the + // key, otherwise the cache key is not unique + if (basePixmap.isNull()) + basePixmap.load(filename); + + int actualSize = qMin(size.width(), size.height()); + QString key = QLatin1String("$qt_theme_") + + QString::number(basePixmap.cacheKey(), 16) + + QLatin1Char('_') + + QString::number(mode) + + QLatin1Char('_') + + QString::number(qApp->palette().cacheKey(), 16) + + QLatin1Char('_') + + QString::number(actualSize); + + QPixmap cachedPixmap; + if (QPixmapCache::find(key, &cachedPixmap)) { + return cachedPixmap; + } else { + QStyleOption opt(0); + opt.palette = qApp->palette(); + cachedPixmap = qApp->style()->generatedIconPixmap(mode, basePixmap, &opt); + QPixmapCache::insert(key, cachedPixmap); + } + return cachedPixmap; +} + +QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + if (svgIcon.isNull()) + svgIcon = QIcon(filename); + + // Simply reuse svg icon engine + return svgIcon.pixmap(size, mode, state); +} + +QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode, + QIcon::State state) +{ + ensureLoaded(); + + QIconLoaderEngineEntry *entry = entryForSize(size); + if (entry) + return entry->pixmap(size, mode, state); + + return QPixmap(); +} + +QString QIconLoaderEngine::key() const +{ + return QLatin1String("QIconLoaderEngine"); +} + +void QIconLoaderEngine::virtual_hook(int id, void *data) +{ + ensureLoaded(); + + switch (id) { + case QIconEngineV2::AvailableSizesHook: + { + QIconEngineV2::AvailableSizesArgument &arg + = *reinterpret_cast<QIconEngineV2::AvailableSizesArgument*>(data); + const QList<QIconDirInfo> directoryKey = iconLoaderInstance()->theme().keyList(); + arg.sizes.clear(); + + // Gets all sizes from the DirectoryInfo entries + for (int i = 0 ; i < m_entries.size() ; ++i) { + int size = m_entries.at(i)->dir.size; + arg.sizes.append(QSize(size, size)); + } + } + break; + default: + QIconEngineV2::virtual_hook(id, data); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h new file mode 100644 index 0000000..707107c --- /dev/null +++ b/src/gui/image/qiconloader_p.h @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDESKTOPICON_P_H +#define QDESKTOPICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/QIcon> +#include <QtGui/QIconEngine> +#include <QtGui/QPixmapCache> +#include <private/qicon_p.h> +#include <private/qfactoryloader_p.h> + +QT_BEGIN_NAMESPACE + +class QIconLoader; + +struct QIconDirInfo +{ + enum Type { Fixed, Scalable, Threshold }; + QIconDirInfo(const QString &_path = QString()) : + path(_path), + size(0), + maxSize(0), + minSize(0), + threshold(0), + type(Threshold) {} + QString path; + short size; + short maxSize; + short minSize; + short threshold; + Type type : 4; +}; + +class QIconLoaderEngineEntry + { +public: + virtual QPixmap pixmap(const QSize &size, + QIcon::Mode mode, + QIcon::State state) = 0; + QString filename; + QIconDirInfo dir; + static int count; +}; + +struct ScalableEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIcon svgIcon; +}; + +struct PixmapEntry : public QIconLoaderEngineEntry +{ + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QPixmap basePixmap; +}; + +typedef QList<QIconLoaderEngineEntry*> QThemeIconEntries; + +class QIconLoaderEngine : public QIconEngineV2 +{ +public: + QIconLoaderEngine(const QString& iconName = QString()); + ~QIconLoaderEngine(); + + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state); + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state); + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state); + QIconEngineV2 *clone() const; + bool read(QDataStream &in); + bool write(QDataStream &out) const; + +private: + QString key() const; + bool hasIcon() const; + void ensureLoaded(); + void virtual_hook(int id, void *data); + QIconLoaderEngineEntry *entryForSize(const QSize &size); + QIconLoaderEngine(const QIconLoaderEngine &other); + QThemeIconEntries m_entries; + QString m_iconName; + uint m_key; + + friend class QIconLoader; +}; + +class QIconTheme +{ +public: + QIconTheme(const QString &name); + QIconTheme() : m_valid(false) {}; + QStringList parents() { return m_parents; } + QList <QIconDirInfo> keyList() { return m_keyList; } + QString contentDir() { return m_contentDir; } + bool isValid() { return m_valid; } + +private: + QString m_contentDir; + QList <QIconDirInfo> m_keyList; + QStringList m_parents; + bool m_valid; +}; + +class QIconLoader : public QObject +{ +public: + QIconLoader(); + QThemeIconEntries loadIcon(const QString &iconName) const; + uint themeKey() const { return m_themeKey; } + + QString themeName() const { return m_userTheme.isEmpty() ? m_systemTheme : m_userTheme; } + void setThemeName(const QString &themeName); + QIconTheme theme() { return themeList.value(themeName()); } + void setThemeSearchPath(const QStringList &searchPaths); + QStringList themeSearchPaths() const; + QIconDirInfo dirInfo(int dirindex); + static QIconLoader *instance(); + void updateSystemTheme(); + void invalidateKey() { m_themeKey++; } + +private: + QThemeIconEntries findIconHelper(const QString &themeName, + const QString &iconName, + QStringList &visited) const; + uint m_themeKey; + bool m_supportsSvg; + + mutable QString m_userTheme; + mutable QString m_systemTheme; + mutable QStringList m_iconDirs; + mutable QHash <QString, QIconTheme> themeList; +}; + +QT_END_NAMESPACE + +#endif // QDESKTOPICON_P_H |