Warning, /education/gcompris/src/activities/morse_code/MorseCode.qml is written in an unsupported language. File is not indexed.
0001 /* GCompris - MorseCode.qml 0002 * 0003 * SPDX-FileCopyrightText: 2016 SOURADEEP BARUA <sourad97@gmail.com> 0004 * SPDX-FileCopyrightText: 2022 Johnny Jazeix <jazeix@gmail.com> 0005 * SPDX-FileCopyrightText: 2022 Timothée Giet <animtim@gmail.com> 0006 * 0007 * Authors: 0008 * SOURADEEP BARUA <sourad97@gmail.com> 0009 * Johnny Jazeix <jazeix@gmail.com> 0010 * Timothée Giet <animtim@gmail.com> 0011 * 0012 * SPDX-License-Identifier: GPL-3.0-or-later 0013 */ 0014 import QtQuick 2.12 0015 import "../../core" 0016 import "../../core/core.js" as Core 0017 import GCompris 1.0 0018 0019 ActivityBase { 0020 id: activity 0021 property string resourcesUrl: "qrc:/gcompris/src/activities/morse_code/resource/" 0022 onStart: focus = true 0023 onStop: {} 0024 0025 // When opening a dialog, it steals the focus and re set it to the activity. 0026 // We need to set it back to the textinput item in order to have key events. 0027 signal resetFocus 0028 onFocusChanged: { 0029 if(focus) 0030 resetFocus(); 0031 } 0032 0033 pageComponent: Image { 0034 id: background 0035 source: "qrc:/gcompris/src/activities/braille_alphabets/resource/background.svg" 0036 fillMode: Image.PreserveAspectCrop 0037 sourceSize.width: Math.max(parent.width, parent.height) 0038 0039 signal start 0040 signal stop 0041 signal resetFocus 0042 0043 Component.onCompleted: { 0044 dialogActivityConfig.initialize() 0045 activity.start.connect(start) 0046 activity.stop.connect(stop) 0047 activity.resetFocus.connect(resetFocus) 0048 } 0049 0050 onResetFocus: { 0051 if (!ApplicationInfo.isMobile) 0052 textInput.forceActiveFocus(); 0053 } 0054 0055 property int layoutMargins: 10 * ApplicationInfo.ratio 0056 0057 // Add here the QML items you need to access in javascript 0058 QtObject { 0059 id: items 0060 property Item main: activity.main 0061 property alias background: background 0062 property GCSfx audioEffects: activity.audioEffects 0063 property int currentLevel: activity.currentLevel 0064 property alias bonus: bonus 0065 property alias score: score 0066 property alias textInput: textInput 0067 readonly property var dataset: activity.datasetLoader.data 0068 property bool toAlpha: dataset[currentLevel].toAlpha 0069 property bool audioMode: dataset[currentLevel].audioMode ? dataset[currentLevel].audioMode : false 0070 property string questionText: dataset[currentLevel].question 0071 property string questionValue 0072 property int numberOfLevel: dataset.length 0073 property bool buttonsBlocked: false 0074 readonly property string middleDot: '·' 0075 readonly property var regexSpaceReplace: new RegExp(keyboard.space, "g") 0076 0077 onToAlphaChanged: { 0078 textInput.text = '' 0079 morseConverter.alpha = '' 0080 if(toAlpha) 0081 keyboard.populateAlpha() 0082 else 0083 keyboard.populateMorse() 0084 } 0085 0086 function start() { 0087 if (!ApplicationInfo.isMobile) 0088 textInput.forceActiveFocus(); 0089 items.currentLevel = Core.getInitialLevel(items.numberOfLevel); 0090 initLevel() 0091 } 0092 0093 function initLevel() { 0094 errorRectangle.resetState(); 0095 // Reset the values on the text fields 0096 toAlphaChanged(); 0097 score.currentSubLevel = 0 0098 score.numberOfSubLevels = dataset[currentLevel].values[1].length 0099 if(dataset[currentLevel].values[0] == '_random_') { 0100 Core.shuffle(dataset[currentLevel].values[1]); 0101 } 0102 initSubLevel() 0103 } 0104 0105 function initSubLevel() { 0106 textInput.text = '' 0107 stopMorseSounds(); 0108 questionValue = dataset[currentLevel].values[1][score.currentSubLevel] 0109 questionValue = questionValue.replace(/\./g, items.middleDot); 0110 questionValue = questionValue.replace(items.regexSpaceReplace, ' '); 0111 activity.audioVoices.clearQueue(); 0112 // Play the audio at start of the sublevel 0113 if(items.audioMode) { 0114 delayTimer.restart(); 0115 } 0116 items.buttonsBlocked = false; 0117 } 0118 0119 function nextLevel() { 0120 score.stopWinAnimation(); 0121 currentLevel = Core.getNextLevel(currentLevel, numberOfLevel); 0122 initLevel(); 0123 } 0124 0125 function previousLevel() { 0126 score.stopWinAnimation(); 0127 currentLevel = Core.getPreviousLevel(currentLevel, numberOfLevel); 0128 initLevel(); 0129 } 0130 0131 function nextSubLevel() { 0132 if(score.currentSubLevel >= score.numberOfSubLevels) { 0133 bonus.good('tux'); 0134 } 0135 else { 0136 initSubLevel(); 0137 } 0138 } 0139 0140 function check() { 0141 items.buttonsBlocked = true; 0142 if(feedback.value === items.questionValue) { 0143 stopMorseSounds(); 0144 score.currentSubLevel++; 0145 score.playWinAnimation(); 0146 items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav"); 0147 } 0148 else { 0149 stopMorseSounds(); 0150 errorRectangle.startAnimation(); 0151 items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav"); 0152 } 0153 } 0154 0155 function stopMorseSounds() { 0156 // Reset soundList and stop running sound 0157 delayTimer.stop(); 0158 ledContainer.soundList = []; 0159 ledContainer.phraseRunning = false; 0160 activity.stopSounds(); 0161 } 0162 } 0163 0164 onStart: { 0165 firstScreen.visible = true 0166 items.start() 0167 } 0168 onStop: { 0169 ledContainer.soundList = [] 0170 activity.audioVoices.stop() 0171 activity.audioVoices.clearQueue() 0172 } 0173 0174 Keys.enabled: !items.buttonsBlocked 0175 Keys.onPressed: { 0176 if ((event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)) { 0177 if(firstScreen.visible) { 0178 firstScreen.visible = false; 0179 } 0180 else { 0181 items.check(); 0182 } 0183 } 0184 } 0185 0186 QtObject { 0187 id: morseConverter 0188 property string alpha 0189 property string morse 0190 // TODO Need to double check the values just in case... 0191 property var table: { 0192 "A" : ".-", "B" : "-...", "C" : "-.-.", "D" : "-..", "E" : ".", "F" : "..-.", "G" : "--.", 0193 "H" : "....", "I" : "..", "J" : ".---", "K" : "-.-", "L" : ".-..","M" : "--","N" : "-.", 0194 "O" : "---", "P" : ".--.", "Q" : "--.-", "R" : ".-.", "S" : "...","T" : "-", "U" : "..-", 0195 "V" : "...-", "W" : ".--", "X" : "-..-", "Y" : "-.--","Z" : "--..","1" : ".----","2" : "..---", 0196 "3" : "...--", "4" : "....-", "5" : ".....", "6" : "-....", "7" : "--...", "8" : "---..", 0197 "9" : "----." , "0" : "-----" 0198 } 0199 function morse2alpha(str) { 0200 var letters = "" 0201 var input = [] 0202 input = str.split(' ') 0203 if(input[0] === "") return '' 0204 0205 for(var index in input) { 0206 for(var key in table) { 0207 if(table[key] === input[index]) { 0208 letters += key 0209 continue; 0210 } 0211 } 0212 } 0213 0214 if(!letters) return '' 0215 return letters 0216 } 0217 0218 function alpha2morse(str) { 0219 var code = ""; 0220 0221 for(var index in str) { 0222 if(table[str[index]]) { 0223 code += table[str[index]] + " "; 0224 } 0225 else { 0226 code = ""; 0227 break; 0228 } 0229 } 0230 code = code.trim(); 0231 return code 0232 } 0233 0234 onAlphaChanged: morse = alpha2morse(alpha); 0235 onMorseChanged: alpha = morse2alpha(morse); 0236 } 0237 0238 Rectangle { 0239 id: questionArea 0240 anchors.top: background.top 0241 anchors.left: background.left 0242 anchors.right: background.right 0243 anchors.margins: background.layoutMargins 0244 color: "#f2f2f2" 0245 radius: background.layoutMargins 0246 height: questionLabel.height + 20 * ApplicationInfo.ratio 0247 Rectangle { 0248 anchors.centerIn: parent 0249 width: parent.width - background.layoutMargins 0250 height: parent.height - background.layoutMargins 0251 color: "#f2f2f2" 0252 radius: parent.radius 0253 border.width: 3 * ApplicationInfo.ratio 0254 border.color: "#9fb8e3" 0255 GCText { 0256 id: questionLabel 0257 anchors.centerIn: parent 0258 wrapMode: TextEdit.WordWrap 0259 text: items.questionValue ? items.questionText.includes("%1") ? items.questionText.arg(items.questionValue) : items.questionText : '' 0260 color: "#373737" 0261 width: parent.width * 0.9 0262 horizontalAlignment: Text.AlignHCenter 0263 } 0264 } 0265 } 0266 0267 Rectangle { 0268 id: inputArea 0269 anchors.top: questionArea.bottom 0270 anchors.left: background.left 0271 anchors.margins: background.layoutMargins 0272 color: "#f2f2f2" 0273 radius: background.layoutMargins 0274 width: questionArea.width * 0.5 - background.layoutMargins * 0.5 0275 height: textInput.height 0276 border.width: 1 * ApplicationInfo.ratio 0277 border.color: "#9fb8e3" 0278 TextInput { 0279 id: textInput 0280 x: parent.width / 2 0281 width: parent.width 0282 color: "#373737" 0283 enabled: !firstScreen.visible && !items.buttonsBlocked 0284 text: '' 0285 // At best, 5 characters when looking for a letter (4 max + 1 space) 0286 maximumLength: items.toAlpha ? items.questionValue.split(' ').length + 1 : 5 * items.questionValue.length 0287 horizontalAlignment: Text.AlignHCenter 0288 verticalAlignment: TextInput.AlignVCenter 0289 anchors.horizontalCenter: parent.horizontalCenter 0290 font.pointSize: questionLabel.pointSize 0291 font.weight: Font.DemiBold 0292 font.family: GCSingletonFontLoader.fontLoader.name 0293 font.capitalization: ApplicationSettings.fontCapitalization 0294 font.letterSpacing: ApplicationSettings.fontLetterSpacing 0295 cursorVisible: true 0296 wrapMode: TextInput.Wrap 0297 // TODO Use RegularExpressionValidator when supporting Qt5.14 minimum 0298 validator: RegExpValidator { regExp: items.toAlpha ? 0299 /^[a-zA-Z0-9 ]+$/ : 0300 /[\.\-\x00B7 ]+$/ 0301 } 0302 onTextChanged: { 0303 if(text) { 0304 text = text.replace(/\./g, items.middleDot); 0305 text = text.replace(items.regexSpaceReplace, ' '); 0306 text = text.toUpperCase(); 0307 if(items.toAlpha) { 0308 morseConverter.alpha = text.replace(/\W/g , ''); 0309 } 0310 else { 0311 morseConverter.morse = text.replace(/·/g, '.'); 0312 } 0313 } 0314 else { 0315 morseConverter.morse = ""; 0316 morseConverter.alpha = ""; 0317 } 0318 } 0319 0320 function appendText(car) { 0321 if(car === keyboard.backspace) { 0322 if(text && cursorPosition > 0) { 0323 var oldPos = cursorPosition 0324 text = text.substring(0, cursorPosition - 1) + text.substring(cursorPosition) 0325 cursorPosition = oldPos - 1 0326 } 0327 return 0328 } 0329 var oldPos = cursorPosition 0330 text = text.substring(0, cursorPosition) + car + text.substring(cursorPosition) 0331 cursorPosition = oldPos + 1 0332 } 0333 } 0334 } 0335 0336 Rectangle { 0337 id: feedbackArea 0338 anchors.top: questionArea.bottom 0339 anchors.margins: background.layoutMargins 0340 anchors.right: background.right 0341 width: inputArea.width 0342 height: inputArea.height 0343 color: "#f2f2f2" 0344 radius: background.layoutMargins 0345 border.width: 1 * ApplicationInfo.ratio 0346 border.color: "#9fb8e3" 0347 0348 GCText { 0349 id: feedback 0350 anchors.horizontalCenter: parent.horizontalCenter 0351 text: items.toAlpha ? 0352 qsTr("Morse value: %1").arg(value) : 0353 qsTr("Alphabet/Numeric value: %1").arg(value) 0354 color: "#373737" 0355 property string value: items.toAlpha ? 0356 morseConverter.morse.replace(/\./g, items.middleDot).replace(items.regexSpaceReplace, ' ') 0357 : 0358 morseConverter.alpha 0359 verticalAlignment: Text.AlignVCenter 0360 width: parent.width * 0.9 0361 height: parent.height * 0.9 0362 fontSizeMode: Text.Fit 0363 minimumPointSize: 10 0364 fontSize: mediumSize 0365 } 0366 } 0367 0368 Item { 0369 id: layoutArea 0370 anchors.top: feedbackArea.bottom 0371 anchors.topMargin: background.layoutMargins 0372 anchors.left: inputArea.left 0373 anchors.right: feedbackArea.right 0374 anchors.bottom: bar.top 0375 anchors.bottomMargin: bar.height * 0.2 0376 } 0377 0378 Rectangle { 0379 id: ledContainer 0380 visible: repeatItem.visible 0381 anchors.verticalCenter: layoutArea.verticalCenter 0382 anchors.right: repeatItem.left 0383 anchors.rightMargin: background.layoutMargins 0384 height: Math.min(70 * ApplicationInfo.ratio, layoutArea.height) 0385 width: height 0386 radius:background.layoutMargins 0387 color: "#f2f2f2" 0388 border.width: 1 * ApplicationInfo.ratio 0389 border.color: "#9fb8e3" 0390 property var soundList: [] 0391 property bool phraseRunning: false 0392 0393 Rectangle { 0394 id: ledContour 0395 anchors.centerIn: parent 0396 width: Math.min(parent.width, parent.height) * 0.9 0397 height: width 0398 radius: width * 0.5 0399 color: "#373737" 0400 } 0401 Image { 0402 id: ledOff 0403 source: "qrc:/gcompris/src/activities/morse_code/resource/ledOff.svg" 0404 anchors.centerIn: ledContour 0405 width: ledContour.width * 0.9 0406 height: width 0407 sourceSize.width: width 0408 sourceSize.height: width 0409 } 0410 Image { 0411 id: ledOn 0412 source: "qrc:/gcompris/src/activities/morse_code/resource/ledOn.svg" 0413 anchors.centerIn: ledContour 0414 width: ledContour.width * 0.9 0415 height: width 0416 sourceSize.width: width 0417 sourceSize.height: width 0418 visible: false 0419 } 0420 SequentialAnimation { 0421 id: dotAnim 0422 PropertyAction { target: ledOn; property: "visible"; value: true } 0423 ScriptAction { script: activity.audioVoices.append("qrc:/gcompris/src/activities/morse_code/resource/dot.wav") } 0424 PauseAnimation { duration: 100 } 0425 PropertyAction { target: ledOn; property: "visible"; value: false } 0426 PauseAnimation { duration: 400 } 0427 ScriptAction { script: ledContainer.playLedAnim() } 0428 } 0429 SequentialAnimation { 0430 id: dashAnim 0431 PropertyAction { target: ledOn; property: "visible"; value: true } 0432 ScriptAction { script: activity.audioVoices.append("qrc:/gcompris/src/activities/morse_code/resource/dash.wav") } 0433 PauseAnimation { duration: 300 } 0434 PropertyAction { target: ledOn; property: "visible"; value: false } 0435 PauseAnimation { duration: 200 } 0436 ScriptAction { script: ledContainer.playLedAnim() } 0437 } 0438 SequentialAnimation { 0439 id: silenceAnim 0440 PropertyAction { target: ledOn; property: "visible"; value: false } 0441 PauseAnimation { duration: 500 } 0442 ScriptAction { script: ledContainer.playLedAnim() } 0443 } 0444 function playLedAnim() { 0445 if(ledContainer.soundList.length > 0) { 0446 var soundType = soundList.shift(); 0447 if (soundType === "dot.wav") { 0448 dotAnim.restart(); 0449 } else if (soundType === "dash.wav") { 0450 dashAnim.restart(); 0451 } else if (soundType === "silence.wav") { 0452 silenceAnim.restart(); 0453 } 0454 } else { 0455 ledContainer.phraseRunning = false; 0456 } 0457 } 0458 } 0459 0460 ErrorRectangle { 0461 id: errorRectangle 0462 anchors.fill: inputArea 0463 radius: inputArea.radius 0464 imageSize: height * 1.5 0465 function releaseControls() { 0466 items.buttonsBlocked = false; 0467 } 0468 } 0469 0470 Timer { 0471 id: delayTimer 0472 interval: 1000 0473 repeat: false 0474 running: false 0475 onTriggered: repeatItem.clicked() 0476 } 0477 0478 MorseMap { 0479 id: morseMap 0480 visible: false 0481 onClose: home() 0482 } 0483 0484 Score { 0485 id: score 0486 visible: !firstScreen.visible 0487 anchors.right: layoutArea.right 0488 anchors.verticalCenter: layoutArea.verticalCenter 0489 anchors.bottom: undefined 0490 currentSubLevel: 0 0491 numberOfSubLevels: 1 0492 onStop: items.nextSubLevel() 0493 } 0494 0495 0496 DialogChooseLevel { 0497 id: dialogActivityConfig 0498 currentActivity: activity.activityInfo 0499 0500 onSaveData: { 0501 levelFolder = dialogActivityConfig.chosenLevels 0502 currentActivity.currentLevels = dialogActivityConfig.chosenLevels 0503 ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) 0504 } 0505 onClose: { 0506 home() 0507 } 0508 onStartActivity: { 0509 background.stop() 0510 background.start() 0511 } 0512 } 0513 0514 DialogHelp { 0515 id: dialogHelp 0516 onClose: home() 0517 } 0518 0519 VirtualKeyboard { 0520 id: keyboard 0521 anchors.bottom: parent.bottom 0522 anchors.horizontalCenter: parent.horizontalCenter 0523 visible: !firstScreen.visible 0524 enabled: visible && !items.buttonsBlocked 0525 function populateAlpha() { 0526 layout = [ [ 0527 { label: "0" }, 0528 { label: "1" }, 0529 { label: "2" }, 0530 { label: "3" }, 0531 { label: "4" }, 0532 { label: "5" }, 0533 { label: "6" }, 0534 { label: "7" }, 0535 { label: "8" }, 0536 { label: "9" } 0537 ], 0538 [ 0539 { label: "A" }, 0540 { label: "B" }, 0541 { label: "C" }, 0542 { label: "D" }, 0543 { label: "E" }, 0544 { label: "F" }, 0545 { label: "G" }, 0546 { label: "H" }, 0547 { label: "I" }, 0548 { label: "J" }, 0549 { label: "K" }, 0550 { label: "L" }, 0551 { label: "M" } 0552 ], 0553 [ 0554 { label: "N" }, 0555 { label: "O" }, 0556 { label: "P" }, 0557 { label: "Q" }, 0558 { label: "R" }, 0559 { label: "S" }, 0560 { label: "T" }, 0561 { label: "U" }, 0562 { label: "V" }, 0563 { label: "W" }, 0564 { label: "X" }, 0565 { label: "Y" }, 0566 { label: "Z" }, 0567 { label: keyboard.space }, 0568 { label: keyboard.backspace } 0569 0570 ] 0571 ] 0572 } 0573 0574 function populateMorse() { 0575 layout = [ [ 0576 { label: items.middleDot }, 0577 { label: "-" }, 0578 { label: keyboard.space }, 0579 { label: keyboard.backspace } 0580 ] ] 0581 } 0582 0583 onKeypress: { 0584 if(!items.buttonsBlocked) { 0585 textInput.appendText(text) 0586 } 0587 // Set the focus back to the InputText for keyboard input 0588 resetFocus(); 0589 } 0590 onError: console.log("VirtualKeyboard error: " + msg); 0591 } 0592 0593 FirstScreen { 0594 id: firstScreen 0595 visible: true 0596 } 0597 0598 Bar { 0599 id: bar 0600 level: items.currentLevel + 1 0601 anchors.bottom: keyboard.top 0602 content: BarEnumContent { 0603 value: !firstScreen.visible ? (help | home | level | hint | activityConfig) : (help | home) 0604 } 0605 onHelpClicked: { 0606 displayDialog(dialogHelp) 0607 } 0608 onActivityConfigClicked: { 0609 displayDialog(dialogActivityConfig) 0610 } 0611 onPreviousLevelClicked: items.previousLevel() 0612 onNextLevelClicked: items.nextLevel() 0613 onHomeClicked: activity.home() 0614 onHintClicked: feedbackArea.visible = !feedbackArea.visible 0615 } 0616 BarButton { 0617 id: repeatItem 0618 source: "qrc:/gcompris/src/core/resource/bar_repeat.svg" 0619 height: ledContainer.height 0620 width: height 0621 sourceSize.height: height 0622 sourceSize.width: height 0623 visible: !firstScreen.visible && items.audioMode 0624 anchors { 0625 verticalCenter: layoutArea.verticalCenter 0626 right: showMapButton.left 0627 rightMargin: background.layoutMargins 0628 } 0629 mouseArea.enabled: ledContainer.phraseRunning == false 0630 mouseArea.hoverEnabled: ledContainer.phraseRunning == false 0631 onClicked: { 0632 ledContainer.soundList = [] 0633 for(var f = 0 ; f < items.questionValue.length; ++ f) { 0634 var letter = items.questionValue[f]; 0635 // If the character to play is a letter, we convert it to morse 0636 if(".·- ".indexOf(items.questionValue[f]) === -1) { 0637 letter = morseConverter.alpha2morse(items.questionValue[f]); 0638 } 0639 // We play each character, one after the other 0640 for(var i = 0 ; i < letter.length; ++ i) { 0641 if(letter[i] === '-') { 0642 ledContainer.soundList.push("dash.wav"); 0643 } 0644 else if(letter[i] === '.' || letter[i] === '·') { 0645 ledContainer.soundList.push("dot.wav"); 0646 } 0647 } 0648 // Add a silence after each letter 0649 ledContainer.soundList.push("silence.wav") 0650 } 0651 ledContainer.phraseRunning = true; 0652 ledContainer.playLedAnim(); 0653 } 0654 } 0655 0656 BarButton { 0657 id: okButton 0658 source: "qrc:/gcompris/src/core/resource/bar_ok.svg"; 0659 visible: !firstScreen.visible 0660 anchors.right: score.left 0661 anchors.verticalCenter: layoutArea.verticalCenter 0662 anchors.rightMargin: background.layoutMargins 0663 enabled: !items.buttonsBlocked 0664 height: ledContainer.height 0665 width: height 0666 sourceSize.height: height 0667 sourceSize.width: height 0668 onClicked: items.check() 0669 } 0670 0671 BarButton { 0672 id: showMapButton 0673 source: "qrc:/gcompris/src/activities/morse_code/resource/morseButton.svg" 0674 visible: !firstScreen.visible 0675 anchors.right: okButton.left 0676 anchors.verticalCenter: layoutArea.verticalCenter 0677 anchors.rightMargin: background.layoutMargins 0678 enabled: !items.buttonsBlocked 0679 height: ledContainer.height 0680 width: height 0681 sourceSize.height: height 0682 sourceSize.width: height 0683 onClicked: { 0684 morseMap.visible = true 0685 displayDialog(morseMap) 0686 } 0687 } 0688 0689 Bonus { 0690 id: bonus 0691 Component.onCompleted: win.connect(items.nextLevel) 0692 } 0693 } 0694 }