diff --git a/src/activities/algebra_by/Algebra.qml b/src/activities/algebra_by/Algebra.qml index 515d2e476..fca81bdfb 100644 --- a/src/activities/algebra_by/Algebra.qml +++ b/src/activities/algebra_by/Algebra.qml @@ -1,239 +1,238 @@ /* GCompris - Algebra.qml * * Copyright (C) 2014 Aruna Sankaranarayanan * * 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 "algebra.js" as Activity ActivityBase { id: activity property int speedSetting: 5 property alias operand: operand onStart: { focus = true; } pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/algebra_by/resource/background.svg" fillMode: Image.PreserveAspectCrop sourceSize.width: Math.max(parent.width, parent.height) signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } Item { id: coreItems property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias balloon: balloon property alias timer: timer property GCSfx audioEffects: activity.audioEffects } onStart: Activity.start(coreItems, otherItems, operand, speedSetting) onStop: Activity.stop() DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias speedSlider: speedSlider height: column.height Column { id: column - spacing: 10 + spacing: 10 * ApplicationInfo.ratio width: parent.width - + GCText { + id: speedSliderText + text: qsTr("Speed") + fontSize: mediumSize + wrapMode: Text.WordWrap + } Flow { width: dialogActivityConfig.width spacing: 5 GCSlider { id: speedSlider width: 250 * ApplicationInfo.ratio value: activity.speedSetting maximumValue: 5 minimumValue: 1 scrollEnabled: false } - GCText { - id: speedSliderText - text: qsTr("Speed") - fontSize: mediumSize - wrapMode: Text.WordWrap - } } } } } onClose: home() onLoadData: { if(dataToSave) { if(dataToSave["speedSetting"]) { activity.speedSetting = dataToSave["speedSetting"]; } } } onSaveData: { var oldSpeed = activity.speedSetting activity.speedSetting = dialogActivityConfig.configItem.speedSlider.value if(oldSpeed != activity.speedSetting) { dataToSave = {"speedSetting": activity.speedSetting}; background.stop(); background.start(); } } } DialogHelp { id: dialogHelpLeftRight onClose: home() } Timer { id: timer interval: 1000 onTriggered: Activity.run() } Item { width: background.width - 60 * ApplicationInfo.ratio height: background.height Bar { id: bar content: BarEnumContent { value: (help | home | level | config) } onHelpClicked: { displayDialog(dialogHelpLeftRight) } onPreviousLevelClicked: { Activity.previousLevel() } onNextLevelClicked: { Activity.nextLevel() } onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onHomeClicked: home() } } Balloon { id: balloon onTimeout: bonus.bad("smiley") } Bonus { id: bonus Component.onCompleted: { loose.connect(Activity.run) win.connect(Activity.nextLevel) } } Score { id: score x: parent.width * 0.25 y: parent.height * 0.65 anchors.right: undefined anchors.bottom: undefined currentSubLevel: 0 numberOfSubLevels: 10 } } Item { id: otherItems property alias iAmReady: iAmReady property alias firstOp: firstOp property alias secondOp: secondOp property alias numpad: numpad property int result } NumPad { id: numpad onAnswerChanged: Activity.questionsLeft() maxDigit: ('' + otherItems.result).length + 1 } ReadyButton { id: iAmReady onClicked: Activity.run() } Flow { id: textFlow x: parent.width / 2 - width / 2 y: 80 width: parent.width / 2 height: 100 anchors.margins: 4 spacing: 10 AlgebraText { id: firstOp visible: !iAmReady.visible } AlgebraText { id: operand visible: firstOp.visible } AlgebraText { id: secondOp visible: !iAmReady.visible } AlgebraText { id: equals visible: firstOp.visible text: "=" } AlgebraText { id: result visible: !iAmReady.visible text: numpad.answer } } Keys.onPressed: { numpad.updateAnswer(event.key, true); } Keys.onReleased: { numpad.updateAnswer(event.key, false); } } diff --git a/src/activities/gletters/Gletters.qml b/src/activities/gletters/Gletters.qml index 31aaed96e..99de523ff 100644 --- a/src/activities/gletters/Gletters.qml +++ b/src/activities/gletters/Gletters.qml @@ -1,354 +1,354 @@ /* 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/" property bool uppercaseOnly: false property int speedSetting: 10 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() 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 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: { // for smallnumbers and smallnumbers2, we want to have the application locale, not the system one if(activity.activityName !== "gletters") { var overridenLocale = ApplicationSettings.locale // Remove .UTF-8 if(overridenLocale.indexOf('.') != -1) { overridenLocale = overridenLocale.substring(0, overridenLocale.indexOf('.')) } background.locale = overridenLocale } Activity.start(items, uppercaseOnly, mode, speedSetting); Activity.focusTextInput() } onStop: { 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 onTextChanged: { if (text != "") { Activity.processKeyPress(text); text = ""; } } } //created to retrieve available menu modes for domino configurations Domino { id: invisibleDomino visible: false } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox property alias dominoModeBox: dominoModeBox property alias uppercaseBox: uppercaseBox property alias speedSlider: speedSlider 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 } + GCText { + id: speedSliderText + text: qsTr("Speed") + fontSize: mediumSize + wrapMode: Text.WordWrap + } Flow { width: dialogActivityConfig.width spacing: 5 GCSlider { id: speedSlider width: 250 * ApplicationInfo.ratio value: activity.speedSetting maximumValue: 10 minimumValue: 1 scrollEnabled: false } - GCText { - id: speedSliderText - text: qsTr("Speed") - fontSize: mediumSize - wrapMode: Text.WordWrap - } } } } } 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"]; } } if(dataToSave && dataToSave["speedSetting"]) { activity.speedSetting = dataToSave["speedSetting"]; } } 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; } } var oldSpeed = activity.speedSetting activity.speedSetting = dialogActivityConfig.configItem.speedSlider.value if(oldSpeed != activity.speedSetting) { dataToSave = {"speedSetting": activity.speedSetting}; configHasChanged = true; } // Restart the activity with new information if(configHasChanged) { background.stop(); background.start(); } } function setDefaultValues() { 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; } } } 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; } } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: (help | home | level | config) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() 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/note_names/NoteNames.qml b/src/activities/note_names/NoteNames.qml index 5c98c0d51..cba2316c2 100644 --- a/src/activities/note_names/NoteNames.qml +++ b/src/activities/note_names/NoteNames.qml @@ -1,498 +1,498 @@ /* GCompris - NoteNames.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Aman Kumar Gupta * * 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 import "../../core" import "../piano_composition" import "note_names.js" as Activity ActivityBase { id: activity property int speedSetting: 5 property int timerNormalInterval: (13500 / speedSetting) isMusicalActivity: true onStart: focus = true onStop: {} property bool horizontalLayout: width >= height pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyNoteBindings = {} keyNoteBindings[Qt.Key_1] = 'C' keyNoteBindings[Qt.Key_2] = 'D' keyNoteBindings[Qt.Key_3] = 'E' keyNoteBindings[Qt.Key_4] = 'F' keyNoteBindings[Qt.Key_5] = 'G' keyNoteBindings[Qt.Key_6] = 'A' keyNoteBindings[Qt.Key_7] = 'B' if(!introMessage.visible && !iAmReady.visible && !messageBox.visible && multipleStaff.musicElementModel.count - 1) { if(keyNoteBindings[event.key]) { // If the key pressed matches the note, pass the correct answer as parameter. isCorrectKey(keyNoteBindings[event.key]) } else if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) { doubleOctave.currentOctaveNb-- } else if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) { doubleOctave.currentOctaveNb++ } } } function isCorrectKey(key) { if(Activity.newNotesSequence[Activity.currentNoteIndex][0] === key) Activity.correctAnswer() else items.displayNoteNameTimer.start() } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias bar: bar property alias multipleStaff: multipleStaff property alias doubleOctave: doubleOctave property alias bonus: bonus property alias iAmReady: iAmReady property alias messageBox: messageBox property alias addNoteTimer: addNoteTimer property alias dataset: dataset property alias progressBar: progressBar property alias introMessage: introMessage property bool isTutorialMode: true property alias displayNoteNameTimer: displayNoteNameTimer } Loader { id: dataset asynchronous: false source: "qrc:/gcompris/src/activities/note_names/resource/dataset_01.qml" } onStart: { Activity.start(items, activity.timerNormalInterval) } onStop: { Activity.stop() } property string clefType: "Treble" DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias speedSlider: speedSlider height: column.height Column { id: column - spacing: 10 + spacing: 10 * ApplicationInfo.ratio width: parent.width + GCText { + id: speedSliderText + text: qsTr("Speed") + fontSize: mediumSize + wrapMode: Text.WordWrap + } Flow { width: dialogActivityConfig.width spacing: 5 GCSlider { id: speedSlider width: 250 * ApplicationInfo.ratio value: activity.speedSetting maximumValue: 5 minimumValue: 1 scrollEnabled: false } - GCText { - id: speedSliderText - text: qsTr("Speed") - fontSize: mediumSize - wrapMode: Text.WordWrap - } } } } } onStart: { if(!introMessage.visible || !iAmReady.visible) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() } } onClose: { home(); introMessage.visible = false; iAmReady.visible = true; } onLoadData: { if(dataToSave) { if(dataToSave["speedSetting"]) { activity.speedSetting = dataToSave["speedSetting"]; } } } onSaveData: { var oldSpeed = activity.speedSetting activity.speedSetting = dialogActivityConfig.configItem.speedSlider.value if(oldSpeed != activity.speedSetting) { dataToSave = {"speedSetting": activity.speedSetting}; background.stop(); introMessage.visible = false; iAmReady.visible = true; } } } Timer { id: displayNoteNameTimer interval: 2000 onRunningChanged: { if(running) { multipleStaff.pauseNoteAnimation() addNoteTimer.pause() messageBox.visible = true } else { messageBox.visible = false if(progressBar.percentage != 100 && Activity.newNotesSequence.length) { Activity.wrongAnswer() addNoteTimer.resume() } } } } Rectangle { id: messageBox width: label.width + 20 height: label.height + 20 border.width: 5 border.color: "black" anchors.centerIn: multipleStaff radius: 10 z: 11 visible: false function getTranslatedNoteName(noteName) { for(var i = 0; i < doubleOctave.keyNames.length; i++) { if(doubleOctave.keyNames[i][0] == noteName) return doubleOctave.keyNames[i][1] } return "" } onVisibleChanged: { if(Activity.targetNotes[0] === undefined) text = "" else if(items.isTutorialMode) text = qsTr("New note: %1").arg(getTranslatedNoteName(Activity.targetNotes[0])) else text = getTranslatedNoteName(Activity.newNotesSequence[Activity.currentNoteIndex]) } property string text GCText { id: label anchors.centerIn: parent fontSize: mediumSize text: parent.text } MouseArea { anchors.fill: parent enabled: items.isTutorialMode onClicked: { items.multipleStaff.pauseNoteAnimation() items.multipleStaff.musicElementModel.remove(1) Activity.showTutorial() } } } Rectangle { id: colorLayer anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 visible: !introMessage.visible onVisibleChanged: { messageBox.visible = false } onClicked: { Activity.initLevel() } } IntroMessage { id: introMessage anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 5 left: parent.left leftMargin: 5 } z: 12 } AdvancedTimer { id: addNoteTimer onTriggered: { Activity.noteIndexToDisplay = (Activity.noteIndexToDisplay + 1) % Activity.newNotesSequence.length Activity.displayNote(Activity.newNotesSequence[Activity.noteIndexToDisplay]) } } ProgressBar { id: progressBar height: 20 * ApplicationInfo.ratio width: parent.width / 4 property int percentage: 0 value: percentage maximumValue: 100 visible: !items.isTutorialMode anchors { top: parent.top topMargin: 10 right: parent.right rightMargin: 10 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.78 height: horizontalLayout ? parent.height * 0.9 : parent.height * 0.7 nbStaves: 1 clef: clefType notesColor: "red" softColorOpacity: 0 isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: progressBar.height + 20 flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 2.7 noteAnimationEnabled: true noteAnimationDuration: items.isTutorialMode ? 9000 : 45000 / activity.speedSetting onNoteAnimationFinished: { if(!items.isTutorialMode) displayNoteNameTimer.start() } } // We present a pair of two joint piano keyboard octaves. Item { id: doubleOctave width: parent.width * 0.95 height: horizontalLayout ? parent.height * 0.22 : 2 * parent.height * 0.18 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 30 readonly property int nbJointKeyboards: 2 readonly property int maxNbOctaves: 3 property int currentOctaveNb: 0 property var coloredKeyLabels: [] property var keyNames: [] Repeater { id: octaveRepeater anchors.fill: parent model: doubleOctave.nbJointKeyboards PianoOctaveKeyboard { id: pianoKeyboard width: horizontalLayout ? octaveRepeater.width / 2 : octaveRepeater.width height: horizontalLayout ? octaveRepeater.height : octaveRepeater.height / 2 blackLabelsVisible: false blackKeysEnabled: blackLabelsVisible whiteKeysEnabled: !messageBox.visible && multipleStaff.musicElementModel.count > 1 onNoteClicked: Activity.checkAnswer(note) currentOctaveNb: doubleOctave.currentOctaveNb anchors.top: (index === 1) ? octaveRepeater.top : undefined anchors.topMargin: horizontalLayout ? 0 : -15 anchors.bottom: (index === 0) ? octaveRepeater.bottom : undefined anchors.right: (index === 1) ? octaveRepeater.right : undefined coloredKeyLabels: doubleOctave.coloredKeyLabels labelsColor: "red" // The octaves sets corresponding to respective clef types are in pairs for the joint piano keyboards at a time when displaying. whiteKeyNoteLabelsBass: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(0, 4), // F1 to B1 whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18) // C3 to B3 ] } else { return [ whiteKeyNoteLabelsArray.slice(4, 11), // C2 to B2 whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25) // C4 to B4 ] } } whiteKeyNoteLabelsTreble: { if(index === 0) { return [ whiteKeyNoteLabelsArray.slice(11, 18), // C3 to B3 whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32) // C5 to B5 ] } else { return [ whiteKeyNoteLabelsArray.slice(18, 25), // C4 to B4 whiteKeyNoteLabelsArray.slice(25, 32), // C5 to B5 whiteKeyNoteLabelsArray.slice(32, 34) // C6 to D6 ] } } Component.onCompleted: doubleOctave.keyNames = whiteKeyNoteLabelsArray } } } Image { id: shiftKeyboardLeft source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb > 0) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top left: doubleOctave.left leftMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb-- } } } Image { id: shiftKeyboardRight source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: horizontalLayout ? doubleOctave.width / 13 : doubleOctave.width / 6 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit visible: (doubleOctave.currentOctaveNb < doubleOctave.maxNbOctaves - 1) && doubleOctave.visible z: 11 anchors { bottom: doubleOctave.top right: doubleOctave.right rightMargin: -37 bottomMargin: horizontalLayout ? 10 : 25 } MouseArea { enabled: !messageBox.visible anchors.fill: parent onClicked: { doubleOctave.currentOctaveNb++ } } } OptionsRow { id: optionsRow iconsWidth: 0 visible: false } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: (help | home | level | reload | config) } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { iAmReady.visible = true Activity.initLevel() } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/readingh/Readingh.qml b/src/activities/readingh/Readingh.qml index 07172477c..e52f6da16 100644 --- a/src/activities/readingh/Readingh.qml +++ b/src/activities/readingh/Readingh.qml @@ -1,334 +1,334 @@ /* GCompris - readingh.qml * * Copyright (C) 2015 Johnny Jazeix * * Authors: * Bruno Coudoin (GTK+ version) * Johnny Jazeix (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 "readingh.js" as Activity import "qrc:/gcompris/src/core/core.js" as Core ActivityBase { id: activity onStart: focus = true onStop: {} property int speedSetting: 5 /* mode of the activity, "readingh" (horizontal) or "readingv" (vertical):*/ property string mode: "readingh" pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "reading-bg.svg" signal start signal stop sourceSize.width: parent.width fillMode: Image.Stretch Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() activity.start.connect(start) activity.stop.connect(stop) } // system locale by default property string locale: "system" // 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 wordlist: wordlist property alias wordDropTimer: wordDropTimer property alias locale: background.locale property alias iAmReady: iAmReady property alias answerButtonFound: answerButtonFound property alias answerButtonNotFound: answerButtonNotFound property alias answerButtonsFlow: answerButtonsFlow property alias wordDisplayRepeater: wordDisplayRepeater property string textToFind property int currentIndex property bool buttonsBlocked: false } onStart: { Activity.start(items, mode) } onStop: { Activity.stop() } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { Item { property alias localeBox: localeBox property alias speedSlider: speedSlider height: column.height property alias availableLangs: langs.languages LanguageList { id: langs } Column { id: column - spacing: 10 + spacing: 10 * ApplicationInfo.ratio width: parent.width Flow { spacing: 5 width: dialogActivityConfig.width GCComboBox { id: localeBox model: langs.languages background: dialogActivityConfig width: dialogActivityConfig.width label: qsTr("Select your locale") } + } + GCText { + id: speedSliderText + text: qsTr("Speed") + fontSize: mediumSize + wrapMode: Text.WordWrap } Flow { width: dialogActivityConfig.width spacing: 5 GCSlider { id: speedSlider width: 250 * ApplicationInfo.ratio value: activity.speedSetting maximumValue: 5 minimumValue: 1 scrollEnabled: false } - GCText { - id: speedSliderText - text: qsTr("Speed") - fontSize: mediumSize - wrapMode: Text.WordWrap - } } } } } onClose: home() onLoadData: { if(dataToSave) { if(dataToSave["locale"]) { background.locale = dataToSave["locale"]; } } if(dataToSave) { if(dataToSave["speedSetting"]) { activity.speedSetting = dataToSave["speedSetting"]; } } } onSaveData: { 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('.')) } dataToSave = { "locale": newLocale, } background.locale = newLocale; // Restart the activity with new information if(oldLocale !== newLocale) { background.stop(); wordDisplayList.layoutDirection = Core.isLeftToRightLocale(background.locale) ? Qt.LeftToRight : Qt.RightToLeft; background.start(); } var oldSpeed = activity.speedSetting activity.speedSetting = dialogActivityConfig.configItem.speedSlider.value if(oldSpeed != activity.speedSetting) { dataToSave = {"speedSetting": activity.speedSetting}; background.stop(); background.start(); } } function setDefaultValues() { 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.loader.item.localeBox.currentIndex = i; break; } } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true dialogActivityConfig.setDefaultValues() displayDialog(dialogActivityConfig) } } Bonus { id: bonus // Do not pass automatically at next level, allowing the child to do more than one try, or add sublevels? Component.onCompleted: { win.connect(resetClickInProgress) loose.connect(resetClickInProgress) } } function resetClickInProgress() { items.buttonsBlocked = false Activity.initLevel() } Flow { id: wordDisplayList spacing: 20 x: 70/800*parent.width y: 100/600*parent.height - 40 * ApplicationInfo.ratio width: 350/800*parent.width-x height: 520/600*parent.height-y - 40 * ApplicationInfo.ratio flow: mode == "readingh" ? Flow.LeftToRight : Flow.TopToBottom layoutDirection: Core.isLeftToRightLocale(locale) ? Qt.LeftToRight : Qt.RightToLeft Repeater { id: wordDisplayRepeater model: Activity.words property int idToHideBecauseOverflow: 0 delegate: GCText { text: modelData color: "#373737" opacity: iAmReady.visible ? false : (index == items.currentIndex ? 1 : 0) onOpacityChanged: { /* Handle case where we go over the image On these cases, we hide all above items to restart to 0 As we don't replay the same level and always replace the model, we do not care about restoring visible to true */ if((x+width > wordDisplayList.width) || (y+height > wordDisplayList.height)) { var i = wordDisplayRepeater.idToHideBecauseOverflow; for(; i < index; ++i) { wordDisplayRepeater.itemAt(i).visible=false } wordDisplayRepeater.idToHideBecauseOverflow = i } } } } } GCText { id: wordToFindBox x: 430/800*parent.width y: 90/600*parent.height text: qsTr("Check if the word
%1
is displayed").arg(items.textToFind) color: "#373737" horizontalAlignment: Text.AlignHCenter width: background.width/3 height: background.height/5 fontSizeMode: Text.Fit } ReadyButton { id: iAmReady onClicked: Activity.run() x: background.width / 2 y: background.height / 2.2 anchors.verticalCenter: undefined anchors.horizontalCenter: undefined theme: "light" } Flow { id: answerButtonsFlow x: iAmReady.x y: iAmReady.y width: wordToFindBox.width AnswerButton { id : answerButtonFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 60 * ApplicationInfo.ratio textLabel: qsTr("Yes, I saw it!") isCorrectAnswer: Activity.words ? Activity.words.indexOf(items.textToFind) != -1 : false onCorrectlyPressed: bonus.good("flower") onIncorrectlyPressed: bonus.bad("flower") blockAllButtonClicks: items.buttonsBlocked onPressed: { items.buttonsBlocked = true } } AnswerButton { id : answerButtonNotFound width: Math.min(250 * ApplicationInfo.ratio, background.width/2-10) height: 60 * ApplicationInfo.ratio textLabel: qsTr("No, it was not there!") isCorrectAnswer: !answerButtonFound.isCorrectAnswer onCorrectlyPressed: bonus.good("flower") onIncorrectlyPressed: bonus.bad("flower") blockAllButtonClicks: items.buttonsBlocked onPressed: { items.buttonsBlocked = true } } } 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("Reading: Wordlist error: " + msg); } Timer { id: wordDropTimer repeat: true interval: items.currentIndex == -1 ? 100 : 5000 / speedSetting; onTriggered: Activity.dropWord(); } } } diff --git a/src/core/GCComboBox.qml b/src/core/GCComboBox.qml index f11aa1da0..b8eb5d3c6 100644 --- a/src/core/GCComboBox.qml +++ b/src/core/GCComboBox.qml @@ -1,322 +1,321 @@ /* GCompris - GCComboBox.qml * * Copyright (C) 2015 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 /** * A QML component unifying comboboxes in GCompris. * @ingroup components * * GCComboBox contains a combobox and a label. * When the combobox isn't active, it is displayed as a button containing the current value * and the combobox label (its description). * Once the button is clicked, the list of all available choices is displayed. * Also, above the list is the combobox label. * * Navigation can be done with keys and mouse/gestures. * As Qt comboboxes, you can either have a js Array or a Qml model as model. * GCComboBox should now be used wherever you'd use a QtQuick combobox. It has * been decided to implement comboboxes ourselves in GCompris because of * some integration problems on some OSes (native dialogs unavailable). */ Item { id: gccombobox focus: true - width: parent.width + width: parent.width * 0.9 height: flow.height /** * type:Item * Where the list containing all choices will be displayed. * Should be the dialogActivityConfig item if used on config. */ property Item background /** * type:int * Current index of the combobox. */ property int currentIndex: -1 /** * type:string * Current text displayed in the combobox when inactive. */ property string currentText /** * type:alias * Model for the list (user has to specify one). */ property alias model: gridview.model /** * type:string * Text besides the combobox, used to describe what the combobox is for. */ property string label /** * type:bool * Internal value. * If model is an js Array, we access data using modelData and [] else qml Model, we need to use model and get(). */ readonly property bool isModelArray: model.constructor === Array // start and stop trigs the animation signal start signal stop // emitted at stop animation end signal close onCurrentIndexChanged: { currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } /** * type:Flow - * Combobox display when inactive: the button with current choice and its label besides. + * Combobox display when inactive: the label and the button with current choice. */ Flow { id: flow width: parent.width spacing: 5 * ApplicationInfo.ratio + Item { + width: labelText.width + height: button.height + GCText { + id: labelText + text: label + anchors.verticalCenter: parent.verticalCenter + fontSize: mediumSize + wrapMode: Text.WordWrap + } + } Rectangle { id: button visible: true // Add radius to add some space between text and borders implicitWidth: Math.max(200, currentTextBox.width+radius) implicitHeight: 50 * ApplicationInfo.ratio border.width: 2 border.color: "#373737" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: mouseArea.pressed ? "#C03ACAFF" : "#23373737" } GradientStop { position: 1 ; color: mouseArea.pressed ? "#803ACAFF" : "#13373737" } } // Current value of combobox GCText { id: currentTextBox anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: currentText fontSize: mediumSize color: "#373737" } MouseArea { id: mouseArea anchors.fill: parent onReleased: { popup.visible = true } } } - - Item { - width: labelText.width - height: button.height - GCText { - id: labelText - text: label - anchors.verticalCenter: parent.verticalCenter - fontSize: mediumSize - wrapMode: Text.WordWrap - } - } } /** * type:Item * Combobox display when active: header with the description and the gridview containing all the available choices. */ Item { id: popup visible: false width: parent.width height: parent.height parent: background z: 100 focus: visible // Forward event to activity if key pressed is not one of the handled key // (ctrl+F should still resize the window for example) Keys.onPressed: { if(event.key !== Qt.Key_Back) { background.currentActivity.Keys.onPressed(event) } } Keys.onReleased: { if(event.key === Qt.Key_Back) { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); event.accepted = true } } Keys.onRightPressed: gridview.moveCurrentIndexRight(); Keys.onLeftPressed: gridview.moveCurrentIndexLeft(); Keys.onDownPressed: gridview.moveCurrentIndexDown(); Keys.onUpPressed: gridview.moveCurrentIndexUp(); Keys.onEscapePressed: { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); } Keys.onEnterPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onReturnPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onSpacePressed: { acceptChange(); hidePopUpAndRestoreFocus(); } // Don't accept the list value, restore previous value function discardChange() { if(isModelArray) { for(var i = 0 ; i < model.count ; ++ i) { if(model[currentIndex].text === currentText) { currentIndex = i; break; } } } else { for(var i = 0 ; i < model.length ; ++ i) { if(model.get(currentIndex).text === currentText) { currentIndex = i; break; } } } gridview.currentIndex = currentIndex; } // Accept the change. Updates the currentIndex and text of the button function acceptChange() { currentIndex = gridview.currentIndex; currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } function hidePopUpAndRestoreFocus() { popup.visible = false; // Restore focus on previous activity for keyboard input background.currentActivity.forceActiveFocus(); } Rectangle { id: listBackground anchors.fill: parent radius: 10 color: "grey" Rectangle { id : headerDescription z: 10 width: gridview.width height: gridview.elementHeight GCText { text: label fontSize: mediumSize wrapMode: Text.WordWrap anchors.horizontalCenter: parent.horizontalCenter } GCButtonCancel { id: discardIcon anchors.right: headerDescription.right anchors.top: headerDescription.top MouseArea { anchors.fill: parent onClicked: { popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } GridView { id: gridview z: 4 readonly property int elementHeight: 40 * ApplicationInfo.ratio // each element has a 300 width size minimum. If the screen is larger than it, // we do a grid with cases with 300px for width at minimum. // If you have a better idea/formula to have a different column number, don't hesitate, change it :). readonly property int numberOfColumns: Math.max(1, Math.floor(width / (300 * ApplicationInfo.ratio))) contentHeight: isModelArray ? elementHeight*model.count/numberOfColumns : elementHeight*model.length/numberOfColumns width: listBackground.width height: listBackground.height-headerDescription.height currentIndex: gccombobox.currentIndex flickableDirection: Flickable.VerticalFlick clip: true anchors.top: headerDescription.bottom cellWidth: width / numberOfColumns cellHeight: elementHeight delegate: Component { Rectangle { width: gridview.cellWidth height: gridview.elementHeight color: GridView.isCurrentItem ? "darkcyan" : "beige" border.width: GridView.isCurrentItem ? 3 : 2 radius: 5 Image { id: isSelectedIcon visible: parent.GridView.isCurrentItem source: "qrc:/gcompris/src/core/resource/apply.svg" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 10 sourceSize.width: (gridview.elementHeight * 0.8) } GCText { id: textValue text: isModelArray ? modelData.text : model.text anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { currentIndex = index popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } } } } }