1/* GCompris - main.qml 2 * 3 * SPDX-FileCopyrightText: 2014 Bruno Coudoin <bruno.coudoin@gcompris.net> 4 * 5 * Authors: 6 * Bruno Coudoin <bruno.coudoin@gcompris.net> 7 * 8 * SPDX-License-Identifier: GPL-3.0-or-later 9 */ 10import QtQuick 2.9 11import QtQuick.Controls 1.5 12import QtQuick.Window 2.2 13import QtQml 2.2 14 15import GCompris 1.0 16import "qrc:/gcompris/src/core/core.js" as Core 17 18/** 19 * GCompris' main QML file defining the top level window. 20 * @ingroup infrastructure 21 * 22 * Handles application start (Component.onCompleted) and shutdown (onClosing) 23 * on the QML layer. 24 * 25 * Contains the central GCAudio objects audio effects and audio voices. 26 * 27 * Contains the top level StackView presenting and animating GCompris' 28 * full screen views. 29 * 30 * @sa BarButton, BarEnumContent 31 * @inherit QtQuick.Window 32 */ 33Window { 34 id: main 35 // Start in window mode at full screen size 36 width: ApplicationSettings.previousWidth 37 height: ApplicationSettings.previousHeight 38 minimumWidth: 400 * ApplicationInfo.ratio 39 minimumHeight: 400 * ApplicationInfo.ratio 40 title: "GCompris" 41 42 /// @cond INTERNAL_DOCS 43 44 property var applicationState: Qt.application.state 45 property var rccBackgroundMusic: ApplicationInfo.getBackgroundMusicFromRcc() 46 property var filteredBackgroundMusic: ApplicationSettings.filteredBackgroundMusic 47 property alias backgroundMusic: backgroundMusic 48 property bool voicesDownloaded: true 49 property bool wordSetDownloaded: true 50 property bool musicDownloaded: true 51 property bool welcomePlayed: false 52 property int lastGCVersionRanCopy: ApplicationInfo.GCVersionCode 53 54 /** 55 * type: bool 56 * It tells whether a musical activity is running. 57 * 58 * 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. 59 */ 60 property bool isMusicalActivityRunning: false 61 62 /** 63 * When a musical activity is started, the backgroundMusic pauses. 64 * 65 * When returning back from the musical activity to menu, backgroundMusic resumes. 66 */ 67 onIsMusicalActivityRunningChanged: { 68 if(isMusicalActivityRunning) { 69 backgroundMusic.pause(); 70 } 71 else { 72 backgroundMusic.resume(); 73 } 74 } 75 76 onApplicationStateChanged: { 77 if(ApplicationInfo.isMobile && applicationState !== Qt.ApplicationActive) { 78 audioVoices.stop(); 79 audioEffects.stop(); 80 backgroundMusic.pause(); 81 } else if(ApplicationInfo.isMobile && applicationState == Qt.ApplicationActive 82 && !isMusicalActivityRunning) { 83 backgroundMusic.resume(); 84 } 85 } 86 87 onClosing: Core.quit(pageView) 88 89 GCAudio { 90 id: audioVoices 91 muted: !ApplicationSettings.isAudioVoicesEnabled && !main.isMusicalActivityRunning 92 93 Timer { 94 id: delayedWelcomeTimer 95 interval: 10000 /* Make sure, that playing welcome.ogg if delayed 96 * because of not yet registered voices, will only 97 * happen max 10sec after startup */ 98 repeat: false 99 100 onTriggered: { 101 DownloadManager.voicesRegistered.disconnect(playWelcome); 102 } 103 104 function playWelcome() { 105 if(!welcomePlayed) { 106 audioVoices.append(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/misc/welcome.$CA")); 107 welcomePlayed = true; 108 } 109 } 110 } 111 112 Component.onCompleted: { 113 if (DownloadManager.areVoicesRegistered()) 114 delayedWelcomeTimer.playWelcome(); 115 else { 116 DownloadManager.voicesRegistered.connect( 117 delayedWelcomeTimer.playWelcome); 118 delayedWelcomeTimer.start(); 119 } 120 } 121 } 122 123 GCSfx { 124 id: audioEffects 125 muted: !ApplicationSettings.isAudioEffectsEnabled && !main.isMusicalActivityRunning 126 volume: ApplicationSettings.audioEffectsVolume 127 } 128 129 GCAudio { 130 id: backgroundMusic 131 isBackgroundMusic: true 132 muted: !ApplicationSettings.isBackgroundMusicEnabled 133 volume: ApplicationSettings.backgroundMusicVolume 134 135 onMutedChanged: { 136 if(!hasAudio && !files.length) { 137 backgroundMusic.playBackgroundMusic() 138 } 139 } 140 141 onDone: backgroundMusic.playBackgroundMusic() 142 143 function playBackgroundMusic() { 144 rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() 145 146 for(var i = 0; i < filteredBackgroundMusic.length; i++) { 147 backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[i])) 148 } 149 if(main.isMusicalActivityRunning) 150 backgroundMusic.pause() 151 } 152 153 Component.onCompleted: { 154 if(ApplicationSettings.isBackgroundMusicEnabled) 155 backgroundMusic.append(ApplicationInfo.getAudioFilePath("qrc:/gcompris/src/core/resource/intro.$CA")) 156 if(ApplicationSettings.isBackgroundMusicEnabled 157 && DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) { 158 backgroundMusic.playBackgroundMusic() 159 } 160 else { 161 DownloadManager.backgroundMusicRegistered.connect(backgroundMusic.playBackgroundMusic) 162 } 163 } 164 } 165 166 function playIntroVoice(name) { 167 name = name.split("/")[0] 168 audioVoices.play(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/intro/" + name + ".$CA")) 169 } 170 171 function checkWordset() { 172 var wordset = ApplicationSettings.wordset 173 if(wordset === '') 174 // Maybe the wordset has been bundled or copied manually 175 // we have to register it if we find it. 176 wordset = 'data2/words/words.rcc' 177 178 // check for words.rcc: 179 if (DownloadManager.isDataRegistered("words")) { 180 // words.rcc is already registered -> nothing to do 181 } else if(DownloadManager.haveLocalResource(wordset)) { 182 // words.rcc is there -> register old file first 183 // then try to update in the background 184 if(DownloadManager.updateResource(wordset)) { 185 ApplicationSettings.wordset = wordset 186 } 187 } else if(ApplicationSettings.useWordset) { // Only if external wordset is enabled 188 // words.rcc has not been downloaded yet -> ask for download 189 wordSetDownloaded = false; 190 } 191 192 //disable wordset if useWordset config is false 193 if(!ApplicationSettings.useWordset) { 194 ApplicationSettings.wordset = ""; 195 } 196 } 197 198 function checkBackgroundMusic() { 199 var music = DownloadManager.getBackgroundMusicResources() 200 if(rccBackgroundMusic === '') { 201 rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() 202 } 203 if(music === '') { 204 music = DownloadManager.getBackgroundMusicResources() 205 } 206 // We have local music but it is not yet registered 207 else if(!DownloadManager.isDataRegistered("backgroundMusic") && DownloadManager.haveLocalResource(music)) { 208 // We have music and automatic download is enabled. Download the music and register it 209 if(DownloadManager.updateResource(music) && DownloadManager.downloadIsRunning()) { 210 DownloadManager.registerResource(music) 211 rccBackgroundMusic = Core.shuffle(ApplicationInfo.getBackgroundMusicFromRcc()) 212 } 213 else { 214 rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc() 215 } 216 } 217 else if(ApplicationSettings.isBackgroundMusicEnabled && !DownloadManager.haveLocalResource(music)) { 218 musicDownloaded = false; 219 } 220 } 221 222 function checkVoices() { 223 if(!DownloadManager.haveLocalResource(DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale))) 224 voicesDownloaded = false; 225 else 226 DownloadManager.registerResource(DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)); 227 } 228 229 function initialAssetsDownload() { 230 checkVoices(); 231 checkWordset(); 232 checkBackgroundMusic(); 233 var voicesLine = voicesDownloaded ? "" : ("<li>" + qsTr("Voices for your language") + "</li>"); 234 var wordSetLine = wordSetDownloaded ? "" : ("<li>" + qsTr("Full word image set") + "</li>"); 235 var musicLine = musicDownloaded ? "" : ("<li>" + qsTr("Background music") + "</li>"); 236 if(!voicesDownloaded || !wordSetDownloaded || ! musicDownloaded) { 237 var dialog; 238 dialog = Core.showMessageDialog( 239 pageView.currentItem, 240 qsTr("Do you want to download the following external assets?") 241 + ("<ul>") 242 + voicesLine 243 + wordSetLine 244 + musicLine 245 + "</ul>", 246 qsTr("Yes"), 247 function() { 248 if(!voicesDownloaded) 249 DownloadManager.downloadResource(DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)); 250 if(!wordSetDownloaded) 251 DownloadManager.downloadResource('data2/words/words.rcc'); 252 if(!musicDownloaded) 253 DownloadManager.downloadResource(DownloadManager.getBackgroundMusicResources()); 254 var downloadDialog = Core.showDownloadDialog(pageView.currentItem, {}); 255 }, 256 qsTr("No"), null, 257 null 258 ); 259 260 } 261 } 262 263 ChangeLog { 264 id: changelog 265 } 266 267 Component.onCompleted: { 268 console.log("enter main.qml (run #" + ApplicationSettings.exeCount 269 + ", ratio=" + ApplicationInfo.ratio 270 + ", fontRatio=" + ApplicationInfo.fontRatio 271 + ", dpi=" + Math.round(Screen.pixelDensity*25.4) 272 + ", userDataPath=" + ApplicationSettings.userDataPath 273 + ")"); 274 if (ApplicationSettings.exeCount === 1 && 275 !ApplicationSettings.isKioskMode && 276 ApplicationInfo.isDownloadAllowed) { 277 // first run 278 var dialog; 279 dialog = Core.showMessageDialog( 280 pageView, 281 qsTr("Welcome to GCompris!") + ("<br>") 282 + qsTr("You are running GCompris for the first time.") + "\n" 283 + qsTr("You should verify that your application settings especially your language is set correctly, and that all language specific sound files are installed. You can do this in the Preferences Dialog.") 284 + "\n" 285 + qsTr("Have Fun!") 286 + ("<br><br>") 287 + qsTr("Your current language is %1 (%2).") 288 .arg(Qt.locale(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)).nativeLanguageName) 289 .arg(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)), 290 "", null, 291 "", null, 292 function() { 293 pageView.currentItem.focus = true; 294 initialAssetsDownload(); 295 } 296 ); 297 } 298 else { 299 // Register voices-resources for current locale, updates/downloads only if 300 // not prohibited by the settings 301 if(!DownloadManager.areVoicesRegistered()) { 302 DownloadManager.updateResource( 303 DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)); 304 } 305 checkWordset(); 306 checkBackgroundMusic(); 307 if(changelog.isNewerVersion(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode)) { 308 lastGCVersionRanCopy = ApplicationSettings.lastGCVersionRan; 309 // display log between ApplicationSettings.lastGCVersionRan and ApplicationInfo.GCVersionCode 310 Core.showMessageDialog( 311 pageView, 312 qsTr("GCompris has been updated! Here are the new changes:<br/>") + changelog.getLogBetween(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode), 313 "", null, 314 "", null, 315 function() { pageView.currentItem.focus = true } 316 ); 317 // Store new version after update 318 ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode; 319 } 320 } 321 //Store version on first run in any case 322 if(ApplicationSettings.lastGCVersionRan === 0) 323 ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode; 324 } 325 326 Loading { 327 id: loading 328 } 329 330 StackView { 331 id: pageView 332 anchors.fill: parent 333 initialItem: { 334 "item": "qrc:/gcompris/src/activities/" + ActivityInfoTree.rootMenu.name, 335 "properties": { 336 'audioVoices': audioVoices, 337 'audioEffects': audioEffects, 338 'loading': loading, 339 'backgroundMusic': backgroundMusic 340 } 341 } 342 343 focus: true 344 345 delegate: StackViewDelegate { 346 id: root 347 function getTransition(properties) 348 { 349 audioVoices.clearQueue() 350 audioVoices.stop() 351 352 if(!properties.exitItem.isDialog && // if coming from menu and 353 !properties.enterItem.isDialog) // going into an activity then 354 playIntroVoice(properties.enterItem.activityInfo.name); // play intro 355 356 // Don't restart an activity if you click on help 357 if (!properties.exitItem.isDialog || // if coming from menu or 358 properties.enterItem.alwaysStart) // start signal enforced (for special case like transition from config-dialog to editor) 359 properties.enterItem.start(); 360 361 if(properties.name === "pushTransition") { 362 if(properties.enterItem.isDialog) { 363 return pushVTransition 364 } else { 365 if(properties.enterItem.isMusicalActivity) 366 main.isMusicalActivityRunning = true 367 return pushHTransition 368 } 369 } else { 370 if(properties.exitItem.isDialog) { 371 return popVTransition 372 } else { 373 main.isMusicalActivityRunning = false 374 return popHTransition 375 } 376 377 } 378 } 379 380 function transitionFinished(properties) 381 { 382 properties.exitItem.opacity = 1 383 if(!properties.enterItem.isDialog) { 384 properties.exitItem.stop() 385 } 386 } 387 388 property Component pushHTransition: StackViewTransition { 389 PropertyAnimation { 390 target: enterItem 391 property: "x" 392 from: target.width 393 to: 0 394 duration: 500 395 easing.type: Easing.OutSine 396 } 397 PropertyAnimation { 398 target: exitItem 399 property: "x" 400 from: 0 401 to: -target.width 402 duration: 500 403 easing.type: Easing.OutSine 404 } 405 } 406 407 property Component popHTransition: StackViewTransition { 408 PropertyAnimation { 409 target: enterItem 410 property: "x" 411 from: -target.width 412 to: 0 413 duration: 500 414 easing.type: Easing.OutSine 415 } 416 PropertyAnimation { 417 target: exitItem 418 property: "x" 419 from: 0 420 to: target.width 421 duration: 500 422 easing.type: Easing.OutSine 423 } 424 } 425 426 property Component pushVTransition: StackViewTransition { 427 PropertyAnimation { 428 target: enterItem 429 property: "y" 430 from: -target.height 431 to: 0 432 duration: 500 433 easing.type: Easing.OutSine 434 } 435 PropertyAnimation { 436 target: exitItem 437 property: "y" 438 from: 0 439 to: target.height 440 duration: 500 441 easing.type: Easing.OutSine 442 } 443 } 444 445 property Component popVTransition: StackViewTransition { 446 PropertyAnimation { 447 target: enterItem 448 property: "y" 449 from: target.height 450 to: 0 451 duration: 500 452 easing.type: Easing.OutSine 453 } 454 PropertyAnimation { 455 target: exitItem 456 property: "y" 457 from: 0 458 to: -target.height 459 duration: 500 460 easing.type: Easing.OutSine 461 } 462 } 463 464 property Component replaceTransition: pushHTransition 465 } 466 } 467 /// @endcond 468} 469