diff --git a/src/activities/clockgame/Clockgame.qml b/src/activities/clockgame/Clockgame.qml index b0ddfd623..6da39fe2e 100644 --- a/src/activities/clockgame/Clockgame.qml +++ b/src/activities/clockgame/Clockgame.qml @@ -1,513 +1,515 @@ /* GCompris - Clockgame.qml * * Copyright (C) 2014 Stephane Mankowski * * Authors: * Bruno Coudoin (GTK+ version) * Stephane Mankowski (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 QtGraphicalEffects 1.0 import GCompris 1.0 import "../../core" import "clockgame.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: { } pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/menu/resource/background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop anchors.fill: parent signal start signal stop Component.onCompleted: { dialogActivityConfig.initialize() activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property alias background: background property alias bar: bar property alias bonus: bonus property int targetH: 12 property int targetM: 0 property int targetS: 0 property int currentH: 1 property int currentM: 25 property int currentS: 43 property int numberOfTry: 3 property int currentTry: 0 - property var levels: activity.datasetLoader.item.data + property var levels: activity.datasetLoader.data property bool minutesHandVisible property bool secondsHandVisible property bool zonesVisible } onStart: { Activity.start(items) } onStop: { Activity.stop() } Score { anchors { bottom: bar.top bottomMargin: 10 * ApplicationInfo.ratio right: bar.right rightMargin: 10 * ApplicationInfo.ratio left: parent.left leftMargin: 10 * ApplicationInfo.ratio top: undefined } numberOfSubLevels: items.numberOfTry currentSubLevel: items.currentTry + 1 } /* Target text */ Rectangle { id: questionItemBackground color: "#c0ceb339" border.width: 0 radius: 10 z: 10 anchors { horizontalCenter: parent.horizontalCenter top: parent.top margins: 10 } height: questionItem.height + anchors.margins * 2 width: questionItem.width + anchors.margins * 2 Behavior on height { PropertyAnimation { duration: 100 } } GCText { id: questionItem text: qsTr("Set the watch to:") + " " + //~ singular %n hour //~ plural %n hours addNbsp(qsTr("%n hour(s)", "", items.targetH)) + " " + //~ singular %n minute //~ plural %n minutes addNbsp(qsTr("%n minute(s)", "", items.targetM)) + //~ singular %n second //~ plural %n seconds (s.visible ? " " + addNbsp(qsTr("%n second(s)", "", items.targetS)) : "") fontSize: mediumSize textFormat: Text.RichText font.weight: Font.DemiBold color: "white" horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap width: background.width anchors { horizontalCenter: parent.horizontalCenter top: parent.top margins: 10 } // We don't want the wrapping to happen anywhere, set no break space function addNbsp(str) { return str.replace(" ", " "); } } } /* The clock */ Image { id: clock source: activity.resourceUrl + "clock_bg.svg" anchors.centerIn: parent fillMode: Image.PreserveAspectFit sourceSize.height: parent.height property int radius: Math.min(background.width * 1.4, background.height) /* The yellow zones */ Image { id: zones source: activity.resourceUrl + "clock_zones.svg" anchors.centerIn: parent fillMode: Image.PreserveAspectFit sourceSize.height: parent.height * 0.7 visible: items.zonesVisible z: 2 } /* The setter */ Image { id: setter source: activity.resourceUrl + "clock_setter.svg" anchors { verticalCenter: parent.verticalCenter left: parent.right leftMargin: -10 } z: 1 } /* The minutes */ Repeater { model: 60 GCText { text: index + 1 font { pointSize: NaN // need to clear font.pointSize explicitly pixelSize: Math.max( (index + 1) % 5 === 0 ? clock.radius / 40 : clock.radius / 45, 1) bold: items.currentM === ((index + 1) % 60) || (items.currentS === ((index + 1) % 60) && s.visible) underline: items.currentM === ((index + 1) % 60) || (items.currentS === ((index + 1) % 60) && s.visible) } anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: -clock.radius * 0.33 * Math.cos( (index + 1) * 2 * Math.PI / 60) horizontalCenterOffset: clock.radius * 0.33 * Math.sin( (index + 1) * 2 * Math.PI / 60) } z: 4 color: "#d56a3a" visible: items.minutesHandVisible } } /* The seconds */ Repeater { model: 60 Rectangle { color: "#d56a3a" width: clock.radius * 0.02 height: 2 rotation: 90 + (index + 1) * 360 / 60 radius: 1 anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: -clock.radius * 0.3 * Math.cos( (index + 1) * 2 * Math.PI / 60) horizontalCenterOffset: clock.radius * 0.3 * Math.sin( (index + 1) * 2 * Math.PI / 60) } z: 4 visible: items.secondsHandVisible } } /* The hours */ Repeater { model: 12 GCText { text: index + 1 font { pointSize: NaN // need to clear font.pointSize explicitly pixelSize: Math.max(clock.radius / 30, 1) bold: items.currentH === ((index + 1) % 12) underline: items.currentH === ((index + 1) % 12) } anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: -clock.radius * 0.26 * Math.cos( (index + 1) * 2 * Math.PI / 12) horizontalCenterOffset: clock.radius * 0.26 * Math.sin( (index + 1) * 2 * Math.PI / 12) } z: 4 color: "#3a81d5" visible: items.bar.level < 7 } } Repeater { model: 12 Rectangle { color: "#3a81d5" width: clock.radius * 0.03 height: 4 rotation: 90 + (index + 1) * 360 / 12 radius: 1 anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: -clock.radius * 0.3 * Math.cos( (index + 1) * 2 * Math.PI / 12) horizontalCenterOffset: clock.radius * 0.3 * Math.sin( (index + 1) * 2 * Math.PI / 12) } z: 4 visible: items.bar.level < 9 || (items.bar.level === 9 && ((index + 1) % 3) === 0) } } /* Help text */ GCText { id: helper text: Activity.get2CharValue( items.currentH) + ":" + Activity.get2CharValue( items.currentM) + ":" + Activity.get2CharValue( items.currentS) font.pointSize: NaN font.pixelSize: Math.max(clock.radius / 30, 1) anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: clock.radius * 0.2 } z: 4 color: "#2a2a2a" visible: false } /* Arrow H */ Rectangle { id: h property alias angle: roth.angle height: clock.radius * 0.2 width: height / 10 radius: width / 2 color: "#3a81d5" transform: Rotation { id: roth origin.x: h.width / 2 origin.y: 0 angle: (180 + 360 * (items.currentH / 12 + items.currentM / 60 / 12)) % 360 Behavior on angle { RotationAnimation { duration: 100 direction: RotationAnimation.Shortest } } } anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: h.height / 2 } z: 5 } /* Arrow M */ Rectangle { id: m property alias angle: rotm.angle height: clock.radius * 0.28 width: height / 20 radius: width / 2 color: "#d56a3a" visible: items.minutesHandVisible transform: Rotation { id: rotm origin.x: m.width / 2 origin.y: 0 angle: (180 + 360 * (items.currentM / 60 + items.currentS / 60 / 60)) % 360 Behavior on angle { RotationAnimation { duration: 100 direction: RotationAnimation.Shortest } } } anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: m.height / 2 } z: 6 } /* Arrow S */ Rectangle { id: s property alias angle: rots.angle height: clock.radius * 0.32 width: height / 30 radius: width / 2 color: "#2ccf4b" visible: items.secondsHandVisible transform: Rotation { id: rots origin.x: s.width / 2 origin.y: 0 angle: (180 + 360 * items.currentS / 60) % 360 Behavior on angle { RotationAnimation { duration: 100 direction: RotationAnimation.Shortest } } } anchors { verticalCenter: clock.verticalCenter horizontalCenter: clock.horizontalCenter verticalCenterOffset: s.height / 2 } z: 7 } /* Center */ Rectangle { id: center color: "#2a2a2a" height: clock.radius / 25 width: height radius: width / 2 anchors.centerIn: clock z: 8 } /* Manage the move */ MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton onPressed: { /* Find the closer Arrow */ var a = (270 + 360 + 180 * Math.atan2( mouseY - (center.y + center.height / 2), mouseX - (center.x + center.width / 2)) / Math.PI) % 360 var agnh = h.angle var angm = m.angle var angs = s.angle var dh = Math.min(Math.abs(a - agnh), Math.abs(a - agnh - 360), Math.abs(a - agnh + 360)) var dm = m.visible ? Math.min(Math.abs(a - angm), Math.abs(a - angm - 360), Math.abs(a - angm + 360)) : 9999 var ds = s.visible ? Math.min( Math.abs(a - angs), Math.abs(a - angs - 360), Math.abs(a - angs + 360)) : 9999 var dmin = Math.min(dh, dm, ds) if (dh === dmin) { Activity.selectedArrow = h } else if (dm === dmin) { Activity.selectedArrow = m } else { Activity.selectedArrow = s } } onReleased: { Activity.selectedArrow = null //todo replace this with Ok button if (items.currentH === items.targetH && items.currentM === items.targetM && items.currentS === items.targetS) { items.bonus.good("gnu") } } onMouseXChanged: { /* Move */ if (Activity.selectedArrow !== null) { var a = (270 + 360 + 180 * Math.atan2( mouseY - (center.y + center.height / 2), mouseX - (center.x + center.width / 2)) / Math.PI) % 360 var previousM = items.currentM var previousS = items.currentS if (Activity.selectedArrow === h) { items.currentH = Math.round( 12 * ((a - 180) / 360 - items.currentM / 60 / 12) + 12) % 12 } else if (Activity.selectedArrow === m) { items.currentM = Math.round( 60 * ((a - 180) / 360 - items.currentS / 60 / 60) + 60) % 60 } else { items.currentS = Math.round( 60 * (a - 180) / 360 + 60) % 60 } if (previousS > 45 && items.currentS < 15) items.currentM = (items.currentM + 1 + 60) % 60 if (previousS < 15 && items.currentS > 45) items.currentM = (items.currentM - 1 + 60) % 60 if (previousM > 45 && items.currentM < 15) items.currentH = (items.currentH + 1 + 12) % 12 if (previousM < 15 && items.currentM > 45) items.currentH = (items.currentH - 1 + 12) % 12 } } } } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) + // restart activity on saving + background.start() } onClose: { home() } onStartActivity: { background.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | hint | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onHintClicked: { helper.visible = !helper.visible } onActivityConfigClicked: { displayDialog(dialogActivityConfig) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextTry) } } } diff --git a/src/activities/gletters/ActivityConfig.qml b/src/activities/gletters/ActivityConfig.qml index a3f1a4419..3ad895019 100644 --- a/src/activities/gletters/ActivityConfig.qml +++ b/src/activities/gletters/ActivityConfig.qml @@ -1,101 +1,106 @@ /* 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 + width: if(background) 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") { + // Recreate the binding + uppercaseBox.checked = Qt.binding(function(){return activityConfiguration.uppercaseOnly;}) + + var localeUtf8 = dataToSave.locale; + if(localeUtf8 !== "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < activityConfiguration.availableLangs.length ; i ++) { if(activityConfiguration.availableLangs[i].locale === localeUtf8) { activityConfiguration.localeBox.currentIndex = i; break; } } + activityConfiguration.locale = localeUtf8 + activityConfiguration.uppercaseOnly = (dataToSave.uppercaseMode === "true") } 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 17ca6e1b5..8e0b240a9 100644 --- a/src/activities/gletters/Gletters.qml +++ b/src/activities/gletters/Gletters.qml @@ -1,265 +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.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 var levels: activity.datasetLoader.data.length !== 0 ? activity.datasetLoader.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 = ""; } } } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onClose: { home() } onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) - home() + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) + // todo this is triggered before the change of locale, so it is not taken in account! background.stop() background.start() } onLoadData: { if (activity.activityName == "gletters") { if(activityData && activityData["locale"]) { background.locale = activityData["locale"]; activity.uppercaseOnly = activityData["uppercaseMode"] === "true" ? true : false; } } else if (activity.activityName == "smallnumbers2") { 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: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() 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/magic-hat-minus/MagicHat.qml b/src/activities/magic-hat-minus/MagicHat.qml index 6f98e95d8..1690795b2 100644 --- a/src/activities/magic-hat-minus/MagicHat.qml +++ b/src/activities/magic-hat-minus/MagicHat.qml @@ -1,263 +1,262 @@ /* GCompris - MagicHat.qml * * Copyright (C) 2014 Thibaut ROMAIN * * Authors: * (GTK+ version) * Thibaut ROMAIN (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 "magic-hat.js" as Activity import "." ActivityBase { id: activity onStart: focus = true onStop: {} property string mode: "minus" pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop property int starSize: Math.min(rightLayout.width / 12, background.height / 21) signal start signal stop property var starColors : ["1", "2", "3"] Component.onCompleted: { dialogActivityConfig.initialize() activity.start.connect(start) activity.stop.connect(stop) } onStart: Activity.start(items, mode) onStop: Activity.stop() property bool vert: background.width >= (background.height - okButton.height) // 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 var levels: activity.datasetLoader.item.data - property int maxValue: activity.datasetLoader.item.maxValue + property var levels: activity.datasetLoader.data property alias bonus: bonus property alias hat: theHat property alias introductionText: introText property var repeatersList: [repeaterFirstRow, repeaterSecondRow, repeaterAnswerRow] } Item { id: mainlayout anchors.left: background.left width: background.width * 0.4 height: background.height z: 11 Hat { id: theHat starsSize: background.starSize audioEffects: activity.audioEffects } GCText { id: introText anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 20 * ApplicationInfo.ratio } width: parent.width - 5 * ApplicationInfo.ratio fontSize: regularSize font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: TextEdit.WordWrap horizontalAlignment: TextEdit.AlignHCenter text: qsTr("Click on the hat to begin the game") } GCText { //: The math operation text: mode == "minus" ? qsTr("−") : qsTr("+") anchors.right: mainlayout.right anchors.rightMargin: 10 y: secondRow.y fontSize: 66 color: "white" style: Text.Outline styleColor: "black" } } Grid { id: rightLayout anchors { left: mainlayout.right right: background.vert ? okButton.left : background.right rightMargin: background.vert ? 0 : 10 verticalCenter: background.verticalCenter verticalCenterOffset: background.height/8 } height: background.height columns: 1 Column { id: firstRow height: background.starSize * 4 spacing: 5 z: 10 Repeater { id: repeaterFirstRow model: 3 StarsBar { barGroupIndex: 0 barIndex: index width: rightLayout.width backgroundColor: "grey" starsColor: starColors[index] theHat: items.hat starsSize: background.starSize opacity: 0 } } } Column { id: secondRow height: background.starSize * 4 spacing: 5 z: 9 Repeater { id: repeaterSecondRow model: 3 StarsBar { barGroupIndex: 1 barIndex: index width: rightLayout.width backgroundColor: "grey" starsColor: starColors[index] theHat: items.hat starsSize: background.starSize opacity: 0 } } } Rectangle { width: (background.starSize + 5) * 10 - 5 height: 5 * ApplicationInfo.ratio color: "white" } Rectangle { width: (background.starSize + 5) * 10 - 5 height: 10 * ApplicationInfo.ratio opacity: 0 } Column { id: answerRow height: background.starSize * 4 spacing: 5 Repeater { id: repeaterAnswerRow model: 3 StarsBar { barGroupIndex: 2 barIndex: index width: rightLayout.width backgroundColor: "#53b9c9" starsColor: starColors[index] authorizeClick: false theHat: items.hat starsSize: background.starSize opacity: 0 } } } } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) - home() + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) + background.start() } onClose: { home() } onStartActivity: { background.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onActivityConfigClicked: { displayDialog(dialogActivityConfig) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } BarButton { id: okButton anchors { bottom: bar.top right: parent.right rightMargin: 10 * ApplicationInfo.ratio bottomMargin: 10 * ApplicationInfo.ratio } source: "qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: 60 * ApplicationInfo.ratio onClicked: Activity.verifyAnswer() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/magic-hat-minus/magic-hat.js b/src/activities/magic-hat-minus/magic-hat.js index 144069e16..a671c4530 100644 --- a/src/activities/magic-hat-minus/magic-hat.js +++ b/src/activities/magic-hat-minus/magic-hat.js @@ -1,250 +1,251 @@ /* GCompris - Hat.qml * * Copyright (C) 2014 Thibaut ROMAIN * * Authors: * Thibaut ROMAIN * * 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.6 as Quick var url = "qrc:/gcompris/src/activities/magic-hat-minus/resource/" var currentLevel var numberOfLevel var numberOfUserStars var items; var mode; var magicHat var numberOfStars var nbStarsToAddOrRemove var nbStarsToCount var animationCount var questionCoefficients = [] var maxStarSlots = 30 var answerCoefficients = [] var coefficientsNeeded = false function start(items_, mode_) { items = items_ mode = mode_ magicHat = items.hat currentLevel = 0 numberOfLevel = items.levels.length initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 magicHat.state = "NormalPosition" numberOfStars = new Array(0, 0, 0) numberOfUserStars = new Array(0, 0, 0) nbStarsToAddOrRemove = new Array(0, 0, 0) nbStarsToCount = new Array(0, 0, 0) animationCount = 0 - + var maxValue = items.levels[currentLevel].maxValue + if(currentLevel > 0) { items.introductionText.visible = false } else { items.introductionText.visible = true } - coefficientsNeeded = (items.maxValue / maxStarSlots <= 1) ? false : true + coefficientsNeeded = (maxValue / maxStarSlots <= 1) ? false : true for(var j = 0; j < 3; j++) { items.repeatersList[0].itemAt(j).initStars() items.repeatersList[1].itemAt(j).initStars() items.repeatersList[2].itemAt(j).resetStars() } if(!coefficientsNeeded) { questionCoefficients[0] = questionCoefficients[1] = questionCoefficients[2] = 1; answerCoefficients[0] = answerCoefficients[1] = answerCoefficients[2] = 1; setCoefficientVisibility(false) } else { for(var i = 0; i < 3; i++) questionCoefficients[i] = Math.round(items.levels[currentLevel].maxStars[i] / 10); - answerCoefficients[0] = items.maxValue / 100; - answerCoefficients[1] = items.maxValue / 20; - answerCoefficients[2] = items.maxValue / 10; + answerCoefficients[0] = maxValue / 100; + answerCoefficients[1] = maxValue / 20; + answerCoefficients[2] = maxValue / 10; setCoefficientVisibility(true) setWantedColor("1") } var subtractor = (mode === "minus") ? 0 : 1 numberOfStars[0] = (items.levels[currentLevel].maxStars[0] > 0) ? getRandomInt(items.levels[currentLevel].minStars[0], (items.levels[currentLevel].maxStars[0] / questionCoefficients[0]) - subtractor) : 0 numberOfStars[1] = (items.levels[currentLevel].maxStars[1] > 0) ? getRandomInt(items.levels[currentLevel].minStars[1], (items.levels[currentLevel].maxStars[1] / questionCoefficients[1]) - subtractor) : 0 numberOfStars[2] = (items.levels[currentLevel].maxStars[2] > 0) ? getRandomInt(items.levels[currentLevel].minStars[2], (items.levels[currentLevel].maxStars[2] / questionCoefficients[2]) - subtractor) : 0 for(var i=0; i<3; i++) { items.repeatersList[0].itemAt(i).nbStarsOn = numberOfStars[i] items.repeatersList[0].itemAt(i).coefficient = questionCoefficients[i] items.repeatersList[1].itemAt(i).nbStarsOn = 0 items.repeatersList[1].itemAt(i).coefficient = questionCoefficients[i] items.repeatersList[2].itemAt(i).nbStarsOn = 0 items.repeatersList[2].itemAt(i).authorizeClick = false items.repeatersList[2].itemAt(i).coefficient = answerCoefficients[i] if(numberOfStars[i] > 0) { items.repeatersList[0].itemAt(i).opacity = 1 items.repeatersList[1].itemAt(i).opacity = 1 items.repeatersList[2].itemAt(i).opacity = 1 if(mode === "minus") nbStarsToAddOrRemove[i] = getRandomInt(1, numberOfStars[i]-1) else nbStarsToAddOrRemove[i] = getRandomInt(1, 10-numberOfStars[i]) } else { items.repeatersList[0].itemAt(i).opacity = 0 items.repeatersList[1].itemAt(i).opacity = 0 items.repeatersList[2].itemAt(i).opacity = 0 } } if(mode === "minus") { for(var i = 0; i < 3; i++) { nbStarsToCount[i] = numberOfStars[i] - nbStarsToAddOrRemove[i] items.repeatersList[1].itemAt(i).nbStarsOn = 0 } } else { for(var i = 0; i < 3; i++) { nbStarsToCount[i] = numberOfStars[i]+nbStarsToAddOrRemove[i] items.repeatersList[1].itemAt(i).nbStarsOn = nbStarsToAddOrRemove[i] } } } function setCoefficientVisibility(visibility) { for(var i = 0; i < 3; i++) { for(var j = 0; j < 3; j++) { items.repeatersList[j].itemAt(i).coefficientVisible = visibility } } } function setWantedColor(colorValue) { if(colorValue != null) { for(var i = 0; i < 3; i++) { for(var j = 0; j < 3; j++) { items.repeatersList[j].itemAt(i).starsColor = colorValue } } } } function userClickedAStar(barIndex,state) { if(state) numberOfUserStars[barIndex]++ else numberOfUserStars[barIndex]-- } function verifyAnswer() { - if(items.maxValue / maxStarSlots <= 1) { + if(items.levels[currentLevel].maxValue / maxStarSlots <= 1) { if(numberOfUserStars[0] === nbStarsToCount[0] && numberOfUserStars[1] === nbStarsToCount[1] && numberOfUserStars[2] === nbStarsToCount[2]) { items.bonus.good("flower") } else { items.bonus.bad("flower") } } else { var starsCalculatedByUser = numberOfUserStars[0] * answerCoefficients[0] + numberOfUserStars[1] * answerCoefficients[1] + numberOfUserStars[2] * answerCoefficients[2]; var actualNumberOfStars = nbStarsToCount[0] * questionCoefficients[0] + nbStarsToCount[1] * questionCoefficients[1] + nbStarsToCount[2] * questionCoefficients[2]; if(starsCalculatedByUser == actualNumberOfStars) items.bonus.good("flower") else items.bonus.bad("flower") } } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function moveStarsUnderHat() { if(currentLevel == 0) { items.introductionText.visible = false } for(var j = 0; j < 3; j++) { items.repeatersList[0].itemAt(j).moveStars() } } function moveBackMinusStars() { for(var j = 0; j < 3; j++) { items.repeatersList[0].itemAt(j). moveBackMinusStars(items.repeatersList[1].itemAt(j), nbStarsToAddOrRemove[j]) } } function movePlusStars() { for(var j = 0; j < 3; j++) { items.repeatersList[1].itemAt(j).moveStars() } } // Function called everytime the first animation ends function animation1Finished(barGroupIndex) { animationCount++ if(barGroupIndex == 0) { if(animationCount === numberOfStars[0] + numberOfStars[1] + numberOfStars[2]) { animationCount = 0 if(mode === "minus") moveBackMinusStars() else movePlusStars() } } else { animationCount = 0 userGuessNumberState() } } // Function called everytime the second animation ends function animation2Finished() { animationCount++ if(animationCount === nbStarsToAddOrRemove[0] + nbStarsToAddOrRemove[1] + nbStarsToAddOrRemove[2]) { animationCount = 0 userGuessNumberState() } } function userGuessNumberState() { for(var i = 0; i < 3; i++) { if(numberOfStars[i] + nbStarsToAddOrRemove[i]) items.repeatersList[2].itemAt(i).authorizeClick = true } magicHat.state = "GuessNumber" } function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } diff --git a/src/activities/magic-hat-minus/resource/1/Data.qml b/src/activities/magic-hat-minus/resource/1/Data.qml index 2f56a9c16..c35407038 100644 --- a/src/activities/magic-hat-minus/resource/1/Data.qml +++ b/src/activities/magic-hat-minus/resource/1/Data.qml @@ -1,43 +1,45 @@ /* 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("Learn to calculate remaining stars up to 5") difficulty: 1 - property int maxValue: 5 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [3, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [4, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - } - ] + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [3, 0, 0] + }, + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [4, 0, 0] + }, + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + } + ] } diff --git a/src/activities/magic-hat-minus/resource/2/Data.qml b/src/activities/magic-hat-minus/resource/2/Data.qml index c8f68417c..8b8371255 100644 --- a/src/activities/magic-hat-minus/resource/2/Data.qml +++ b/src/activities/magic-hat-minus/resource/2/Data.qml @@ -1,59 +1,65 @@ /* 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("Learn to calculate remaining stars up to 10") difficulty: 2 - property int maxValue: 10 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [3, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [6, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [7, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [8, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [9, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [10, 0, 0] - } - ] + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [3, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [6, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [7, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [8, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [9, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [10, 0, 0] + } + ] } diff --git a/src/activities/magic-hat-minus/resource/3/Data.qml b/src/activities/magic-hat-minus/resource/3/Data.qml index eec3acaac..785bea8af 100644 --- a/src/activities/magic-hat-minus/resource/3/Data.qml +++ b/src/activities/magic-hat-minus/resource/3/Data.qml @@ -1,55 +1,60 @@ /* 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("Learn to calculate remaining stars up to 30") difficulty: 3 - property int maxValue: 30 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [10, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [8, 8, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [10, 10, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [9, 9, 7] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [10, 10, 10] - } - ] + { + "maxValue": 30, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 0, 0], + "maxStars" : [10, 0, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 0], + "maxStars" : [8, 8, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 0], + "maxStars" : [10, 10, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 2], + "maxStars" : [9, 9, 7] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 2], + "maxStars" : [10, 10, 10] + } + ] } diff --git a/src/activities/magic-hat-minus/resource/4/Data.qml b/src/activities/magic-hat-minus/resource/4/Data.qml index 4e9a65fd1..569300d0a 100644 --- a/src/activities/magic-hat-minus/resource/4/Data.qml +++ b/src/activities/magic-hat-minus/resource/4/Data.qml @@ -1,51 +1,55 @@ /* 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("Learn to calculate remaining stars up to 100 with coefficients") difficulty: 4 - property int maxValue: 100 data: [ - { - "minStars" : [2, 2, 0], - "maxStars" : [20, 10, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [20, 20, 10] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [20, 20, 20] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [30, 30, 20] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [40, 40, 20] - } - ] + { + "maxValue": 100, + "minStars" : [2, 2, 0], + "maxStars" : [20, 10, 0] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [20, 20, 10] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [20, 20, 20] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [30, 30, 20] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [40, 40, 20] + } + ] } diff --git a/src/activities/magic-hat-minus/resource/5/Data.qml b/src/activities/magic-hat-minus/resource/5/Data.qml index cae43b448..8cd90e3e4 100644 --- a/src/activities/magic-hat-minus/resource/5/Data.qml +++ b/src/activities/magic-hat-minus/resource/5/Data.qml @@ -1,51 +1,53 @@ /* 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("Learn to calculate remaining stars up to 1000 with coefficients") difficulty: 5 - property int maxValue: 1000 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [100, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [100, 100, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [200, 200, 100] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [300, 300, 100] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [400, 400, 200] - } - ] + { + "maxValue": 1000, + "minStars" : [2, 0, 0], + "maxStars" : [100, 0, 0] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 0], + "maxStars" : [100, 100, 0] + }, + { + "minStars" : [2, 2, 2], + "maxStars" : [200, 200, 100] + }, + { + "minStars" : [2, 2, 2], + "maxStars" : [300, 300, 100] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 2], + "maxStars" : [400, 400, 200] + } + ] } diff --git a/src/activities/magic-hat-minus/resource/6/Data.qml b/src/activities/magic-hat-minus/resource/6/Data.qml index 9db84196a..1d72a8e72 100644 --- a/src/activities/magic-hat-minus/resource/6/Data.qml +++ b/src/activities/magic-hat-minus/resource/6/Data.qml @@ -1,51 +1,55 @@ /* 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("Learn to calculate remaining stars up to 10000 with coefficients") difficulty: 6 - property int maxValue: 10000 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [1000, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [1000, 1000, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [2000, 2000, 1000] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [3000, 3000, 1000] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [4000, 4000, 2000] - } + { + "maxValue": 10000, + "minStars" : [2, 0, 0], + "maxStars" : [1000, 0, 0] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 0], + "maxStars" : [1000, 1000, 0] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [2000, 2000, 1000] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [3000, 3000, 1000] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [4000, 4000, 2000] + } ] } diff --git a/src/activities/magic-hat-plus/resource/1/Data.qml b/src/activities/magic-hat-plus/resource/1/Data.qml index 535d044a1..930fabce2 100644 --- a/src/activities/magic-hat-plus/resource/1/Data.qml +++ b/src/activities/magic-hat-plus/resource/1/Data.qml @@ -1,43 +1,45 @@ /* 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("Learn to calculate total stars up to 5") - difficulty: 1 - property int maxValue: 5 - data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [3, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [4, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - } - ] + objective: qsTr("Learn to calculate total stars up to 5") + difficulty: 1 + data: [ + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [3, 0, 0] + }, + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [4, 0, 0] + }, + { + "maxValue": 5, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + } + ] } diff --git a/src/activities/magic-hat-plus/resource/2/Data.qml b/src/activities/magic-hat-plus/resource/2/Data.qml index a79fd64ce..d8d9ca275 100644 --- a/src/activities/magic-hat-plus/resource/2/Data.qml +++ b/src/activities/magic-hat-plus/resource/2/Data.qml @@ -1,59 +1,65 @@ /* 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("Learn to calculate total stars up to 10") - difficulty: 2 - property int maxValue: 10 - data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [3, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [4, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [6, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [7, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [8, 0, 0] - }, - { - "minStars" : [2, 0, 0], - "maxStars" : [9, 0, 0] - } - ] + objective: qsTr("Learn to calculate total stars up to 10") + difficulty: 2 + data: [ + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [3, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [4, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [6, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [7, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [8, 0, 0] + }, + { + "maxValue": 10, + "minStars" : [2, 0, 0], + "maxStars" : [9, 0, 0] + } + ] } diff --git a/src/activities/magic-hat-plus/resource/3/Data.qml b/src/activities/magic-hat-plus/resource/3/Data.qml index 76ca90633..b781966ff 100644 --- a/src/activities/magic-hat-plus/resource/3/Data.qml +++ b/src/activities/magic-hat-plus/resource/3/Data.qml @@ -1,55 +1,60 @@ /* 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("Learn to calculate total stars up to 30") - difficulty: 3 - property int maxValue: 30 - data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [5, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [7, 3, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [8, 8, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [8, 8, 4] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [9, 9, 7] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [9, 9, 9] - } - ] + objective: qsTr("Learn to calculate total stars up to 30") + difficulty: 3 + data: [ + { + "maxValue": 30, + "minStars" : [2, 0, 0], + "maxStars" : [5, 0, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 0], + "maxStars" : [7, 3, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 0], + "maxStars" : [8, 8, 0] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 2], + "maxStars" : [8, 8, 4] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 2], + "maxStars" : [9, 9, 7] + }, + { + "maxValue": 30, + "minStars" : [2, 2, 2], + "maxStars" : [9, 9, 9] + } + ] } diff --git a/src/activities/magic-hat-plus/resource/4/Data.qml b/src/activities/magic-hat-plus/resource/4/Data.qml index 0371a7e0f..dca93d207 100644 --- a/src/activities/magic-hat-plus/resource/4/Data.qml +++ b/src/activities/magic-hat-plus/resource/4/Data.qml @@ -1,51 +1,55 @@ /* 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("Learn to calculate total stars up to 100 with coefficients") difficulty: 4 - property int maxValue: 100 data: [ - { - "minStars" : [2, 2, 0], - "maxStars" : [20, 10, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [20, 20, 10] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [20, 20, 20] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [30, 30, 20] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [40, 40, 20] - } - ] + { + "maxValue": 100, + "minStars" : [2, 2, 0], + "maxStars" : [20, 10, 0] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [20, 20, 10] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [20, 20, 20] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [30, 30, 20] + }, + { + "maxValue": 100, + "minStars" : [2, 2, 2], + "maxStars" : [40, 40, 20] + } + ] } diff --git a/src/activities/magic-hat-plus/resource/5/Data.qml b/src/activities/magic-hat-plus/resource/5/Data.qml index 274de1216..20199f822 100644 --- a/src/activities/magic-hat-plus/resource/5/Data.qml +++ b/src/activities/magic-hat-plus/resource/5/Data.qml @@ -1,51 +1,55 @@ /* 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("Learn to calculate total stars up to 1000 with coefficients") difficulty: 5 - property int maxValue: 1000 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [100, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [100, 100, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [200, 200, 100] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [300, 300, 100] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [400, 400, 200] - } - ] + { + "maxValue": 1000, + "minStars" : [2, 0, 0], + "maxStars" : [100, 0, 0] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 0], + "maxStars" : [100, 100, 0] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 2], + "maxStars" : [200, 200, 100] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 2], + "maxStars" : [300, 300, 100] + }, + { + "maxValue": 1000, + "minStars" : [2, 2, 2], + "maxStars" : [400, 400, 200] + } + ] } diff --git a/src/activities/magic-hat-plus/resource/6/Data.qml b/src/activities/magic-hat-plus/resource/6/Data.qml index 642eab7f9..83be1ed12 100644 --- a/src/activities/magic-hat-plus/resource/6/Data.qml +++ b/src/activities/magic-hat-plus/resource/6/Data.qml @@ -1,51 +1,55 @@ /* 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("Learn to calculate total stars up to 10000 with coefficients") difficulty: 6 - property int maxValue: 10000 data: [ - { - "minStars" : [2, 0, 0], - "maxStars" : [1000, 0, 0] - }, - { - "minStars" : [2, 2, 0], - "maxStars" : [1000, 1000, 0] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [2000, 2000, 1000] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [3000, 3000, 1000] - }, - { - "minStars" : [2, 2, 2], - "maxStars" : [4000, 4000, 2000] - } - ] + { + "maxValue": 10000, + "minStars" : [2, 0, 0], + "maxStars" : [1000, 0, 0] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 0], + "maxStars" : [1000, 1000, 0] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [2000, 2000, 1000] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [3000, 3000, 1000] + }, + { + "maxValue": 10000, + "minStars" : [2, 2, 2], + "maxStars" : [4000, 4000, 2000] + } + ] } diff --git a/src/activities/menu/Menu.qml b/src/activities/menu/Menu.qml index 3f1d64b26..a26557ca0 100644 --- a/src/activities/menu/Menu.qml +++ b/src/activities/menu/Menu.qml @@ -1,915 +1,915 @@ /* GCompris - Menu.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (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 "../../core" import GCompris 1.0 import QtGraphicalEffects 1.0 import "qrc:/gcompris/src/core/core.js" as Core import QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 /** * GCompris' top level menu screen. * * Displays a grid of available activities divided subdivided in activity * categories/sections. * * The visibility of the section row is toggled by the setting * ApplicationSettings.sectionVisible. * * The list of available activities depends on the following settings: * * * ApplicationSettings.showLockedActivities * * ApplicationSettings.filterLevelMin * * ApplicationSettings.filterLevelMax * * @inherit QtQuick.Item */ ActivityBase { id: activity focus: true activityInfo: ActivityInfoTree.rootMenu onBack: { pageView.pop(to); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } onHome: { if(pageView.depth === 1 && !ApplicationSettings.isKioskMode) { Core.quit(main); } else { pageView.pop(); // Restore focus that has been taken by the loaded activity if(pageView.currentItem == activity) focus = true; } } onDisplayDialog: pageView.push(dialog) onDisplayDialogs: { var toPush = new Array(); for (var i = 0; i < dialogs.length; i++) { toPush.push({item: dialogs[i]}); } pageView.push(toPush); } Connections { // At the launch of the application, box2d check is performed after we // first initialize the menu. This connection is to refresh // automatically the menu at start. target: ApplicationInfo onIsBox2DInstalledChanged: { ActivityInfoTree.filterByTag(activity.currentTag, currentCategory) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } // @cond INTERNAL_DOCS property string url: "qrc:/gcompris/src/activities/menu/resource/" property var sections: [ { icon: activity.url + "all.svg", tag: "favorite" }, { icon: activity.url + "computer.svg", tag: "computer" }, { icon: activity.url + "discovery.svg", tag: "discovery", categories: [{ "logic": qsTr("Logic") }, { "arts": qsTr("Fine Arts") }, { "music": qsTr("Music") } ] }, { icon: activity.url + "sciences.svg", tag: "sciences", categories: [{ "experiment": qsTr("Experiment") }, { "history": qsTr("History") }, { "geography": qsTr("Geography") } ] }, { icon: activity.url + "fun.svg", tag: "fun" }, { icon: activity.url + "math.svg", tag: "math", categories: [{ "numeration": qsTr("Numeration") }, { "arithmetic": qsTr("Arithmetic") }, { "measures": qsTr("Measures") } ] }, { icon: activity.url + "puzzle.svg", tag: "puzzle" }, { icon: activity.url + "reading.svg", tag: "reading", categories: [{ "letters": qsTr("Letters") }, { "words": qsTr("Words") }, { "vocabulary": qsTr("Vocabulary") } ] }, { icon: activity.url + "strategy.svg", tag: "strategy" }, { icon: activity.url + "search-icon.svg", tag: "search" } ] property string currentTag: sections[0].tag property var currentTagCategories: [] property string currentCategory: "" /// @endcond property string clickMode: "play" pageComponent: Image { id: background source: activity.url + "background.svg" sourceSize.width: Math.max(parent.width, parent.height) height: main.height fillMode: Image.PreserveAspectCrop Timer { // triggered once at startup to populate the keyboard id: keyboardFiller interval: 1000; running: true; onTriggered: { keyboard.populate(); } } function loadActivity() { // @TODO init of item would be better in setsource but it crashes on Qt5.6 // https://bugreports.qt.io/browse/QTBUG-49793 activityLoader.item.audioVoices = audioVoices activityLoader.item.audioEffects = audioEffects activityLoader.item.loading = loading //take the focus away from textField before starting an activity searchTextField.focus = false pageView.push(activityLoader.item) } Loader { id: activityLoader asynchronous: true onStatusChanged: { if (status == Loader.Loading) { loading.start(); } else if (status == Loader.Ready) { loading.stop(); loadActivity(); } else if (status == Loader.Error) loading.stop(); } } // Filters property bool horizontal: main.width >= main.height property int sectionIconWidth: { if(horizontal) return Math.min(100 * ApplicationInfo.ratio, main.width / (sections.length + 1)) else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) return Math.min(100 * ApplicationInfo.ratio, (background.height - (bar.height+keyboard.height)) / (sections.length + 1)) else return Math.min(100 * ApplicationInfo.ratio, (background.height - bar.height) / (sections.length + 1)) } property int sectionIconHeight: sectionIconWidth property int sectionCellWidth: sectionIconWidth * 1.1 property int sectionCellHeight: sectionIconHeight * 1.1 property var currentActiveGrid: activitiesGrid property bool keyboardMode: false Keys.onPressed: { // Ctrl-modifiers should never be handled by the search-field if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_S) { // Ctrl+S toggle show / hide section ApplicationSettings.sectionVisible = !ApplicationSettings.sectionVisible } } else if(currentTag === "search") { // forward to the virtual keyboard the pressed keys if(event.key == Qt.Key_Backspace) keyboard.keypress(keyboard.backspace); else keyboard.keypress(event.text); } else if(event.key === Qt.Key_Space && currentActiveGrid.currentItem) { currentActiveGrid.currentItem.selectCurrentItem() } } Keys.onReleased: { keyboardMode = true event.accepted = false } Keys.onTabPressed: { if(currentActiveGrid == section) { if(currentTagCategories && currentTagCategories.length != 0) { currentActiveGrid = categoriesGrid; } else { currentActiveGrid = activitiesGrid; } } else if(currentActiveGrid == categoriesGrid) { currentActiveGrid = activitiesGrid; } else { currentActiveGrid = section; } } Keys.onEnterPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onReturnPressed: if(currentActiveGrid.currentItem) currentActiveGrid.currentItem.selectCurrentItem(); Keys.onRightPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexRight(); Keys.onLeftPressed: if(currentActiveGrid.currentItem) currentActiveGrid.moveCurrentIndexLeft(); Keys.onDownPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYEnd) currentActiveGrid.moveCurrentIndexDown(); Keys.onUpPressed: if(currentActiveGrid.currentItem && !currentActiveGrid.atYBeginning) currentActiveGrid.moveCurrentIndexUp(); GridView { id: section model: sections width: horizontal ? main.width : sectionCellWidth height: { if(horizontal) return sectionCellHeight else if(activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) return sectionCellHeight * (sections.length+1) else return main.height - bar.height } x: ApplicationSettings.sectionVisible ? section.initialX : -sectionCellWidth y: ApplicationSettings.sectionVisible ? section.initialY : -sectionCellHeight visible: ApplicationSettings.sectionVisible cellWidth: sectionCellWidth cellHeight: sectionCellHeight interactive: false keyNavigationWraps: true property int initialX: 4 property int initialY: 4 Component { id: sectionDelegate Item { id: backgroundSection width: sectionCellWidth height: sectionCellHeight Image { source: modelData.icon sourceSize.height: sectionIconHeight anchors.margins: 5 anchors.horizontalCenter: parent.horizontalCenter } ParticleSystemStarLoader { id: particles anchors.fill: backgroundSection clip: false } MouseArea { anchors.fill: backgroundSection onClicked: { selectCurrentItem() } } function selectCurrentItem() { section.currentIndex = index activity.currentTag = modelData.tag activity.currentTagCategories = modelData.categories if(modelData.categories != undefined) { currentCategory = Object.keys(modelData.categories[0])[0]; } else { currentCategory = "" } particles.burst(10) if(modelData.tag === "search") { ActivityInfoTree.filterBySearch(searchTextField.text); } else { ActivityInfoTree.filterByTag(modelData.tag, currentCategory) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } } } delegate: sectionDelegate highlight: Item { width: sectionCellWidth height: sectionCellHeight Rectangle { anchors.fill: parent color: "#5AFFFFFF" } Image { source: "qrc:/gcompris/src/core/resource/button.svg" anchors.fill: parent } Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } // Activities property int iconWidth: 120 * ApplicationInfo.ratio property int activityCellWidth: horizontal ? background.width / Math.floor(background.width / iconWidth) : (background.width - section.width) / Math.floor((background.width - section.width) / iconWidth) property int activityCellHeight: iconWidth * 1.7 Loader { id: warningOverlay anchors { top: horizontal ? section.bottom : parent.top bottom: parent.bottom left: horizontal ? parent.left : section.right right: parent.right margins: 4 } active: (ActivityInfoTree.menuTree.length === 0) && (currentTag === "favorite") sourceComponent: Item { anchors.fill: parent GCText { id: instructionTxt fontSize: smallSize y: height * 0.2 x: (parent.width - width) / 2 z: 2 width: parent.width * 0.6 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap font.weight: Font.DemiBold color: 'white' text: qsTr("Put your favorite activities here by selecting the " + "sun at the top right of that activity.") } Rectangle { anchors.fill: instructionTxt anchors.margins: -6 z: 1 opacity: 0.5 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" } } } } } GridView { id: categoriesGrid model: currentTagCategories anchors.top: horizontal ? section.bottom : parent.top interactive: false keyNavigationWraps: true width: horizontal ? main.width : main.width - section.width visible: activity.currentTag !== "search" x: { if(currentTagCategories) { if(horizontal) { return categoriesGrid.width / (4 * (currentTagCategories.length+1)) } else { return categoriesGrid.width / (4 * (currentTagCategories.length+1)) + section.width } } else { return 0 } } cellWidth: currentTagCategories ? categoriesGrid.width / currentTagCategories.length : 0 cellHeight: height height: searchTextField.height delegate: Button { id: button style: GCButtonStyle { selected: currentCategory === button.category } width: categoriesGrid.width / (currentTagCategories.length + 1) height: categoriesGrid.cellHeight text: Object.values(modelData)[0] property string category: Object.keys(modelData)[0] onClicked: { selectCurrentItem() } function selectCurrentItem() { categoriesGrid.currentIndex = index currentCategory = Object.keys(modelData)[0] ActivityInfoTree.filterByTag(currentTag, currentCategory) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } } highlight: Rectangle { width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing color: "#AAFFFFFF" border.width: 3 border.color: "black" visible: background.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } } GridView { id: activitiesGrid anchors { top: { if(searchBar.visible) return searchBar.bottom else return categoriesGrid.bottom } bottom: bar.top left: horizontal ? parent.left : section.right margins: 4 } width: background.width cellWidth: activityCellWidth cellHeight: activityCellHeight clip: true model: ActivityInfoTree.menuTree keyNavigationWraps: true property int spacing: 10 delegate: Item { id: delegateItem width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing enabled: clickMode === "play" || dialogChooseLevel.hasConfigOrDataset Rectangle { id: activityBackground width: parent.width height: parent.height anchors.horizontalCenter: parent.horizontalCenter color: "white" opacity: 0.5 } Image { source: "qrc:/gcompris/src/activities/" + icon; anchors.top: activityBackground.top anchors.horizontalCenter: parent.horizontalCenter width: iconWidth - activitiesGrid.spacing height: width sourceSize.width: width fillMode: Image.PreserveAspectFit anchors.margins: 5 opacity: delegateItem.enabled ? 1 : 0.5 Image { source: "qrc:/gcompris/src/core/resource/difficulty" + ActivityInfoTree.menuTree[index].difficulty + ".svg"; anchors.top: parent.top sourceSize.width: iconWidth * 0.15 x: 5 } Image { anchors { horizontalCenter: parent.horizontalCenter top: parent.top rightMargin: 4 } source: demo || !ApplicationSettings.isDemoMode ? "" : activity.url + "lock.svg" sourceSize.width: 30 * ApplicationInfo.ratio } Image { anchors { left: parent.left bottom: parent.bottom } source: ActivityInfoTree.menuTree[index].createdInVersion == ApplicationInfo.GCVersionCode ? activity.url + "new.svg" : "" sourceSize.width: 30 * ApplicationInfo.ratio } GCText { id: title anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].title } // If we have enough room at the bottom display the description GCText { id: description visible: delegateItem.height - (title.y + title.height) > description.height ? 1 : 0 anchors.top: title.bottom anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter width: activityBackground.width fontSizeMode: Text.Fit minimumPointSize: 7 fontSize: regularSize elide: Text.ElideRight maximumLineCount: 3 wrapMode: Text.WordWrap text: ActivityInfoTree.menuTree[index].description } } ParticleSystemStarLoader { id: particles anchors.fill: activityBackground } MouseArea { anchors.fill: activityBackground onClicked: selectCurrentItem() } Image { source: activity.url + (favorite ? "all.svg" : "all_disabled.svg"); anchors { top: parent.top right: parent.right rightMargin: 4 * ApplicationInfo.ratio } sourceSize.width: iconWidth * 0.25 visible: ApplicationSettings.sectionVisible MouseArea { anchors.fill: parent onClicked: favorite = !favorite } } DialogChooseLevel { id: dialogChooseLevel displayDatasetAtStart: hasDataset currentActivity: ActivityInfoTree.menuTree[index] inMenu: true onClose: { home() } onSaveData: { - currentLevel = dialogChooseLevel.chosenLevel - ApplicationSettings.setCurrentLevel(name, currentLevel) + currentLevels = dialogChooseLevel.chosenLevels + ApplicationSettings.setCurrentLevels(name, currentLevels) } onStartActivity: { clickMode = "play" // immediately pop the Dialog to load the activity // if we don't do it immediately the page is busy // and it does not load the activity pageView.pop({immediate: true}) selectCurrentItem() } } function selectCurrentItem() { if(pageView.busy || !delegateItem.enabled) return if(clickMode == "play") { particles.burst(50) ActivityInfoTree.currentActivity = ActivityInfoTree.menuTree[index] activityLoader.setSource("qrc:/gcompris/src/activities/" + ActivityInfoTree.currentActivity.name, { 'menu': activity, 'activityInfo': ActivityInfoTree.currentActivity, - 'levelFolder': currentLevel + 'levelFolder': currentLevels }) if (activityLoader.status == Loader.Ready) loadActivity() } else { displayDialog(dialogChooseLevel); } } } highlight: Rectangle { width: activityCellWidth - activitiesGrid.spacing height: activityCellHeight - activitiesGrid.spacing color: "#AAFFFFFF" border.width: 3 border.color: "black" visible: background.keyboardMode Behavior on x { SpringAnimation { spring: 2; damping: 0.2 } } Behavior on y { SpringAnimation { spring: 2; damping: 0.2 } } } Rectangle { id: activitiesMask visible: false anchors.fill: activitiesGrid gradient: Gradient { GradientStop { position: 0.0; color: "#FFFFFFFF" } GradientStop { position: 0.92; color: "#FFFFFFFF" } GradientStop { position: 0.96; color: "#00FFFFFF"} } } layer.enabled: ApplicationInfo.useOpenGL layer.effect: OpacityMask { id: activitiesOpacity source: activitiesGrid maskSource: activitiesMask anchors.fill: activitiesGrid } } // The scroll buttons GCButtonScroll { visible: !ApplicationInfo.useOpenGL anchors.right: parent.right anchors.rightMargin: 5 * ApplicationInfo.ratio anchors.bottom: activitiesGrid.bottom anchors.bottomMargin: 30 * ApplicationInfo.ratio onUp: activitiesGrid.flick(0, 1127) onDown: activitiesGrid.flick(0, -1127) upVisible: activitiesGrid.visibleArea.yPosition <= 0 ? false : true downVisible: activitiesGrid.visibleArea.yPosition >= 1 ? false : true } Rectangle { id: categories width: horizontal ? parent.width : parent.width - (section.width+10) height: searchTextField.height visible: sections[activity.currentTag] === "search" anchors { top: horizontal ? section.bottom : categoriesGrid.top left: horizontal ? undefined : section.right } } Rectangle { id: searchBar width: horizontal ? parent.width/2 : parent.width - (section.width+10) height: searchTextField.height visible: activity.currentTag === "search" anchors { top: horizontal ? section.bottom : parent.top left: horizontal ? undefined : section.right } anchors.topMargin: horizontal ? 0 : 4 anchors.bottomMargin: horizontal ? 0 : 4 anchors.horizontalCenter: horizontal ? parent.horizontalCenter : undefined opacity: 0.5 radius: 10 border.width: 2 border.color: "black" gradient: Gradient { GradientStop { position: 0.3; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } Connections { // On mobile with GCompris' virtual keyboard activated: // Force invisibility of Androids virtual keyboard: target: (ApplicationInfo.isMobile && activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard) ? Qt.inputMethod : null onVisibleChanged: { if (ApplicationSettings.isVirtualKeyboard && visible) Qt.inputMethod.hide(); } onAnimatingChanged: { // note: seems to be never fired! if (ApplicationSettings.isVirtualKeyboard && Qt.inputMethod.visible) Qt.inputMethod.hide(); } } Connections { target: activity onCurrentTagChanged: { if (activity.currentTag === 'search') { searchTextField.focus = true; } else activity.focus = true; } } TextField { id: searchTextField width: parent.width visible: activity.currentTag === "search" textColor: "black" font.pointSize: 16 font.bold: true horizontalAlignment: TextInput.AlignHCenter verticalAlignment: TextInput.AlignVCenter font.family: GCSingletonFontLoader.fontLoader.name inputMethodHints: Qt.ImhNoPredictiveText // Note: we give focus to the textfield also in case // isMobile && !ApplicationSettings.isVirtualKeyboard // in conjunction with auto-hiding the inputMethod to always get // an input-cursor: activeFocusOnPress: true //ApplicationInfo.isMobile ? !ApplicationSettings.isVirtualKeyboard : true Keys.onReturnPressed: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } onEditingFinished: { if (ApplicationInfo.isMobile && !ApplicationSettings.isVirtualKeyboard) Qt.inputMethod.hide(); activity.focus = true; } style: TextFieldStyle { placeholderTextColor: "black" } placeholderText: qsTr("Search specific activities") onTextChanged: ActivityInfoTree.filterBySearch(searchTextField.text); } } Rectangle { id: activityConfigTextBar width: horizontal ? parent.width/2 : parent.width - (section.width+10) height: activitySettingsLabel.height visible: clickMode === "activityConfig" anchors { bottom: bar.top bottomMargin: height * 0.3 left: horizontal ? undefined : section.right } anchors.horizontalCenter: horizontal ? parent.horizontalCenter : undefined radius: 10 border.width: height * 0.05 border.color: "#8b66b2" color: "#eeeeee" GCText { id: activitySettingsLabel text: qsTr("Activity Settings") visible: parent.visible width: parent.width height: paintedHeight anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter color: "#232323" } } VirtualKeyboard { id: keyboard readonly property var letter: ActivityInfoTree.characters width: parent.width visible: activity.currentTag === "search" && ApplicationSettings.isVirtualKeyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter onKeypress: { if(text == keyboard.backspace) { searchTextField.text = searchTextField.text.slice(0, -1); } else if(text == keyboard.space) { searchTextField.text = searchTextField.text.concat(" "); } else { searchTextField.text = searchTextField.text.concat(text); } } function populate() { var tmplayout = []; var row = 0; var offset = 0; var cols; while(offset < letter.length-1) { if(letter.length <= 100) { cols = Math.ceil((letter.length-offset) / (3 - row)); } else { cols = background.horizontal ? (Math.ceil((letter.length-offset) / (15 - row))) :(Math.ceil((letter.length-offset) / (22 - row))) if(row == 0) { tmplayout[row] = new Array(); tmplayout[row].push({ label: keyboard.backspace }); tmplayout[row].push({ label: keyboard.space }); row ++; } } tmplayout[row] = new Array(); for (var j = 0; j < cols; j++) tmplayout[row][j] = { label: letter[j+offset] }; offset += j; row ++; } if(letter.length <= 100) { tmplayout[0].push({ label: keyboard.space }); tmplayout[row-1].push({ label: keyboard.backspace }); } keyboard.layout = tmplayout } } Bar { id: bar // No exit button on mobile, UI Guidelines prohibits it content: BarEnumContent { value: help | config | activityConfig | about | (ApplicationInfo.isMobile ? 0 : exit) } anchors.bottom: keyboard.visible ? keyboard.top : parent.bottom onAboutClicked: { searchTextField.focus = false displayDialog(dialogAbout) } onHelpClicked: { searchTextField.focus = false displayDialog(dialogHelp) } onActivityConfigClicked: { if(clickMode == "play") { clickMode = "activityConfig" } else { clickMode = "play" } } onConfigClicked: { searchTextField.focus = false dialogActivityConfig.active = true dialogActivityConfig.loader.item.loadFromConfig() displayDialog(dialogActivityConfig) } } DialogAbout { id: dialogAbout onClose: home() } DialogHelp { id: dialogHelp onClose: home() activityInfo: ActivityInfoTree.rootMenu } DialogActivityConfig { id: dialogActivityConfig currentActivity: activity content: Component { ConfigurationItem { id: configItem width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio } } onSaveData: { dialogActivityConfig.configItem.save(); } onClose: { if(activity.currentTag != "search") { ActivityInfoTree.filterByTag(activity.currentTag, currentCategory) ActivityInfoTree.filterLockedActivities() ActivityInfoTree.filterEnabledActivities() } else ActivityInfoTree.filterBySearch(searchTextField.text); home() } } } } diff --git a/src/activities/money/MoneyCore.qml b/src/activities/money/MoneyCore.qml index 9b714926c..0d4e57563 100644 --- a/src/activities/money/MoneyCore.qml +++ b/src/activities/money/MoneyCore.qml @@ -1,298 +1,297 @@ /* GCompris - MoneyCore.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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 "money.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} property var dataset pageComponent: Image { id: background anchors.fill: parent source: Activity.url + "/background.svg" sourceSize.width: parent.width signal start signal stop Component.onCompleted: { dialogActivityConfig.initialize() 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 GCSfx audioEffects: activity.audioEffects property alias answerModel: answerArea.pocketModel property alias pocketModel: pocketArea.pocketModel property alias store: store property alias instructions: instructions property alias tux: tux - property var levels: activity.datasetLoader.item.data + property var levels: activity.datasetLoader.data property alias tuxMoney: tuxMoney property alias bar: bar property alias bonus: bonus property int itemIndex property int pocketRows property bool verticalOrientation: background.height > background.width - bar.height property var selectedArea property alias pocket: pocketArea.answer property alias answer: answerArea.answer } onStart: { Activity.start(items, dataset) } onStop: { Activity.stop() } Column { id: columnLayout spacing: 10 x: parent.width * 0.05 y: parent.height * 0.05 width: parent.width * 0.9 property int nbColumns: 5 property int nbLines: (items.verticalOrientation) ? items.pocketRows + 1 : items.pocketRows property int itemWidth: Math.min(width / nbColumns - 10 - 10 / nbColumns, parent.height * 0.4 / nbLines - 10 - 10 / nbLines) property int itemHeight: itemWidth * 0.59 // === The Answer Area === MoneyArea { id: answerArea onTransaction: Activity.unpay(index) } // === The Store Area === property int nbStoreColumns: activity.dataset === "BACK_WITHOUT_CENTS" || activity.dataset === "BACK_WITH_CENTS" ? store.model.length + 1 : store.model.length //tempSpace is a workaround to replace instructionsArea.realHeight that is freezing with Qt-5.9.1 property int tempSpace: bar.level === 1 ? 140 + columnLayout.spacing : 0 property int itemStoreWidth: Math.min((columnLayout.width - storeAreaFlow.spacing * nbStoreColumns) / nbStoreColumns, (parent.height - answerArea.height - pocketArea.height - bar.height) * 0.8) - tempSpace property int itemStoreHeight: itemStoreWidth Rectangle { id: storeArea height: columnLayout.itemStoreHeight + 10 width: columnLayout.width color: "#55333333" border.color: "black" border.width: 2 radius: 5 Flow { id: storeAreaFlow anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 20 anchors.rightMargin: 20 anchors.fill: parent spacing: 40 add: Transition { NumberAnimation { properties: "x" from: parent.width * 0.05 duration: 300 } } Image { id: tux visible: activity.dataset === "BACK_WITHOUT_CENTS" || activity.dataset === "BACK_WITH_CENTS" source: Activity.url + "/tux.svg" sourceSize.height: columnLayout.itemStoreHeight sourceSize.width: columnLayout.itemStoreHeight Repeater { id: tuxMoney Image { source: modelData.img sourceSize.height: columnLayout.itemStoreHeight * 0.3 x: tux.x + index * 50 y: tux.y + tux.height / 2 + index * 20 } } } Repeater { id: store Image { source: modelData.img sourceSize.height: columnLayout.itemStoreHeight sourceSize.width: columnLayout.itemStoreHeight GCText { text: modelData.price height: parent.height width: parent.width fontSizeMode: Text.Fit font.weight: Font.DemiBold style: Text.Outline styleColor: "black" color: "white" anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: index % 2 == 0 ? 0 : parent.height - height } } } } } // == The instructions Area == Rectangle { id: instructionsArea height: instructions.height width: columnLayout.width color: "#55333333" border.color: "black" border.width: 2 radius: 5 anchors.topMargin: 4 anchors.bottomMargin: 4 anchors.leftMargin: 10 anchors.rightMargin: 10 visible: bar.level === 1 property int realHeight: bar.level === 1 ? height + columnLayout.spacing : 0 GCText { id: instructions horizontalAlignment: Text.AlignHCenter width: columnLayout.width height: columnLayout.height / 6 wrapMode: Text.WordWrap fontSizeMode: Text.Fit } } // === The Pocket Area === MoneyArea { id: pocketArea onTransaction: Activity.pay(index) } } Keys.onPressed: { if(event.key === Qt.Key_Tab) { if(items.selectedArea.count !== 0 && items.itemIndex !== -1) items.selectedArea.itemAt(items.itemIndex).selected = false if(items.selectedArea == items.pocket) { items.selectedArea = items.answer } else { items.selectedArea = items.pocket } items.itemIndex = 0 } if(items.selectedArea.count !== 0) { if(items.itemIndex >= 0) items.selectedArea.itemAt(items.itemIndex).selected = false if(event.key === Qt.Key_Right) { if(items.itemIndex != (items.selectedArea.count-1)) items.itemIndex++ else items.itemIndex = 0 } if(event.key === Qt.Key_Left) { if(items.itemIndex > 0) items.itemIndex-- else items.itemIndex = items.selectedArea.count-1 } if([Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return].indexOf(event.key) != -1 && items.itemIndex !== -1 ) { if(items.selectedArea == items.pocket) Activity.pay(items.itemIndex) else Activity.unpay(items.itemIndex) if(items.itemIndex > 0) items.itemIndex-- } } if(items.selectedArea.count !== 0 && items.itemIndex !== -1) items.selectedArea.itemAt(items.itemIndex).selected = true } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) - home() + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) background.stop() background.start() } onClose: { home() } onStartActivity: { background.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onActivityConfigClicked: { displayDialog(dialogActivityConfig) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextLevel) } } } diff --git a/src/activities/reversecount/Reversecount.qml b/src/activities/reversecount/Reversecount.qml index f8bd211f0..85b8e4a54 100644 --- a/src/activities/reversecount/Reversecount.qml +++ b/src/activities/reversecount/Reversecount.qml @@ -1,260 +1,262 @@ /* GCompris - ReverseCount.qml * * Copyright (C) 2014 Emmanuel Charruau * * Authors: * Bruno Coudoin (GTK+ version) * Emmanuel Charruau (Qt Quick port) * Bruno Coudoin (Major rework) * * 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 "reversecount.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ff00a4b0" signal start signal stop Component.onCompleted: { dialogActivityConfig.initialize() 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 GCSfx audioEffects: activity.audioEffects readonly property string resourceUrl: activity.resourceUrl - property var levels: activity.datasetLoader.item.data + property var levels: activity.datasetLoader.data property alias background: background property alias backgroundImg: backgroundImg property alias bar: bar property alias bonus: bonus property alias chooseDiceBar: chooseDiceBar property alias tux: tux property alias fishToReach: fishToReach property int clockPosition: 4 property string mode: "dot" } onStart: { Activity.start(items) } onStop: { Activity.stop() } Keys.onEnterPressed: Activity.moveTux() Keys.onReturnPressed: Activity.moveTux() onWidthChanged: { if(Activity.fishIndex > 0) { // set x fishToReach.x = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][0] * background.width / 5 + (background.width / 5 - tux.width) / 2 // set y fishToReach.y = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][1] * (background.height - background.height/5) / 5 + (background.height / 5 - tux.height) / 2 // Move Tux Activity.moveTuxToIceBlock() } } onHeightChanged: { if(Activity.fishIndex > 0) { // set x fishToReach.x = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][0] * background.width / 5 + (background.width / 5 - tux.width) / 2 // set y fishToReach.y = Activity.iceBlocksLayout[Activity.fishIndex % Activity.iceBlocksLayout.length][1] * (background.height - background.height/5) / 5 + (background.height / 5 - tux.height) / 2 // Move Tux Activity.moveTuxToIceBlock() } } Image { id: backgroundImg source: activity.resourceUrl + Activity.backgrounds[0] sourceSize.height: parent.height * 0.5 anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } // === The ice blocks === Repeater { model: Activity.iceBlocksLayout Image { x: modelData[0] * background.width / 5 y: modelData[1] * (background.height- background.height/5) / 5 width: background.width / 5 height: background.height / 5 source: activity.resourceUrl + "iceblock.svg" } } Tux { id: tux sourceSize.width: Math.min(background.width / 6, background.height / 6) z: 11 } Image { id: fishToReach sourceSize.width: Math.min(background.width / 6, background.height / 6) z: 10 property string nextSource property int nextX property int nextY function showParticles() { particles.burst(40) } ParticleSystemStarLoader { id: particles clip: false } onOpacityChanged: { if(opacity == 0) { source = ""; source = nextSource; } } onSourceChanged: { if(source != "") { x = nextX y = nextY opacity = 1 } } Behavior on opacity { NumberAnimation { duration: 500 } } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onActivityConfigClicked: { displayDialog(dialogActivityConfig) } } Image { id: clock anchors { right: parent.right bottom: parent.bottom margins: 10 } sourceSize.width: 66 * bar.barZoom property int remainingLife: items.clockPosition onRemainingLifeChanged: if(remainingLife >= 0) clockAnim.restart() SequentialAnimation { id: clockAnim alwaysRunToEnd: true ParallelAnimation { NumberAnimation { target: clock; properties: "opacity"; to: 0; duration: 800; easing.type: Easing.OutCubic } NumberAnimation { target: clock; properties: "rotation"; from: 0; to: 180; duration: 800; easing.type: Easing.OutCubic } } PropertyAction { target: clock; property: 'source'; value: activity.resourceUrl + "flower" + items.clockPosition + ".svg" } ParallelAnimation { NumberAnimation { target: clock; properties: "opacity"; to: 1; duration: 800; easing.type: Easing.OutCubic } NumberAnimation { target: clock; properties: "rotation"; from: 180; to: 0; duration: 800; easing.type: Easing.OutCubic } } } } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onClose: { home() } onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) + // restart activity on saving + background.start() } onLoadData: { if(activityData && activityData["mode"]) { items.mode = activityData["mode"]; } } onStartActivity: { background.start() } } ChooseDiceBar { id: chooseDiceBar mode: items.mode x: background.width / 5 + 20 y: (background.height - background.height/5) * 3 / 5 audioEffects: activity.audioEffects } Bonus { id: bonus winSound: "qrc:/gcompris/src/activities/ballcatch/resource/tuxok.wav" looseSound: "qrc:/gcompris/src/activities/ballcatch/resource/youcannot.wav" onWin: Activity.nextLevel() onLoose: Activity.initLevel() } } } diff --git a/src/activities/target/Target.qml b/src/activities/target/Target.qml index e4be14bc6..52c04aee6 100644 --- a/src/activities/target/Target.qml +++ b/src/activities/target/Target.qml @@ -1,266 +1,265 @@ /* GCompris - target.qml * * Copyright (C) 2014 Bruno coudoin * * Authors: * Bruno Coudoin (GTK+ version) * Bruno Coudoin (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 "target.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Item { id: background anchors.fill: parent signal start signal stop signal targetReached Keys.onPressed: { if(items.currentArrow != items.nbArrow) return if(event.key === Qt.Key_Backspace) { backspace() } appendText(event.text) } Component.onCompleted: { dialogActivityConfig.initialize() 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 targetModel: targetItem.model - property var levels: activity.datasetLoader.item.data + property var levels: activity.datasetLoader.data property alias targetItem: targetItem property alias userEntry: userEntry property int currentArrow property int nbArrow property int currentSubLevel property int numberOfSubLevel property bool arrowFlying onNbArrowChanged: { arrowRepeater.init(nbArrow) } } onStart: { keyboard.populate(); Activity.start(items) } onStop: { Activity.stop() } TargetItem { id: targetItem } onTargetReached: { items.arrowFlying = false if(items.currentArrow == items.nbArrow) { targetItem.stop() targetItem.scoreText += " = " userEntry.text = "?" } } Arrow { id: arrowRepeater } Image { id: cross anchors.centerIn: parent source: Activity.url + "cross.svg" opacity: items.currentArrow != items.nbArrow ? 1 : 0 sourceSize.width: 50 * ApplicationInfo.ratio } MouseArea { id: mouseArea anchors.fill: parent enabled: items.currentArrow != items.nbArrow && !items.arrowFlying onClicked: { activity.audioEffects.play(Activity.url + 'arrow.wav') items.arrowFlying = true if(items.currentArrow != items.nbArrow) { arrowRepeater.itemAt(items.currentArrow).opacity = 1 arrowRepeater.itemAt(items.currentArrow++).scale = 0.5 } } } GCText { id: scoreItem anchors.horizontalCenter: parent.horizontalCenter width: parent.width text: targetItem.scoreText fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } function backspace() { userEntry.text = userEntry.text.slice(0, -1) if(userEntry.text.length === 0) { userEntry.text = "?" } else { if(targetItem.scoreTotal === userEntry.text) bonus.good("flower") } } function appendText(text) { if(text === keyboard.backspace) { backspace() return } var number = parseInt(text) if(isNaN(number)) return if(userEntry.text === "?") { userEntry.text = "" } if(userEntry.text.length > ('' + targetItem.scoreTotal).length) { return } userEntry.text += text if(targetItem.scoreTotal.toString() === userEntry.text) bonus.good("flower") } GCText { id: userEntry anchors.top: scoreItem.bottom width: parent.width fontSize: 22 font.bold: true style: Text.Outline styleColor: "black" color: "white" wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter } VirtualKeyboard { id: keyboard anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter hide: items.currentArrow == items.nbArrow ? false : true function populate() { layout = [ [ { label: "0" }, { label: "1" }, { label: "2" }, { label: "3" }, { label: "4" }, { label: "5" }, { label: "6" }, { label: "7" }, { label: "8" }, { label: "9" }, { label: keyboard.backspace } ] ] } onKeypress: background.appendText(text) onError: console.log("VirtualKeyboard error: " + msg); } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) + levelFolder = dialogActivityConfig.chosenLevels + currentActivity.currentLevels = dialogActivityConfig.chosenLevels + ApplicationSettings.setCurrentLevels(currentActivity.name, dialogActivityConfig.chosenLevels) background.start() - home() } onClose: { home() } onStartActivity: { background.start() } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar anchors.bottom: keyboard.top content: BarEnumContent { value: help | home | level | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onActivityConfigClicked: { displayDialog(dialogActivityConfig) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Score { id: score anchors.right: parent.right anchors.top: parent.top anchors.bottom: undefined currentSubLevel: items.currentSubLevel + 1 numberOfSubLevels: items.numberOfSubLevel } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } } diff --git a/src/activities/traffic/Traffic.qml b/src/activities/traffic/Traffic.qml index 8dd04bec1..39678a927 100644 --- a/src/activities/traffic/Traffic.qml +++ b/src/activities/traffic/Traffic.qml @@ -1,158 +1,153 @@ /* GCompris - Traffic.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 "traffic.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Image { id: background source: "qrc:/gcompris/src/activities/traffic/resource/traffic_bg.svg" sourceSize.width: Math.max(parent.width, parent.height) fillMode: Image.PreserveAspectCrop signal start signal stop property string mode: "IMAGE" // allow to choose between "COLOR" and "IMAGE" // mode, candidate for a config dialog Component.onCompleted: { dialogActivityConfig.initialize() activity.start.connect(start) activity.stop.connect(stop) } QtObject { id: items property Item main: activity.main property GCSfx audioEffects: activity.audioEffects property alias background: background property alias bar: bar property alias bonus: bonus property alias score: score property alias jamBox: jamBox property alias jamGrid: jamGrid } onStart: { Activity.start(items, mode) } onStop: { Activity.stop() } Image { id: jamBox source: "qrc:/gcompris/src/activities/traffic/resource/traffic_box.svg" anchors.centerIn: parent sourceSize.width: Math.min(background.width * 0.85, background.height * 0.85) fillMode: Image.PreserveAspectFit property double scaleFactor: background.width / background.sourceSize.width Grid { id: jamGrid anchors.centerIn: parent width: parent.width - 86 * jamBox.scaleFactor * ApplicationInfo.ratio height: parent.height - 86 * jamBox.scaleFactor * ApplicationInfo.ratio columns: 6 rows: 6 spacing: 0 // Add an alias to mode so it can be used on Car items property alias mode: background.mode Repeater { id: gridRepeater model: jamGrid.columns * jamGrid.rows delegate: Rectangle { id: gridDelegate height: jamGrid.height/ jamGrid.rows width: height border.width: 1 border.color: "white" color: "#444444" } } } } DialogHelp { id: dialogHelp onClose: home() } DialogChooseLevel { id: dialogActivityConfig currentActivity: activity.activityInfo onClose: { home() } - onSaveData: { - levelFolder = dialogActivityConfig.chosenLevel - currentActivity.currentLevel = dialogActivityConfig.chosenLevel - ApplicationSettings.setCurrentLevel(currentActivity.name, dialogActivityConfig.chosenLevel) - } onLoadData: { if(activityData && activityData["mode"]) { background.mode = activityData["mode"]; } } } Bar { id: bar content: BarEnumContent { value: help | home | level | reload | activityConfig } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.initLevel() onActivityConfigClicked: { displayDialog(dialogActivityConfig) } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } Score { id: score anchors.top: parent.top anchors.topMargin: 10 * ApplicationInfo.ratio anchors.right: parent.right anchors.rightMargin: 10 * ApplicationInfo.ratio anchors.bottom: undefined } } } diff --git a/src/activities/wordsgame/ActivityConfig.qml b/src/activities/wordsgame/ActivityConfig.qml index a3f1a4419..6568cfe46 100644 --- a/src/activities/wordsgame/ActivityConfig.qml +++ b/src/activities/wordsgame/ActivityConfig.qml @@ -1,101 +1,106 @@ /* 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 + width: if(background) 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") { + // Recreate the binding + uppercaseBox.checked = Qt.binding(function(){return activityConfiguration.uppercaseOnly;}) + + var localeUtf8 = dataToSave.locale; + if(localeUtf8 !== "system") { localeUtf8 += ".UTF-8"; } for(var i = 0 ; i < activityConfiguration.availableLangs.length ; i ++) { if(activityConfiguration.availableLangs[i].locale === localeUtf8) { activityConfiguration.localeBox.currentIndex = i; break; } } + activityConfiguration.locale = localeUtf8 + activityConfiguration.uppercaseOnly = (dataToSave.uppercaseMode === "true") } 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; } + activityConfiguration.uppercaseOnly = activityConfiguration.uppercaseBox.checked + activityConfiguration.locale = newLocale; // Restart the activity with new information if(configHasChanged) { background.stop(); background.start(); } } } diff --git a/src/core/ActivityBase.qml b/src/core/ActivityBase.qml index c084588e2..fe29d8056 100644 --- a/src/core/ActivityBase.qml +++ b/src/core/ActivityBase.qml @@ -1,254 +1,291 @@ /* GCompris - ActivityBase.qml * * Copyright (C) 2014 Bruno Coudoin * * Authors: * 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 QtQuick 2.6 import GCompris 1.0 import "qrc:/gcompris/src/core/core.js" as Core /** * The base QML component for activities in GCompris. * @ingroup components * * Each activity should be derived from this component. It is responsible for * * * basic common key handling, * * unified audio handling, * * screen switching dynamics (from/to Menu/DialogHelp/etc.) * * The following common keys are handled so far: * * * @c Ctrl+q: Exit the application. * * @c Ctrl+b: Toggle the bar. * * @c Ctrl+f: Toggle fullscreen. * * @c Ctrl+m: Toggle audio effects. * * @c Ctrl+w: Exit the current activity and return to the menu. * * @c Ctrl+p: Make a screenshot. * * @c Back: Return to the home screen (corresponds to the 'Back' button on * Android). * * Cf. Template.qml for a sample skeleton activity. * * Cf. * [the wiki](https://gcompris.net/wiki/Qt_Quick_development_process#Adding_a_new_activity) * for further information about creating a new activity. * * @inherit QtQuick.Item */ Item { id: page /** * type:Item * Parent object. */ property Item main: parent; /** * type:Component * The top-level component containing the visible viewport of an activity. * * Put all you want to present the user into this container. Mostly * implemented using a Rectangle or Image component, itself * containing further graphical elements. You are pretty free of doing * whatever you want inside this component. * * Also common elements as Bar, Score, DialogHelp, etc. should be placed * inside this element. */ property Component pageComponent /** * type:QtObject * Reference to the menu activity. * * Populated automatically during activity-loading. */ property QtObject menu /** * type:QtObject * Reference to the ActivityInfo object of the activity. * * Populated automatically during activity-loading. */ property QtObject activityInfo /** * type:GCAudio * The global audio item for voices. * * Because of problems synchronizing multiple Audio objects between * global/menu/main and individual activities, activities should refrain * from implementing additional Audio elements. * * Instead append to this global object to play your voices after the * intro music. * @sa GCAudio audioVoices */ property GCAudio audioVoices /** * type:GCSfx * The global audio item for audio effects. * * Use it to play your effects. * @sa GCSfx audioEffects */ property GCSfx audioEffects /** * type:Loading * The global loading object. * * Start it to signal heavy computation in case of GUI freezes. * @sa Loading */ property Loading loading /** * type:string * The resource folder for the current activity. The resources * of each activity needs to be stored with the same pattern. * "qrc:/gcompris/src/activities/" + activity name + "/resource/" * */ property string resourceUrl: (activityInfo && activityInfo.name) ? "qrc:/gcompris/src/activities/" + activityInfo.name.split('/')[0] + "/resource/": "" /** * type: bool * This variable stores if the activity is a musical activity. * * If it is a musical activity and the audioEffects is disabled, we temporarily unmute the GCSfx audioEffects for that activity and mute again on exiting it in main.qml. */ property bool isMusicalActivity: false property alias datasetLoader: datasetLoader - property string levelFolder + property var levelFolder /** * Emitted when the user wants to return to the Home/Menu screen. */ signal home /** * Emitted when the user wants to return several views back in the * page stack. */ signal back(Item to) /** * Emitted every time the activity has been started. * * Initialize your activity upon this signal. */ signal start /** * Emitted when the activity is about to stop * * Shutdown whatever you need to upon this signal. */ signal stop /** * Emitted when dialog @p dialog should be shown * * Emit this signal when you want to show another dialog, e.g. on * Bar.onHelpClicked * * @param dialog Dialog to show. */ signal displayDialog(Item dialog) /** * Emitted when multiple @p dialogs should be pushed on the page-stack * * Emit this signal when you want to stack >1 views. The last one will be * shown the intermediated ones will be kept on the page stack for later * pop() calls. * * @param dialogs Array of dialogs to push; */ signal displayDialogs(var dialogs) onBack: menu ? menu.back(to) : "" onHome: menu ? menu.home() : "" onDisplayDialog: menu ? menu.displayDialog(dialog) : "" onDisplayDialogs: menu ? menu.displayDialogs(dialogs) : "" Keys.forwardTo: activity.children Keys.onEscapePressed: home(); Keys.onPressed: { if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_Q) { // Ctrl+Q exit the application Core.quit(main); } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_B) { // Ctrl+B toggle the bar ApplicationSettings.isBarHidden = !ApplicationSettings.isBarHidden; } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_F) { // Ctrl+F toggle fullscreen ApplicationSettings.isFullscreen = !ApplicationSettings.isFullscreen } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_M) { // Ctrl+M toggle sound // We mute / unmute both channels in sync ApplicationSettings.isAudioVoicesEnabled = !ApplicationSettings.isAudioVoicesEnabled ApplicationSettings.isAudioEffectsEnabled = !ApplicationSettings.isAudioEffectsEnabled } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_W) { // Ctrl+W exit the current activity home() } else if (event.modifiers === Qt.ControlModifier && event.key === Qt.Key_P) { // Ctrl+P Screenshot ApplicationInfo.screenshot("/tmp/" + activityInfo.name.split('/')[0] + ".png") } } Keys.onReleased: { if (event.key === Qt.Key_Back) { event.accepted = true home() } } Loader { id: activity sourceComponent: pageComponent anchors.fill: parent } Loader { id: demoPageLoader source: ApplicationSettings.activationMode == 1 ? "BuyMeOverlayInapp.qml" : "BuyMeOverlay.qml" anchors.fill: parent active: !activityInfo.demo && ApplicationSettings.isDemoMode } + onLevelFolderChanged: { + if(levelFolder === undefined || levelFolder.length === 0) { + return + } + + datasetLoader.data = [] + for(var level in levelFolder) { + datasetLoader.dataFiles.push({"file": resourceUrl+levelFolder[level]+"/Data.qml"}) + } + datasetLoader.start() + } Loader { id: datasetLoader asynchronous: false - source: resourceUrl + levelFolder + "/Data.qml" - active: levelFolder != "" + + property var dataFiles: [] + property var currentFile + property var data: [] + signal start + signal stop + + onStart: { + var file = dataFiles.shift() + currentFile = file + source = file.file.toString() + } + + onLoaded: { + data = data.concat(item.data) + + if(dataFiles.length != 0) { + start() + } + else { + stop() + } + } + onStop: { + //print("stop", JSON.stringify(data)) + source = "" + // Core.shuffle(data) do we want to shuffle??? Should depend on the activity (if we want increasing levels) or teachers (random multiplication tables for example) + } } } diff --git a/src/core/ActivityInfo.cpp b/src/core/ActivityInfo.cpp index d248df661..e04b1ce8f 100644 --- a/src/core/ActivityInfo.cpp +++ b/src/core/ActivityInfo.cpp @@ -1,232 +1,232 @@ /* GCompris - ActivityInfo.cpp * * Copyright (C) 2014 Bruno Coudoin * * Authors: * 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 . */ #include "ActivityInfo.h" #include #include #include #include #include "ApplicationSettings.h" ActivityInfo::ActivityInfo(QObject *parent): QObject(parent), m_difficulty(0), m_demo(true), m_favorite(false), m_enabled(true), m_createdInVersion(0) { } QString ActivityInfo::name() const { return m_name; } void ActivityInfo::setName(const QString &name) { m_name = name; // Once we are given a name, we can get the favorite property // from the persistant configuration m_favorite = ApplicationSettings::getInstance()->isFavorite(m_name); - setCurrentLevel(); + setCurrentLevels(); emit nameChanged(); } QString ActivityInfo::section() const { return m_section; } void ActivityInfo::setSection(const QString §ion) { m_section = section; emit sectionChanged(); } quint32 ActivityInfo::difficulty() const { return m_difficulty; } void ActivityInfo::setDifficulty(const quint32 &difficulty) { m_difficulty = difficulty; emit difficultyChanged(); } QString ActivityInfo::icon() const { return m_icon; } void ActivityInfo::setIcon(const QString &icon) { m_icon = icon; emit iconChanged(); } QString ActivityInfo::author() const { return m_author; } void ActivityInfo::setAuthor(const QString &author) { m_author = author; emit authorChanged(); } bool ActivityInfo::demo() const { return m_demo; } void ActivityInfo::setDemo(const bool &demo) { m_demo = demo; emit demoChanged(); } QString ActivityInfo::title() const { return m_title; } void ActivityInfo::setTitle(const QString &title) { m_title = title; emit titleChanged(); } QString ActivityInfo::description() const { return m_description; } void ActivityInfo::setDescription(const QString &description) { m_description = description; emit descriptionChanged(); } QString ActivityInfo::goal() const { return m_goal; } void ActivityInfo::setGoal(const QString &goal) { m_goal = goal; emit goalChanged(); } QString ActivityInfo::prerequisite() const { return m_prerequisite; } void ActivityInfo::setPrerequisite(const QString &prerequisite) { m_prerequisite = prerequisite; emit prerequisiteChanged(); } QString ActivityInfo::manual() const { return m_manual; } void ActivityInfo::setManual(const QString &manual) { m_manual = manual; emit manualChanged(); } QString ActivityInfo::credit() const { return m_credit; } void ActivityInfo::setCredit(const QString &credit) { m_credit = credit; emit creditChanged(); } bool ActivityInfo::favorite() const { return m_favorite; } void ActivityInfo::setFavorite(const bool favorite) { m_favorite = favorite; ApplicationSettings::getInstance()->setFavorite(m_name, m_favorite); emit favoriteChanged(); } bool ActivityInfo::enabled() const { return m_enabled; } void ActivityInfo::setEnabled(const bool enabled) { m_enabled = enabled; emit enabledChanged(); } int ActivityInfo::createdInVersion() const { return m_createdInVersion; } void ActivityInfo::setCreatedInVersion(const int created) { m_createdInVersion = created; emit createdInVersionChanged(); } QStringList ActivityInfo::levels() const { return m_levels; } void ActivityInfo::setLevels(const QStringList &levels) { // levels only contains one element containing all the difficulties m_levels = levels.front().split(','); - setCurrentLevel(); + setCurrentLevels(); emit levelsChanged(); } -QString ActivityInfo::currentLevel() const +QStringList ActivityInfo::currentLevels() const { - return m_currentLevel; + return m_currentLevels; } -void ActivityInfo::setCurrentLevel(const QString ¤tLevel) +void ActivityInfo::setCurrentLevels(const QStringList ¤tLevels) { - m_currentLevel = currentLevel; - emit currentLevelChanged(); + m_currentLevels = currentLevels; + emit currentLevelsChanged(); } -void ActivityInfo::setCurrentLevel() +void ActivityInfo::setCurrentLevels() { if(!m_name.isEmpty()) { - if(!m_levels.empty() && ApplicationSettings::getInstance()->currentLevel(m_name) == "") { - ApplicationSettings::getInstance()->setCurrentLevel(m_name, m_levels[0]); + if(!m_levels.empty() && ApplicationSettings::getInstance()->currentLevels(m_name).empty()) { + ApplicationSettings::getInstance()->setCurrentLevels(m_name, {m_levels[0]}); } - m_currentLevel = ApplicationSettings::getInstance()->currentLevel(m_name); + m_currentLevels = ApplicationSettings::getInstance()->currentLevels(m_name); } } diff --git a/src/core/ActivityInfo.h b/src/core/ActivityInfo.h index c63ca6c3d..bb7ec816b 100644 --- a/src/core/ActivityInfo.h +++ b/src/core/ActivityInfo.h @@ -1,220 +1,220 @@ /* GCompris - ActivityInfo.h * * Copyright (C) 2014 Bruno Coudoin * * Authors: * 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 . */ #ifndef ACTIVITYINFO_H #define ACTIVITYINFO_H #include #include #include #include /** * @class ActivityInfo * @short A QML component holding meta information about an activity. * @ingroup components * * Each GCompris activity has to provide some meta data about itself in form * of an ActivityInfo definition. This data will be used to register it in the * ActivityInfoTree, and populate the full screen help dialog. * * @sa DialogHelp */ class ActivityInfo : public QObject { Q_OBJECT /** * Name of the main activity QML file. * * Example: "activity/Activity.qml" */ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) /** * Section(s) this activity belongs to. * * An activity can belong to one or multiple activity sections * (separated by whitespace) out of: * computer, discovery, experiment, fun, math, puzzle, * reading, strategy. */ Q_PROPERTY(QString section READ section WRITE setSection NOTIFY sectionChanged) /** * Difficulty of the activity. * * A difficulty level from 1 (easiest) to 6 (most difficult). */ Q_PROPERTY(quint32 difficulty READ difficulty WRITE setDifficulty NOTIFY difficultyChanged) /** * Relative path to the icon of the activity. * * Example: "activity/activity.svg" */ Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) /** * Author of the activity. */ Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged) /** * Whether the activity is part of the demo version of GCompris. */ Q_PROPERTY(bool demo READ demo WRITE setDemo NOTIFY demoChanged) /** * Title of the activity. */ Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) /** * Description of the activity. */ Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged) /** * Goal that this activity wants to achieve. */ Q_PROPERTY(QString goal READ goal WRITE setGoal NOTIFY goalChanged) /** * Prerequisite for using this activity. */ Q_PROPERTY(QString prerequisite READ prerequisite WRITE setPrerequisite NOTIFY prerequisiteChanged) /** * Manual describing the activity's usage. */ Q_PROPERTY(QString manual READ manual WRITE setManual NOTIFY manualChanged) /** * Credits to third parties. */ Q_PROPERTY(QString credit READ credit WRITE setCredit NOTIFY creditChanged) Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged) /** * This activity is enabled. */ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) /** * Version in which this activity has been created */ Q_PROPERTY(int createdInVersion READ createdInVersion WRITE setCreatedInVersion NOTIFY createdInVersionChanged) /** * Contains a list of string defining the folder names for the different datasets. */ Q_PROPERTY(QStringList levels READ levels WRITE setLevels NOTIFY levelsChanged) /** - * Current dataset used for the activity (it is among the 'levels' list) + * Current datasets used for the activity (it is among the 'levels' list) */ - Q_PROPERTY(QString currentLevel READ currentLevel WRITE setCurrentLevel NOTIFY currentLevelChanged) + Q_PROPERTY(QStringList currentLevels READ currentLevels WRITE setCurrentLevels NOTIFY currentLevelsChanged) public: /// @cond INTERNAL_DOCS explicit ActivityInfo(QObject *parent = 0); QString name() const; void setName(const QString &); QString section() const; void setSection(const QString &); quint32 difficulty() const; void setDifficulty(const quint32 &); QString icon() const; void setIcon(const QString &); QString author() const; void setAuthor(const QString &); bool demo() const; void setDemo(const bool &); QString title() const; void setTitle(const QString &); QString description() const; void setDescription(const QString &); QString goal() const; void setGoal(const QString &); QString prerequisite() const; void setPrerequisite(const QString &); QString manual() const; void setManual(const QString &); QString credit() const; void setCredit(const QString &); bool favorite() const; void setFavorite(const bool); bool enabled() const; void setEnabled(const bool); int createdInVersion() const; void setCreatedInVersion(const int); QStringList levels() const; void setLevels(const QStringList&); - QString currentLevel() const; - void setCurrentLevel(const QString&); + QStringList currentLevels() const; + void setCurrentLevels(const QStringList&); signals: void nameChanged(); void sectionChanged(); void difficultyChanged(); void iconChanged(); void authorChanged(); void demoChanged(); void titleChanged(); void descriptionChanged(); void goalChanged(); void prerequisiteChanged(); void manualChanged(); void creditChanged(); void favoriteChanged(); void enabledChanged(); void createdInVersionChanged(); void levelsChanged(); - void currentLevelChanged(); + void currentLevelsChanged(); /// @endcond private: QString m_name; QString m_section; quint32 m_difficulty; QString m_icon; QString m_author; bool m_demo; QString m_title; QString m_description; QString m_goal; QString m_prerequisite; QString m_manual; QString m_credit; bool m_favorite; bool m_enabled; int m_createdInVersion; QStringList m_levels; - QString m_currentLevel; + QStringList m_currentLevels; /* * Set current level once we have the name and the levels */ - void setCurrentLevel(); + void setCurrentLevels(); }; #endif // ACTIVITYINFO_H diff --git a/src/core/ApplicationSettings.cpp b/src/core/ApplicationSettings.cpp index 0e57d9f8a..a665d8642 100644 --- a/src/core/ApplicationSettings.cpp +++ b/src/core/ApplicationSettings.cpp @@ -1,522 +1,522 @@ /* GCompris - ApplicationSettings.cpp * * Copyright (C) 2014-2016 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 . */ #include "ApplicationSettings.h" #include "ApplicationInfo.h" #include "DownloadManager.h" #include #include #include #include #include #include #include #include #include #include #define GC_DEFAULT_FONT QLatin1String("Andika-R.otf") #define GC_DEFAULT_FONT_CAPITALIZATION 0 // Font.MixedCase #define GC_DEFAULT_FONT_LETTER_SPACING 0 static const char *GENERAL_GROUP_KEY = "General"; static const char *ADMIN_GROUP_KEY = "Admin"; static const char *INTERNAL_GROUP_KEY = "Internal"; static const char *FAVORITE_GROUP_KEY = "Favorite"; static const char *LEVELS_GROUP_KEY = "Levels"; static const char *FULLSCREEN_KEY = "fullscreen"; static const char *PREVIOUS_HEIGHT_KEY = "previousHeight"; static const char *PREVIOUS_WIDTH_KEY = "previousWidth"; static const char *SHOW_LOCKED_ACTIVITIES_KEY = "showLockedActivities"; static const char *ENABLE_AUDIO_VOICES_KEY = "enableAudioVoices"; static const char *ENABLE_AUDIO_EFFECTS_KEY = "enableAudioEffects"; static const char *VIRTUALKEYBOARD_KEY = "virtualKeyboard"; static const char *LOCALE_KEY = "locale"; static const char *FONT_KEY = "font"; static const char *IS_CURRENT_FONT_EMBEDDED = "isCurrentFontEmbedded"; static const char *ENABLE_AUTOMATIC_DOWNLOADS = "enableAutomaticDownloads"; static const char *DOWNLOAD_SERVER_URL_KEY = "downloadServerUrl"; static const char *CACHE_PATH_KEY = "cachePath"; static const char *USERDATA_PATH_KEY = "userDataPath"; static const char *RENDERER_KEY = "renderer"; static const char *EXE_COUNT_KEY = "exeCount"; static const char *LAST_GC_VERSION_RAN = "lastGCVersionRan"; static const char *FILTER_LEVEL_MIN = "filterLevelMin"; static const char *FILTER_LEVEL_MAX = "filterLevelMax"; static const char *BASE_FONT_SIZE_KEY = "baseFontSize"; static const char *FONT_CAPITALIZATION = "fontCapitalization"; static const char *FONT_LETTER_SPACING = "fontLetterSpacing"; static const char *DEFAULT_CURSOR = "defaultCursor"; static const char *NO_CURSOR = "noCursor"; static const char *DEMO_KEY = "demo"; static const char *CODE_KEY = "key"; static const char *KIOSK_KEY = "kiosk"; static const char *SECTION_VISIBLE = "sectionVisible"; static const char *WORDSET = "wordset"; static const char *PROGRESS_KEY = "progress"; static const char *DEFAULT_DOWNLOAD_SERVER = "https://cdn.kde.org/gcompris"; ApplicationSettings *ApplicationSettings::m_instance = nullptr; ApplicationSettings::ApplicationSettings(const QString &configPath, QObject *parent): QObject(parent), m_baseFontSizeMin(-7), m_baseFontSizeMax(7), m_fontLetterSpacingMin(0.0), m_fontLetterSpacingMax(8.0), m_config(configPath, QSettings::IniFormat) { const QRect &screenSize = QApplication::desktop()->screenGeometry(); // initialize from settings file or default // general group m_config.beginGroup(GENERAL_GROUP_KEY); m_isAudioEffectsEnabled = m_config.value(ENABLE_AUDIO_EFFECTS_KEY, true).toBool(); m_isFullscreen = m_config.value(FULLSCREEN_KEY, true).toBool(); m_previousHeight = m_config.value(PREVIOUS_HEIGHT_KEY, screenSize.height()).toUInt(); m_previousWidth = m_config.value(PREVIOUS_WIDTH_KEY, screenSize.width()).toUInt(); m_isAudioVoicesEnabled = m_config.value(ENABLE_AUDIO_VOICES_KEY, true).toBool(); m_isVirtualKeyboard = m_config.value(VIRTUALKEYBOARD_KEY, ApplicationInfo::getInstance()->isMobile()).toBool(); m_locale = m_config.value(LOCALE_KEY, GC_DEFAULT_LOCALE).toString(); m_font = m_config.value(FONT_KEY, GC_DEFAULT_FONT).toString(); if(m_font == QLatin1String("Andika-R.ttf")) m_font = "Andika-R.otf"; m_fontCapitalization = m_config.value(FONT_CAPITALIZATION, GC_DEFAULT_FONT_CAPITALIZATION).toUInt(); m_fontLetterSpacing = m_config.value(FONT_LETTER_SPACING, GC_DEFAULT_FONT_LETTER_SPACING).toReal(); m_isEmbeddedFont = m_config.value(IS_CURRENT_FONT_EMBEDDED, true).toBool(); // Init the activation mode if(QLatin1String(ACTIVATION_MODE) == "no") m_activationMode = 0; else if(QLatin1String(ACTIVATION_MODE) == "inapp") m_activationMode = 1; else if(QLatin1String(ACTIVATION_MODE) == "internal") m_activationMode = 2; else qFatal("Unknown activation mode"); // Set the demo mode if(QLatin1String(ACTIVATION_MODE) != "no") m_isDemoMode = m_config.value(DEMO_KEY, true).toBool(); else m_isDemoMode = false; m_codeKey = m_config.value(CODE_KEY, "").toString(); #if defined(WITH_KIOSK_MODE) m_isKioskMode = m_config.value(KIOSK_KEY, true).toBool(); #else m_isKioskMode = m_config.value(KIOSK_KEY, false).toBool(); #endif // Option only useful if we are in demo mode (else all the activities are available and unlocked) // By default, all the activities are displayed (even locked ones) m_showLockedActivities = m_config.value(SHOW_LOCKED_ACTIVITIES_KEY, m_isDemoMode).toBool(); m_sectionVisible = m_config.value(SECTION_VISIBLE, true).toBool(); m_wordset = m_config.value(WORDSET, "").toString(); m_isAutomaticDownloadsEnabled = m_config.value(ENABLE_AUTOMATIC_DOWNLOADS, !ApplicationInfo::getInstance()->isMobile() && ApplicationInfo::isDownloadAllowed()).toBool(); m_filterLevelMin = m_config.value(FILTER_LEVEL_MIN, 1).toUInt(); m_filterLevelMax = m_config.value(FILTER_LEVEL_MAX, 6).toUInt(); m_defaultCursor = m_config.value(DEFAULT_CURSOR, false).toBool(); m_noCursor = m_config.value(NO_CURSOR, false).toBool(); m_baseFontSize = m_config.value(BASE_FONT_SIZE_KEY, 0).toInt(); m_config.sync(); // make sure all defaults are written back m_config.endGroup(); // admin group m_config.beginGroup(ADMIN_GROUP_KEY); m_downloadServerUrl = m_config.value(DOWNLOAD_SERVER_URL_KEY, QLatin1String(DEFAULT_DOWNLOAD_SERVER)).toString(); if(m_downloadServerUrl == "http://gcompris.net") { setDownloadServerUrl(DEFAULT_DOWNLOAD_SERVER); } m_cachePath = m_config.value(CACHE_PATH_KEY, QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).toString(); m_userDataPath = m_config.value(USERDATA_PATH_KEY, QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/GCompris")).toString(); m_renderer = m_config.value(RENDERER_KEY, GRAPHICAL_RENDERER).toString(); m_config.endGroup(); // internal group m_config.beginGroup(INTERNAL_GROUP_KEY); m_exeCount = m_config.value(EXE_COUNT_KEY, 0).toUInt(); m_lastGCVersionRan = m_config.value(LAST_GC_VERSION_RAN, 0).toUInt(); m_config.endGroup(); // no group m_isBarHidden = false; connect(this, &ApplicationSettings::showLockedActivitiesChanged, this, &ApplicationSettings::notifyShowLockedActivitiesChanged); connect(this, &ApplicationSettings::audioVoicesEnabledChanged, this, &ApplicationSettings::notifyAudioVoicesEnabledChanged); connect(this, &ApplicationSettings::audioEffectsEnabledChanged, this, &ApplicationSettings::notifyAudioEffectsEnabledChanged); connect(this, &ApplicationSettings::fullscreenChanged, this, &ApplicationSettings::notifyFullscreenChanged); connect(this, &ApplicationSettings::previousHeightChanged, this, &ApplicationSettings::notifyPreviousHeightChanged); connect(this, &ApplicationSettings::previousWidthChanged, this, &ApplicationSettings::notifyPreviousWidthChanged); connect(this, &ApplicationSettings::localeChanged, this, &ApplicationSettings::notifyLocaleChanged); connect(this, &ApplicationSettings::fontChanged, this, &ApplicationSettings::notifyFontChanged); connect(this, &ApplicationSettings::virtualKeyboardChanged, this, &ApplicationSettings::notifyVirtualKeyboardChanged); connect(this, &ApplicationSettings::automaticDownloadsEnabledChanged, this, &ApplicationSettings::notifyAutomaticDownloadsEnabledChanged); connect(this, &ApplicationSettings::filterLevelMinChanged, this, &ApplicationSettings::notifyFilterLevelMinChanged); connect(this, &ApplicationSettings::filterLevelMaxChanged, this, &ApplicationSettings::notifyFilterLevelMaxChanged); connect(this, &ApplicationSettings::sectionVisibleChanged, this, &ApplicationSettings::notifySectionVisibleChanged); connect(this, &ApplicationSettings::wordsetChanged, this, &ApplicationSettings::notifyWordsetChanged); connect(this, &ApplicationSettings::demoModeChanged, this, &ApplicationSettings::notifyDemoModeChanged); connect(this, &ApplicationSettings::codeKeyChanged, this, &ApplicationSettings::notifyCodeKeyChanged); connect(this, &ApplicationSettings::kioskModeChanged, this, &ApplicationSettings::notifyKioskModeChanged); connect(this, &ApplicationSettings::downloadServerUrlChanged, this, &ApplicationSettings::notifyDownloadServerUrlChanged); connect(this, &ApplicationSettings::cachePathChanged, this, &ApplicationSettings::notifyCachePathChanged); connect(this, &ApplicationSettings::userDataPathChanged, this, &ApplicationSettings::notifyUserDataPathChanged); connect(this, &ApplicationSettings::rendererChanged, this, &ApplicationSettings::notifyRendererChanged); connect(this, &ApplicationSettings::exeCountChanged, this, &ApplicationSettings::notifyExeCountChanged); connect(this, &ApplicationSettings::barHiddenChanged, this, &ApplicationSettings::notifyBarHiddenChanged); connect(this, &ApplicationSettings::lastGCVersionRanChanged, this, &ApplicationSettings::notifyLastGCVersionRanChanged); } ApplicationSettings::~ApplicationSettings() { // make sure settings file is up2date: // general group m_config.beginGroup(GENERAL_GROUP_KEY); m_config.setValue(SHOW_LOCKED_ACTIVITIES_KEY, m_showLockedActivities); m_config.setValue(ENABLE_AUDIO_VOICES_KEY, m_isAudioVoicesEnabled); m_config.setValue(LOCALE_KEY, m_locale); m_config.setValue(FONT_KEY, m_font); m_config.setValue(IS_CURRENT_FONT_EMBEDDED, m_isEmbeddedFont); m_config.setValue(FULLSCREEN_KEY, m_isFullscreen); m_config.setValue(PREVIOUS_HEIGHT_KEY, m_previousHeight); m_config.setValue(PREVIOUS_WIDTH_KEY, m_previousWidth); m_config.setValue(VIRTUALKEYBOARD_KEY, m_isVirtualKeyboard); m_config.setValue(ENABLE_AUTOMATIC_DOWNLOADS, m_isAutomaticDownloadsEnabled); m_config.setValue(FILTER_LEVEL_MIN, m_filterLevelMin); m_config.setValue(FILTER_LEVEL_MAX, m_filterLevelMax); m_config.setValue(DEMO_KEY, m_isDemoMode); m_config.setValue(CODE_KEY, m_codeKey); m_config.setValue(KIOSK_KEY, m_isKioskMode); m_config.setValue(SECTION_VISIBLE, m_sectionVisible); m_config.setValue(WORDSET, m_wordset); m_config.setValue(DEFAULT_CURSOR, m_defaultCursor); m_config.setValue(NO_CURSOR, m_noCursor); m_config.setValue(BASE_FONT_SIZE_KEY, m_baseFontSize); m_config.setValue(FONT_CAPITALIZATION, m_fontCapitalization); m_config.setValue(FONT_LETTER_SPACING, m_fontLetterSpacing); m_config.endGroup(); // admin group m_config.beginGroup(ADMIN_GROUP_KEY); m_config.setValue(DOWNLOAD_SERVER_URL_KEY, m_downloadServerUrl); m_config.setValue(CACHE_PATH_KEY, m_cachePath); m_config.setValue(USERDATA_PATH_KEY, m_userDataPath); m_config.setValue(RENDERER_KEY, m_renderer); m_config.endGroup(); // internal group m_config.beginGroup(INTERNAL_GROUP_KEY); m_config.setValue(EXE_COUNT_KEY, m_exeCount); m_config.setValue(LAST_GC_VERSION_RAN, m_lastGCVersionRan); m_config.endGroup(); m_config.sync(); m_instance = nullptr; } void ApplicationSettings::notifyShowLockedActivitiesChanged() { updateValueInConfig(GENERAL_GROUP_KEY, SHOW_LOCKED_ACTIVITIES_KEY, m_showLockedActivities); qDebug() << "notifyShowLockedActivitiesChanged: " << m_showLockedActivities; } void ApplicationSettings::notifyAudioVoicesEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUDIO_VOICES_KEY, m_isAudioVoicesEnabled); qDebug() << "notifyAudioVoices: " << m_isAudioVoicesEnabled; } void ApplicationSettings::notifyAudioEffectsEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUDIO_EFFECTS_KEY, m_isAudioEffectsEnabled); qDebug() << "notifyAudioEffects: " << m_isAudioEffectsEnabled; } void ApplicationSettings::notifyLocaleChanged() { updateValueInConfig(GENERAL_GROUP_KEY, LOCALE_KEY, m_locale); qDebug() << "new locale: " << m_locale; } void ApplicationSettings::notifyFontChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_KEY, m_font); qDebug() << "new font: " << m_font; } void ApplicationSettings::notifyEmbeddedFontChanged() { updateValueInConfig(GENERAL_GROUP_KEY, IS_CURRENT_FONT_EMBEDDED, m_isEmbeddedFont); qDebug() << "new font is embedded: " << m_isEmbeddedFont; } void ApplicationSettings::notifyFontCapitalizationChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_CAPITALIZATION, m_fontCapitalization); qDebug() << "new fontCapitalization: " << m_fontCapitalization; } void ApplicationSettings::notifyFontLetterSpacingChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FONT_LETTER_SPACING, m_fontLetterSpacing); qDebug() << "new fontLetterSpacing: " << m_fontLetterSpacing; } void ApplicationSettings::notifyFullscreenChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FULLSCREEN_KEY, m_isFullscreen); qDebug() << "fullscreen set to: " << m_isFullscreen; } void ApplicationSettings::notifyPreviousHeightChanged() { updateValueInConfig(GENERAL_GROUP_KEY, PREVIOUS_HEIGHT_KEY, m_previousHeight); qDebug() << "previous height set to: " << m_previousHeight; } void ApplicationSettings::notifyPreviousWidthChanged() { updateValueInConfig(GENERAL_GROUP_KEY, PREVIOUS_WIDTH_KEY, m_previousWidth); qDebug() << "previous width set to: " << m_previousWidth; } void ApplicationSettings::notifyVirtualKeyboardChanged() { updateValueInConfig(GENERAL_GROUP_KEY, VIRTUALKEYBOARD_KEY, m_isVirtualKeyboard); qDebug() << "virtualkeyboard set to: " << m_isVirtualKeyboard; } bool ApplicationSettings::isAutomaticDownloadsEnabled() const { return m_isAutomaticDownloadsEnabled && ApplicationInfo::isDownloadAllowed(); } void ApplicationSettings::setIsAutomaticDownloadsEnabled(const bool newIsAutomaticDownloadsEnabled) { if(ApplicationInfo::isDownloadAllowed()) { m_isAutomaticDownloadsEnabled = newIsAutomaticDownloadsEnabled; emit automaticDownloadsEnabledChanged(); } } void ApplicationSettings::notifyAutomaticDownloadsEnabledChanged() { updateValueInConfig(GENERAL_GROUP_KEY, ENABLE_AUTOMATIC_DOWNLOADS, m_isAutomaticDownloadsEnabled); qDebug() << "enableAutomaticDownloads set to: " << m_isAutomaticDownloadsEnabled; } void ApplicationSettings::notifyFilterLevelMinChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FILTER_LEVEL_MIN, m_filterLevelMin); qDebug() << "filterLevelMin set to: " << m_filterLevelMin; } void ApplicationSettings::notifyFilterLevelMaxChanged() { updateValueInConfig(GENERAL_GROUP_KEY, FILTER_LEVEL_MAX, m_filterLevelMax); qDebug() << "filterLevelMax set to: " << m_filterLevelMax; } void ApplicationSettings::notifyDemoModeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, DEMO_KEY, m_isDemoMode); qDebug() << "notifyDemoMode: " << m_isDemoMode; } void ApplicationSettings::notifyCodeKeyChanged() { checkPayment(); if(!m_isDemoMode) updateValueInConfig(GENERAL_GROUP_KEY, CODE_KEY, m_codeKey); qDebug() << "notifyCodeKey: " << m_codeKey; } void ApplicationSettings::notifyKioskModeChanged() { updateValueInConfig(GENERAL_GROUP_KEY, KIOSK_KEY, m_isKioskMode); qDebug() << "notifyKioskMode: " << m_isKioskMode; } void ApplicationSettings::notifySectionVisibleChanged() { updateValueInConfig(GENERAL_GROUP_KEY, SECTION_VISIBLE, m_sectionVisible); qDebug() << "notifySectionVisible: " << m_sectionVisible; } void ApplicationSettings::notifyWordsetChanged() { if(!m_wordset.isEmpty() && DownloadManager::getInstance()->haveLocalResource(m_wordset) && !DownloadManager::getInstance()->isDataRegistered("words")) { // words.rcc is there -> register old file first // then try to update in the background DownloadManager::getInstance()->updateResource(m_wordset); } updateValueInConfig(GENERAL_GROUP_KEY, WORDSET, m_wordset); qDebug() << "notifyWordset: " << m_wordset; } void ApplicationSettings::notifyDownloadServerUrlChanged() { updateValueInConfig(ADMIN_GROUP_KEY, DOWNLOAD_SERVER_URL_KEY, m_downloadServerUrl); qDebug() << "downloadServerUrl set to: " << m_downloadServerUrl; } void ApplicationSettings::notifyCachePathChanged() { updateValueInConfig(ADMIN_GROUP_KEY, CACHE_PATH_KEY, m_cachePath); qDebug() << "cachePath set to: " << m_cachePath; } void ApplicationSettings::notifyUserDataPathChanged() { updateValueInConfig(ADMIN_GROUP_KEY, USERDATA_PATH_KEY, m_userDataPath); qDebug() << "userDataPath set to: " << m_userDataPath; } void ApplicationSettings::notifyRendererChanged() { updateValueInConfig(ADMIN_GROUP_KEY, RENDERER_KEY, m_renderer); qDebug() << "renderer set to: " << m_renderer; } void ApplicationSettings::notifyExeCountChanged() { updateValueInConfig(INTERNAL_GROUP_KEY, EXE_COUNT_KEY, m_exeCount); qDebug() << "exeCount set to: " << m_exeCount; } void ApplicationSettings::notifyLastGCVersionRanChanged() { updateValueInConfig(INTERNAL_GROUP_KEY, LAST_GC_VERSION_RAN, m_lastGCVersionRan); qDebug() << "lastVersionRan set to: " << m_lastGCVersionRan; } void ApplicationSettings::notifyBarHiddenChanged() { qDebug() << "is bar hidden: " << m_isBarHidden; } void ApplicationSettings::saveBaseFontSize() { updateValueInConfig(GENERAL_GROUP_KEY, BASE_FONT_SIZE_KEY, m_baseFontSize); } void ApplicationSettings::saveActivityConfiguration(const QString &activity, const QVariantMap &data) { qDebug() << "save configuration for:" << activity; QMapIterator i(data); while (i.hasNext()) { i.next(); updateValueInConfig(activity, i.key(), i.value()); } } QVariantMap ApplicationSettings::loadActivityConfiguration(const QString &activity) { qDebug() << "load configuration for:" << activity; m_config.beginGroup(activity); QStringList keys = m_config.childKeys(); QVariantMap data; for(const QString &key : keys) { data[key] = m_config.value(key); } m_config.endGroup(); return data; } void ApplicationSettings::setFavorite(const QString &activity, bool favorite) { updateValueInConfig(FAVORITE_GROUP_KEY, activity, favorite); } bool ApplicationSettings::isFavorite(const QString &activity) { m_config.beginGroup(FAVORITE_GROUP_KEY); bool favorite = m_config.value(activity, false).toBool(); m_config.endGroup(); return favorite; } -void ApplicationSettings::setCurrentLevel(const QString &activity, const QString &level) +void ApplicationSettings::setCurrentLevels(const QString &activity, const QStringList &level) { updateValueInConfig(LEVELS_GROUP_KEY, activity, level); } -QString ApplicationSettings::currentLevel(const QString &activity) +QStringList ApplicationSettings::currentLevels(const QString &activity) { m_config.beginGroup(LEVELS_GROUP_KEY); - QString level = m_config.value(activity, "").toString(); + QStringList level = m_config.value(activity, QStringList()).toStringList(); m_config.endGroup(); return level; } template void ApplicationSettings::updateValueInConfig(const QString& group, const QString& key, const T& value) { m_config.beginGroup(group); m_config.setValue(key, value); m_config.endGroup(); m_config.sync(); } int ApplicationSettings::loadActivityProgress(const QString &activity) { int progress = 0; m_config.beginGroup(activity); progress = m_config.value(PROGRESS_KEY, 0).toInt(); m_config.endGroup(); qDebug() << "loaded progress for activity" << activity << ":" << progress; return progress; } void ApplicationSettings::saveActivityProgress(const QString &activity, int progress) { updateValueInConfig(activity, PROGRESS_KEY, progress); } bool ApplicationSettings::useExternalWordset() { return !m_wordset.isEmpty() && DownloadManager::getInstance()->isDataRegistered("words"); } QObject *ApplicationSettings::applicationSettingsProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) ApplicationSettings* appSettings = getInstance(); return appSettings; } diff --git a/src/core/ApplicationSettings.h b/src/core/ApplicationSettings.h index f702e6e4e..122f5a37b 100644 --- a/src/core/ApplicationSettings.h +++ b/src/core/ApplicationSettings.h @@ -1,659 +1,659 @@ /* GCompris - ApplicationSettings.h * * Copyright (C) 2014 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 . */ #ifndef APPLICATIONSETTINGS_H #define APPLICATIONSETTINGS_H #include #include #include #include #include #include #include #define GC_DEFAULT_LOCALE "system" /** * @class ApplicationSettings * @short Singleton that contains GCompris' persistent settings. * @ingroup infrastructure * * Settings are persisted using QSettings, which stores them in platform * specific locations. * * The settings are subdivided in different groups of settings. * * [General] settings are mostly changeable by users in the DialogConfig * dialog. * * [Admin] and [Internal] settings are not changeable by the * user and used for internal purposes. Should only be changed if you really know * what you are doing. * * The [Favorite] group is auto-generated from the favorite activities * selected by a user. * * The [Levels] group is auto-generated from the levels chosen by * a user (if the activity provides multiple datasets). * * Besides these global settings there is one group for each activity that * stores persistent settings. * * Settings defaults are defined in the source code. * * @sa DialogActivityConfig */ class ApplicationSettings : public QObject { Q_OBJECT /* General group */ /** * Whether to show locked activities. * False if in Demo mode, true otherwise. */ Q_PROPERTY(bool showLockedActivities READ showLockedActivities WRITE setShowLockedActivities NOTIFY showLockedActivitiesChanged) /** * Whether audio voices/speech should be enabled. */ Q_PROPERTY(bool isAudioVoicesEnabled READ isAudioVoicesEnabled WRITE setIsAudioVoicesEnabled NOTIFY audioVoicesEnabledChanged) /** * Whether audio effects should be enabled. */ Q_PROPERTY(bool isAudioEffectsEnabled READ isAudioEffectsEnabled WRITE setIsAudioEffectsEnabled NOTIFY audioEffectsEnabledChanged) /** * Whether GCompris should run in fullscreen mode. */ Q_PROPERTY(bool isFullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged) /** * Window Height on Application's Startup */ Q_PROPERTY(quint32 previousHeight READ previousHeight WRITE setPreviousHeight NOTIFY previousHeightChanged) /** * Window Width on Application's Startup */ Q_PROPERTY(quint32 previousWidth READ previousWidth WRITE setPreviousWidth NOTIFY previousWidthChanged) /** * Whether on-screen keyboard should be enabled per default in activities * that use it. */ Q_PROPERTY(bool isVirtualKeyboard READ isVirtualKeyboard WRITE setVirtualKeyboard NOTIFY virtualKeyboardChanged) /** * Locale string for currently active language. */ Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged) /** * Currently selected font. */ Q_PROPERTY(QString font READ font WRITE setFont NOTIFY fontChanged) /** * Whether currently active font is a shipped font (or a system font). * * Updated automatically. * @sa font */ Q_PROPERTY(bool isEmbeddedFont READ isEmbeddedFont WRITE setIsEmbeddedFont NOTIFY embeddedFontChanged) /** * Font Capitalization * * Force all texts to be rendered in UpperCase, LowerCase or MixedCase (default) * @sa font */ Q_PROPERTY(quint32 fontCapitalization READ fontCapitalization WRITE setFontCapitalization NOTIFY fontCapitalizationChanged) /** * Font letter spacing * * Change the letter spacing of all the texts * @sa font */ Q_PROPERTY(qreal fontLetterSpacing READ fontLetterSpacing WRITE setFontLetterSpacing NOTIFY fontLetterSpacingChanged) /** * Minimum allowed value for font spacing letter. * * Constant value: +0.0 */ Q_PROPERTY(qreal fontLetterSpacingMin READ fontLetterSpacingMin CONSTANT) /** * Maximum allowed value for font spacing letter. * * Constant value: +8.0 */ Q_PROPERTY(qreal fontLetterSpacingMax READ fontLetterSpacingMax CONSTANT) /** * Whether downloads/updates of resource files should be done automatically, * without user-interaction. * * Note, that on Android GCompris currently can't distinguish Wifi * from mobile data connections (cf. Qt ticket #30394). */ Q_PROPERTY(bool isAutomaticDownloadsEnabled READ isAutomaticDownloadsEnabled WRITE setIsAutomaticDownloadsEnabled NOTIFY automaticDownloadsEnabledChanged) /** * Minimum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMin READ filterLevelMin WRITE setFilterLevelMin NOTIFY filterLevelMinChanged) /** * Maximum value for difficulty level filter. */ Q_PROPERTY(quint32 filterLevelMax READ filterLevelMax WRITE setFilterLevelMax NOTIFY filterLevelMaxChanged) /** * Whether in demo mode. */ Q_PROPERTY(bool isDemoMode READ isDemoMode WRITE setDemoMode NOTIFY demoModeChanged) /** * Activation code key. */ Q_PROPERTY(QString codeKey READ codeKey WRITE setCodeKey NOTIFY codeKeyChanged) /** * Activation mode. */ Q_PROPERTY(quint32 activationMode READ activationMode CONSTANT) /** * Whether kiosk mode is currently active. */ Q_PROPERTY(bool isKioskMode READ isKioskMode WRITE setKioskMode NOTIFY kioskModeChanged) /** * Whether the section selection row is visible in the menu view. */ Q_PROPERTY(bool sectionVisible READ sectionVisible WRITE setSectionVisible NOTIFY sectionVisibleChanged) /** * The name of the default wordset to use. If empty then the internal sample wordset is used. */ Q_PROPERTY(QString wordset READ wordset WRITE setWordset NOTIFY wordsetChanged) /** * Current base font-size used for font scaling. * * This setting is the basis for application-wide font-scaling. A value * of 0 means to use the font-size as set by the application. Other values * between @ref baseFontSizeMin and @ref baseFontSizeMax enforce * font-scaling. * * @sa GCText.fontSize baseFontSizeMin baseFontSizeMax */ Q_PROPERTY(int baseFontSize READ baseFontSize WRITE setBaseFontSize NOTIFY baseFontSizeChanged) /** * Minimum allowed value for font-scaling. * * Constant value: -7 */ Q_PROPERTY(int baseFontSizeMin READ baseFontSizeMin CONSTANT) /** * Maximum allowed value for font-scaling. * * Constant value: +7 */ Q_PROPERTY(int baseFontSizeMax READ baseFontSizeMax CONSTANT) // admin group /** * Base-URL for resource downloads. * * @sa DownloadManager */ Q_PROPERTY(QString downloadServerUrl READ downloadServerUrl WRITE setDownloadServerUrl NOTIFY downloadServerUrlChanged) /** * Path where resources are downloaded and stored. * * @sa DownloadManager */ Q_PROPERTY(QString cachePath READ cachePath WRITE setCachePath NOTIFY cachePathChanged) /** * Return the platform specific path for storing data shared between apps * * On Android: /storage/emulated/0/GCompris (>= Android 4.2), * /storage/sdcard0/GCompris (< Android 4.2) * On Linux: $HOME/local/share/GCompris */ Q_PROPERTY(QString userDataPath READ userDataPath WRITE setUserDataPath NOTIFY userDataPathChanged) /** * Define the renderer used. * Either openGL or software renderer (only for Qt >= 5.8) */ Q_PROPERTY(QString renderer READ renderer WRITE setRenderer NOTIFY rendererChanged) // internal group Q_PROPERTY(quint32 exeCount READ exeCount WRITE setExeCount NOTIFY exeCountChanged) // keep last version ran. If different than ApplicationInfo.GCVersionCode(), it means a new version is running Q_PROPERTY(int lastGCVersionRan READ lastGCVersionRan WRITE setLastGCVersionRan NOTIFY lastGCVersionRanChanged) // no group Q_PROPERTY(bool isBarHidden READ isBarHidden WRITE setBarHidden NOTIFY barHiddenChanged) public: /// @cond INTERNAL_DOCS explicit ApplicationSettings(const QString &configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/gcompris/" + GCOMPRIS_APPLICATION_NAME + ".conf", QObject *parent = 0); virtual ~ApplicationSettings(); // It is not recommended to create a singleton of Qml Singleton registered // object but we could not found a better way to let us access ApplicationInfo // on the C++ side. All our test shows that it works. static ApplicationSettings *getInstance() { if(!m_instance) { m_instance = new ApplicationSettings(); } return m_instance; } static QObject *applicationSettingsProvider(QQmlEngine *engine, QJSEngine *scriptEngine); bool showLockedActivities() const { return m_showLockedActivities; } void setShowLockedActivities(const bool newMode) { m_showLockedActivities = newMode; emit showLockedActivitiesChanged(); } bool isAudioVoicesEnabled() const { return m_isAudioVoicesEnabled; } void setIsAudioVoicesEnabled(const bool newMode) { m_isAudioVoicesEnabled = newMode; emit audioVoicesEnabledChanged(); } bool isAudioEffectsEnabled() const { return m_isAudioEffectsEnabled; } void setIsAudioEffectsEnabled(const bool newMode) { m_isAudioEffectsEnabled = newMode; emit audioEffectsEnabledChanged(); } bool isFullscreen() const { return m_isFullscreen; } void setFullscreen(const bool newMode) { if(m_isFullscreen != newMode) { m_isFullscreen = newMode; emit fullscreenChanged(); } } quint32 previousHeight() const { return m_previousHeight; } void setPreviousHeight(quint32 height) { if(m_previousHeight != height) { m_previousHeight = height; emit previousHeightChanged(); } } quint32 previousWidth() const { return m_previousWidth; } void setPreviousWidth(quint32 width) { if(m_previousWidth != width) { m_previousWidth = width; emit previousWidthChanged(); } } bool isVirtualKeyboard() const { return m_isVirtualKeyboard; } void setVirtualKeyboard(const bool newMode) { m_isVirtualKeyboard = newMode; emit virtualKeyboardChanged(); } QString locale() const { return m_locale; } void setLocale(const QString &newLocale) { m_locale = newLocale; emit localeChanged(); } QString font() const { return m_font; } void setFont(const QString &newFont) { m_font = newFont; emit fontChanged(); } bool isEmbeddedFont() const { return m_isEmbeddedFont; } void setIsEmbeddedFont(const bool newIsEmbeddedFont) { m_isEmbeddedFont = newIsEmbeddedFont; emit embeddedFontChanged(); } quint32 fontCapitalization() const { return m_fontCapitalization; } void setFontCapitalization(quint32 newFontCapitalization) { m_fontCapitalization = newFontCapitalization; emit fontCapitalizationChanged(); } qreal fontLetterSpacing() const { return m_fontLetterSpacing; } void setFontLetterSpacing(qreal newFontLetterSpacing) { m_fontLetterSpacing = newFontLetterSpacing; emit fontLetterSpacingChanged(); } qreal fontLetterSpacingMin() const { return m_fontLetterSpacingMin; } qreal fontLetterSpacingMax() const { return m_fontLetterSpacingMax; } bool isAutomaticDownloadsEnabled() const; void setIsAutomaticDownloadsEnabled(const bool newIsAutomaticDownloadsEnabled); quint32 filterLevelMin() const { return m_filterLevelMin; } void setFilterLevelMin(const quint32 newFilterLevelMin) { m_filterLevelMin = newFilterLevelMin; emit filterLevelMinChanged(); } quint32 filterLevelMax() const { return m_filterLevelMax; } void setFilterLevelMax(const quint32 newFilterLevelMax) { m_filterLevelMax = newFilterLevelMax; emit filterLevelMaxChanged(); } bool isDemoMode() const { return m_isDemoMode; } void setDemoMode(const bool newMode); QString codeKey() const { return m_codeKey; } void setCodeKey(const QString &newCodeKey) { m_codeKey = newCodeKey; emit codeKeyChanged(); } /** * @brief activationMode * @return 0: no, 1: inapp, 2: internal */ quint32 activationMode() const { return m_activationMode; } bool isKioskMode() const { return m_isKioskMode; } void setKioskMode(const bool newMode) { m_isKioskMode = newMode; emit kioskModeChanged(); } /** * Check validity of the activation code * @param code An activation code to check * @returns 0 if the code is not valid or we don't know yet * 1 if the code is valid but out of date * 2 if the code is valid and under 2 years */ Q_INVOKABLE uint checkActivationCode(const QString &code); /** * Check Payment API * Call a payment system to sync our demoMode state with it */ void checkPayment(); // Called by the payment system void bought(const bool isBought) { if(m_isDemoMode != !isBought) { m_isDemoMode = !isBought; emit demoModeChanged(); } } bool sectionVisible() const { return m_sectionVisible; } void setSectionVisible(const bool newMode) { qDebug() << "c++ setSectionVisible=" << newMode; m_sectionVisible = newMode; emit sectionVisibleChanged(); } QString wordset() const { return m_wordset; } void setWordset(const QString &newWordset) { m_wordset = newWordset; emit wordsetChanged(); } QString downloadServerUrl() const { return m_downloadServerUrl; } void setDownloadServerUrl(const QString &newDownloadServerUrl) { m_downloadServerUrl = newDownloadServerUrl; emit downloadServerUrlChanged(); } QString cachePath() const { return m_cachePath; } void setCachePath(const QString &newCachePath) { m_cachePath = newCachePath; emit cachePathChanged(); } QString userDataPath() const { return m_userDataPath; } void setUserDataPath(const QString &newUserDataPath) { m_userDataPath = newUserDataPath; emit userDataPathChanged(); } quint32 exeCount() const { return m_exeCount; } void setExeCount(const quint32 newExeCount) { m_exeCount = newExeCount; emit exeCountChanged(); } bool isBarHidden() const { return m_isBarHidden; } void setBarHidden(const bool newBarHidden) { m_isBarHidden = newBarHidden; emit barHiddenChanged(); } int baseFontSize() const { return m_baseFontSize; } void setBaseFontSize(const int newBaseFontSize) { m_baseFontSize = qMax(qMin(newBaseFontSize, baseFontSizeMax()), baseFontSizeMin()); emit baseFontSizeChanged(); } int baseFontSizeMin() const { return m_baseFontSizeMin; } int baseFontSizeMax() const { return m_baseFontSizeMax; } int lastGCVersionRan() const { return m_lastGCVersionRan; } void setLastGCVersionRan(const int newLastGCVersionRan) { m_lastGCVersionRan = newLastGCVersionRan; emit lastGCVersionRanChanged(); } QString renderer() const { return m_renderer; } void setRenderer(const QString &newRenderer) { m_renderer = newRenderer; emit rendererChanged(); } /** * Check if we use the external wordset for activity based on lang_api * @returns true if wordset is loaded * false if wordset is not loaded */ Q_INVOKABLE bool useExternalWordset(); protected slots: Q_INVOKABLE void notifyShowLockedActivitiesChanged(); Q_INVOKABLE void notifyAudioVoicesEnabledChanged(); Q_INVOKABLE void notifyAudioEffectsEnabledChanged(); Q_INVOKABLE void notifyFullscreenChanged(); Q_INVOKABLE void notifyPreviousHeightChanged(); Q_INVOKABLE void notifyPreviousWidthChanged(); Q_INVOKABLE void notifyVirtualKeyboardChanged(); Q_INVOKABLE void notifyLocaleChanged(); Q_INVOKABLE void notifyFontChanged(); Q_INVOKABLE void notifyFontCapitalizationChanged(); Q_INVOKABLE void notifyFontLetterSpacingChanged(); Q_INVOKABLE void notifyEmbeddedFontChanged(); Q_INVOKABLE void notifyAutomaticDownloadsEnabledChanged(); Q_INVOKABLE void notifyFilterLevelMinChanged(); Q_INVOKABLE void notifyFilterLevelMaxChanged(); Q_INVOKABLE void notifyDemoModeChanged(); Q_INVOKABLE void notifyCodeKeyChanged(); Q_INVOKABLE void notifyKioskModeChanged(); Q_INVOKABLE void notifySectionVisibleChanged(); Q_INVOKABLE void notifyWordsetChanged(); Q_INVOKABLE void notifyDownloadServerUrlChanged(); Q_INVOKABLE void notifyCachePathChanged(); Q_INVOKABLE void notifyUserDataPathChanged(); Q_INVOKABLE void notifyExeCountChanged(); Q_INVOKABLE void notifyLastGCVersionRanChanged(); Q_INVOKABLE void notifyRendererChanged(); Q_INVOKABLE void notifyBarHiddenChanged(); public slots: Q_INVOKABLE bool isFavorite(const QString &activity); Q_INVOKABLE void setFavorite(const QString &activity, bool favorite); - Q_INVOKABLE void setCurrentLevel(const QString &activity, const QString &level); - Q_INVOKABLE QString currentLevel(const QString &activity); + Q_INVOKABLE void setCurrentLevels(const QString &activity, const QStringList &level); + Q_INVOKABLE QStringList currentLevels(const QString &activity); Q_INVOKABLE void saveBaseFontSize(); /// @endcond /** * Stores per-activity configuration @p data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @param data Map of configuration data so save. */ Q_INVOKABLE void saveActivityConfiguration(const QString &activity, const QVariantMap &data); /** * Loads per-activity configuration data for @p activity. * * @param activity Name of the activity that wants to persist settings. * @returns Map of configuration items. */ Q_INVOKABLE QVariantMap loadActivityConfiguration(const QString &activity); /** * Loads per-activity progress using the default "progress" key. * * @param activity Name of the activity to load progress for. * @returns Last started level of the activity, 0 if none saved. */ Q_INVOKABLE int loadActivityProgress(const QString &activity); /** * Saves per-activity progress using the default "progress" key. * * @param activity Name of the activity that wants to persist settings. * @param progress Last started level to save as progress value. */ Q_INVOKABLE void saveActivityProgress(const QString &activity, int progress); signals: void showLockedActivitiesChanged(); void audioVoicesEnabledChanged(); void audioEffectsEnabledChanged(); void fullscreenChanged(); void previousHeightChanged(); void previousWidthChanged(); void virtualKeyboardChanged(); void localeChanged(); void fontChanged(); void fontCapitalizationChanged(); void fontLetterSpacingChanged(); void embeddedFontChanged(); void automaticDownloadsEnabledChanged(); void filterLevelMinChanged(); void filterLevelMaxChanged(); void demoModeChanged(); void codeKeyChanged(); void kioskModeChanged(); void sectionVisibleChanged(); void wordsetChanged(); void baseFontSizeChanged(); void downloadServerUrlChanged(); void cachePathChanged(); void userDataPathChanged(); void exeCountChanged(); void lastGCVersionRanChanged(); void rendererChanged(); void barHiddenChanged(); protected: static ApplicationSettings *m_instance; private: // Update in configuration the couple {key, value} in the group. template void updateValueInConfig(const QString& group, const QString& key, const T& value); bool m_showLockedActivities; bool m_isAudioVoicesEnabled; bool m_isAudioEffectsEnabled; bool m_isFullscreen; quint32 m_previousHeight; quint32 m_previousWidth; bool m_isVirtualKeyboard; bool m_isAutomaticDownloadsEnabled; bool m_isEmbeddedFont; quint32 m_fontCapitalization; qreal m_fontLetterSpacing; quint32 m_filterLevelMin; quint32 m_filterLevelMax; bool m_defaultCursor; bool m_noCursor; QString m_locale; QString m_font; bool m_isDemoMode; QString m_codeKey; quint32 m_activationMode; bool m_isKioskMode; bool m_sectionVisible; QString m_wordset; int m_baseFontSize; const int m_baseFontSizeMin; const int m_baseFontSizeMax; const qreal m_fontLetterSpacingMin; const qreal m_fontLetterSpacingMax; QString m_downloadServerUrl; QString m_cachePath; QString m_userDataPath; quint32 m_exeCount; int m_lastGCVersionRan; QString m_renderer; bool m_isBarHidden; QSettings m_config; }; #endif // APPLICATIONSETTINGS_H diff --git a/src/core/DialogChooseLevel.qml b/src/core/DialogChooseLevel.qml index d1d4f71d8..e70635882 100644 --- a/src/core/DialogChooseLevel.qml +++ b/src/core/DialogChooseLevel.qml @@ -1,399 +1,408 @@ /* 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 chosenLevels: [] 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 property bool inMenu: false onVisibleChanged: { if(visible) { configLoader.initializePanel() } } function initialize() { // dataset information - chosenLevel = currentActivity.currentLevel + chosenLevels = currentActivity.currentLevels 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}) + difficultiesModel.push({"level": currentFile.level, "objective": item.objective, "difficulty": item.difficulty, "selectedInConfig": chosenLevels.includes(currentFile.level)}) 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: 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" : "" // Load configuration at start of activity // in the menu, it's done when the visibility property // of the dialog changes onItemChanged: if(!inMenu) { initializePanel(); } function initializePanel() { if(item) { // only connect once the signal to save data if(item.background !== dialogChooseLevel) { 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 + // to be fixed by all last used levels + checked: modelData.selectedInConfig + onClicked: { + if(checked) { + chosenLevels.push(modelData.level) + } + else if(chosenLevels.length > 1) { + chosenLevels.splice(chosenLevels.indexOf(modelData.level), 1) + } + else { + // At least one must be selected + checked = true; + } + } } } } } } // 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: 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 + visible: inMenu === true style: GCButtonStyle { } onClicked: { saveData(); startActivity(); } } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { id: cancel onClose: { parent.close() } } File { id: activityConfigFile } }