1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Controls 2 module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "qquickimageselector_p.h"
38
39#include <QtCore/qdir.h>
40#include <QtCore/qfileinfo.h>
41#include <QtCore/qcache.h>
42#include <QtCore/qloggingcategory.h>
43#include <QtCore/qfileselector.h>
44#include <QtQml/qqmlfile.h>
45#include <QtQml/private/qqmlproperty_p.h>
46#include <algorithm>
47
48QT_BEGIN_NAMESPACE
49
50static const int DEFAULT_CACHE = 500;
51
52static inline int cacheSize()
53{
54 static bool ok = false;
55 static const int size = qEnvironmentVariableIntValue(varName: "QT_QUICK_CONTROLS_IMAGINE_CACHE", ok: &ok);
56 return ok ? size : DEFAULT_CACHE;
57}
58
59Q_DECLARE_LOGGING_CATEGORY(lcQtQuickControlsImagine)
60
61// input: [focused, pressed]
62// => [[focused, pressed], [pressed, focused], [focused], [pressed]]
63static QList<QStringList> permutations(const QStringList &input, int count = -1)
64{
65 if (count == -1)
66 count = input.count();
67
68 QList<QStringList> output;
69 for (int i = 0; i < input.count(); ++i) {
70 QStringList sub = input.mid(pos: i, alength: count);
71
72 if (count > 1) {
73 if (i + count > input.count())
74 sub += input.mid(pos: 0, alength: count - i + 1);
75
76 std::sort(first: sub.begin(), last: sub.end());
77 do {
78 if (!sub.isEmpty())
79 output += sub;
80 } while (std::next_permutation(first: sub.begin(), last: sub.end()));
81 } else {
82 output += sub;
83 }
84
85 if (count == input.count())
86 break;
87 }
88
89 if (count > 1)
90 output += permutations(input, count: --count);
91
92 return output;
93}
94
95static QString findFile(const QDir &dir, const QString &baseName, const QStringList &extensions)
96{
97 for (const QString &ext : extensions) {
98 QString filePath = dir.filePath(fileName: baseName + QLatin1Char('.') + ext);
99 if (QFile::exists(fileName: filePath))
100 return QFileSelector().select(filePath);
101 }
102 // return an empty string to indicate that the lookup has been done
103 // even if no matching asset was found
104 return QLatin1String("");
105}
106
107QQuickImageSelector::QQuickImageSelector(QObject *parent)
108 : QObject(parent),
109 m_cache(cacheSize() > 0)
110{
111}
112
113QUrl QQuickImageSelector::source() const
114{
115 return m_source;
116}
117
118void QQuickImageSelector::setSource(const QUrl &source)
119{
120 if (m_property.isValid())
121 QQmlPropertyPrivate::write(that: m_property, source, QQmlPropertyData::BypassInterceptor | QQmlPropertyData::DontRemoveBinding);
122 if (m_source == source)
123 return;
124
125 m_source = source;
126 emit sourceChanged();
127}
128
129QString QQuickImageSelector::name() const
130{
131 return m_name;
132}
133
134void QQuickImageSelector::setName(const QString &name)
135{
136 if (m_name == name)
137 return;
138
139 m_name = name;
140 if (m_complete)
141 updateSource();
142}
143
144QString QQuickImageSelector::path() const
145{
146 return m_path;
147}
148
149void QQuickImageSelector::setPath(const QString &path)
150{
151 if (m_path == path)
152 return;
153
154 m_path = path;
155 if (m_complete)
156 updateSource();
157}
158
159QVariantList QQuickImageSelector::states() const
160{
161 return m_allStates;
162}
163
164void QQuickImageSelector::setStates(const QVariantList &states)
165{
166 if (m_allStates == states)
167 return;
168
169 m_allStates = states;
170 if (updateActiveStates() && m_complete)
171 updateSource();
172}
173
174QString QQuickImageSelector::separator() const
175{
176 return m_separator;
177}
178
179void QQuickImageSelector::setSeparator(const QString &separator)
180{
181 if (m_separator == separator)
182 return;
183
184 m_separator = separator;
185 if (m_complete)
186 updateSource();
187}
188
189bool QQuickImageSelector::cache() const
190{
191 return m_cache;
192}
193
194void QQuickImageSelector::setCache(bool cache)
195{
196 m_cache = cache;
197}
198
199void QQuickImageSelector::write(const QVariant &value)
200{
201 setUrl(value.toUrl());
202}
203
204void QQuickImageSelector::setTarget(const QQmlProperty &property)
205{
206 m_property = property;
207}
208
209void QQuickImageSelector::classBegin()
210{
211}
212
213void QQuickImageSelector::componentComplete()
214{
215 setUrl(m_property.read().toUrl());
216 m_complete = true;
217 updateSource();
218}
219
220QStringList QQuickImageSelector::fileExtensions() const
221{
222 static const QStringList extensions = QStringList() << QStringLiteral("png");
223 return extensions;
224}
225
226QString QQuickImageSelector::cacheKey() const
227{
228 if (!m_cache)
229 return QString();
230
231 return m_path + m_name + m_activeStates.join(sep: m_separator);
232}
233
234void QQuickImageSelector::updateSource()
235{
236 static QCache<QString, QString> cache(cacheSize());
237
238 const QString key = cacheKey();
239
240 QString bestFilePath;
241
242 if (m_cache) {
243 QString *cachedPath = cache.object(key);
244 if (cachedPath)
245 bestFilePath = *cachedPath;
246 }
247
248 // note: a cached file path may be empty
249 if (bestFilePath.isNull()) {
250 QDir dir(m_path);
251 int bestScore = -1;
252
253 const QStringList extensions = fileExtensions();
254
255 const QList<QStringList> statePerms = permutations(input: m_activeStates);
256 for (const QStringList &perm : statePerms) {
257 const QString filePath = findFile(dir, baseName: m_name + m_separator + perm.join(sep: m_separator), extensions);
258 if (!filePath.isEmpty()) {
259 int score = calculateScore(states: perm);
260 if (score > bestScore) {
261 bestScore = score;
262 bestFilePath = filePath;
263 }
264 }
265 }
266
267 if (bestFilePath.isEmpty())
268 bestFilePath = findFile(dir, baseName: m_name, extensions);
269
270 if (m_cache)
271 cache.insert(akey: key, aobject: new QString(bestFilePath));
272 }
273
274 qCDebug(lcQtQuickControlsImagine) << m_name << m_activeStates << "->" << bestFilePath;
275
276 if (bestFilePath.startsWith(c: QLatin1Char(':')))
277 setSource(QUrl(QLatin1String("qrc") + bestFilePath));
278 else
279 setSource(QUrl::fromLocalFile(localfile: bestFilePath));
280}
281
282void QQuickImageSelector::setUrl(const QUrl &url)
283{
284 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(url));
285 setName(fileInfo.fileName());
286 setPath(fileInfo.path());
287}
288
289bool QQuickImageSelector::updateActiveStates()
290{
291 QStringList active;
292 for (const QVariant &v : qAsConst(t&: m_allStates)) {
293 const QVariantMap state = v.toMap();
294 if (state.isEmpty())
295 continue;
296 auto it = state.begin();
297 if (it.value().toBool())
298 active += it.key();
299 }
300
301 if (m_activeStates == active)
302 return false;
303
304 m_activeStates = active;
305 return true;
306}
307
308int QQuickImageSelector::calculateScore(const QStringList &states) const
309{
310 int score = 0;
311 for (int i = 0; i < states.count(); ++i)
312 score += (m_activeStates.count() - m_activeStates.indexOf(t: states.at(i))) << 1;
313 return score;
314}
315
316QQuickNinePatchImageSelector::QQuickNinePatchImageSelector(QObject *parent)
317 : QQuickImageSelector(parent)
318{
319}
320
321QStringList QQuickNinePatchImageSelector::fileExtensions() const
322{
323 static const QStringList extensions = QStringList() << QStringLiteral("9.png") << QStringLiteral("png");
324 return extensions;
325}
326
327QQuickAnimatedImageSelector::QQuickAnimatedImageSelector(QObject *parent)
328 : QQuickImageSelector(parent)
329{
330}
331
332QStringList QQuickAnimatedImageSelector::fileExtensions() const
333{
334 static const QStringList extensions = QStringList() << QStringLiteral("webp") << QStringLiteral("gif");
335 return extensions;
336}
337
338QT_END_NAMESPACE
339

source code of qtquickcontrols2/src/imports/controls/imagine/qquickimageselector.cpp