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 }