1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qquickimageselector_p.h" |
5 | |
6 | #include <QtCore/qdir.h> |
7 | #include <QtCore/qfileinfo.h> |
8 | #include <QtCore/qcache.h> |
9 | #include <QtCore/qloggingcategory.h> |
10 | #include <QtCore/qfileselector.h> |
11 | #include <QtQml/qqmlfile.h> |
12 | #include <QtQml/private/qqmlproperty_p.h> |
13 | #include <algorithm> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | Q_LOGGING_CATEGORY(lcQtQuickControlsImageSelector, "qt.quick.controls.imageselector" ) |
18 | |
19 | static const int DEFAULT_CACHE = 500; |
20 | |
21 | static inline int cacheSize() |
22 | { |
23 | static bool ok = false; |
24 | static const int size = qEnvironmentVariableIntValue(varName: "QT_QUICK_CONTROLS_IMAGESELECTOR_CACHE" , ok: &ok); |
25 | return ok ? size : DEFAULT_CACHE; |
26 | } |
27 | |
28 | // input: [focused, pressed] |
29 | // => [[focused, pressed], [pressed, focused], [focused], [pressed]] |
30 | static QList<QStringList> permutations(const QStringList &input, int count = -1) |
31 | { |
32 | if (count == -1) |
33 | count = input.size(); |
34 | |
35 | QList<QStringList> output; |
36 | for (int i = 0; i < input.size(); ++i) { |
37 | QStringList sub = input.mid(pos: i, len: count); |
38 | |
39 | if (count > 1) { |
40 | if (i + count > input.size()) |
41 | sub += input.mid(pos: 0, len: count - i + 1); |
42 | |
43 | std::sort(first: sub.begin(), last: sub.end()); |
44 | do { |
45 | if (!sub.isEmpty()) |
46 | output += sub; |
47 | } while (std::next_permutation(first: sub.begin(), last: sub.end())); |
48 | } else { |
49 | output += sub; |
50 | } |
51 | |
52 | if (count == input.size()) |
53 | break; |
54 | } |
55 | |
56 | if (count > 1) |
57 | output += permutations(input, count: --count); |
58 | |
59 | return output; |
60 | } |
61 | |
62 | static QString findFile(const QDir &dir, const QString &baseName, const QStringList &extensions) |
63 | { |
64 | for (const QString &ext : extensions) { |
65 | QString filePath = dir.filePath(fileName: baseName + QLatin1Char('.') + ext); |
66 | if (QFile::exists(fileName: filePath)) |
67 | return QFileSelector().select(filePath); |
68 | } |
69 | // return an empty string to indicate that the lookup has been done |
70 | // even if no matching asset was found |
71 | return QLatin1String("" ); |
72 | } |
73 | |
74 | QQuickImageSelector::QQuickImageSelector(QObject *parent) |
75 | : QObject(parent), |
76 | m_cache(cacheSize() > 0) |
77 | { |
78 | } |
79 | |
80 | QUrl QQuickImageSelector::source() const |
81 | { |
82 | return m_source; |
83 | } |
84 | |
85 | void QQuickImageSelector::setSource(const QUrl &source) |
86 | { |
87 | if (m_property.isValid()) |
88 | QQmlPropertyPrivate::write(that: m_property, source, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding); |
89 | if (m_source == source) |
90 | return; |
91 | |
92 | m_source = source; |
93 | emit sourceChanged(); |
94 | } |
95 | |
96 | QString QQuickImageSelector::name() const |
97 | { |
98 | return m_name; |
99 | } |
100 | |
101 | void QQuickImageSelector::setName(const QString &name) |
102 | { |
103 | if (m_name == name) |
104 | return; |
105 | |
106 | m_name = name; |
107 | if (m_complete) |
108 | updateSource(); |
109 | } |
110 | |
111 | QString QQuickImageSelector::path() const |
112 | { |
113 | return m_path; |
114 | } |
115 | |
116 | void QQuickImageSelector::setPath(const QString &path) |
117 | { |
118 | if (m_path == path) |
119 | return; |
120 | |
121 | m_path = path; |
122 | if (m_complete) |
123 | updateSource(); |
124 | } |
125 | |
126 | QVariantList QQuickImageSelector::states() const |
127 | { |
128 | return m_allStates; |
129 | } |
130 | |
131 | void QQuickImageSelector::setStates(const QVariantList &states) |
132 | { |
133 | if (m_allStates == states) |
134 | return; |
135 | |
136 | m_allStates = states; |
137 | if (updateActiveStates() && m_complete) |
138 | updateSource(); |
139 | } |
140 | |
141 | QString QQuickImageSelector::separator() const |
142 | { |
143 | return m_separator; |
144 | } |
145 | |
146 | void QQuickImageSelector::setSeparator(const QString &separator) |
147 | { |
148 | if (m_separator == separator) |
149 | return; |
150 | |
151 | m_separator = separator; |
152 | if (m_complete) |
153 | updateSource(); |
154 | } |
155 | |
156 | bool QQuickImageSelector::cache() const |
157 | { |
158 | return m_cache; |
159 | } |
160 | |
161 | void QQuickImageSelector::setCache(bool cache) |
162 | { |
163 | m_cache = cache; |
164 | } |
165 | |
166 | void QQuickImageSelector::write(const QVariant &value) |
167 | { |
168 | setUrl(value.toUrl()); |
169 | } |
170 | |
171 | void QQuickImageSelector::setTarget(const QQmlProperty &property) |
172 | { |
173 | m_property = property; |
174 | } |
175 | |
176 | void QQuickImageSelector::classBegin() |
177 | { |
178 | } |
179 | |
180 | void QQuickImageSelector::componentComplete() |
181 | { |
182 | setUrl(m_property.read().toUrl()); |
183 | m_complete = true; |
184 | updateSource(); |
185 | } |
186 | |
187 | QStringList QQuickImageSelector::fileExtensions() const |
188 | { |
189 | static const QStringList extensions = QStringList() << QStringLiteral("png" ); |
190 | return extensions; |
191 | } |
192 | |
193 | QString QQuickImageSelector::cacheKey() const |
194 | { |
195 | if (!m_cache) |
196 | return QString(); |
197 | |
198 | return m_path + m_name + m_activeStates.join(sep: m_separator); |
199 | } |
200 | |
201 | void QQuickImageSelector::updateSource() |
202 | { |
203 | static QCache<QString, QString> cache(cacheSize()); |
204 | |
205 | const QString key = cacheKey(); |
206 | |
207 | QString bestFilePath; |
208 | |
209 | if (m_cache) { |
210 | QString *cachedPath = cache.object(key); |
211 | if (cachedPath) |
212 | bestFilePath = *cachedPath; |
213 | } |
214 | |
215 | // note: a cached file path may be empty |
216 | if (bestFilePath.isNull()) { |
217 | QDir dir(m_path); |
218 | int bestScore = -1; |
219 | |
220 | const QStringList extensions = fileExtensions(); |
221 | |
222 | const QList<QStringList> statePerms = permutations(input: m_activeStates); |
223 | for (const QStringList &perm : statePerms) { |
224 | const QString filePath = findFile(dir, baseName: m_name + m_separator + perm.join(sep: m_separator), extensions); |
225 | if (!filePath.isEmpty()) { |
226 | int score = calculateScore(states: perm); |
227 | if (score > bestScore) { |
228 | bestScore = score; |
229 | bestFilePath = filePath; |
230 | } |
231 | } |
232 | } |
233 | |
234 | if (bestFilePath.isEmpty()) |
235 | bestFilePath = findFile(dir, baseName: m_name, extensions); |
236 | |
237 | if (m_cache) |
238 | cache.insert(key, object: new QString(bestFilePath)); |
239 | } |
240 | |
241 | qCDebug(lcQtQuickControlsImageSelector) << m_name << m_activeStates << "->" << bestFilePath; |
242 | |
243 | if (bestFilePath.startsWith(c: QLatin1Char(':'))) |
244 | setSource(QUrl(QLatin1String("qrc" ) + bestFilePath)); |
245 | else |
246 | setSource(QUrl::fromLocalFile(localfile: bestFilePath)); |
247 | } |
248 | |
249 | void QQuickImageSelector::setUrl(const QUrl &url) |
250 | { |
251 | QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(url)); |
252 | setName(fileInfo.fileName()); |
253 | setPath(fileInfo.path()); |
254 | } |
255 | |
256 | bool QQuickImageSelector::updateActiveStates() |
257 | { |
258 | QStringList active; |
259 | for (const QVariant &v : std::as_const(t&: m_allStates)) { |
260 | const QVariantMap state = v.toMap(); |
261 | if (state.isEmpty()) |
262 | continue; |
263 | auto it = state.begin(); |
264 | if (it.value().toBool()) |
265 | active += it.key(); |
266 | } |
267 | |
268 | if (m_activeStates == active) |
269 | return false; |
270 | |
271 | m_activeStates = active; |
272 | return true; |
273 | } |
274 | |
275 | int QQuickImageSelector::calculateScore(const QStringList &states) const |
276 | { |
277 | int score = 0; |
278 | for (int i = 0; i < states.size(); ++i) |
279 | score += (m_activeStates.size() - m_activeStates.indexOf(str: states.at(i))) << 1; |
280 | return score; |
281 | } |
282 | |
283 | QQuickNinePatchImageSelector::QQuickNinePatchImageSelector(QObject *parent) |
284 | : QQuickImageSelector(parent) |
285 | { |
286 | } |
287 | |
288 | QStringList QQuickNinePatchImageSelector::fileExtensions() const |
289 | { |
290 | static const QStringList extensions = QStringList() << QStringLiteral("9.png" ) << QStringLiteral("png" ); |
291 | return extensions; |
292 | } |
293 | |
294 | QQuickAnimatedImageSelector::QQuickAnimatedImageSelector(QObject *parent) |
295 | : QQuickImageSelector(parent) |
296 | { |
297 | } |
298 | |
299 | QStringList QQuickAnimatedImageSelector::fileExtensions() const |
300 | { |
301 | static const QStringList extensions = QStringList() << QStringLiteral("webp" ) << QStringLiteral("gif" ); |
302 | return extensions; |
303 | } |
304 | |
305 | QT_END_NAMESPACE |
306 | |
307 | #include "moc_qquickimageselector_p.cpp" |
308 | |