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 connect(qApp, SIGNAL(aboutToQuit()), SLOT(cleanupOnQuit()));
76}
77
79
81
83{
84 if (QThread::currentThread() != qApp->thread()) {
85 qWarning() << "KisRecentFileIconCache::instance() called from non-GUI thread!";
86 return nullptr;
87 }
88 return s_instance;
89}
90
92{
93 const QMap<QUrl, CacheItem>::const_iterator findItem = m_iconCacheMap.constFind(url);
94 if (findItem != m_iconCacheMap.constEnd()) {
95 // If the icon is still being fetched, this returns `QIcon()`.
96 return findItem.value().cachedIcon;
97 } else {
98 if (!url.isLocalFile()) {
99 // We don't fetch thumbnails for non-local files.
100 return QIcon();
101 }
103 const GetFileIconParameters param = {
104 url, // m_documentUrl
105 iconSize, // m_iconSize
106 1.0, // m_devicePixelRatioF
107 };
108 QFuture<IconFetchResult> future = QtConcurrent::run(&m_iconFetchThreadPool, getFileIcon, param);
109 auto *watcher = new QFutureWatcher<IconFetchResult>(this);
110 watcher->setFuture(future);
111 connect(watcher, SIGNAL(finished()), SLOT(iconFetched()));
112 connect(watcher, SIGNAL(canceled()), SLOT(futureCanceled()));
113 const CacheItem cacheItem = { url, future, QIcon() };
114 m_iconCacheMap.insert(url, cacheItem);
115 return QIcon();
116 }
117}
118
120{
121 QMap<QUrl, CacheItem>::iterator findItem = m_iconCacheMap.find(url);
122 if (findItem == m_iconCacheMap.end()) {
123 return;
124 }
125 // Note: Futures returned by `QtConcurrent::run` does not support
126 // cancellation, but we try anyway.
127 if (!findItem.value().fetchingFuture.isCanceled()) {
128 findItem.value().fetchingFuture.cancel();
129 }
130 m_iconCacheMap.erase(findItem);
131}
132
134{
137}
138
140{
141 // We need to wait for the icon fetching to finish before letting qApp
142 // be deleted, because the icon generation relies on qApp.
143 m_iconFetchThreadPool.clear();
144 m_iconFetchThreadPool.waitForDone();
145}
146
148{
149 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
150 if (!watcher) {
151 qWarning() << "KisRecentFileIconCache::iconFetched() called but sender is not a QFutureWatcher";
152 return;
153 }
154 QFuture<IconFetchResult> future = watcher->future();
155 watcher->deleteLater();
156 IconFetchResult result = future.result();
157 auto findItem = m_iconCacheMap.find(result.m_documentUrl);
158 if (findItem == m_iconCacheMap.end()) {
159 qWarning() << "KisRecentFileIconCache item not found!";
160 return;
161 }
162#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
163 if (findItem.value().fetchingFuture != future) {
164 qWarning() << "KisRecentFileIconCache item has a different QFuture";
165 return;
166 }
167#endif
168 findItem.value().fetchingFuture = QFuture<IconFetchResult>();
169 if (result.m_iconWasFetchedOk) {
170 findItem.value().cachedIcon = result.m_icon;
171 Q_EMIT fileIconChanged(result.m_documentUrl, result.m_icon);
172 }
173}
174
176{
177 auto *watcher = dynamic_cast<QFutureWatcher<IconFetchResult> *>(QObject::sender());
178 if (!watcher) {
179 qWarning() << "KisRecentFileIconCache::futureCanceled() called but sender is not a QFutureWatcher";
180 return;
181 }
182 watcher->deleteLater();
183}
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
PythonPluginManager * instance
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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