diff --git a/src/activities/babymatch/Babymatch.qml b/src/activities/babymatch/Babymatch.qml index 73e83e252..8a890c183 100644 --- a/src/activities/babymatch/Babymatch.qml +++ b/src/activities/babymatch/Babymatch.qml @@ -1,296 +1,295 @@ /* GCompris - Babymatch.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Bruno Coudoin (GTK+ version) * Pulkit Gupta (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.1 import GCompris 1.0 import "../../core" import "babymatch.js" as Activity ActivityBase { id: activity // In most cases, these 3 are the same. // But for imageName for example, we reuse the images of babymatch, so we need to differentiate them property string imagesUrl: boardsUrl property string soundsUrl: boardsUrl property string boardsUrl: "qrc:/gcompris/src/activities/babymatch/resource/" property int levelCount: 7 property bool answerGlow: true //For highlighting the answers property bool displayDropCircle: true //For displaying drop circles onStart: focus = true onStop: {} pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop property bool vert: background.width > background.height Component.onCompleted: { 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 alias availablePieces: availablePieces property alias backgroundPiecesModel: backgroundPiecesModel property alias file: file property alias grid: grid property alias backgroundImage: backgroundImage property alias leftWidget: leftWidget property alias instruction: instruction property alias toolTip: toolTip property alias score: score property alias dataset: dataset } Loader { id: dataset asynchronous: false } onStart: { Activity.start(items, imagesUrl, soundsUrl, boardsUrl, levelCount, answerGlow, displayDropCircle) } onStop: { Activity.stop() } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: {displayDialog(dialogHelp)} onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Score { id: score visible: numberOfSubLevels > 1 } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } File { id: file name: "" } Rectangle { id: leftWidget width: background.vert ? 90 * ApplicationInfo.ratio : background.width height: background.vert ? background.height : 90 * ApplicationInfo.ratio color: "#FFFF42" border.color: "#FFD85F" border.width: 4 anchors.left: parent.left ListWidget { id: availablePieces vert: background.vert } } Rectangle { id: toolTip anchors { bottom: bar.top bottomMargin: 10 left: leftWidget.left leftMargin: 5 } width: toolTipTxt.width + 10 height: toolTipTxt.height + 5 opacity: 1 radius: 10 z: 100 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" } } property alias text: toolTipTxt.text Behavior on opacity { NumberAnimation { duration: 120 } } function show(newText) { if(newText) { text = newText opacity = 0.8 } else { opacity = 0 } } GCText { id: toolTipTxt anchors.centerIn: parent fontSize: regularSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter wrapMode: TextEdit.WordWrap - text: "coucou" } } Rectangle { id: grid color: "transparent" z: 2 x: background.vert ? 90 * ApplicationInfo.ratio : 0 y: background.vert ? 0 : 90 * ApplicationInfo.ratio width: background.vert ? background.width - 90 * ApplicationInfo.ratio : background.width height: background.vert ? background.height - (bar.height * 1.1) : background.height - (bar.height * 1.1) - 90 * ApplicationInfo.ratio Image { id: backgroundImage fillMode: Image.PreserveAspectFit property double ratio: sourceSize.width / sourceSize.height property double gridRatio: grid.width / grid.height property alias instruction: instruction source: "" z: 2 width: source == "" ? grid.width : (ratio > gridRatio ? grid.width : grid.height * ratio) height: source == "" ? grid.height : (ratio < gridRatio ? grid.height : grid.width / ratio) anchors.topMargin: 10 anchors.centerIn: parent //Inserting static background images Repeater { id: backgroundPieces model: backgroundPiecesModel delegate: piecesDelegate z: 2 Component { id: piecesDelegate Image { id: shapeBackground source: Activity.imagesUrl + imgName x: posX * backgroundImage.width - width / 2 y: posY * backgroundImage.height - height / 2 height: imgHeight ? imgHeight * backgroundImage.height : (backgroundImage.source == "" ? backgroundImage.height * shapeBackground.sourceSize.height / backgroundImage.height : backgroundImage.height * shapeBackground.sourceSize.height / backgroundImage.sourceSize.height) width: imgWidth ? imgWidth * backgroundImage.width : (backgroundImage.source == "" ? backgroundImage.width * shapeBackground.sourceSize.width / backgroundImage.width : backgroundImage.width * shapeBackground.sourceSize.width / backgroundImage.sourceSize.width) fillMode: Image.PreserveAspectFit } } } MouseArea { anchors.fill: parent onClicked: (instruction.opacity === 0 ? instruction.show() : instruction.hide()) } } } Rectangle { id: instruction anchors.fill: instructionTxt opacity: 0.8 radius: 10 z: 3 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" } } property alias text: instructionTxt.text Behavior on opacity { PropertyAnimation { duration: 200 } } function show() { if(text) opacity = 1 } function hide() { opacity = 0 } } GCText { id: instructionTxt anchors { top: background.vert ? grid.top : leftWidget.bottom topMargin: -10 horizontalCenter: background.vert ? grid.horizontalCenter : leftWidget.horizontalCenter } opacity: instruction.opacity z: instruction.z fontSize: regularSize color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter width: Math.max(Math.min(parent.width * 0.9, text.length * 11), parent.width * 0.3) wrapMode: TextEdit.WordWrap } ListModel { id: backgroundPiecesModel } } } diff --git a/src/activities/babymatch/ListWidget.qml b/src/activities/babymatch/ListWidget.qml index d767c5218..ad9c2c8e5 100644 --- a/src/activities/babymatch/ListWidget.qml +++ b/src/activities/babymatch/ListWidget.qml @@ -1,279 +1,278 @@ /* gcompris - ListWidget.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Pulkit Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import GCompris 1.0 import "../../core" import "babymatch.js" as Activity Item { id: listWidget anchors.fill: parent anchors.topMargin: 5 * ApplicationInfo.ratio anchors.leftMargin: 5 * ApplicationInfo.ratio z: 10 property bool vert - property alias model: mymodel; - property alias view: view; - property alias showOk : showOk - property alias hideOk : hideOk - property alias repeater : repeater + property alias model: mymodel + property alias view: view + property alias showOk: showOk + property alias hideOk: hideOk + property alias repeater: repeater ListModel { id: mymodel } PropertyAnimation { id: showOk target: ok properties: "height" from: 0 to: view.iconSize * 0.9 duration: 300 onStopped: { view.okShowed = true instruction.show() } - } PropertyAnimation { id: hideOk target: ok properties: "height" from: view.iconSize * 0.9 to: 0 duration: 200 onStopped: view.checkDisplayedGroup() } Image { id: ok source:"qrc:/gcompris/src/core/resource/bar_ok.svg" sourceSize.width: view.iconSize fillMode: Image.PreserveAspectFit anchors.horizontalCenter: parent.horizontalCenter MouseArea { anchors.fill: parent onClicked: view.checkAnswer() } } Grid { id: view width: listWidget.vert ? leftWidget.width : 2 * bar.height height: listWidget.vert ? background.height - 2 * bar.height : bar.height spacing: 10 z: 20 columns: listWidget.vert ? 1 : nbItemsByGroup + 1 property int currentDisplayedGroup: 0 property int setCurrentDisplayedGroup property int nbItemsByGroup: listWidget.vert ? parent.height / iconSize - 2 : parent.width / iconSize - 2 property int nbDisplayedGroup: Math.ceil(model.count / nbItemsByGroup) property int iconSize: 80 * ApplicationInfo.ratio property int previousNavigation: 1 property int nextNavigation: 1 property bool okShowed: false property bool showGlow: false property var displayedGroup: [] property alias ok: ok onNbDisplayedGroupChanged: correctDisplayedGroup() add: Transition { NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 } NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 } } move: Transition { NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutBounce } } // For correcting values of Displayed Groups when height or width is changed function correctDisplayedGroup() { if (nbDisplayedGroup > 0) { for(var i = 0 ; i < nbDisplayedGroup ; i++) { var groupEmpty = true for(var j = 0 ; j < nbItemsByGroup, i*nbItemsByGroup + j < model.count ; j++) { if (repeater.itemAt(i*nbItemsByGroup + j).dropStatus < 0) { groupEmpty = false break } } if (groupEmpty) displayedGroup[i] = false else displayedGroup[i] = true } view.refreshLeftWidget() view.checkDisplayedGroup() } } //For setting navigation buttons function setNextNavigation() { nextNavigation = 0 for(var i = currentDisplayedGroup + 1 ; i < nbDisplayedGroup ; i++) { if(displayedGroup[i]) { nextNavigation = i - currentDisplayedGroup break } } } function setPreviousNavigation() { previousNavigation = 0 for(var i = currentDisplayedGroup - 1 ; i >= 0 ; i--) { if(displayedGroup[i]) { previousNavigation = currentDisplayedGroup - i break } } } function checkDisplayedGroup() { var i = currentDisplayedGroup * nbItemsByGroup var groupEmpty = true while(i < model.count && i < (currentDisplayedGroup + 1) * nbItemsByGroup) { if (repeater.itemAt(i).dropStatus < 0) { groupEmpty = false break } i++ } if (groupEmpty) { displayedGroup[currentDisplayedGroup] = false previousNavigation = 0 nextNavigation = 0 for(var i = 0 ; i < nbDisplayedGroup ; ++i) { if(displayedGroup[i]) { view.setCurrentDisplayedGroup = i view.refreshLeftWidget() break } } } } - + function refreshLeftWidget() { availablePieces.view.currentDisplayedGroup = availablePieces.view.setCurrentDisplayedGroup availablePieces.view.setNextNavigation() availablePieces.view.setPreviousNavigation() } function areAllPlaced() { for(var i = 0 ; i < model.count ; ++i) { if(repeater.itemAt(i).dropStatus < 0) { return false } } return true } function checkAnswer() { view.showGlow = true for(var i = 0 ; i < model.count ; ++i) { if(repeater.itemAt(i).dropStatus !== 1) { return } } Activity.win() } Repeater { id: repeater property int currentIndex onCurrentIndexChanged: { for(var i = 0; i < mymodel.count; i++) { if(currentIndex != i) repeater.itemAt(i).selected = false else repeater.itemAt(i).selected = true } if(currentIndex == -1) toolTip.opacity = 0 } DragListItem { id: contactsDelegate z: 1 heightInColumn: view.iconSize * 0.85 widthInColumn: view.iconSize * 0.85 tileWidth: view.iconSize tileHeight: view.iconSize visible: view.currentDisplayedGroup * view.nbItemsByGroup <= index && index <= (view.currentDisplayedGroup+1) * view.nbItemsByGroup-1 onPressed: repeater.currentIndex = index } - + clip: true model: mymodel onModelChanged: repeater.currentIndex = -1 } Row { spacing: view.iconSize * 0.20 - + Image { id: previous opacity: (model.count > view.nbItemsByGroup && view.previousNavigation != 0 && view.currentDisplayedGroup != 0) ? 1 : 0 source:"qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: view.iconSize * 0.35 fillMode: Image.PreserveAspectFit MouseArea { - anchors.fill: parent + anchors.fill: parent onClicked: { repeater.currentIndex = -1 if(previous.opacity == 1) { view.setCurrentDisplayedGroup = view.currentDisplayedGroup - view.previousNavigation view.refreshLeftWidget() } } } } - + Image { id: next visible: model.count > view.nbItemsByGroup && view.nextNavigation != 0 && view.currentDisplayedGroup < view.nbDisplayedGroup - 1 source:"qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: view.iconSize * 0.35 fillMode: Image.PreserveAspectFit MouseArea { anchors.fill: parent onClicked: { repeater.currentIndex = -1 view.setCurrentDisplayedGroup = view.currentDisplayedGroup + view.nextNavigation view.refreshLeftWidget() } } } } } } diff --git a/src/activities/babymatch/TextItem.qml b/src/activities/babymatch/TextItem.qml index 988fea016..47c7a95a5 100644 --- a/src/activities/babymatch/TextItem.qml +++ b/src/activities/babymatch/TextItem.qml @@ -1,79 +1,78 @@ -/* GCompris - DropAnswerItem.qml +/* GCompris - TextItem.qml * * Copyright (C) 2015 Pulkit Gupta * * Authors: * Pulkit Gupta * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import GCompris 1.0 import "../../core" -import "babymatch.js" as Activity Item { id: displayText - + property double posX property double posY property double textWidth property string showText - + x: posX * parent.width y: posY * parent.height - + GCText { id: displayTxt anchors { horizontalCenter: parent.horizontalCenter } property bool firstTime: true fontSize: Math.max(Math.min(displayText.parent.width / 20 , 12), 5) color: "white" style: Text.Outline styleColor: "black" horizontalAlignment: Text.AlignHCenter width: Math.min(implicitWidth, textWidth * displayText.parent.width) wrapMode: TextEdit.WordWrap z: 2 text: showText onHeightChanged: { if(firstTime) { displayTxtContainer.height = displayTxt.height * Math.ceil(displayTxt.implicitWidth / displayTxt.width) firstTime = false } else displayTxtContainer.height = displayTxt.height } } Rectangle { id: displayTxtContainer anchors.top: displayTxt.top anchors.horizontalCenter: displayTxt.horizontalCenter width: displayTxt.width + 10 height: displayTxt.fontSize * 2.25 * Math.ceil(displayTxt.implicitWidth / displayTxt.width) z: 1 opacity: 0.8 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" } } } } diff --git a/src/activities/chess/chess.js b/src/activities/chess/chess.js index ae6268a67..3b9b7b41b 100644 --- a/src/activities/chess/chess.js +++ b/src/activities/chess/chess.js @@ -1,342 +1,341 @@ /* GCompris - chess.js * * Copyright (C) 2015 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 . */ .pragma library .import QtQuick 2.0 as Quick .import "qrc:/gcompris/src/core/core.js" as Core .import "engine.js" as Engine var url = "qrc:/gcompris/src/activities/chess/resource/" var currentLevel var numberOfLevel var items var state function start(items_) { items = items_ currentLevel = 0 numberOfLevel = items.fen.length initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 state = Engine.p4_fen2state(items.fen[currentLevel][1]) items.from = -1 items.gameOver = false items.redo_stack = [] refresh() Engine.p4_prepare(state) items.positions = 0 // Force a model reload items.positions = simplifiedState(state['board']) clearAcceptMove() } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function simplifiedState(state) { var newState = new Array() for(var i = state.length - 1; i >= 0; --i) { if(state[i] != 16) { var img = "" switch(state[i]) { case 2: img = "wp" break case 3: img = "bp" break case 4: img = "wr" break case 5: img = "br" break case 6: img = "wn" break case 7: img = "bn" break case 8: img = "wb" break case 9: img = "bb" break case 10: img = "wk" break case 11: img = "bk" break case 12: img = "wq" break case 13: img = "bq" break default: break } newState.push( { 'pos': engineToViewPos(i), 'img': img }) } } return newState } function updateMessage(move) { items.gameOver = false items.message = items.blackTurn ? qsTr("Black's turn") : qsTr("White's turn") if(!move) return if((move.flags & (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) == (Engine.P4_MOVE_FLAG_CHECK | Engine.P4_MOVE_FLAG_MATE)) { items.message = items.blackTurn ? qsTr("White mates", "white wins") : qsTr("Black mates", "black wins") items.gameOver = true if(!items.twoPlayer) if(state.to_play != 0) items.bonus.good('gnu') else items.bonus.good('tux') else items.bonus.good('flower') } else if((move.flags & Engine.P4_MOVE_FLAG_MATE) == Engine.P4_MOVE_FLAG_MATE) { items.message = qsTr("Drawn game") items.gameOver = true items.bonus.good('flower') } else if((move.flags & Engine.P4_MOVE_FLAG_CHECK) == Engine.P4_MOVE_FLAG_CHECK) { items.message = items.blackTurn ? qsTr("White checks", "black king is under attack") : qsTr("Black checks", "white king is under attack") } else if(move.flags == Engine.P4_MOVE_ILLEGAL) { items.message = qsTr("Invalid, your king may be in check") } } function refresh(move) { items.blackTurn = state.to_play // 0=w 1=b items.history = state.history updateMessage(move) } // Convert view position (QML) to the chess engine coordinate // // The engine manages coordinate into a 120 element array, which is conceptually // a 10x12 board, with the 8x8 board placed at the centre, thus: // + 0123456789 // 0 ########## // 10 ########## // 20 #RNBQKBNR# // 30 #PPPPPPPP# // 40 #........# // 50 #........# // 60 #........# // 70 #........# // 80 #pppppppp# // 90 #rnbqkbnr# //100 ########## //110 ########## // // In QML each cell is in the regular range [0-63] // function viewPosToEngine(pos) { return (Math.floor(pos / 8) + 2) * 10 + pos % 8 + 1 } // Convert chess engine coordinate to view position (QML) function engineToViewPos(pos) { var newpos = pos - 21 - (Math.floor((pos - 20) / 10) * 2) return newpos } // move is the result from the engine move function visibleMove(move, from, to) { items.pieces.moveTo(from, to) // Castle move if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_KING) items.pieces.moveTo(from + 3, to - 1) else if(move.flags & Engine.P4_MOVE_FLAG_CASTLE_QUEEN) items.pieces.moveTo(from - 4, to + 1) else if(items.pieces.getPieceAt(to).img == 'wp' && to > 55) items.pieces.promotion(to) else if(items.pieces.getPieceAt(to).img == 'bp' && to < 8) items.pieces.promotion(to) } function computerMove() { var computer = state.findmove(3) var move = state.move(computer[0], computer[1]) if(move.ok) { visibleMove(move, engineToViewPos(computer[0]), engineToViewPos(computer[1])) refresh(move) } return move } function moveTo(from, to) { var move = state.move(viewPosToEngine(from), viewPosToEngine(to)) if(move.ok) { visibleMove(move, from, to) refresh(move) clearAcceptMove() items.redo_stack = [] if(!items.twoPlayer) items.trigComputerMove.start() items.from = -1; } else { // Probably a check makes the move is invalid updateMessage(move) } return move.ok } function undo() { var redo_stack = items.redo_stack redo_stack.push(state.history[state.moveno - 1]) state.jump_to_moveno(state.moveno - 1) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play if(!items.twoPlayer && state.to_play != 0) { redo_stack.push(state.history[state.moveno - 1]) state.jump_to_moveno(state.moveno - 1) } items.redo_stack = redo_stack refresh() items.positions = [] // Force a model reload items.positions = simplifiedState(state['board']) } function moveByEngine(engineMove) { if(!engineMove) return var move = state.move(engineMove[0], engineMove[1]) visibleMove(move, engineToViewPos(engineMove[0]), engineToViewPos(engineMove[1])) refresh(move) } function redo() { var redo_stack = items.redo_stack moveByEngine(items.redo_stack.pop()) // In computer mode, the white always starts, take care // of undo after a mate which requires us to revert on // a white play if(!items.twoPlayer && state.to_play != 0) { items.redoTimer.moveByEngine(items.redo_stack.pop()) } // Force refresh items.redo_stack = [] items.redo_stack = redo_stack } // Random move depending on the level function randomMove() { if(!items.difficultyByLevel) { computerMove() return } // Disable random move if the situation is too bad for the user // This avoid having the computer playing bad against a user // with too few pieces making the game last too long var score = getScore() - console.log(score[0] / score[1]) if(score[0] / score[1] < 0.7) { computerMove() return } // At level 2 we let the computer play 20% of the time // and 80% of the time we make a random move. if(Math.random() < currentLevel / (numberOfLevel - 1)) { computerMove() return } // Get all possible moves var moves = Engine.p4_parse(state, state.to_play, 0, 0) moves = Core.shuffle(moves) var move = state.move(moves[0][1], moves[0][2]) if(move.ok) { visibleMove(move, engineToViewPos(moves[0][1]), engineToViewPos(moves[0][2])) refresh(move) } else { // Bad move, should not happens computerMove() } } // Clear all accept move marker from the chessboard function clearAcceptMove() { for(var i=0; i < items.positions.length; ++i) items.squares.getSquareAt(i)['acceptMove'] = false } // Highlight the possible moves for the piece at position 'from' function showPossibleMoves(from) { var result = Engine.p4_parse(state, state.to_play, 0, 0) clearAcceptMove() for(var i=0; i < result.length; ++i) { if(viewPosToEngine(from) == result[i][1]) { var pos = engineToViewPos(result[i][2]) items.squares.getSquareAt(pos)['acceptMove'] = true } } } // Calculate the score for black and white // Count the number of pieces with each piece having a given weight // Piece pawn knight bishop rook queen // Value 1 3 3 5 9 // @return [white, black] function getScore() { var lut = {2: 1, 4: 5, 6: 3, 8: 3, 12: 9} var white = 0 var black = 0 for(var i=0; i < state['board'].length; ++i) { var score = lut[state['board'][i] & 0xFE] if(score) if(state['board'][i] & 0x01) black += score else white += score } return [white, black] } diff --git a/src/activities/share/share.js b/src/activities/share/share.js index 8f51d476d..892897b30 100644 --- a/src/activities/share/share.js +++ b/src/activities/share/share.js @@ -1,218 +1,220 @@ /* GCompris - share.js * * Copyright (C) 2016 Stefan Toncu * * 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.0 as Quick var currentLevel = 0 var numberOfLevel = 10 var items var savedTotalBoys var savedTotalGirls var savedTotalCandies var savedPlacedInGirls var savedPlacedInBoys var savedCurrentCandies function start(items_) { items = items_ currentLevel = 0 initLevel() } function stop() { } function initLevel() { items.bar.level = currentLevel + 1 var filename = "resource/board/"+ "board" + currentLevel + ".qml" items.dataset.source = filename setUp() } function setUp() { var levelData = items.dataset.item // use board levels if (currentLevel < 7) { var subLevelData = levelData.levels[items.currentSubLevel]; items.totalBoys = subLevelData.totalBoys items.totalGirls = subLevelData.totalGirls items.totalCandies = subLevelData.totalCandies items.instruction.text = subLevelData.instruction items.nbSubLevel = levelData.levels.length items.background.currentCandies = items.totalGirls * subLevelData.placedInGirls + items.totalBoys * subLevelData.placedInBoys items.background.placedInGirls = subLevelData.placedInGirls items.background.placedInBoys = subLevelData.placedInBoys items.background.showCount = subLevelData.showCount items.background.rest = items.totalCandies - Math.floor(items.totalCandies / items.totalChildren) * (items.totalBoys+items.totalGirls) items.basketWidget.element.opacity = (subLevelData.forceShowBasket === true || items.background.rest !== 0) ? 1 : 0 items.background.wrongMove.visible = false } else { // create random (guided) levels // get a random number between 1 and max for boys, girls and candies var maxBoys = levelData.levels[0].maxBoys var maxGirls = levelData.levels[0].maxGirls var maxCandies = levelData.levels[0].maxCandies items.totalBoys = Math.floor(Math.random() * maxBoys) + 1 items.totalGirls = Math.floor(Math.random() * maxGirls) + 1 var sum = items.totalBoys + items.totalGirls // use sum * 4 as top margin (max 4 candies per rectangle) items.totalCandies = Math.floor(Math.random() * (4 * sum - sum + 1)) + sum // stay within the max margin if (items.totalCandies > maxCandies) items.totalCandies = maxCandies - //~ singular Place %1 boy - //~ plural Place %1 boys - items.instruction.text = qsTr("Place %1 boy(s) ", "First part of Place %1 boy(s) and %2 girl(s) in the center.").arg(items.totalBoys) + - //~ singular and %2 girl in the center. - //~ plural and %2 girls in the center. - qsTr("and %1 girl(s) in the center. ", "Second part of Place %1 boy(s) and %2 girl(s) in the center.").arg(items.totalGirls) + - //~ singular Then equally split %1 candy between them. - //~ plural Then equally split %1 candies between them. - qsTr("Then equally split %1 candies between them.", "Third part of Place %1 boy(s) and %2 girl(s) in the center.").arg(items.totalCandies) + //~ singular Place %n boy + //~ plural Place %n boys + items.instruction.text = qsTr("Place %n boy(s) ", "First part of Place %n boy(s) and %n girl(s) in the center. Then equally split %n candies between them.").arg(items.totalBoys); + + //~ singular and %n girl in the center. + //~ plural and %n girls in the center. + items.instruction.text += qsTr("and %n girl(s) in the center. ", "Second part of Place %n boy(s) and %n girl(s) in the center. Then equally split %n candies between them.").arg(items.totalGirls); + + //~ singular Then equally split %n candy between them. + //~ plural Then equally split %n candies between them. + items.instruction.text += qsTr("Then equally split %n candies between them.", "Third part of Place %n boy(s) and %n girl(s) in the center. Then equally split %n candies between them.").arg(items.totalCandies) items.background.showCount = false items.nbSubLevel = 5 // depending on the levels configuration, add candies from start in a child rectangle if (levelData.levels[0].alreadyPlaced == false) { items.background.placedInGirls = 0 items.background.placedInBoys = 0 items.background.currentCandies = 0 } else { items.background.currentCandies = items.totalCandies * 2 // Place randomly between 0 and 3 candies for each child while (items.background.currentCandies > items.totalCandies / 3) { items.background.placedInGirls = Math.floor(Math.random() * 3) items.background.placedInBoys = Math.floor(Math.random() * 3) items.background.currentCandies = items.totalGirls * items.background.placedInGirls + items.totalBoys * items.background.placedInBoys } } items.background.rest = items.totalCandies - Math.floor(items.totalCandies / items.totalChildren) * (items.totalBoys+items.totalGirls) items.basketWidget.element.opacity = 1 items.background.wrongMove.visible = false; saveVariables() } resetBoard() } function resetBoard() { items.background.currentGirls = 0 items.background.currentBoys = 0 items.background.resetCandy() items.background.finished = false items.acceptCandy = false items.instruction.opacity = 1 items.listModel.clear() items.girlWidget.current = 0 items.girlWidget.canDrag = true items.girlWidget.element.opacity = 1 items.boyWidget.current = 0 items.boyWidget.canDrag = true items.boyWidget.element.opacity = 1 items.candyWidget.canDrag = true items.candyWidget.element.opacity = 1 if (items.totalCandies - items.background.currentCandies == 0) items.candyWidget.element.opacity = 0.6 items.basketWidget.canDrag = true } function saveVariables() { savedTotalBoys = items.totalBoys savedTotalGirls = items.totalGirls savedTotalCandies = items.totalCandies savedPlacedInGirls = items.background.placedInGirls savedPlacedInBoys = items.background.placedInBoys savedCurrentCandies = items.background.currentCandies } function loadVariables() { items.totalBoys = savedTotalBoys items.totalGirls = savedTotalGirls items.totalCandies = savedTotalCandies items.background.placedInGirls = savedPlacedInGirls items.background.placedInBoys = savedPlacedInBoys items.background.currentCandies = savedCurrentCandies } function reloadRandom() { if (currentLevel < 7) { initLevel() } else { loadVariables() resetBoard() items.background.rest = items.totalCandies - Math.floor(items.totalCandies / items.totalChildren) * (items.totalBoys+items.totalGirls) items.background.showCount = false items.basketWidget.element.opacity = 1 } } function nextSubLevel() { items.currentSubLevel ++ if (items.currentSubLevel === items.nbSubLevel) { nextLevel() } else { setUp() } } function nextLevel() { if(numberOfLevel <= ++currentLevel) { currentLevel = 0 } items.currentSubLevel = 0; initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } items.currentSubLevel = 0; initLevel(); } diff --git a/src/core/ApplicationSettings.h b/src/core/ApplicationSettings.h index a76883759..a29a7b32e 100644 --- a/src/core/ApplicationSettings.h +++ b/src/core/ApplicationSettings.h @@ -1,604 +1,604 @@ /* GCompris - ApplicationSettingsDefault.cpp * * 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 #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. * * 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) // 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(QObject *parent = 0); ~ApplicationSettings(); static void init(); // 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 *systeminfoProvider(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(); } } qint32 previousHeight() const { return m_previousHeight; } void setPreviousHeight(qint32 height) { if(m_previousHeight != height) { m_previousHeight = height; emit previousHeightChanged(); } } qint32 previousWidth() const { return m_previousWidth; } void setPreviousWidth(qint32 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) { + void setLocale(const QString &newLocale) { m_locale = newLocale; emit localeChanged(); } QString font() const { return m_font; } - void setFont(const QString newFont) { + 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) { + void setCodeKey(const QString &newCodeKey) { m_codeKey = newCodeKey; emit notifyCodeKeyChanged(); } /** * @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); + 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) { + void setWordset(const QString &newWordset) { m_wordset = newWordset; emit wordsetChanged(); } QString downloadServerUrl() const { return m_downloadServerUrl; } - void setDownloadServerUrl(const QString newDownloadServerUrl) { + void setDownloadServerUrl(const QString &newDownloadServerUrl) { m_downloadServerUrl = newDownloadServerUrl; emit downloadServerUrlChanged(); } 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(); } /** * 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 notifyExeCountChanged(); Q_INVOKABLE void notifyLastGCVersionRanChanged(); 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 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 exeCountChanged(); void lastGCVersionRanChanged(); void barHiddenChanged(); private: // Update in configuration the couple {key, value} in the group. template void updateValueInConfig(const QString& group, const QString& key, const T& value); static ApplicationSettings *m_instance; 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; quint32 m_exeCount; int m_lastGCVersionRan; bool m_isBarHidden; QSettings m_config; }; #endif // APPLICATIONSETTINGS_H diff --git a/src/core/ApplicationSettingsDefault.cpp b/src/core/ApplicationSettingsDefault.cpp index 13e543e93..3c5d1cace 100644 --- a/src/core/ApplicationSettingsDefault.cpp +++ b/src/core/ApplicationSettingsDefault.cpp @@ -1,58 +1,58 @@ /* GCompris - ApplicationSettingsDefault.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 "ApplicationSettings.h" #include "config.h" #include void ApplicationSettings::setDemoMode(const bool newMode) { m_isDemoMode = newMode; emit demoModeChanged(); } void ApplicationSettings::checkPayment() { if(m_activationMode == 2) setDemoMode(checkActivationCode(m_codeKey) < 2); } -uint ApplicationSettings::checkActivationCode(const QString code) { +uint ApplicationSettings::checkActivationCode(const QString &code) { if(code.length() != 12) { return 0; } bool ok; - uint year = code.mid(4, 3).toUInt(&ok, 16); - uint month = code.mid(7, 1).toUInt(&ok, 16); - uint crc = code.mid(8, 4).toUInt(&ok, 16); + uint year = code.midRef(4, 3).toUInt(&ok, 16); + uint month = code.midRef(7, 1).toUInt(&ok, 16); + uint crc = code.midRef(8, 4).toUInt(&ok, 16); uint expectedCrc = - code.mid(0, 4).toUInt(&ok, 16) ^ - code.mid(4, 4).toUInt(&ok, 16) ^ + code.midRef(0, 4).toUInt(&ok, 16) ^ + code.midRef(4, 4).toUInt(&ok, 16) ^ 0xCECA; ok = (expectedCrc == crc && year < 2100 && month <= 12); if(!ok) // Bad crc, year or month return 0; // Check date is under 2 years ok = year * 100 + month + 200 >= atoi(BUILD_DATE); return(ok ? 2 : 1); } diff --git a/src/core/DownloadManager.h b/src/core/DownloadManager.h index d58f1d44e..a9b6e4a0b 100644 --- a/src/core/DownloadManager.h +++ b/src/core/DownloadManager.h @@ -1,380 +1,380 @@ /* GCompris - DownloadManager.h * * Copyright (C) 2014 Holger Kaelberer * * Authors: * Holger Kaelberer * * 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 DOWNLOADMANAGER_H #define DOWNLOADMANAGER_H #include #include #include #include #include #include #include #include #include #include #include /** * @class DownloadManager * @short A singleton class responsible for downloading, updating and * maintaining remote resources. * @ingroup infrastructure * * DownloadManager is responsible for downloading, updating and registering * additional resources (in Qt's binary .rcc format) used by GCompris. * It downloads from a upstream URL (ApplicationSettings.downloadServerUrl) to the * default local writable location. Downloads are based on common relative * resource paths, such that a URL of the form * * http://\/\ * * will be downloaded to a local path * * /\/\ * * and registered with a resource root path * * qrc:/\/ * * Internally resources are uniquely identified by their relative resource * path * * \ * (e.g. data2/voices-ogg/voices-en.rcc>) * * Downloading and verification of local files is controlled by MD5 * checksums that are expected to be stored in @c Contents files in each * upstream directory according to the syntax produced by the @c md5sum * tool. The checksums are used for checking whether a local rcc file is * up-to-date (to avoid unnecesary rcc downloads) and to verify that the * transfer was complete. Only valid rcc files (with correct checksums) * are registered. * * A resource file must reference the "/gcompris/data" prefix with * \. All data are loaded and referenced * from this prefix. It is possible to check if a data is registered with * isDataRegistered. * * @sa DownloadDialog, ApplicationSettings.downloadServerUrl, * ApplicationSettings.isAutomaticDownloadsEnabled */ class DownloadManager : public QObject { Q_OBJECT private: DownloadManager(); // prohibit external creation, we are a singleton! static DownloadManager* _instance; // singleton instance /** Container for a full download job */ typedef struct DownloadJob { QUrl url; ///< url of the currently running sub-job QFile file; ///< target file for the currently running sub-job QNetworkReply *reply; ///< reply object for the currently running sub-job QList queue; ///< q of remaining sub jobs (QList for convenience) QMap contents; ///< checksum map for download verification QList knownContentsUrls; ///< store already tried upstream Contents files (for infinite loop protection) - DownloadJob(QUrl u = QUrl()) : url(u), file(), reply(0), + DownloadJob(const QUrl &u = QUrl()) : url(u), file(), reply(0), queue(QList()) {} } DownloadJob; QList activeJobs; ///< track active jobs to allow for parallel downloads QMutex jobsMutex; ///< not sure if we need to expect concurrent access, better lockit! static const QString contentsFilename; static const QCryptographicHash::Algorithm hashMethod = QCryptographicHash::Md5; QList registeredResources; QMutex rcMutex; ///< not sure if we need to expect concurrent access, better lockit! QNetworkAccessManager accessManager; QUrl serverUrl; /** * Get the platform-specific path storing downloaded resources. * * Uses QStandardPaths::writableLocation(QStandardPaths::CacheLocation) * which returns * - on desktop linux $HOME/.cache/KDE/gcompris-qt/ * - on other platforms check * * @return An absolute path. */ QString getSystemDownloadPath() const; /** * Get all paths that are used for storing resources. * * @returns A list of absolute paths used for storing local resources. * The caller should keep the returned list order when looking for * resources, for now the lists contains: * 1. data folder in the application path * 2. getSystemDownloadPath() * 3. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)/gcompris-qt * which is * - $HOME/.local/share/gcompris-qt (on linux desktop) * - /storage/sdcard0/GCompris (on android) * 4. [QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)]/gcompris-qt * which is on GNU/Linux * - $HOME/.local/share/KDE/gcompris-qt * - $HOME/.local/share/gcompris-qt * - $HOME/.local/share/applications/gcompris-qt * - /usr/local/share/KDE/gcompris-qt * - /usr/share/KDE/gcompris-qt */ QStringList getSystemResourcePaths() const; QString getResourceRootForFilename(const QString& filename) const; QString getFilenameForUrl(const QUrl& url) const; QUrl getUrlForFilename(const QString& filename) const; /** * Transforms the passed relative path to an absolute resource path of an * existing .rcc file, honouring the order of the systemResourcePaths * * @returns The absolute path of the .rcc file if it exists, QString() * otherwise */ QString getAbsoluteResourcePath(const QString& path) const; /** * Transforms the passed absolute path to a relative resource path if * possible. * * @returns The relative path if it could be generated, QString() otherwise. */ QString getRelativeResourcePath(const QString& path) const; QString tempFilenameForFilename(const QString &filename) const; QString filenameForTempFilename(const QString &tempFilename) const; bool checkDownloadRestriction() const; DownloadJob* getJobByReply(QNetworkReply *r); DownloadJob* getJobByUrl_locked(const QUrl& url) const; /** Start a new download specified by the passed DownloadJob */ bool download(DownloadJob* job); /** Parses upstream Contents file and build checksum map. */ bool parseContents(DownloadJob *job); /** Compares the checksum of the file in filename with the contents map in * the passed DownloadJob */ bool checksumMatches(DownloadJob *job, const QString& filename) const; bool registerResourceAbsolute(const QString& filename); /** Unregisters the passed resource * * Caller must lock rcMutex. */ void unregisterResource_locked(const QString& filename); bool isRegistered(const QString& filename) const; QStringList getLocalResources(); private slots: /** Handle a finished download. * * Called whenever a single download (sub-job) has finished. Responsible * for iterating over possibly remaining sub-jobs of our DownloadJob. */ void downloadFinished(); void downloadReadyRead(); void handleError(QNetworkReply::NetworkError code); public: // public interface: /** * Possible return codes of a finished download */ enum DownloadFinishedCode { Success = 0, /**< Download finished successfully */ Error = 1, /**< Download error */ NoChange = 2 /**< Local files are up-to-date, no download was needed */ }; virtual ~DownloadManager(); /** * Registers DownloadManager singleton in the QML engine. */ static void init(); static QObject *systeminfoProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static DownloadManager* getInstance(); /** * Generates a relative voices resources file-path for a given @p locale. * * @param locale Locale name string of the form \_\. * * @returns A relative voices resource path. */ Q_INVOKABLE QString getVoicesResourceForLocale(const QString& locale) const; /** * Checks whether the given relative resource @p path exists locally. * * @param path A relative resource path. */ Q_INVOKABLE bool haveLocalResource(const QString& path) const; /** * Whether any download is currently running. */ Q_INVOKABLE bool downloadIsRunning() const; /** * Whether the passed relative @p data is registered. * * For example, if you have a resource file which loads files under the * 'words' path like 'words/one.png'. You can call this method with 'words/one.png' * or with 'words'. * * @param data Relative resource path (file or directory). * * @sa areVoicesRegistered */ Q_INVOKABLE bool isDataRegistered(const QString& data) const; /** * Whether voices for the currently active locale are registered. * * @sa isDataRegistered */ Q_INVOKABLE bool areVoicesRegistered() const; /* * Registers a rcc resource file given by a relative resource path * * @param filename Relative resource path. */ Q_INVOKABLE bool registerResource(const QString& filename); public slots: /** * Updates a resource @p path from the upstream server unless prohibited * by the settings and registers it if possible. * * If not prohibited by the setting 'isAutomaticDownloadsEnabled' checks * for an available upstream resource specified by @p path. * If the corresponding local resource does not exist or is out of date, * the resource is downloaded and registered. * * If automatic downloads/updates are prohibited and a local copy of the * specified resource exists, it is registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool updateResource(const QString& path); /** * Download a resource specified by the relative resource @p path and * register it if possible. * * If a corresponding local resource exists, an update will only be * downloaded if it is not up-to-date according to checksum comparison. * Whenever at the end we have a valid .rcc file it will be registered. * * @param path A relative resource path. * * @returns success */ Q_INVOKABLE bool downloadResource(const QString& path); /** * Shutdown DownloadManager instance. * * Aborts all currently running downloads. */ Q_INVOKABLE void shutdown(); /** * Abort all currently running downloads. */ Q_INVOKABLE void abortDownloads(); #if 0 Q_INVOKABLE bool checkForUpdates(); // might be helpful later with other use-cases! Q_INVOKABLE void registerLocalResources(); #endif signals: /** Emitted when a download error occurs. * * @param code enum NetworkError code. * @param msg Error string. */ void error(int code, const QString& msg); /** Emitted when a download has started. * * @param resource Relative resource path of the started download. */ void downloadStarted(const QString& resource); /** Emitted during a running download. * * All values refer to the currently active sub-job. * * @param bytesReceived Downloaded bytes. * @param bytesTotal Total bytes to download. */ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); /** Emitted when a download has finished. * * Also emitted in error cases. * * @param code DownloadFinishedCode. FIXME: when using DownloadFinishedCode * instead of int the code will not be passed to the QML layer, * use QENUMS? */ void downloadFinished(int code); /** Emitted when a resource has been registered. * * @param resource Relative resource path of the registered resource. * * @sa voicesRegistered */ void resourceRegistered(const QString& resource); /** Emitted when voices resources for current locale have been registered. * * Convenience signal and special case of resourceRegistered. * * @sa resourceRegistered */ void voicesRegistered(); }; #endif /* DOWNLOADMANAGER_H */