diff --git a/src/activities/gletters/ActivityConfig.qml b/src/activities/gletters/ActivityConfig.qml new file mode 100644 index 000000000..a3f1a4419 --- /dev/null +++ b/src/activities/gletters/ActivityConfig.qml @@ -0,0 +1,101 @@ +/* GCompris - ActivityConfig.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 + +import "../../core" + +Item { + id: activityConfiguration + property Item background + property alias localeBox: localeBox + property alias uppercaseBox: uppercaseBox + property bool uppercaseOnly: false + property string locale: "system" + height: column.height + width: background.width + property alias availableLangs: langs.languages + LanguageList { + id: langs + } + + Column { + id: column + spacing: 10 + Flow { + spacing: 5 + width: activityConfiguration.width + GCComboBox { + id: localeBox + visible: true + model: langs.languages + background: activityConfiguration.background + label: qsTr("Select your locale") + } + } + GCDialogCheckBox { + id: uppercaseBox + visible: true + width: parent.width + text: qsTr("Uppercase only mode") + checked: activityConfiguration.uppercaseOnly + } + } + + property var dataToSave + function setDefaultValues() { + var localeUtf8 = activityConfiguration.locale; + if(activityConfiguration.locale != "system") { + localeUtf8 += ".UTF-8"; + } + + for(var i = 0 ; i < activityConfiguration.availableLangs.length ; i ++) { + if(activityConfiguration.availableLangs[i].locale === localeUtf8) { + activityConfiguration.localeBox.currentIndex = i; + break; + } + } + } + + function saveValues() { + var configHasChanged = false + var oldLocale = activityConfiguration.locale; + var newLocale = activityConfiguration.availableLangs[activityConfiguration.localeBox.currentIndex].locale; + // Remove .UTF-8 + if(newLocale.indexOf('.') != -1) { + newLocale = newLocale.substring(0, newLocale.indexOf('.')) + } + + var oldUppercaseMode = activityConfiguration.uppercaseOnly + activityConfiguration.uppercaseOnly = activityConfiguration.uppercaseBox.checked + dataToSave = {"locale": newLocale, "uppercaseMode": "" + activityConfiguration.uppercaseOnly} + + activityConfiguration.locale = newLocale; + if(oldLocale !== newLocale || oldUppercaseMode !== activityConfiguration.uppercaseOnly) { + configHasChanged = true; + } + + // Restart the activity with new information + if(configHasChanged) { + background.stop(); + background.start(); + } + } +} diff --git a/src/activities/gletters/Gletters.qml b/src/activities/gletters/Gletters.qml index 2490f79fa..17ca6e1b5 100644 --- a/src/activities/gletters/Gletters.qml +++ b/src/activities/gletters/Gletters.qml @@ -1,316 +1,265 @@ /* GCompris - gletters.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import "../../core" import "gletters.js" as Activity ActivityBase { id: activity // Overload this in your activity to change it // Put you default-.json files in it property string dataSetUrl: "qrc:/gcompris/src/activities/gletters/resource/" /* no need to display the configuration button for smallnumbers */ property bool configurationButtonVisible: true property bool uppercaseOnly: false property string activityName: "gletters" /* mode of the activity, "letter" (gletters) or "word" (wordsgame):*/ property string mode: "letter" // Override if you want to replace texts by your image function getImage(key) { return "" } // Override if you want to replace texts by the domino function getDominoValues(key) { return [] } onStart: focus = true onStop: {} // When going on configuration, it steals the focus and re set it to the activity. // We need to set it back to the textinput item in order to have key events. onFocusChanged: { if(focus) { Activity.focusTextInput() } } pageComponent: Image { id: background source: activity.dataSetUrl + "background.svg" fillMode: Image.PreserveAspectCrop sourceSize.height: parent.height signal start signal stop // system locale by default property string locale: "system" Component.onCompleted: { - dialogActivityConfig.getInitialConfiguration() + dialogActivityConfig.initialize() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property Item ourActivity: activity property GCAudio audioVoices: activity.audioVoices + property var levels: activity.datasetLoader.item !== null ? activity.datasetLoader.item.data : null + property string instructionText: "" property alias background: background property alias bar: bar property alias bonus: bonus property alias wordlist: wordlist property alias score: score property alias keyboard: keyboard property alias wordDropTimer: wordDropTimer property GCSfx audioEffects: activity.audioEffects property alias locale: background.locale property alias textinput: textinput } onStart: { Activity.start(items, uppercaseOnly, mode); Activity.focusTextInput() } onStop: { Activity.stop() } + //instruction rectangle + Rectangle { + id: instruction + anchors { + top: parent.top + topMargin: 5 + horizontalCenter: parent.horizontalCenter + } + height: instructionTxt.contentHeight * 1.1 + width: Math.max(Math.min(parent.width * 0.8, instructionTxt.text.length * 10), parent.width * 0.3) + opacity: 0.8 + visible: items.levels + radius: 10 + border.width: 2 + z: 10 + border.color: "#DDD" + color: "#373737" + + Behavior on opacity { PropertyAnimation { duration: 200 } } + + //shows/hides the Instruction + MouseArea { + anchors.fill: parent + onClicked: instruction.opacity = instruction.opacity == 0 ? 0.8 : 0 + } + + GCText { + id: instructionTxt + anchors { + top: parent.top + topMargin: 5 + horizontalCenter: parent.horizontalCenter + } + opacity: instruction.opacity + z: instruction.z + fontSize: smallSize + color: "white" + text: items.instructionText + horizontalAlignment: Text.AlignHCenter + width: parent.width * 0.8 + wrapMode: TextEdit.WordWrap + } + } + TextInput { // Helper element to capture composed key events like french ô which // are not available via Keys.onPressed() on linux. Must be // disabled on mobile! id: textinput anchors.centerIn: background enabled: !ApplicationInfo.isMobile focus: true visible: false onTextChanged: { if (text != "") { Activity.processKeyPress(text); text = ""; } } } - //created to retrieve available menu modes for domino configurations - Domino { - id: invisibleDomino - visible: false - } - - DialogActivityConfig { + DialogChooseLevel { id: dialogActivityConfig - currentActivity: activity - content: Component { - Item { - property alias localeBox: localeBox - property alias dominoModeBox: dominoModeBox - property alias uppercaseBox: uppercaseBox - height: column.height - - property alias availableLangs: langs.languages - LanguageList { - id: langs - } - property var availableModes: invisibleDomino.menuModes - - Column { - id: column - spacing: 10 - width: parent.width - Flow { - spacing: 5 - width: dialogActivityConfig.width - GCComboBox { - id: localeBox - visible: (activity.activityName == "gletters") - model: langs.languages - background: dialogActivityConfig - label: qsTr("Select your locale") - } - GCComboBox { - id: dominoModeBox - visible: (activity.activityName == "smallnumbers2") - model: availableModes - background: dialogActivityConfig - label: qsTr("Select Domino mode") - } - } - GCDialogCheckBox { - id: uppercaseBox - visible: (activity.activityName == "gletters") - width: dialogActivityConfig.width - text: qsTr("Uppercase only mode") - checked: activity.uppercaseOnly - } - } - } - } - - onClose: home() - onLoadData: { - if (activity.activityName == "gletters") { - if(dataToSave && dataToSave["locale"]) { - background.locale = dataToSave["locale"]; - activity.uppercaseOnly = dataToSave["uppercaseMode"] === "true" ? true : false; - } - } else if (activity.activityName == "smallnumbers2") { - if(dataToSave && dataToSave["mode"]) { - activity.dominoMode = dataToSave["mode"]; - } - } + currentActivity: activity.activityInfo + onClose: { + home() } onSaveData: { - var configHasChanged = false - if (activity.activityName == "gletters") { - var oldLocale = background.locale; - var newLocale = dialogActivityConfig.configItem.availableLangs[dialogActivityConfig.loader.item.localeBox.currentIndex].locale; - // Remove .UTF-8 - if(newLocale.indexOf('.') != -1) { - newLocale = newLocale.substring(0, newLocale.indexOf('.')) - } - - var oldUppercaseMode = activity.uppercaseOnly - activity.uppercaseOnly = dialogActivityConfig.configItem.uppercaseBox.checked - dataToSave = {"locale": newLocale, "uppercaseMode": ""+activity.uppercaseOnly} - - background.locale = newLocale; - if(oldLocale !== newLocale || oldUppercaseMode !== activity.uppercaseOnly) { - configHasChanged = true; - } - } else if (activity.activityName == "smallnumbers2") { - var newMode = dialogActivityConfig.configItem.availableModes[dialogActivityConfig.configItem.dominoModeBox.currentIndex].value; - if (newMode !== activity.dominoMode) { - activity.dominoMode = newMode; - dataToSave = {"mode": activity.dominoMode}; - configHasChanged = true; - } - } - - // Restart the activity with new information - if(configHasChanged) { - background.stop(); - background.start(); - } + levelFolder = dialogActivityConfig.chosenLevel + currentActivity.currentLevel = dialogActivityConfig.chosenLevel + ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) + home() + background.stop() + background.start() } - - function setDefaultValues() { + onLoadData: { if (activity.activityName == "gletters") { - var localeUtf8 = background.locale; - if(background.locale != "system") { - localeUtf8 += ".UTF-8"; - } - - for(var i = 0 ; i < dialogActivityConfig.configItem.availableLangs.length ; i ++) { - if(dialogActivityConfig.configItem.availableLangs[i].locale === localeUtf8) { - dialogActivityConfig.configItem.localeBox.currentIndex = i; - break; - } + if(activityData && activityData["locale"]) { + background.locale = activityData["locale"]; + activity.uppercaseOnly = activityData["uppercaseMode"] === "true" ? true : false; } } else if (activity.activityName == "smallnumbers2") { - for(var i = 0 ; i < dialogActivityConfig.configItem.availableModes.length ; i++) { - if(dialogActivityConfig.configItem.availableModes[i].value === activity.dominoMode) { - dialogActivityConfig.configItem.dominoModeBox.currentIndex = i; - break; - } + if(activityData && activityData["mode"]) { + activity.dominoMode = activityData["mode"]; } } } + onStartActivity: { + background.start() + } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top - content: BarEnumContent { value: configurationButtonVisible ? (help | home | level | config) : (help | home | level)} + content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() - onConfigClicked: { - dialogActivityConfig.active = true - dialogActivityConfig.setDefaultValues() + onActivityConfigClicked: { displayDialog(dialogActivityConfig) } } Bonus { id: bonus interval: 2000 Component.onCompleted: win.connect(Activity.nextLevel) } Score { id: score anchors.top: undefined anchors.topMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.bottom: keyboard.top } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter width: parent.width onKeypress: Activity.processKeyPress(text) onError: console.log("VirtualKeyboard error: " + msg); } Wordlist { id: wordlist defaultFilename: activity.dataSetUrl + "default-en.json" // To switch between locales: xx_XX stored in configuration and // possibly correct xx if available (ie fr_FR for french but dataset is fr.) useDefault: false filename: "" onError: console.log("Gletters: Wordlist error: " + msg); } Timer { id: wordDropTimer repeat: false onTriggered: Activity.dropWord(); } } } diff --git a/src/activities/gletters/gletters.js b/src/activities/gletters/gletters.js index 5fe02004d..8eea703f0 100644 --- a/src/activities/gletters/gletters.js +++ b/src/activities/gletters/gletters.js @@ -1,418 +1,424 @@ /* GCompris - gletters.js * * Copyright (C) 2014-2016 Holger Kaelberer * * Authors: * Bruno Coudoin (GTK+ version) * Holger Kaelberer (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* ToDo / open issues: * - adjust wordlist filenames once we have an ApplicationInfo.dataPath() or so */ .pragma library .import QtQuick 2.6 as Quick .import GCompris 1.0 as GCompris //for ApplicationInfo .import "qrc:/gcompris/src/core/core.js" as Core var currentLevel = 0; var currentSubLevel = 0; var level = null; var maxLevel = 0; var maxSubLevel = 0; var items; var uppercaseOnly; var mode; //speed calculations, common: var speed = 0; // how fast letters fall var fallSpeed = 0; // how often new letters are dropped var incFallSpeed = 1000; // how much drop rate increases per sublevel var incSpeed = 10; // how much speed increases per sublevel // gletters: var fallRateBase = 40; // default for how fast letters fall (smaller is faster) var fallRateMult = 80; // default for how much falling speed increases per level (smaller is faster) var dropRateBase = 5000; // default for how often new letters are dropped var dropRateMult = 100; // default for how much drop rate increases per level // wordsgame: var wgMaxFallSpeed = 7000; var wgMaxSpeed = 150; var wgMinFallSpeed = 3000; var wgMinSpeed = 50; var wgDefaultFallSpeed = 8000; var wgDefaultSpeed = 170; var wgAddSpeed = 20; var wgAddFallSpeed = 1000; var wgMaxFallingItems; var droppedWords; var currentWord = null; // reference to the word currently typing, null if n/a var wordComponent = null; var successRate // Falling speed depends on it function start(items_, uppercaseOnly_, _mode) { items = items_; uppercaseOnly = uppercaseOnly_; mode = _mode; currentLevel = 0; currentSubLevel = 0; var locale = items.locale == "system" ? "$LOCALE" : items.locale // register the voices for the locale GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(GCompris.ApplicationInfo.getVoicesLocale(items.locale))); - - items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( + if(!items.levels) + items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( items.ourActivity.dataSetUrl + "default-"+locale+".json")); + else + items.wordlist.loadFromJSON(items.levels); // If wordlist is empty, we try to load from short locale and if not present again, we switch to default one var localeUnderscoreIndex = locale.indexOf('_') // probably exist a better way to see if the list is empty if(items.wordlist.maxLevel == 0) { var localeShort; // We will first look again for locale xx (without _XX if exist) if(localeUnderscoreIndex > 0) { localeShort = locale.substring(0, localeUnderscoreIndex) } else { localeShort = locale; } // If not found, we will use the default file items.wordlist.useDefault = true - items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( - items.ourActivity.dataSetUrl + "default-"+localeShort+".json")); + if(!items.levels) + items.wordlist.loadFromFile(GCompris.ApplicationInfo.getLocaleFilePath( + items.ourActivity.dataSetUrl + "default-"+localeShort+".json")); + else + items.wordlist.loadFromJSON(items.levels); // We remove the using of default file for next time we enter this function items.wordlist.useDefault = false } maxLevel = items.wordlist.maxLevel; droppedWords = new Array(); initLevel(); } function stop() { deleteWords(); wordComponent = null items.wordDropTimer.stop(); } function initLevel() { + if(items.levels) + items.instructionText = items.levels[currentLevel].objective items.audioVoices.clearQueue() items.bar.level = currentLevel + 1; wgMaxFallingItems = 3 successRate = 1.0 // initialize level deleteWords(); level = items.wordlist.getLevelWordList(currentLevel + 1); maxSubLevel = items.wordlist.getMaxSubLevel(currentLevel + 1); if (maxSubLevel == 0) { // If "sublevels" length is not set in wordlist, use the words length maxSubLevel = level.words.length } items.score.numberOfSubLevels = maxSubLevel; setSpeed(); /*console.log("Gletters: initializing level " + (currentLevel + 1) + " maxSubLvl=" + maxSubLevel + " wordCount=" + level.words.length + " speed=" + speed + " fallspeed=" + fallSpeed);*/ { /* populate VirtualKeyboard for mobile: * 1. for < 10 letters print them all in the same row * 2. for > 10 letters create 3 rows with equal amount of keys per row * if possible, otherwise more keys in the upper rows * 3. if we have both upper- and lowercase letters activate the shift * key*/ // first generate a map of needed letters var letters = new Array(); items.keyboard.shiftKey = false; for (var i = 0; i < level.words.length; i++) { if(mode ==='letter') { // The word is a letter, even if it has several chars (digraph) var letter = level.words[i]; var isUpper = (letter == letter.toLocaleUpperCase()); if (isUpper && letters.indexOf(letter.toLocaleLowerCase()) !== -1) items.keyboard.shiftKey = true; else if (!isUpper && letters.indexOf(letter.toLocaleUpperCase()) !== -1) items.keyboard.shiftKey = true; else if (letters.indexOf(letter) === -1) letters.push(level.words[i]); } else { // We split each word in char to create the keyboard for (var j = 0; j < level.words[i].length; j++) { var letter = level.words[i].charAt(j); var isUpper = (letter == letter.toLocaleUpperCase()); if (isUpper && letters.indexOf(letter.toLocaleLowerCase()) !== -1) items.keyboard.shiftKey = true; else if (!isUpper && letters.indexOf(letter.toLocaleUpperCase()) !== -1) items.keyboard.shiftKey = true; else if (letters.indexOf(letter) === -1) letters.push(level.words[i].charAt(j)); } } } letters = GCompris.ApplicationInfo.localeSort(letters, items.locale); // generate layout from letter map var layout = new Array(); var row = 0; var offset = 0; while (offset < letters.length-1) { var cols = letters.length <= 10 ? letters.length : (Math.ceil((letters.length-offset) / (3 - row))); layout[row] = new Array(); for (var j = 0; j < cols; j++) layout[row][j] = { label: letters[j+offset] }; offset += j; row++; } items.keyboard.layout = layout; } items.wordlist.initRandomWord(currentLevel + 1) initSubLevel() } function initSubLevel() { currentWord = null; if (currentSubLevel != 0) { // increase speed speed = Math.max(speed - incSpeed, wgMinSpeed); items.wordDropTimer.interval = fallSpeed = Math.max(fallSpeed - incFallSpeed, wgMinFallSpeed); } items.score.currentSubLevel = currentSubLevel + 1; if (currentSubLevel == 0 || droppedWords.length <= 1) // note, last word is still fading out dropWord(); //console.log("Gletters: initializing subLevel " + (currentSubLevel + 1) + " words=" + JSON.stringify(level.words)); } function processKeyPress(text) { var typedText = uppercaseOnly ? text.toLocaleUpperCase() : text; if (currentWord !== null) { // check against a currently typed word if (!currentWord.checkMatch(typedText)) { currentWord = null; audioCrashPlay() } else { playLetter(text) } } else { // no current word, check against all available words var found = false for (var i = 0; i< droppedWords.length; i++) { if (droppedWords[i].checkMatch(typedText)) { // typed correctly currentWord = droppedWords[i]; playLetter(text) found = true break; } } if(!found) { audioCrashPlay() } } if (currentWord !== null && currentWord.isCompleted()) { // win! currentWord.won(); // note: deleteWord() is triggered after fadeout successRate += 0.1 currentWord = null nextSubLevel(); } } function setSpeed() { if (mode === "letter") { speed = (level.speed !== undefined) ? level.speed : (fallRateBase + Math.floor(fallRateMult / (currentLevel + 1))); fallSpeed = (level.fallspeed !== undefined) ? level.fallspeed : Math.floor((dropRateBase + (dropRateMult * (currentLevel + 1)))); } else { // wordsgame speed = (level.speed !== undefined) ? level.speed : wgDefaultSpeed - (currentLevel+1)*wgAddSpeed; fallSpeed = (level.fallspeed !== undefined) ? level.fallspeed : wgDefaultFallSpeed - (currentLevel+1)*wgAddFallSpeed if(speed < wgMinSpeed ) speed = wgMinSpeed; if(speed > wgMaxSpeed ) speed = wgMaxSpeed; if(fallSpeed < wgMinFallSpeed ) fallSpeed = wgMinFallSpeed; if(fallSpeed > wgMaxFallSpeed ) fallSpeed = wgMaxFallSpeed; } items.wordDropTimer.interval = fallSpeed; } function deleteWords() { if (droppedWords === undefined || droppedWords.length < 1) return; for (var i = 0; i< droppedWords.length; i++) droppedWords[i].destroy(); droppedWords.length = 0; } function deleteWord(w) { if (droppedWords === undefined || droppedWords.length < 1) return; if (w == currentWord) currentWord = null; for (var i = 0; i< droppedWords.length; i++) if (droppedWords[i] == w) { droppedWords[i].destroy(); droppedWords.splice(i, 1); break; } } function createWord() { if (wordComponent.status == 1 /* Component.Ready */) { var text = items.wordlist.getRandomWord(); if(!text) { items.wordDropTimer.restart(); return } // if uppercaseOnly case does not matter otherwise it does if (uppercaseOnly) text = text.toLocaleUpperCase(); var word if(items.ourActivity.getImage(text)) { word = wordComponent.createObject( items.background, { "text": text, "image": items.ourActivity.getImage(text), // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, }); } else if(items.ourActivity.getDominoValues(text).length) { word = wordComponent.createObject( items.background, { "text": text, "mode": items.ourActivity.getMode(), "dominoValues": items.ourActivity.getDominoValues(text), // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, }); } else { word = wordComponent.createObject( items.background, { "text": text, // assume x=width-25px for now, Word auto-adjusts onCompleted(): "x": Math.random() * (items.main.width - 25), "y": -25, "mode": mode, }); } if (word === null) console.log("Gletters: Error creating word object"); else { droppedWords[droppedWords.length] = word; // speed to duration: var duration = (items.main.height / 2) * speed / successRate; /* console.debug("Gletters: dropping new word " + word.text + " duration=" + duration + " (speed=" + speed + ")" + " num=" + droppedWords.length);*/ word.startMoving(duration); } items.wordDropTimer.restart(); } else if (wordComponent.status == 3 /* Component.Error */) { console.log("Gletters: error creating word component: " + wordComponent.errorString()); } } function dropWord() { // Do not create too many falling items if(droppedWords.length > wgMaxFallingItems) { items.wordDropTimer.restart(); return } if (wordComponent !== null) createWord(); else { var text = items.wordlist.getRandomWord(); items.wordlist.appendRandomWord(text) var fallingItem if(items.ourActivity.getImage(text)) fallingItem = "FallingImage.qml" else if(items.ourActivity.getDominoValues(text).length) fallingItem = "FallingDomino.qml" else fallingItem = "FallingWord.qml" wordComponent = Qt.createComponent("qrc:/gcompris/src/activities/gletters/" + fallingItem); if (wordComponent.status == 1 /* Component.Ready */) createWord(); else if (wordComponent.status == 3 /* Component.Error */) { console.log("Gletters: error creating word component: " + wordComponent.errorString()); } else wordComponent.statusChanged.connect(createWord); } } function appendRandomWord(word) { items.wordlist.appendRandomWord(word) } function audioCrashPlay() { if(successRate > 0.5) successRate -= 0.1 items.audioEffects.play("qrc:/gcompris/src/core/resource/sounds/crash.wav") } function nextLevel() { if(maxLevel <= ++currentLevel ) { currentLevel = 0 } currentSubLevel = 0; initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = maxLevel - 1 } currentSubLevel = 0; initLevel(); } function nextSubLevel() { if( ++currentSubLevel >= maxSubLevel) { currentSubLevel = 0 items.bonus.good("lion") } else items.score.playWinAnimation(); initSubLevel(); } function playLetter(letter) { var locale = GCompris.ApplicationInfo.getVoicesLocale(items.locale) items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/"+locale+"/alphabet/" + Core.getSoundFilenamForChar(letter))) } - function focusTextInput() { if (!GCompris.ApplicationInfo.isMobile && items && items.textinput) items.textinput.forceActiveFocus(); } diff --git a/src/activities/smallnumbers/ActivityInfo.qml b/src/activities/smallnumbers/ActivityInfo.qml index 4f1d2e651..666a43782 100644 --- a/src/activities/smallnumbers/ActivityInfo.qml +++ b/src/activities/smallnumbers/ActivityInfo.qml @@ -1,40 +1,41 @@ /* GCompris - ActivityInfo.qml * * Copyright (C) 2015 Bruno Coudoin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import GCompris 1.0 ActivityInfo { name: "smallnumbers/Smallnumbers.qml" difficulty: 2 icon: "smallnumbers/smallnumbers.svg" author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" demo: false //: Activity title title: qsTr("Numbers With Dice") //: Help title description: qsTr("Count the number of dots on dice before they reach the ground") // intro: "Count the number on your dice and type it on your keyboard before it reaches the ground." //: Help goal goal: qsTr("In a limited time, count the number of dots") //: Help prerequisite prerequisite: qsTr("Counting skills") //: Help manual manual: qsTr("With the keyboard, type the number of dots you see on the falling dice.") credit: "" section: "computer keyboard math numeration" createdInVersion: 0 + levels: "1,2,3" } diff --git a/src/activities/smallnumbers/resource/1/Data.qml b/src/activities/smallnumbers/resource/1/Data.qml new file mode 100644 index 000000000..2bde63930 --- /dev/null +++ b/src/activities/smallnumbers/resource/1/Data.qml @@ -0,0 +1,57 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dice up to 4") + difficulty: 1 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dice up to 2"), + "words" : [ + "1", + "2" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dice up to 3"), + "words" : [ + "1", + "2", + "3", + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dice up to 4"), + "words" : [ + "1", + "2", + "3", + "4" + ] + } + ] +} diff --git a/src/activities/smallnumbers/resource/2/Data.qml b/src/activities/smallnumbers/resource/2/Data.qml new file mode 100644 index 000000000..e8529d3e7 --- /dev/null +++ b/src/activities/smallnumbers/resource/2/Data.qml @@ -0,0 +1,76 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dice up to 7") + difficulty: 2 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dice up to 4"), + "words" : [ + "1", + "2", + "3", + "4" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dice up to 5"), + "words" : [ + "1", + "2", + "3", + "4", + "5" + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dice up to 6"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6" + ] + }, + { + "level" : "4", + "objective" : qsTr("Select the number on dice up to 7"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + } + ] +} diff --git a/src/activities/smallnumbers/resource/3/Data.qml b/src/activities/smallnumbers/resource/3/Data.qml new file mode 100644 index 000000000..297a6a412 --- /dev/null +++ b/src/activities/smallnumbers/resource/3/Data.qml @@ -0,0 +1,84 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dice up to 9") + difficulty: 3 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dice up to 6"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dice up to 7"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dice up to 8"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ] + }, + { + "level" : "4", + "objective" : qsTr("Select the number on dice up to 9"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ] + } + ] +} diff --git a/src/activities/smallnumbers2/ActivityConfig.qml b/src/activities/smallnumbers2/ActivityConfig.qml new file mode 100644 index 000000000..0d980e6bc --- /dev/null +++ b/src/activities/smallnumbers2/ActivityConfig.qml @@ -0,0 +1,60 @@ +/* GCompris - ActivityConfig.qml + * +* Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 + +import "../../core" + +Item { + id: activityConfiguration + property Item background + property alias modeBox: modeBox + property var availableModes: [ + { "text": qsTr("Dots"), "value": "dot" }, + { "text": qsTr("Arabic numbers"), "value": "number" }, + { "text": qsTr("Roman numbers"), "value": "roman" }, + { "text": qsTr("Images"), "value": "image" } + ] + Flow { + id: flow + spacing: 5 + width: parent.width + GCComboBox { + id: modeBox + model: availableModes + background: activityConfiguration.background + label: qsTr("Select Domino Representation") + } + } + + property var dataToSave + function setDefaultValues() { + for(var i = 0 ; i < availableModes.length ; i++) { + if(availableModes[i].value === dataToSave["mode"]) { + modeBox.currentIndex = i; + break; + } + } + } + function saveValues() { + var newMode = availableModes[modeBox.currentIndex].value; + dataToSave = {"mode": newMode}; + } +} diff --git a/src/activities/smallnumbers2/ActivityInfo.qml b/src/activities/smallnumbers2/ActivityInfo.qml index bee3fbc0c..97f8742bc 100644 --- a/src/activities/smallnumbers2/ActivityInfo.qml +++ b/src/activities/smallnumbers2/ActivityInfo.qml @@ -1,40 +1,41 @@ /* GCompris - ActivityInfo.qml * * Copyright (C) 2015 Bruno Coudoin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import GCompris 1.0 ActivityInfo { name: "smallnumbers2/Smallnumbers2.qml" difficulty: 2 icon: "smallnumbers2/smallnumbers2.svg" author: "Bruno Coudoin <bruno.coudoin@gcompris.net>" demo: false //: Activity title title: qsTr("Numbers with dominoes") //: Help title description: qsTr("Count the number of dots on the dominoes before they reach the ground") // intro: "Count the number of the points on the dominoes then type the result on your keyboard." //: Help goal goal: qsTr("In a limited time, count the number of dots") //: Help prerequisite prerequisite: qsTr("Counting skills") //: Help manual manual: qsTr("With the keyboard, type the number of dots you see on the falling dominoes.") credit: "" section: "math numeration" createdInVersion: 0 + levels: "1,2,3" } diff --git a/src/activities/smallnumbers2/resource/1/Data.qml b/src/activities/smallnumbers2/resource/1/Data.qml new file mode 100644 index 000000000..70fa8c1b8 --- /dev/null +++ b/src/activities/smallnumbers2/resource/1/Data.qml @@ -0,0 +1,57 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dominoes up to 4") + difficulty: 1 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dominoes up to 2"), + "words" : [ + "1", + "2" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dominoes up to 3"), + "words" : [ + "1", + "2", + "3", + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dominoes up to 4"), + "words" : [ + "1", + "2", + "3", + "4" + ] + } + ] +} diff --git a/src/activities/smallnumbers2/resource/2/Data.qml b/src/activities/smallnumbers2/resource/2/Data.qml new file mode 100644 index 000000000..fbff899a8 --- /dev/null +++ b/src/activities/smallnumbers2/resource/2/Data.qml @@ -0,0 +1,76 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dominoes up to 7") + difficulty: 2 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dominoes up to 4"), + "words" : [ + "1", + "2", + "3", + "4" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dominoes up to 5"), + "words" : [ + "1", + "2", + "3", + "4", + "5" + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dominoes up to 6"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6" + ] + }, + { + "level" : "4", + "objective" : qsTr("Select the number on dominoes up to 7"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + } + ] +} diff --git a/src/activities/smallnumbers2/resource/3/Data.qml b/src/activities/smallnumbers2/resource/3/Data.qml new file mode 100644 index 000000000..3294fb04c --- /dev/null +++ b/src/activities/smallnumbers2/resource/3/Data.qml @@ -0,0 +1,84 @@ +/* GCompris - Data.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 +import GCompris 1.0 +import "../../../../core" + +Dataset { + objective: qsTr("Select a number on dominoes up to 9") + difficulty: 3 + data: [ + { + "level" : "1", + "objective" : qsTr("Select the number on dominoes up to 6"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6" + ] + }, + { + "level" : "2", + "objective" : qsTr("Select the number on dominoes up to 7"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7" + ] + }, + { + "level" : "3", + "objective" : qsTr("Select the number on dominoes up to 8"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + ] + }, + { + "level" : "4", + "objective" : qsTr("Select the number on dominoes up to 9"), + "words" : [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ] + } + ] +} diff --git a/src/activities/wordsgame/ActivityConfig.qml b/src/activities/wordsgame/ActivityConfig.qml new file mode 100644 index 000000000..a3f1a4419 --- /dev/null +++ b/src/activities/wordsgame/ActivityConfig.qml @@ -0,0 +1,101 @@ +/* GCompris - ActivityConfig.qml + * + * Copyright (C) 2019 Akshay Kumar + * + * Authors: + * Akshay Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +import QtQuick 2.6 + +import "../../core" + +Item { + id: activityConfiguration + property Item background + property alias localeBox: localeBox + property alias uppercaseBox: uppercaseBox + property bool uppercaseOnly: false + property string locale: "system" + height: column.height + width: background.width + property alias availableLangs: langs.languages + LanguageList { + id: langs + } + + Column { + id: column + spacing: 10 + Flow { + spacing: 5 + width: activityConfiguration.width + GCComboBox { + id: localeBox + visible: true + model: langs.languages + background: activityConfiguration.background + label: qsTr("Select your locale") + } + } + GCDialogCheckBox { + id: uppercaseBox + visible: true + width: parent.width + text: qsTr("Uppercase only mode") + checked: activityConfiguration.uppercaseOnly + } + } + + property var dataToSave + function setDefaultValues() { + var localeUtf8 = activityConfiguration.locale; + if(activityConfiguration.locale != "system") { + localeUtf8 += ".UTF-8"; + } + + for(var i = 0 ; i < activityConfiguration.availableLangs.length ; i ++) { + if(activityConfiguration.availableLangs[i].locale === localeUtf8) { + activityConfiguration.localeBox.currentIndex = i; + break; + } + } + } + + function saveValues() { + var configHasChanged = false + var oldLocale = activityConfiguration.locale; + var newLocale = activityConfiguration.availableLangs[activityConfiguration.localeBox.currentIndex].locale; + // Remove .UTF-8 + if(newLocale.indexOf('.') != -1) { + newLocale = newLocale.substring(0, newLocale.indexOf('.')) + } + + var oldUppercaseMode = activityConfiguration.uppercaseOnly + activityConfiguration.uppercaseOnly = activityConfiguration.uppercaseBox.checked + dataToSave = {"locale": newLocale, "uppercaseMode": "" + activityConfiguration.uppercaseOnly} + + activityConfiguration.locale = newLocale; + if(oldLocale !== newLocale || oldUppercaseMode !== activityConfiguration.uppercaseOnly) { + configHasChanged = true; + } + + // Restart the activity with new information + if(configHasChanged) { + background.stop(); + background.start(); + } + } +} diff --git a/src/core/DialogChooseLevel.qml b/src/core/DialogChooseLevel.qml index 940de57d1..fa6b2e247 100644 --- a/src/core/DialogChooseLevel.qml +++ b/src/core/DialogChooseLevel.qml @@ -1,377 +1,382 @@ /* GCompris - DialogChooseLevel.qml * * Copyright (C) 2018 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import QtQuick.Controls 1.5 import GCompris 1.0 /** * todo * @ingroup components * * todo * * @sa ApplicationSettings * @inherit QtQuick.Item */ Rectangle { id: dialogChooseLevel visible: false /* Public interface: */ /** * type:string * The name of the activity in case of per-activity config. * * Will be autogenerated unless set by the caller. */ property string activityName: currentActivity.name.split('/')[0] /// @cond INTERNAL_DOCS property bool isDialog: true /** * type:string * Title of the configuration dialog. */ readonly property string title: currentActivity ? qsTr("%1 settings").arg(currentActivity.title) : "" property var difficultiesModel: [] property QtObject currentActivity property string chosenLevel property var activityData onActivityDataChanged: loadData() /// @endcond /** * By default, we display configuration (this avoids to add code in each * activity to set it by default). */ property bool displayDatasetAtStart: !hasConfig /** * Emitted when the config dialog has been closed. */ signal close /** * Emitted when the config dialog has been started. */ signal start onStart: initialize() signal stop /** * Emitted when the settings are to be saved. * * The actual persisting of the settings in the settings file is done by * DialogActivityConfig. The activity has to take care to update its * internal state. */ signal saveData signal startActivity /** * Emitted when the config settings have been loaded. */ signal loadData property bool hasConfigOrDataset: hasConfig || hasDataset property bool hasConfig: activityConfigFile.exists("qrc:/gcompris/src/activities/"+activityName+"/ActivityConfig.qml") property bool hasDataset: currentActivity && currentActivity.levels.length !== 0 color: "#696da3" border.color: "black" border.width: 1 function initialize() { // dataset information chosenLevel = currentActivity.currentLevel difficultiesModel = [] if(currentActivity.levels.length == 0) { print("no levels to load for", activityName) } else { for(var level in currentActivity.levels) { objectiveLoader.dataFiles.push({"level": currentActivity.levels[level], "file": "qrc:/gcompris/src/activities/"+activityName+"/resource/"+currentActivity.levels[level]+"/Data.qml"}) } objectiveLoader.start() } // Defaults to config if in an activity else to dataset if in menu if(displayDatasetAtStart) { datasetVisibleButton.clicked() } else { optionsVisibleButton.clicked() } } Loader { id: objectiveLoader property var dataFiles: [] property var currentFile signal start signal stop onStart: { var file = dataFiles.shift() currentFile = file source = file.file.toString() } onLoaded: { difficultiesModel.push({"level": currentFile.level, "objective": item.objective, "difficulty": item.difficulty}) if(dataFiles.length != 0) { start() } else { stop() } } onStop: { difficultiesRepeater.model = difficultiesModel } } Row { visible: true spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogChooseLevel.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 Row { spacing: 2 padding: 8 Image { id: titleIcon anchors { left: parent.left top: parent.top margins: 4 * ApplicationInfo.ratio } } GCText { id: title text: dialogChooseLevel.title width: dialogChooseLevel.width - (30 + cancel.width) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } } // Header buttons Row { id: datasetOptionsRow - height: datasetVisibleButton.height + height: dialogChooseLevel.height / 12 width: parent.width spacing: parent.width / 4 anchors.leftMargin: parent.width / 8 Button { id: datasetVisibleButton text: qsTr("Dataset") enabled: hasDataset + height: parent.height opacity: enabled ? 1 : 0 width: parent.width / 3 property bool selected: true style: GCButtonStyle { selected: datasetVisibleButton.selected } onClicked: { selected = true; } } Button { id: optionsVisibleButton text: qsTr("Options") enabled: hasConfig + height: parent.height opacity: enabled ? 1 : 0 width: parent.width / 3 style: GCButtonStyle { selected: !datasetVisibleButton.selected } onClicked: { datasetVisibleButton.selected = false; } //showOptions() } } // "Dataset"/"Options" content Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogChooseLevel.width - 30 height: dialogChooseLevel.height - (30 + title.height * 1.2) - saveAndPlayRow.height - datasetOptionsRow.height - 3 * parent.spacing border.color: "black" border.width: 2 Flickable { id: flick anchors.margins: 8 anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom flickableDirection: Flickable.VerticalFlick clip: true contentHeight: contentItem.childrenRect.height + 40 * ApplicationInfo.ratio Loader { id: configLoader visible: !datasetVisibleButton.selected active: optionsVisibleButton.enabled source: active ? "qrc:/gcompris/src/activities/"+activityName+"/ActivityConfig.qml" : "" onItemChanged: { if(item) { item.background = dialogChooseLevel dialogChooseLevel.saveData.connect(save) getInitialConfiguration() } } function getInitialConfiguration() { activityData = Qt.binding(function() { return item.dataToSave }) if(item) { item.dataToSave = ApplicationSettings.loadActivityConfiguration(activityName) item.setDefaultValues() } } function save() { item.saveValues() ApplicationSettings.saveActivityConfiguration(activityName, item.dataToSave) } } Column { visible: datasetVisibleButton.selected spacing: 10 ExclusiveGroup { id: levelsGroup } Repeater { id: difficultiesRepeater delegate: Row { height: objective.height Image { id: difficultyIcon source: "qrc:/gcompris/src/core/resource/difficulty" + modelData.difficulty + ".svg"; sourceSize.height: objective.indicatorImageHeight anchors.verticalCenter: objective.verticalCenter } GCDialogCheckBox { id: objective width: dialogChooseLevel.width - 30 - difficultyIcon.width - 2 * flick.anchors.margins text: modelData.objective exclusiveGroup: levelsGroup checked: chosenLevel === modelData.level onClicked: chosenLevel = modelData.level } } } } } // The scroll buttons GCButtonScroll { anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: flick.bottom anchors.bottomMargin: 5 * ApplicationInfo.ratio onUp: flick.flick(0, 1400) onDown: flick.flick(0, -1400) upVisible: flick.visibleArea.yPosition <= 0 ? false : true downVisible: flick.visibleArea.yPosition + flick.visibleArea.heightRatio >= 1 ? false : true } } // Footer buttons Row { id: saveAndPlayRow - height: cancelButton.height + height: dialogChooseLevel.height / 12 width: parent.width spacing: parent.width / 16 Button { id: cancelButton text: qsTr("Cancel") + height: parent.height width: parent.width / 4 property bool selected: true style: GCButtonStyle {} onClicked: dialogChooseLevel.close() } Button { id: saveButton text: qsTr("Save") + height: parent.height width: parent.width / 4 property bool selected: true style: GCButtonStyle { } onClicked: { saveData(); close(); } } Button { id: saveAndStartButton text: qsTr("Save and start") + height: parent.height width: parent.width / 3 style: GCButtonStyle { } onClicked: { saveData(); startActivity(); } } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { id: cancel onClose: { parent.close() } } File { id: activityConfigFile } } diff --git a/src/core/Wordlist.qml b/src/core/Wordlist.qml index 48f6e25ed..e9c1d205d 100644 --- a/src/core/Wordlist.qml +++ b/src/core/Wordlist.qml @@ -1,226 +1,237 @@ /* GCompris - Wordlist.qml * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import "core.js" as Core /** * A Wordlist component loads and maintains GCompris wordlists. * @ingroup components * * It loads wordlists from json-files, validates its content and exposes * wordlists and levels to activities. * * It expects and returns the following wordlist format (UTF8 encoded): * * @code * { * "name":"default-gd", * "description":"Gàidhlig", * "locale":"gd", * "levels":[ { "level":1, * "speed":150, <-- optional * "fallspeed":7000, <-- optional * "sublevels":10, <-- optional * "words":["a","jim", "beam", ... ]}, <-- mandatory * { "level":2, ... } * ] * } * @endcode * * @inherit QtQuick.Item * @sa JsonParser */ Item { id: wordlist /** * type:string * Default filename to be used if the language specific wordlist file could * not be loaded. * Default is emtpy. */ property string defaultFilename: "" /** * type:bool * Whether to automatically fallback to the default filename if the * language specific wordlist file could not be loaded. * Default is true. */ property bool useDefault: true /** * type:string * Name of the file to load the language specific wordlist from. * Default is empty. Can also be passed directly in loadFromFile(). * If set in the QML definition, the wordlist is autoloaded onCompleted. */ property string filename: "" /** * type:object * Complete Wordlist content loaded. You probably want to use one of the * convenience accessors like getLevelWordList(). * Default is empty. */ property var wordList: ({}) /// @cond INTERNAL_DOCS property var randomWordList: [] property int maxLevel: 0 /// @endcond /** * Emitted if an error occurs. * @param msg Error message. */ signal error(string msg); + /** + * Load Wordlist from JSON Object. + * + * @param type:object levels to load wordlist from. + */ + function loadFromJSON(levels) { + wordList = {levels: levels}; + maxLevel = wordList.levels.length; + return wordList; + } + /** * Load Wordlist from file @p fname. * * @param type:string fname Filename to load wordlist from. */ function loadFromFile(fname) { filename = fname; var from; maxLevel = 0 wordList = parser.parseFromUrl(filename, validateWordlist); if (wordList == null) { error("Wordlist: Invalid wordlist file " + fname); if (useDefault) { // fallback to default file: wordList = parser.parseFromUrl(defaultFilename, validateWordlist); if (wordList == null) { error("Wordlist: Invalid wordlist file " + defaultFilename); return; } from = "default-file " + defaultFilename; } else { error("Wordlist: do not use default list, no list loaded"); return; } } else { from = "file " + fname; } // at this point we have valid levels maxLevel = wordList.levels.length; console.log("Wordlist: loaded " + maxLevel + " levels from " + from); return wordList; } /** * Get wordlist data for @p level * * @param type:int level Level. * @returns type:object wordlist data. */ function getLevelWordList(level) { if (level > maxLevel) return null; return wordList.levels[level - 1]; } /** * Get number of sub-levels in @p level. * * @param type:int level Level. * @returns type:int Number of sublevels. */ function getMaxSubLevel(level) { if (level > maxLevel || level === 0) { console.log("ERROR: Wordlist.getMaxSubLevel out of range, requested level", level, "out of expected range", 1, "-", maxLevel) return null; } return wordList.levels[level - 1].sublevels !== undefined ? wordList.levels[level - 1].sublevels : 0; } /** * Build a random word list for @p level. * * We don't want to propose several time the same word. First call * initRandomWord(level) to create the initial shuffled list of words. * Then call getRandomWord() to get the words one at a time. * If a word was not found by the child, add it again to the list * with appendRandomWord(word) * * @param type:int level Level. */ function initRandomWord(level) { randomWordList = Core.shuffle(wordList.levels[level - 1].words).slice(0) } /** * Re-add a random word to a shuffled word list. * * @param type:string word Word to append. * @sa initRandomWord */ function appendRandomWord(word) { randomWordList.unshift(word) } /** * Returns the next random word from a shuffled wordlist. * * @sa initRandomWord */ function getRandomWord() { return randomWordList.pop() } /// @cond INTERNAL_DOCS function validateWordlist(doc) { // minimal syntax check: var i; if (undefined === doc.levels) return false; for (i = 0; i < doc.levels.length; i++) { // check mandatory level properties only (speed, fallspeed and sublevels are optional) if (doc.levels[i].words.length < 1) return false; } if (i < 1) return false; return true; } /// @endcond JsonParser { id: parser onError: wordlist.error(msg); } Component.onCompleted: { if (filename != "") loadFromFile(filename); } }