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 }