1 /* GCompris - ApplicationInfo.cpp
2 *
3 * SPDX-FileCopyrightText: 2014-2015 Bruno Coudoin <bruno.coudoin@gcompris.net>
4 *
5 * Authors:
6 * Bruno Coudoin <bruno.coudoin@gcompris.net>
7 *
8 * This file was originally created from Digia example code under BSD license
9 * and heavily modified since then.
10 *
11 * SPDX-License-Identifier: GPL-3.0-or-later
12 */
13
14 #include "ApplicationInfo.h"
15
16 #include <QtQml>
17 #include <QtMath>
18 #include <QUrl>
19 #include <QUrlQuery>
20 #include <QGuiApplication>
21 #include <QScreen>
22 #include <QLocale>
23 #include <QQuickWindow>
24 #include <QStandardPaths>
25 #include <QSensor>
26
27 #include <qmath.h>
28 #include <QDebug>
29
30 #include <QFontDatabase>
31 #include <QDir>
32
33 QQuickWindow *ApplicationInfo::m_window = nullptr;
34 ApplicationInfo *ApplicationInfo::m_instance = nullptr;
35
ApplicationInfo(QObject * parent)36 ApplicationInfo::ApplicationInfo(QObject *parent) :
37 QObject(parent)
38 {
39
40 #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(SAILFISHOS) || defined(UBUNTUTOUCH)
41 m_isMobile = true;
42 #else
43 m_isMobile = false;
44 #endif
45
46 #if defined(Q_OS_ANDROID)
47 // Put android before checking linux/unix as it is also a linux
48 m_platform = Android;
49 #elif defined(UBUNTUTOUCH)
50 m_platform = UbuntuTouchOS;
51 #elif defined(Q_OS_MAC)
52 m_platform = MacOSX;
53 #elif (defined(Q_OS_LINUX) || defined(Q_OS_UNIX))
54 m_platform = Linux;
55 #elif defined(Q_OS_WIN)
56 m_platform = Windows;
57 #elif defined(Q_OS_IOS)
58 m_platform = Ios;
59 #elif defined(Q_OS_BLACKBERRY)
60 m_platform = Blackberry;
61 #elif defined(SAILFISHOS)
62 m_platform = SailfishOS;
63 #else // default is Linux
64 m_platform = Linux;
65 #endif
66
67 m_isBox2DInstalled = false;
68
69 QRect rect = qApp->primaryScreen()->geometry();
70 m_ratio = qMin(qMax(rect.width(), rect.height()) / 800., qMin(rect.width(), rect.height()) / 520.);
71 // calculate a factor for font-scaling, cf.
72 // https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio
73 qreal refDpi = 216.;
74 qreal refHeight = 1776.;
75 qreal refWidth = 1080.;
76 qreal height = qMax(rect.width(), rect.height());
77 qreal width = qMin(rect.width(), rect.height());
78 qreal dpi = qApp->primaryScreen()->logicalDotsPerInch();
79
80 #if defined(UBUNTUTOUCH)
81 m_fontRatio = floor(m_ratio * 10) / 10;
82 #else
83 m_fontRatio = qMax(qreal(1.0), qMin(height * refDpi / (dpi * refHeight), width * refDpi / (dpi * refWidth)));
84 #endif
85 m_isPortraitMode = m_isMobile ? rect.height() > rect.width() : false;
86 m_applicationWidth = m_isMobile ? rect.width() : 1120;
87
88 m_useOpenGL = true;
89
90 if (m_isMobile)
91 connect(qApp->primaryScreen(), &QScreen::physicalSizeChanged, this, &ApplicationInfo::notifyPortraitMode);
92
93 // @FIXME this does not work on iOS: https://bugreports.qt.io/browse/QTBUG-50624
94 #if !defined(Q_OS_IOS)
95 // Get all symbol fonts to remove them
96 QFontDatabase database;
97 m_excludedFonts = database.families(QFontDatabase::Symbol);
98 #endif
99 // Get fonts from rcc
100 const QStringList fontFilters = { "*.otf", "*.ttf" };
101 m_fontsFromRcc = QDir(":/gcompris/src/core/resource/fonts").entryList(fontFilters);
102 }
103
~ApplicationInfo()104 ApplicationInfo::~ApplicationInfo()
105 {
106 m_instance = nullptr;
107 }
108
sensorIsSupported(const QString & sensorType)109 bool ApplicationInfo::sensorIsSupported(const QString &sensorType)
110 {
111 return QSensor::sensorTypes().contains(sensorType.toUtf8());
112 }
113
getNativeOrientation()114 Qt::ScreenOrientation ApplicationInfo::getNativeOrientation()
115 {
116 return QGuiApplication::primaryScreen()->nativeOrientation();
117 }
118
setApplicationWidth(const int newWidth)119 void ApplicationInfo::setApplicationWidth(const int newWidth)
120 {
121 if (newWidth != m_applicationWidth) {
122 m_applicationWidth = newWidth;
123 emit applicationWidthChanged();
124 }
125 }
126
getResourceDataPaths()127 QStringList ApplicationInfo::getResourceDataPaths()
128 {
129 return { ApplicationSettings::getInstance()->userDataPath(), "qrc:/gcompris/data" };
130 }
131
getFilePath(const QString & file)132 QString ApplicationInfo::getFilePath(const QString &file)
133 {
134 #if defined(Q_OS_ANDROID)
135 return QString("assets:/share/GCompris/rcc/%1").arg(file);
136 #elif defined(Q_OS_MACX)
137 return QString("%1/../Resources/rcc/%2").arg(QCoreApplication::applicationDirPath(), file);
138 #elif defined(Q_OS_IOS)
139 return QString("%1/rcc/%2").arg(QCoreApplication::applicationDirPath(), file);
140 #else
141 return QString("%1/%2/rcc/%3").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER, file);
142 #endif
143 }
144
getAudioFilePath(const QString & file)145 QString ApplicationInfo::getAudioFilePath(const QString &file)
146 {
147 QString localeName = getVoicesLocale(ApplicationSettings::getInstance()->locale());
148 return getAudioFilePathForLocale(file, localeName);
149 }
150
getAudioFilePathForLocale(const QString & file,const QString & localeName)151 QString ApplicationInfo::getAudioFilePathForLocale(const QString &file,
152 const QString &localeName)
153 {
154 QString filename = file;
155 filename.replace("$LOCALE", localeName);
156 filename.replace("$CA", CompressedAudio());
157 // Absolute paths are returned as is
158 if (file.startsWith('/') || file.startsWith(QLatin1String("qrc:")) || file.startsWith(':'))
159 return filename;
160
161 // For relative paths, look into resource folders if found
162 const QStringList dataPaths = getResourceDataPaths();
163 for (const QString &dataPath: dataPaths) {
164 if (QFile::exists(dataPath + '/' + filename)) {
165 if (dataPath.startsWith('/'))
166 return "file://" + dataPath + '/' + filename;
167 else
168 return dataPath + '/' + filename;
169 }
170 }
171 // If not found, return the default qrc:/gcompris/data path
172 return dataPaths.last() + '/' + filename;
173 }
174
getLocaleFilePath(const QString & file)175 QString ApplicationInfo::getLocaleFilePath(const QString &file)
176 {
177 QString localeShortName = localeShort();
178
179 QString filename = file;
180 filename.replace("$LOCALE", localeShortName);
181 return filename;
182 }
183
getSystemExcludedFonts()184 QStringList ApplicationInfo::getSystemExcludedFonts()
185 {
186 return m_excludedFonts;
187 }
188
getFontsFromRcc()189 QStringList ApplicationInfo::getFontsFromRcc()
190 {
191 return m_fontsFromRcc;
192 }
193
getBackgroundMusicFromRcc()194 QStringList ApplicationInfo::getBackgroundMusicFromRcc()
195 {
196 const QStringList backgroundMusicFilters = { QString("*.%1").arg(COMPRESSED_AUDIO) };
197 m_backgroundMusicFromRcc = QDir(":/gcompris/data/backgroundMusic").entryList(backgroundMusicFilters);
198 return m_backgroundMusicFromRcc;
199 }
200
notifyPortraitMode()201 void ApplicationInfo::notifyPortraitMode()
202 {
203 int width = qApp->primaryScreen()->geometry().width();
204 int height = qApp->primaryScreen()->geometry().height();
205 setIsPortraitMode(height > width);
206 }
207
setIsPortraitMode(const bool newMode)208 void ApplicationInfo::setIsPortraitMode(const bool newMode)
209 {
210 if (m_isPortraitMode != newMode) {
211 m_isPortraitMode = newMode;
212 emit portraitModeChanged();
213 }
214 }
215
setWindow(QQuickWindow * window)216 void ApplicationInfo::setWindow(QQuickWindow *window)
217 {
218 m_window = window;
219 }
220
screenshot(const QString & file)221 void ApplicationInfo::screenshot(const QString &file)
222 {
223 QImage img = m_window->grabWindow();
224 img.save(file);
225 }
226
notifyFullscreenChanged()227 void ApplicationInfo::notifyFullscreenChanged()
228 {
229 if (ApplicationSettings::getInstance()->isFullscreen())
230 m_window->showFullScreen();
231 else
232 m_window->showNormal();
233 }
234
235 // Would be better to create a component importing Box2D 2.0 using QQmlContext and test if it exists but it does not work.
setBox2DInstalled(const QQmlEngine & engine)236 void ApplicationInfo::setBox2DInstalled(const QQmlEngine &engine)
237 {
238 /*
239 QQmlContext *context = new QQmlContext(engine.rootContext());
240 context->setContextObject(&myDataSet);
241
242 QQmlComponent component(&engine);
243 component.setData("import QtQuick 2.0\nimport Box2D 2.0\nItem { }", QUrl());
244 component.create(context);
245 box2dInstalled = (component != nullptr);
246 */
247 bool box2dInstalled = false;
248 for (const QString &folder: engine.importPathList()) {
249 if (QDir(folder).entryList().contains(QStringLiteral("Box2D.2.0"))) {
250 if (QDir(folder + "/Box2D.2.0").entryList().contains("qmldir")) {
251 qDebug() << "Found box2d in " << folder;
252 box2dInstalled = true;
253 break;
254 }
255 }
256 }
257 m_isBox2DInstalled = box2dInstalled;
258 emit isBox2DInstalledChanged();
259 }
260
261 // return the shortest possible locale name for the given locale, describing
262 // a unique voices dataset
getVoicesLocale(const QString & locale)263 QString ApplicationInfo::getVoicesLocale(const QString &locale)
264 {
265 QString _locale = locale;
266 if (_locale == GC_DEFAULT_LOCALE) {
267 _locale = QLocale::system().name();
268 if (_locale == "C")
269 _locale = "en_US";
270 }
271 // locales we have country-specific voices for:
272 /* clang-format off */
273 if (_locale.startsWith(QLatin1String("en_GB")) ||
274 _locale.startsWith(QLatin1String("en_US")) ||
275 _locale.startsWith(QLatin1String("pt_BR")) ||
276 _locale.startsWith(QLatin1String("zh_CN")) ||
277 _locale.startsWith(QLatin1String("zh_TW"))) {
278 return QLocale(_locale).name();
279 }
280 /* clang-format on */
281
282 // short locale for all the rest:
283 return localeShort(_locale);
284 }
285
localeSort(QVariantList list,const QString & locale) const286 QVariantList ApplicationInfo::localeSort(QVariantList list,
287 const QString &locale) const
288 {
289 std::sort(list.begin(), list.end(),
290 [&locale, this](const QVariant &a, const QVariant &b) {
291 return (localeCompare(a.toString(), b.toString(), locale) < 0);
292 });
293 return list;
294 }
295
applicationInfoProvider(QQmlEngine * engine,QJSEngine * scriptEngine)296 QObject *ApplicationInfo::applicationInfoProvider(QQmlEngine *engine,
297 QJSEngine *scriptEngine)
298 {
299 Q_UNUSED(engine)
300 Q_UNUSED(scriptEngine)
301 /*
302 * Connect the fullscreen change signal to applicationInfo in order to change
303 * the QQuickWindow value
304 */
305 ApplicationInfo *appInfo = getInstance();
306 connect(ApplicationSettings::getInstance(), &ApplicationSettings::fullscreenChanged, appInfo,
307 &ApplicationInfo::notifyFullscreenChanged);
308
309 return appInfo;
310 }
311