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 }