main.qml 17.3 KB
Newer Older
1 2
/* GCompris - main.qml
 *
3
 * Copyright (C) 2014 Bruno Coudoin <bruno.coudoin@gcompris.net>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * Authors:
 *   Bruno Coudoin <bruno.coudoin@gcompris.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
19
 *   along with this program; if not, see <https://www.gnu.org/licenses/>.
20
 */
21 22
import QtQuick 2.6
import QtQuick.Controls 1.5
23
import QtQuick.Window 2.2
24
import QtQml 2.2
25

26
import GCompris 1.0
27
import "qrc:/gcompris/src/core/core.js" as Core
28

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
/**
 * GCompris' main QML file defining the top level window.
 * @ingroup infrastructure
 *
 * Handles application start (Component.onCompleted) and shutdown (onClosing)
 * on the QML layer.
 *
 * Contains the central GCAudio objects audio effects and audio voices.
 *
 * Contains the top level StackView presenting and animating GCompris'
 * full screen views.
 *
 * @sa BarButton, BarEnumContent
 * @inherit QtQuick.Window
 */
44
Window {
45
    id: main
46
    // Start in window mode at full screen size
47 48
    width: ApplicationSettings.previousWidth
    height: ApplicationSettings.previousHeight
49 50
    minimumWidth: 400 * ApplicationInfo.ratio
    minimumHeight: 400 * ApplicationInfo.ratio
51
    title: "GCompris"
52

53 54
    /// @cond INTERNAL_DOCS

55
    property var applicationState: Qt.application.state
56
    property var rccBackgroundMusic: ApplicationInfo.getBackgroundMusicFromRcc()
57
    property var filteredBackgroundMusic: ApplicationSettings.filteredBackgroundMusic
58
    property alias backgroundMusic: backgroundMusic
Timothée Giet's avatar
Timothée Giet committed
59 60 61
    property bool voicesDownloaded: true
    property bool wordSetDownloaded: true
    property bool musicDownloaded: true
62

63 64
    /**
     * type: bool
65
     * It tells whether a musical activity is running.
66
     *
67
     * 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.
68
     */
69
    property bool isMusicalActivityRunning: false
70

71
    /**
72
     * When a musical activity is started, the backgroundMusic pauses.
73
     *
74
     * When returning back from the musical activity to menu, backgroundMusic resumes.
75
     */
76 77
    onIsMusicalActivityRunningChanged: {
        if(isMusicalActivityRunning) {
78 79 80 81 82 83 84
            backgroundMusic.pause()
        }
        else {
            backgroundMusic.resume()
        }
    }

85 86 87 88 89 90 91
    onApplicationStateChanged: {
        if (ApplicationInfo.isMobile && applicationState != Qt.ApplicationActive) {
            audioVoices.stop();
            audioEffects.stop();
        }
    }

92
    onClosing: Core.quit(pageView)
93

94
    GCAudio {
Bruno Coudoin's avatar
Bruno Coudoin committed
95
        id: audioVoices
96
        muted: !ApplicationSettings.isAudioVoicesEnabled && !main.isMusicalActivityRunning
97

98 99 100 101 102 103 104 105
        Timer {
            id: delayedWelcomeTimer
            interval: 10000 /* Make sure, that playing welcome.ogg if delayed
                             * because of not yet registered voices, will only
                             * happen max 10sec after startup */
            repeat: false

            onTriggered: {
106
                DownloadManager.voicesRegistered.disconnect(playWelcome);
107 108 109
            }

            function playWelcome() {
110
                audioVoices.append(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/misc/welcome.$CA"));
111 112 113
            }
        }

114
        Component.onCompleted: {
115 116 117 118 119 120 121
            if (DownloadManager.areVoicesRegistered())
                delayedWelcomeTimer.playWelcome();
            else {
                DownloadManager.voicesRegistered.connect(
                        delayedWelcomeTimer.playWelcome);
                delayedWelcomeTimer.start();
            }
122
        }
Bruno Coudoin's avatar
Bruno Coudoin committed
123 124
    }

Timothée Giet's avatar
Timothée Giet committed
125
    GCSfx {
Bruno Coudoin's avatar
Bruno Coudoin committed
126
        id: audioEffects
127
        muted: !ApplicationSettings.isAudioEffectsEnabled && !main.isMusicalActivityRunning
128
        volume: ApplicationSettings.audioEffectsVolume
129
    }
130

131 132
    GCAudio {
        id: backgroundMusic
133
        isBackgroundMusic: true
134
        muted: !ApplicationSettings.isBackgroundMusicEnabled
135 136 137
        volume: ApplicationSettings.backgroundMusicVolume

        onMutedChanged: {
138 139
            if(!hasAudio && !files.length) {
                backgroundMusic.playBackgroundMusic()
140 141 142
            }
        }

143
        onDone: backgroundMusic.playBackgroundMusic()
144

145 146
        function playBackgroundMusic() {
            rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc()
147

148 149
            for(var i = 0; i < filteredBackgroundMusic.length; i++) {
                backgroundMusic.append(ApplicationInfo.getAudioFilePath("backgroundMusic/" + filteredBackgroundMusic[i]))
150
            }
151 152
            if(main.isMusicalActivityRunning)
                backgroundMusic.pause()
153
        }
154

155
        Component.onCompleted: {
156 157
            if(ApplicationSettings.isBackgroundMusicEnabled)
                backgroundMusic.append(ApplicationInfo.getAudioFilePath("qrc:/gcompris/src/core/resource/intro.$CA"))
158 159
            if(ApplicationSettings.isBackgroundMusicEnabled
               && DownloadManager.haveLocalResource(DownloadManager.getBackgroundMusicResources())) {
160
                   backgroundMusic.playBackgroundMusic()
161 162
            }
            else {
163
                DownloadManager.backgroundMusicRegistered.connect(backgroundMusic.playBackgroundMusic)
164 165
            }
        }
166
    }
167 168 169

    function playIntroVoice(name) {
        name = name.split("/")[0]
170
        audioVoices.play(ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/intro/" + name + ".$CA"))
171 172
    }

173
    function checkWordset() {
174
        var wordset = ApplicationSettings.wordset
175 176 177
        if(wordset == '')
            // Maybe the wordset has been bundled or copied manually
            // we have to register it if we find it.
178
            wordset = 'data2/words/words.rcc'
179 180 181 182

        // check for words.rcc:
        if (DownloadManager.isDataRegistered("words")) {
            // words.rcc is already registered -> nothing to do
183
        } else if(DownloadManager.haveLocalResource(wordset)) {
184 185
            // words.rcc is there -> register old file first
            // then try to update in the background
186
            if(DownloadManager.updateResource(wordset)) {
187
                ApplicationSettings.wordset = wordset
188
            }
189
        } else if(ApplicationSettings.useWordset) { // Only if external wordset is enabled
190
            // words.rcc has not been downloaded yet -> ask for download
Timothée Giet's avatar
Timothée Giet committed
191
            wordSetDownloaded = false;
192
        }
193

Timothée Giet's avatar
Timothée Giet committed
194
        //disable wordset if useWordset config is false
195 196 197
        if(!ApplicationSettings.useWordset) {
            ApplicationSettings.wordset = "";
        }
198
    }
199 200 201 202 203 204 205 206 207

    function checkBackgroundMusic() {
        var music = DownloadManager.getBackgroundMusicResources()
        if(rccBackgroundMusic == '') {
            rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc()
        }
        if(music == '') {
            music = DownloadManager.getBackgroundMusicResources()
        }
208
        // We have local music but it is not yet registered
209
        else if(!DownloadManager.isDataRegistered("backgroundMusic") && DownloadManager.haveLocalResource(music)) {
210 211 212 213 214 215 216
            // We have music and automatic download is enabled. Download the music and register it
            if(DownloadManager.updateResource(music) && DownloadManager.downloadIsRunning()) {
                DownloadManager.registerResource(music)
                rccBackgroundMusic = Core.shuffle(ApplicationInfo.getBackgroundMusicFromRcc())
            }
            else {
                rccBackgroundMusic = ApplicationInfo.getBackgroundMusicFromRcc()
217 218
            }
        }
219
        else if(ApplicationSettings.isBackgroundMusicEnabled && !DownloadManager.haveLocalResource(music)) {
Timothée Giet's avatar
Timothée Giet committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
            musicDownloaded = false;
        }
    }

    function checkVoices() {
        if(!DownloadManager.haveLocalResource(DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale)))
            voicesDownloaded = false;
    }

    function initialAssetsDownload() {
        checkVoices();
        checkWordset();
        checkBackgroundMusic();
        var voicesLine = voicesDownloaded ? "" : ("<br>") + "-" + qsTr("Voices for your language");
        var wordSetLine = wordSetDownloaded ? "" : ("<br>") + "-" + qsTr("Full word image set");
        var musicLine = musicDownloaded ? "" : ("<br>") + "-" + qsTr("Background music");
        if(!voicesDownloaded || !wordSetDownloaded || ! musicDownloaded) {
            var dialog;
            dialog = Core.showMessageDialog(
                pageView,
Johnny Jazeix's avatar
Johnny Jazeix committed
240
                qsTr("Do you want to download the following external assets?")
Timothée Giet's avatar
Timothée Giet committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
                + ("<br>")
                + voicesLine
                + wordSetLine
                + musicLine,
                qsTr("Yes"),
                function() {
                    if(!voicesDownloaded)
                        DownloadManager.downloadResource(DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale));
                    if(!wordSetDownloaded)
                        DownloadManager.downloadResource('data2/words/words.rcc');
                    if(!musicDownloaded)
                        DownloadManager.downloadResource(DownloadManager.getBackgroundMusicResources());
                    var downloadDialog = Core.showDownloadDialog(pageView, {});
                },
                qsTr("No"), null,
                function() {
                    pageView.currentItem.focus = true;
258
                }
259
            );
Timothée Giet's avatar
Timothée Giet committed
260

261
        }
262 263
    }

264 265 266
    ChangeLog {
       id: changelog
    }
267

268
    Component.onCompleted: {
269
        console.log("enter main.qml (run #" + ApplicationSettings.exeCount
270 271 272
                    + ", ratio=" + ApplicationInfo.ratio
                    + ", fontRatio=" + ApplicationInfo.fontRatio
                    + ", dpi=" + Math.round(Screen.pixelDensity*25.4)
273
                    + ", userDataPath=" + ApplicationSettings.userDataPath
274
                    + ")");
275 276 277
        if (ApplicationSettings.exeCount === 1 &&
                !ApplicationSettings.isKioskMode &&
                ApplicationInfo.isDownloadAllowed) {
278 279
            // first run
            var dialog;
280
            dialog = Core.showMessageDialog(
281
                        pageView,
Timothée Giet's avatar
Timothée Giet committed
282 283
                        qsTr("Welcome to GCompris!") + ("<br>")
                        + qsTr("You are running GCompris for the first time.") + "\n"
284 285 286
                        + 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.")
                        + "\n"
                        + qsTr("Have Fun!")
Timothée Giet's avatar
Timothée Giet committed
287
                        + ("<br><br>")
288
                        + qsTr("Your current language is %1 (%2).")
289
                          .arg(Qt.locale(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)).nativeLanguageName)
Timothée Giet's avatar
Timothée Giet committed
290 291 292
                          .arg(ApplicationInfo.getVoicesLocale(ApplicationSettings.locale)),
                        "", null,
                        "", null,
293
                        function() {
Timothée Giet's avatar
Timothée Giet committed
294 295
                            pageView.currentItem.focus = true;
                            initialAssetsDownload();
296
                        }
297
             );
298 299
        }
        else {
300 301
            // Register voices-resources for current locale, updates/downloads only if
            // not prohibited by the settings
Timothée Giet's avatar
Timothée Giet committed
302
            if(!DownloadManager.areVoicesRegistered()) {
303 304
                DownloadManager.updateResource(
                    DownloadManager.getVoicesResourceForLocale(ApplicationSettings.locale));
305
            }
Timothée Giet's avatar
Timothée Giet committed
306 307
            checkWordset();
            checkBackgroundMusic();
308 309
            if(changelog.isNewerVersion(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode)) {
                // display log between ApplicationSettings.lastGCVersionRan and ApplicationInfo.GCVersionCode
310
                Core.showMessageDialog(
311
                pageView,
Johnny Jazeix's avatar
Johnny Jazeix committed
312
                qsTr("GCompris has been updated! Here are the new changes:<br/>") + changelog.getLogBetween(ApplicationSettings.lastGCVersionRan, ApplicationInfo.GCVersionCode),
313 314 315 316 317 318 319
                "", null,
                "", null,
                function() { pageView.currentItem.focus = true }
                );
                // Store new version
                ApplicationSettings.lastGCVersionRan = ApplicationInfo.GCVersionCode;
            }
320 321
        }
    }
