diff --git a/src/activities/missing-letter/MissingLetter.qml b/src/activities/missing-letter/MissingLetter.qml index 4d8eec532..22aac3249 100644 --- a/src/activities/missing-letter/MissingLetter.qml +++ b/src/activities/missing-letter/MissingLetter.qml @@ -1,333 +1,335 @@ /* GCompris - missing-letter.qml * * Copyright (C) 2014 "Amit Tomar" * * Authors: * "Pascal Georges" (GTK+ version) * "Amit Tomar" (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.1 import GCompris 1.0 import "../../core" import "missing-letter.js" as Activity ActivityBase { id: activity 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.url + "background.svg" sourceSize.width: parent.width fillMode: Image.PreserveAspectCrop readonly property string wordsResource: "data2/words/words.rcc" property bool englishFallback: false property bool downloadWordsNeeded: false signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias questionImage: questionImage property alias questionText: questionText property alias answers: answers property GCAudio audioVoices: activity.audioVoices property alias englishFallbackDialog: englishFallbackDialog property alias parser: parser property string answer property alias textinput: textinput } function handleResourceRegistered(resource) { if (resource == wordsResource) Activity.start(); } onStart: { Activity.init(items) Activity.focusTextInput() // check for words.rcc: if (DownloadManager.isDataRegistered("words")) { // words.rcc is already registered -> start right away Activity.start(); } else if(DownloadManager.haveLocalResource(wordsResource)) { // words.rcc is there -> register old file first if (DownloadManager.registerResource(wordsResource)) Activity.start(items); else // could not register the old data -> react to a possible update DownloadManager.resourceRegistered.connect(handleResourceRegistered); // then try to update in the background DownloadManager.updateResource(wordsResource); } else { // words.rcc has not been downloaded yet -> ask for download downloadWordsNeeded = true } } onStop: { DownloadManager.resourceRegistered.disconnect(handleResourceRegistered); Activity.stop() } 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 - // TODO, do we want to have keyboard input disreguards casing - property bool uppercaseOnly: false - onTextChanged: { if (text != '') { - var typedText = uppercaseOnly ? text.toLocaleUpperCase() : text; - var question = Activity.getCurrentQuestion() - if(typedText === question.answer) { + var typedText = text + var answerText = Activity.getCurrentQuestion().answer + if(ApplicationSettings.fontCapitalization === Font.AllUppercase) + typedText = text.toLocaleUpperCase() + else if(ApplicationSettings.fontCapitalization === Font.AllLowercase) + typedText = text.toLocaleLowerCase() + + if(typedText === answerText) { questionAnim.start() Activity.showAnswer() } text = ''; } } } // Buttons with possible answers shown on the left of screen Column { id: buttonHolder spacing: 10 * ApplicationInfo.ratio x: holder.x - width - 10 * ApplicationInfo.ratio y: holder.y add: Transition { NumberAnimation { properties: "y"; from: holder.y; duration: 500 } } Repeater { id: answers AnswerButton { width: 120 * ApplicationInfo.ratio height: (holder.height - buttonHolder.spacing * answers.model.length) / answers.model.length textLabel: modelData isCorrectAnswer: modelData === items.answer onCorrectlyPressed: questionAnim.start() onPressed: modelData == items.answer ? Activity.showAnswer() : '' } } } // Picture holder for different images being shown Rectangle { id: holder width: Math.max(questionImage.width * 1.1, questionImage.height * 1.1) height: questionTextBg.y + questionTextBg.height x: (background.width - width - 130 * ApplicationInfo.ratio) / 2 + 130 * ApplicationInfo.ratio y: 20 color: "black" radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#80FFFFFF" } GradientStop { position: 0.9; color: "#80EEEEEE" } GradientStop { position: 1.0; color: "#80AAAAAA" } } Item { id: spacer height: 20 } Image { id: questionImage anchors.horizontalCenter: holder.horizontalCenter anchors.top: spacer.bottom width: Math.min((background.width - 120 * ApplicationInfo.ratio) * 0.7, (background.height - 100 * ApplicationInfo.ratio) * 0.7) height: width } Rectangle { id: questionTextBg width: holder.width height: questionText.height * 1.1 anchors.horizontalCenter: holder.horizontalCenter anchors.top: questionImage.bottom radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } GCText { id: questionText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter style: Text.Outline styleColor: "black" color: "white" fontSize: largeSize wrapMode: Text.WordWrap width: holder.width SequentialAnimation { id: questionAnim NumberAnimation { target: questionText property: 'scale' to: 1.05 duration: 500 easing.type: Easing.OutQuad } NumberAnimation { target: questionText property: 'scale' to: 0.95 duration: 1000 easing.type: Easing.OutQuad } NumberAnimation { target: questionText property: 'scale' to: 1.0 duration: 500 easing.type: Easing.OutQuad } ScriptAction { script: Activity.nextSubLevel() } } } } } Score { id: score anchors.bottom: undefined anchors.bottomMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.top: parent.top } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | repeat } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onRepeatClicked: Activity.playCurrentWord() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } JsonParser { id: parser onError: console.error("missing letter: Error parsing json: " + msg); } Loader { id: downloadWordsDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("The images for this activity are not yet installed.") button1Text: qsTr("Download the images") onClose: background.downloadWordsNeeded = false onButton1Hit: { DownloadManager.resourceRegistered.connect(handleResourceRegistered); DownloadManager.downloadResource(wordsResource) var downloadDialog = Core.showDownloadDialog(activity, {}); } } anchors.fill: parent focus: true active: background.downloadWordsNeeded onStatusChanged: if (status == Loader.Ready) item.start() } Loader { id: englishFallbackDialog sourceComponent: GCDialog { parent: activity.main message: qsTr("We are sorry, we don't have yet a translation for your language.") + " " + qsTr("GCompris is developed by the KDE community, you can translate GCompris by joining a translation team on %2").arg("http://l10n.kde.org/") + "

" + qsTr("We switched to English for this activity but you can select another language in the configuration dialog.") onClose: background.englishFallback = false } anchors.fill: parent focus: true active: background.englishFallback onStatusChanged: if (status == Loader.Ready) item.start() } } } diff --git a/src/activities/missing-letter/missing-letter.js b/src/activities/missing-letter/missing-letter.js index 108791948..0b7f65c37 100644 --- a/src/activities/missing-letter/missing-letter.js +++ b/src/activities/missing-letter/missing-letter.js @@ -1,242 +1,258 @@ /* GCompris - missing-letter.js * * Copyright (C) 2014 "Amit Tomar" * * Authors: * "Pascal Georges" (GTK+ version) * "Amit Tomar" (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 . */ .pragma library .import QtQuick 2.0 as Quick .import "qrc:/gcompris/src/core/core.js" as Core .import GCompris 1.0 as GCompris //for ApplicationInfo .import "qrc:/gcompris/src/activities/lang/lang_api.js" as Lang var url = "qrc:/gcompris/src/activities/missing-letter/resource/" var langUrl = "qrc:/gcompris/src/activities/lang/resource/"; var items var currentLevel var numberOfLevel var questions var dataset var lessons // Do not propose these letter in the choices -var ignoreLetters = '[ ,;:-_\']' +var ignoreLetters = '[ ,;:\u0027]' function init(items_) { items = items_ } function start() { currentLevel = 0 var locale = GCompris.ApplicationInfo.getVoicesLocale(GCompris.ApplicationSettings.locale) // register the voices for the locale GCompris.DownloadManager.updateResource(GCompris.DownloadManager.getVoicesResourceForLocale(locale)) dataset = Lang.load(items.parser, langUrl, "words.json", "content-"+ locale +".json") // If dataset is empty, we try to load from short locale // and if not present again, we switch to default one var localeUnderscoreIndex = locale.indexOf('_') if(!dataset) { 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; } dataset = Lang.load(items.parser, langUrl, "words.json", "content-"+localeShort+ ".json") } // If still dataset is empty then fallback to english if(!dataset) { // English fallback items.background.englishFallback = true dataset = Lang.load(items.parser, langUrl, "words.json", "content-en.json") } else { items.background.englishFallback = false } lessons = Lang.getAllLessons(dataset) questions = initDataset() numberOfLevel = questions.length initLevel() } function initDataset() { var questions = [] for (var lessonIndex = 0; lessonIndex < lessons.length; lessonIndex++) { var lesson = Lang.getLessonWords(dataset, lessons[lessonIndex]) var guessLetters = getRandomLetters(lesson) questions[lessonIndex] = [] for (var j in lesson) { var clearQuestion = lesson[j].translatedTxt + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + clearQuestion = clearQuestion.toLocaleUpperCase() + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + clearQuestion = clearQuestion.toLocaleLowerCase() + var maskedQuestion = getRandomMaskedQuestion(clearQuestion, guessLetters, lessonIndex) questions[lessonIndex].push( { 'image': lesson[j].image, 'clearQuestion': clearQuestion, 'maskedQuestion': maskedQuestion[0], 'answer': maskedQuestion[1], 'choices': maskedQuestion[2], 'voice': lesson[j].voice, }) } } return questions } -// Get all the letters for all the words in the lesson +// Get all the letters for all the words in the lesson excluding ignoreLetters function getRandomLetters(lesson) { var letters = [] var re = new RegExp(ignoreLetters, 'g'); for (var i in lesson) { - letters = letters.concat(lesson[i].translatedTxt.replace(re, '').split('')) + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').toLocaleUpperCase().split('')) + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').toLocaleLowerCase().split('')) + else + letters = letters.concat(lesson[i].translatedTxt.replace(re, '').split('')) } return sortUnique(letters) } // Get a random letter in the given word excluding ignoreLetters function getRandomLetter(word) { var re = new RegExp(ignoreLetters, 'g') var letters = word.replace(re, '').split('') - return Core.shuffle(letters)[0] + var letter = Core.shuffle(letters)[0] + if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllUppercase) + return letter.toLocaleUpperCase() + else if(GCompris.ApplicationSettings.fontCapitalization === Quick.Font.AllLowercase) + return letter.toLocaleLowerCase() + + return letter } function getRandomMaskedQuestion(clearQuestion, guessLetters, level) { var maskedQuestion = clearQuestion var goodLetter = getRandomLetter(maskedQuestion) var index = maskedQuestion.search(goodLetter) // Replace the char at index with '_' var repl = maskedQuestion.split('') repl[index] = '_' maskedQuestion = repl.join('') // Get some other letter to confuse the children var confusingLetters = [] for(var i = 0; i < Math.min(level + 2, 6); i++) { var letter = guessLetters.shift() confusingLetters.push(letter) guessLetters.push(letter) } confusingLetters.push(goodLetter) return [maskedQuestion, goodLetter, Core.shuffle(sortUnique(confusingLetters))] } function sortUnique(arr) { arr = arr.sort(function (a, b) { return a.localeCompare(b); }); var ret = [arr[0]]; for (var i = 1; i < arr.length; i++) { // start loop at 1 as element 0 can never be a duplicate if (arr[i-1] !== arr[i]) { ret.push(arr[i]); } } return ret; } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 items.score.currentSubLevel = 1 items.score.numberOfSubLevels = questions[currentLevel].length showQuestion() } function getCurrentQuestion() { return questions[currentLevel][items.score.currentSubLevel - 1] } function showQuestion() { var question = getCurrentQuestion() playWord(question.voice) items.answer = question.answer items.answers.model = question.choices items.questionText.text = question.maskedQuestion items.questionImage.source = "qrc:/gcompris/data/" + question.image } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function nextSubLevel() { var question = getCurrentQuestion() if(++items.score.currentSubLevel > questions[currentLevel].length) { items.bonus.good('flower') nextLevel() return } showQuestion() } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function showAnswer() { var question = getCurrentQuestion() playLetter(question.answer) items.questionText.text = question.clearQuestion } function playLetter(letter) { items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath("voices-$CA/$LOCALE/alphabet/" + Core.getSoundFilenamForChar(letter))) } function playCurrentWord() { var question = getCurrentQuestion() playWord(question.voice) } function playWord(word) { items.audioVoices.append(GCompris.ApplicationInfo.getAudioFilePath(word)) } function focusTextInput() { if (!GCompris.ApplicationInfo.isMobile && items && items.textinput) items.textinput.forceActiveFocus(); }