Projects: Create QIcons in the UI thread

Creating QIcons elsewhere is not safe because of image reader plugin
loading and the pixmap cache.

Fixes: QTCREATORBUG-25301
Change-Id: Ia22a0cd571f808d7f5c639353fdf2e548743f8ca
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Eike Ziller
2021-04-08 16:31:31 +02:00
parent f4ab1279fd
commit bce81fd992
9 changed files with 116 additions and 41 deletions

View File

@@ -46,17 +46,14 @@ CMakeInputsNode::CMakeInputsNode(const Utils::FilePath &cmakeLists) :
{
setPriority(Node::DefaultPriority - 10); // Bottom most!
setDisplayName(QCoreApplication::translate("CMakeFilesProjectNode", "CMake Modules"));
static const QIcon modulesIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_MODULES);
setIcon(modulesIcon);
setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_MODULES));
setListInProject(false);
}
CMakeListsNode::CMakeListsNode(const Utils::FilePath &cmakeListPath) :
ProjectExplorer::ProjectNode(cmakeListPath)
{
static QIcon folderIcon = Core::FileIconProvider::directoryIcon(Constants::FILE_OVERLAY_CMAKE);
setIcon(folderIcon);
setIcon(DirectoryIcon(Constants::FILE_OVERLAY_CMAKE));
setListInProject(false);
}
@@ -74,9 +71,7 @@ CMakeProjectNode::CMakeProjectNode(const Utils::FilePath &directory) :
ProjectExplorer::ProjectNode(directory)
{
setPriority(Node::DefaultProjectPriority + 1000);
static const QIcon productIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_PRODUCT);
setIcon(productIcon);
setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_PRODUCT));
setListInProject(false);
}
@@ -90,7 +85,7 @@ CMakeTargetNode::CMakeTargetNode(const Utils::FilePath &directory, const QString
{
m_target = target;
setPriority(Node::DefaultProjectPriority + 900);
setIcon(QIcon(":/projectexplorer/images/build.png")); // TODO: Use proper icon!
setIcon(":/projectexplorer/images/build.png"); // TODO: Use proper icon!
setListInProject(false);
setProductType(ProductType::Other);
}

View File

@@ -474,7 +474,8 @@ FolderNode *createSourceGroupNode(const QString &sourceGroupName,
if (!existingNode) {
auto node = createCMakeVFolder(sourceDirectory, Node::DefaultFolderPriority + 5, p);
node->setListInProject(false);
node->setIcon(QIcon::fromTheme("edit-copy", ::Utils::Icons::COPY.icon()));
node->setIcon(
[] { return QIcon::fromTheme("edit-copy", ::Utils::Icons::COPY.icon()); });
existingNode = node.get();

View File

@@ -182,14 +182,12 @@ void addHeaderNodes(ProjectNode *root,
if (root->isEmpty())
return;
static QIcon headerNodeIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_H);
auto headerNode = std::make_unique<VirtualFolderNode>(root->filePath());
headerNode->setPriority(Node::DefaultPriority - 5);
headerNode->setDisplayName(
QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
"<Headers>"));
headerNode->setIcon(headerNodeIcon);
headerNode->setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_H));
// Add scanned headers:
for (const FileNode *fn : allFiles) {
@@ -212,15 +210,13 @@ void addFileSystemNodes(ProjectNode *root, const QList<const FileNode *> &allFil
{
QTC_ASSERT(root, return );
static QIcon fileSystemNodeIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_UNKNOWN);
auto fileSystemNode = std::make_unique<VirtualFolderNode>(root->filePath());
// just before special nodes like "CMake Modules"
fileSystemNode->setPriority(Node::DefaultPriority - 6);
fileSystemNode->setDisplayName(
QCoreApplication::translate("CMakeProjectManager::Internal::ProjectTreeHelper",
"<File System>"));
fileSystemNode->setIcon(fileSystemNodeIcon);
fileSystemNode->setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_UNKNOWN));
for (const FileNode *fn : allFiles) {
if (!fn->filePath().isChildOf(root->filePath()))

View File

@@ -38,18 +38,15 @@ namespace Internal {
MesonProjectNode::MesonProjectNode(const Utils::FilePath &directory)
: ProjectExplorer::ProjectNode{directory}
{
static const auto MesonIcon = QIcon(Constants::Icons::MESON);
setPriority(Node::DefaultProjectPriority + 1000);
setIcon(MesonIcon);
setIcon(Constants::Icons::MESON);
setListInProject(false);
}
MesonFileNode::MesonFileNode(const Utils::FilePath &file)
: ProjectExplorer::ProjectNode{file}
{
static const auto MesonFolderIcon = Core::FileIconProvider::directoryIcon(
Constants::Icons::MESON);
setIcon(MesonFolderIcon);
setIcon(ProjectExplorer::DirectoryIcon(Constants::Icons::MESON));
setListInProject(true);
}
@@ -58,7 +55,7 @@ MesonTargetNode::MesonTargetNode(const Utils::FilePath &directory, const QString
, m_name{name}
{
setPriority(Node::DefaultProjectPriority + 900);
setIcon(QIcon(":/projectexplorer/images/build.png"));
setIcon(":/projectexplorer/images/build.png");
setListInProject(false);
setShowWhenEmpty(true);
setProductType(ProjectExplorer::ProductType::Other);

View File

@@ -46,16 +46,19 @@
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <QFileInfo>
#include <QDir>
#include <QFileInfo>
#include <QIcon>
#include <QStyle>
#include <QThread>
#include <QTimer>
#include <memory>
namespace ProjectExplorer {
QHash<QString, QIcon> DirectoryIcon::m_cache;
static FolderNode *recursiveFindOrCreateFolderNode(FolderNode *folder,
const Utils::FilePath &directory,
const Utils::FilePath &overrideBaseDir,
@@ -482,15 +485,27 @@ QString FolderNode::displayName() const
/*!
Contains the icon that should be used in a view. Default is the directory icon
(QStyle::S_PDirIcon).
s\a setIcon()
(QStyle::S_PDirIcon). Calling this method is only safe in the UI thread.
\sa setIcon()
*/
QIcon FolderNode::icon() const
{
QTC_CHECK(QThread::currentThread() == QCoreApplication::instance()->thread());
// Instantiating the Icon provider is expensive.
if (m_icon.isNull())
if (auto strPtr = Utils::get_if<QString>(&m_icon)) {
m_icon = QIcon(*strPtr);
} else if (auto directoryIconPtr = Utils::get_if<DirectoryIcon>(&m_icon)) {
m_icon = directoryIconPtr->icon();
} else if (auto creatorPtr = Utils::get_if<IconCreator>(&m_icon)) {
m_icon = (*creatorPtr)();
} else {
auto iconPtr = Utils::get_if<QIcon>(&m_icon);
if (!iconPtr || iconPtr->isNull())
m_icon = Core::FileIconProvider::icon(QFileIconProvider::Folder);
return m_icon;
}
return Utils::get<QIcon>(m_icon);
}
Node *FolderNode::findNode(const std::function<bool(Node *)> &filter)
@@ -724,11 +739,38 @@ void FolderNode::setDisplayName(const QString &name)
m_displayName = name;
}
/*!
Sets the \a icon for this node. Note that creating QIcon instances is only safe in the UI thread.
*/
void FolderNode::setIcon(const QIcon &icon)
{
m_icon = icon;
}
/*!
Sets the \a directoryIcon that is used to create the icon for this node on demand.
*/
void FolderNode::setIcon(const DirectoryIcon &directoryIcon)
{
m_icon = directoryIcon;
}
/*!
Sets the \a path that is used to create the icon for this node on demand.
*/
void FolderNode::setIcon(const QString &path)
{
m_icon = path;
}
/*!
Sets the \a iconCreator function that is used to create the icon for this node on demand.
*/
void FolderNode::setIcon(const IconCreator &iconCreator)
{
m_icon = iconCreator;
}
void FolderNode::setLocationInfo(const QVector<FolderNode::LocationInfo> &info)
{
m_locations = info;
@@ -1048,4 +1090,34 @@ void ContainerNode::handleSubTreeChanged(FolderNode *node)
m_project->handleSubTreeChanged(node);
}
/*!
\class ProjectExplorer::DirectoryIcon
The DirectoryIcon class represents a directory icon with an overlay.
The QIcon is created on demand and globally cached, so other DirectoryIcon
instances with the same overlay share the same QIcon instance.
*/
/*!
Creates a DirectoryIcon for the specified \a overlay.
*/
DirectoryIcon::DirectoryIcon(const QString &overlay)
: m_overlay(overlay)
{}
/*!
Returns the icon for this DirectoryIcon. Calling this method is only safe in the UI thread.
*/
QIcon DirectoryIcon::icon() const
{
QTC_CHECK(QThread::currentThread() == QCoreApplication::instance()->thread());
const auto it = m_cache.find(m_overlay);
if (it != m_cache.end())
return it.value();
const QIcon icon = Core::FileIconProvider::directoryIcon(m_overlay);
m_cache.insert(m_overlay, icon);
return icon;
}
} // namespace ProjectExplorer

View File

@@ -34,6 +34,7 @@
#include <utils/fileutils.h>
#include <utils/id.h>
#include <utils/optional.h>
#include <utils/variant.h>
#include <functional>
@@ -93,6 +94,20 @@ class FolderNode;
class ProjectNode;
class ContainerNode;
class PROJECTEXPLORER_EXPORT DirectoryIcon
{
public:
explicit DirectoryIcon(const QString &overlay);
QIcon icon() const; // only safe in UI thread
private:
QString m_overlay;
static QHash<QString, QIcon> m_cache;
};
using IconCreator = std::function<QIcon()>;
// Documentation inside.
class PROJECTEXPLORER_EXPORT Node
{
@@ -218,6 +233,7 @@ public:
explicit FolderNode(const Utils::FilePath &folderPath);
QString displayName() const override;
// only safe from UI thread
QIcon icon() const;
bool isFolderNodeType() const override { return true; }
@@ -253,7 +269,11 @@ public:
bool replaceSubtree(Node *oldNode, std::unique_ptr<Node> &&newNode);
void setDisplayName(const QString &name);
// you have to make sure the QIcon is created in the UI thread if you are calling setIcon(QIcon)
void setIcon(const QIcon &icon);
void setIcon(const DirectoryIcon &directoryIcon);
void setIcon(const QString &path);
void setIcon(const IconCreator &iconCreator);
class LocationInfo
{
@@ -328,7 +348,7 @@ private:
QString m_displayName;
QString m_addFileFilter;
mutable QIcon m_icon;
mutable Utils::variant<QIcon, DirectoryIcon, QString, IconCreator> m_icon;
bool m_showWhenEmpty = false;
};

View File

@@ -73,8 +73,7 @@ const QbsProductNode *parentQbsProductNode(const ProjectExplorer::Node *node)
QbsGroupNode::QbsGroupNode(const QJsonObject &grp) : ProjectNode(FilePath()), m_groupData(grp)
{
static QIcon groupIcon = QIcon(QString(ProjectExplorer::Constants::FILEOVERLAY_GROUP));
setIcon(groupIcon);
setIcon(ProjectExplorer::Constants::FILEOVERLAY_GROUP);
setDisplayName(grp.value("name").toString());
setEnabled(grp.value("is-enabled").toBool());
}
@@ -108,9 +107,7 @@ QVariant QbsGroupNode::data(Id role) const
QbsProductNode::QbsProductNode(const QJsonObject &prd) : ProjectNode(FilePath()), m_productData(prd)
{
static QIcon productIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_PRODUCT);
setIcon(productIcon);
setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_PRODUCT));
if (prd.value("is-runnable").toBool()) {
setProductType(ProductType::App);
} else {
@@ -265,9 +262,7 @@ QJsonObject QbsProductNode::mainGroup() const
QbsProjectNode::QbsProjectNode(const QJsonObject &projectData)
: ProjectNode(FilePath()), m_projectData(projectData)
{
static QIcon projectIcon = Core::FileIconProvider::directoryIcon(
ProjectExplorer::Constants::FILEOVERLAY_QT);
setIcon(projectIcon);
setIcon(DirectoryIcon(ProjectExplorer::Constants::FILEOVERLAY_QT));
setDisplayName(projectData.value("name").toString());
}

View File

@@ -40,8 +40,7 @@ QmlProjectNode::QmlProjectNode(Project *project)
{
setDisplayName(project->projectFilePath().toFileInfo().completeBaseName());
static QIcon qmlProjectIcon = Core::FileIconProvider::directoryIcon(":/projectexplorer/images/fileoverlay_qml.png");
setIcon(qmlProjectIcon);
setIcon(DirectoryIcon(":/projectexplorer/images/fileoverlay_qml.png"));
}
} // namespace Internal

View File

@@ -243,7 +243,7 @@ ResourceTopLevelNode::ResourceTopLevelNode(const FilePath &filePath,
const QString &contents)
: FolderNode(filePath)
{
setIcon(FileIconProvider::icon(filePath.toFileInfo()));
setIcon([filePath] { return FileIconProvider::icon(filePath.toFileInfo()); });
setPriority(Node::DefaultFilePriority);
setListInProject(true);
setAddFileFilter("*.png; *.jpg; *.gif; *.svg; *.ico; *.qml; *.qml.ui");