diff --git a/src/activities/piano_composition/MelodyList.qml b/src/activities/piano_composition/MelodyList.qml --- a/src/activities/piano_composition/MelodyList.qml +++ b/src/activities/piano_composition/MelodyList.qml @@ -115,6 +115,7 @@ onClicked: { dialogBackground.selectedMelodyIndex = index items.multipleStaff.stopAudios() + items.multipleStaff.nbStaves = 2 items.multipleStaff.loadFromData(melody) lyricsArea.setLyrics(title, _origin, lyrics) } diff --git a/src/activities/piano_composition/MultipleStaff.qml b/src/activities/piano_composition/MultipleStaff.qml --- a/src/activities/piano_composition/MultipleStaff.qml +++ b/src/activities/piano_composition/MultipleStaff.qml @@ -32,12 +32,10 @@ property string clef property int distanceBetweenStaff: multipleStaff.height / 3.3 - property int currentStaff: 0 - - property int nbMaxNotesPerStaff: 6 + property int insertingIndex: 0 // Stores the note number which is to be replaced. - property int noteToReplace: -1 + property int selectedIndex: -1 property bool noteIsColored property bool noteHoverEnabled: true property bool centerNotesPosition: false @@ -45,8 +43,10 @@ readonly property bool isMusicPlaying: musicTimer.running property alias flickableStaves: flickableStaves + property alias notesModel: notesModel property real flickableTopMargin: multipleStaff.height / 14 + distanceBetweenStaff / 3.5 property bool isFlickable: true + property int currentEnteringStaff: 0 /** * Emitted when a note is clicked. @@ -55,11 +55,6 @@ */ signal noteClicked(string noteName, string noteType, int noteIndex) - /** - * Emitted when a change in any note is made to push it to the undo stack. - */ - signal pushToUndoStack(int noteIndex, string oldNoteName, string oldNoteType) - /** * Emitted for the notes while a melody is playing. * @@ -98,7 +93,7 @@ } } - property real noteWidth: (multipleStaff.width - 15 - staves.itemAt(0).clefImageWidth) / 11.3 + readonly property real noteWidth: (multipleStaff.width - 15 - staves.itemAt(0).clefImageWidth) / 10 Repeater { id: notesRepeater model: notesModel @@ -110,15 +105,8 @@ width: flickableStaves.noteWidth height: multipleStaff.height / 5 - readonly property int currentStaffNb: index / nbMaxNotesPerStaff - readonly property bool isFirstNoteOnStaff: (index % nbMaxNotesPerStaff) === 0 - readonly property real defaultX: isFirstNoteOnStaff ? staves.itemAt(0).clefImageWidth - : (notesRepeater.itemAt(index - 1) == undefined) ? 0 - : (notesRepeater.itemAt(index - 1).x + flickableStaves.noteWidth) - readonly property real centeredPosition: (multipleStaff.width / 2.5 - (flickableStaves.noteWidth * notesModel.count / 2) + defaultX) - readonly property real shiftDistance: blackType != "" ? width / 7 : 0 - - x: shiftDistance + (multipleStaff.centerNotesPosition ? centeredPosition : defaultX) + property int staffNb: staffNb_ + readonly property real shiftDistance: blackType != "" ? flickableStaves.noteWidth / 6 : 0 noteDetails: multipleStaff.getNoteDetails(noteName, noteType) @@ -133,14 +121,18 @@ highlightTimer.start() } + x: shiftDistance + (isFirstNote_ ? (staves.itemAt(0).clefImageWidth + 5) + : (notesRepeater.itemAt(index - 1) == undefined) ? 0 + : (notesRepeater.itemAt(index - 1).x + flickableStaves.noteWidth)) + y: { - if(noteDetails === undefined || staves.itemAt(currentStaffNb) == undefined) + if(noteDetails === undefined || staves.itemAt(staffNb) == undefined) return 0 var verticalDistanceBetweenLines = staves.itemAt(0).verticalDistanceBetweenLines var shift = -verticalDistanceBetweenLines / 2 var relativePosition = noteDetails.positionOnStaff - var imageY = flickableTopMargin + staves.itemAt(currentStaffNb).y + 2 * verticalDistanceBetweenLines + var imageY = flickableTopMargin + staves.itemAt(staffNb).y + 2 * verticalDistanceBetweenLines if(rotation === 180) { return imageY - (4 - relativePosition) * verticalDistanceBetweenLines + shift @@ -189,34 +181,77 @@ /** * Adds a note to the staff. */ - function addNote(noteName, noteType, highlightWhenPlayed, playAudio, isReplacing) { + function addNote(noteName, noteType, highlightWhenPlayed, playAudio) { var duration if(noteType === "Rest") duration = calculateTimerDuration(noteName) else duration = calculateTimerDuration(noteType) - if(!isReplacing) { - pushToUndoStack(notesModel.count, "none", "none", noteName, noteType) - notesModel.append({"noteName_": noteName, "noteType_": noteType, "mDuration": duration, - "highlightWhenPlayed": highlightWhenPlayed}) + var isNextStaff = notesModel.count && ((staves.itemAt(0).width - notesRepeater.itemAt(notesModel.count - 1).x) < 2 * flickableStaves.noteWidth) + var isFirstPosition = false + if((notesModel.count == 0) || isNextStaff) { + if(isNextStaff) + multipleStaff.currentEnteringStaff++ + + if(multipleStaff.currentEnteringStaff >= multipleStaff.nbStaves) { + var melody = getAllNotes() + multipleStaff.nbStaves++ + flickableStaves.flick(0, - nbStaves * multipleStaff.height) + multipleStaff.currentEnteringStaff = 0 + loadFromData(melody) + multipleStaff.currentEnteringStaff++ + } + + isFirstPosition = true } + + if(multipleStaff.insertingIndex == notesModel.count) + notesModel.append({"noteName_": noteName, "noteType_": noteType, "mDuration": duration, + "highlightWhenPlayed": highlightWhenPlayed, "staffNb_": multipleStaff.currentEnteringStaff, + "isFirstNote_": isFirstPosition}) else { - var oldNoteDetails = notesModel.get(noteToReplace) - pushToUndoStack(noteToReplace, oldNoteDetails.noteName_, oldNoteDetails.noteType_) - notesModel.set(noteToReplace, { "noteName_": noteName, "noteType_": noteType, "mDuration": duration }) + var tempModel = createNotesBackup() + tempModel.splice(multipleStaff.insertingIndex, 0, { "noteName_": noteName, "noteType_": noteType }) + redrawNotes(tempModel) + } + + multipleStaff.insertingIndex = notesModel.count + multipleStaff.selectedIndex = -1 + + if(playAudio) + playNoteAudio(noteName, noteType) + } + + /** + * Creates a backup of the notesModel before erasing it. + * + * This backup data is used to redraw the notes. + */ + function createNotesBackup() { + var tempModel = [] + for(var i = 0; i < notesModel.count; i++) + tempModel.push(JSON.parse(JSON.stringify(notesModel.get(i)))) + + return tempModel + } + + /** + * Redraws all the notes on the staves. + */ + function redrawNotes(notes) { + eraseAllNotes() + for(var i = 0; i < notes.length; i++) { + addNote(notes[i]["noteName_"], notes[i]["noteType_"], false, false) } - if(notesModel.count > nbMaxNotesPerStaff * nbStaves) { + if((multipleStaff.currentEnteringStaff + 1 < multipleStaff.nbStaves) && (multipleStaff.nbStaves > 2)) { var melody = getAllNotes() - nbStaves++ + multipleStaff.nbStaves = multipleStaff.currentEnteringStaff + 1 flickableStaves.flick(0, - nbStaves * multipleStaff.height) - currentStaff = 0 + multipleStaff.currentEnteringStaff = 0 loadFromData(melody) } - - if(playAudio) - playNoteAudio(noteName, noteType) } /** @@ -226,10 +261,12 @@ * @param noteType: new note type. */ function replaceNote(noteName, noteType) { - if(noteToReplace != -1) { - addNote(noteName, noteType, false, true, true) + if(selectedIndex != -1) { + var tempModel = createNotesBackup() + tempModel[selectedIndex]= { "noteName_": noteName, "noteType_": noteType } + redrawNotes(tempModel) } - noteToReplace = -1 + selectedIndex = -1 } /** @@ -249,9 +286,9 @@ else restName = "eighth" - var oldNoteDetails = notesModel.get(noteIndex) - pushToUndoStack(noteIndex, oldNoteDetails.noteName_, oldNoteDetails.noteType_) notesModel.set(noteIndex, { "noteName_": restName, "noteType_": "Rest" }) + var tempModel = createNotesBackup() + redrawNotes(tempModel) } /** @@ -259,20 +296,28 @@ */ function eraseAllNotes() { notesModel.clear() - noteToReplace = -1 + selectedIndex = -1 + multipleStaff.insertingIndex = 0 + multipleStaff.currentEnteringStaff = 0 } /** * Undo the change made to the last note. */ function undoChange(undoNoteDetails) { - if(undoNoteDetails.oldNoteName_ === "none") + if(undoNoteDetails.oldNoteName_ === "none") { + if((undoNoteDetails.noteIndex_ === (notesModel.count - 1)) && notesModel.get(notesModel.count - 1).isFirstNote_ && (multipleStaff.currentEnteringStaff != 0)) + multipleStaff.currentEnteringStaff-- notesModel.remove(undoNoteDetails.noteIndex_) + + var tempModel = createNotesBackup() + redrawNotes(tempModel) + } else { - noteToReplace = undoNoteDetails.noteIndex_ + selectedIndex = undoNoteDetails.noteIndex_ replaceNote(undoNoteDetails.oldNoteName_, undoNoteDetails.oldNoteType_) } - noteToReplace = -1 + selectedIndex = -1 } /** @@ -354,7 +399,7 @@ noteName += melody[i][1] } - addNote(noteName, noteType, false, false, false) + addNote(noteName, noteType, false, false) } } @@ -380,10 +425,12 @@ function play() { musicTimer.currentNote = 0 musicTimer.interval = 500 + /* for(var v = 1 ; v < currentStaff ; ++ v) staves.itemAt(v).showMetronome = false // Only display metronome if we want to staves.itemAt(0).showMetronome = isMetronomeDisplayed + **/ musicTimer.start() } diff --git a/src/activities/piano_composition/Note.qml b/src/activities/piano_composition/Note.qml --- a/src/activities/piano_composition/Note.qml +++ b/src/activities/piano_composition/Note.qml @@ -101,14 +101,14 @@ } Rectangle { - id: replaceIndicator + id: selectedNoteIndicator width: noteImage.width height: noteImage.height * 0.9 color: "blue" opacity: 0.6 border.color: "white" radius: width / 5 - visible: noteToReplace === index + visible: selectedIndex == index } Image { diff --git a/src/activities/piano_composition/OptionsRow.qml b/src/activities/piano_composition/OptionsRow.qml --- a/src/activities/piano_composition/OptionsRow.qml +++ b/src/activities/piano_composition/OptionsRow.qml @@ -59,6 +59,7 @@ signal clearButtonClicked signal openButtonClicked signal saveButtonClicked + signal restReplaced signal emitOptionMessage(string message) SwitchableOptions { @@ -113,9 +114,8 @@ onClicked: { background.staffMode = optionsRow.staffModes[currentIndex][1] emitOptionMessage(optionsRow.staffModes[currentIndex][0]) - if(background.staffMode != "replace") { - multipleStaff.noteToReplace = -1 - } + multipleStaff.selectedIndex = -1 + multipleStaff.insertingIndex = multipleStaff.notesModel.count } } @@ -239,10 +239,18 @@ //: %1 is the name of the rest which is added and displayed from the variable translatedRestNames. emitOptionMessage(qsTr("Added %1").arg(optionsRow.translatedRestNames[restOptionIndex])) parent.scale = 1 - if(background.staffMode === "add") - multipleStaff.addNote(restType.toLowerCase(), "Rest", false, false) - else - multipleStaff.replaceNote(restType.toLowerCase(), "Rest") + if(background.staffMode === "add") { + if(multipleStaff.selectedIndex == 0) + background.askInsertDirection(restType.toLowerCase(), "Rest") + else + background.addNoteAndPushToStack(restType.toLowerCase(), "Rest") + } + else { + if(multipleStaff.selectedIndex != -1) { + restReplaced() + multipleStaff.replaceNote(restType.toLowerCase(), "Rest") + } + } } } } diff --git a/src/activities/piano_composition/Piano_composition.qml b/src/activities/piano_composition/Piano_composition.qml --- a/src/activities/piano_composition/Piano_composition.qml +++ b/src/activities/piano_composition/Piano_composition.qml @@ -120,7 +120,6 @@ property string restType: "Whole" property string clefType: bar.level == 2 ? "bass" : "treble" property string staffMode: "add" - property bool undidChange: false property bool isLyricsMode: (optionsRow.lyricsOrPianoModeIndex === 1) && optionsRow.lyricsOrPianoModeOptionVisible File { @@ -230,7 +229,6 @@ height: horizontalLayout ? parent.height * 0.58 : parent.height * 0.3 nbStaves: 2 clef: clefType - nbMaxNotesPerStaff: 10 noteIsColored: true isMetronomeDisplayed: true anchors.right: horizontalLayout ? parent.right: undefined @@ -239,17 +237,18 @@ anchors.topMargin: parent.height * 0.1 anchors.rightMargin: parent.width * 0.043 onNoteClicked: { - if(background.staffMode === "add") + if(background.staffMode === "add") { + selectedIndex = noteIndex + multipleStaff.insertingIndex = noteIndex + 1 playNoteAudio(noteName, noteType) + } else if(background.staffMode === "replace") - noteToReplace = noteIndex - else + selectedIndex = noteIndex + else { + var oldNoteDetails = notesModel.get(noteIndex) + Activity.pushToStack(noteIndex, oldNoteDetails.noteName_, oldNoteDetails.noteType_) multipleStaff.eraseNote(noteIndex) - } - onPushToUndoStack: { - // If we have undid the change, we won't push the undid change in the stack as the undoStack will enter in a loop. - if(!background.undidChange) - Activity.pushToStack(noteIndex, oldNoteName, oldNoteType) + } } onNotePlayed: piano.indicateKey(noteName) } @@ -279,13 +278,39 @@ blackLabelsVisible: [3, 4, 5, 6, 7, 8].indexOf(items.bar.level) == -1 ? false : true useSharpNotation: bar.level != 4 blackKeysEnabled: bar.level > 2 + visible: !background.isLyricsMode onNoteClicked: { - if(background.staffMode === "add") - multipleStaff.addNote(note, currentType, false, true) - else if(background.staffMode === "replace") + if(background.staffMode === "add") { + if(multipleStaff.selectedIndex == 0) + parent.askInsertDirection(note, currentType) + else + parent.addNoteAndPushToStack(note, currentType) + } + else if(background.staffMode === "replace") { + var oldNoteDetails = multipleStaff.notesModel.get(multipleStaff.selectedIndex) + Activity.pushToStack(multipleStaff.selectedIndex, oldNoteDetails.noteName_, oldNoteDetails.noteType_) multipleStaff.replaceNote(note, currentType) + } } - visible: !background.isLyricsMode + } + + function askInsertDirection(noteName, noteType) { + Core.showMessageDialog(main, + qsTr("Do you want to insert it to the left/right of the 1st note?"), + qsTr("Left"), function() { + multipleStaff.insertingIndex-- + addNoteAndPushToStack(noteName, noteType) + }, + qsTr("Right"), function() { + addNoteAndPushToStack(noteName, noteType) + }, + null + ) + } + + function addNoteAndPushToStack(noteName, noteType) { + Activity.pushToStack(multipleStaff.insertingIndex, "none", "none") + multipleStaff.addNote(noteName, noteType, false, true) } Image { @@ -348,9 +373,7 @@ restOptionsVisible: bar.level > 5 onUndoButtonClicked: { - background.undidChange = true Activity.undoChange() - background.undidChange = false } onClearButtonClicked: { Core.showMessageDialog(main, @@ -359,6 +382,7 @@ Activity.undoStack = [] lyricsArea.resetLyricsArea() multipleStaff.eraseAllNotes() + multipleStaff.nbStaves = 2 }, qsTr("No"), null, null @@ -377,6 +401,10 @@ } onSaveButtonClicked: Activity.saveMelody() onEmitOptionMessage: clickedOptionMessage.show(message) + onRestReplaced: { + var oldNoteDetails = multipleStaff.notesModel.get(multipleStaff.selectedIndex) + Activity.pushToStack(multipleStaff.selectedIndex, oldNoteDetails.noteName_, oldNoteDetails.noteType_) + } } DialogHelp { diff --git a/src/activities/piano_composition/piano_composition.js b/src/activities/piano_composition/piano_composition.js --- a/src/activities/piano_composition/piano_composition.js +++ b/src/activities/piano_composition/piano_composition.js @@ -102,7 +102,6 @@ items.piano.currentOctaveNb = items.piano.defaultOctaveNb items.multipleStaff.nbStaves = 2 items.background.staffMode = "add" - items.multipleStaff.noteToReplace = -1 items.optionsRow.staffModeIndex = 0 items.lyricsArea.resetLyricsArea() undoStack = []