1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Quick Controls module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquickcontrolsettings_p.h"
41#include <qquickitem.h>
42#include <qcoreapplication.h>
43#include <qdebug.h>
44#include <qqmlengine.h>
45#include <qfileinfo.h>
46#if QT_CONFIG(library)
47#include <qlibrary.h>
48#endif
49#include <qdir.h>
50#include <QTouchDevice>
51#include <QGuiApplication>
52#include <QStyleHints>
53#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
54#include <private/qjnihelpers_p.h>
55#endif
56
57QT_BEGIN_NAMESPACE
58
59static QString defaultStyleName()
60{
61 static const QMap<QString, QString> styleMap {
62#if defined(QT_WIDGETS_LIB)
63 {QLatin1String("cocoa"), QLatin1String("Desktop")},
64 {QLatin1String("wayland"), QLatin1String("Desktop")},
65 {QLatin1String("windows"), QLatin1String("Desktop")},
66 {QLatin1String("xcb"), QLatin1String("Desktop")},
67#endif
68 {QLatin1String("android"), QLatin1String("Android")},
69 {QLatin1String("ios"), QLatin1String("iOS")},
70#if 0 // Enable once style is ready
71 {QLatin1String("winrt"), QLatin1String("WinRT")},
72#endif
73 };
74
75 QGuiApplication *app = static_cast<QGuiApplication *>(
76 QCoreApplication::instance());
77 const QString styleName = styleMap.value(akey: app->platformName(), adefaultValue: QLatin1String("Base"));
78
79#if defined(QT_WIDGETS_LIB)
80 // Only enable QStyle support when we are using QApplication
81 if (styleName == QLatin1String("Desktop") && !app->inherits(classname: "QApplication"))
82 return QLatin1String("Base");
83#endif
84
85 return styleName;
86}
87
88static QString styleEnvironmentVariable()
89{
90 QString style = qgetenv(varName: "QT_QUICK_CONTROLS_1_STYLE");
91 if (style.isEmpty())
92 style = qgetenv(varName: "QT_QUICK_CONTROLS_STYLE");
93 return style;
94}
95
96static QString styleImportName()
97{
98 QString name = styleEnvironmentVariable();
99 if (name.isEmpty())
100 name = defaultStyleName();
101 return QFileInfo(name).fileName();
102}
103
104static bool fromResource(const QString &path)
105{
106 return path.startsWith(s: ":/");
107}
108
109bool QQuickControlSettings1::hasTouchScreen() const
110{
111 const auto devices = QTouchDevice::devices();
112 for (const QTouchDevice *dev : devices)
113 if (dev->type() == QTouchDevice::TouchScreen)
114 return true;
115 return false;
116}
117
118bool QQuickControlSettings1::isMobile() const
119{
120#if defined(Q_OS_IOS) || defined(Q_OS_ANDROID) || defined(Q_OS_BLACKBERRY) || defined(Q_OS_QNX) || defined(Q_OS_WINRT)
121 return true;
122#else
123 if (qEnvironmentVariableIsSet(varName: "QT_QUICK_CONTROLS_MOBILE")) {
124 return true;
125 }
126 return false;
127#endif
128}
129
130bool QQuickControlSettings1::hoverEnabled() const
131{
132 return !isMobile() || !hasTouchScreen();
133}
134
135QString QQuickControlSettings1::makeStyleComponentPath(const QString &controlStyleName, const QString &styleDirPath)
136{
137 return styleDirPath + QStringLiteral("/") + controlStyleName;
138}
139
140QUrl QQuickControlSettings1::makeStyleComponentUrl(const QString &controlStyleName, const QString &styleDirPath)
141{
142 QString styleFilePath = makeStyleComponentPath(controlStyleName, styleDirPath);
143
144 if (styleDirPath.startsWith(s: QLatin1String(":/")))
145 return QUrl(QStringLiteral("qrc") + styleFilePath);
146
147 return QUrl::fromLocalFile(localfile: styleFilePath);
148}
149
150QQmlComponent *QQuickControlSettings1::styleComponent(const QUrl &styleDirUrl, const QString &controlStyleName, QObject *control)
151{
152 Q_UNUSED(styleDirUrl); // required for hack that forces this function to be re-called from within QML when style changes
153
154 // QUrl doesn't consider qrc-based URLs as local files, so bypass it here.
155 QString styleFilePath = makeStyleComponentPath(controlStyleName, styleDirPath: m_styleMap.value(akey: m_name).m_styleDirPath);
156 QUrl styleFileUrl;
157 if (QFile::exists(fileName: styleFilePath)) {
158 styleFileUrl = makeStyleComponentUrl(controlStyleName, styleDirPath: m_styleMap.value(akey: m_name).m_styleDirPath);
159 } else {
160 // It's OK for a style to pick and choose which controls it wants to provide style files for.
161 styleFileUrl = makeStyleComponentUrl(controlStyleName, styleDirPath: m_styleMap.value(QStringLiteral("Base")).m_styleDirPath);
162 }
163
164 return new QQmlComponent(qmlEngine(control), styleFileUrl, this);
165}
166
167static QString relativeStyleImportPath(QQmlEngine *engine, const QString &styleName)
168{
169 QString path;
170#ifndef QT_STATIC
171 bool found = false;
172 const auto importPathList = engine->importPathList(); // ideally we'd call QQmlImportDatabase::importPathList(Local) here, but it's not exported
173 for (QString import : importPathList) {
174 bool localPath = QFileInfo(import).isAbsolute();
175 if (import.startsWith(s: QLatin1String("qrc:/"), cs: Qt::CaseInsensitive)) {
176 import = QLatin1Char(':') + import.mid(position: 4);
177 localPath = true;
178 }
179 if (localPath) {
180 QDir dir(import + QStringLiteral("/QtQuick/Controls/Styles"));
181 if (dir.exists(name: styleName)) {
182 found = true;
183 path = dir.absolutePath();
184 break;
185 }
186 }
187 }
188 if (!found)
189 path = ":/QtQuick/Controls/Styles";
190#else
191 Q_UNUSED(engine);
192 Q_UNUSED(styleName);
193 path = ":/qt-project.org/imports/QtQuick/Controls/Styles";
194#endif
195 return path;
196}
197
198static QString styleImportPath(QQmlEngine *engine, const QString &styleName)
199{
200 QString path = styleEnvironmentVariable();
201 QFileInfo info(path);
202 if (fromResource(path)) {
203 path = info.path();
204 } else if (info.isRelative()) {
205 path = relativeStyleImportPath(engine, styleName);
206 } else {
207#ifndef QT_STATIC
208 path = info.absolutePath();
209#else
210 path = "qrc:/qt-project.org/imports/QtQuick/Controls/Styles";
211#endif
212 }
213 return path;
214}
215
216QQuickControlSettings1::QQuickControlSettings1(QQmlEngine *engine)
217 : m_engine(engine)
218{
219 // First, register all style paths in the default style location.
220 QDir dir;
221 const QString defaultStyle = defaultStyleName();
222#ifndef QT_STATIC
223 dir.setPath(relativeStyleImportPath(engine, styleName: defaultStyle));
224#else
225 dir.setPath(":/qt-project.org/imports/QtQuick/Controls/Styles");
226#endif
227 dir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
228 const auto list = dir.entryList();
229 for (const QString &styleDirectory : list) {
230 findStyle(engine, styleName: styleDirectory);
231 }
232
233 m_name = styleImportName();
234
235 // If the style name is a path..
236 const QString styleNameFromEnvVar = styleEnvironmentVariable();
237 if (!styleNameFromEnvVar.isEmpty() && QFile::exists(fileName: styleNameFromEnvVar)) {
238 StyleData styleData;
239 styleData.m_styleDirPath = styleNameFromEnvVar;
240 m_styleMap[m_name] = styleData;
241 }
242
243 // Then check if the style the user wanted is known to us. Otherwise, use the fallback style.
244 if (m_styleMap.contains(akey: m_name)) {
245 m_path = m_styleMap.value(akey: m_name).m_styleDirPath;
246 } else {
247 m_path = m_styleMap.value(akey: defaultStyle).m_styleDirPath;
248 // Maybe the requested style is not next to the default style, but elsewhere in the import path
249 findStyle(engine, styleName: m_name);
250 if (!m_styleMap.contains(akey: m_name)) {
251 QString unknownStyle = m_name;
252 m_name = defaultStyle;
253 qWarning() << "WARNING: Cannot find style" << unknownStyle << "- fallback:" << styleFilePath();
254 }
255 }
256
257 // Can't really do anything about this failing here, so don't bother checking...
258 resolveCurrentStylePath();
259
260 connect(asender: this, SIGNAL(styleNameChanged()), SIGNAL(styleChanged()));
261 connect(asender: this, SIGNAL(stylePathChanged()), SIGNAL(styleChanged()));
262}
263
264bool QQuickControlSettings1::resolveCurrentStylePath()
265{
266#if QT_CONFIG(library)
267 if (!m_styleMap.contains(akey: m_name)) {
268 qWarning() << "WARNING: Cannot find style" << m_name;
269 return false;
270 }
271
272 StyleData styleData = m_styleMap.value(akey: m_name);
273
274 if (styleData.m_stylePluginPath.isEmpty())
275 return true; // It's not a plugin; don't have to do anything.
276
277 typedef bool (*StyleInitFunc)();
278 typedef const char *(*StylePathFunc)();
279
280 QLibrary lib(styleData.m_stylePluginPath);
281 if (!lib.load()) {
282 qWarning().nospace() << "WARNING: Cannot load plugin " << styleData.m_stylePluginPath
283 << " for style " << m_name << ": " << lib.errorString();
284 return false;
285 }
286
287 // Check for the existence of this first, as we don't want to init if this doesn't exist.
288 StyleInitFunc initFunc = (StyleInitFunc) lib.resolve(symbol: "qt_quick_controls_style_init");
289 if (initFunc)
290 initFunc();
291 StylePathFunc pathFunc = (StylePathFunc) lib.resolve(symbol: "qt_quick_controls_style_path");
292 if (pathFunc) {
293 styleData.m_styleDirPath = QString::fromLocal8Bit(str: pathFunc());
294 m_styleMap[m_name] = styleData;
295 m_path = styleData.m_styleDirPath;
296 }
297#endif // QT_CONFIG(library)
298 return true;
299}
300
301void QQuickControlSettings1::findStyle(QQmlEngine *engine, const QString &styleName)
302{
303 QString path = styleImportPath(engine, styleName);
304 QDir dir;
305 dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
306 dir.setPath(path);
307 if (!dir.cd(dirName: styleName))
308 return;
309
310 StyleData styleData;
311
312#if QT_CONFIG(library) && !defined(QT_STATIC)
313 const auto list = dir.entryList();
314 for (const QString &fileName : list) {
315 // This assumes that there is only one library in the style directory,
316 // which should be a safe assumption. If at some point it's determined
317 // not to be safe, we'll have to resolve the init and path functions
318 // here, to be sure that it is the correct library.
319 if (QLibrary::isLibrary(fileName)) {
320 styleData.m_stylePluginPath = dir.absoluteFilePath(fileName);
321 break;
322 }
323 }
324#endif
325
326 // If there's no plugin for the style, then the style's files are
327 // contained in this directory (which contains a qmldir file instead).
328 styleData.m_styleDirPath = dir.absolutePath();
329
330 m_styleMap[styleName] = styleData;
331}
332
333QUrl QQuickControlSettings1::style() const
334{
335 QUrl result;
336 QString path = styleFilePath();
337 if (fromResource(path)) {
338 result.setScheme("qrc");
339 path.remove(i: 0, len: 1); // remove ':' prefix
340 result.setPath(path);
341 } else
342 result = QUrl::fromLocalFile(localfile: path);
343 return result;
344}
345
346QString QQuickControlSettings1::styleName() const
347{
348 return m_name;
349}
350
351void QQuickControlSettings1::setStyleName(const QString &name)
352{
353 if (m_name != name) {
354 QString oldName = m_name;
355 m_name = name;
356
357 if (!m_styleMap.contains(akey: name)) {
358 // Maybe this style is not next to the default style, but elsewhere in the import path
359 findStyle(engine: m_engine, styleName: name);
360 }
361
362 // Don't change the style if it can't be resolved.
363 if (!resolveCurrentStylePath())
364 m_name = oldName;
365 else
366 emit styleNameChanged();
367 }
368}
369
370QString QQuickControlSettings1::stylePath() const
371{
372 return m_path;
373}
374
375void QQuickControlSettings1::setStylePath(const QString &path)
376{
377 if (m_path != path) {
378 m_path = path;
379 emit stylePathChanged();
380 }
381}
382
383QString QQuickControlSettings1::styleFilePath() const
384{
385 return m_path;
386}
387
388extern Q_GUI_EXPORT int qt_defaultDpiX();
389
390qreal QQuickControlSettings1::dpiScaleFactor() const
391{
392#ifndef Q_OS_MAC
393 return (qreal(qt_defaultDpiX()) / 96.0);
394#endif
395 return 1.0;
396}
397
398qreal QQuickControlSettings1::dragThreshold() const
399{
400 return qApp->styleHints()->startDragDistance();
401}
402
403
404QT_END_NAMESPACE
405

source code of qtquickcontrols/src/controls/Private/qquickcontrolsettings.cpp