Warning, /education/gcompris/src/core/main.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - main.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014 Bruno Coudoin <bruno.coudoin@gcompris.net>
0004  *
0005  * Authors:
0006  *   Bruno Coudoin <bruno.coudoin@gcompris.net>
0007  *
0008  *   SPDX-License-Identifier: GPL-3.0-or-later
0009  */
0010 import QtQuick 2.12
0011 import QtQuick.Controls 2.12
0012 import QtQuick.Window 2.12
0013 import QtQml 2.12
0014 
0015 import GCompris 1.0
0016 import "qrc:/gcompris/src/core/core.js" as Core
0017 
0018 /**
0019  * GCompris' main QML file defining the top level window.
0020  * @ingroup infrastructure
0021  *
0022  * Handles application start (Component.onCompleted) and shutdown (onClosing)
0023  * on the QML layer.
0024  *
0025  * Contains the global shortcuts:
0026  *
0027  * * @c Ctrl+q: Exit the application.
0028  * * @c Ctrl+b: Toggle the bar.
0029  * * @c Ctrl+f: Toggle fullscreen.
0030  * * @c Ctrl+m: Toggle audio effects.
0031  * * @c Ctrl+p: Take a screenshot.
0032  *
0033  * Contains the central GCAudio objects audio effects and audio voices.
0034  *
0035  * Contains the top level StackView presenting and animating GCompris'
0036  * full screen views.
0037  *
0038  * @sa BarButton, BarEnumContent
0039  * @inherit QtQuick.Window
0040  */
0041 Window {
0042     id: main
0043     // Start in window mode at full screen size
0044     width: ApplicationSettings.previousWidth
0045     height: ApplicationSettings.previousHeight
0046     minimumWidth: 400 * ApplicationInfo.ratio
0047     minimumHeight: 400 * ApplicationInfo.ratio
0048     title: "GCompris"
0049 
0050     /// @cond INTERNAL_DOCS
0051 
0052     property var applicationState: Qt.application.state
0053     property var rccBackgroundMusic: ApplicationInfo.getBackgroundMusicFromRcc()
0054     property var filteredBackgroundMusic: ApplicationSettings.filteredBackgroundMusic
0055     property alias backgroundMusic: backgroundMusic
0056     property bool voicesDownloaded: true
0057     property bool wordSetDownloaded: true
0058     property bool musicDownloaded: true
0059     property bool welcomePlayed: false
0060     property int lastGCVersionRanCopy: ApplicationInfo.GCVersionCode
0061 
0062     Shortcut {
0063         sequence: "Ctrl+q"
0064         onActivated: Core.quit(pageView);
0065     }
0066     Shortcut {
0067         sequence: "Ctrl+b"
0068         onActivated: ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden;
0069     }
0070     Shortcut {
0071         sequence: "Ctrl+f"
0072         onActivated: ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen;
0073     }
0074     Shortcut {
0075         sequence: "Ctrl+m"
0076         onActivated: {
0077             // We mute / unmute both channels in sync
0078             ApplicationSettings.isAudioVoicesEnabled = !ApplicationSettings.isAudioVoicesEnabled;
0079             ApplicationSettings.isAudioEffectsEnabled = ApplicationSettings.isAudioVoicesEnabled;
0080             ApplicationSettings.isBackgroundMusicEnabled = ApplicationSettings.isAudioVoicesEnabled;
0081         }
0082     }
0083     Shortcut {
0084         sequence: "Ctrl+p"
0085         onActivated: {
0086             if(pageView.get(pageView.depth-1).activityInfo) {
0087                 ApplicationInfo.screenshot("/tmp/" + pageView.get(pageView.depth-1).activityInfo.name.split('/')[0] + ".png");
0088             } else {
0089                 ApplicationInfo.screenshot("/tmp/gcompris.png");
0090             }
0091         }
0092     }
0093 
0094     /**
0095      * type: bool
0096      * It tells whether a musical activity is running.
0097      *
0098      * It changes to true if the started activity is a musical activity and back to false when the activity is closed, allowing to play background music.
0099      */
0100     property bool isMusicalActivityRunning: false
0101 
0102     /**
0103      * When a musical activity is started, the backgroundMusic pauses.
0104      *
0105      * When returning back from the musical activity to menu, backgroundMusic resumes.
0106      */
0107     onIsMusicalActivityRunningChanged: {
0108         if(isMusicalActivityRunning) {
0109             backgroundMusic.pause()
0110         }
0111         else {
0112             backgroundMusic.resume()
0113         }
0114     }
0115 
0116     onApplicationStateChanged: {
0117         if (ApplicationInfo.isMobile && applicationState !== Qt.ApplicationActive) {
0118             backgroundMusic.pause();
0119             audioVoices.stop();
0120             audioEffects.stop();
0121         }
0122         else if (ApplicationInfo.isMobile && !isMusicalActivityRunning) {
0123             backgroundMusic.resume();
0124         }
0125     }
0126 
0127     onClosing: Core.quit(pageView)
0128 
0129     GCAudio {
0130         id: audioVoices
0131         muted: !ApplicationSettings.isAudioVoicesEnabled && !main.isMusicalActivityRunning
0132 
0133         Timer {
0134             id: delayedWelcomeTimer
0135             interval: 10000 /* Make sure, that playing welcome.ogg if delayed
0136                              * because of not yet registered voices, will only
0137                              * happen max 10sec after startup */
0138             repeat: false
0139 
0140             onTriggered: {
0141                 DownloadManager.voicesRegistered.disconnect(playWelcome);
0142             }
0143 
0144             function playWelcome() {
0145                 if(!welcomePlayed) {
0146                     audioVoices.append(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/misc/welcome.$CA"));
0147                     welcomePlayed = true;
0148                 }
0149             }
0150         }
0151 
0152         Component.onCompleted: {
0153             if(ActivityInfoTree.startingActivity != "") {
0154                 // Don't play welcome intro
0155                 welcomePlayed = true;
0156             }
0157             else if (DownloadManager.areVoicesRegistered(ApplicationSettings.locale)) {
0158                 delayedWelcomeTimer.playWelcome();
0159             }
0160             else {
0161                 DownloadManager.voicesRegistered.connect(
0162                         delayedWelcomeTimer.playWelcome);
0163                 delayedWelcomeTimer.start();
0164             }
0165         }
0166     }
0167 
0168     GCSfx {
0169         id: audioEffects
0170         muted: !ApplicationSettings.isAudioEffectsEnabled && !main.isMusicalActivityRunning
0171         volume: ApplicationSettings.audioEffectsVolume
0172     }
0173 
0174     GCAudio {
0175         id: backgroundMusic
0176         isBackgroundMusic: true
0177         muted: !ApplicationSettings.isBackgroundMusicEnabled
0178         volume: ApplicationSettings.backgroundMusicVolume
0179 
0180         onMutedChanged: {
0181             if(!hasAudio && !files.length) {
0182                 backgroundMusic.playBackgroundMusic()
0183             }
0184         }
0185 
0186         onDone: {
0187             backgroundMusic.source = "" // Avoid play again intro music if backgroundMusic not installed
0188             backgroundMusic.playBackgroundMusic()
0189         }
0190 
0191         function playBackgroundMusic() {
0192             rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc()
0193             filteredBackgroundMusic = ApplicationSettings.filteredBackgroundMusic;
0194             if(filteredBackgroundMusic.length === 0) {
0195                 filteredBackgroundMusic = rccBackgroundMusic
0196             }
0197 
0198             for(var i = 0; i < filteredBackgroundMusic.length; i++) {
0199                 backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[i]))
0200             }
0201             if(main.isMusicalActivityRunning)
0202                 backgroundMusic.pause()
0203         }
0204 
0205         Component.onCompleted: {
0206             if(ApplicationSettings.isBackgroundMusicEnabled && ActivityInfoTree.startingActivity == "") {
0207                 backgroundMusic.append(ApplicationInfo.getAudioFilePath("qrc:/gcompris/src/core/resource/intro.$CA"))
0208             }
0209             if(ApplicationSettings.isBackgroundMusicEnabled
0210                && DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) {
0211                    backgroundMusic.playBackgroundMusic()
0212             }
0213             else {
0214                 DownloadManager.backgroundMusicRegistered.connect(backgroundMusic.playBackgroundMusic)
0215             }
0216         }
0217     }
0218 
0219     function playIntroVoice(name) {
0220         name = name.split("/")[0]
0221         audioVoices.play(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/intro/" + name + ".$CA"))
0222     }
0223 
0224     function checkWordset() {
0225         var wordset = DownloadManager.getResourcePath(GCompris.WORDSET, {})
0226 
0227         // check for words-webp.rcc:
0228         if(wordset != "" && DownloadManager.haveLocalResource(wordset)) {
0229             // words-webp.rcc is there -> register old file first
0230             // then try to update in the background
0231             DownloadManager.updateResource(GCompris.WORDSET, {});
0232         } else {
0233             // words-webp.rcc has not been downloaded yet -> ask for download
0234             wordSetDownloaded = false;
0235         }
0236     }
0237 
0238     function checkBackgroundMusic() {
0239         var music = DownloadManager.getBackgroundMusicResources()
0240         if(rccBackgroundMusic === '') {
0241             rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc()
0242         }
0243         if(music === '' || !DownloadManager.haveLocalResource(music)) {
0244             musicDownloaded = false;
0245         }
0246         // We have local music but it is not yet registered
0247         else if(music !== "") {
0248             // We have music and automatic download is enabled. Download the music and register it
0249             DownloadManager.updateResource(GCompris.BACKGROUND_MUSIC, {})
0250         }
0251         else if(ApplicationSettings.isBackgroundMusicEnabled && !DownloadManager.haveLocalResource(music)) {
0252             musicDownloaded = false;
0253         }
0254     }
0255 
0256     function checkVoices() {
0257         var voicesRcc = DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)
0258         if(voicesRcc == "" || !DownloadManager.haveLocalResource(voicesRcc))
0259             voicesDownloaded = false;
0260         else {
0261             if(voicesRcc !== "") {
0262                 DownloadManager.updateResource(GCompris.VOICES, {"locale": ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)});
0263             }
0264         }
0265     }
0266 
0267     function initialAssetsDownload() {
0268         var dialogText;
0269         dialogText = qsTr("Do you want to automatically download or update the following external assets when starting GCompris?")
0270         + ("<br>")
0271         + ("<br>") + "-" + qsTr("Voices for your language")
0272         + ("<br>") + "-" + qsTr("Full word image set")
0273         + ("<br>") + "-" + qsTr("Background music");
0274 
0275         var dialog;
0276         dialog = Core.showMessageDialog(
0277             pageView.currentItem,
0278             dialogText,
0279             qsTr("Yes"),
0280             function() {
0281                 DownloadManager.downloadResource(GCompris.VOICES, {"locale": ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)});
0282                 DownloadManager.downloadResource(GCompris.WORDSET);
0283                 DownloadManager.downloadResource(GCompris.BACKGROUND_MUSIC);
0284                 var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {});
0285             },
0286             qsTr("No"), 
0287             function() {
0288                 ApplicationSettings.isAutomaticDownloadsEnabled = false;
0289             },
0290             null
0291         );
0292     }
0293 
0294     ChangeLog {
0295        id: changelog
0296     }
0297 
0298     Component.onCompleted: {
0299         console.log("enter main.qml (run #" + ApplicationSettings.exeCount
0300                     + ", ratio=" + ApplicationInfo.ratio
0301                     + ", fontRatio=" + ApplicationInfo.fontRatio
0302                     + ", dpi=" + Math.round(Screen.pixelDensity*25.4)
0303                     + ", userDataPath=" + ApplicationSettings.userDataPath
0304                     + ")");
0305         DownloadManager.initializeAssets();
0306 
0307         // Register local full rcc if it exists. We don't try to check if there is one more up to date on the server, we register the one we have
0308         var fullRccPath = DownloadManager.getResourcePath(GCompris.FULL, {});
0309         if(fullRccPath != "") {
0310             DownloadManager.registerResource(fullRccPath);
0311         }
0312 
0313         if (ApplicationSettings.exeCount === 1 &&
0314                 !ApplicationSettings.isKioskMode) {
0315             checkVoices();
0316             checkWordset();
0317             checkBackgroundMusic();
0318             // first run
0319             var dialog;
0320             dialog = Core.showMessageDialog(
0321                         pageView,
0322                         qsTr("Welcome to GCompris!") + ("<br>")
0323                         + qsTr("You are running GCompris for the first time.") + "\n"
0324                         + qsTr("You should verify that your application settings especially your language is set correctly. You can do this in the Configuration dialog.")
0325                         + "\n"
0326                         + qsTr("Have Fun!")
0327                         + ("<br><br>")
0328                         + qsTr("Your current language is %1 (%2).")
0329                           .arg(Qt.locale(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)).nativeLanguageName)
0330                           .arg(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)),
0331                         "", null,
0332                         "", null,
0333                         function() {
0334                             pageView.currentItem.focus = true;
0335                             if(ApplicationInfo.isDownloadAllowed) {
0336                                 initialAssetsDownload();
0337                             }
0338                         }
0339              );
0340         }
0341         else {
0342             // Register voices-resources for current locale, updates/downloads only if
0343             // not prohibited by the settings
0344             DownloadManager.updateResource(GCompris.VOICES, {"locale": ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)});
0345 
0346             checkWordset();
0347             DownloadManager.updateResource(GCompris.WORDSET, {});
0348 
0349             checkBackgroundMusic();
0350             DownloadManager.updateResource(GCompris.BACKGROUND_MUSIC, {});
0351 
0352             if(changelog.isNewerVersion(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode)) {
0353                 lastGCVersionRanCopy = ApplicationSettings.lastGCVersionRan;
0354 
0355                 const newDatasets = changelog.getNewDatasetsBetween(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode);
0356 
0357                 // display log between ApplicationSettings.lastGCVersionRan and ApplicationInfo.GCVersionCode
0358                 Core.showMessageDialog(
0359                 pageView,
0360                 qsTr("GCompris has been updated! Here are the new changes:<br/>") + changelog.getLogBetween(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode),
0361                 "", null,
0362                 "", null,
0363                 function() {
0364                     if(newDatasets.length != 0) {
0365                         showNewDatasetsDialog(newDatasets);
0366                     }
0367                     else {
0368                         pageView.currentItem.focus = true;
0369                     }
0370                 }
0371                 );
0372 
0373                 // Store new version after update
0374                 ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode;
0375             }
0376         }
0377         //Store version on first run in any case
0378         if(ApplicationSettings.lastGCVersionRan === 0)
0379             ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode;
0380     }
0381 
0382     Loader {
0383         id: newDatasetsDialog
0384         property var newDatasetsModel
0385         sourceComponent: GCDialog {
0386             parent: pageView
0387             isDestructible: false
0388             message: qsTr("Some activities have new dataset available. Do you want to reset their dataset selection?")
0389             button1Text: qsTr("Apply")
0390             button2Text: qsTr("Cancel")
0391             onButton1Hit: {
0392                 newDatasetsModel.forEach(function(activity) {
0393                     if(activity.overrideExistingLevels) {
0394                         ActivityInfoTree.resetLevels(activity.activityName);
0395                     }
0396                 })
0397             }
0398             content: Component {
0399                 Column {
0400                     id: activitiesWithNewDatasetsColumn
0401                     spacing: 5 * ApplicationInfo.ratio
0402                     width: parent.width
0403 
0404                     Repeater {
0405                         id: newDatasetsRepeater
0406                         model: newDatasetsDialog.newDatasetsModel
0407 
0408                         delegate: GCDialogCheckBox {
0409                             id: activityCheckbox
0410                             width: parent.width
0411                             labelTextFontSize: 12
0412                             indicatorImageHeight: 40 * ApplicationInfo.ratio
0413                             text: modelData.activityTitle
0414                             checked: modelData.overrideExistingLevels
0415                             onClicked: {
0416                                 var dataset = newDatasetsModel.find(function(v) {
0417                                     return v.activity == modelData.activity;
0418                                 })
0419                                 dataset.overrideExistingLevels = checked;
0420                             }
0421                         }
0422                     }
0423                 }
0424             }
0425 
0426             onClose: {
0427                 newDatasetsDialog.active = false;
0428                 pageView.currentItem.focus = true;
0429             }
0430         }
0431         anchors.fill: pageView
0432         active: false
0433         onStatusChanged: if (status == Loader.Ready) item.start()
0434     }
0435 
0436     function showNewDatasetsDialog(newDatasets) {
0437         newDatasetsDialog.newDatasetsModel = newDatasets;
0438         newDatasetsDialog.active = true;
0439     }
0440 
0441     Loading {
0442         id: loading
0443     }
0444 
0445     StackView {
0446         id: pageView
0447         anchors.fill: parent
0448         focus: true
0449         Component.onCompleted: {
0450             push("qrc:/gcompris/src/activities/" + ActivityInfoTree.rootMenu.name, {
0451                 'audioVoices': audioVoices,
0452                 'audioEffects': audioEffects,
0453                 'loading': loading,
0454                 'backgroundMusic': backgroundMusic
0455             })
0456 
0457             if(ActivityInfoTree.startingActivity != "") {
0458                 startApplicationTimer.start();
0459             }
0460         }
0461 
0462         Timer {
0463             id: startApplicationTimer
0464             interval: 1000
0465             repeat: false
0466 
0467             onTriggered: {
0468                 print("Start activity", ActivityInfoTree.startingActivity, "at level", ActivityInfoTree.startingLevel);
0469                 pageView.currentItem.startActivity(ActivityInfoTree.startingActivity, ActivityInfoTree.startingLevel);
0470             }
0471         }
0472 
0473 
0474         property var enterItem
0475         property var exitItem
0476 
0477         popEnter: (exitItem && exitItem.isDialog) ? popVTransition : popHTransition
0478         popExit: (exitItem && exitItem.isDialog) ? popVTransitionExit : popHTransitionExit
0479         pushEnter: (enterItem && enterItem.isDialog) ? pushVTransition : pushHTransition
0480         pushExit: (enterItem && enterItem.isDialog) ? pushVTransitionExit : pushHTransitionExit
0481 
0482         property Transition pushHTransition: Transition {
0483             PropertyAnimation {
0484                 property: "x"
0485                 from: pageView.width
0486                 to: 0
0487                 duration: 500
0488                 easing.type: Easing.OutSine
0489             }
0490         }
0491 
0492         property Transition pushHTransitionExit: Transition {
0493             PropertyAnimation {
0494                 property: "x"
0495                 from: 0
0496                 to: -pageView.width
0497                 duration: 500
0498                 easing.type: Easing.OutSine
0499             }
0500         }
0501         property Transition popHTransition: Transition {
0502             PropertyAnimation {
0503                 property: "x"
0504                 from: -pageView.width
0505                 to: 0
0506                 duration: 500
0507                 easing.type: Easing.OutSine
0508             }
0509         }
0510         property Transition popHTransitionExit: Transition {
0511             PropertyAnimation {
0512                 property: "x"
0513                 from: 0
0514                 to: pageView.width
0515                 duration: 500
0516                 easing.type: Easing.OutSine
0517             }
0518         }
0519 
0520         property Transition pushVTransition: Transition {
0521             PropertyAnimation {
0522                 property: "y"
0523                 from: -pageView.height
0524                 to: 0
0525                 duration: 500
0526                 easing.type: Easing.OutSine
0527             }
0528         }
0529         property Transition pushVTransitionExit: Transition {
0530             PropertyAnimation {
0531                 property: "y"
0532                 from: 0
0533                 to: pageView.height
0534                 duration: 500
0535                 easing.type: Easing.OutSine
0536             }
0537         }
0538         property Transition popVTransition: Transition {
0539             PropertyAnimation {
0540                 property: "y"
0541                 from: pageView.height
0542                 to: 0
0543                 duration: 500
0544                 easing.type: Easing.OutSine
0545             }
0546         }
0547         property Transition popVTransitionExit: Transition {
0548             PropertyAnimation {
0549                 property: "y"
0550                 from: 0
0551                 to: -pageView.height
0552                 duration: 500
0553                 easing.type: Easing.OutSine
0554             }
0555         }
0556 
0557         function pushElement(element) {
0558             audioVoices.clearQueue()
0559             audioVoices.stop()
0560             enterItem = element
0561             exitItem = currentItem
0562             push(element)
0563 
0564             // if coming from menu and going into an activity then
0565             if(!exitItem.isDialog && !enterItem.isDialog) {
0566                 playIntroVoice(enterItem.activityInfo.name);
0567             }
0568             
0569             if(enterItem.isMusicalActivity) {
0570                 main.isMusicalActivityRunning = true
0571             }
0572 
0573             exitItem.opacity = 1
0574             if(!enterItem.isDialog) {
0575                 exitItem.stop()
0576             }
0577             // Don't restart an activity if you click on help
0578             if (!exitItem.isDialog ||    // if coming from menu or
0579                 enterItem.alwaysStart) { // start signal enforced (for special case like transition from config-dialog to editor)
0580                     enterItem.start();
0581             }
0582         }
0583 
0584         function popElement(element) {
0585             enterItem = pageView.get(pageView.depth-2)
0586             exitItem = currentItem
0587 
0588             if(exitItem.isMusicalActivity) {
0589                 main.isMusicalActivityRunning = false
0590             }
0591 
0592             if(!enterItem.isDialog) {
0593                 currentItem.stop()
0594             }
0595             pop()
0596             // Don't restart an activity if you click on help
0597             if (!exitItem.isDialog ||    // if coming from menu or
0598                 enterItem.alwaysStart) { // start signal enforced (for special case like transition from config-dialog to editor)
0599                     enterItem.start();
0600             }
0601         }
0602     }
0603     /// @endcond
0604 }