File indexing completed on 2024-04-14 14:08:45

0001 /* GCompris - ApplicationInfo.cpp
0002  *
0003  * SPDX-FileCopyrightText: 2014-2015 Bruno Coudoin <bruno.coudoin@gcompris.net>
0004  *
0005  * Authors:
0006  *   Bruno Coudoin <bruno.coudoin@gcompris.net>
0007  *
0008  * This file was originally created from Digia example code under BSD license
0009  * and heavily modified since then.
0010  *
0011  *   SPDX-License-Identifier: GPL-3.0-or-later
0012  */
0013 
0014 #include "ApplicationInfo.h"
0015 
0016 #include <QtQml>
0017 #include <QGuiApplication>
0018 #include <QScreen>
0019 #include <QLocale>
0020 #include <QQuickWindow>
0021 #include <QSensor>
0022 
0023 #include <QDebug>
0024 
0025 #include <QFontDatabase>
0026 #include <QDir>
0027 
0028 QQuickWindow *ApplicationInfo::m_window = nullptr;
0029 ApplicationInfo *ApplicationInfo::m_instance = nullptr;
0030 
0031 ApplicationInfo::ApplicationInfo(QObject *parent) :
0032     QObject(parent)
0033 {
0034 
0035 #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_BLACKBERRY) || defined(UBUNTUTOUCH)
0036     m_isMobile = true;
0037 #else
0038     m_isMobile = false;
0039 #endif
0040 
0041 #if defined(Q_OS_ANDROID)
0042     // Put android before checking linux/unix as it is also a linux
0043     m_platform = Android;
0044 #elif defined(UBUNTUTOUCH)
0045     m_platform = UbuntuTouchOS;
0046 #elif defined(Q_OS_MAC)
0047     m_platform = MacOSX;
0048 #elif (defined(Q_OS_LINUX) || defined(Q_OS_UNIX))
0049     m_platform = Linux;
0050 #elif defined(Q_OS_WIN)
0051     m_platform = Windows;
0052 #elif defined(Q_OS_IOS)
0053     m_platform = Ios;
0054 #elif defined(Q_OS_BLACKBERRY)
0055     m_platform = Blackberry;
0056 #else // default is Linux
0057     m_platform = Linux;
0058 #endif
0059 
0060     m_isBox2DInstalled = false;
0061 
0062     QRect rect = qApp->primaryScreen()->geometry();
0063     m_ratio = qMin(qMax(rect.width(), rect.height()) / 800., qMin(rect.width(), rect.height()) / 520.);
0064     // calculate a factor for font-scaling, cf.
0065     // https://doc.qt.io/qt-5/scalability.html#calculating-scaling-ratio
0066     qreal refDpi = 216.;
0067     qreal refHeight = 1776.;
0068     qreal refWidth = 1080.;
0069     qreal height = qMax(rect.width(), rect.height());
0070     qreal width = qMin(rect.width(), rect.height());
0071     qreal dpi = qApp->primaryScreen()->logicalDotsPerInch();
0072 
0073 #if defined(UBUNTUTOUCH)
0074     m_fontRatio = floor(m_ratio * 10) / 10;
0075 #else
0076     m_fontRatio = qMax(qreal(1.0), qMin(height * refDpi / (dpi * refHeight), width * refDpi / (dpi * refWidth)));
0077 #endif
0078     m_isPortraitMode = m_isMobile ? rect.height() > rect.width() : false;
0079     m_applicationWidth = m_isMobile ? rect.width() : 1120;
0080 
0081     m_useOpenGL = true;
0082 
0083     if (m_isMobile)
0084         connect(qApp->primaryScreen(), &QScreen::physicalSizeChanged, this, &ApplicationInfo::notifyPortraitMode);
0085 
0086 // @FIXME this does not work on iOS: https://bugreports.qt.io/browse/QTBUG-50624
0087 #if !defined(Q_OS_IOS)
0088     // Get all symbol fonts to remove them
0089     QFontDatabase database;
0090     m_excludedFonts = database.families(QFontDatabase::Symbol);
0091 #endif
0092     // Get fonts from rcc
0093     const QStringList fontFilters = { "*.otf", "*.ttf" };
0094     m_fontsFromRcc = QDir(":/gcompris/src/core/resource/fonts").entryList(fontFilters);
0095 }
0096 
0097 ApplicationInfo::~ApplicationInfo()
0098 {
0099     m_instance = nullptr;
0100 }
0101 
0102 bool ApplicationInfo::sensorIsSupported(const QString &sensorType)
0103 {
0104     return QSensor::sensorTypes().contains(sensorType.toUtf8());
0105 }
0106 
0107 Qt::ScreenOrientation ApplicationInfo::getNativeOrientation()
0108 {
0109     return QGuiApplication::primaryScreen()->nativeOrientation();
0110 }
0111 
0112 void ApplicationInfo::setApplicationWidth(const int newWidth)
0113 {
0114     if (newWidth != m_applicationWidth) {
0115         m_applicationWidth = newWidth;
0116         Q_EMIT applicationWidthChanged();
0117     }
0118 }
0119 
0120 QStringList ApplicationInfo::getResourceDataPaths()
0121 {
0122     return { ApplicationSettings::getInstance()->userDataPath(), "qrc:/gcompris/data" };
0123 }
0124 
0125 QString ApplicationInfo::getFilePath(const QString &file)
0126 {
0127 #if defined(Q_OS_ANDROID)
0128     return QString("assets:/share/GCompris/rcc/%1").arg(file);
0129 #elif defined(Q_OS_MACX)
0130     return QString("%1/../Resources/rcc/%2").arg(QCoreApplication::applicationDirPath(), file);
0131 #elif defined(Q_OS_IOS)
0132     return QString("%1/rcc/%2").arg(QCoreApplication::applicationDirPath(), file);
0133 #else
0134     return QString("%1/%2/rcc/%3").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER, file);
0135 #endif
0136 }
0137 
0138 QString ApplicationInfo::getAudioFilePath(const QString &file)
0139 {
0140     QString localeName = getVoicesLocale(ApplicationSettings::getInstance()->locale());
0141     return getAudioFilePathForLocale(file, localeName);
0142 }
0143 
0144 QString ApplicationInfo::getAudioFilePathForLocale(const QString &file,
0145                                                    const QString &localeName)
0146 {
0147     QString filename = file;
0148     filename.replace("$LOCALE", localeName);
0149     filename.replace("$CA", CompressedAudio());
0150     // Absolute paths are returned as is
0151     if (file.startsWith('/') || file.startsWith(QLatin1String("qrc:")) || file.startsWith(':'))
0152         return filename;
0153 
0154     // For relative paths, look into resource folders if found
0155     const QStringList dataPaths = getResourceDataPaths();
0156     for (const QString &dataPath: dataPaths) {
0157         if (QFile::exists(dataPath + '/' + filename)) {
0158             if (dataPath.startsWith('/'))
0159                 return "file://" + dataPath + '/' + filename;
0160             else
0161                 return dataPath + '/' + filename;
0162         }
0163     }
0164     // If not found, return the default qrc:/gcompris/data path
0165     return dataPaths.last() + '/' + filename;
0166 }
0167 
0168 QString ApplicationInfo::getLocaleFilePath(const QString &file)
0169 {
0170     QString localeShortName = localeShort();
0171 
0172     QString filename = file;
0173     filename.replace("$LOCALE", localeShortName);
0174     return filename;
0175 }
0176 
0177 QStringList ApplicationInfo::getSystemExcludedFonts()
0178 {
0179     return m_excludedFonts;
0180 }
0181 
0182 QStringList ApplicationInfo::getFontsFromRcc()
0183 {
0184     return m_fontsFromRcc;
0185 }
0186 
0187 QStringList ApplicationInfo::getBackgroundMusicFromRcc()
0188 {
0189     const QStringList backgroundMusicFilters = { QString("*.%1").arg(COMPRESSED_AUDIO) };
0190     m_backgroundMusicFromRcc = QDir(":/gcompris/data/backgroundMusic").entryList(backgroundMusicFilters);
0191     return m_backgroundMusicFromRcc;
0192 }
0193 
0194 void ApplicationInfo::notifyPortraitMode()
0195 {
0196     int width = qApp->primaryScreen()->geometry().width();
0197     int height = qApp->primaryScreen()->geometry().height();
0198     setIsPortraitMode(height > width);
0199 }
0200 
0201 void ApplicationInfo::setIsPortraitMode(const bool newMode)
0202 {
0203     if (m_isPortraitMode != newMode) {
0204         m_isPortraitMode = newMode;
0205         Q_EMIT portraitModeChanged();
0206     }
0207 }
0208 
0209 void ApplicationInfo::setWindow(QQuickWindow *window)
0210 {
0211     m_window = window;
0212 }
0213 
0214 void ApplicationInfo::screenshot(const QString &file)
0215 {
0216     QImage img = m_window->grabWindow();
0217     img.save(file);
0218 }
0219 
0220 void ApplicationInfo::notifyFullscreenChanged()
0221 {
0222     if (ApplicationSettings::getInstance()->isFullscreen())
0223         m_window->showFullScreen();
0224     else
0225         m_window->showNormal();
0226 }
0227 
0228 // Would be better to create a component importing Box2D 2.0 using QQmlContext and test if it exists but it does not work.
0229 void ApplicationInfo::setBox2DInstalled(const QQmlEngine &engine)
0230 {
0231     /*
0232       QQmlContext *context = new QQmlContext(engine.rootContext());
0233       context->setContextObject(&myDataSet);
0234 
0235       QQmlComponent component(&engine);
0236       component.setData("import QtQuick 2.12\nimport Box2D 2.0\nItem { }", QUrl());
0237       component.create(context);
0238       box2dInstalled = (component != nullptr);
0239     */
0240     bool box2dInstalled = false;
0241     const QStringList importPathList = engine.importPathList();
0242     for (const QString &folder: importPathList) {
0243         if (QDir(folder).entryList().contains(QLatin1String("Box2D.2.0"))) {
0244             if (QDir(folder + "/Box2D.2.0").entryList().contains("qmldir")) {
0245                 qDebug() << "Found box2d in " << folder;
0246                 box2dInstalled = true;
0247                 break;
0248             }
0249         }
0250     }
0251     m_isBox2DInstalled = box2dInstalled;
0252     Q_EMIT isBox2DInstalledChanged();
0253 }
0254 
0255 // return the shortest possible locale name for the given locale, describing
0256 // a unique voices dataset
0257 QString ApplicationInfo::getVoicesLocale(const QString &locale)
0258 {
0259     QString _locale = locale;
0260     if (_locale == GC_DEFAULT_LOCALE) {
0261         _locale = QLocale::system().name();
0262         if (_locale == "C")
0263             _locale = "en_US";
0264     }
0265     // locales we have country-specific voices for:
0266     /* clang-format off */
0267     if (_locale.startsWith(QLatin1String("en_GB")) ||
0268         _locale.startsWith(QLatin1String("en_US")) ||
0269         _locale.startsWith(QLatin1String("pt_BR")) ||
0270         _locale.startsWith(QLatin1String("zh_CN")) ||
0271         _locale.startsWith(QLatin1String("zh_TW"))) {
0272         return QLocale(_locale).name();
0273     }
0274     /* clang-format on */
0275 
0276     // short locale for all the rest:
0277     return localeShort(_locale);
0278 }
0279 
0280 QVariantList ApplicationInfo::localeSort(QVariantList list,
0281                                          const QString &locale) const
0282 {
0283     std::sort(list.begin(), list.end(),
0284               [&locale, this](const QVariant &a, const QVariant &b) {
0285                   return (localeCompare(a.toString(), b.toString(), locale) < 0);
0286               });
0287     return list;
0288 }
0289 
0290 QObject *ApplicationInfo::applicationInfoProvider(QQmlEngine *engine,
0291                                                   QJSEngine *scriptEngine)
0292 {
0293     Q_UNUSED(engine)
0294     Q_UNUSED(scriptEngine)
0295     /*
0296      * Connect the fullscreen change signal to applicationInfo in order to change
0297      * the QQuickWindow value
0298      */
0299     ApplicationInfo *appInfo = getInstance();
0300     connect(ApplicationSettings::getInstance(), &ApplicationSettings::fullscreenChanged, appInfo,
0301             &ApplicationInfo::notifyFullscreenChanged);
0302 
0303     return appInfo;
0304 }
0305 
0306 #include "moc_ApplicationInfo.cpp"