diff --git a/src/activities/numeration_weights_integer/NumberClassDropArea.qml b/src/activities/numeration_weights_integer/NumberClassDropArea.qml index 9a63bd2d9..c7ccdbbb4 100644 --- a/src/activities/numeration_weights_integer/NumberClassDropArea.qml +++ b/src/activities/numeration_weights_integer/NumberClassDropArea.qml @@ -1,207 +1,194 @@ /* GCompris - NumberClassDropArea.qml * * Copyright (C) 2019 Emmanuel Charruau * * 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 QtQuick.Layouts 1.3 import "../../core" import "numeration_weights_integer.js" as Activity Rectangle { id: numberClassDropArea property string className property var unitColumnWeightImagesArray: ["","","","","","","","",""] //? property int unitColumnWeightImagesArrayIndex: 0 property var tenColumnWeightImagesArray: ["","","","","","","","",""] property int tenColumnWeightImagesArrayIndex: 0 property var hundredColumnWeightImagesArray: ["","","","","","","","",""] property int hundredColumnWeightImagesArrayIndex: 0 property string numberClassDropAreaIndex: index property string defaultColor: "darkseagreen" property string overlapColor: "grey" - property alias numberWeightsDropAreasRepeaterAlias: numberWeightsDropAreasRepeater + property alias numberWeightsDropAreasRepeater: numberWeightsDropAreasRepeater width: parent.width height: parent.height - numberClassHeaders.height color: "blue" ListModel { id: numberWeightHeadersModel ListElement { weightType: "Hundred" } ListElement { weightType: "Ten" } ListElement { weightType: "Unit" } } RowLayout { id: numberWeightsDropAreasRowLayout width: parent.width height: parent.height spacing: 10 Repeater { id: numberWeightsDropAreasRepeater model: numberWeightHeadersModel - Rectangle { id: numberWeightDropAreaRectangle property string numberWeightDropAreaRectangleIndex: index property string numberWeightKey: weightType property alias numberWeightsDropTiles: numberWeightsDropTiles property alias numberWeightHeaderElement: numberWeightHeaderElement property string numberWeightType: weightType color: "lightsteelblue" Layout.fillWidth: true Layout.fillHeight: true Layout.minimumWidth: 50 Layout.preferredWidth: 100 NumberWeightHeaderElement { id: numberWeightHeaderElement x: 0 y: 0 width: numberWeightDropAreaRectangle.width height: numberWeightDropAreaRectangle.height /10 } // Implement columns where the numberWeights are set by the user Rectangle { id: numberWeightsDropTiles property alias numberWeightDropAreaGridRepeater: numberWeightDropAreaGridRepeater - anchors.top: numberWeightHeaderElement.bottom width: parent.width height: parent.height - numberWeightHeaderElement.height - Grid { id: numberWeightDropAreaGrid anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom; width: parent.width height: parent.height columns: 1 - Repeater { id: numberWeightDropAreaGridRepeater model: 9 - DropArea { property alias numberWeightImageTile: numberWeightImageTile property alias numberWeightComponentRectangle: numberWeightComponentRectangle keys: "numberWeightKey" - width: parent.width height: parent.height/9 - onEntered: { numberWeightComponentRectangle.color = overlapColor } - onExited: { numberWeightComponentRectangle.color = defaultColor } - onDropped: { var imageName = drag.source.imageName var caption = drag.source.caption var weightValue = drag.source.weightValue Activity.setNumberWeightComponent(numberWeightImageTile,imageName,caption, weightValue) numberWeightComponentRectangle.color = defaultColor } - Rectangle { id: numberWeightComponentRectangle border.color: "black" border.width: 5 radius: 10 width: parent.width height: parent.height color: defaultColor - Image { id: numberWeightImageTile property string caption: "" property string weightValue: "" property alias border: numberWeightComponentRectangle.border - anchors.fill: parent // sourceSize.width: parent.width sourceSize.height: parent.height fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent onClicked: { if (numberWeightImageTile.status === Image.Ready) { Activity.removeNumberWeightComponent(numberWeightImageTile) } else { if (Activity.selectedNumberWeightDragElementIndex !== -1) { var imageName = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).imageName var caption = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).caption var weightValue = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).weightValue Activity.setNumberWeightComponent(numberWeightImageTile,imageName,caption,weightValue) } } - } + } } - GCText { id: numberClassElementCaption anchors.fill: parent anchors.bottom: parent.bottom fontSizeMode: Text.Fit color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: numberWeightImageTile.caption } } } } } } } } } } } diff --git a/src/activities/numeration_weights_integer/NumberWeightDragElement.qml b/src/activities/numeration_weights_integer/NumberWeightDragElement.qml index 3072e6228..785119c75 100644 --- a/src/activities/numeration_weights_integer/NumberWeightDragElement.qml +++ b/src/activities/numeration_weights_integer/NumberWeightDragElement.qml @@ -1,107 +1,105 @@ /* GCompris - NumberWeightDragElement.qml * * Copyright (C) 2019 Emmanuel Charruau * * 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 "numeration_weights_integer.js" as Activity Rectangle { id: numberWeightDragElement + property bool animationIsRunning property int lastX property int lastY property string imageName property string name property bool canDrag: true property string caption property string src property int weightValue property bool selected // callback defined in each numberWeightDragElement called when we release the element in background //? property var releaseElement: null //? width: parent.width - parent.width/5 height: parent.height / 15 color: "transparent" border.color: "red" border.width: selected === true ? 1 : 0 - - - Drag.active: numberWeightDragElementMouseArea.drag.active + Drag.active: numberWeightDragElementMouseArea.drag.active || animationIsRunning src: "resource/images/" + imageName Image { id: numberWeightDragElementImage sourceSize.width: parent.width sourceSize.height: parent.height source: imageName !== "" ? numberWeightDragElement.src : "" anchors.fill: parent //number of available items GCText { id: numberWeightDragElementCaption anchors.fill: parent anchors.margins: 10 fontSizeMode: Text.Fit color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: numberWeightDragElement.caption } } MouseArea { id: numberWeightDragElementMouseArea anchors.fill: parent onPressed: { //set the initial position numberWeightDragElement.lastX = numberWeightDragElement.x numberWeightDragElement.lastY = numberWeightDragElement.y } onPositionChanged: { Activity.unselectAllNumberWeightDragElement() } - onClicked: { Activity.selectNumberWeightDragElement(index) } drag.target: numberWeightDragElement drag.axis: numberWeightDragElement.x < parent.width ? Drag.XAxis : Drag.XAndYAxis Drag.hotSpot.x: width Drag.hotSpot.y: height onReleased: { parent.Drag.drop() //set the element to its initial coordinates numberWeightDragElement.x = numberWeightDragElement.lastX numberWeightDragElement.y = numberWeightDragElement.lastY } } } diff --git a/src/activities/numeration_weights_integer/NumberWeightHeaderElement.qml b/src/activities/numeration_weights_integer/NumberWeightHeaderElement.qml index 49da48767..9c5aaccc5 100644 --- a/src/activities/numeration_weights_integer/NumberWeightHeaderElement.qml +++ b/src/activities/numeration_weights_integer/NumberWeightHeaderElement.qml @@ -1,115 +1,115 @@ /* GCompris - NumberWeightHeaderElement.qml * * Copyright (C) 2019 Emmanuel Charruau * * 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 "numeration_weights_integer.js" as Activity Rectangle { id: numberWeightHeaderElement property string defaultColor: "darkred" property string overlapColor: "blue" color: "darkred" radius: 0.2 anchors.top: parent.top //? property int lastX property int lastY property Item dragParent property alias textAlias: numberWeightHeaderCaption.text border.color: "red" border.width: 0 DropArea { id: numberWeightsHeaderDropArea anchors.fill: parent keys: "numberWeightHeaderKey" onEntered: { numberWeightHeaderElement.color = overlapColor } onExited: { numberWeightHeaderElement.color = defaultColor } onDropped: { numberWeightHeaderElement.color = defaultColor numberWeightHeaderCaption.text = drag.source.caption } Image { id: numberWeightHeaderImage anchors.fill: parent sourceSize.width: parent.width sourceSize.height: parent.height GCText { id: numberClassElementCaption anchors.fill: parent anchors.bottom: parent.bottom fontSizeMode: Text.Fit color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } } } GCText { id: numberWeightHeaderCaption anchors.fill: parent - anchors.margins: 10 //find a way to fit the text without truncating it + anchors.margins: 10 //? find a way to fit the text without truncating it fontSizeMode: Text.Fit color: "white" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap text: qsTr("Drag column weight here.") } MouseArea { anchors.fill: parent onClicked: { - if (numberWeightHeaderImage.status === Image.Ready) { + /* if (numberWeightHeaderImage.status === Image.Ready) { //? needs certainly to be removed Activity.removeNumberWeightComponent(numberWeightImageTile) } - else { + else {*/ if (Activity.selectedNumberWeightDragElementIndex !== -1) { if (numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).dragkeys === "numberWeightHeaderKey") { - numberWeightHeaderImage.source = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).imageName + //numberWeightHeaderImage.source = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).imageName numberWeightHeaderCaption.text = numberWeightDragListModel.get(Activity.selectedNumberWeightDragElementIndex).caption } } - } + // } } } } diff --git a/src/activities/numeration_weights_integer/Numeration_weights_integer.qml b/src/activities/numeration_weights_integer/Numeration_weights_integer.qml index c4127630e..74ecb4da8 100644 --- a/src/activities/numeration_weights_integer/Numeration_weights_integer.qml +++ b/src/activities/numeration_weights_integer/Numeration_weights_integer.qml @@ -1,664 +1,666 @@ /* GCompris - Share.qml * * Copyright (C) 2019 Emmanuel Charruau * * 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 . */ // TODO: ne valider que si les poids sont dans la bonne case // TODO: ajouter des niveaux pour pouvoir le faire tester par un élève // TODO: fix error when removing a class dragging it // TODO: give new number only when bonus is finished // TODO: remove cellsize // TODO: is it possible to have a vertical mode? If not remove everything related to vertical mode // TODO: remove settings in bar menu import QtQuick 2.13 import GCompris 1.0 import QtQuick.Layouts 1.3 import QtQuick.Controls 1.5 import QtQml.Models 2.1 import "../../core" import "numeration_weights_integer.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ffffb3" signal start signal stop Component.onCompleted: { dialogActivityConfig.getInitialConfiguration() 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 mainZoneArea: mainZoneArea property alias bar: bar property alias bonus: bonus property alias instruction: instruction property alias warningRectangle: warningRectangle property alias dataset: dataset property alias numberClassListModel: numberClassListModel property alias numberClassDragListModel: numberClassDragListModel property alias numberWeightDragListModel: numberWeightDragListModel property alias numberClassTypeModel: numberClassTypeModel property alias leftWidget: leftWidget property alias progressBar: progressBar property alias numberClassDropAreaRepeater: numberClassDropAreaRepeater property alias classNameListView: classNameListView property int barHeightAddon: ApplicationSettings.isBarHidden ? 1 : 3 property int cellSize: Math.min(background.width / 11, background.height / (9 + barHeightAddon)) property var levels: activity.datasetLoader.item.data property alias numberToConvertRectangle: numberToConvertRectangle } Loader { id: dataset asynchronous: false } onStart: { Activity.start(items) } onStop: { Activity.stop() } property bool vert: background.width >= background.height //mainZone DropArea { id: mainZoneArea width: background.vert ? background.width - leftWidget.width - 40 : background.width - 40 height: ApplicationSettings.isBarHidden ? background.height : background.vert ? background.height - (bar.height * 1.1) : background.height - (bar.height * 1.1) - leftWidget.height anchors { top: background.vert ? background.top : leftWidget.bottom left: background.vert ? leftWidget.right : parent.left leftMargin: 20 } keys: "NumberClassKey" //shows/hides the objective/instruction MouseArea { anchors.fill: mainZoneArea onClicked: instruction.show() } Rectangle { id: mainZoneAreaDropRectangleVisualisation anchors.fill: parent color: "pink" } onDropped: { console.log("Here is should also drop when dragging with animation, it does only with mouse area dragging") var className = drag.source.name drag.source.dragEnabled = false Activity.appendClassNameColumn(className, drag.source, false) } Rectangle { id: topBanner height: mainZoneArea.height / 10 width: mainZoneArea.width anchors { left: mainZoneArea.left top: mainZoneArea.top } color: "green" Rectangle { id: numberToConvertRectangle anchors.fill: numberToConvertRectangleTxt color: "blue" opacity: 0.8 property alias text: numberToConvertRectangleTxt.text } //display number to convert GCText { id: numberToConvertRectangleTxt height: parent.height width: parent.width / 3 anchors { left: numberToConvertRectangle.left top: numberToConvertRectangle.top } opacity: numberToConvertRectangle.opacity //z: instruction.z fontSize: background.vert ? regularSize : smallSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap } ProgressBar { id: progressBar height: parent.height width: parent.width / 3 property int percentage: 0 maximumValue: 100 visible: true //!items.isTutorialMode anchors { bottom: parent.bottom right: parent.right rightMargin: 40 } GCText { anchors.centerIn: parent fontSize: mediumSize font.bold: true color: "black" //: The following translation represents percentage. text: qsTr("%1%").arg(parent.value) z: 2 } } } //store strings Integer and Decimal to display in numeration table header ListModel { id: numberClassTypeModel } Rectangle { id: numberClassTypeHeader width: mainZoneArea.width height: mainZoneArea.height / 20 anchors.top: topBanner.bottom anchors.left: parent.left anchors.right: parent.right border.color: "blue" border.width: 1 ListView { id: numberClassTypeHeaderListView anchors { fill: parent} model: numberClassTypeModel orientation: ListView.Horizontal delegate: Rectangle { width: numberClassTypeModel.get(index).numberClassTypeHeaderWidth height: numberClassTypeHeader.height border.width: 1 border.color: "black" color: "lightsteelblue" radius: 2 GCText { id: numberClassHeaderCaption anchors.fill: parent anchors.bottom: parent.bottom fontSizeMode: Text.Fit color: "black" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: numberClassTypeModel.get(index).numberClassType visible: numberClassTypeModel.get(index).numberClassTypeHeaderWidth === 0 ? false : true } } } } Rectangle { id: numberClassHeaders width: mainZoneArea.width height: mainZoneArea.height / 10 anchors.top: numberClassTypeHeader.bottom anchors.left: parent.left anchors.right: parent.right border.color: "red" border.width: 1 GCText { id: numberClassHeadersRectangleAdvice height: parent.height width: parent.width anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } opacity: visualModel.count === 0 ? 1 : 0 fontSizeMode: Text.Fit color: "black" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: qsTr("Drag here the class numbers") } ListView { id: classNameListView anchors { fill: parent} model: visualModel orientation: ListView.Horizontal interactive: false cacheBuffer: 50 } DelegateModel { id: visualModel model: numberClassListModel delegate: nnumberClassHeaderElement //Qt.createComponent("NumberClassHeaderElement.qml") //ask johnny for some help there } } Component { // ? what to do when removed element ? TypeError: Cannot read property 'misplaced' of undefined id: nnumberClassHeaderElement MouseArea { id: dragArea property bool held: false width: mainZoneArea.width / numberClassListModel.count height: numberClassHeaders.height drag.target: held ? content : undefined drag.axis: Drag.XAxis onPressed: held = true onReleased: { if ((content.x < leftWidget.width) && held) //? don't understand why I have a content.x = 0 when held is not true, this point needs to be cleared { console.log("index className element",index) numberClassListModel.get(index).element_src.dragEnabled = true numberClassListModel.remove(index,1) Activity.updateIntegerAndDecimalHeaderWidth() } held = false } Rectangle { id: content anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } width: mainZoneArea.width / numberClassListModel.count height: numberClassHeaders.height / 1.5 - border.width: 1 + border.width: numberClassListModel.get(index).misplaced === true ? 5 : 1 //FIXME: when removing an element of NumberClassList model border color is stilll asked border.color: numberClassListModel.get(index).misplaced === true ? "red" : "lightsteelblue" color: dragArea.held ? "lightsteelblue" : "white" Behavior on color { ColorAnimation { duration: 100 } } radius: 2 Drag.active: dragArea.held Drag.source: dragArea Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 states: State { when: dragArea.held ParentChange { target: content; parent: root } AnchorChanges { target: content anchors { horizontalCenter: undefined; verticalCenter: undefined } } } GCText { id: numberClassHeaderCaption anchors.fill: parent anchors.bottom: parent.bottom fontSizeMode: Text.Fit color: "black" verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: numberClassListModel.get(index).name //here there a problem when removing a number class z: 100 } } DropArea { anchors { fill: parent; margins: 10 } onEntered: { console.log("dragArea.DelegateModel.itemsIndex",dragArea.DelegateModel.itemsIndex) console.log("classNameListView.count",classNameListView.count) //move class name columns except the decimal part which stays always on the last position if (dragArea.DelegateModel.itemsIndex < classNameListView.count - 1) { numberClassListModel.move(drag.source.DelegateModel.itemsIndex, dragArea.DelegateModel.itemsIndex,1) } } } } } RowLayout { id: numberClassDropAreasGridLayout + property alias numberClassDropAreaRepeater: numberClassDropAreaRepeater + anchors.top: numberClassHeaders.bottom width: parent.width height: parent.height - topBanner.height - numberClassHeaders.height - numberClassTypeHeader.height spacing: 10 Repeater { id: numberClassDropAreaRepeater model: numberClassListModel NumberClassDropArea { id: numberClassDropAreaElement //property alias numberWeightDragListModel: activity.background.numberWeightDragListModel //? className: name //name comes from numberClassListModel Layout.fillHeight: true Layout.fillWidth: true Layout.minimumWidth: 50 Layout.preferredWidth: 100 } } } Tutorial { id: tutorialSection tutorialText.anchors.top: undefined tutorialText.anchors.bottom: tutorialSection.bottom tutorialText.anchors.margins: tutorialSection.tutorialText.height tutorialDetails: Activity.tutorialInstructions useImage: false onSkipPressed: { Activity.initLevel() tutorialImage.visible = false } } } ListModel { id: numberClassListModel } ListModel { id: numberClassDragListModel } ListModel { id: numberWeightDragListModel } //instruction rectangle Rectangle { id: instruction anchors.fill: instructionTxt opacity: 0.8 radius: 10 border.width: 2 z: 10 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } property alias text: instructionTxt.text Behavior on opacity { PropertyAnimation { duration: 200 } } //shows/hides the Instruction MouseArea { anchors.fill: parent onClicked: instruction.hide() enabled: instruction.opacity !== 0 } function show() { if(text) opacity = 0.8 } function hide() { opacity = 0 } } //display level objective GCText { id: instructionTxt anchors { top: background.vert ? parent.top : leftWidget.bottom topMargin: -10 horizontalCenter: background.horizontalCenter } opacity: instruction.opacity z: instruction.z fontSize: background.vert ? regularSize : smallSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter width: Math.max(Math.min(parent.width * 0.8, text.length * 8), parent.width * 0.3) wrapMode: TextEdit.WordWrap } //display level objective GCText { id: warningTxt anchors { horizontalCenter: background.horizontalCenter verticalCenter: background.verticalCenter } opacity: warningRectangle.opacity z: warningRectangle.z + 1 fontSize: regularSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter width: Math.max(Math.min(parent.width * 0.8, text.length * 8), parent.width * 0.3) wrapMode: TextEdit.WordWrap } Rectangle { id: warningRectangle anchors.fill: warningTxt opacity: 0 radius: 10 border.width: 2 z: 10 border.color: "black" gradient: Gradient { GradientStop { position: 0.0; color: "#000" } GradientStop { position: 0.9; color: "#666" } GradientStop { position: 1.0; color: "#AAA" } } property alias text: warningTxt.text Behavior on opacity { PropertyAnimation { duration: 200 } } //shows/hides the Instruction MouseArea { anchors.fill: parent onClicked: warningRectangle.hide() enabled: warningRectangle.opacity !== 0 } function show() { if(text) opacity = 0.8 } function hide() { opacity = 0 } } //dragable weights list (leftwidget) Rectangle { id: leftWidget width: background.vert ? items.cellSize * 1.74 : background.width height: background.vert ? background.height : items.cellSize * 1.74 color: "#FFFF42" border.color: "#FFD85F" border.width: 4 z: 4 //grid with ok button and the different draggable number weights Flickable { id: flickableElement anchors.fill: parent width: background.height height: leftWidget.width //contentHeight: gridView.height contentHeight: gridView.height * 1.8 //? contentWidth: leftWidget.width boundsBehavior: Flickable.DragAndOvershootBounds Grid { id: gridView x: 10 y: 10 width: parent.width height: background.height //width: background.vert ? leftWidget.width : 3 * bar.height // height: background.vert ? background.height - 2 * bar.height : bar.height spacing: 10 columns: background.vert ? 1 : 5 //ok button Image { id: okButton source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: items.cellSize * 1.5 fillMode: Image.PreserveAspectFit MouseArea { id: mouseArea anchors.fill: parent enabled: background.finished ? false : true onPressed: okButton.opacity = 0.6 onReleased: okButton.opacity = 1 onClicked: { Activity.checkAnswer() } } } // numbers classes drag elements Repeater { id: numberClassDragElements model: numberClassDragListModel NumberClassDragElement { id: classDragElement name: model.name color: model.color Drag.keys: model.dragkeys } } // numbers columns weights and numbers weigths drag elements Repeater { id: numberWeightDragElements model: numberWeightDragListModel NumberWeightDragElement { id: weightComponentDragElement name: model.name imageName: model.imageName Drag.keys: model.dragkeys weightValue: model.weightValue caption: model.caption color: model.color selected: model.selected } } } } } //bar buttons DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | reload | config} onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onReloadClicked: Activity.reloadRandom() //? onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } } Bonus { id: bonus } } } diff --git a/src/activities/numeration_weights_integer/numeration_weights_integer.js b/src/activities/numeration_weights_integer/numeration_weights_integer.js index 8a9c24d18..bf7e214c8 100644 --- a/src/activities/numeration_weights_integer/numeration_weights_integer.js +++ b/src/activities/numeration_weights_integer/numeration_weights_integer.js @@ -1,600 +1,596 @@ /* GCompris - numeration.js * * Copyright (C) 2019 Emmanuel Charruau * * 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 currentLevel = 0 var numberOfLevel = 0 //? var items var numbersToConvert = [] var originalNumbersToConvert = [] var numbersCorrectlyAnswered = [] var scorePercentage = 0 var scorePourcentageStep = 0 var wrongAnswerAlreadyGiven = false var selectedNumberWeightDragElementIndex = -1 var numberHasADecimalPart = false var fullClassNamesConstantArray = ["Decimal Part","Unit class","Thousand class","Million class","Milliard class"] var classNamesUsedArray var numberClassesObj = { "Decimal Part": { name: qsTr("Decimal Part"), color: "black", dragkeys: "NumberClassKey"}, "Unit class": { name: qsTr("Unit class"), color: "black", dragkeys: "NumberClassKey"}, "Thousand class": { name: qsTr("Thousand class"), color: "black", dragkeys: "NumberClassKey"}, "Million class": { name: qsTr("Million class"), color: "black", dragkeys: "NumberClassKey"}, "Milliard class": { name: qsTr("Milliard class"), color: "black", dragkeys: "NumberClassKey"} } var numberWeightsColumnsArray = ["HundredColumn","TenColumn","UnitColumn"] var numberWeightComponentConstantArray = ["UnitColumn","TenColumn","HundredColumn","Unit","Ten","Hundred","Thousand","TenThousand", "OneHundredThousand","OneMillion","TenMillion","OneHundredMillion", "OneMilliard","TenMilliard","OneHundredMilliard"] var numberWeightDragArray = { "UnitColumn": { name: qsTr("Unit"), caption: "Unit", imageName: "", weightValue: "1", dragkeys: "numberWeightHeaderKey", color: "lightskyblue", selected: false }, "TenColumn": { name: qsTr("Ten"), caption: "Ten", imageName: "", weightValue: "10", dragkeys: "numberWeightHeaderKey", color: "lightskyblue", selected: false }, "HundredColumn": { name: qsTr("Hundred"), caption: "Hundred", imageName: "", weightValue: "100", dragkeys: "numberWeightHeaderKey", color: "lightskyblue", selected: false }, "Unit": { name: qsTr("Unit"), caption: "", imageName: "unit.svg", weightValue: "1", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "Ten": { name: qsTr("Unit"), caption: "", imageName: "ten.svg", weightValue: "10", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "Hundred": { name: qsTr("Unit"), caption: "", imageName: "hundred.svg", weightValue: "100", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "Thousand": { name: qsTr("Unit"), caption: "1000", imageName: "weightCaption.svg", weightValue: "1000", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "TenThousand": { name: qsTr("Unit"), caption: "10 000", imageName: "weightCaption.svg", weightValue: "10000", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "OneHundredThousand": { name: qsTr("Unit"), caption: "100 000", imageName: "weightCaption.svg", weightValue: "100000", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "OneMillion": { name: qsTr("Unit"), caption: "1 000 000", imageName: "weightCaption.svg", weightValue: "1000000", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "TenMillion": { name: qsTr("Unit"), caption: "10 000 000", imageName: "weightCaption.svg", weightValue: "10000000", dragkeys: "numberWeightKey", color: "transparent", selected: false }, "OneHundredMillion": { name: qsTr("Unit"), caption: "100 000 000", imageName: "weightCaption.svg", weightValue: "100000000", dragkeys: "numberWeightKey", color: "transparent" , selected: false }, "OneMilliard": { name: qsTr("Unit"), caption: "1 000 000 000", imageName: "weightCaption.svg", weightValue: "1000000000", dragkeys: "numberWeightKey", color: "transparent" , selected: false }, "TenMilliard": { name: qsTr("Unit"), caption: "10 000 000 000", imageName: "weightCaption.svg", weightValue: "10000000000", dragkeys: "numberWeightKey", color: "transparent" , selected: false }, "OneHundredMilliard": { name: qsTr("Unit"), caption: "100 000 000 000", imageName: "weightCaption.svg", weightValue: "100000000000", dragkeys: "numberWeightKey", color: "transparent" , selected: false } } // for what is used name in numberWeightDragArray ? //? var numberClassTypeColumnsArray = ["Integer Part","Decimal Part"] var tutorialInstructions = [ { "instruction": qsTr("This activity teaches how to place numbers weights to represent a number quantity.") - //"instructionQml" : "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial1.qml" }, { "instruction": qsTr("Before to enter any number weights you have to enter the number classes (unit class only if numbers are less than 1000 and unit classes and thousand class if they are more than 999.") - //"instructionQml" : "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial2.qml" }, { "instruction": qsTr("Here using drag and drop we add the unit class and the thousand class."), "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial1.qml" }, { - "instruction": qsTr("Binary system uses these numbers very efficiently, allowing to count from 0 to 255 with 8 bits only."), - "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial4.qml" + "instruction": qsTr("The we have to enter the weight name in the rights columns.") }, { - "instruction": qsTr("Each bit adds a progressive value, corresponding to the powers of 2, ascending from right to left: bit 1 → 2⁰=1 , bit 2 → 2¹=2 , bit 3 → 2²=4 , bit 4 → 2³=8 , bit 5 → 2⁴=16 , bit 6 → 2⁵=32 , bit 7 → 2⁶=64 , bit 8 → 2⁷=128."), - "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial5.qml" + "instruction": qsTr("Here using drag and drop we add the weight unit in the unit column."), + "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial2.qml" }, { "instruction": qsTr("To convert a decimal 5 to a binary value, 1 and 4 are added."), "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial6.qml" }, { "instruction": qsTr("Their corresponding bits are set to 1, the others set to 0. Decimal 5 is equal to binary 101."), "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial7.qml" }, { "instruction": qsTr("This image will help you to compute bits' value."), "instructionQml": "qrc:/gcompris/src/activities/numeration_weights_integer/resource/tutorial5.qml" } ] function removeClassInNumberClassesArray(className) { console.log(numberClassesArray) var index = numberClassesArray.indexOf(className); if (index > -1) { numberClassesArray.splice(index, 1); } console.log(numberClassesArray) } function removeClassInNumberClassesArray() { numberClassesArray.pop(numberClass) } function setNumberWeightHeader(numberWeightImageTile,imageName,caption,weightValue) { if ( imageName !== "") { numberWeightImageTile.source = "qrc:/gcompris/src/activities/numeration_weights_integer/resource/images/" + imageName } numberWeightImageTile.caption = caption numberWeightImageTile.weightValue = weightValue } function setNumberWeightComponent(numberWeightImageTile,imageName,caption,weightValue) { if ( imageName !== "") { numberWeightImageTile.source = "qrc:/gcompris/src/activities/numeration_weights_integer/resource/images/" + imageName numberWeightImageTile.caption = caption numberWeightImageTile.weightValue = weightValue } } function removeNumberWeightComponent(numberWeightImageTile) { numberWeightImageTile.source = "" numberWeightImageTile.caption = "" numberWeightImageTile.weightValue = "" numberWeightImageTile.border.color = "black" } function resetNumerationTable() { for (var i = 0; i=0; i--,classNamesUsedIndex++) { if (items.numberClassListModel.get(i).name === classNamesUsedArray[classNamesUsedIndex]) { items.numberClassListModel.setProperty(i, "misplaced", false) } else { items.numberClassListModel.setProperty(i, "misplaced", true) allClassesColumnsInRightPositions = false } } return allClassesColumnsInRightPositions } function expectedAndEnteredValuesAreEquals() { var enteredValue = readNumerationTableEnteredValue() console.log("enteredValue/expected value:",enteredValue + " / " + parseInt(numbersToConvert[0],10)) //test if entered value is equal to number expected if (enteredValue === parseInt(numbersToConvert[0],10)) { return true } else { return false } } function checkEnteredValue() { var _expectedAndEnteredValuesAreEquals = expectedAndEnteredValuesAreEquals() if (_expectedAndEnteredValuesAreEquals) { return true } else { return false } } function evaluateAndDisplayProgresses(correctAnswer) { if (correctAnswer) { wrongAnswerAlreadyGiven = false items.bonus.good("flower") console.log("correct: scorePercentage before incrementation",scorePercentage) numbersCorrectlyAnswered.push(numbersToConvert.shift()) //remove first element and copy it in numbersCorrectlyAnswered scorePercentage = scorePercentage + scorePourcentageStep console.log("correct: scorePercentage after incrementation",scorePercentage) items.progressBar.value = scorePercentage if (scorePercentage > 97) { return true } items.numberToConvertRectangle.text = numbersToConvert[0] console.log("original NumbersToConvert: " + originalNumbersToConvert) console.log("NumbersToConvert: " + numbersToConvert) console.log("numbersCorrectlyAnswered: " + numbersCorrectlyAnswered) return } else { items.bonus.bad("flower") items.numberToConvertRectangle.text = numbersToConvert[0] console.log("incorrect: scorePercentage before incrementation",scorePercentage) console.log("wrongAnswerAlreadyGiven: ", wrongAnswerAlreadyGiven) if (wrongAnswerAlreadyGiven === false) { scorePercentage = scorePercentage - scorePourcentageStep if (scorePercentage < 0) scorePercentage = 0 items.progressBar.value = scorePercentage if (numbersToConvert.length < 2) { numbersToConvert.splice(2, 0, numbersCorrectlyAnswered[Math.floor(Math.random() * numbersCorrectlyAnswered.length)]) } console.log("number to convert before splice: ",numbersToConvert) numbersToConvert.splice(2, 0, numbersToConvert[0]); console.log("number to convert after splice: ",numbersToConvert) wrongAnswerAlreadyGiven = true console.log("incorrect: scorePercentage after incrementation",scorePercentage) } console.log("NumbersToConvert length: " + numbersToConvert.length) console.log("original NumbersToConvert: " + originalNumbersToConvert) console.log("NumbersToConvert: " + numbersToConvert) console.log("numbersCorrectlyAnswered: " + numbersCorrectlyAnswered) return false } } function start(items_) { items = items_ currentLevel = 0 setNumberWeightDragListModel(numberWeightComponentConstantArray) initLevel() numberOfLevel = items.levels.length // ? } function setNumberClassTypeListModel() { items.numberClassTypeModel.append({"numberClassType": "Integer Part", "numberClassTypeHeaderWidth": 0}) if (hasNumberADecimalPart()) { addDecimalHeaderToNumberClassTypeModel() } } function setClassNamesUsedArray(fullClassNamesArray) { var smallerNumberClass = items.levels[currentLevel].smallerNumberClass var biggerNumberClass = items.levels[currentLevel].biggerNumberClass if (!isClassNamePresentInfullClassNamesArray(fullClassNamesArray, smallerNumberClass)) { return fullClassNamesConstantArray } if (!isClassNamePresentInfullClassNamesArray(fullClassNamesArray, biggerNumberClass)) { return fullClassNamesConstantArray } return fullClassNamesArray.slice(fullClassNamesArray.indexOf(smallerNumberClass),fullClassNamesArray.indexOf(biggerNumberClass)+1) } function isClassNamePresentInfullClassNamesArray(fullClassNamesArray, className) { if (fullClassNamesArray.indexOf(className) !== -1) { return true } else { items.warningRectangle.text = qsTr("The class name \"" + className + "\" is not present in the available list: \"" + fullClassNamesArray+ "\". Check your configuration file (lower case or uppercase error?).") items.warningRectangle.show() return false } } function hasNumberADecimalPart() { for (var i=0; i * * Authors: * Timothée Giet * * 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.13 import GCompris 1.0 import "../../../core" import "../../../activities" Item { id: tutorial1 property bool animationIsRunning: animationIsRunning property int unitClassDragButtonOrigX property int unitClassDragButtonOrigY property int thousandClassDragButtonOrigX property int thousandClassDragButtonOrigY Component.onCompleted: { console.log("tutorial1_screen_loaded") animUnitClassX.running = true animUnitClassY.running = true } NumberAnimation{ id: animUnitClassX target: numberClassDragElements.itemAt(0) property: "x"; to: background.width * 2/3 - duration: 3000 + duration: 300 //0 onStarted: { unitClassDragButtonOrigX = numberClassDragElements.itemAt(0).x unitClassDragButtonOrigY = numberClassDragElements.itemAt(0).y animationIsRunning = true numberClassDragElements.itemAt(0).animationIsRunning = animationIsRunning console.log("onStarted") } onFinished: { numberClassDragElements.itemAt(0).Drag.drop() console.log("Sent Drag drop") animationIsRunning = false numberClassDragElements.itemAt(0).x = unitClassDragButtonOrigX - numberClassDragElements.itemAt(0).z = 1000 animThousandClassX.running = true animThousandClassY.running = true } } NumberAnimation{ id: animUnitClassY target: numberClassDragElements.itemAt(0) property: "y"; to: background.height * 1/3 - duration: 3000 - + duration: 300 //0 onFinished: { numberClassDragElements.itemAt(0).y = unitClassDragButtonOrigY } - } NumberAnimation{ id: animThousandClassX target: numberClassDragElements.itemAt(1) property: "x"; to: background.width * 1/3 - duration: 3000 + duration: 300 //0 onStarted: { thousandClassDragButtonOrigX = numberClassDragElements.itemAt(1).x thousandClassDragButtonOrigY = numberClassDragElements.itemAt(1).y animationIsRunning = true numberClassDragElements.itemAt(1).animationIsRunning = animationIsRunning console.log("onStarted") } onFinished: { numberClassDragElements.itemAt(1).Drag.drop() console.log("Sent Drag drop") animationIsRunning = false numberClassDragElements.itemAt(1).x = thousandClassDragButtonOrigX numberClassDragElements.itemAt(1).z = 1000 } } NumberAnimation{ id: animThousandClassY target: numberClassDragElements.itemAt(1) property: "y"; to: background.height * 1/3 - duration: 3000 - + duration: 300 //0 onFinished: { numberClassDragElements.itemAt(1).y = thousandClassDragButtonOrigY } - } - - } diff --git a/src/activities/numeration_weights_integer/resource/tutorial2.qml b/src/activities/numeration_weights_integer/resource/tutorial2.qml index c846e841a..19e0d891c 100644 --- a/src/activities/numeration_weights_integer/resource/tutorial2.qml +++ b/src/activities/numeration_weights_integer/resource/tutorial2.qml @@ -1,93 +1,132 @@ /* GCompris - tutorial1.qml * * Copyright (C) 2018 Timothée Giet * * Authors: * Timothée Giet * * 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.13 import GCompris 1.0 import "../../../core" import "../../../activities" - -Rectangle { +Item { id: tutorial2 - Component.onCompleted: { - tutorial2.state = '' - tutorial2.state === '' ? tutorial2.state = 'other' : tutorial2.state = '' - console.log("test") - animid.running = true + property bool animationIsRunning: animationIsRunning + property int unitWeightColumnDragButtonOrigX + property int unitWeightColumnDragButtonOrigY + property int thousandClassDragButtonOrigX + property int thousandClassDragButtonOrigY + property int animationSequenceIndex: 0 + property int numberClassIndex: 1 + property int numberColumnWeightIndex: 2 + property int numberColumnWeightDragButtonIndex: 0 + property int headerOriginY + Component.onCompleted: { + console.log("tutorial2_screen_loaded") + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true + headerOriginY = numberClassDropAreaRepeater.itemAt(0).numberWeightsDropAreasRepeater.itemAt(0).mapToItem(activity, 0, 0).y } - anchors.fill: parent - color: "#80FFFFFF" - - states: [ - // This adds a second state to the container where the rectangle is farther to the right - - State { name: "other" - - PropertyChanges { - target: numberClassDragElements.itemAt(0) - x: 500 - y: 500 + NumberAnimation { + id: animUnitColumnWeightX + target: numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex) + property: "x"; + to: numberClassDropAreaRepeater.itemAt(numberClassIndex).numberWeightsDropAreasRepeater.itemAt(numberColumnWeightIndex).mapToItem(activity, 0, 0).x + duration: 3000 + onStarted: { + unitWeightColumnDragButtonOrigX = numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x + unitWeightColumnDragButtonOrigY = numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).y + animationIsRunning = true + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).animationIsRunning = animationIsRunning + } + onFinished: { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).Drag.drop() + animationIsRunning = false + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).z = 1000 + if (animationSequenceIndex === 0) { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + animationSequenceIndex = 1 + numberClassIndex = 1 + numberColumnWeightIndex = 1 + numberColumnWeightDragButtonIndex = 1 + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true + animUnitColumnWeightX.start() + } else if (animationSequenceIndex === 1) { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + animationSequenceIndex = 2 + numberClassIndex = 1 + numberColumnWeightIndex = 0 + numberColumnWeightDragButtonIndex = 2 + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true + animUnitColumnWeightX.start() + console.log("fail") + } else if (animationSequenceIndex === 2) { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + animationSequenceIndex = 3 + numberClassIndex = 0 + numberColumnWeightIndex = 2 + numberColumnWeightDragButtonIndex = 0 + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true + animUnitColumnWeightX.start() + } else if (animationSequenceIndex === 3) { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + animationSequenceIndex = 4 + numberClassIndex = 0 + numberColumnWeightIndex = 1 + numberColumnWeightDragButtonIndex = 1 + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true + } else if (animationSequenceIndex === 4) { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).x = unitWeightColumnDragButtonOrigX + animationSequenceIndex = 5 + numberClassIndex = 0 + numberColumnWeightIndex = 0 + numberColumnWeightDragButtonIndex = 2 + animUnitColumnWeightX.running = true + animUnitColumnWeightY.running = true } } - ] - transitions: [ - // This adds a transition that defaults to applying to all state changes - - Transition { - - // This applies a default NumberAnimation to any changes a state change makes to x or y properties - NumberAnimation { - id: animid - - - - properties: "x,y" - onRunningChanged: { - console.log("onRunningChanged") - } + } + NumberAnimation { + id: animUnitColumnWeightY - onStarted: { + target: numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex) + property: "y"; + //to: numberClassDropAreaRepeater.itemAt(numberClassIndex).numberWeightsDropAreasRepeater.itemAt(numberColumnWeightIndex).mapToItem(activity, 0, 0).y + to : 33 //headerOriginY + duration: 3000 + onFinished: { + numberWeightDragElements.itemAt(numberColumnWeightDragButtonIndex).y = unitWeightColumnDragButtonOrigY + } + } - /* animationIsRunning = true - numberClassDragElements.itemAt(0).Drag.active = true //? had to add this line why? - numberClassDragElements.itemAt(0).animationIsRunning = animationIsRunning //? why is that not enough to set Drag.active? - numberClassDragElements.itemAt(0).Drag.startDrag()*/ - console.log("onStarted") - } - onFinished: { - // numberClassDragElements.itemAt(0).Drag.drop() - console.log("Sent Drag drop") - // animationIsRunning = false - } - } - } - ] }