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  SPDX-FileCopyrightText: 2000 Geert Jansen <[email protected]>
7  SPDX-FileCopyrightText: 2000 Antonio Larrosa <[email protected]>
8  SPDX-FileCopyrightText: 2010 Michael Pyne <[email protected]>
9 
10  SPDX-License-Identifier: LGPL-2.0-only
11 */
12 
13 #include "kiconloader.h"
14 #include "kiconloader_p.h"
15 
16 // kdecore
17 #include <KConfigGroup>
18 #include <KSharedConfig>
19 #include <kshareddatacache.h>
20 #ifdef QT_DBUS_LIB
21 #include <QDBusConnection>
22 #include <QDBusMessage>
23 #endif
24 #include <QCryptographicHash>
25 #include <QXmlStreamReader>
26 #include <QXmlStreamWriter>
27 
28 // kdeui
29 #include "debug.h"
30 #include "kiconcolors.h"
31 #include "kiconeffect.h"
32 #include "kicontheme.h"
33 
34 // kwidgetsaddons
35 #include <KPixmapSequence>
36 
37 #include <KColorScheme>
38 #include <KCompressionDevice>
39 
40 #include <QBuffer>
41 #include <QByteArray>
42 #include <QDataStream>
43 #include <QDir>
44 #include <QElapsedTimer>
45 #include <QFileInfo>
46 #include <QGuiApplication>
47 #include <QIcon>
48 #include <QImage>
49 #include <QMovie>
50 #include <QPainter>
51 #include <QPixmap>
52 #include <QPixmapCache>
53 #include <QStringBuilder> // % operator for QString
54 #include <QtGui/private/qiconloader_p.h>
55 
56 #include <qplatformdefs.h> //for readlink
57 
58 #include <assert.h>
59 
60 namespace
61 {
62 // Used to make cache keys for icons with no group. Result type is QString*
63 QString NULL_EFFECT_FINGERPRINT()
64 {
65  return QStringLiteral("noeffect");
66 }
67 
68 }
69 
70 /**
71  * Function to convert an uint32_t to AARRGGBB hex values.
72  *
73  * W A R N I N G !
74  * This function is for internal use!
75  */
76 KICONTHEMES_EXPORT void uintToHex(uint32_t colorData, QChar *buffer)
77 {
78  static const char hexLookup[] = "0123456789abcdef";
79  buffer += 7;
80  uchar *colorFields = reinterpret_cast<uchar *>(&colorData);
81 
82  for (int i = 0; i < 4; i++) {
83  *buffer-- = hexLookup[*colorFields & 0xf];
84  *buffer-- = hexLookup[*colorFields >> 4];
85  colorFields++;
86  }
87 }
88 
89 static QString paletteId(const KIconColors &colors)
90 {
91  // 8 per color. We want 3 colors thus 8*4=32.
92  QString buffer(32, Qt::Uninitialized);
93 
94  uintToHex(colors.text().rgba(), buffer.data());
95  uintToHex(colors.highlight().rgba(), buffer.data() + 8);
96  uintToHex(colors.highlightedText().rgba(), buffer.data() + 16);
97  uintToHex(colors.background().rgba(), buffer.data() + 24);
98 
99  return buffer;
100 }
101 
102 /*** KIconThemeNode: A node in the icon theme dependency tree. ***/
103 
104 class KIconThemeNode
105 {
106 public:
107  KIconThemeNode(KIconTheme *_theme);
108  ~KIconThemeNode();
109 
110  KIconThemeNode(const KIconThemeNode &) = delete;
111  KIconThemeNode &operator=(const KIconThemeNode &) = delete;
112 
113  void queryIcons(QStringList *lst, int size, KIconLoader::Context context) const;
114  void queryIconsByContext(QStringList *lst, int size, KIconLoader::Context context) const;
115  QString findIcon(const QString &name, int size, KIconLoader::MatchType match) const;
116 
117  KIconTheme *theme;
118 };
119 
120 KIconThemeNode::KIconThemeNode(KIconTheme *_theme)
121 {
122  theme = _theme;
123 }
124 
125 KIconThemeNode::~KIconThemeNode()
126 {
127  delete theme;
128 }
129 
130 void KIconThemeNode::queryIcons(QStringList *result, int size, KIconLoader::Context context) const
131 {
132  // add the icons of this theme to it
133  *result += theme->queryIcons(size, context);
134 }
135 
136 void KIconThemeNode::queryIconsByContext(QStringList *result, int size, KIconLoader::Context context) const
137 {
138  // add the icons of this theme to it
139  *result += theme->queryIconsByContext(size, context);
140 }
141 
142 QString KIconThemeNode::findIcon(const QString &name, int size, KIconLoader::MatchType match) const
143 {
144  return theme->iconPath(name, size, match);
145 }
146 
147 extern KICONTHEMES_EXPORT int kiconloader_ms_between_checks;
148 KICONTHEMES_EXPORT int kiconloader_ms_between_checks = 5000;
149 
150 class KIconLoaderGlobalData : public QObject
151 {
152  Q_OBJECT
153 
154 public:
155  KIconLoaderGlobalData()
156  {
157  const QStringList genericIconsFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/generic-icons"));
158  // qCDebug(KICONTHEMES) << genericIconsFiles;
159  for (const QString &file : genericIconsFiles) {
160  parseGenericIconsFiles(file);
161  }
162 
163 #ifdef QT_DBUS_LIB
165  QStringLiteral("/KIconLoader"),
166  QStringLiteral("org.kde.KIconLoader"),
167  QStringLiteral("iconChanged"),
168  this,
169  SIGNAL(iconChanged(int)));
170 #endif
171  }
172 
173  void emitChange(KIconLoader::Group group)
174  {
175 #ifdef QT_DBUS_LIB
176  QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KIconLoader"), QStringLiteral("org.kde.KIconLoader"), QStringLiteral("iconChanged"));
177  message.setArguments(QList<QVariant>() << int(group));
179 #endif
180  }
181 
182  QString genericIconFor(const QString &icon) const
183  {
184  return m_genericIcons.value(icon);
185  }
186 
187 Q_SIGNALS:
188  void iconChanged(int group);
189 
190 private:
191  void parseGenericIconsFiles(const QString &fileName);
192  QHash<QString, QString> m_genericIcons;
193 };
194 
195 void KIconLoaderGlobalData::parseGenericIconsFiles(const QString &fileName)
196 {
197  QFile file(fileName);
198  if (file.open(QIODevice::ReadOnly)) {
199  QTextStream stream(&file);
200  // In Qt6 the encoding is UTF-8 by default, so it should work for icon file names;
201  // I think this code had "ISO 8859-1" (i.e. Latin-1) as an optimization, but file
202  // names on Linux are UTF-8 by default, so this would be more robust.
203  // Note that in Qt6 we can have the same behaviour by using QTextStream::setEncoding().
204 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
205  stream.setCodec("ISO 8859-1");
206 #endif
207  while (!stream.atEnd()) {
208  const QString line = stream.readLine();
209  if (line.isEmpty() || line[0] == QLatin1Char('#')) {
210  continue;
211  }
212  const int pos = line.indexOf(QLatin1Char(':'));
213  if (pos == -1) { // syntax error
214  continue;
215  }
216  QString mimeIcon = line.left(pos);
217  const int slashindex = mimeIcon.indexOf(QLatin1Char('/'));
218  if (slashindex != -1) {
219  mimeIcon[slashindex] = QLatin1Char('-');
220  }
221 
222  const QString genericIcon = line.mid(pos + 1);
223  m_genericIcons.insert(mimeIcon, genericIcon);
224  // qCDebug(KICONTHEMES) << mimeIcon << "->" << genericIcon;
225  }
226  }
227 }
228 
229 Q_GLOBAL_STATIC(KIconLoaderGlobalData, s_globalData)
230 
231 KIconLoaderPrivate::KIconLoaderPrivate(const QString &_appname, const QStringList &extraSearchPaths, KIconLoader *qq)
232  : q(qq)
233  , m_appname(_appname)
234 {
235  q->connect(s_globalData, &KIconLoaderGlobalData::iconChanged, q, [this](int group) {
236  _k_refreshIcons(group);
237  });
238  init(m_appname, extraSearchPaths);
239 }
240 
241 KIconLoaderPrivate::~KIconLoaderPrivate()
242 {
243  clear();
244 }
245 
246 KIconLoaderPrivate *KIconLoaderPrivate::get(KIconLoader *loader)
247 {
248  return loader->d.get();
249 }
250 
251 void KIconLoaderPrivate::clear()
252 {
253  /* antlarr: There's no need to delete d->mpThemeRoot as it's already
254  deleted when the elements of d->links are deleted */
255  qDeleteAll(links);
256  delete[] mpGroups;
257  delete mIconCache;
258  mpGroups = nullptr;
259  mIconCache = nullptr;
260  mPixmapCache.clear();
261  m_appname.clear();
262  searchPaths.clear();
263  links.clear();
264  mIconThemeInited = false;
265  mThemesInTree.clear();
266 }
267 
268 void KIconLoaderPrivate::drawOverlays(const KIconLoader *iconLoader, KIconLoader::Group group, int state, QPixmap &pix, const QStringList &overlays)
269 {
270  if (overlays.isEmpty()) {
271  return;
272  }
273 
274  const int width = pix.size().width();
275  const int height = pix.size().height();
276  const int iconSize = qMin(width, height);
277  int overlaySize;
278 
279  if (iconSize < 32) {
280  overlaySize = 8;
281  } else if (iconSize <= 48) {
282  overlaySize = 16;
283  } else if (iconSize <= 96) {
284  overlaySize = 22;
285  } else if (iconSize < 256) {
286  overlaySize = 32;
287  } else {
288  overlaySize = 64;
289  }
290 
291  QPainter painter(&pix);
292 
293  int count = 0;
294  for (const QString &overlay : overlays) {
295  // Ensure empty strings fill up a emblem spot
296  // Needed when you have several emblems to ensure they're always painted
297  // at the same place, even if one is not here
298  if (overlay.isEmpty()) {
299  ++count;
300  continue;
301  }
302 
303  // TODO: should we pass in the kstate? it results in a slower
304  // path, and perhaps emblems should remain in the default state
305  // anyways?
306  QPixmap pixmap = iconLoader->loadIcon(overlay, group, overlaySize, state, QStringList(), nullptr, true);
307 
308  if (pixmap.isNull()) {
309  continue;
310  }
311 
312  // match the emblem's devicePixelRatio to the original pixmap's
314  const int margin = pixmap.devicePixelRatio() * 0.05 * iconSize;
315 
316  QPoint startPoint;
317  switch (count) {
318  case 0:
319  // bottom right corner
320  startPoint = QPoint(width - overlaySize - margin, height - overlaySize - margin);
321  break;
322  case 1:
323  // bottom left corner
324  startPoint = QPoint(margin, height - overlaySize - margin);
325  break;
326  case 2:
327  // top left corner
328  startPoint = QPoint(margin, margin);
329  break;
330  case 3:
331  // top right corner
332  startPoint = QPoint(width - overlaySize - margin, margin);
333  break;
334  }
335 
336  startPoint /= pix.devicePixelRatio();
337 
338  painter.drawPixmap(startPoint, pixmap);
339 
340  ++count;
341  if (count > 3) {
342  break;
343  }
344  }
345 }
346 
347 void KIconLoaderPrivate::_k_refreshIcons(int group)
348 {
349  KSharedConfig::Ptr sharedConfig = KSharedConfig::openConfig();
350  sharedConfig->reparseConfiguration();
351  const QString newThemeName = sharedConfig->group("Icons").readEntry("Theme", QStringLiteral("breeze"));
352  if (!newThemeName.isEmpty()) {
353  // NOTE Do NOT use QIcon::setThemeName here it makes Qt not use icon engine of the platform theme
354  // anymore (KIconEngine on Plasma, which breaks recoloring) and overwrites a user set themeName
355  // TODO KF6 this should be done in the Plasma QPT
356  QIconLoader::instance()->updateSystemTheme();
357  }
358 
359  q->newIconLoader();
360  mIconAvailability.clear();
361  Q_EMIT q->iconChanged(group);
362 }
363 
364 bool KIconLoaderPrivate::shouldCheckForUnknownIcons()
365 {
366  if (mLastUnknownIconCheck.isValid() && mLastUnknownIconCheck.elapsed() < kiconloader_ms_between_checks) {
367  return false;
368  }
369  mLastUnknownIconCheck.start();
370  return true;
371 }
372 
373 KIconLoader::KIconLoader(const QString &appname, const QStringList &extraSearchPaths, QObject *parent)
374  : QObject(parent)
375  , d(new KIconLoaderPrivate(appname, extraSearchPaths, this))
376 {
377  setObjectName(appname);
378 }
379 
380 void KIconLoader::reconfigure(const QString &_appname, const QStringList &extraSearchPaths)
381 {
382  d->mIconCache->clear();
383  d->clear();
384  d->init(_appname, extraSearchPaths);
385 }
386 
387 void KIconLoaderPrivate::init(const QString &_appname, const QStringList &extraSearchPaths)
388 {
389  extraDesktopIconsLoaded = false;
390  mIconThemeInited = false;
391  mpThemeRoot = nullptr;
392 
393  searchPaths = extraSearchPaths;
394 
395  m_appname = !_appname.isEmpty() ? _appname : QCoreApplication::applicationName();
396 
397  // Initialize icon cache
398  mIconCache = new KSharedDataCache(QStringLiteral("icon-cache"), 10 * 1024 * 1024);
399  // Cost here is number of pixels, not size. So this is actually a bit
400  // smaller.
401  mPixmapCache.setMaxCost(10 * 1024 * 1024);
402 
403  // These have to match the order in kiconloader.h
404  static const char *const groups[] = {"Desktop", "Toolbar", "MainToolbar", "Small", "Panel", "Dialog", nullptr};
405  KSharedConfig::Ptr config = KSharedConfig::openConfig();
406 
407  // loading config and default sizes
408  initIconThemes();
409  KIconTheme *defaultSizesTheme = links.empty() ? nullptr : links.first()->theme;
410  mpGroups = new KIconGroup[static_cast<int>(KIconLoader::LastGroup)];
412  if (groups[i] == nullptr) {
413  break;
414  }
415 
416  KConfigGroup cg(config, QLatin1String(groups[i]) + QStringLiteral("Icons"));
417  mpGroups[i].size = cg.readEntry("Size", 0);
418 
419  if (!mpGroups[i].size && defaultSizesTheme) {
420  mpGroups[i].size = defaultSizesTheme->defaultSize(i);
421  }
422  }
423 }
424 
425 void KIconLoaderPrivate::initIconThemes()
426 {
427  if (mIconThemeInited) {
428  return;
429  }
430  // qCDebug(KICONTHEMES);
431  mIconThemeInited = true;
432 
433  // Add the default theme and its base themes to the theme tree
434  KIconTheme *def = new KIconTheme(KIconTheme::current(), m_appname);
435  if (!def->isValid()) {
436  delete def;
437  // warn, as this is actually a small penalty hit
438  qCDebug(KICONTHEMES) << "Couldn't find current icon theme, falling back to default.";
439  def = new KIconTheme(KIconTheme::defaultThemeName(), m_appname);
440  if (!def->isValid()) {
441  qCDebug(KICONTHEMES) << "Standard icon theme" << KIconTheme::defaultThemeName() << "not found!";
442  delete def;
443  return;
444  }
445  }
446  mpThemeRoot = new KIconThemeNode(def);
447  mThemesInTree.append(def->internalName());
448  links.append(mpThemeRoot);
449  addBaseThemes(mpThemeRoot, m_appname);
450 
451  // Insert application specific themes at the top.
452  searchPaths.append(m_appname + QStringLiteral("/pics"));
453 
454  // Add legacy icon dirs.
455  searchPaths.append(QStringLiteral("icons")); // was xdgdata-icon in KStandardDirs
456  // These are not in the icon spec, but e.g. GNOME puts some icons there anyway.
457  searchPaths.append(QStringLiteral("pixmaps")); // was xdgdata-pixmaps in KStandardDirs
458 }
459 
460 KIconLoader::~KIconLoader() = default;
461 
463 {
464  return d->searchPaths;
465 }
466 
467 void KIconLoader::addAppDir(const QString &appname, const QString &themeBaseDir)
468 {
469  d->searchPaths.append(appname + QStringLiteral("/pics"));
470  d->addAppThemes(appname, themeBaseDir);
471 }
472 
473 void KIconLoaderPrivate::addAppThemes(const QString &appname, const QString &themeBaseDir)
474 {
475  KIconTheme *def = new KIconTheme(QStringLiteral("hicolor"), appname, themeBaseDir);
476  if (!def->isValid()) {
477  delete def;
478  def = new KIconTheme(KIconTheme::defaultThemeName(), appname, themeBaseDir);
479  }
480  KIconThemeNode *node = new KIconThemeNode(def);
481  bool addedToLinks = false;
482 
483  if (!mThemesInTree.contains(appname)) {
484  mThemesInTree.append(appname);
485  links.append(node);
486  addedToLinks = true;
487  }
488  addBaseThemes(node, appname);
489 
490  if (!addedToLinks) {
491  // Nodes in links are being deleted later - this one needs manual care.
492  delete node;
493  }
494 }
495 
496 void KIconLoaderPrivate::addBaseThemes(KIconThemeNode *node, const QString &appname)
497 {
498  // Quote from the icon theme specification:
499  // The lookup is done first in the current theme, and then recursively
500  // in each of the current theme's parents, and finally in the
501  // default theme called "hicolor" (implementations may add more
502  // default themes before "hicolor", but "hicolor" must be last).
503  //
504  // So we first make sure that all inherited themes are added, then we
505  // add the KDE default theme as fallback for all icons that might not be
506  // present in an inherited theme, and hicolor goes last.
507 
508  addInheritedThemes(node, appname);
509  addThemeByName(QIcon::fallbackThemeName(), appname);
510  addThemeByName(QStringLiteral("hicolor"), appname);
511 }
512 
513 void KIconLoaderPrivate::addInheritedThemes(KIconThemeNode *node, const QString &appname)
514 {
515  const QStringList inheritedThemes = node->theme->inherits();
516 
517  for (const auto &inheritedTheme : inheritedThemes) {
518  if (inheritedTheme == QLatin1String("hicolor")) {
519  // The icon theme spec says that "hicolor" must be the very last
520  // of all inherited themes, so don't add it here but at the very end
521  // of addBaseThemes().
522  continue;
523  }
524  addThemeByName(inheritedTheme, appname);
525  }
526 }
527 
528 void KIconLoaderPrivate::addThemeByName(const QString &themename, const QString &appname)
529 {
530  if (mThemesInTree.contains(themename + appname)) {
531  return;
532  }
533  KIconTheme *theme = new KIconTheme(themename, appname);
534  if (!theme->isValid()) {
535  delete theme;
536  return;
537  }
538  KIconThemeNode *n = new KIconThemeNode(theme);
539  mThemesInTree.append(themename + appname);
540  links.append(n);
541  addInheritedThemes(n, appname);
542 }
543 
544 void KIconLoaderPrivate::addExtraDesktopThemes()
545 {
546  if (extraDesktopIconsLoaded) {
547  return;
548  }
549 
552  for (const auto &iconDir : icnlibs) {
553  QDir dir(iconDir);
554  if (!dir.exists()) {
555  continue;
556  }
557  const auto defaultEntries = dir.entryInfoList(QStringList(QStringLiteral("default.*")), QDir::Dirs);
558  for (const auto &defaultEntry : defaultEntries) {
559  if (!QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.desktop")) //
560  && !QFileInfo::exists(defaultEntry.filePath() + QLatin1String("/index.theme"))) {
561  continue;
562  }
563  if (defaultEntry.isSymbolicLink()) {
564  const QString themeName = QDir(defaultEntry.symLinkTarget()).dirName();
565  if (!list.contains(themeName)) {
566  list.append(themeName);
567  }
568  }
569  }
570  }
571 
572  for (const auto &theme : list) {
573  // Don't add the KDE defaults once more, we have them anyways.
574  if (theme == QLatin1String("default.kde") || theme == QLatin1String("default.kde4")) {
575  continue;
576  }
577  addThemeByName(theme, QLatin1String(""));
578  }
579 
580  extraDesktopIconsLoaded = true;
581 }
582 
583 void KIconLoader::drawOverlays(const QStringList &overlays, QPixmap &pixmap, KIconLoader::Group group, int state) const
584 {
585  d->drawOverlays(this, group, state, pixmap, overlays);
586 }
587 
588 QString KIconLoaderPrivate::removeIconExtension(const QString &name) const
589 {
590  if (name.endsWith(QLatin1String(".png")) //
591  || name.endsWith(QLatin1String(".xpm")) //
592  || name.endsWith(QLatin1String(".svg"))) {
593  return name.left(name.length() - 4);
594  } else if (name.endsWith(QLatin1String(".svgz"))) {
595  return name.left(name.length() - 5);
596  }
597 
598  return name;
599 }
600 
601 void KIconLoaderPrivate::normalizeIconMetadata(KIconLoader::Group &group, QSize &size, int &state) const
602 {
603  if ((state < 0) || (state >= KIconLoader::LastState)) {
604  qCWarning(KICONTHEMES) << "Invalid icon state:" << state << ", should be one of KIconLoader::States";
606  }
607 
608  if (size.width() < 0 || size.height() < 0) {
609  size = {};
610  }
611 
612  // For "User" icons, bail early since the size should be based on the size on disk,
613  // which we've already checked.
614  if (group == KIconLoader::User) {
615  return;
616  }
617 
618  if ((group < -1) || (group >= KIconLoader::LastGroup)) {
619  qCWarning(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
620  group = KIconLoader::Desktop;
621  }
622 
623  // If size == 0, use default size for the specified group.
624  if (size.isNull()) {
625  if (group < 0) {
626  qWarning() << "Neither size nor group specified!";
627  group = KIconLoader::Desktop;
628  }
629  size = QSize(mpGroups[group].size, mpGroups[group].size);
630  }
631 }
632 
633 QString KIconLoaderPrivate::makeCacheKey(const QString &name,
634  KIconLoader::Group group,
635  const QStringList &overlays,
636  const QSize &size,
637  qreal scale,
638  int state,
639  const KIconColors &colors) const
640 {
641  // The KSharedDataCache is shared so add some namespacing. The following code
642  // uses QStringBuilder (new in Qt 4.6)
643 
644  /* clang-format off */
645  return (group == KIconLoader::User ? QLatin1String("$kicou_") : QLatin1String("$kico_"))
646  % name
647  % QLatin1Char('_')
648  % (size.width() == size.height() ? QString::number(size.height()) : QString::number(size.height()) % QLatin1Char('x') % QString::number(size.width()))
649  % QLatin1Char('@')
650  % QString::number(scale, 'f', 1)
651  % QLatin1Char('_')
652  % overlays.join(QLatin1Char('_'))
653  % (group >= 0 ? mpEffect.fingerprint(group, state) : NULL_EFFECT_FINGERPRINT())
654  % QLatin1Char('_')
655  % paletteId(colors)
656  % (q->theme() && q->theme()->followsColorScheme() && state == KIconLoader::SelectedState ? QStringLiteral("_selected") : QString());
657  /* clang-format on */
658 }
659 
660 QByteArray KIconLoaderPrivate::processSvg(const QString &path, KIconLoader::States state, const KIconColors &colors) const
661 {
662  std::unique_ptr<QIODevice> device;
663 
664  if (path.endsWith(QLatin1String("svgz"))) {
665  device.reset(new KCompressionDevice(path, KCompressionDevice::GZip));
666  } else {
667  device.reset(new QFile(path));
668  }
669 
670  if (!device->open(QIODevice::ReadOnly)) {
671  return QByteArray();
672  }
673 
674  const QString styleSheet = colors.stylesheet(state);
675  QByteArray processedContents;
676  QXmlStreamReader reader(device.get());
677 
678  QBuffer buffer(&processedContents);
679  buffer.open(QIODevice::WriteOnly);
680  QXmlStreamWriter writer(&buffer);
681  while (!reader.atEnd()) {
682  if (reader.readNext() == QXmlStreamReader::StartElement //
683  && reader.qualifiedName() == QLatin1String("style") //
684  && reader.attributes().value(QLatin1String("id")) == QLatin1String("current-color-scheme")) {
685  writer.writeStartElement(QStringLiteral("style"));
686  writer.writeAttributes(reader.attributes());
687  writer.writeCharacters(styleSheet);
688  writer.writeEndElement();
689  while (reader.tokenType() != QXmlStreamReader::EndElement) {
690  reader.readNext();
691  }
692  } else if (reader.tokenType() != QXmlStreamReader::Invalid) {
693  writer.writeCurrentToken(reader);
694  }
695  }
696  buffer.close();
697 
698  return processedContents;
699 }
700 
701 QImage KIconLoaderPrivate::createIconImage(const QString &path, const QSize &size, qreal scale, KIconLoader::States state, const KIconColors &colors)
702 {
703  // TODO: metadata in the theme to make it do this only if explicitly supported?
704  QImageReader reader;
705  QBuffer buffer;
706 
707  if (q->theme() && q->theme()->followsColorScheme() && (path.endsWith(QLatin1String("svg")) || path.endsWith(QLatin1String("svgz")))) {
708  buffer.setData(processSvg(path, state, colors));
709  reader.setDevice(&buffer);
710  } else {
711  reader.setFileName(path);
712  }
713 
714  if (!reader.canRead()) {
715  return QImage();
716  }
717 
718  if (!size.isNull()) {
719  // ensure we keep aspect ratio
720  const QSize wantedSize = size * scale;
721  QSize finalSize(reader.size());
722  if (finalSize.isNull()) {
723  // nothing to scale
724  finalSize = wantedSize;
725  } else {
726  // like QSvgIconEngine::pixmap try to keep aspect ratio
727  finalSize.scale(wantedSize, Qt::KeepAspectRatio);
728  }
729  reader.setScaledSize(finalSize);
730  }
731 
732  return reader.read();
733 }
734 
735 void KIconLoaderPrivate::insertCachedPixmapWithPath(const QString &key, const QPixmap &data, const QString &path = QString())
736 {
737  // Even if the pixmap is null, we add it to the caches so that we record
738  // the fact that whatever icon led to us getting a null pixmap doesn't
739  // exist.
740 
741  QBuffer output;
742  output.open(QIODevice::WriteOnly);
743 
744  QDataStream outputStream(&output);
745  outputStream.setVersion(QDataStream::Qt_4_6);
746 
747  outputStream << path;
748 
749  // Convert the QPixmap to PNG. This is actually done by Qt's own operator.
750  outputStream << data;
751 
752  output.close();
753 
754  // The byte array contained in the QBuffer is what we want in the cache.
755  mIconCache->insert(key, output.buffer());
756 
757  // Also insert the object into our process-local cache for even more
758  // speed.
759  PixmapWithPath *pixmapPath = new PixmapWithPath;
760  pixmapPath->pixmap = data;
761  pixmapPath->path = path;
762 
763  mPixmapCache.insert(key, pixmapPath, data.width() * data.height() + 1);
764 }
765 
766 bool KIconLoaderPrivate::findCachedPixmapWithPath(const QString &key, QPixmap &data, QString &path)
767 {
768  // If the pixmap is present in our local process cache, use that since we
769  // don't need to decompress and upload it to the X server/graphics card.
770  const PixmapWithPath *pixmapPath = mPixmapCache.object(key);
771  if (pixmapPath) {
772  path = pixmapPath->path;
773  data = pixmapPath->pixmap;
774 
775  return true;
776  }
777 
778  // Otherwise try to find it in our shared memory cache since that will
779  // be quicker than the disk, especially for SVGs.
780  QByteArray result;
781 
782  if (!mIconCache->find(key, &result) || result.isEmpty()) {
783  return false;
784  }
785 
786  QBuffer buffer;
787  buffer.setBuffer(&result);
788  buffer.open(QIODevice::ReadOnly);
789 
790  QDataStream inputStream(&buffer);
791  inputStream.setVersion(QDataStream::Qt_4_6);
792 
793  QString tempPath;
794  inputStream >> tempPath;
795 
796  if (inputStream.status() == QDataStream::Ok) {
797  QPixmap tempPixmap;
798  inputStream >> tempPixmap;
799 
800  if (inputStream.status() == QDataStream::Ok) {
801  data = tempPixmap;
802  path = tempPath;
803 
804  // Since we're here we didn't have a QPixmap cache entry, add one now.
805  PixmapWithPath *newPixmapWithPath = new PixmapWithPath;
806  newPixmapWithPath->pixmap = data;
807  newPixmapWithPath->path = path;
808 
809  mPixmapCache.insert(key, newPixmapWithPath, data.width() * data.height() + 1);
810 
811  return true;
812  }
813  }
814 
815  return false;
816 }
817 
818 QString KIconLoaderPrivate::findMatchingIconWithGenericFallbacks(const QString &name, int size, qreal scale) const
819 {
820  QString path = findMatchingIcon(name, size, scale);
821  if (!path.isEmpty()) {
822  return path;
823  }
824 
825  const QString genericIcon = s_globalData()->genericIconFor(name);
826  if (!genericIcon.isEmpty()) {
827  path = findMatchingIcon(genericIcon, size, scale);
828  }
829  return path;
830 }
831 
832 QString KIconLoaderPrivate::findMatchingIcon(const QString &name, int size, qreal scale) const
833 {
834  // This looks for the exact match and its
835  // generic fallbacks in each themeNode one after the other.
836 
837  // In theory we should only do this for mimetype icons, not for app icons,
838  // but that would require different APIs. The long term solution is under
839  // development for Qt >= 5.8, QFileIconProvider calling QPlatformTheme::fileIcon,
840  // using QMimeType::genericIconName() to get the proper -x-generic fallback.
841  // Once everyone uses that to look up mimetype icons, we can kill the fallback code
842  // from this method.
843 
844  bool genericFallback = name.endsWith(QLatin1String("-x-generic"));;
845  QString path;
846  for (KIconThemeNode *themeNode : std::as_const(links)) {
847  QString currentName = name;
848 
849  while (!currentName.isEmpty()) {
850  path = themeNode->theme->iconPathByName(currentName, size, KIconLoader::MatchBest, scale);
851  if (!path.isEmpty()) {
852  return path;
853  }
854 
855  if (genericFallback) {
856  // we already tested the base name
857  break;
858  }
859 
860  int rindex = currentName.lastIndexOf(QLatin1Char('-'));
861  if (rindex > 1) { // > 1 so that we don't split x-content or x-epoc
862  currentName.truncate(rindex);
863 
864  if (currentName.endsWith(QLatin1String("-x"))) {
865  currentName.chop(2);
866  }
867  } else {
868  // From update-mime-database.c
869  static const QSet<QString> mediaTypes = QSet<QString>{QStringLiteral("text"),
870  QStringLiteral("application"),
871  QStringLiteral("image"),
872  QStringLiteral("audio"),
873  QStringLiteral("inode"),
874  QStringLiteral("video"),
875  QStringLiteral("message"),
876  QStringLiteral("model"),
877  QStringLiteral("multipart"),
878  QStringLiteral("x-content"),
879  QStringLiteral("x-epoc")};
880  // Shared-mime-info spec says:
881  // "If [generic-icon] is not specified then the mimetype is used to generate the
882  // generic icon by using the top-level media type (e.g. "video" in "video/ogg")
883  // and appending "-x-generic" (i.e. "video-x-generic" in the previous example)."
884  if (mediaTypes.contains(currentName)) {
885  currentName += QLatin1String("-x-generic");
886  genericFallback = true;
887  } else {
888  break;
889  }
890  }
891  }
892  }
893 
894  if (path.isEmpty()) {
895  const QStringList fallbackPaths = QIcon::fallbackSearchPaths();
896 
897  for (const QString &path : fallbackPaths) {
898  const QString extensions[] = {QStringLiteral(".png"), QStringLiteral(".svg"), QStringLiteral(".svgz"), QStringLiteral(".xpm")};
899 
900  for (const QString &ext : extensions) {
901  const QString file = path + '/' + name + ext;
902 
903  if (QFileInfo::exists(file)) {
904  return file;
905  }
906  }
907  }
908  }
909 
910  return path;
911 }
912 
913 QString KIconLoaderPrivate::preferredIconPath(const QString &name)
914 {
915  QString path;
916 
917  auto it = mIconAvailability.constFind(name);
918  const auto end = mIconAvailability.constEnd();
919 
920  if (it != end && it.value().isEmpty() && !shouldCheckForUnknownIcons()) {
921  return path; // known to be unavailable
922  }
923 
924  if (it != end) {
925  path = it.value();
926  }
927 
928  if (path.isEmpty()) {
929  path = q->iconPath(name, KIconLoader::Desktop, KIconLoader::MatchBest);
930  mIconAvailability.insert(name, path);
931  }
932 
933  return path;
934 }
935 
936 inline QString KIconLoaderPrivate::unknownIconPath(int size, qreal scale) const
937 {
938  QString path = findMatchingIcon(QStringLiteral("unknown"), size, scale);
939  if (path.isEmpty()) {
940  qCDebug(KICONTHEMES) << "Warning: could not find \"unknown\" icon for size" << size << "at scale" << scale;
941  return QString();
942  }
943  return path;
944 }
945 
946 QString KIconLoaderPrivate::locate(const QString &fileName)
947 {
948  for (const QString &dir : std::as_const(searchPaths)) {
949  const QString path = dir + QLatin1Char('/') + fileName;
950  if (QDir(dir).isAbsolute()) {
951  if (QFileInfo::exists(path)) {
952  return path;
953  }
954  } else {
956  if (!fullPath.isEmpty()) {
957  return fullPath;
958  }
959  }
960  }
961  return QString();
962 }
963 
964 // Finds the absolute path to an icon.
965 
966 QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull) const
967 {
968  return iconPath(_name, group_or_size, canReturnNull, 1 /*scale*/);
969 }
970 
971 QString KIconLoader::iconPath(const QString &_name, int group_or_size, bool canReturnNull, qreal scale) const
972 {
973  // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
974  if (_name.isEmpty() || QDir::isAbsolutePath(_name)) {
975  // we have either an absolute path or nothing to work with
976  return _name;
977  }
978 
979  QString name = d->removeIconExtension(_name);
980 
981  QString path;
982  if (group_or_size == KIconLoader::User) {
983  path = d->locate(name + QLatin1String(".png"));
984  if (path.isEmpty()) {
985  path = d->locate(name + QLatin1String(".svgz"));
986  }
987  if (path.isEmpty()) {
988  path = d->locate(name + QLatin1String(".svg"));
989  }
990  if (path.isEmpty()) {
991  path = d->locate(name + QLatin1String(".xpm"));
992  }
993  return path;
994  }
995 
996  if (group_or_size >= KIconLoader::LastGroup) {
997  qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
998  return path;
999  }
1000 
1001  int size;
1002  if (group_or_size >= 0) {
1003  size = d->mpGroups[group_or_size].size;
1004  } else {
1005  size = -group_or_size;
1006  }
1007 
1008  if (_name.isEmpty()) {
1009  if (canReturnNull) {
1010  return QString();
1011  } else {
1012  return d->unknownIconPath(size, scale);
1013  }
1014  }
1015 
1016  path = d->findMatchingIconWithGenericFallbacks(name, size, scale);
1017 
1018  if (path.isEmpty()) {
1019  // Try "User" group too.
1020  path = iconPath(name, KIconLoader::User, true);
1021  if (!path.isEmpty() || canReturnNull) {
1022  return path;
1023  }
1024 
1025  return d->unknownIconPath(size, scale);
1026  }
1027  return path;
1028 }
1029 
1030 QPixmap
1031 KIconLoader::loadMimeTypeIcon(const QString &_iconName, KIconLoader::Group group, int size, int state, const QStringList &overlays, QString *path_store) const
1032 {
1033  QString iconName = _iconName;
1034  const int slashindex = iconName.indexOf(QLatin1Char('/'));
1035  if (slashindex != -1) {
1036  iconName[slashindex] = QLatin1Char('-');
1037  }
1038 
1039  if (!d->extraDesktopIconsLoaded) {
1040  const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1041  if (!pixmap.isNull()) {
1042  return pixmap;
1043  }
1044  d->addExtraDesktopThemes();
1045  }
1046  const QPixmap pixmap = loadIcon(iconName, group, size, state, overlays, path_store, true);
1047  if (pixmap.isNull()) {
1048  // Icon not found, fallback to application/octet-stream
1049  return loadIcon(QStringLiteral("application-octet-stream"), group, size, state, overlays, path_store, false);
1050  }
1051  return pixmap;
1052 }
1053 
1055  KIconLoader::Group group,
1056  int size,
1057  int state,
1058  const QStringList &overlays,
1059  QString *path_store,
1060  bool canReturnNull) const
1061 {
1062  return loadScaledIcon(_name, group, 1.0 /*scale*/, size, state, overlays, path_store, canReturnNull);
1063 }
1064 
1066  KIconLoader::Group group,
1067  qreal scale,
1068  int size,
1069  int state,
1070  const QStringList &overlays,
1071  QString *path_store,
1072  bool canReturnNull) const
1073 {
1074  return loadScaledIcon(_name, group, scale, QSize(size, size), state, overlays, path_store, canReturnNull);
1075 }
1076 
1078  KIconLoader::Group group,
1079  qreal scale,
1080  const QSize &size,
1081  int state,
1082  const QStringList &overlays,
1083  QString *path_store,
1084  bool canReturnNull) const
1085 {
1086  return loadScaledIcon(_name, group, scale, size, state, overlays, path_store, canReturnNull, {});
1087 }
1088 
1090  KIconLoader::Group group,
1091  qreal scale,
1092  const QSize &_size,
1093  int state,
1094  const QStringList &overlays,
1095  QString *path_store,
1096  bool canReturnNull,
1097  const std::optional<KIconColors> &colors) const
1098 
1099 {
1100  QString name = _name;
1101  bool favIconOverlay = false;
1102 
1103  if (_size.width() < 0 || _size.height() < 0 || _name.isEmpty()) {
1104  return QPixmap();
1105  }
1106 
1107  QSize size = _size;
1108 
1109  /*
1110  * This method works in a kind of pipeline, with the following steps:
1111  * 1. Sanity checks.
1112  * 2. Convert _name, group, size, etc. to a key name.
1113  * 3. Check if the key is already cached.
1114  * 4. If not, initialize the theme and find/load the icon.
1115  * 4a Apply overlays
1116  * 4b Re-add to cache.
1117  */
1118 
1119  // Special case for absolute path icons.
1120  if (name.startsWith(QLatin1String("favicons/"))) {
1121  favIconOverlay = true;
1123  }
1124 
1125  // we need to honor resource :/ paths and QDir::searchPaths => use QDir::isAbsolutePath, see bug 434451
1126  const bool absolutePath = QDir::isAbsolutePath(name);
1127  if (!absolutePath) {
1128  name = d->removeIconExtension(name);
1129  }
1130 
1131  // Don't bother looking for an icon with no name.
1132  if (name.isEmpty()) {
1133  return QPixmap();
1134  }
1135 
1136  // May modify group, size, or state. This function puts them into sane
1137  // states.
1138  d->normalizeIconMetadata(group, size, state);
1139 
1140  // See if the image is already cached.
1141  auto usedColors = colors ? *colors : d->mCustomColors ? d->mColors : KIconColors(qApp->palette());
1142  QString key = d->makeCacheKey(name, group, overlays, size, scale, state, usedColors);
1143  QPixmap pix;
1144 
1145  bool iconWasUnknown = false;
1146  QString path;
1147 
1148  if (d->findCachedPixmapWithPath(key, pix, path)) {
1149  pix.setDevicePixelRatio(scale);
1150 
1151  if (path_store) {
1152  *path_store = path;
1153  }
1154 
1155  if (!path.isEmpty()) {
1156  return pix;
1157  } else {
1158  // path is empty for "unknown" icons, which should be searched for
1159  // anew regularly
1160  if (!d->shouldCheckForUnknownIcons()) {
1161  return canReturnNull ? QPixmap() : pix;
1162  }
1163  }
1164  }
1165 
1166  // Image is not cached... go find it and apply effects.
1167 
1168  favIconOverlay = favIconOverlay && std::min(size.height(), size.width()) > 22;
1169 
1170  // First we look for non-User icons. If we don't find one we'd search in
1171  // the User space anyways...
1172  if (group != KIconLoader::User) {
1173  if (absolutePath && !favIconOverlay) {
1174  path = name;
1175  } else {
1176  path = d->findMatchingIconWithGenericFallbacks(favIconOverlay ? QStringLiteral("text-html") : name, std::min(size.height(), size.width()), scale);
1177  }
1178  }
1179 
1180  if (path.isEmpty()) {
1181  // We do have a "User" icon, or we couldn't find the non-User one.
1182  path = (absolutePath) ? name : iconPath(name, KIconLoader::User, canReturnNull);
1183  }
1184 
1185  // Still can't find it? Use "unknown" if we can't return null.
1186  // We keep going in the function so we can ensure this result gets cached.
1187  if (path.isEmpty() && !canReturnNull) {
1188  path = d->unknownIconPath(std::min(size.height(), size.width()), scale);
1189  iconWasUnknown = true;
1190  }
1191 
1192  QImage img;
1193  if (!path.isEmpty()) {
1194  img = d->createIconImage(path, size, scale, static_cast<KIconLoader::States>(state), usedColors);
1195  }
1196 
1197  if (group >= 0 && group < KIconLoader::LastGroup) {
1198  img = d->mpEffect.apply(img, group, state);
1199  }
1200 
1201  if (favIconOverlay) {
1202  QImage favIcon(name, "PNG");
1203  if (!favIcon.isNull()) { // if favIcon not there yet, don't try to blend it
1204  QPainter p(&img);
1205 
1206  // Align the favicon overlay
1207  QRect r(favIcon.rect());
1208  r.moveBottomRight(img.rect().bottomRight());
1209  r.adjust(-1, -1, -1, -1); // Move off edge
1210 
1211  // Blend favIcon over img.
1212  p.drawImage(r, favIcon);
1213  }
1214  }
1215 
1216  pix = QPixmap::fromImage(img);
1217 
1218  // TODO: If we make a loadIcon that returns the image we can convert
1219  // drawOverlays to use the image instead of pixmaps as well so we don't
1220  // have to transfer so much to the graphics card.
1221  d->drawOverlays(this, group, state, pix, overlays);
1222 
1223  // Don't add the path to our unknown icon to the cache, only cache the
1224  // actual image.
1225  if (iconWasUnknown) {
1226  path.clear();
1227  }
1228 
1229  d->insertCachedPixmapWithPath(key, pix, path);
1230 
1231  if (path_store) {
1232  *path_store = path;
1233  }
1234 
1235  return pix;
1236 }
1237 
1238 KPixmapSequence KIconLoader::loadPixmapSequence(const QString &xdgIconName, int size) const
1239 {
1240  return KPixmapSequence(iconPath(xdgIconName, -size), size);
1241 }
1242 
1243 QMovie *KIconLoader::loadMovie(const QString &name, KIconLoader::Group group, int size, QObject *parent) const
1244 {
1245  QString file = moviePath(name, group, size);
1246  if (file.isEmpty()) {
1247  return nullptr;
1248  }
1249  int dirLen = file.lastIndexOf(QLatin1Char('/'));
1250  const QString icon = iconPath(name, size ? -size : group, true);
1251  if (!icon.isEmpty() && file.left(dirLen) != icon.left(dirLen)) {
1252  return nullptr;
1253  }
1254  QMovie *movie = new QMovie(file, QByteArray(), parent);
1255  if (!movie->isValid()) {
1256  delete movie;
1257  return nullptr;
1258  }
1259  return movie;
1260 }
1261 
1262 QString KIconLoader::moviePath(const QString &name, KIconLoader::Group group, int size) const
1263 {
1264  if (!d->mpGroups) {
1265  return QString();
1266  }
1267 
1268  if ((group < -1 || group >= KIconLoader::LastGroup) && group != KIconLoader::User) {
1269  qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1270  group = KIconLoader::Desktop;
1271  }
1272  if (size == 0 && group < 0) {
1273  qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1274  group = KIconLoader::Desktop;
1275  }
1276 
1277  QString file = name + QStringLiteral(".mng");
1278  if (group == KIconLoader::User) {
1279  file = d->locate(file);
1280  } else {
1281  if (size == 0) {
1282  size = d->mpGroups[group].size;
1283  }
1284 
1285  QString path;
1286 
1287  for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1288  path = themeNode->theme->iconPath(file, size, KIconLoader::MatchExact);
1289  if (!path.isEmpty()) {
1290  break;
1291  }
1292  }
1293 
1294  if (path.isEmpty()) {
1295  for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1296  path = themeNode->theme->iconPath(file, size, KIconLoader::MatchBest);
1297  if (!path.isEmpty()) {
1298  break;
1299  }
1300  }
1301  }
1302 
1303  file = path;
1304  }
1305  return file;
1306 }
1307 
1309 {
1310  QStringList lst;
1311 
1312  if (!d->mpGroups) {
1313  return lst;
1314  }
1315 
1316  d->initIconThemes();
1317 
1318  if ((group < -1) || (group >= KIconLoader::LastGroup)) {
1319  qCDebug(KICONTHEMES) << "Invalid icon group: " << group << ", should be one of KIconLoader::Group";
1320  group = KIconLoader::Desktop;
1321  }
1322  if ((size == 0) && (group < 0)) {
1323  qCDebug(KICONTHEMES) << "Neither size nor group specified!";
1324  group = KIconLoader::Desktop;
1325  }
1326 
1327  QString file = name + QStringLiteral("/0001");
1328  if (group == KIconLoader::User) {
1329  file = d->locate(file + QStringLiteral(".png"));
1330  } else {
1331  if (size == 0) {
1332  size = d->mpGroups[group].size;
1333  }
1334  file = d->findMatchingIcon(file, size, 1); // FIXME scale
1335  }
1336  if (file.isEmpty()) {
1337  return lst;
1338  }
1339 
1340  QString path = file.left(file.length() - 8);
1341  QDir dir(QFile::encodeName(path));
1342  if (!dir.exists()) {
1343  return lst;
1344  }
1345 
1346  const auto entryList = dir.entryList();
1347  for (const QString &entry : entryList) {
1348 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1349  const QStringView chunk = QStringView(entry).left(4);
1350 #else
1351  const QStringRef chunk = entry.leftRef(4);
1352 #endif
1353  if (!chunk.toUInt()) {
1354  continue;
1355  }
1356 
1357  lst += path + entry;
1358  }
1359  lst.sort();
1360  return lst;
1361 }
1362 
1364 {
1365  if (d->mpThemeRoot) {
1366  return d->mpThemeRoot->theme;
1367  }
1368  return nullptr;
1369 }
1370 
1372 {
1373  if (!d->mpGroups) {
1374  return -1;
1375  }
1376 
1377  if (group < 0 || group >= KIconLoader::LastGroup) {
1378  qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1379  return -1;
1380  }
1381  return d->mpGroups[group].size;
1382 }
1383 
1385 {
1386  const QDir dir(iconsDir);
1387  const QStringList formats = QStringList() << QStringLiteral("*.png") << QStringLiteral("*.xpm") << QStringLiteral("*.svg") << QStringLiteral("*.svgz");
1388  const QStringList lst = dir.entryList(formats, QDir::Files);
1389  QStringList result;
1390  for (const auto &file : lst) {
1391  result += iconsDir + QLatin1Char('/') + file;
1392  }
1393  return result;
1394 }
1395 
1397 {
1398  QStringList result;
1399  if (group_or_size >= KIconLoader::LastGroup) {
1400  qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
1401  return result;
1402  }
1403  int size;
1404  if (group_or_size >= 0) {
1405  size = d->mpGroups[group_or_size].size;
1406  } else {
1407  size = -group_or_size;
1408  }
1409 
1410  for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1411  themeNode->queryIconsByContext(&result, size, context);
1412  }
1413 
1414  // Eliminate duplicate entries (same icon in different directories)
1415  QString name;
1416  QStringList res2;
1417  QStringList entries;
1418  for (const auto &icon : std::as_const(result)) {
1419  const int n = icon.lastIndexOf(QLatin1Char('/'));
1420  if (n == -1) {
1421  name = icon;
1422  } else {
1423  name = icon.mid(n + 1);
1424  }
1425  name = d->removeIconExtension(name);
1426  if (!entries.contains(name)) {
1427  entries += name;
1428  res2 += icon;
1429  }
1430  }
1431  return res2;
1432 }
1433 
1435 {
1436  d->initIconThemes();
1437 
1438  QStringList result;
1439  if (group_or_size >= KIconLoader::LastGroup) {
1440  qCDebug(KICONTHEMES) << "Invalid icon group:" << group_or_size;
1441  return result;
1442  }
1443  int size;
1444  if (group_or_size >= 0) {
1445  size = d->mpGroups[group_or_size].size;
1446  } else {
1447  size = -group_or_size;
1448  }
1449 
1450  for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1451  themeNode->queryIcons(&result, size, context);
1452  }
1453 
1454  // Eliminate duplicate entries (same icon in different directories)
1455  QString name;
1456  QStringList res2;
1457  QStringList entries;
1458  for (const auto &icon : std::as_const(result)) {
1459  const int n = icon.lastIndexOf(QLatin1Char('/'));
1460  if (n == -1) {
1461  name = icon;
1462  } else {
1463  name = icon.mid(n + 1);
1464  }
1465  name = d->removeIconExtension(name);
1466  if (!entries.contains(name)) {
1467  entries += name;
1468  res2 += icon;
1469  }
1470  }
1471  return res2;
1472 }
1473 
1474 // used by KIconDialog to find out which contexts to offer in a combobox
1476 {
1477  for (KIconThemeNode *themeNode : std::as_const(d->links)) {
1478  if (themeNode->theme->hasContext(context)) {
1479  return true;
1480  }
1481  }
1482  return false;
1483 }
1484 
1486 {
1487  return &d->mpEffect;
1488 }
1489 
1490 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 82)
1492 {
1493  if (!d->mpGroups) {
1494  return false;
1495  }
1496 
1497  if (group < 0 || group >= KIconLoader::LastGroup) {
1498  qCDebug(KICONTHEMES) << "Invalid icon group:" << group << ", should be one of KIconLoader::Group";
1499  return false;
1500  }
1501  return true;
1502 }
1503 #endif
1504 
1505 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1506 QIcon KIconLoader::loadIconSet(const QString &name, KIconLoader::Group g, int s, bool canReturnNull)
1507 {
1508  QIcon iconset;
1509  QPixmap tmp = loadIcon(name, g, s, KIconLoader::ActiveState, QStringList(), nullptr, canReturnNull);
1510  iconset.addPixmap(tmp, QIcon::Active, QIcon::On);
1511  // we don't use QIconSet's resizing anyway
1512  tmp = loadIcon(name, g, s, KIconLoader::DisabledState, QStringList(), nullptr, canReturnNull);
1513  iconset.addPixmap(tmp, QIcon::Disabled, QIcon::On);
1514  tmp = loadIcon(name, g, s, KIconLoader::DefaultState, QStringList(), nullptr, canReturnNull);
1515  iconset.addPixmap(tmp, QIcon::Normal, QIcon::On);
1516  return iconset;
1517 }
1518 #endif
1519 
1520 // Easy access functions
1521 
1522 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1523 QPixmap DesktopIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1524 {
1525  KIconLoader *loader = KIconLoader::global();
1526  return loader->loadIcon(name, KIconLoader::Desktop, force_size, state, overlays);
1527 }
1528 #endif
1529 
1530 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1531 QIcon DesktopIconSet(const QString &name, int force_size)
1532 {
1533  KIconLoader *loader = KIconLoader::global();
1534  return loader->loadIconSet(name, KIconLoader::Desktop, force_size);
1535 }
1536 #endif
1537 
1538 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1539 QPixmap BarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1540 {
1541  KIconLoader *loader = KIconLoader::global();
1542  return loader->loadIcon(name, KIconLoader::Toolbar, force_size, state, overlays);
1543 }
1544 #endif
1545 
1546 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1547 QIcon BarIconSet(const QString &name, int force_size)
1548 {
1549  KIconLoader *loader = KIconLoader::global();
1550  return loader->loadIconSet(name, KIconLoader::Toolbar, force_size);
1551 }
1552 #endif
1553 
1554 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1555 QPixmap SmallIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1556 {
1557  KIconLoader *loader = KIconLoader::global();
1558  return loader->loadIcon(name, KIconLoader::Small, force_size, state, overlays);
1559 }
1560 #endif
1561 
1562 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1563 QIcon SmallIconSet(const QString &name, int force_size)
1564 {
1565  KIconLoader *loader = KIconLoader::global();
1566  return loader->loadIconSet(name, KIconLoader::Small, force_size);
1567 }
1568 #endif
1569 
1570 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 63)
1571 QPixmap MainBarIcon(const QString &name, int force_size, int state, const QStringList &overlays)
1572 {
1573  KIconLoader *loader = KIconLoader::global();
1574  return loader->loadIcon(name, KIconLoader::MainToolbar, force_size, state, overlays);
1575 }
1576 #endif
1577 
1578 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1579 QIcon MainBarIconSet(const QString &name, int force_size)
1580 {
1581  KIconLoader *loader = KIconLoader::global();
1582  return loader->loadIconSet(name, KIconLoader::MainToolbar, force_size);
1583 }
1584 #endif
1585 
1586 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 65)
1587 QPixmap UserIcon(const QString &name, int state, const QStringList &overlays)
1588 {
1589  KIconLoader *loader = KIconLoader::global();
1590  return loader->loadIcon(name, KIconLoader::User, 0, state, overlays);
1591 }
1592 #endif
1593 
1594 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 0)
1596 {
1597  KIconLoader *loader = KIconLoader::global();
1598  return loader->loadIconSet(name, KIconLoader::User);
1599 }
1600 #endif
1601 
1602 #if KICONTHEMES_BUILD_DEPRECATED_SINCE(5, 66)
1604 {
1605  KIconLoader *loader = KIconLoader::global();
1606  return loader->currentSize(group);
1607 }
1608 #endif
1609 
1611 {
1612  QPixmap pix;
1613  if (QPixmapCache::find(QStringLiteral("unknown"), &pix)) { // krazy:exclude=iconnames
1614  return pix;
1615  }
1616 
1617  const QString path = global()->iconPath(QStringLiteral("unknown"), KIconLoader::Small, true); // krazy:exclude=iconnames
1618  if (path.isEmpty()) {
1619  qCDebug(KICONTHEMES) << "Warning: Cannot find \"unknown\" icon.";
1620  pix = QPixmap(32, 32);
1621  } else {
1622  pix.load(path);
1623  QPixmapCache::insert(QStringLiteral("unknown"), pix); // krazy:exclude=iconnames
1624  }
1625 
1626  return pix;
1627 }
1628 
1629 bool KIconLoader::hasIcon(const QString &name) const
1630 {
1631  return !d->preferredIconPath(name).isEmpty();
1632 }
1633 
1635 {
1636  d->mCustomColors = true;
1637  d->mColors = KIconColors(palette);
1638 }
1639 
1641 {
1642  return d->mCustomColors ? d->mPalette : QPalette();
1643 }
1644 
1646 {
1647  d->mCustomColors = false;
1648 }
1649 
1651 {
1652  return d->mCustomColors;
1653 }
1654 
1655 /*** the global icon loader ***/
1656 Q_GLOBAL_STATIC(KIconLoader, globalIconLoader)
1657 
1659 {
1660  return globalIconLoader();
1661 }
1662 
1664 {
1665  if (global() == this) {
1667  }
1668 
1671 }
1672 
1674 {
1675  s_globalData->emitChange(g);
1676 }
1677 
1678 #include <kiconengine.h>
1679 QIcon KDE::icon(const QString &iconName, KIconLoader *iconLoader)
1680 {
1681  return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global()));
1682 }
1683 
1684 QIcon KDE::icon(const QString &iconName, const QStringList &overlays, KIconLoader *iconLoader)
1685 {
1686  return QIcon(new KIconEngine(iconName, iconLoader ? iconLoader : KIconLoader::global(), overlays));
1687 }
1688 
1689 QIcon KDE::icon(const QString &iconName, const KIconColors &colors, KIconLoader *iconLoader)
1690 {
1691  return QIcon(new KIconEngine(iconName, colors, iconLoader ? iconLoader : KIconLoader::global()));
1692 }
1693 
1694 #include "kiconloader.moc"
1695 #include "moc_kiconloader.moc"
Q_OBJECTQ_OBJECT
void append(const T &value)
bool hasCustomPalette() const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
bool isValid() const
The icon theme exists?
Definition: kicontheme.cpp:430
QByteArray & buffer()
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QPalette customPalette() const
The colors that will be used for the svg stylesheet in case the loaded icons are svg-based,...
void truncate(int position)
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
QString number(int n, int base)
qreal devicePixelRatio() const const
KICONTHEMES_EXPORT QIcon icon(const QString &iconName, KIconLoader *iconLoader=nullptr)
virtual bool open(QIODevice::OpenMode flags) override
QStringList loadAnimated(const QString &name, KIconLoader::Group group, int size=0) const
Loads an animated icon as a series of still frames.
Q_EMITQ_EMIT
KICONTHEMES_EXPORT int IconSize(KIconLoader::Group group)
QSize size() const const
~KIconLoader() override
Cleanup.
bool alphaBlending(KIconLoader::Group group) const
Checks whether the user wants to blend the icons with the background using the alpha channel informat...
bool isValid() const const
QCA_EXPORT void init()
QByteArray encodeName(const QString &fileName)
void setFileName(const QString &fileName)
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void clear()
void setScaledSize(const QSize &size)
void chop(int n)
QString moviePath(const QString &name, KIconLoader::Group group, int size=0) const
Returns the path to an animated icon.
QString fallbackThemeName()
QStringList queryIconsByContext(int group_or_size, KIconLoader::Context context=KIconLoader::Any) const
Queries all available icons for a specific context.
QString writableLocation(QStandardPaths::StandardLocation type)
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.
QRect rect() const const
int width() const const
QPoint bottomRight() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QSize size() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool hasContext(KIconLoader::Context context) const
QStringView left(qsizetype length) const const
QString locate(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
bool exists() 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.
@ Small
Small icons, e.g. for buttons.
Definition: kiconloader.h:137
QString iconPath(const QString &name, int group_or_size, bool canReturnNull=false) const
Returns the path of an icon.
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...
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
@ Desktop
Desktop icons.
Definition: kiconloader.h:129
@ LastGroup
Last group.
Definition: kiconloader.h:144
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
static QString defaultThemeName()
Returns the default icon theme.
Definition: kicontheme.cpp:659
bool send(const QDBusMessage &message) const const
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 setCustomPalette(const QPalette &palette)
Sets the colors for this KIconLoader.
@ DisabledState
Icon is disabled.
Definition: kiconloader.h:175
KICONTHEMES_EXPORT QPixmap SmallIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
QString dirName() const const
KeepAspectRatio
static QString current()
Returns the current icon theme.
Definition: kicontheme.cpp:567
KGuiItem clear()
@ ActiveState
Icon is active.
Definition: kiconloader.h:174
QDBusConnection sessionBus()
int height() const const
static void reconfigure()
Reconfigure the theme.
Definition: kicontheme.cpp:652
void setBuffer(QByteArray *byteArray)
QString stylesheet(KIconLoader::States state) const
bool canRead() const const
bool isEmpty() const const
int length() 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.
static KIconLoader * global()
Returns the global icon loader initialized with the application name.
QImage read()
bool isNull() const const
bool isEmpty() const const
QStringList searchPaths() const
Returns all the search paths for this icon loader, either absolute or relative to GenericDataLocation...
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
int height() const const
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
@ MatchBest
Take the best match if there is no exact match.
Definition: kiconloader.h:118
KSharedConfigPtr config()
KICONTHEMES_EXPORT QIcon BarIconSet(const QString &name, int size=0)
void setDevice(QIODevice *device)
KIconEffect * iconEffect() const
Returns a pointer to the KIconEffect object used by the icon loader.
virtual void close() override
void setData(const QByteArray &data)
bool contains(const T &value) const const
bool isAbsolutePath(const QString &path)
int currentSize(KIconLoader::Group group) const
Returns the current size of the icon group.
bool isNull() const const
Q_SIGNALSQ_SIGNALS
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
uint toUInt(bool *ok, int base) const const
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
QPixmap * find(const QString &key)
KICONTHEMES_EXPORT QPixmap BarIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
MatchType
The type of a match.
Definition: kiconloader.h:116
bool isEmpty() const const
void iconLoaderSettingsChanged()
Emitted by newIconLoader once the new settings have been loaded.
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
KICONTHEMES_EXPORT QIcon SmallIconSet(const QString &name, int size=0)
@ LastState
Last state (last constant)
Definition: kiconloader.h:177
@ Toolbar
Toolbar icons.
Definition: kiconloader.h:133
int defaultSize(KIconLoader::Group group) const
The default size of this theme for a certain icon group.
Definition: kicontheme.cpp:445
QString & insert(int position, QChar ch)
QString left(int n) const const
void setObjectName(const QString &name)
@ MatchExact
Only try to find an exact match.
Definition: kiconloader.h:117
QString name(StandardShortcut id)
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.
@ MainToolbar
Main toolbar icons.
Definition: kiconloader.h:135
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
KICONTHEMES_EXPORT QIcon UserIconSet(const QString &name)
States
Defines the possible states of an icon.
Definition: kiconloader.h:172
KPixmapSequence loadPixmapSequence(const QString &iconName, int size=SizeSmall) const
Loads a pixmapSequence given the xdg icon name.
void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
void addAppDir(const QString &appname, const QString &themeBaseDir=QString())
Adds appname to the list of application specific directories with themeBaseDir as its base directory.
@ User
User icons.
Definition: kiconloader.h:146
Context
Defines the context of the icon.
Definition: kiconloader.h:81
Group
The group of the icon.
Definition: kiconloader.h:125
KIOCORE_EXPORT QString number(KIO::filesize_t size)
void scale(int width, int height, Qt::AspectRatioMode mode)
static void emitChange(Group group)
Emits an iconChanged() signal on all the KIconLoader instances in the system indicating that a system...
A class to provide rendering of KDE icons.
Definition: kiconengine.h:34
QMovie * loadMovie(const QString &name, KIconLoader::Group group, int size=0, QObject *parent=nullptr) const
Loads an animated icon.
QString internalName() const
The internal name of the icon theme (same as the name argument passed to the constructor).
Definition: kicontheme.cpp:400
@ DefaultState
The default state.
Definition: kiconloader.h:173
QString mid(int position, int n) const const
KICONTHEMES_EXPORT QIcon MainBarIconSet(const QString &name, int size=0)
QStringList queryIconsByDir(const QString &iconsDir) const
Returns a list of all icons (*.png or *.xpm extension) in the given directory.
bool insert(const QString &key, const QPixmap &pixmap)
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.
KICONTHEMES_EXPORT QPixmap UserIcon(const QString &name, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
QRgb rgba() const const
KICONTHEMES_EXPORT QPixmap MainBarIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
KICONTHEMES_EXPORT QPixmap DesktopIcon(const QString &name, int size=0, int state=KIconLoader::DefaultState, const QStringList &overlays=QStringList())
QObject * parent() const const
KIconTheme * theme() const
Returns a pointer to the current theme.
@ FirstGroup
First group.
Definition: kiconloader.h:131
QString message
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.
const QList< QKeySequence > & end()
int width() const const
void setDevicePixelRatio(qreal scaleFactor)
static QPixmap unknown()
Returns the unknown icon.
void resetPalette()
Resets the custom palette used by the KIconLoader to use the QGuiApplication::palette() again (and to...
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
void newIconLoader()
Re-initialize the global icon loader.
void sort(Qt::CaseSensitivity cs)
QStringList fallbackSearchPaths()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Sat Oct 1 2022 04:02:39 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.