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 <QtConcurrentRun>
11#include <QApplication>
12#include <QGlobalStatic>
13#include <QFutureWatcher>
14
15#include "KisFileIconCreator.h"
17
18
19namespace {
20 /*
21 * Parameters for fetching a file icon
22 */
23 struct GetFileIconParameters
24 {
25 QUrl m_documentUrl;
26 QSize m_iconSize;
27 qreal m_devicePixelRatioF;
28 };
29
33 struct IconFetchResult
34 {
35 bool m_iconWasFetchedOk = false;
39 QUrl m_documentUrl;
43 QIcon m_icon;
44 };
45
46
47 IconFetchResult getFileIcon(GetFileIconParameters gfip)
48 {
49 KisFileIconCreator iconCreator;
50 IconFetchResult iconFetched;
51 iconFetched.m_documentUrl = gfip.m_documentUrl;
52 iconFetched.m_iconWasFetchedOk = iconCreator.createFileIcon(gfip.m_documentUrl.toLocalFile(),
53 iconFetched.m_icon,
54 gfip.m_devicePixelRatioF,
55 gfip.m_iconSize);
56 return iconFetched;
57 }
58} /* namespace */
59
60
67
69{
70 // Limit the number of threads used for icon fetching to prevent it from
71 // impacting normal usage too much, and to prevent it from consuming too
72 // much memory by loading too many large files.
73 if (QThread::idealThreadCount() > 2) {
74 m_iconFetchThreadPool.setMaxThreadCount(2);
75 }
76 if (qApp) {
77 m_devicePixelRatioF = qMax(qApp->devicePixelRatio(), 1.0);
78 }
79 connect(qApp, SIGNAL(aboutToQuit()), SLOT(cleanupOnQuit()));
80}
81
83
85
87{
88 if (QThread::currentThread() != qApp->thread()) {
89 qWarning() << "KisRecentFileIconCache::instance() called from non-GUI thread!";
90 return nullptr;
91 }
92 return s_instance;
93}
94
96{
97 const QMap<QUrl, CacheItem>::const_iterator findItem = m_iconCacheMap.constFind(url);
98 if (findItem != m_iconCacheMap.constEnd()) {
99 // If the icon is still being fetched, this returns `QIcon()`.
100 return findItem.value().cachedIcon;
101 } else {
102 if (!url.isLocalFile()) {
103 // We don't fetch thumbnails for non-local files.
104 return QIcon();
105 }
107 const GetFileIconParameters param = {
108 url, // m_documentUrl
109 iconSize, // m_iconSize
110 m_devicePixelRatioF, // m_devicePixelRatioF
111 };
112 QFuture<IconFetchResult> future = QtConcurrent::run(&m_iconFetchThreadPool, getFileIcon, param);
113 auto *watcher = new QFutureWatcher<IconFetchResult>(this);
114 watcher->setFuture(future);
115 connect(watcher, SIGNAL(finished()), SLOT(iconFetched()));
116 connect(watcher, SIGNAL(canceled()), SLOT(futureCanceled()));
117 const CacheItem cacheItem = { url, future, QIcon() };
118 m_iconCacheMap.insert(url, cacheItem);
119 return QIcon();
120 }
121}
122
124{
125 QMap<QUrl, CacheItem>::iterator findItem = m_iconCacheMap.find(url);
126 if (findItem == m_iconCacheMap.end()) {
127 return;
128 }
129 // Note: Futures returned by `QtConcurrent::run` does not support
130 // cancellation, but we try anyway.
131 if (!findItem.value().fetchingFuture.isCanceled()) {
132 findItem.value().fetchingFuture.cancel();
133 }
134 m_iconCacheMap.erase(findItem);
135}
136
138{
141}
142
144{
145 // We need to wait for the icon fetching to finish before letting qApp
146 // be deleted, because the icon generation relies on qApp.
147 m_iconFetchThreadPool.clear();
148 m_iconFetchThreadPool.waitForDone();
149}
150
152{
153 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
154 if (!watcher) {
155 qWarning() << "KisRecentFileIconCache::iconFetched() called but sender is not a QFutureWatcher";
156 return;
157 }
158 QFuture<IconFetchResult> future = watcher->future();
159 watcher->deleteLater();
160 IconFetchResult result = future.result();
161 auto findItem = m_iconCacheMap.find(result.m_documentUrl);
162 if (findItem == m_iconCacheMap.end()) {
163 qWarning() << "KisRecentFileIconCache item not found!";
164 return;
165 }
166#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
167 if (findItem.value().fetchingFuture != future) {
168 qWarning() << "KisRecentFileIconCache item has a different QFuture";
169 return;
170 }
171#endif
172 findItem.value().fetchingFuture = QFuture<IconFetchResult>();
173 if (result.m_iconWasFetchedOk) {
174 findItem.value().cachedIcon = result.m_icon;
175 Q_EMIT fileIconChanged(result.m_documentUrl, result.m_icon);
176 }
177}
178
180{
181 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
182 if (!watcher) {
183 qWarning() << "KisRecentFileIconCache::futureCanceled() called but sender is not a QFutureWatcher";
184 return;
185 }
186 watcher->deleteLater();
187}
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