Warning, /education/gcompris/src/activities/piano_composition/Piano_composition.qml is written in an unsupported language. File is not indexed.

0001 /* GCompris - Piano_composition.qml
0002  *
0003  * SPDX-FileCopyrightText: 2016 Johnny Jazeix <jazeix@gmail.com>
0004  * SPDX-FileCopyrightText: 2018 Aman Kumar Gupta <gupta2140@gmail.com>
0005  *
0006  * Authors:
0007  *   Beth Hadley <bethmhadley@gmail.com> (GTK+ version)
0008  *   Johnny Jazeix <jazeix@gmail.com> (Qt Quick port)
0009  *   Aman Kumar Gupta <gupta2140@gmail.com> (Qt Quick port)
0010  *   Timothée Giet <animtim@gmail.com> (refactoring)
0011  * 
0012  *   SPDX-License-Identifier: GPL-3.0-or-later
0013  */
0014 import QtQuick 2.12
0015 import GCompris 1.0
0016 
0017 import "../../core"
0018 import "qrc:/gcompris/src/core/core.js" as Core
0019 import "piano_composition.js" as Activity
0020 import "melodies.js" as Dataset
0021 
0022 ActivityBase {
0023     id: activity
0024 
0025     onStart: focus = true
0026     onStop: {}
0027     isMusicalActivity: true
0028 
0029     property bool horizontalLayout: background.width >= background.height
0030 
0031     pageComponent: Rectangle {
0032         id: background
0033         anchors.fill: parent
0034         color: "#ABCDEF"
0035         signal start
0036         signal stop
0037 
0038         // if audio is disabled, we display a dialog to tell users this activity requires audio anyway
0039         property bool audioDisabled: false
0040 
0041         Component.onCompleted: {
0042             activity.start.connect(start)
0043             activity.stop.connect(stop)
0044         }
0045 
0046         Keys.onPressed: {
0047             var keyboardBindings = {}
0048             keyboardBindings[Qt.Key_1] = 0
0049             keyboardBindings[Qt.Key_2] = 1
0050             keyboardBindings[Qt.Key_3] = 2
0051             keyboardBindings[Qt.Key_4] = 3
0052             keyboardBindings[Qt.Key_5] = 4
0053             keyboardBindings[Qt.Key_6] = 5
0054             keyboardBindings[Qt.Key_7] = 6
0055             keyboardBindings[Qt.Key_F2] = 1
0056             keyboardBindings[Qt.Key_F3] = 2
0057             keyboardBindings[Qt.Key_F5] = 4
0058             keyboardBindings[Qt.Key_F6] = 5
0059             keyboardBindings[Qt.Key_F7] = 6
0060 
0061             if(event.key >= Qt.Key_1 && event.key <= Qt.Key_7) {
0062                 piano.keyRepeater.playKey(keyboardBindings[event.key], "white");
0063             }
0064             else if(event.key >= Qt.Key_F2 && event.key <= Qt.Key_F7) {
0065                 if(piano.blackKeysEnabled)
0066                     piano.keyRepeater.playKey(keyboardBindings[event.key], "black");
0067             }
0068             if(event.key === Qt.Key_Left && shiftKeyboardLeft.visible) {
0069                 piano.currentOctaveNb--
0070             }
0071             if(event.key === Qt.Key_Right && shiftKeyboardRight.visible) {
0072                 piano.currentOctaveNb++
0073             }
0074             if(event.key === Qt.Key_Delete) {
0075                 optionsRow.clearButtonClicked()
0076             }
0077             if(event.key === Qt.Key_Backspace) {
0078                 optionsRow.undoButtonClicked()
0079             }
0080             if(event.key === Qt.Key_Space) {
0081                 optionsRow.playButtonClicked()
0082             }
0083         }
0084 
0085         // Add here the QML items you need to access in javascript
0086         QtObject {
0087             id: items
0088             property Item main: activity.main
0089             property alias background: background
0090             property GCSfx audioEffects: activity.audioEffects
0091             property int currentLevel: activity.currentLevel
0092             property alias bonus: bonus
0093             property alias multipleStaff: multipleStaff
0094             property string staffLength: "short"
0095             property alias melodyList: melodyList
0096             property alias file: file
0097             property alias piano: piano
0098             property alias optionsRow: optionsRow
0099             property alias lyricsArea: lyricsArea
0100         }
0101 
0102         onStart: {
0103             Activity.start(items);
0104             if(!ApplicationSettings.isAudioVoicesEnabled || !ApplicationSettings.isAudioEffectsEnabled) {
0105                     background.audioDisabled = true;
0106             }
0107         }
0108         onStop: { Activity.stop() }
0109 
0110         property string currentType: "Quarter"
0111         property string restType: "Whole"
0112         property string clefType: items.currentLevel == 1 ? "Bass" : "Treble"
0113         property bool isLyricsMode: (optionsRow.lyricsOrPianoModeIndex === 1) && optionsRow.lyricsOrPianoModeOptionVisible
0114         property int layoutMargins: 5 * ApplicationInfo.ratio
0115 
0116         File {
0117             id: file
0118             onError: console.error("File error: " + msg)
0119         }
0120 
0121         Item {
0122             id: clickedOptionMessage
0123 
0124             signal show(string message)
0125             onShow: {
0126                 messageText.text = message
0127                 messageAnimation.stop()
0128                 messageAnimation.start()
0129             }
0130 
0131             height: width * 0.4
0132             visible: false
0133             anchors.top: optionsRow.bottom
0134             anchors.horizontalCenter: optionsRow.horizontalCenter
0135             z: 5
0136             Rectangle {
0137                 id: messageRectangle
0138                 width: messageText.contentWidth + 5
0139                 height: messageText.height + 5
0140                 anchors.centerIn: messageText
0141                 color: "black"
0142                 opacity: 0.5
0143                 border.width: 3
0144                 border.color: "black"
0145                 radius: 15
0146             }
0147 
0148             GCText {
0149                 id: messageText
0150                 anchors.fill: parent
0151                 anchors.rightMargin: parent.width * 0.02
0152                 anchors.leftMargin: parent.width * 0.02
0153                 horizontalAlignment: Text.AlignHCenter
0154                 verticalAlignment: Text.AlignVCenter
0155                 fontSizeMode: Text.Fit
0156                 color: "white"
0157             }
0158 
0159             SequentialAnimation {
0160                 id: messageAnimation
0161                 onStarted: clickedOptionMessage.visible = true
0162                 PauseAnimation {
0163                     duration: 1000
0164                 }
0165                 NumberAnimation {
0166                     targets: [messageRectangle, messageText]
0167                     property: "opacity"
0168                     to: 0
0169                     duration: 200
0170                 }
0171                 onStopped: {
0172                     clickedOptionMessage.visible = false
0173                     messageRectangle.opacity = 0.5
0174                     messageText.opacity = 1
0175                 }
0176             }
0177         }
0178 
0179         MelodyList {
0180             id: melodyList
0181             onClose: {
0182                 visible = false
0183                 piano.enabled = true
0184                 focus = false
0185                 activity.focus = true
0186             }
0187         }
0188 
0189         Rectangle {
0190             id: instructionBox
0191             radius: 10
0192             width: background.width * 0.75
0193             height: background.height * 0.15
0194             anchors.right: parent.right
0195             opacity: 0.8
0196             border.width: 6
0197             color: "white"
0198             border.color: "#87A6DD"
0199 
0200             GCText {
0201                 id: instructionText
0202                 color: "black"
0203                 z: 3
0204                 anchors.fill: parent
0205                 anchors.rightMargin: parent.width * 0.02
0206                 anchors.leftMargin: parent.width * 0.02
0207                 horizontalAlignment: Text.AlignHCenter
0208                 verticalAlignment: Text.AlignVCenter
0209                 fontSizeMode: Text.Fit
0210                 wrapMode: Text.WordWrap
0211                 text: Activity.instructions[items.currentLevel].text
0212             }
0213         }
0214 
0215         MultipleStaff {
0216             id: multipleStaff
0217             nbStaves: 2
0218             clef: clefType
0219             coloredNotes: ['C','D', 'E', 'F', 'G', 'A', 'B']
0220             noteHoverEnabled: true
0221             anchors.margins: layoutMargins
0222 
0223             onNoteClicked: {
0224                 if(selectedIndex === noteIndex)
0225                     selectedIndex = -1
0226                 else {
0227                     selectedIndex = noteIndex
0228                     background.clefType = musicElementModel.get(selectedIndex).soundPitch_
0229                     playNoteAudio(musicElementModel.get(selectedIndex).noteName_, musicElementModel.get(selectedIndex).noteType_, background.clefType, musicElementRepeater.itemAt(selectedIndex).duration)
0230                 }
0231             }
0232         }
0233 
0234         GCButtonScroll {
0235             id: multipleStaffFlickButton
0236             anchors.left: multipleStaff.right
0237             anchors.verticalCenter: multipleStaff.verticalCenter
0238             width: bar.height * 0.3
0239             height: width * 2
0240             onUp: multipleStaff.flickableStaves.flick(0, multipleStaff.height * 1.3)
0241             onDown: multipleStaff.flickableStaves.flick(0, -multipleStaff.height * 1.3)
0242             upVisible: multipleStaff.flickableStaves.atYBeginning ? false : true
0243             downVisible: multipleStaff.flickableStaves.atYEnd ? false : true
0244         }
0245 
0246         Item {
0247             id: pianoLayout
0248             width: multipleStaff.width + multipleStaffFlickButton.width
0249             height: multipleStaff.height
0250             anchors.margins: layoutMargins
0251 
0252             PianoOctaveKeyboard {
0253                 id: piano
0254                 height: parent.height
0255                 width: parent.width * 0.8
0256                 anchors.horizontalCenter: parent.horizontalCenter
0257                 anchors.verticalCenter: parent.verticalCenter
0258                 blackLabelsVisible: [3, 4, 5, 6, 7, 8].indexOf(items.currentLevel + 1) == -1 ? false : true
0259                 useSharpNotation: items.currentLevel != 3
0260                 blackKeysEnabled: items.currentLevel > 1
0261                 visible: !background.isLyricsMode
0262                 currentOctaveNb: (background.clefType === "Bass") ? 0 : 1
0263 
0264                 onNoteClicked: {
0265                     parent.addMusicElementAndPushToStack(note, currentType)
0266                 }
0267             }
0268 
0269             function addMusicElementAndPushToStack(noteName, noteType, elementType) {
0270                 if(noteType === "Rest")
0271                     elementType = "rest"
0272                     else if(elementType === undefined)
0273                         elementType = "note"
0274 
0275                         var tempModel = multipleStaff.createNotesBackup()
0276                         Activity.pushToStack(tempModel)
0277                         multipleStaff.addMusicElement(elementType, noteName, noteType, false, true, background.clefType)
0278             }
0279 
0280             Image {
0281                 id: shiftKeyboardLeft
0282                 source: "qrc:/gcompris/src/core/resource/bar_previous.svg"
0283                 sourceSize.width: parent.width * 0.1
0284                 width: sourceSize.width
0285                 height: parent.height
0286                 fillMode: Image.PreserveAspectFit
0287                 visible: (piano.currentOctaveNb > 0) && piano.visible
0288                 anchors.right: piano.left
0289                 anchors.verticalCenter: parent.verticalCenter
0290                 MouseArea {
0291                     anchors.fill: parent
0292                     onClicked: piano.currentOctaveNb--
0293                 }
0294             }
0295 
0296             Image {
0297                 id: shiftKeyboardRight
0298                 source: "qrc:/gcompris/src/core/resource/bar_next.svg"
0299                 sourceSize.width: parent.width * 0.1
0300                 width: sourceSize.width
0301                 height: parent.height
0302                 fillMode: Image.PreserveAspectFit
0303                 visible: (piano.currentOctaveNb < piano.maxNbOctaves - 1) && piano.visible
0304                 anchors.left: piano.right
0305                 anchors.verticalCenter: parent.verticalCenter
0306                 MouseArea {
0307                     anchors.fill: parent
0308                     onClicked: piano.currentOctaveNb++
0309                 }
0310             }
0311 
0312             LyricsArea {
0313                 id: lyricsArea
0314                 width: parent.width
0315                 height: parent.height
0316                 anchors.fill: pianoLayout
0317             }
0318 
0319         }
0320 
0321         GCCreationHandler {
0322             id: creationHandler
0323             onFileLoaded:  {
0324                 // We need to draw the notes twice since we first need to count the number of staffs needed for the melody (we get that from
0325                 // the 1st redraw call), then we redraw the 2nd time to actually display the notes perfectly. This is done because for some reason, the
0326                 // staves model is updated slower than the addition of notes, so the notes aggregates in their default position instead of
0327                 // their required position, due to unavailability of the updated staff at that instant. So calculating the number of required staffs first seems the only solution for now.
0328                 multipleStaff.redraw(data)
0329                 multipleStaff.redraw(data)
0330                 lyricsArea.resetLyricsArea()
0331             }
0332             onClose: {
0333                 optionsRow.lyricsOrPianoModeIndex = 0
0334             }
0335         }
0336 
0337         OptionsRow {
0338             id: optionsRow
0339             anchors.margins: layoutMargins
0340             anchors.left: background.left
0341             iconsWidth: 0
0342             noteOptionsVisible: items.currentLevel > 3
0343             playButtonVisible: true
0344             keyOption.clefButtonVisible: items.currentLevel > 1
0345             clearButtonVisible: true
0346             undoButtonVisible: true
0347             openButtonVisible: items.currentLevel > 5
0348             saveButtonVisible: items.currentLevel > 5
0349             changeAccidentalStyleButtonVisible: items.currentLevel >= 3
0350             lyricsOrPianoModeOptionVisible: items.currentLevel > 5
0351             restOptionsVisible: items.currentLevel > 4
0352             bpmVisible: true
0353 
0354             onUndoButtonClicked: {
0355                 Activity.undoChange()
0356             }
0357             onClearButtonClicked: {
0358                 if((multipleStaff.musicElementModel.count > 1) && multipleStaff.selectedIndex === -1) {
0359                     Core.showMessageDialog(activity,
0360                         qsTr("You have not selected any note. Do you want to erase all the notes?"),
0361                         qsTr("Yes"), function() {
0362                             Activity.undoStack = []
0363                             lyricsArea.resetLyricsArea()
0364                            multipleStaff.eraseAllNotes()
0365                            multipleStaff.nbStaves = 2
0366                         },
0367                         qsTr("No"), null,
0368                         null
0369                     )
0370                 }
0371                 else if((multipleStaff.musicElementModel.count > 1) && !multipleStaff.musicElementModel.get(multipleStaff.selectedIndex).isDefaultClef_) {
0372                     var noteIndex = multipleStaff.selectedIndex
0373                     var tempModel = multipleStaff.createNotesBackup()
0374                     Activity.pushToStack(tempModel)
0375                     multipleStaff.eraseNote(noteIndex)
0376                 }
0377             }
0378             onOpenButtonClicked: {
0379                 dialogActivityConfig.active = true
0380                 displayDialog(dialogActivityConfig)
0381             }
0382             onSaveButtonClicked: {
0383                 var notesToSave = multipleStaff.createNotesBackup()
0384                 // add bpm at start
0385                 notesToSave.unshift({"bpm": multipleStaff.bpmValue});
0386                 creationHandler.saveWindow(notesToSave)
0387             }
0388             keyOption.onClefAdded: {
0389                 var insertingIndex = multipleStaff.selectedIndex === -1 ? multipleStaff.musicElementModel.count : multipleStaff.selectedIndex
0390                 var tempModel = multipleStaff.createNotesBackup()
0391                 Activity.pushToStack(tempModel)
0392                 multipleStaff.addMusicElement("clef", "", "", false, false, background.clefType)
0393                 if(background.clefType === "Bass")
0394                     piano.currentOctaveNb = 0
0395                 else
0396                     piano.currentOctaveNb = 1
0397             }
0398             onBpmDecreased: {
0399                 if(multipleStaff.bpmValue - 1 >= 1)
0400                     multipleStaff.bpmValue--
0401             }
0402             onBpmIncreased: {
0403                 multipleStaff.bpmValue++
0404             }
0405             onEmitOptionMessage: clickedOptionMessage.show(message)
0406         }
0407 
0408         DialogActivityConfig {
0409             id: dialogActivityConfig
0410             content: Component {
0411                 Column {
0412                     id: column
0413                     spacing: 10
0414                     width: dialogActivityConfig.width - 50 * ApplicationInfo.ratio
0415 
0416                     GCText {
0417                         text: qsTr("Select the type of melody to load.")
0418                         fontSizeMode: mediumSize
0419                     }
0420 
0421                     GCButton {
0422                         text: qsTr("Pre-defined melodies")
0423                         onClicked: {
0424                             melodyList.melodiesModel.clear()
0425                             var dataset = Dataset.get()
0426                             for(var i = 0; i < dataset.length; i++) {
0427                                 melodyList.melodiesModel.append(dataset[i])
0428                             }
0429                             dialogActivityConfig.close()
0430                             melodyList.visible = true
0431                             piano.enabled = false
0432                             melodyList.forceActiveFocus()
0433                         }
0434                         width: 150 * ApplicationInfo.ratio
0435                         height: 60 * ApplicationInfo.ratio
0436                         theme: "dark"
0437                     }
0438 
0439                     GCButton {
0440                         text: qsTr("Your saved melodies")
0441                         onClicked: {
0442                             dialogActivityConfig.close()
0443                             creationHandler.loadWindow()
0444                         }
0445                         width: 150 * ApplicationInfo.ratio
0446                         height: 60 * ApplicationInfo.ratio
0447                         theme: "dark"
0448                     }
0449                 }
0450             }
0451             onClose: home()
0452         }
0453 
0454         DialogHelp {
0455             id: dialogHelp
0456             onClose: home()
0457         }
0458 
0459         Bar {
0460             id: bar
0461             level: items.currentLevel + 1
0462             content: BarEnumContent { value: help | home | level }
0463             onHelpClicked: {
0464                 displayDialog(dialogHelp)
0465             }
0466             onPreviousLevelClicked: Activity.previousLevel()
0467             onNextLevelClicked: Activity.nextLevel()
0468             onHomeClicked: activity.home()
0469         }
0470 
0471         Bonus {
0472             id: bonus
0473             Component.onCompleted: win.connect(Activity.nextLevel)
0474         }
0475 
0476         states: [
0477             State {
0478                 name: "hScreen"
0479                 when: horizontalLayout
0480                 PropertyChanges {
0481                     target: clickedOptionMessage
0482                     width: background.width / 12
0483                 }
0484                 AnchorChanges {
0485                     target: optionsRow
0486                     anchors.top: instructionBox.bottom
0487                 }
0488                 PropertyChanges {
0489                     target: optionsRow
0490                     columns: 11
0491                     iconsWidth: background.width / 15
0492                 }
0493                 AnchorChanges {
0494                     target: multipleStaff
0495                     anchors.left: background.horizontalCenter
0496                     anchors.top: optionsRow.bottom
0497                 }
0498                 PropertyChanges {
0499                     target: multipleStaff
0500                     width: background.width * 0.5 - multipleStaffFlickButton.width - layoutMargins * 3
0501                     height: background.height - instructionBox.height - optionsRow.height - bar.height - layoutMargins * 4
0502                 }
0503                 AnchorChanges {
0504                     target: pianoLayout
0505                     anchors.left: background.left
0506                     anchors.top: optionsRow.bottom
0507                 }
0508             },
0509             State {
0510                 name: "vScreen"
0511                 when: !horizontalLayout
0512                 PropertyChanges {
0513                     target: clickedOptionMessage
0514                     width: background.width / 6
0515                 }
0516                 AnchorChanges{
0517                     target: optionsRow
0518                     anchors.top: background.top
0519                 }
0520                 PropertyChanges {
0521                     target: optionsRow
0522                     columns: 1
0523                     iconsWidth: (background.height - bar.height) / 12
0524                 }
0525                 AnchorChanges {
0526                     target: multipleStaff
0527                     anchors.left: optionsRow.right
0528                     anchors.top: instructionBox.bottom
0529                 }
0530                 PropertyChanges {
0531                     target: multipleStaff
0532                     width: background.width - multipleStaffFlickButton.width - optionsRow.width - layoutMargins * 3
0533                     height: (background.height - instructionBox.height - bar.height - layoutMargins * 4) * 0.5
0534                 }
0535                 AnchorChanges {
0536                     target: pianoLayout
0537                     anchors.left: optionsRow.right
0538                     anchors.top: multipleStaff.bottom
0539                 }
0540             }
0541         ]
0542 
0543         Loader {
0544             id: audioNeededDialog
0545             sourceComponent: GCDialog {
0546                 parent: activity
0547                 isDestructible: false
0548                 message: qsTr("This activity requires sound, so it will play some sounds even if the audio voices or effects are disabled in the main configuration.")
0549                 button1Text: qsTr("Quit")
0550                 button2Text: qsTr("Continue")
0551                 onButton1Hit: activity.home();
0552                 onClose: {
0553                     background.audioDisabled = false;
0554                 }
0555             }
0556             anchors.fill: parent
0557             focus: true
0558             active: background.audioDisabled
0559             onStatusChanged: if (status == Loader.Ready) item.start()
0560         }
0561     }
0562 }