LCOV - code coverage report
Current view: top level - src/notation/internal - notationinteraction.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 4995 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "notationinteraction.h"
      24              : 
      25              : #include "log.h"
      26              : 
      27              : #include <memory>
      28              : #include <limits>
      29              : 
      30              : #include <QApplication> // for QApplication::cursorFlashTime()
      31              : #include <QClipboard>
      32              : #include <QDrag>
      33              : #include <QEventLoop>
      34              : #include <QGuiApplication>
      35              : #include <QKeyEvent>
      36              : #include <QKeySequence>
      37              : #include <QMimeData>
      38              : #include <QPainter>
      39              : #include <QRectF>
      40              : #include <QRegularExpression>
      41              : #include <QRegularExpressionMatch>
      42              : 
      43              : #include "defer.h"
      44              : #include "ptrutils.h"
      45              : #include "containers.h"
      46              : 
      47              : #include "draw/painter.h"
      48              : #include "draw/types/pen.h"
      49              : 
      50              : #include "engraving/iengravingconfiguration.h" // IWYU pragma: keep
      51              : 
      52              : // TODO: Don't include from engraving/internal
      53              : #include "engraving/internal/qmimedataadapter.h"
      54              : 
      55              : #include "engraving/dom/accidental.h"
      56              : #include "engraving/dom/actionicon.h"
      57              : #include "engraving/dom/anchors.h"
      58              : #include "engraving/dom/articulation.h"
      59              : #include "engraving/dom/barline.h"
      60              : #include "engraving/dom/box.h"
      61              : #include "engraving/dom/bracket.h"
      62              : #include "engraving/dom/chord.h"
      63              : #include "engraving/dom/drumset.h"
      64              : #include "engraving/dom/dummyparent.h"
      65              : #include "engraving/dom/dynamic.h"
      66              : #include "engraving/dom/elementgroup.h"
      67              : #include "engraving/dom/factory.h"
      68              : #include "engraving/dom/figuredbass.h"
      69              : #include "engraving/dom/fret.h"
      70              : #include "engraving/dom/gradualtempochange.h"
      71              : #include "engraving/dom/guitarbend.h"
      72              : #include "engraving/dom/hammeronpulloff.h"
      73              : #include "engraving/dom/image.h"
      74              : #include "engraving/dom/instrchange.h"
      75              : #include "engraving/dom/keysig.h"
      76              : #include "engraving/dom/lasso.h"
      77              : #include "engraving/dom/layoutbreak.h"
      78              : #include "engraving/dom/linkedobjects.h" // IWYU pragma: keep
      79              : #include "engraving/dom/lyrics.h"
      80              : #include "engraving/dom/masterscore.h"
      81              : #include "engraving/dom/measure.h"
      82              : #include "engraving/dom/mscore.h"
      83              : #include "engraving/dom/page.h"
      84              : #include "engraving/dom/part.h"
      85              : #include "engraving/dom/pitchspelling.h"
      86              : #include "engraving/dom/realizedharmony.h"
      87              : #include "engraving/dom/rest.h"
      88              : #include "engraving/dom/shadownote.h"
      89              : #include "engraving/dom/slur.h"
      90              : #include "engraving/dom/soundflag.h"
      91              : #include "engraving/dom/staff.h"
      92              : #include "engraving/dom/stafflines.h"
      93              : #include "engraving/dom/stafftype.h"
      94              : #include "engraving/dom/stafftypechange.h"
      95              : #include "engraving/dom/system.h"
      96              : #include "engraving/dom/tuplet.h"
      97              : #include "engraving/dom/utils.h"
      98              : #include "engraving/editing/editbeam.h"
      99              : #include "engraving/editing/editchord.h"
     100              : #include "engraving/editing/editclef.h"
     101              : #include "engraving/editing/editcrossstaff.h"
     102              : #include "engraving/editing/editduration.h"
     103              : #include "engraving/editing/editenharmonicspelling.h"
     104              : #include "engraving/editing/edithairpin.h"
     105              : #include "engraving/editing/editnote.h"
     106              : #include "engraving/editing/editspanner.h"
     107              : #include "engraving/editing/editaccidentalbrackets.h"
     108              : #include "engraving/editing/editparentheses.h"
     109              : #include "engraving/editing/editrehearsalmark.h"
     110              : #include "engraving/editing/editvisibility.h"
     111              : #include "engraving/editing/editvoice.h"
     112              : #include "engraving/editing/paste.h"
     113              : #include "engraving/editing/realizechordsymbols.h"
     114              : #include "engraving/editing/regroup.h"
     115              : #include "engraving/editing/reset.h"
     116              : #include "engraving/editing/navigation.h"
     117              : #include "engraving/editing/noteinput.h"
     118              : #include "engraving/editing/editpart.h"
     119              : #include "engraving/editing/editslashnotation.h"
     120              : #include "engraving/editing/editstretch.h"
     121              : #include "engraving/editing/edittie.h"
     122              : #include "engraving/editing/edittimesig.h"
     123              : #include "engraving/editing/edittuplet.h"
     124              : #include "engraving/editing/editpagelocks.h"
     125              : #include "engraving/editing/editposition.h"
     126              : #include "engraving/editing/editsystemlocks.h"
     127              : #include "engraving/editing/flip.h"
     128              : #include "engraving/editing/exchangevoices.h"
     129              : #include "engraving/editing/implodeexplode.h"
     130              : #include "engraving/editing/splitjoinmeasure.h"
     131              : #include "engraving/editing/textedit.h"
     132              : #include "engraving/editing/transaction/transaction.h"
     133              : #include "engraving/editing/transpose.h"
     134              : #include "engraving/rendering/iscorerenderer.h"
     135              : #include "engraving/rw/rwregister.h"
     136              : #include "engraving/rw/xmlreader.h"
     137              : 
     138              : #include "inotationviewstate.h"
     139              : #include "notationerrors.h"
     140              : #include "notation.h"
     141              : #include "notationnoteinput.h"
     142              : #include "notationselection.h"
     143              : #include "notationselectionfilter.h"
     144              : #include "scorecallbacks.h"
     145              : #include "inotationelements.h"
     146              : 
     147              : #include "utilities/scorerangeutilities.h"
     148              : 
     149              : #include "app_config.h"
     150              : 
     151              : using namespace mu::notation;
     152              : using namespace mu::engraving;
     153              : using namespace muse;
     154              : using namespace muse::draw;
     155              : using namespace muse::io;
     156              : 
     157            0 : static mu::engraving::KeyboardModifier keyboardModifier(Qt::KeyboardModifiers km)
     158              : {
     159            0 :     return mu::engraving::KeyboardModifier(int(km));
     160              : }
     161              : 
     162            0 : static double nudgeDistance(const mu::engraving::EditData& editData)
     163              : {
     164            0 :     double spatium = editData.element->spatium();
     165              : 
     166            0 :     if (editData.element->isBeam()) {
     167            0 :         if (editData.modifiers & Qt::ControlModifier) {
     168            0 :             return spatium;
     169            0 :         } else if (editData.modifiers & Qt::AltModifier) {
     170            0 :             return spatium * 4;
     171              :         }
     172              : 
     173            0 :         return spatium * 0.25;
     174              :     }
     175              : 
     176            0 :     if (editData.modifiers & Qt::ControlModifier) {
     177            0 :         return spatium * mu::engraving::MScore::nudgeStep10;
     178            0 :     } else if (editData.modifiers & Qt::AltModifier) {
     179            0 :         return spatium * mu::engraving::MScore::nudgeStep50;
     180              :     }
     181              : 
     182            0 :     return spatium * mu::engraving::MScore::nudgeStep;
     183              : }
     184              : 
     185            0 : static double nudgeDistance(const mu::engraving::EditData& editData, double raster)
     186              : {
     187            0 :     double distance = nudgeDistance(editData);
     188            0 :     if (raster > 0) {
     189            0 :         raster = editData.element->spatium() / raster;
     190            0 :         if (distance < raster) {
     191            0 :             distance = raster;
     192              :         }
     193              :     }
     194              : 
     195            0 :     return distance;
     196              : }
     197              : 
     198            0 : static PointF bindCursorPosToText(const PointF& cursorPos, const EngravingItem* text)
     199              : {
     200            0 :     if (cursorPos.isNull() || !text || !text->isTextBase()) {
     201            0 :         return PointF();
     202              :     }
     203              : 
     204            0 :     muse::RectF bbox = text->canvasBoundingRect();
     205              :     muse::PointF boundPos = muse::PointF(
     206            0 :         cursorPos.x() < bbox.left() ? bbox.left()
     207            0 :         : cursorPos.x() >= bbox.right() ? bbox.right() - 1 : cursorPos.x(),
     208            0 :         cursorPos.y() < bbox.top() ? bbox.top()
     209            0 :         : cursorPos.y() >= bbox.bottom() ? bbox.bottom() - 1 : cursorPos.y());
     210              : 
     211            0 :     return boundPos;
     212              : }
     213              : 
     214            0 : static AccidentalType accidentalType(const InputState& is, const NoteVal& nval, int line)
     215              : {
     216            0 :     if (is.rest()) {
     217            0 :         return AccidentalType::NONE;
     218              :     }
     219              : 
     220            0 :     AccidentalVal accVal = AccidentalVal::NATURAL;
     221            0 :     if (is.accidentalType() != AccidentalType::NONE) {
     222            0 :         accVal = Accidental::subtype2value(is.accidentalType());
     223              :     } else {
     224            0 :         accVal = mu::engraving::noteValToAccidentalVal(nval, is.staff(), is.tick());
     225              :     }
     226              : 
     227            0 :     bool error = false;
     228            0 :     const AccidentalVal existingAccVal = is.segment()->measure()->findAccidental(is.segment(), is.staffIdx(), line, error);
     229              : 
     230            0 :     if (!error && accVal != existingAccVal) {
     231            0 :         AccidentalType type = Accidental::value2subtype(accVal);
     232            0 :         if (type == AccidentalType::NONE) {
     233            0 :             type = AccidentalType::NATURAL;
     234              :         }
     235              : 
     236            0 :         return type;
     237              :     }
     238              : 
     239            0 :     return AccidentalType::NONE;
     240              : }
     241              : 
     242            0 : inline QString extractSyllable(const QString& text)
     243              : {
     244            0 :     QString _text = text;
     245              : 
     246            0 :     static const QRegularExpression lineBreaks("(\r|\n)+");
     247            0 :     _text.replace(lineBreaks, "\n");
     248            0 :     if (_text.startsWith(u"\n")) {
     249            0 :         _text.remove("\n");
     250              :     }
     251              : 
     252            0 :     static const QRegularExpression nonWhitespace("\\S");
     253            0 :     int textPos = _text.indexOf(nonWhitespace);
     254            0 :     if (textPos == -1) {
     255            0 :         return QString();
     256              :     }
     257              : 
     258            0 :     static const QRegularExpression separators("(_| |-|\n)");
     259            0 :     QRegularExpressionMatch match;
     260            0 :     int splitPos = _text.indexOf(separators, textPos, &match);
     261            0 :     if (splitPos == -1) {
     262            0 :         splitPos = _text.size();
     263              :     } else {
     264            0 :         splitPos += match.capturedLength();
     265              :     }
     266              : 
     267            0 :     return _text.mid(textPos, splitPos - textPos);
     268            0 : }
     269              : 
     270            0 : NotationInteraction::NotationInteraction(Notation* notation, INotationUndoStackPtr undoStack)
     271            0 :     : muse::Contextable(notation->iocContext()), m_notation(notation), m_undoStack(undoStack), m_editData(&m_scoreCallbacks)
     272              : {
     273            0 :     m_selection = std::make_shared<NotationSelection>(notation, this);
     274            0 :     m_noteInput = std::make_shared<NotationNoteInput>(notation, this, m_undoStack, iocContext());
     275              : 
     276            0 :     m_selectionFilter = std::make_shared<NotationSelectionFilter>(notation, [this]() {
     277            0 :         m_selection->notifyAboutSelectionChangedIfNeed();
     278            0 :     });
     279              : 
     280            0 :     m_errorsController = std::make_shared<MScoreErrorsController>(iocContext());
     281              : 
     282            0 :     m_noteInput->stateChanged().onNotify(this, [this]() {
     283            0 :         if (!m_noteInput->isNoteInputMode()) {
     284            0 :             hideShadowNote();
     285              :         }
     286            0 :     });
     287              : 
     288            0 :     m_undoStack->undoRedoNotification().onNotify(this, [this]() {
     289            0 :         if (!isTextEditingStarted()) {
     290            0 :             endEditElement();
     291              :         }
     292            0 :     });
     293              : 
     294            0 :     m_undoStack->stackChanged().onNotify(this, [this]() {
     295            0 :         m_selection->notifyAboutSelectionChangedIfNeed();
     296            0 :         notifyAboutNoteInputStateChanged();
     297            0 :     });
     298              : 
     299            0 :     m_selection->selectionChanged().onNotify(this, [this]() {
     300            0 :         if (noteInput()->isNoteInputMode()) {
     301            0 :             notifyAboutNoteInputStateChanged();
     302              :         }
     303            0 :     });
     304              : 
     305            0 :     m_dragData.ed = mu::engraving::EditData(&m_scoreCallbacks);
     306              : 
     307            0 :     m_scoreCallbacks.setNotationInteraction(this);
     308              : 
     309            0 :     m_notation->scoreInited().onNotify(this, [this]() {
     310            0 :         onScoreInited();
     311            0 :     });
     312              : 
     313            0 :     m_notation->viewModeChanged().onNotify(this, [this]() {
     314            0 :         onViewModeChanged();
     315            0 :     });
     316              : 
     317            0 :     QObject::connect(&m_textCursorBlinkTimer, &QTimer::timeout, [this]() { blinkTextCursor(); });
     318            0 :     textEditingChanged().onNotify(this, [this]() {
     319            0 :         onTextEditingChanged();
     320            0 :     });
     321            0 : }
     322              : 
     323            0 : mu::engraving::Score* NotationInteraction::score() const
     324              : {
     325            0 :     return m_notation->score();
     326              : }
     327              : 
     328            0 : void NotationInteraction::onScoreInited()
     329              : {
     330            0 :     if (!score()) {
     331            0 :         return;
     332              :     }
     333              : 
     334            0 :     m_previewMeasure.setScore(score());
     335            0 :     m_scoreCallbacks.setScore(score());
     336              : 
     337            0 :     score()->elementDestroyed().onReceive(this, [this](mu::engraving::EngravingItem* element) {
     338            0 :         onElementDestroyed(element);
     339            0 :     });
     340              : }
     341              : 
     342            0 : void NotationInteraction::onViewModeChanged()
     343              : {
     344            0 :     if (!score()) {
     345            0 :         return;
     346              :     }
     347              : 
     348            0 :     if (!score()->isLayoutMode(LayoutMode::LINE) && !score()->isLayoutMode(LayoutMode::HORIZONTAL_FIXED)) {
     349            0 :         return;
     350              :     }
     351              : 
     352              :     // VBoxes are not included in horizontal layouts - deselect them (and their contents) when switching to horizontal mode...
     353            0 :     const std::vector<EngravingItem*> sel = selection()->elements();
     354            0 :     for (EngravingItem* item : sel) {
     355            0 :         if (!item->findAncestor(ElementType::VBOX) || !item->findAncestor(ElementType::FBOX)) {
     356            0 :             continue;
     357              :         }
     358            0 :         score()->deselect(item);
     359            0 :         if (item == m_editData.element) {
     360            0 :             endEditElement();
     361              :         }
     362              :     }
     363            0 : }
     364              : 
     365            0 : void NotationInteraction::transaction(const muse::TranslatableString& actionName, std::function<void(mu::engraving::Transaction&)> func)
     366              : {
     367            0 :     m_undoStack->transaction(actionName, func);
     368            0 : }
     369              : 
     370            0 : void NotationInteraction::startEdit(const muse::TranslatableString& actionName)
     371              : {
     372            0 :     m_notifyAboutDropChanged = false;
     373              : 
     374            0 :     m_undoStack->prepareChanges(actionName);
     375            0 : }
     376              : 
     377            0 : void NotationInteraction::apply()
     378              : {
     379            0 :     m_undoStack->commitChanges();
     380              : 
     381            0 :     if (m_notifyAboutDropChanged) {
     382            0 :         notifyAboutDropChanged();
     383              :     } else {
     384            0 :         notifyAboutNotationChanged();
     385              :     }
     386            0 : }
     387              : 
     388            0 : void NotationInteraction::rollback()
     389              : {
     390            0 :     m_undoStack->rollbackChanges();
     391            0 : }
     392              : 
     393            0 : void NotationInteraction::notifyAboutDragChanged()
     394              : {
     395            0 :     m_dragChanged.notify();
     396            0 : }
     397              : 
     398            0 : void NotationInteraction::notifyAboutDropChanged()
     399              : {
     400            0 :     m_dropChanged.notify();
     401            0 : }
     402              : 
     403            0 : void NotationInteraction::notifyAboutNotationChanged(const muse::RectF& updateRect)
     404              : {
     405            0 :     TRACEFUNC;
     406              : 
     407            0 :     m_notation->notifyAboutNotationChanged(updateRect);
     408            0 : }
     409              : 
     410            0 : void NotationInteraction::notifyAboutTextEditingStarted()
     411              : {
     412            0 :     m_textEditingStarted.notify();
     413            0 : }
     414              : 
     415            0 : void NotationInteraction::notifyAboutTextEditingChanged()
     416              : {
     417            0 :     m_textEditingChanged.notify();
     418            0 : }
     419              : 
     420            0 : void NotationInteraction::notifyAboutTextEditingEnded(TextBase* text)
     421              : {
     422            0 :     m_textEditingEnded.send(text);
     423            0 : }
     424              : 
     425            0 : void NotationInteraction::notifyAboutNoteInputStateChanged()
     426              : {
     427            0 :     m_noteInput->stateChanged().notify();
     428            0 : }
     429              : 
     430            0 : void NotationInteraction::paint(Painter* painter, const engraving::rendering::PaintOptions& opt)
     431              : {
     432            0 :     m_previewMeasure.paint(painter);
     433              : 
     434            0 :     if (shouldDrawInputPreview()) {
     435            0 :         drawInputPreview(painter, opt);
     436              :     }
     437              : 
     438            0 :     score()->renderer()->drawItem(score()->shadowNote(), painter, opt);
     439              : 
     440            0 :     drawAnchorLines(painter);
     441            0 :     drawTextEditMode(painter, opt);
     442            0 :     drawSelectionRange(painter);
     443            0 :     drawGripPoints(painter, opt);
     444            0 :     drawLasso(painter, opt);
     445            0 :     drawDrop(painter);
     446            0 : }
     447              : 
     448            0 : INotationNoteInputPtr NotationInteraction::noteInput() const
     449              : {
     450            0 :     return m_noteInput;
     451              : }
     452              : 
     453            0 : mu::engraving::ShadowNote* NotationInteraction::shadowNote() const
     454              : {
     455            0 :     return score()->shadowNote();
     456              : }
     457              : 
     458            0 : void NotationInteraction::showShadowNoteForPosition(const PointF& pos)
     459              : {
     460            0 :     const mu::engraving::InputState& inputState = score()->inputState();
     461            0 :     mu::engraving::ShadowNote& shadowNote = *score()->shadowNote();
     462              : 
     463            0 :     ShadowNoteParams params;
     464            0 :     if (!score()->getPosition(&params.position, pos, inputState.voice())) {
     465            0 :         shadowNote.setVisible(false);
     466            0 :         m_shadowNoteChanged.send(/*visible*/ false);
     467            0 :         return;
     468              :     }
     469              : 
     470            0 :     params.duration = inputState.duration();
     471            0 :     params.accidentalType = inputState.accidentalType();
     472            0 :     params.articulationIds = inputState.articulationIds();
     473              : 
     474            0 :     const bool show = doShowShadowNote(shadowNote, params);
     475            0 :     m_shadowNoteChanged.send(show);
     476            0 : }
     477              : 
     478            0 : void NotationInteraction::showShadowNoteForMidiPitch(const uint8_t pitch)
     479              : {
     480            0 :     const mu::engraving::InputState& inputState = score()->inputState();
     481            0 :     mu::engraving::ShadowNote& shadowNote = *score()->shadowNote();
     482              : 
     483            0 :     Segment* inputSegment = inputState.segment();
     484            0 :     const Staff* inputStaff = inputState.staff();
     485            0 :     const Fraction tick = inputSegment->tick();
     486            0 :     if (!inputSegment || !inputStaff || !tick.isValid() || inputStaff->isDrumStaff(tick) || inputStaff->isTabStaff(tick)) {
     487              :         //! NOTE: Not yet compatible with drum or tab staves...
     488            0 :         hideShadowNote();
     489            0 :         return;
     490              :     }
     491              : 
     492            0 :     const double mag = inputStaff->staffMag(tick);
     493            0 :     const double lineDist = inputStaff->staffType(tick)->lineDistance().val()
     494            0 :                             * 0.5 * mag * score()->style().spatium();
     495              : 
     496            0 :     const int octave = pitch / 12;
     497            0 :     const int line = octave * 7 + mu::engraving::pitch2step(pitch);
     498            0 :     const ClefType clef = inputStaff->clef(tick);
     499            0 :     const int rLine = mu::engraving::relStep(line, clef);
     500              : 
     501            0 :     const double xPos = inputSegment->canvasPos().x();
     502            0 :     const double yPos = inputSegment->system()->staffCanvasYpage(inputStaff->idx()) + rLine * lineDist;
     503              : 
     504            0 :     const PointF point = { xPos, yPos };
     505              : 
     506            0 :     const Position position { inputSegment, inputStaff->idx(), rLine, INVALID_FRET_INDEX, point };
     507            0 :     ShadowNoteParams params { inputState.duration(), inputState.accidentalType(), inputState.articulationIds(), position };
     508              : 
     509            0 :     const bool show = doShowShadowNote(shadowNote, params);
     510            0 :     m_shadowNoteChanged.send(show);
     511            0 : }
     512              : 
     513            0 : bool NotationInteraction::doShowShadowNote(ShadowNote& shadowNote, ShadowNoteParams& params)
     514              : {
     515            0 :     Position& position = params.position;
     516              : 
     517            0 :     const mu::engraving::InputState& inputState = score()->inputState();
     518            0 :     const Staff* staff = score()->staff(position.staffIdx);
     519              : 
     520            0 :     mu::engraving::Segment* segment = position.segment;
     521              : 
     522            0 :     mu::engraving::Segment* shadowNoteActualSegment = position.segment->prev1enabled();
     523            0 :     if (shadowNoteActualSegment) {
     524            0 :         segment = shadowNoteActualSegment;
     525              :     }
     526              : 
     527            0 :     Fraction tick = segment->tick();
     528            0 :     qreal mag = staff->staffMag(tick);
     529              : 
     530            0 :     const mu::engraving::Instrument* instr = staff->part()->instrument(tick);
     531              : 
     532            0 :     mu::engraving::NoteHeadGroup noteheadGroup = mu::engraving::NoteHeadGroup::HEAD_NORMAL;
     533            0 :     mu::engraving::NoteHeadType noteHead = params.duration.headType();
     534              : 
     535            0 :     int line = position.line;
     536            0 :     voice_idx_t voice = 0;
     537            0 :     int drumNotePitch = -1;
     538              : 
     539            0 :     if (instr->useDrumset()) {
     540            0 :         const Drumset* ds = instr->drumset();
     541              : 
     542            0 :         const int noteInputPitch = inputState.drumNote();
     543              : 
     544            0 :         if (ds->isValid(noteInputPitch) && ds->line(noteInputPitch) == line) {
     545            0 :             noteheadGroup = ds->noteHead(noteInputPitch);
     546            0 :             voice = ds->voice(noteInputPitch);
     547            0 :             drumNotePitch = noteInputPitch;
     548              :         } else {
     549            0 :             const int pitch = ds->defaultPitchForLine(line);
     550            0 :             if (ds->isValid(pitch)) {
     551            0 :                 noteheadGroup = ds->noteHead(pitch);
     552            0 :                 voice = ds->voice(pitch);
     553            0 :                 drumNotePitch = pitch;
     554              :             }
     555            0 :             if (drumNotePitch < 0) {
     556            0 :                 shadowNote.setVisible(false);
     557            0 :                 return false;
     558              :             }
     559              :         }
     560              :     } else {
     561            0 :         voice = inputState.voice();
     562              :     }
     563              : 
     564            0 :     shadowNote.setVisible(true);
     565            0 :     shadowNote.mutldata()->setMag(mag);
     566            0 :     shadowNote.setTick(tick);
     567            0 :     shadowNote.setStaffIdx(position.staffIdx);
     568            0 :     shadowNote.setVoice(voice);
     569            0 :     shadowNote.setLineIndex(line);
     570              : 
     571            0 :     const Color color = configuration()->noteInputPreviewColor();
     572            0 :     if (color.isValid() && color != configuration()->selectionColor()) {
     573            0 :         shadowNote.setColor(color);
     574              :     } else {
     575            0 :         shadowNote.setColor(configuration()->highlightSelectionColor(voice));
     576              :     }
     577              : 
     578              :     mu::engraving::SymId symNotehead;
     579              : 
     580            0 :     if (inputState.rest()) {
     581            0 :         Score* s = score()->paletteScore() ? score()->paletteScore() : score();
     582            0 :         mu::engraving::Rest* rest = mu::engraving::Factory::createRest(s->dummy(), params.duration.type());
     583            0 :         rest->setTicks(params.duration.fraction());
     584            0 :         symNotehead = rest->getSymbol(params.duration.type(), 0, staff->lines(position.segment->tick()));
     585            0 :         shadowNote.setState(symNotehead, params.duration, true, params.position.beyondScore);
     586            0 :         delete rest;
     587              :     } else {
     588            0 :         if (mu::engraving::NoteHeadGroup::HEAD_CUSTOM == noteheadGroup) {
     589            0 :             symNotehead = instr->drumset()->noteHeads(drumNotePitch, noteHead);
     590              :         } else {
     591            0 :             symNotehead = Note::noteHead(0, noteheadGroup, noteHead);
     592              :         }
     593              : 
     594            0 :         shadowNote.setState(symNotehead, params.duration, false, params.position.beyondScore, params.accidentalType,
     595            0 :                             params.articulationIds);
     596              :     }
     597              : 
     598              :     // if upscaled, note appears by default a distance away from the segment start,
     599              :     // as described in ChordLayout::centreChords
     600            0 :     if (mag > 1.0) {
     601            0 :         qreal xOffset = (mag - 1.0) * 0.5 * shadowNote.symWidth(symNotehead);
     602            0 :         if (shadowNote.computeUp()) {
     603            0 :             position.pos.rx() += xOffset;
     604              :         } else {
     605            0 :             position.pos.rx() -= xOffset;
     606              :         }
     607              :     }
     608              : 
     609              :     // in any empty measure, pos will be right next to barline
     610              :     // so pad this by barNoteDistance
     611            0 :     qreal relX = position.pos.x() - position.segment->measure()->canvasPos().x();
     612            0 :     position.pos.rx() -= qMin(relX - score()->style().styleAbsolute(mu::engraving::Sid::barNoteDistance), 0.0);
     613              : 
     614            0 :     score()->renderer()->layoutItem(&shadowNote);
     615              : 
     616            0 :     shadowNote.setPos(position.pos);
     617              : 
     618            0 :     return true;
     619              : }
     620              : 
     621            0 : void NotationInteraction::hideShadowNote()
     622              : {
     623            0 :     score()->shadowNote()->setVisible(false);
     624            0 :     m_shadowNoteChanged.send(/*visible*/ false);
     625            0 : }
     626              : 
     627            0 : RectF NotationInteraction::shadowNoteRect() const
     628              : {
     629            0 :     const ShadowNote* note = score()->shadowNote();
     630            0 :     if (!note) {
     631            0 :         return RectF();
     632              :     }
     633              : 
     634            0 :     RectF rect = note->canvasBoundingRect();
     635              : 
     636            0 :     double penWidth = note->style().styleAbsolute(Sid::stemWidth);
     637            0 :     if (note->ledgerLinesVisible()) {
     638            0 :         double w = note->style().styleAbsolute(Sid::ledgerLineWidth);
     639            0 :         penWidth = std::max(penWidth, w);
     640              :     }
     641              : 
     642            0 :     penWidth *= note->mag();
     643            0 :     rect.adjust(-penWidth, -penWidth, penWidth, penWidth);
     644              : 
     645            0 :     return rect;
     646              : }
     647              : 
     648            0 : RectF NotationInteraction::previewMeasureRect() const
     649              : {
     650            0 :     return m_previewMeasure.rect();
     651              : }
     652              : 
     653            0 : muse::async::Channel<bool> NotationInteraction::shadowNoteChanged() const
     654              : {
     655            0 :     return m_shadowNoteChanged;
     656              : }
     657              : 
     658            0 : void NotationInteraction::toggleVisible()
     659              : {
     660            0 :     transaction(TranslatableString("undoableAction", "Toggle visible"), [this](Transaction& tx) {
     661            0 :         EditVisibility::toggleVisible(tx, score());
     662            0 :     });
     663            0 : }
     664              : 
     665            0 : void NotationInteraction::setSelectionVisible(bool visible)
     666              : {
     667            0 :     if (visible) {
     668            0 :         transaction(TranslatableString("undoableAction", "Make element(s) visible"), [this](Transaction& tx) {
     669            0 :             EditVisibility::setSelectedElementsVisible(tx, score());
     670            0 :         });
     671              :     } else {
     672            0 :         transaction(TranslatableString("undoableAction", "Make element(s) invisible"), [this](Transaction& tx) {
     673            0 :             EditVisibility::setSelectedElementsInvisible(tx, score());
     674            0 :         });
     675              :     }
     676            0 : }
     677              : 
     678            0 : EngravingItem* NotationInteraction::hitElement(const PointF& pos, float width) const
     679              : {
     680            0 :     std::vector<mu::engraving::EngravingItem*> elements = hitElements(pos, width);
     681            0 :     if (elements.empty()) {
     682            0 :         return nullptr;
     683              :     }
     684            0 :     m_selection->onElementHit(elements.back());
     685            0 :     return elements.back();
     686            0 : }
     687              : 
     688            0 : Staff* NotationInteraction::hitStaff(const PointF& pos) const
     689              : {
     690            0 :     return hitMeasure(pos).staff;
     691              : }
     692              : 
     693            0 : mu::engraving::Page* NotationInteraction::point2page(const PointF& p, bool useNearestPage) const
     694              : {
     695            0 :     if (score()->linearMode()) {
     696            0 :         return score()->pages().empty() ? nullptr : score()->pages().front();
     697              :     }
     698              : 
     699            0 :     double shortestDistance = std::numeric_limits<double>::max();
     700            0 :     Page* nearestPage = nullptr;
     701            0 :     for (mu::engraving::Page* page : score()->pages()) {
     702            0 :         auto pageRect = page->ldata()->bbox().translated(page->pos());
     703            0 :         if (pageRect.contains(p)) {
     704            0 :             return page;
     705            0 :         } else if (useNearestPage && pageRect.distanceTo(p) < shortestDistance) {
     706            0 :             shortestDistance = pageRect.distanceTo(p);
     707            0 :             nearestPage = page;
     708              :         }
     709              :     }
     710            0 :     return nearestPage;
     711              : }
     712              : 
     713            0 : std::vector<EngravingItem*> NotationInteraction::elementsAt(const PointF& p) const
     714              : {
     715            0 :     mu::engraving::Page* page = point2page(p);
     716            0 :     if (!page) {
     717            0 :         return {};
     718              :     }
     719              : 
     720            0 :     std::vector<EngravingItem*> el = page->items(p - page->pos());
     721            0 :     if (el.empty()) {
     722            0 :         return {};
     723              :     }
     724              : 
     725            0 :     std::sort(el.begin(), el.end(), NotationInteraction::elementIsLess);
     726              : 
     727            0 :     return el;
     728            0 : }
     729              : 
     730            0 : EngravingItem* NotationInteraction::elementAt(const PointF& p) const
     731              : {
     732            0 :     std::vector<EngravingItem*> el = elementsAt(p);
     733            0 :     return el.empty() || el.back()->isPage() ? nullptr : el.back();
     734            0 : }
     735              : 
     736            0 : std::vector<EngravingItem*> NotationInteraction::hitElements(const PointF& pos, float width) const
     737              : {
     738            0 :     mu::engraving::Page* page = point2page(pos);
     739            0 :     if (!page) {
     740            0 :         return {};
     741              :     }
     742              : 
     743            0 :     std::vector<EngravingItem*> hitElements;
     744              : 
     745            0 :     PointF posOnPage = pos - page->pos();
     746              : 
     747            0 :     if (isTextEditingStarted() && page == m_editData.element->findAncestor(ElementType::PAGE)) {
     748            0 :         auto editW = width * 2;
     749            0 :         RectF editHitRect(posOnPage.x() - editW, posOnPage.y() - editW, 2.0 * editW, 2.0 * editW);
     750            0 :         if (m_editData.element->intersects(editHitRect)) {
     751            0 :             hitElements.push_back(m_editData.element);
     752            0 :             return hitElements;
     753              :         }
     754              :     }
     755              : 
     756            0 :     RectF hitRect(posOnPage.x() - width, posOnPage.y() - width, 3.0 * width, 3.0 * width);
     757              : 
     758            0 :     std::vector<EngravingItem*> potentiallyHitElements = page->items(hitRect);
     759              : 
     760            0 :     auto canHitElement = [](const EngravingItem* element) {
     761            0 :         if (!element->selectable() || element->isPage()) {
     762            0 :             return false;
     763              :         }
     764              : 
     765            0 :         if (element->isSoundFlag()) {
     766            0 :             return !toSoundFlag(element)->shouldHide();
     767            0 :         } else if (!element->isInteractionAvailable()) {
     768            0 :             return false;
     769              :         }
     770              : 
     771            0 :         return true;
     772              :     };
     773              : 
     774            0 :     for (EngravingItem* element : potentiallyHitElements) {
     775            0 :         element->itemDiscovered = 0;
     776              : 
     777            0 :         if (!canHitElement(element)) {
     778            0 :             continue;
     779              :         }
     780              : 
     781            0 :         if (element->hitShapeContains(posOnPage)) {
     782            0 :             hitElements.push_back(element);
     783              :         }
     784              :     }
     785              : 
     786            0 :     if (hitElements.empty() || (hitElements.size() == 1 && hitElements.front()->isMeasure())) {
     787              :         //
     788              :         // if no relevant element hit, look nearby
     789              :         //
     790            0 :         for (EngravingItem* element : potentiallyHitElements) {
     791            0 :             if (!canHitElement(element)) {
     792            0 :                 continue;
     793              :             }
     794              : 
     795            0 :             if (element->hitShapeIntersects(hitRect)) {
     796            0 :                 hitElements.push_back(element);
     797              :             }
     798              :         }
     799              :     }
     800              : 
     801            0 :     if (!hitElements.empty()) {
     802            0 :         std::sort(hitElements.begin(), hitElements.end(), NotationInteraction::elementIsLess);
     803              :     } else {
     804            0 :         Measure* measure = hitMeasure(pos).measure;
     805            0 :         if (measure) {
     806            0 :             hitElements.push_back(measure);
     807              :         }
     808              :     }
     809              : 
     810            0 :     return hitElements;
     811            0 : }
     812              : 
     813            0 : NotationInteraction::HitMeasureData NotationInteraction::hitMeasure(const PointF& pos) const
     814              : {
     815            0 :     mu::engraving::staff_idx_t staffIndex = muse::nidx;
     816            0 :     mu::engraving::Segment* segment = nullptr;
     817            0 :     PointF offset;
     818              : 
     819            0 :     Measure* measure = score()->pos2measure(pos, &staffIndex, /*pitch*/ 0, &segment, &offset);
     820              : 
     821            0 :     HitMeasureData result;
     822            0 :     if (measure && measure->staffLines(staffIndex)->canvasHitShape().contains(pos)) {
     823            0 :         result.measure = measure;
     824            0 :         result.staff = score()->staff(staffIndex);
     825              :     }
     826              : 
     827            0 :     return result;
     828              : }
     829              : 
     830            0 : bool NotationInteraction::elementIsLess(const EngravingItem* e1, const EngravingItem* e2)
     831              : {
     832            0 :     if (e1->selectable() && !e2->selectable()) {
     833            0 :         return false;
     834              :     }
     835            0 :     if (!e1->selectable() && e2->selectable()) {
     836            0 :         return true;
     837              :     }
     838            0 :     if (e1->isNote() && (e2->isStem() || e2->isHook())) {
     839            0 :         return false;
     840              :     }
     841            0 :     if (e2->isNote() && (e1->isStem() || e1->isHook())) {
     842            0 :         return true;
     843              :     }
     844            0 :     if (e1->isStem() && e2->isHook()) {
     845            0 :         return false;
     846              :     }
     847            0 :     if (e2->isStem() && e1->isHook()) {
     848            0 :         return true;
     849              :     }
     850            0 :     if (e1->isText() && e2->isBox()) {
     851            0 :         return false;
     852              :     }
     853            0 :     if (e1->isBox() && e2->isText()) {
     854            0 :         return true;
     855              :     }
     856            0 :     if (e1->isImage() && e2->isBox()) {
     857            0 :         return false;
     858              :     }
     859            0 :     if (e1->isBox() && e2->isImage()) {
     860            0 :         return true;
     861              :     }
     862            0 :     if ((e1->isHarmony() || e1->isFretDiagram()) && e2->isFBox()) {
     863            0 :         return false;
     864              :     }
     865            0 :     if (e1->isFBox() && (e2->isHarmony() || e2->isFretDiagram())) {
     866            0 :         return true;
     867              :     }
     868            0 :     if (e1->z() == e2->z()) {
     869              :         // same stacking order, prefer non-hidden elements
     870            0 :         if (e1->type() == e2->type()) {
     871            0 :             if (e1->isNoteDot()) {
     872            0 :                 const NoteDot* n1 = toNoteDot(e1);
     873            0 :                 const NoteDot* n2 = toNoteDot(e2);
     874            0 :                 if (n1->note() && n1->note()->hidden()) {
     875            0 :                     return false;
     876            0 :                 } else if (n2->note() && n2->note()->hidden()) {
     877            0 :                     return true;
     878              :                 }
     879            0 :             } else if (e1->isNote()) {
     880            0 :                 const Note* n1 = toNote(e1);
     881            0 :                 const Note* n2 = toNote(e2);
     882            0 :                 if (n1->hidden()) {
     883            0 :                     return false;
     884            0 :                 } else if (n2->hidden()) {
     885            0 :                     return true;
     886              :                 }
     887              :             }
     888              :         }
     889              :         // different types, or same type but nothing hidden - use track
     890            0 :         return e1->track() < e2->track();
     891              :     }
     892              : 
     893              :     // default case, use stacking order
     894            0 :     return e1->z() < e2->z();
     895              : }
     896              : 
     897            0 : const NotationInteraction::HitElementContext& NotationInteraction::hitElementContext() const
     898              : {
     899            0 :     return m_hitElementContext;
     900              : }
     901              : 
     902            0 : void NotationInteraction::setHitElementContext(const HitElementContext& context)
     903              : {
     904            0 :     m_hitElementContext = context;
     905            0 : }
     906              : 
     907            0 : EngravingItem* NotationInteraction::contextItem() const
     908              : {
     909            0 :     EngravingItem* item = selection()->element();
     910            0 :     return item ? item : hitElementContext().element;
     911              : }
     912              : 
     913            0 : void NotationInteraction::findAndSelectChordRest(const Fraction& tick)
     914              : {
     915            0 :     IF_ASSERT_FAILED(selection() && score()) {
     916            0 :         return;
     917              :     }
     918              : 
     919            0 :     Segment* startSeg = score()->tick2leftSegment(tick, /*useMMrest*/ false, SegmentType::ChordRest);
     920            0 :     IF_ASSERT_FAILED(startSeg && startSeg->isChordRestType()) {
     921            0 :         return;
     922              :     }
     923              : 
     924            0 :     staff_idx_t lastSelectedStaffIdx = 0;
     925            0 :     voice_idx_t lastSelectedVoiceIdx = 0;
     926            0 :     if (selection()->isRange()) {
     927            0 :         lastSelectedStaffIdx = selection()->range()->startStaffIndex();
     928            0 :     } else if (!selection()->elements().empty()) {
     929            0 :         const EngravingItem* lastSelectedItem = selection()->elements().back();
     930            0 :         if (lastSelectedItem) {
     931            0 :             lastSelectedStaffIdx = lastSelectedItem->staffIdx();
     932            0 :             lastSelectedVoiceIdx = lastSelectedItem->voice();
     933              :         }
     934              :     }
     935              : 
     936              :     // startSeg could be a CR segment in any staff. The idea of this method is to select the CR in the last selected
     937              :     // staff (or the top staff if none was selected). The following outer loop iterates backwards through previous CR
     938              :     // segments until a valid CR is found in the desired staff...
     939            0 :     EngravingItem* toSelect = nullptr;
     940            0 :     for (Segment* currSeg = startSeg; currSeg && !toSelect; currSeg = currSeg->prev(SegmentType::ChordRest)) {
     941            0 :         toSelect = currSeg->element(staff2track(lastSelectedStaffIdx, lastSelectedVoiceIdx));
     942            0 :         if (toSelect && toSelect->isChordRest()) {
     943            0 :             break;
     944              :         }
     945            0 :         toSelect = nullptr;
     946              :         // Inner loop: Couldn't find a valid CR in the last selected voice - try other voices in the same staff...
     947            0 :         for (voice_idx_t currVoice = 0; currVoice < VOICES; ++currVoice) {
     948            0 :             if (currVoice == lastSelectedVoiceIdx) {
     949              :                 // Already tried this...
     950            0 :                 continue;
     951              :             }
     952            0 :             toSelect = currSeg->element(staff2track(lastSelectedStaffIdx, currVoice));
     953            0 :             if (toSelect && toSelect->isChordRest()) {
     954            0 :                 break;
     955              :             }
     956            0 :             toSelect = nullptr;
     957              :         }
     958              :     }
     959              : 
     960            0 :     IF_ASSERT_FAILED(toSelect) {
     961            0 :         return;
     962              :     }
     963              : 
     964            0 :     std::vector<EngravingItem*> itemsToSelect;
     965            0 :     if (toSelect->isChord()) {
     966            0 :         const std::vector<Note*>& notes = toChord(toSelect)->notes();
     967            0 :         itemsToSelect.insert(itemsToSelect.end(), notes.begin(), notes.end());
     968              :     } else {
     969            0 :         itemsToSelect = { toSelect };
     970              :     }
     971              : 
     972            0 :     select(itemsToSelect, SelectType::REPLACE);
     973            0 : }
     974              : 
     975            0 : void NotationInteraction::select(const std::vector<EngravingItem*>& elements, SelectType type, staff_idx_t staffIndex)
     976              : {
     977            0 :     if (needEndTextEditing(elements)) {
     978            0 :         endEditText();
     979            0 :     } else if (needEndElementEditing(elements)) {
     980            0 :         endEditElement();
     981              :     }
     982              : 
     983            0 :     m_selection->select(elements, type, staffIndex);
     984            0 : }
     985              : 
     986            0 : void NotationInteraction::select(SelectionTarget target)
     987              : {
     988            0 :     m_selection->select(target);
     989            0 : }
     990              : 
     991            0 : void NotationInteraction::addToSelection(SelectionTarget target)
     992              : {
     993            0 :     m_selection->addToSelection(target);
     994            0 : }
     995              : 
     996            0 : void NotationInteraction::selectAndStartEditIfNeeded(EngravingItem* element)
     997              : {
     998            0 :     if (element->isSpanner() && !toSpanner(element)->segmentsEmpty()) {
     999            0 :         element = toSpanner(element)->frontSegment();
    1000              :     }
    1001              : 
    1002            0 :     select({ element }, SelectType::SINGLE);
    1003              : 
    1004            0 :     if (element->needStartEditingAfterSelecting()) {
    1005            0 :         startEditElement(element);
    1006              :     }
    1007            0 : }
    1008              : 
    1009            0 : INotationSelectionPtr NotationInteraction::selection() const
    1010              : {
    1011            0 :     return m_selection;
    1012              : }
    1013              : 
    1014            0 : void NotationInteraction::clearSelection()
    1015              : {
    1016            0 :     TRACEFUNC;
    1017              : 
    1018            0 :     if (isEditingElement()) {
    1019            0 :         endEditElement();
    1020              :     }
    1021              : 
    1022            0 :     if (isDragStarted()) {
    1023            0 :         doEndDrag();
    1024            0 :         rollback();
    1025            0 :         notifyAboutNotationChanged();
    1026              :     }
    1027              : 
    1028            0 :     if (selection()->isNone()) {
    1029            0 :         return;
    1030              :     }
    1031              : 
    1032            0 :     m_selection->clearSelection();
    1033              : 
    1034            0 :     setHitElementContext(HitElementContext());
    1035            0 : }
    1036              : 
    1037            0 : muse::async::Notification NotationInteraction::selectionChanged() const
    1038              : {
    1039            0 :     return m_selection->selectionChanged();
    1040              : }
    1041              : 
    1042            0 : INotationSelectionFilterPtr NotationInteraction::selectionFilter() const
    1043              : {
    1044            0 :     return m_selectionFilter;
    1045              : }
    1046              : 
    1047            0 : INotationElementsPtr NotationInteraction::elements() const
    1048              : {
    1049            0 :     return m_notation->elements();
    1050              : }
    1051              : 
    1052            0 : bool NotationInteraction::isDragStarted() const
    1053              : {
    1054            0 :     if (m_dragData.dragGroups.size() > 0) {
    1055            0 :         return true;
    1056              :     }
    1057              : 
    1058            0 :     if (m_lasso && !m_lasso->isEmpty()) {
    1059            0 :         return true;
    1060              :     }
    1061              : 
    1062            0 :     return false;
    1063              : }
    1064              : 
    1065            0 : void NotationInteraction::DragData::reset()
    1066              : {
    1067            0 :     beginMove = QPointF();
    1068            0 :     elementOffset = QPointF();
    1069            0 :     ed = mu::engraving::EditData(ed.view());
    1070            0 :     dragGroups.clear();
    1071            0 : }
    1072              : 
    1073            0 : void NotationInteraction::startDrag(const std::vector<EngravingItem*>& elems,
    1074              :                                     const PointF& eoffset,
    1075              :                                     const IsDraggable& isDraggable)
    1076              : {
    1077            0 :     m_dragData.reset();
    1078            0 :     m_dragData.elements = elems;
    1079            0 :     m_dragData.elementOffset = eoffset;
    1080            0 :     m_editData.modifiers = keyboardModifier(QGuiApplication::keyboardModifiers());
    1081              : 
    1082            0 :     for (EngravingItem* e : m_dragData.elements) {
    1083            0 :         bool draggable = isDraggable(e);
    1084              : 
    1085            0 :         if (!draggable && e->isSpanner()) {
    1086            0 :             Spanner* s = toSpanner(e);
    1087            0 :             draggable = !s->segmentsEmpty() && isDraggable(s->frontSegment());
    1088              :         }
    1089              : 
    1090            0 :         if (!draggable) {
    1091            0 :             continue;
    1092              :         }
    1093              : 
    1094            0 :         std::unique_ptr<mu::engraving::ElementGroup> g = e->getDragGroup(isDraggable);
    1095            0 :         if (g && g->enabled()) {
    1096            0 :             m_dragData.dragGroups.push_back(std::move(g));
    1097              :         }
    1098            0 :     }
    1099              : 
    1100            0 :     startEdit(TranslatableString("undoableAction", "Drag element(s)", nullptr, int(m_dragData.elements.size())));
    1101              : 
    1102            0 :     double scaling = m_notation->viewState()->matrix().m11();
    1103            0 :     double proximity = configuration()->selectionProximity() * 0.5 / scaling;
    1104            0 :     m_scoreCallbacks.setSelectionProximity(proximity);
    1105              : 
    1106            0 :     if (isGripEditStarted()) {
    1107            0 :         m_editData.element->startDragGrip(m_editData);
    1108            0 :         return;
    1109              :     }
    1110              : 
    1111            0 :     m_dragData.ed.modifiers = keyboardModifier(QGuiApplication::keyboardModifiers());
    1112            0 :     for (auto& group : m_dragData.dragGroups) {
    1113            0 :         group->startDrag(m_dragData.ed);
    1114              :     }
    1115              : }
    1116              : 
    1117            0 : void NotationInteraction::doDragLasso(const PointF& pt)
    1118              : {
    1119            0 :     if (!m_lasso) {
    1120            0 :         m_lasso = new mu::engraving::Lasso(score());
    1121              :     }
    1122              : 
    1123            0 :     score()->addRefresh(m_lasso->canvasBoundingRect());
    1124            0 :     RectF r;
    1125            0 :     r.setCoords(m_dragData.beginMove.x(), m_dragData.beginMove.y(), pt.x(), pt.y());
    1126            0 :     m_lasso->setbbox(r.normalized());
    1127            0 :     score()->addRefresh(m_lasso->canvasBoundingRect());
    1128            0 :     score()->lassoSelect(m_lasso->ldata()->bbox());
    1129            0 :     score()->update();
    1130            0 : }
    1131              : 
    1132            0 : void NotationInteraction::endLasso()
    1133              : {
    1134            0 :     if (!m_lasso) {
    1135            0 :         return;
    1136              :     }
    1137              : 
    1138            0 :     score()->addRefresh(m_lasso->canvasBoundingRect());
    1139            0 :     m_lasso->setbbox(RectF());
    1140            0 :     score()->lassoSelectEnd();
    1141            0 :     score()->update();
    1142              : }
    1143              : 
    1144            0 : void NotationInteraction::drag(const PointF& fromPos, const PointF& toPos, DragMode mode)
    1145              : {
    1146            0 :     if (m_dragData.beginMove.isNull()) {
    1147            0 :         m_dragData.beginMove = fromPos;
    1148            0 :         m_dragData.ed.pos = fromPos;
    1149              :     }
    1150              : 
    1151            0 :     PointF normalizedBegin = m_dragData.beginMove - m_dragData.elementOffset;
    1152            0 :     PointF delta = toPos - normalizedBegin;
    1153            0 :     PointF evtDelta = toPos - m_dragData.ed.pos;
    1154            0 :     PointF moveDelta = delta - m_dragData.elementOffset;
    1155              : 
    1156            0 :     bool constrainDirection = !(isGripEditStarted() && m_editData.element && m_editData.element->isBarLine());
    1157            0 :     if (constrainDirection) {
    1158            0 :         switch (mode) {
    1159            0 :         case DragMode::BothXY:
    1160            0 :             break;
    1161            0 :         case DragMode::OnlyX:
    1162            0 :             delta.setY(m_dragData.ed.delta.y());
    1163            0 :             moveDelta.setY(m_dragData.ed.moveDelta.y());
    1164            0 :             evtDelta.setY(0.0);
    1165            0 :             break;
    1166            0 :         case DragMode::OnlyY:
    1167            0 :             delta.setX(m_dragData.ed.delta.x());
    1168            0 :             moveDelta.setX(m_dragData.ed.moveDelta.x());
    1169            0 :             evtDelta.setX(0.0);
    1170            0 :             break;
    1171              :         }
    1172              :     }
    1173              : 
    1174            0 :     m_dragData.ed.lastPos = m_dragData.ed.pos;
    1175              : 
    1176            0 :     m_dragData.ed.hRaster = configuration()->isSnappedToGrid(muse::Orientation::Horizontal);
    1177            0 :     m_dragData.ed.vRaster = configuration()->isSnappedToGrid(muse::Orientation::Vertical);
    1178            0 :     m_dragData.ed.delta = delta;
    1179            0 :     m_dragData.ed.moveDelta = moveDelta;
    1180            0 :     m_dragData.ed.evtDelta = evtDelta;
    1181            0 :     m_dragData.ed.pos = toPos;
    1182            0 :     m_dragData.ed.modifiers = keyboardModifier(QGuiApplication::keyboardModifiers());
    1183              : 
    1184            0 :     m_dragData.ed.isEditMode = isEditingElement();
    1185              : 
    1186            0 :     if (isTextEditingStarted()) {
    1187            0 :         m_editData.pos = toPos;
    1188            0 :         toTextBase(m_editData.element)->dragTo(m_editData);
    1189              : 
    1190            0 :         notifyAboutTextEditingChanged();
    1191            0 :         return;
    1192              :     }
    1193              : 
    1194            0 :     if (isGripEditStarted()) {
    1195            0 :         m_dragData.ed.curGrip = m_editData.curGrip;
    1196            0 :         m_dragData.ed.delta = evtDelta;
    1197            0 :         m_dragData.ed.moveDelta = m_dragData.ed.delta - m_dragData.elementOffset;
    1198            0 :         m_dragData.ed.addData(m_editData.getData(m_editData.element));
    1199            0 :         m_editData.element->dragGrip(m_dragData.ed);
    1200              : 
    1201            0 :         if (m_editData.element->isDynamic()) {
    1202              :             // When the dynamic has no left grip, the right grip will have index zero, a.k.a. Grip::LEFT.
    1203              :             // TODO: refactor all code that works with Grips, so that this is not necessary
    1204            0 :             Dynamic* dynamic = toDynamic(m_editData.element);
    1205            0 :             bool isLeftGrip = dynamic->hasLeftGrip() ? m_editData.curGrip == Grip::LEFT : false;
    1206            0 :             addHairpinOnGripDrag(m_editData, isLeftGrip);
    1207              :         }
    1208              :     } else {
    1209            0 :         for (auto& group : m_dragData.dragGroups) {
    1210            0 :             score()->addRefresh(group->drag(m_dragData.ed));
    1211              :         }
    1212              :     }
    1213              : 
    1214            0 :     score()->update();
    1215              : 
    1216            0 :     if (isGripEditStarted()) {
    1217            0 :         if (m_editData.element->isDynamic() && !m_editData.isStartEndGrip()) {
    1218            0 :             updateDragAnchorLines();
    1219              :         } else {
    1220            0 :             updateGripAnchorLines();
    1221              :         }
    1222              :     } else {
    1223            0 :         updateDragAnchorLines();
    1224              :     }
    1225              : 
    1226            0 :     if (m_dragData.elements.size() == 0) {
    1227            0 :         doDragLasso(toPos);
    1228              :     }
    1229              : 
    1230            0 :     notifyAboutDragChanged();
    1231              : }
    1232              : 
    1233            0 : void NotationInteraction::doEndDrag()
    1234              : {
    1235            0 :     if (isGripEditStarted()) {
    1236            0 :         m_editData.element->endDragGrip(m_editData);
    1237            0 :         m_editData.element->endEdit(m_editData);
    1238              :     } else {
    1239            0 :         for (auto& group : m_dragData.dragGroups) {
    1240            0 :             group->endDrag(m_dragData.ed);
    1241              :         }
    1242            0 :         if (m_lasso && !m_lasso->isEmpty()) {
    1243            0 :             endLasso();
    1244              :         }
    1245              :     }
    1246              : 
    1247            0 :     m_dragData.reset();
    1248              : 
    1249            0 :     if (m_dropData.elementDropData.has_value()) {
    1250            0 :         setDropTarget(nullptr, false);
    1251              :     } else {
    1252            0 :         resetAnchorLines();
    1253              :     }
    1254            0 : }
    1255              : 
    1256            0 : void NotationInteraction::endDrag()
    1257              : {
    1258            0 :     doEndDrag();
    1259            0 :     apply();
    1260              : 
    1261            0 :     if (m_editData.isHairpinDragCreatedFromDynamic) {
    1262              :         // Merge the two actions of hairpin creation + hairpin drag
    1263            0 :         assert(m_undoStack->currentStateIndex() >= 2);
    1264            0 :         if (m_undoStack->currentStateIndex() >= 2) {
    1265            0 :             m_undoStack->mergeTransactions(m_undoStack->currentStateIndex() - 2);
    1266              :         }
    1267            0 :         m_editData.isHairpinDragCreatedFromDynamic = false;
    1268              :     }
    1269              : 
    1270            0 :     notifyAboutDragChanged();
    1271            0 :     checkAndShowError();
    1272            0 : }
    1273              : 
    1274            0 : muse::async::Notification NotationInteraction::dragChanged() const
    1275              : {
    1276            0 :     return m_dragChanged;
    1277              : }
    1278              : 
    1279            0 : bool NotationInteraction::isOutgoingDragElementAllowed(const EngravingItem* element) const
    1280              : {
    1281            0 :     if (!element) {
    1282            0 :         return false;
    1283              :     }
    1284              : 
    1285            0 :     switch (element->type()) {
    1286            0 :     case ElementType::MEASURE:
    1287              :     case ElementType::NOTE:
    1288              :     case ElementType::VBOX:
    1289              :     case ElementType::HBOX:
    1290              :     case ElementType::TBOX:
    1291              :     case ElementType::FBOX:
    1292              :     // TODO: Bends & NoteLines can't be copy-dragged until corresponding SingleLayout::layout and SingleDraw::draw methods have been implemented
    1293              :     case ElementType::GUITAR_BEND:
    1294              :     case ElementType::GUITAR_BEND_SEGMENT:
    1295              :     case ElementType::GUITAR_BEND_HOLD:
    1296              :     case ElementType::GUITAR_BEND_HOLD_SEGMENT:
    1297              :     case ElementType::GUITAR_BEND_TEXT:
    1298              :     case ElementType::NOTELINE:
    1299              :     case ElementType::NOTELINE_SEGMENT:
    1300            0 :         return false;
    1301            0 :     default: return true;
    1302              :     }
    1303              : }
    1304              : 
    1305              : //! NOTE: Copied from ScoreView::cloneElement
    1306            0 : void NotationInteraction::startOutgoingDragElement(const EngravingItem* element, QObject* dragSource, const PointF& hotSpot)
    1307              : {
    1308            0 :     if (isDragStarted()) {
    1309            0 :         endOutgoingDrag();
    1310              :     }
    1311              : 
    1312            0 :     QMimeData* mimeData = new QMimeData();
    1313            0 :     if (element->isSpannerSegment()) {
    1314            0 :         Spanner* s = toSpannerSegment(element)->spanner();
    1315            0 :         mimeData->setData(mu::engraving::mimeSymbolFormat, s->mimeData().toQByteArray());
    1316              :     } else {
    1317            0 :         mimeData->setData(mu::engraving::mimeSymbolFormat, element->mimeData().toQByteArray());
    1318              :     }
    1319              : 
    1320            0 :     m_outgoingDrag = new QDrag(dragSource);
    1321            0 :     m_outgoingDrag->setMimeData(mimeData);
    1322              : 
    1323            0 :     QObject::connect(m_outgoingDrag, &QDrag::destroyed, [this]() {
    1324            0 :         m_outgoingDrag = nullptr;
    1325            0 :     });
    1326              : 
    1327            0 :     const QScreen* screen = QGuiApplication::primaryScreen();
    1328            0 :     const qreal physDpiRatio = screen->physicalDotsPerInch() / mu::engraving::DPI;
    1329            0 :     const qreal devicePixelRatio = screen->devicePixelRatio();
    1330              : 
    1331            0 :     const RectF bbox = element->ldata()->bbox().adjusted(-4.0, -4.0, 4.0, 4.0);
    1332            0 :     const double scaledWidth = bbox.width() * physDpiRatio;
    1333            0 :     const double scaledHeight = bbox.height() * physDpiRatio;
    1334              : 
    1335              :     QPixmap pixmap(qCeil(scaledWidth * devicePixelRatio),
    1336            0 :                    qCeil(scaledHeight * devicePixelRatio));
    1337            0 :     pixmap.setDevicePixelRatio(devicePixelRatio);
    1338            0 :     pixmap.fill(Qt::transparent);
    1339              : 
    1340            0 :     QPainter qp(&pixmap);
    1341              : 
    1342            0 :     Painter p(&qp, "startOutgoingDragElement");
    1343            0 :     p.setAntialiasing(true);
    1344              : 
    1345            0 :     p.scale(physDpiRatio, physDpiRatio);
    1346            0 :     p.translate(qAbs(bbox.x()), qAbs(bbox.y()));
    1347              : 
    1348            0 :     mu::engraving::rendering::PaintOptions opt;
    1349            0 :     opt.invertColors = configuration()->shouldInvertScore();
    1350            0 :     engravingRenderer()->drawItem(element, &p, opt);
    1351              : 
    1352            0 :     m_outgoingDrag->setPixmap(pixmap);
    1353            0 :     m_outgoingDrag->setHotSpot((hotSpot * physDpiRatio).toQPoint());
    1354              : 
    1355            0 :     m_outgoingDrag->exec(Qt::CopyAction);
    1356            0 : }
    1357              : 
    1358            0 : void NotationInteraction::startOutgoingDragRange(QObject* dragSource)
    1359              : {
    1360            0 :     if (isDragStarted()) {
    1361            0 :         endOutgoingDrag();
    1362              :     }
    1363              : 
    1364            0 :     if (!selection()->isRange() || !selection()->canCopy()) {
    1365            0 :         return;
    1366              :     }
    1367              : 
    1368            0 :     QMimeData* mimeData = selection()->qMimeData();
    1369            0 :     if (!mimeData) {
    1370            0 :         return;
    1371              :     }
    1372              : 
    1373            0 :     m_outgoingDrag = new QDrag(dragSource);
    1374            0 :     m_outgoingDrag->setMimeData(mimeData);
    1375              : 
    1376            0 :     QObject::connect(m_outgoingDrag, &QDrag::destroyed, [this]() {
    1377            0 :         m_outgoingDrag = nullptr;
    1378            0 :     });
    1379              : 
    1380            0 :     QPixmap pixmap(1, 1);
    1381            0 :     pixmap.fill(Qt::transparent);
    1382            0 :     m_outgoingDrag->setPixmap(pixmap);
    1383              : 
    1384            0 :     m_outgoingDrag->exec(Qt::MoveAction | Qt::CopyAction);
    1385            0 : }
    1386              : 
    1387            0 : bool NotationInteraction::isOutgoingDragStarted() const
    1388              : {
    1389            0 :     return m_outgoingDrag != nullptr;
    1390              : }
    1391              : 
    1392            0 : void NotationInteraction::endOutgoingDrag()
    1393              : {
    1394            0 :     if (m_outgoingDrag) {
    1395            0 :         delete m_outgoingDrag;
    1396            0 :         m_outgoingDrag = nullptr;
    1397              :     }
    1398            0 : }
    1399              : 
    1400              : //! NOTE Copied from ScoreView::dragEnterEvent
    1401            0 : bool NotationInteraction::startDropSingle(const QByteArray& edata)
    1402              : {
    1403            0 :     resetDropData();
    1404              : 
    1405            0 :     m_dropData.elementDropData = ElementDropData();
    1406            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    1407            0 :     edd.ed = mu::engraving::EditData(&m_scoreCallbacks);
    1408              : 
    1409            0 :     mu::engraving::XmlReader e(edata);
    1410            0 :     edd.ed.dragOffset = QPointF();
    1411            0 :     Fraction duration;      // dummy
    1412            0 :     ElementType type = EngravingItem::readType(e, &edd.ed.dragOffset, &duration);
    1413              : 
    1414            0 :     EngravingItem* el = engraving::Factory::createItem(type, score()->dummy());
    1415            0 :     if (el) {
    1416            0 :         if (type == ElementType::BAR_LINE || type == ElementType::BRACKET) {
    1417            0 :             double spatium = score()->style().spatium();
    1418            0 :             el->setHeight(spatium * 5);
    1419              :         }
    1420            0 :         edd.ed.dropElement = el;
    1421              : 
    1422            0 :         rw::RWRegister::reader()->readItem(edd.ed.dropElement, e);
    1423              : 
    1424            0 :         engravingRenderer()->layoutItem(edd.ed.dropElement);
    1425            0 :         return true;
    1426              :     }
    1427              : 
    1428            0 :     resetDropData();
    1429            0 :     return false;
    1430            0 : }
    1431              : 
    1432            0 : bool NotationInteraction::startDropRange(const QByteArray& data)
    1433              : {
    1434            0 :     resetDropData();
    1435              : 
    1436            0 :     m_dropData.rangeDropData = RangeDropData();
    1437            0 :     RangeDropData& rdd = m_dropData.rangeDropData.value();
    1438              : 
    1439            0 :     mu::engraving::XmlReader reader(data);
    1440            0 :     while (reader.readNextStartElement()) {
    1441            0 :         if (reader.name() == "StaffList") {
    1442            0 :             rdd.sourceTick = Fraction::fromString(reader.attribute("tick"));
    1443            0 :             rdd.tickLength = Fraction::fromString(reader.attribute("len"));
    1444            0 :             rdd.timeStretch = reader.hasAttribute("timeStretch") ? Fraction::fromString(reader.attribute("timeStretch")) : Fraction(1, 1);
    1445            0 :             rdd.sourceStaffIdx = static_cast<staff_idx_t>(reader.intAttribute("staff", -1));
    1446            0 :             rdd.numStaves = reader.intAttribute("staves", 0);
    1447            0 :             break;
    1448              :         }
    1449              :     }
    1450              : 
    1451            0 :     if (rdd.tickLength.isZero() || rdd.numStaves == 0) {
    1452            0 :         resetDropData();
    1453            0 :         return false;
    1454              :     }
    1455              : 
    1456            0 :     return true;
    1457            0 : }
    1458              : 
    1459            0 : bool NotationInteraction::startDropRange(const Fraction& sourceTick, const Fraction& tickLength,
    1460              :                                          engraving::staff_idx_t sourceStaffIdx, size_t numStaves, bool preserveMeasureAlignment)
    1461              : {
    1462            0 :     if (tickLength.isZero() || numStaves == 0) {
    1463            0 :         return false;
    1464              :     }
    1465              : 
    1466            0 :     resetDropData();
    1467              : 
    1468            0 :     m_dropData.rangeDropData = RangeDropData();
    1469            0 :     RangeDropData& rdd = m_dropData.rangeDropData.value();
    1470              : 
    1471            0 :     rdd.sourceTick = sourceTick;
    1472            0 :     rdd.tickLength = tickLength;
    1473            0 :     rdd.timeStretch = score()->staff(sourceStaffIdx)->timeStretch(sourceTick);
    1474            0 :     rdd.sourceStaffIdx = sourceStaffIdx;
    1475            0 :     rdd.numStaves = numStaves;
    1476            0 :     rdd.preserveMeasureAlignment = preserveMeasureAlignment;
    1477              : 
    1478            0 :     return true;
    1479              : }
    1480              : 
    1481            0 : bool NotationInteraction::startDropImage(const QUrl& url)
    1482              : {
    1483            0 :     if (url.scheme() != "file") {
    1484            0 :         return false;
    1485              :     }
    1486              : 
    1487            0 :     auto image = toImage(Factory::createItem(mu::engraving::ElementType::IMAGE, score()->dummy()));
    1488            0 :     if (!image->loadFromFile(url.toLocalFile())) {
    1489            0 :         return false;
    1490              :     }
    1491              : 
    1492            0 :     resetDropData();
    1493              : 
    1494            0 :     m_dropData.elementDropData = ElementDropData();
    1495            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    1496            0 :     edd.ed = mu::engraving::EditData(&m_scoreCallbacks);
    1497              : 
    1498            0 :     edd.ed.dropElement = image;
    1499            0 :     edd.ed.dragOffset = QPointF();
    1500            0 :     edd.ed.dropElement->setOwnershipParent(nullptr);
    1501              : 
    1502            0 :     engravingRenderer()->layoutItem(edd.ed.dropElement);
    1503              : 
    1504            0 :     return true;
    1505              : }
    1506              : 
    1507              : //! NOTE Copied from ScoreView::dragMoveEvent
    1508            0 : bool NotationInteraction::updateDropSingle(const PointF& pos, Qt::KeyboardModifiers modifiers)
    1509              : {
    1510            0 :     if (!m_dropData.elementDropData.has_value()) {
    1511            0 :         return false;
    1512              :     }
    1513              : 
    1514            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    1515              : 
    1516            0 :     EngravingItem* dropElem = edd.ed.dropElement;
    1517            0 :     if (!dropElem) {
    1518            0 :         return false;
    1519              :     }
    1520              : 
    1521            0 :     switch (dropElem->type()) {
    1522            0 :     case ElementType::BRACKET:
    1523              :     case ElementType::GRADUAL_TEMPO_CHANGE:
    1524              :     case ElementType::JUMP:
    1525              :     case ElementType::KEYSIG:
    1526              :     case ElementType::LAYOUT_BREAK:
    1527              :     case ElementType::MARKER:
    1528              :     case ElementType::MEASURE_REPEAT:
    1529              :     case ElementType::MEASURE_NUMBER:
    1530              :     case ElementType::TIMESIG:
    1531              :     case ElementType::SPACER:
    1532              :     case ElementType::STAFFTYPE_CHANGE:
    1533              :     case ElementType::STRING_TUNINGS:
    1534              :     case ElementType::VOLTA: {
    1535            0 :         edd.ed.modifiers = keyboardModifier(modifiers);
    1536            0 :         return prepareDropMeasureBaseAnchorElement(pos);
    1537              :     }
    1538            0 :     case ElementType::PEDAL:
    1539              :     case ElementType::LET_RING:
    1540              :     case ElementType::VIBRATO:
    1541              :     case ElementType::PALM_MUTE:
    1542              :     case ElementType::OTTAVA:
    1543              :     case ElementType::TRILL:
    1544              :     case ElementType::HAIRPIN:
    1545              :     case ElementType::TEXTLINE:
    1546              :     case ElementType::WHAMMY_BAR: {
    1547            0 :         edd.ed.modifiers = keyboardModifier(modifiers);
    1548            0 :         return prepareDropTimeAnchorElement(pos);
    1549              :     }
    1550            0 :     case ElementType::IMAGE:
    1551              :     case ElementType::SYMBOL:
    1552              :     case ElementType::FSYMBOL:
    1553              :     case ElementType::DYNAMIC:
    1554              :     case ElementType::CLEF:
    1555              :     case ElementType::BAR_LINE:
    1556              :     case ElementType::ARPEGGIO:
    1557              :     case ElementType::CHORD_BRACKET:
    1558              :     case ElementType::BREATH:
    1559              :     case ElementType::GLISSANDO:
    1560              :     case ElementType::ARTICULATION:
    1561              :     case ElementType::TAPPING:
    1562              :     case ElementType::FERMATA:
    1563              :     case ElementType::CHORDLINE:
    1564              :     case ElementType::BEND:
    1565              :     case ElementType::ACCIDENTAL:
    1566              :     case ElementType::TEXT:
    1567              :     case ElementType::FINGERING:
    1568              :     case ElementType::TEMPO_TEXT:
    1569              :     case ElementType::ORNAMENT:
    1570              :     case ElementType::EXPRESSION:
    1571              :     case ElementType::STAFF_TEXT:
    1572              :     case ElementType::STAVE_SHARING_LABEL:
    1573              :     case ElementType::SYSTEM_TEXT:
    1574              :     case ElementType::TRIPLET_FEEL:
    1575              :     case ElementType::PLAYTECH_ANNOTATION:
    1576              :     case ElementType::CAPO:
    1577              :     case ElementType::NOTEHEAD:
    1578              :     case ElementType::TREMOLO_SINGLECHORD:
    1579              :     case ElementType::TREMOLO_TWOCHORD:
    1580              :     case ElementType::STAFF_STATE:
    1581              :     case ElementType::INSTRUMENT_CHANGE:
    1582              :     case ElementType::REHEARSAL_MARK:
    1583              :     case ElementType::CHORD:
    1584              :     case ElementType::SLUR:
    1585              :     case ElementType::HAMMER_ON_PULL_OFF:
    1586              :     case ElementType::HARMONY:
    1587              :     case ElementType::BAGPIPE_EMBELLISHMENT:
    1588              :     case ElementType::AMBITUS:
    1589              :     case ElementType::TREMOLOBAR:
    1590              :     case ElementType::FIGURED_BASS:
    1591              :     case ElementType::LYRICS:
    1592              :     case ElementType::FRET_DIAGRAM:
    1593              :     case ElementType::HARP_DIAGRAM: {
    1594            0 :         return prepareDropStandardElement(pos, modifiers);
    1595              :     }
    1596              :     //! NOTE: See Measure::acceptDrop
    1597            0 :     case ElementType::ACTION_ICON: {
    1598            0 :         switch (toActionIcon(dropElem)->actionType()) {
    1599            0 :         case ActionIconType::VFRAME:
    1600              :         case ActionIconType::HFRAME:
    1601              :         case ActionIconType::TFRAME:
    1602              :         case ActionIconType::FFRAME:
    1603              :         case ActionIconType::MEASURE:
    1604              :         case ActionIconType::SYSTEM_LOCK:
    1605              :         case ActionIconType::PAGE_LOCK:
    1606              :         case ActionIconType::STAFF_TYPE_CHANGE: {
    1607            0 :             edd.ed.modifiers = keyboardModifier(modifiers);
    1608            0 :             return prepareDropMeasureBaseAnchorElement(pos);
    1609              :         }
    1610              :         // Other action icons (e.g parenthesis) can be dragged normally
    1611            0 :         default: return prepareDropStandardElement(pos, modifiers);
    1612              :         }
    1613              :     }
    1614            0 :     default:
    1615            0 :         break;
    1616              :     }
    1617              : 
    1618            0 :     return false;
    1619              : }
    1620              : 
    1621            0 : static Measure* rangeEndMeasure(Score* score, const Fraction& endTick)
    1622              : {
    1623            0 :     Measure* endMeasure = score->tick2measure(endTick);
    1624            0 :     if (!endMeasure) {
    1625            0 :         endMeasure = score->lastMeasure();
    1626            0 :         if (!endMeasure) {
    1627            0 :             return nullptr;
    1628              :         }
    1629              :     }
    1630            0 :     if (endMeasure->tick() == endTick) {
    1631              :         // If the end tick is at the start of a measure, return the previous measure
    1632            0 :         return endMeasure->prevMeasure();
    1633              :     }
    1634            0 :     return endMeasure;
    1635              : }
    1636              : 
    1637            0 : static Segment* rangeEndSegment(Score* score, const Fraction& endTick)
    1638              : {
    1639            0 :     Segment* endSegment = score->tick2rightSegment(endTick,
    1640              :                                                    true,
    1641              :                                                    SegmentType::Duration);
    1642              : 
    1643            0 :     if (endSegment && !endSegment->enabled()) {
    1644            0 :         endSegment = endSegment->next1MMenabled();
    1645              :     }
    1646              : 
    1647            0 :     return endSegment ? endSegment : score->lastSegmentMM();
    1648              : }
    1649              : 
    1650            0 : static bool dropRangePosition(Score* score, const PointF& pos,
    1651              :                               Fraction sourceStartTick, Fraction tickLength, Fraction timeStretch,
    1652              :                               staff_idx_t numStaves, staff_idx_t* targetStartStaffIdx,
    1653              :                               Segment** targetStartSegment, const Segment** targetEndSegment = nullptr,
    1654              :                               bool preserveMeasureAlignment = false,
    1655              :                               ShowAnchors* showAnchors = nullptr)
    1656              : {
    1657            0 :     IF_ASSERT_FAILED(score && targetStartStaffIdx && targetStartSegment) {
    1658            0 :         return false;
    1659              :     }
    1660              : 
    1661            0 :     Measure* targetStartMeasure = nullptr;
    1662            0 :     if (!dragPositionToMeasure(pos, score, &targetStartMeasure, targetStartStaffIdx)) {
    1663            0 :         return false;
    1664              :     }
    1665              : 
    1666            0 :     Fraction targetStartTick;
    1667            0 :     Fraction targetEndTick;
    1668              : 
    1669            0 :     if (preserveMeasureAlignment) {
    1670            0 :         Measure* sourceStartMeasure = score->tick2measure(sourceStartTick);
    1671            0 :         IF_ASSERT_FAILED(sourceStartMeasure) {
    1672            0 :             return false;
    1673              :         }
    1674              : 
    1675            0 :         Fraction startTickOffset = sourceStartTick - sourceStartMeasure->tick();
    1676            0 :         targetStartTick = targetStartMeasure->tick() + startTickOffset;
    1677            0 :         targetEndTick = targetStartTick + tickLength;
    1678              : 
    1679            0 :         if (targetStartTick >= targetStartMeasure->endTick()) {
    1680              :             // Target start tick is beyond the end of the measure, so cannot preserve alignment
    1681              :             // This can happen when the target measure is shorter than the source measure
    1682            0 :             preserveMeasureAlignment = false;
    1683              :         }
    1684              :     }
    1685            0 :     if (!preserveMeasureAlignment) {
    1686              :         // Assign temporary values until target segment is determined
    1687            0 :         targetStartTick = targetStartMeasure->tick();
    1688            0 :         targetEndTick = targetStartMeasure->endTick() - Fraction::fromTicks(1) + tickLength;
    1689              :     }
    1690              : 
    1691            0 :     Measure* targetEndMeasure = rangeEndMeasure(score, targetEndTick);
    1692            0 :     if (!targetEndMeasure) {
    1693            0 :         return false;
    1694              :     }
    1695              : 
    1696            0 :     IF_ASSERT_FAILED(targetStartMeasure == targetEndMeasure || targetStartMeasure->isBefore(targetEndMeasure)) {
    1697            0 :         return false;
    1698              :     }
    1699              : 
    1700            0 :     const staff_idx_t targetEndStaffIdx = std::min(*targetStartStaffIdx + numStaves, score->nstaves());
    1701              : 
    1702              :     // Add time tick anchors throughout these measures
    1703            0 :     for (Measure* m = targetStartMeasure; m && m->tick() <= targetEndMeasure->tick(); m = m->nextMeasure()) {
    1704            0 :         std::set<Fraction> additionalAnchorRelTicks;
    1705            0 :         if (preserveMeasureAlignment) {
    1706            0 :             if (m == targetStartMeasure) {
    1707            0 :                 additionalAnchorRelTicks.insert(targetStartTick - m->tick());
    1708              :             }
    1709            0 :             if (m == targetEndMeasure) {
    1710            0 :                 additionalAnchorRelTicks.insert(targetEndTick - m->tick());
    1711              :             }
    1712              :         }
    1713            0 :         for (staff_idx_t i = *targetStartStaffIdx; i < targetEndStaffIdx; ++i) {
    1714            0 :             EditTimeTickAnchors::updateAnchors(m, i, additionalAnchorRelTicks);
    1715              :         }
    1716            0 :     }
    1717              : 
    1718            0 :     if (preserveMeasureAlignment) {
    1719            0 :         *targetStartSegment = segmentOrChordRestSegmentAtSameTick(
    1720              :             targetStartMeasure->findSegment(SegmentType::Duration, targetStartTick));
    1721              :     } else {
    1722              :         static constexpr double spacingFactor = 0.5;
    1723              :         static constexpr bool useTimeAnchors = true;
    1724              : 
    1725              :         // Depends on time tick anchors that we just created
    1726            0 :         if (!dragPositionToSegment(pos, targetStartMeasure, *targetStartStaffIdx, targetStartSegment, spacingFactor, useTimeAnchors)) {
    1727            0 :             return false;
    1728              :         }
    1729              : 
    1730            0 :         targetStartTick = (*targetStartSegment)->tick();
    1731            0 :         targetEndTick = targetStartTick + tickLength;
    1732              : 
    1733            0 :         targetEndMeasure = rangeEndMeasure(score, targetEndTick);
    1734            0 :         if (!targetEndMeasure) {
    1735            0 :             return false;
    1736              :         }
    1737              :     }
    1738              : 
    1739            0 :     if (targetEndSegment) {
    1740            0 :         *targetEndSegment = rangeEndSegment(score, targetEndTick);
    1741            0 :         if (!*targetEndSegment) {
    1742            0 :             return false;
    1743              :         }
    1744              :     }
    1745              : 
    1746              :     // Check the time stretch for all measures overlapping the destination range.
    1747            0 :     for (Measure* m = targetStartMeasure; m && m->tick() <= targetEndMeasure->tick(); m = m->nextMeasure()) {
    1748            0 :         for (staff_idx_t staffIdx = *targetStartStaffIdx; staffIdx < targetEndStaffIdx; ++staffIdx) {
    1749            0 :             Fraction mTimeStretch = score->staff(staffIdx)->timeStretch(m->tick());
    1750            0 :             if (mTimeStretch != timeStretch) {
    1751            0 :                 return false;
    1752              :             }
    1753              :         }
    1754              :     }
    1755              : 
    1756            0 :     if (showAnchors) {
    1757            0 :         *showAnchors = ShowAnchors(0, *targetStartStaffIdx, *targetStartStaffIdx + numStaves,
    1758              :                                    targetStartTick, targetEndTick,
    1759            0 :                                    targetStartMeasure->tick(), targetEndMeasure->endTick());
    1760              :     }
    1761              : 
    1762            0 :     return true;
    1763              : }
    1764              : 
    1765            0 : bool NotationInteraction::updateDropRange(const PointF& pos, std::optional<bool> preserveMeasureAlignment)
    1766              : {
    1767            0 :     IF_ASSERT_FAILED(m_dropData.rangeDropData.has_value()) {
    1768            0 :         return false;
    1769              :     }
    1770              : 
    1771            0 :     RangeDropData& rdd = m_dropData.rangeDropData.value();
    1772              : 
    1773            0 :     if (preserveMeasureAlignment.has_value()) {
    1774            0 :         rdd.preserveMeasureAlignment = preserveMeasureAlignment.value();
    1775              :     }
    1776              : 
    1777            0 :     staff_idx_t staffIdx = muse::nidx;
    1778            0 :     Segment* segment = nullptr;
    1779            0 :     const Segment* endSegment = nullptr;
    1780            0 :     ShowAnchors showAnchors;
    1781              : 
    1782            0 :     const bool ok = dropRangePosition(score(), pos,
    1783              :                                       rdd.sourceTick, rdd.tickLength, rdd.timeStretch, rdd.numStaves,
    1784              :                                       &staffIdx, &segment, &endSegment,
    1785            0 :                                       rdd.preserveMeasureAlignment, &showAnchors);
    1786              : 
    1787            0 :     if (showAnchors != score()->showAnchors()) {
    1788            0 :         score()->setShowAnchors(showAnchors);
    1789              : 
    1790              :         // Invalidate BSP tree of affected pages
    1791            0 :         System* lastSeenSystem = nullptr;
    1792            0 :         Page* lastSeenPage = nullptr;
    1793            0 :         for (MeasureBase* mb = score()->tick2measure(showAnchors.startTickExtendedRegion);
    1794            0 :              mb && mb->tick() <= showAnchors.endTickExtendedRegion;
    1795            0 :              mb = mb->next()) {
    1796            0 :             System* s = mb->system();
    1797            0 :             if (!s || s == lastSeenSystem) {
    1798            0 :                 continue;
    1799              :             }
    1800              : 
    1801            0 :             lastSeenSystem = s;
    1802              : 
    1803            0 :             Page* p = s->page();
    1804            0 :             if (!p || p == lastSeenPage) {
    1805            0 :                 continue;
    1806              :             }
    1807              : 
    1808            0 :             lastSeenPage = p;
    1809              : 
    1810            0 :             p->invalidateBspTree();
    1811              :         }
    1812              :     }
    1813              : 
    1814            0 :     if (ok) {
    1815            0 :         rdd.targetSegment = segment;
    1816            0 :         rdd.targetStaffIdx = staffIdx;
    1817              : 
    1818            0 :         const staff_idx_t endStaffIdx = std::min(staffIdx + rdd.numStaves, score()->nstaves());
    1819              : 
    1820            0 :         rdd.dropRects = ScoreRangeUtilities::boundingArea(score(),
    1821              :                                                           segment, endSegment,
    1822              :                                                           staffIdx, endStaffIdx,
    1823            0 :                                                           /*startBox*/ nullptr, /*endBox*/ nullptr);
    1824              :     } else {
    1825            0 :         rdd.targetSegment = nullptr;
    1826            0 :         rdd.targetStaffIdx = muse::nidx;
    1827            0 :         rdd.dropRects.clear();
    1828              :     }
    1829              : 
    1830            0 :     notifyAboutDragChanged();
    1831              : 
    1832            0 :     return ok;
    1833              : }
    1834              : 
    1835              : //! NOTE Copied from ScoreView::dropEvent
    1836            0 : bool NotationInteraction::dropSingle(const PointF& pos, Qt::KeyboardModifiers modifiers)
    1837              : {
    1838            0 :     if (!m_dropData.elementDropData.has_value()) {
    1839            0 :         return false;
    1840              :     }
    1841              : 
    1842            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    1843              : 
    1844            0 :     if (!edd.ed.dropElement) {
    1845            0 :         return false;
    1846              :     }
    1847              : 
    1848            0 :     IF_ASSERT_FAILED(edd.ed.dropElement->score() == score()) {
    1849            0 :         return false;
    1850              :     }
    1851              : 
    1852            0 :     bool accepted = false;
    1853              : 
    1854              :     // If the drop position hasn't been set already through isDropAccepted's helper methods, use the mouse position
    1855            0 :     if (edd.ed.pos.isNull()) {
    1856            0 :         edd.ed.pos = pos;
    1857              :     }
    1858              : 
    1859            0 :     edd.ed.modifiers = keyboardModifier(modifiers);
    1860            0 :     edd.ed.dropElement->styleChanged();
    1861              : 
    1862            0 :     bool systemStavesOnly = false;
    1863            0 :     bool applyUserOffset = false;
    1864              : 
    1865            0 :     startEdit(TranslatableString("undoableAction", "Drop element: %1").arg(edd.ed.dropElement->typeUserName()));
    1866            0 :     score()->addRefresh(edd.ed.dropElement->canvasBoundingRect());
    1867            0 :     engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
    1868            0 :     ElementType et = edd.ed.dropElement->type();
    1869            0 :     switch (et) {
    1870            0 :     case ElementType::VOLTA:
    1871              :     case ElementType::GRADUAL_TEMPO_CHANGE:
    1872              :         // voltas drop to system staves by default, or closest staff if Control is held
    1873            0 :         systemStavesOnly = !(edd.ed.modifiers & Qt::ControlModifier);
    1874              :         [[fallthrough]];
    1875            0 :     case ElementType::TEXTLINE:
    1876              :     case ElementType::OTTAVA:
    1877              :     case ElementType::TRILL:
    1878              :     case ElementType::PEDAL:
    1879              :     case ElementType::LET_RING:
    1880              :     case ElementType::VIBRATO:
    1881              :     case ElementType::PALM_MUTE:
    1882              :     case ElementType::HAIRPIN:
    1883              :     case ElementType::WHAMMY_BAR:
    1884              :     {
    1885            0 :         systemStavesOnly |=  edd.ed.dropElement->systemFlag();
    1886            0 :         mu::engraving::Spanner* spanner = ptr::checked_cast<mu::engraving::Spanner>(edd.ed.dropElement);
    1887            0 :         EditSpanner::addSpanner(tx, score(), spanner, pos, systemStavesOnly);
    1888            0 :         score()->setUpdateAll();
    1889            0 :         accepted = true;
    1890              :     }
    1891            0 :     break;
    1892            0 :     case ElementType::SYMBOL:
    1893              :     case ElementType::FSYMBOL:
    1894              :     case ElementType::IMAGE:
    1895            0 :         applyUserOffset = true;
    1896              :         [[fallthrough]];
    1897            0 :     case ElementType::DYNAMIC:
    1898              :     case ElementType::HARMONY:
    1899            0 :         accepted = doDropTextBaseAndSymbols(tx, pos, applyUserOffset);
    1900            0 :         break;
    1901            0 :     case ElementType::FRET_DIAGRAM:
    1902            0 :         accepted = doDropTextBaseAndSymbols(tx, pos, applyUserOffset);
    1903            0 :         if (accepted) {
    1904            0 :             doFinishAddFretboardDiagram();
    1905              :         }
    1906            0 :         break;
    1907            0 :     case ElementType::HBOX:
    1908              :     case ElementType::VBOX:
    1909              :     case ElementType::KEYSIG:
    1910              :     case ElementType::CLEF:
    1911              :     case ElementType::TIMESIG:
    1912              :     case ElementType::BAR_LINE:
    1913              :     case ElementType::ARPEGGIO:
    1914              :     case ElementType::CHORD_BRACKET:
    1915              :     case ElementType::BREATH:
    1916              :     case ElementType::GLISSANDO:
    1917              :     case ElementType::MEASURE_NUMBER:
    1918              :     case ElementType::BRACKET:
    1919              :     case ElementType::ARTICULATION:
    1920              :     case ElementType::TAPPING:
    1921              :     case ElementType::FERMATA:
    1922              :     case ElementType::CHORDLINE:
    1923              :     case ElementType::BEND:
    1924              :     case ElementType::ACCIDENTAL:
    1925              :     case ElementType::TEXT:
    1926              :     case ElementType::FINGERING:
    1927              :     case ElementType::TEMPO_TEXT:
    1928              :     case ElementType::ORNAMENT:
    1929              :     case ElementType::EXPRESSION:
    1930              :     case ElementType::STAFF_TEXT:
    1931              :     case ElementType::STAVE_SHARING_LABEL:
    1932              :     case ElementType::SYSTEM_TEXT:
    1933              :     case ElementType::TRIPLET_FEEL:
    1934              :     case ElementType::PLAYTECH_ANNOTATION:
    1935              :     case ElementType::CAPO:
    1936              :     case ElementType::STRING_TUNINGS:
    1937              :     case ElementType::NOTEHEAD:
    1938              :     case ElementType::TREMOLO_SINGLECHORD:
    1939              :     case ElementType::TREMOLO_TWOCHORD:
    1940              :     case ElementType::LAYOUT_BREAK:
    1941              :     case ElementType::MARKER:
    1942              :     case ElementType::STAFF_STATE:
    1943              :     case ElementType::INSTRUMENT_CHANGE:
    1944              :     case ElementType::REHEARSAL_MARK:
    1945              :     case ElementType::JUMP:
    1946              :     case ElementType::MEASURE_REPEAT:
    1947              :     case ElementType::ACTION_ICON:
    1948              :     case ElementType::NOTE:
    1949              :     case ElementType::CHORD:
    1950              :     case ElementType::SPACER:
    1951              :     case ElementType::BAGPIPE_EMBELLISHMENT:
    1952              :     case ElementType::AMBITUS:
    1953              :     case ElementType::TREMOLOBAR:
    1954              :     case ElementType::FIGURED_BASS:
    1955              :     case ElementType::LYRICS:
    1956              :     case ElementType::HARP_DIAGRAM:
    1957              :     case ElementType::STAFFTYPE_CHANGE:
    1958            0 :         accepted = doDropStandard(tx);
    1959            0 :         break;
    1960            0 :     case ElementType::SLUR:
    1961              :     case ElementType::HAMMER_ON_PULL_OFF:
    1962              :     {
    1963            0 :         EngravingItem* el = dropTarget(edd.ed);
    1964            0 :         mu::engraving::Slur* dropElement = toSlur(edd.ed.dropElement);
    1965            0 :         if (toNote(el)->chord()) {
    1966            0 :             doAddSlur(toNote(el)->chord(), nullptr, dropElement);
    1967            0 :             accepted = true;
    1968              :         }
    1969              :     }
    1970            0 :     break;
    1971            0 :     default:
    1972            0 :         resetDropData();
    1973            0 :         break;
    1974              :     }
    1975              : 
    1976            0 :     edd.ed.dropElement = nullptr;
    1977            0 :     edd.ed.pos = PointF();
    1978            0 :     edd.ed.modifiers = {};
    1979              : 
    1980            0 :     setDropTarget(nullptr); // this also resets dropRectangle and dropAnchor
    1981            0 :     apply();
    1982              : 
    1983            0 :     if (accepted) {
    1984            0 :         notifyAboutDropChanged();
    1985              :     }
    1986              : 
    1987            0 :     checkAndShowError();
    1988              : 
    1989            0 :     return accepted;
    1990              : }
    1991              : 
    1992              : //! NOTE: Helper method for NotationInteraction::drop. Handles drop logic for majority of elements (returns "accepted")
    1993            0 : bool NotationInteraction::doDropStandard(engraving::Transaction& tx)
    1994              : {
    1995            0 :     if (!m_dropData.elementDropData.has_value()) {
    1996            0 :         return false;
    1997              :     }
    1998              : 
    1999            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    2000              : 
    2001            0 :     EngravingItem* el = edd.dropTarget ? edd.dropTarget : dropTarget(edd.ed);
    2002            0 :     if (!el) {
    2003            0 :         if (!dropCanvas(edd.ed.dropElement)) {
    2004            0 :             LOGD("cannot drop %s(%p) to canvas", edd.ed.dropElement->typeName(), edd.ed.dropElement);
    2005            0 :             resetDropData();
    2006              :         }
    2007            0 :         return false;
    2008              :     }
    2009            0 :     score()->addRefresh(el->canvasBoundingRect());
    2010              : 
    2011            0 :     EngravingItem* dropElement = el->drop(tx, edd.ed);
    2012              : 
    2013            0 :     if (dropElement && dropElement->isInstrumentChange()) {
    2014            0 :         if (!selectInstrument(toInstrumentChange(dropElement))) {
    2015            0 :             rollback();
    2016            0 :             return true; // Really?
    2017              :         }
    2018              :     }
    2019              : 
    2020            0 :     score()->addRefresh(el->canvasBoundingRect());
    2021            0 :     if (dropElement) {
    2022            0 :         if (!score()->noteEntryMode()) {
    2023            0 :             selectAndStartEditIfNeeded(dropElement);
    2024              :         }
    2025            0 :         score()->addRefresh(dropElement->canvasBoundingRect());
    2026              :     }
    2027            0 :     return true;
    2028              : }
    2029              : 
    2030              : //! NOTE: Helper method for NotationInteraction::drop. Handles drop logic for text base items & symbols (returns "accepted")
    2031            0 : bool NotationInteraction::doDropTextBaseAndSymbols(engraving::Transaction& tx, const PointF& pos, bool applyUserOffset)
    2032              : {
    2033            0 :     IF_ASSERT_FAILED(m_dropData.elementDropData.has_value()) {
    2034            0 :         return false;
    2035              :     }
    2036              : 
    2037            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    2038              : 
    2039            0 :     EngravingItem* el = edd.dropTarget ? edd.dropTarget : elementAt(pos);
    2040            0 :     if (!el || el->isStaffLines()) {
    2041              :         mu::engraving::staff_idx_t staffIdx;
    2042              :         mu::engraving::Segment* seg;
    2043            0 :         PointF offset;
    2044            0 :         el = score()->pos2measure(pos, &staffIdx, /*pitch*/ 0, &seg, &offset);
    2045            0 :         if (el && el->isMeasure()) {
    2046            0 :             edd.ed.dropElement->setTrack(staff2track(staffIdx));
    2047            0 :             edd.ed.dropElement->setOwnershipParent(seg);
    2048              : 
    2049            0 :             if (applyUserOffset) {
    2050            0 :                 edd.ed.dropElement->setOffset(offset);
    2051              :             }
    2052              : 
    2053            0 :             score()->undoAddElement(edd.ed.dropElement);
    2054              :         } else {
    2055            0 :             LOGD("cannot drop here");
    2056            0 :             resetDropData();
    2057              :         }
    2058              :     } else {
    2059            0 :         score()->addRefresh(el->canvasBoundingRect());
    2060            0 :         score()->addRefresh(edd.ed.dropElement->canvasBoundingRect());
    2061              : 
    2062            0 :         if (!el->acceptDrop(edd.ed)) {
    2063            0 :             LOGD("drop %s onto %s not accepted", edd.ed.dropElement->typeName(), el->typeName());
    2064            0 :             return false;
    2065              :         }
    2066            0 :         edd.ed.pos = pos;
    2067            0 :         EngravingItem* dropElement = el->drop(tx, edd.ed);
    2068            0 :         score()->addRefresh(el->canvasBoundingRect());
    2069            0 :         if (dropElement) {
    2070            0 :             selectAndStartEditIfNeeded(dropElement);
    2071            0 :             score()->addRefresh(dropElement->canvasBoundingRect());
    2072              :         }
    2073              :     }
    2074              : 
    2075            0 :     return true;
    2076              : }
    2077              : 
    2078            0 : bool NotationInteraction::dropRange(const QByteArray& data, const PointF& pos, bool deleteSourceMaterial)
    2079              : {
    2080            0 :     IF_ASSERT_FAILED(m_dropData.rangeDropData.has_value()) {
    2081            0 :         return false;
    2082              :     }
    2083              : 
    2084            0 :     RangeDropData& rdd = m_dropData.rangeDropData.value();
    2085              : 
    2086            0 :     if (rdd.tickLength.isZero() || rdd.numStaves == 0) {
    2087            0 :         return false;
    2088              :     }
    2089              : 
    2090            0 :     staff_idx_t staffIdx = muse::nidx;
    2091            0 :     Segment* segment = nullptr;
    2092              : 
    2093            0 :     const bool ok = dropRangePosition(score(), pos,
    2094              :                                       rdd.sourceTick, rdd.tickLength, rdd.timeStretch, rdd.numStaves,
    2095            0 :                                       &staffIdx, &segment, nullptr, rdd.preserveMeasureAlignment);
    2096            0 :     if (!ok) {
    2097            0 :         return false;
    2098              :     }
    2099              : 
    2100            0 :     if (segment->isTupletSubdivisionOnStaff(staffIdx) || segment->isInsideTupletOnStaff(staffIdx)) {
    2101            0 :         endDrop();
    2102            0 :         notifyAboutDropChanged();
    2103              :         //MScore::setError(MsError::DEST_TUPLET);
    2104              :         // checkAndShowError();
    2105              :         // NOTE: if we show the error popup here it seems that the mouse-release event is missed
    2106              :         // so the dragged region stays sticked to the mouse and move around. Don't know how to fix it. [M.S.]
    2107            0 :         return false;
    2108              :     }
    2109              : 
    2110            0 :     rdd.targetSegment = segment;
    2111            0 :     rdd.targetStaffIdx = staffIdx;
    2112              : 
    2113            0 :     startEdit(deleteSourceMaterial
    2114            0 :               ? TranslatableString("undoableAction", "Move range")
    2115              :               : TranslatableString("undoableAction", "Copy range"));
    2116              : 
    2117            0 :     if (deleteSourceMaterial && rdd.sourceStaffIdx != muse::nidx) {
    2118            0 :         Segment* sourceStartSegment = score()->tick2leftSegmentMM(rdd.sourceTick);
    2119            0 :         if (sourceStartSegment && !sourceStartSegment->enabled()) {
    2120            0 :             sourceStartSegment = sourceStartSegment->next1MMenabled();
    2121              :         }
    2122              : 
    2123            0 :         Segment* sourceEndSegment = rangeEndSegment(score(), rdd.sourceTick + rdd.tickLength);
    2124              : 
    2125            0 :         if (sourceStartSegment && sourceEndSegment) {
    2126            0 :             score()->deleteRange(sourceStartSegment, sourceEndSegment,
    2127              :                                  engraving::staff2track(rdd.sourceStaffIdx),
    2128            0 :                                  engraving::staff2track(rdd.sourceStaffIdx + rdd.numStaves),
    2129            0 :                                  score()->selectionFilter(), score()->selection().rangeContainsMultiNoteChords());
    2130              :         }
    2131              :     }
    2132              : 
    2133            0 :     XmlReader e(data);
    2134            0 :     bool succeeded = Paste::pasteStaff(score()->transactionManager()->currentOrDummyTransaction(), score(), e, segment, staffIdx);
    2135              : 
    2136            0 :     endDrop();
    2137              : 
    2138            0 :     if (succeeded) {
    2139            0 :         apply();
    2140              :     } else {
    2141            0 :         rollback();
    2142              :     }
    2143              : 
    2144            0 :     checkAndShowError();
    2145              : 
    2146            0 :     return succeeded;
    2147            0 : }
    2148              : 
    2149            0 : bool NotationInteraction::selectInstrument(mu::engraving::InstrumentChange* instrumentChange)
    2150              : {
    2151            0 :     if (!instrumentChange) {
    2152            0 :         return false;
    2153              :     }
    2154              : 
    2155              : #ifdef MUE_CONFIGURATION_IS_APPWEB
    2156              :     interactive()->info("", muse::trc("notation", "Sorry, instrument change on the web is not supported at this time."));
    2157              :     return false;
    2158              : #else
    2159              : 
    2160              :     //! HACK To remake the asynchronous mode, a lot of refactoring is needed,
    2161              :     //! so for now this is a hack
    2162            0 :     bool result = false;
    2163            0 :     QEventLoop loop;
    2164            0 :     async::Promise<InstrumentTemplate> templ = selectInstrumentScenario()->selectInstrument();
    2165            0 :     templ.onResolve(this, [this, instrumentChange, &loop, &result](const InstrumentTemplate& val) {
    2166            0 :         Instrument newInstrument = Instrument::fromTemplate(&val);
    2167              : 
    2168              :         // If switching back to the part's original instrument, restore its player number
    2169            0 :         if (Part* part = instrumentChange->part()) {
    2170            0 :             const Instrument* baseInstrument = part->instrument();
    2171            0 :             if (baseInstrument && baseInstrument->id() == newInstrument.id()) {
    2172            0 :                 InstrumentLabel& newLabel = newInstrument.instrumentLabel();
    2173            0 :                 const InstrumentLabel& baseLabel = baseInstrument->instrumentLabel();
    2174            0 :                 newLabel.setNumber(baseLabel.number());
    2175            0 :                 newLabel.setShowNumberLong(baseLabel.showNumberLong());
    2176            0 :                 newLabel.setShowNumberShort(baseLabel.showNumberShort());
    2177              :             }
    2178              :         }
    2179              : 
    2180            0 :         instrumentChange->setInit(true);
    2181            0 :         instrumentChange->setupInstrument(&newInstrument);
    2182              : 
    2183            0 :         if (newInstrument.useDrumset()) {
    2184            0 :             cleanupDrumsetChanges(instrumentChange);
    2185              :         }
    2186              : 
    2187            0 :         result = true;
    2188            0 :         loop.quit();
    2189            0 :     }).onReject(this, [&loop, &result](int, const std::string&) {
    2190            0 :         result = false;
    2191            0 :         loop.quit();
    2192            0 :     });
    2193              : 
    2194            0 :     loop.exec();
    2195              : 
    2196            0 :     return result;
    2197              : #endif
    2198            0 : }
    2199              : 
    2200            0 : void NotationInteraction::cleanupDrumsetChanges(mu::engraving::InstrumentChange* instrumentChange) const
    2201              : {
    2202            0 :     Part* part = instrumentChange ? instrumentChange->part() : nullptr;
    2203            0 :     Instrument* newInstrument = instrumentChange ? instrumentChange->instrument() : nullptr;
    2204            0 :     if (!part || !newInstrument) {
    2205            0 :         return;
    2206              :     }
    2207              : 
    2208            0 :     for (auto pair : part->instruments()) {
    2209            0 :         const Instrument* otherInst = pair.second;
    2210            0 :         if (!otherInst || otherInst == newInstrument) {
    2211            0 :             continue;
    2212              :         }
    2213              : 
    2214              :         // If the following conditional is true, it means that we're trying to change to a drumset that already exists for this part. Due to the fact
    2215              :         // that we don't create new tracks for identical instruments in a given part, the knock-on effect is that the new drumset won't have a chance
    2216              :         // to load a MuseSampler patch (see usage of shouldLoadDrumset in PlaybackController). The following logic resolves this by copying the patch
    2217              :         // from the existing drumset into the new one...
    2218            0 :         if (otherInst->drumset() && newInstrument->id() == otherInst->id()) {
    2219            0 :             score()->undo(new engraving::ChangeDrumset(newInstrument, *otherInst->drumset(), part));
    2220            0 :             return;
    2221              :         }
    2222              :     }
    2223              : }
    2224              : 
    2225              : //! NOTE Copied from Palette::applyPaletteElement
    2226            0 : bool NotationInteraction::applyPaletteElement(mu::engraving::EngravingItem* element, Qt::KeyboardModifiers modifiers)
    2227              : {
    2228            0 :     IF_ASSERT_FAILED(element) {
    2229            0 :         return false;
    2230              :     }
    2231              : 
    2232            0 :     mu::engraving::Score* score = this->score();
    2233            0 :     if (!score) {
    2234            0 :         return false;
    2235              :     }
    2236              : 
    2237            0 :     const mu::engraving::Selection sel = score->selection();   // make a copy of selection state before applying the operation.
    2238            0 :     if (sel.isNone()) {
    2239            0 :         return false;
    2240              :     }
    2241              : 
    2242            0 :     startEdit(TranslatableString("undoableAction", "Apply palette element: %1").arg(element->typeUserName()));
    2243              : 
    2244            0 :     if (sel.isList()) {
    2245            0 :         applyPaletteElementToList(element, score, sel, modifiers);
    2246            0 :     } else if (sel.isRange()) {
    2247            0 :         applyPaletteElementToRange(element, score, sel, modifiers);
    2248              :     } else {
    2249            0 :         LOGD("unknown selection state");
    2250              :     }
    2251              : 
    2252            0 :     m_notifyAboutDropChanged = true;
    2253            0 :     score->setSelectionChanged(true);
    2254            0 :     apply();
    2255              : 
    2256            0 :     setDropTarget(nullptr);
    2257              : 
    2258            0 :     checkAndShowError();
    2259              : 
    2260            0 :     return true;
    2261            0 : }
    2262              : 
    2263            0 : void NotationInteraction::applyPaletteElementToList(EngravingItem* element, mu::engraving::Score* score,
    2264              :                                                     const mu::engraving::Selection& sel, Qt::KeyboardModifiers modifiers)
    2265              : {
    2266            0 :     engraving::Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
    2267              : 
    2268            0 :     const ElementType elementType = element->type();
    2269              : 
    2270            0 :     ChordRest* cr1 = sel.firstChordRest();
    2271            0 :     ChordRest* cr2 = sel.lastChordRest();
    2272            0 :     bool addSingle = false;           // add a single line only
    2273            0 :     if (cr1 && cr2 == cr1) {
    2274              :         // one chordrest selected, ok to add line
    2275            0 :         addSingle = true;
    2276            0 :     } else if (sel.elements().size() == 2 && cr1 && cr2 && cr1 != cr2) {
    2277              :         // two chordrests selected
    2278              :         // must be on same staff in order to add line, except for slur
    2279            0 :         if (element->isSlur() || cr1->staffIdx() == cr2->staffIdx()) {
    2280            0 :             addSingle = true;
    2281            0 :         } else if (sel.element() && sel.element()->isSlurSegment() && element->isHammerOnPullOff()) {
    2282            0 :             addSingle = true;
    2283              :         }
    2284              :     }
    2285              : 
    2286            0 :     auto isEntryDrumStaff = [score]() {
    2287            0 :         const mu::engraving::InputState& is = score->inputState();
    2288            0 :         const mu::engraving::Staff* staff = score->staff(track2staff(is.track()));
    2289            0 :         return staff ? staff->staffType(is.tick())->group() == mu::engraving::StaffGroup::PERCUSSION : false;
    2290            0 :     };
    2291              : 
    2292            0 :     const bool elementIsStandardBend = element->isActionIcon() && (toActionIcon(element)->actionType() == ActionIconType::STANDARD_BEND
    2293            0 :                                                                    || toActionIcon(element)->actionType() == ActionIconType::DIVE);
    2294            0 :     const bool elementIsNoteLine = element->isActionIcon() && toActionIcon(element)->actionType() == ActionIconType::NOTE_ANCHORED_LINE;
    2295            0 :     const bool isLineNoteToNote = (element->isGlissando() || elementIsStandardBend || elementIsNoteLine)
    2296            0 :                                   && sel.isList() && sel.elements().size() == 2
    2297            0 :                                   && sel.elements()[0]->isNote() && sel.elements()[1]->isNote()
    2298            0 :                                   && sel.elements()[1]->tick() != sel.elements()[0]->tick();
    2299              : 
    2300            0 :     if (isEntryDrumStaff() && element->isChord()) {
    2301            0 :         mu::engraving::InputState& is = score->inputState();
    2302            0 :         EngravingItem* e = nullptr;
    2303            0 :         if (!(modifiers & Qt::ShiftModifier)) {
    2304              :             // shift+double-click: add note to "chord"
    2305              :             // use input position rather than selection if possible
    2306              :             // look for a cr in the voice predefined for the drum in the palette
    2307              :             // back up if necessary
    2308              :             // TODO: refactor this with similar code in putNote()
    2309            0 :             if (is.segment()) {
    2310            0 :                 mu::engraving::Segment* seg = is.segment();
    2311            0 :                 while (seg) {
    2312            0 :                     if (seg->element(is.track())) {
    2313            0 :                         break;
    2314              :                     }
    2315            0 :                     seg = seg->prev(mu::engraving::SegmentType::ChordRest);
    2316              :                 }
    2317            0 :                 if (seg) {
    2318            0 :                     is.setSegment(seg);
    2319              :                 } else {
    2320            0 :                     is.setSegment(is.segment()->measure()->first(mu::engraving::SegmentType::ChordRest));
    2321              :                 }
    2322              :             }
    2323            0 :             score->expandVoice();
    2324            0 :             e = is.cr();
    2325              :         }
    2326            0 :         if (!e) {
    2327            0 :             e = sel.elements().front();
    2328              :         }
    2329            0 :         if (e) {
    2330              :             // get note if selection was full chord
    2331            0 :             if (e->isChord()) {
    2332            0 :                 e = toChord(e)->upNote();
    2333              :             }
    2334              : 
    2335            0 :             applyDropPaletteElement(score, e, element, modifiers);
    2336              :             // note has already been played (and what would play otherwise may be *next* input position)
    2337            0 :             score->setPlayNote(false);
    2338            0 :             score->setPlayChord(false);
    2339              :             // continue in same track
    2340            0 :             is.setTrack(e->track());
    2341              :         } else {
    2342            0 :             LOGD("nowhere to place drum note");
    2343              :         }
    2344            0 :         return;
    2345              :     }
    2346              : 
    2347            0 :     if (element->isLayoutBreak()) {
    2348            0 :         mu::engraving::LayoutBreak* breakElement = toLayoutBreak(element);
    2349            0 :         score->cmdToggleLayoutBreak(breakElement->layoutBreakType());
    2350            0 :         return;
    2351              :     }
    2352              : 
    2353            0 :     if (element->isActionIcon()) {
    2354            0 :         const ActionIcon* icon = toActionIcon(element);
    2355            0 :         switch (icon->actionType()) {
    2356            0 :         case ActionIconType::SYSTEM_LOCK: {
    2357            0 :             EditSystemLocks::toggleSystemLock(tx, score, score->selection().selectedSystems());
    2358            0 :             return;
    2359              :         }
    2360            0 :         case ActionIconType::PAGE_LOCK: {
    2361            0 :             EditPageLocks::togglePageLock(tx, score, score->selection().pagesContainingSelection());
    2362            0 :             return;
    2363              :         }
    2364            0 :         case ActionIconType::PARENTHESES: {
    2365            0 :             if (!sel.noteList().empty()) {
    2366            0 :                 EditParentheses::addParenthesesToNotes(tx, score);
    2367            0 :                 return;
    2368              :             }
    2369              :         }
    2370              :         default:
    2371            0 :             break;
    2372              :         }
    2373              :     }
    2374              : 
    2375            0 :     if (element->isSlur()) {
    2376            0 :         doAddSlur(toSlur(element));
    2377            0 :         return;
    2378              :     }
    2379              : 
    2380            0 :     if (element->isSLine() && !element->isGlissando() && !element->isGuitarBend() && addSingle) {
    2381            0 :         mu::engraving::Segment* startSegment = cr1->segment();
    2382            0 :         mu::engraving::Segment* endSegment = cr2->segment();
    2383            0 :         if (elementType == mu::engraving::ElementType::PEDAL && cr2 != cr1) {
    2384            0 :             endSegment = endSegment->nextCR(cr2->track());
    2385              :         } else {
    2386              :             // Ensure that list-selection results in the same endSegment as range selection
    2387            0 :             endSegment = cr2->nextSegmentAfterCR(SegmentType::ChordRest | SegmentType::EndBarLine | SegmentType::Clef);
    2388              :         }
    2389            0 :         mu::engraving::Spanner* spanner = toSpanner(element->clone());
    2390            0 :         spanner->setScore(score);
    2391            0 :         spanner->styleChanged();
    2392            0 :         if (spanner->isHairpin()) {
    2393            0 :             EditHairpin::addHairpin(tx, score, toHairpin(spanner), cr1, cr2);
    2394            0 :             if (!spanner->segmentsEmpty() && !score->noteEntryMode()) {
    2395            0 :                 SpannerSegment* frontSegment = spanner->frontSegment();
    2396            0 :                 score->select(frontSegment);
    2397            0 :                 startEditElement(frontSegment);
    2398              :             }
    2399              :         } else {
    2400            0 :             bool firstStaffOnly = isSystemTextLine(element) && !(modifiers & Qt::ControlModifier);
    2401            0 :             staff_idx_t targetStaff = firstStaffOnly ? 0 : cr1->staffIdx();
    2402            0 :             EditSpanner::addSpanner(tx, score, spanner, targetStaff, startSegment, endSegment, modifiers & Qt::ControlModifier);
    2403              :         }
    2404            0 :         if (spanner->hasVoiceAssignmentProperties()) {
    2405            0 :             spanner->setInitialTrackAndVoiceAssignment(cr1->track(), modifiers & ControlModifier);
    2406            0 :         } else if (spanner->isVoiceSpecific()) {
    2407            0 :             spanner->setTrack(cr1->track());
    2408              :         }
    2409            0 :         return;
    2410              :     }
    2411              : 
    2412            0 :     if (element->isArticulationFamily() && sel.elements().size() == 1) {
    2413              :         // understand adding an articulation to another articulation as adding it to the chord it's attached to
    2414            0 :         EngravingItem* e = sel.elements().front();
    2415            0 :         if (e->isArticulationFamily()) {
    2416            0 :             if (Chord* c = toChord(toArticulation(e)->ownershipParent())) {
    2417            0 :                 applyDropPaletteElement(score, c->notes().front(), element, modifiers);
    2418              :             }
    2419              :         } else {
    2420            0 :             applyDropPaletteElement(score, e, element, modifiers);
    2421              :         }
    2422            0 :         return;
    2423              :     }
    2424              : 
    2425            0 :     if (isLineNoteToNote) {
    2426            0 :         applyLineNoteToNote(score, toNote(sel.elements()[0]), toNote(sel.elements()[1]), element);
    2427            0 :         return;
    2428              :     }
    2429              : 
    2430            0 :     if ((element->isClef() || element->isTimeSig() || element->isKeySig()) && score->noteEntryMode()) {
    2431              :         // in note input mode place clef / time sig / key sig before cursor
    2432            0 :         EngravingItem* e = score->inputState().cr();
    2433            0 :         if (!e) {
    2434            0 :             e = sel.elements().front();
    2435            0 :         } else if (e->isChord()) {
    2436            0 :             e = toChord(e)->notes().front();
    2437              :         }
    2438            0 :         applyDropPaletteElement(score, e, element, modifiers);
    2439            0 :         return;
    2440              :     }
    2441              : 
    2442              :     bool unique;
    2443            0 :     std::vector<EngravingItem*> targetElements = mu::engraving::filterTargetElements(sel, element, unique);
    2444              : 
    2445            0 :     for (EngravingItem* target : targetElements) {
    2446            0 :         applyDropPaletteElement(score, target, element, modifiers);
    2447              :     }
    2448              : }
    2449              : 
    2450            0 : void NotationInteraction::applyPaletteElementToRange(EngravingItem* element, mu::engraving::Score* score,
    2451              :                                                      const mu::engraving::Selection& sel, Qt::KeyboardModifiers modifiers)
    2452              : {
    2453            0 :     engraving::Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
    2454            0 :     const ElementType elementType = element->type();
    2455              : 
    2456            0 :     bool isMeasureAnchoredElement = false;
    2457            0 :     std::vector<EngravingItem*> targetElements = mu::engraving::filterTargetElements(sel, element, isMeasureAnchoredElement);
    2458            0 :     if (isMeasureAnchoredElement) {
    2459            0 :         if (elementType == ElementType::BAR_LINE) {
    2460              :             // In a range selection, add barlines to all selected measures
    2461            0 :             for (EngravingItem* target : targetElements) {
    2462            0 :                 applyDropPaletteElement(score, target->findMeasure(), element, modifiers, target->track());
    2463              :             }
    2464              :         } else {
    2465            0 :             for (EngravingItem* target : targetElements) {
    2466            0 :                 applyDropPaletteElement(score, target, element, modifiers);
    2467              :             }
    2468              :         }
    2469            0 :         return;
    2470              :     }
    2471              : 
    2472            0 :     if (elementType == ElementType::LAYOUT_BREAK) {
    2473            0 :         LayoutBreak* breakElement = toLayoutBreak(element);
    2474            0 :         score->cmdToggleLayoutBreak(breakElement->layoutBreakType());
    2475            0 :         return;
    2476              :     }
    2477              : 
    2478            0 :     if (element->isClef() || element->isKeySig() || element->isTimeSig()) {
    2479            0 :         Measure* m1 = sel.startSegment()->measure();
    2480            0 :         Measure* m2 = sel.endSegment() ? sel.endSegment()->measure() : nullptr;
    2481            0 :         if (m2 == m1 && sel.startSegment()->rtick().isZero()) {
    2482            0 :             m2 = nullptr;             // don't restore original if one full measure selected
    2483            0 :         } else if (m2) {
    2484            0 :             m2 = m2->nextMeasureMM();
    2485              :         }
    2486              :         // Clefs always apply to every selected staff, key and time signatures only when added locally
    2487            0 :         const bool applyToEachStaff = element->isClef() || (modifiers & Qt::ControlModifier);
    2488            0 :         staff_idx_t staffIdx1 = sel.staffStart();
    2489            0 :         staff_idx_t staffIdx2 = applyToEachStaff ? sel.staffEnd() : staffIdx1 + 1;
    2490            0 :         for (staff_idx_t i = staffIdx1; i < staffIdx2; ++i) {
    2491              :             // For clefs, use mid-measure changes if appropriate
    2492              :             // Mid-measure key signature changes are done via list selections
    2493            0 :             EngravingItem* e1 = nullptr;
    2494            0 :             EngravingItem* e2 = nullptr;
    2495            0 :             if (element->isClef()) {
    2496            0 :                 if (sel.startSegment()->isChordRestType() && sel.startSegment()->rtick().isNotZero()) {
    2497            0 :                     ChordRest* cr = toChordRest(sel.startSegment()->nextChordRest(staff2track(i)));
    2498            0 :                     if (cr && cr->isChord()) {
    2499            0 :                         e1 = toChord(cr)->upNote();
    2500              :                     } else {
    2501            0 :                         e1 = cr;
    2502              :                     }
    2503              :                 }
    2504            0 :                 if (sel.endSegment() && sel.endSegment()->segmentType() == mu::engraving::SegmentType::ChordRest) {
    2505            0 :                     ChordRest* cr = toChordRest(sel.endSegment()->nextChordRest(staff2track(i)));
    2506            0 :                     if (cr && cr->isChord()) {
    2507            0 :                         e2 = toChord(cr)->upNote();
    2508              :                     } else {
    2509            0 :                         e2 = cr;
    2510              :                     }
    2511              :                 }
    2512              :             }
    2513              :             // restore clef/keysig/timesig that was in effect at end of selection
    2514            0 :             if (m2 || e2) {
    2515            0 :                 mu::engraving::Staff* staff = score->staff(i);
    2516            0 :                 mu::engraving::Fraction tick2 = sel.endSegment()->tick();
    2517            0 :                 mu::engraving::EngravingItem* oelement = nullptr;
    2518            0 :                 switch (elementType) {
    2519            0 :                 case mu::engraving::ElementType::CLEF:
    2520              :                 {
    2521            0 :                     mu::engraving::Clef* oclef = engraving::Factory::createClef(score->dummy());
    2522            0 :                     oclef->setClefType(staff->clef(tick2));
    2523            0 :                     oelement = oclef;
    2524            0 :                     break;
    2525              :                 }
    2526            0 :                 case mu::engraving::ElementType::KEYSIG:
    2527              :                 {
    2528            0 :                     mu::engraving::KeySig* okeysig = engraving::Factory::createKeySig(score->dummy());
    2529            0 :                     okeysig->setKeySigEvent(staff->keySigEvent(tick2));
    2530            0 :                     Key ck = okeysig->concertKey();
    2531            0 :                     okeysig->setKey(ck);
    2532            0 :                     oelement = okeysig;
    2533            0 :                     break;
    2534              :                 }
    2535            0 :                 case mu::engraving::ElementType::TIMESIG:
    2536              :                 {
    2537            0 :                     mu::engraving::TimeSig* otimesig = engraving::Factory::createTimeSig(score->dummy());
    2538            0 :                     otimesig->setFrom(staff->timeSig(tick2));
    2539            0 :                     oelement = otimesig;
    2540            0 :                     break;
    2541              :                 }
    2542            0 :                 default:
    2543            0 :                     break;
    2544              :                 }
    2545            0 :                 if (oelement) {
    2546            0 :                     if (e2) {
    2547            0 :                         applyDropPaletteElement(score, e2, oelement, modifiers);
    2548              :                     } else {
    2549            0 :                         applyDropPaletteElement(score, m2, oelement, modifiers, staff2track(i));
    2550              :                     }
    2551            0 :                     delete oelement;
    2552              :                 }
    2553              :             }
    2554              :             // apply new clef/keysig/timesig
    2555            0 :             if (e1) {
    2556            0 :                 applyDropPaletteElement(score, e1, element, modifiers);
    2557              :             } else {
    2558            0 :                 applyDropPaletteElement(score, m1, element, modifiers, staff2track(i));
    2559              :             }
    2560              :         }
    2561            0 :         return;
    2562              :     }
    2563              : 
    2564            0 :     if (element->isSlur()) {
    2565            0 :         doAddSlur(toSlur(element));
    2566            0 :         return;
    2567              :     }
    2568              : 
    2569            0 :     if (element->isSLine() && elementType != ElementType::GLISSANDO) {
    2570            0 :         Segment* startSegment = sel.startSegment();
    2571            0 :         Segment* endSegment = sel.endSegment();
    2572            0 :         bool firstStaffOnly = isSystemTextLine(element) && !(modifiers & Qt::ControlModifier);
    2573            0 :         staff_idx_t startStaff = firstStaffOnly ? 0 : sel.staffStart();
    2574            0 :         staff_idx_t endStaff   = firstStaffOnly ? 1 : sel.staffEnd();
    2575            0 :         for (staff_idx_t i = startStaff; i < endStaff; ++i) {
    2576            0 :             Spanner* spanner = toSpanner(element->clone());
    2577            0 :             spanner->setScore(score);
    2578            0 :             spanner->styleChanged();
    2579            0 :             EditSpanner::addSpanner(tx, score, spanner, i, startSegment, endSegment, modifiers & Qt::ControlModifier);
    2580            0 :             if (spanner->hasVoiceAssignmentProperties()) {
    2581            0 :                 spanner->setInitialTrackAndVoiceAssignment(staff2track(i), modifiers & ControlModifier);
    2582              :             }
    2583            0 :             selectAndStartEditIfNeeded(spanner);
    2584              :         }
    2585            0 :         return;
    2586              :     }
    2587              : 
    2588            0 :     if (element->isTextBase() && !element->isFingering() && !element->isSticking()) {
    2589            0 :         Segment* firstSegment = sel.startSegment();
    2590            0 :         staff_idx_t firstStaffIndex = sel.staffStart();
    2591            0 :         staff_idx_t lastStaffIndex = sel.staffEnd();
    2592              : 
    2593              :         // A text should only be added at the start of the selection
    2594              :         // There shouldn't be a text at each element
    2595            0 :         if (element->systemFlag()) {
    2596            0 :             applyDropPaletteElement(score, firstSegment->firstElementForNavigation(0), element, modifiers);
    2597              :         } else {
    2598            0 :             for (staff_idx_t staff = firstStaffIndex; staff < lastStaffIndex; staff++) {
    2599            0 :                 applyDropPaletteElement(score, firstSegment->firstElementForNavigation(staff), element, modifiers);
    2600              :             }
    2601              :         }
    2602            0 :         return;
    2603              :     }
    2604              : 
    2605            0 :     if (element->isActionIcon()) {
    2606            0 :         const ActionIconType actionType = toActionIcon(element)->actionType();
    2607            0 :         switch (actionType) {
    2608            0 :         case ActionIconType::SYSTEM_LOCK: {
    2609            0 :             EditSystemLocks::toggleSystemLock(tx, score, score->selection().selectedSystems());
    2610            0 :             return;
    2611              :         }
    2612            0 :         case ActionIconType::PAGE_LOCK: {
    2613            0 :             EditPageLocks::togglePageLock(tx, score, score->selection().pagesContainingSelection());
    2614            0 :             return;
    2615              :         }
    2616            0 :         case ActionIconType::PARENTHESES: {
    2617            0 :             EditParentheses::addParenthesesToNotes(tx, score);
    2618            0 :             return;
    2619              :         }
    2620            0 :         case ActionIconType::STANDARD_BEND:
    2621              :         case ActionIconType::PRE_BEND:
    2622              :         case ActionIconType::GRACE_NOTE_BEND:
    2623              :         case ActionIconType::SLIGHT_BEND:
    2624              :         case ActionIconType::DIVE:
    2625              :         case ActionIconType::PRE_DIVE:
    2626              :         case ActionIconType::DIP:
    2627              :         case ActionIconType::SCOOP:
    2628              :         {
    2629              :             // Insertion of bend may alter the segment list, so collect the original list here and loop on this
    2630            0 :             std::vector<Segment*> segList;
    2631            0 :             for (Segment* seg = sel.startSegment(); seg && seg != sel.endSegment(); seg = seg->next1()) {
    2632            0 :                 if (seg->isChordRestType()) {
    2633            0 :                     segList.push_back(seg);
    2634              :                 }
    2635              :             }
    2636            0 :             const track_idx_t track1 = staff2track(sel.staffStart());
    2637            0 :             const track_idx_t track2 = staff2track(sel.staffEnd());
    2638            0 :             for (Segment* seg : segList) {
    2639            0 :                 for (track_idx_t track = track1; track < track2; ++track) {
    2640            0 :                     EngravingItem* item = seg->element(track);
    2641            0 :                     if (!item || !item->isChord()) {
    2642            0 :                         continue;
    2643              :                     }
    2644            0 :                     for (Note* note : toChord(item)->notes()) {
    2645            0 :                         applyDropPaletteElement(score, note, element, modifiers);
    2646              :                     }
    2647              :                 }
    2648              :             }
    2649            0 :             return;
    2650            0 :         }
    2651            0 :         default: break;
    2652              :         }
    2653              :     }
    2654              : 
    2655            0 :     const track_idx_t track1 = staff2track(sel.staffStart());
    2656            0 :     const track_idx_t track2 = staff2track(sel.staffEnd());
    2657            0 :     Segment* startSegment = sel.startSegment();
    2658            0 :     Segment* endSegment = sel.endSegment();           //keep it, it could change during the loop
    2659              : 
    2660            0 :     const bool rangeContainsMultiNoteChords = sel.rangeContainsMultiNoteChords();
    2661              : 
    2662            0 :     const auto applyDropPaletteElementToChord = [this, score, element, modifiers, rangeContainsMultiNoteChords](Chord* chord) {
    2663            0 :         const size_t totalNotesInChord = chord->notes().size();
    2664            0 :         for (size_t noteIdx = 0; noteIdx < totalNotesInChord; ++noteIdx) {
    2665            0 :             Note* note = chord->notes().at(noteIdx);
    2666            0 :             if (!note || !score->selectionFilter().canSelectNoteIdx(noteIdx, totalNotesInChord, rangeContainsMultiNoteChords)) {
    2667            0 :                 continue;
    2668              :             }
    2669            0 :             applyDropPaletteElement(score, note, element, modifiers);
    2670            0 :             if (!element->isAccidental() && !element->isNoteHead() && !element->isGlissando() && !element->isChordLine()) {
    2671              :                 // Only need to apply to one note...
    2672            0 :                 break;
    2673              :             }
    2674              :         }
    2675            0 :     };
    2676              : 
    2677            0 :     for (Segment* s = startSegment; s && s != endSegment; s = s->next1()) {
    2678            0 :         for (track_idx_t track = track1; track < track2; ++track) {
    2679            0 :             EngravingItem* e = s->element(track);
    2680            0 :             if (!e || !score->selectionFilter().canSelect(e) || !score->selectionFilter().canSelectVoice(track)) {
    2681            0 :                 continue;
    2682              :             }
    2683            0 :             if (!e->isChord()) {
    2684              :                 // do not apply articulation or fermata to barline in a range selection
    2685            0 :                 if (!e->isBarLine() || !element->isArticulationOrFermata()) {
    2686            0 :                     applyDropPaletteElement(score, e, element, modifiers);
    2687              :                 }
    2688            0 :                 continue;
    2689              :             }
    2690              : 
    2691            0 :             Chord* chord = toChord(e);
    2692            0 :             applyDropPaletteElementToChord(chord);
    2693              :         }
    2694            0 :         if (!element->placeMultiple()) {
    2695            0 :             break;
    2696              :         }
    2697              :     }
    2698            0 : }
    2699              : 
    2700              : //! NOTE Copied from Palette applyDrop
    2701            0 : void NotationInteraction::applyDropPaletteElement(mu::engraving::Score* score, mu::engraving::EngravingItem* target,
    2702              :                                                   mu::engraving::EngravingItem* e, Qt::KeyboardModifiers modifiers,
    2703              :                                                   track_idx_t track)
    2704              : {
    2705            0 :     if (!target) {
    2706            0 :         return;
    2707              :     }
    2708              : 
    2709            0 :     mu::engraving::EditData newData(&m_scoreCallbacks);
    2710            0 :     mu::engraving::EditData* dropData = &newData;
    2711              : 
    2712            0 :     if (isTextEditingStarted()) {
    2713            0 :         dropData = &m_editData;
    2714              :     }
    2715              : 
    2716            0 :     dropData->modifiers = keyboardModifier(modifiers);
    2717            0 :     dropData->dropElement = e;
    2718            0 :     dropData->track = (track == muse::nidx) ? target->track() : track;
    2719            0 :     dropData->pos = target->pageBoundingRect().topLeft();
    2720              : 
    2721            0 :     if (target->acceptDrop(*dropData)) {
    2722              :         // use same code path as drag&drop
    2723              : 
    2724            0 :         ByteArray a = e->mimeData();
    2725              : 
    2726            0 :         mu::engraving::XmlReader n(a);
    2727              : 
    2728            0 :         Fraction duration;      // dummy
    2729            0 :         PointF dragOffset;
    2730            0 :         ElementType type = EngravingItem::readType(n, &dragOffset, &duration);
    2731            0 :         dropData->dropElement = engraving::Factory::createItem(type, score->dummy());
    2732            0 :         rw::RWRegister::reader()->readItem(dropData->dropElement, n);
    2733            0 :         dropData->dropElement->styleChanged();       // update to local style
    2734              : 
    2735            0 :         engraving::Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
    2736            0 :         EngravingItem* el = target->drop(tx, *dropData);
    2737              : 
    2738            0 :         if (el && el->isInstrumentChange()) {
    2739            0 :             if (!selectInstrument(toInstrumentChange(el))) {
    2740            0 :                 rollback();
    2741            0 :                 return;
    2742              :             }
    2743              :         }
    2744              : 
    2745            0 :         if (el && el->hasVoiceAssignmentProperties()) {
    2746              :             // If target has voice assignment properties, dropped element takes those and discards the default
    2747            0 :             if (!target->hasVoiceAssignmentProperties()) {
    2748            0 :                 el->setInitialTrackAndVoiceAssignment(target->track(), modifiers & ControlModifier);
    2749              :             }
    2750              :         }
    2751              : 
    2752            0 :         if (el && !score->inputState().noteEntryMode()) {
    2753            0 :             selectAndStartEditIfNeeded(el);
    2754              :         }
    2755              : 
    2756            0 :         if (score->selection().elements().size() == 1) {
    2757            0 :             doFinishAddFretboardDiagram();
    2758              :         }
    2759              : 
    2760            0 :         dropData->dropElement = nullptr;
    2761              : 
    2762            0 :         m_notifyAboutDropChanged = true;
    2763            0 :     }
    2764            0 : }
    2765              : 
    2766            0 : void NotationInteraction::applyLineNoteToNote(Score* score, Note* note1, Note* note2, EngravingItem* line)
    2767              : {
    2768            0 :     mu::engraving::EditData newData(&m_scoreCallbacks);
    2769            0 :     mu::engraving::EditData* dropData = &newData;
    2770              : 
    2771            0 :     Chord* chord1 = note1->chord();
    2772            0 :     Chord* chord2 = note2->chord();
    2773            0 :     bool swapNotes = chord2->tick() < chord1->tick() || chord2->graceIndex() > chord1->graceIndex()
    2774            0 :                      || (chord2->isGraceBefore() && chord2->parent() == chord1)
    2775            0 :                      || (chord1->isGraceAfter() && chord1->parent() == chord2);
    2776              : 
    2777            0 :     if (swapNotes) {
    2778            0 :         std::swap(note1, note2);
    2779              :     }
    2780              : 
    2781            0 :     if (line->isActionIcon() && (toActionIcon(line)->actionType() == ActionIconType::STANDARD_BEND
    2782            0 :                                  || toActionIcon(line)->actionType() == ActionIconType::DIVE)) {
    2783            0 :         score->addGuitarBend(toActionIcon(line)->actionType() == ActionIconType::STANDARD_BEND
    2784              :                              ? GuitarBendType::BEND : GuitarBendType::DIVE, note1, note2);
    2785            0 :     } else if (line->isActionIcon() && toActionIcon(line)->actionType() == ActionIconType::NOTE_ANCHORED_LINE) {
    2786            0 :         score->addNoteLine();
    2787            0 :     } else if (line->isSLine()) {
    2788            0 :         SLine* dropLine = ((SLine*)line->clone());
    2789            0 :         dropData->dropElement = dropLine;
    2790            0 :         if (note1->acceptDrop(*dropData)) {
    2791            0 :             dropLine->setEndElement(note2);
    2792            0 :             dropLine->styleChanged();
    2793              : 
    2794            0 :             engraving::Transaction& tx = score->transactionManager()->currentOrDummyTransaction();
    2795            0 :             mu::engraving::EngravingItem* el = note1->drop(tx, *dropData);
    2796            0 :             if (el && !score->inputState().noteEntryMode()) {
    2797            0 :                 m_selection->select({ el }, mu::engraving::SelectType::SINGLE, 0);
    2798              :             }
    2799              : 
    2800            0 :             dropData->dropElement = nullptr;
    2801              : 
    2802            0 :             m_notifyAboutDropChanged = true;
    2803              :         }
    2804              :     }
    2805            0 : }
    2806              : 
    2807              : //! NOTE Copied from ScoreView::cmdAddSlur
    2808            0 : void NotationInteraction::doAddSlur(const Slur* slurTemplate)
    2809              : {
    2810            0 :     startEdit(TranslatableString("undoableAction", "Add slur"));
    2811            0 :     m_notifyAboutDropChanged = true;
    2812              : 
    2813            0 :     ChordRest* firstChordRest = nullptr;
    2814            0 :     ChordRest* secondChordRest = nullptr;
    2815            0 :     const auto& sel = score()->selection();
    2816            0 :     auto el = sel.uniqueElements();
    2817              : 
    2818            0 :     if (sel.isRange()) {
    2819            0 :         track_idx_t startTrack = staff2track(sel.staffStart());
    2820            0 :         track_idx_t endTrack = staff2track(sel.staffEnd());
    2821            0 :         for (track_idx_t track = startTrack; track < endTrack; ++track) {
    2822            0 :             firstChordRest = nullptr;
    2823            0 :             secondChordRest = nullptr;
    2824            0 :             for (EngravingItem* e : el) {
    2825            0 :                 if (e->track() != track) {
    2826            0 :                     continue;
    2827              :                 }
    2828            0 :                 if (e->isNote()) {
    2829            0 :                     e = toNote(e)->chord();
    2830              :                 }
    2831            0 :                 if (!e->isChord()) {
    2832            0 :                     continue;
    2833              :                 }
    2834            0 :                 ChordRest* cr = toChordRest(e);
    2835            0 :                 if (!firstChordRest || firstChordRest->tick() > cr->tick()) {
    2836            0 :                     firstChordRest = cr;
    2837              :                 }
    2838            0 :                 if (!secondChordRest || secondChordRest->tick() < cr->tick()) {
    2839            0 :                     secondChordRest = cr;
    2840              :                 }
    2841              :             }
    2842              : 
    2843            0 :             bool firstCrTrill = firstChordRest && firstChordRest->isChord() && toChord(firstChordRest)->isTrillCueNote();
    2844            0 :             bool secondCrTrill = secondChordRest && secondChordRest->isChord() && toChord(secondChordRest)->isTrillCueNote();
    2845              : 
    2846            0 :             if (firstChordRest && (firstChordRest != secondChordRest)
    2847            0 :                 && !(firstCrTrill || secondCrTrill)) {
    2848            0 :                 doAddSlur(firstChordRest, secondChordRest, slurTemplate);
    2849              :             }
    2850              :         }
    2851            0 :     } else if (sel.isSingle()) {
    2852            0 :         if (sel.element()->isNote() && !toNote(sel.element())->isTrillCueNote()) {
    2853            0 :             doAddSlur(toNote(sel.element())->chord(), nullptr, slurTemplate);
    2854            0 :         } else if (sel.element()->isSlurSegment() && slurTemplate && slurTemplate->isHammerOnPullOff()) {
    2855            0 :             Slur* slur = toSlurSegment(sel.element())->slur();
    2856            0 :             doAddSlur(slur->startElement(), slur->endElement(), slurTemplate);
    2857              :         }
    2858              :     } else {
    2859            0 :         EngravingItem* firstItem = nullptr;
    2860            0 :         EngravingItem* secondItem = nullptr;
    2861            0 :         for (EngravingItem* e : el) {
    2862            0 :             if (e->isNote()) {
    2863            0 :                 e = toNote(e)->chord();
    2864              :             }
    2865              : 
    2866            0 :             Measure* meas = e->findMeasure();
    2867            0 :             bool header = meas && meas->header() && e->tick() == meas->tick();
    2868              : 
    2869            0 :             if (!e->isChord() && !e->isBarLine() && !header) {
    2870            0 :                 continue;
    2871              :             }
    2872              : 
    2873            0 :             if (!firstItem || e->isBefore(firstItem)) {
    2874            0 :                 firstItem = e;
    2875              :             }
    2876            0 :             if (!secondItem || secondItem->isBefore(e)) {
    2877            0 :                 secondItem = e;
    2878              :             }
    2879              :         }
    2880              : 
    2881            0 :         bool firstCrTrill = firstItem && firstItem->isChord() && toChord(firstItem)->isTrillCueNote();
    2882            0 :         bool secondCrTrill = secondItem && secondItem->isChord() && toChord(secondItem)->isTrillCueNote();
    2883              : 
    2884            0 :         if (firstItem && firstItem == secondItem) {
    2885              :             // A single chord is slurred as a single note is
    2886            0 :             if (firstItem->isChord() && !firstCrTrill) {
    2887            0 :                 doAddSlur(firstItem, nullptr, slurTemplate);
    2888              :             }
    2889            0 :         } else if (firstItem && secondItem && (firstItem->isChordRest() || secondItem->isChordRest())
    2890            0 :                    && !(firstCrTrill || secondCrTrill)) {
    2891            0 :             doAddSlur(firstItem, secondItem, slurTemplate);
    2892              :         }
    2893              :     }
    2894              : 
    2895            0 :     apply();
    2896            0 : }
    2897              : 
    2898            0 : void NotationInteraction::doAddSlur(EngravingItem* firstItem, EngravingItem* secondItem, const Slur* slurTemplate)
    2899              : {
    2900            0 :     ChordRest* firstChordRest = nullptr;
    2901            0 :     ChordRest* secondChordRest = nullptr;
    2902              : 
    2903            0 :     Measure* meas = firstItem ? firstItem->findMeasure() : nullptr;
    2904            0 :     bool header = meas && meas->header() && firstItem->tick() == meas->tick() && !firstItem->isChordRest();
    2905              : 
    2906            0 :     if (firstItem && secondItem
    2907            0 :         && ((firstItem->isBarLine() != secondItem->isBarLine()) || (header && secondItem->isChordRest()))) {
    2908            0 :         const bool outgoing = firstItem->isChordRest();
    2909            0 :         const EngravingItem* otherElement = outgoing ? secondItem : firstItem;
    2910            0 :         ChordRest* cr = outgoing ? toChordRest(firstItem) : toChordRest(secondItem);
    2911              : 
    2912              :         // Check that cr and otherElement are part of the same repeat section
    2913            0 :         Segment* seg1 = toSegment(firstItem->findAncestor(ElementType::SEGMENT));
    2914            0 :         Segment* seg2 = toSegment(secondItem->findAncestor(ElementType::SEGMENT));
    2915              : 
    2916            0 :         bool differentRepeatSegments = segmentsAreInDifferentRepeatSegments(seg1, seg2);
    2917            0 :         bool validBarline = isValidBarLineForRepeatSection(seg1, seg2);
    2918              : 
    2919            0 :         if (!cr || (!header && (differentRepeatSegments && !validBarline))) {
    2920            0 :             return;
    2921              :         }
    2922              : 
    2923            0 :         Slur* partialSlur = slurTemplate ? slurTemplate->clone() : Factory::createSlur(score()->dummy());
    2924            0 :         if (outgoing) {
    2925            0 :             partialSlur->undoSetOutgoing(true);
    2926            0 :             firstChordRest = toChordRest(cr);
    2927            0 :             const Measure* endMeas = otherElement->findMeasure();
    2928            0 :             ChordRest* endCr = endMeas->lastChordRest(otherElement->track());
    2929            0 :             secondChordRest = endCr;
    2930              :         } else {
    2931            0 :             partialSlur->undoSetIncoming(true);
    2932            0 :             secondChordRest = toChordRest(cr);
    2933            0 :             const Measure* startMeas = otherElement->findMeasure();
    2934            0 :             if (otherElement->isBarLine()) {
    2935            0 :                 if (toBarLine(otherElement)->barLineType() == BarLineType::END_REPEAT) {
    2936            0 :                     startMeas = startMeas->nextMeasure();
    2937              :                 }
    2938              :             }
    2939            0 :             ChordRest* startCr = startMeas->firstChordRest(otherElement->track());
    2940            0 :             firstChordRest = startCr;
    2941              :         }
    2942            0 :         slurTemplate = partialSlur;
    2943              :     } else {
    2944            0 :         firstChordRest = toChordRest(firstItem);
    2945            0 :         secondChordRest = toChordRest(secondItem);
    2946              :     }
    2947              : 
    2948            0 :     Slur* slur = firstChordRest->slur(secondChordRest);
    2949            0 :     if (!slur || slur->slurDirection() != DirectionV::AUTO) {
    2950            0 :         slur = score()->addSlur(firstChordRest, secondChordRest, slurTemplate);
    2951            0 :     } else if (slurTemplate && slurTemplate->isHammerOnPullOff()) {
    2952              :         // Replace existing slur with HOPO
    2953            0 :         endEditElement();
    2954            0 :         score()->undoRemoveElement(slur);
    2955            0 :         slur = score()->addSlur(firstChordRest, secondChordRest, slurTemplate);
    2956            0 :         SlurSegment* segment = slur->frontSegment();
    2957            0 :         select({ segment }, SelectType::SINGLE);
    2958            0 :         startEditGrip(segment, Grip::END);
    2959              :     }
    2960              : 
    2961            0 :     if (m_noteInput->isNoteInputMode()) {
    2962            0 :         m_noteInput->addSlur(slur);
    2963            0 :     } else if (!secondItem) {
    2964            0 :         SlurSegment* segment = slur->frontSegment();
    2965            0 :         select({ segment }, SelectType::SINGLE);
    2966            0 :         startEditGrip(segment, Grip::END);
    2967              :     }
    2968              : }
    2969              : 
    2970            0 : bool NotationInteraction::scoreHasMeasure() const
    2971              : {
    2972            0 :     mu::engraving::Page* page = score()->pages().empty() ? nullptr : score()->pages().front();
    2973            0 :     const std::vector<mu::engraving::System*>* systems = page ? &page->systems() : nullptr;
    2974            0 :     if (systems == nullptr || systems->empty() || systems->front()->measures().empty()) {
    2975            0 :         return false;
    2976              :     }
    2977              : 
    2978            0 :     return true;
    2979              : }
    2980              : 
    2981            0 : bool NotationInteraction::notesHaveActiculation(const std::vector<Note*>& notes, SymbolId articulationSymbolId) const
    2982              : {
    2983            0 :     for (Note* note: notes) {
    2984            0 :         Chord* chord = note->chord();
    2985              : 
    2986            0 :         std::set<SymbolId> chordArticulations = chord->articulationSymbolIds();
    2987            0 :         chordArticulations = mu::engraving::flipArticulations(chordArticulations, mu::engraving::PlacementV::ABOVE);
    2988            0 :         chordArticulations = mu::engraving::splitArticulations(chordArticulations);
    2989              : 
    2990            0 :         if (chordArticulations.find(articulationSymbolId) == chordArticulations.end()) {
    2991            0 :             return false;
    2992              :         }
    2993            0 :     }
    2994              : 
    2995            0 :     return true;
    2996              : }
    2997              : 
    2998              : //! NOTE Copied from ScoreView::dragLeaveEvent
    2999            0 : void NotationInteraction::endDrop()
    3000              : {
    3001            0 :     score()->hideAnchors();
    3002            0 :     score()->setUpdateAll();
    3003            0 :     setDropTarget(nullptr, /*notify=*/ false);
    3004            0 :     resetDropData();
    3005            0 :     score()->update();
    3006            0 :     notifyAboutDragChanged();
    3007            0 : }
    3008              : 
    3009            0 : muse::async::Notification NotationInteraction::dropChanged() const
    3010              : {
    3011            0 :     return m_dropChanged;
    3012              : }
    3013              : 
    3014              : //! NOTE Copied from ScoreView::dropCanvas
    3015            0 : bool NotationInteraction::dropCanvas(EngravingItem* e)
    3016              : {
    3017            0 :     if (e->isActionIcon()) {
    3018            0 :         switch (mu::engraving::toActionIcon(e)->actionType()) {
    3019            0 :         case mu::engraving::ActionIconType::VFRAME
    3020            0 :             : score()->insertBox(ElementType::VBOX);
    3021            0 :             break;
    3022            0 :         case mu::engraving::ActionIconType::HFRAME:
    3023            0 :             score()->insertBox(ElementType::HBOX);
    3024            0 :             break;
    3025            0 :         case mu::engraving::ActionIconType::TFRAME:
    3026            0 :             score()->insertBox(ElementType::TBOX);
    3027            0 :             break;
    3028            0 :         case mu::engraving::ActionIconType::FFRAME:
    3029            0 :             score()->insertBox(ElementType::FBOX);
    3030            0 :             break;
    3031            0 :         case mu::engraving::ActionIconType::MEASURE:
    3032            0 :             score()->insertMeasure(ElementType::MEASURE);
    3033            0 :             break;
    3034            0 :         default:
    3035            0 :             return false;
    3036              :         }
    3037            0 :         delete e;
    3038            0 :         return true;
    3039              :     }
    3040            0 :     return false;
    3041              : }
    3042              : 
    3043              : //! NOTE Copied from ScoreView::getDropTarget
    3044            0 : EngravingItem* NotationInteraction::dropTarget(mu::engraving::EditData& ed) const
    3045              : {
    3046            0 :     std::vector<EngravingItem*> el = elementsAt(ed.pos);
    3047            0 :     mu::engraving::Measure* fallbackMeasure = nullptr;
    3048            0 :     for (EngravingItem* e : el) {
    3049            0 :         if (e->isStaffLines()) {
    3050            0 :             fallbackMeasure = mu::engraving::toStaffLines(e)->measure();
    3051            0 :             continue;
    3052              :         }
    3053            0 :         if (e->acceptDrop(ed)) {
    3054            0 :             return e;
    3055              :         }
    3056              :     }
    3057            0 :     if (fallbackMeasure && fallbackMeasure->acceptDrop(ed)) {
    3058            0 :         return fallbackMeasure;
    3059              :     }
    3060            0 :     return nullptr;
    3061            0 : }
    3062              : 
    3063            0 : bool NotationInteraction::prepareDropStandardElement(const PointF& pos, Qt::KeyboardModifiers modifiers)
    3064              : {
    3065            0 :     IF_ASSERT_FAILED(m_dropData.elementDropData.has_value()) {
    3066            0 :         return false;
    3067              :     }
    3068              : 
    3069            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    3070              : 
    3071            0 :     Page* page = point2page(pos, true);
    3072            0 :     if (!page) {
    3073            0 :         return false;
    3074              :     }
    3075              : 
    3076              :     // Update the tree when starting a new drag (ed.pos hasn't been set yet), if the modifier changed, or when crossing a page
    3077            0 :     if (edd.ed.pos.isNull() || edd.ed.modifiers != modifiers || page != m_currentDropPage) {
    3078            0 :         m_currentDropPage = page;
    3079            0 :         edd.ed.modifiers = keyboardModifier(modifiers);
    3080              : 
    3081              :         // Where "m" is the number of page elements and "n" is the number of elements accepting a drop...
    3082              :         // O(log m) operation
    3083            0 :         m_droppableTree.initialize(page->pageBoundingRect(), static_cast<int>(page->elements().size()));
    3084              : 
    3085              :         // O(m log n) operation
    3086            0 :         for (EngravingItem* elem : page->elements()) {
    3087            0 :             if (elem->acceptDrop(edd.ed)) {
    3088            0 :                 m_droppableTree.insert(elem);
    3089              :             }
    3090            0 :         }
    3091              :     }
    3092              : 
    3093            0 :     PointF posInPage(pos.x() - page->pos().x(), pos.y() - page->pos().y());
    3094              :     // O(log n) operation
    3095            0 :     EngravingItem* targetElem = m_droppableTree.nearestNeighbor(posInPage);
    3096            0 :     EngravingItem* dropElem = edd.ed.dropElement;
    3097              : 
    3098            0 :     if (!targetElem || !dropElem) {
    3099            0 :         return false;
    3100              :     }
    3101              : 
    3102              :     // Clefs are a special case somewhere between a measure anchored and standard element (hence why
    3103              :     // we handle it here instead of dragMeasureAnchorElement). They can be applied to individual items
    3104              :     // but we should switch targets to the whole measure if the target element is the first in the measure...
    3105            0 :     if (dropElem->isClef() && targetElem->rtick() == Fraction(0, 1)) {
    3106            0 :         Measure* targetMeasure = targetElem->findMeasure();
    3107            0 :         if (targetMeasure && dropElem->type() != targetElem->type()) {
    3108            0 :             setDropTarget(targetMeasure, true);
    3109              : 
    3110            0 :             RectF measureRect = targetMeasure->staffPageBoundingRect(targetElem->staffIdx());
    3111            0 :             measureRect.adjust(page->x(), page->y(), page->x(), page->y());
    3112            0 :             edd.ed.pos = measureRect.center();
    3113            0 :             edd.ed.track = trackZeroVoice(targetElem->track());
    3114            0 :             setAnchorLines({ LineF(pos, measureRect.topLeft()) });
    3115              : 
    3116            0 :             const bool dropAccepted = targetMeasure->acceptDrop(edd.ed);
    3117            0 :             if (dropAccepted) {
    3118            0 :                 setDropRects(dropHighlightRects(dropElem, targetMeasure, measureRect, edd.ed.modifiers));
    3119              :             }
    3120            0 :             return dropAccepted;
    3121              :         }
    3122              :     }
    3123              : 
    3124            0 :     setDropTarget(targetElem, true);
    3125            0 :     edd.ed.pos = targetElem->canvasBoundingRect().center();
    3126            0 :     setAnchorLines({ LineF(pos, targetElem->canvasBoundingRect().center()) });
    3127              : 
    3128            0 :     return true;
    3129              : }
    3130              : 
    3131              : //! NOTE Copied from ScoreView::dragMeasureAnchorElement
    3132            0 : bool NotationInteraction::prepareDropMeasureBaseAnchorElement(const PointF& pos)
    3133              : {
    3134            0 :     IF_ASSERT_FAILED(m_dropData.elementDropData.has_value()) {
    3135            0 :         return false;
    3136              :     }
    3137              : 
    3138            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    3139              : 
    3140            0 :     EngravingItem* dropElem = edd.ed.dropElement;
    3141            0 :     Page* page = point2page(pos);
    3142            0 :     if (!dropElem || !page) {
    3143            0 :         return false;
    3144              :     }
    3145              : 
    3146              :     // "Measure anchored only" means we're dropping something can't anchor to other MeasureBase types (i.e. boxes)...
    3147            0 :     bool isMeasureAnchorOnly = !dropElem->isLayoutBreak();
    3148            0 :     if (dropElem->isActionIcon()) {
    3149            0 :         const ActionIconType actionType = toActionIcon(dropElem)->actionType();
    3150            0 :         isMeasureAnchorOnly = actionType != ActionIconType::PAGE_LOCK && actionType != ActionIconType::SYSTEM_LOCK;
    3151              :     }
    3152              : 
    3153            0 :     mu::engraving::staff_idx_t staffIdx = muse::nidx;
    3154            0 :     mu::engraving::MeasureBase* mb = score()->pos2measureBase(pos, /*scanMeasuresOnly*/ isMeasureAnchorOnly, &staffIdx, 0, nullptr, 0);
    3155              : 
    3156              :     //! NOTE: Should match Measure::acceptDrop / MeasureBase::acceptDrop
    3157            0 :     switch (dropElem->type()) {
    3158            0 :     case ElementType::VOLTA:
    3159              :     case ElementType::GRADUAL_TEMPO_CHANGE:
    3160              :     case ElementType::KEYSIG:
    3161              :     case ElementType::TIMESIG: {
    3162              :         // If ctrl is pressed, break and target a specific staff
    3163            0 :         if (edd.ed.modifiers & Qt::ControlModifier) {
    3164            0 :             break;
    3165              :         }
    3166              :     }
    3167              :     // fall through
    3168              :     case ElementType::JUMP:
    3169              :     case ElementType::LAYOUT_BREAK:
    3170              :     case ElementType::MARKER:
    3171              :     case ElementType::MEASURE_NUMBER:
    3172              :     case ElementType::VBOX:
    3173              :     case ElementType::TBOX:
    3174              :     case ElementType::FBOX:
    3175              :     case ElementType::HBOX:
    3176              :         // Target all staves
    3177            0 :         staffIdx = 0;
    3178              :     // fall through
    3179            0 :     default: break;
    3180              :     }
    3181              : 
    3182              :     // Apart from STAFF_TYPE_CHANGE, measure anchored action icons are applied to all staves
    3183            0 :     if (dropElem->isActionIcon() && toActionIcon(dropElem)->actionType() != ActionIconType::STAFF_TYPE_CHANGE) {
    3184            0 :         staffIdx = 0;
    3185              :     }
    3186              : 
    3187            0 :     if (mb) {
    3188            0 :         setDropTarget(mb, true);
    3189            0 :         edd.ed.track = staff2track(staffIdx);
    3190              : 
    3191            0 :         RectF mbRect = mb->isMeasure() ? toMeasure(mb)->staffPageBoundingRect(staffIdx) : mb->pageBoundingRect();
    3192            0 :         mbRect.adjust(page->x(), page->y(), page->x(), page->y());
    3193            0 :         edd.ed.pos = mbRect.center();
    3194              : 
    3195            0 :         const bool dropAccepted = mb->acceptDrop(edd.ed);
    3196            0 :         if (dropAccepted) {
    3197            0 :             setAnchorLines({ LineF(pos, mbRect.topLeft()) });
    3198            0 :             setDropRects(dropHighlightRects(dropElem, mb, mbRect, edd.ed.modifiers));
    3199              :         } else {
    3200            0 :             score()->addRefresh(mb->canvasBoundingRect());
    3201            0 :             setDropTarget(nullptr);
    3202              :         }
    3203              : 
    3204            0 :         return dropAccepted;
    3205              :     }
    3206            0 :     dropElem->score()->addRefresh(dropElem->canvasBoundingRect());
    3207            0 :     setDropTarget(nullptr);
    3208            0 :     return false;
    3209              : }
    3210              : 
    3211            0 : std::vector<RectF> NotationInteraction::dropHighlightRects(const EngravingItem* dropElem, const MeasureBase* targetMeasureBase,
    3212              :                                                            const RectF& staffRect, KeyboardModifiers modifiers) const
    3213              : {
    3214            0 :     switch (dropElem->type()) {
    3215            0 :     case ElementType::VBOX:
    3216              :     case ElementType::TBOX:
    3217              :     case ElementType::FBOX:
    3218              :     case ElementType::HBOX:
    3219              :     case ElementType::MEASURE_NUMBER:
    3220              :     case ElementType::JUMP:
    3221              :     case ElementType::MARKER:
    3222              :     case ElementType::LAYOUT_BREAK:
    3223            0 :         return { targetMeasureBase->canvasBoundingRect() };
    3224              : 
    3225            0 :     case ElementType::VOLTA:
    3226              :     case ElementType::GRADUAL_TEMPO_CHANGE:
    3227              :     case ElementType::KEYSIG:
    3228              :     case ElementType::TIMESIG:
    3229            0 :         if (modifiers & ControlModifier) {
    3230            0 :             return { staffRect };
    3231              :         }
    3232            0 :         return { targetMeasureBase->canvasBoundingRect() };
    3233              : 
    3234            0 :     case ElementType::BRACKET:
    3235              :     case ElementType::MEASURE_REPEAT:
    3236              :     case ElementType::MEASURE:
    3237              :     case ElementType::SPACER:
    3238              :     case ElementType::IMAGE:
    3239              :     case ElementType::BAR_LINE:
    3240              :     case ElementType::SYMBOL:
    3241              :     case ElementType::CLEF:
    3242              :     case ElementType::STAFFTYPE_CHANGE:
    3243              :     case ElementType::STRING_TUNINGS:
    3244            0 :         return { staffRect };
    3245              : 
    3246            0 :     case ElementType::ACTION_ICON:
    3247            0 :         switch (toActionIcon(dropElem)->actionType()) {
    3248            0 :         case ActionIconType::VFRAME:
    3249              :         case ActionIconType::HFRAME:
    3250              :         case ActionIconType::TFRAME:
    3251              :         case ActionIconType::FFRAME:
    3252              :         case ActionIconType::MEASURE:
    3253            0 :             return { targetMeasureBase->canvasBoundingRect() };
    3254              : 
    3255            0 :         case ActionIconType::STAFF_TYPE_CHANGE:
    3256            0 :             return { staffRect };
    3257              : 
    3258            0 :         case ActionIconType::SYSTEM_LOCK: {
    3259            0 :             const System* sys = targetMeasureBase->system();
    3260            0 :             const MeasureBase* first = sys ? sys->first() : nullptr;
    3261            0 :             const PointF topLeft = first ? first->canvasBoundingRect().topLeft() : PointF(0.0, 0.0);
    3262            0 :             return { RectF(topLeft, targetMeasureBase->canvasBoundingRect().bottomRight()) };
    3263              :         }
    3264              : 
    3265            0 :         case ActionIconType::PAGE_LOCK: {
    3266            0 :             std::vector<RectF> dropRects;
    3267            0 :             for (System* sys : targetMeasureBase->page()->systems()) {
    3268            0 :                 const bool lastSelectedSys = sys == targetMeasureBase->system();
    3269            0 :                 const MeasureBase* first = sys ? sys->first() : nullptr;
    3270            0 :                 const MeasureBase* last = sys ? sys->last() : nullptr;
    3271            0 :                 if (lastSelectedSys) {
    3272            0 :                     last = targetMeasureBase;
    3273              :                 }
    3274            0 :                 if (!first || !last) {
    3275            0 :                     continue;
    3276              :                 }
    3277            0 :                 dropRects.push_back(RectF(first->canvasBoundingRect().topLeft(), last->canvasBoundingRect().bottomRight()));
    3278              : 
    3279            0 :                 if (lastSelectedSys) {
    3280            0 :                     break;
    3281              :                 }
    3282              :             }
    3283            0 :             return dropRects;
    3284            0 :         }
    3285              : 
    3286            0 :         default:
    3287            0 :             return {};
    3288              :         }
    3289              : 
    3290            0 :     default:
    3291            0 :         return {};
    3292              :     }
    3293              : }
    3294              : 
    3295              : //! NOTE Copied from ScoreView::dragTimeAnchorElement
    3296            0 : bool NotationInteraction::prepareDropTimeAnchorElement(const PointF& pos)
    3297              : {
    3298            0 :     IF_ASSERT_FAILED(m_dropData.elementDropData.has_value()) {
    3299            0 :         return false;
    3300              :     }
    3301              : 
    3302            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    3303              : 
    3304            0 :     mu::engraving::staff_idx_t staffIdx = 0;
    3305            0 :     mu::engraving::Segment* seg = nullptr;
    3306            0 :     mu::engraving::Measure* measure = score()->pos2measure(pos, &staffIdx, /*pitch*/ 0, &seg, /*offset*/ 0);
    3307            0 :     mu::engraving::track_idx_t track = staff2track(staffIdx);
    3308              : 
    3309            0 :     if (measure && seg->element(track)) {
    3310            0 :         mu::engraving::System* s = measure->system();
    3311            0 :         qreal y = s->staff(staffIdx)->y() + s->pos().y() + s->page()->pos().y();
    3312            0 :         PointF anchor(seg->canvasBoundingRect().x(), y);
    3313            0 :         setAnchorLines({ LineF(pos, anchor) });
    3314            0 :         edd.ed.dropElement->score()->addRefresh(edd.ed.dropElement->canvasBoundingRect());
    3315            0 :         edd.ed.dropElement->setTrack(track);
    3316            0 :         edd.ed.dropElement->score()->addRefresh(edd.ed.dropElement->canvasBoundingRect());
    3317            0 :         notifyAboutDragChanged();
    3318            0 :         return true;
    3319              :     }
    3320              : 
    3321            0 :     edd.ed.dropElement->score()->addRefresh(edd.ed.dropElement->canvasBoundingRect());
    3322            0 :     setDropTarget(nullptr);
    3323              : 
    3324            0 :     return false;
    3325              : }
    3326              : 
    3327              : //! NOTE Copied from ScoreView::setDropTarget
    3328            0 : void NotationInteraction::setDropTarget(EngravingItem* item, bool notify)
    3329              : {
    3330            0 :     if (!m_dropData.elementDropData.has_value()) {
    3331            0 :         return;
    3332              :     }
    3333              : 
    3334            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    3335              : 
    3336            0 :     if (edd.dropTarget != item) {
    3337            0 :         if (edd.dropTarget) {
    3338            0 :             edd.dropTarget->setDropTarget(false);
    3339            0 :             edd.ed.track = muse::nidx;
    3340            0 :             edd.dropTarget = nullptr;
    3341              :         }
    3342              : 
    3343            0 :         edd.dropTarget = item;
    3344            0 :         if (edd.dropTarget) {
    3345            0 :             edd.dropTarget->setDropTarget(true);
    3346            0 :             edd.ed.track = edd.dropTarget->track();
    3347              :         }
    3348              :     }
    3349              : 
    3350            0 :     resetAnchorLines();
    3351              : 
    3352            0 :     if (!edd.dropRects.empty()) {
    3353            0 :         edd.dropRects.clear();
    3354              :     }
    3355              : 
    3356            0 :     if (notify) {
    3357            0 :         notifyAboutDragChanged();
    3358              :     }
    3359              : }
    3360              : 
    3361              : //! NOTE: Copied from ScoreView::setDropRectangles
    3362            0 : void NotationInteraction::setDropRects(const std::vector<RectF>& rects)
    3363              : {
    3364            0 :     if (!m_dropData.elementDropData.has_value()) {
    3365            0 :         return;
    3366              :     }
    3367              : 
    3368            0 :     ElementDropData& edd = m_dropData.elementDropData.value();
    3369              : 
    3370            0 :     if (edd.dropRects == rects) {
    3371            0 :         return;
    3372              :     }
    3373              : 
    3374            0 :     edd.dropRects = rects;
    3375              : 
    3376            0 :     for (const RectF& rect : rects) {
    3377            0 :         if (rect.isValid()) {
    3378            0 :             score()->addRefresh(rect);
    3379              :         }
    3380              :     }
    3381              : 
    3382            0 :     if (edd.dropTarget) {
    3383            0 :         edd.dropTarget->setDropTarget(false);
    3384            0 :         score()->addRefresh(edd.dropTarget->canvasBoundingRect());
    3385            0 :     } else if (!m_anchorLines.empty()) {
    3386            0 :         RectF rf;
    3387            0 :         rf.setTopLeft(m_anchorLines.front().p1());
    3388            0 :         rf.setBottomRight(m_anchorLines.front().p2());
    3389            0 :         score()->addRefresh(rf.normalized());
    3390            0 :         resetAnchorLines();
    3391              :     }
    3392              : 
    3393            0 :     notifyAboutDragChanged();
    3394              : }
    3395              : 
    3396            0 : void NotationInteraction::resetDropData()
    3397              : {
    3398            0 :     if (m_dropData.elementDropData.has_value()) {
    3399            0 :         delete m_dropData.elementDropData->ed.dropElement;
    3400              :     }
    3401              : 
    3402            0 :     m_dropData = {};
    3403            0 : }
    3404              : 
    3405            0 : void NotationInteraction::doFinishAddFretboardDiagram()
    3406              : {
    3407            0 :     EngravingItem* selectedElement = score()->selection().element();
    3408            0 :     if (!selectedElement || !selectedElement->isFretDiagram()) {
    3409            0 :         return;
    3410              :     }
    3411              : 
    3412            0 :     FretDiagram* fretDiagram = toFretDiagram(selectedElement);
    3413            0 :     if (!fretDiagram->harmonyPlainText().empty()) {
    3414            0 :         return;
    3415              :     }
    3416              : 
    3417              :     //! first we need to apply changes
    3418            0 :     apply();
    3419              : 
    3420              :     //! then add harmony
    3421              :     // NOTE: needs to be done on a separate transaction because when text editing ends, if text is empty
    3422              :     // the last action is rolled back to remove the text, this ensures that only the text insertion is
    3423              :     // rolled back (and not the diagram insertion).
    3424            0 :     engraving::TextBase* text = nullptr;
    3425            0 :     transaction(TranslatableString("undoableAction", "Add chord symbol to fretboard diagram"), [&, this](engraving::Transaction&) {
    3426            0 :         text = score()->addText(TextStyleType::HARMONY_A, fretDiagram);
    3427            0 :     });
    3428              : 
    3429            0 :     m_selection->select({ text }, SelectType::SINGLE);
    3430            0 :     startEditElement(text);
    3431              : }
    3432              : 
    3433            0 : void NotationInteraction::setAnchorLines(const std::vector<LineF>& anchorList)
    3434              : {
    3435            0 :     m_anchorLines = anchorList;
    3436            0 : }
    3437              : 
    3438            0 : void NotationInteraction::resetAnchorLines()
    3439              : {
    3440            0 :     m_anchorLines.clear();
    3441            0 : }
    3442              : 
    3443            0 : double NotationInteraction::getHRaster() const
    3444              : {
    3445            0 :     double hRaster = mu::engraving::MScore::hRaster();
    3446              : 
    3447            0 :     if (m_editData.element->isBarLine()) {
    3448              :         // Always adjust by .25sp
    3449            0 :         hRaster = 4.0;
    3450            0 :     } else if (m_editData.element->isLyricsLineSegment()) {
    3451              :         // Ignore grid
    3452            0 :         hRaster = 0.0;
    3453              :     }
    3454              : 
    3455            0 :     return hRaster;
    3456              : }
    3457              : 
    3458            0 : double NotationInteraction::getVRaster() const
    3459              : {
    3460            0 :     double vRaster = mu::engraving::MScore::vRaster();
    3461              : 
    3462            0 :     if (m_editData.element->isBarLine()) {
    3463              :         // Always adjust by .25sp
    3464            0 :         vRaster = 4.0;
    3465            0 :     } else if (m_editData.element->isLyricsLineSegment()) {
    3466              :         // Ignore grid
    3467            0 :         vRaster = 0.0;
    3468              :     }
    3469              : 
    3470            0 :     return vRaster;
    3471              : }
    3472              : 
    3473            0 : double NotationInteraction::currentScaling(Painter* painter) const
    3474              : {
    3475            0 :     qreal guiScaling = contextConfiguration()->guiScaling();
    3476            0 :     return painter->worldTransform().m11() / guiScaling;
    3477              : }
    3478              : 
    3479            0 : std::vector<NotationInteraction::ShadowNoteParams> NotationInteraction::previewNotes() const
    3480              : {
    3481            0 :     std::vector<ShadowNoteParams> result;
    3482              : 
    3483            0 :     const InputState& is = score()->inputState();
    3484            0 :     if (!is.isValid()) {
    3485            0 :         return result;
    3486              :     }
    3487              : 
    3488            0 :     Segment* segment = is.segment();
    3489            0 :     const staff_idx_t staffIdx = is.staffIdx();
    3490            0 :     const Staff* staff = score()->staff(staffIdx);
    3491            0 :     const System* system = segment->system();
    3492            0 :     const SysStaff* sysStaff = system ? system->staff(staffIdx) : nullptr;
    3493            0 :     const Measure* measure = segment->measure();
    3494              : 
    3495            0 :     if (!staff || !sysStaff || !measure) {
    3496            0 :         return result;
    3497              :     }
    3498              : 
    3499            0 :     const Fraction tick = is.tick();
    3500            0 :     const PointF measurePos = measure->canvasPos();
    3501            0 :     const double lineDist = staff->staffType(tick)->lineDistance().val()
    3502            0 :                             * (staff->isTabStaff(tick) ? 1 : .5)
    3503            0 :                             * staff->staffMag(tick)
    3504            0 :                             * score()->style().spatium();
    3505              : 
    3506            0 :     NoteValList nvals;
    3507            0 :     if (is.rest()) {
    3508            0 :         nvals.push_back(NoteVal());
    3509              :     } else {
    3510            0 :         nvals = is.notes();
    3511              :     }
    3512              : 
    3513            0 :     ShadowNoteParams params;
    3514            0 :     params.duration = is.rest() ? is.duration() : TDuration();
    3515            0 :     params.position.segment = segment;
    3516            0 :     params.position.staffIdx = staffIdx;
    3517              : 
    3518            0 :     result.reserve(nvals.size());
    3519              : 
    3520            0 :     for (const NoteVal& nval : nvals) {
    3521            0 :         const int line = mu::engraving::noteValToLine(nval, staff, tick);
    3522            0 :         const double y = sysStaff->y() + line * lineDist;
    3523              : 
    3524            0 :         params.accidentalType = accidentalType(is, nval, line);
    3525            0 :         params.position.line = line;
    3526              : 
    3527            0 :         if (is.beyondScore()) {
    3528            0 :             params.position.pos = PointF(measure->ldata()->bbox().width() + score()->style().spatium(), y) + measurePos;
    3529              :         } else {
    3530            0 :             params.position.pos = PointF(segment->x(), y) + measurePos;
    3531              :         }
    3532              : 
    3533            0 :         result.push_back(params);
    3534              :     }
    3535              : 
    3536            0 :     std::sort(result.begin(), result.end(), [](const ShadowNoteParams& p1, const ShadowNoteParams& p2) {
    3537            0 :         return p1.position.line < p2.position.line;
    3538              :     });
    3539              : 
    3540            0 :     return result;
    3541            0 : }
    3542              : 
    3543            0 : bool NotationInteraction::shouldDrawInputPreview() const
    3544              : {
    3545            0 :     return m_noteInput->isNoteInputMode() && m_noteInput->usingNoteInputMethod(NoteInputMethod::BY_DURATION);
    3546              : }
    3547              : 
    3548            0 : void NotationInteraction::drawInputPreview(Painter* painter, const engraving::rendering::PaintOptions& opt)
    3549              : {
    3550            0 :     std::vector<ShadowNoteParams> paramsList = previewNotes();
    3551            0 :     if (paramsList.empty()) {
    3552            0 :         return;
    3553              :     }
    3554              : 
    3555            0 :     std::vector<ShadowNote*> previewList;
    3556            0 :     previewList.reserve(paramsList.size());
    3557              : 
    3558            0 :     const InputState& is = score()->inputState();
    3559              : 
    3560            0 :     for (ShadowNoteParams& params : paramsList) {
    3561            0 :         ShadowNote* preview = new ShadowNote(score());
    3562            0 :         doShowShadowNote(*preview, params);
    3563            0 :         previewList.push_back(preview);
    3564              :     }
    3565              : 
    3566            0 :     const bool isUp = !is.rest() && previewList.front()->computeUp();
    3567            0 :     bool isLeft = isUp;
    3568            0 :     int prevLine = INT_MAX;
    3569              : 
    3570            0 :     auto correctNotePositionIfNeed = [&](ShadowNote* note) {
    3571            0 :         if (paramsList.size() == 1) {
    3572            0 :             return;
    3573              :         }
    3574              : 
    3575            0 :         const bool conflict = std::abs(prevLine - note->lineIndex()) < 2;
    3576            0 :         prevLine = note->lineIndex();
    3577              : 
    3578            0 :         if (conflict || (isUp != isLeft)) {
    3579            0 :             isLeft = !isLeft;
    3580              :         }
    3581              : 
    3582            0 :         if (isUp == isLeft) {
    3583            0 :             return;
    3584              :         }
    3585              : 
    3586            0 :         const RectF noteheadBbox = note->symBbox(note->noteheadSymbol());
    3587              : 
    3588            0 :         if (isLeft) {
    3589            0 :             note->move(PointF(-noteheadBbox.width(), 0.));
    3590              :         } else {
    3591            0 :             note->move(PointF(noteheadBbox.width(), 0.));
    3592              :         }
    3593            0 :     };
    3594              : 
    3595            0 :     if (isUp) {
    3596            0 :         for (auto it = previewList.rbegin(); it != previewList.rend(); ++it) {
    3597            0 :             correctNotePositionIfNeed(*it);
    3598            0 :             score()->renderer()->drawItem(*it, painter, opt);
    3599              :         }
    3600              :     } else {
    3601            0 :         for (auto it = previewList.begin(); it != previewList.end(); ++it) {
    3602            0 :             correctNotePositionIfNeed(*it);
    3603            0 :             score()->renderer()->drawItem(*it, painter, opt);
    3604              :         }
    3605              :     }
    3606              : 
    3607            0 :     DeleteAll(previewList);
    3608            0 : }
    3609              : 
    3610            0 : void NotationInteraction::drawAnchorLines(Painter* painter)
    3611              : {
    3612            0 :     if (m_anchorLines.empty()) {
    3613            0 :         return;
    3614              :     }
    3615              : 
    3616            0 :     const auto dropAnchorColor = score()->configuration()->formattingColor();
    3617            0 :     Pen pen(dropAnchorColor, 2.0 / currentScaling(painter), PenStyle::DotLine);
    3618              : 
    3619            0 :     for (const LineF& anchor : m_anchorLines) {
    3620            0 :         painter->setPen(pen);
    3621            0 :         painter->drawLine(anchor);
    3622              : 
    3623            0 :         qreal d = 4.0 / currentScaling(painter);
    3624            0 :         RectF rect(-d, -d, 2 * d, 2 * d);
    3625              : 
    3626            0 :         painter->setBrush(Brush(dropAnchorColor));
    3627            0 :         painter->setNoPen();
    3628            0 :         rect.moveCenter(anchor.p1());
    3629            0 :         painter->drawEllipse(rect);
    3630            0 :         rect.moveCenter(anchor.p2());
    3631            0 :         painter->drawEllipse(rect);
    3632              :     }
    3633            0 : }
    3634              : 
    3635            0 : void NotationInteraction::drawTextEditMode(muse::draw::Painter* painter, const engraving::rendering::PaintOptions& opt)
    3636              : {
    3637            0 :     if (!isTextEditingStarted()) {
    3638            0 :         return;
    3639              :     }
    3640              : 
    3641            0 :     editModeRenderer()->drawItem(m_editData.element, painter, m_editData, currentScaling(painter), opt);
    3642              : }
    3643              : 
    3644            0 : void NotationInteraction::drawSelectionRange(muse::draw::Painter* painter)
    3645              : {
    3646              :     using namespace muse::draw;
    3647            0 :     if (!m_selection->isRange()) {
    3648            0 :         return;
    3649              :     }
    3650              : 
    3651            0 :     painter->setBrush(BrushStyle::NoBrush);
    3652              : 
    3653            0 :     Color selectionColor = configuration()->selectionColor();
    3654            0 :     double penWidth = 3.0 / currentScaling(painter);
    3655            0 :     double minPenWidth = 0.20 * m_selection->range()->measureBaseRange().startMeasureBase->spatium();
    3656            0 :     penWidth = std::max(penWidth, minPenWidth);
    3657              : 
    3658            0 :     Pen pen;
    3659            0 :     pen.setColor(selectionColor);
    3660            0 :     pen.setWidthF(penWidth);
    3661            0 :     pen.setStyle(PenStyle::SolidLine);
    3662            0 :     painter->setPen(pen);
    3663              : 
    3664            0 :     std::vector<RectF> rangeArea = m_selection->range()->boundingArea();
    3665            0 :     for (const RectF& rect: rangeArea) {
    3666            0 :         PainterPath path;
    3667            0 :         path.addRoundedRect(rect, 4, 4);
    3668              : 
    3669            0 :         Color fillColor = selectionColor;
    3670            0 :         fillColor.setAlpha(10);
    3671            0 :         painter->fillPath(path, fillColor);
    3672            0 :         painter->drawPath(path);
    3673            0 :     }
    3674            0 : }
    3675              : 
    3676            0 : void NotationInteraction::drawGripPoints(muse::draw::Painter* painter, const engraving::rendering::PaintOptions& opt)
    3677              : {
    3678            0 :     if (isDragStarted() && !isGripEditStarted()) {
    3679            0 :         return;
    3680              :     }
    3681              : 
    3682            0 :     mu::engraving::EngravingItem* editedElement = m_editData.element;
    3683            0 :     if (!editedElement) {
    3684            0 :         EngravingItem* selectedElement = m_selection->element();
    3685            0 :         if (selectedElement && selectedElement->isDynamic()) {
    3686            0 :             editedElement = selectedElement;
    3687              :         }
    3688              :     }
    3689              : 
    3690            0 :     if (editedElement && editedElement->isDynamic()) {
    3691            0 :         toDynamic(editedElement)->findAdjacentHairpins();
    3692              :     }
    3693              : 
    3694            0 :     int gripsCount = editedElement ? editedElement->gripsCount() : 0;
    3695              : 
    3696            0 :     if (gripsCount == 0) {
    3697            0 :         return;
    3698              :     }
    3699              : 
    3700            0 :     m_editData.grips = gripsCount;
    3701            0 :     m_editData.grip.resize(m_editData.grips);
    3702              : 
    3703            0 :     constexpr qreal DEFAULT_GRIP_SIZE = 8;
    3704            0 :     qreal scaling = currentScaling(painter);
    3705            0 :     qreal gripSize = DEFAULT_GRIP_SIZE / scaling;
    3706            0 :     RectF newRect(-gripSize / 2, -gripSize / 2, gripSize, gripSize);
    3707              : 
    3708            0 :     const EngravingItem* page = editedElement->findAncestor(ElementType::PAGE);
    3709            0 :     PointF pageOffset = page ? page->pos() : editedElement->pos();
    3710              : 
    3711            0 :     for (RectF& gripRect: m_editData.grip) {
    3712            0 :         gripRect = newRect.translated(pageOffset);
    3713              :     }
    3714              : 
    3715            0 :     editedElement->updateGrips(m_editData);
    3716            0 :     editModeRenderer()->drawItem(editedElement, painter, m_editData, scaling, opt);
    3717              : }
    3718              : 
    3719            0 : void NotationInteraction::drawLasso(muse::draw::Painter* painter, const engraving::rendering::PaintOptions& opt)
    3720              : {
    3721            0 :     if (!m_lasso || m_lasso->isEmpty()) {
    3722            0 :         return;
    3723              :     }
    3724              : 
    3725            0 :     score()->renderer()->drawItem(m_lasso, painter, opt);
    3726              : }
    3727              : 
    3728            0 : void NotationInteraction::drawDrop(muse::draw::Painter* painter)
    3729              : {
    3730            0 :     if (m_dropData.elementDropData.has_value()) {
    3731            0 :         const ElementDropData& edd = m_dropData.elementDropData.value();
    3732              : 
    3733            0 :         for (const RectF& rect : edd.dropRects) {
    3734            0 :             if (rect.isValid()) {
    3735            0 :                 painter->fillRect(rect, configuration()->dropRectColor());
    3736              :             }
    3737              :         }
    3738              :     }
    3739              : 
    3740            0 :     if (m_dropData.rangeDropData.has_value()) {
    3741            0 :         const RangeDropData& rdd = m_dropData.rangeDropData.value();
    3742              : 
    3743            0 :         Color selectionColor = configuration()->selectionColor();
    3744            0 :         double penWidth = 3.0 / currentScaling(painter);
    3745            0 :         double minPenWidth = 0.20 * score()->style().spatium();
    3746            0 :         penWidth = std::max(penWidth, minPenWidth);
    3747              : 
    3748            0 :         Pen pen;
    3749            0 :         pen.setColor(selectionColor);
    3750            0 :         pen.setWidthF(penWidth);
    3751            0 :         pen.setStyle(PenStyle::SolidLine);
    3752            0 :         painter->setPen(pen);
    3753              : 
    3754            0 :         for (const RectF& rect: rdd.dropRects) {
    3755            0 :             PainterPath path;
    3756            0 :             path.addRoundedRect(rect, 4, 4);
    3757              : 
    3758            0 :             Color fillColor = selectionColor;
    3759            0 :             fillColor.setAlpha(10);
    3760            0 :             painter->fillPath(path, fillColor);
    3761            0 :             painter->drawPath(path);
    3762            0 :         }
    3763            0 :     }
    3764            0 : }
    3765              : 
    3766            0 : ChordRest* activeCr(mu::engraving::Score* score)
    3767              : {
    3768            0 :     ChordRest* cr = score->selection().activeCR();
    3769            0 :     if (!cr) {
    3770            0 :         cr = score->selection().lastChordRest();
    3771            0 :         if (!cr && score->noteEntryMode()) {
    3772            0 :             cr = score->inputState().cr();
    3773              :         }
    3774              :     }
    3775            0 :     return cr;
    3776              : }
    3777              : 
    3778            0 : void NotationInteraction::expandSelection(ExpandSelectionMode mode)
    3779              : {
    3780            0 :     ChordRest* cr = activeCr(score());
    3781            0 :     if (!cr) {
    3782            0 :         return;
    3783              :     }
    3784            0 :     ChordRest* el = 0;
    3785            0 :     switch (mode) {
    3786            0 :     case ExpandSelectionMode::BeginSystem: {
    3787            0 :         Measure* measure = cr->segment()->measure()->system()->firstMeasure();
    3788            0 :         if (measure) {
    3789            0 :             el = measure->first()->nextChordRest(cr->track());
    3790              :         }
    3791            0 :         break;
    3792              :     }
    3793            0 :     case ExpandSelectionMode::EndSystem: {
    3794            0 :         Measure* measure = cr->segment()->measure()->system()->lastMeasure();
    3795            0 :         if (measure) {
    3796            0 :             el = measure->last()->nextChordRest(cr->track(), true);
    3797              :         }
    3798            0 :         break;
    3799              :     }
    3800            0 :     case ExpandSelectionMode::BeginScore: {
    3801            0 :         Measure* measure = score()->firstMeasureMM();
    3802            0 :         if (measure) {
    3803            0 :             el = measure->first()->nextChordRest(cr->track());
    3804              :         }
    3805            0 :         break;
    3806              :     }
    3807            0 :     case ExpandSelectionMode::EndScore: {
    3808            0 :         Measure* measure = score()->lastMeasureMM();
    3809            0 :         if (measure) {
    3810            0 :             el = measure->last()->nextChordRest(cr->track(), true);
    3811              :         }
    3812            0 :         break;
    3813              :     }
    3814              :     }
    3815              : 
    3816            0 :     if (el) {
    3817            0 :         select({ el }, SelectType::RANGE, el->staffIdx());
    3818              :     }
    3819              : }
    3820              : 
    3821            0 : bool NotationInteraction::moveSelectionAvailable(MoveSelectionType type) const
    3822              : {
    3823            0 :     if (type != MoveSelectionType::EngravingItem) {
    3824            0 :         return !isEditingElement();
    3825              :     }
    3826              : 
    3827            0 :     EngravingItem* el = score()->selection().element();
    3828              :     const std::set<ElementType> allowedTextEditTypes = {
    3829              :         ElementType::STAFF_TEXT,
    3830              :         ElementType::SYSTEM_TEXT,
    3831              :         ElementType::EXPRESSION,
    3832              :         ElementType::REHEARSAL_MARK
    3833            0 :     };
    3834            0 :     if (isTextEditingStarted() && el && allowedTextEditTypes.find(el->type()) != allowedTextEditTypes.end()) {
    3835            0 :         return true;
    3836              :     }
    3837              : 
    3838            0 :     if (isGripEditStarted()) {
    3839            0 :         return true;
    3840              :     }
    3841              : 
    3842            0 :     return m_editData.element && m_editData.element->isTextBase() ? !isTextEditingStarted() : !isEditingElement();
    3843            0 : }
    3844              : 
    3845            0 : void NotationInteraction::moveSelectionDeprecated(MoveDirection d, MoveSelectionType type)
    3846              : {
    3847            0 :     m_selection->moveSelection(d, type);
    3848            0 : }
    3849              : 
    3850            0 : static ChordRest* asChordRest(EngravingItem* e)
    3851              : {
    3852            0 :     if (e && e->isNote()) {
    3853            0 :         return toNote(e)->chord();
    3854            0 :     } else if (e && e->isChordRest()) {
    3855            0 :         return toChordRest(e);
    3856              :     }
    3857            0 :     return nullptr;
    3858              : }
    3859              : 
    3860            0 : void NotationInteraction::moveChordRestToStaff(MoveDirection dir)
    3861              : {
    3862            0 :     startEdit(TranslatableString("undoableAction", "Move chord/rest to staff"));
    3863              : 
    3864            0 :     engraving::Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
    3865            0 :     for (EngravingItem* e: score()->selection().uniqueElements()) {
    3866            0 :         ChordRest* cr = asChordRest(e);
    3867            0 :         if (cr != nullptr) {
    3868            0 :             if (dir == MoveDirection::Up) {
    3869            0 :                 EditCrossStaff::moveUp(tx, score(), cr);
    3870            0 :             } else if (dir == MoveDirection::Down) {
    3871            0 :                 EditCrossStaff::moveDown(tx, score(), cr);
    3872              :             }
    3873              :         }
    3874            0 :     }
    3875              : 
    3876            0 :     apply();
    3877            0 : }
    3878              : 
    3879            0 : void NotationInteraction::swapChordRest(MoveDirection direction)
    3880              : {
    3881            0 :     ChordRest* cr = asChordRest(score()->getSelectedElement());
    3882            0 :     if (!cr) {
    3883            0 :         return;
    3884              :     }
    3885            0 :     QList<ChordRest*> crl;
    3886            0 :     if (cr->links()) {
    3887            0 :         for (auto* l : *cr->links()) {
    3888            0 :             crl.append(toChordRest(l));
    3889              :         }
    3890              :     } else {
    3891            0 :         crl.append(cr);
    3892              :     }
    3893            0 :     startEdit(TranslatableString("undoableAction", "Move chord/rest"));
    3894            0 :     for (ChordRest* cr1 : crl) {
    3895            0 :         if (cr1->isRest()) {
    3896            0 :             Measure* m = toRest(cr1)->measure();
    3897            0 :             if (m && m->isMMRest()) {
    3898            0 :                 break;
    3899              :             }
    3900              :         }
    3901              :         ChordRest* cr2;
    3902              :         // ensures cr1 is the left chord, useful in SwapCR::flip()
    3903            0 :         if (direction == MoveDirection::Left) {
    3904            0 :             cr2 = cr1;
    3905            0 :             cr1 = engraving::Navigation::prevChordRest(cr2);
    3906              :         } else {
    3907            0 :             cr2 = engraving::Navigation::nextChordRest(cr1);
    3908              :         }
    3909            0 :         if (cr1 && cr2 && cr1->measure() == cr2->measure() && !cr1->tuplet() && !cr2->tuplet()
    3910            0 :             && cr1->durationType() == cr2->durationType() && cr1->ticks() == cr2->ticks()
    3911              :             // if two chords belong to different two-note tremolos, abort
    3912            0 :             && !(cr1->isChord() && toChord(cr1)->tremoloTwoChord()
    3913            0 :                  && cr2->isChord() && toChord(cr2)->tremoloTwoChord()
    3914            0 :                  && toChord(cr1)->tremoloTwoChord() != toChord(cr2)->tremoloTwoChord())) {
    3915            0 :             score()->undo(new mu::engraving::SwapCR(cr1, cr2));
    3916              :         }
    3917              :     }
    3918            0 :     apply();
    3919            0 : }
    3920              : 
    3921            0 : void NotationInteraction::toggleSnapToPrevious()
    3922              : {
    3923            0 :     bool newSnapValue = false;
    3924              : 
    3925              :     // Collect items to toggle...
    3926            0 :     std::unordered_set<Hairpin*> hairpins;
    3927            0 :     for (EngravingItem* e : score()->selection().elements()) {
    3928            0 :         if (e->isHairpinSegment()) {
    3929            0 :             Hairpin* h = toHairpinSegment(e)->hairpin();
    3930              :             // If any item in the selection has a false snapping value, then we should
    3931              :             // toggle all to true (handles mixed/indeterminate state)
    3932            0 :             if (!h->snapToItemBefore() && !newSnapValue) {
    3933            0 :                 newSnapValue = true;
    3934              :             }
    3935            0 :             hairpins.emplace(h);
    3936              :         }
    3937              :     }
    3938            0 :     if (hairpins.empty()) {
    3939            0 :         return;
    3940              :     }
    3941              : 
    3942              :     // Do toggle...
    3943            0 :     startEdit(TranslatableString("undoableAction", "Toggle snap to previous"));
    3944            0 :     for (Hairpin* h : hairpins) {
    3945            0 :         if (h->snapToItemBefore() == newSnapValue) {
    3946            0 :             continue;
    3947              :         }
    3948            0 :         h->undoChangeProperty(Pid::SNAP_BEFORE, PropertyValue(newSnapValue));
    3949              :     }
    3950            0 :     apply();
    3951            0 : }
    3952              : 
    3953            0 : void NotationInteraction::toggleSnapToNext()
    3954              : {
    3955            0 :     bool newSnapValue = false;
    3956              : 
    3957              :     // Collect items to toggle...
    3958            0 :     std::unordered_set<Hairpin*> hairpins;
    3959            0 :     std::unordered_set<GradualTempoChange*> gradualTempoChanges;
    3960            0 :     for (EngravingItem* e : score()->selection().elements()) {
    3961            0 :         if (e->isHairpinSegment()) {
    3962            0 :             Hairpin* h = toHairpinSegment(e)->hairpin();
    3963              :             // If any item in the selection has a false snapping value, then we should
    3964              :             // toggle all to true (handles mixed/indeterminate state)
    3965            0 :             if (!h->snapToItemAfter() && !newSnapValue) {
    3966            0 :                 newSnapValue = true;
    3967              :             }
    3968            0 :             hairpins.emplace(h);
    3969              :         }
    3970            0 :         if (e->isGradualTempoChangeSegment()) {
    3971            0 :             GradualTempoChange* gtc = toGradualTempoChangeSegment(e)->tempoChange();
    3972              :             // Same here (see above)
    3973            0 :             if (!gtc->snapToItemAfter() && !newSnapValue) {
    3974            0 :                 newSnapValue = true;
    3975              :             }
    3976            0 :             gradualTempoChanges.emplace(gtc);
    3977              :         }
    3978              :     }
    3979            0 :     if (hairpins.empty() && gradualTempoChanges.empty()) {
    3980            0 :         return;
    3981              :     }
    3982              : 
    3983              :     // Do toggle...
    3984            0 :     startEdit(TranslatableString("undoableAction", "Toggle snap to next"));
    3985            0 :     for (Hairpin* h : hairpins) {
    3986            0 :         if (h->snapToItemAfter() == newSnapValue) {
    3987            0 :             continue;
    3988              :         }
    3989            0 :         h->undoChangeProperty(Pid::SNAP_AFTER, PropertyValue(newSnapValue));
    3990              :     }
    3991            0 :     for (GradualTempoChange* gtc : gradualTempoChanges) {
    3992            0 :         if (gtc->snapToItemAfter() == newSnapValue) {
    3993            0 :             continue;
    3994              :         }
    3995            0 :         gtc->undoChangeProperty(Pid::SNAP_AFTER, PropertyValue(newSnapValue));
    3996              :     }
    3997            0 :     apply();
    3998            0 : }
    3999              : 
    4000            0 : inline mu::engraving::DirectionV toDirection(MoveDirection d)
    4001              : {
    4002            0 :     return d == MoveDirection::Up ? mu::engraving::DirectionV::UP : mu::engraving::DirectionV::DOWN;
    4003              : }
    4004              : 
    4005            0 : void NotationInteraction::movePitch(MoveDirection d, PitchMode mode)
    4006              : {
    4007            0 :     IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
    4008            0 :         return;
    4009              :     }
    4010            0 :     EngravingItem* selected = score()->selection().element();
    4011            0 :     if (selected && selected->isRest()) {
    4012            0 :         if (noteInput()->state().staffGroup() == mu::engraving::StaffGroup::TAB) {
    4013              :             // The rest won't be visible - don't try to move it...
    4014            0 :             return;
    4015              :         }
    4016            0 :         startEdit(TranslatableString("undoableAction", "Change vertical position"));
    4017            0 :         score()->cmdMoveRest(toRest(selected), toDirection(d));
    4018              :     } else {
    4019            0 :         startEdit(TranslatableString("undoableAction", "Change pitch"));
    4020            0 :         EditNote::upDown(score(), MoveDirection::Up == d, mode);
    4021              :     }
    4022              : 
    4023            0 :     apply();
    4024              : }
    4025              : 
    4026            0 : void NotationInteraction::moveLyrics(MoveDirection d)
    4027              : {
    4028            0 :     EngravingItem* el = score()->selection().element();
    4029            0 :     IF_ASSERT_FAILED(el && el->isLyrics()) {
    4030            0 :         return;
    4031              :     }
    4032            0 :     startEdit(TranslatableString("undoableAction", "Move lyrics"));
    4033            0 :     score()->cmdMoveLyrics(toLyrics(el), toDirection(d));
    4034            0 :     apply();
    4035              : }
    4036              : 
    4037            0 : void NotationInteraction::nudge(MoveDirection d, bool quickly)
    4038              : {
    4039            0 :     EngravingItem* el = score()->selection().element();
    4040            0 :     IF_ASSERT_FAILED(el && (el->isTextBase() || el->isArticulationFamily())) {
    4041            0 :         return;
    4042              :     }
    4043              : 
    4044            0 :     startEdit(TranslatableString("undoableAction", "Nudge element"));
    4045              : 
    4046            0 :     qreal step = quickly ? mu::engraving::MScore::nudgeStep10 : mu::engraving::MScore::nudgeStep;
    4047            0 :     step = step * el->spatium();
    4048              : 
    4049            0 :     switch (d) {
    4050            0 :     case MoveDirection::Undefined: {
    4051            0 :         IF_ASSERT_FAILED(d != MoveDirection::Undefined) {
    4052            0 :             return;
    4053              :         }
    4054            0 :     } break;
    4055            0 :     case MoveDirection::Left:
    4056            0 :         el->undoChangeProperty(mu::engraving::Pid::OFFSET, el->offset() - PointF(step, 0.0), mu::engraving::PropertyFlags::UNSTYLED);
    4057            0 :         break;
    4058            0 :     case MoveDirection::Right:
    4059            0 :         el->undoChangeProperty(mu::engraving::Pid::OFFSET, el->offset() + PointF(step, 0.0), mu::engraving::PropertyFlags::UNSTYLED);
    4060            0 :         break;
    4061            0 :     case MoveDirection::Up:
    4062            0 :         el->undoChangeProperty(mu::engraving::Pid::OFFSET, el->offset() - PointF(0.0, step), mu::engraving::PropertyFlags::UNSTYLED);
    4063            0 :         break;
    4064            0 :     case MoveDirection::Down:
    4065            0 :         el->undoChangeProperty(mu::engraving::Pid::OFFSET, el->offset() + PointF(0.0, step), mu::engraving::PropertyFlags::UNSTYLED);
    4066            0 :         break;
    4067              :     }
    4068              : 
    4069            0 :     apply();
    4070              : 
    4071            0 :     notifyAboutDragChanged();
    4072              : }
    4073              : 
    4074            0 : void NotationInteraction::nudgeAnchors(MoveDirection d)
    4075              : {
    4076            0 :     IF_ASSERT_FAILED(m_editData.element) {
    4077            0 :         return;
    4078              :     }
    4079              : 
    4080            0 :     startEdit(TranslatableString("undoableAction", "Nudge"));
    4081            0 :     double vRaster = getVRaster();
    4082            0 :     double hRaster = getHRaster();
    4083              : 
    4084            0 :     switch (d) {
    4085            0 :     case MoveDirection::Left:
    4086            0 :         m_editData.delta = PointF(-nudgeDistance(m_editData, hRaster), 0);
    4087            0 :         break;
    4088            0 :     case MoveDirection::Right:
    4089            0 :         m_editData.delta = PointF(nudgeDistance(m_editData, hRaster), 0);
    4090            0 :         break;
    4091            0 :     case MoveDirection::Up:
    4092            0 :         m_editData.delta = PointF(0, -nudgeDistance(m_editData, vRaster));
    4093            0 :         break;
    4094            0 :     case MoveDirection::Down:
    4095            0 :         m_editData.delta = PointF(0, nudgeDistance(m_editData, vRaster));
    4096            0 :         break;
    4097            0 :     default:
    4098            0 :         rollback();
    4099            0 :         return;
    4100              :     }
    4101              : 
    4102            0 :     m_editData.evtDelta = m_editData.moveDelta = m_editData.delta;
    4103            0 :     m_editData.hRaster = hRaster;
    4104            0 :     m_editData.vRaster = vRaster;
    4105              : 
    4106            0 :     if (m_editData.curGrip != mu::engraving::Grip::NO_GRIP && int(m_editData.curGrip) < m_editData.grips) {
    4107            0 :         m_editData.pos = m_editData.grip[int(m_editData.curGrip)].center() + m_editData.delta;
    4108              : 
    4109            0 :         m_editData.element->startDragGrip(m_editData);
    4110            0 :         m_editData.element->dragGrip(m_editData);
    4111            0 :         m_editData.element->endDragGrip(m_editData);
    4112              :     } else {
    4113            0 :         m_editData.element->startDrag(m_editData);
    4114            0 :         m_editData.element->drag(m_editData);
    4115            0 :         m_editData.element->endDrag(m_editData);
    4116              :     }
    4117              : 
    4118            0 :     apply();
    4119              : }
    4120              : 
    4121            0 : bool NotationInteraction::isTextSelected() const
    4122              : {
    4123            0 :     EngravingItem* selectedElement = m_selection->element();
    4124            0 :     if (!selectedElement) {
    4125            0 :         return false;
    4126              :     }
    4127              : 
    4128            0 :     if (!selectedElement->isTextBase()) {
    4129            0 :         return false;
    4130              :     }
    4131              : 
    4132            0 :     return true;
    4133              : }
    4134              : 
    4135            0 : bool NotationInteraction::isTextEditingStarted() const
    4136              : {
    4137            0 :     EngravingItem* element = m_editData.element;
    4138            0 :     return element && element->isTextBase() && toTextBase(element)->cursor() && toTextBase(element)->cursor()->editing();
    4139              : }
    4140              : 
    4141            0 : bool NotationInteraction::textEditingAllowed(const EngravingItem* element) const
    4142              : {
    4143            0 :     return element && element->isEditable() && (element->isTextBase() || element->isTBox());
    4144              : }
    4145              : 
    4146            0 : void NotationInteraction::startEditText(EngravingItem* element, const PointF& cursorPos)
    4147              : {
    4148            0 :     if (!element) {
    4149            0 :         return;
    4150              :     }
    4151              : 
    4152            0 :     if (element->isTBox()) {
    4153            0 :         element = toTBox(element)->text();
    4154              :     }
    4155              : 
    4156            0 :     if (element != m_selection->element()) {
    4157            0 :         select({ element }, SelectType::SINGLE);
    4158              :     }
    4159              : 
    4160            0 :     m_editData.clear();
    4161            0 :     m_editData.element = element;
    4162            0 :     m_editData.startMove = bindCursorPosToText(cursorPos, m_editData.element);
    4163              : 
    4164            0 :     m_editData.element->startEdit(m_editData);
    4165            0 :     startTextCursorBlinkTimer();
    4166              : 
    4167            0 :     m_isEditingElementChanged.notify();
    4168            0 :     notifyAboutTextEditingStarted();
    4169            0 :     notifyAboutTextEditingChanged();
    4170              : }
    4171              : 
    4172              : //! NOTE: Copied from TextBase::inputTransition
    4173            0 : void NotationInteraction::editText(QInputMethodEvent* event)
    4174              : {
    4175            0 :     if (!isTextEditingStarted()) {
    4176            0 :         return;
    4177              :     }
    4178              : 
    4179            0 :     mu::engraving::TextBase* text = mu::engraving::toTextBase(m_editData.element);
    4180            0 :     mu::engraving::TextCursor* cursor = text->cursor();
    4181            0 :     String& preeditString = m_editData.preeditString;
    4182              : 
    4183              :     // remove preedit string
    4184            0 :     size_t n = preeditString.size();
    4185            0 :     while (n--) {
    4186            0 :         if (cursor->movePosition(mu::engraving::TextCursor::MoveOperation::Left)) {
    4187            0 :             mu::engraving::TextBlock& curLine = cursor->curLine();
    4188            0 :             curLine.remove(static_cast<int>(cursor->column()), cursor);
    4189            0 :             text->triggerLayout();
    4190            0 :             text->setTextInvalid();
    4191              :         }
    4192              :     }
    4193              : 
    4194            0 :     if (!event->commitString().isEmpty()) {
    4195            0 :         score()->startCmd(TranslatableString("undoableAction", "Edit text"));
    4196            0 :         text->insertText(event->commitString());
    4197            0 :         score()->endCmd();
    4198            0 :         preeditString.clear();
    4199              :     } else {
    4200            0 :         preeditString = event->preeditString();
    4201              : 
    4202            0 :         if (!preeditString.isEmpty()) {
    4203            0 :             cursor->updateCursorFormat();
    4204            0 :             text->editInsertText(cursor, preeditString);
    4205            0 :             text->setTextInvalid();
    4206            0 :             score()->renderer()->layoutText1(text);
    4207            0 :             score()->update();
    4208              :         }
    4209              :     }
    4210              : 
    4211            0 :     event->accept();
    4212            0 :     notifyAboutTextEditingChanged();
    4213              : }
    4214              : 
    4215            0 : bool NotationInteraction::needStartEditGrip(QKeyEvent* event) const
    4216              : {
    4217            0 :     if (!m_editData.element || !m_editData.element->hasGrips()) {
    4218            0 :         return false;
    4219              :     }
    4220              : 
    4221            0 :     if (isGripEditStarted()) {
    4222            0 :         return false;
    4223              :     }
    4224              : 
    4225              :     static const std::set<Qt::Key> arrows {
    4226              :         Qt::Key_Left,
    4227              :         Qt::Key_Right,
    4228              :         Qt::Key_Up,
    4229              :         Qt::Key_Down
    4230            0 :     };
    4231              : 
    4232            0 :     return muse::contains(arrows, static_cast<Qt::Key>(event->key()));
    4233              : }
    4234              : 
    4235            0 : bool NotationInteraction::handleKeyPress(QKeyEvent* event)
    4236              : {
    4237            0 :     mu::engraving::EngravingItem* editElem = m_editData.element;
    4238            0 :     IF_ASSERT_FAILED(editElem) {
    4239            0 :         return false;
    4240              :     }
    4241              : 
    4242            0 :     if (editElem->isTextBase() && doTextEdit(event, toTextBase(editElem))) {
    4243            0 :         return true;
    4244              :     }
    4245              : 
    4246              :     //: Means: an editing operation triggered by a keystroke
    4247            0 :     startEdit(TranslatableString("undoableAction", "Keystroke edit"));
    4248              : 
    4249            0 :     if (editElem->edit(m_editData)) {
    4250            0 :         apply();
    4251            0 :         return true;
    4252              :     } else {
    4253            0 :         rollback();
    4254              :     }
    4255              : 
    4256            0 :     if (event->modifiers() & Qt::KeyboardModifier::AltModifier) {
    4257            0 :         return false;
    4258              :     }
    4259              : 
    4260            0 :     const double vRaster = editElem->isBeam() ? 4 : mu::engraving::MScore::vRaster();
    4261            0 :     const double hRaster = editElem->isBeam() ? 4 : mu::engraving::MScore::hRaster();
    4262              : 
    4263            0 :     switch (event->key()) {
    4264            0 :     case Qt::Key_Tab:
    4265            0 :         if (!editElem->hasGrips()) {
    4266            0 :             return false;
    4267              :         }
    4268            0 :         editElem->nextGrip(m_editData);
    4269            0 :         notifyAboutNotationChanged();
    4270            0 :         return true;
    4271            0 :     case Qt::Key_Backtab:
    4272            0 :         if (!editElem->hasGrips()) {
    4273            0 :             return false;
    4274              :         }
    4275            0 :         editElem->prevGrip(m_editData);
    4276            0 :         notifyAboutNotationChanged();
    4277            0 :         return true;
    4278            0 :     case Qt::Key_Left:
    4279            0 :         m_editData.delta = PointF(-nudgeDistance(m_editData, hRaster), 0);
    4280            0 :         break;
    4281            0 :     case Qt::Key_Right:
    4282            0 :         m_editData.delta = PointF(nudgeDistance(m_editData, hRaster), 0);
    4283            0 :         break;
    4284            0 :     case Qt::Key_Up:
    4285            0 :         m_editData.delta = PointF(0, -nudgeDistance(m_editData, vRaster));
    4286            0 :         break;
    4287            0 :     case Qt::Key_Down:
    4288            0 :         m_editData.delta = PointF(0, nudgeDistance(m_editData, vRaster));
    4289            0 :         break;
    4290            0 :     default:
    4291            0 :         return false;
    4292              :     }
    4293              : 
    4294            0 :     m_editData.evtDelta = m_editData.moveDelta = m_editData.delta;
    4295            0 :     m_editData.hRaster = hRaster;
    4296            0 :     m_editData.vRaster = vRaster;
    4297              : 
    4298            0 :     m_editData.isEditMode = isEditingElement();
    4299              : 
    4300              :     //: Means: an editing operation triggered by a keystroke
    4301            0 :     startEdit(TranslatableString("undoableAction", "Keystroke edit"));
    4302              : 
    4303            0 :     if (m_editData.curGrip != mu::engraving::Grip::NO_GRIP && int(m_editData.curGrip) < m_editData.grips) {
    4304            0 :         m_editData.pos = m_editData.grip[int(m_editData.curGrip)].center() + m_editData.delta;
    4305              : 
    4306            0 :         editElem->startDragGrip(m_editData);
    4307            0 :         editElem->dragGrip(m_editData);
    4308            0 :         editElem->endDragGrip(m_editData);
    4309              :     } else {
    4310            0 :         editElem->startDrag(m_editData);
    4311            0 :         editElem->drag(m_editData);
    4312            0 :         editElem->endDrag(m_editData);
    4313              :     }
    4314              : 
    4315            0 :     apply();
    4316            0 :     return true;
    4317              : }
    4318              : 
    4319            0 : bool NotationInteraction::doTextEdit(QKeyEvent* event, TextBase* tb)
    4320              : {
    4321            0 :     IF_ASSERT_FAILED(event && tb) {
    4322            0 :         return false;
    4323              :     }
    4324              : 
    4325              :     //: Means: an editing operation triggered by a keystroke
    4326            0 :     startEdit(TranslatableString("undoableAction", "Keystroke edit"));
    4327              : 
    4328            0 :     if (!tb->edit(m_editData)) {
    4329            0 :         rollback();
    4330            0 :         return false;
    4331              :     }
    4332              : 
    4333            0 :     apply();
    4334              : 
    4335              :     // Replace newly added straight quotes with curly ones in a separate undo action...
    4336            0 :     const bool isSingleQuoteInput = event->text() == u"\'";
    4337            0 :     const bool isDoubleQuoteInput = event->text() == u"\"";
    4338            0 :     if (!isSingleQuoteInput && !isDoubleQuoteInput) {
    4339            0 :         return true;
    4340              :     }
    4341              : 
    4342            0 :     TextCursor* cursor = tb->cursor();
    4343              : 
    4344            0 :     bool useCloseQuote = false; // Use close if there's a non-space before the newly inputted quote
    4345              : 
    4346            0 :     const int row = static_cast<int>(cursor->row());
    4347            0 :     const int col = static_cast<int>(cursor->column());
    4348            0 :     if (col > 1) {
    4349            0 :         const String prev = cursor->extractText(row, col - 2, row, col - 1);
    4350            0 :         useCloseQuote = !prev.isEmpty() && !prev.front().isSpace();
    4351            0 :     }
    4352              : 
    4353              :     //: Means: an editing operation triggered by a keystroke
    4354            0 :     startEdit(TranslatableString("undoableAction", "Keystroke edit"));
    4355              : 
    4356            0 :     cursor->movePosition(TextCursor::MoveOperation::Left);
    4357            0 :     score()->undo(new RemoveText(cursor, event->text()));
    4358              : 
    4359              :     //: Single open quotation mark
    4360            0 :     const String singleOpenQuote = muse::mtrc("notation", u"‘");
    4361              :     //: Single close quotation mark
    4362            0 :     const String singleCloseQuote = muse::mtrc("notation", u"’");
    4363              :     //: Double open quotation mark
    4364            0 :     const String doubleOpenQuote = muse::mtrc("notation", u"“");
    4365              :     //: Double close quotation mark
    4366            0 :     const String doubleCloseQuote = muse::mtrc("notation", u"”");
    4367              : 
    4368              :     const String replacement = isSingleQuoteInput
    4369              :                                ? (useCloseQuote ? singleCloseQuote : singleOpenQuote)
    4370            0 :                                : (useCloseQuote ? doubleCloseQuote : doubleOpenQuote);
    4371              : 
    4372            0 :     tb->insertText(replacement);
    4373            0 :     apply();
    4374              : 
    4375            0 :     return true;
    4376            0 : }
    4377              : 
    4378            0 : void NotationInteraction::endEditText()
    4379              : {
    4380            0 :     if (!isTextEditingStarted()) {
    4381            0 :         return;
    4382              :     }
    4383              : 
    4384            0 :     stopTextCursorBlinkTimer();
    4385              : 
    4386            0 :     TextBase* editedElement = toTextBase(m_editData.element);
    4387            0 :     doEndEditElement();
    4388              : 
    4389            0 :     m_isEditingElementChanged.notify();
    4390            0 :     notifyAboutTextEditingEnded(editedElement);
    4391              : 
    4392            0 :     notifyAboutTextEditingChanged();
    4393            0 :     m_selection->notifyAboutSelectionChangedIfNeed();
    4394              : }
    4395              : 
    4396            0 : void NotationInteraction::blinkTextCursor()
    4397              : {
    4398            0 :     if (!isTextEditingStarted()) {
    4399            0 :         stopTextCursorBlinkTimer();
    4400            0 :         return;
    4401              :     }
    4402              : 
    4403            0 :     const TextBase* editedElement = editedText();
    4404            0 :     IF_ASSERT_FAILED(editedElement) {
    4405            0 :         return;
    4406              :     }
    4407            0 :     TextCursor* cursor = editedElement->cursor();
    4408            0 :     IF_ASSERT_FAILED(cursor) {
    4409            0 :         return;
    4410              :     }
    4411            0 :     cursor->toggleVisible();
    4412              : 
    4413            0 :     muse::RectF cursorCanvasRect = cursor->cursorCanvasRect();
    4414            0 :     if (cursorCanvasRect.isValid()) {
    4415            0 :         notifyAboutNotationChanged(cursorCanvasRect);
    4416              :     }
    4417              : }
    4418              : 
    4419            0 : void NotationInteraction::startTextCursorBlinkTimer()
    4420              : {
    4421            0 :     const int cursorFlashTime = QApplication::cursorFlashTime() / 2;
    4422            0 :     if (cursorFlashTime > 0) {
    4423            0 :         m_textCursorBlinkTimer.start(cursorFlashTime);
    4424              :     } else {
    4425              :         // text cursor flashing is disabled
    4426            0 :         stopTextCursorBlinkTimer();
    4427              :     }
    4428            0 : }
    4429              : 
    4430            0 : void NotationInteraction::stopTextCursorBlinkTimer()
    4431              : {
    4432            0 :     if (m_textCursorBlinkTimer.isActive()) {
    4433            0 :         m_textCursorBlinkTimer.stop();
    4434              :     }
    4435            0 : }
    4436              : 
    4437            0 : void NotationInteraction::onTextEditingChanged()
    4438              : {
    4439            0 :     updateTextCursorVisibility();
    4440            0 : }
    4441              : 
    4442            0 : void NotationInteraction::updateTextCursorVisibility()
    4443              : {
    4444            0 :     if (!isTextEditingStarted()) {
    4445            0 :         return;
    4446              :     }
    4447              : 
    4448            0 :     const TextBase* editedElement = editedText();
    4449            0 :     IF_ASSERT_FAILED(editedElement) {
    4450            0 :         return;
    4451              :     }
    4452            0 :     TextCursor* cursor = editedElement->cursor();
    4453            0 :     IF_ASSERT_FAILED(cursor) {
    4454            0 :         return;
    4455              :     }
    4456              : 
    4457              :     // Whenever the cursor moves, we update its visibility.
    4458              :     // If it has to be visible, we display it immediately
    4459              :     // regardless of the blinking, and restart the blinking.
    4460            0 :     bool showCursor = !cursor->hasSelection();
    4461            0 :     cursor->setVisible(showCursor);
    4462            0 :     if (showCursor) {
    4463            0 :         startTextCursorBlinkTimer();
    4464              :     } else {
    4465            0 :         stopTextCursorBlinkTimer();
    4466              :     }
    4467              : }
    4468              : 
    4469            0 : void NotationInteraction::changeTextCursorPosition(const PointF& newCursorPos)
    4470              : {
    4471            0 :     IF_ASSERT_FAILED(isTextEditingStarted() && m_editData.element) {
    4472            0 :         return;
    4473              :     }
    4474              : 
    4475            0 :     m_editData.startMove = bindCursorPosToText(newCursorPos, m_editData.element);
    4476              : 
    4477            0 :     mu::engraving::TextBase* textEl = mu::engraving::toTextBase(m_editData.element);
    4478              : 
    4479            0 :     textEl->mousePress(m_editData);
    4480            0 :     if (m_editData.buttons == mu::engraving::MiddleButton) {
    4481            0 :         QString txt = QGuiApplication::clipboard()->text();
    4482            0 :         textEl->paste(txt);
    4483            0 :     }
    4484              : 
    4485            0 :     notifyAboutTextEditingChanged();
    4486              : }
    4487              : 
    4488            0 : void NotationInteraction::selectText(mu::engraving::SelectTextType type)
    4489              : {
    4490            0 :     if (!isTextEditingStarted()) {
    4491            0 :         return;
    4492              :     }
    4493              : 
    4494            0 :     mu::engraving::TextBase* text = mu::engraving::toTextBase(m_editData.element);
    4495            0 :     text->select(type);
    4496            0 :     text->endHexState();
    4497            0 :     text->setPrimed(false);
    4498              : 
    4499            0 :     notifyAboutTextEditingChanged();
    4500              : }
    4501              : 
    4502            0 : void NotationInteraction::selectAllText()
    4503              : {
    4504            0 :     if (!isTextEditingStarted()) {
    4505            0 :         return;
    4506              :     }
    4507              : 
    4508            0 :     mu::engraving::TextBase* text = mu::engraving::toTextBase(m_editData.element);
    4509            0 :     text->selectAll();
    4510              : }
    4511              : 
    4512            0 : const TextBase* NotationInteraction::editedText() const
    4513              : {
    4514            0 :     return mu::engraving::toTextBase(m_editData.element);
    4515              : }
    4516              : 
    4517            0 : void NotationInteraction::undo()
    4518              : {
    4519            0 :     m_undoStack->undo(&m_editData);
    4520            0 : }
    4521              : 
    4522            0 : void NotationInteraction::redo()
    4523              : {
    4524            0 :     m_undoStack->redo(&m_editData);
    4525            0 : }
    4526              : 
    4527            0 : void NotationInteraction::undoRedoToIndex(size_t idx)
    4528              : {
    4529            0 :     endEditElement();
    4530            0 :     m_undoStack->undoRedoToIndex(idx, &m_editData);
    4531            0 : }
    4532              : 
    4533            0 : muse::async::Notification NotationInteraction::textEditingStarted() const
    4534              : {
    4535            0 :     return m_textEditingStarted;
    4536              : }
    4537              : 
    4538            0 : muse::async::Notification NotationInteraction::textEditingChanged() const
    4539              : {
    4540            0 :     return m_textEditingChanged;
    4541              : }
    4542              : 
    4543            0 : muse::async::Channel<TextBase*> NotationInteraction::textEditingEnded() const
    4544              : {
    4545            0 :     return m_textEditingEnded;
    4546              : }
    4547              : 
    4548            0 : bool NotationInteraction::isGripEditStarted() const
    4549              : {
    4550            0 :     return m_editData.element && m_editData.curGrip != mu::engraving::Grip::NO_GRIP;
    4551              : }
    4552              : 
    4553            0 : bool NotationInteraction::isStartEndGripSelected() const
    4554              : {
    4555            0 :     return m_editData.element && (m_editData.curGrip == mu::engraving::Grip::START || m_editData.curGrip == mu::engraving::Grip::END);
    4556              : }
    4557              : 
    4558            0 : static int findGrip(const std::vector<muse::RectF>& grips, const muse::PointF& canvasPos)
    4559              : {
    4560            0 :     if (grips.empty()) {
    4561            0 :         return -1;
    4562              :     }
    4563            0 :     qreal align = grips[0].width() / 2;
    4564            0 :     for (size_t i = 0; i < grips.size(); ++i) {
    4565            0 :         if (grips[i].adjusted(-align, -align, align, align).contains(canvasPos)) {
    4566            0 :             return static_cast<int>(i);
    4567              :         }
    4568              :     }
    4569            0 :     return -1;
    4570              : }
    4571              : 
    4572            0 : bool NotationInteraction::isHitGrip(const PointF& pos) const
    4573              : {
    4574            0 :     return selection()->element() && findGrip(m_editData.grip, pos) != -1;
    4575              : }
    4576              : 
    4577            0 : void NotationInteraction::startEditGrip(const PointF& pos)
    4578              : {
    4579            0 :     int grip = findGrip(m_editData.grip, pos);
    4580            0 :     if (grip == -1) {
    4581            0 :         return;
    4582              :     }
    4583            0 :     startEditGrip(selection()->element(), mu::engraving::Grip(grip));
    4584              : }
    4585              : 
    4586            0 : void NotationInteraction::startEditGrip(EngravingItem* element, mu::engraving::Grip grip)
    4587              : {
    4588            0 :     if (m_editData.element == element && m_editData.curGrip == grip) {
    4589            0 :         return;
    4590              :     }
    4591              : 
    4592            0 :     m_editData.element = element;
    4593            0 :     m_editData.curGrip = grip;
    4594              : 
    4595            0 :     updateGripAnchorLines();
    4596            0 :     m_editData.element->startEdit(m_editData);
    4597              : 
    4598            0 :     notifyAboutNotationChanged();
    4599              : }
    4600              : 
    4601            0 : void NotationInteraction::endEditGrip()
    4602              : {
    4603            0 :     if (m_editData.curGrip == Grip::NO_GRIP) {
    4604            0 :         return;
    4605              :     }
    4606              : 
    4607            0 :     m_editData.curGrip = Grip::NO_GRIP;
    4608            0 :     notifyAboutNotationChanged();
    4609              : }
    4610              : 
    4611            0 : void NotationInteraction::updateGripAnchorLines()
    4612              : {
    4613            0 :     if (!m_editData.element) {
    4614            0 :         return;
    4615              :     }
    4616              : 
    4617            0 :     std::vector<LineF> lines;
    4618            0 :     mu::engraving::Grip anchorLinesGrip = m_editData.curGrip == mu::engraving::Grip::NO_GRIP
    4619            0 :                                           ? m_editData.element->defaultGrip() : m_editData.curGrip;
    4620            0 :     std::vector<LineF> anchorLines = m_editData.element->gripAnchorLines(anchorLinesGrip);
    4621              : 
    4622            0 :     if (!anchorLines.empty()) {
    4623            0 :         for (LineF& line : anchorLines) {
    4624            0 :             if (line.p1() != line.p2()) {
    4625            0 :                 lines.push_back(line);
    4626              :             }
    4627              :         }
    4628              :     }
    4629              : 
    4630            0 :     setAnchorLines(lines);
    4631            0 : }
    4632              : 
    4633            0 : void NotationInteraction::updateDragAnchorLines()
    4634              : {
    4635            0 :     std::vector<LineF> anchorLines;
    4636            0 :     for (const EngravingItem* e : selection()->elements()) {
    4637            0 :         std::vector<LineF> elAnchorLines = e->dragAnchorLines();
    4638            0 :         if (!elAnchorLines.empty()) {
    4639            0 :             for (LineF& l : elAnchorLines) {
    4640            0 :                 anchorLines.push_back(l);
    4641              :             }
    4642              :         }
    4643            0 :     }
    4644              : 
    4645            0 :     setAnchorLines(anchorLines);
    4646            0 : }
    4647              : 
    4648            0 : bool NotationInteraction::isEditingElement() const
    4649              : {
    4650            0 :     return m_editData.element != nullptr;
    4651              : }
    4652              : 
    4653            0 : void NotationInteraction::startEditElement(EngravingItem* element)
    4654              : {
    4655            0 :     if (!element || !element->isEditable()) {
    4656            0 :         return;
    4657              :     }
    4658              : 
    4659            0 :     if (isEditingElement()) {
    4660            0 :         return;
    4661              :     }
    4662              : 
    4663            0 :     if (element->isTextBase()) {
    4664            0 :         startEditText(element);
    4665            0 :     } else if (element->hasGrips() && !element->isImage()) {
    4666            0 :         startEditGrip(element, element->defaultGrip());
    4667              :     } else {
    4668            0 :         element->startEdit(m_editData);
    4669            0 :         m_editData.element = element;
    4670              :     }
    4671              : 
    4672            0 :     m_isEditingElementChanged.notify();
    4673              : }
    4674              : 
    4675            0 : void NotationInteraction::changeEditElement(EngravingItem* newElement)
    4676              : {
    4677            0 :     IF_ASSERT_FAILED(newElement) {
    4678            0 :         return;
    4679              :     }
    4680              : 
    4681            0 :     if (m_editData.element == newElement) {
    4682            0 :         return;
    4683              :     }
    4684              : 
    4685            0 :     mu::engraving::Grip currentGrip = m_editData.curGrip;
    4686            0 :     bool gripEditStarted = isGripEditStarted();
    4687              : 
    4688            0 :     doEndEditElement();
    4689              : 
    4690            0 :     if (gripEditStarted) {
    4691            0 :         startEditGrip(newElement, currentGrip);
    4692              :     } else {
    4693            0 :         startEditElement(newElement);
    4694              :     }
    4695              : }
    4696              : 
    4697            0 : bool NotationInteraction::isEditAllowed(QKeyEvent* event)
    4698              : {
    4699            0 :     if (!m_editData.element) {
    4700            0 :         return false;
    4701              :     }
    4702              : 
    4703            0 :     mu::engraving::EditData editData = m_editData;
    4704            0 :     editData.modifiers = keyboardModifier(event->modifiers());
    4705            0 :     editData.key = event->key();
    4706            0 :     editData.s = event->text();
    4707              : 
    4708            0 :     if (editData.element->isEditAllowed(editData)) {
    4709            0 :         return true;
    4710              :     }
    4711              : 
    4712            0 :     if (event->modifiers() & Qt::KeyboardModifier::AltModifier) {
    4713            0 :         return false;
    4714              :     }
    4715              : 
    4716            0 :     if (editData.element->isTextBase()) {
    4717            0 :         return false;
    4718              :     }
    4719              : 
    4720              :     static QSet<int> navigationKeys = {
    4721              :         Qt::Key_Left,
    4722              :         Qt::Key_Right,
    4723              :         Qt::Key_Up,
    4724              :         Qt::Key_Down
    4725            0 :     };
    4726              : 
    4727            0 :     if (editData.element->hasGrips()) {
    4728            0 :         navigationKeys += { Qt::Key_Tab, Qt::Key_Backtab };
    4729              :     }
    4730              : 
    4731            0 :     return navigationKeys.contains(event->key());
    4732            0 : }
    4733              : 
    4734            0 : void NotationInteraction::editElement(QKeyEvent* event)
    4735              : {
    4736              : #if 0
    4737              :     static int index = 0;
    4738              :     LOGDA() << "[" << ++index << "] " << QKeySequence(event->keyCombination()).toString();
    4739              : #endif
    4740              : 
    4741            0 :     if (!m_editData.element) {
    4742            0 :         return;
    4743              :     }
    4744              : 
    4745            0 :     m_editData.modifiers = keyboardModifier(event->modifiers());
    4746              : 
    4747            0 :     if (isDragStarted()) {
    4748            0 :         return; // ignore all key strokes while dragging
    4749              :     }
    4750              : 
    4751            0 :     m_editData.key = event->key();
    4752            0 :     m_editData.s = event->text();
    4753              : 
    4754              :     // Brackets get deleted during layout with their system, so the best we can do to maintain
    4755              :     // the bracket selected is to store their indices and use them to find the new ones
    4756            0 :     bool isBracket = m_editData.element->isBracket();
    4757            0 :     size_t bracketIndex = muse::nidx;
    4758            0 :     size_t systemIndex = muse::nidx;
    4759              : 
    4760            0 :     if (isBracket) {
    4761            0 :         const Bracket* bracket = toBracket(m_editData.element);
    4762            0 :         System* system = bracket->system();
    4763              : 
    4764            0 :         if (system) {
    4765            0 :             bracketIndex = muse::indexOf(system->brackets(), bracket);
    4766            0 :             systemIndex = muse::indexOf(score()->systems(), system);
    4767              :         }
    4768            0 :     } else if (m_editData.element->isHarmony()) {
    4769            0 :         if (isTextEditingStarted() && (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)) {
    4770            0 :             endEditText();
    4771            0 :             return;
    4772              :         }
    4773              :     }
    4774              : 
    4775            0 :     if (needStartEditGrip(event)) {
    4776            0 :         m_editData.curGrip = m_editData.element->defaultGrip();
    4777              :     }
    4778              : 
    4779            0 :     if (handleKeyPress(event)) {
    4780            0 :         if (isBracket && bracketIndex != muse::nidx && systemIndex != muse::nidx) {
    4781              :             // Try to restore selected bracket
    4782            0 :             System* system = systemIndex < score()->systems().size() ? score()->systems().at(systemIndex) : nullptr;
    4783            0 :             EngravingItem* bracket = system && bracketIndex < system->brackets().size() ? system->brackets().at(bracketIndex) : nullptr;
    4784            0 :             if (bracket) {
    4785            0 :                 m_editData.element = bracket;
    4786            0 :                 select({ bracket }, SelectType::SINGLE);
    4787              :             }
    4788              :         }
    4789              : 
    4790            0 :         if (isGripEditStarted()) {
    4791            0 :             updateGripAnchorLines();
    4792              :         }
    4793              : 
    4794            0 :         event->accept();
    4795              :     }
    4796              : 
    4797            0 :     if (isTextEditingStarted()) {
    4798            0 :         notifyAboutTextEditingChanged();
    4799              :     }
    4800              : }
    4801              : 
    4802            0 : void NotationInteraction::endEditElement()
    4803              : {
    4804            0 :     if (!m_editData.element) {
    4805            0 :         return;
    4806              :     }
    4807              : 
    4808            0 :     if (isTextEditingStarted()) {
    4809            0 :         endEditText();
    4810            0 :         return;
    4811              :     }
    4812              : 
    4813            0 :     if (isDragStarted()) {
    4814            0 :         doEndDrag();
    4815            0 :         rollback();
    4816              :     }
    4817              : 
    4818            0 :     doEndEditElement();
    4819            0 :     resetAnchorLines();
    4820              : 
    4821            0 :     m_isEditingElementChanged.notify();
    4822            0 :     notifyAboutNotationChanged();
    4823              : }
    4824              : 
    4825            0 : muse::async::Notification NotationInteraction::isEditingElementChanged() const
    4826              : {
    4827            0 :     return m_isEditingElementChanged;
    4828              : }
    4829              : 
    4830            0 : void NotationInteraction::updateTimeTickAnchors(QKeyEvent* event)
    4831              : {
    4832            0 :     EngravingItem* selectedElement = m_selection->element();
    4833            0 :     if (selectedElement && selectedElement->allowTimeAnchor() && event->type() == QKeyEvent::Type::KeyPress && !isTextEditingStarted()) {
    4834            0 :         EditTimeTickAnchors::updateAnchors(selectedElement);
    4835            0 :         updateDragAnchorLines();
    4836            0 :         score()->update();
    4837              :     } else {
    4838            0 :         score()->hideAnchors();
    4839            0 :         resetAnchorLines();
    4840              :     }
    4841              : 
    4842            0 :     notifyAboutNotationChanged();
    4843            0 : }
    4844              : 
    4845            0 : void NotationInteraction::moveElementAnchors(QKeyEvent* event)
    4846              : {
    4847            0 :     EngravingItem* element = selection()->element();
    4848            0 :     IF_ASSERT_FAILED(element) {
    4849            0 :         return;
    4850              :     }
    4851              : 
    4852            0 :     startEdit(TranslatableString("undoableAction", "Move element anchors"));
    4853              : 
    4854            0 :     MoveElementAnchors::moveElementAnchors(element, KeyboardKey(event->key()), keyboardModifier(event->modifiers()));
    4855              : 
    4856            0 :     apply();
    4857              : 
    4858            0 :     updateDragAnchorLines();
    4859              : }
    4860              : 
    4861            0 : void NotationInteraction::doEndEditElement()
    4862              : {
    4863            0 :     if (m_editData.element) {
    4864            0 :         m_editData.element->endEdit(m_editData);
    4865              :     }
    4866            0 :     m_editData.clear();
    4867            0 : }
    4868              : 
    4869            0 : void NotationInteraction::onElementDestroyed(EngravingItem* element)
    4870              : {
    4871            0 :     if (m_editData.element == element) {
    4872            0 :         m_editData.element = nullptr;
    4873              :     }
    4874              : 
    4875            0 :     if (m_hitElementContext.element == element) {
    4876            0 :         m_hitElementContext.element = nullptr;
    4877            0 :         m_hitElementContext.staff = nullptr;
    4878              :     }
    4879            0 : }
    4880              : 
    4881            0 : void NotationInteraction::splitSelectedMeasure()
    4882              : {
    4883            0 :     EngravingItem* selectedElement = m_selection->element();
    4884            0 :     if (!selectedElement) {
    4885            0 :         return;
    4886              :     }
    4887              : 
    4888            0 :     if (!selectedElement->isNote() && !selectedElement->isRest()) {
    4889            0 :         return;
    4890              :     }
    4891              : 
    4892            0 :     if (selectedElement->isNote()) {
    4893            0 :         selectedElement = toNote(selectedElement)->chord();
    4894              :     }
    4895              : 
    4896            0 :     ChordRest* chordRest = toChordRest(selectedElement);
    4897              : 
    4898            0 :     transaction(TranslatableString("undoableAction", "Split measure"), [&](engraving::Transaction& tx) {
    4899            0 :         SplitJoinMeasure::splitMeasure(tx, score()->masterScore(), chordRest->tick());
    4900            0 :     });
    4901              : 
    4902            0 :     checkAndShowError();
    4903              : }
    4904              : 
    4905            0 : void NotationInteraction::joinSelectedMeasures()
    4906              : {
    4907            0 :     if (!m_selection->isRange()) {
    4908            0 :         return;
    4909              :     }
    4910              : 
    4911            0 :     const INotationSelectionRange::MeasureBaseRange measureBaseRange = m_selection->range()->measureBaseRange();
    4912            0 :     const MeasureBase* startMeasureBase = measureBaseRange.startMeasureBase;
    4913            0 :     const MeasureBase* endMeasureBase = measureBaseRange.endMeasureBase;
    4914            0 :     if (!startMeasureBase || !endMeasureBase) {
    4915            0 :         return;
    4916              :     }
    4917              : 
    4918            0 :     const Measure* startMeasure = startMeasureBase->isMeasure() ? toMeasure(startMeasureBase) : startMeasureBase->nextMeasure();
    4919            0 :     const Measure* endMeasure = endMeasureBase->isMeasure() ? toMeasure(endMeasureBase) : endMeasureBase->prevMeasure();
    4920            0 :     if (!startMeasure || !endMeasure || endMeasure->tick() < startMeasure->tick()) {
    4921            0 :         return;
    4922              :     }
    4923              : 
    4924            0 :     transaction(TranslatableString("undoableAction", "Join measures"), [&](engraving::Transaction& tx) {
    4925            0 :         SplitJoinMeasure::joinMeasures(tx, score()->masterScore(), startMeasure->tick(), endMeasure->tick());
    4926            0 :     });
    4927              : 
    4928            0 :     checkAndShowError();
    4929              : }
    4930              : 
    4931            0 : void NotationInteraction::addBoxes(BoxType boxType, int count, AddBoxesTarget target)
    4932              : {
    4933            0 :     int beforeBoxIndex = -1;
    4934            0 :     bool moveSignaturesClefs = (target != AddBoxesTarget::AfterSelection);
    4935            0 :     const EngravingItem* selectedItem = nullptr;
    4936            0 :     const MeasureBase* selectedItemMeasure = nullptr;
    4937              : 
    4938            0 :     switch (target) {
    4939            0 :     case AddBoxesTarget::AfterSelection:
    4940              :     case AddBoxesTarget::BeforeSelection: {
    4941            0 :         if (selection()->isNone()) {
    4942            0 :             MScore::setError(MsError::NO_MEASURE_SELECTED);
    4943            0 :             checkAndShowError();
    4944            0 :             return;
    4945              :         }
    4946              : 
    4947            0 :         if (selection()->isRange()) {
    4948            0 :             INotationSelectionRange::MeasureBaseRange range = selection()->range()->measureBaseRange();
    4949              : 
    4950            0 :             const int startMeasureIndex = range.startMeasureBase ? range.startMeasureBase->index() : 0;
    4951            0 :             const int endMeasureIndex = range.endMeasureBase ? range.endMeasureBase->index() + 1 : 0;
    4952              : 
    4953            0 :             beforeBoxIndex = target == AddBoxesTarget::BeforeSelection
    4954            0 :                              ? startMeasureIndex
    4955              :                              : endMeasureIndex;
    4956            0 :             break;
    4957              :         }
    4958              : 
    4959            0 :         const std::vector<EngravingItem*>& elements = selection()->elements();
    4960            0 :         IF_ASSERT_FAILED(!elements.empty()) {
    4961              :             // This would contradict the fact that selection()->isNone() == false at this point
    4962            0 :             MScore::setError(MsError::NO_MEASURE_SELECTED);
    4963            0 :             checkAndShowError();
    4964            0 :             return;
    4965              :         }
    4966              : 
    4967            0 :         for (const EngravingItem* item : elements) {
    4968            0 :             const MeasureBase* itemMeasure = item->findMeasureBase();
    4969            0 :             if (!itemMeasure) {
    4970            0 :                 continue;
    4971              :             }
    4972              : 
    4973            0 :             int itemMeasureIndex = itemMeasure->index();
    4974            0 :             if (itemMeasureIndex < 0) {
    4975            0 :                 continue;
    4976              :             }
    4977              : 
    4978            0 :             if (target == AddBoxesTarget::BeforeSelection) {
    4979            0 :                 if (beforeBoxIndex < 0 || itemMeasureIndex < beforeBoxIndex) {
    4980            0 :                     beforeBoxIndex = itemMeasureIndex;
    4981            0 :                     selectedItem = item;
    4982            0 :                     selectedItemMeasure = itemMeasure;
    4983              :                 }
    4984              :             } else {
    4985            0 :                 if (itemMeasureIndex + 1 > beforeBoxIndex) {
    4986            0 :                     beforeBoxIndex = itemMeasureIndex + 1;
    4987            0 :                     selectedItem = item;
    4988            0 :                     selectedItemMeasure = itemMeasure;
    4989              :                 }
    4990              :             }
    4991              :         }
    4992              : 
    4993            0 :         const bool selectedIsValid = selectedItem && selectedItemMeasure;
    4994            0 :         const ElementType selectedItemType = selectedIsValid ? selectedItem->type() : ElementType::INVALID;
    4995              : 
    4996              :         // special cases for "between measures elements"
    4997            0 :         switch (selectedItemType) {
    4998            0 :         case ElementType::CLEF:
    4999              :         case ElementType::BAR_LINE:
    5000              :         case ElementType::TIMESIG:
    5001              :         case ElementType::KEYSIG: {
    5002            0 :             const Fraction itemTick = selectedItem->tick();
    5003            0 :             const Fraction measureTick = selectedItemMeasure->tick();
    5004            0 :             const Fraction measureLastTick = measureTick + selectedItemMeasure->ticks();
    5005              : 
    5006            0 :             if (itemTick == measureTick) {
    5007            0 :                 if (target == AddBoxesTarget::AfterSelection) {
    5008            0 :                     beforeBoxIndex -= 1;
    5009              :                 }
    5010            0 :                 moveSignaturesClefs = (target == AddBoxesTarget::AfterSelection);
    5011            0 :             } else if (itemTick == measureLastTick) {
    5012            0 :                 if (target == AddBoxesTarget::BeforeSelection) {
    5013            0 :                     beforeBoxIndex += 1;
    5014              :                 }
    5015            0 :                 moveSignaturesClefs = (target == AddBoxesTarget::AfterSelection);
    5016              :             }
    5017              :         }
    5018            0 :         default: break;
    5019              :         }
    5020              : 
    5021            0 :         if (beforeBoxIndex < 0) {
    5022            0 :             MScore::setError(MsError::NO_MEASURE_SELECTED);
    5023            0 :             checkAndShowError();
    5024            0 :             return;
    5025              :         }
    5026            0 :     } break;
    5027            0 :     case AddBoxesTarget::AtStartOfScore: {
    5028            0 :         Measure* firstMeasure = score()->firstMeasure();
    5029            0 :         beforeBoxIndex = firstMeasure ? firstMeasure->index() : -1;
    5030            0 :     } break;
    5031            0 :     case AddBoxesTarget::AtEndOfScore:
    5032            0 :         beforeBoxIndex = -1;
    5033            0 :         break;
    5034              :     }
    5035              : 
    5036            0 :     addBoxes(boxType, count, beforeBoxIndex, moveSignaturesClefs);
    5037              : }
    5038              : 
    5039            0 : void NotationInteraction::addBoxes(BoxType boxType, int count, int beforeBoxIndex, bool moveSignaturesClef)
    5040              : {
    5041            0 :     if (count < 1) {
    5042            0 :         return;
    5043              :     }
    5044              : 
    5045            0 :     auto boxTypeToElementType = [](BoxType boxType) {
    5046            0 :         switch (boxType) {
    5047            0 :         case BoxType::Horizontal: return mu::engraving::ElementType::HBOX;
    5048            0 :         case BoxType::Vertical: return mu::engraving::ElementType::VBOX;
    5049            0 :         case BoxType::Text: return mu::engraving::ElementType::TBOX;
    5050            0 :         case BoxType::Fret: return mu::engraving::ElementType::FBOX;
    5051            0 :         case BoxType::Measure: return mu::engraving::ElementType::MEASURE;
    5052            0 :         case BoxType::Unknown: return mu::engraving::ElementType::INVALID;
    5053              :         }
    5054              : 
    5055            0 :         return ElementType::INVALID;
    5056              :     };
    5057              : 
    5058            0 :     mu::engraving::ElementType elementType = boxTypeToElementType(boxType);
    5059            0 :     if (elementType == mu::engraving::ElementType::INVALID) {
    5060            0 :         return;
    5061              :     }
    5062              : 
    5063            0 :     auto boxTypeDescription = [count](BoxType boxType) -> TranslatableString {
    5064            0 :         switch (boxType) {
    5065            0 :         case BoxType::Horizontal: return TranslatableString("undoableAction", "Add horizontal frame");
    5066            0 :         case BoxType::Vertical: return TranslatableString("undoableAction", "Add vertical frame");
    5067            0 :         case BoxType::Text: return TranslatableString("undoableAction", "Add text frame");
    5068            0 :         case BoxType::Fret: return TranslatableString("undoableAction", "Add fretboard diagram legend");
    5069            0 :         case BoxType::Measure: return TranslatableString("undoableAction", "Add %Ln measure(s)", nullptr, count);
    5070            0 :         case BoxType::Unknown: break;
    5071              :         }
    5072              : 
    5073            0 :         return {};
    5074            0 :     };
    5075              : 
    5076            0 :     startEdit(boxTypeDescription(boxType));
    5077              : 
    5078            0 :     mu::engraving::MeasureBase* beforeBox = beforeBoxIndex >= 0 ? score()->measure(beforeBoxIndex) : nullptr;
    5079              : 
    5080            0 :     mu::engraving::Score::InsertMeasureOptions options;
    5081            0 :     options.createMeasureRests = false;
    5082            0 :     options.moveSignaturesClef = moveSignaturesClef;
    5083            0 :     options.needDeselectAll = false;
    5084            0 :     options.cloneBoxToAllParts = boxType != BoxType::Fret;
    5085              : 
    5086            0 :     for (int i = 0; i < count; ++i) {
    5087            0 :         score()->insertMeasure(elementType, beforeBox, options);
    5088              :     }
    5089              : 
    5090            0 :     apply();
    5091              : 
    5092            0 :     int indexOfFirstAddedMeasure = beforeBoxIndex >= 0 ? beforeBoxIndex : score()->measures()->size() - count;
    5093            0 :     MeasureBase* firstAddedMeasure = score()->measure(indexOfFirstAddedMeasure);
    5094            0 :     m_selection->select({ firstAddedMeasure }, SelectType::REPLACE);
    5095            0 :     showItem(firstAddedMeasure);
    5096              : 
    5097              :     // For other box types, it makes little sense to select them all
    5098            0 :     if (boxType == BoxType::Measure) {
    5099            0 :         m_selection->select({ score()->measure(indexOfFirstAddedMeasure + count - 1) }, SelectType::RANGE);
    5100              :     }
    5101              : 
    5102            0 :     m_selection->notifyAboutSelectionChangedIfNeed();
    5103              : }
    5104              : 
    5105            0 : void NotationInteraction::copySelection()
    5106              : {
    5107            0 :     if (!selection()->canCopy()) {
    5108            0 :         checkAndShowError();
    5109            0 :         return;
    5110              :     }
    5111              : 
    5112            0 :     if (isTextEditingStarted()) {
    5113            0 :         m_editData.element->editCopy(m_editData);
    5114            0 :         mu::engraving::TextEditData* ted = static_cast<mu::engraving::TextEditData*>(m_editData.getData(m_editData.element).get());
    5115            0 :         if (!ted->selectedText.isEmpty()) {
    5116            0 :             QMimeData* mimeData = new QMimeData();
    5117            0 :             mimeData->setData(TextEditData::mimeRichTextFormat, ted->selectedText.toQString().toUtf8());
    5118            0 :             mimeData->setText(ted->selectedPlainText);
    5119            0 :             QGuiApplication::clipboard()->setMimeData(mimeData);
    5120              :         }
    5121              :     } else {
    5122            0 :         QMimeData* mimeData = selection()->qMimeData();
    5123            0 :         if (!mimeData) {
    5124            0 :             return;
    5125              :         }
    5126            0 :         QGuiApplication::clipboard()->setMimeData(mimeData);
    5127              :     }
    5128              : }
    5129              : 
    5130            0 : void NotationInteraction::repeatSelection()
    5131              : {
    5132            0 :     const Selection& selection = score()->selection();
    5133            0 :     if (score()->noteEntryMode() && selection.isSingle()) {
    5134              :         // Single selections require special handling in note entry mode...
    5135            0 :         EngravingItem* el = selection.element();
    5136            0 :         if (!el) {
    5137            0 :             return;
    5138              :         }
    5139            0 :         Chord* c = nullptr;
    5140            0 :         if (el->isNote()) {
    5141            0 :             c = toNote(el)->chord();
    5142            0 :         } else if (el->isRest()) {
    5143            0 :             Segment* prevSegment = toRest(el)->segment()->prev1WithElemsOnTrack(el->track());
    5144              : 
    5145              :             // Looking for the previous Chord
    5146            0 :             while (prevSegment) {
    5147            0 :                 if (prevSegment->element(el->track())->isChord()) {
    5148            0 :                     c = toChord(prevSegment->element(el->track()));
    5149            0 :                     break;
    5150              :                 }
    5151            0 :                 prevSegment = prevSegment->prev1WithElemsOnTrack(el->track());
    5152              :             }
    5153              :         }
    5154            0 :         if (c) {
    5155            0 :             transaction(TranslatableString("undoableAction", "Repeat selection"), [&](engraving::Transaction& tx) {
    5156            0 :                 for (Note* note : c->notes()) {
    5157            0 :                     NoteVal nval = note->noteVal();
    5158            0 :                     NoteInput::addPitch(tx, score(), nval, note != c->notes()[0]);
    5159              :                 }
    5160            0 :             });
    5161              :         }
    5162            0 :         return;
    5163              :     }
    5164              : 
    5165              :     //! NOTE: Ideally we would use our copy-paste logic for this case, but this isn't
    5166              :     //! fully compatible with list selections right now...
    5167            0 :     if (selection.isList() && !selection.noteList().empty()) {
    5168            0 :         const Fraction& firstTick = selection.tickStart();
    5169            0 :         const Fraction& lastTick = selection.tickEnd();
    5170              :         // Only "single-tick" list selections are currently supported...
    5171            0 :         if (firstTick != lastTick) {
    5172            0 :             MScore::setError(MsError::CANNOT_REPEAT_SELECTION);
    5173            0 :             checkAndShowError();
    5174            0 :             return;
    5175              :         }
    5176            0 :         startEdit(TranslatableString("undoableAction", "Repeat selection"));
    5177            0 :         if (!Paste::repeatListSelection(score()->transactionManager()->currentOrDummyTransaction(), score())) {
    5178            0 :             rollback();
    5179            0 :             MScore::setError(MsError::CANNOT_REPEAT_SELECTION);
    5180            0 :             return;
    5181              :         }
    5182            0 :         apply();
    5183            0 :         return;
    5184              :     }
    5185              : 
    5186              :     // If a list selection with no notes is a rest, convert to a range selection
    5187            0 :     if (selection.isList()) {
    5188            0 :         ChordRest* cr = score()->getSelectedChordRest();
    5189            0 :         if (!cr) {
    5190            0 :             MScore::setError(MsError::CANNOT_REPEAT_SELECTION);
    5191            0 :             checkAndShowError();
    5192            0 :             return;
    5193              :         }
    5194            0 :         score()->select(cr, SelectType::RANGE);
    5195              :     }
    5196              : 
    5197              :     // Use copy-paste logic for range selections...
    5198            0 :     if (!selection.isRange() || !m_selection->canCopy()) {
    5199            0 :         MScore::setError(MsError::CANNOT_REPEAT_SELECTION);
    5200            0 :         checkAndShowError();
    5201            0 :         return;
    5202              :     }
    5203              : 
    5204            0 :     XmlReader xml(selection.mimeData());
    5205            0 :     staff_idx_t dStaff = selection.staffStart();
    5206            0 :     mu::engraving::Segment* endSegment = selection.endSegment();
    5207              : 
    5208            0 :     startEdit(TranslatableString("undoableAction", "Repeat selection"));
    5209            0 :     if (endSegment && endSegment->segmentType() != SegmentType::ChordRest) {
    5210            0 :         if (!endSegment->next1(SegmentType::ChordRest)) {
    5211            0 :             score()->appendMeasures(1);
    5212              :         }
    5213            0 :         endSegment = endSegment->next1(SegmentType::ChordRest);
    5214              :     }
    5215            0 :     if (!endSegment) {
    5216            0 :         return;
    5217              :     }
    5218            0 :     for (track_idx_t track = staff2track(dStaff); track < staff2track(dStaff + 1); ++track) {
    5219            0 :         EngravingItem* e = endSegment->element(track);
    5220            0 :         if (!e) {
    5221            0 :             continue;
    5222              :         }
    5223              : 
    5224            0 :         ChordRest* cr = toChordRest(e);
    5225            0 :         if (!Paste::pasteStaff(score()->transactionManager()->currentOrDummyTransaction(), score(), xml, cr->segment(), cr->staffIdx())) {
    5226            0 :             rollback();
    5227            0 :             checkAndShowError();
    5228            0 :             return;
    5229              :         }
    5230            0 :         apply();
    5231              : 
    5232            0 :         showItem(cr);
    5233            0 :         break;
    5234              :     }
    5235            0 : }
    5236              : 
    5237            0 : void NotationInteraction::copyLyrics()
    5238              : {
    5239            0 :     QString text = score()->extractLyrics();
    5240            0 :     QGuiApplication::clipboard()->setText(text);
    5241            0 : }
    5242              : 
    5243            0 : void NotationInteraction::pasteSelection(const Fraction& scale)
    5244              : {
    5245            0 :     startEdit(TranslatableString("undoableAction", "Paste"));
    5246              : 
    5247            0 :     bool succeeded = true;
    5248            0 :     if (isTextEditingStarted()) {
    5249            0 :         pasteIntoTextEdit();
    5250              :     } else {
    5251            0 :         const QMimeData* mimeData = QGuiApplication::clipboard()->mimeData();
    5252            0 :         QMimeDataAdapter ma(mimeData);
    5253            0 :         succeeded = Paste::paste(score()->transactionManager()->currentOrDummyTransaction(), score(), &ma, nullptr, scale);
    5254            0 :         m_editData.element = nullptr;
    5255            0 :     }
    5256              : 
    5257            0 :     if (succeeded) {
    5258            0 :         apply();
    5259            0 :         if (EngravingItem* pastedElement = selection()->element()) {
    5260            0 :             selectAndStartEditIfNeeded(pastedElement);
    5261              :         }
    5262              :     } else {
    5263            0 :         rollback();
    5264              :     }
    5265              : 
    5266            0 :     checkAndShowError();
    5267            0 : }
    5268              : 
    5269            0 : void NotationInteraction::pasteIntoTextEdit()
    5270              : {
    5271            0 :     const QMimeData* mimeData = QGuiApplication::clipboard()->mimeData();
    5272            0 :     if (mimeData->hasFormat(TextEditData::mimeRichTextFormat)) {
    5273            0 :         const QString txt = QString::fromUtf8(mimeData->data(TextEditData::mimeRichTextFormat));
    5274            0 :         toTextBase(m_editData.element)->paste(txt);
    5275            0 :         notifyAboutTextEditingChanged();
    5276            0 :         return;
    5277            0 :     }
    5278              : 
    5279            0 :     QString clipboardText = mimeData->text();
    5280            0 :     QString textForPaste = clipboardText;
    5281            0 :     if ((!clipboardText.startsWith('<') || !clipboardText.contains('>')) && m_editData.element->isLyrics()) {
    5282            0 :         textForPaste = extractSyllable(clipboardText);
    5283              :     }
    5284              : 
    5285            0 :     toTextBase(m_editData.element)->paste(textForPaste);
    5286            0 :     notifyAboutTextEditingChanged();
    5287              : 
    5288            0 :     if (textForPaste.isEmpty() || !m_editData.element->isLyrics()) {
    5289            0 :         return;
    5290              :     }
    5291              : 
    5292            0 :     if (textForPaste.endsWith('-')) {
    5293            0 :         navigateToNextSyllable();
    5294            0 :     } else if (textForPaste.endsWith('_')) {
    5295            0 :         addMelisma();
    5296              :     } else {
    5297            0 :         navigateToLyrics(false, false, false);
    5298              :     }
    5299              : 
    5300            0 :     const QString textForNextPaste = clipboardText.remove(0, clipboardText.indexOf(textForPaste) + textForPaste.size());
    5301            0 :     QGuiApplication::clipboard()->setText(textForNextPaste);
    5302            0 : }
    5303              : 
    5304            0 : void NotationInteraction::swapSelection()
    5305              : {
    5306            0 :     if (!selection()->canCopy()) {
    5307            0 :         return;
    5308              :     }
    5309              : 
    5310              :     // Store the old selection...
    5311            0 :     mu::engraving::Selection& selection = score()->selection();
    5312            0 :     QByteArray oldSelection = selection.mimeData().toQByteArray();
    5313              : 
    5314              :     // Paste using contents of clipboard...
    5315            0 :     pasteSelection();
    5316              : 
    5317              :     // Save old selection to clipboard...
    5318            0 :     QMimeData* mimeData = new QMimeData();
    5319            0 :     mimeData->setData(selection.mimeType(), oldSelection);
    5320            0 :     QGuiApplication::clipboard()->setMimeData(mimeData);
    5321            0 : }
    5322              : 
    5323            0 : void NotationInteraction::deleteSelection()
    5324              : {
    5325            0 :     if (selection()->isNone()) {
    5326            0 :         return;
    5327              :     }
    5328              : 
    5329            0 :     startEdit(TranslatableString("undoableAction", "Delete"));
    5330              : 
    5331            0 :     if (isTextEditingStarted()) {
    5332            0 :         mu::engraving::TextBase* textBase = toTextBase(m_editData.element);
    5333            0 :         if (!textBase->deleteSelectedText()) {
    5334            0 :             m_editData.key = Qt::Key_Backspace;
    5335            0 :             m_editData.modifiers = {};
    5336            0 :             if (textBase->edit(m_editData)) {
    5337            0 :                 notifyAboutTextEditingChanged();
    5338              :             }
    5339              :         } else {
    5340            0 :             notifyAboutTextEditingChanged();
    5341              :         }
    5342              :     } else {
    5343            0 :         doEndEditElement();
    5344            0 :         resetGripEdit();
    5345            0 :         score()->cmdDeleteSelection();
    5346              :     }
    5347              : 
    5348            0 :     checkAndShowError();
    5349            0 :     apply();
    5350            0 :     resetHitElementContext();
    5351              : }
    5352              : 
    5353            0 : void NotationInteraction::flipSelection()
    5354              : {
    5355            0 :     if (selection()->isNone()) {
    5356            0 :         return;
    5357              :     }
    5358              : 
    5359            0 :     transaction(TranslatableString("undoableAction", "Flip direction"), [&](auto& tx) {
    5360            0 :         Flip::flip(tx, score());
    5361            0 :     });
    5362              : 
    5363            0 :     updateGripAnchorLines();
    5364              : }
    5365              : 
    5366            0 : void NotationInteraction::flipSelectionHorizontally()
    5367              : {
    5368            0 :     if (selection()->isNone()) {
    5369            0 :         return;
    5370              :     }
    5371              : 
    5372            0 :     transaction(TranslatableString("undoableAction", "Flip horizontally"), [&](auto& tx) {
    5373            0 :         Flip::flipHorizontally(tx, score());
    5374            0 :     });
    5375              : }
    5376              : 
    5377            0 : void NotationInteraction::mirrorNotes()
    5378              : {
    5379            0 :     if (selection()->isNone()) {
    5380            0 :         return;
    5381              :     }
    5382              : 
    5383            0 :     transaction(TranslatableString("undoableAction", "Mirror notehead"), [&](auto& tx) {
    5384            0 :         Flip::mirrorNoteHead(tx, score());
    5385            0 :     });
    5386              : 
    5387            0 :     notifyAboutNotationChanged();
    5388              : }
    5389              : 
    5390            0 : void NotationInteraction::toggleTieForSelection()
    5391              : {
    5392              :     // Calls `startEdit` internally
    5393            0 :     Tie* newTie = mu::engraving::EditTie::cmdToggleTie(score());
    5394              : 
    5395            0 :     notifyAboutNotationChanged();
    5396            0 :     m_selection->selectionChanged().notify();
    5397              : 
    5398            0 :     if (newTie && newTie->tieJumpPoints() && newTie->tieJumpPoints()->size() > 1) {
    5399            0 :         selectAndStartEditIfNeeded(newTie);
    5400              :     }
    5401            0 : }
    5402              : 
    5403            0 : void NotationInteraction::addLaissezVibToSelection()
    5404              : {
    5405              :     // Calls `startEdit` internally
    5406            0 :     mu::engraving::EditTie::cmdToggleLaissezVib(score());
    5407              : 
    5408            0 :     notifyAboutNotationChanged();
    5409            0 :     m_selection->selectionChanged().notify();
    5410            0 : }
    5411              : 
    5412            0 : void NotationInteraction::addSlurToSelection()
    5413              : {
    5414            0 :     if (selection()->isNone()) {
    5415            0 :         return;
    5416              :     }
    5417              : 
    5418              :     // Calls `startEdit` internally
    5419            0 :     doAddSlur();
    5420            0 :     m_selection->selectionChanged().notify();
    5421              : }
    5422              : 
    5423            0 : void NotationInteraction::addHammerOnPullOffToSelection()
    5424              : {
    5425            0 :     if (selection()->isNone()) {
    5426            0 :         return;
    5427              :     }
    5428              : 
    5429            0 :     HammerOnPullOff* hopo = Factory::createHammerOnPullOff(score()->dummy());
    5430              :     // Calls `startEdit` internally
    5431            0 :     doAddSlur(hopo);
    5432              : }
    5433              : 
    5434            0 : void NotationInteraction::addOttavaToSelection(OttavaType type)
    5435              : {
    5436            0 :     if (selection()->isNone()) {
    5437            0 :         return;
    5438              :     }
    5439              : 
    5440            0 :     transaction(TranslatableString("undoableAction", "Add ottava"), [&](auto&) {
    5441            0 :         score()->cmdAddOttava(type);
    5442            0 :     });
    5443              : }
    5444              : 
    5445            0 : void NotationInteraction::addHairpinOnGripDrag(EditData& ed, bool isLeftGrip)
    5446              : {
    5447            0 :     startEdit(TranslatableString("undoableAction", "Add hairpin"));
    5448              : 
    5449            0 :     Dynamic* dynamic = toDynamic(ed.element);
    5450              : 
    5451            0 :     const PointF pos = m_dragData.ed.pos;
    5452            0 :     Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
    5453            0 :     Hairpin* hairpin = EditHairpin::addHairpinToDynamicOnGripDrag(tx, score(), dynamic, isLeftGrip, pos);
    5454              : 
    5455            0 :     if (!hairpin) {
    5456            0 :         rollback();
    5457            0 :         return;
    5458              :     }
    5459              : 
    5460            0 :     apply();
    5461              : 
    5462              :     // Reset grip offset to zero after drawing the hairpin
    5463            0 :     dynamic->resetRightDragOffset();
    5464              : 
    5465            0 :     IF_ASSERT_FAILED(!hairpin->segmentsEmpty()) {
    5466            0 :         return;
    5467              :     }
    5468              : 
    5469            0 :     if (isLeftGrip) {
    5470            0 :         LineSegment* segment = hairpin->frontSegment();
    5471            0 :         select({ segment });
    5472            0 :         startEditGrip(segment, Grip::START);
    5473              :     } else {
    5474            0 :         LineSegment* segment = hairpin->backSegment();
    5475            0 :         select({ segment });
    5476            0 :         startEditGrip(segment, Grip::END);
    5477              :     }
    5478              : 
    5479            0 :     ed.isHairpinDragCreatedFromDynamic = true;
    5480              : }
    5481              : 
    5482            0 : void NotationInteraction::addHairpinsToSelection(HairpinType type)
    5483              : {
    5484            0 :     if (selection()->isNone()) {
    5485            0 :         return;
    5486              :     }
    5487              : 
    5488            0 :     std::vector<mu::engraving::Hairpin*> hairpins;
    5489            0 :     transaction(TranslatableString("undoableAction", "Add hairpin"), [&](Transaction& tx) {
    5490            0 :         hairpins = EditHairpin::addHairpins(tx, score(), type);
    5491            0 :     });
    5492              : 
    5493            0 :     if (!noteInput()->isNoteInputMode() && hairpins.size() == 1) {
    5494            0 :         mu::engraving::LineSegment* segment = hairpins.front()->frontSegment();
    5495            0 :         select({ segment });
    5496            0 :         startEditGrip(segment, mu::engraving::Grip::END);
    5497              :     }
    5498            0 : }
    5499              : 
    5500            0 : void NotationInteraction::putRestToSelection()
    5501              : {
    5502            0 :     mu::engraving::InputState& is = score()->inputState();
    5503            0 :     if (!is.duration().isValid() || is.duration().isZero() || is.duration().isMeasure()) {
    5504            0 :         is.setDuration(DurationType::V_QUARTER);
    5505              :     }
    5506              : 
    5507            0 :     if (!m_noteInput->isNoteInputMode()) {
    5508            0 :         m_noteInput->startNoteInput(configuration()->defaultNoteInputMethod());
    5509              :     }
    5510              : 
    5511            0 :     if (is.usingNoteEntryMethod(NoteEntryMethod::BY_DURATION) || is.usingNoteEntryMethod(NoteEntryMethod::RHYTHM)) {
    5512            0 :         m_noteInput->toggleRest();
    5513            0 :     } else if (is.usingNoteEntryMethod(NoteEntryMethod::REPITCH)) {
    5514            0 :         ChordRest* cr = is.cr();
    5515            0 :         if (cr) {
    5516            0 :             TDuration duration = cr->durationType();
    5517            0 :             if (duration.isValid() && !duration.isZero()) {
    5518            0 :                 if (duration.isMeasure()) {
    5519            0 :                     is.moveToNextInputPos();
    5520              :                 } else {
    5521            0 :                     putRest(duration);
    5522              :                 }
    5523              :             }
    5524              :         }
    5525              :     } else {
    5526            0 :         putRest(is.duration());
    5527              :     }
    5528            0 : }
    5529              : 
    5530            0 : void NotationInteraction::putRest(Duration duration)
    5531              : {
    5532            0 :     if (selection()->isNone()) {
    5533            0 :         return;
    5534              :     }
    5535              : 
    5536              :     // Calls `startEdit` internally
    5537            0 :     score()->cmdEnterRest(duration);
    5538              : 
    5539            0 :     notifyAboutNotationChanged();
    5540              : }
    5541              : 
    5542            0 : void NotationInteraction::addBracketsToSelection(BracketsType type)
    5543              : {
    5544            0 :     if (selection()->isNone()) {
    5545            0 :         return;
    5546              :     }
    5547              : 
    5548            0 :     switch (type) {
    5549            0 :     case BracketsType::Brackets:
    5550            0 :         transaction(TranslatableString("undoableAction", "Add brackets"), [&](auto& tx) {
    5551            0 :             EditAccidentalBrackets::addBracket(tx, score());
    5552            0 :         });
    5553            0 :         break;
    5554            0 :     case BracketsType::Braces:
    5555            0 :         transaction(TranslatableString("undoableAction", "Add braces"), [&](auto& tx) {
    5556            0 :             EditAccidentalBrackets::addBraces(tx, score());
    5557            0 :         });
    5558            0 :         break;
    5559            0 :     case BracketsType::Parentheses:
    5560            0 :         transaction(TranslatableString("undoableAction", "Add parentheses"), [&](auto& tx) {
    5561            0 :             EditParentheses::toggleParentheses(tx, score());
    5562            0 :         });
    5563            0 :         break;
    5564              :     }
    5565              : }
    5566              : 
    5567            0 : void NotationInteraction::toggleAccidentalForSelection(AccidentalType type)
    5568              : {
    5569            0 :     if (selection()->isNone()) {
    5570            0 :         return;
    5571              :     }
    5572              : 
    5573            0 :     bool accidentalAlreadyAdded = false;
    5574            0 :     for (const EngravingItem* item : score()->selection().elements()) {
    5575            0 :         if (!item->isNote()) {
    5576            0 :             continue;
    5577              :         }
    5578              : 
    5579            0 :         if (toNote(item)->accidentalType() != type) {
    5580            0 :             accidentalAlreadyAdded = false;
    5581            0 :             break;
    5582              :         }
    5583              : 
    5584            0 :         accidentalAlreadyAdded = true;
    5585              :     }
    5586              : 
    5587            0 :     transaction(TranslatableString("undoableAction", "Toggle accidental"), [&](auto&) {
    5588            0 :         if (accidentalAlreadyAdded) {
    5589            0 :             mu::engraving::EditNote::changeAccidental(score(), AccidentalType::NONE);
    5590              :         } else {
    5591            0 :             mu::engraving::EditNote::changeAccidental(score(), type);
    5592              :         }
    5593            0 :     });
    5594              : }
    5595              : 
    5596            0 : void NotationInteraction::toggleArticulationForSelection(SymbolId articulationSymbolId)
    5597              : {
    5598            0 :     if (selection()->isNone()) {
    5599            0 :         return;
    5600              :     }
    5601              : 
    5602            0 :     std::vector<mu::engraving::Note*> notes = score()->selection().noteList();
    5603            0 :     if (notes.empty()) {
    5604              :         // no notes, but maybe they have an articulation selected. we should use that chord
    5605            0 :         EngravingItem* e = score()->selection().element();
    5606            0 :         if (e && e->isArticulationFamily()) {
    5607            0 :             Chord* c = toChord(toArticulation(e)->ownershipParent());
    5608            0 :             if (c) {
    5609            0 :                 notes.insert(notes.begin(), c->notes().begin(), c->notes().end());
    5610              :             }
    5611              :         }
    5612              :     }
    5613              : 
    5614            0 :     auto updateMode = notesHaveActiculation(notes, articulationSymbolId)
    5615            0 :                       ? mu::engraving::ArticulationsUpdateMode::Remove : mu::engraving::ArticulationsUpdateMode::Insert;
    5616              : 
    5617            0 :     std::set<Chord*> chords;
    5618            0 :     for (Note* note: notes) {
    5619            0 :         if (note->isTrillCueNote()) {
    5620            0 :             return;
    5621              :         }
    5622            0 :         Chord* chord = note->chord();
    5623            0 :         if (chords.find(chord) == chords.end()) {
    5624            0 :             chords.insert(chord);
    5625              :         }
    5626              :     }
    5627              : 
    5628            0 :     startEdit(TranslatableString("undoableAction", "Toggle articulation"));
    5629            0 :     for (Chord* chord: chords) {
    5630            0 :         chord->updateArticulations({ articulationSymbolId }, updateMode);
    5631              :     }
    5632            0 :     apply();
    5633            0 : }
    5634              : 
    5635            0 : void NotationInteraction::toggleDotsForSelection(int dots)
    5636              : {
    5637            0 :     IF_ASSERT_FAILED(dots >= 1 && dots <= 4) {
    5638            0 :         return;
    5639              :     }
    5640              : 
    5641            0 :     if (selection()->isNone()) {
    5642            0 :         return;
    5643              :     }
    5644              : 
    5645            0 :     transaction(TranslatableString("undoableAction", "Toggle augmentation dots"), [&](auto& tx) {
    5646            0 :         NoteInput::toggleDots(tx, score(), dots, true /*toggleForSelectionOnly*/);
    5647            0 :     });
    5648              : 
    5649            0 :     notifyAboutNotationChanged();
    5650              : }
    5651              : 
    5652            0 : void NotationInteraction::addGraceNotesToSelectedNotes(GraceNoteType type)
    5653              : {
    5654            0 :     if (selection()->isNone()) {
    5655            0 :         return;
    5656              :     }
    5657              : 
    5658            0 :     int denominator = 1;
    5659              : 
    5660            0 :     switch (type) {
    5661            0 :     case GraceNoteType::GRACE4:
    5662              :     case GraceNoteType::INVALID:
    5663              :     case GraceNoteType::NORMAL:
    5664            0 :         denominator = 1;
    5665            0 :         break;
    5666            0 :     case GraceNoteType::ACCIACCATURA:
    5667              :     case GraceNoteType::APPOGGIATURA:
    5668              :     case GraceNoteType::GRACE8_AFTER:
    5669            0 :         denominator = 2;
    5670            0 :         break;
    5671            0 :     case GraceNoteType::GRACE16:
    5672              :     case GraceNoteType::GRACE16_AFTER:
    5673            0 :         denominator = 4;
    5674            0 :         break;
    5675            0 :     case GraceNoteType::GRACE32:
    5676              :     case GraceNoteType::GRACE32_AFTER:
    5677            0 :         denominator = 8;
    5678            0 :         break;
    5679              :     }
    5680              : 
    5681            0 :     startEdit(TranslatableString("undoableAction", "Add grace note"));
    5682            0 :     score()->cmdAddGrace(type, mu::engraving::Constants::DIVISION / denominator);
    5683            0 :     apply();
    5684              : }
    5685              : 
    5686            0 : bool NotationInteraction::canAddTupletToSelectedChordRests() const
    5687              : {
    5688            0 :     for (ChordRest* chordRest : score()->getSelectedChordRests()) {
    5689            0 :         if (chordRest->isGrace() || (chordRest->isChord() && toChord(chordRest)->isTrillCueNote())) {
    5690            0 :             continue;
    5691              :         }
    5692              : 
    5693            0 :         if (chordRest->durationType() < mu::engraving::TDuration(mu::engraving::DurationType::V_512TH)
    5694            0 :             && chordRest->durationType() != mu::engraving::TDuration(mu::engraving::DurationType::V_MEASURE)) {
    5695            0 :             return false;
    5696              :         }
    5697            0 :     }
    5698              : 
    5699            0 :     return true;
    5700              : }
    5701              : 
    5702            0 : void NotationInteraction::addTupletToSelectedChordRests(const TupletOptions& options)
    5703              : {
    5704            0 :     if (selection()->isNone()) {
    5705            0 :         return;
    5706              :     }
    5707              : 
    5708            0 :     if (options.selectedDurationAs == TupletSelectedDuration::SUBDIVISION) {
    5709            0 :         if (!EditTuplet::checkAddTupletsWithSelectedDurations(score(), options.ratio)) {
    5710            0 :             checkAndShowError();
    5711            0 :             return;
    5712              :         }
    5713            0 :     } else if (!canAddTupletToSelectedChordRests()) {
    5714            0 :         MScore::setError(MsError::TUPLET_NOTE_VALUE_TOO_SHORT);
    5715            0 :         checkAndShowError();
    5716            0 :         return;
    5717              :     }
    5718              : 
    5719            0 :     transaction(TranslatableString("undoableAction", "Add tuplet"), [&](mu::engraving::Transaction& tx) {
    5720            0 :         EditTuplet::addTuplets(tx, score(), options.ratio, options.numberType, options.bracketType, options.autoBaseLen,
    5721            0 :                                options.selectedDurationAs);
    5722            0 :     });
    5723            0 :     notifyAboutNotationChanged();
    5724              : }
    5725              : 
    5726            0 : void NotationInteraction::addBeamToSelectedChordRests(BeamMode mode)
    5727              : {
    5728            0 :     if (selection()->isNone()) {
    5729            0 :         return;
    5730              :     }
    5731              : 
    5732            0 :     transaction(TranslatableString("undoableAction", "Set beam type"), [&](Transaction& tx) {
    5733            0 :         EditBeam::setBeamMode(tx, score(), mode);
    5734            0 :     });
    5735            0 :     notifyAboutNotationChanged();
    5736              : }
    5737              : 
    5738            0 : void NotationInteraction::beamSelectedRange()
    5739              : {
    5740            0 :     if (selection()->isNone()) {
    5741            0 :         return;
    5742              :     }
    5743              : 
    5744            0 :     transaction(TranslatableString("undoableAction", "Beam selected"), [&](Transaction& tx) {
    5745            0 :         EditBeam::beamSelectedRange(tx, score());
    5746            0 :     });
    5747            0 :     notifyAboutNotationChanged();
    5748              : }
    5749              : 
    5750            0 : void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots)
    5751              : {
    5752            0 :     if (selection()->isNone()) {
    5753            0 :         return;
    5754              :     }
    5755              : 
    5756            0 :     m_undoStack->transaction(steps > 0 // negative: increase, positive: decrease
    5757            0 :                              ? TranslatableString("undoableAction", "Decrease duration")
    5758              :                              : TranslatableString("undoableAction", "Increase duration"),
    5759            0 :                              [&](Transaction& tx) {
    5760            0 :         EditDuration::incDecDuration(tx, score(), steps, stepByDots);
    5761            0 :     });
    5762            0 :     notifyAboutNotationChanged();
    5763              : }
    5764              : 
    5765            0 : void NotationInteraction::increaseDecreaseSelectedDynamicsValues(int delta)
    5766              : {
    5767            0 :     if (selection()->isNone()) {
    5768            0 :         return;
    5769              :     }
    5770              : 
    5771            0 :     const auto inRange = [](DynamicType type) {
    5772            0 :         return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF;
    5773              :     };
    5774              : 
    5775            0 :     std::map<Dynamic*, /*newType*/ DynamicType> dynamicsMap;
    5776              : 
    5777              :     // Collect valid dynamics before trying anything (avoid unnecessary calls to startEdit etc)...
    5778            0 :     for (EngravingItem* item : selection()->elements()) {
    5779            0 :         if (!item->isDynamic()) {
    5780            0 :             continue;
    5781              :         }
    5782            0 :         Dynamic* dynamic = toDynamic(item);
    5783            0 :         DynamicType newType = static_cast<DynamicType>(static_cast<int>(dynamic->dynamicType()) + delta);
    5784            0 :         if (inRange(newType)) {
    5785            0 :             dynamicsMap.emplace(dynamic, newType);
    5786              :         }
    5787              :     }
    5788              : 
    5789            0 :     if (dynamicsMap.empty()) {
    5790            0 :         return;
    5791              :     }
    5792              : 
    5793            0 :     startEdit(delta > 0 // positive: increase dynamics, negative: decrease dynamics
    5794            0 :               ? TranslatableString("undoableAction", "Increase dynamics")
    5795              :               : TranslatableString("undoableAction", "Decrease dynamics"));
    5796              : 
    5797            0 :     for (const auto& [dynamic, newType] : dynamicsMap) {
    5798            0 :         dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType);
    5799            0 :         dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType));
    5800            0 :         doAutoFlipHairpinsType(dynamic);
    5801              :     }
    5802              : 
    5803            0 :     apply();
    5804            0 : }
    5805              : 
    5806            0 : void NotationInteraction::autoFlipHairpinsType(Dynamic* dynamic)
    5807              : {
    5808            0 :     if (!dynamic || dynamic->dynamicType() == DynamicType::OTHER || dynamic->dynamicType() >= DynamicType::FP) {
    5809            0 :         return;
    5810              :     }
    5811            0 :     startEdit(TranslatableString("undoableAction", "Change hairpin type"));
    5812            0 :     doAutoFlipHairpinsType(dynamic);
    5813            0 :     apply();
    5814              : }
    5815              : 
    5816            0 : void NotationInteraction::doAutoFlipHairpinsType(Dynamic* dynamic)
    5817              : {
    5818            0 :     dynamic->findAdjacentHairpins();
    5819            0 :     if (Hairpin* leftHp = dynamic->leftHairpin()) {
    5820            0 :         const Dynamic* startDyn = leftHp->dynamicSnappedBefore();
    5821            0 :         if (startDyn
    5822            0 :             && !(startDyn->dynamicType() == DynamicType::OTHER || startDyn->dynamicType() >= DynamicType::FP)
    5823            0 :             && !leftHp->isLineType()) {
    5824            0 :             if (int(startDyn->dynamicType()) > int(dynamic->dynamicType())) {
    5825            0 :                 leftHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DIM_HAIRPIN));
    5826              :             } else {
    5827            0 :                 leftHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_HAIRPIN));
    5828              :             }
    5829              :         }
    5830              :     }
    5831            0 :     if (Hairpin* rightHp = dynamic->rightHairpin()) {
    5832            0 :         const Dynamic* endDyn = rightHp->dynamicSnappedAfter();
    5833            0 :         if (endDyn
    5834            0 :             && !(endDyn->dynamicType() == DynamicType::OTHER || endDyn->dynamicType() >= DynamicType::FP)
    5835            0 :             && !rightHp->isLineType()) {
    5836            0 :             if (int(endDyn->dynamicType()) > int(dynamic->dynamicType())) {
    5837            0 :                 rightHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::CRESC_HAIRPIN));
    5838              :             } else {
    5839            0 :                 rightHp->undoChangeProperty(Pid::HAIRPIN_TYPE, int(HairpinType::DIM_HAIRPIN));
    5840              :             }
    5841              :         }
    5842              :     }
    5843            0 : }
    5844              : 
    5845            0 : void NotationInteraction::toggleDynamicPopup()
    5846              : {
    5847            0 :     EngravingItem* el = selection()->element();
    5848            0 :     if (!el) {
    5849            0 :         return;
    5850              :     }
    5851            0 :     if (!el->isHairpinSegment()) {
    5852            0 :         addTextToItem(TextStyleType::DYNAMICS, el);
    5853            0 :         return;
    5854              :     }
    5855              : 
    5856            0 :     HairpinSegment* hairpinSeg = toHairpinSegment(el);
    5857            0 :     Hairpin* hairpin = hairpinSeg->hairpin();
    5858              : 
    5859            0 :     auto addDynamic = [this](Fraction tick, track_idx_t track, VoiceAssignment voiceAssignment) {
    5860            0 :         endEditElement();
    5861            0 :         startEdit(TranslatableString("undoableAction", "Add dynamic"));
    5862            0 :         Measure* measure = score()->tick2measure(tick);
    5863            0 :         Segment* segment = measure->undoGetChordRestOrTimeTickSegment(tick);
    5864            0 :         Dynamic* dynamic = Factory::createDynamic(segment);
    5865            0 :         dynamic->setOwnershipParent(segment);
    5866            0 :         dynamic->setTrack(track);
    5867            0 :         dynamic->setVoiceAssignment(voiceAssignment);
    5868            0 :         score()->undoAddElement(dynamic);
    5869            0 :         apply();
    5870            0 :         startEditText(dynamic);
    5871            0 :     };
    5872              :     // if the selected grip is at the left end, place the dynamic at the start
    5873            0 :     if (m_editData.curGrip == Grip::START || (m_editData.curGrip == Grip::APERTURE && hairpin->isDecrescendo())) {
    5874            0 :         if (EngravingItem* startDynOrExp = hairpinSeg->findElementToSnapBefore()) {
    5875              :             // If there is already a dynamic, select it instead of opening an empty popup
    5876            0 :             select({ startDynOrExp });
    5877            0 :             if (startDynOrExp->isDynamic()) {
    5878            0 :                 autoFlipHairpinsType(toDynamic(startDynOrExp));
    5879              :             }
    5880              :         } else {
    5881            0 :             addDynamic(hairpin->tick(), hairpin->track(), hairpin->voiceAssignment());
    5882              :         }
    5883              :     } else {
    5884            0 :         if (EngravingItem* endDynOrExp = hairpinSeg->findElementToSnapAfter()) {
    5885              :             // If there is already a dynamic, select it instead of opening an empty popup
    5886            0 :             select({ endDynOrExp });
    5887            0 :             if (endDynOrExp->isDynamic()) {
    5888            0 :                 autoFlipHairpinsType(toDynamic(endDynOrExp));
    5889              :             }
    5890              :         } else {
    5891            0 :             addDynamic(hairpin->tick2(), hairpin->track2(), hairpin->voiceAssignment());
    5892              :         }
    5893              :     }
    5894              : }
    5895              : 
    5896            0 : bool NotationInteraction::toggleLayoutBreakAvailable() const
    5897              : {
    5898            0 :     return !selection()->isNone() && !isTextEditingStarted();
    5899              : }
    5900              : 
    5901            0 : void NotationInteraction::toggleLayoutBreak(LayoutBreakType breakType)
    5902              : {
    5903            0 :     transaction(TranslatableString("undoableAction", "Toggle layout break"), [&](auto&) {
    5904            0 :         score()->cmdToggleLayoutBreak(breakType);
    5905            0 :     });
    5906            0 : }
    5907              : 
    5908            0 : void NotationInteraction::moveMeasureToPrevSystem()
    5909              : {
    5910            0 :     MeasureBase* m = score()->selection().endMeasureBase();
    5911            0 :     if (!m) {
    5912            0 :         return;
    5913              :     }
    5914            0 :     transaction(TranslatableString("undoableAction", "Move measure to previous system"), [&](auto& tx) {
    5915            0 :         EditSystemLocks::moveMeasureToPrevSystem(tx, score(), m);
    5916            0 :     });
    5917              : }
    5918              : 
    5919            0 : void NotationInteraction::moveMeasureToNextSystem()
    5920              : {
    5921            0 :     MeasureBase* m = score()->selection().startMeasureBase();
    5922            0 :     if (!m) {
    5923            0 :         return;
    5924              :     }
    5925            0 :     transaction(TranslatableString("undoableAction", "Move measure to next system"), [&](auto& tx) {
    5926            0 :         EditSystemLocks::moveMeasureToNextSystem(tx, score(), m);
    5927            0 :     });
    5928              : }
    5929              : 
    5930            0 : void NotationInteraction::toggleSystemLock()
    5931              : {
    5932            0 :     transaction(TranslatableString("undoableAction", "Lock/unlock selected system(s)"), [&](auto& tx) {
    5933            0 :         EditSystemLocks::toggleSystemLock(tx, score(), selection()->selectedSystems());
    5934            0 :     });
    5935            0 : }
    5936              : 
    5937            0 : void NotationInteraction::toggleScoreLock()
    5938              : {
    5939            0 :     transaction(TranslatableString("undoableAction", "Lock/unlock all systems"), [&](auto& tx) {
    5940            0 :         EditSystemLocks::toggleScoreLock(tx, score());
    5941            0 :     });
    5942            0 : }
    5943              : 
    5944            0 : void NotationInteraction::makeIntoSystem()
    5945              : {
    5946            0 :     MeasureBase* first = score()->selection().startMeasureBase();
    5947            0 :     MeasureBase* last = score()->selection().endMeasureBase();
    5948            0 :     if (!first || !last) {
    5949            0 :         return;
    5950              :     }
    5951              : 
    5952            0 :     transaction(TranslatableString("undoableAction", "Create system from selection"), [&](auto& tx) {
    5953            0 :         EditSystemLocks::makeIntoSystem(tx, score(), first, last);
    5954            0 :     });
    5955              : }
    5956              : 
    5957            0 : void NotationInteraction::applySystemLock()
    5958              : {
    5959            0 :     transaction(TranslatableString("undoableAction", "Apply system lock to selection"), [&](auto& tx) {
    5960            0 :         EditSystemLocks::applyLockToSelection(tx, score());
    5961            0 :     });
    5962            0 : }
    5963              : 
    5964            0 : void NotationInteraction::moveSystemToPrevPage()
    5965              : {
    5966            0 :     MeasureBase* firstMeas = score()->selection().startMeasureBase();
    5967            0 :     MeasureBase* endMeas = score()->selection().endMeasureBase();
    5968            0 :     System* startSys = firstMeas ? firstMeas->system() : nullptr;
    5969            0 :     System* endSys = endMeas ? endMeas->system() : nullptr;
    5970            0 :     if (!startSys || !endSys) {
    5971            0 :         return;
    5972              :     }
    5973            0 :     MeasureBase* firstMb = startSys->first();
    5974            0 :     MeasureBase* lastMb = endSys->last();
    5975            0 :     transaction(TranslatableString("undoableAction", "Move system to previous page"), [&](auto& tx) {
    5976            0 :         EditPageLocks::moveMeasuresToPrevPage(tx, score(), firstMb, lastMb);
    5977            0 :     });
    5978              : }
    5979              : 
    5980            0 : void NotationInteraction::moveSystemToNextPage()
    5981              : {
    5982            0 :     MeasureBase* firstMeas = score()->selection().startMeasureBase();
    5983            0 :     MeasureBase* endMeas = score()->selection().endMeasureBase();
    5984            0 :     System* startSys = firstMeas ? firstMeas->system() : nullptr;
    5985            0 :     System* endSys = endMeas ? endMeas->system() : nullptr;
    5986            0 :     if (!startSys || !endSys) {
    5987            0 :         return;
    5988              :     }
    5989            0 :     MeasureBase* firstMb = startSys->first();
    5990            0 :     MeasureBase* lastMb = endSys->last();
    5991            0 :     transaction(TranslatableString("undoableAction", "Move system to next page"), [&](auto& tx) {
    5992            0 :         EditPageLocks::moveMeasuresToNextPage(tx, score(), firstMb, lastMb);
    5993            0 :     });
    5994              : }
    5995              : 
    5996            0 : void NotationInteraction::togglePageLock()
    5997              : {
    5998            0 :     transaction(TranslatableString("undoableAction", "Lock/unlock selected page(s)"), [&](auto& tx) {
    5999            0 :         EditPageLocks::togglePageLock(tx, score(), selection()->pagesContainingSelection());
    6000            0 :     });
    6001            0 : }
    6002              : 
    6003            0 : void NotationInteraction::makeIntoPage()
    6004              : {
    6005            0 :     MeasureBase* first = score()->selection().startMeasureBase();
    6006            0 :     MeasureBase* last = score()->selection().endMeasureBase();
    6007            0 :     if (!first || !last) {
    6008            0 :         return;
    6009              :     }
    6010              : 
    6011            0 :     transaction(TranslatableString("undoableAction", "Create page from selection"), [&](auto& tx) {
    6012            0 :         EditPageLocks::makeIntoPage(tx, score(), first, last);
    6013            0 :     });
    6014              : }
    6015              : 
    6016            0 : void NotationInteraction::applyPageLock()
    6017              : {
    6018            0 :     transaction(TranslatableString("undoableAction", "Apply page lock to selection"), [&](auto& tx) {
    6019            0 :         EditPageLocks::applyLockToSelection(tx, score());
    6020            0 :     });
    6021            0 : }
    6022              : 
    6023            0 : void NotationInteraction::addRemoveSystemLocks(AddRemoveSystemLockType intervalType, int interval)
    6024              : {
    6025            0 :     interval = intervalType == AddRemoveSystemLockType::MeasuresInterval ? interval : 0;
    6026            0 :     bool afterEachSystem = intervalType == AddRemoveSystemLockType::AfterEachSystem;
    6027              : 
    6028            0 :     transaction(TranslatableString("undoableAction", "Measures per system"), [&](auto& tx) {
    6029            0 :         EditSystemLocks::addRemoveSystemLocks(tx, score(), interval, afterEachSystem);
    6030            0 :     });
    6031            0 : }
    6032              : 
    6033            0 : bool NotationInteraction::transpose(const TransposeOptions& options)
    6034              : {
    6035            0 :     bool ok = false;
    6036              : 
    6037            0 :     transaction(TranslatableString("undoableAction", "Transposition"), [&](auto& tx) {
    6038            0 :         ok = Transpose::transpose(tx, score(), options.mode, options.direction, options.key, options.interval,
    6039            0 :                                   options.needTransposeKeys, options.needTransposeChordNames, options.needTransposeDoubleSharpsFlats);
    6040            0 :     });
    6041              : 
    6042            0 :     return ok;
    6043              : }
    6044              : 
    6045            0 : void NotationInteraction::swapVoices(voice_idx_t voiceIndex1, voice_idx_t voiceIndex2)
    6046              : {
    6047            0 :     if (selection()->isNone()) {
    6048            0 :         return;
    6049              :     }
    6050              : 
    6051            0 :     if (voiceIndex1 == voiceIndex2) {
    6052            0 :         return;
    6053              :     }
    6054              : 
    6055            0 :     if (!isVoiceIndexValid(voiceIndex1) || !isVoiceIndexValid(voiceIndex2)) {
    6056            0 :         return;
    6057              :     }
    6058              : 
    6059            0 :     startEdit(TranslatableString("undoableAction", "Swap voices"));
    6060            0 :     ExchangeVoices::exchangeVoicesInSelection(score(), voiceIndex1, voiceIndex2);
    6061            0 :     apply();
    6062              : }
    6063              : 
    6064            0 : void NotationInteraction::addIntervalToSelectedNotes(int interval)
    6065              : {
    6066            0 :     if (!isNotesIntervalValid(interval)) {
    6067            0 :         return;
    6068              :     }
    6069              : 
    6070            0 :     std::vector<Note*> notes;
    6071              : 
    6072            0 :     if (score()->selection().isRange()) {
    6073            0 :         const bool hasMultiNoteChords = score()->selection().rangeContainsMultiNoteChords();
    6074            0 :         for (const ChordRest* chordRest : score()->getSelectedChordRests()) {
    6075            0 :             if (!chordRest->isChord()) {
    6076            0 :                 continue;
    6077              :             }
    6078            0 :             const Chord* chord = toChord(chordRest);
    6079            0 :             const std::vector<Note*> nl = chord->notes();
    6080            0 :             if (interval > 0) {
    6081            0 :                 for (size_t noteIdx = nl.size(); noteIdx > 0; --noteIdx) {
    6082            0 :                     if (score()->selectionFilter().canSelectNoteIdx(noteIdx - 1, nl.size(), hasMultiNoteChords)) {
    6083            0 :                         notes.push_back(nl.at(noteIdx - 1));
    6084            0 :                         break;
    6085              :                     }
    6086              :                 }
    6087              :             } else {
    6088            0 :                 for (size_t noteIdx = 0; noteIdx < nl.size(); ++noteIdx) {
    6089            0 :                     if (score()->selectionFilter().canSelectNoteIdx(noteIdx, nl.size(), hasMultiNoteChords)) {
    6090            0 :                         notes.push_back(nl.at(noteIdx));
    6091            0 :                         break;
    6092              :                     }
    6093              :                 }
    6094              :             }
    6095            0 :         }
    6096              :     } else {
    6097            0 :         notes = score()->selection().noteList();
    6098              :     }
    6099              : 
    6100            0 :     if (notes.empty()) {
    6101            0 :         MScore::setError(MsError::NO_NOTE_SELECTED);
    6102            0 :         checkAndShowError();
    6103            0 :         return;
    6104              :     }
    6105              : 
    6106            0 :     startEdit(TranslatableString("undoableAction", "Add interval"));
    6107            0 :     score()->addInterval(interval, notes);
    6108            0 :     apply();
    6109            0 : }
    6110              : 
    6111            0 : void NotationInteraction::addFret(int fretIndex)
    6112              : {
    6113            0 :     transaction(TranslatableString("undoableAction", "Enter note at fret %1").arg(fretIndex), [&](auto& tx) {
    6114            0 :         NoteInput::addFret(tx, score(), fretIndex);
    6115            0 :     });
    6116              : 
    6117            0 :     notifyAboutNotationChanged();
    6118            0 : }
    6119              : 
    6120            0 : void NotationInteraction::changeSelectedElementsVoice(voice_idx_t voiceIndex)
    6121              : {
    6122            0 :     if (selection()->isNone()) {
    6123            0 :         return;
    6124              :     }
    6125              : 
    6126            0 :     if (!isVoiceIndexValid(voiceIndex)) {
    6127            0 :         return;
    6128              :     }
    6129              : 
    6130            0 :     startEdit(TranslatableString("undoableAction", "Change voice"));
    6131            0 :     EditVoice::changeSelectedElementsVoice(score()->transactionManager()->currentOrDummyTransaction(), score(), voiceIndex);
    6132            0 :     apply();
    6133              : }
    6134              : 
    6135            0 : void NotationInteraction::changeSelectedElementsVoiceAssignment(VoiceAssignment voiceAssignment)
    6136              : {
    6137            0 :     if (selection()->isNone()) {
    6138            0 :         return;
    6139              :     }
    6140              : 
    6141            0 :     startEdit(TranslatableString("undoableAction", "Change voice assignment"));
    6142            0 :     EditVoice::changeSelectedElementsVoiceAssignment(score()->transactionManager()->currentOrDummyTransaction(), score(),
    6143              :                                                      voiceAssignment);
    6144            0 :     apply();
    6145              : }
    6146              : 
    6147            0 : void NotationInteraction::addAnchoredLineToSelectedNotes()
    6148              : {
    6149            0 :     if (selection()->isNone()) {
    6150            0 :         return;
    6151              :     }
    6152              : 
    6153            0 :     startEdit(TranslatableString("undoableAction", "Add note-anchored line"));
    6154            0 :     score()->addNoteLine();
    6155            0 :     apply();
    6156              : }
    6157              : 
    6158            0 : void NotationInteraction::addTextToTopFrame(TextStyleType type)
    6159              : {
    6160            0 :     addText(type);
    6161            0 : }
    6162              : 
    6163            0 : bool NotationInteraction::canAddTextToItem(TextStyleType type, const EngravingItem* item) const
    6164              : {
    6165            0 :     if (isVerticalBoxTextStyle(type)) {
    6166            0 :         return item && item->isVBox();
    6167              :     }
    6168              : 
    6169            0 :     if (type == TextStyleType::FRAME) {
    6170            0 :         return item && item->isBox() ? muse::make_ok() : make_ret(Err::EmptySelection);
    6171              :     }
    6172              : 
    6173              :     static const std::set<TextStyleType> harmonyTypes {
    6174              :         TextStyleType::HARMONY_A,
    6175              :         TextStyleType::HARMONY_ROMAN,
    6176              :         TextStyleType::HARMONY_NASHVILLE,
    6177            0 :     };
    6178              : 
    6179            0 :     const bool isHarmony = muse::contains(harmonyTypes, type);
    6180            0 :     if (isHarmony && item && item->isFretDiagram()) {
    6181            0 :         return true;
    6182              :     }
    6183              : 
    6184              :     static const std::set<TextStyleType> needSelectNoteOrRestTypes {
    6185              :         TextStyleType::SYSTEM,
    6186              :         TextStyleType::STAFF,
    6187              :         TextStyleType::DYNAMICS,
    6188              :         TextStyleType::EXPRESSION,
    6189              :         TextStyleType::REHEARSAL_MARK,
    6190              :         TextStyleType::INSTRUMENT_CHANGE,
    6191              :         TextStyleType::FINGERING,
    6192              :         TextStyleType::LH_GUITAR_FINGERING,
    6193              :         TextStyleType::RH_GUITAR_FINGERING,
    6194              :         TextStyleType::STRING_NUMBER,
    6195              :         TextStyleType::STICKING,
    6196              :         TextStyleType::HARMONY_A,
    6197              :         TextStyleType::HARMONY_ROMAN,
    6198              :         TextStyleType::HARMONY_NASHVILLE,
    6199              :         TextStyleType::LYRICS_ODD,
    6200              :         TextStyleType::TEMPO,
    6201            0 :     };
    6202              : 
    6203            0 :     if (muse::contains(needSelectNoteOrRestTypes, type)) {
    6204              :         static const std::set<ElementType> requiredElementTypes {
    6205              :             ElementType::NOTE,
    6206              :             ElementType::REST,
    6207              :             ElementType::MMREST,
    6208              :             ElementType::MEASURE_REPEAT,
    6209              :             ElementType::CHORD,
    6210            0 :         };
    6211              : 
    6212            0 :         return item && muse::contains(requiredElementTypes, item->type());
    6213              :     }
    6214              : 
    6215            0 :     return true;
    6216              : }
    6217              : 
    6218            0 : void NotationInteraction::addTextToItem(TextStyleType type, EngravingItem* item)
    6219              : {
    6220            0 :     if (!scoreHasMeasure()) {
    6221            0 :         LOGE() << "Need to create measure";
    6222            0 :         return;
    6223              :     }
    6224              : 
    6225            0 :     if (!canAddTextToItem(type, item)) {
    6226            0 :         MScore::setError(MsError::NO_NOTE_REST_SELECTED);
    6227            0 :         checkAndShowError();
    6228            0 :         return;
    6229              :     }
    6230              : 
    6231            0 :     addText(type, item);
    6232              : }
    6233              : 
    6234            0 : void NotationInteraction::addText(TextStyleType type, EngravingItem* item)
    6235              : {
    6236            0 :     if (m_noteInput->isNoteInputMode()) {
    6237            0 :         m_noteInput->endNoteInput();
    6238              :     }
    6239              : 
    6240            0 :     startEdit(TranslatableString("undoableAction", "Add text"));
    6241            0 :     mu::engraving::TextBase* text = score()->addText(type, item);
    6242              : 
    6243            0 :     if (!text) {
    6244            0 :         rollback();
    6245            0 :         return;
    6246              :     }
    6247              : 
    6248            0 :     if (text->isInstrumentChange()) {
    6249            0 :         if (!selectInstrument(toInstrumentChange(text))) {
    6250            0 :             rollback();
    6251            0 :             return;
    6252              :         }
    6253              :     }
    6254              : 
    6255            0 :     if (text->hasVoiceAssignmentProperties()) {
    6256            0 :         text->setInitialTrackAndVoiceAssignment(item->track(), false);
    6257              :     }
    6258              : 
    6259            0 :     apply();
    6260            0 :     showItem(text);
    6261              : 
    6262            0 :     if (!text->isInstrumentChange()) {
    6263            0 :         startEditText(text);
    6264              :     }
    6265              : 
    6266            0 :     if (text->isRehearsalMark() || text->isTempoText()) {
    6267            0 :         text->cursor()->selectWord();
    6268              :     }
    6269              : }
    6270              : 
    6271            0 : Ret NotationInteraction::canAddImageToItem(const EngravingItem* item) const
    6272              : {
    6273            0 :     return item && item->isMeasureBase();
    6274              : }
    6275              : 
    6276            0 : void NotationInteraction::addImageToItem(const muse::io::path_t& imagePath, EngravingItem* item)
    6277              : {
    6278            0 :     if (imagePath.empty() || !item) {
    6279            0 :         return;
    6280              :     }
    6281              : 
    6282              :     static const std::map<muse::io::path_t, ImageType> suffixToType {
    6283            0 :         { "svg", ImageType::SVG },
    6284            0 :         { "svgz", ImageType::SVG },
    6285            0 :         { "jpg", ImageType::RASTER },
    6286            0 :         { "jpeg", ImageType::RASTER },
    6287            0 :         { "png", ImageType::RASTER },
    6288            0 :         { "bmp", ImageType::RASTER },
    6289            0 :         { "tif", ImageType::RASTER },
    6290            0 :         { "tiff", ImageType::RASTER },
    6291            0 :     };
    6292              : 
    6293            0 :     muse::io::path_t suffix = io::suffix(imagePath);
    6294              : 
    6295            0 :     ImageType type = muse::value(suffixToType, suffix, ImageType::NONE);
    6296            0 :     if (type == ImageType::NONE) {
    6297            0 :         return;
    6298              :     }
    6299              : 
    6300            0 :     Image* image = Factory::createImage(item);
    6301            0 :     image->setImageType(type);
    6302              : 
    6303            0 :     if (!image->loadFromFile(imagePath)) {
    6304            0 :         delete image;
    6305            0 :         return;
    6306              :     }
    6307              : 
    6308            0 :     startEdit(TranslatableString("undoableAction", "Add image"));
    6309            0 :     score()->undoAddElement(image);
    6310            0 :     apply();
    6311            0 : }
    6312              : 
    6313            0 : void NotationInteraction::addFiguredBass()
    6314              : {
    6315              :     static const ElementTypeSet REQUIRED_TYPES {
    6316              :         ElementType::NOTE,
    6317              :         ElementType::FIGURED_BASS,
    6318              :         ElementType::REST
    6319            0 :     };
    6320              : 
    6321            0 :     if (!m_selection->elementsSelected(REQUIRED_TYPES)) {
    6322            0 :         MScore::setError(MsError::NO_NOTE_FIGUREDBASS_SELECTED);
    6323            0 :         checkAndShowError();
    6324            0 :         return;
    6325              :     }
    6326              : 
    6327            0 :     startEdit(TranslatableString("undoableAction", "Add figured bass"));
    6328            0 :     mu::engraving::FiguredBass* figuredBass = score()->addFiguredBass();
    6329              : 
    6330            0 :     if (figuredBass) {
    6331            0 :         apply();
    6332            0 :         startEditText(figuredBass, PointF());
    6333            0 :         m_selection->notifyAboutSelectionChangedIfNeed();
    6334              :     } else {
    6335            0 :         rollback();
    6336              :     }
    6337              : }
    6338              : 
    6339            0 : void NotationInteraction::addStretch(qreal value)
    6340              : {
    6341            0 :     TranslatableString name = value >= 0
    6342            0 :                               ? TranslatableString("undoableAction", "Increase layout stretch")
    6343            0 :                               : TranslatableString("undoableAction", "Decrease layout stretch");
    6344            0 :     transaction(name, [&](Transaction& tx) {
    6345            0 :         mu::engraving::EditStretch::addStretch(tx, score(), value);
    6346            0 :     });
    6347            0 : }
    6348              : 
    6349            0 : Measure* NotationInteraction::selectedMeasure() const
    6350              : {
    6351            0 :     INotationInteraction::HitElementContext context = hitElementContext();
    6352            0 :     mu::engraving::Measure* measure = context.element && context.element->isMeasure() ? mu::engraving::toMeasure(context.element) : nullptr;
    6353            0 :     if (measure) {
    6354            0 :         return measure;
    6355              :     }
    6356              : 
    6357            0 :     INotationSelectionPtr selection = this->selection();
    6358            0 :     if (selection->isRange()) {
    6359            0 :         MeasureBase* mb = selection->range()->measureBaseRange().endMeasureBase;
    6360            0 :         if (!mb) {
    6361            0 :             return nullptr;
    6362              :         }
    6363            0 :         return mb->isMeasure() ? toMeasure(mb) : mb->prevMeasure();
    6364              :     }
    6365              : 
    6366            0 :     if (selection->element()) {
    6367            0 :         return selection->element()->findMeasure();
    6368              :     }
    6369              : 
    6370            0 :     return nullptr;
    6371            0 : }
    6372              : 
    6373            0 : void NotationInteraction::addTimeSignature(Measure* measure, staff_idx_t staffIndex, TimeSignature* timeSignature)
    6374              : {
    6375            0 :     startEdit(TranslatableString("undoableAction", "Add time signature"));
    6376            0 :     Transaction& tx = score()->transactionManager()->currentOrDummyTransaction();
    6377            0 :     EditTimeSig::addTimeSig(tx, score(), measure, staffIndex, timeSignature, true);
    6378            0 :     apply();
    6379            0 : }
    6380              : 
    6381            0 : void NotationInteraction::explodeSelectedStaff()
    6382              : {
    6383            0 :     if (!selection()->isRange()) {
    6384            0 :         return;
    6385              :     }
    6386              : 
    6387            0 :     startEdit(TranslatableString("undoableAction", "Explode"));
    6388            0 :     if (ImplodeExplode::explode(score())) {
    6389            0 :         apply();
    6390              :     } else {
    6391            0 :         rollback();
    6392              :     }
    6393              : 
    6394            0 :     checkAndShowError();
    6395              : }
    6396              : 
    6397            0 : void NotationInteraction::implodeSelectedStaff()
    6398              : {
    6399            0 :     if (!selection()->isRange()) {
    6400            0 :         return;
    6401              :     }
    6402              : 
    6403            0 :     startEdit(TranslatableString("undoableAction", "Implode"));
    6404            0 :     if (ImplodeExplode::implode(score())) {
    6405            0 :         apply();
    6406              :     } else {
    6407            0 :         rollback();
    6408              :     }
    6409              : 
    6410            0 :     checkAndShowError();
    6411              : }
    6412              : 
    6413            0 : void NotationInteraction::realizeSelectedChordSymbols(bool literal, Voicing voicing, HDuration durationType)
    6414              : {
    6415            0 :     if (selection()->isNone()) {
    6416            0 :         return;
    6417              :     }
    6418              : 
    6419            0 :     startEdit(TranslatableString("undoableAction", "Realize chord symbols"));
    6420            0 :     RealizeChordSymbols::realizeChordSymbols(score()->transactionManager()->currentOrDummyTransaction(), score(), literal, voicing,
    6421              :                                              durationType);
    6422            0 :     apply();
    6423              : }
    6424              : 
    6425            0 : void NotationInteraction::extendToNextNote()
    6426              : {
    6427            0 :     if (selection()->isNone()) {
    6428            0 :         return;
    6429              :     }
    6430              : 
    6431            0 :     m_undoStack->transaction(TranslatableString("undoableAction", "Extend to next note"), [&](mu::engraving::Transaction& tx) {
    6432            0 :         EditDuration::extendToNextNote(tx, score());
    6433            0 :     });
    6434            0 :     notifyAboutNotationChanged();
    6435              : }
    6436              : 
    6437            0 : void NotationInteraction::removeSelectedMeasures()
    6438              : {
    6439            0 :     if (selection()->isNone()) {
    6440            0 :         return;
    6441              :     }
    6442              : 
    6443            0 :     mu::engraving::MeasureBase* firstMeasureBase = nullptr;
    6444            0 :     mu::engraving::MeasureBase* lastMeasureBase = nullptr;
    6445              : 
    6446            0 :     if (selection()->isRange()) {
    6447            0 :         INotationSelectionRange::MeasureBaseRange measureBaseRange = selection()->range()->measureBaseRange();
    6448            0 :         firstMeasureBase = measureBaseRange.startMeasureBase;
    6449            0 :         lastMeasureBase = measureBaseRange.endMeasureBase;
    6450              :     } else {
    6451            0 :         const std::vector<EngravingItem*>& elements = selection()->elements();
    6452            0 :         if (elements.empty()) {
    6453            0 :             return;
    6454              :         }
    6455              : 
    6456            0 :         for (EngravingItem* element : elements) {
    6457            0 :             mu::engraving::MeasureBase* elementMeasure = element->findMeasureBase();
    6458              : 
    6459            0 :             if (!firstMeasureBase || firstMeasureBase->index() > elementMeasure->index()) {
    6460            0 :                 firstMeasureBase = elementMeasure;
    6461              :             }
    6462              : 
    6463            0 :             if (!lastMeasureBase || lastMeasureBase->index() < elementMeasure->index()) {
    6464            0 :                 lastMeasureBase = elementMeasure;
    6465              :             }
    6466              :         }
    6467              :     }
    6468              : 
    6469            0 :     IF_ASSERT_FAILED(firstMeasureBase && lastMeasureBase) {
    6470            0 :         return;
    6471              :     }
    6472              : 
    6473            0 :     m_selection->select({ firstMeasureBase }, SelectType::REPLACE);
    6474            0 :     m_selection->select({ lastMeasureBase }, SelectType::RANGE);
    6475              : 
    6476              :     // Slight hack because measureIndex is only meaningful for Measure types (perhaps this property shouldn't
    6477              :     // exist on MeasureBase in the first place)...
    6478            0 :     const Measure* firstMeasure = firstMeasureBase->isMeasure() ? toMeasure(firstMeasureBase) : firstMeasureBase->nextMeasure();
    6479            0 :     const Measure* lastMeasure = lastMeasureBase->isMeasure() ? toMeasure(lastMeasureBase) : lastMeasureBase->prevMeasure();
    6480              : 
    6481            0 :     int numDeletedMeasures = 0;
    6482            0 :     if (firstMeasure && lastMeasure && firstMeasure->measureIndex() <= lastMeasure->measureIndex()) {
    6483            0 :         numDeletedMeasures = 1 + lastMeasure->measureIndex() - firstMeasure->measureIndex();
    6484              :     }
    6485              : 
    6486            0 :     startEdit(TranslatableString("undoableAction", "Delete %Ln measure(s)", nullptr, numDeletedMeasures));
    6487            0 :     score()->cmdTimeDelete();
    6488            0 :     apply();
    6489              : }
    6490              : 
    6491            0 : void NotationInteraction::removeSelectedRange()
    6492              : {
    6493            0 :     if (selection()->isNone()) {
    6494            0 :         return;
    6495              :     }
    6496              : 
    6497            0 :     startEdit(TranslatableString("undoableAction", "Delete range"));
    6498            0 :     score()->cmdTimeDelete();
    6499            0 :     apply();
    6500              : }
    6501              : 
    6502            0 : void NotationInteraction::removeEmptyTrailingMeasures()
    6503              : {
    6504            0 :     startEdit(TranslatableString("undoableAction", "Remove empty trailing measures"));
    6505            0 :     score()->cmdRemoveEmptyTrailingMeasures();
    6506            0 :     apply();
    6507            0 : }
    6508              : 
    6509            0 : void NotationInteraction::fillSelectionWithSlashes()
    6510              : {
    6511            0 :     if (selection()->isNone()) {
    6512            0 :         return;
    6513              :     }
    6514              : 
    6515            0 :     transaction(TranslatableString("undoableAction", "Fill with slashes"), [&](auto& tx) {
    6516            0 :         EditSlashNotation::slashFill(tx, score());
    6517            0 :     });
    6518              : 
    6519            0 :     notifyAboutNotationChanged();
    6520              : }
    6521              : 
    6522            0 : void NotationInteraction::replaceSelectedNotesWithSlashes()
    6523              : {
    6524            0 :     if (selection()->isNone()) {
    6525            0 :         return;
    6526              :     }
    6527              : 
    6528            0 :     transaction(TranslatableString("undoableAction", "Toggle rhythmic slash notation"), [&](auto& tx) {
    6529            0 :         EditSlashNotation::slashRhythm(tx, score());
    6530            0 :     });
    6531              : 
    6532            0 :     notifyAboutNotationChanged();
    6533              : }
    6534              : 
    6535            0 : void NotationInteraction::changeEnharmonicSpelling(bool both)
    6536              : {
    6537            0 :     transaction(TranslatableString("undoableAction", "Change enharmonic spelling"), [&](auto&) {
    6538            0 :         EditEnharmonicSpelling::changeEnharmonicSpelling(score(), both);
    6539            0 :     });
    6540              : 
    6541            0 :     notifyAboutNotationChanged();
    6542            0 : }
    6543              : 
    6544            0 : void NotationInteraction::spellPitches()
    6545              : {
    6546            0 :     transaction(TranslatableString("undoableAction", "Optimize enharmonic spelling"), [&](auto&) {
    6547            0 :         EditEnharmonicSpelling::spell(score());
    6548            0 :     });
    6549              : 
    6550            0 :     notifyAboutNotationChanged();
    6551            0 : }
    6552              : 
    6553            0 : void NotationInteraction::spellPitchesWithSharps()
    6554              : {
    6555            0 :     transaction(TranslatableString("undoableAction", "Respell pitches with sharps"), [&](auto&) {
    6556            0 :         EditEnharmonicSpelling::spellWithSharpsOrFlats(score(), Prefer::SHARPS);
    6557            0 :     });
    6558              : 
    6559            0 :     notifyAboutNotationChanged();
    6560            0 : }
    6561              : 
    6562            0 : void NotationInteraction::spellPitchesWithFlats()
    6563              : {
    6564            0 :     transaction(TranslatableString("undoableAction", "Respell pitches with flats"), [&](auto&) {
    6565            0 :         EditEnharmonicSpelling::spellWithSharpsOrFlats(score(), Prefer::FLATS);
    6566            0 :     });
    6567              : 
    6568            0 :     notifyAboutNotationChanged();
    6569            0 : }
    6570              : 
    6571            0 : void NotationInteraction::regroupNotesAndRests()
    6572              : {
    6573            0 :     transaction(TranslatableString("undoableAction", "Regroup rhythms"), [&](Transaction& tx) {
    6574            0 :         Regroup::regroupNotesAndRestsInSelection(tx, score());
    6575            0 :     });
    6576              : 
    6577            0 :     notifyAboutNotationChanged();
    6578            0 : }
    6579              : 
    6580            0 : void NotationInteraction::resequenceRehearsalMarks()
    6581              : {
    6582            0 :     transaction(TranslatableString("undoableAction", "Resequence rehearsal marks"), [&](Transaction& tx) {
    6583            0 :         EditRehearsalMark::resequenceRehearsalMarks(tx, score());
    6584            0 :     });
    6585              : 
    6586            0 :     notifyAboutNotationChanged();
    6587            0 : }
    6588              : 
    6589            0 : void NotationInteraction::resetStretch()
    6590              : {
    6591            0 :     transaction(TranslatableString("undoableAction", "Reset layout stretch"), [&](Transaction& tx) {
    6592            0 :         mu::engraving::EditStretch::resetUserStretch(tx, score());
    6593            0 :     });
    6594            0 : }
    6595              : 
    6596            0 : void NotationInteraction::resetTextStyleOverrides()
    6597              : {
    6598            0 :     startEdit(TranslatableString("undoableAction", "Reset text style overrides"));
    6599            0 :     Reset::resetTextStyleOverrides(score());
    6600            0 :     apply();
    6601            0 : }
    6602              : 
    6603            0 : void NotationInteraction::resetBeamMode()
    6604              : {
    6605            0 :     transaction(TranslatableString("undoableAction", "Reset beams"), [&](Transaction& tx) {
    6606            0 :         EditBeam::resetBeamMode(tx, score());
    6607            0 :     });
    6608            0 :     notifyAboutNotationChanged();
    6609            0 : }
    6610              : 
    6611            0 : void NotationInteraction::resetShapesAndPosition()
    6612              : {
    6613            0 :     auto resetItem = [](EngravingItem* item) {
    6614            0 :         item->reset();
    6615              : 
    6616            0 :         if (item->isSpanner()) {
    6617            0 :             for (mu::engraving::SpannerSegment* spannerSegment : toSpanner(item)->spannerSegments()) {
    6618            0 :                 spannerSegment->reset();
    6619              :             }
    6620              :         }
    6621            0 :     };
    6622              : 
    6623            0 :     startEdit(TranslatableString("undoableAction", "Reset shapes and positions"));
    6624              : 
    6625            0 :     DEFER {
    6626            0 :         apply();
    6627            0 :     };
    6628              : 
    6629            0 :     if (selection()->element()) {
    6630            0 :         resetItem(selection()->element());
    6631            0 :         return;
    6632              :     }
    6633              : 
    6634            0 :     for (EngravingItem* item : selection()->elements()) {
    6635            0 :         resetItem(item);
    6636              :     }
    6637            0 : }
    6638              : 
    6639            0 : void NotationInteraction::freezeSelectionPosition()
    6640              : {
    6641            0 :     std::vector<EngravingItem*> items = selection()->elements();
    6642            0 :     if (items.empty()) {
    6643            0 :         return;
    6644              :     }
    6645              : 
    6646            0 :     transaction(TranslatableString("undoableAction", "Freeze current placement"), [&](mu::engraving::Transaction& tx) {
    6647            0 :         EditPosition::freezeItemsPositions(tx, items);
    6648            0 :     });
    6649              : 
    6650            0 :     notifyAboutNotationChanged();
    6651            0 : }
    6652              : 
    6653            0 : void NotationInteraction::resetToDefaultLayout()
    6654              : {
    6655            0 :     TRACEFUNC;
    6656              : 
    6657            0 :     startEdit(TranslatableString("undoableAction", "Reset to default layout"));
    6658            0 :     Reset::resetToDefaultLayout(score()->transactionManager()->currentOrDummyTransaction(), score());
    6659            0 :     apply();
    6660            0 : }
    6661              : 
    6662            0 : ScoreConfig NotationInteraction::scoreConfig() const
    6663              : {
    6664            0 :     ScoreConfig config;
    6665            0 :     config.setShown(ScoreConfigType::ShowInvisibleElements, score()->isShowInvisible());
    6666            0 :     config.setShown(ScoreConfigType::ShowUnprintableElements, score()->showUnprintable());
    6667            0 :     config.setShown(ScoreConfigType::ShowFrames, score()->showFrames());
    6668            0 :     config.setShown(ScoreConfigType::ShowPageMargins, score()->showPageborders());
    6669            0 :     config.setShown(ScoreConfigType::ShowSoundFlags, score()->showSoundFlags());
    6670            0 :     config.setShown(ScoreConfigType::MarkIrregularMeasures, score()->markIrregularMeasures());
    6671            0 :     return config;
    6672            0 : }
    6673              : 
    6674            0 : void NotationInteraction::setScoreConfig(const ScoreConfig& config)
    6675              : {
    6676            0 :     const ScoreConfig oldConfig = scoreConfig();
    6677            0 :     if (oldConfig == config) {
    6678            0 :         return;
    6679              :     }
    6680              : 
    6681              :     // TODO: refactor together with `NotationActionController::toggleScoreConfig`
    6682              :     // to be able to give a more precise name
    6683            0 :     startEdit(TranslatableString("undoableAction", "Set score view settings"));
    6684            0 :     score()->setShowInvisible(config.isShown(ScoreConfigType::ShowInvisibleElements));
    6685            0 :     score()->setShowUnprintable(config.isShown(ScoreConfigType::ShowUnprintableElements));
    6686            0 :     score()->setShowFrames(config.isShown(ScoreConfigType::ShowFrames));
    6687            0 :     score()->setShowPageborders(config.isShown(ScoreConfigType::ShowPageMargins));
    6688            0 :     score()->setShowSoundFlags(config.isShown(ScoreConfigType::ShowSoundFlags));
    6689            0 :     score()->setMarkIrregularMeasures(config.isShown(ScoreConfigType::MarkIrregularMeasures));
    6690              : 
    6691            0 :     EngravingItem* selectedElement = selection()->element();
    6692            0 :     if (selectedElement && !selectedElement->isInteractionAvailable()) {
    6693            0 :         clearSelection();
    6694              :     }
    6695              : 
    6696            0 :     apply();
    6697              : 
    6698            0 :     for (const auto& p : oldConfig.config) {
    6699            0 :         if (p.second != config.isShown(p.first)) {
    6700            0 :             m_scoreConfigChanged.send(p.first);
    6701              :         }
    6702              :     }
    6703            0 : }
    6704              : 
    6705            0 : muse::async::Channel<ScoreConfigType> NotationInteraction::scoreConfigChanged() const
    6706              : {
    6707            0 :     return m_scoreConfigChanged;
    6708              : }
    6709              : 
    6710            0 : bool NotationInteraction::needEndTextEditing(const std::vector<EngravingItem*>& newSelectedElements) const
    6711              : {
    6712            0 :     if (!isTextEditingStarted()) {
    6713            0 :         return false;
    6714              :     }
    6715              : 
    6716            0 :     if (newSelectedElements.empty()) {
    6717            0 :         return false;
    6718              :     }
    6719              : 
    6720            0 :     if (newSelectedElements.size() > 1) {
    6721            0 :         return true;
    6722              :     }
    6723              : 
    6724            0 :     if (m_editData.element && m_editData.element->isStaffText()) {
    6725            0 :         EngravingItem* element = newSelectedElements.front();
    6726            0 :         if (element && element->isSoundFlag() && element->ownershipParent() == m_editData.element) {
    6727            0 :             return false;
    6728              :         }
    6729              :     }
    6730              : 
    6731            0 :     return newSelectedElements.front() != m_editData.element;
    6732              : }
    6733              : 
    6734            0 : bool NotationInteraction::needEndElementEditing(const std::vector<EngravingItem*>& newSelectedElements) const
    6735              : {
    6736            0 :     if (!isEditingElement()) {
    6737            0 :         return false;
    6738              :     }
    6739              : 
    6740            0 :     if (newSelectedElements.size() != 1) {
    6741            0 :         return true;
    6742              :     }
    6743              : 
    6744            0 :     if (m_editData.element && m_editData.element->isStaffText()) {
    6745            0 :         EngravingItem* element = newSelectedElements.front();
    6746            0 :         if (element && element->isSoundFlag() && element->ownershipParent() == m_editData.element) {
    6747            0 :             return false;
    6748              :         }
    6749              :     }
    6750              : 
    6751            0 :     return newSelectedElements.front() != score()->selection().element();
    6752              : }
    6753              : 
    6754            0 : void NotationInteraction::resetGripEdit()
    6755              : {
    6756            0 :     m_editData.grips = 0;
    6757            0 :     m_editData.curGrip = mu::engraving::Grip::NO_GRIP;
    6758            0 :     m_editData.grip.clear();
    6759              : 
    6760            0 :     resetAnchorLines();
    6761            0 : }
    6762              : 
    6763            0 : void NotationInteraction::resetHitElementContext()
    6764              : {
    6765            0 :     setHitElementContext(HitElementContext());
    6766            0 : }
    6767              : 
    6768              : //! NOTE: Copied from ScoreView::lyricsTab
    6769            0 : void NotationInteraction::navigateToLyrics(bool back, bool moveOnly, bool end)
    6770              : {
    6771            0 :     if (!m_editData.element || !m_editData.element->isLyrics()) {
    6772            0 :         LOGW("nextLyric called with invalid current element");
    6773            0 :         return;
    6774              :     }
    6775            0 :     mu::engraving::Lyrics* lyrics = toLyrics(m_editData.element);
    6776            0 :     track_idx_t track = lyrics->track();
    6777            0 :     mu::engraving::Segment* segment = lyrics->segment();
    6778            0 :     int verse = lyrics->verse();
    6779            0 :     mu::engraving::PlacementV placement = lyrics->placement();
    6780            0 :     mu::engraving::PropertyFlags pFlags = lyrics->propertyFlags(mu::engraving::Pid::PLACEMENT);
    6781            0 :     mu::engraving::FontStyle fStyle = lyrics->fontStyle();
    6782            0 :     mu::engraving::PropertyFlags fFlags = lyrics->propertyFlags(mu::engraving::Pid::FONT_STYLE);
    6783            0 :     mu::engraving::AutoOnOff centering = lyrics->centerBetweenStaves();
    6784            0 :     mu::engraving::TextStyleType styleType = lyrics->textStyleType();
    6785            0 :     double yOffset = lyrics->offset().y();
    6786              : 
    6787            0 :     mu::engraving::Segment* nextSegment = segment;
    6788            0 :     if (back) {
    6789              :         // search prev chord
    6790            0 :         while ((nextSegment = nextSegment->prev1(mu::engraving::SegmentType::ChordRest))) {
    6791            0 :             EngravingItem* el = nextSegment->element(track);
    6792            0 :             if (!el) {
    6793            0 :                 continue;
    6794              :             }
    6795            0 :             if (el->isChord()) {
    6796            0 :                 break;
    6797            0 :             } else if (el->isRest()) {
    6798            0 :                 mu::engraving::Lyrics* nextLyrics = toChordRest(el)->lyrics(verse, placement);
    6799            0 :                 if (nextLyrics) {
    6800            0 :                     break;
    6801              :                 }
    6802              :             }
    6803              :         }
    6804              :     } else {
    6805              :         // search next chord
    6806            0 :         while ((nextSegment = nextSegment->next1(mu::engraving::SegmentType::ChordRest))) {
    6807            0 :             EngravingItem* el = nextSegment->element(track);
    6808            0 :             if (!el) {
    6809            0 :                 continue;
    6810              :             }
    6811            0 :             if (el->isChord()) {
    6812            0 :                 break;
    6813            0 :             } else if (el->isRest()) {
    6814            0 :                 mu::engraving::Lyrics* nextLyrics = toChordRest(el)->lyrics(verse, placement);
    6815            0 :                 if (nextLyrics) {
    6816            0 :                     break;
    6817              :                 }
    6818              :             }
    6819              :         }
    6820              :     }
    6821            0 :     if (!nextSegment) {
    6822            0 :         return;
    6823              :     }
    6824              : 
    6825            0 :     endEditText();
    6826              : 
    6827              :     // look for the lyrics we are moving from; may be the current lyrics or a previous one
    6828              :     // if we are skipping several chords with spaces
    6829            0 :     mu::engraving::Lyrics* fromLyrics = 0;
    6830            0 :     if (!back) {
    6831            0 :         while (segment) {
    6832            0 :             ChordRest* cr = toChordRest(segment->element(track));
    6833            0 :             if (cr) {
    6834            0 :                 fromLyrics = cr->lyrics(verse, placement);
    6835            0 :                 if (fromLyrics) {
    6836            0 :                     break;
    6837              :                 }
    6838              :             }
    6839            0 :             segment = segment->prev1(mu::engraving::SegmentType::ChordRest);
    6840              :         }
    6841              :     }
    6842              : 
    6843            0 :     ChordRest* cr = toChordRest(nextSegment->element(track));
    6844            0 :     if (!cr) {
    6845            0 :         LOGD("no next lyrics list: %s", nextSegment->element(track)->typeName());
    6846            0 :         return;
    6847              :     }
    6848            0 :     mu::engraving::Lyrics* nextLyrics = cr->lyrics(verse, placement);
    6849              : 
    6850            0 :     bool newLyrics = false;
    6851            0 :     if (!nextLyrics) {
    6852            0 :         nextLyrics = Factory::createLyrics(cr);
    6853            0 :         nextLyrics->setTrack(track);
    6854            0 :         cr = toChordRest(nextSegment->element(track));
    6855            0 :         nextLyrics->setOwnershipParent(cr);
    6856            0 :         nextLyrics->setVerse(verse);
    6857            0 :         nextLyrics->setTextStyleType(styleType);
    6858            0 :         nextLyrics->setPlacement(placement);
    6859            0 :         nextLyrics->setPropertyFlags(mu::engraving::Pid::PLACEMENT, pFlags);
    6860            0 :         nextLyrics->setCenterBetweenStaves(centering);
    6861            0 :         nextLyrics->setSyllabic(mu::engraving::LyricsSyllabic::SINGLE);
    6862            0 :         nextLyrics->setFontStyle(fStyle);
    6863            0 :         nextLyrics->setPropertyFlags(mu::engraving::Pid::FONT_STYLE, fFlags);
    6864            0 :         nextLyrics->setOffset(PointF(0.0, yOffset));
    6865            0 :         newLyrics = true;
    6866              :     }
    6867              : 
    6868            0 :     score()->startCmd(TranslatableString("undoableAction", "Navigate to lyrics"));
    6869            0 :     if (fromLyrics && !moveOnly) {
    6870            0 :         switch (nextLyrics->syllabic()) {
    6871              :         // as we arrived at nextLyrics by a [Space], it can be the beginning
    6872              :         // of a multi-syllable, but cannot have syllabic dashes before
    6873            0 :         case mu::engraving::LyricsSyllabic::SINGLE:
    6874              :         case mu::engraving::LyricsSyllabic::BEGIN:
    6875            0 :             break;
    6876            0 :         case mu::engraving::LyricsSyllabic::END:
    6877            0 :             nextLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::SINGLE));
    6878            0 :             break;
    6879            0 :         case mu::engraving::LyricsSyllabic::MIDDLE:
    6880            0 :             nextLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::BEGIN));
    6881            0 :             break;
    6882              :         }
    6883              :         // as we moved away from fromLyrics by a [Space], it can be
    6884              :         // the end of a multi-syllable, but cannot have syllabic dashes after
    6885            0 :         switch (fromLyrics->syllabic()) {
    6886            0 :         case mu::engraving::LyricsSyllabic::SINGLE:
    6887              :         case mu::engraving::LyricsSyllabic::END:
    6888            0 :             break;
    6889            0 :         case mu::engraving::LyricsSyllabic::BEGIN:
    6890            0 :             fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::SINGLE));
    6891            0 :             break;
    6892            0 :         case mu::engraving::LyricsSyllabic::MIDDLE:
    6893            0 :             fromLyrics->undoChangeProperty(mu::engraving::Pid::SYLLABIC, int(mu::engraving::LyricsSyllabic::END));
    6894            0 :             break;
    6895              :         }
    6896              :         // for the same reason, it cannot have a melisma
    6897            0 :         if (fromLyrics->separator() && !fromLyrics->separator()->isEndMelisma()) {
    6898            0 :             fromLyrics->undoChangeProperty(mu::engraving::Pid::LYRIC_TICKS, Fraction::fromTicks(0));
    6899              :         }
    6900              :     }
    6901              : 
    6902            0 :     if (newLyrics) {
    6903            0 :         score()->undoAddElement(nextLyrics);
    6904              :     }
    6905            0 :     score()->endCmd();
    6906            0 :     score()->select(nextLyrics, SelectType::SINGLE, 0);
    6907            0 :     score()->setLayoutAll();
    6908              : 
    6909            0 :     startEditText(nextLyrics, PointF());
    6910              : 
    6911            0 :     if (end) {
    6912            0 :         nextLyrics->selectAll();
    6913            0 :     } else if (!newLyrics) {
    6914            0 :         mu::engraving::TextCursor* cursor = nextLyrics->cursor();
    6915            0 :         cursor->movePosition(mu::engraving::TextCursor::MoveOperation::End, mu::engraving::TextCursor::MoveMode::MoveAnchor);
    6916            0 :         cursor->movePosition(mu::engraving::TextCursor::MoveOperation::Start, mu::engraving::TextCursor::MoveMode::KeepAnchor);
    6917              :     }
    6918              : 
    6919            0 :     showItem(nextLyrics);
    6920              : }
    6921              : 
    6922            0 : void NotationInteraction::navigateToLyrics(MoveDirection direction, bool moveOnly)
    6923              : {
    6924            0 :     navigateToLyrics(direction == MoveDirection::Left, moveOnly, false);
    6925            0 : }
    6926              : 
    6927              : //! NOTE: Copied from ScoreView::lyricsTab
    6928            0 : void NotationInteraction::navigateToNextSyllable()
    6929              : {
    6930            0 :     if (!m_editData.element || !m_editData.element->isLyrics()) {
    6931            0 :         LOGW("nextSyllable called with invalid current element");
    6932            0 :         return;
    6933              :     }
    6934            0 :     Lyrics* lyrics = toLyrics(m_editData.element);
    6935            0 :     ChordRest* initialCR = lyrics->chordRest();
    6936            0 :     const bool hasPrecedingRepeat = initialCR->hasPrecedingJumpItem();
    6937            0 :     const bool hasFollowingRepeat = initialCR->hasFollowingJumpItem();
    6938            0 :     track_idx_t track = lyrics->track();
    6939            0 :     track_idx_t toLyricTrack = track;
    6940            0 :     Segment* segment = lyrics->segment();
    6941            0 :     int verse = lyrics->verse();
    6942            0 :     PlacementV placement = lyrics->placement();
    6943            0 :     PropertyFlags pFlags = lyrics->propertyFlags(Pid::PLACEMENT);
    6944            0 :     FontStyle fStyle = lyrics->fontStyle();
    6945            0 :     PropertyFlags fFlags = lyrics->propertyFlags(Pid::FONT_STYLE);
    6946            0 :     AutoOnOff centering = lyrics->centerBetweenStaves();
    6947            0 :     mu::engraving::TextStyleType styleType = lyrics->textStyleType();
    6948            0 :     double yOffset = lyrics->offset().y();
    6949              : 
    6950              :     // search next chord
    6951            0 :     Segment* nextSegment = segment;
    6952            0 :     while ((nextSegment = nextSegment->next1(SegmentType::ChordRest))) {
    6953            0 :         EngravingItem* el = nextSegment->element(track);
    6954            0 :         if (!el || !el->isChord()) {
    6955            0 :             const track_idx_t strack = trackZeroVoice(track);
    6956            0 :             const track_idx_t etrack = strack + VOICES;
    6957            0 :             for (track_idx_t t = strack; t < etrack; ++t) {
    6958            0 :                 el = nextSegment->element(t);
    6959            0 :                 if (el && el->isChord() && toChord(el)->lyrics(verse, placement)) {
    6960            0 :                     toLyricTrack = t;
    6961            0 :                     break;
    6962              :                 }
    6963              :             }
    6964              :         }
    6965              : 
    6966            0 :         if (el && el->isChord()) {
    6967            0 :             break;
    6968              :         }
    6969              :     }
    6970              : 
    6971            0 :     if (!segmentsAreAdjacent(segment, nextSegment)) {
    6972            0 :         nextSegment = nullptr;
    6973              :     }
    6974              : 
    6975            0 :     if (!nextSegment && !hasFollowingRepeat && !hasPrecedingRepeat) {
    6976            0 :         return;
    6977              :     }
    6978              : 
    6979            0 :     PartialLyricsLine* prevPartialLyricsLine = findPrevPartialLyricsLineDash(lyrics);
    6980              : 
    6981            0 :     endEditText();
    6982              : 
    6983              :     // look for the lyrics we are moving from; may be the current lyrics or a previous one
    6984              :     // we are extending with several dashes
    6985            0 :     Lyrics* fromLyrics = nullptr;
    6986            0 :     Segment* curSeg = segment;
    6987            0 :     while (segment && segmentsAreAdjacent(segment, curSeg)) {
    6988            0 :         ChordRest* cr = toChordRest(segment->element(track));
    6989            0 :         if (!cr) {
    6990            0 :             segment = segment->prev1(SegmentType::ChordRest);
    6991            0 :             continue;
    6992              :         }
    6993            0 :         fromLyrics = cr->lyrics(verse, placement);
    6994            0 :         if (fromLyrics) {
    6995            0 :             break;
    6996              :         }
    6997            0 :         segment = segment->prev1(SegmentType::ChordRest);
    6998              :     }
    6999              : 
    7000            0 :     if (!nextSegment) {
    7001            0 :         score()->startCmd(TranslatableString("undoableAction", "Navigate to next syllable"));
    7002            0 :         if (fromLyrics && hasFollowingRepeat) {
    7003              :             // Allow dash with no end syllable if there is a repeat
    7004            0 :             switch (fromLyrics->syllabic()) {
    7005            0 :             case LyricsSyllabic::BEGIN:
    7006              :             case LyricsSyllabic::MIDDLE:
    7007            0 :                 break;
    7008            0 :             case LyricsSyllabic::SINGLE:
    7009            0 :                 fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN));
    7010            0 :                 break;
    7011            0 :             case LyricsSyllabic::END:
    7012            0 :                 fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE));
    7013            0 :                 break;
    7014              :             }
    7015            0 :             fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, Fraction(0, 1));
    7016            0 :             score()->setLayoutAll();
    7017            0 :             score()->endCmd();
    7018            0 :             return;
    7019              :         }
    7020              : 
    7021            0 :         if (hasPrecedingRepeat) {
    7022            0 :             score()->endCmd();
    7023            0 :             score()->startCmd(TranslatableString("undoableAction", "Add partial lyrics dash"));
    7024              :             // No from lyrics - create incoming partial dash
    7025            0 :             PartialLyricsLine* dash = Factory::createPartialLyricsLine(score()->dummy());
    7026            0 :             dash->setIsEndMelisma(false);
    7027            0 :             dash->setVerse(verse);
    7028            0 :             dash->setPlacement(placement);
    7029            0 :             dash->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7030            0 :             dash->setCenterBetweenStaves(centering);
    7031            0 :             dash->setTick(initialCR->tick());
    7032            0 :             dash->setTicks(Fraction(0, 1));
    7033            0 :             dash->setTrack(initialCR->track());
    7034            0 :             dash->setTrack2(initialCR->track());
    7035              : 
    7036            0 :             score()->undoAddElement(dash);
    7037              : 
    7038              :             // Don't advance cursor
    7039              : 
    7040            0 :             Lyrics* toLyrics = Factory::createLyrics(initialCR);
    7041            0 :             toLyrics->setTrack(track);
    7042            0 :             toLyrics->setVerse(verse);
    7043            0 :             toLyrics->setTextStyleType(styleType);
    7044            0 :             toLyrics->setPlacement(placement);
    7045            0 :             toLyrics->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7046            0 :             toLyrics->setCenterBetweenStaves(centering);
    7047            0 :             toLyrics->setSyllabic(LyricsSyllabic::END);
    7048            0 :             toLyrics->setFontStyle(fStyle);
    7049            0 :             toLyrics->setPropertyFlags(Pid::FONT_STYLE, fFlags);
    7050            0 :             toLyrics->setOffset(PointF(0.0, yOffset));
    7051              : 
    7052            0 :             score()->undoAddElement(toLyrics);
    7053            0 :             score()->endCmd();
    7054            0 :             score()->select(toLyrics, SelectType::SINGLE, 0);
    7055            0 :             score()->setLayoutAll();
    7056            0 :             startEditText(toLyrics, PointF());
    7057            0 :             toLyrics->selectAll();
    7058            0 :             showItem(toLyrics);
    7059              : 
    7060            0 :             return;
    7061              :         }
    7062              :     }
    7063              : 
    7064            0 :     score()->startCmd(TranslatableString("undoableAction", "Navigate to next syllable"));
    7065            0 :     ChordRest* cr = nextSegment ? toChordRest(nextSegment->element(toLyricTrack)) : nullptr;
    7066            0 :     Lyrics* toLyrics = cr ? cr->lyrics(verse, placement) : nullptr;
    7067              : 
    7068            0 :     if (!toLyrics && !cr) {
    7069            0 :         return;
    7070              :     }
    7071              : 
    7072              :     // If no lyrics in current track, check others
    7073            0 :     if (!toLyrics) {
    7074            0 :         const track_idx_t strack = trackZeroVoice(track);
    7075            0 :         const track_idx_t etrack = strack + VOICES;
    7076            0 :         for (track_idx_t t = strack; t < etrack; ++t) {
    7077            0 :             if (t == track) {
    7078            0 :                 continue;
    7079              :             }
    7080            0 :             cr = toChordRest(nextSegment->element(t));
    7081            0 :             if (cr) {
    7082            0 :                 toLyrics = cr->lyrics(verse, placement);
    7083            0 :                 if (toLyrics) {
    7084            0 :                     break;
    7085              :                 }
    7086              :             }
    7087              :         }
    7088              :     }
    7089              : 
    7090              :     // This will be a partial dash at the start of a measure after a repeat
    7091              :     // We don't want toLyrics
    7092            0 :     toLyrics = hasPrecedingRepeat && !fromLyrics ? nullptr : toLyrics;
    7093              : 
    7094              :     // Make sure we end up with either the cr of toLyrics or cr on correct track
    7095            0 :     cr = !toLyrics ? toChordRest(nextSegment->element(track)) : cr;
    7096              : 
    7097              :     // Disallow dashes between non-adjacent repeat sections eg. 1st volta -> 2nd volta
    7098              :     // Instead, try to add partial dashes
    7099            0 :     if (cr && fromLyrics) {
    7100            0 :         Measure* toLyricsMeasure = cr->measure();
    7101            0 :         Measure* fromLyricsMeasure = fromLyrics->measure();
    7102              : 
    7103            0 :         if (toLyricsMeasure != fromLyricsMeasure && fromLyricsMeasure->lastChordRest(track)->hasFollowingJumpItem()) {
    7104            0 :             const std::vector<Measure*> previousRepeats = findPreviousRepeatMeasures(toLyricsMeasure);
    7105            0 :             const bool inPrecedingRepeatSeg = muse::contains(previousRepeats, fromLyricsMeasure);
    7106            0 :             if (!previousRepeats.empty() && !inPrecedingRepeatSeg) {
    7107            0 :                 fromLyrics = nullptr;
    7108              :             }
    7109            0 :         }
    7110              :     }
    7111              : 
    7112            0 :     const bool startIncomingPartialDash = hasPrecedingRepeat && !fromLyrics
    7113            0 :                                           && !prevPartialLyricsLine && lyrics->xmlText().empty();
    7114              : 
    7115            0 :     const bool newLyrics = !toLyrics || startIncomingPartialDash;
    7116            0 :     if (newLyrics) {
    7117              :         /* Don't advance the cursor when starting an incoming partial dash after a repeat,
    7118              :          * i.e. when there is no adjacent preceding syllable to dash from: */
    7119            0 :         ChordRest* toLyricsChord = startIncomingPartialDash ? initialCR : cr;
    7120              : 
    7121            0 :         toLyrics = Factory::createLyrics(toLyricsChord);
    7122            0 :         toLyrics->setTrack(track);
    7123              : 
    7124            0 :         toLyrics->setVerse(verse);
    7125            0 :         toLyrics->setTextStyleType(styleType);
    7126              : 
    7127            0 :         toLyrics->setPlacement(placement);
    7128            0 :         toLyrics->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7129            0 :         toLyrics->setCenterBetweenStaves(centering);
    7130            0 :         toLyrics->setSyllabic(LyricsSyllabic::END);
    7131            0 :         toLyrics->setFontStyle(fStyle);
    7132            0 :         toLyrics->setPropertyFlags(Pid::FONT_STYLE, fFlags);
    7133            0 :         toLyrics->setOffset(PointF(0.0, yOffset));
    7134              :     } else {
    7135              :         // as we arrived at toLyrics by a dash, it cannot be initial or isolated
    7136            0 :         if (toLyrics->syllabic() == LyricsSyllabic::BEGIN) {
    7137            0 :             toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE));
    7138            0 :         } else if (toLyrics->syllabic() == LyricsSyllabic::SINGLE) {
    7139            0 :             toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END));
    7140              :         }
    7141              :     }
    7142              : 
    7143            0 :     if (fromLyrics) {
    7144              :         // as we moved away from fromLyrics by a dash,
    7145              :         // it can have syll. dashes before and after but cannot be isolated or terminal
    7146            0 :         switch (fromLyrics->syllabic()) {
    7147            0 :         case LyricsSyllabic::BEGIN:
    7148              :         case LyricsSyllabic::MIDDLE:
    7149            0 :             break;
    7150            0 :         case LyricsSyllabic::SINGLE:
    7151            0 :             fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN));
    7152            0 :             break;
    7153            0 :         case LyricsSyllabic::END:
    7154            0 :             fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::MIDDLE));
    7155            0 :             break;
    7156              :         }
    7157              :         // for the same reason, it cannot have a melisma
    7158            0 :         fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, Fraction(0, 1));
    7159            0 :     } else if (hasPrecedingRepeat && !prevPartialLyricsLine) {
    7160              :         // No from lyrics - create incoming partial dash
    7161            0 :         PartialLyricsLine* dash = Factory::createPartialLyricsLine(score()->dummy());
    7162            0 :         dash->setIsEndMelisma(false);
    7163            0 :         dash->setVerse(verse);
    7164            0 :         dash->setPlacement(placement);
    7165            0 :         dash->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7166            0 :         dash->setCenterBetweenStaves(centering);
    7167            0 :         dash->setTick(initialCR->tick());
    7168            0 :         dash->setTicks(hasPrecedingRepeat ? Fraction(0, 1) : initialCR->ticks());
    7169            0 :         dash->setTrack(initialCR->track());
    7170            0 :         dash->setTrack2(initialCR->track());
    7171              : 
    7172            0 :         score()->undoAddElement(dash);
    7173            0 :     } else if (prevPartialLyricsLine) {
    7174            0 :         const Fraction tickDiff = cr->tick() - prevPartialLyricsLine->tick2();
    7175            0 :         prevPartialLyricsLine->undoMoveEnd(tickDiff);
    7176            0 :         score()->undoAddElement(prevPartialLyricsLine);
    7177            0 :         prevPartialLyricsLine->triggerLayout();
    7178              :     }
    7179              : 
    7180            0 :     if (newLyrics) {
    7181            0 :         score()->undoAddElement(toLyrics);
    7182              :     }
    7183              : 
    7184            0 :     score()->endCmd();
    7185            0 :     score()->select(toLyrics, SelectType::SINGLE, 0);
    7186            0 :     score()->setLayoutAll();
    7187              : 
    7188            0 :     startEditText(toLyrics, PointF());
    7189              : 
    7190            0 :     toLyrics->selectAll();
    7191            0 :     showItem(toLyrics);
    7192              : }
    7193              : 
    7194              : //! NOTE: Copied from ScoreView::lyricsUpDown
    7195            0 : void NotationInteraction::navigateToLyricsVerse(MoveDirection direction)
    7196              : {
    7197            0 :     if (!m_editData.element || !m_editData.element->isLyrics()) {
    7198            0 :         LOGW("nextLyricVerse called with invalid current element");
    7199            0 :         return;
    7200              :     }
    7201            0 :     mu::engraving::Lyrics* lyrics = toLyrics(m_editData.element);
    7202            0 :     engraving::track_idx_t track = lyrics->track();
    7203            0 :     ChordRest* cr = lyrics->chordRest();
    7204            0 :     int verse = lyrics->verse();
    7205            0 :     mu::engraving::PlacementV placement = lyrics->placement();
    7206            0 :     mu::engraving::PropertyFlags pFlags = lyrics->propertyFlags(mu::engraving::Pid::PLACEMENT);
    7207            0 :     mu::engraving::FontStyle fStyle = lyrics->fontStyle();
    7208            0 :     mu::engraving::PropertyFlags fFlags = lyrics->propertyFlags(mu::engraving::Pid::FONT_STYLE);
    7209            0 :     mu::engraving::AutoOnOff centering = lyrics->centerBetweenStaves();
    7210            0 :     mu::engraving::TextStyleType styleType = lyrics->textStyleType();
    7211              : 
    7212            0 :     if (direction == MoveDirection::Up) {
    7213            0 :         if (verse == 0) {
    7214            0 :             return;
    7215              :         }
    7216            0 :         --verse;
    7217              :     } else {
    7218            0 :         ++verse;
    7219            0 :         if (verse > cr->lastVerse(placement)) {
    7220            0 :             return;
    7221              :         }
    7222              :     }
    7223              : 
    7224            0 :     endEditText();
    7225              : 
    7226            0 :     lyrics = cr->lyrics(verse, placement);
    7227            0 :     if (!lyrics) {
    7228            0 :         lyrics = Factory::createLyrics(cr);
    7229            0 :         lyrics->setTrack(track);
    7230            0 :         lyrics->setVerse(verse);
    7231            0 :         lyrics->setTextStyleType(styleType);
    7232            0 :         lyrics->setPlacement(placement);
    7233            0 :         lyrics->setPropertyFlags(mu::engraving::Pid::PLACEMENT, pFlags);
    7234            0 :         lyrics->setCenterBetweenStaves(centering);
    7235            0 :         lyrics->setFontStyle(fStyle);
    7236            0 :         lyrics->setPropertyFlags(mu::engraving::Pid::FONT_STYLE, fFlags);
    7237              : 
    7238            0 :         score()->startCmd(TranslatableString("undoableAction", "Navigate to verse"));
    7239            0 :         score()->undoAddElement(lyrics);
    7240            0 :         score()->endCmd();
    7241              :     }
    7242              : 
    7243            0 :     score()->select(lyrics, SelectType::SINGLE, 0);
    7244            0 :     startEditText(lyrics, PointF());
    7245              : 
    7246            0 :     lyrics = toLyrics(m_editData.element);
    7247              : 
    7248            0 :     score()->setLayoutAll();
    7249            0 :     score()->update();
    7250              : 
    7251            0 :     lyrics->selectAll();
    7252            0 :     showItem(lyrics);
    7253              : }
    7254              : 
    7255              : //! NOTE: Copied from ScoreView::harmonyBeatsTab
    7256            0 : void NotationInteraction::navigateToNearHarmony(MoveDirection direction, bool nearNoteOrRest)
    7257              : {
    7258            0 :     Harmony* harmony = editedHarmony();
    7259            0 :     if (!harmony) {
    7260            0 :         return;
    7261              :     }
    7262              : 
    7263            0 :     Segment* segment = harmonySegment(harmony);
    7264            0 :     if (!segment) {
    7265            0 :         LOGD("no segment");
    7266            0 :         return;
    7267              :     }
    7268              : 
    7269            0 :     Measure* measure = segment->measure();
    7270            0 :     Fraction tick = segment->tick();
    7271            0 :     engraving::track_idx_t track = harmony->track();
    7272            0 :     bool backDirection = direction == MoveDirection::Left;
    7273              : 
    7274            0 :     if (backDirection && tick == measure->tick()) {
    7275              :         // previous bar, if any
    7276            0 :         measure = measure->prevMeasure();
    7277            0 :         if (!measure) {
    7278            0 :             LOGD("no previous measure");
    7279            0 :             return;
    7280              :         }
    7281              :     }
    7282              : 
    7283            0 :     Fraction f = measure->ticks();
    7284            0 :     int ticksPerBeat   = f.ticks()
    7285            0 :                          / ((f.numerator() > 3 && (f.numerator() % 3) == 0 && f.denominator() > 4) ? f.numerator() / 3 : f.numerator());
    7286            0 :     Fraction tickInBar = tick - measure->tick();
    7287            0 :     Fraction newTick   = measure->tick()
    7288            0 :                          + Fraction::fromTicks((
    7289            0 :                                                    (tickInBar.ticks() + (backDirection ? -1 : ticksPerBeat)) / ticksPerBeat
    7290              :                                                    )
    7291            0 :                                                * ticksPerBeat);
    7292              : 
    7293            0 :     endEditText();
    7294              : 
    7295            0 :     startEdit(TranslatableString("undoableAction", "Navigate to next chord symbol"));
    7296              : 
    7297              :     // look for next/prev beat, note, rest or chord
    7298              :     for (;;) {
    7299            0 :         segment = backDirection ? segment->prev1(mu::engraving::SegmentType::ChordRest) : segment->next1(
    7300              :             mu::engraving::SegmentType::ChordRest);
    7301              : 
    7302            0 :         if (!segment || (backDirection ? (segment->tick() < newTick) : (segment->tick() > newTick))) {
    7303              :             // no segment or moved past the beat - create new segment
    7304            0 :             if (!backDirection && newTick >= measure->tick() + f) {
    7305              :                 // next bar, if any
    7306            0 :                 measure = measure->nextMeasure();
    7307            0 :                 if (!measure) {
    7308            0 :                     LOGD("no next measure");
    7309            0 :                     rollback();
    7310            0 :                     return;
    7311              :                 }
    7312              :             }
    7313              : 
    7314            0 :             segment = measure->undoGetSegment(SegmentType::ChordRest, newTick);
    7315            0 :             break;
    7316              :         }
    7317              : 
    7318            0 :         if (segment->tick() == newTick) {
    7319            0 :             break;
    7320              :         }
    7321              : 
    7322            0 :         if (nearNoteOrRest) {
    7323            0 :             track_idx_t minTrack = trackZeroVoice(track);
    7324            0 :             track_idx_t maxTrack = minTrack + VOICES - 1;
    7325            0 :             if (segment->hasAnnotationOrElement(ElementType::HARMONY, minTrack, maxTrack)) {
    7326            0 :                 break;
    7327              :             }
    7328              :         }
    7329            0 :     }
    7330              : 
    7331            0 :     mu::engraving::Harmony* nextHarmony = findHarmonyInSegment(segment, track, harmony->textStyleType());
    7332            0 :     if (!nextHarmony) {
    7333            0 :         nextHarmony = createHarmony(segment, track, harmony->harmonyType());
    7334            0 :         score()->undoAddElement(nextHarmony);
    7335              :     }
    7336              : 
    7337            0 :     apply();
    7338            0 :     startEditText(nextHarmony);
    7339            0 :     showItem(nextHarmony);
    7340              : }
    7341              : 
    7342              : //! NOTE: Copied from ScoreView::harmonyTab
    7343            0 : void NotationInteraction::navigateToHarmonyInNearMeasure(MoveDirection direction)
    7344              : {
    7345            0 :     Harmony* harmony = editedHarmony();
    7346            0 :     if (!harmony) {
    7347            0 :         return;
    7348              :     }
    7349              : 
    7350            0 :     Segment* segment = harmonySegment(harmony);
    7351            0 :     if (!segment) {
    7352            0 :         LOGD("harmonyTicksTab: no segment");
    7353            0 :         return;
    7354              :     }
    7355              : 
    7356              :     // moving to next/prev measure
    7357            0 :     Measure* measure = segment->measure();
    7358            0 :     if (measure) {
    7359            0 :         if (direction == MoveDirection::Left) {
    7360            0 :             measure = measure->prevMeasure();
    7361              :         } else {
    7362            0 :             measure = measure->nextMeasure();
    7363              :         }
    7364              :     }
    7365              : 
    7366            0 :     if (!measure) {
    7367            0 :         LOGD("no prev/next measure");
    7368            0 :         return;
    7369              :     }
    7370              : 
    7371            0 :     segment = measure->findSegment(mu::engraving::SegmentType::ChordRest, measure->tick());
    7372            0 :     if (!segment) {
    7373            0 :         LOGD("no ChordRest segment as measure");
    7374            0 :         return;
    7375              :     }
    7376              : 
    7377            0 :     track_idx_t track = harmony->track();
    7378              : 
    7379            0 :     doEndEditElement();
    7380              : 
    7381            0 :     mu::engraving::Harmony* nextHarmony = findHarmonyInSegment(segment, track, harmony->textStyleType());
    7382            0 :     if (!nextHarmony) {
    7383            0 :         nextHarmony = createHarmony(segment, track, harmony->harmonyType());
    7384              : 
    7385            0 :         startEdit(TranslatableString("undoableAction", "Navigate to next chord symbol"));
    7386            0 :         score()->undoAddElement(nextHarmony);
    7387            0 :         apply();
    7388              :     }
    7389              : 
    7390            0 :     startEditText(nextHarmony);
    7391            0 :     showItem(nextHarmony);
    7392              : }
    7393              : 
    7394              : //! NOTE: Copied from ScoreView::harmonyBeatsTab
    7395            0 : void NotationInteraction::navigateToHarmony(const Fraction& ticks)
    7396              : {
    7397            0 :     Harmony* harmony = editedHarmony();
    7398            0 :     if (!harmony) {
    7399            0 :         return;
    7400              :     }
    7401              : 
    7402            0 :     Segment* segment = harmonySegment(harmony);
    7403            0 :     if (!segment) {
    7404            0 :         LOGD("no segment");
    7405            0 :         return;
    7406              :     }
    7407              : 
    7408            0 :     Measure* measure = segment->measure();
    7409              : 
    7410            0 :     Fraction newTick   = segment->tick() + ticks;
    7411              : 
    7412              :     // find the measure containing the target tick
    7413            0 :     while (newTick >= measure->endTick()) {
    7414            0 :         measure = measure->nextMeasure();
    7415            0 :         if (!measure) {
    7416            0 :             LOGD("no next measure");
    7417            0 :             return;
    7418              :         }
    7419              :     }
    7420              : 
    7421              :     // look for a segment at this tick; if none, create one
    7422            0 :     while (segment && segment->tick() < newTick) {
    7423            0 :         segment = segment->next1(mu::engraving::SegmentType::ChordRest);
    7424              :     }
    7425              : 
    7426            0 :     doEndEditElement();
    7427              : 
    7428            0 :     startEdit(TranslatableString("undoableAction", "Navigate to chord symbol"));
    7429              : 
    7430            0 :     if (!segment || segment->tick() > newTick) {      // no ChordRest segment at this tick
    7431            0 :         segment = measure->undoGetSegment(SegmentType::ChordRest, newTick);
    7432              :     }
    7433              : 
    7434            0 :     engraving::track_idx_t track = harmony->track();
    7435              : 
    7436            0 :     mu::engraving::Harmony* nextHarmony = findHarmonyInSegment(segment, track, harmony->textStyleType());
    7437            0 :     if (!nextHarmony) {
    7438            0 :         nextHarmony = createHarmony(segment, track, harmony->harmonyType());
    7439            0 :         score()->undoAddElement(nextHarmony);
    7440              :     }
    7441              : 
    7442            0 :     apply();
    7443            0 :     startEditText(nextHarmony);
    7444            0 :     showItem(nextHarmony);
    7445              : }
    7446              : 
    7447              : //! NOTE: Copied from ScoreView::figuredBassTab
    7448            0 : void NotationInteraction::navigateToNearFiguredBass(MoveDirection direction)
    7449              : {
    7450            0 :     mu::engraving::FiguredBass* fb = mu::engraving::toFiguredBass(m_editData.element);
    7451            0 :     mu::engraving::Segment* segm = fb->segment();
    7452            0 :     track_idx_t track = fb->track();
    7453            0 :     bool backDirection = direction == MoveDirection::Left;
    7454              : 
    7455            0 :     if (!segm) {
    7456            0 :         LOGD("figuredBassTab: no segment");
    7457            0 :         return;
    7458              :     }
    7459              : 
    7460              :     // search next chord segment in same staff
    7461            0 :     mu::engraving::Segment* nextSegm = backDirection ? segm->prev1(mu::engraving::SegmentType::ChordRest) : segm->next1(
    7462            0 :         mu::engraving::SegmentType::ChordRest);
    7463            0 :     track_idx_t minTrack = trackZeroVoice(track);
    7464            0 :     track_idx_t maxTrack = minTrack + VOICES - 1;
    7465              : 
    7466            0 :     while (nextSegm) { // look for a ChordRest in the compatible track range
    7467            0 :         if (nextSegm->hasAnnotationOrElement(ElementType::FIGURED_BASS, minTrack, maxTrack)) {
    7468            0 :             break;
    7469              :         }
    7470            0 :         nextSegm = backDirection ? nextSegm->prev1(mu::engraving::SegmentType::ChordRest) : nextSegm->next1(
    7471              :             mu::engraving::SegmentType::ChordRest);
    7472              :     }
    7473              : 
    7474            0 :     if (!nextSegm) {
    7475            0 :         LOGD("figuredBassTab: no prev/next segment");
    7476            0 :         return;
    7477              :     }
    7478              : 
    7479            0 :     doEndEditElement();
    7480              : 
    7481            0 :     bool bNew = false;
    7482              :     // add a (new) FB element, using chord duration as default duration
    7483            0 :     mu::engraving::FiguredBass* fbNew = mu::engraving::FiguredBass::addFiguredBassToSegment(nextSegm, track, Fraction(0, 1), &bNew);
    7484            0 :     if (bNew) {
    7485            0 :         startEdit(TranslatableString("undoableAction", "Navigate to next figured bass"));
    7486            0 :         score()->undoAddElement(fbNew);
    7487            0 :         apply();
    7488              :     }
    7489              : 
    7490            0 :     startEditText(fbNew);
    7491            0 :     showItem(fbNew);
    7492              : }
    7493              : 
    7494              : //! NOTE: Copied from ScoreView::figuredBassTab
    7495            0 : void NotationInteraction::navigateToFiguredBassInNearMeasure(MoveDirection direction)
    7496              : {
    7497            0 :     mu::engraving::FiguredBass* fb = mu::engraving::toFiguredBass(m_editData.element);
    7498            0 :     mu::engraving::Segment* segm = fb->segment();
    7499              : 
    7500            0 :     if (!segm) {
    7501            0 :         LOGD("figuredBassTab: no segment");
    7502            0 :         return;
    7503              :     }
    7504              : 
    7505              :     // if moving to next/prev measure
    7506            0 :     Measure* meas = segm->measure();
    7507            0 :     if (meas) {
    7508            0 :         if (direction == MoveDirection::Left) {
    7509            0 :             meas = meas->prevMeasure();
    7510              :         } else {
    7511            0 :             meas = meas->nextMeasure();
    7512              :         }
    7513              :     }
    7514            0 :     if (!meas) {
    7515            0 :         LOGD("figuredBassTab: no prev/next measure");
    7516            0 :         return;
    7517              :     }
    7518              :     // find initial ChordRest segment
    7519            0 :     mu::engraving::Segment* nextSegm = meas->findSegment(mu::engraving::SegmentType::ChordRest, meas->tick());
    7520            0 :     if (!nextSegm) {
    7521            0 :         LOGD("figuredBassTab: no ChordRest segment at measure");
    7522            0 :         return;
    7523              :     }
    7524              : 
    7525            0 :     doEndEditElement();
    7526              : 
    7527            0 :     bool bNew = false;
    7528              :     // add a (new) FB element, using chord duration as default duration
    7529            0 :     mu::engraving::FiguredBass* fbNew = mu::engraving::FiguredBass::addFiguredBassToSegment(nextSegm, fb->track(), Fraction(0, 1), &bNew);
    7530            0 :     if (bNew) {
    7531            0 :         startEdit(TranslatableString("undoableAction", "Navigate to next figured bass"));
    7532            0 :         score()->undoAddElement(fbNew);
    7533            0 :         apply();
    7534              :     }
    7535              : 
    7536            0 :     startEditText(fbNew);
    7537            0 :     showItem(fbNew);
    7538              : }
    7539              : 
    7540              : //! NOTE: Copied from ScoreView::figuredBassTicksTab
    7541            0 : void NotationInteraction::navigateToFiguredBass(const Fraction& ticks)
    7542              : {
    7543            0 :     mu::engraving::FiguredBass* fb = mu::engraving::toFiguredBass(m_editData.element);
    7544            0 :     track_idx_t track = fb->track();
    7545            0 :     mu::engraving::Segment* segm = fb->segment();
    7546            0 :     if (!segm) {
    7547            0 :         LOGD("figuredBassTicksTab: no segment");
    7548            0 :         return;
    7549              :     }
    7550            0 :     Measure* measure = segm->measure();
    7551              : 
    7552            0 :     Fraction nextSegTick   = segm->tick() + ticks;
    7553              : 
    7554              :     // find the measure containing the target tick
    7555            0 :     while (nextSegTick >= measure->endTick()) {
    7556            0 :         measure = measure->nextMeasure();
    7557            0 :         if (!measure) {
    7558            0 :             LOGD("figuredBassTicksTab: no next measure");
    7559            0 :             return;
    7560              :         }
    7561              :     }
    7562              : 
    7563              :     // look for a segment at this tick; if none, create one
    7564            0 :     mu::engraving::Segment* nextSegm = segm;
    7565            0 :     while (nextSegm && nextSegm->tick() < nextSegTick) {
    7566            0 :         nextSegm = nextSegm->next1(mu::engraving::SegmentType::ChordRest);
    7567              :     }
    7568              : 
    7569            0 :     if (!nextSegm || nextSegm->tick() > nextSegTick) {      // no ChordRest segm at this tick
    7570            0 :         nextSegm = measure->undoGetSegment(SegmentType::ChordRest, nextSegTick);
    7571            0 :         if (!nextSegm) {
    7572            0 :             LOGD("figuredBassTicksTab: no next segment");
    7573            0 :             return;
    7574              :         }
    7575              :     }
    7576              : 
    7577            0 :     doEndEditElement();
    7578              : 
    7579            0 :     startEdit(TranslatableString("undoableAction", "Navigate to figured bass"));
    7580              : 
    7581            0 :     bool bNew = false;
    7582            0 :     mu::engraving::FiguredBass* fbNew = mu::engraving::FiguredBass::addFiguredBassToSegment(nextSegm, track, ticks, &bNew);
    7583            0 :     if (bNew) {
    7584            0 :         score()->undoAddElement(fbNew);
    7585              :     }
    7586              : 
    7587            0 :     apply();
    7588            0 :     startEditText(fbNew);
    7589            0 :     showItem(fbNew);
    7590              : }
    7591              : 
    7592              : //! NOTE: Copied from ScoreView::textTab
    7593            0 : void NotationInteraction::navigateToNearText(MoveDirection direction)
    7594              : {
    7595            0 :     mu::engraving::EngravingItem* oe = m_editData.element;
    7596            0 :     if (!oe || !oe->isTextBase()) {
    7597            0 :         return;
    7598              :     }
    7599              : 
    7600            0 :     mu::engraving::EngravingItem* op = toEngravingItem(oe->parent());
    7601            0 :     if (!op || !(op->isSegment() || op->isNote())) {
    7602            0 :         LOGD("navigateToNearText: parent not note or segment.");
    7603            0 :         return;
    7604              :     }
    7605              : 
    7606            0 :     TextBase* ot = mu::engraving::toTextBase(oe);
    7607            0 :     mu::engraving::TextStyleType textStyleType = ot->textStyleType();
    7608            0 :     ElementType type = ot->type();
    7609            0 :     mu::engraving::staff_idx_t staffIdx = ot->staffIdx();
    7610            0 :     bool back = direction == MoveDirection::Left;
    7611            0 :     int minTrack = static_cast<int>(trackZeroVoice(oe->track()));
    7612            0 :     int maxTrack = minTrack + VOICES - 1;
    7613              : 
    7614            0 :     mu::engraving::EngravingItem* el = nullptr;
    7615              : 
    7616            0 :     if (op->isNote()) {
    7617              :         // go to next/prev note in same chord, or go to next/prev chord, which may be in another voice
    7618            0 :         Note* origNote = toNote(op);
    7619            0 :         Chord* ch = origNote->chord();
    7620            0 :         const std::vector<Note*>& notes = ch->notes();
    7621              : 
    7622              :         // first, try going to prev/next note in the current chord
    7623            0 :         if (origNote != (back ? notes.back() : notes.front())) {
    7624            0 :             auto it = std::find(notes.begin(), notes.end(), origNote);
    7625            0 :             if (it != notes.end()) {
    7626            0 :                 el = back ? *std::next(it) : *std::prev(it);
    7627              :             }
    7628              :         }
    7629              : 
    7630              :         // next, try going to next/prev grace note chord in the same group as the current
    7631            0 :         const std::vector<Chord*> chordList = ch->allGraceChordsOfMainChord();
    7632              : 
    7633            0 :         if (!el && ch != (back ? chordList.front() : chordList.back())) {
    7634            0 :             auto it = std::find(chordList.begin(), chordList.end(), ch);
    7635            0 :             if (it != chordList.end()) {
    7636            0 :                 if (back) {
    7637            0 :                     const Chord* targetChord = *std::prev(it);
    7638            0 :                     el = targetChord->notes().front();
    7639              :                 } else {
    7640            0 :                     const Chord* targetChord = *std::next(it);
    7641            0 :                     el = targetChord->notes().back();
    7642              :                 }
    7643              :             }
    7644              :         }
    7645              : 
    7646              :         // next, try going to prev/next chord in another voice
    7647            0 :         if (!el) {
    7648            0 :             Segment* seg = ch->segment();
    7649            0 :             if (!seg) {
    7650            0 :                 LOGD("navigateToNearText: no segment");
    7651            0 :                 return;
    7652              :             }
    7653            0 :             int inc = back ? -1 : 1;
    7654            0 :             int sTrack =  int(oe->track()) + inc;
    7655            0 :             int eTrack = back ? minTrack : maxTrack;
    7656            0 :             for (int track = sTrack; back ? (track >= eTrack) : (track <= eTrack); track += inc) {
    7657            0 :                 EngravingItem* e = seg->element(track);
    7658            0 :                 if (e && e->isChord()) {
    7659            0 :                     const std::vector<Chord*> targetChordList = toChord(e)->allGraceChordsOfMainChord();
    7660            0 :                     if (back) {
    7661            0 :                         Chord* targetChord = targetChordList.back();
    7662            0 :                         el = targetChord->notes().front();
    7663              :                     } else {
    7664            0 :                         Chord* targetChord = targetChordList.front();
    7665            0 :                         el = targetChord->notes().back();
    7666              :                     }
    7667            0 :                     break;
    7668            0 :                 }
    7669              :             }
    7670              :         }
    7671              : 
    7672              :         // finally, try going to chord in prev/next segments
    7673            0 :         if (!el) {
    7674            0 :             Segment* seg = ch->segment();
    7675            0 :             seg = back ? seg->prev1(SegmentType::ChordRest) : seg->next1(SegmentType::ChordRest);
    7676            0 :             int sTrack = back ? maxTrack : minTrack;
    7677            0 :             int eTrack = back ? minTrack : maxTrack;
    7678            0 :             int inc = back ? -1 : 1;
    7679            0 :             while (seg) {
    7680            0 :                 for (int track = sTrack; back ? (track >= eTrack) : (track <= eTrack); track += inc) {
    7681            0 :                     EngravingItem* e = seg->element(track);
    7682            0 :                     if (e && e->isChord()) {
    7683            0 :                         const std::vector<Chord*> targetChordList = toChord(e)->allGraceChordsOfMainChord();
    7684            0 :                         if (back) {
    7685            0 :                             Chord* targetChord = targetChordList.back();
    7686            0 :                             el = targetChord->notes().front();
    7687              :                         } else {
    7688            0 :                             Chord* targetChord = targetChordList.front();
    7689            0 :                             el = targetChord->notes().back();
    7690              :                         }
    7691            0 :                         break;
    7692            0 :                     }
    7693              :                 }
    7694              : 
    7695            0 :                 if (el) {
    7696            0 :                     break;
    7697              :                 }
    7698              : 
    7699            0 :                 seg = back ? seg->prev1(SegmentType::ChordRest) : seg->next1(SegmentType::ChordRest);
    7700              :             }
    7701              :         }
    7702            0 :     } else if (op->isSegment()) {
    7703            0 :         Segment* seg = toSegment(op);
    7704            0 :         seg = back ? seg->prev1(SegmentType::ChordRest) : seg->next1(SegmentType::ChordRest);
    7705              : 
    7706              :         // go to first segment with a chord or an existing text
    7707            0 :         while (seg) {
    7708            0 :             for (int track = minTrack; track <= maxTrack; ++track) {
    7709            0 :                 EngravingItem* e = seg->element(track);
    7710            0 :                 if (e && e->isChord()) {
    7711            0 :                     el = e;
    7712            0 :                     break;
    7713              :                 }
    7714              :             }
    7715            0 :             if (el) {
    7716            0 :                 break;
    7717              :             }
    7718              : 
    7719              :             // this segment only contains rests, check for existing text
    7720            0 :             for (EngravingItem* e : seg->annotations()) {
    7721            0 :                 if (e->staffIdx() != staffIdx || e->type() != type) {
    7722            0 :                     continue;
    7723              :                 }
    7724            0 :                 TextBase* nt = mu::engraving::toTextBase(e);
    7725            0 :                 if (nt->textStyleType() == textStyleType) {
    7726            0 :                     el = seg->firstElement(staffIdx);
    7727            0 :                     break;
    7728              :                 }
    7729              :             }
    7730            0 :             if (el) {
    7731            0 :                 break;
    7732              :             }
    7733              : 
    7734            0 :             seg = back ? seg->prev1(SegmentType::ChordRest) : seg->next1(SegmentType::ChordRest);
    7735              :         }
    7736              :     }
    7737              : 
    7738            0 :     if (!el) {
    7739            0 :         return;
    7740              :     }
    7741              : 
    7742              :     // get existing text to edit
    7743            0 :     EngravingItem* textEl = nullptr;
    7744            0 :     if (op->isNote()) {
    7745            0 :         if (!el->isNote()) {
    7746            0 :             LOGD("navigateToNearText: new element is not Note.");
    7747            0 :             return;
    7748              :         }
    7749              :         // check element list of new note
    7750            0 :         for (mu::engraving::EngravingItem* e : toNote(el)->el()) {
    7751            0 :             if (e->type() != type) {
    7752            0 :                 continue;
    7753              :             }
    7754            0 :             TextBase* nt = mu::engraving::toTextBase(e);
    7755            0 :             if (nt->textStyleType() == textStyleType) {
    7756            0 :                 textEl = e;
    7757            0 :                 break;
    7758              :             }
    7759              :         }
    7760            0 :     } else if (op->isSegment()) {
    7761            0 :         if (!el->isChordRest()) {
    7762            0 :             LOGD("navigateToNearText: new element is not ChordRest.");
    7763            0 :             return;
    7764              :         }
    7765              :         // check annotation list of new segment
    7766            0 :         mu::engraving::Segment* ns = toChordRest(el)->segment();
    7767            0 :         for (mu::engraving::EngravingItem* e : ns->annotations()) {
    7768            0 :             if (e->staffIdx() != staffIdx || e->type() != type) {
    7769            0 :                 continue;
    7770              :             }
    7771            0 :             TextBase* nt = mu::engraving::toTextBase(e);
    7772            0 :             if (nt->textStyleType() == textStyleType) {
    7773            0 :                 textEl = e;
    7774            0 :                 break;
    7775              :             }
    7776              :         }
    7777              :     }
    7778              : 
    7779            0 :     doEndEditElement();
    7780              : 
    7781            0 :     if (textEl) {
    7782              :         // edit existing text
    7783            0 :         TextBase* text = toTextBase(textEl);
    7784              : 
    7785            0 :         if (text) {
    7786            0 :             startEditText(text);
    7787            0 :             text->selectAll();
    7788            0 :             showItem(text);
    7789              :         }
    7790              :     } else {
    7791              :         // add new text if no existing element to edit
    7792              :         // TODO: for tempo text, would be better to create empty tempo element
    7793            0 :         if (type != ElementType::TEMPO_TEXT) {
    7794            0 :             addTextToItem(textStyleType, el);
    7795              :         }
    7796              :     }
    7797              : }
    7798              : 
    7799              : //! NOTE: Copied from ScoreView::lyricsUnderscore
    7800            0 : void NotationInteraction::addMelisma()
    7801              : {
    7802            0 :     if (!m_editData.element || !m_editData.element->isLyrics()) {
    7803            0 :         LOGW("addMelisma called with invalid current element");
    7804            0 :         return;
    7805              :     }
    7806            0 :     Lyrics* lyrics = toLyrics(m_editData.element);
    7807            0 :     ChordRest* initialCR = lyrics->chordRest();
    7808            0 :     const bool hasPrecedingRepeat = initialCR->hasPrecedingJumpItem();
    7809            0 :     track_idx_t track = lyrics->track();
    7810            0 :     Segment* segment = lyrics->segment();
    7811            0 :     int verse = lyrics->verse();
    7812            0 :     PlacementV placement = lyrics->placement();
    7813            0 :     PropertyFlags pFlags = lyrics->propertyFlags(Pid::PLACEMENT);
    7814            0 :     FontStyle fStyle = lyrics->fontStyle();
    7815            0 :     PropertyFlags fFlags = lyrics->propertyFlags(Pid::FONT_STYLE);
    7816            0 :     double yOffset = lyrics->offset().y();
    7817            0 :     AutoOnOff centering = lyrics->centerBetweenStaves();
    7818            0 :     Fraction endTick = segment->tick(); // a previous melisma cannot extend beyond this point
    7819            0 :     endEditText();
    7820              : 
    7821              :     // search next chord
    7822            0 :     Segment* nextSegment = segment;
    7823            0 :     while ((nextSegment = nextSegment->next1(SegmentType::ChordRest))) {
    7824            0 :         EngravingItem* el = nextSegment->element(track);
    7825            0 :         if (el && el->isChord()) {
    7826            0 :             break;
    7827              :         }
    7828              :     }
    7829              : 
    7830            0 :     if (!segmentsAreAdjacent(segment, nextSegment)) {
    7831            0 :         nextSegment = nullptr;
    7832              :     }
    7833              : 
    7834              :     // look for the lyrics we are moving from; may be the current lyrics or a previous one
    7835              :     // we are extending with several underscores
    7836            0 :     Segment* curSeg = segment;
    7837            0 :     Lyrics* fromLyrics = nullptr;
    7838            0 :     PartialLyricsLine* prevPartialLyricsLine = nullptr;
    7839            0 :     while (segment && segmentsAreAdjacent(segment, curSeg)) {
    7840            0 :         ChordRest* cr = toChordRest(segment->element(track));
    7841            0 :         if (cr) {
    7842            0 :             fromLyrics = cr->lyrics(verse, placement);
    7843            0 :             if (fromLyrics) {
    7844            0 :                 break;
    7845              :             }
    7846              :             // Check if there is a partial melisma to extend
    7847            0 :             auto spanners = score()->spannerMap().findOverlapping(cr->tick().ticks(), cr->endTick().ticks());
    7848            0 :             for (auto& spanner : spanners) {
    7849            0 :                 if (!spanner.value->isPartialLyricsLine() || spanner.value->staffIdx() != cr->staffIdx()) {
    7850            0 :                     continue;
    7851              :                 }
    7852            0 :                 PartialLyricsLine* lyricsLine = toPartialLyricsLine(spanner.value);
    7853            0 :                 if (lyricsLine->verse() != verse || lyricsLine->placement() != placement || !lyricsLine->isEndMelisma()) {
    7854            0 :                     continue;
    7855              :                 }
    7856              : 
    7857            0 :                 prevPartialLyricsLine = lyricsLine;
    7858            0 :                 break;
    7859              :             }
    7860              : 
    7861            0 :             if (prevPartialLyricsLine) {
    7862            0 :                 break;
    7863              :             }
    7864            0 :         }
    7865            0 :         segment = segment->prev1(SegmentType::ChordRest);
    7866              :     }
    7867              : 
    7868              :     // one-chord melisma?
    7869              :     // if still at melisma initial chord and there is a valid next chord (if not,
    7870              :     // there will be no melisma anyway), set a temporary melisma duration
    7871            0 :     if (fromLyrics == lyrics && nextSegment) {
    7872            0 :         score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line"));
    7873            0 :         lyrics->undoChangeProperty(Pid::LYRIC_TICKS, Lyrics::TEMP_MELISMA_TICKS);
    7874            0 :         score()->setLayoutAll();
    7875            0 :         score()->endCmd();
    7876              :     }
    7877              : 
    7878            0 :     auto getPartialLyricLineEndSegment = [&]() -> Fraction {
    7879            0 :         Segment* endSegment = segment;
    7880            0 :         while (endSegment && endSegment->tick() < endTick) {
    7881            0 :             endSegment = endSegment->nextCR(track, true);
    7882              :         }
    7883            0 :         EngravingItem* endSegmentElement = endSegment->element(track);
    7884            0 :         if (endSegment->tick() == endTick && endSegmentElement && endSegmentElement->isChord()) {
    7885            0 :             Segment* endChordSeg = endSegment;
    7886            0 :             Chord* endChord = toChord(endSegmentElement);
    7887              : 
    7888            0 :             endSegment = endChordSeg->nextCR(track, false);
    7889              : 
    7890            0 :             if (!endSegment || endSegment->tick() > endChord->endTick()) {
    7891            0 :                 endSegment = endChordSeg;
    7892            0 :                 while (endSegment && endSegment->tick() < endChord->endTick()) {
    7893            0 :                     endSegment = endSegment->nextCR(muse::nidx, true);
    7894              :                 }
    7895              :             }
    7896              :         }
    7897            0 :         return endSegment ? endSegment->tick() : score()->endTick();
    7898            0 :     };
    7899              : 
    7900            0 :     if (!nextSegment) {
    7901            0 :         score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line"));
    7902            0 :         if (fromLyrics) {
    7903            0 :             switch (fromLyrics->syllabic()) {
    7904            0 :             case LyricsSyllabic::SINGLE:
    7905              :             case LyricsSyllabic::END:
    7906            0 :                 break;
    7907            0 :             default:
    7908            0 :                 fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END));
    7909            0 :                 break;
    7910              :             }
    7911            0 :             if (fromLyrics->segment()->tick() < endTick
    7912            0 :                 || (endTick + initialCR->actualTicks() == segment->measure()->endTick() && initialCR->hasFollowingJumpItem())) {
    7913            0 :                 Fraction ticks = std::max(endTick - fromLyrics->segment()->tick(), Lyrics::TEMP_MELISMA_TICKS);
    7914            0 :                 fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, ticks);
    7915              :             }
    7916              :         }
    7917              : 
    7918            0 :         if (hasPrecedingRepeat && !prevPartialLyricsLine) {
    7919              :             // No from lyrics - create incoming partial melisma
    7920            0 :             PartialLyricsLine* melisma = Factory::createPartialLyricsLine(score()->dummy());
    7921            0 :             melisma->setIsEndMelisma(true);
    7922            0 :             melisma->setVerse(verse);
    7923            0 :             melisma->setPlacement(placement);
    7924            0 :             melisma->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7925            0 :             melisma->setCenterBetweenStaves(centering);
    7926            0 :             melisma->setTick(initialCR->tick());
    7927            0 :             melisma->setTicks(initialCR->ticks());
    7928            0 :             melisma->setTrack(initialCR->track());
    7929            0 :             melisma->setTrack2(initialCR->track());
    7930              : 
    7931            0 :             score()->undoAddElement(melisma);
    7932            0 :         } else if (prevPartialLyricsLine) {
    7933            0 :             const Fraction segEndTick = getPartialLyricLineEndSegment();
    7934            0 :             const Fraction tickDiff = segEndTick - prevPartialLyricsLine->tick2();
    7935            0 :             prevPartialLyricsLine->undoMoveEnd(tickDiff);
    7936              :         }
    7937              : 
    7938            0 :         if (fromLyrics) {
    7939            0 :             score()->select(fromLyrics, SelectType::SINGLE, 0);
    7940              :         }
    7941            0 :         score()->setLayoutAll();
    7942            0 :         score()->endCmd();
    7943            0 :         return;
    7944              :     }
    7945              : 
    7946              :     // if a place for a new lyrics has been found, create a lyrics there
    7947            0 :     ChordRest* nextCR = toChordRest(nextSegment->element(track));
    7948              : 
    7949              :     // Disallow melisma lines between non-adjacent repeat sections eg. 1st volta -> 2nd volta
    7950              :     // Instead, try to add partial melisma line
    7951            0 :     if (nextCR && fromLyrics) {
    7952            0 :         Measure* toLyricsMeasure = nextCR->measure();
    7953            0 :         Measure* fromLyricsMeasure = fromLyrics->measure();
    7954              : 
    7955            0 :         if (toLyricsMeasure != fromLyricsMeasure && fromLyricsMeasure->lastChordRest(track)->hasFollowingJumpItem()) {
    7956            0 :             const std::vector<Measure*> previousRepeats = findPreviousRepeatMeasures(toLyricsMeasure);
    7957            0 :             const bool inPrecedingRepeatSeg = muse::contains(previousRepeats, fromLyricsMeasure);
    7958            0 :             if (!previousRepeats.empty() && !inPrecedingRepeatSeg) {
    7959            0 :                 fromLyrics = nullptr;
    7960              :             }
    7961            0 :         }
    7962              :     }
    7963              : 
    7964            0 :     score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line"));
    7965            0 :     Lyrics* toLyrics = nextCR->lyrics(verse, placement);
    7966            0 :     bool newLyrics = (toLyrics == nullptr);
    7967            0 :     if (!toLyrics) {
    7968            0 :         toLyrics = Factory::createLyrics(nextCR);
    7969            0 :         toLyrics->setTrack(track);
    7970            0 :         toLyrics->setVerse(verse);
    7971            0 :         const TextStyleType styleType(toLyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD);
    7972            0 :         toLyrics->setTextStyleType(styleType);
    7973              : 
    7974            0 :         toLyrics->setPlacement(placement);
    7975            0 :         toLyrics->setPropertyFlags(Pid::PLACEMENT, pFlags);
    7976            0 :         toLyrics->setCenterBetweenStaves(centering);
    7977            0 :         toLyrics->setSyllabic(LyricsSyllabic::SINGLE);
    7978            0 :         toLyrics->setFontStyle(fStyle);
    7979            0 :         toLyrics->setPropertyFlags(Pid::FONT_STYLE, fFlags);
    7980            0 :         toLyrics->setOffset(PointF(0.0, yOffset));
    7981              :     }
    7982              :     // as we arrived at toLyrics by an underscore, it cannot have syllabic dashes before
    7983            0 :     else if (toLyrics->syllabic() == LyricsSyllabic::MIDDLE) {
    7984            0 :         toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::BEGIN));
    7985            0 :     } else if (toLyrics->syllabic() == LyricsSyllabic::END) {
    7986            0 :         toLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::SINGLE));
    7987              :     }
    7988              : 
    7989            0 :     if (fromLyrics) {
    7990              :         // as we moved away from fromLyrics by an underscore,
    7991              :         // it can be isolated or terminal but cannot have dashes after
    7992            0 :         switch (fromLyrics->syllabic()) {
    7993            0 :         case LyricsSyllabic::SINGLE:
    7994              :         case LyricsSyllabic::END:
    7995            0 :             break;
    7996            0 :         default:
    7997            0 :             fromLyrics->undoChangeProperty(Pid::SYLLABIC, int(LyricsSyllabic::END));
    7998            0 :             break;
    7999              :         }
    8000              :         // for the same reason, if it has a melisma, this cannot extend beyond toLyrics
    8001            0 :         if (fromLyrics->segment()->tick() < endTick) {
    8002            0 :             fromLyrics->undoChangeProperty(Pid::LYRIC_TICKS, endTick - fromLyrics->segment()->tick());
    8003              :         }
    8004            0 :     } else if (hasPrecedingRepeat && !prevPartialLyricsLine) {
    8005              :         // No from lyrics - create incoming partial melisma
    8006            0 :         PartialLyricsLine* melisma = Factory::createPartialLyricsLine(score()->dummy());
    8007            0 :         melisma->setIsEndMelisma(true);
    8008            0 :         melisma->setVerse(verse);
    8009            0 :         melisma->setPlacement(placement);
    8010            0 :         melisma->setPropertyFlags(Pid::PLACEMENT, pFlags);
    8011            0 :         melisma->setCenterBetweenStaves(centering);
    8012            0 :         melisma->setTick(initialCR->tick());
    8013            0 :         melisma->setTicks(initialCR->ticks());
    8014            0 :         melisma->setTrack(initialCR->track());
    8015            0 :         melisma->setTrack2(initialCR->track());
    8016              : 
    8017            0 :         score()->undoAddElement(melisma);
    8018            0 :     } else if (prevPartialLyricsLine) {
    8019            0 :         const Fraction segEndTick = getPartialLyricLineEndSegment();
    8020            0 :         const Fraction tickDiff = segEndTick - prevPartialLyricsLine->tick2();
    8021            0 :         prevPartialLyricsLine->undoMoveEnd(tickDiff);
    8022            0 :         prevPartialLyricsLine->triggerLayout();
    8023              :     }
    8024            0 :     score()->endCmd();
    8025              : 
    8026            0 :     score()->startCmd(TranslatableString("undoableAction", "Enter lyrics extension line"));
    8027            0 :     if (newLyrics) {
    8028            0 :         score()->undoAddElement(toLyrics);
    8029              :     }
    8030            0 :     score()->endCmd();
    8031              : 
    8032            0 :     score()->select(toLyrics, SelectType::SINGLE, 0);
    8033            0 :     startEditText(toLyrics, PointF());
    8034              : 
    8035            0 :     toLyrics->selectAll();
    8036              : }
    8037              : 
    8038              : //! NOTE: Copied from ScoreView::lyricsReturn
    8039            0 : void NotationInteraction::addLyricsVerse()
    8040              : {
    8041            0 :     if (!m_editData.element || !m_editData.element->isLyrics()) {
    8042            0 :         LOGW("nextLyricVerse called with invalid current element");
    8043            0 :         return;
    8044              :     }
    8045            0 :     mu::engraving::Lyrics* oldLyrics = toLyrics(m_editData.element);
    8046            0 :     mu::engraving::FontStyle fStyle = oldLyrics->fontStyle();
    8047            0 :     mu::engraving::PropertyFlags fFlags = oldLyrics->propertyFlags(mu::engraving::Pid::FONT_STYLE);
    8048            0 :     mu::engraving::AutoOnOff centering = oldLyrics->centerBetweenStaves();
    8049              : 
    8050            0 :     endEditText();
    8051              : 
    8052            0 :     score()->startCmd(TranslatableString("undoableAction", "Add lyrics verse"));
    8053            0 :     int newVerse = oldLyrics->verse() + 1;
    8054              : 
    8055            0 :     mu::engraving::Lyrics* lyrics = Factory::createLyrics(oldLyrics->chordRest());
    8056            0 :     lyrics->setTrack(oldLyrics->track());
    8057            0 :     lyrics->setPlacement(oldLyrics->placement());
    8058            0 :     lyrics->setPropertyFlags(mu::engraving::Pid::PLACEMENT, oldLyrics->propertyFlags(mu::engraving::Pid::PLACEMENT));
    8059            0 :     lyrics->setCenterBetweenStaves(centering);
    8060              : 
    8061            0 :     lyrics->setVerse(newVerse);
    8062            0 :     const mu::engraving::TextStyleType styleType(lyrics->isEven() ? TextStyleType::LYRICS_EVEN : TextStyleType::LYRICS_ODD);
    8063            0 :     lyrics->setTextStyleType(styleType);
    8064              : 
    8065            0 :     lyrics->setFontStyle(fStyle);
    8066            0 :     lyrics->setPropertyFlags(mu::engraving::Pid::FONT_STYLE, fFlags);
    8067              : 
    8068            0 :     score()->undoAddElement(lyrics);
    8069            0 :     score()->endCmd();
    8070              : 
    8071            0 :     score()->select(lyrics, SelectType::SINGLE, 0);
    8072            0 :     startEditText(lyrics, PointF());
    8073              : }
    8074              : 
    8075            0 : void NotationInteraction::addGuitarBend(GuitarBendType bendType)
    8076              : {
    8077            0 :     Score* score = this->score();
    8078            0 :     IF_ASSERT_FAILED(score) {
    8079            0 :         MScore::setError(MsError::NO_NOTE_SELECTED);
    8080            0 :         checkAndShowError();
    8081            0 :         return;
    8082              :     }
    8083              : 
    8084            0 :     const Selection& selection = score->selection();
    8085            0 :     const std::vector<Note*>& noteList = selection.noteList();
    8086            0 :     if (selection.isNone() || noteList.empty()) {
    8087            0 :         MScore::setError(MsError::NO_NOTE_SELECTED);
    8088            0 :         checkAndShowError();
    8089            0 :         return;
    8090              :     }
    8091              : 
    8092            0 :     startEdit(TranslatableString("undoableAction", "Enter guitar bend"));
    8093              : 
    8094            0 :     Note* startNote = nullptr;
    8095            0 :     Note* endNote = nullptr;
    8096            0 :     bool noteToNote = false;
    8097            0 :     if (selection.isList() && noteList.size() == 2) {
    8098            0 :         startNote = noteList.front();
    8099            0 :         endNote = noteList.back();
    8100            0 :         if (endNote->tick() > startNote->tick()) {
    8101            0 :             noteToNote = true;
    8102              :         }
    8103              :     }
    8104              : 
    8105            0 :     mu::engraving::GuitarBend* guitarBend = nullptr;
    8106            0 :     if (noteToNote) {
    8107            0 :         guitarBend = score->addGuitarBend(bendType, startNote, endNote);
    8108              :     } else {
    8109            0 :         for (Note* note : noteList) {
    8110              :             // (will select the last one)
    8111            0 :             guitarBend = score->addGuitarBend(bendType, note, nullptr);
    8112              :         }
    8113              :     }
    8114              : 
    8115            0 :     if (guitarBend) {
    8116            0 :         apply();
    8117            0 :         select({ guitarBend });
    8118              :     } else {
    8119            0 :         rollback();
    8120              :     }
    8121            0 : }
    8122              : 
    8123            0 : void NotationInteraction::addFretboardDiagram()
    8124              : {
    8125            0 :     Score* score = this->score();
    8126            0 :     IF_ASSERT_FAILED(score) {
    8127            0 :         MScore::setError(MsError::NO_NOTE_REST_HARMONY_SELECTED);
    8128            0 :         checkAndShowError();
    8129            0 :         return;
    8130              :     };
    8131              : 
    8132            0 :     auto selection = this->selection();
    8133            0 :     std::vector<EngravingItem*> filteredElements;
    8134              : 
    8135            0 :     if (selection && !selection->isNone()) {
    8136            0 :         std::vector<EngravingItem*> selectedElements;
    8137              : 
    8138            0 :         if (EngravingItem* element = selection->element()) {
    8139            0 :             selectedElements = { element };
    8140              :         } else {
    8141            0 :             selectedElements = selection->elements();
    8142              :         }
    8143              : 
    8144            0 :         for (EngravingItem* element : selectedElements) {
    8145            0 :             if (!element || !element->isHarmony()) {
    8146            0 :                 continue;
    8147              :             }
    8148              : 
    8149            0 :             if (!element->ownershipParent()->isFretDiagram()) {
    8150            0 :                 filteredElements.emplace_back(element);
    8151              :             }
    8152              :         }
    8153            0 :     }
    8154              : 
    8155            0 :     if (filteredElements.empty()) {
    8156            0 :         MScore::setError(MsError::NO_NOTE_REST_HARMONY_SELECTED);
    8157            0 :         checkAndShowError();
    8158            0 :         return;
    8159              :     }
    8160              : 
    8161            0 :     startEdit(TranslatableString("undoableAction", "Add fretboard diagram"));
    8162              : 
    8163            0 :     engraving::FretDiagram* lastAddedDiagram = nullptr;
    8164            0 :     std::vector<engraving::FretDiagram*> created;
    8165              : 
    8166            0 :     for (EngravingItem* element : filteredElements) {
    8167            0 :         engraving::FretDiagram* diagram = engraving::Factory::createFretDiagram(score->dummy());
    8168            0 :         diagram->setTrack(element->track());
    8169              : 
    8170            0 :         Harmony* harmony = toHarmony(element);
    8171            0 :         diagram->updateDiagram(harmony->harmonyName());
    8172              : 
    8173            0 :         diagram->setOwnershipParent(harmony->parent());
    8174            0 :         score->undoAddElement(diagram);
    8175            0 :         created.push_back(diagram);
    8176            0 :         lastAddedDiagram = diagram;
    8177              :     }
    8178              : 
    8179            0 :     for (int i = int(created.size()) - 1; i >= 0; --i) {
    8180            0 :         FretDiagram* diagram = created[i];
    8181            0 :         Harmony* harmony = toHarmony(filteredElements[i]);
    8182              : 
    8183            0 :         harmony->undoResetProperty(Pid::OFFSET);
    8184            0 :         harmony->undoChangeProperty(Pid::ALIGN, Align(AlignH::HCENTER, AlignV::BASELINE));
    8185            0 :         if (harmony->propertyFlags(Pid::ALIGN) == PropertyFlags::STYLED) {
    8186            0 :             harmony->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
    8187              :         }
    8188              : 
    8189            0 :         score->undoChangeParent(harmony, diagram,
    8190            0 :                                 track2staff(filteredElements[i]->track()));
    8191              :     }
    8192              : 
    8193            0 :     apply();
    8194              : 
    8195            0 :     if (lastAddedDiagram) {
    8196            0 :         select({ lastAddedDiagram });
    8197              :     }
    8198            0 : }
    8199              : 
    8200            0 : Harmony* NotationInteraction::editedHarmony() const
    8201              : {
    8202            0 :     Harmony* harmony = toHarmony(m_editData.element);
    8203            0 :     if (!harmony) {
    8204            0 :         return nullptr;
    8205              :     }
    8206              : 
    8207            0 :     if (!harmony->parent() || (!harmony->parent()->isSegment()
    8208            0 :                                && !harmony->parent()->isFretDiagram())) {
    8209            0 :         LOGD("no segment parent");
    8210            0 :         return nullptr;
    8211              :     }
    8212              : 
    8213            0 :     return harmony;
    8214              : }
    8215              : 
    8216            0 : Segment* NotationInteraction::harmonySegment(const Harmony* harmony) const
    8217              : {
    8218            0 :     if (!harmony || !harmony->parent()) {
    8219            0 :         return nullptr;
    8220              :     }
    8221              : 
    8222            0 :     EngravingObject* parent = harmony->parent();
    8223            0 :     Segment* segment = parent->isSegment() ? toSegment(parent) : nullptr;
    8224            0 :     if (!segment && parent->isFretDiagram()) {
    8225            0 :         segment = toFretDiagram(parent)->segment();
    8226              :     }
    8227              : 
    8228            0 :     return segment;
    8229              : }
    8230              : 
    8231            0 : mu::engraving::Harmony* NotationInteraction::findHarmonyInSegment(const mu::engraving::Segment* segment, track_idx_t track,
    8232              :                                                                   mu::engraving::TextStyleType textStyleType) const
    8233              : {
    8234            0 :     for (mu::engraving::EngravingItem* e : segment->annotations()) {
    8235            0 :         if (!e->isHarmony() && !e->isFretDiagram()) {
    8236            0 :             continue;
    8237              :         }
    8238              : 
    8239            0 :         Harmony* harmony = e->isHarmony() ? toHarmony(e) : nullptr;
    8240            0 :         if (e->isFretDiagram()) {
    8241            0 :             harmony = toFretDiagram(e)->harmony();
    8242              :         }
    8243              : 
    8244            0 :         if (!harmony) {
    8245            0 :             continue;
    8246              :         }
    8247              : 
    8248            0 :         if (harmony->track() == track && harmony->textStyleType() == textStyleType) {
    8249            0 :             return harmony;
    8250              :         }
    8251              :     }
    8252              : 
    8253            0 :     return nullptr;
    8254              : }
    8255              : 
    8256            0 : mu::engraving::Harmony* NotationInteraction::createHarmony(mu::engraving::Segment* segment, track_idx_t track,
    8257              :                                                            mu::engraving::HarmonyType type) const
    8258              : {
    8259            0 :     mu::engraving::Harmony* harmony = Factory::createHarmony(segment);
    8260            0 :     harmony->setScore(score());
    8261            0 :     harmony->setTrack(track);
    8262            0 :     harmony->setHarmonyType(type);
    8263              : 
    8264            0 :     return harmony;
    8265              : }
    8266              : 
    8267            0 : void NotationInteraction::startEditText(mu::engraving::TextBase* text)
    8268              : {
    8269            0 :     doEndEditElement();
    8270            0 :     select({ text }, SelectType::SINGLE);
    8271              : 
    8272              :     //! NOTE: Copied from ScoreView::cmdAddText
    8273            0 :     Measure* measure = text->findMeasure();
    8274            0 :     if (measure && measure->hasMMRest() && text->links()) {
    8275            0 :         Measure* mmRest = measure->mmRest();
    8276            0 :         for (EngravingObject* link : *text->links()) {
    8277            0 :             TextBase* linkedText = toTextBase(link);
    8278            0 :             if (text != linkedText && linkedText->findMeasure() == mmRest) {
    8279            0 :                 text = linkedText;
    8280            0 :                 break;
    8281              :             }
    8282              :         }
    8283              :     }
    8284              : 
    8285            0 :     startEditText(text, PointF());
    8286            0 :     text->cursor()->moveCursorToEnd();
    8287            0 : }
    8288              : 
    8289            0 : bool NotationInteraction::needEndTextEdit() const
    8290              : {
    8291            0 :     if (isTextEditingStarted()) {
    8292            0 :         const mu::engraving::TextBase* text = mu::engraving::toTextBase(m_editData.element);
    8293            0 :         return !text || !text->cursor()->editing();
    8294              :     }
    8295              : 
    8296            0 :     return false;
    8297              : }
    8298              : 
    8299            0 : void NotationInteraction::toggleFontStyle(mu::engraving::FontStyle style)
    8300              : {
    8301            0 :     if (!m_editData.element || !m_editData.element->isTextBase()) {
    8302            0 :         LOGW("toggleFontStyle called with invalid current element");
    8303            0 :         return;
    8304              :     }
    8305            0 :     mu::engraving::TextBase* text = toTextBase(m_editData.element);
    8306            0 :     int currentStyle = text->getProperty(mu::engraving::Pid::FONT_STYLE).toInt();
    8307            0 :     score()->startCmd(TranslatableString("undoableAction", "Format text"));
    8308            0 :     text->undoChangeProperty(mu::engraving::Pid::FONT_STYLE, PropertyValue::fromValue(
    8309            0 :                                  currentStyle ^ static_cast<int>(style)), mu::engraving::PropertyFlags::UNSTYLED);
    8310            0 :     score()->endCmd();
    8311            0 :     notifyAboutTextEditingChanged();
    8312              : }
    8313              : 
    8314            0 : void NotationInteraction::toggleVerticalAlignment(VerticalAlignment align)
    8315              : {
    8316            0 :     if (!m_editData.element || !m_editData.element->isTextBase()) {
    8317            0 :         LOGW("toggleVerticalAlignment called with invalid current element");
    8318            0 :         return;
    8319              :     }
    8320            0 :     mu::engraving::TextBase* text = toTextBase(m_editData.element);
    8321            0 :     int ialign = static_cast<int>(align);
    8322            0 :     int currentAlign = text->getProperty(mu::engraving::Pid::TEXT_SCRIPT_ALIGN).toInt();
    8323              : 
    8324            0 :     TranslatableString actionName = [align]() {
    8325            0 :         switch (align) {
    8326            0 :         case VerticalAlignment::AlignSubScript:
    8327            0 :             return TranslatableString("undoableAction", "Toggle subscript");
    8328            0 :         case VerticalAlignment::AlignSuperScript:
    8329            0 :             return TranslatableString("undoableAction", "Toggle superscript");
    8330            0 :         default:
    8331            0 :             return TranslatableString("undoableAction", "Toggle subscript/superscript");
    8332              :         }
    8333            0 :     }();
    8334              : 
    8335            0 :     score()->startCmd(actionName);
    8336            0 :     text->undoChangeProperty(mu::engraving::Pid::TEXT_SCRIPT_ALIGN, PropertyValue::fromValue(
    8337            0 :                                  (currentAlign == ialign) ? static_cast<int>(VerticalAlignment::AlignNormal) : ialign),
    8338              :                              mu::engraving::PropertyFlags::UNSTYLED);
    8339            0 :     score()->endCmd();
    8340            0 :     notifyAboutTextEditingChanged();
    8341            0 : }
    8342              : 
    8343            0 : void NotationInteraction::toggleBold()
    8344              : {
    8345            0 :     toggleFontStyle(mu::engraving::FontStyle::Bold);
    8346            0 : }
    8347              : 
    8348            0 : void NotationInteraction::toggleItalic()
    8349              : {
    8350            0 :     toggleFontStyle(mu::engraving::FontStyle::Italic);
    8351            0 : }
    8352              : 
    8353            0 : void NotationInteraction::toggleUnderline()
    8354              : {
    8355            0 :     toggleFontStyle(mu::engraving::FontStyle::Underline);
    8356            0 : }
    8357              : 
    8358            0 : void NotationInteraction::toggleStrike()
    8359              : {
    8360            0 :     toggleFontStyle(mu::engraving::FontStyle::Strike);
    8361            0 : }
    8362              : 
    8363            0 : void NotationInteraction::toggleSubScript()
    8364              : {
    8365            0 :     toggleVerticalAlignment(VerticalAlignment::AlignSubScript);
    8366            0 : }
    8367              : 
    8368            0 : void NotationInteraction::toggleSuperScript()
    8369              : {
    8370            0 :     toggleVerticalAlignment(VerticalAlignment::AlignSuperScript);
    8371            0 : }
    8372              : 
    8373              : template<typename P>
    8374            0 : void NotationInteraction::execute(void (mu::engraving::Score::* function)(P), P param, const TranslatableString& actionName)
    8375              : {
    8376            0 :     startEdit(actionName);
    8377            0 :     (score()->*function)(param);
    8378            0 :     apply();
    8379            0 : }
    8380              : 
    8381            0 : void NotationInteraction::toggleArticulation(mu::engraving::SymId symId)
    8382              : {
    8383            0 :     startEdit(TranslatableString("undoableAction", "Toggle articulation"));
    8384            0 :     mu::engraving::EditChord::toggleArticulation(score(), symId);
    8385            0 :     apply();
    8386            0 : }
    8387              : 
    8388            0 : void NotationInteraction::toggleOrnament(mu::engraving::SymId symId)
    8389              : {
    8390            0 :     startEdit(TranslatableString("undoableAction", "Toggle ornament"));
    8391            0 :     mu::engraving::EditNote::toggleOrnament(score(), symId);
    8392            0 :     apply();
    8393            0 : }
    8394              : 
    8395            0 : void NotationInteraction::toggleAutoplace(bool all)
    8396              : {
    8397            0 :     execute(&mu::engraving::Score::cmdToggleAutoplace, all, TranslatableString("undoableAction", "Toggle automatic placement"));
    8398            0 : }
    8399              : 
    8400            0 : bool NotationInteraction::canInsertClef(ClefType type) const
    8401              : {
    8402            0 :     const Score* score = this->score();
    8403            0 :     return score && mu::engraving::EditClef::canInsertClef(score, type);
    8404              : }
    8405              : 
    8406            0 : void NotationInteraction::insertClef(ClefType type)
    8407              : {
    8408            0 :     transaction(TranslatableString("undoableAction", "Add clef"), [&](Transaction& tx) {
    8409            0 :         mu::engraving::EditClef::insertClef(tx, score(), type);
    8410            0 :     });
    8411            0 : }
    8412              : 
    8413            0 : void NotationInteraction::changeAccidental(mu::engraving::AccidentalType accidental)
    8414              : {
    8415            0 :     transaction(TranslatableString("undoableAction", "Add accidental"), [&](auto& ) {
    8416            0 :         mu::engraving::EditNote::changeAccidental(score(), accidental);
    8417            0 :     });
    8418            0 : }
    8419              : 
    8420            0 : void NotationInteraction::transposeSemitone(int steps)
    8421              : {
    8422            0 :     transaction(TranslatableString("undoableAction", "Transpose semitone"), [&](auto& tx) {
    8423            0 :         Transpose::transposeSemitone(tx, score(), steps);
    8424            0 :     });
    8425            0 : }
    8426              : 
    8427            0 : void NotationInteraction::transposeDiatonicAlterations(mu::engraving::TransposeDirection direction)
    8428              : {
    8429            0 :     transaction(TranslatableString("undoableAction", "Transpose diatonically"), [&](auto& tx) {
    8430            0 :         Transpose::transposeDiatonicAlterations(tx, score(), direction);
    8431            0 :     });
    8432            0 : }
    8433              : 
    8434            0 : void NotationInteraction::getLocation()
    8435              : {
    8436            0 :     auto* e = score()->selection().element();
    8437            0 :     if (!e) {
    8438              :         // no current selection - restore lost selection
    8439            0 :         e = score()->selection().currentCR();
    8440            0 :         if (e && e->isChord()) {
    8441            0 :             e = toChord(e)->upNote();
    8442              :         }
    8443              :     }
    8444            0 :     if (!e) {
    8445            0 :         e = Navigation::firstElement(score(), false);
    8446              :     }
    8447            0 :     if (e) {
    8448            0 :         if (e->isNote() || e->isHarmony()) {
    8449            0 :             score()->setPlayNote(true);
    8450              :         }
    8451            0 :         select({ e }, SelectType::SINGLE);
    8452            0 :         showItem(e);
    8453              :     }
    8454            0 : }
    8455              : 
    8456            0 : void NotationInteraction::execute(void (mu::engraving::Score::* function)(), const TranslatableString& actionName)
    8457              : {
    8458            0 :     startEdit(actionName);
    8459            0 :     (score()->*function)();
    8460            0 :     apply();
    8461            0 : }
    8462              : 
    8463              : //! NOTE: Copied from ScoreView::adjustCanvasPosition
    8464            0 : void NotationInteraction::showItem(const mu::engraving::EngravingItem* el, int staffIndex)
    8465              : {
    8466            0 :     if (!el) {
    8467            0 :         return;
    8468              :     }
    8469              : 
    8470            0 :     if (!configuration()->isAutomaticallyPanEnabled()) {
    8471            0 :         return;
    8472              :     }
    8473              : 
    8474            0 :     const mu::engraving::MeasureBase* m = nullptr;
    8475              : 
    8476            0 :     if (el->isNote()) {
    8477            0 :         m = toNote(el)->chord()->measure();
    8478            0 :     } else if (el->isRest()) {
    8479            0 :         m = toRest(el)->measure();
    8480            0 :     } else if (el->isChord()) {
    8481            0 :         m = toChord(el)->measure();
    8482            0 :     } else if (el->isSegment()) {
    8483            0 :         m = toSegment(el)->measure();
    8484            0 :     } else if (el->isLyrics()) {
    8485            0 :         m = toLyrics(el)->measure();
    8486            0 :     } else if ((el->isHarmony() || el->isFiguredBass()) && el->parent()->isSegment()) {
    8487            0 :         m = toSegment(el->parent())->measure();
    8488            0 :     } else if (el->isHarmony() && el->parent()->isFretDiagram() && el->parent()->parent()->isSegment()) {
    8489            0 :         m = toSegment(el->parent()->parent())->measure();
    8490            0 :     } else if (el->isMeasureBase()) {
    8491            0 :         m = toMeasureBase(el);
    8492            0 :     } else if (el->isSpannerSegment()) {
    8493            0 :         EngravingItem* se = toSpannerSegment(el)->spanner()->startElement();
    8494            0 :         m = se->findMeasure();
    8495            0 :     } else if (el->isSpanner()) {
    8496            0 :         EngravingItem* se = toSpanner(el)->startElement();
    8497            0 :         m = se->findMeasure();
    8498            0 :     } else if (el->isPage()) {
    8499            0 :         const mu::engraving::Page* p = toPage(el);
    8500            0 :         mu::engraving::System* s = !p->systems().empty() ? p->systems().front() : nullptr;
    8501            0 :         m = s && !s->measures().empty() ? s->measures().front() : nullptr;
    8502              :     } else {
    8503              :         // attempt to find measure
    8504            0 :         mu::engraving::EngravingObject* e = el->parent();
    8505            0 :         while (e && !e->isMeasureBase()) {
    8506            0 :             e = e->parent();
    8507              :         }
    8508            0 :         if (e) {
    8509            0 :             m = toMeasureBase(e);
    8510              :         } else {
    8511            0 :             return;
    8512              :         }
    8513              :     }
    8514            0 :     if (!m) {
    8515            0 :         return;
    8516              :     }
    8517              : 
    8518            0 :     mu::engraving::System* sys = m->system();
    8519            0 :     if (!sys) {
    8520            0 :         return;
    8521              :     }
    8522              : 
    8523            0 :     RectF mRect(m->canvasBoundingRect());
    8524            0 :     RectF sysRect = mRect;
    8525              : 
    8526            0 :     double _spatium    = score()->style().spatium();
    8527            0 :     const qreal border = _spatium * 3;
    8528            0 :     RectF showRect;
    8529            0 :     if (staffIndex == -1) {
    8530            0 :         showRect = RectF(mRect.x(), sysRect.y(), mRect.width(), sysRect.height())
    8531            0 :                    .adjusted(-border, -border, border, border);
    8532              :     } else {
    8533              :         // find a box for the individual stave in a system
    8534            0 :         RectF stave = RectF(sys->canvasBoundingRect().left(),
    8535              :                             sys->staffCanvasYpage(staffIndex),
    8536              :                             sys->width(),
    8537            0 :                             sys->staff(staffIndex)->bbox().height());
    8538            0 :         showRect = mRect.intersected(stave).adjusted(-border, -border, border, border);
    8539              :     }
    8540              : 
    8541            0 :     ShowItemRequest request;
    8542            0 :     request.item = el;
    8543            0 :     request.showRect = showRect;
    8544              : 
    8545            0 :     m_showItemRequested.send(request);
    8546              : }
    8547              : 
    8548            0 : void NotationInteraction::toggleDebugShowGapRests()
    8549              : {
    8550              :     // Doesn't perform any action on the score, just triggers the necessary relayout
    8551            0 :     startEdit(TranslatableString("debugOption", "Toggle show gap rests"));
    8552            0 :     score()->setLayoutAll();
    8553            0 :     apply();
    8554            0 : }
    8555              : 
    8556            0 : muse::async::Channel<NotationInteraction::ShowItemRequest> NotationInteraction::showItemRequested() const
    8557              : {
    8558            0 :     return m_showItemRequested;
    8559              : }
    8560              : 
    8561            0 : void NotationInteraction::setGetViewRectFunc(const std::function<RectF()>& func)
    8562              : {
    8563            0 :     static_cast<NotationNoteInput*>(m_noteInput.get())->setGetViewRectFunc(func);
    8564            0 : }
    8565              : 
    8566            0 : void NotationInteraction::checkAndShowError()
    8567              : {
    8568            0 :     IF_ASSERT_FAILED(m_errorsController) {
    8569            0 :         return;
    8570              :     }
    8571            0 :     m_errorsController->checkAndShowMScoreError();
    8572              : }
        

Generated by: LCOV version 2.0-1