summaryrefslogtreecommitdiffstats
path: root/src/gui/image
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/image')
-rw-r--r--src/gui/image/qicon.cpp50
-rw-r--r--src/gui/image/qicon.h3
-rw-r--r--src/gui/image/qiconengine.cpp17
-rw-r--r--src/gui/image/qiconengine.h3
-rw-r--r--src/gui/image/qiconloader.cpp168
-rw-r--r--src/gui/image/qiconloader_p.h5
-rw-r--r--src/gui/image/qimage.h4
-rw-r--r--src/gui/image/qpixmapcache.cpp10
-rw-r--r--src/gui/image/qpixmapcache.h1
9 files changed, 239 insertions, 22 deletions
diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp
index b2d9ed18f5..cc00f5c172 100644
--- a/src/gui/image/qicon.cpp
+++ b/src/gui/image/qicon.cpp
@@ -919,7 +919,7 @@ void QIcon::paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment,
*/
bool QIcon::isNull() const
{
- return !d;
+ return !d || d->engine->isNull();
}
/*!\internal
@@ -934,7 +934,12 @@ bool QIcon::isDetached() const
void QIcon::detach()
{
if (d) {
- if (d->ref.load() != 1) {
+ if (d->engine->isNull()) {
+ if (!d->ref.deref())
+ delete d;
+ d = 0;
+ return;
+ } else if (d->ref.load() != 1) {
QIconPrivate *x = new QIconPrivate;
x->engine = d->engine->clone();
if (!d->ref.deref())
@@ -958,11 +963,10 @@ void QIcon::addPixmap(const QPixmap &pixmap, Mode mode, State state)
{
if (pixmap.isNull())
return;
+ detach();
if (!d) {
d = new QIconPrivate;
d->engine = new QPixmapIconEngine;
- } else {
- detach();
}
d->engine->addPixmap(pixmap, mode, state);
}
@@ -1002,6 +1006,7 @@ void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State
{
if (fileName.isEmpty())
return;
+ detach();
if (!d) {
#ifndef QT_NO_LIBRARY
QFileInfo info(fileName);
@@ -1024,8 +1029,6 @@ void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State
d = new QIconPrivate;
d->engine = new QPixmapIconEngine;
}
- } else {
- detach();
}
d->engine->addFile(fileName, size, mode, state);
@@ -1135,8 +1138,7 @@ QString QIcon::themeName()
\since 4.6
Returns the QIcon corresponding to \a name in the current
- icon theme. If no such icon is found in the current theme
- \a fallback is returned instead.
+ icon theme.
The latest version of the freedesktop icon specification and naming
specification can be obtained here:
@@ -1150,19 +1152,18 @@ QString QIcon::themeName()
\snippet code/src_gui_image_qicon.cpp 3
- Or if you want to provide a guaranteed fallback for platforms that
- do not support theme icons, you can use the second argument:
-
- \snippet code/src_gui_image_qicon.cpp 4
-
\note By default, only X11 will support themed icons. In order to
use themed icons on Mac and Windows, you will have to bundle a
compliant theme in one of your themeSearchPaths() and set the
appropriate themeName().
+ \note Qt will make use of GTK's icon-theme.cache if present to speed up
+ the lookup. These caches can be generated using gtk-update-icon-cache:
+ \l{https://developer.gnome.org/gtk3/stable/gtk-update-icon-cache.html}.
+
\sa themeName(), setThemeName(), themeSearchPaths()
*/
-QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
+QIcon QIcon::fromTheme(const QString &name)
{
QIcon icon;
@@ -1178,7 +1179,26 @@ QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
qtIconCache()->insert(name, cachedIcon);
}
- if (qApp && icon.availableSizes().isEmpty())
+ return icon;
+}
+
+/*!
+ \overload
+
+ Returns the QIcon corresponding to \a name in the current
+ icon theme. If no such icon is found in the current theme
+ \a fallback is returned instead.
+
+ If you want to provide a guaranteed fallback for platforms that
+ do not support theme icons, you can use the second argument:
+
+ \snippet code/src_gui_image_qicon.cpp 4
+*/
+QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
+{
+ QIcon icon = fromTheme(name);
+
+ if (icon.isNull() || icon.availableSizes().isEmpty())
return fallback;
return icon;
diff --git a/src/gui/image/qicon.h b/src/gui/image/qicon.h
index 9ed7336502..989e40bbb5 100644
--- a/src/gui/image/qicon.h
+++ b/src/gui/image/qicon.h
@@ -105,7 +105,8 @@ public:
void setIsMask(bool isMask);
bool isMask() const;
- static QIcon fromTheme(const QString &name, const QIcon &fallback = QIcon());
+ static QIcon fromTheme(const QString &name);
+ static QIcon fromTheme(const QString &name, const QIcon &fallback);
static bool hasThemeIcon(const QString &name);
static QStringList themeSearchPaths();
diff --git a/src/gui/image/qiconengine.cpp b/src/gui/image/qiconengine.cpp
index c09933d45f..7411dbb054 100644
--- a/src/gui/image/qiconengine.cpp
+++ b/src/gui/image/qiconengine.cpp
@@ -150,6 +150,11 @@ void QIconEngine::addFile(const QString &/*fileName*/, const QSize &/*size*/, QI
icon, for example when instantiating an icon using
QIcon::fromTheme().
+ \value IsNullHook Allow to query if this engine represents a null
+ icon. The \a data argument of the virtual_hook() is a pointer to a
+ bool that can be set to true if the icon is null. This enum value
+ was added in Qt 5.7.
+
\sa virtual_hook()
*/
@@ -283,4 +288,16 @@ QString QIconEngine::iconName() const
return name;
}
+/*!
+ \since 5.7
+
+ Returns true if this icon engine represent a null QIcon.
+ */
+bool QIconEngine::isNull() const
+{
+ bool isNull = false;
+ const_cast<QIconEngine *>(this)->virtual_hook(QIconEngine::IsNullHook, &isNull);
+ return isNull;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/image/qiconengine.h b/src/gui/image/qiconengine.h
index 9977113054..6c45cd216f 100644
--- a/src/gui/image/qiconengine.h
+++ b/src/gui/image/qiconengine.h
@@ -58,7 +58,7 @@ public:
virtual bool read(QDataStream &in);
virtual bool write(QDataStream &out) const;
- enum IconEngineHook { AvailableSizesHook = 1, IconNameHook };
+ enum IconEngineHook { AvailableSizesHook = 1, IconNameHook, IsNullHook };
struct AvailableSizesArgument
{
@@ -71,6 +71,7 @@ public:
QIcon::State state = QIcon::Off) const;
virtual QString iconName() const;
+ bool isNull() const; // ### Qt6 make virtual
virtual void virtual_hook(int id, void *data);
};
diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp
index 3ead72dfbb..ecce7f9967 100644
--- a/src/gui/image/qiconloader.cpp
+++ b/src/gui/image/qiconloader.cpp
@@ -155,6 +155,141 @@ QStringList QIconLoader::themeSearchPaths() const
return m_iconDirs;
}
+/*!
+ \class QIconCacheGtkReader
+ \internal
+ Helper class that reads and looks up into the icon-theme.cache generated with
+ gtk-update-icon-cache. If at any point we detect a corruption in the file
+ (because the offsets point at wrong locations for example), the reader
+ is marked as invalid.
+*/
+class QIconCacheGtkReader
+{
+public:
+ explicit QIconCacheGtkReader(const QString &themeDir);
+ QVector<const char *> lookup(const QString &);
+ bool isValid() const { return m_isValid; }
+private:
+ QFile m_file;
+ const unsigned char *m_data;
+ quint64 m_size;
+ bool m_isValid;
+
+ quint16 read16(uint offset)
+ {
+ if (offset > m_size - 2 || (offset & 0x1)) {
+ m_isValid = false;
+ return 0;
+ }
+ return m_data[offset+1] | m_data[offset] << 8;
+ }
+ quint32 read32(uint offset)
+ {
+ if (offset > m_size - 4 || (offset & 0x3)) {
+ m_isValid = false;
+ return 0;
+ }
+ return m_data[offset+3] | m_data[offset+2] << 8
+ | m_data[offset+1] << 16 | m_data[offset] << 24;
+ }
+};
+
+
+QIconCacheGtkReader::QIconCacheGtkReader(const QString &dirName)
+ : m_isValid(false)
+{
+ QFileInfo info(dirName + QLatin1Literal("/icon-theme.cache"));
+ if (!info.exists() || info.lastModified() < QFileInfo(dirName).lastModified())
+ return;
+ m_file.setFileName(info.absoluteFilePath());
+ if (!m_file.open(QFile::ReadOnly))
+ return;
+ m_size = m_file.size();
+ m_data = m_file.map(0, m_size);
+ if (!m_data)
+ return;
+ if (read16(0) != 1) // VERSION_MAJOR
+ return;
+
+ m_isValid = true;
+
+ // Check that all the directories are older than the cache
+ auto lastModified = info.lastModified();
+ quint32 dirListOffset = read32(8);
+ quint32 dirListLen = read32(dirListOffset);
+ for (uint i = 0; i < dirListLen; ++i) {
+ quint32 offset = read32(dirListOffset + 4 + 4 * i);
+ if (!m_isValid || offset >= m_size || lastModified < QFileInfo(dirName + QLatin1Char('/')
+ + QString::fromUtf8(reinterpret_cast<const char*>(m_data + offset))).lastModified()) {
+ m_isValid = false;
+ return;
+ }
+ }
+}
+
+static quint32 icon_name_hash(const char *p)
+{
+ quint32 h = static_cast<signed char>(*p);
+ for (p += 1; *p != '\0'; p++)
+ h = (h << 5) - h + *p;
+ return h;
+}
+
+/*! \internal
+ lookup the icon name and return the list of subdirectories in which an icon
+ with this name is present. The char* are pointers to the mapped data.
+ For example, this would return { "32x32/apps", "24x24/apps" , ... }
+ */
+QVector<const char *> QIconCacheGtkReader::lookup(const QString &name)
+{
+ QVector<const char *> ret;
+ if (!isValid())
+ return ret;
+
+ QByteArray nameUtf8 = name.toUtf8();
+ quint32 hash = icon_name_hash(nameUtf8);
+
+ quint32 hashOffset = read32(4);
+ quint32 hashBucketCount = read32(hashOffset);
+
+ if (!isValid() || hashBucketCount == 0) {
+ m_isValid = false;
+ return ret;
+ }
+
+ quint32 bucketIndex = hash % hashBucketCount;
+ quint32 bucketOffset = read32(hashOffset + 4 + bucketIndex * 4);
+ while (bucketOffset > 0 && bucketOffset <= m_size - 12) {
+ quint32 nameOff = read32(bucketOffset + 4);
+ if (nameOff < m_size && strcmp(reinterpret_cast<const char*>(m_data + nameOff), nameUtf8) == 0) {
+ quint32 dirListOffset = read32(8);
+ quint32 dirListLen = read32(dirListOffset);
+
+ quint32 listOffset = read32(bucketOffset+8);
+ quint32 listLen = read32(listOffset);
+
+ if (!m_isValid || listOffset + 4 + 8 * listLen > m_size) {
+ m_isValid = false;
+ return ret;
+ }
+
+ ret.reserve(listLen);
+ for (uint j = 0; j < listLen && m_isValid; ++j) {
+ quint32 dirIndex = read16(listOffset + 4 + 8 * j);
+ quint32 o = read32(dirListOffset + 4 + dirIndex*4);
+ if (!m_isValid || dirIndex >= dirListLen || o >= m_size) {
+ m_isValid = false;
+ return ret;
+ }
+ ret.append(reinterpret_cast<const char*>(m_data) + o);
+ }
+ return ret;
+ }
+ bucketOffset = read32(bucketOffset);
+ }
+ return ret;
+}
+
QIconTheme::QIconTheme(const QString &themeName)
: m_valid(false)
{
@@ -166,8 +301,10 @@ QIconTheme::QIconTheme(const QString &themeName)
QString themeDir = iconDir.path() + QLatin1Char('/') + themeName;
QFileInfo themeDirInfo(themeDir);
- if (themeDirInfo.isDir())
+ if (themeDirInfo.isDir()) {
m_contentDirs << themeDir;
+ m_gtkCaches << QSharedPointer<QIconCacheGtkReader>::create(themeDir);
+ }
if (!m_valid) {
themeIndex.setFileName(themeDir + QLatin1String("/index.theme"));
@@ -257,7 +394,6 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
}
const QStringList contentDirs = theme.contentDirs();
- const QVector<QIconDirInfo> subDirs = theme.keyList();
QString iconNameFallback = iconName;
@@ -268,6 +404,29 @@ QThemeIconInfo QIconLoader::findIconHelper(const QString &themeName,
// Add all relevant files
for (int i = 0; i < contentDirs.size(); ++i) {
+ QVector<QIconDirInfo> subDirs = theme.keyList();
+
+ // Try to reduce the amount of subDirs by looking in the GTK+ cache in order to save
+ // a massive amount of file stat (especially if the icon is not there)
+ auto cache = theme.m_gtkCaches.at(i);
+ if (cache->isValid()) {
+ auto result = cache->lookup(iconNameFallback);
+ if (cache->isValid()) {
+ const QVector<QIconDirInfo> subDirsCopy = subDirs;
+ subDirs.clear();
+ subDirs.reserve(result.count());
+ foreach (const char *s, result) {
+ QString path = QString::fromUtf8(s);
+ auto it = std::find_if(subDirsCopy.cbegin(), subDirsCopy.cend(),
+ [&](const QIconDirInfo &info) {
+ return info.path == path; } );
+ if (it != subDirsCopy.cend()) {
+ subDirs.append(*it);
+ }
+ }
+ }
+ }
+
QString contentDir = contentDirs.at(i) + QLatin1Char('/');
for (int j = 0; j < subDirs.size() ; ++j) {
const QIconDirInfo &dirInfo = subDirs.at(j);
@@ -587,6 +746,11 @@ void QIconLoaderEngine::virtual_hook(int id, void *data)
name = m_info.iconName;
}
break;
+ case QIconEngine::IsNullHook:
+ {
+ *reinterpret_cast<bool*>(data) = m_info.entries.isEmpty();
+ }
+ break;
default:
QIconEngine::virtual_hook(id, data);
}
diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h
index ccf0a9d438..193154e44e 100644
--- a/src/gui/image/qiconloader_p.h
+++ b/src/gui/image/qiconloader_p.h
@@ -139,6 +139,8 @@ private:
friend class QIconLoader;
};
+class QIconCacheGtkReader;
+
class QIconTheme
{
public:
@@ -148,12 +150,13 @@ public:
QVector<QIconDirInfo> keyList() { return m_keyList; }
QStringList contentDirs() { return m_contentDirs; }
bool isValid() { return m_valid; }
-
private:
QStringList m_contentDirs;
QVector<QIconDirInfo> m_keyList;
QStringList m_parents;
bool m_valid;
+public:
+ QVector<QSharedPointer<QIconCacheGtkReader>> m_gtkCaches;
};
class Q_GUI_EXPORT QIconLoader
diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h
index 888c7beb32..9d8e3efcc4 100644
--- a/src/gui/image/qimage.h
+++ b/src/gui/image/qimage.h
@@ -168,9 +168,9 @@ public:
Format format() const;
#if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(QT_COMPILING_QIMAGE_COMPAT_CPP)
- QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) const & Q_REQUIRED_RESULT
+ Q_ALWAYS_INLINE QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) const & Q_REQUIRED_RESULT
{ return convertToFormat_helper(f, flags); }
- QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) && Q_REQUIRED_RESULT
+ Q_ALWAYS_INLINE QImage convertToFormat(Format f, Qt::ImageConversionFlags flags = Qt::AutoColor) && Q_REQUIRED_RESULT
{
if (convertToFormat_inplace(f, flags))
return std::move(*this);
diff --git a/src/gui/image/qpixmapcache.cpp b/src/gui/image/qpixmapcache.cpp
index d29ddcf978..6265a0c16d 100644
--- a/src/gui/image/qpixmapcache.cpp
+++ b/src/gui/image/qpixmapcache.cpp
@@ -156,6 +156,16 @@ bool QPixmapCache::Key::operator ==(const Key &key) const
*/
/*!
+ Returns \c true if there is a cached pixmap associated with this key.
+ Otherwise, if pixmap was flushed, the key is no longer valid.
+ \since 5.7
+*/
+bool QPixmapCache::Key::isValid() const Q_DECL_NOTHROW
+{
+ return d && d->isValid;
+}
+
+/*!
\internal
*/
QPixmapCache::Key &QPixmapCache::Key::operator =(const Key &other)
diff --git a/src/gui/image/qpixmapcache.h b/src/gui/image/qpixmapcache.h
index 37a0588e06..ca18f299a7 100644
--- a/src/gui/image/qpixmapcache.h
+++ b/src/gui/image/qpixmapcache.h
@@ -59,6 +59,7 @@ public:
Key &operator =(const Key &other);
void swap(Key &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
+ bool isValid() const Q_DECL_NOTHROW;
private:
KeyData *d;