From 54069cb8956418885a4fb0ec7c796968ac0bcb39 Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Fri, 7 Feb 2025 13:57:14 +0000 Subject: [PATCH] Percussion mouse input popup - implement percussion note popup content --- src/engraving/dom/noteentry.cpp | 14 +- src/engraving/dom/shadownote.h | 1 + src/notation/CMakeLists.txt | 4 + src/notation/internal/notationinteraction.cpp | 43 ++-- src/notation/internal/notationinteraction.h | 2 +- src/notation/notationmodule.cpp | 3 + src/notation/notationscene.qrc | 1 + .../internal/PercussionNotePopupContent.qml | 115 ++++++++++ .../internal/ShadowNotePopup.qml | 8 +- .../percussionnotepopupcontentmodel.cpp | 206 ++++++++++++++++++ .../percussionnotepopupcontentmodel.h | 69 ++++++ .../view/internal/shadownotepopupmodel.cpp | 20 +- .../view/notationviewinputcontroller.cpp | 8 +- 13 files changed, 468 insertions(+), 26 deletions(-) create mode 100644 src/notation/qml/MuseScore/NotationScene/internal/PercussionNotePopupContent.qml create mode 100644 src/notation/view/internal/percussionnotepopupcontentmodel.cpp create mode 100644 src/notation/view/internal/percussionnotepopupcontentmodel.h diff --git a/src/engraving/dom/noteentry.cpp b/src/engraving/dom/noteentry.cpp index af32724f9b902..0c6c7341ca50d 100644 --- a/src/engraving/dom/noteentry.cpp +++ b/src/engraving/dom/noteentry.cpp @@ -80,7 +80,18 @@ NoteVal Score::noteValForPosition(Position pos, AccidentalType at, bool& error) break; } const Drumset* ds = instr->drumset(); - nval.pitch = m_is.drumNote(); + nval.pitch = m_is.drumNote(); + if (nval.pitch < 0 || !ds->isValid(nval.pitch) || ds->line(nval.pitch) != line) { + // Drum note from input state is not valid - fall back to the first valid pitch for this line... + m_is.setDrumNote(-1); + for (int pitch = 0; pitch < mu::engraving::DRUM_INSTRUMENTS; ++pitch) { + if (!ds->isValid(pitch) || ds->line(pitch) != line) { + continue; + } + nval.pitch = pitch; + break; + } + } if (nval.pitch < 0) { error = true; return nval; @@ -447,6 +458,7 @@ Ret Score::putNote(const Position& p, bool replace) if (ds) { stemDirection = ds->stemDirection(nval.pitch); + m_is.setVoice(ds->voice(nval.pitch)); } break; } diff --git a/src/engraving/dom/shadownote.h b/src/engraving/dom/shadownote.h index 5ee14391be265..071484ce9e007 100644 --- a/src/engraving/dom/shadownote.h +++ b/src/engraving/dom/shadownote.h @@ -73,6 +73,7 @@ class ShadowNote final : public EngravingItem SymId flagSym() const; AccidentalType accidentalType() const; const std::set& articulationIds() const; + double segmentSkylineBottomY() const; double segmentSkylineTopY() const; diff --git a/src/notation/CMakeLists.txt b/src/notation/CMakeLists.txt index 6de1193f01b21..0fd35dc9ca9b9 100644 --- a/src/notation/CMakeLists.txt +++ b/src/notation/CMakeLists.txt @@ -176,8 +176,12 @@ set(MODULE_SRC ${CMAKE_CURRENT_LIST_DIR}/view/internal/dynamicpopupmodel.h ${CMAKE_CURRENT_LIST_DIR}/view/internal/partialtiepopupmodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/internal/partialtiepopupmodel.h + ${CMAKE_CURRENT_LIST_DIR}/view/internal/shadownotepopupmodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/internal/shadownotepopupmodel.h + ${CMAKE_CURRENT_LIST_DIR}/view/internal/percussionnotepopupcontentmodel.cpp + ${CMAKE_CURRENT_LIST_DIR}/view/internal/percussionnotepopupcontentmodel.h + ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.cpp ${CMAKE_CURRENT_LIST_DIR}/view/selectionfiltermodel.h ${CMAKE_CURRENT_LIST_DIR}/view/editgridsizedialogmodel.cpp diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 3f08527d2d14e..574af626f87ce 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -383,11 +383,10 @@ bool NotationInteraction::showShadowNote(const PointF& pos) params.accidentalType = inputState.accidentalType(); params.articulationIds = inputState.articulationIds(); - showShadowNoteAtPosition(shadowNote, params, position); - return true; + return showShadowNoteAtPosition(shadowNote, params, position); } -void NotationInteraction::showShadowNoteAtPosition(ShadowNote& shadowNote, const ShadowNoteParams& params, Position& position) +bool NotationInteraction::showShadowNoteAtPosition(ShadowNote& shadowNote, const ShadowNoteParams& params, Position& position) { const mu::engraving::InputState& inputState = score()->inputState(); const Staff* staff = score()->staff(position.staffIdx); @@ -414,20 +413,35 @@ void NotationInteraction::showShadowNoteAtPosition(ShadowNote& shadowNote, const mu::engraving::NoteHeadGroup noteheadGroup = mu::engraving::NoteHeadGroup::HEAD_NORMAL; mu::engraving::NoteHeadType noteHead = params.duration.headType(); + int line = position.line; + voice_idx_t voice = 0; + int drumNotePitch = -1; if (instr->useDrumset()) { - const mu::engraving::Drumset* ds = instr->drumset(); - int pitch = inputState.drumNote(); - if (pitch >= 0 && ds->isValid(pitch)) { - line = ds->line(pitch); - noteheadGroup = ds->noteHead(pitch); - } - } + const Drumset* ds = instr->drumset(); - voice_idx_t voice = 0; - if (inputState.drumNote() != -1 && inputState.drumset() && inputState.drumset()->isValid(inputState.drumNote())) { - voice = inputState.drumset()->voice(inputState.drumNote()); + const int noteInputPitch = inputState.drumNote(); + + if (noteInputPitch > 0 && ds->isValid(noteInputPitch) && ds->line(noteInputPitch) == line) { + noteheadGroup = ds->noteHead(noteInputPitch); + voice = ds->voice(noteInputPitch); + drumNotePitch = noteInputPitch; + } else { + for (int pitch = 0; pitch < mu::engraving::DRUM_INSTRUMENTS; ++pitch) { + if (ds->isValid(pitch) && ds->line(pitch) == line) { + noteheadGroup = ds->noteHead(pitch); + voice = ds->voice(pitch); + drumNotePitch = pitch; + break; + } + } + if (drumNotePitch < 0) { + shadowNote.setVisible(false); + m_shadowNoteChanged.notify(); + return false; + } + } } else { voice = inputState.voice(); } @@ -458,7 +472,7 @@ void NotationInteraction::showShadowNoteAtPosition(ShadowNote& shadowNote, const delete rest; } else { if (mu::engraving::NoteHeadGroup::HEAD_CUSTOM == noteheadGroup) { - symNotehead = instr->drumset()->noteHeads(inputState.drumNote(), noteHead); + symNotehead = instr->drumset()->noteHeads(drumNotePitch, noteHead); } else { symNotehead = Note::noteHead(0, noteheadGroup, noteHead); } @@ -472,6 +486,7 @@ void NotationInteraction::showShadowNoteAtPosition(ShadowNote& shadowNote, const shadowNote.setPos(position.pos); m_shadowNoteChanged.notify(); + return true; } void NotationInteraction::hideShadowNote() diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 31b61158a84d3..0f428eacfe6c0 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -325,7 +325,7 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable std::set articulationIds; }; - void showShadowNoteAtPosition(mu::engraving::ShadowNote& note, const ShadowNoteParams& params, mu::engraving::Position& pos); + bool showShadowNoteAtPosition(mu::engraving::ShadowNote& note, const ShadowNoteParams& params, mu::engraving::Position& pos); bool needStartEditGrip(QKeyEvent* event) const; bool handleKeyPress(QKeyEvent* event); diff --git a/src/notation/notationmodule.cpp b/src/notation/notationmodule.cpp index 3fac6920aab1d..1eb5bfe182e23 100644 --- a/src/notation/notationmodule.cpp +++ b/src/notation/notationmodule.cpp @@ -77,7 +77,9 @@ #include "view/internal/stringtuningssettingsmodel.h" #include "view/internal/dynamicpopupmodel.h" #include "view/internal/partialtiepopupmodel.h" + #include "view/internal/shadownotepopupmodel.h" +#include "view/internal/percussionnotepopupcontentmodel.h" #include "view/percussionpanel/percussionpanelmodel.h" @@ -201,6 +203,7 @@ void NotationModule::registerUiTypes() qmlRegisterUncreatableType("MuseScore.NotationScene", 1, 0, "ShadowNotePopupContent", "Cannot create"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "ShadowNotePopupModel"); + qmlRegisterType("MuseScore.NotationScene", 1, 0, "PercussionNotePopupContentModel"); qmlRegisterType("MuseScore.NotationScene", 1, 0, "PaintedEngravingItem"); diff --git a/src/notation/notationscene.qrc b/src/notation/notationscene.qrc index b5ba3b9676fc1..f3f17fb460f10 100644 --- a/src/notation/notationscene.qrc +++ b/src/notation/notationscene.qrc @@ -79,5 +79,6 @@ qml/MuseScore/NotationScene/internal/PartialTieMenuRowItem.qml qml/MuseScore/NotationScene/EditPercussionShortcutDialog.qml qml/MuseScore/NotationScene/internal/ShadowNotePopup.qml + qml/MuseScore/NotationScene/internal/PercussionNotePopupContent.qml diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionNotePopupContent.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionNotePopupContent.qml new file mode 100644 index 0000000000000..c0d84a66f619a --- /dev/null +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionNotePopupContent.qml @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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.15 + +import Muse.Ui 1.0 +import Muse.UiComponents 1.0 +import MuseScore.NotationScene 1.0 + +Row { + id: root + + height: 72 + spacing: 6 + + PercussionNotePopupContentModel { + id: contentModel + + Component.onCompleted: { + contentModel.init() + } + } + + Row { + id: buttonRow + + visible: contentModel.shouldShowButtons + + height: root.height + spacing: 6 + + FlatButton { + id: prevButton + + height: buttonRow.height + width: 48 + + contentItem: StyledIconLabel { + iconCode: IconCode.CHEVRON_LEFT + font.pixelSize: 36 + } + + onClicked: { + contentModel.prevDrumNote() + } + } + + FlatButton { + id: nextButton + + height: buttonRow.height + width: 48 + + contentItem: StyledIconLabel { + iconCode: IconCode.CHEVRON_RIGHT + font.pixelSize: 36 + } + + onClicked: { + contentModel.nextDrumNote() + } + } + } + + Row { + id: labelRow + + height: root.height + spacing: 18 + + rightPadding: 18 + leftPadding: 18 + + StyledTextLabel { + id: percussionNoteName + + height: labelRow.height + verticalAlignment: Text.AlignVCenter + + text: contentModel.percussionNoteName + font: ui.theme.headerBoldFont + } + + StyledTextLabel { + id: keyboardShortcut + + height: labelRow.height + verticalAlignment: Text.AlignVCenter + + text: contentModel.keyboardShortcut + font: ui.theme.headerBoldFont + opacity: 0.8 + } + } +} + diff --git a/src/notation/qml/MuseScore/NotationScene/internal/ShadowNotePopup.qml b/src/notation/qml/MuseScore/NotationScene/internal/ShadowNotePopup.qml index 3a2aed940972b..9858e878b8306 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/ShadowNotePopup.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/ShadowNotePopup.qml @@ -41,7 +41,7 @@ StyledPopupView { takeFocusOnClick: false padding: 0 // The popup will "steal" mouse events if the padding overlaps with the shadow note area - margins: 0 + margins: 6 signal elementRectChanged(var elementRect) @@ -75,11 +75,7 @@ StyledPopupView { Component { id: percussionContent - - Rectangle { // Placeholder... - color: "red" - width: 200 - height: 50 + PercussionNotePopupContent { } } } diff --git a/src/notation/view/internal/percussionnotepopupcontentmodel.cpp b/src/notation/view/internal/percussionnotepopupcontentmodel.cpp new file mode 100644 index 0000000000000..b260151086029 --- /dev/null +++ b/src/notation/view/internal/percussionnotepopupcontentmodel.cpp @@ -0,0 +1,206 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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 . + */ + +#include "percussionnotepopupcontentmodel.h" + +#include "engraving/dom/drumset.h" +#include "engraving/dom/shadownote.h" + +using namespace mu::notation; + +PercussionNotePopupContentModel::PercussionNotePopupContentModel(QObject* parent) + : QObject{parent} +{ +} + +void PercussionNotePopupContentModel::init() +{ + interaction()->shadowNoteChanged().onNotify(this, [this]() { + emit shouldShowButtonsChanged(); + emit percussionNoteNameChanged(); + emit keyboardShortcutChanged(); + }); +} + +void PercussionNotePopupContentModel::prevDrumNote() +{ + const Drumset* ds = currentDrumset(); + mu::engraving::ShadowNote* shadowNote = currentShadowNote(); + IF_ASSERT_FAILED(ds && shadowNote) { + return; + } + + const int currNote = currentDrumPitch(); + const int currLine = ds->line(currNote); + + int pitch = currNote - 1; + while (pitch != currNote) { + if (pitch < 0) { + // Wrap around + pitch = mu::engraving::DRUM_INSTRUMENTS - 1; + } + if (ds->isValid(pitch) && ds->line(pitch) == currLine) { + noteInput()->setDrumNote(pitch); + interaction()->showShadowNote(shadowNote->pos()); + interaction()->selectionChanged().notify(); // TEMPORARY HACK - triggers AbstractNotationPaintView::scheduleRedraw + emit percussionNoteNameChanged(); + emit keyboardShortcutChanged(); + return; + } + --pitch; + } + + UNREACHABLE; +} + +void PercussionNotePopupContentModel::nextDrumNote() +{ + const Drumset* ds = currentDrumset(); + mu::engraving::ShadowNote* shadowNote = currentShadowNote(); + IF_ASSERT_FAILED(ds && shadowNote) { + return; + } + + const int currNote = currentDrumPitch(); + const int currLine = ds->line(currNote); + + int pitch = currNote + 1; + while (pitch != currNote) { + if (pitch == mu::engraving::DRUM_INSTRUMENTS) { + // Wrap around + pitch = 0; + } + if (ds->isValid(pitch) && ds->line(pitch) == currLine) { + noteInput()->setDrumNote(pitch); + interaction()->showShadowNote(shadowNote->pos()); + interaction()->selectionChanged().notify(); // TEMPORARY HACK - triggers AbstractNotationPaintView::scheduleRedraw + emit percussionNoteNameChanged(); + emit keyboardShortcutChanged(); + return; + } + ++pitch; + } + + UNREACHABLE; +} + +bool PercussionNotePopupContentModel::shouldShowButtons() const +{ + const Drumset* ds = currentDrumset(); + if (!ds || currentDrumPitch() < 0) { + return false; + } + + const int line = ds->line(currentDrumPitch()); + + int drumsOnSameLine = 0; + for (int pitch = 0; pitch < mu::engraving::DRUM_INSTRUMENTS; ++pitch) { + if (ds->isValid(pitch) && ds->line(pitch) == line) { + ++drumsOnSameLine; + } + if (drumsOnSameLine > 1) { + return true; + } + } + + return false; +} + +QString PercussionNotePopupContentModel::percussionNoteName() const +{ + const Drumset* ds = currentDrumset(); + if (!ds || currentDrumPitch() < 0) { + return QString(); + } + + return ds->name(currentDrumPitch()); +} + +QString PercussionNotePopupContentModel::keyboardShortcut() const +{ + const Drumset* ds = currentDrumset(); + if (!ds || currentDrumPitch() < 0) { + return QString(); + } + + const muse::String shortcut = ds->shortcut(currentDrumPitch()); + return !shortcut.isEmpty() ? QString("(%1)").arg(shortcut) : QString(); +} + +INotationInteractionPtr PercussionNotePopupContentModel::interaction() const +{ + INotationPtr notation = globalContext()->currentNotation(); + return notation ? notation->interaction() : nullptr; +} + +INotationNoteInputPtr PercussionNotePopupContentModel::noteInput() const +{ + return interaction() ? interaction()->noteInput() : nullptr; +} + +mu::engraving::ShadowNote* PercussionNotePopupContentModel::currentShadowNote() const +{ + return interaction() ? interaction()->shadowNote() : nullptr; +} + +const Drumset* PercussionNotePopupContentModel::currentDrumset() const +{ + const mu::engraving::ShadowNote* shadowNote = currentShadowNote(); + if (!shadowNote) { + return nullptr; + } + + const Part* part = shadowNote->part(); + if (!part) { + return nullptr; + } + + const Instrument* inst = part->instrument(shadowNote->tick()); + return inst ? inst->drumset() : nullptr; +} + +int PercussionNotePopupContentModel::currentDrumPitch() const +{ + const Drumset* ds = currentDrumset(); + const mu::engraving::ShadowNote* shadowNote = currentShadowNote(); + if (!noteInput() || !ds || !shadowNote) { + return -1; + } + + const int shadowNoteLine = shadowNote->lineIndex(); + + // First check whether a drum note has been specified in the input state, and whether it's valid... + const int noteInputPitch = noteInput()->state().drumNote(); + if (noteInputPitch > 0 && ds->isValid(noteInputPitch) && ds->line(noteInputPitch) == shadowNoteLine) { + return noteInputPitch; + } + + // Otherwise just return the first pitch that matches the line that the shadowNote is currently on... + for (int pitch = 0; pitch < mu::engraving::DRUM_INSTRUMENTS; ++pitch) { + if (!ds->isValid(pitch) || ds->line(pitch) != shadowNoteLine) { + continue; + } + return pitch; + } + + return -1; +} diff --git a/src/notation/view/internal/percussionnotepopupcontentmodel.h b/src/notation/view/internal/percussionnotepopupcontentmodel.h new file mode 100644 index 0000000000000..3c37c1595b6cf --- /dev/null +++ b/src/notation/view/internal/percussionnotepopupcontentmodel.h @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * 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 . + */ + +#pragma once + +#include + +#include "modularity/ioc.h" +#include "context/iglobalcontext.h" + +#include "async/asyncable.h" + +namespace mu::notation { +class PercussionNotePopupContentModel : public QObject, public muse::Injectable, public muse::async::Asyncable +{ + Q_OBJECT + + Q_PROPERTY(bool shouldShowButtons READ shouldShowButtons NOTIFY shouldShowButtonsChanged) + Q_PROPERTY(QString percussionNoteName READ percussionNoteName NOTIFY percussionNoteNameChanged) + Q_PROPERTY(QString keyboardShortcut READ keyboardShortcut NOTIFY keyboardShortcutChanged) + + muse::Inject globalContext = { this }; + +public: + explicit PercussionNotePopupContentModel(QObject* parent = nullptr); + + Q_INVOKABLE void init(); + + Q_INVOKABLE void prevDrumNote(); + Q_INVOKABLE void nextDrumNote(); + + bool shouldShowButtons() const; + QString percussionNoteName() const; + QString keyboardShortcut() const; + +signals: + void shouldShowButtonsChanged(); + void percussionNoteNameChanged(); + void keyboardShortcutChanged(); + +private: + INotationInteractionPtr interaction() const; + INotationNoteInputPtr noteInput() const; + + mu::engraving::ShadowNote* currentShadowNote() const; + const Drumset* currentDrumset() const; + + int currentDrumPitch() const; +}; +} diff --git a/src/notation/view/internal/shadownotepopupmodel.cpp b/src/notation/view/internal/shadownotepopupmodel.cpp index 35c43e88d3d9b..ea5c8d4948fa3 100644 --- a/src/notation/view/internal/shadownotepopupmodel.cpp +++ b/src/notation/view/internal/shadownotepopupmodel.cpp @@ -32,7 +32,15 @@ ShadowNotePopupModel::ShadowNotePopupModel(QObject* parent) bool ShadowNotePopupModel::canOpen(const EngravingItem* element) { - // TODO: Determine based on context + if (!element || !element->isShadowNote() || !element->visible()) { + return false; + } + + if (element->staff() && element->staff()->isDrumStaff(element->tick())) { + // Can open with PERCUSSON_CONTENT type + return true; + } + return false; } @@ -51,7 +59,15 @@ void ShadowNotePopupModel::init() ShadowNotePopupContent::ContentType ShadowNotePopupModel::currentPopupType() const { - // TODO: Determine based on context + const mu::engraving::ShadowNote* shadowNote = interaction()->shadowNote(); + if (!shadowNote || !shadowNote->visible()) { + return ShadowNotePopupContent::ContentType::NONE; + } + + if (shadowNote->staff() && shadowNote->staff()->isDrumStaff(shadowNote->tick())) { + return ShadowNotePopupContent::ContentType::PERCUSSION_CONTENT; + } + return ShadowNotePopupContent::ContentType::NONE; } diff --git a/src/notation/view/notationviewinputcontroller.cpp b/src/notation/view/notationviewinputcontroller.cpp index b8cfd2c8c91d4..cc78365536def 100644 --- a/src/notation/view/notationviewinputcontroller.cpp +++ b/src/notation/view/notationviewinputcontroller.cpp @@ -1088,13 +1088,17 @@ void NotationViewInputController::keyPressEvent(QKeyEvent* event) } else if (event->key() == Qt::Key_Shift) { updateShadowNotePopupVisibility(); } + + updateShadowNotePopupVisibility(); } void NotationViewInputController::keyReleaseEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Shift) { - viewInteraction()->editElement(event); + if (event->key() != Qt::Key_Shift) { + return; } + + viewInteraction()->editElement(event); updateShadowNotePopupVisibility(/*forceHide*/ true); }