diff --git a/src/core/DialogActivityConfig.qml b/src/core/DialogActivityConfig.qml index cffdddc4c..b997127df 100644 --- a/src/core/DialogActivityConfig.qml +++ b/src/core/DialogActivityConfig.qml @@ -1,250 +1,253 @@ /* GCompris - DialogActivityConfig.qml * * 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 . */ import QtQuick 2.6 import GCompris 1.0 /** * A QML component for a full screen configuration dialog. * @ingroup components * * All user editable settings are presented to the user in a * DialogActivityConfig dialog. The global configuration can be accessed * through the Bar in the main menu, activity specific configuration from the * respective activity. * * All config items that are shown in this dialog are persisted * using ApplicationSettings. * * For an example have a look at Menu.qml. * * For more details on how to add configuration to an activity cf. * [the wiki](https://gcompris.net/wiki/Qt_Quick_development_process#Adding_a_configuration_for_a_specific_activity) * * @sa ApplicationSettings * @inherit QtQuick.Item */ Rectangle { id: dialogActivityContent visible: false /* Public interface: */ /** * type:object * The content object as loaded dynamically. */ property alias configItem: loader.item /** * type:Component * Content component which holds the visual presentation of the * config settings in the QML scene. */ property Component content /** * type:string * The name of the activity in case of per-activity config. * * Will be autogenerated unless set by the caller. */ property string activityName: "" /** * type:object * Map containing all settings as key/value-pairs. * * Will be populated from ApplicationSettings.loadActivityConfiguration * and can be passed to ApplicationSettings.saveActivityConfiguration. */ property var dataToSave property var dataValidationFunc: null /// @cond INTERNAL_DOCS property bool isDialog: true /** * type:string * Title of the configuration dialog. * Global configuration name is "Configuration". * For activities, it is "activity name configuration". */ readonly property string title: { if(activityName != "") qsTr("%1 configuration").arg(activityInfo.title) else qsTr("Configuration") } property alias active: loader.active property alias loader: loader property QtObject activityInfo: ActivityInfoTree.currentActivity property ActivityBase currentActivity /// @endcond /** * Emitted when the config dialog has been closed. */ signal close /** * Emitted when the config dialog has been started. */ signal start /** * 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 /** * Emitted when the config settings have been loaded. */ signal loadData signal stop color: "#696da3" border.color: "black" border.width: 1 function getInitialConfiguration() { if(activityName == "") { activityName = activityInfo.name.split('/')[0]; } dataToSave = ApplicationSettings.loadActivityConfiguration(activityName) loadData() } function saveDatainConfiguration() { saveData() ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) } Row { visible: dialogActivityContent.active spacing: 2 Item { width: 10; height: 1 } Column { spacing: 10 anchors.top: parent.top Item { width: 1; height: 10 } Rectangle { + id: titleRectangle color: "#e6e6e6" - radius: 6.0 + radius: 10 * ApplicationInfo.ratio width: dialogActivityContent.width - 30 height: title.height * 1.2 border.color: "black" - border.width: 2 + border.width: 0 + + // The apply button + GCButtonCancel { + id: apply + apply: true + anchors.verticalCenter: titleRectangle.verticalCenter + anchors.margins: 2 * ApplicationInfo.ratio + onClose: { + if (dialogActivityContent.dataValidationFunc && ! + dialogActivityContent.dataValidationFunc()) { + console.log("Configuration data is invalid, not saving!"); + return; + } + saveData() + if(activityName != "") { + ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) + } + dialogActivityContent.close() + } + } Row { spacing: 2 padding: 8 Image { id: titleIcon anchors { left: parent.left top: parent.top margins: 4 * ApplicationInfo.ratio } } GCText { id: title text: dialogActivityContent.title - width: dialogActivityContent.width - (30 + cancel.width) + width: dialogActivityContent.width - (30 + apply.width) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } } Rectangle { - color: "#e6e6e6" - radius: 6.0 + color: "#bdbed0" + radius: 10 * ApplicationInfo.ratio width: dialogActivityContent.width - 30 height: dialogActivityContent.height - (30 + title.height * 1.2) - border.color: "black" - border.width: 2 + border.color: "white" + border.width: 3 * ApplicationInfo.ratio anchors.margins: 100 Flickable { id: flick - anchors.margins: 8 + anchors.margins: 10 * ApplicationInfo.ratio anchors.fill: parent flickableDirection: Flickable.VerticalFlick clip: true contentHeight: contentItem.childrenRect.height + 40 * ApplicationInfo.ratio Loader { id: loader active: false sourceComponent: dialogActivityContent.content property alias rootItem: dialogActivityContent } } // 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 } } Item { width: 1; height: 10 } } } - - // The cancel button - GCButtonCancel { - id: cancel - onClose: { - if (dialogActivityContent.dataValidationFunc && ! - dialogActivityContent.dataValidationFunc()) { - console.log("Configuration data is invalid, not saving!"); - return; - } - saveData() - if(activityName != "") { - ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) - } - parent.close() - } - } - } diff --git a/src/core/DialogChooseLevel.qml b/src/core/DialogChooseLevel.qml index 74bb045d3..76de69f26 100644 --- a/src/core/DialogChooseLevel.qml +++ b/src/core/DialogChooseLevel.qml @@ -1,408 +1,410 @@ /* 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 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 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, "selectedInConfig": (chosenLevels.indexOf(currentFile.level) != -1)}) 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 { + id: titleRectangle color: "#e6e6e6" - radius: 6.0 + radius: 10 * ApplicationInfo.ratio width: dialogChooseLevel.width - 30 height: title.height * 1.2 border.color: "black" - border.width: 2 + border.width: 0 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) + width: dialogChooseLevel.width - 30 //- (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 { + theme: "settingsButton" 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 { + theme: "settingsButton" selected: !datasetVisibleButton.selected } onClicked: { datasetVisibleButton.selected = false; } //showOptions() } } // "Dataset"/"Options" content Rectangle { - color: "#e6e6e6" - radius: 6.0 + color: "#bdbed0" + radius: 10 * ApplicationInfo.ratio 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 + border.color: "white" + border.width: 3 * ApplicationInfo.ratio + anchors.margins: 100 Flickable { id: flick - anchors.margins: 8 + anchors.margins: 10 * ApplicationInfo.ratio 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 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 // 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() + style: GCButtonStyle { + theme: "settingsButton" + } + onClicked: close(); } Button { id: saveButton text: qsTr("Save") height: parent.height width: parent.width / 4 property bool selected: true - style: GCButtonStyle { } + style: GCButtonStyle { + theme: "settingsButton" + } onClicked: { saveData(); close(); } } Button { id: saveAndStartButton text: qsTr("Save and start") height: parent.height width: parent.width / 3 visible: inMenu === true - style: GCButtonStyle { } + style: GCButtonStyle { + theme: "settingsButton" + } onClicked: { saveData(); startActivity(); } } } Item { width: 1; height: 10 } } } - // The cancel button - GCButtonCancel { - id: cancel - onClose: { - parent.close() - } - } - File { id: activityConfigFile } } diff --git a/src/core/GCButtonCancel.qml b/src/core/GCButtonCancel.qml index 523b69471..64d3626ba 100644 --- a/src/core/GCButtonCancel.qml +++ b/src/core/GCButtonCancel.qml @@ -1,63 +1,66 @@ /* GCompris - GCButtonCancel.qml * * Copyright (C) 2015 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 /** * A QML component representing GCompris' cancel button. * @ingroup components * * @inherit QtQuick.Image */ Image { id: cancel - source: "qrc:/gcompris/src/core/resource/cancel.svg"; + source: apply ? "qrc:/gcompris/src/core/resource/apply.svg" : "qrc:/gcompris/src/core/resource/cancel.svg"; anchors.right: parent.right anchors.top: parent.top smooth: true sourceSize.width: 60 * ApplicationInfo.ratio + fillMode: Image.PreserveAspectFit anchors.margins: 10 signal close + property bool apply: false + SequentialAnimation { id: anim running: true loops: Animation.Infinite NumberAnimation { target: cancel property: "rotation" from: -10; to: 10 duration: 500 easing.type: Easing.InOutQuad } NumberAnimation { target: cancel property: "rotation" from: 10; to: -10 duration: 500 easing.type: Easing.InOutQuad } } MouseArea { anchors.fill: parent onClicked: close() } } diff --git a/src/core/GCButtonStyle.qml b/src/core/GCButtonStyle.qml index 569aa5472..902dd8324 100644 --- a/src/core/GCButtonStyle.qml +++ b/src/core/GCButtonStyle.qml @@ -1,143 +1,151 @@ /* GCompris - GCButtonStyle.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 QtQuick.Controls 1.5 import QtQuick.Controls.Styles 1.4 import GCompris 1.0 /** * Provides styling for GCompris' Buttons. * @ingroup components * * @inherit QtQuick.Controls.Styles.ButtonStyle */ ButtonStyle { id: buttonStyle /** * type:real * Fixed font size of the label in pt. * * Set to a value > 0 for enforcing a fixed font.pointSize for the label, * that won't be updated with ApplicationSettings.baseFontSize. * @sa ApplicationSettings.baseFontSize, GCText.fixFontSize */ property real fixedFontSize: -1 /** * type:string * theme of the button. For now, three themes are accepted: "light" and "dark" and "highContrast" * * Default is dark. */ property string theme: "dark" /** * type:var * existing themes for the button. * A theme is composed of: * the colors of the button when selected: selectedColorGradient0 and selectedColorGradient1. * the colors of the button when not selected: backgroundColorGradient0 and backgroundColorGradient1. * the button's border color * the text color */ property var themes: { "dark": { backgroundColorGradient0: "#23373737", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#13373737", selectedColorGradient1: "#803ACAFF", borderColor: "#FF373737", textColor: "#FF373737" }, "light": { backgroundColorGradient0: "#42FFFFFF", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#23FFFFFF", selectedColorGradient1: "#803ACAFF", borderColor: "white", textColor: "white" }, "highContrast": { backgroundColorGradient0: "#EEFFFFFF", selectedColorGradient0: "#C03ACAFF", backgroundColorGradient1: "#AAFFFFFF", selectedColorGradient1: "#803ACAFF", borderColor: "white", textColor: "373737" + }, + "settingsButton": { + backgroundColorGradient0: "#bdbed0", + selectedColorGradient0: "#e6e6e6", + backgroundColorGradient1: "#bdbed0", + selectedColorGradient1: "#e6e6e6", + borderColor: selected ? "#ffffffff" : "#00ffffff", + textColor: "black" } } property bool selected: false property string textSize: "regular" property var textSizes: { "regular": { fontSize: 14, fontBold: false }, "subtitle": { fontSize: 16, fontBold: true }, "title": { fontSize: 24, fontBold: true } } background: Rectangle { - border.width: control.activeFocus ? 4 : 2 + border.width: theme === "settingsButton" ? 3 * ApplicationInfo.ratio : control.activeFocus ? 3 * ApplicationInfo.ratio : 1 * ApplicationInfo.ratio border.color: themes[theme].borderColor - radius: 10 + radius: 10 * ApplicationInfo.ratio gradient: Gradient { GradientStop { position: 0 ; color: (control.pressed || buttonStyle.selected) ? themes[theme].selectedColorGradient0 : themes[theme].backgroundColorGradient0 } GradientStop { position: 1 ; color: (control.pressed || buttonStyle.selected) ? themes[theme].selectedColorGradient1 : themes[theme].backgroundColorGradient1 } } } label: Item { id: labelItem anchors.fill: parent implicitWidth: labelText.implicitWidth implicitHeight: labelText.implicitHeight GCText { id: labelText color: themes[theme].textColor text: control.text fontSize: textSizes[textSize].fontSize font.bold: textSizes[textSize].fontBold anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap fontSizeMode: Text.Fit Component.onCompleted: { if (fixedFontSize > 0) { labelText.fixFontSize = true; labelText.fontSize = fixedFontSize; } } } } }