source: trunk/src/gui/image/qiconloader.cpp@ 966

Last change on this file since 966 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41#ifndef QT_NO_ICON
42#include <private/qiconloader_p.h>
43
44#include <private/qapplication_p.h>
45#include <private/qicon_p.h>
46#include <private/qguiplatformplugin_p.h>
47
48#include <QtGui/QIconEnginePlugin>
49#include <QtGui/QPixmapCache>
50#include <QtGui/QIconEngine>
51#include <QtGui/QStyleOption>
52#include <QtCore/QList>
53#include <QtCore/QHash>
54#include <QtCore/QDir>
55#include <QtCore/QSettings>
56#include <QtGui/QPainter>
57
58#ifdef Q_WS_MAC
59#include <private/qt_cocoa_helpers_mac_p.h>
60#endif
61
62#ifdef Q_WS_X11
63#include <private/qt_x11_p.h>
64#endif
65
66#include <private/qstylehelper_p.h>
67
68QT_BEGIN_NAMESPACE
69
70Q_GLOBAL_STATIC(QIconLoader, iconLoaderInstance)
71
72/* Theme to use in last resort, if the theme does not have the icon, neither the parents */
73static QString fallbackTheme()
74{
75#ifdef Q_WS_X11
76 if (X11->desktopEnvironment == DE_GNOME) {
77 return QLatin1String("gnome");
78 } else if (X11->desktopEnvironment == DE_KDE) {
79 return X11->desktopVersion >= 4
80 ? QString::fromLatin1("oxygen")
81 : QString::fromLatin1("crystalsvg");
82 } else {
83 return QLatin1String("hicolor");
84 }
85#endif
86 return QString();
87}
88
89QIconLoader::QIconLoader() :
90 m_themeKey(1), m_supportsSvg(false), m_initialized(false)
91{
92}
93
94// We lazily initialize the loader to make static icons
95// work. Though we do not officially support this.
96void QIconLoader::ensureInitialized()
97{
98 if (!m_initialized) {
99 m_initialized = true;
100
101 Q_ASSERT(qApp);
102
103 m_systemTheme = qt_guiPlatformPlugin()->systemIconThemeName();
104 if (m_systemTheme.isEmpty())
105 m_systemTheme = fallbackTheme();
106#ifndef QT_NO_LIBRARY
107 QFactoryLoader iconFactoryLoader(QIconEngineFactoryInterfaceV2_iid,
108 QLatin1String("/iconengines"),
109 Qt::CaseInsensitive);
110 if (iconFactoryLoader.keys().contains(QLatin1String("svg")))
111 m_supportsSvg = true;
112#endif //QT_NO_LIBRARY
113 }
114}
115
116QIconLoader *QIconLoader::instance()
117{
118 return iconLoaderInstance();
119}
120
121// Queries the system theme and invalidates existing
122// icons if the theme has changed.
123void QIconLoader::updateSystemTheme()
124{
125 // Only change if this is not explicitly set by the user
126 if (m_userTheme.isEmpty()) {
127 QString theme = qt_guiPlatformPlugin()->systemIconThemeName();
128 if (theme.isEmpty())
129 theme = fallbackTheme();
130 if (theme != m_systemTheme) {
131 m_systemTheme = theme;
132 invalidateKey();
133 }
134 }
135}
136
137void QIconLoader::setThemeName(const QString &themeName)
138{
139 m_userTheme = themeName;
140 invalidateKey();
141}
142
143void QIconLoader::setThemeSearchPath(const QStringList &searchPaths)
144{
145 m_iconDirs = searchPaths;
146 themeList.clear();
147 invalidateKey();
148}
149
150QStringList QIconLoader::themeSearchPaths() const
151{
152 if (m_iconDirs.isEmpty()) {
153 m_iconDirs = qt_guiPlatformPlugin()->iconThemeSearchPaths();
154 // Always add resource directory as search path
155 m_iconDirs.append(QLatin1String(":/icons"));
156 }
157 return m_iconDirs;
158}
159
160QIconTheme::QIconTheme(const QString &themeName)
161 : m_valid(false)
162{
163 QFile themeIndex;
164
165 QList <QIconDirInfo> keyList;
166 QStringList iconDirs = QIcon::themeSearchPaths();
167 for ( int i = 0 ; i < iconDirs.size() ; ++i) {
168 QDir iconDir(iconDirs[i]);
169 QString themeDir = iconDir.path() + QLatin1Char('/') + themeName;
170 themeIndex.setFileName(themeDir + QLatin1String("/index.theme"));
171 if (themeIndex.exists()) {
172 m_contentDir = themeDir;
173 m_valid = true;
174 break;
175 }
176 }
177#ifndef QT_NO_SETTINGS
178 if (themeIndex.exists()) {
179 const QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
180 QStringListIterator keyIterator(indexReader.allKeys());
181 while (keyIterator.hasNext()) {
182
183 const QString key = keyIterator.next();
184 if (key.endsWith(QLatin1String("/Size"))) {
185 // Note the QSettings ini-format does not accept
186 // slashes in key names, hence we have to cheat
187 if (int size = indexReader.value(key).toInt()) {
188 QString directoryKey = key.left(key.size() - 5);
189 QIconDirInfo dirInfo(directoryKey);
190 dirInfo.size = size;
191 QString type = indexReader.value(directoryKey +
192 QLatin1String("/Type")
193 ).toString();
194
195 if (type == QLatin1String("Fixed"))
196 dirInfo.type = QIconDirInfo::Fixed;
197 else if (type == QLatin1String("Scalable"))
198 dirInfo.type = QIconDirInfo::Scalable;
199 else
200 dirInfo.type = QIconDirInfo::Threshold;
201
202 dirInfo.threshold = indexReader.value(directoryKey +
203 QLatin1String("/Threshold"),
204 2).toInt();
205
206 dirInfo.minSize = indexReader.value(directoryKey +
207 QLatin1String("/MinSize"),
208 size).toInt();
209
210 dirInfo.maxSize = indexReader.value(directoryKey +
211 QLatin1String("/MaxSize"),
212 size).toInt();
213 m_keyList.append(dirInfo);
214 }
215 }
216 }
217
218 // Parent themes provide fallbacks for missing icons
219 m_parents = indexReader.value(
220 QLatin1String("Icon Theme/Inherits")).toStringList();
221
222 // Ensure a default platform fallback for all themes
223 if (m_parents.isEmpty())
224 m_parents.append(fallbackTheme());
225
226 // Ensure that all themes fall back to hicolor
227 if (!m_parents.contains(QLatin1String("hicolor")))
228 m_parents.append(QLatin1String("hicolor"));
229 }
230#endif //QT_NO_SETTINGS
231}
232
233QThemeIconEntries QIconLoader::findIconHelper(const QString &themeName,
234 const QString &iconName,
235 QStringList &visited) const
236{
237 QThemeIconEntries entries;
238 Q_ASSERT(!themeName.isEmpty());
239
240 QPixmap pixmap;
241
242 // Used to protect against potential recursions
243 visited << themeName;
244
245 QIconTheme theme = themeList.value(themeName);
246 if (!theme.isValid()) {
247 theme = QIconTheme(themeName);
248 if (!theme.isValid())
249 theme = QIconTheme(fallbackTheme());
250
251 themeList.insert(themeName, theme);
252 }
253
254 QString contentDir = theme.contentDir() + QLatin1Char('/');
255 QList<QIconDirInfo> subDirs = theme.keyList();
256
257 const QString svgext(QLatin1String(".svg"));
258 const QString pngext(QLatin1String(".png"));
259
260 // Add all relevant files
261 for (int i = 0; i < subDirs.size() ; ++i) {
262 const QIconDirInfo &dirInfo = subDirs.at(i);
263 QString subdir = dirInfo.path;
264 QDir currentDir(contentDir + subdir);
265 if (currentDir.exists(iconName + pngext)) {
266 PixmapEntry *iconEntry = new PixmapEntry;
267 iconEntry->dir = dirInfo;
268 iconEntry->filename = currentDir.filePath(iconName + pngext);
269 // Notice we ensure that pixmap entries always come before
270 // scalable to preserve search order afterwards
271 entries.prepend(iconEntry);
272 } else if (m_supportsSvg &&
273 currentDir.exists(iconName + svgext)) {
274 ScalableEntry *iconEntry = new ScalableEntry;
275 iconEntry->dir = dirInfo;
276 iconEntry->filename = currentDir.filePath(iconName + svgext);
277 entries.append(iconEntry);
278 }
279 }
280
281 if (entries.isEmpty()) {
282 const QStringList parents = theme.parents();
283 // Search recursively through inherited themes
284 for (int i = 0 ; i < parents.size() ; ++i) {
285
286 const QString parentTheme = parents.at(i).trimmed();
287
288 if (!visited.contains(parentTheme)) // guard against recursion
289 entries = findIconHelper(parentTheme, iconName, visited);
290
291 if (!entries.isEmpty()) // success
292 break;
293 }
294 }
295 return entries;
296}
297
298QThemeIconEntries QIconLoader::loadIcon(const QString &name) const
299{
300 if (!themeName().isEmpty()) {
301 QStringList visited;
302 return findIconHelper(themeName(), name, visited);
303 }
304
305 return QThemeIconEntries();
306}
307
308
309// -------- Icon Loader Engine -------- //
310
311
312QIconLoaderEngine::QIconLoaderEngine(const QString& iconName)
313 : m_iconName(iconName), m_key(0)
314{
315}
316
317QIconLoaderEngine::~QIconLoaderEngine()
318{
319 while (!m_entries.isEmpty())
320 delete m_entries.takeLast();
321 Q_ASSERT(m_entries.size() == 0);
322}
323
324QIconLoaderEngine::QIconLoaderEngine(const QIconLoaderEngine &other)
325 : QIconEngineV2(other),
326 m_iconName(other.m_iconName),
327 m_key(0)
328{
329}
330
331QIconEngineV2 *QIconLoaderEngine::clone() const
332{
333 return new QIconLoaderEngine(*this);
334}
335
336bool QIconLoaderEngine::read(QDataStream &in) {
337 in >> m_iconName;
338 return true;
339}
340
341bool QIconLoaderEngine::write(QDataStream &out) const
342{
343 out << m_iconName;
344 return true;
345}
346
347bool QIconLoaderEngine::hasIcon() const
348{
349 return !(m_entries.isEmpty());
350}
351
352// Lazily load the icon
353void QIconLoaderEngine::ensureLoaded()
354{
355
356 iconLoaderInstance()->ensureInitialized();
357
358 if (!(iconLoaderInstance()->themeKey() == m_key)) {
359
360 while (!m_entries.isEmpty())
361 delete m_entries.takeLast();
362
363 Q_ASSERT(m_entries.size() == 0);
364 m_entries = iconLoaderInstance()->loadIcon(m_iconName);
365 m_key = iconLoaderInstance()->themeKey();
366 }
367}
368
369void QIconLoaderEngine::paint(QPainter *painter, const QRect &rect,
370 QIcon::Mode mode, QIcon::State state)
371{
372 QSize pixmapSize = rect.size();
373#if defined(Q_WS_MAC)
374 pixmapSize *= qt_mac_get_scalefactor();
375#endif
376 painter->drawPixmap(rect, pixmap(pixmapSize, mode, state));
377}
378
379/*
380 * This algorithm is defined by the freedesktop spec:
381 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
382 */
383static bool directoryMatchesSize(const QIconDirInfo &dir, int iconsize)
384{
385 if (dir.type == QIconDirInfo::Fixed) {
386 return dir.size == iconsize;
387
388 } else if (dir.type == QIconDirInfo::Scalable) {
389 return dir.size <= dir.maxSize &&
390 iconsize >= dir.minSize;
391
392 } else if (dir.type == QIconDirInfo::Threshold) {
393 return iconsize >= dir.size - dir.threshold &&
394 iconsize <= dir.size + dir.threshold;
395 }
396
397 Q_ASSERT(1); // Not a valid value
398 return false;
399}
400
401/*
402 * This algorithm is defined by the freedesktop spec:
403 * http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
404 */
405static int directorySizeDistance(const QIconDirInfo &dir, int iconsize)
406{
407 if (dir.type == QIconDirInfo::Fixed) {
408 return qAbs(dir.size - iconsize);
409
410 } else if (dir.type == QIconDirInfo::Scalable) {
411 if (iconsize < dir.minSize)
412 return dir.minSize - iconsize;
413 else if (iconsize > dir.maxSize)
414 return iconsize - dir.maxSize;
415 else
416 return 0;
417
418 } else if (dir.type == QIconDirInfo::Threshold) {
419 if (iconsize < dir.size - dir.threshold)
420 return dir.minSize - iconsize;
421 else if (iconsize > dir.size + dir.threshold)
422 return iconsize - dir.maxSize;
423 else return 0;
424 }
425
426 Q_ASSERT(1); // Not a valid value
427 return INT_MAX;
428}
429
430QIconLoaderEngineEntry *QIconLoaderEngine::entryForSize(const QSize &size)
431{
432 int iconsize = qMin(size.width(), size.height());
433
434 // Note that m_entries are sorted so that png-files
435 // come first
436
437 // Search for exact matches first
438 for (int i = 0; i < m_entries.count(); ++i) {
439 QIconLoaderEngineEntry *entry = m_entries.at(i);
440 if (directoryMatchesSize(entry->dir, iconsize)) {
441 return entry;
442 }
443 }
444
445 // Find the minimum distance icon
446 int minimalSize = INT_MAX;
447 QIconLoaderEngineEntry *closestMatch = 0;
448 for (int i = 0; i < m_entries.count(); ++i) {
449 QIconLoaderEngineEntry *entry = m_entries.at(i);
450 int distance = directorySizeDistance(entry->dir, iconsize);
451 if (distance < minimalSize) {
452 minimalSize = distance;
453 closestMatch = entry;
454 }
455 }
456 return closestMatch;
457}
458
459/*
460 * Returns the actual icon size. For scalable svg's this is equivalent
461 * to the requested size. Otherwise the closest match is returned but
462 * we can never return a bigger size than the requested size.
463 *
464 */
465QSize QIconLoaderEngine::actualSize(const QSize &size, QIcon::Mode mode,
466 QIcon::State state)
467{
468 ensureLoaded();
469
470 QIconLoaderEngineEntry *entry = entryForSize(size);
471 if (entry) {
472 const QIconDirInfo &dir = entry->dir;
473 if (dir.type == QIconDirInfo::Scalable)
474 return size;
475 else {
476 int result = qMin<int>(dir.size, qMin(size.width(), size.height()));
477 return QSize(result, result);
478 }
479 }
480 return QIconEngineV2::actualSize(size, mode, state);
481}
482
483QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
484{
485 Q_UNUSED(state);
486
487 // Ensure that basePixmap is lazily initialized before generating the
488 // key, otherwise the cache key is not unique
489 if (basePixmap.isNull())
490 basePixmap.load(filename);
491
492 int actualSize = qMin(size.width(), size.height());
493
494 QString key = QLatin1Literal("$qt_theme_")
495 % HexString<qint64>(basePixmap.cacheKey())
496 % HexString<int>(mode)
497 % HexString<qint64>(qApp->palette().cacheKey())
498 % HexString<int>(actualSize);
499
500 QPixmap cachedPixmap;
501 if (QPixmapCache::find(key, &cachedPixmap)) {
502 return cachedPixmap;
503 } else {
504 QStyleOption opt(0);
505 opt.palette = qApp->palette();
506 cachedPixmap = qApp->style()->generatedIconPixmap(mode, basePixmap, &opt);
507 QPixmapCache::insert(key, cachedPixmap);
508 }
509 return cachedPixmap;
510}
511
512QPixmap ScalableEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
513{
514 if (svgIcon.isNull())
515 svgIcon = QIcon(filename);
516
517 // Simply reuse svg icon engine
518 return svgIcon.pixmap(size, mode, state);
519}
520
521QPixmap QIconLoaderEngine::pixmap(const QSize &size, QIcon::Mode mode,
522 QIcon::State state)
523{
524 ensureLoaded();
525
526 QIconLoaderEngineEntry *entry = entryForSize(size);
527 if (entry)
528 return entry->pixmap(size, mode, state);
529
530 return QPixmap();
531}
532
533QString QIconLoaderEngine::key() const
534{
535 return QLatin1String("QIconLoaderEngine");
536}
537
538void QIconLoaderEngine::virtual_hook(int id, void *data)
539{
540 ensureLoaded();
541
542 switch (id) {
543 case QIconEngineV2::AvailableSizesHook:
544 {
545 QIconEngineV2::AvailableSizesArgument &arg
546 = *reinterpret_cast<QIconEngineV2::AvailableSizesArgument*>(data);
547 const QList<QIconDirInfo> directoryKey = iconLoaderInstance()->theme().keyList();
548 arg.sizes.clear();
549
550 // Gets all sizes from the DirectoryInfo entries
551 for (int i = 0 ; i < m_entries.size() ; ++i) {
552 int size = m_entries.at(i)->dir.size;
553 arg.sizes.append(QSize(size, size));
554 }
555 }
556 break;
557 case QIconEngineV2::IconNameHook:
558 {
559 QString &name = *reinterpret_cast<QString*>(data);
560 name = m_iconName;
561 }
562 break;
563 default:
564 QIconEngineV2::virtual_hook(id, data);
565 }
566}
567
568QT_END_NAMESPACE
569
570#endif //QT_NO_ICON
Note: See TracBrowser for help on using the repository browser.