1/* GCompris - lang.qml 2* 3* Copyright (C) Siddhesh suthar <siddhesh.it@gmail.com> (Qt Quick port) 4* 5* Authors: 6* Pascal Georges (pascal.georges1@free.fr) (GTK+ version) 7* Holger Kaelberer <holger.k@elberer.de> (Qt Quick port of imageid) 8* Siddhesh suthar <siddhesh.it@gmail.com> (Qt Quick port) 9* Bruno Coudoin <bruno.coudoin@gcompris.net> (Integration Lang dataset) 10* 11* SPDX-License-Identifier: GPL-3.0-or-later 12*/ 13import QtQuick 2.9 14import GCompris 1.0 15import QtGraphicalEffects 1.0 16 17import "../../core" 18import "lang.js" as Activity 19import "qrc:/gcompris/src/core/core.js" as Core 20 21Item { 22 id: imageReview 23 anchors.fill: parent 24 25 property alias category: categoryText.text 26 property int wordListIndex // This is the current sub list of words 27 property var word: rootItem.opacity == 1 ? items.wordList[wordListIndex][score.currentSubLevel - 1] : undefined 28 // miniGames is list of miniGames 29 // first element is Activity name, 30 // second element is mode of miniGame 31 // third element is the qml to load 32 property var miniGames: [ 33 ["QuizActivity", 1, "Quiz.qml"], 34 ["QuizActivity", 2, "Quiz.qml"], 35 ["QuizActivity", 3, "Quiz.qml"], 36 ["SpellActivity", 1, "SpellIt.qml"] 37 ] 38 property var currentMiniGame 39 property var loadedItems 40 property bool started: rootItem.opacity == 1 41 42 // Start at last wordListIndex 43 function start() { 44 initLevel(wordListIndex) 45 } 46 47 // Start the image review at wordList sublesson 48 function initLevel(wordListIndex_) { 49 wordListIndex = wordListIndex_ 50 score.currentSubLevel = 1 51 score.numberOfSubLevels = items.wordList[wordListIndex].length 52 focus = true 53 forceActiveFocus() 54 miniGameLoader.source = "" 55 currentMiniGame = -1 56 rootItem.opacity = 1 57 } 58 59 function restoreMinigameFocus() { 60 miniGameLoader.item.restoreFocus(); 61 } 62 63 function stop() { 64 focus = false 65 rootItem.opacity = 0 66 wordImage.changeSource('') 67 wordText.changeText('') 68 repeatItem.visible = false 69 } 70 71 onWordChanged: { 72 if(word) { 73 if (Activity.playWord(word.voice)) { 74 word['hasVoice'] = true 75 repeatItem.visible = true 76 } else { 77 word['hasVoice'] = false 78 repeatItem.visible = false 79 } 80 wordImage.changeSource(word.image) 81 wordText.changeText(word.translatedTxt) 82 } 83 } 84 85 // Cheat codes to access mini games directly 86 Keys.onPressed: { 87 if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_1)) { 88 initLevel(wordListIndex) 89 event.accepted = true 90 } 91 if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_2)) { 92 startMiniGame(0) 93 event.accepted = true 94 } 95 if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_3)) { 96 startMiniGame(1) 97 event.accepted = true 98 } 99 if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_4)) { 100 startMiniGame(2) 101 event.accepted = true 102 } 103 if((event.modifiers & Qt.ControlModifier) && (event.key === Qt.Key_5)) { 104 startMiniGame(3) 105 event.accepted = true 106 } 107 } 108 109 Keys.onEscapePressed: { 110 Activity.launchMenuScreen() 111 } 112 113 Keys.onLeftPressed: { 114 if( score.currentSubLevel > 1 ) { 115 imageReview.prevWord() 116 event.accepted = true 117 } 118 } 119 Keys.onRightPressed: { 120 imageReview.nextWord() 121 event.accepted = true 122 } 123 Keys.onSpacePressed: { 124 imageReview.nextWord() 125 event.accepted = true 126 } 127 Keys.onEnterPressed: { 128 imageReview.nextWord() 129 event.accepted = true 130 } 131 Keys.onReturnPressed: { 132 imageReview.nextWord() 133 event.accepted = true 134 } 135 Keys.onTabPressed: { 136 repeatItem.clicked() 137 } 138 139 Keys.onReleased: { 140 if (event.key === Qt.Key_Back) { 141 event.accepted = true 142 Activity.launchMenuScreen() 143 } 144 } 145 146 Item { 147 id: rootItem 148 anchors.fill: parent 149 opacity: 0 150 Behavior on opacity { PropertyAnimation { duration: 200 } } 151 152 Rectangle { 153 id: categoryTextbg 154 parent: rootItem 155 x: categoryText.x - 4 156 y: categoryText.y - 4 157 width: imageFrame.width + 8 158 height: categoryText.height + 8 159 color: "#5090ff" 160 border.color: "#000000" 161 border.width: 2 162 radius: 16 163 anchors { 164 horizontalCenter: parent.horizontalCenter 165 top: rootItem.top 166 topMargin: 4 * ApplicationInfo.ratio 167 } 168 169 GCText { 170 id: categoryText 171 fontSize: mediumSize 172 font.weight: Font.DemiBold 173 width: parent.width - 8 174 horizontalAlignment: Text.AlignHCenter 175 verticalAlignment: Text.AlignVCenter 176 color: "white" 177 wrapMode: Text.WordWrap 178 } 179 } 180 181 Image { 182 id: imageFrame 183 parent: rootItem 184 source: "qrc:/gcompris/src/activities/lang/resource/imageid_frame.svg" 185 width: (parent.width - previousWordButton.width * 2) * 0.8 186 height: (parent.height - categoryTextbg.height - wordTextbg.height - bar.height * 1.1) * 0.8 187 sourceSize.width: width 188 fillMode: Image.PreserveAspectFit 189 anchors { 190 horizontalCenter: parent.horizontalCenter 191 top: categoryTextbg.bottom 192 margins: 10 * ApplicationInfo.ratio 193 } 194 z: 11 195 196 Image { 197 id: wordImage 198 // Images are not svg 199 width: parent.paintedHeight * 0.9 200 height: width 201 anchors.centerIn: parent 202 203 property string nextSource 204 function changeSource(nextSource_) { 205 nextSource = nextSource_ 206 animImage.restart() 207 } 208 209 SequentialAnimation { 210 id: animImage 211 PropertyAnimation { 212 target: wordImage 213 property: "opacity" 214 to: 0 215 duration: 100 216 } 217 PropertyAction { 218 target: wordImage 219 property: "source" 220 value: wordImage.nextSource 221 } 222 PropertyAnimation { 223 target: wordImage 224 property: "opacity" 225 to: 1 226 duration: 100 227 } 228 } 229 MouseArea { 230 anchors.fill: parent 231 enabled: rootItem.opacity == 1 232 onClicked: Activity.playWord(word.voice) 233 } 234 } 235 236 Image { 237 id: previousWordButton 238 source: "qrc:/gcompris/src/core/resource/bar_previous.svg"; 239 sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio 240 visible: score.currentSubLevel > 1 ? true : false 241 anchors { 242 right: parent.left 243 rightMargin: 30 244 top: parent.top 245 topMargin: parent.height/2 - previousWordButton.height/2 246 } 247 MouseArea { 248 id: previousWordButtonArea 249 anchors.fill: parent 250 onClicked: imageReview.prevWord() 251 } 252 } 253 254 Image { 255 id: nextWordButton 256 source: "qrc:/gcompris/src/core/resource/bar_next.svg"; 257 sourceSize.width: 30 * 1.2 * ApplicationInfo.ratio 258 anchors { 259 left: parent.right 260 leftMargin: 30 261 top: parent.top 262 topMargin: parent.height/2 - previousWordButton.height/2 263 } 264 MouseArea { 265 id: nextWordButtonArea 266 anchors.fill: parent 267 onClicked: imageReview.nextWord(); 268 } 269 } 270 } 271 272 Rectangle { 273 id: wordTextbg 274 parent: rootItem 275 x: wordText.x - 4 276 y: wordText.y - 4 277 width: imageFrame.width 278 height: wordText.height + 4 279 color: "#5090ff" 280 border.color: "#000000" 281 border.width: 2 282 radius: 16 283 anchors { 284 top: imageFrame.bottom 285 left: imageFrame.left 286 margins: 10 * ApplicationInfo.ratio 287 } 288 289 GCText { 290 id: wordText 291 text: "" 292 fontSize: largeSize 293 font.weight: Font.DemiBold 294 width: parent.width 295 horizontalAlignment: Text.AlignHCenter 296 verticalAlignment: Text.AlignVCenter 297 color: "white" 298 wrapMode: Text.WordWrap 299 300 property string nextWord 301 function changeText(nextWord_) { 302 nextWord = nextWord_ 303 animWord.restart() 304 } 305 306 SequentialAnimation { 307 id: animWord 308 PropertyAnimation { 309 target: wordText 310 property: "opacity" 311 to: 0 312 duration: 100 313 } 314 PropertyAction { 315 target: wordText 316 property: "text" 317 value: wordText.nextWord 318 } 319 PropertyAnimation { 320 target: wordText 321 property: "opacity" 322 to: 1 323 duration: 100 324 } 325 } 326 } 327 } 328 329 BarButton { 330 id: repeatItem 331 parent: rootItem 332 source: "qrc:/gcompris/src/core/resource/bar_repeat.svg"; 333 sourceSize.width: Math.min(imageFrame.x, 100 * ApplicationInfo.ratio) - 2 * anchors.margins 334 335 z: 12 336 anchors { 337 top: parent.top 338 left: parent.left 339 margins: 10 * ApplicationInfo.ratio 340 } 341 onClicked: Activity.playWord(imageReview.word.voice) 342 Behavior on opacity { PropertyAnimation { duration: 200 } } 343 } 344 345 Score { 346 id: score 347 parent: rootItem 348 } 349 } 350 Loader { 351 id: miniGameLoader 352 width: parent.width 353 height: parent.height 354 anchors.fill: parent 355 asynchronous: false 356 } 357 358 function nextPressed() { 359 if(currentMiniGame === 0) { 360 nextSubLevel() 361 } 362 } 363 364 function nextWord() { 365 ++score.currentSubLevel; 366 367 if(score.currentSubLevel == score.numberOfSubLevels + 1) { 368 nextMiniGame() 369 } 370 } 371 372 function prevWord() { 373 --score.currentSubLevel 374 } 375 376 function startMiniGame(miniGameIndex) { 377 currentMiniGame = miniGameIndex 378 var mode = miniGames[miniGameIndex][1]; 379 var itemToLoad = miniGames[miniGameIndex][2]; 380 381 // Starting a minigame we don't want pending voices to play 382 Activity.clearVoiceQueue() 383 384 // preparing the wordList 385 var wordList = Core.shuffle(items.wordList[wordListIndex]).slice() 386 387 miniGameLoader.source = itemToLoad; 388 var loadedItems = miniGameLoader.item 389 390 rootItem.opacity = 0 391 focus = false 392 393 // Initiate the loaded item mini game 394 // Some Mini Games may not start because they miss voices 395 // In this case we try the next one 396 if(!loadedItems.init(loadedItems, wordList, mode)) 397 nextMiniGame() 398 } 399 400 //called by a miniGame when it is won 401 function nextMiniGame() { 402 if(currentMiniGame < miniGames.length - 1) { 403 startMiniGame(++currentMiniGame) 404 } else { 405 Activity.markProgress() 406 if(wordListIndex < items.wordList.length - 1) { 407 initLevel(wordListIndex + 1) 408 } else { 409 Activity.launchMenuScreen() 410 miniGameLoader.source = "" 411 } 412 } 413 } 414} 415