Krita Source Code Documentation
Loading...
Searching...
No Matches
KisRecentFileIconCache.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021 Felipe Lema <felipelema@mortemale.org>
3 * SPDX-FileCopyrightText: 2022 Alvin Wong <alvin@alvinhc.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <QtConcurrent>
11#include <QApplication>
12#include <QGlobalStatic>
13
14#include "KisFileIconCreator.h"
16
17
18namespace {
19 /*
20 * Parameters for fetching a file icon
21 */
22 struct GetFileIconParameters
23 {
24 QUrl m_documentUrl;
25 QSize m_iconSize;
26 qreal m_devicePixelRatioF;
27 };
28
32 struct IconFetchResult
33 {
34 bool m_iconWasFetchedOk = false;
38 QUrl m_documentUrl;
42 QIcon m_icon;
43 };
44
45
46 IconFetchResult getFileIcon(GetFileIconParameters gfip)
47 {
48 KisFileIconCreator iconCreator;
49 IconFetchResult iconFetched;
50 iconFetched.m_documentUrl = gfip.m_documentUrl;
51 iconFetched.m_iconWasFetchedOk = iconCreator.createFileIcon(gfip.m_documentUrl.toLocalFile(),
52 iconFetched.m_icon,
53 gfip.m_devicePixelRatioF,
54 gfip.m_iconSize);
55 return iconFetched;
56 }
57} /* namespace */
58
59
66
68{
69 // Limit the number of threads used for icon fetching to prevent it from
70 // impacting normal usage too much, and to prevent it from consuming too
71 // much memory by loading too many large files.
72 if (QThread::idealThreadCount() > 2) {
73 m_iconFetchThreadPool.setMaxThreadCount(2);
74 }
75 if (qApp) {
76 m_devicePixelRatioF = qMax(qApp->devicePixelRatio(), 1.0);
77 }
78 connect(qApp, SIGNAL(aboutToQuit()), SLOT(cleanupOnQuit()));
79}
80
82
84
86{
87 if (QThread::currentThread() != qApp->thread()) {
88 qWarning() << "KisRecentFileIconCache::instance() called from non-GUI thread!";
89 return nullptr;
90 }
91 return s_instance;
92}
93
95{
96 const QMap<QUrl, CacheItem>::const_iterator findItem = m_iconCacheMap.constFind(url);
97 if (findItem != m_iconCacheMap.constEnd()) {
98 // If the icon is still being fetched, this returns `QIcon()`.
99 return findItem.value().cachedIcon;
100 } else {
101 if (!url.isLocalFile()) {
102 // We don't fetch thumbnails for non-local files.
103 return QIcon();
104 }
106 const GetFileIconParameters param = {
107 url, // m_documentUrl
108 iconSize, // m_iconSize
109 m_devicePixelRatioF, // m_devicePixelRatioF
110 };
111 QFuture<IconFetchResult> future = QtConcurrent::run(&m_iconFetchThreadPool, getFileIcon, param);
112 auto *watcher = new QFutureWatcher<IconFetchResult>(this);
113 watcher->setFuture(future);
114 connect(watcher, SIGNAL(finished()), SLOT(iconFetched()));
115 connect(watcher, SIGNAL(canceled()), SLOT(futureCanceled()));
116 const CacheItem cacheItem = { url, future, QIcon() };
117 m_iconCacheMap.insert(url, cacheItem);
118 return QIcon();
119 }
120}
121
123{
124 QMap<QUrl, CacheItem>::iterator findItem = m_iconCacheMap.find(url);
125 if (findItem == m_iconCacheMap.end()) {
126 return;
127 }
128 // Note: Futures returned by `QtConcurrent::run` does not support
129 // cancellation, but we try anyway.
130 if (!findItem.value().fetchingFuture.isCanceled()) {
131 findItem.value().fetchingFuture.cancel();
132 }
133 m_iconCacheMap.erase(findItem);
134}
135
137{
140}
141
143{
144 // We need to wait for the icon fetching to finish before letting qApp
145 // be deleted, because the icon generation relies on qApp.
146 m_iconFetchThreadPool.clear();
147 m_iconFetchThreadPool.waitForDone();
148}
149
151{
152 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
153 if (!watcher) {
154 qWarning() << "KisRecentFileIconCache::iconFetched() called but sender is not a QFutureWatcher";
155 return;
156 }
157 QFuture<IconFetchResult> future = watcher->future();
158 watcher->deleteLater();
159 IconFetchResult result = future.result();
160 auto findItem = m_iconCacheMap.find(result.m_documentUrl);
161 if (findItem == m_iconCacheMap.end()) {
162 qWarning() << "KisRecentFileIconCache item not found!";
163 return;
164 }
165#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
166 if (findItem.value().fetchingFuture != future) {
167 qWarning() << "KisRecentFileIconCache item has a different QFuture";
168 return;
169 }
170#endif
171 findItem.value().fetchingFuture = QFuture<IconFetchResult>();
172 if (result.m_iconWasFetchedOk) {
173 findItem.value().cachedIcon = result.m_icon;
174 Q_EMIT fileIconChanged(result.m_documentUrl, result.m_icon);
175 }
176}
177
179{
180 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
181 if (!watcher) {
182 qWarning() << "KisRecentFileIconCache::futureCanceled() called but sender is not a QFutureWatcher";
183 return;
184 }
185 watcher->deleteLater();
186}
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
PythonPluginManager * instance
int iconSize(qreal width, qreal height)
The KisFileIconCreator class creates a thumbnail from a file on disk.
bool createFileIcon(QString path, QIcon &icon, qreal devicePixelRatioF, QSize iconSize) override
createFileIcon creates an icon from the file on disk
void fileIconChanged(const QUrl &url, const QIcon &icon)
QIcon getOrQueueFileIcon(const QUrl &url)
QMap< QUrl, CacheItem > m_iconCacheMap
void invalidateFileIcon(const QUrl &url)
void reloadFileIcon(const QUrl &url)
QFuture< IconFetchResult > fetchingFuture