diff --git a/src/core/DialogActivityConfig.qml b/src/core/DialogActivityConfig.qml index 22abe55de..68737dba2 100644 --- a/src/core/DialogActivityConfig.qml +++ b/src/core/DialogActivityConfig.qml @@ -1,232 +1,230 @@ /* 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.2 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](http://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 - z: 1000 function getInitialConfiguration() { if(activityName == "") { activityName = ActivityInfoTree.currentActivity.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 { color: "#e6e6e6" radius: 6.0 width: dialogActivityContent.width - 30 height: title.height * 1.2 border.color: "black" border.width: 2 Image { id: titleIcon anchors { left: parent.left top: parent.top margins: 4 * ApplicationInfo.ratio } } GCText { id: title text: dialogActivityContent.title width: dialogActivityContent.width - 30 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: "black" fontSize: 20 font.weight: Font.DemiBold wrapMode: Text.WordWrap } } Rectangle { color: "#e6e6e6" radius: 6.0 width: dialogActivityContent.width - 30 height: dialogActivityContent.height - 100 border.color: "black" border.width: 2 anchors.margins: 100 Flickable { id: flick anchors.margins: 8 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 } } } Item { width: 1; height: 10 } } } // The cancel button GCButtonCancel { onClose: { if (dialogActivityContent.dataValidationFunc && ! dialogActivityContent.dataValidationFunc()) { console.log("Configuration data is invalid, not saving!"); return; } saveData() ApplicationSettings.saveActivityConfiguration(activityName, dataToSave) parent.close() } } } diff --git a/src/core/GCButtonCancel.qml b/src/core/GCButtonCancel.qml index 66faa0bfd..45bb9e81d 100644 --- a/src/core/GCButtonCancel.qml +++ b/src/core/GCButtonCancel.qml @@ -1,64 +1,63 @@ /* 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.2 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"; anchors.right: parent.right anchors.top: parent.top smooth: true sourceSize.width: 60 * ApplicationInfo.ratio anchors.margins: 10 - z: 200 signal close 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/GCComboBox.qml b/src/core/GCComboBox.qml index ff354af33..5fae74439 100644 --- a/src/core/GCComboBox.qml +++ b/src/core/GCComboBox.qml @@ -1,323 +1,322 @@ /* GCompris - GCComboBox.qml * * Copyright (C) 2015 Johnny Jazeix * * Authors: * Johnny Jazeix * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.2 import QtQuick.Controls 1.0 import GCompris 1.0 /** * A QML component unifying comboboxes in GCompris. * @ingroup components * * GCComboBox contains a combobox and a label. * When the combobox isn't active, it is displayed as a button containing the current value * and the combobox label (its description). * Once the button is clicked, the list of all available choices is displayed. * Also, above the list is the combobox label. * * Navigation can be done with keys and mouse/gestures. * As Qt comboboxes, you can either have a js Array or a Qml model as model. * GCComboBox should now be used wherever you'd use a QtQuick combobox. It has * been decided to implement comboboxes ourselves in GCompris because of * some integration problems on some OSes (native dialogs unavailable). */ Item { id: gccombobox focus: true width: parent.width height: flow.height /** * type:Item * Where the list containing all choices will be displayed. * Should be the dialogActivityConfig item if used on config. */ property Item background /** * type:int * Current index of the combobox. */ property int currentIndex: -1 /** * type:string * Current text displayed in the combobox when inactive. */ property string currentText /** * type:alias * Model for the list (user has to specify one). */ property alias model: gridview.model /** * type:string * Text besides the combobox, used to describe what the combobox is for. */ property string label /** * type:bool * Internal value. * If model is an js Array, we access data using modelData and [] else qml Model, we need to use model and get(). */ readonly property bool isModelArray: model.constructor === Array // start and stop trigs the animation signal start signal stop // emitted at stop animation end signal close onCurrentIndexChanged: { currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } /** * type:Flow * Combobox display when inactive: the button with current choice and its label besides. */ Flow { id: flow width: parent.width spacing: 5 * ApplicationInfo.ratio Rectangle { id: button visible: true // Add radius to add some space between text and borders implicitWidth: Math.max(200, currentTextBox.width+radius) implicitHeight: 50 * ApplicationInfo.ratio border.width: 2 border.color: "#373737" radius: 10 gradient: Gradient { GradientStop { position: 0 ; color: mouseArea.pressed ? "#C03ACAFF" : "#23373737" } GradientStop { position: 1 ; color: mouseArea.pressed ? "#803ACAFF" : "#13373737" } } // Current value of combobox GCText { id: currentTextBox anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter text: currentText fontSize: mediumSize color: "#373737" } MouseArea { id: mouseArea anchors.fill: parent onReleased: { popup.visible = true } } } Item { width: labelText.width height: button.height GCText { id: labelText text: label anchors.verticalCenter: parent.verticalCenter fontSize: mediumSize wrapMode: Text.WordWrap } } } /** * type:Item * Combobox display when active: header with the description and the gridview containing all the available choices. */ Item { id: popup visible: false width: parent.width height: parent.height parent: background - + z: 100 focus: visible // Forward event to activity if key pressed is not one of the handled key // (ctrl+F should still resize the window for example) Keys.onPressed: { if(event.key !== Qt.Key_Back) { background.currentActivity.Keys.onPressed(event) } } Keys.onReleased: { if(event.key === Qt.Key_Back) { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); event.accepted = true } } Keys.onRightPressed: gridview.moveCurrentIndexRight(); Keys.onLeftPressed: gridview.moveCurrentIndexLeft(); Keys.onDownPressed: gridview.moveCurrentIndexDown(); Keys.onUpPressed: gridview.moveCurrentIndexUp(); Keys.onEscapePressed: { // Keep the old value discardChange(); hidePopUpAndRestoreFocus(); } Keys.onEnterPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onReturnPressed: { acceptChange(); hidePopUpAndRestoreFocus(); } Keys.onSpacePressed: { acceptChange(); hidePopUpAndRestoreFocus(); } // Don't accept the list value, restore previous value function discardChange() { if(isModelArray) { for(var i = 0 ; i < model.count ; ++ i) { if(model[currentIndex].text === currentText) { currentIndex = i; break; } } } else { for(var i = 0 ; i < model.length ; ++ i) { if(model.get(currentIndex).text === currentText) { currentIndex = i; break; } } } gridview.currentIndex = currentIndex; } // Accept the change. Updates the currentIndex and text of the button function acceptChange() { currentIndex = gridview.currentIndex; currentText = isModelArray ? model[currentIndex].text : (model && model.get(currentIndex) ? model.get(currentIndex).text : "") } function hidePopUpAndRestoreFocus() { popup.visible = false; // Restore focus on previous activity for keyboard input background.currentActivity.forceActiveFocus(); } Rectangle { id: listBackground anchors.fill: parent radius: 10 color: "grey" Rectangle { id : headerDescription - z: 10 + z: 10 width: gridview.width height: gridview.elementHeight GCText { text: label fontSize: mediumSize wrapMode: Text.WordWrap anchors.horizontalCenter: parent.horizontalCenter } GCButtonCancel { id: discardIcon anchors.right: headerDescription.right anchors.top: headerDescription.top - MouseArea { anchors.fill: parent onClicked: { popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } GridView { id: gridview z: 4 readonly property int elementHeight: 40 * ApplicationInfo.ratio // each element has a 300 width size minimum. If the screen is larger than it, // we do a grid with cases with 300px for width at minimum. // If you have a better idea/formula to have a different column number, don't hesitate, change it :). readonly property int numberOfColumns: Math.max(1, Math.floor(width / (300 * ApplicationInfo.ratio))) contentHeight: isModelArray ? elementHeight*model.count/numberOfColumns : elementHeight*model.length/numberOfColumns width: listBackground.width height: listBackground.height-headerDescription.height currentIndex: gccombobox.currentIndex flickableDirection: Flickable.VerticalFlick clip: true anchors.top: headerDescription.bottom cellWidth: width / numberOfColumns cellHeight: elementHeight delegate: Component { Rectangle { width: gridview.cellWidth height: gridview.elementHeight color: GridView.isCurrentItem ? "darkcyan" : "beige" border.width: GridView.isCurrentItem ? 3 : 2 radius: 5 Image { id: isSelectedIcon visible: parent.GridView.isCurrentItem source: "qrc:/gcompris/src/core/resource/apply.svg" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 10 sourceSize.width: (gridview.elementHeight * 0.8) } GCText { id: textValue text: isModelArray ? modelData.text : model.text anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { currentIndex = index popup.acceptChange(); popup.hidePopUpAndRestoreFocus(); } } } } } } } }