LCOV - code coverage report
Current view: top level - src/notation/internal - notationnoteinput.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 605 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              : #include "notationnoteinput.h"
      23              : 
      24              : #include "engraving/dom/articulation.h"
      25              : #include "engraving/dom/chord.h"
      26              : #include "engraving/dom/drumset.h"
      27              : #include "engraving/dom/input.h"
      28              : #include "engraving/dom/masterscore.h"
      29              : #include "engraving/dom/mscore.h"
      30              : #include "engraving/dom/note.h"
      31              : #include "engraving/dom/part.h"
      32              : #include "engraving/dom/segment.h"
      33              : #include "engraving/dom/slur.h"
      34              : #include "engraving/dom/staff.h"
      35              : #include "engraving/dom/stafftype.h"
      36              : #include "engraving/dom/system.h"
      37              : #include "engraving/dom/tuplet.h"
      38              : #include "engraving/dom/utils.h"
      39              : 
      40              : #include "engraving/editing/editnote.h"
      41              : #include "engraving/editing/edittie.h"
      42              : #include "engraving/editing/noteinput.h"
      43              : #include "engraving/editing/transaction/transaction.h"
      44              : 
      45              : #include "log.h"
      46              : 
      47              : using namespace mu::notation;
      48              : using namespace mu::engraving;
      49              : using namespace muse;
      50              : using namespace muse::async;
      51              : 
      52            0 : TranslatableString nameOfNoteInputMethod(NoteInputMethod method)
      53              : {
      54            0 :     switch (method) {
      55            0 :     case NoteInputMethod::UNKNOWN:          break;
      56            0 :     case NoteInputMethod::BY_NOTE_NAME:     return TranslatableString("noteInputMethod", "Input by note name mode");
      57            0 :     case NoteInputMethod::BY_DURATION:      return TranslatableString("noteInputMethod", "Input by duration mode");
      58            0 :     case NoteInputMethod::REPITCH:          return TranslatableString("noteInputMethod", "Re-pitch existing notes mode");
      59            0 :     case NoteInputMethod::RHYTHM:           return TranslatableString("noteInputMethod", "Rhythm-only input mode");
      60            0 :     case NoteInputMethod::REALTIME_AUTO:    return TranslatableString("noteInputMethod", "Metronome real-time input mode");
      61            0 :     case NoteInputMethod::REALTIME_MANUAL:  return TranslatableString("noteInputMethod", "Pedal real-time input mode");
      62            0 :     case NoteInputMethod::TIMEWISE:         return TranslatableString("noteInputMethod", "Insert mode (grow measures)");
      63              :         // No default case. We want a compiler warning if an enum value is not handled here.
      64              :     }
      65              : 
      66            0 :     UNREACHABLE;
      67              :     return TranslatableString("noteInputMethod", "Unknown note input mode");
      68              : }
      69              : 
      70            0 : static bool noteInputMethodAvailable(NoteInputMethod method, const Staff* staff, const Fraction& tick)
      71              : {
      72            0 :     if (method == NoteInputMethod::BY_DURATION) {
      73            0 :         return staff && !staff->isTabStaff(tick);
      74              :     }
      75              : 
      76            0 :     return true;
      77              : }
      78              : 
      79            0 : NotationNoteInput::NotationNoteInput(const IGetScore* getScore, INotationInteraction* interaction, INotationUndoStackPtr undoStack
      80            0 :                                      , const modularity::ContextPtr& iocCtx)
      81            0 :     : muse::Contextable(iocCtx), m_getScore(getScore), m_interaction(interaction), m_undoStack(undoStack)
      82              : {
      83            0 :     m_interaction->selectionChanged().onNotify(this, [this]() {
      84            0 :         if (!isNoteInputMode()) {
      85            0 :             updateInputState();
      86            0 :         } else if (shouldSetupInputNote()) {
      87            0 :             const NoteInputState& is = state();
      88            0 :             const staff_idx_t prevStaffIdx = track2staff(is.prevTrack());
      89              : 
      90            0 :             if (prevStaffIdx != is.staffIdx()) {
      91            0 :                 setupInputNote();
      92              :             }
      93              :         }
      94            0 :     });
      95            0 : }
      96              : 
      97            0 : bool NotationNoteInput::isNoteInputMode() const
      98              : {
      99            0 :     return score()->inputState().noteEntryMode();
     100              : }
     101              : 
     102            0 : const NoteInputState& NotationNoteInput::state() const
     103              : {
     104            0 :     return score()->inputState();
     105              : }
     106              : 
     107              : //! NOTE Copied from `void ScoreView::startNoteEntry()`
     108            0 : void NotationNoteInput::startNoteInput(NoteInputMethod method, bool focusNotation)
     109              : {
     110            0 :     TRACEFUNC;
     111              : 
     112            0 :     if (isNoteInputMode()) {
     113            0 :         return;
     114              :     }
     115              : 
     116            0 :     EngravingItem* el = resolveNoteInputStartPosition();
     117            0 :     if (!el) {
     118            0 :         return;
     119              :     }
     120              : 
     121            0 :     if (Part* part = el->part(); part && part->isSharedPart()) {
     122            0 :         return;
     123              :     }
     124              : 
     125            0 :     m_interaction->select({ el }, SelectType::SINGLE, 0);
     126              : 
     127            0 :     InputState& is = score()->inputState();
     128              : 
     129            0 :     Duration d(is.duration());
     130            0 :     if (!d.isValid() || d.isZero() || d.type() == DurationType::V_MEASURE) {
     131            0 :         is.setDuration(Duration(DurationType::V_QUARTER));
     132              :     }
     133            0 :     is.setAccidentalType(AccidentalType::NONE);
     134              : 
     135            0 :     is.setRest(false);
     136            0 :     is.setNoteEntryMode(true);
     137              : 
     138            0 :     const Staff* staff = score()->staff(is.track() / VOICES);
     139              : 
     140            0 :     if (noteInputMethodAvailable(method, staff, is.tick())) {
     141            0 :         is.setNoteEntryMethod(method);
     142              :     } else {
     143            0 :         is.setNoteEntryMethod(NoteInputMethod::BY_NOTE_NAME); // fallback
     144              :     }
     145              : 
     146            0 :     if (shouldSetupInputNote()) {
     147            0 :         setupInputNote();
     148              :     }
     149              : 
     150            0 :     switch (staff->staffType(is.tick())->group()) {
     151            0 :     case StaffGroup::STANDARD:
     152            0 :         break;
     153            0 :     case StaffGroup::TAB: {
     154            0 :         int strg = 0;                           // assume topmost string as current string
     155              :         // if entering note entry with a note selected and the note has a string
     156              :         // set InputState::_string to note physical string
     157            0 :         if (el->isNote()) {
     158            0 :             strg = (toNote(el))->string();
     159              :         }
     160            0 :         is.setString(strg);
     161            0 :         break;
     162              :     }
     163            0 :     case StaffGroup::PERCUSSION:
     164            0 :         break;
     165              :     }
     166              : 
     167            0 :     notifyAboutNoteInputStarted(focusNotation);
     168            0 :     notifyAboutStateChanged();
     169              : 
     170            0 :     m_interaction->showItem(el);
     171            0 :     accessibilityController()->announce(nameOfNoteInputMethod(method).translated());
     172            0 : }
     173              : 
     174            0 : EngravingItem* NotationNoteInput::resolveNoteInputStartPosition() const
     175              : {
     176            0 :     ChordRest* topLeftChordRest = nullptr;
     177              : 
     178            0 :     if (score()->selection().isNone() && m_getViewRectFunc) {
     179              :         // no selection
     180              :         // choose page in current view (favor top left quadrant if possible)
     181              :         // select first (top/left) chordrest of that page in current view
     182              :         // or, CR at last selected position if that is in view
     183            0 :         RectF viewRect = m_getViewRectFunc();
     184            0 :         PointF topLeft = viewRect.topLeft();
     185              : 
     186            0 :         std::vector<PointF> points;
     187            0 :         points.push_back({ topLeft.x() + viewRect.width() * 0.25, topLeft.y() + viewRect.height() * 0.25 });
     188            0 :         points.push_back(topLeft);
     189            0 :         points.push_back(viewRect.bottomLeft());
     190            0 :         points.push_back(viewRect.topRight());
     191            0 :         points.push_back(viewRect.bottomRight());
     192              : 
     193            0 :         Page* page = nullptr;
     194            0 :         for (const PointF& point : points) {
     195            0 :             page = score()->searchPage(point);
     196            0 :             if (page) {
     197            0 :                 break;
     198              :             }
     199              :         }
     200              : 
     201            0 :         if (page) {
     202            0 :             qreal tlY = 0.0;
     203            0 :             Fraction tlTick = Fraction(0, 1);
     204            0 :             RectF pageRect  = page->ldata()->bbox().translated(page->x(), page->y());
     205            0 :             RectF intersect = viewRect & pageRect;
     206            0 :             intersect.translate(-page->x(), -page->y());
     207            0 :             std::vector<EngravingItem*> el = page->items(intersect);
     208              : 
     209            0 :             const ChordRest* lastSelected = score()->selection().currentCR();
     210              : 
     211            0 :             if (lastSelected && lastSelected->voice()) {
     212              :                 // if last selected CR was not in voice 1,
     213              :                 // find CR in voice 1 instead
     214            0 :                 track_idx_t track = trackZeroVoice(lastSelected->track());
     215            0 :                 const Segment* s = lastSelected->segment();
     216            0 :                 if (s) {
     217            0 :                     lastSelected = s->nextChordRest(track, true);
     218              :                 }
     219              :             }
     220              : 
     221            0 :             for (EngravingItem* e : el) {
     222              :                 // loop through visible elements
     223              :                 // looking for the CR in voice 1 with earliest tick and highest staff position
     224              :                 // but stop if we find the last selected CR
     225            0 :                 ElementType et = e->type();
     226            0 :                 if (et == ElementType::NOTE || et == ElementType::REST) {
     227            0 :                     if (e->voice()) {
     228            0 :                         continue;
     229              :                     }
     230              :                     ChordRest* cr;
     231            0 :                     if (et == ElementType::NOTE) {
     232            0 :                         cr = toChordRest(e->parent());
     233            0 :                         if (!cr) {
     234            0 :                             continue;
     235              :                         }
     236              :                     } else {
     237            0 :                         cr = toChordRest(e);
     238              :                     }
     239            0 :                     if (cr == lastSelected) {
     240            0 :                         topLeftChordRest = cr;
     241            0 :                         break;
     242              :                     }
     243              :                     // compare ticks rather than x position
     244              :                     // to make sure we favor earlier rather than later systems
     245              :                     // even though later system might have note farther to left
     246            0 :                     Fraction crTick = Fraction(0, 1);
     247            0 :                     if (cr->segment()) {
     248            0 :                         crTick = cr->segment()->tick();
     249              :                     } else {
     250            0 :                         continue;
     251              :                     }
     252              :                     // compare staff Y position rather than note Y position
     253              :                     // to be sure we do not reject earliest note
     254              :                     // just because it is lower in pitch than subsequent notes
     255            0 :                     qreal crY = 0.0;
     256            0 :                     if (cr->measure() && cr->measure()->system()) {
     257            0 :                         crY = cr->measure()->system()->staffYpage(cr->staffIdx());
     258              :                     } else {
     259            0 :                         continue;
     260              :                     }
     261            0 :                     if (topLeftChordRest) {
     262            0 :                         if (crTick <= tlTick && crY <= tlY) {
     263            0 :                             topLeftChordRest = cr;
     264            0 :                             tlTick = crTick;
     265            0 :                             tlY = crY;
     266              :                         }
     267              :                     } else {
     268            0 :                         topLeftChordRest = cr;
     269            0 :                         tlTick = crTick;
     270            0 :                         tlY = crY;
     271              :                     }
     272              :                 }
     273              :             }
     274            0 :         }
     275            0 :     }
     276              : 
     277            0 :     EngravingItem* el = topLeftChordRest ? topLeftChordRest : score()->selection().element();
     278            0 :     if (!el) {
     279            0 :         el = score()->selection().firstChordRest();
     280              :     }
     281              : 
     282            0 :     const InputState& is = score()->inputState();
     283              : 
     284            0 :     if (!el) {
     285            0 :         if (const Segment* segment = is.lastSegment()) {
     286            0 :             el = segment->element(is.track());
     287              :         }
     288              :     }
     289              : 
     290            0 :     if (!el || (!el->isChordRest() && !el->isNote())) {
     291              :         // if no note/rest is selected, start with voice 0
     292            0 :         engraving::track_idx_t track = is.track() == muse::nidx ? 0 : (is.track() / VOICES) * VOICES;
     293              :         // try to find an appropriate measure to start in
     294            0 :         Fraction tick = el ? el->tick() : Fraction(0, 1);
     295            0 :         el = score()->searchNote(tick, track);
     296            0 :         if (!el) {
     297            0 :             el = score()->searchNote(Fraction(0, 1), track);
     298              :         }
     299              :     }
     300              : 
     301            0 :     if (!el) {
     302            0 :         return nullptr;
     303              :     }
     304              : 
     305            0 :     if (el->isChord()) {
     306            0 :         Chord* c = toChord(el);
     307            0 :         Note* note = c->selectedNote();
     308            0 :         if (note == 0) {
     309            0 :             note = c->upNote();
     310              :         }
     311            0 :         el = note;
     312              :     }
     313              : 
     314            0 :     return el;
     315              : }
     316              : 
     317            0 : bool NotationNoteInput::shouldSetupInputNote() const
     318              : {
     319            0 :     if (!score()->inputState().isValid()) {
     320            0 :         return false;
     321              :     }
     322            0 :     return usingNoteInputMethod(NoteInputMethod::BY_DURATION)
     323            0 :            || usingNoteInputMethod(NoteInputMethod::RHYTHM);
     324              : }
     325              : 
     326            0 : void NotationNoteInput::setupInputNote()
     327              : {
     328            0 :     InputState& is = score()->inputState();
     329            0 :     const EngravingItem* selectedItem = score()->selection().element();
     330              : 
     331            0 :     if (selectedItem && selectedItem->isNote()) {
     332            0 :         is.setNotes({ toNote(selectedItem)->noteVal() });
     333            0 :         return;
     334              :     }
     335              : 
     336            0 :     Staff* staff = is.staff();
     337            0 :     IF_ASSERT_FAILED(staff) {
     338            0 :         return;
     339              :     }
     340              : 
     341            0 :     const Fraction tick = is.tick();
     342            0 :     NoteVal nval;
     343              : 
     344            0 :     if (staff->isTabStaff(tick)) {
     345            0 :         if (const StringData* stringData = staff->part()->stringData(tick, is.staffIdx())) {
     346            0 :             nval.fret = 0;
     347            0 :             nval.string = is.string();
     348            0 :             nval.pitch = stringData->getPitch(nval.string, nval.fret, staff);
     349              :         }
     350            0 :     } else if (staff->isDrumStaff(tick)) {
     351            0 :         if (const Drumset* drumset = is.drumset()) {
     352            0 :             nval.pitch = is.drumNote();
     353              : 
     354            0 :             if (nval.pitch < 0) {
     355            0 :                 nval.pitch = drumset->nextPitch(nval.pitch);
     356              :             }
     357              : 
     358            0 :             if (drumset->isValid(nval.pitch)) {
     359            0 :                 nval.headGroup = drumset->noteHead(nval.pitch);
     360              :             }
     361              :         }
     362              :     } else {
     363            0 :         nval = noteValForLine(staff->middleLine(tick));
     364              :     }
     365              : 
     366            0 :     if (nval.pitch > 0) {
     367            0 :         is.setNotes({ nval });
     368              :     }
     369              : }
     370              : 
     371            0 : NoteVal NotationNoteInput::noteValForLine(int line) const
     372              : {
     373            0 :     const InputState& is = score()->inputState();
     374              : 
     375            0 :     Position pos;
     376            0 :     pos.segment = is.segment();
     377            0 :     pos.staffIdx = is.staffIdx();
     378            0 :     pos.line = line;
     379              : 
     380            0 :     bool error = false;
     381            0 :     const NoteVal nval = NoteInput::noteValForPosition(score(), pos, is.accidentalType(), error);
     382            0 :     if (error) {
     383            0 :         LOGE() << "Could not find note val for position, staffIdx: " << pos.staffIdx << ", line: " << pos.line;
     384              :     }
     385              : 
     386            0 :     return nval;
     387              : }
     388              : 
     389            0 : void NotationNoteInput::endNoteInput(bool resetState)
     390              : {
     391            0 :     TRACEFUNC;
     392              : 
     393            0 :     if (!isNoteInputMode()) {
     394            0 :         return;
     395              :     }
     396              : 
     397            0 :     InputState& is = score()->inputState();
     398            0 :     is.setNoteEntryMode(false);
     399              : 
     400            0 :     if (is.slur()) {
     401            0 :         const std::vector<SpannerSegment*>& el = is.slur()->spannerSegments();
     402            0 :         if (!el.empty()) {
     403            0 :             el.front()->setSelected(false);
     404              :         }
     405            0 :         is.setSlur(0);
     406              :     }
     407              : 
     408            0 :     if (resetState) {
     409            0 :         is.setTrack(muse::nidx);
     410            0 :         is.setString(-1);
     411            0 :         is.setSegment(nullptr);
     412            0 :         is.setNotes({});
     413              :     }
     414              : 
     415            0 :     notifyAboutNoteInputEnded();
     416            0 :     updateInputState();
     417            0 :     accessibilityController()->announce(TranslatableString("noteInputMethod", "Normal mode").translated());
     418            0 : }
     419              : 
     420            0 : Channel</*focusNotation*/ bool> NotationNoteInput::noteInputStarted() const
     421              : {
     422            0 :     return m_noteInputStarted;
     423              : }
     424              : 
     425            0 : Notification NotationNoteInput::noteInputEnded() const
     426              : {
     427            0 :     return m_noteInputEnded;
     428              : }
     429              : 
     430            0 : bool NotationNoteInput::usingNoteInputMethod(NoteInputMethod method) const
     431              : {
     432            0 :     return score()->usingNoteEntryMethod(method);
     433              : }
     434              : 
     435            0 : void NotationNoteInput::setNoteInputMethod(NoteInputMethod method)
     436              : {
     437            0 :     TRACEFUNC;
     438              : 
     439            0 :     NoteInputState& is = score()->inputState();
     440            0 :     if (is.usingNoteEntryMethod(method)) {
     441            0 :         return;
     442              :     }
     443              : 
     444            0 :     if (!noteInputMethodAvailable(method, is.staff(), is.tick())) {
     445            0 :         return;
     446              :     }
     447              : 
     448            0 :     is.setNoteEntryMethod(method);
     449            0 :     if (shouldSetupInputNote()) {
     450            0 :         setupInputNote();
     451              :     }
     452              : 
     453            0 :     notifyAboutStateChanged();
     454            0 : }
     455              : 
     456            0 : void NotationNoteInput::addNote(const NoteInputParams& params, NoteAddingMode addingMode)
     457              : {
     458            0 :     TRACEFUNC;
     459              : 
     460            0 :     m_interaction->hideShadowNote();
     461              : 
     462            0 :     bool addToUpOnCurrentChord = addingMode == NoteAddingMode::CurrentChord;
     463            0 :     bool insertNewChord = addingMode == NoteAddingMode::InsertChord;
     464              : 
     465            0 :     if (addToUpOnCurrentChord) {
     466            0 :         startEdit(TranslatableString("undoableAction", "Add note to chord"));
     467            0 :     } else if (insertNewChord) {
     468            0 :         startEdit(TranslatableString("undoableAction", "Insert note"));
     469              :     } else {
     470            0 :         startEdit(TranslatableString("undoableAction", "Enter note"));
     471              :     }
     472            0 :     NoteInput::addPitch(score()->transactionManager()->currentOrDummyTransaction(), score(), params, addToUpOnCurrentChord,
     473              :                         insertNewChord);
     474              : 
     475            0 :     apply();
     476              : 
     477            0 :     if (shouldSetupInputNote()) {
     478            0 :         setupInputNote();
     479              :     }
     480              : 
     481            0 :     notifyNoteAddedChanged();
     482            0 :     notifyAboutStateChanged();
     483              : 
     484            0 :     m_interaction->checkAndShowError();
     485            0 :     m_interaction->showItem(state().cr());
     486            0 : }
     487              : 
     488            0 : void NotationNoteInput::applyNoteValueChange(const muse::TranslatableString& actionName,
     489              :                                              const std::function<void(mu::engraving::Transaction&)>& change)
     490              : {
     491            0 :     TRACEFUNC;
     492              : 
     493            0 :     m_interaction->hideShadowNote();
     494              : 
     495            0 :     startEdit(actionName);
     496            0 :     change(score()->transactionManager()->currentOrDummyTransaction());
     497            0 :     apply();
     498              : 
     499            0 :     notifyAboutStateChanged();
     500              : 
     501            0 :     m_interaction->checkAndShowError();
     502            0 : }
     503              : 
     504            0 : void NotationNoteInput::setDuration(DurationType duration)
     505              : {
     506            0 :     applyNoteValueChange(muse::TranslatableString("undoableAction", "Set duration"), [this, duration](mu::engraving::Transaction& tx) {
     507            0 :         NoteInput::setDuration(tx, score(), duration);
     508            0 :     });
     509            0 : }
     510              : 
     511            0 : void NotationNoteInput::toggleRest()
     512              : {
     513            0 :     applyNoteValueChange(muse::TranslatableString("undoableAction", "Toggle rest"), [this](mu::engraving::Transaction& tx) {
     514            0 :         NoteInput::toggleRest(tx, score());
     515            0 :     });
     516            0 : }
     517              : 
     518            0 : void NotationNoteInput::toggleDots(int dots)
     519              : {
     520            0 :     applyNoteValueChange(muse::TranslatableString("undoableAction", "Toggle dots"), [this, dots](mu::engraving::Transaction& tx) {
     521            0 :         NoteInput::toggleDots(tx, score(), dots);
     522            0 :     });
     523            0 : }
     524              : 
     525            0 : Ret NotationNoteInput::putNote(const PointF& pos, bool replace, bool insert)
     526              : {
     527            0 :     TRACEFUNC;
     528              : 
     529            0 :     startEdit(TranslatableString("undoableAction", "Enter note"));
     530            0 :     Ret ret = NoteInput::putNote(score()->transactionManager()->currentOrDummyTransaction(), score(), pos, replace, insert);
     531            0 :     apply();
     532              : 
     533            0 :     if (ret) {
     534            0 :         if (shouldSetupInputNote()) {
     535            0 :             setupInputNote();
     536              :         }
     537              :     }
     538              : 
     539            0 :     notifyNoteAddedChanged();
     540            0 :     notifyAboutStateChanged();
     541              : 
     542            0 :     m_interaction->checkAndShowError();
     543              : 
     544            0 :     return ret;
     545            0 : }
     546              : 
     547            0 : void NotationNoteInput::removeNote(const PointF& pos)
     548              : {
     549            0 :     TRACEFUNC;
     550              : 
     551            0 :     InputState& inputState = score()->inputState();
     552            0 :     bool restMode = inputState.rest();
     553              : 
     554            0 :     startEdit(TranslatableString("undoableAction", "Delete note"));
     555            0 :     inputState.setRest(!restMode);
     556            0 :     NoteInput::putNote(score()->transactionManager()->currentOrDummyTransaction(), score(), pos, false, false);
     557            0 :     inputState.setRest(restMode);
     558            0 :     apply();
     559              : 
     560            0 :     notifyAboutStateChanged();
     561              : 
     562            0 :     m_interaction->checkAndShowError();
     563            0 : }
     564              : 
     565            0 : void NotationNoteInput::setInputNote(const NoteInputParams& params)
     566              : {
     567            0 :     TRACEFUNC;
     568              : 
     569            0 :     NoteInputState& is = score()->inputState();
     570            0 :     IF_ASSERT_FAILED(is.isValid()) {
     571            0 :         return;
     572              :     }
     573            0 :     const Staff* staff = is.staff();
     574            0 :     const Fraction tick = is.tick();
     575              : 
     576            0 :     NoteVal nval;
     577              : 
     578            0 :     if (staff->isDrumStaff(tick)) {
     579            0 :         const Drumset* drumset = is.drumset();
     580            0 :         if (drumset && drumset->isValid(params.drumPitch)) {
     581            0 :             nval.pitch = params.drumPitch;
     582            0 :             nval.headGroup = drumset->noteHead(params.drumPitch);
     583              :         }
     584              :     } else {
     585            0 :         nval = noteValForLine(relStep(params.step, staff->clef(tick)));
     586              :     }
     587              : 
     588            0 :     if (nval.pitch > 0) {
     589            0 :         setInputNotes({ nval });
     590              :     }
     591            0 : }
     592              : 
     593            0 : void NotationNoteInput::setInputNotes(const NoteValList& notes)
     594              : {
     595            0 :     TRACEFUNC;
     596              : 
     597            0 :     NoteInputState& is = score()->inputState();
     598              : 
     599            0 :     if (is.notes() == notes) {
     600            0 :         return;
     601              :     }
     602              : 
     603            0 :     if (!notes.empty()) {
     604            0 :         if (const Drumset* drumset = is.drumset()) {
     605            0 :             const int pitch = notes.front().pitch;
     606            0 :             is.setDrumNote(pitch);
     607            0 :             is.setVoice(drumset->voice(pitch));
     608              :         }
     609              :     }
     610              : 
     611            0 :     is.setNotes(notes);
     612            0 :     notifyAboutStateChanged();
     613            0 : }
     614              : 
     615            0 : void NotationNoteInput::moveInputNotes(bool up, PitchMode mode)
     616              : {
     617            0 :     TRACEFUNC;
     618              : 
     619            0 :     InputState& is = score()->inputState();
     620            0 :     IF_ASSERT_FAILED(is.isValid()) {
     621            0 :         return;
     622              :     }
     623              : 
     624            0 :     is.setAccidentalType(AccidentalType::NONE);
     625              : 
     626            0 :     const Staff* staff = is.staff();
     627            0 :     const Fraction tick = is.tick();
     628              : 
     629            0 :     NoteValList notes;
     630              : 
     631            0 :     for (const NoteVal& val : is.notes()) {
     632            0 :         NoteVal newVal;
     633              : 
     634            0 :         if (staff->isDrumStaff(tick)) {
     635            0 :             if (const Drumset* drumset = is.drumset()) {
     636            0 :                 newVal.pitch = up ? drumset->nextPitch(val.pitch) : drumset->prevPitch(val.pitch);
     637              : 
     638            0 :                 if (drumset->isValid(newVal.pitch)) {
     639            0 :                     newVal.headGroup = drumset->noteHead(newVal.pitch);
     640            0 :                     notes.push_back(newVal);
     641              :                 }
     642              :             }
     643            0 :             continue;
     644            0 :         }
     645              : 
     646            0 :         switch (mode) {
     647            0 :         case PitchMode::CHROMATIC:
     648            0 :             newVal.pitch = val.pitch + (up ? 1 : -1);
     649            0 :             break;
     650            0 :         case PitchMode::DIATONIC: {
     651            0 :             const int oldLine = noteValToLine(val, staff, tick);
     652            0 :             const int newLine = oldLine + (up ? -1 : 1);
     653            0 :             newVal = noteValForLine(newLine);
     654            0 :         } break;
     655            0 :         case PitchMode::OCTAVE:
     656            0 :             newVal = val;
     657            0 :             newVal.pitch += up ? PITCH_DELTA_OCTAVE : -PITCH_DELTA_OCTAVE;
     658            0 :             break;
     659              :         }
     660              : 
     661            0 :         newVal.pitch = clampPitch(newVal.pitch);
     662            0 :         notes.push_back(newVal);
     663              :     }
     664              : 
     665            0 :     setInputNotes(notes);
     666            0 : }
     667              : 
     668            0 : void NotationNoteInput::setRestMode(bool rest)
     669              : {
     670            0 :     InputState& is = score()->inputState();
     671            0 :     if (is.rest() == rest) {
     672            0 :         return;
     673              :     }
     674              : 
     675            0 :     is.setRest(rest);
     676            0 :     notifyAboutStateChanged();
     677              : }
     678              : 
     679            0 : void NotationNoteInput::setAccidental(AccidentalType accidentalType)
     680              : {
     681            0 :     TRACEFUNC;
     682              : 
     683            0 :     EditNote::toggleAccidental(score(), accidentalType);
     684              : 
     685            0 :     notifyAboutStateChanged();
     686              : 
     687            0 :     m_interaction->checkAndShowError();
     688            0 : }
     689              : 
     690            0 : void NotationNoteInput::setArticulation(SymbolId articulationSymbolId)
     691              : {
     692            0 :     TRACEFUNC;
     693              : 
     694            0 :     InputState& inputState = score()->inputState();
     695              : 
     696              :     std::set<SymbolId> articulations = updateArticulations(
     697            0 :         inputState.articulationIds(), articulationSymbolId, ArticulationsUpdateMode::Remove);
     698            0 :     inputState.setArticulationIds(articulations);
     699              : 
     700            0 :     notifyAboutStateChanged();
     701              : 
     702            0 :     m_interaction->checkAndShowError();
     703            0 : }
     704              : 
     705            0 : void NotationNoteInput::setDrumNote(int note)
     706              : {
     707            0 :     TRACEFUNC;
     708              : 
     709            0 :     InputState& is = score()->inputState();
     710            0 :     if (is.drumNote() == note) {
     711            0 :         return;
     712              :     }
     713              : 
     714            0 :     is.setDrumNote(note);
     715            0 :     if (shouldSetupInputNote()) {
     716            0 :         setupInputNote();
     717              :     }
     718              : 
     719            0 :     notifyAboutStateChanged();
     720            0 : }
     721              : 
     722            0 : void NotationNoteInput::setCurrentVoice(voice_idx_t voiceIndex)
     723              : {
     724            0 :     TRACEFUNC;
     725              : 
     726            0 :     InputState& inputState = score()->inputState();
     727            0 :     if (!isVoiceIndexValid(voiceIndex) || voiceIndex == inputState.voice()) {
     728            0 :         return;
     729              :     }
     730              : 
     731            0 :     inputState.setVoice(voiceIndex);
     732            0 :     notifyAboutStateChanged();
     733            0 : }
     734              : 
     735            0 : void NotationNoteInput::setCurrentTrack(track_idx_t trackIndex)
     736              : {
     737            0 :     TRACEFUNC;
     738              : 
     739            0 :     score()->inputState().setTrack(trackIndex);
     740            0 :     notifyAboutStateChanged();
     741            0 : }
     742              : 
     743            0 : void NotationNoteInput::addTuplet(const TupletOptions& options)
     744              : {
     745            0 :     TRACEFUNC;
     746              : 
     747            0 :     const InputState& inputState = score()->inputState();
     748              : 
     749            0 :     const bool durationAsSubdivision = options.selectedDurationAs == TupletSelectedDuration::SUBDIVISION;
     750            0 :     if (durationAsSubdivision
     751            0 :         && !score()->canAddTuplet(inputState.segment(), inputState.track(), options.ratio, inputState.duration())) {
     752            0 :         MScore::setError(MsError::NO_SPACE_FOR_TUPLET);
     753            0 :         m_interaction->checkAndShowError();
     754            0 :         return;
     755              :     }
     756              : 
     757            0 :     if (!durationAsSubdivision && !m_interaction->canAddTupletToSelectedChordRests()) {
     758            0 :         MScore::setError(MsError::TUPLET_NOTE_VALUE_TOO_SHORT);
     759            0 :         m_interaction->checkAndShowError();
     760            0 :         return;
     761              :     }
     762              : 
     763            0 :     startEdit(TranslatableString("undoableAction", "Add tuplet"));
     764            0 :     score()->expandVoice();
     765            0 :     ChordRest* chordRest = inputState.cr();
     766            0 :     if (chordRest) {
     767            0 :         Tuplet* tuplet = nullptr;
     768            0 :         if (durationAsSubdivision) {
     769            0 :             tuplet = score()->addTuplet(chordRest, options.ratio, inputState.duration(), options.numberType, options.bracketType);
     770              :         } else {
     771            0 :             Fraction ratio = options.ratio;
     772            0 :             if (options.autoBaseLen) {
     773            0 :                 ratio.setDenominator(Tuplet::computeTupletDenominator(ratio.numerator(), inputState.ticks()));
     774              :             }
     775            0 :             score()->changeCRlen(chordRest, inputState.duration());
     776            0 :             tuplet = score()->addTuplet(chordRest, ratio, options.numberType, options.bracketType);
     777              :         }
     778            0 :         if (tuplet) {
     779            0 :             score()->selectNewTuplet(tuplet);
     780              :         }
     781              :     }
     782            0 :     apply();
     783              : 
     784            0 :     notifyAboutStateChanged();
     785            0 : }
     786              : 
     787            0 : static muse::RectF segmentContentRect(const Segment* segment, track_idx_t track)
     788              : {
     789            0 :     RectF result;
     790              : 
     791            0 :     const EngravingItem* el = segment->element(track);
     792            0 :     if (!el) {
     793            0 :         return result;
     794              :     }
     795              : 
     796            0 :     if (el->isChord()) {
     797            0 :         const Chord* chord = toChord(el);
     798            0 :         for (const Note* note: chord->notes()) {
     799            0 :             result.unite(note->ldata()->bbox().translated(note->pos()));
     800              :         }
     801              : 
     802            0 :         if (Hook* hook = chord->hook()) {
     803            0 :             result.unite(hook->ldata()->bbox().translated(hook->pos()));
     804              :         }
     805            0 :     } else if (el->isRest() && !toRest(el)->isFullMeasureRest()) {
     806            0 :         result.unite(el->ldata()->bbox());
     807              :     }
     808              : 
     809            0 :     return result;
     810              : }
     811              : 
     812            0 : muse::RectF NotationNoteInput::cursorRect() const
     813              : {
     814            0 :     TRACEFUNC;
     815              : 
     816            0 :     if (!isNoteInputMode()) {
     817            0 :         return {};
     818              :     }
     819              : 
     820            0 :     const InputState& inputState = score()->inputState();
     821            0 :     const Segment* segment = inputState.segment();
     822            0 :     if (!segment) {
     823            0 :         return {};
     824              :     }
     825              : 
     826            0 :     const System* system = segment->measure()->system();
     827            0 :     if (!system) {
     828            0 :         return {};
     829              :     }
     830              : 
     831            0 :     const track_idx_t track = inputState.track() == muse::nidx ? 0 : inputState.track();
     832            0 :     const staff_idx_t staffIdx = track2staff(track);
     833              : 
     834            0 :     const Staff* staff = score()->staff(staffIdx);
     835            0 :     if (!staff) {
     836            0 :         return {};
     837              :     }
     838              : 
     839            0 :     const double globalSpatium = score()->style().spatium();
     840              : 
     841            0 :     const StaffType* staffType = staff->staffType(inputState.tick());
     842            0 :     const bool isTabStaff = staffType->isTabStaff();
     843            0 :     const double localSpatium = staffType->spatium();
     844              : 
     845            0 :     const double defaultWidth = score()->noteHeadWidth() * localSpatium / globalSpatium;
     846              : 
     847            0 :     const RectF segmentContentRect = ::segmentContentRect(segment, track);
     848            0 :     double x = segmentContentRect.x() + segment->pagePos().x();
     849              : 
     850            0 :     const double barNoteDist = score()->style().styleAbsolute(Sid::barNoteDistance);
     851            0 :     if (segment->x() < barNoteDist) {
     852            0 :         x += barNoteDist - segment->x();
     853              :     }
     854              : 
     855            0 :     double y = system->staffCanvasYpage(staffIdx);
     856            0 :     double w = segmentContentRect.width() > 0 ? segmentContentRect.width() : defaultWidth;
     857            0 :     double h = 0.0;
     858              : 
     859            0 :     if (inputState.beyondScore()) {
     860            0 :         const Measure* lastMeasure = score()->lastMeasure();
     861            0 :         x = lastMeasure->pageBoundingRect().right() + globalSpatium;
     862            0 :         w = defaultWidth;
     863              :     }
     864              : 
     865            0 :     double sideMargin = 0.0;
     866              : 
     867            0 :     if (isTabStaff) {
     868            0 :         const double minWidth = 2.0 * globalSpatium;
     869            0 :         sideMargin = std::max(0.3 * globalSpatium, 0.5 * (minWidth - w));
     870              :     } else {
     871            0 :         sideMargin = 0.5 * globalSpatium;
     872              :     }
     873              : 
     874              :     // Don't extend further to the left than the center between the current and previous segment
     875            0 :     const engraving::Segment* prevSeg = segment->prev1WithElemsOnTrack(track, engraving::SegmentType::ChordRest);
     876            0 :     if (prevSeg && prevSeg->measure() == segment->measure()) {
     877            0 :         const RectF prevSegContentRect = ::segmentContentRect(prevSeg, track);
     878            0 :         if (prevSegContentRect.width() > 0) {
     879            0 :             const double centerBetweenPrevSegRightAndCurrSegLeft = (prevSeg->pagePos().x() + prevSegContentRect.right() + x) * 0.5;
     880            0 :             sideMargin = std::min(sideMargin, x - centerBetweenPrevSegRightAndCurrSegLeft);
     881              :         }
     882              :     }
     883              : 
     884            0 :     x -= sideMargin;
     885            0 :     w += 2 * sideMargin;
     886              : 
     887            0 :     const double yOffset = staffType->yoffset().val() * localSpatium;
     888            0 :     y += yOffset;
     889              : 
     890            0 :     const int inputStateStringsCount = inputState.string();
     891            0 :     const int instrumentStringsCount = static_cast<int>(staff->part()->instrument()->stringData()->strings());
     892              : 
     893            0 :     const double lineDist = staffType->lineDistance().val() * localSpatium;
     894              : 
     895            0 :     if (isTabStaff && inputStateStringsCount >= 0 && inputStateStringsCount <= instrumentStringsCount) {
     896            0 :         h = lineDist;
     897            0 :         y += staffType->physStringToYOffset(inputStateStringsCount).toAbsolute(localSpatium);
     898            0 :         y -= (staffType->onLines() ? lineDist * 0.5 : lineDist);
     899              :     } else {
     900            0 :         const double skylineMargin = 0.75 * localSpatium;
     901            0 :         const int lines = staffType->lines();
     902              : 
     903            0 :         h = (lines - 1) * lineDist + 2 * skylineMargin;
     904            0 :         y -= skylineMargin;
     905              :     }
     906              : 
     907            0 :     RectF result { x, y, w, h };
     908              : 
     909            0 :     if (configuration()->canvasOrientation().val == muse::Orientation::Horizontal) {
     910            0 :         result.translate(system->page()->pos());
     911              :     }
     912              : 
     913            0 :     return result;
     914            0 : }
     915              : 
     916            0 : void NotationNoteInput::addSlur(Slur* slur)
     917              : {
     918            0 :     TRACEFUNC;
     919              : 
     920            0 :     InputState& inputState = score()->inputState();
     921            0 :     inputState.setSlur(slur);
     922              : 
     923            0 :     if (slur) {
     924            0 :         std::vector<SpannerSegment*> slurSpannerSegments = slur->spannerSegments();
     925            0 :         if (!slurSpannerSegments.empty()) {
     926            0 :             slurSpannerSegments.front()->setSelected(true);
     927              :         }
     928            0 :     }
     929              : 
     930            0 :     notifyAboutStateChanged();
     931            0 : }
     932              : 
     933            0 : void NotationNoteInput::resetSlur()
     934              : {
     935            0 :     TRACEFUNC;
     936              : 
     937            0 :     InputState& inputState = score()->inputState();
     938            0 :     Slur* slur = inputState.slur();
     939            0 :     if (!slur) {
     940            0 :         return;
     941              :     }
     942              : 
     943            0 :     score()->deselect(slur);
     944              : 
     945            0 :     addSlur(nullptr);
     946            0 : }
     947              : 
     948            0 : void NotationNoteInput::addTie()
     949              : {
     950            0 :     TRACEFUNC;
     951              : 
     952              :     // Calls `startEdit` internally
     953            0 :     EditTie::cmdAddTie(score());
     954              : 
     955            0 :     notifyAboutStateChanged();
     956            0 :     m_interaction->checkAndShowError();
     957            0 : }
     958              : 
     959            0 : void NotationNoteInput::addLaissezVib()
     960              : {
     961            0 :     TRACEFUNC;
     962              : 
     963              :     // Calls `startEdit` internally
     964            0 :     EditTie::cmdToggleLaissezVib(score());
     965              : 
     966            0 :     notifyAboutStateChanged();
     967            0 :     m_interaction->checkAndShowError();
     968            0 : }
     969              : 
     970            0 : Notification NotationNoteInput::noteAdded() const
     971              : {
     972            0 :     return m_noteAdded;
     973              : }
     974              : 
     975            0 : Notification NotationNoteInput::stateChanged() const
     976              : {
     977            0 :     return m_stateChanged;
     978              : }
     979              : 
     980            0 : void NotationNoteInput::setGetViewRectFunc(const std::function<RectF()>& func)
     981              : {
     982            0 :     m_getViewRectFunc = func;
     983            0 : }
     984              : 
     985            0 : Score* NotationNoteInput::score() const
     986              : {
     987            0 :     return m_getScore->score();
     988              : }
     989              : 
     990            0 : void NotationNoteInput::startEdit(const muse::TranslatableString& actionName)
     991              : {
     992            0 :     m_undoStack->prepareChanges(actionName);
     993            0 : }
     994              : 
     995            0 : void NotationNoteInput::apply()
     996              : {
     997            0 :     m_undoStack->commitChanges();
     998              : 
     999            0 :     if (ChordRest* chordRest = score()->inputState().cr()) {
    1000            0 :         m_interaction->showItem(chordRest);
    1001              :     }
    1002            0 : }
    1003              : 
    1004            0 : void NotationNoteInput::updateInputState()
    1005              : {
    1006            0 :     TRACEFUNC;
    1007              : 
    1008            0 :     NoteInputState& is = score()->inputState();
    1009            0 :     is.update(score()->selection());
    1010              : 
    1011            0 :     if (!configuration()->addAccidentalDotsArticulationsToNextNoteEntered()) {
    1012            0 :         is.setAccidentalType(AccidentalType::NONE);
    1013            0 :         is.setDots(0);
    1014            0 :         is.setArticulationIds({});
    1015              :     }
    1016              : 
    1017            0 :     notifyAboutStateChanged();
    1018            0 : }
    1019              : 
    1020            0 : void NotationNoteInput::notifyAboutStateChanged()
    1021              : {
    1022            0 :     m_stateChanged.notify();
    1023            0 : }
    1024              : 
    1025            0 : void NotationNoteInput::notifyNoteAddedChanged()
    1026              : {
    1027            0 :     m_noteAdded.notify();
    1028            0 : }
    1029              : 
    1030            0 : void NotationNoteInput::notifyAboutNoteInputStarted(bool focusNotation)
    1031              : {
    1032            0 :     m_noteInputStarted.send(focusNotation);
    1033            0 : }
    1034              : 
    1035            0 : void NotationNoteInput::notifyAboutNoteInputEnded()
    1036              : {
    1037            0 :     m_noteInputEnded.notify();
    1038            0 : }
    1039              : 
    1040            0 : void NotationNoteInput::doubleNoteInputDuration()
    1041              : {
    1042            0 :     TRACEFUNC;
    1043              : 
    1044            0 :     startEdit(TranslatableString("undoableAction", "Double note input duration"));
    1045            0 :     NoteInput::increaseDuration(score()->transactionManager()->currentOrDummyTransaction(), score());
    1046            0 :     apply();
    1047              : 
    1048            0 :     notifyAboutStateChanged();
    1049            0 :     m_interaction->checkAndShowError();
    1050            0 : }
    1051              : 
    1052            0 : void NotationNoteInput::halveNoteInputDuration()
    1053              : {
    1054            0 :     TRACEFUNC;
    1055              : 
    1056            0 :     startEdit(TranslatableString("undoableAction", "Halve note input duration"));
    1057            0 :     NoteInput::decreaseDuration(score()->transactionManager()->currentOrDummyTransaction(), score());
    1058            0 :     apply();
    1059              : 
    1060            0 :     notifyAboutStateChanged();
    1061            0 :     m_interaction->checkAndShowError();
    1062            0 : }
        

Generated by: LCOV version 2.0-1