diff --git a/src/engraving/dom/undo.h b/src/engraving/dom/undo.h index e2ca09bf1924d..7db6f5a602f0c 100644 --- a/src/engraving/dom/undo.h +++ b/src/engraving/dom/undo.h @@ -1505,8 +1505,8 @@ class ChangeDrumset : public UndoCommand void flip(EditData*) override; public: - ChangeDrumset(Instrument* i, const Drumset* d, Part* p) - : instrument(i), drumset(*d), part(p) {} + ChangeDrumset(Instrument* i, const Drumset& d, Part* p) + : instrument(i), drumset(d), part(p) {} UNDO_TYPE(CommandType::ChangeDrumset) UNDO_NAME("ChangeDrumset") diff --git a/src/notation/internal/notationconfiguration.h b/src/notation/internal/notationconfiguration.h index a24da7e3a5687..bcce30892ff6b 100644 --- a/src/notation/internal/notationconfiguration.h +++ b/src/notation/internal/notationconfiguration.h @@ -207,11 +207,11 @@ class NotationConfiguration : public INotationConfiguration, public muse::async: muse::async::Notification autoShowPercussionPanelChanged() const override; bool showPercussionPanelPadSwapDialog() const override; - void setShowPercussionPanelPadSwapDialog(bool show); + void setShowPercussionPanelPadSwapDialog(bool show) override; muse::async::Notification showPercussionPanelPadSwapDialogChanged() const override; bool percussionPanelMoveMidiNotesAndShortcuts() const override; - void setPercussionPanelMoveMidiNotesAndShortcuts(bool move); + void setPercussionPanelMoveMidiNotesAndShortcuts(bool move) override; muse::async::Notification percussionPanelMoveMidiNotesAndShortcutsChanged() const override; muse::io::path_t styleFileImportPath() const override; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 50627b3412eb2..2a96236561858 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -194,6 +194,7 @@ NotationInteraction::NotationInteraction(Notation* notation, INotationUndoStackP m_undoStack->undoRedoNotification().onNotify(this, [this]() { endEditElement(); + notifyAboutNoteInputStateChanged(); }); m_undoStack->stackChanged().onNotify(this, [this]() { diff --git a/src/notation/internal/notationparts.cpp b/src/notation/internal/notationparts.cpp index 919d9e86451ef..022212fd9e088 100644 --- a/src/notation/internal/notationparts.cpp +++ b/src/notation/internal/notationparts.cpp @@ -640,7 +640,7 @@ void NotationParts::replaceDrumset(const InstrumentKey& instrumentKey, const Dru if (undoable) { startEdit(TranslatableString("undoableAction", "Edit drumset")); - score()->undo(new mu::engraving::ChangeDrumset(instrument, &newDrumset, part)); + score()->undo(new mu::engraving::ChangeDrumset(instrument, newDrumset, part)); apply(); } else { instrument->setDrumset(&newDrumset); diff --git a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml index 446243502b166..3052c00ab85c0 100644 --- a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml +++ b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml @@ -415,6 +415,7 @@ Item { orientation: Qt.Horizontal navigation.panel: addRowButtonPanel + drawFocusBorderInsideRect: true onClicked: { padGrid.model.addEmptyRow() @@ -428,9 +429,7 @@ Item { visible: !percModel.enabled - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: (padGrid.cellHeight / 2) - (panelDisabledLabel.height / 2) + anchors.centerIn: parent font: ui.theme.bodyFont text: qsTrc("notation/percussion", "Select an unpitched percussion staff to see available sounds") diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml index f988adc02d9ea..664b5c5e0181f 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml @@ -121,10 +121,6 @@ DropArea { accessible.visualItem: footerFocusBorder accessible.enabled: footerNavCtrl.enabled - - onTriggered: { - // TODO: trigger context menu (not yet implemented) - } } Rectangle { @@ -188,6 +184,8 @@ DropArea { id: padContentComponent PercussionPanelPadContent { + id: padContent + padModel: root.padModel panelMode: root.panelMode useNotationPreview: root.useNotationPreview @@ -195,6 +193,13 @@ DropArea { footerHeight: prv.footerHeight padSwapActive: dragHandler.active + + Connections { + target: footerNavCtrl + function onTriggered() { + padContent.openFooterContextMenu() + } + } } } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml index cc5cf174b1dec..5f64099c6ff8c 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml @@ -37,6 +37,13 @@ Column { property bool padSwapActive: false + function openFooterContextMenu() { + if (!root.padModel) { + return + } + menuLoader.toggleOpened(root.padModel.footerContextMenuItems) + } + Item { id: mainContentArea @@ -154,6 +161,17 @@ Column { color: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.buttonOpacityNormal) + MouseArea { + id: footerMouseArea + + anchors.fill: parent + enabled: root.panelMode !== PanelMode.EDIT_LAYOUT + + onClicked: { + root.openFooterContextMenu() + } + } + StyledTextLabel { id: shortcutLabel @@ -190,5 +208,13 @@ Column { text: Boolean(root.padModel) ? root.padModel.midiNote : "" } + + StyledMenuLoader { + id: menuLoader + + onHandleMenuItem: function(itemId) { + root.padModel.handleMenuItem(itemId) + } + } } } diff --git a/src/notation/view/paintedengravingitem.cpp b/src/notation/view/paintedengravingitem.cpp index 896095e7b0f58..6d0458ec70450 100644 --- a/src/notation/view/paintedengravingitem.cpp +++ b/src/notation/view/paintedengravingitem.cpp @@ -88,7 +88,7 @@ void PaintedEngravingItem::paintNotationPreview(muse::draw::Painter& painter, qr params.drawStaff = true; - painter.fillRect(params.rect, configuration()->scoreInversionColor()); + painter.fillRect(params.rect, configuration()->noteBackgroundColor()); EngravingItemPreviewPainter::paintPreview(m_item.get(), params); } diff --git a/src/notation/view/percussionpanel/percussionpanelmodel.cpp b/src/notation/view/percussionpanel/percussionpanelmodel.cpp index 7ca2978a90e56..da697654b4bd2 100644 --- a/src/notation/view/percussionpanel/percussionpanelmodel.cpp +++ b/src/notation/view/percussionpanel/percussionpanelmodel.cpp @@ -21,6 +21,9 @@ */ #include "percussionpanelmodel.h" + +#include "notation/utilities/percussionutilities.h" + #include "types/translatablestring.h" #include "ui/view/iconcodes.h" @@ -125,36 +128,27 @@ void PercussionPanelModel::init() QList PercussionPanelModel::layoutMenuItems() const { - const TranslatableString padNamesTitle("notation/percussion", "Pad names"); - // Using IconCode for this instead of "checked" because we want the tick to display on the left - const int padNamesIcon = static_cast(m_useNotationPreview ? IconCode::Code::NONE : IconCode::Code::TICK_RIGHT_ANGLE); - - const TranslatableString notationPreviewTitle("notation/percussion", "Notation preview"); - // Using IconCode for this instead of "checked" because we want the tick to display on the left - const int notationPreviewIcon = static_cast(m_useNotationPreview ? IconCode::Code::TICK_RIGHT_ANGLE : IconCode::Code::NONE); + const auto editLayoutTitle = m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT + ? muse::qtrc("notation/percussion", "Finish editing") + : muse::qtrc("notation/percussion", "Edit layout"); - const TranslatableString editLayoutTitle = m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT - ? TranslatableString("notation/percussion", "Finish editing") - : TranslatableString("notation/percussion", "Edit layout"); - const int editLayoutIcon = static_cast(IconCode::Code::CONFIGURE); - - const TranslatableString resetLayoutTitle("notation/percussion", "Reset layout"); - const int resetLayoutIcon = static_cast(IconCode::Code::UNDO); + static constexpr int editLayoutIcon = static_cast(IconCode::Code::CONFIGURE); + static constexpr int resetLayoutIcon = static_cast(IconCode::Code::UNDO); QList menuItems = { - { { "id", PAD_NAMES_CODE }, - { "title", padNamesTitle.qTranslated() }, { "icon", padNamesIcon }, { "enabled", true } }, + { { "id", PAD_NAMES_CODE }, { "title", muse::qtrc("notation/percussion", "Pad names") }, + { "checkable", true }, { "checked", !m_useNotationPreview }, { "enabled", true } }, - { { "id", NOTATION_PREVIEW_CODE }, - { "title", notationPreviewTitle.qTranslated() }, { "icon", notationPreviewIcon }, { "enabled", true } }, + { { "id", NOTATION_PREVIEW_CODE }, { "title", muse::qtrc("notation/percussion", "Notation preview") }, + { "checkable", true }, { "checked", m_useNotationPreview }, { "enabled", true } }, { }, // separator { { "id", EDIT_LAYOUT_CODE }, - { "title", editLayoutTitle.qTranslated() }, { "icon", editLayoutIcon }, { "enabled", true } }, + { "title", editLayoutTitle }, { "icon", editLayoutIcon }, { "enabled", true } }, { { "id", RESET_LAYOUT_CODE }, - { "title", resetLayoutTitle.qTranslated() }, { "icon", resetLayoutIcon }, { "enabled", true } }, + { "title", muse::qtrc("notation/percussion", "Reset layout") }, { "icon", resetLayoutIcon }, { "enabled", true } }, }; return menuItems; @@ -167,8 +161,13 @@ void PercussionPanelModel::handleMenuItem(const QString& itemId) } else if (itemId == NOTATION_PREVIEW_CODE) { setUseNotationPreview(true); } else if (itemId == EDIT_LAYOUT_CODE) { - const bool currentlyEditing = m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT; - currentlyEditing ? finishEditing() : setCurrentPanelMode(PanelMode::Mode::EDIT_LAYOUT); + if (m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT) { + finishEditing(); + m_padListModel->focusFirstActivePad(); + return; + } + setCurrentPanelMode(PanelMode::Mode::EDIT_LAYOUT); + m_padListModel->padFocusRequested(0); } else if (itemId == RESET_LAYOUT_CODE) { resetLayout(); } @@ -183,27 +182,23 @@ void PercussionPanelModel::finishEditing(bool discardChanges) return; } - Drumset* updatedDrumset = m_padListModel->drumset(); m_padListModel->removeEmptyRows(); - NoteInputState inputState = interaction()->noteInput()->state(); - const Staff* staff = inputState.staff; + const std::pair instAndPart = getCurrentInstrumentAndPart(); + Instrument* inst = instAndPart.first; + Part* part = instAndPart.second; - IF_ASSERT_FAILED(staff && staff->part()) { + if (discardChanges) { + m_padListModel->setDrumset(inst ? inst->drumset() : nullptr); + setCurrentPanelMode(m_panelModeToRestore); return; } - Instrument* inst = staff->part()->instrument(inputState.segment->tick()); - - IF_ASSERT_FAILED(inst && inst->drumset()) { + IF_ASSERT_FAILED(inst && inst->drumset() && part) { return; } - if (discardChanges) { - m_padListModel->setDrumset(inst->drumset()); - setCurrentPanelMode(m_panelModeToRestore); - return; - } + Drumset updatedDrumset = *m_padListModel->drumset(); for (int i = 0; i < m_padListModel->padList().size(); ++i) { const PercussionPanelPadModel* model = m_padListModel->padList().at(i); @@ -214,7 +209,7 @@ void PercussionPanelModel::finishEditing(bool discardChanges) const int row = i / m_padListModel->numColumns(); const int column = i % m_padListModel->numColumns(); - engraving::DrumInstrument& drum = updatedDrumset->drum(model->pitch()); + engraving::DrumInstrument& drum = updatedDrumset.drum(model->pitch()); drum.panelRow = row; drum.panelColumn = column; @@ -224,8 +219,7 @@ void PercussionPanelModel::finishEditing(bool discardChanges) } // Return if nothing changed after edit... - if (inst->drumset() && updatedDrumset - && *inst->drumset() == *updatedDrumset) { + if (*inst->drumset() == updatedDrumset) { setCurrentPanelMode(m_panelModeToRestore); m_padListModel->focusLastActivePad(); return; @@ -234,7 +228,7 @@ void PercussionPanelModel::finishEditing(bool discardChanges) INotationUndoStackPtr undoStack = notation()->undoStack(); undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Edit percussion panel layout")); - score()->undo(new engraving::ChangeDrumset(inst, updatedDrumset, staff->part())); + score()->undo(new engraving::ChangeDrumset(inst, updatedDrumset, part)); undoStack->commitChanges(); setCurrentPanelMode(m_panelModeToRestore); @@ -283,11 +277,22 @@ void PercussionPanelModel::setUpConnections() setEnabled(m_padListModel->hasActivePads()); }); - m_padListModel->padTriggered().onReceive(this, [this](int pitch) { - switch (currentPanelMode()) { - case PanelMode::Mode::EDIT_LAYOUT: return; - case PanelMode::Mode::WRITE: writePitch(pitch); // fall through - case PanelMode::Mode::SOUND_PREVIEW: playPitch(pitch); + m_padListModel->padActionRequested().onReceive(this, [this](PercussionPanelPadModel::PadAction action, int pitch) { + switch (action) { + case PercussionPanelPadModel::PadAction::TRIGGER: + onPadTriggered(pitch); + break; + case PercussionPanelPadModel::PadAction::DUPLICATE: + onDuplicatePadRequested(pitch); + break; + case PercussionPanelPadModel::PadAction::DELETE: + onDeletePadRequested(pitch); + break; + case PercussionPanelPadModel::PadAction::DEFINE_SHORTCUT: + onDefinePadShortcutRequested(pitch); + break; + default: + break; } }); @@ -338,6 +343,76 @@ bool PercussionPanelModel::eventFilter(QObject* watched, QEvent* event) return true; } +void PercussionPanelModel::onPadTriggered(int pitch) +{ + switch (currentPanelMode()) { + case PanelMode::Mode::EDIT_LAYOUT: return; + case PanelMode::Mode::WRITE: writePitch(pitch); // fall through + case PanelMode::Mode::SOUND_PREVIEW: playPitch(pitch); + default: break; + } +} + +void PercussionPanelModel::onDuplicatePadRequested(int pitch) +{ + const std::pair instAndPart = getCurrentInstrumentAndPart(); + Instrument* inst = instAndPart.first; + Part* part = instAndPart.second; + + IF_ASSERT_FAILED(inst && part) { + return; + } + + const int nextAvailablePitch = m_padListModel->nextAvailablePitch(pitch); + if (nextAvailablePitch < 0) { + // TODO: Show some sort of warning dialog? + LOGE() << "No space to duplicate drum pad"; + return; + } + const int nextAvailableIndex = m_padListModel->nextAvailableIndex(pitch); + + Drumset updatedDrumset = *m_padListModel->drumset(); + + engraving::DrumInstrument duplicateDrum = updatedDrumset.drum(pitch); + + duplicateDrum.shortcut = '\0'; // Don't steal the shortcut + duplicateDrum.panelRow = nextAvailableIndex / m_padListModel->numColumns(); + duplicateDrum.panelColumn = nextAvailableIndex % m_padListModel->numColumns(); + + updatedDrumset.setDrum(nextAvailablePitch, duplicateDrum); + + INotationUndoStackPtr undoStack = notation()->undoStack(); + + undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Duplicate percussion panel pad")); + score()->undo(new engraving::ChangeDrumset(inst, updatedDrumset, part)); + undoStack->commitChanges(); +} + +void PercussionPanelModel::onDeletePadRequested(int pitch) +{ + const std::pair instAndPart = getCurrentInstrumentAndPart(); + Instrument* inst = instAndPart.first; + Part* part = instAndPart.second; + + IF_ASSERT_FAILED(inst && part) { + return; + } + + Drumset updatedDrumset = *m_padListModel->drumset(); + updatedDrumset.setDrum(pitch, engraving::DrumInstrument()); + + INotationUndoStackPtr undoStack = notation()->undoStack(); + + undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Delete percussion panel pad")); + score()->undo(new engraving::ChangeDrumset(inst, updatedDrumset, part)); + undoStack->commitChanges(); +} + +void PercussionPanelModel::onDefinePadShortcutRequested(int) +{ + // TODO: Design in progress... +} + void PercussionPanelModel::writePitch(int pitch) { INotationUndoStackPtr undoStack = notation()->undoStack(); @@ -360,26 +435,17 @@ void PercussionPanelModel::writePitch(int pitch) void PercussionPanelModel::playPitch(int pitch) { - const mu::engraving::InputState& inputState = score()->inputState(); - if (!inputState.cr()) { + if (!interaction()) { return; } - Chord* chord = mu::engraving::Factory::createChord(inputState.lastSegment()); - chord->setParent(inputState.lastSegment()); - - Note* note = mu::engraving::Factory::createNote(chord); - note->setParent(chord); - - note->setStaffIdx(mu::engraving::track2staff(inputState.cr()->track())); - - const mu::engraving::NoteVal nval = score()->noteVal(pitch, /*transpose*/ false); - note->setNval(nval); + const NoteInputState inputState = interaction()->noteInput()->state(); + std::shared_ptr chord = PercussionUtilities::getDrumNoteForPreview(m_padListModel->drumset(), pitch); - playbackController()->playElements({ note }); + chord->setParent(inputState.segment); + chord->setTrack(inputState.currentTrack); - note->setParent(nullptr); - delete note; + playbackController()->playElements({ chord.get() }); } void PercussionPanelModel::resetLayout() @@ -388,16 +454,11 @@ void PercussionPanelModel::resetLayout() finishEditing(/*discardChanges*/ true); } - NoteInputState inputState = interaction()->noteInput()->state(); - const Staff* staff = inputState.staff; + const std::pair instAndPart = getCurrentInstrumentAndPart(); + Instrument* inst = instAndPart.first; + Part* part = instAndPart.second; - IF_ASSERT_FAILED(staff && staff->part()) { - return; - } - - Instrument* inst = staff->part()->instrument(inputState.segment->tick()); - - IF_ASSERT_FAILED(inst) { + IF_ASSERT_FAILED(inst && inst->drumset() && part) { return; } @@ -417,7 +478,7 @@ void PercussionPanelModel::resetLayout() INotationUndoStackPtr undoStack = notation()->undoStack(); undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Reset percussion panel layout")); - score()->undo(new engraving::ChangeDrumset(inst, &defaultLayout, staff->part())); + score()->undo(new engraving::ChangeDrumset(inst, defaultLayout, part)); undoStack->commitChanges(); } @@ -437,6 +498,23 @@ InstrumentTrackId PercussionPanelModel::currentTrackId() const return { staff->part()->id(), staff->part()->instrumentId(inputState.segment->tick()) }; } +std::pair PercussionPanelModel::getCurrentInstrumentAndPart() const +{ + if (!interaction()) { + return { nullptr, nullptr }; + } + + NoteInputState inputState = interaction()->noteInput()->state(); + + const Staff* staff = inputState.staff; + + Part* part = staff ? staff->part() : nullptr; + + Instrument* inst = part ? part->instrument(inputState.segment->tick()) : nullptr; + + return { inst, part }; +} + const project::IProjectAudioSettingsPtr PercussionPanelModel::audioSettings() const { return globalContext()->currentProject() ? globalContext()->currentProject()->audioSettings() : nullptr; diff --git a/src/notation/view/percussionpanel/percussionpanelmodel.h b/src/notation/view/percussionpanel/percussionpanelmodel.h index 0eec3c27d9cc3..2b612dfcf00ee 100644 --- a/src/notation/view/percussionpanel/percussionpanelmodel.h +++ b/src/notation/view/percussionpanel/percussionpanelmodel.h @@ -113,6 +113,11 @@ class PercussionPanelModel : public QObject, public muse::Injectable, public mus bool eventFilter(QObject* watched, QEvent* event) override; + void onPadTriggered(int pitch); + void onDuplicatePadRequested(int pitch); + void onDeletePadRequested(int pitch); + void onDefinePadShortcutRequested(int pitch); + void writePitch(int pitch); void playPitch(int pitch); @@ -121,6 +126,9 @@ class PercussionPanelModel : public QObject, public muse::Injectable, public mus mu::engraving::InstrumentTrackId currentTrackId() const; const project::IProjectAudioSettingsPtr audioSettings() const; + + std::pair getCurrentInstrumentAndPart() const; + const mu::notation::INotationPtr notation() const; const mu::notation::INotationInteractionPtr interaction() const; diff --git a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp index 39625d0d7b8f5..c8f4c23f2dc57 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp +++ b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp @@ -225,6 +225,16 @@ mu::engraving::Drumset PercussionPanelPadListModel::constructDefaultLayout(const return defaultLayout; } +void PercussionPanelPadListModel::focusFirstActivePad() +{ + for (int i = 0; i < m_padModels.size(); i++) { + if (m_padModels.at(i)) { + emit padFocusRequested(i); + return; + } + } +} + void PercussionPanelPadListModel::focusLastActivePad() { for (int i = m_padModels.size() - 1; i >= 0; --i) { @@ -235,6 +245,35 @@ void PercussionPanelPadListModel::focusLastActivePad() } } +int PercussionPanelPadListModel::nextAvailableIndex(int pitch) const +{ + const int currentModelIndex = getModelIndexForPitch(pitch); + for (int candidateIndex = currentModelIndex + 1; candidateIndex != currentModelIndex; ++candidateIndex) { + if (candidateIndex == m_padModels.size()) { + // Wrap around + candidateIndex = 0; + } + if (!m_padModels.at(candidateIndex)) { + return candidateIndex; + } + } + return m_padModels.size(); +} + +int PercussionPanelPadListModel::nextAvailablePitch(int pitch) const +{ + for (int candidatePitch = pitch + 1; candidatePitch != pitch; ++candidatePitch) { + if (candidatePitch == mu::engraving::DRUM_INSTRUMENTS) { + // Wrap around + candidatePitch = 0; + } + if (!m_drumset->isValid(candidatePitch)) { + return candidatePitch; + } + } + return -1; +} + void PercussionPanelPadListModel::load() { beginResetModel(); @@ -317,8 +356,8 @@ PercussionPanelPadModel* PercussionPanelPadListModel::createPadModelForPitch(int model->setPitch(pitch); - model->padTriggered().onNotify(this, [this, pitch]() { - m_triggeredChannel.send(pitch); + model->padActionTriggered().onReceive(this, [this, pitch](PercussionPanelPadModel::PadAction action) { + m_padActionRequestChannel.send(action, pitch); }); model->setNotationPreviewItem(PercussionUtilities::getDrumNoteForPreview(m_drumset, pitch)); @@ -395,6 +434,22 @@ void PercussionPanelPadListModel::swapMidiNotesAndShortcuts(int fromIndex, int t toModel->setKeyboardShortcut(tempShortcut); } +int PercussionPanelPadListModel::getModelIndexForPitch(int pitch) const +{ + IF_ASSERT_FAILED(m_drumset && m_drumset->isValid(pitch)) { + return -1; + } + + for (int i = 0; i < m_padModels.size(); ++i) { + const PercussionPanelPadModel* model = m_padModels.at(i); + if (model && model->pitch() == pitch) { + return i; + } + } + + return -1; +} + void PercussionPanelPadListModel::movePad(int fromIndex, int toIndex) { const int fromRow = fromIndex / NUM_COLUMNS; diff --git a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h index ee8d5610534ff..0920dc3b40df2 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h +++ b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h @@ -73,16 +73,19 @@ class PercussionPanelPadListModel : public QAbstractListModel, public muse::Inje int numPads() const { return m_padModels.count(); } void setDrumset(const engraving::Drumset* drumset); - engraving::Drumset* drumset() const { return m_drumset; } + const engraving::Drumset* drumset() const { return m_drumset; } QList padList() const { return m_padModels; } mu::engraving::Drumset constructDefaultLayout(const engraving::Drumset* templateDrumset) const; + int nextAvailableIndex(int pitch) const; //! NOTE: This may be equal to m_padModels.size() + int nextAvailablePitch(int pitch) const; + void focusFirstActivePad(); void focusLastActivePad(); muse::async::Notification hasActivePadsChanged() const { return m_hasActivePadsChanged; } - muse::async::Channel padTriggered() const { return m_triggeredChannel; } + muse::async::Channel padActionRequested() const { return m_padActionRequestChannel; } signals: void numPadsChanged(); @@ -103,6 +106,8 @@ class PercussionPanelPadListModel : public QAbstractListModel, public muse::Inje PercussionPanelPadModel* createPadModelForPitch(int pitch); int createModelIndexForPitch(int pitch) const; + int getModelIndexForPitch(int pitch) const; + void movePad(int fromIndex, int toIndex); muse::RetVal openPadSwapDialog(); @@ -116,6 +121,6 @@ class PercussionPanelPadListModel : public QAbstractListModel, public muse::Inje int m_padSwapStartIndex = -1; muse::async::Notification m_hasActivePadsChanged; - muse::async::Channel m_triggeredChannel; + muse::async::Channel m_padActionRequestChannel; }; } diff --git a/src/notation/view/percussionpanel/percussionpanelpadmodel.cpp b/src/notation/view/percussionpanel/percussionpanelpadmodel.cpp index 7a3ea0e4601c7..d9f2b94a90d4a 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadmodel.cpp +++ b/src/notation/view/percussionpanel/percussionpanelpadmodel.cpp @@ -22,7 +22,14 @@ #include "percussionpanelpadmodel.h" +#include "ui/view/iconcodes.h" + +static const QString DUPLICATE_PAD_CODE("duplicate-pad"); +static const QString DELETE_PAD_CODE("delete-pad"); +static const QString DEFINE_PAD_SHORTCUT_CODE("define-pad-shortcut"); + using namespace mu::notation; +using namespace muse::ui; PercussionPanelPadModel::PercussionPanelPadModel(QObject* parent) : QObject(parent) @@ -74,7 +81,40 @@ const QVariant PercussionPanelPadModel::notationPreviewItemVariant() const return QVariant::fromValue(m_notationPreviewItem); } +QList PercussionPanelPadModel::footerContextMenuItems() const +{ + static constexpr int duplicatePadIcon = static_cast(IconCode::Code::COPY); + static constexpr int deletePadIcon = static_cast(IconCode::Code::DELETE_TANK); + static constexpr int definePadShortcutIcon = static_cast(IconCode::Code::SHORTCUTS); + + QList menuItems = { + { { "id", DUPLICATE_PAD_CODE }, { "title", muse::qtrc("global", "Duplicate") }, + { "icon", duplicatePadIcon }, { "enabled", true } }, + + { { "id", DELETE_PAD_CODE }, { "title", muse::qtrc("global", "Delete") }, + { "icon", deletePadIcon }, { "enabled", true } }, + + { }, // separator + + { { "id", DEFINE_PAD_SHORTCUT_CODE }, { "title", muse::qtrc("shortcuts", "Define keyboard shortcut") }, + { "icon", definePadShortcutIcon }, { "enabled", true } }, + }; + + return menuItems; +} + +void PercussionPanelPadModel::handleMenuItem(const QString& itemId) +{ + if (itemId == DUPLICATE_PAD_CODE) { + m_padActionTriggered.send(PadAction::DUPLICATE); + } else if (itemId == DELETE_PAD_CODE) { + m_padActionTriggered.send(PadAction::DELETE); + } else if (itemId == DEFINE_PAD_SHORTCUT_CODE) { + m_padActionTriggered.send(PadAction::DEFINE_SHORTCUT); + } +} + void PercussionPanelPadModel::triggerPad() { - m_triggeredNotification.notify(); + m_padActionTriggered.send(PadAction::TRIGGER); } diff --git a/src/notation/view/percussionpanel/percussionpanelpadmodel.h b/src/notation/view/percussionpanel/percussionpanelpadmodel.h index 1a6c0d7c44026..9ed63728fc363 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadmodel.h +++ b/src/notation/view/percussionpanel/percussionpanelpadmodel.h @@ -43,6 +43,8 @@ class PercussionPanelPadModel : public QObject, public muse::async::Asyncable Q_PROPERTY(QVariant notationPreviewItem READ notationPreviewItemVariant NOTIFY notationPreviewItemChanged) + Q_PROPERTY(QList footerContextMenuItems READ footerContextMenuItems CONSTANT) + public: explicit PercussionPanelPadModel(QObject* parent = nullptr); @@ -62,8 +64,19 @@ class PercussionPanelPadModel : public QObject, public muse::async::Asyncable const QVariant notationPreviewItemVariant() const; + QList footerContextMenuItems() const; + Q_INVOKABLE void handleMenuItem(const QString& itemId); + Q_INVOKABLE void triggerPad(); - muse::async::Notification padTriggered() const { return m_triggeredNotification; } + + enum class PadAction { + TRIGGER, + DUPLICATE, + DELETE, + DEFINE_SHORTCUT, + }; + + muse::async::Channel padActionTriggered() const { return m_padActionTriggered; } signals: void padNameChanged(); @@ -81,6 +94,6 @@ class PercussionPanelPadModel : public QObject, public muse::async::Asyncable mu::engraving::ElementPtr m_notationPreviewItem; - muse::async::Notification m_triggeredNotification; + muse::async::Channel m_padActionTriggered; }; }