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