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 }