forked from qt-creator/qt-creator
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user