322

323 324 325 326
    Loading {
        id: loading
    }

327 328 329
    StackView {
        id: pageView
        anchors.fill: parent
330 331 332 333
        initialItem: {
            "item": "qrc:/gcompris/src/activities/" + ActivityInfoTree.rootMenu.name,
            "properties": {
                'audioVoices': audioVoices,
334
                'audioEffects': audioEffects,
335 336
                'loading': loading,
                'backgroundMusic': backgroundMusic
337 338 339
            }
        }

340
        focus: true
Johnny Jazeix's avatar
Johnny Jazeix committed
341

342
        delegate: StackViewDelegate {
343 344 345
            id: root
            function getTransition(properties)
            {
346
                audioVoices.clearQueue()
347 348
                audioVoices.stop()

349 350
                if(!properties.exitItem.isDialog &&        // if coming from menu and
                        !properties.enterItem.isDialog)    // going into an activity then
351
                    playIntroVoice(properties.enterItem.activityInfo.name); // play intro
352

353 354 355
                // Don't restart an activity if you click on help
                if (!properties.exitItem.isDialog ||       // if coming from menu or
                    properties.enterItem.alwaysStart)  // start signal enforced (for special case like transition from config-dialog to editor)
356
                    properties.enterItem.start();
357

358 359 360 361
                if(properties.name === "pushTransition") {
                    if(properties.enterItem.isDialog) {
                        return pushVTransition
                    } else {
362 363
                        if(properties.enterItem.isMusicalActivity)
                            main.isMusicalActivityRunning = true
364 365 366 367 368 369
                        return pushHTransition
                    }
                } else {
                    if(properties.exitItem.isDialog) {
                        return popVTransition
                    } else {
370
                        main.isMusicalActivityRunning = false
371 372 373 374 375 376
                        return popHTransition
                    }

                }
            }

377 378 379 380 381
            function transitionFinished(properties)
            {
                properties.exitItem.opacity = 1
                if(!properties.enterItem.isDialog) {
                    properties.exitItem.stop()
382
                }
383 384 385
            }

            property Component pushHTransition: StackViewTransition {
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
                PropertyAnimation {
                    target: enterItem
                    property: "x"
                    from: target.width
                    to: 0
                    duration: 500
                    easing.type: Easing.OutSine
                }
                PropertyAnimation {
                    target: exitItem
                    property: "x"
                    from: 0
                    to: -target.width
                    duration: 500
                    easing.type: Easing.OutSine
                }
            }
403 404

            property Component popHTransition: StackViewTransition {
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
                PropertyAnimation {
                    target: enterItem
                    property: "x"
                    from: -target.width
                    to: 0
                    duration: 500
                    easing.type: Easing.OutSine
                }
                PropertyAnimation {
                    target: exitItem
                    property: "x"
                    from: 0
                    to: target.width
                    duration: 500
                    easing.type: Easing.OutSine
420 421
                }
            }
422

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
            property Component pushVTransition: StackViewTransition {
                PropertyAnimation {
                    target: enterItem
                    property: "y"
                    from: -target.height
                    to: 0
                    duration: 500
                    easing.type: Easing.OutSine
                }
                PropertyAnimation {
                    target: exitItem
                    property: "y"
                    from: 0
                    to: target.height
                    duration: 500
                    easing.type: Easing.OutSine
                }
            }

            property Component popVTransition: StackViewTransition {
                PropertyAnimation {
                    target: enterItem
                    property: "y"
                    from: target.height
                    to: 0
                    duration: 500
                    easing.type: Easing.OutSine
                }
                PropertyAnimation {
                    target: exitItem
                    property: "y"
                    from: 0
                    to: -target.height
                    duration: 500
                    easing.type: Easing.OutSine
458 459
                }
            }
460 461

            property Component replaceTransition: pushHTransition
462 463
        }
    }
464
    /// @endcond
465
}