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 }