Warning, /education/gcompris/src/activities/roman_numerals/RomanNumerals.qml is written in an unsupported language. File is not indexed.
0001 /* GCompris - roman_numerals.qml 0002 * 0003 * SPDX-FileCopyrightText: 2016 Bruno Coudoin <bruno.coudoin@gcompris.net> 0004 * 0005 * Authors: 0006 * Bruno Coudoin <bruno.coudoin@gcompris.net> 0007 * Timothée Giet <animtim@gmail.com> (layout refactoring) 0008 * 0009 * SPDX-License-Identifier: GPL-3.0-or-later 0010 */ 0011 import QtQuick 2.12 0012 import GCompris 1.0 0013 0014 import "../../core" 0015 import "qrc:/gcompris/src/core/core.js" as Core 0016 0017 ActivityBase { 0018 id: activity 0019 0020 onStart: focus = true 0021 onStop: {} 0022 0023 // When opening a dialog, it steals the focus and re set it to the activity. 0024 // We need to set it back to the textinput item in order to have key events. 0025 signal resetFocus 0026 onFocusChanged: { 0027 if(focus) 0028 resetFocus(); 0029 } 0030 0031 pageComponent: Image { 0032 id: background 0033 source: items.toArabic ? 0034 "qrc:/gcompris/src/activities/roman_numerals/resource/arabian-building.svg" : 0035 "qrc:/gcompris/src/activities/roman_numerals/resource/roman-building.svg" 0036 fillMode: Image.PreserveAspectCrop 0037 sourceSize.width: width 0038 sourceSize.height: height 0039 signal start 0040 signal stop 0041 signal resetFocus 0042 0043 onResetFocus: { 0044 if (!ApplicationInfo.isMobile) 0045 textInput.forceActiveFocus(); 0046 } 0047 0048 property int layoutMargins: 10 * ApplicationInfo.ratio 0049 0050 Component.onCompleted: { 0051 activity.start.connect(start) 0052 activity.stop.connect(stop) 0053 activity.resetFocus.connect(resetFocus) 0054 } 0055 0056 // Add here the QML items you need to access in javascript 0057 QtObject { 0058 id: items 0059 property Item main: activity.main 0060 property alias background: background 0061 property alias bonus: bonus 0062 property alias score: score 0063 property alias textInput: textInput 0064 0065 property bool toArabic: dataset[currentLevel].toArabic 0066 property string questionText: dataset[currentLevel].question 0067 property string instruction: dataset[currentLevel].instruction 0068 property string questionValue 0069 0070 // we initialize it to -1, so on start() function, 0071 // it forces a layout refresh when it's set to 0 0072 property int currentLevel: -1 0073 property int numberOfLevel: dataset.length 0074 property bool buttonsBlocked: false 0075 0076 property var dataset: [ 0077 { 0078 values: ['I', 'V', 'X', 'L', 'C', 'D', 'M'], 0079 instruction: qsTr("The roman numbers are all built out of these 7 numbers:\nI and V (units, 1 and 5)\nX and L (tens, 10 and 50)\nC and D (hundreds, 100 and 500)\n and M (1000).\n An interesting observation here is that the roman numeric system lacks the number 0."), 0080 question: qsTr("Convert the roman number %1 to arabic."), 0081 toArabic: true 0082 }, 0083 { 0084 values: [1, 5, 10, 50, 100, 500, 1000], 0085 instruction: qsTr("The roman numbers are all built out of these 7 numbers:\nI and V (units, 1 and 5)\nX and L (tens, 10 and 50)\nC and D (hundreds, 100 and 500)\n and M (1000).\n An interesting observation here is that the roman numeric system lacks the number 0."), 0086 question: qsTr("Convert the arabic number %1 to roman."), 0087 toArabic: false 0088 }, 0089 { 0090 values: ['II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'], 0091 instruction: qsTr("All the units except 4 and 9 are built using sums of I and V:\nI, II, III, V, VI, VII, VIII.\n The 4 and the 9 units are built using differences:\nIV (5 – 1) and IX (10 – 1)."), 0092 question: qsTr("Convert the roman number %1 to arabic."), 0093 toArabic: true 0094 }, 0095 { 0096 values: [2, 3, 4, 5, 6, 7, 8, 9], 0097 instruction: qsTr("All the units except 4 and 9 are built using sums of I and V:\nI, II, III, V, VI, VII, VIII.\n The 4 and the 9 units are built using differences:\nIV (5 – 1) and IX (10 – 1)."), 0098 question: qsTr("Convert the arabic number %1 to roman."), 0099 toArabic: false 0100 }, 0101 { 0102 values: ['XX', 'XXX', 'XL', 'LX', 'LXX', 'LXXX', 'XC'], 0103 instruction: qsTr("All the tens except 40 and 90 are built using sums of X and L:\nX, XX, XXX, L, LX, LXX, LXXX.\nThe 40 and the 90 tens are built using differences:\nXL (10 taken from 50) and XC (10 taken from 100)."), 0104 question: qsTr("Convert the roman number %1 to arabic."), 0105 toArabic: true 0106 }, 0107 { 0108 values: [20, 30, 40, 60, 70, 80, 90], 0109 instruction: qsTr("All the tens except 40 and 90 are built using sums of X and L:\nX, XX, XXX, L, LX, LXX, LXXX.\nThe 40 and the 90 tens are built using differences:\nXL (10 taken from 50) and XC (10 taken from 100)."), 0110 question: qsTr("Convert the arabic number %1 to roman."), 0111 toArabic: false 0112 }, 0113 { 0114 values: ['CC', 'CCC', 'CD', 'DC', 'DCC', 'DCCC', 'CM', ], 0115 instruction: qsTr("All the hundreds except 400 and 900 are built using sums of C and D:\nC, CC, CCC, D, DC, DCC, DCCC.\nThe 400 and the 900 hundreds are built using differences:\nCD (100 taken from 500) and CM (100 taken from 1000)."), 0116 question: qsTr("Convert the roman number %1 to arabic."), 0117 toArabic: true 0118 }, 0119 { 0120 values: [200, 300, 400, 600, 700, 800, 900], 0121 instruction: qsTr("All the hundreds except 400 and 900 are built using sums of C and D:\nC, CC, CCC, D, DC, DCC, DCCC.\nThe 400 and the 900 hundreds are built using differences:\nCD (100 taken from 500) and CM (100 taken from 1000)."), 0122 question: qsTr("Convert the arabic number %1 to roman."), 0123 toArabic: false 0124 }, 0125 { 0126 values: ['MM', 'MMM'], 0127 instruction: qsTr("Sums of M are used for building thousands: M, MM, MMM.\nNotice that you cannot join more than three identical symbols. The first implication of this rule is that you cannot use just sums for building all possible units, tens or hundreds, you must use differences too. On the other hand, it limits the maximum roman number to 3999 (MMMCMXCIX)."), 0128 question: qsTr("Convert the roman number %1 to arabic."), 0129 toArabic: true 0130 }, 0131 { 0132 values: [2000, 3000], 0133 instruction: qsTr("Sums of M are used for building thousands: M, MM, MMM.\nNotice that you cannot join more than three identical symbols. The first implication of this rule is that you cannot use just sums for building all possible units, tens or hundreds, you must use differences too. On the other hand, it limits the maximum roman number to 3999 (MMMCMXCIX)."), 0134 question: qsTr("Convert the arabic number %1 to roman."), 0135 toArabic: false 0136 }, 0137 { 0138 values: ['_random_', 50 /* up to this number */ , 10 /* sublevels */], 0139 instruction: qsTr("Now you know the rules, you can read and write numbers in roman numerals."), 0140 question: qsTr("Convert the arabic number %1 to roman."), 0141 toArabic: false 0142 }, 0143 { 0144 values: ['_random_', 100, 10], 0145 instruction: '', 0146 question: qsTr("Convert the arabic number %1 to roman."), 0147 toArabic: false 0148 }, 0149 { 0150 values: ['_random_', 500, 10], 0151 instruction: '', 0152 question: qsTr("Convert the arabic number %1 to roman."), 0153 toArabic: false 0154 }, 0155 { 0156 values: ['_random_', 1000, 10], 0157 instruction: '', 0158 question: qsTr("Convert the arabic number %1 to roman."), 0159 toArabic: false 0160 } 0161 ] 0162 0163 onQuestionValueChanged: { 0164 textInput.text = '' 0165 romanConverter.arabic = 0 0166 if(toArabic) 0167 keyboard.populateArabic() 0168 else 0169 keyboard.populateRoman() 0170 0171 } 0172 0173 function start() { 0174 if (!ApplicationInfo.isMobile) 0175 textInput.forceActiveFocus(); 0176 items.currentLevel = Core.getInitialLevel(items.numberOfLevel) 0177 initLevel() 0178 } 0179 0180 function initLevel() { 0181 errorRectangle.resetState() 0182 score.currentSubLevel = 0 0183 initSubLevel() 0184 } 0185 0186 function initSubLevel() { 0187 if(dataset[currentLevel].values[0] === '_random_') { 0188 questionValue = Math.round(Math.random() * dataset[currentLevel].values[1] + 1) 0189 score.numberOfSubLevels = dataset[currentLevel].values[2] 0190 } else { 0191 questionValue = dataset[currentLevel].values[score.currentSubLevel] 0192 score.numberOfSubLevels = dataset[currentLevel].values.length 0193 } 0194 items.buttonsBlocked = false 0195 } 0196 0197 function nextLevel() { 0198 score.stopWinAnimation() 0199 currentLevel = Core.getNextLevel(currentLevel, numberOfLevel); 0200 initLevel(); 0201 } 0202 0203 function previousLevel() { 0204 score.stopWinAnimation() 0205 currentLevel = Core.getPreviousLevel(currentLevel, numberOfLevel); 0206 initLevel(); 0207 } 0208 0209 function nextSubLevel() { 0210 if(score.currentSubLevel >= score.numberOfSubLevels) { 0211 bonus.good("tux") 0212 } else { 0213 initSubLevel(); 0214 } 0215 } 0216 0217 function check() { 0218 items.buttonsBlocked = true 0219 if(feedback.value == items.questionValue) { 0220 score.currentSubLevel += 1; 0221 score.playWinAnimation(); 0222 activity.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/completetask.wav"); 0223 } else { 0224 activity.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav"); 0225 errorRectangle.startAnimation() 0226 } 0227 } 0228 } 0229 0230 onStart: { 0231 items.start() 0232 } 0233 onStop: { } 0234 0235 Keys.onPressed: { 0236 if ((event.key === Qt.Key_Enter) || (event.key === Qt.Key_Return)) { 0237 if(!items.buttonsBlocked) 0238 items.check() 0239 } 0240 } 0241 0242 QtObject { 0243 id: romanConverter 0244 property int arabic 0245 property string roman 0246 0247 // conversion code copied from: 0248 // http://blog.stevenlevithan.com/archives/javascript-roman-numeral-converter 0249 function arabic2Roman(num) { 0250 if (!+num || num > 3999) 0251 return ''; 0252 var digits = String(+num).split(""), 0253 key = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM", 0254 "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC", 0255 "","I","II","III","IV","V","VI","VII","VIII","IX"], 0256 roman = "", 0257 i = 3; 0258 while (i--) 0259 roman = (key[+digits.pop() + (i * 10)] || "") + roman; 0260 return new Array(+digits.join("") + 1).join("M") + roman; 0261 } 0262 0263 function roman2Arabic(str) { 0264 var str = str.toUpperCase(), 0265 validator = /^M*(?:D?C{0,3}|C[MD])(?:L?X{0,3}|X[CL])(?:V?I{0,3}|I[XV])$/, 0266 token = /[MDLV]|C[MD]?|X[CL]?|I[XV]?/g, 0267 key = {M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}, 0268 num = 0, m; 0269 if (!(str && validator.test(str))) 0270 return false; 0271 while (m = token.exec(str)) 0272 num += key[m[0]]; 0273 return num; 0274 } 0275 onArabicChanged: roman = arabic2Roman(arabic) 0276 onRomanChanged: arabic = roman2Arabic(roman) 0277 } 0278 0279 Rectangle { 0280 id: questionArea 0281 anchors.top: background.top 0282 anchors.left: background.left 0283 anchors.right: background.right 0284 anchors.margins: background.layoutMargins 0285 color: "#f2f2f2" 0286 radius: background.layoutMargins 0287 height: questionLabel.height + 20 * ApplicationInfo.ratio 0288 Rectangle { 0289 anchors.centerIn: parent 0290 width: parent.width - background.layoutMargins 0291 height: parent.height - background.layoutMargins 0292 color: "#f2f2f2" 0293 radius: parent.radius 0294 border.width: 3 * ApplicationInfo.ratio 0295 border.color: "#9fb8e3" 0296 GCText { 0297 id: questionLabel 0298 anchors.centerIn: parent 0299 wrapMode: TextEdit.WordWrap 0300 text: items.questionValue ? items.questionText.arg(items.questionValue) : '' 0301 color: "#373737" 0302 width: parent.width * 0.9 0303 horizontalAlignment: Text.AlignHCenter 0304 } 0305 } 0306 } 0307 0308 Rectangle { 0309 id: inputArea 0310 anchors.top: questionArea.bottom 0311 anchors.left: background.left 0312 anchors.margins: background.layoutMargins 0313 color: "#f2f2f2" 0314 radius: background.layoutMargins 0315 width: questionArea.width * 0.5 - background.layoutMargins * 0.5 0316 height: textInput.height 0317 TextInput { 0318 id: textInput 0319 x: parent.width / 2 0320 width: parent.width 0321 enabled: !items.buttonsBlocked 0322 color: "#373737" 0323 text: '' 0324 maximumLength: items.toArabic ? 0325 ('' + romanConverter.roman2Arabic(items.questionValue)).length + 1 : 0326 romanConverter.arabic2Roman(items.questionValue).length + 1 0327 horizontalAlignment: Text.AlignHCenter 0328 verticalAlignment: TextInput.AlignVCenter 0329 anchors.horizontalCenter: parent.horizontalCenter 0330 font.pointSize: questionLabel.pointSize 0331 font.weight: Font.DemiBold 0332 font.family: GCSingletonFontLoader.fontLoader.name 0333 font.capitalization: ApplicationSettings.fontCapitalization 0334 font.letterSpacing: ApplicationSettings.fontLetterSpacing 0335 cursorVisible: true 0336 wrapMode: TextInput.Wrap 0337 validator: RegExpValidator{regExp: items.toArabic ? 0338 /[0-9]+/ : 0339 /[ivxlcdmIVXLCDM]*/} 0340 onTextChanged: if(text) { 0341 text = text.toUpperCase(); 0342 if(items.toArabic) 0343 romanConverter.arabic = parseInt(text) 0344 else 0345 romanConverter.roman = text 0346 } 0347 0348 function appendText(car) { 0349 if(car === keyboard.backspace) { 0350 if(text && cursorPosition > 0) { 0351 var oldPos = cursorPosition 0352 text = text.substring(0, cursorPosition - 1) + text.substring(cursorPosition) 0353 cursorPosition = oldPos - 1 0354 } 0355 return 0356 } 0357 var oldPos = cursorPosition 0358 text = text.substring(0, cursorPosition) + car + text.substring(cursorPosition) 0359 cursorPosition = oldPos + 1 0360 } 0361 } 0362 } 0363 0364 Rectangle { 0365 id: feedbackArea 0366 anchors.top: questionArea.bottom 0367 anchors.margins: background.layoutMargins 0368 anchors.right: background.right 0369 width: inputArea.width 0370 height: inputArea.height 0371 color: "#f2f2f2" 0372 radius: background.layoutMargins 0373 0374 GCText { 0375 id: feedback 0376 anchors.horizontalCenter: parent.horizontalCenter 0377 text: items.toArabic ? 0378 qsTr("Roman value: %1").arg(value) : 0379 qsTr('Arabic value: %1').arg(value) 0380 color: "#373737" 0381 property string value: items.toArabic ? 0382 romanConverter.roman : 0383 romanConverter.arabic ? romanConverter.arabic : '' 0384 verticalAlignment: Text.AlignVCenter 0385 width: parent.width * 0.9 0386 height: parent.height * 0.9 0387 fontSizeMode: Text.Fit 0388 minimumPointSize: 10 0389 fontSize: mediumSize 0390 } 0391 } 0392 0393 Rectangle { 0394 id: instructionArea 0395 visible: items.instruction != '' 0396 anchors.top: feedbackArea.bottom 0397 anchors.bottom: okButton.top 0398 anchors.left: background.left 0399 anchors.right: background.right 0400 anchors.margins: background.layoutMargins 0401 color: "#9fb8e3" 0402 Rectangle { 0403 width: parent.width - background.layoutMargins 0404 height: parent.height - background.layoutMargins 0405 anchors.centerIn: parent 0406 color: "#f2f2f2" 0407 GCText { 0408 id: instruction 0409 wrapMode: TextEdit.WordWrap 0410 anchors.centerIn: parent 0411 width: parent.width 0412 height: parent.height 0413 text: items.instruction 0414 horizontalAlignment: Text.AlignHCenter 0415 verticalAlignment: Text.AlignVCenter 0416 color: "#373737" 0417 fontSizeMode: Text.Fit 0418 minimumPointSize: 8 0419 fontSize: mediumSize 0420 } 0421 } 0422 } 0423 0424 ErrorRectangle { 0425 id: errorRectangle 0426 anchors.fill: inputArea 0427 radius: inputArea.radius 0428 imageSize: height * 2 0429 function releaseControls() { items.buttonsBlocked = false; } 0430 } 0431 0432 Score { 0433 id: score 0434 anchors.right: okButton.left 0435 anchors.verticalCenter: okButton.verticalCenter 0436 anchors.bottom: undefined 0437 currentSubLevel: 0 0438 numberOfSubLevels: 1 0439 onStop: items.nextSubLevel() 0440 } 0441 0442 DialogHelp { 0443 id: dialogHelp 0444 onClose: home() 0445 } 0446 0447 VirtualKeyboard { 0448 id: keyboard 0449 anchors.bottom: parent.bottom 0450 anchors.horizontalCenter: parent.horizontalCenter 0451 enabled: visible && !items.buttonsBlocked 0452 0453 function populateArabic() { 0454 layout = [ [ 0455 { label: "0" }, 0456 { label: "1" }, 0457 { label: "2" }, 0458 { label: "3" }, 0459 { label: "4" }, 0460 { label: "5" }, 0461 { label: "6" }, 0462 { label: "7" }, 0463 { label: "8" }, 0464 { label: "9" }, 0465 { label: keyboard.backspace } 0466 ] ] 0467 } 0468 0469 function populateRoman() { 0470 layout = [ [ 0471 { label: "I" }, 0472 { label: "V" }, 0473 { label: "X" }, 0474 { label: "L" }, 0475 { label: "C" }, 0476 { label: "D" }, 0477 { label: "M" }, 0478 { label: keyboard.backspace } 0479 ] ] 0480 } 0481 0482 onKeypress: textInput.appendText(text) 0483 0484 onError: console.log("VirtualKeyboard error: " + msg); 0485 } 0486 0487 Bar { 0488 id: bar 0489 level: items.currentLevel + 1 0490 anchors.bottom: keyboard.top 0491 content: BarEnumContent { value: help | home | level | hint } 0492 onHelpClicked: { 0493 displayDialog(dialogHelp) 0494 } 0495 onPreviousLevelClicked: items.previousLevel() 0496 onNextLevelClicked: items.nextLevel() 0497 onHomeClicked: activity.home() 0498 onHintClicked: feedbackArea.visible = !feedbackArea.visible 0499 } 0500 BarButton { 0501 id: okButton 0502 source: "qrc:/gcompris/src/core/resource/bar_ok.svg"; 0503 visible: true 0504 anchors.right: background.right 0505 anchors.bottom: bar.top 0506 anchors.margins: 2 * background.layoutMargins 0507 enabled: !items.buttonsBlocked 0508 height: bar.height 0509 width: height 0510 sourceSize.height: height 0511 sourceSize.width: height 0512 onClicked: items.check() 0513 } 0514 0515 Bonus { 0516 id: bonus 0517 Component.onCompleted: win.connect(items.nextLevel) 0518 } 0519 } 0520 }