Warning, /education/gcompris/src/activities/click_on_letter/ClickOnLetter.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - ClickOnLetter.qml
0002  *
0003  * SPDX-FileCopyrightText: 2014 Holger Kaelberer <holger.k@elberer.de>
0004  *
0005  * Authors:
0006  *   Pascal Georges <pascal.georges1@free.fr> (GTK+ version)
0007  *   Bruno Coudoin <bruno.coudoin@gcompris.net> (GTK+ Mostly full rewrite)
0008  *   Holger Kaelberer <holger.k@elberer.de> (Qt Quick port)
0009  *
0010  *   SPDX-License-Identifier: GPL-3.0-or-later
0011  */
0012 
0013 import QtQuick 2.12
0014 import GCompris 1.0
0015 import "../../core"
0016 import "click_on_letter.js" as Activity
0017 import "qrc:/gcompris/src/core/core.js" as Core
0018 
0019 ActivityBase {
0020     id: activity
0021     focus: true
0022 
0023     /* mode of the activity, either "lowercase" (click_on_letter)
0024      * or "uppercase" (click_on_letter_up): */
0025     property string mode: "lowercase"
0026 
0027     onStart: focus = true
0028 
0029     // When opening a dialog, it steals the focus and re set it to the activity.
0030     // We need to set it back to the eventHandler item in order to have key events.
0031     onFocusChanged: {
0032         if(focus) {
0033             Activity.focusEventHandler()
0034         }
0035     }
0036 
0037     pageComponent: Image {
0038         id: background
0039         source: Activity.url + "background.svg"
0040         sourceSize.width: width
0041         sourceSize.height: height
0042         fillMode: Image.PreserveAspectCrop
0043         focus: true
0044 
0045         // system locale by default
0046         property string locale: "system"
0047 
0048         signal start
0049         signal stop
0050         signal voiceError
0051         signal voiceDone
0052 
0053         Component.onCompleted: {
0054             dialogActivityConfig.initialize()
0055             activity.start.connect(start)
0056             activity.stop.connect(stop)
0057         }
0058 
0059         QtObject {
0060             id: items
0061             property Item activityPage: activity
0062             property int currentLevel: activity.currentLevel
0063             property alias trainModel: trainModel
0064             property GCAudio audioVoices: activity.audioVoices
0065             property GCSfx audioEffects: activity.audioEffects
0066             property alias parser: parser
0067             property alias questionItem: questionItem
0068             property alias repeatItem: repeatItem
0069             property alias score: score
0070             property alias bonus: bonus
0071             property alias locale: background.locale
0072             property bool keyNavigationMode: false
0073             property int lastSelectedIndex: -1
0074             property alias eventHandler: eventHandler
0075             property alias errorRectangle: errorRectangle
0076             property bool buttonsBlocked: false
0077         }
0078 
0079         onVoiceError: {
0080             questionItem.visible = true;
0081             repeatItem.visible = false;
0082         }
0083 
0084         onStart: {
0085             activity.audioVoices.done.connect(voiceDone);
0086             activity.audioVoices.error.connect(voiceError);
0087             Activity.start(items, mode);
0088             eventHandler.forceActiveFocus();
0089         }
0090 
0091         onStop: Activity.stop()
0092 
0093         Item {
0094             id: eventHandler
0095             focus: true
0096             Keys.enabled: !items.buttonsBlocked && !dialogActivityConfig.visible
0097             Keys.onPressed: {
0098                 if(event.key === Qt.Key_Tab) {
0099                     activity.audioVoices.clearQueue();
0100                     activity.audioVoices.stop();
0101                     Activity.playLetter(Activity.currentLetter);
0102                 } else {
0103                     background.handleKeys(event);
0104                 }
0105             }
0106         }
0107 
0108         DialogChooseLevel {
0109             id: dialogActivityConfig
0110             currentActivity: activity.activityInfo
0111             onClose: {
0112                 home();
0113                 eventHandler.forceActiveFocus();
0114             }
0115             onSaveData: {
0116                 levelFolder = dialogActivityConfig.chosenLevels;
0117                 currentActivity.currentLevels = dialogActivityConfig.chosenLevels;
0118                 ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels);
0119             }
0120             onLoadData: {
0121                 if(activityData && activityData["locale"]) {
0122                     background.locale = Core.resolveLocale(activityData["locale"]);
0123                 }
0124                 else {
0125                     background.locale = Core.resolveLocale(background.locale);
0126                 }
0127             }
0128             onStartActivity: {
0129                 background.stop();
0130                 background.start();
0131                 eventHandler.forceActiveFocus();
0132             }
0133         }
0134 
0135         DialogHelp {
0136             id: dialogHelpLeftRight
0137             onClose: home()
0138         }
0139 
0140         Bar {
0141             id: bar
0142             level: items.currentLevel + 1
0143             content: BarEnumContent { value: help | home | level | activityConfig }
0144             onHelpClicked: {
0145                 displayDialog(dialogHelpLeftRight)
0146             }
0147             onPreviousLevelClicked: Activity.previousLevel()
0148             onNextLevelClicked: Activity.nextLevel()
0149             onHomeClicked: home()
0150             onActivityConfigClicked: {
0151                 displayDialog(dialogActivityConfig)
0152             }
0153         }
0154 
0155         Score {
0156             id: score
0157             anchors.top: parent.top
0158             anchors.topMargin: 10 * ApplicationInfo.ratio
0159             anchors.left: parent.left
0160             anchors.leftMargin: 10 * ApplicationInfo.ratio
0161             anchors.bottom: undefined
0162             anchors.right: undefined
0163             onStop: Activity.nextSubLevel()
0164         }
0165 
0166         Bonus {
0167             id: bonus
0168             Component.onCompleted: win.connect(Activity.nextLevel)
0169         }
0170 
0171         BarButton {
0172             id: repeatItem
0173             source: "qrc:/gcompris/src/core/resource/bar_repeat.svg";
0174             sourceSize.width: 80 * ApplicationInfo.ratio
0175             anchors {
0176                 top: parent.top
0177                 right: parent.right
0178                 margins: 10
0179             }
0180             onClicked: {
0181                 if(!activity.audioVoices.isPlaying()) {
0182                     Activity.playLetter(Activity.currentLetter);
0183                 }
0184             }
0185         }
0186 
0187         Image {
0188             id: railway
0189             source: Activity.url + "railway.svg"
0190             fillMode: Image.PreserveAspectCrop
0191             anchors.bottom: bar.top
0192             anchors.left: parent.left
0193             anchors.right: parent.right
0194             sourceSize.width: width
0195             sourceSize.height: height
0196             anchors.bottomMargin: 13 * ApplicationInfo.ratio
0197         }
0198 
0199         Rectangle {
0200             id: questionItem
0201             anchors.fill: repeatItem
0202             border.color: "#FFFFFF"
0203             border.width: 2
0204             color: "#2881C3"
0205             radius: 10
0206             visible: false
0207 
0208             property alias text: questionText.text
0209 
0210             GCText {
0211                 id: questionText
0212                 anchors.centerIn: parent
0213                 width: repeatItem.width
0214                 height: repeatItem.height
0215                 horizontalAlignment: Text.AlignHCenter
0216                 verticalAlignment: Text.AlignVCenter
0217                 z:11
0218                 text: ""
0219                 fontSizeMode: Text.Fit
0220                 minimumPointSize: 10
0221                 fontSize: width > 0 ? width : 128
0222                 font.bold: true
0223                 color: "white"
0224             }
0225         }
0226 
0227         ListModel {
0228             id: trainModel
0229         }
0230 
0231         //always calculate layout for a maximum of 24 letters per level
0232         //adding 1 extra to px and py to include one more item (the engine) in the calculation
0233         property int itemWidth: Core.fitItems(wholeTrainArea.width, wholeTrainArea.height, 24, 1)
0234 
0235         Image {
0236             id: engine
0237             source: Activity.url + "engine.svg"
0238             anchors.bottom: railway.bottom
0239             anchors.left: railway.left
0240             anchors.leftMargin: 10 * ApplicationInfo.ratio
0241             anchors.bottomMargin: 5 * ApplicationInfo.ratio
0242             sourceSize.width: itemWidth
0243             sourceSize.height: itemWidth
0244             fillMode: Image.PreserveAspectFit
0245         }
0246 
0247         Image {
0248             id: smoke
0249             source: Activity.url + "smoke.svg"
0250             anchors.bottom: engine.top
0251             anchors.left: railway.left
0252             anchors.leftMargin: 10 * ApplicationInfo.ratio
0253             anchors.bottomMargin: 5 * ApplicationInfo.ratio
0254             sourceSize.width: engine.width
0255             fillMode: Image.PreserveAspectFit
0256         }
0257 
0258         Item {
0259             id: wholeTrainArea
0260             anchors.bottom: bar.top
0261             anchors.left: background.left
0262             anchors.right: background.right
0263             anchors.top: repeatItem.bottom
0264             anchors.leftMargin: 10 * ApplicationInfo.ratio
0265             anchors.bottomMargin: 5 * ApplicationInfo.ratio
0266         }
0267 
0268         GridView {
0269             id: train
0270             anchors.bottom: engine.bottom
0271             anchors.top: wholeTrainArea.top
0272             anchors.right: wholeTrainArea.right
0273             anchors.left: engine.right
0274             cellWidth: itemWidth
0275             cellHeight: itemWidth
0276             clip: false
0277             interactive: false
0278             verticalLayoutDirection: GridView.BottomToTop
0279             layoutDirection: Qt.LeftToRight
0280             keyNavigationWraps: true
0281             currentIndex: -1
0282 
0283             model: trainModel
0284             delegate: Carriage {
0285                 width: train.cellWidth
0286                 height: train.cellHeight
0287                 nbCarriage: train.width / train.cellWidth - 1
0288                 clickEnabled: items.buttonsBlocked ? false : (activity.audioVoices.playbackState == 1 ? false : true)
0289                 isSelected: train.currentIndex === index
0290             }
0291         }
0292 
0293         ErrorRectangle {
0294             id: errorRectangle
0295             z: 20
0296             anchors.centerIn: parent
0297             width: 2
0298             height: 2
0299             imageSize: train.cellWidth * 0.75
0300             function releaseControls() {
0301                 items.buttonsBlocked = false;
0302             }
0303         }
0304 
0305         function moveErrorRectangle(clickedItem) {
0306             errorRectangle.parent = clickedItem
0307             errorRectangle.startAnimation()
0308             items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav");
0309         }
0310 
0311         function handleKeys(event) {
0312             if(!items.keyNavigationMode) {
0313                 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav');
0314                 items.keyNavigationMode = true;
0315                 if(items.lastSelectedIndex > 0 && items.lastSelectedIndex < trainModel.count)
0316                     train.currentIndex = items.lastSelectedIndex;
0317                 else
0318                     train.currentIndex = 0;
0319             } else if(event.key === Qt.Key_Right) {
0320                 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav');
0321                 train.moveCurrentIndexRight();
0322             } else if(event.key === Qt.Key_Left) {
0323                 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav');
0324                 train.moveCurrentIndexLeft();
0325             } else if(event.key === Qt.Key_Up) {
0326                 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav');
0327                 train.moveCurrentIndexUp();
0328             } else if(event.key === Qt.Key_Down) {
0329                 activity.audioEffects.play('qrc:/gcompris/src/core/resource/sounds/smudge.wav');
0330                 train.moveCurrentIndexDown();
0331             } else if(event.key === Qt.Key_Space && activity.audioVoices.playbackState != 1) {
0332                 items.buttonsBlocked = true;
0333                 if(Activity.checkAnswer(train.currentIndex)) {
0334                     train.currentItem.successAnimation.restart();
0335                     train.currentItem.particle.burst(30);
0336                 } else {
0337                     moveErrorRectangle(train.currentItem);
0338                 }
0339             }
0340         }
0341 
0342         JsonParser {
0343             id: parser
0344             onError: console.error("Click_on_letter: Error parsing JSON: " + msg);
0345         }
0346 
0347     }
0348 }