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