KIconThemes

kiconloader.cpp
1 /* vi: ts=8 sts=4 sw=4
2  *
3  * kiconloader.cpp: An icon loader for KDE with theming functionality.
4  *
5  * This file is part of the KDE project, module kdeui.
6  * Copyright (C) 2000 Geert Jansen <[email protected]>
7  * Antonio Larrosa <[email protected]>
8  * 2010 Michael Pyne <[email protected]>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License version 2 as published by the Free Software Foundation.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB. If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24 
25 #include "kiconloader.h"
26 
27 #include <qplatformdefs.h> //for readlink
28 #include <assert.h>
29 
30 #include <QCache>
31 #include <QFileInfo>
32 #include <QDir>
33 #include <QBuffer>
34 #include <QDataStream>
35 #include <QByteArray>
36 #include <QStringBuilder> // % operator for QString
37 #include <QElapsedTimer>
38 #include <QIcon>
39 #include <QImage>
40 #include <QMovie>
41 #include <QPainter>
42 #include <QPixmap>
43 #include <QPixmapCache>
44 #include <QGuiApplication>
45 
46 // kdecore
47 #include <KConfigGroup>
48 #include <kshareddatacache.h>
49 #include <KSharedConfig>
50 #ifdef QT_DBUS_LIB
51 #include <QDBusConnection>
52 #include <QDBusMessage>
53 #endif
54 #include <QXmlStreamReader>
55 #include <QXmlStreamWriter>
56 #include <QCryptographicHash>
57 
58 // kdeui
59 #include "kicontheme.h"
60 #include "kiconeffect.h"
61 #include "debug.h"
62 
63 //kwidgetsaddons
64 #include <KPixmapSequence>
65 
66 #include <KColorScheme>
67 #include <KCompressionDevice>
68 
69 namespace {
70 // Used to make cache keys for icons with no group. Result type is QString*
71 QString NULL_EFFECT_FINGERPRINT()
72 {
73  return QStringLiteral("noeffect");
74 }
75 
76 QString STYLESHEET_TEMPLATE()
77 {
78  return QStringLiteral(".ColorScheme-Text {\
79 color:%1;\
80 }\
81 .ColorScheme-Background{\
82 color:%2;\
83 }\
84 .ColorScheme-Highlight{\
85 color:%3;\
86 }\
87 .ColorScheme-PositiveText{\
88 color:%4;\
89 }\
90 .ColorScheme-NeutralText{\
91 color:%5;\
92 }\
93 .ColorScheme-NegativeText{\
94 color:%6;\
95 }");
96 }
97 }
98 
102 static bool pathIsRelative(const QString &path)
103 {
104 #ifdef Q_OS_UNIX
105  return (!path.isEmpty() && path[0] != QLatin1Char('/'));
106 #else
107  return QDir::isRelativePath(path);
108 #endif
109 }
110 
114 struct PixmapWithPath {
115  QPixmap pixmap;
116  QString path;
117 };
118 
125 KICONTHEMES_EXPORT void uintToHex(uint32_t colorData, QChar *buffer)
126 {
127  static const char hexLookup[] = "0123456789abcdef";
128  buffer += 7;
129  uchar *colorFields = reinterpret_cast<uchar*>(&colorData);
130 
131  for (int i = 0; i < 4; i++) {
132  *buffer-- = hexLookup[*colorFields & 0xf];
133  *buffer-- = hexLookup[*colorFields >> 4];
134  colorFields++;
135  }
136 }
137 
138 static QString paletteId(const QPalette &pal)
139 {
140  // 8 per color. We want 3 colors thus 8*4=32.
141  QString buffer(32, Qt::Uninitialized);
142 
143  uintToHex(pal.windowText().color().rgba(), buffer.data());
144  uintToHex(pal.highlight().color().rgba(), buffer.data() + 8);
145  uintToHex(pal.highlightedText().color().rgba(), buffer.data() + 16);
146  uintToHex(pal.window().color().rgba(), buffer.data() + 24);
147 
148  return buffer;
149 }
150 
151 /*** KIconThemeNode: A node in the icon theme dependancy tree. ***/
152 
153 class KIconThemeNode
154 {
155 public:
156 
157  KIconThemeNode(KIconTheme *_theme);
158  ~KIconThemeNode();
159 
160  KIconThemeNode(const KIconThemeNode &) = delete;
161  KIconThemeNode &operator=(const KIconThemeNode &) = delete;
162 
163  void queryIcons(QStringList *lst, int size, KIconLoader::Context context) const;
164  void queryIconsByContext(QStringList *lst, int size, KIconLoader::Context context) const;
165  QString findIcon(const QString &name, int size, KIconLoader::MatchType match) const;
166 
167  KIconTheme *theme;
168 };
169 
170 KIconThemeNode::KIconThemeNode(KIconTheme *_theme)
171 {
172  theme = _theme;
173 }
174 
175 KIconThemeNode::~KIconThemeNode()
176 {
177  delete theme;
178 }
179 
180 void KIconThemeNode::queryIcons(QStringList *result,
181  int size, KIconLoader::Context context) const
182 {
183  // add the icons of this theme to it
184  *result += theme->queryIcons(size, context);
185 }
186 
187 void KIconThemeNode::queryIconsByContext(QStringList *result,
188  int size, KIconLoader::Context context) const
189 {
190  // add the icons of this theme to it
191  *result += theme->queryIconsByContext(size, context);
192 }
193 
194 QString KIconThemeNode::findIcon(const QString &name, int size,
195  KIconLoader::MatchType match) const
196 {
197  return theme->iconPath(name, size, match);
198 }
199 
200 /*** KIconGroup: Icon type description. ***/
201 
202 struct KIconGroup {
203  int size;
204 };
205 
206 extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks;
207 KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000;
208 
209 /*** d pointer for KIconLoader. ***/
210 class KIconLoaderPrivate
211 {
212 public:
213  KIconLoaderPrivate(KIconLoader *q)
214  : q(q)
215  {
216  }
217 
218  ~KIconLoaderPrivate()
219  {
220  clear();
221  }
222 
223  void clear()
224  {
225  /* antlarr: There's no need to delete d->mpThemeRoot as it's already
226  deleted when the elements of d->links are deleted */
227  qDeleteAll(links);
228  delete[] mpGroups;
229  delete mIconCache;
230  mpGroups = nullptr;
231  mIconCache = nullptr;
232  mPixmapCache.clear();
233  appname.clear();
234  searchPaths.clear();
235  links.clear();
236  mIconThemeInited = false;
237  mThemesInTree.clear();
238  }
239 
243  void init(const QString &_appname, const QStringList &extraSearchPaths = QStringList());
244 
248  bool initIconThemes();
249 
255  QString findMatchingIcon(const QString &name, int size, qreal scale) const;
256 
263  QString findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const;
264 
269  void addAppThemes(const QString &appname, const QString &themeBaseDir = QString());
270 
276  void addBaseThemes(KIconThemeNode *node, const QString &appname);
277 
283  void addInheritedThemes(KIconThemeNode *node, const QString &appname);
284 
291  void addThemeByName(const QString &themename, const QString &appname);
292 
297  void addExtraDesktopThemes();
298 
303  QString unknownIconPath(int size, qreal scale) const;
304 
309  QString removeIconExtension(const QString &name) const;
310 
317  void normalizeIconMetadata(KIconLoader::Group &group, int &size, int &state) const;
318 
324  QString makeCacheKey(const QString &name, KIconLoader::Group group, const QStringList &overlays,
325  int size, qreal scale, int state) const;
326 
334  QByteArray processSvg(const QString &path, KIconLoader::States state) const;
335 
342  QImage createIconImage(const QString &path, int size = 0, qreal scale = 1.0, KIconLoader::States state = KIconLoader::DefaultState);
343 
348  void insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path);
349 
355  bool findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path);
356 
360  QString locate(const QString &fileName);
361 
366  void _k_refreshIcons(int group);
367 
368  bool shouldCheckForUnknownIcons()
369  {
370  if (mLastUnknownIconCheck.isValid() && mLastUnknownIconCheck.elapsed() < kiconloader_ms_between_checks) {
371  return false;
372  }
373  mLastUnknownIconCheck.start();
374  return true;
375  }
376 
377  KIconLoader *const q;
378 
379  QStringList mThemesInTree;
380  KIconGroup *mpGroups = nullptr;
381  KIconThemeNode *mpThemeRoot = nullptr;
382  QStringList searchPaths;
383  KIconEffect mpEffect;
385 
386  // This shares the icons across all processes
387  KSharedDataCache *mIconCache = nullptr;
388 
389  // This caches rendered QPixmaps in just this process.
390  QCache<QString, PixmapWithPath> mPixmapCache;
391 
392  bool extraDesktopIconsLoaded : 1;
393  // lazy loading: initIconThemes() is only needed when the "links" list is needed
394  // mIconThemeInited is used inside initIconThemes() to init only once
395  bool mIconThemeInited : 1;
396  QString appname;
397 
398  void drawOverlays(const KIconLoader *loader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays);
399 
400  QHash<QString, bool> mIconAvailability; // icon name -> true (known to be available) or false (known to be unavailable)
401  QElapsedTimer mLastUnknownIconCheck; // recheck for unknown icons after kiconloader_ms_between_checks
402  //the palette used to recolor svg icons stylesheets
403  QPalette mPalette;
404  //to keep track if we are using a custom palette or just falling back to qApp;
405  bool mCustomPalette = false;
406 };
407 
408 class KIconLoaderGlobalData : public QObject
409 {
410  Q_OBJECT
411 
412 public:
413  KIconLoaderGlobalData()
414  {
415  const QStringList genericIconsFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/generic-icons"));
416  //qCDebug(KICONTHEMES) << genericIconsFiles;
417  for (const QString &file : genericIconsFiles) {
418  parseGenericIconsFiles(file);
419  }
420 
421 #ifdef QT_DBUS_LIB
422  QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"),
423  QStringLiteral("iconChanged"), this, SIGNAL(iconChanged(int)));
424 #endif
425  }
426 
427  void emitChange(KIconLoader::Group group)
428  {
429 #ifdef QT_DBUS_LIB
430  QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
431  message.setArguments(QList<QVariant>() << int(group));
433 #endif
434  }
435 
436  QString genericIconFor(const QString &icon) const
437  {
438  return m_genericIcons.value(icon);
439  }
440 
441 Q_SIGNALS:
442  void iconChanged(int group);
443 
444 private:
445  void parseGenericIconsFiles(const QString &fileName);
446  QHash<QString, QString> m_genericIcons;
447 };
448 
449 void KIconLoaderGlobalData::parseGenericIconsFiles(const QString &fileName)
450 {
451  QFile file(fileName);
452  if (file.open(QIODevice::ReadOnly)) {
453  QTextStream stream(&file);
454  stream.setCodec("ISO 8859-1");
455  while (!stream.atEnd()) {
456  const QString line = stream.readLine();
457  if (line.isEmpty() || line[0] == QLatin1Char('#')) {
458  continue;
459  }
460  const int pos = line.indexOf(QLatin1Char(':'));
461  if (pos == -1) { // syntax error
462  continue;
463  }
464  QString mimeIcon = line.left(pos);
465  const int slashindex = mimeIcon.indexOf(QLatin1Char('/'));
466  if (slashindex != -1) {
467  mimeIcon[slashindex] = QLatin1Char('-');
468  }
469 
470  const QString genericIcon = line.mid(pos + 1);
471  m_genericIcons.insert(mimeIcon, genericIcon);
472  //qCDebug(KICONTHEMES) << mimeIcon << "->" << genericIcon;
473  }
474  }
475 }
476 
477 Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData)
478 
479 void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays)
480 {
481  if (overlays.isEmpty()) {
482  return;
483  }
484 
485  const int width = pix.size().width();
486  const int height = pix.size().height();
487  const int iconSize = qMin(width, height);
488  int overlaySize;
489 
490  if (iconSize < 32) {
491  overlaySize = 8;
492  } else if (iconSize <= 48) {
493  overlaySize = 16;
494  } else if (iconSize <= 96) {
495  overlaySize = 22;
496  } else if (iconSize < 256) {
497  overlaySize = 32;
498  } else {
499  overlaySize = 64;
500  }
501 
502  QPainter painter(&pix);
503 
504  int count = 0;
505  for (const QString &overlay : overlays) {
506  // Ensure empty strings fill up a emblem spot
507  // Needed when you have several emblems to ensure they're always painted
508  // at the same place, even if one is not here
509  if (overlay.isEmpty()) {
510  ++count;
511  continue;
512  }
513 
514  //TODO: should we pass in the kstate? it results in a slower
515  // path, and perhaps emblems should remain in the default state
516  // anyways?
517  QPixmap pixmap = iconLoader->loadIcon(overlay, group, overlaySize, state, QStringList(), nullptr, true);
518 
519  if (pixmap.isNull()) {
520  continue;
521  }
522 
523  // match the emblem's devicePixelRatio to the original pixmap's
524  pixmap.setDevicePixelRatio(pix.devicePixelRatio());
525  const int margin = pixmap.devicePixelRatio() * 0.05 * iconSize;
526 
527  QPoint startPoint;
528  switch (count) {
529  case 0:
530  // bottom right corner
531  startPoint = QPoint(width - overlaySize - margin,
532  height - overlaySize - margin);
533  break;
534  case 1:
535  // bottom left corner
536  startPoint = QPoint(margin, height - overlaySize - margin);
537  break;
538  case 2:
539  // top left corner
540  startPoint = QPoint(margin, margin);
541  break;
542  case 3:
543  // top right corner
544  startPoint = QPoint(width - overlaySize - margin, margin);
545  break;
546  }
547 
548  startPoint /= pix.devicePixelRatio();
549 
550  painter.drawPixmap(startPoint, pixmap);
551 
552  ++count;
553  if (count > 3) {
554  break;
555  }
556  }
557 }
558 
559 void KIconLoaderPrivate::_k_refreshIcons(int group)
560 {
561  KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig();
562  sharedConfig->reparseConfiguration();
563  const QString newThemeName = sharedConfig->group("Icons")
564  .readEntry("Theme", QStringLiteral("breeze"));
565  if (!newThemeName.isEmpty()) {
566  // If we're refreshing icons the Qt platform plugin has probably
567  // already cached the old theme, which will accidentally filter back
568  // into KIconTheme unless we reset it
569  QIcon::setThemeName(newThemeName);
570  }
571 
572  q->newIconLoader();
573  mIconAvailability.clear();
574  emit q->iconChanged(group);
575 }
576 
577 KIconLoader::KIconLoader(const QString &_appname, const QStringList &extraSearchPaths, QObject *parent)
578  : QObject(parent)
579 {
580  setObjectName(_appname);
581  d = new KIconLoaderPrivate(this);
582 
583  connect(s_globalData, SIGNAL(iconChanged(int)), SLOT(_k_refreshIcons(int)));
584  d->init(_appname, extraSearchPaths);
585 }
586 
587 void KIconLoader::reconfigure(const QString &_appname, const QStringList &extraSearchPaths)
588 {
589  d->mIconCache->clear();
590  d->clear();
591  d->init(_appname, extraSearchPaths);
592 }
593 
594 void KIconLoaderPrivate::init(const QString &_appname, const QStringList &extraSearchPaths)
595 {
596  extraDesktopIconsLoaded = false;
597  mIconThemeInited = false;
598  mpThemeRoot = nullptr;
599 
600  searchPaths = extraSearchPaths;
601 
602  appname = _appname;
603  if (appname.isEmpty()) {
605  }
606 
607  // Initialize icon cache
608  mIconCache = new KSharedDataCache(QStringLiteral("icon-cache"), 10 * 1024 * 1024);
609  // Cost here is number of pixels, not size. So this is actually a bit
610  // smaller.
611  mPixmapCache.setMaxCost(10 * 1024 * 1024);
612 
613  // These have to match the order in kiconloader.h
614  static const char *const groups[] = { "Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", nullptr };
615  KSharedConfig::Ptr config = KSharedConfig::openConfig();
616 
617  // loading config and default sizes
618  initIconThemes();
619  KIconTheme *defaultSizesTheme = links.empty() ? nullptr : links.first()->theme;
620  mpGroups = new KIconGroup[static_cast<int>(KIconLoader::LastGroup)];
622  if (groups[i] == nullptr) {
623  break;
624  }
625 
626  KConfigGroup cg(config, QLatin1String(groups[i]) + QStringLiteral("Icons"));
627  mpGroups[i].size = cg.readEntry("Size", 0);
628 
629  if (!mpGroups[i].size && defaultSizesTheme) {
630  mpGroups[i].size = defaultSizesTheme->defaultSize(i);
631  }
632  }
633 }
634 
635 bool KIconLoaderPrivate::initIconThemes()
636 {
637  if (mIconThemeInited) {
638  // If mpThemeRoot isn't 0 then initing has succeeded
639  return (mpThemeRoot != nullptr);
640  }
641  //qCDebug(KICONTHEMES);
642  mIconThemeInited = true;
643 
644  // Add the default theme and its base themes to the theme tree
645  KIconTheme *def = new KIconTheme(KIconTheme::current(), appname);
646  if (!def->isValid()) {
647  delete def;
648  // warn, as this is actually a small penalty hit
649  qCDebug(KICONTHEMES) << "Couldn't find current icon theme, falling back to default.";
650  def = new KIconTheme(KIconTheme::defaultThemeName(), appname);
651  if (!def->isValid()) {
652  qWarning() << "Error: standard icon theme" << KIconTheme::defaultThemeName() << "not found!";
653  delete def;
654  return false;
655  }
656  }
657  mpThemeRoot = new KIconThemeNode(def);
658  mThemesInTree.append(def->internalName());
659  links.append(mpThemeRoot);
660  addBaseThemes(mpThemeRoot, appname);
661 
662  // Insert application specific themes at the top.
663  searchPaths.append(appname + QStringLiteral("/pics"));
664 
665  // Add legacy icon dirs.
666  searchPaths.append(QStringLiteral("icons")); // was xdgdata-icon in KStandardDirs
667  // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
668  searchPaths.append(QStringLiteral("pixmaps")); // was xdgdata-pixmaps in KStandardDirs
669 
670  return true;
671 }
672 
674 {
675  delete d;
676 }
677 
679 {
680  return d->searchPaths;
681 }
682 
683 void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir)
684 {
685  d->initIconThemes();
686 
687  d->searchPaths.append(appname + QStringLiteral("/pics"));
688  d->addAppThemes(appname, themeBaseDir);
689 }
690 
691 void KIconLoaderPrivate::addAppThemes(const QString &appname, const QString &themeBaseDir)
692 {
693  initIconThemes();
694 
695  KIconTheme *def = new KIconTheme(QStringLiteral("hicolor"), appname, themeBaseDir);
696  if (!def->isValid()) {
697  delete def;
698  def = new KIconTheme(KIconTheme::defaultThemeName(), appname, themeBaseDir);
699  }
700  KIconThemeNode *node = new KIconThemeNode(def);
701  bool addedToLinks = false;
702 
703  if (!mThemesInTree.contains(appname)) {
704  mThemesInTree.append(appname);
705  links.append(node);
706  addedToLinks = true;
707  }
708  addBaseThemes(node, appname);
709 
710  if (!addedToLinks) {
711  // Nodes in links are being deleted later - this one needs manual care.
712  delete node;
713  }
714 }
715 
716 void KIconLoaderPrivate::addBaseThemes(KIconThemeNode *node, const QString &appname)
717 {
718  // Quote from the icon theme specification:
719  // The lookup is done first in the current theme, and then recursively
720  // in each of the current theme's parents, and finally in the
721  // default theme called "hicolor" (implementations may add more
722  // default themes before "hicolor", but "hicolor" must be last).
723  //
724  // So we first make sure that all inherited themes are added, then we
725  // add the KDE default theme as fallback for all icons that might not be
726  // present in an inherited theme, and hicolor goes last.
727 
728  addInheritedThemes(node, appname);
729  addThemeByName(QStringLiteral("hicolor"), appname);
730 }
731 
732 void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname)
733 {
734  const QStringList lst = node->theme->inherits();
735 
736  for (QStringList::ConstIterator it = lst.begin(), total = lst.end(); it != total; ++it) {
737  if ((*it) == QLatin1String("hicolor")) {
738  // The icon theme spec says that "hicolor" must be the very last
739  // of all inherited themes, so don't add it here but at the very end
740  // of addBaseThemes().
741  continue;
742  }
743  addThemeByName(*it, appname);
744  }
745 }
746 
747 void KIconLoaderPrivate::addThemeByName(const QString &themename, const QString &appname)
748 {
749  if (mThemesInTree.contains(themename + appname)) {
750  return;
751  }
752  KIconTheme *theme = new KIconTheme(themename, appname);
753  if (!theme->isValid()) {
754  delete theme;
755  return;
756  }
757  KIconThemeNode *n = new KIconThemeNode(theme);
758  mThemesInTree.append(themename + appname);
759  links.append(n);
760  addInheritedThemes(n, appname);
761 }
762 
763 void KIconLoaderPrivate::addExtraDesktopThemes()
764 {
765  if (extraDesktopIconsLoaded) {
766  return;
767  }
768 
769  initIconThemes();
770 
771  QStringList list;
773  char buf[1000];
774  for (QStringList::ConstIterator it = icnlibs.begin(), total = icnlibs.end(); it != total; ++it) {
775  QDir dir(*it);
776  if (!dir.exists()) {
777  continue;
778  }
779  const QStringList lst = dir.entryList(QStringList(QStringLiteral("default.*")), QDir::Dirs);
780  for (QStringList::ConstIterator it2 = lst.begin(), total = lst.end(); it2 != total; ++it2) {
781  if (!QFileInfo::exists(*it + *it2 + QStringLiteral("/index.desktop"))
782  && !QFileInfo::exists(*it + *it2 + QStringLiteral("/index.theme"))) {
783  continue;
784  }
785  //TODO: Is any special handling required for NTFS symlinks?
786 #ifndef Q_OS_WIN
787  const int r = readlink(QFile::encodeName(*it + *it2), buf, sizeof(buf) - 1);
788  if (r > 0) {
789  buf[r] = 0;
790  const QDir dir2(buf);
791  QString themeName = dir2.dirName();
792 
793  if (!list.contains(themeName)) {
794  list.append(themeName);
795  }
796  }
797 #endif
798  }
799  }
800 
801  for (QStringList::ConstIterator it = list.constBegin(), total = list.constEnd(); it != total; ++it) {
802  // Don't add the KDE defaults once more, we have them anyways.
803  if (*it == QLatin1String("default.kde")
804  || *it == QLatin1String("default.kde4")) {
805  continue;
806  }
807  addThemeByName(*it, QLatin1String(""));
808  }
809 
810  extraDesktopIconsLoaded = true;
811 
812 }
813 
814 void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const
815 {
816  d->drawOverlays(this, group, state, pixmap, overlays);
817 }
818 
819 QString KIconLoaderPrivate::removeIconExtension(const QString &name) const
820 {
821  if (name.endsWith(QLatin1String(".png"))
822  || name.endsWith(QLatin1String(".xpm"))
823  || name.endsWith(QLatin1String(".svg"))) {
824  return name.left(name.length() - 4);
825  } else if (name.endsWith(QLatin1String(".svgz"))) {
826  return name.left(name.length() - 5);
827  }
828 
829  return name;
830 }
831 
832 void KIconLoaderPrivate::normalizeIconMetadata(KIconLoader::Group &group, int &size, int &state) const
833 {
834  if ((state < 0) || (state >= KIconLoader::LastState)) {
835  qWarning() << "Illegal icon state:" << state;
837  }
838 
839  if (size < 0) {
840  size = 0;
841  }
842 
843  // For "User" icons, bail early since the size should be based on the size on disk,
844  // which we've already checked.
845  if (group == KIconLoader::User) {
846  return;
847  }
848 
849  if ((group < -1) || (group >= KIconLoader::LastGroup)) {
850  qWarning() << "Illegal icon group:" << group;
851  group = KIconLoader::Desktop;
852  }
853 
854  // If size == 0, use default size for the specified group.
855  if (size == 0) {
856  if (group < 0) {
857  qWarning() << "Neither size nor group specified!";
858  group = KIconLoader::Desktop;
859  }
860  size = mpGroups[group].size;
861  }
862 }
863 
864 QString KIconLoaderPrivate::makeCacheKey(const QString &name, KIconLoader::Group group,
865  const QStringList &overlays, int size, qreal scale, int state) const
866 {
867  // The KSharedDataCache is shared so add some namespacing. The following code
868  // uses QStringBuilder (new in Qt 4.6)
869 
870  return (group == KIconLoader::User
871  ? QLatin1String("$kicou_")
872  : QLatin1String("$kico_"))
873  % name
874  % QLatin1Char('_')
875  % QString::number(size)
876  % QLatin1Char('@')
877  % QString::number(scale, 'f', 1)
878  % QLatin1Char('_')
879  % overlays.join(QLatin1Char('_'))
880  % (group >= 0 ? mpEffect.fingerprint(group, state)
881  : NULL_EFFECT_FINGERPRINT())
882  % QLatin1Char('_')
883  % paletteId(mCustomPalette ? mPalette : qApp->palette())
884  % (q->theme() && q->theme()->followsColorScheme() && state == KIconLoader::SelectedState ? QStringLiteral("_selected") : QString());
885 }
886 
887 QByteArray KIconLoaderPrivate::processSvg(const QString &path, KIconLoader::States state) const
888 {
890 
891  if (path.endsWith(QLatin1String("svgz"))) {
892  device.reset(new KCompressionDevice(path, KCompressionDevice::GZip));
893  } else {
894  device.reset(new QFile(path));
895  }
896 
897  if (!device->open(QIODevice::ReadOnly)) {
898  return QByteArray();
899  }
900 
901  const QPalette pal = mCustomPalette ? mPalette : qApp->palette();
903  QString styleSheet = STYLESHEET_TEMPLATE().arg(
904  state == KIconLoader::SelectedState ? pal.highlightedText().color().name() : pal.windowText().color().name(),
905  state == KIconLoader::SelectedState ? pal.highlight().color().name() : pal.window().color().name(),
906  state == KIconLoader::SelectedState ? pal.highlightedText().color().name() : pal.highlight().color().name(),
910 
911  QByteArray processedContents;
912  QXmlStreamReader reader(device.data());
913 
914  QBuffer buffer(&processedContents);
915  buffer.open(QIODevice::WriteOnly);
916  QXmlStreamWriter writer(&buffer);
917  while (!reader.atEnd()) {
918  if (reader.readNext() == QXmlStreamReader::StartElement &&
919  reader.qualifiedName() == QLatin1String("style") &&
920  reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) {
921  writer.writeStartElement(QStringLiteral("style"));
922  writer.writeAttributes(reader.attributes());
923  writer.writeCharacters(styleSheet);
924  writer.writeEndElement();
925  while (reader.tokenType() != QXmlStreamReader::EndElement) {
926  reader.readNext();
927  }
928  } else if (reader.tokenType() != QXmlStreamReader::Invalid) {
929  writer.writeCurrentToken(reader);
930  }
931  }
932  buffer.close();
933 
934  return processedContents;
935 }
936 
937 QImage KIconLoaderPrivate::createIconImage(const QString &path, int size, qreal scale, KIconLoader::States state)
938 {
939  //TODO: metadata in the theme to make it do this only if explicitly supported?
940  QImageReader reader;
941  QBuffer buffer;
942 
943  if (q->theme()->followsColorScheme() && (path.endsWith(QLatin1String("svg")) || path.endsWith(QLatin1String("svgz")))) {
944  buffer.setData(processSvg(path, state));
945  reader.setDevice(&buffer);
946  } else {
947  reader.setFileName(path);
948  }
949 
950  if (!reader.canRead()) {
951  return QImage();
952  }
953 
954  if (size != 0) {
955  reader.setScaledSize(QSize(size * scale, size * scale));
956  }
957 
958  return reader.read();
959 }
960 
961 void KIconLoaderPrivate::insertCachedPixmapWithPath(
962  const QString &key,
963  const QPixmap &data,
964  const QString &path = QString())
965 {
966  // Even if the pixmap is null, we add it to the caches so that we record
967  // the fact that whatever icon led to us getting a null pixmap doesn't
968  // exist.
969 
970  QBuffer output;
971  output.open(QIODevice::WriteOnly);
972 
973  QDataStream outputStream(&output);
974  outputStream.setVersion(QDataStream::Qt_4_6);
975 
976  outputStream << path;
977 
978  // Convert the QPixmap to PNG. This is actually done by Qt's own operator.
979  outputStream << data;
980 
981  output.close();
982 
983  // The byte array contained in the QBuffer is what we want in the cache.
984  mIconCache->insert(key, output.buffer());
985 
986  // Also insert the object into our process-local cache for even more
987  // speed.
988  PixmapWithPath *pixmapPath = new PixmapWithPath;
989  pixmapPath->pixmap = data;
990  pixmapPath->path = path;
991 
992  mPixmapCache.insert(key, pixmapPath, data.width() * data.height() + 1);
993 }
994 
995 bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path)
996 {
997  // If the pixmap is present in our local process cache, use that since we
998  // don't need to decompress and upload it to the X server/graphics card.
999  const PixmapWithPath *pixmapPath = mPixmapCache.object(key);
1000  if (pixmapPath) {
1001  path = pixmapPath->path;
1002  data = pixmapPath->pixmap;
1003 
1004  return true;
1005  }
1006 
1007  // Otherwise try to find it in our shared memory cache since that will
1008  // be quicker than the disk, especially for SVGs.
1009  QByteArray result;
1010 
1011  if (!mIconCache->find(key, &result) || result.isEmpty()) {
1012  return false;
1013  }
1014 
1015  QBuffer buffer;
1016  buffer.setBuffer(&result);
1017  buffer.open(QIODevice::ReadOnly);
1018 
1019  QDataStream inputStream(&buffer);
1020  inputStream.setVersion(QDataStream::Qt_4_6);
1021 
1022  QString tempPath;
1023  inputStream >> tempPath;
1024 
1025  if (inputStream.status() == QDataStream::Ok) {
1026  QPixmap tempPixmap;
1027  inputStream >> tempPixmap;
1028 
1029  if (inputStream.status() == QDataStream::Ok) {
1030  data = tempPixmap;
1031  path = tempPath;
1032 
1033  // Since we're here we didn't have a QPixmap cache entry, add one now.
1034  PixmapWithPath *newPixmapWithPath = new PixmapWithPath;
1035  newPixmapWithPath->pixmap = data;
1036  newPixmapWithPath->path = path;
1037 
1038  mPixmapCache.insert(key, newPixmapWithPath, data.width() * data.height() + 1);
1039 
1040  return true;
1041  }
1042  }
1043 
1044  return false;
1045 }
1046 
1047 QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const
1048 {
1049  QString path = findMatchingIcon(name, size, scale);
1050  if (!path.isEmpty()) {
1051  return path;
1052  }
1053 
1054  const QString genericIcon = s_globalData()->genericIconFor(name);
1055  if (!genericIcon.isEmpty()) {
1056  path = findMatchingIcon(genericIcon, size, scale);
1057  }
1058  return path;
1059 }
1060 
1061 QString KIconLoaderPrivate::findMatchingIcon(const QString &name, int size, qreal scale) const
1062 {
1063  const_cast<KIconLoaderPrivate *>(this)->initIconThemes();
1064 
1065  // Do two passes through themeNodes.
1066  //
1067  // The first pass looks for an exact match in each themeNode one after the other.
1068  // If one is found and it is an app icon then return that icon.
1069  //
1070  // In the next pass (assuming the first pass failed), it looks for
1071  // generic fallbacks in each themeNode one after the other.
1072 
1073  // In theory we should only do this for mimetype icons, not for app icons,
1074  // but that would require different APIs. The long term solution is under
1075  // development for Qt >= 5.8, QFileIconProvider calling QPlatformTheme::fileIcon,
1076  // using QMimeType::genericIconName() to get the proper -x-generic fallback.
1077  // Once everone uses that to look up mimetype icons, we can kill the fallback code
1078  // from this method.
1079 
1080  for (KIconThemeNode *themeNode : qAsConst(links)) {
1081  const QString path = themeNode->theme->iconPathByName(name, size, KIconLoader::MatchBest, scale);
1082  if (!path.isEmpty()) {
1083  return path;
1084  }
1085  }
1086 
1087  if (name.endsWith(QLatin1String("-x-generic"))) {
1088  return QString(); // no further fallback
1089  }
1090  bool genericFallback = false;
1091  QString path;
1092  for (KIconThemeNode *themeNode : qAsConst(links)) {
1093  QString currentName = name;
1094 
1095  while (!currentName.isEmpty()) {
1096  if (genericFallback) {
1097  // we already tested the base name
1098  break;
1099  }
1100 
1101  int rindex = currentName.lastIndexOf(QLatin1Char('-'));
1102  if (rindex > 1) { // > 1 so that we don't split x-content or x-epoc
1103  currentName.truncate(rindex);
1104 
1105  if (currentName.endsWith(QLatin1String("-x"))) {
1106  currentName.chop(2);
1107  }
1108  } else {
1109  // From update-mime-database.c
1110  static const QSet<QString> mediaTypes = QSet<QString>()
1111  << QStringLiteral("text") << QStringLiteral("application") << QStringLiteral("image") << QStringLiteral("audio")
1112  << QStringLiteral("inode") << QStringLiteral("video") << QStringLiteral("message") << QStringLiteral("model") << QStringLiteral("multipart")
1113  << QStringLiteral("x-content") << QStringLiteral("x-epoc");
1114  // Shared-mime-info spec says:
1115  // "If [generic-icon] is not specified then the mimetype is used to generate the
1116  // generic icon by using the top-level media type (e.g. "video" in "video/ogg")
1117  // and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
1118  if (mediaTypes.contains(currentName)) {
1119  currentName += QLatin1String("-x-generic");
1120  genericFallback = true;
1121  } else {
1122  break;
1123  }
1124  }
1125 
1126  if (currentName.isEmpty()) {
1127  break;
1128  }
1129 
1130  //qCDebug(KICONTHEMES) << "Looking up" << currentName;
1131  path = themeNode->theme->iconPathByName(currentName, size, KIconLoader::MatchBest, scale);
1132  if (!path.isEmpty()) {
1133  return path;
1134  }
1135  }
1136  }
1137 
1138  if (path.isEmpty()) {
1139  const QStringList fallbackPaths = QIcon::fallbackSearchPaths();
1140 
1141  for (const QString &path : fallbackPaths) {
1142  const QString extensions[] = { QStringLiteral(".png"), QStringLiteral(".svg"), QStringLiteral(".svgz"), QStringLiteral(".xpm") };
1143 
1144  for (const QString &ext : extensions) {
1145  const QString file = path + '/' + name + ext;
1146 
1147  if (QFileInfo::exists(file)) {
1148  return file;
1149  }
1150  }
1151  }
1152  }
1153 
1154  return path;
1155 }
1156 
1157 inline QString KIconLoaderPrivate::unknownIconPath(int size, qreal scale) const
1158 {
1159  QString path = findMatchingIcon(QStringLiteral("unknown"), size, scale);
1160  if (path.isEmpty()) {
1161  qCDebug(KICONTHEMES) << "Warning: could not find \"unknown\" icon for size" << size << "at scale" << scale;
1162  return QString();
1163  }
1164  return path;
1165 }
1166 
1167 QString KIconLoaderPrivate::locate(const QString &fileName)
1168 {
1169  for (const QString &dir : qAsConst(searchPaths)) {
1170  const QString path = dir + QLatin1Char('/') + fileName;
1171  if (QDir(dir).isAbsolute()) {
1172  if (QFileInfo::exists(path)) {
1173  return path;
1174  }
1175  } else {
1177  if (!fullPath.isEmpty()) {
1178  return fullPath;
1179  }
1180  }
1181  }
1182  return QString();
1183 }
1184 
1185 // Finds the absolute path to an icon.
1186 
1187 QString KIconLoader::iconPath(const QString &_name, int group_or_size,
1188  bool canReturnNull) const
1189 {
1190  return iconPath(_name, group_or_size, canReturnNull, 1 /*scale*/);
1191 }
1192 
1193 QString KIconLoader::iconPath(const QString &_name, int group_or_size,
1194  bool canReturnNull, qreal scale) const
1195 {
1196  if (!d->initIconThemes()) {
1197  return QString();
1198  }
1199 
1200  if (_name.isEmpty() || !pathIsRelative(_name)) {
1201  // we have either an absolute path or nothing to work with
1202  return _name;
1203  }
1204 
1205  QString name = d->removeIconExtension(_name);
1206 
1207  QString path;
1208  if (group_or_size == KIconLoader::User) {
1209  path = d->locate(name + QLatin1String(".png"));
1210  if (path.isEmpty()) {
1211  path = d->locate(name + QLatin1String(".svgz"));
1212  }
1213  if (path.isEmpty()) {
1214  path = d->locate(name + QLatin1String(".svg"));
1215  }
1216  if (path.isEmpty()) {
1217  path = d->locate(name + QLatin1String(".xpm"));
1218  }
1219  return path;
1220  }
1221 
1222  if (group_or_size >= KIconLoader::LastGroup) {
1223  qCDebug(KICONTHEMES) << "Illegal icon group:" << group_or_size;
1224  return path;
1225  }
1226 
1227  int size;
1228  if (group_or_size >= 0) {
1229  size = d->mpGroups[group_or_size].size;
1230  } else {
1231  size = -group_or_size;
1232  }
1233 
1234  if (_name.isEmpty()) {
1235  if (canReturnNull) {
1236  return QString();
1237  } else {
1238  return d->unknownIconPath(size, scale);
1239  }
1240  }
1241 
1242  path = d->findMatchingIconWithGenericFallbacks(name, size, scale);
1243 
1244  if (path.isEmpty()) {
1245  // Try "User" group too.
1246  path = iconPath(name, KIconLoader::User, true);
1247  if (!path.isEmpty() || canReturnNull) {
1248  return path;
1249  }
1250 
1251  return d->unknownIconPath(size, scale);
1252  }
1253  return path;
1254 }
1255 
1257  int state, const QStringList &overlays, QString *path_store) const
1258 {
1259  QString iconName = _iconName;
1260  const int slashindex = iconName.indexOf(QLatin1Char('/'));
1261  if (slashindex != -1) {
1262  iconName[slashindex] = QLatin1Char('-');
1263  }
1264 
1265  if (!d->extraDesktopIconsLoaded) {
1266  const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1267  if (!pixmap.isNull()) {
1268  return pixmap;
1269  }
1270  d->addExtraDesktopThemes();
1271  }
1272  const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1273  if (pixmap.isNull()) {
1274  // Icon not found, fallback to application/octet-stream
1275  return loadIcon(QStringLiteral("application-octet-stream"), group, size, state, overlays, path_store, false);
1276  }
1277  return pixmap;
1278 }
1279 
1280 
1281 
1283  int state, const QStringList &overlays,
1284  QString *path_store, bool canReturnNull) const
1285 {
1286 return loadScaledIcon(_name, group, 1.0 /*scale*/, size, state, overlays, path_store, canReturnNull);
1287 }
1288 
1290  int size, int state, const QStringList &overlays,
1291  QString *path_store, bool canReturnNull) const
1292 {
1293  QString name = _name;
1294  bool favIconOverlay = false;
1295 
1296  if (size < 0 || _name.isEmpty()) {
1297  return QPixmap();
1298  }
1299 
1300  /*
1301  * This method works in a kind of pipeline, with the following steps:
1302  * 1. Sanity checks.
1303  * 2. Convert _name, group, size, etc. to a key name.
1304  * 3. Check if the key is already cached.
1305  * 4. If not, initialize the theme and find/load the icon.
1306  * 4a Apply overlays
1307  * 4b Re-add to cache.
1308  */
1309 
1310  // Special case for absolute path icons.
1311  if (name.startsWith(QLatin1String("favicons/"))) {
1312  favIconOverlay = true;
1313  name = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + name + QStringLiteral(".png");
1314  }
1315 
1316  bool absolutePath = !pathIsRelative(name);
1317  if (!absolutePath) {
1318  name = d->removeIconExtension(name);
1319  }
1320 
1321  // Don't bother looking for an icon with no name.
1322  if (name.isEmpty()) {
1323  return QPixmap();
1324  }
1325 
1326  // May modify group, size, or state. This function puts them into sane
1327  // states.
1328  d->normalizeIconMetadata(group, size, state);
1329 
1330  // See if the image is already cached.
1331  QString key = d->makeCacheKey(name, group, overlays, size, scale, state);
1332  QPixmap pix;
1333 
1334  bool iconWasUnknown = false;
1335  QString path;
1336 
1337  if (d->findCachedPixmapWithPath(key, pix, path)) {
1338  pix.setDevicePixelRatio(scale);
1339 
1340  if (path_store) {
1341  *path_store = path;
1342  }
1343 
1344  if (!path.isEmpty()) {
1345  return pix;
1346  } else {
1347  // path is empty for "unknown" icons, which should be searched for
1348  // anew regularly
1349  if (!d->shouldCheckForUnknownIcons()) {
1350  return canReturnNull ? QPixmap() : pix;
1351  }
1352  }
1353  }
1354 
1355  // Image is not cached... go find it and apply effects.
1356  if (!d->initIconThemes()) {
1357  return QPixmap();
1358  }
1359 
1360  favIconOverlay = favIconOverlay && size > 22;
1361 
1362  // First we look for non-User icons. If we don't find one we'd search in
1363  // the User space anyways...
1364  if (group != KIconLoader::User) {
1365  if (absolutePath && !favIconOverlay) {
1366  path = name;
1367  } else {
1368  path = d->findMatchingIconWithGenericFallbacks(favIconOverlay ? QStringLiteral("text-html") : name, size, scale);
1369  }
1370  }
1371 
1372  if (path.isEmpty()) {
1373  // We do have a "User" icon, or we couldn't find the non-User one.
1374  path = (absolutePath) ? name :
1375  iconPath(name, KIconLoader::User, canReturnNull);
1376  }
1377 
1378  // Still can't find it? Use "unknown" if we can't return null.
1379  // We keep going in the function so we can ensure this result gets cached.
1380  if (path.isEmpty() && !canReturnNull) {
1381  path = d->unknownIconPath(size, scale);
1382  iconWasUnknown = true;
1383  }
1384 
1385  QImage img;
1386  if (!path.isEmpty()) {
1387  img = d->createIconImage(path, size, scale, static_cast<KIconLoader::States>(state));
1388  }
1389 
1390  if (group >= 0 && group < KIconLoader::LastGroup) {
1391  img = d->mpEffect.apply(img, group, state);
1392  }
1393 
1394  if (favIconOverlay) {
1395  QImage favIcon(name, "PNG");
1396  if (!favIcon.isNull()) { // if favIcon not there yet, don't try to blend it
1397  QPainter p(&img);
1398 
1399  // Align the favicon overlay
1400  QRect r(favIcon.rect());
1401  r.moveBottomRight(img.rect().bottomRight());
1402  r.adjust(-1, -1, -1, -1); // Move off edge
1403 
1404  // Blend favIcon over img.
1405  p.drawImage(r, favIcon);
1406  }
1407  }
1408 
1409  pix = QPixmap::fromImage(img);
1410 
1411  // TODO: If we make a loadIcon that returns the image we can convert
1412  // drawOverlays to use the image instead of pixmaps as well so we don't
1413  // have to transfer so much to the graphics card.
1414  d->drawOverlays(this, group, state, pix, overlays);
1415 
1416  // Don't add the path to our unknown icon to the cache, only cache the
1417  // actual image.
1418  if (iconWasUnknown) {
1419  path.clear();
1420  }
1421 
1422  d->insertCachedPixmapWithPath(key, pix, path);
1423 
1424  if (path_store) {
1425  *path_store = path;
1426  }
1427 
1428  return pix;
1429 }
1430 
1431 KPixmapSequence KIconLoader::loadPixmapSequence(const QString &xdgIconName, int size) const
1432 {
1433  return KPixmapSequence(iconPath(xdgIconName, -size), size);
1434 }
1435 
1437 {
1438  QString file = moviePath(name, group, size);
1439  if (file.isEmpty()) {
1440  return nullptr;
1441  }
1442  int dirLen = file.lastIndexOf(QLatin1Char('/'));
1443  const QString icon = iconPath(name, size ? -size : group, true);
1444  if (!icon.isEmpty() && file.left(dirLen) != icon.left(dirLen)) {
1445  return nullptr;
1446  }
1447  QMovie *movie = new QMovie(file, QByteArray(), parent);
1448  if (!movie->isValid()) {
1449  delete movie;
1450  return nullptr;
1451  }
1452  return movie;
1453 }
1454 
1455 QString KIconLoader::moviePath(const QString &name, KIconLoader::Group group, int size) const
1456 {
1457  if (!d->mpGroups) {
1458  return QString();
1459  }
1460 
1461  d->initIconThemes();
1462 
1463  if ((group < -1 || group >= KIconLoader::LastGroup) && group != KIconLoader::User) {
1464  qCDebug(KICONTHEMES) << "Illegal icon group:" << group;
1465  group = KIconLoader::Desktop;
1466  }
1467  if (size == 0 && group < 0) {
1468  qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1469  group = KIconLoader::Desktop;
1470  }
1471 
1472  QString file = name + QStringLiteral(".mng");
1473  if (group == KIconLoader::User) {
1474  file = d->locate(file);
1475  } else {
1476  if (size == 0) {
1477  size = d->mpGroups[group].size;
1478  }
1479 
1480  QString path;
1481 
1482  for (KIconThemeNode *themeNode : qAsConst(d->links)) {
1483  path = themeNode->theme->iconPath(file, size, KIconLoader::MatchExact);
1484  if (!path.isEmpty()) {
1485  break;
1486  }
1487  }
1488 
1489  if (path.isEmpty()) {
1490  for (KIconThemeNode *themeNode : qAsConst(d->links)) {
1491  path = themeNode->theme->iconPath(file, size, KIconLoader::MatchBest);
1492  if (!path.isEmpty()) {
1493  break;
1494  }
1495  }
1496  }
1497 
1498  file = path;
1499  }
1500  return file;
1501 }
1502 
1504 {
1505  QStringList lst;
1506 
1507  if (!d->mpGroups) {
1508  return lst;
1509  }
1510 
1511  d->initIconThemes();
1512 
1513  if ((group < -1) || (group >= KIconLoader::LastGroup)) {
1514  qCDebug(KICONTHEMES) << "Illegal icon group: " << group;
1515  group = KIconLoader::Desktop;
1516  }
1517  if ((size == 0) && (group < 0)) {
1518  qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1519  group = KIconLoader::Desktop;
1520  }
1521 
1522  QString file = name + QStringLiteral("/0001");
1523  if (group == KIconLoader::User) {
1524  file = d->locate(file + QStringLiteral(".png"));
1525  } else {
1526  if (size == 0) {
1527  size = d->mpGroups[group].size;
1528  }
1529  file = d->findMatchingIcon(file, size, 1); // FIXME scale
1530  }
1531  if (file.isEmpty()) {
1532  return lst;
1533  }
1534 
1535  QString path = file.left(file.length() - 8);
1536  QDir dir(QFile::encodeName(path));
1537  if (!dir.exists()) {
1538  return lst;
1539  }
1540 
1541  const auto entryList = dir.entryList();
1542  for (const QString &entry : entryList) {
1543  if (!(entry.leftRef(4)).toUInt()) {
1544  continue;
1545  }
1546 
1547  lst += path + entry;
1548  }
1549  lst.sort();
1550  return lst;
1551 }
1552 
1554 {
1555  d->initIconThemes();
1556  if (d->mpThemeRoot) {
1557  return d->mpThemeRoot->theme;
1558  }
1559  return nullptr;
1560 }
1561 
1563 {
1564  if (!d->mpGroups) {
1565  return -1;
1566  }
1567 
1568  if (group < 0 || group >= KIconLoader::LastGroup) {
1569  qCDebug(KICONTHEMES) << "Illegal icon group:" << group;
1570  return -1;
1571  }
1572  return d->mpGroups[group].size;
1573 }
1574 
1576 {
1577  const QDir dir(iconsDir);
1578  const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.xpm") << QStringLiteral("*.svg") << QStringLiteral("*.svgz");
1579  const QStringList lst = dir.entryList(formats, QDir::Files);
1580  QStringList result;
1581  for (QStringList::ConstIterator it = lst.begin(), total = lst.end(); it != total; ++it) {
1582  result += iconsDir + QLatin1Char('/') + *it;
1583  }
1584  return result;
1585 }
1586 
1588  KIconLoader::Context context) const
1589 {
1590  d->initIconThemes();
1591 
1592  QStringList result;
1593  if (group_or_size >= KIconLoader::LastGroup) {
1594  qCDebug(KICONTHEMES) << "Illegal icon group:" << group_or_size;
1595  return result;
1596  }
1597  int size;
1598  if (group_or_size >= 0) {
1599  size = d->mpGroups[group_or_size].size;
1600  } else {
1601  size = -group_or_size;
1602  }
1603 
1604  for (KIconThemeNode *themeNode : qAsConst(d->links)) {
1605  themeNode->queryIconsByContext(&result, size, context);
1606  }
1607 
1608  // Eliminate duplicate entries (same icon in different directories)
1609  QString name;
1610  QStringList res2, entries;
1612  for (it = result.constBegin(); it != result.constEnd(); ++it) {
1613  int n = (*it).lastIndexOf(QLatin1Char('/'));
1614  if (n == -1) {
1615  name = *it;
1616  } else {
1617  name = (*it).mid(n + 1);
1618  }
1619  name = d->removeIconExtension(name);
1620  if (!entries.contains(name)) {
1621  entries += name;
1622  res2 += *it;
1623  }
1624  }
1625  return res2;
1626 
1627 }
1628 
1630 {
1631  d->initIconThemes();
1632 
1633  QStringList result;
1634  if (group_or_size >= KIconLoader::LastGroup) {
1635  qCDebug(KICONTHEMES) << "Illegal icon group:" << group_or_size;
1636  return result;
1637  }
1638  int size;
1639  if (group_or_size >= 0) {
1640  size = d->mpGroups[group_or_size].size;
1641  } else {
1642  size = -group_or_size;
1643  }
1644 
1645  for (KIconThemeNode *themeNode : qAsConst(d->links)) {
1646  themeNode->queryIcons(&result, size, context);
1647  }
1648 
1649  // Eliminate duplicate entries (same icon in different directories)
1650  QString name;
1651  QStringList res2, entries;
1653  for (it = result.constBegin(); it != result.constEnd(); ++it) {
1654  int n = (*it).lastIndexOf(QLatin1Char('/'));
1655  if (n == -1) {
1656  name = *it;
1657  } else {
1658  name = (*it).mid(n + 1);
1659  }
1660  name = d->removeIconExtension(name);
1661  if (!entries.contains(name)) {
1662  entries += name;
1663  res2 += *it;
1664  }
1665  }
1666  return res2;
1667 }
1668 
1669 // used by KIconDialog to find out which contexts to offer in a combobox
1671 {
1672  d->initIconThemes();
1673 
1674  for (KIconThemeNode *themeNode : qAsConst(d->links))
1675  if (themeNode->theme->hasContext(context)) {
1676  return true;
1677  }
1678  return false;
1679 }
1680 
1682 {
1683  return &d->mpEffect;
1684 }
1685 
1687 {
1688  if (!d->mpGroups) {
1689  return false;
1690  }
1691 
1692  if (group < 0 || group >= KIconLoader::LastGroup) {
1693  qCDebug(KICONTHEMES) << "Illegal icon group:" << group;
1694  return false;
1695  }
1696  return true;
1697 }
1698 
1699 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1701  bool canReturnNull)
1702 {
1703  QIcon iconset;
1704  QPixmap tmp = loadIcon(name, g, s, KIconLoader::ActiveState, QStringList(), nullptr, canReturnNull);
1705  iconset.addPixmap(tmp, QIcon::Active, QIcon::On);
1706  // we don't use QIconSet's resizing anyway
1707  tmp = loadIcon(name, g, s, KIconLoader::DisabledState, QStringList(), nullptr, canReturnNull);
1708  iconset.addPixmap(tmp, QIcon::Disabled, QIcon::On);
1709  tmp = loadIcon(name, g, s, KIconLoader::DefaultState, QStringList(), nullptr, canReturnNull);
1710  iconset.addPixmap(tmp, QIcon::Normal, QIcon::On);
1711  return iconset;
1712 }
1713 #endif
1714 
1715 // Easy access functions
1716 
1717 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1718 QPixmap DesktopIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1719 {
1720  KIconLoader *loader = KIconLoader::global();
1721  return loader->loadIcon(name, KIconLoader::Desktop, force_size, state, overlays);
1722 }
1723 #endif
1724 
1725 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1726 QIcon DesktopIconSet(const QString &name, int force_size)
1727 {
1728  KIconLoader *loader = KIconLoader::global();
1729  return loader->loadIconSet(name, KIconLoader::Desktop, force_size);
1730 }
1731 #endif
1732 
1733 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1734 QPixmap BarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1735 {
1736  KIconLoader *loader = KIconLoader::global();
1737  return loader->loadIcon(name, KIconLoader::Toolbar, force_size, state, overlays);
1738 }
1739 #endif
1740 
1741 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1742 QIcon BarIconSet(const QString &name, int force_size)
1743 {
1744  KIconLoader *loader = KIconLoader::global();
1745  return loader->loadIconSet(name, KIconLoader::Toolbar, force_size);
1746 }
1747 #endif
1748 
1749 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1750 QPixmap SmallIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1751 {
1752  KIconLoader *loader = KIconLoader::global();
1753  return loader->loadIcon(name, KIconLoader::Small, force_size, state, overlays);
1754 }
1755 #endif
1756 
1757 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1758 QIcon SmallIconSet(const QString &name, int force_size)
1759 {
1760  KIconLoader *loader = KIconLoader::global();
1761  return loader->loadIconSet(name, KIconLoader::Small, force_size);
1762 }
1763 #endif
1764 
1765 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1766 QPixmap MainBarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1767 {
1768  KIconLoader *loader = KIconLoader::global();
1769  return loader->loadIcon(name, KIconLoader::MainToolbar, force_size, state, overlays);
1770 }
1771 #endif
1772 
1773 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1774 QIcon MainBarIconSet(const QString &name, int force_size)
1775 {
1776  KIconLoader *loader = KIconLoader::global();
1777  return loader->loadIconSet(name, KIconLoader::MainToolbar, force_size);
1778 }
1779 #endif
1780 
1781 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 65)
1782 QPixmap UserIcon(const QString &name, int state, const QStringList &overlays)
1783 {
1784  KIconLoader *loader = KIconLoader::global();
1785  return loader->loadIcon(name, KIconLoader::User, 0, state, overlays);
1786 }
1787 #endif
1788 
1789 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1791 {
1792  KIconLoader *loader = KIconLoader::global();
1793  return loader->loadIconSet(name, KIconLoader::User);
1794 }
1795 #endif
1796 
1797 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 66)
1799 {
1800  KIconLoader *loader = KIconLoader::global();
1801  return loader->currentSize(group);
1802 }
1803 #endif
1804 
1806 {
1807  QPixmap pix;
1808  if (QPixmapCache::find(QStringLiteral("unknown"), &pix)) { //krazy:exclude=iconnames
1809  return pix;
1810  }
1811 
1812  const QString path = global()->iconPath(QStringLiteral("unknown"), KIconLoader::Small, true); //krazy:exclude=iconnames
1813  if (path.isEmpty()) {
1814  qCDebug(KICONTHEMES) << "Warning: Cannot find \"unknown\" icon.";
1815  pix = QPixmap(32, 32);
1816  } else {
1817  pix.load(path);
1818  QPixmapCache::insert(QStringLiteral("unknown"), pix); //krazy:exclude=iconnames
1819  }
1820 
1821  return pix;
1822 }
1823 
1824 bool KIconLoader::hasIcon(const QString &name) const
1825 {
1826  auto it = d->mIconAvailability.constFind(name);
1827  const auto end = d->mIconAvailability.constEnd();
1828  if (it != end && !it.value() && !d->shouldCheckForUnknownIcons()) {
1829  return false; // known to be unavailable
1830  }
1831  bool found = it != end && it.value();
1832  if (!found) {
1833  if (!iconPath(name, KIconLoader::Desktop, KIconLoader::MatchBest).isEmpty()) {
1834  found = true;
1835  }
1836  d->mIconAvailability.insert(name, found); // remember whether the icon is available or not
1837  }
1838  return found;
1839 }
1840 
1842 {
1843  d->mCustomPalette = true;
1844  d->mPalette = palette;
1845 }
1846 
1848 {
1849  return d->mCustomPalette ? d->mPalette : QPalette();
1850 }
1851 
1853 {
1854  d->mCustomPalette = false;
1855 }
1856 
1857 /*** the global icon loader ***/
1858 Q_GLOBAL_STATIC(KIconLoader, globalIconLoader)
1859 
1861 {
1862  return globalIconLoader();
1863 }
1864 
1866 {
1867  if (global() == this) {
1869  }
1870 
1873 }
1874 
1876 {
1877  s_globalData->emitChange(g);
1878 }
1879 
1880 #include <kiconengine.h>
1881 QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader)
1882 {
1883  return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global()));
1884 }
1885 
1886 QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader)
1887 {
1888  return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays));
1889 }
1890 
1891 #include "kiconloader.moc"
1892 #include "moc_kiconloader.moc"
static void reconfigure()
Reconfigure the theme.
Definition: kicontheme.cpp:666
KCOREADDONS_EXPORT void message(KMessage::MessageType messageType, const QString &text, const QString &caption=QString())
The default state.
Definition: kiconloader.h:176
QStringList loadAnimated(const QString &name, KIconLoader::Group group, int size=0) const
Loads an animated icon as a series of still frames.
int defaultSize(KIconLoader::Group group) const
The default size of this theme for a certain icon group.
Definition: kicontheme.cpp:438
void newIconLoader()
Re-initialize the global icon loader.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
const QBrush & highlight() const const
KICONTHEMES_EXPORT QIcon icon(const QString &iconName, KIconLoader *iconLoader=nullptr)
int currentSize(KIconLoader::Group group) const
Returns the current size of the icon group.
void setScaledSize(const QSize &size)
void truncate(int position)
KICONTHEMES_EXPORT int IconSize(KIconLoader::Group group)
void moveBottomRight(const QPoint &position)
int width() const const
QString writableLocation(QStandardPaths::StandardLocation type)
bool hasContext(KIconLoader::Context context) const
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
QString name() const const
QPixmap loadScaledIcon(const QString &name, KIconLoader::Group group, qreal scale, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList(), QString *path_store=nullptr, bool canReturnNull=false) const
Loads an icon.
A class to provide rendering of KDE icons.
Definition: kiconengine.h:43
bool isValid() const
The icon theme exists?
Definition: kicontheme.cpp:423
void setThemeName(const QString &name)
bool isValid() const const
int size() const const
QPoint bottomRight() const const
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
void setData(const QByteArray &data)
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QDBusConnection sessionBus()
QString join(const QString &separator) const const
bool isNull() const const
bool alphaBlending(KIconLoader::Group group) const
Checks whether the user wants to blend the icons with the background using the alpha channel informat...
QString moviePath(const QString &name, KIconLoader::Group group, int size=0) const
Returns the path to an animated icon.
void chop(int n)
QDataStream::Status status() const const
QStringList queryIcons(int group_or_size, KIconLoader::Context context=KIconLoader::Any) const
Queries all available icons for a specific group, having a specific context.
void reconfigure(const QString &appname, const QStringList &extraSearchPaths=QStringList())
Reconfigure the icon loader, for instance to change the associated app name or extra search paths...
Desktop icons.
Definition: kiconloader.h:133
QByteArray & buffer()
QMovie * loadMovie(const QString &name, KIconLoader::Group group, int size=0, QObject *parent=nullptr) const
Loads an animated icon.
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QStringList queryIconsByContext(int group_or_size, KIconLoader::Context context=KIconLoader::Any) const
Queries all available icons for a specific context.
void reset(T *other)
void setCustomPalette(const QPalette &palette)
Sets the colors for this KIconLoader.
void clear()
const QColor & color() const const
void writeAttributes(const QXmlStreamAttributes &attributes)
void iconChanged(int group)
Emitted when the system icon theme changes.
Icon is disabled.
Definition: kiconloader.h:178
QString number(int n, int base)
bool exists() const const
void append(const T &value)
QBrush foreground(ForegroundRole=NormalText) const
QString & insert(int position, QChar ch)
Small icons, e.g. for buttons.
Definition: kiconloader.h:141
Icon is active.
Definition: kiconloader.h:177
bool canRead() const const
KICONTHEMES_EXPORT QPixmap SmallIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
void setObjectName(const QString &name)
bool send(const QDBusMessage &message) const const
bool isEmpty() const const
QRect rect() const const
Take the best match if there is no exact match.
Definition: kiconloader.h:122
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setFileName(const QString &fileName)
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
virtual bool open(QIODevice::OpenMode flags) override
void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
static KIconLoader * global()
Returns the global icon loader initialized with the application name.
KIconTheme * theme() const
Returns a pointer to the current theme.
KIconEffect * iconEffect() const
Returns a pointer to the KIconEffect object used by the icon loader.
QPixmap loadIcon(const QString &name, KIconLoader::Group group, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList(), QString *path_store=nullptr, bool canReturnNull=false) const
Loads an icon.
void setVersion(int v)
T * data() const const
QList::iterator end()
bool exists() const const
void writeCurrentToken(const QXmlStreamReader &reader)
bool isNull() const const
int height() const const
Main toolbar icons.
Definition: kiconloader.h:139
bool isRelativePath(const QString &path)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
MatchType
The type of a match.
Definition: kiconloader.h:120
QCA_EXPORT void init()
bool contains(const T &value) const const
KICONTHEMES_EXPORT QIcon BarIconSet(const QString &name, int size=0)
virtual void close() override
QStringList searchPaths() const
Returns all the search paths for this icon loader, either absolute or relative to GenericDataLocation...
Toolbar icons.
Definition: kiconloader.h:137
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
KICONTHEMES_EXPORT QIcon UserIconSet(const QString &name)
QString mid(int position, int n) const const
QString dirName() const const
KICONTHEMES_EXPORT QPixmap BarIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
States
Defines the possible states of an icon.
Definition: kiconloader.h:175
Last state (last constant)
Definition: kiconloader.h:180
void addAppDir(const QString &appname, const QString &themeBaseDir=QString())
Adds appname to the list of application specific directories with themeBaseDir as its base directory...
void iconLoaderSettingsChanged()
Emitted by newIconLoader once the new settings have been loaded.
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
QImage read()
KICONTHEMES_EXPORT QIcon SmallIconSet(const QString &name, int size=0)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
Context
Defines the context of the icon.
Definition: kiconloader.h:86
Only try to find an exact match.
Definition: kiconloader.h:121
Group
The group of the icon.
Definition: kiconloader.h:129
KPixmapSequence loadPixmapSequence(const QString &iconName, int size=SizeSmall) const
Loads a pixmapSequence given the xdg icon name.
typedef ConstIterator
QPixmap * find(const QString &key)
Icon is selected.
Definition: kiconloader.h:179
void setBuffer(QByteArray *byteArray)
static QString defaultThemeName()
Returns the default icon theme.
Definition: kicontheme.cpp:673
int length() const const
QIcon loadIconSet(const QString &name, KIconLoader::Group group, int size=0, bool canReturnNull=false)
Creates an icon set, that will do on-demand loading of the icon.
User icons.
Definition: kiconloader.h:149
QString left(int n) const const
void setArguments(const QList< QVariant > &arguments)
void sort(Qt::CaseSensitivity cs)
qreal devicePixelRatio() const const
void setDevice(QIODevice *device)
void drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state=KIconLoader::DefaultState) const
Draws overlays on the specified pixmap, it takes the width and height of the pixmap into consideratio...
QStringList queryIconsByDir(const QString &iconsDir) const
Returns a list of all icons (*.png or *.xpm extension) in the given directory.
QPixmap loadMimeTypeIcon(const QString &iconName, KIconLoader::Group group, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList(), QString *path_store=nullptr) const
Loads an icon for a mimetype.
const QBrush & window() const const
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
KICONTHEMES_EXPORT QPixmap DesktopIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
static void emitChange(Group group)
Emits an iconChanged() signal on all the KIconLoader instances in the system indicating that a system...
KICONTHEMES_EXPORT QPixmap UserIcon(const QString &name, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
KICONTHEMES_EXPORT QPixmap MainBarIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
bool insert(const QString &key, const QPixmap &pixmap)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const const
Applies effects to icons.
Definition: kiconeffect.h:48
void setDevicePixelRatio(qreal scaleFactor)
void writeCharacters(const QString &text)
QStringList fallbackSearchPaths()
static QPixmap unknown()
Returns the unknown icon.
QPalette customPalette() const
The colors that will be used for the svg stylesheet in case the loaded icons are svg-based, icons can be colored in different ways in different areas of the application.
const QBrush & windowText() const const
void resetPalette()
Resets the custom palette used by the KIconLoader to use the QGuiApplication::palette() again (and to...
T readEntry(const QString &key, const T &aDefault) const
QList::iterator begin()
QByteArray encodeName(const QString &fileName)
KICONTHEMES_EXPORT QIcon MainBarIconSet(const QString &name, int size=0)
static QString current()
Returns the current icon theme.
Definition: kicontheme.cpp:583
const QBrush & highlightedText() const const
QString applicationName()
QRgb rgba() const const
void writeEndElement()
void writeStartElement(const QString &qualifiedName)
QString internalName() const
The internal name of the icon theme (same as the name argument passed to the constructor).
Definition: kicontheme.cpp:393
Iconloader for KDE.
Definition: kiconloader.h:78
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
~KIconLoader()
Cleanup.
QString iconPath(const QString &name, int group_or_size, bool canReturnNull=false) const
Returns the path of an icon.
KICONTHEMES_EXPORT QIcon DesktopIconSet(const QString &name, int size=0)
KIconLoader(const QString &appname=QString(), const QStringList &extraSearchPaths=QStringList(), QObject *parent=nullptr)
Constructs an iconloader.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jul 13 2020 22:41:18 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.