Warning, /education/minuet/src/app/qml/ExerciseView.qml is written in an unsupported language. File is not indexed.
0001 /**************************************************************************** 0002 ** 0003 ** Copyright (C) 2016 by Sandro S. Andrade <sandroandrade@kde.org> 0004 ** 0005 ** This program is free software; you can redistribute it and/or 0006 ** modify it under the terms of the GNU General Public License as 0007 ** published by the Free Software Foundation; either version 2 of 0008 ** the License or (at your option) version 3 or any later version 0009 ** accepted by the membership of KDE e.V. (or its successor approved 0010 ** by the membership of KDE e.V.), which shall act as a proxy 0011 ** defined in Section 14 of version 3 of the license. 0012 ** 0013 ** This program is distributed in the hope that it will be useful, 0014 ** but WITHOUT ANY WARRANTY; without even the implied warranty of 0015 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0016 ** GNU General Public License for more details. 0017 ** 0018 ** You should have received a copy of the GNU General Public License 0019 ** along with this program. If not, see <http://www.gnu.org/licenses/>. 0020 ** 0021 ****************************************************************************/ 0022 0023 import QtQuick 2.7 0024 import QtQuick.Controls 2.0 0025 import QtQuick.Layouts 1.3 0026 import QtQuick.Window 2.0 0027 0028 Item { 0029 id: exerciseView 0030 0031 visible: currentExercise != undefined 0032 0033 property var currentExercise 0034 0035 QtObject { 0036 id: internal 0037 0038 property int currentAnswer 0039 property var colors: ["#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f", "#a6cee3", "#1f78b4", "#b2df8a", "#33a02c", "#fb9a99", "#e31a1c", "#fdbf6f", "#ff7f00", "#cab2d6", "#6a3d9a", "#ffff99", "#b15928"] 0040 property Item rightAnswerRectangle 0041 property variant userAnswers: [] 0042 property bool answersAreRight 0043 property bool giveUp 0044 property bool isTest: false 0045 property int correctAnswers: 0 0046 property int currentExercise: 0 0047 property int maximumExercises: 10 0048 0049 onCurrentAnswerChanged: { 0050 for (var i = 0; i < yourAnswersParent.children.length; ++i) 0051 yourAnswersParent.children[i].destroy() 0052 yourAnswersParent.children = "" 0053 for (var i = 0; i < currentAnswer; ++i) 0054 answerOption.createObject(yourAnswersParent, {"model": userAnswers[i].model, "index": userAnswers[i].index, "position": i, "color": userAnswers[i].color, "border.width": 2}) 0055 } 0056 } 0057 0058 onCurrentExerciseChanged: { 0059 clearUserAnswers() 0060 for (var i = 0; i < answerGrid.children.length; ++i) 0061 answerGrid.children[i].destroy() 0062 answerGrid.children = "" 0063 if (currentExercise != undefined) { 0064 var currentExerciseOptions = currentExercise["options"]; 0065 if (currentExerciseOptions != undefined) { 0066 var length = currentExerciseOptions.length 0067 for (var i = 0; i < length; ++i) 0068 answerOption.createObject(answerGrid, {"model": currentExerciseOptions[i], "index": i, "color": internal.colors[i%internal.colors.length]}) 0069 } 0070 sheetMusicView.spaced = (currentExercise["playMode"] == "chord") ? false:true 0071 messageText.text = i18n("Click 'New Question' to start!") 0072 exerciseView.state = "waitingForNewQuestion" 0073 } 0074 } 0075 0076 function clearUserAnswers() { 0077 pianoView.clearAllMarks() 0078 sheetMusicView.clearAllMarks() 0079 for (var i = 0; i < yourAnswersParent.children.length; ++i) 0080 yourAnswersParent.children[i].destroy() 0081 yourAnswersParent.children = "" 0082 internal.currentAnswer = 0 0083 internal.userAnswers = [] 0084 } 0085 0086 function checkAnswers() { 0087 var rightAnswers = core.exerciseController.selectedExerciseOptions 0088 internal.answersAreRight = true 0089 for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { 0090 if (internal.userAnswers[i].name != rightAnswers[i].name) { 0091 yourAnswersParent.children[i].border.color = "red" 0092 internal.answersAreRight = false 0093 } 0094 else { 0095 yourAnswersParent.children[i].border.color = "green" 0096 if (internal.isTest) 0097 internal.correctAnswers++ 0098 } 0099 } 0100 messageText.text = (internal.giveUp) ? i18n("Here is the answer") : (internal.answersAreRight) ? i18n("Congratulations, you answered correctly!"):i18n("Oops, not this time! Try again!") 0101 if (internal.currentExercise == internal.maximumExercises) { 0102 messageText.text = i18n("You answered correctly %1%", internal.correctAnswers * 100 / internal.maximumExercises / currentExercise.numberOfSelectedOptions) 0103 resetTest() 0104 } 0105 0106 if (currentExercise.numberOfSelectedOptions == 1) 0107 highlightRightAnswer() 0108 else 0109 exerciseView.state = "waitingForNewQuestion" 0110 internal.giveUp = false 0111 } 0112 0113 function highlightRightAnswer() { 0114 var chosenExercises = core.exerciseController.selectedExerciseOptions 0115 for (var i = 0; i < answerGrid.children.length; ++i) { 0116 if (answerGrid.children[i].model.name != chosenExercises[0].name) { 0117 answerGrid.children[i].opacity = 0.25 0118 } 0119 else { 0120 internal.rightAnswerRectangle = answerGrid.children[i] 0121 answerGrid.children[i].opacity = 1 0122 } 0123 } 0124 var array = [core.exerciseController.chosenRootNote()] 0125 internal.rightAnswerRectangle.model.sequence.split(' ').forEach(function(note) { 0126 pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.rightAnswerRectangle.color) 0127 array.push(core.exerciseController.chosenRootNote() + parseInt(note)) 0128 }) 0129 sheetMusicView.model = array 0130 animation.start() 0131 } 0132 0133 function resetTest() { 0134 internal.isTest = false 0135 internal.correctAnswers = 0 0136 internal.currentExercise = 0 0137 } 0138 0139 function nextTestExercise() { 0140 for (var i = 0; i < answerGrid.children.length; ++i) 0141 answerGrid.children[i].opacity = 1 0142 pianoView.clearAllMarks() 0143 sheetMusicView.clearAllMarks() 0144 clearUserAnswers() 0145 generateNewQuestion(true) 0146 core.soundController.play() 0147 } 0148 0149 function generateNewQuestion () { 0150 clearUserAnswers() 0151 if (internal.isTest) 0152 messageText.text = i18n("Question %1 out of %2", internal.currentExercise + 1, internal.maximumExercises) 0153 else 0154 messageText.text = "" 0155 core.exerciseController.randomlySelectExerciseOptions() 0156 var chosenExercises = core.exerciseController.selectedExerciseOptions 0157 core.soundController.prepareFromExerciseOptions(chosenExercises) 0158 if (currentExercise["playMode"] != "rhythm") { 0159 pianoView.noteMark(0, core.exerciseController.chosenRootNote(), 0, "white") 0160 pianoView.scrollToNote(core.exerciseController.chosenRootNote()) 0161 sheetMusicView.model = [core.exerciseController.chosenRootNote()] 0162 sheetMusicView.clef.type = (core.exerciseController.chosenRootNote() >= 60) ? 0:1 0163 } 0164 exerciseView.state = "waitingForAnswer" 0165 if (internal.isTest) 0166 internal.currentExercise++ 0167 } 0168 0169 0170 ColumnLayout { 0171 anchors.fill: parent 0172 spacing: Screen.width >= 1024 ? 20:10 0173 0174 Text { 0175 id: userMessage 0176 0177 Layout.preferredWidth: parent.width 0178 Layout.alignment: Qt.AlignHCenter 0179 0180 horizontalAlignment: Text.AlignHCenter 0181 font.pointSize: Screen.width >= 1024 ? 18:14 0182 wrapMode: Text.WordWrap 0183 text: (currentExercise != undefined) ? i18nc("technical term, do you have a musician friend?", currentExercise["userMessage"]):"" 0184 } 0185 Text { 0186 id: messageText 0187 0188 font.pointSize: Screen.width >= 1024 ? 18:14 0189 Layout.preferredWidth: parent.width 0190 Layout.alignment: Qt.AlignHCenter 0191 horizontalAlignment: Text.AlignHCenter 0192 } 0193 Row { 0194 Layout.alignment: Qt.AlignHCenter 0195 spacing: 10 0196 0197 Button { 0198 id: newPlayQuestionButton 0199 0200 width: 120; height: 40 0201 text: (exerciseView.state == "waitingForNewQuestion") ? i18n("New Question"):i18n("Play Question") 0202 enabled: !animation.running 0203 0204 onClicked: { 0205 if (exerciseView.state == "waitingForNewQuestion") { 0206 generateNewQuestion() 0207 } 0208 core.soundController.play() 0209 } 0210 } 0211 Button { 0212 id: giveUpButton 0213 0214 width: 120; height: 40 0215 text: i18n("Give Up") 0216 enabled: exerciseView.state == "waitingForAnswer" && !animation.running 0217 0218 onClicked: { 0219 if (internal.isTest) 0220 internal.correctAnswers-- 0221 internal.giveUp = true 0222 var rightAnswers = core.exerciseController.selectedExerciseOptions 0223 internal.userAnswers = [] 0224 for (var i = 0; i < currentExercise.numberOfSelectedOptions; ++i) { 0225 for (var j = 0; j < answerGrid.children.length; ++j) { 0226 if (answerGrid.children[j].model.name == rightAnswers[i].name) { 0227 internal.userAnswers.push({"name": rightAnswers[i].name, "model": answerGrid.children[j].model, "index": j, "color": internal.colors[j]}) 0228 break 0229 } 0230 } 0231 } 0232 internal.currentAnswer = currentExercise.numberOfSelectedOptions 0233 checkAnswers() 0234 } 0235 } 0236 Button { 0237 id: testButton 0238 0239 width: 120; height: 40 0240 text: internal.isTest ? i18n("Stop Test") : i18n("Start Test") 0241 enabled: true 0242 0243 onClicked: { 0244 if (!internal.isTest) { 0245 resetTest() 0246 internal.isTest = true 0247 generateNewQuestion() 0248 if (internal.isTest) 0249 core.soundController.play() 0250 } else { 0251 resetTest() 0252 exerciseView.state = "waitingForNewQuestion" 0253 messageText.text = i18n("Click 'New Question' to start") 0254 } 0255 } 0256 } 0257 } 0258 GroupBox { 0259 id: availableAnswers 0260 0261 title: i18n("Available Answers") 0262 Layout.preferredWidth: parent.width 0263 Layout.alignment: Qt.AlignHCenter 0264 Layout.fillHeight: true 0265 0266 Flickable { 0267 anchors.fill: parent 0268 contentHeight: answerGrid.height 0269 clip: true 0270 0271 Grid { 0272 id: answerGrid 0273 0274 anchors.centerIn: parent 0275 spacing: 10 0276 0277 columns: Math.max(1, parent.width / (120+ spacing)) 0278 0279 Component { 0280 id: answerOption 0281 0282 Rectangle { 0283 id: answerRectangle 0284 0285 property var model 0286 property int index 0287 property int position 0288 0289 width: 120 0290 height: 60 0291 0292 Text { 0293 id: option 0294 0295 property string originalText: model.name 0296 0297 visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" 0298 text: i18nc("technical term, do you have a musician friend?", model.name) 0299 width: parent.width - 4 0300 anchors.centerIn: parent 0301 horizontalAlignment: Qt.AlignHCenter 0302 color: "black" 0303 wrapMode: Text.Wrap 0304 } 0305 Image { 0306 id: rhythmImage 0307 0308 anchors.centerIn: parent 0309 visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" 0310 source: (currentExercise != undefined && currentExercise["playMode"] == "rhythm") ? "exercise-images/" + model.name + ".png":"" 0311 fillMode: Image.Pad 0312 } 0313 MouseArea { 0314 anchors.fill: parent 0315 onClicked: { 0316 if (exerciseView.state == "waitingForAnswer" && !animation.running) { 0317 onExited() 0318 internal.userAnswers.push({"name": option.originalText, "model": answerRectangle.model, "index": answerRectangle.index, "color": answerRectangle.color}) 0319 internal.currentAnswer++ 0320 if (internal.currentAnswer == currentExercise.numberOfSelectedOptions) 0321 checkAnswers() 0322 } 0323 } 0324 hoverEnabled: Qt.platform.os != "android" && !animation.running 0325 onEntered: { 0326 answerRectangle.color = Qt.darker(answerRectangle.color, 1.1) 0327 if (currentExercise["playMode"] != "rhythm" && exerciseView.state == "waitingForAnswer") { 0328 if (parent.parent == answerGrid) { 0329 var array = [core.exerciseController.chosenRootNote()] 0330 model.sequence.split(' ').forEach(function(note) { 0331 array.push(core.exerciseController.chosenRootNote() + parseInt(note)) 0332 pianoView.noteMark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0, internal.colors[answerRectangle.index%internal.colors.length]) 0333 }) 0334 sheetMusicView.model = array 0335 } 0336 } 0337 else { 0338 var rightAnswers = core.exerciseController.selectedExerciseOptions 0339 if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { 0340 parent.border.color = "green" 0341 for (var i = 0; i < answerGrid.children.length; ++i) { 0342 if (answerGrid.children[i].model.name == rightAnswers[position].name) { 0343 parent.color = answerGrid.children[i].color 0344 break 0345 } 0346 } 0347 rhythmImage.source = "exercise-images/" + rightAnswers[position].name + ".png" 0348 } 0349 } 0350 } 0351 onExited: { 0352 answerRectangle.color = internal.colors[answerRectangle.index%internal.colors.length] 0353 if (currentExercise["playMode"] != "rhythm") { 0354 if (parent.parent == answerGrid) { 0355 if (!animation.running) 0356 model.sequence.split(' ').forEach(function(note) { 0357 pianoView.noteUnmark(0, core.exerciseController.chosenRootNote() + parseInt(note), 0) 0358 }) 0359 sheetMusicView.model = [core.exerciseController.chosenRootNote()] 0360 } 0361 } 0362 else { 0363 var rightAnswers = core.exerciseController.selectedExerciseOptions 0364 if (parent.parent == yourAnswersParent && internal.userAnswers[position].name != rightAnswers[position].name) { 0365 parent.border.color = "red" 0366 parent.color = internal.userAnswers[position].color 0367 rhythmImage.source = "exercise-images/" + internal.userAnswers[position].name + ".png" 0368 } 0369 } 0370 } 0371 } 0372 } 0373 } 0374 } 0375 ScrollIndicator.vertical: ScrollIndicator { active: true } 0376 } 0377 } 0378 GroupBox { 0379 id: yourAnswers 0380 0381 title: i18n("Your Answer(s)") 0382 Layout.preferredWidth: parent.width 0383 Layout.alignment: Qt.AlignHCenter 0384 contentHeight: ((currentExercise != undefined && currentExercise["playMode"] != "rhythm") ? 40:59) 0385 0386 Flickable { 0387 width: (currentExercise != undefined) ? Math.min(parent.width, internal.currentAnswer*130):0; height: parent.height 0388 anchors.horizontalCenter: parent.horizontalCenter 0389 contentWidth: (currentExercise != undefined) ? internal.currentAnswer*130:0 0390 boundsBehavior: Flickable.StopAtBounds 0391 clip: true 0392 0393 Row { 0394 id: yourAnswersParent 0395 anchors.centerIn: parent 0396 spacing: Screen.width >= 1024 ? 10:5 0397 } 0398 0399 ScrollIndicator.horizontal: ScrollIndicator { active: true } 0400 } 0401 } 0402 Button { 0403 id: backspaceButton 0404 0405 text: i18n("Backspace") 0406 Layout.alignment: Qt.AlignHCenter 0407 visible: currentExercise != undefined && currentExercise["playMode"] == "rhythm" 0408 enabled: internal.currentAnswer > 0 && internal.currentAnswer < currentExercise.numberOfSelectedOptions 0409 onClicked: { 0410 internal.userAnswers.pop() 0411 internal.currentAnswer-- 0412 } 0413 } 0414 Row { 0415 Layout.preferredWidth: parent.width 0416 Layout.alignment: Qt.AlignHCenter 0417 spacing: (parent.width/2 - sheetMusicView.width)/2 0418 PianoView { 0419 id: pianoView 0420 width: parent.width/2 - 10 0421 visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" 0422 ScrollIndicator.horizontal: ScrollIndicator { active: true } 0423 } 0424 SheetMusicView { 0425 id: sheetMusicView 0426 0427 height: pianoView.height 0428 anchors { bottom: parent.bottom; bottomMargin: 15 } 0429 visible: currentExercise != undefined && currentExercise["playMode"] != "rhythm" 0430 } 0431 } 0432 } 0433 states: [ 0434 State { 0435 name: "waitingForNewQuestion" 0436 }, 0437 State { 0438 name: "waitingForAnswer" 0439 StateChangeScript { 0440 script: { 0441 for (var i = 0; i < answerGrid.children.length; ++i) { 0442 answerGrid.children[i].opacity = 1 0443 } 0444 } 0445 } 0446 } 0447 ] 0448 ParallelAnimation { 0449 id: animation 0450 0451 loops: 2 0452 0453 SequentialAnimation { 0454 PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: -45; duration: 200 } 0455 PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 45; duration: 200 } 0456 PropertyAnimation { target: internal.rightAnswerRectangle; property: "rotation"; to: 0; duration: 200 } 0457 } 0458 SequentialAnimation { 0459 PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.2; duration: 300 } 0460 PropertyAnimation { target: internal.rightAnswerRectangle; property: "scale"; to: 1.0; duration: 300 } 0461 } 0462 0463 onStopped: { 0464 exerciseView.state = internal.isTest ? "waitingForAnswer" : "waitingForNewQuestion" 0465 if (internal.isTest) { 0466 nextTestExercise() 0467 if (internal.currentExercise == internal.maximumExercises+1) 0468 internal.isTest = false 0469 } 0470 } 0471 } 0472 Connections { 0473 target: core.exerciseController 0474 function onSelectedExerciseOptionsChanged() { pianoView.clearAllMarks() } 0475 } 0476 Connections { 0477 target: core.exerciseController 0478 function onSelectedExerciseOptionsChanged() { sheetMusicView.clearAllMarks() } 0479 } 0480 }