diff --git a/src/activities/piano_composition/OptionsRow.qml b/src/activities/piano_composition/OptionsRow.qml index 6fa917f5e..e0ee641c9 100644 --- a/src/activities/piano_composition/OptionsRow.qml +++ b/src/activities/piano_composition/OptionsRow.qml @@ -1,373 +1,373 @@ /* GCompris - OptionsRow.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Johnny Jazeix (Qt Quick port) * Aman Kumar Gupta (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import GCompris 1.0 import "../../core" Row { id: optionsRow spacing: iconsWidth * 0.1 //: Whole note, Half note, Quarter note and Eighth note are the different length notes in the musical notation. readonly property var noteLengthName: [[qsTr("Whole note"), "Whole"], [qsTr("Half note"), "Half"], [qsTr("Quarter note"), "Quarter"], [qsTr("Eighth note"), "Eighth"]] //: Whole rest, Half rest, Quarter rest and Eighth rest are the different length rests (silences) in the musical notation. readonly property var translatedRestNames: [qsTr("Whole rest"), qsTr("Half rest"), qsTr("Quarter rest"), qsTr("Eighth rest")] readonly property var restAddedMessage: [qsTr("Added whole rest"), qsTr("Added half rest"), qsTr("Added quarter rest"), qsTr("Added eighth rest")] readonly property var lyricsOrPianoModes: [[qsTr("Piano"), "piano"], [qsTr("Lyrics"), "lyrics"]] - property real iconsWidth: bar.height * 0.8 + property real iconsWidth: score.height * 1.2 property alias noteOptionsIndex: noteOptions.currentIndex property alias lyricsOrPianoModeIndex: lyricsOrPianoModeOption.currentIndex property alias restOptionIndex: restOptions.currentIndex property alias clefButtonIndex: clefButton.currentIndex property bool noteOptionsVisible: false property bool playButtonVisible: false property bool clefButtonVisible: false property bool clearButtonVisible: false property bool undoButtonVisible: false property bool openButtonVisible: false property bool saveButtonVisible: false property bool changeAccidentalStyleButtonVisible: false property bool lyricsOrPianoModeOptionVisible: false property bool restOptionsVisible: false property bool bpmVisible: false signal undoButtonClicked signal clearButtonClicked signal openButtonClicked signal saveButtonClicked signal playButtonClicked signal clefAdded signal bpmIncreased signal bpmDecreased signal emitOptionMessage(string message) SwitchableOptions { id: noteOptions source: "qrc:/gcompris/src/activities/piano_composition/resource/genericNote%1.svg".arg(optionsRow.noteLengthName[currentIndex][1]) nbOptions: optionsRow.noteLengthName.length currentIndex: 2 onClicked: { background.currentType = optionsRow.noteLengthName[currentIndex][1] emitOptionMessage(optionsRow.noteLengthName[currentIndex][0]) } visible: noteOptionsVisible } Item { id: bpmMeter width: 4 * optionsRow.iconsWidth height: optionsRow.iconsWidth + 10 visible: bpmVisible Rectangle { color: "yellow" opacity: 0.1 border.width: 2 border.color: "black" anchors.fill: parent radius: 10 } Image { source: "qrc:/gcompris/src/core/resource/bar_previous.svg" sourceSize.width: parent.width / 4 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit anchors.left: parent.left anchors.leftMargin: -10 anchors.verticalCenter: parent.verticalCenter Timer { id: decreaseBpm interval: 500 repeat: true onTriggered: { bpmDecreased() interval = 1 } onRunningChanged: { if(!running) interval = 500 } } MouseArea { anchors.fill: parent onPressed: { parent.scale = 0.85 bpmDecreased() decreaseBpm.start() } onReleased: { decreaseBpm.stop() parent.scale = 1 } } } GCText { //: BPM is the abbreviation for Beats Per Minute. text: qsTr("%1 BPM").arg(multipleStaff.bpmValue) width: 0.6 * parent.width height: width verticalAlignment: Text.AlignVCenter anchors.centerIn: parent fontSizeMode: Text.Fit } Image { source: "qrc:/gcompris/src/core/resource/bar_next.svg" sourceSize.width: parent.width / 4 width: sourceSize.width height: width fillMode: Image.PreserveAspectFit anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: -10 Timer { id: increaseBpm interval: 500 repeat: true onTriggered: { bpmIncreased() interval = 1 } onRunningChanged: { if(!running) interval = 500 } } MouseArea { anchors.fill: parent onPressed: { parent.scale = 0.85 bpmIncreased() increaseBpm.start() } onReleased: { increaseBpm.stop() parent.scale = 1 } } } } Image { id: playButton source: "qrc:/gcompris/src/activities/piano_composition/resource/play.svg" sourceSize.width: optionsRow.iconsWidth visible: playButtonVisible MouseArea { anchors.fill: parent onClicked: { optionsRow.playButtonClicked() emitOptionMessage(qsTr("Play melody")) multipleStaff.play() } } } Item { id: clefOption width: 2.3 * optionsRow.iconsWidth height: optionsRow.iconsWidth + 10 visible: clefButtonVisible Rectangle { color: "yellow" opacity: 0.1 border.width: 2 border.color: "black" anchors.fill: parent radius: 10 } SwitchableOptions { id: clefButton nbOptions: 2 source: "qrc:/gcompris/src/activities/piano_composition/resource/" + (!currentIndex ? "trebbleClefButton.svg" : "bassClefButton.svg") sourceSize.width: optionsRow.iconsWidth visible: clefButtonVisible onClicked: { //: Treble clef and Bass clef are the notations to indicate the pitch of the sound written on it. emitOptionMessage(!currentIndex ? qsTr("Treble clef") : qsTr("Bass clef")) } anchors.topMargin: 3 anchors.left: parent.left anchors.leftMargin: 5 } Image { id: addClefButton sourceSize.width: optionsRow.iconsWidth / 1.4 source: "qrc:/gcompris/src/activities/piano_composition/resource/add.svg" anchors.left: clefButton.right anchors.leftMargin: 8 visible: clefButton.visible anchors.top: parent.top anchors.topMargin: 10 MouseArea { anchors.fill: parent onPressed: parent.scale = 0.8 onReleased: { background.clefType = !clefButton.currentIndex ? "Treble" : "Bass" emitOptionMessage(!clefButton.currentIndex ? qsTr("Added Treble clef") : qsTr("Added Bass clef")) parent.scale = 1 clefAdded() } } } } Image { id: clearButton source: "qrc:/gcompris/src/activities/piano_composition/resource/edit-clear.svg" sourceSize.width: optionsRow.iconsWidth visible: clearButtonVisible MouseArea { anchors.fill: parent onClicked: clearButtonClicked() } } Image { id: undoButton source: "qrc:/gcompris/src/activities/piano_composition/resource/undo.svg" sourceSize.width: optionsRow.iconsWidth visible: undoButtonVisible MouseArea { anchors.fill: parent onClicked: { emitOptionMessage(qsTr("Undo")) undoButtonClicked() } } } Image { id: openButton source: "qrc:/gcompris/src/activities/piano_composition/resource/open.svg" sourceSize.width: optionsRow.iconsWidth visible: openButtonVisible MouseArea { anchors.fill: parent onClicked: openButtonClicked() } } Image { id: saveButton source: "qrc:/gcompris/src/activities/piano_composition/resource/save.svg" sourceSize.width: optionsRow.iconsWidth visible: saveButtonVisible MouseArea { anchors.fill: parent onClicked: saveButtonClicked() } } Image { id: changeAccidentalStyleButton source: changeAccidentalStyleButtonVisible ? (piano.useSharpNotation ? "qrc:/gcompris/src/activities/piano_composition/resource/blacksharp.svg" : "qrc:/gcompris/src/activities/piano_composition/resource/blackflat.svg") : "" sourceSize.width: optionsRow.iconsWidth visible: changeAccidentalStyleButtonVisible MouseArea { anchors.fill: parent onClicked: { piano.useSharpNotation = !piano.useSharpNotation //: Sharp notes and Flat notes represents the accidental style of the notes in the music. emitOptionMessage(piano.useSharpNotation ? qsTr("Sharp notes") : qsTr("Flat notes")) } } } SwitchableOptions { id: lyricsOrPianoModeOption nbOptions: optionsRow.lyricsOrPianoModes.length source: "qrc:/gcompris/src/activities/piano_composition/resource/%1-icon.svg".arg(optionsRow.lyricsOrPianoModes[currentIndex][1]) anchors.top: parent.top anchors.topMargin: 4 visible: lyricsOrPianoModeOptionVisible onClicked: emitOptionMessage(optionsRow.lyricsOrPianoModes[currentIndex][0]) } Item { id: rests width: 2.3 * optionsRow.iconsWidth height: optionsRow.iconsWidth + 10 visible: restOptionsVisible Rectangle { color: "yellow" opacity: 0.1 border.width: 2 border.color: "black" anchors.fill: parent radius: 10 } // Since the half rest image is just the rotated image of whole rest image, we check if the current rest type is half, we assign the source as whole rest and rotate it by 180 degrees. SwitchableOptions { id: restOptions readonly property string restTypeImage: ((optionsRow.noteLengthName[currentIndex][1] === "Half") ? "Whole" : optionsRow.noteLengthName[currentIndex][1]).toLowerCase() source: "qrc:/gcompris/src/activities/piano_composition/resource/%1Rest.svg".arg(restTypeImage) nbOptions: optionsRow.noteLengthName.length onClicked: { background.restType = optionsRow.noteLengthName[currentIndex][1] emitOptionMessage(optionsRow.translatedRestNames[currentIndex]) } rotation: optionsRow.noteLengthName[currentIndex][1] === "Half" ? 180 : 0 visible: restOptionsVisible anchors.topMargin: -3 anchors.left: parent.left anchors.leftMargin: 5 } Image { id: addRestButton sourceSize.width: optionsRow.iconsWidth / 1.4 source: "qrc:/gcompris/src/activities/piano_composition/resource/add.svg" anchors.left: restOptions.right anchors.leftMargin: 8 visible: restOptions.visible anchors.top: parent.top anchors.topMargin: 10 MouseArea { anchors.fill: parent onPressed: parent.scale = 0.8 onReleased: { emitOptionMessage(optionsRow.restAddedMessage[restOptionIndex]) parent.scale = 1 background.addMusicElementAndPushToStack(restType.toLowerCase(), "Rest") } } } } } diff --git a/src/activities/play_piano/PlayPiano.qml b/src/activities/play_piano/PlayPiano.qml index f35fdf508..851285ae1 100644 --- a/src/activities/play_piano/PlayPiano.qml +++ b/src/activities/play_piano/PlayPiano.qml @@ -1,337 +1,336 @@ /* GCompris - PlayPiano.qml * * Copyright (C) 2018 Aman Kumar Gupta * * Authors: * Beth Hadley (GTK+ version) * Aman Kumar Gupta (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.6 import QtQuick.Controls 1.5 import GCompris 1.0 import QtMultimedia 5.0 import "../../core" import "../piano_composition" import "play_piano.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} isMusicalActivity: true - property bool horizontalLayout: width > height + property bool horizontalLayout: width > height * 1.2 pageComponent: Rectangle { id: background anchors.fill: parent color: "#ABCDEF" signal start signal stop Directory { id: directory } Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } Keys.onPressed: { var keyboardBindings = {} keyboardBindings[Qt.Key_1] = 0 keyboardBindings[Qt.Key_2] = 1 keyboardBindings[Qt.Key_3] = 2 keyboardBindings[Qt.Key_4] = 3 keyboardBindings[Qt.Key_5] = 4 keyboardBindings[Qt.Key_6] = 5 keyboardBindings[Qt.Key_7] = 6 keyboardBindings[Qt.Key_8] = 7 keyboardBindings[Qt.Key_F1] = 1 keyboardBindings[Qt.Key_F2] = 2 keyboardBindings[Qt.Key_F3] = 3 keyboardBindings[Qt.Key_F4] = 4 keyboardBindings[Qt.Key_F5] = 5 if(piano.whiteKeysEnabled && !iAmReady.visible) { if(event.key >= Qt.Key_1 && event.key <= Qt.Key_8) { piano.keyRepeater.itemAt(keyboardBindings[event.key]).whiteKey.keyPressed() } else if(event.key >= Qt.Key_F1 && event.key <= Qt.Key_F5) { if(piano.blackKeysEnabled) findBlackKey(keyboardBindings[event.key]) } else if(event.key === Qt.Key_Space) { multipleStaff.play() } else if(event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { Activity.undoPreviousAnswer() } } } function findBlackKey(keyNumber) { for(var i = 0; keyNumber; i++) { if(piano.keyRepeater.itemAt(i) == undefined) break if(piano.keyRepeater.itemAt(i).blackKey.visible) keyNumber-- if(keyNumber === 0) piano.keyRepeater.itemAt(i).blackKey.keyPressed() } } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property GCSfx audioEffects: activity.audioEffects property alias multipleStaff: multipleStaff property alias piano: piano property alias bar: bar property alias bonus: bonus property alias score: score property alias iAmReady: iAmReady property alias introductoryAudioTimer: introductoryAudioTimer property alias parser: parser property string mode: "coloredNotes" } onStart: { dialogActivityConfig.getInitialConfiguration() Activity.start(items) } onStop: { Activity.stop() } property string clefType: (items.bar.level <= 5) ? "Treble" : "Bass" Timer { id: introductoryAudioTimer interval: 4000 onRunningChanged: { if(running) Activity.isIntroductoryAudioPlaying = true else { Activity.isIntroductoryAudioPlaying = false Activity.initSubLevel() } } } JsonParser { id: parser } Rectangle { anchors.fill: parent color: "black" opacity: 0.3 visible: iAmReady.visible z: 10 MouseArea { anchors.fill: parent } } ReadyButton { id: iAmReady focus: true z: 10 onClicked: { Activity.initLevel() } } Score { id: score anchors.top: background.top anchors.bottom: undefined numberOfSubLevels: 5 width: horizontalLayout ? parent.width / 10 : (parent.width - instruction.x - instruction.width - 1.5 * anchors.rightMargin) } Rectangle { id: instruction radius: 10 width: background.width * 0.6 height: background.height / 9 anchors.horizontalCenter: parent.horizontalCenter opacity: 0.8 border.width: 6 color: "white" border.color: "#87A6DD" GCText { color: "black" z: 3 anchors.fill: parent anchors.rightMargin: parent.width * 0.02 anchors.leftMargin: parent.width * 0.02 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter fontSizeMode: Text.Fit wrapMode: Text.WordWrap text: qsTr("Click on the piano keys that match the given notes.") } } MultipleStaff { id: multipleStaff width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.8 height: horizontalLayout ? parent.height * 0.85 : parent.height * 0.58 nbStaves: 1 clef: clefType coloredNotes: (items.mode === "coloredNotes") ? ['C', 'D', 'E', 'F', 'G', 'A', 'B'] : [] isFlickable: false anchors.horizontalCenter: parent.horizontalCenter anchors.top: instruction.bottom anchors.topMargin: horizontalLayout ? parent.height * 0.02 : parent.height * 0.15 onNoteClicked: { playNoteAudio(musicElementModel.get(noteIndex).noteName_, musicElementModel.get(noteIndex).noteType_, musicElementModel.get(noteIndex).soundPitch_) } centerNotesPosition: true } PianoOctaveKeyboard { id: piano - width: horizontalLayout ? parent.width * 0.3 : parent.width * 0.7 - height: horizontalLayout ? parent.height * 0.3 : parent.width * 0.26 + width: horizontalLayout ? parent.width * 0.5 : parent.width * 0.7 + height: parent.height * 0.3 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: bar.top anchors.bottomMargin: 20 blackLabelsVisible: ([4, 5, 9, 10].indexOf(items.bar.level) != -1) blackKeysEnabled: blackLabelsVisible && !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeysEnabled: !multipleStaff.isMusicPlaying && !introductoryAudioTimer.running whiteKeyNoteLabelsTreble: [ whiteKeyNoteLabelsArray.slice(18, 26) ] whiteKeyNoteLabelsBass: [ whiteKeyNoteLabelsArray.slice(11, 19)] onNoteClicked: { multipleStaff.playNoteAudio(note, "Quarter", clefType, 1000) Activity.checkAnswer(note) } useSharpNotation: true } Rectangle { id: optionDeck width: optionsRow.changeAccidentalStyleButtonVisible ? optionsRow.iconsWidth * 3.3 : optionsRow.iconsWidth * 2.2 height: optionsRow.iconsWidth * 1.1 color: "white" opacity: 0.5 radius: 10 y: horizontalLayout ? piano.y : multipleStaff.y / 2 + instruction.height - height / 2 x: horizontalLayout ? multipleStaff.x + multipleStaff.width : background.width / 2 - width / 2 } OptionsRow { id: optionsRow - anchors.horizontalCenter: optionDeck.horizontalCenter - anchors.verticalCenter: optionDeck.verticalCenter + anchors.centerIn: optionDeck playButtonVisible: true undoButtonVisible: true onUndoButtonClicked: Activity.undoPreviousAnswer() } ExclusiveGroup { id: configOptions } DialogActivityConfig { id: dialogActivityConfig content: Component { Column { id: column spacing: 5 width: dialogActivityConfig.width height: dialogActivityConfig.height property alias coloredNotesModeBox: coloredNotesModeBox property alias colorlessNotesModeBox: colorlessNotesModeBox GCDialogCheckBox { id: coloredNotesModeBox width: column.width - 50 text: qsTr("Display colored notes.") checked: items.mode === "coloredNotes" exclusiveGroup: configOptions onCheckedChanged: { if(coloredNotesModeBox.checked) { items.mode = "coloredNotes" } } } GCDialogCheckBox { id: colorlessNotesModeBox width: coloredNotesModeBox.width text: qsTr("Display colorless notes.") checked: items.mode === "colorlessNotes" exclusiveGroup: configOptions onCheckedChanged: { if(colorlessNotesModeBox.checked) { items.mode = "colorlessNotes" } } } } } onLoadData: { if(dataToSave && dataToSave["mode"]) items.mode = dataToSave["mode"] } onSaveData: dataToSave["mode"] = items.mode onClose: { home() } onVisibleChanged: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level | config | reload } onHelpClicked: displayDialog(dialogHelp) onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() onConfigClicked: { dialogActivityConfig.active = true displayDialog(dialogActivityConfig) } onReloadClicked: { multipleStaff.eraseAllNotes() iAmReady.visible = true } } Bonus { id: bonus Component.onCompleted: win.connect(Activity.nextSubLevel) } } }