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

Last change on this file since 651 was 651, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.2 sources.

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