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 }