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
15QT_BEGIN_NAMESPACE
16
17Q_LOGGING_CATEGORY(lcQtQuickControlsImageSelector, "qt.quick.controls.imageselector")
18
19static const int DEFAULT_CACHE = 500;
20
21static 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]]
30static 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
62static 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
74QQuickImageSelector::QQuickImageSelector(QObject *parent)
75 : QObject(parent),
76 m_cache(cacheSize() > 0)
77{
78}
79
80QUrl QQuickImageSelector::source() const
81{
82 return m_source;
83}
84
85void 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
96QString QQuickImageSelector::name() const
97{
98 return m_name;
99}
100
101void 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
111QString QQuickImageSelector::path() const
112{
113 return m_path;
114}
115
116void 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
126QVariantList QQuickImageSelector::states() const
127{
128 return m_allStates;
129}
130
131void 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
141QString QQuickImageSelector::separator() const
142{
143 return m_separator;
144}
145
146void 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
156bool QQuickImageSelector::cache() const
157{
158 return m_cache;
159}
160
161void QQuickImageSelector::setCache(bool cache)
162{
163 m_cache = cache;
164}
165
166void QQuickImageSelector::write(const QVariant &value)
167{
168 setUrl(value.toUrl());
169}
170
171void QQuickImageSelector::setTarget(const QQmlProperty &property)
172{
173 m_property = property;
174}
175
176void QQuickImageSelector::classBegin()
177{
178}
179
180void QQuickImageSelector::componentComplete()
181{
182 setUrl(m_property.read().toUrl());
183 m_complete = true;
184 updateSource();
185}
186
187QStringList QQuickImageSelector::fileExtensions() const
188{
189 static const QStringList extensions = QStringList() << QStringLiteral("png");
190 return extensions;
191}
192
193QString 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
201void 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
249void QQuickImageSelector::setUrl(const QUrl &url)
250{
251 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(url));
252 setName(fileInfo.fileName());
253 setPath(fileInfo.path());
254}
255
256bool 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
275int 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
283QQuickNinePatchImageSelector::QQuickNinePatchImageSelector(QObject *parent)
284 : QQuickImageSelector(parent)
285{
286}
287
288QStringList QQuickNinePatchImageSelector::fileExtensions() const
289{
290 static const QStringList extensions = QStringList() << QStringLiteral("9.png") << QStringLiteral("png");
291 return extensions;
292}
293
294QQuickAnimatedImageSelector::QQuickAnimatedImageSelector(QObject *parent)
295 : QQuickImageSelector(parent)
296{
297}
298
299QStringList QQuickAnimatedImageSelector::fileExtensions() const
300{
301 static const QStringList extensions = QStringList() << QStringLiteral("webp") << QStringLiteral("gif");
302 return extensions;
303}
304
305QT_END_NAMESPACE
306
307#include "moc_qquickimageselector_p.cpp"
308

source code of qtdeclarative/src/quickcontrolsimpl/qquickimageselector.cpp