LCOV - code coverage report
Current view: top level - src/engraving/dom - select.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 72.5 % 953 691
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              : /**
      24              :  \file
      25              :  Implementation of class Selection plus other selection related functions.
      26              : */
      27              : 
      28              : #include "global/containers.h"
      29              : 
      30              : #include "../types/types.h"
      31              : 
      32              : #include "rw/rwregister.h"
      33              : 
      34              : #include "accidental.h"
      35              : #include "arpeggio.h"
      36              : #include "articulation.h"
      37              : #include "beam.h"
      38              : #include "box.h"
      39              : #include "breath.h"
      40              : #include "chord.h"
      41              : #include "dynamic.h"
      42              : #include "engravingitem.h"
      43              : #include "expression.h"
      44              : #include "figuredbass.h"
      45              : #include "fingering.h"
      46              : #include "fret.h"
      47              : #include "guitarbend.h"
      48              : #include "harmony.h"
      49              : #include "harppedaldiagram.h"
      50              : #include "hook.h"
      51              : #include "laissezvib.h"
      52              : #include "linkedobjects.h"
      53              : #include "lyrics.h"
      54              : #include "measure.h"
      55              : #include "mscore.h"
      56              : #include "note.h"
      57              : #include "notedot.h"
      58              : #include "pagelockindicator.h"
      59              : #include "part.h"
      60              : #include "partialtie.h"
      61              : #include "rest.h"
      62              : #include "score.h"
      63              : #include "segment.h"
      64              : #include "select.h"
      65              : #include "staff.h"
      66              : #include "stafftextbase.h"
      67              : #include "stem.h"
      68              : #include "stemslash.h"
      69              : #include "sticking.h"
      70              : #include "system.h"
      71              : #include "systemlockindicator.h"
      72              : #include "tie.h"
      73              : #include "tremolosinglechord.h"
      74              : #include "tremolotwochord.h"
      75              : #include "tuplet.h"
      76              : 
      77              : #include "utils.h"
      78              : 
      79              : #include "log.h"
      80              : 
      81              : using namespace mu;
      82              : using namespace mu::engraving;
      83              : 
      84              : // ====================================================
      85              : // Selection
      86              : // ====================================================
      87              : 
      88         2334 : Selection::Selection(Score* s)
      89              : {
      90         2334 :     m_score         = s;
      91         2334 :     m_state         = SelState::NONE;
      92         2334 :     m_startSegment  = 0;
      93         2334 :     m_endSegment    = 0;
      94         2334 :     m_startBox      = 0;
      95         2334 :     m_endBox        = 0;
      96         2334 :     m_activeSegment = 0;
      97         2334 :     m_staffStart    = 0;
      98         2334 :     m_staffEnd      = 0;
      99         2334 :     m_activeTrack   = 0;
     100         2334 :     m_currentTick   = Fraction(-1, 1);
     101         2334 :     m_currentTrack  = 0;
     102         2334 : }
     103              : 
     104         3564 : Fraction Selection::tickStart() const
     105              : {
     106         3564 :     switch (m_state) {
     107         3559 :     case SelState::RANGE:
     108         3559 :         return m_startSegment ? m_startSegment->tick() : Fraction(-1, 1);
     109            5 :     case SelState::LIST: {
     110            5 :         ChordRest* cr = firstChordRest();
     111            5 :         return (cr) ? cr->tick() : Fraction(-1, 1);
     112              :     }
     113            0 :     default:
     114            0 :         return Fraction(-1, 1);
     115              :     }
     116              : }
     117              : 
     118         3538 : Fraction Selection::tickEnd() const
     119              : {
     120         3538 :     switch (m_state) {
     121         3533 :     case SelState::RANGE: {
     122         3533 :         if (m_endSegment) {
     123         2923 :             return m_endSegment->tick();
     124              :         } else {         // endsegment == 0 if end of score
     125          610 :             Measure* m = m_score->lastMeasure();
     126          610 :             return m->endTick();
     127              :         }
     128              :         break;
     129              :     }
     130            5 :     case SelState::LIST: {
     131            5 :         ChordRest* cr = lastChordRest();
     132            5 :         return (cr) ? cr->segment()->tick() : Fraction(-1, 1);
     133              :         break;
     134              :     }
     135            0 :     default:
     136            0 :         return Fraction(-1, 1);
     137              :     }
     138              : }
     139              : 
     140            0 : bool Selection::isStartActive() const
     141              : {
     142            0 :     return activeSegment() && activeSegment()->tick() == tickStart();
     143              : }
     144              : 
     145            0 : bool Selection::isEndActive() const
     146              : {
     147            0 :     return activeSegment() && activeSegment()->tick() == tickEnd();
     148              : }
     149              : 
     150         6542 : EngravingItem* Selection::element() const
     151              : {
     152         6542 :     return ((state() != SelState::RANGE) && (m_el.size() == 1)) ? m_el[0] : 0;
     153              : }
     154              : 
     155            0 : ChordRest* Selection::cr() const
     156              : {
     157            0 :     EngravingItem* e = element();
     158            0 :     if (!e) {
     159            0 :         return 0;
     160              :     }
     161            0 :     if (e->isNote()) {
     162            0 :         e = toNote(e)->chord();
     163              :     }
     164            0 :     if (e->isChordRest()) {
     165            0 :         return toChordRest(e);
     166              :     }
     167            0 :     return 0;
     168              : }
     169              : 
     170            0 : ChordRest* Selection::currentCR() const
     171              : {
     172              :     // no selection yet - start at very beginning, not first cr
     173            0 :     if (m_currentTick == Fraction(-1, 1)) {
     174            0 :         return nullptr;
     175              :     }
     176            0 :     Segment* s = score()->tick2rightSegment(m_currentTick, true);
     177            0 :     if (!s) {
     178            0 :         return nullptr;
     179              :     }
     180            0 :     track_idx_t track = m_currentTrack;
     181              :     // staff may have been removed - start at top
     182            0 :     if (track >= score()->ntracks()) {
     183            0 :         track = 0;
     184              :     }
     185            0 :     EngravingItem* e = s->element(track);
     186            0 :     if (e && e->isChordRest()) {
     187            0 :         return toChordRest(e);
     188              :     } else {
     189            0 :         return nullptr;
     190              :     }
     191              : }
     192              : 
     193         3438 : ChordRest* Selection::activeCR() const
     194              : {
     195         3438 :     if (m_state != SelState::RANGE || !m_activeSegment) {
     196         2759 :         return nullptr;
     197              :     }
     198          679 :     if (m_activeSegment == m_startSegment) {
     199            0 :         return firstChordRest(m_activeTrack);
     200              :     } else {
     201          679 :         return lastChordRest(m_activeTrack);
     202              :     }
     203              : }
     204              : 
     205           87 : ChordRest* Selection::firstChordRestInRange(track_idx_t preferredTrack) const
     206              : {
     207           87 :     IF_ASSERT_FAILED(isRange()) {
     208            0 :         return nullptr;
     209              :     }
     210              : 
     211           87 :     Segment* firstCRSegment = nullptr;
     212              : 
     213           87 :     for (Segment* currSeg = m_startSegment; currSeg && (currSeg != m_endSegment); currSeg = currSeg->next1MM()) {
     214           87 :         if (!currSeg->enabled() || !currSeg->isChordRestType()) {
     215            0 :             continue;
     216              :         }
     217           87 :         if (preferredTrack == muse::nidx) {
     218              :             // no track specified, use the first CR segment we find...
     219           87 :             firstCRSegment = currSeg;
     220           87 :             break;
     221              :         }
     222            0 :         EngravingItem* e = currSeg->element(preferredTrack);
     223            0 :         if (e && e->isChordRest()) {
     224            0 :             return toChordRest(e);
     225              :         }
     226              :     }
     227              : 
     228           87 :     if (!firstCRSegment) {
     229            0 :         return nullptr;
     230              :     }
     231              : 
     232           87 :     for (track_idx_t track = staff2track(staffStart()); track < staff2track(staffEnd()); ++track) {
     233           87 :         EngravingItem* e = firstCRSegment->element(track);
     234           87 :         if (e && e->isChordRest()) {
     235           87 :             return toChordRest(e);
     236              :         }
     237              :     }
     238            0 :     return nullptr;
     239              : }
     240              : 
     241          757 : ChordRest* Selection::lastChordRestInRange(track_idx_t preferredTrack) const
     242              : {
     243          757 :     IF_ASSERT_FAILED(isRange()) {
     244            0 :         return nullptr;
     245              :     }
     246              : 
     247          757 :     Segment* lastCRSegment = nullptr;
     248              : 
     249          757 :     Segment* lastSegInScore = m_score->lastMeasureMM() ? m_score->lastMeasureMM()->last() : nullptr;
     250          757 :     Segment* currSeg = m_endSegment ? m_endSegment->prev1MM() : lastSegInScore;
     251          872 :     while (currSeg) {
     252          872 :         if (!currSeg->enabled() || !currSeg->isChordRestType()) {
     253           31 :             currSeg = currSeg->prev1MM();
     254           31 :             continue;
     255              :         }
     256          841 :         if (preferredTrack == muse::nidx) {
     257              :             // no track specified, use the first CR segment we find...
     258           78 :             lastCRSegment = currSeg;
     259           78 :             break;
     260              :         }
     261          763 :         EngravingItem* e = currSeg->element(preferredTrack);
     262          763 :         if (e && e->isChordRest()) {
     263          679 :             return toChordRest(e);
     264              :         }
     265           84 :         currSeg = currSeg->prev1MM();
     266           84 :         if (currSeg == m_startSegment->prev1MM()) {
     267            0 :             break;
     268              :         }
     269              :     }
     270              : 
     271           78 :     if (!lastCRSegment) {
     272            0 :         return nullptr;
     273              :     }
     274              : 
     275           79 :     for (track_idx_t track = staff2track(staffStart()); track < staff2track(staffEnd()); ++track) {
     276           79 :         EngravingItem* e = lastCRSegment->element(track);
     277           79 :         if (e && e->isChordRest()) {
     278           78 :             return toChordRest(e);
     279              :         }
     280              :     }
     281              : 
     282            0 :     return nullptr;
     283              : }
     284              : 
     285           94 : ChordRest* Selection::firstChordRest(track_idx_t track) const
     286              : {
     287           94 :     if (isRange()) {
     288           87 :         return firstChordRestInRange(track);
     289              :     }
     290            7 :     if (m_el.size() == 1) {
     291            2 :         EngravingItem* el = m_el[0];
     292            2 :         if (el->isNote()) {
     293            0 :             return toChordRest(el->ownershipParent());
     294            2 :         } else if (el->isChordRest()) {
     295            2 :             return toChordRest(el);
     296              :         }
     297            0 :         return nullptr;
     298              :     }
     299            5 :     ChordRest* cr = nullptr;
     300           18 :     for (EngravingItem* el : m_el) {
     301           13 :         if (el->isNote()) {
     302            3 :             el = toNote(el)->chord();
     303              :         }
     304           13 :         if (el->isChordRest()) {
     305           13 :             if (track != muse::nidx && el->track() != track) {
     306            0 :                 continue;
     307              :             }
     308           13 :             if (cr) {
     309            8 :                 if (toChordRest(el)->tick() < cr->tick()) {
     310            2 :                     cr = toChordRest(el);
     311              :                 }
     312              :             } else {
     313            5 :                 cr = toChordRest(el);
     314              :             }
     315              :         }
     316              :     }
     317            5 :     return cr;
     318              : }
     319              : 
     320          762 : ChordRest* Selection::lastChordRest(track_idx_t track) const
     321              : {
     322          762 :     if (isRange()) {
     323          757 :         return lastChordRestInRange(track);
     324              :     }
     325            5 :     if (m_el.size() == 1) {
     326            0 :         EngravingItem* el = m_el[0];
     327            0 :         if (el) {
     328            0 :             if (el->isNote()) {
     329            0 :                 return toChordRest(el->ownershipParent());
     330            0 :             } else if (el->isChordRest()) {
     331            0 :                 return toChordRest(el);
     332              :             }
     333              :         }
     334            0 :         return nullptr;
     335              :     }
     336            5 :     ChordRest* cr = nullptr;
     337           18 :     for (EngravingItem* el : m_el) {
     338           13 :         if (el->isNote()) {
     339            3 :             el = toNote(el)->chord();
     340              :         }
     341           13 :         if (el->isChordRest() && toChordRest(el)->segment()->isChordRestType()) {
     342           13 :             if (track != muse::nidx && el->track() != track) {
     343            0 :                 continue;
     344              :             }
     345           13 :             if (cr) {
     346            8 :                 if (toChordRest(el)->tick() >= cr->tick()) {
     347            6 :                     cr = toChordRest(el);
     348              :                 }
     349              :             } else {
     350            5 :                 cr = toChordRest(el);
     351              :             }
     352              :         }
     353              :     }
     354            5 :     return cr;
     355              : }
     356              : 
     357            0 : Measure* Selection::findMeasure() const
     358              : {
     359            0 :     Measure* m = 0;
     360            0 :     if (m_el.size() > 0) {
     361            0 :         EngravingItem* el = m_el[0];
     362            0 :         m = toMeasure(el->findMeasure());
     363              :     }
     364            0 :     return m;
     365              : }
     366              : 
     367           21 : MeasureBase* Selection::startMeasureBase() const
     368              : {
     369           21 :     EngravingItem* selectionElement = element();
     370           21 :     if (selectionElement) {
     371            0 :         if (selectionElement->isBox()) {
     372            0 :             return toMeasureBase(selectionElement);
     373              :         }
     374            0 :         MeasureBase* mb = selectionElement->findMeasureBase();
     375            0 :         if (mb) {
     376            0 :             return mb;
     377              :         }
     378              :     }
     379              : 
     380           21 :     if (m_startBox) {
     381           12 :         return m_startBox;
     382              :     }
     383              : 
     384            9 :     if (tickStart().negative()) { // Tick is not set
     385            0 :         return nullptr;
     386              :     }
     387              : 
     388            9 :     MeasureBase* mb = m_score->tick2measureBase(tickStart());
     389            9 :     if (mb && mb->isMeasure()) {
     390            5 :         mb = toMeasure(mb)->coveringMMRestOrThis();
     391              :     }
     392            9 :     return mb;
     393              : }
     394              : 
     395           18 : MeasureBase* Selection::endMeasureBase() const
     396              : {
     397           18 :     EngravingItem* selectionElement = element();
     398           18 :     if (selectionElement) {
     399            0 :         if (selectionElement->isBox()) {
     400            0 :             return toMeasureBase(selectionElement);
     401              :         }
     402            0 :         MeasureBase* mb = selectionElement->findMeasureBase();
     403            0 :         if (mb) {
     404            0 :             return mb;
     405              :         }
     406              :     }
     407              : 
     408           18 :     if (m_endBox) {
     409            8 :         return m_endBox;
     410              :     }
     411              : 
     412           10 :     if (tickEnd().negative()) { // Tick is not set
     413            0 :         return nullptr;
     414              :     }
     415              : 
     416           10 :     MeasureBase* mb = m_score->tick2measureBase(tickEnd() - Fraction::eps());
     417           10 :     if (mb && mb->isMeasure()) {
     418           10 :         mb = toMeasure(mb)->coveringMMRestOrThis();
     419              :     }
     420           10 :     return mb;
     421              : }
     422              : 
     423            4 : std::vector<MeasureBase*> Selection::measureBasesInSelection() const
     424              : {
     425            4 :     MeasureBase* startMB = startMeasureBase();
     426            4 :     MeasureBase* endMB = endMeasureBase();
     427            4 :     if (!startMB || !endMB) {
     428            0 :         return {};
     429              :     }
     430              : 
     431            4 :     std::vector<MeasureBase*> measureBases;
     432           10 :     for (MeasureBase* mb = startMB; mb && mb->isBeforeOrEqual(endMB); mb = mb->nextMM()) {
     433            6 :         measureBases.push_back(mb);
     434              :     }
     435              : 
     436            4 :     return measureBases;
     437            4 : }
     438              : 
     439            2 : std::vector<System*> Selection::selectedSystems() const
     440              : {
     441            2 :     EngravingItem* el = element();
     442            2 :     if (el && (el->isSystemLockIndicator() || el->isPageLockIndicator() /*TODO: || el->isStaffVisibilityIndicator*/)) {
     443            0 :         return { const_cast<System*>(toIndicatorIcon(el)->system()) };
     444              :     }
     445              : 
     446            2 :     std::vector<System*> systems;
     447            4 :     for (const MeasureBase* mb : measureBasesInSelection()) {
     448            2 :         if (!mb->isMeasure() && !mb->isHBox()) {
     449            0 :             continue;
     450              :         }
     451            2 :         System* sys = mb->system();
     452            2 :         IF_ASSERT_FAILED(sys) {
     453            0 :             continue;
     454              :         }
     455            2 :         if (systems.empty() || sys != systems.back()) {
     456            2 :             systems.push_back(sys);
     457              :         }
     458            2 :     }
     459              : 
     460            2 :     return systems;
     461            2 : }
     462              : 
     463            2 : std::vector<Page*> mu::engraving::Selection::pagesContainingSelection() const
     464              : {
     465            2 :     if (const EngravingItem* el = element()) {
     466            0 :         if (el->isPageLockIndicator()) {
     467            0 :             return { const_cast<Page*>(toPageLockIndicator(el)->page()) };
     468              :         }
     469            0 :         if (el->isSystemLockIndicator()) {
     470            0 :             Page* page = toSystemLockIndicator(el)->system()->page();
     471            0 :             return page ? std::vector<Page*> { page } : std::vector<Page*> {};
     472              :         }
     473              :     }
     474              : 
     475            2 :     std::vector<Page*> pages;
     476            6 :     for (const MeasureBase* mb : measureBasesInSelection()) {
     477            4 :         Page* page = mb->page();
     478            4 :         if (page && !muse::contains(pages, page)) {
     479            2 :             pages.push_back(page);
     480              :         }
     481            2 :     }
     482            2 :     return pages;
     483            2 : }
     484              : 
     485         2802 : void Selection::deselectAll()
     486              : {
     487         2802 :     if (m_state == SelState::RANGE) {
     488          291 :         m_score->setUpdateAll();
     489              :     }
     490         2802 :     clear();
     491         2802 :     updateState();
     492         2802 : }
     493              : 
     494        12311 : static RectF changeSelection(EngravingItem* e, bool b)
     495              : {
     496        12311 :     RectF r = e->canvasBoundingRect();
     497        12311 :     e->setSelected(b);
     498        12311 :     r.unite(e->canvasBoundingRect());
     499        12311 :     return r;
     500              : }
     501              : 
     502         2806 : void Selection::clear()
     503              : {
     504         2806 :     IF_ASSERT_FAILED(!isLocked()) {
     505            0 :         LOGE() << "selection locked, reason: " << lockReason();
     506            0 :         return;
     507              :     }
     508              : 
     509        15096 :     for (EngravingItem* e : m_el) {
     510        12290 :         e->score()->addRefresh(changeSelection(e, false));
     511        12290 :         if (e->isSpanner()) {       // TODO: only visible elements should be selectable?
     512           21 :             Spanner* sp = toSpanner(e);
     513           42 :             for (auto s : sp->spannerSegments()) {
     514           21 :                 e->score()->addRefresh(changeSelection(s, false));
     515              :             }
     516              :         }
     517              :     }
     518         2806 :     m_el.clear();
     519         2806 :     m_startSegment  = 0;
     520         2806 :     m_endSegment    = 0;
     521         2806 :     m_startBox      = 0;
     522         2806 :     m_endBox        = 0;
     523         2806 :     m_activeSegment = 0;
     524         2806 :     m_staffStart    = 0;
     525         2806 :     m_staffEnd      = 0;
     526         2806 :     m_activeTrack   = 0;
     527         2806 :     setState(SelState::NONE);
     528              : }
     529              : 
     530       231282 : void Selection::remove(EngravingItem* el)
     531              : {
     532       231282 :     const bool removed = muse::remove(m_el, el);
     533       231282 :     el->setSelected(false);
     534       231282 :     if (removed) {
     535           42 :         updateState();
     536              :     }
     537       231282 : }
     538              : 
     539         2478 : void Selection::add(EngravingItem* el)
     540              : {
     541         2478 :     IF_ASSERT_FAILED(!isLocked()) {
     542            0 :         LOGE() << "selection locked, reason: " << lockReason();
     543            0 :         return;
     544              :     }
     545         2478 :     m_el.push_back(el);
     546         2478 :     update();
     547              : }
     548              : 
     549         7980 : void Selection::appendFiltered(EngravingItem* e)
     550              : {
     551         7980 :     IF_ASSERT_FAILED(!isLocked()) {
     552            0 :         LOGE() << "selection locked, reason: " << lockReason();
     553            0 :         return;
     554              :     }
     555         7980 :     if (canSelectVoice(e->voice()) && canSelect(e)) {
     556         7882 :         m_el.push_back(e);
     557              :     }
     558              : }
     559              : 
     560        22451 : void Selection::appendFiltered(const std::unordered_set<EngravingItem*>& elems)
     561              : {
     562        22451 :     IF_ASSERT_FAILED(!isLocked()) {
     563            0 :         LOGE() << "selection locked, reason: " << lockReason();
     564            0 :         return;
     565              :     }
     566              : 
     567        24601 :     for (EngravingItem* elem : elems) {
     568         2150 :         IF_ASSERT_FAILED(!elem->isSpannerSegment()) {
     569            0 :             LOGE() << "Append whole spanners instead of spanner segments";
     570            0 :             continue;
     571              :         }
     572              : 
     573              :         // Special handling for bends...
     574         2150 :         if (elem->isGuitarBend()) {
     575            0 :             GuitarBend* bend = toGuitarBend(elem);
     576            0 :             appendGuitarBend(bend);
     577            0 :             continue;
     578            0 :         }
     579              : 
     580              :         // Special handling for spanners...
     581         2150 :         if (elem->isSpanner() && !elem->isPartialTie() && !elem->isLaissezVib()) {
     582         1788 :             const Spanner* spanner = toSpanner(elem);
     583         1788 :             if (!noteAnchoredSpannerIsInRange(spanner, tickStart(), tickEnd())) {
     584            0 :                 continue;
     585              :             }
     586         3576 :             for (SpannerSegment* spannerSeg : spanner->spannerSegments()) {
     587         1788 :                 appendFiltered(spannerSeg);
     588              :             }
     589         1788 :             continue;
     590         1788 :         }
     591              : 
     592              :         // Special handling for grace notes...
     593          362 :         if (elem->isChord() && toChord(elem)->isGrace()) {
     594           41 :             appendChordRest(toChordRest(elem));
     595           41 :             continue;
     596              :         }
     597              : 
     598          321 :         appendFiltered(elem);
     599              :     }
     600              : }
     601              : 
     602        12613 : void Selection::appendChordRest(ChordRest* cr)
     603              : {
     604        12613 :     IF_ASSERT_FAILED(!isLocked()) {
     605            0 :         LOGE() << "selection locked, reason: " << lockReason();
     606         4968 :         return;
     607              :     }
     608              : 
     609        12613 :     if (!canSelectVoice(cr->voice())) {
     610            7 :         return;
     611              :     }
     612              : 
     613        12606 :     const std::unordered_set<EngravingItem*> crAnchored = collectElementsAnchoredToChordRest(cr);
     614        12606 :     appendFiltered(crAnchored);
     615              : 
     616        12606 :     if (cr->isRestFamily()) {
     617         4739 :         appendFiltered(cr);
     618         4739 :         Rest* r = toRest(cr);
     619         4847 :         for (int i = 0; i < r->dots(); ++i) {
     620          108 :             appendFiltered(r->dot(i));
     621              :         }
     622         4739 :         return;
     623              :     }
     624              : 
     625         7867 :     IF_ASSERT_FAILED(cr->isChord()) {
     626            0 :         return;
     627              :     }
     628              : 
     629         7867 :     Chord* chord = toChord(cr);
     630              : 
     631         7867 :     const size_t totalNotesInChord = chord->notes().size();
     632         7867 :     const bool isSingleNote = totalNotesInChord == 1;
     633              : 
     634         7867 :     size_t totalAppendedNotes = 0;
     635        17368 :     for (size_t noteIdx = 0; noteIdx < totalNotesInChord; ++noteIdx) {
     636         9501 :         Note* note = chord->notes().at(noteIdx);
     637              : 
     638         9501 :         const std::unordered_set<EngravingItem*> noteAnchored = collectElementsAnchoredToNote(note, true, false);
     639         9501 :         appendFiltered(noteAnchored);
     640              : 
     641         9501 :         if (chord->isGrace() && !canSelect(chord)) {
     642            8 :             continue;
     643              :         }
     644              : 
     645              :         //! Hack Explainer: Due to the fact that this method is called while we're still in the process of "building" our
     646              :         //! selection, we can't know for certain whether the selection as a whole will contain multi-note Chords (and thus
     647              :         //! whether the "includeSingleNotes" flag should apply - see includeSingleNotes in select.h). For this reason, this
     648              :         //! method ALWAYS appends single note chords. If single note chords should be ommitted from a selection, we simply
     649              :         //! don't call this method (see usage of appendChordRest in Selection::updateSelectedElements).
     650         9493 :         if (/*hack*/ !isSingleNote && !canSelectNoteIdx(noteIdx, totalNotesInChord, /*hack*/ true)) {
     651          388 :             continue;
     652              :         }
     653              : 
     654         9105 :         m_el.push_back(note);
     655         9105 :         ++totalAppendedNotes;
     656              : 
     657         9105 :         if (note->accidental()) {
     658          399 :             m_el.push_back(note->accidental());
     659              :         }
     660         9109 :         for (EngravingItem* el : note->el()) {
     661            4 :             if (el->isFingering()) {
     662              :                 // Slight hack (already handled, see collectElementsAnchoredToNote)...
     663            4 :                 continue;
     664              :             }
     665            0 :             m_el.push_back(el);
     666              :         }
     667         9315 :         for (NoteDot* dot : note->dots()) {
     668          210 :             m_el.push_back(dot);
     669              :         }
     670         9501 :     }
     671              : 
     672              :     //! NOTE: Beams, stems, etc should only be added if all notes in chord are selected...
     673         7867 :     if (totalAppendedNotes < totalNotesInChord) {
     674          222 :         return;
     675              :     }
     676         7645 :     if (chord->beam() && !muse::contains(m_el, static_cast<EngravingItem*>(chord->beam()))) {
     677         1083 :         m_el.push_back(chord->beam());
     678              :     }
     679         7645 :     if (chord->stem()) {
     680         7299 :         m_el.push_back(chord->stem());
     681              :     }
     682         7645 :     if (chord->hook()) {
     683          932 :         m_el.push_back(chord->hook());
     684              :     }
     685         7645 :     if (chord->stemSlash()) {
     686           13 :         m_el.push_back(chord->stemSlash());
     687              :     }
     688        12606 : }
     689              : 
     690          603 : void Selection::appendTupletHierarchy(Tuplet* innermostTuplet)
     691              : {
     692          603 :     if (!canSelectVoice(innermostTuplet->voice()) || muse::contains(m_el, static_cast<EngravingItem*>(innermostTuplet))) {
     693           52 :         return;
     694              :     }
     695              : 
     696              :     //! NOTE: Not hugely efficient since canSelectTuplet will recursively check all contained tuplets...
     697         1102 :     if (!selectionFilter().canSelectTuplet(innermostTuplet, tickStart(), tickEnd(),
     698          551 :                                            rangeContainsMultiNoteChords())) {
     699          280 :         return;
     700              :     }
     701              : 
     702          271 :     m_el.push_back(innermostTuplet);
     703              : 
     704              :     // Recursively append upwards/outwards
     705          271 :     Tuplet* outerTuplet = innermostTuplet->tuplet();
     706          271 :     if (outerTuplet) {
     707           99 :         appendTupletHierarchy(outerTuplet);
     708              :     }
     709              : }
     710              : 
     711            0 : void Selection::appendGuitarBend(GuitarBend* guitarBend)
     712              : {
     713            0 :     if (!guitarBend || !canSelectVoice(guitarBend->voice())) {
     714            0 :         return;
     715              :     }
     716              : 
     717            0 :     m_el.push_back(guitarBend);
     718              : 
     719            0 :     if (GuitarBendHold* hold = guitarBend->holdLine()) {
     720            0 :         if (hold->tick2() < tickEnd()) {
     721            0 :             m_el.push_back(hold);
     722              :         }
     723              :     }
     724              : 
     725            0 :     if (GuitarBendSegment* bendSeg = toGuitarBendSegment(guitarBend->frontSegment())) {
     726            0 :         if (GuitarBendText* bendText = bendSeg->bendText()) {
     727            0 :             m_el.push_back(bendText);
     728              :         }
     729              :     }
     730              : }
     731              : 
     732          717 : void Selection::updateSelectedElements()
     733              : {
     734          717 :     IF_ASSERT_FAILED(!isLocked()) {
     735            0 :         LOGE() << "selection locked, reason: " << lockReason();
     736            0 :         return;
     737              :     }
     738          717 :     if (m_state != SelState::RANGE) {
     739            0 :         m_rangeContainsMultiNoteChords = false;
     740            0 :         update();
     741            0 :         return;
     742              :     }
     743              : 
     744              :     // TODO: Quite a lot of nesting - should clean this up...
     745          717 :     if (m_state == SelState::RANGE && m_plannedTick1 != Fraction(-1, 1) && m_plannedTick2 != Fraction(-1, 1)) {
     746           80 :         const staff_idx_t staffStart = m_staffStart;
     747           80 :         const staff_idx_t staffEnd = m_staffEnd;
     748           80 :         Box* startBox = m_startBox;
     749           80 :         Box* endBox = m_endBox;
     750              : 
     751           80 :         deselectAll();
     752              : 
     753           80 :         Segment* s1 = nullptr;
     754           80 :         Segment* s2 = nullptr;
     755              : 
     756           80 :         if (startBox && endBox && m_plannedTick1 == m_plannedTick2) {
     757              :             // Box-only range...
     758            0 :             if (m_plannedTick1 == m_score->endTick()) {
     759            0 :                 s1 = m_score->lastSegmentMM();
     760              :             } else {
     761            0 :                 s1 = m_score->tick2segmentMM(m_plannedTick1, true, SegmentType::Duration);
     762            0 :                 s2 = s1;
     763              :             }
     764              :         } else {
     765           80 :             s1 = m_score->tick2segmentMM(m_plannedTick1);
     766           80 :             s2 = m_score->tick2segmentMM(m_plannedTick2, /* first */ true /* HACK */);
     767           80 :             if (s2 && s2->measure()->isMMRest()) {
     768            0 :                 s2 = s2->prev1MM(); // HACK
     769              :             }
     770              :             // These hacks are needed to prevent https://musescore.org/node/173381.
     771              :             // This should exclude any segments belonging to MM-rest range from the selection.
     772           80 :             if (s1 && s2 && s1->tick() + s1->ticks() > s2->tick()) {
     773              :                 // can happen with MM rests as tick2measure returns only the first segment for them.
     774              : 
     775            0 :                 m_plannedTick1 = Fraction(-1, 1);
     776            0 :                 m_plannedTick2 = Fraction(-1, 1);
     777            0 :                 return;
     778              :             }
     779              : 
     780           80 :             if (s2 && s2 == s2->measure()->first()) {
     781              :                 // we want the last segment of the previous measure (unless it's part of a MMrest)
     782           15 :                 Measure* prevMeasure = s2->measure()->prevMeasure();
     783           15 :                 if (!(prevMeasure && prevMeasure != prevMeasure->coveringMMRestOrThis())) {
     784           15 :                     s2 = s2->prev1();
     785              :                 }
     786              :             }
     787              :         }
     788              : 
     789           80 :         setRange(s1, s2, staffStart, staffEnd, startBox, endBox);
     790              : 
     791           80 :         m_plannedTick1 = Fraction(-1, 1);
     792           80 :         m_plannedTick2 = Fraction(-1, 1);
     793              :     }
     794              : 
     795        14011 :     for (EngravingItem* e : m_el) {
     796        13294 :         e->setSelected(false);
     797              :     }
     798          717 :     m_el.clear();
     799              : 
     800              :     // assert:
     801          717 :     size_t staves = m_score->nstaves();
     802          717 :     if (m_staffStart == muse::nidx || m_staffStart >= staves || m_staffEnd == muse::nidx || m_staffEnd > staves
     803          717 :         || m_staffStart >= m_staffEnd) {
     804            0 :         LOGD("updateSelectedElements: bad staff selection %zu - %zu, staves %zu", m_staffStart, m_staffEnd, staves);
     805            0 :         m_staffStart = 0;
     806            0 :         m_staffEnd   = 0;
     807              :     }
     808          717 :     track_idx_t startTrack = m_staffStart * VOICES;
     809          717 :     track_idx_t endTrack   = m_staffEnd * VOICES;
     810              : 
     811              :     //! NOTE: See appendChord/appendChordRest - we should include single notes if the selection consists solely of
     812              :     //! single notes, even if the "include single notes" filter flag is false...
     813          717 :     std::vector<Chord*> singleNoteChords;
     814          717 :     size_t totalChordsFound = 0;
     815              : 
     816              :     //! NOTE: We also need to delay the appending of tuplets. We should only display tuplets as selected
     817              :     //! if all of their contained elements are selected...
     818          717 :     std::unordered_set<Tuplet*> innerTuplets;
     819              : 
     820         4057 :     for (track_idx_t st = startTrack; st < endTrack; ++st) {
     821        65916 :         for (Segment* s = m_startSegment; s && (s != m_endSegment); s = s->next1MM()) {
     822        62576 :             if (!s->enabled() || s->isEndBarLineType()) { // do not select end bar line
     823        54571 :                 continue;
     824              :             }
     825        57632 :             for (EngravingItem* e : s->annotations()) {
     826         5060 :                 if (e->track() != st) {
     827         4299 :                     continue;
     828              :                 }
     829          761 :                 if (e->isFretDiagram()) {
     830          145 :                     FretDiagram* fd = toFretDiagram(e);
     831          145 :                     if (Harmony* harm = fd->harmony()) {
     832           72 :                         appendFiltered(harm);
     833              :                     }
     834              :                 }
     835          761 :                 appendFiltered(e);
     836              :             }
     837        52572 :             EngravingItem* e = s->element(st);
     838        52572 :             if (!e || e->generated() || e->isTimeSig() || e->isKeySig()) {
     839        39813 :                 continue;
     840              :             }
     841              : 
     842        12759 :             if (!e->isChordRest()) {
     843           15 :                 appendFiltered(e);
     844           15 :                 continue;
     845              :             }
     846              : 
     847        12744 :             ChordRest* cr = toChordRest(e);
     848        12744 :             if (Tuplet* tuplet = cr->tuplet()) {
     849         1233 :                 innerTuplets.emplace(tuplet);
     850              :             }
     851              : 
     852        12744 :             if (!cr->isChord()) {
     853         4739 :                 appendChordRest(cr);
     854         4739 :                 continue;
     855              :             }
     856              : 
     857         8005 :             Chord* chord = toChord(cr);
     858         8005 :             const std::vector<Note*> notes = chord->notes();
     859         8005 :             if (notes.size() == 1) {
     860         7304 :                 singleNoteChords.emplace_back(chord);
     861              :             } else {
     862          701 :                 appendChordRest(chord);
     863              :             }
     864         8005 :             ++totalChordsFound;
     865         8005 :         }
     866              :     }
     867              : 
     868          717 :     m_rangeContainsMultiNoteChords = totalChordsFound > singleNoteChords.size();
     869              : 
     870         8021 :     for (Chord* singleNoteChord : singleNoteChords) {
     871         7304 :         if (!m_rangeContainsMultiNoteChords || selectionFilter().includeSingleNotes()) {
     872         7132 :             appendChordRest(singleNoteChord);
     873              :         } else {
     874              :             // Include elements anchored to the note even if the note itself isn't included...
     875          172 :             const Note* note = singleNoteChord->notes().front();
     876          172 :             const std::unordered_set<EngravingItem*> noteAnchored = collectElementsAnchoredToNote(note, true, false);
     877          172 :             appendFiltered(noteAnchored);
     878          172 :             const std::unordered_set<EngravingItem*> crAnchored = collectElementsAnchoredToChordRest(singleNoteChord);
     879          172 :             appendFiltered(crAnchored);
     880          172 :         }
     881              :     }
     882              : 
     883         1221 :     for (Tuplet* tuplet : innerTuplets) {
     884          504 :         appendTupletHierarchy(tuplet);
     885              :     }
     886              : 
     887          717 :     const Fraction rangeStart = tickStart();
     888          717 :     const Fraction rangeEnd = tickEnd();
     889              : 
     890              :     //! NOTE: Ties are NOT handled in here - these are note anchored and handled in appendChordRest...
     891         2494 :     for (auto i = m_score->spanner().begin(); i != m_score->spanner().end(); ++i) {
     892         1777 :         Spanner* sp = (*i).second;
     893              :         // ignore spanners belonging to other tracks
     894         1777 :         if (sp->track() < startTrack || sp->track() >= endTrack) {
     895           26 :             continue;
     896              :         }
     897              : 
     898         1751 :         const bool startAndEndCalculated = sp->startElement() && sp->endElement();
     899         1751 :         if (!startAndEndCalculated || !canSelectVoice(sp->track()) || sp->isVolta()) {
     900            8 :             continue;
     901              :         }
     902              : 
     903              :         //! NOTE: isZeroLengthAtStart (for lack of a better name) covers an edge case that may occur if sp spans from
     904              :         //! a grace note to its parent note or vice versa. In this scenario sp->tick == sp->tick2 == rangeStart and
     905              :         //! we should include the spanner in our selection...
     906         1743 :         const bool overlapsSelectionRange = rangeStart < sp->tick2() && rangeEnd > sp->tick();
     907         1743 :         const bool isZeroLengthAtStart = sp->tick() == sp->tick2() && sp->tick() == rangeStart;
     908         1743 :         if (!overlapsSelectionRange && !isZeroLengthAtStart) {
     909         1564 :             continue;
     910              :         }
     911              : 
     912          355 :         for (auto seg : sp->spannerSegments()) {
     913          176 :             appendFiltered(seg);
     914              :         }
     915              :     }
     916          717 :     update();
     917          717 :     m_score->setSelectionChanged(true);
     918          717 : }
     919              : 
     920         2820 : bool Selection::rangeContainsMultiNoteChords() const
     921              : {
     922         2820 :     IF_ASSERT_FAILED(m_state == SelState::RANGE) {
     923            0 :         return false;
     924              :     }
     925         2820 :     return m_rangeContainsMultiNoteChords;
     926              : }
     927              : 
     928           11 : void Selection::setStartSegment(Segment* s)
     929              : {
     930           11 :     m_startSegment = s;
     931           11 :     validateBoxesAndSegments();
     932           11 : }
     933              : 
     934           11 : void Selection::setEndSegment(Segment* s)
     935              : {
     936           11 :     m_endSegment = s;
     937           11 :     validateBoxesAndSegments();
     938           11 : }
     939              : 
     940          570 : void Selection::validateBoxesAndSegments()
     941              : {
     942          570 :     if (m_startBox) {
     943           22 :         IF_ASSERT_FAILED(m_startSegment && m_startSegment->tick() == m_startBox->tick()) {
     944            0 :             m_startBox = nullptr;
     945              :         }
     946              :     }
     947              : 
     948          570 :     if (m_endBox) {
     949            3 :         const Fraction endTick = m_endSegment ? m_endSegment->tick() : m_score->endTick();
     950            3 :         IF_ASSERT_FAILED(endTick == m_endBox->tick()) {
     951            0 :             m_endBox = nullptr;
     952              :         }
     953              :     }
     954          570 : }
     955              : 
     956          345 : void Selection::setRange(Segment* startSegment, Segment* endSegment, staff_idx_t staffStart, staff_idx_t staffEnd,
     957              :                          Box* startBox, Box* endBox)
     958              : {
     959          345 :     assert(staffEnd > staffStart && staffEnd <= m_score->nstaves());
     960          345 :     assert(!(endSegment && !startSegment));
     961              : 
     962          345 :     m_startSegment = startSegment;
     963          345 :     m_endSegment = endSegment;
     964          345 :     m_startBox = startBox;
     965          345 :     m_endBox = endBox;
     966          345 :     validateBoxesAndSegments();
     967              : 
     968          345 :     m_activeSegment = endSegment;
     969          345 :     m_staffStart = staffStart;
     970          345 :     m_staffEnd = staffEnd;
     971          345 :     m_activeTrack = staff2track(staffStart);
     972              : 
     973          345 :     if (m_state == SelState::RANGE) {
     974            0 :         m_score->setSelectionChanged(true);
     975              :     } else {
     976          345 :         setState(SelState::RANGE);
     977              :     }
     978          345 : }
     979              : 
     980              : //---------------------------------------------------------
     981              : //   setRangeTicks
     982              : //    sets the range to be selected on next
     983              : //    updateSelectedElements() call. Can be used if some
     984              : //    segment structure changes are expected (e.g. if
     985              : //    creating MM rests is pending).
     986              : //---------------------------------------------------------
     987              : 
     988           92 : void Selection::setRangeTicks(const Fraction& tick1, const Fraction& tick2, staff_idx_t staffStart, staff_idx_t staffEnd,
     989              :                               Box* startBox, Box* endBox)
     990              : {
     991           92 :     assert(staffEnd > staffStart && staffEnd <= m_score->nstaves());
     992              : 
     993           92 :     m_plannedTick1 = tick1;
     994           92 :     m_plannedTick2 = tick2;
     995           92 :     m_startSegment = m_endSegment = m_activeSegment = nullptr;
     996           92 :     m_startBox = startBox;
     997           92 :     m_endBox = endBox;
     998           92 :     m_staffStart = staffStart;
     999           92 :     m_staffEnd = staffEnd;
    1000           92 :     m_activeTrack = staff2track(staffStart);
    1001              : 
    1002           92 :     if (m_state == SelState::RANGE) {
    1003           22 :         m_score->setSelectionChanged(true);
    1004              :     } else {
    1005           70 :         setState(SelState::RANGE);
    1006              :     }
    1007           92 : }
    1008              : 
    1009              : //---------------------------------------------------------
    1010              : //   update
    1011              : ///   Set select flag for all Elements in select list.
    1012              : //---------------------------------------------------------
    1013              : 
    1014         3438 : void Selection::update()
    1015              : {
    1016        55020 :     for (EngravingItem* e : m_el) {
    1017        51582 :         e->setSelected(true); // also tells accessibility that e has focus
    1018              :     }
    1019              : 
    1020         3438 :     ChordRest* cr = activeCR();
    1021         3438 :     if (!cr) {
    1022         2759 :         updateState();
    1023         2759 :         return;
    1024              :     }
    1025              : 
    1026              :     // Only one element can have focus at a time, and currently the final element in m_el has
    1027              :     // focus. That's ok for a LIST selection (because it corresponds to the last element the
    1028              :     // user clicked on) but for range selections, we should focus something in activeCR...
    1029          679 :     EngravingItem* toSelectAgain = nullptr;
    1030          679 :     if (cr->isChord()) {
    1031              :         // Use the top selected note in the chord...
    1032          471 :         const std::vector<Note*> notes = toChord(cr)->notes();
    1033          471 :         const size_t noteCount = notes.size();
    1034          480 :         for (size_t noteIdx = noteCount - 1; noteIdx < noteCount; --noteIdx) {
    1035          475 :             Note* note = notes.at(noteIdx);
    1036          475 :             if (selectionFilter().canSelectNoteIdx(noteIdx, notes.size(), rangeContainsMultiNoteChords())) {
    1037          466 :                 toSelectAgain = note;
    1038          466 :                 break;
    1039              :             }
    1040              :         }
    1041          471 :     } else {
    1042          208 :         toSelectAgain = cr;
    1043              :     }
    1044              : 
    1045          679 :     if (!toSelectAgain || !toSelectAgain->selected()) {
    1046              :         // Means we have a range with no selected elements...
    1047            6 :         updateState();
    1048            6 :         return;
    1049              :     }
    1050              : 
    1051          673 :     toSelectAgain->setSelected(true); // HACK: select it again so accessibility thinks it has focus
    1052              : 
    1053          673 :     updateState();
    1054              : }
    1055              : 
    1056            0 : void Selection::dump()
    1057              : {
    1058            0 :     LOGD("Selection dump: ");
    1059            0 :     switch (m_state) {
    1060            0 :     case SelState::NONE:   LOGD("NONE");
    1061            0 :         return;
    1062            0 :     case SelState::RANGE:  LOGD("RANGE");
    1063            0 :         break;
    1064            0 :     case SelState::LIST:   LOGD("LIST");
    1065            0 :         break;
    1066              :     }
    1067            0 :     for (const EngravingItem* e : m_el) {
    1068            0 :         LOGD("  %p %s", e, e->typeName());
    1069              :     }
    1070              : }
    1071              : 
    1072         3026 : bool Selection::rangeIsValid() const
    1073              : {
    1074         3026 :     if (!isRange()) {
    1075         3025 :         return false;
    1076              :     }
    1077              : 
    1078              :     //! NOTE: m_startSegment being non-null and m_endSegment being null is a
    1079              :     //! valid case. It means we've selected the last segment of the final measure...
    1080            1 :     if (!m_startSegment) {
    1081            0 :         return false;
    1082              :     }
    1083              : 
    1084            1 :     if (m_endSegment && m_endSegment->tick() <= m_startSegment->tick()) {
    1085            0 :         const bool isBoxOnlyRange = m_startBox && m_endBox && tickStart() == tickEnd()
    1086            0 :                                     && m_startBox->isBeforeOrEqual(m_endBox);
    1087            0 :         if (!isBoxOnlyRange) {
    1088            0 :             return false;
    1089              :         }
    1090              :     }
    1091              : 
    1092            1 :     const size_t totalStaves = m_score->nstaves();
    1093            1 :     if (m_staffStart == muse::nidx || m_staffStart >= totalStaves) {
    1094            0 :         return false;
    1095              :     }
    1096            1 :     if (m_staffEnd == muse::nidx || m_staffEnd > totalStaves) {
    1097            0 :         return false;
    1098              :     }
    1099              : 
    1100            1 :     return m_staffStart < m_staffEnd;
    1101              : }
    1102              : 
    1103              : //---------------------------------------------------------
    1104              : //   updateState
    1105              : ///   update selection and input state
    1106              : //---------------------------------------------------------
    1107              : 
    1108         6282 : void Selection::updateState()
    1109              : {
    1110              :     //! NOTE: Range can be valid even if no elements are selectable in that range...
    1111         6282 :     if (m_el.size() == 0 && !rangeIsValid()) {
    1112         3025 :         setState(SelState::NONE);
    1113         3257 :     } else if (m_state == SelState::NONE) {
    1114         1554 :         setState(SelState::LIST);
    1115              :     }
    1116              : 
    1117         6282 :     const EngravingItem* e = element();
    1118         6282 :     if (!e) {
    1119         4692 :         return;
    1120              :     }
    1121              : 
    1122         1590 :     if (e->isSpannerSegment()) {
    1123            2 :         m_currentTick = toSpannerSegment(e)->spanner()->tick();
    1124              :     } else {
    1125         1588 :         m_currentTick = e->tick();
    1126              :     }
    1127              :     // ignore system elements (e.g., frames)
    1128         1590 :     if (e->track() != muse::nidx) {
    1129         1565 :         m_currentTrack = e->track();
    1130              :     }
    1131              : }
    1132              : 
    1133        10276 : void Selection::setState(SelState s)
    1134              : {
    1135        10276 :     if (m_state == s) {
    1136         6700 :         return;
    1137              :     }
    1138              : 
    1139         3576 :     m_state = s;
    1140         3576 :     m_score->setSelectionChanged(true);
    1141              : }
    1142              : 
    1143          102 : String Selection::mimeType() const
    1144              : {
    1145          102 :     switch (m_state) {
    1146           27 :     case SelState::LIST:
    1147           27 :         return isSingle() ? String::fromAscii(mimeSymbolFormat) : String::fromAscii(mimeSymbolListFormat);
    1148           75 :     case SelState::RANGE:
    1149           75 :         return String::fromAscii(mimeStaffListFormat);
    1150            0 :     case SelState::NONE:
    1151              :     default:
    1152            0 :         break;
    1153              :     }
    1154              : 
    1155            0 :     return String();
    1156              : }
    1157              : 
    1158          108 : muse::ByteArray Selection::mimeData() const
    1159              : {
    1160          108 :     muse::ByteArray a;
    1161          108 :     switch (m_state) {
    1162           27 :     case SelState::LIST:
    1163           27 :         if (isSingle()) {
    1164           16 :             a = element()->mimeData();
    1165              :         } else {
    1166           11 :             a = symbolListMimeData();
    1167              :         }
    1168           27 :         break;
    1169            0 :     case SelState::NONE:
    1170            0 :         break;
    1171           81 :     case SelState::RANGE:
    1172           81 :         a = staffMimeData();
    1173           81 :         break;
    1174              :     }
    1175          108 :     return a;
    1176            0 : }
    1177              : 
    1178           81 : muse::ByteArray Selection::staffMimeData() const
    1179              : {
    1180          324 :     return rw::RWRegister::writer()->writeStaffSelection(m_score, selectionFilter(), staffStart(), staffEnd(), tickStart(), tickEnd(),
    1181          324 :                                                          m_startSegment, m_endSegment);
    1182              : }
    1183              : 
    1184           11 : muse::ByteArray Selection::symbolListMimeData() const
    1185              : {
    1186              :     struct MapData {
    1187              :         EngravingItem* e;
    1188              :         Segment* s;
    1189              :     };
    1190              : 
    1191           11 :     track_idx_t topTrack    = 1000000;
    1192           11 :     track_idx_t bottomTrack = 0;
    1193           11 :     Segment* firstSeg    = 0;
    1194           11 :     Fraction firstTick   = Fraction(0x7FFFFFFF, 1);
    1195           11 :     Segment* seg         = 0;
    1196           11 :     std::multimap<int64_t, MapData> map;
    1197              : 
    1198              :     // scan selection element list, inserting relevant elements in a tick-sorted map
    1199          120 :     for (EngravingItem* e : m_el) {
    1200          109 :         switch (e->type()) {
    1201           55 :         case ElementType::ARTICULATION:
    1202              :         case ElementType::ORNAMENT:
    1203              :         case ElementType::TAPPING:
    1204              :         case ElementType::ARPEGGIO:
    1205              :         case ElementType::CHORD_BRACKET:
    1206              :         case ElementType::TREMOLO_SINGLECHORD: {
    1207              :             // ignore articulations not attached to chords/rest or segment
    1208           55 :             if (!e->ownershipParent()->isChordRest()) {
    1209            0 :                 continue;
    1210              :             }
    1211           55 :             ChordRest* cr = toChordRest(e->ownershipParent());
    1212           55 :             seg = cr->segment();
    1213           55 :         } break;
    1214            0 :         case ElementType::FERMATA:
    1215            0 :             seg = toFermata(e)->segment();
    1216            0 :             break;
    1217            0 :         case ElementType::BREATH:
    1218            0 :             seg = toBreath(e)->segment();
    1219            0 :             break;
    1220           15 :         case ElementType::PLAYTECH_ANNOTATION:
    1221              :         case ElementType::CAPO:
    1222              :         case ElementType::STRING_TUNINGS:
    1223              :         case ElementType::STAFF_TEXT:
    1224              :         case ElementType::STAVE_SHARING_LABEL:
    1225           15 :             seg = toStaffTextBase(e)->segment();
    1226           15 :             break;
    1227            0 :         case ElementType::EXPRESSION:
    1228            0 :             seg = toExpression(e)->segment();
    1229            0 :             break;
    1230           15 :         case ElementType::STICKING:
    1231           15 :             seg = toSticking(e)->segment();
    1232           15 :             break;
    1233            0 :         case ElementType::FIGURED_BASS:
    1234            0 :             seg = toFiguredBass(e)->segment();
    1235            0 :             break;
    1236           11 :         case ElementType::LYRICS:
    1237           11 :             seg = toLyrics(e)->segment();
    1238           11 :             break;
    1239            0 :         case ElementType::DYNAMIC:
    1240            0 :             seg = toDynamic(e)->segment();
    1241            0 :             break;
    1242           13 :         case ElementType::HARMONY:
    1243              :         case ElementType::FRET_DIAGRAM:
    1244              :             // ignore chord symbols or fret diagrams not attached to segment
    1245           13 :             if (e->ownershipParent()->isSegment()) {
    1246           13 :                 seg = toSegment(e->ownershipParent());
    1247           13 :                 break;
    1248              :             }
    1249            0 :             continue;
    1250            0 :         case ElementType::HARP_DIAGRAM:
    1251            0 :             seg = toHarpPedalDiagram(e)->segment();
    1252            0 :             break;
    1253            0 :         case ElementType::SLUR_SEGMENT:
    1254              :         case ElementType::HAIRPIN_SEGMENT:
    1255              :         case ElementType::OTTAVA_SEGMENT:
    1256              :         case ElementType::TRILL_SEGMENT:
    1257              :         case ElementType::LET_RING_SEGMENT:
    1258              :         case ElementType::VIBRATO_SEGMENT:
    1259              :         case ElementType::PALM_MUTE_SEGMENT:
    1260              :         case ElementType::WHAMMY_BAR_SEGMENT:
    1261              :         case ElementType::RASGUEADO_SEGMENT:
    1262              :         case ElementType::HARMONIC_MARK_SEGMENT:
    1263              :         case ElementType::PICK_SCRAPE_SEGMENT:
    1264              :         case ElementType::TEXTLINE_SEGMENT:
    1265              :         case ElementType::PEDAL_SEGMENT:
    1266            0 :             e = toSpannerSegment(e)->spanner();
    1267              :             [[fallthrough]];
    1268            0 :         case ElementType::SLUR:
    1269              :         case ElementType::HAMMER_ON_PULL_OFF:
    1270              :         case ElementType::HAIRPIN:
    1271              :         case ElementType::OTTAVA:
    1272              :         case ElementType::TRILL:
    1273              :         case ElementType::LET_RING:
    1274              :         case ElementType::VIBRATO:
    1275              :         case ElementType::PALM_MUTE:
    1276              :         case ElementType::WHAMMY_BAR:
    1277              :         case ElementType::RASGUEADO:
    1278              :         case ElementType::HARMONIC_MARK:
    1279              :         case ElementType::PICK_SCRAPE:
    1280              :         case ElementType::TEXTLINE:
    1281              :         case ElementType::PEDAL:
    1282            0 :             seg = toSpanner(e)->startSegment();
    1283            0 :             break;
    1284            0 :         default:
    1285              :             // Elements of other types are ignored. To allow copying them,
    1286              :             // add support for them here and in `Score::pasteSymbols`.
    1287            0 :             continue;
    1288            0 :         }
    1289          109 :         track_idx_t track = e->track();
    1290          109 :         if (track < topTrack) {
    1291           11 :             topTrack = track;
    1292              :         }
    1293          109 :         if (track > bottomTrack) {
    1294            1 :             bottomTrack = track;
    1295              :         }
    1296          109 :         if (seg->tick() < firstTick) {
    1297           11 :             firstSeg  = seg;
    1298           11 :             firstTick = seg->tick();
    1299              :         }
    1300          109 :         MapData mapData = { e, seg };
    1301          109 :         map.insert(std::pair<int64_t, MapData>(((int64_t)track << 32) + seg->tick().ticks(), mapData));
    1302              :     }
    1303              : 
    1304              :     // scan the map, computing for each element a relative track and tick offset,
    1305              :     // and the number of CR segments to skip
    1306            0 :     std::vector<SelectedSymbol> symbols;
    1307           11 :     track_idx_t currTrack = muse::nidx;
    1308          120 :     for (auto iter = map.cbegin(); iter != map.cend(); ++iter) {
    1309              :         int numSegs;
    1310          109 :         track_idx_t track = static_cast<track_idx_t>(iter->first >> 32);
    1311          109 :         if (currTrack != track) {
    1312           12 :             currTrack = track;
    1313           12 :             seg       = firstSeg;
    1314              :         }
    1315          109 :         int tickOffset = static_cast<int>(iter->first & 0xFFFFFFFF) - firstTick.ticks();
    1316          109 :         numSegs = 0;
    1317              :         // with figured bass, we need to look for the proper segment
    1318              :         // not only according to ChordRest elements, but also annotations
    1319          109 :         if (iter->second.e->isFiguredBass()) {
    1320            0 :             bool done = false;
    1321            0 :             for (; seg; seg = seg->next1()) {
    1322            0 :                 if (seg->isChordRestType()) {
    1323              :                     // if no ChordRest in right track, look in annotations
    1324            0 :                     if (seg->element(currTrack) == nullptr) {
    1325            0 :                         for (EngravingItem* el : seg->annotations()) {
    1326              :                             // do annotations include our element?
    1327            0 :                             if (el == iter->second.e) {
    1328            0 :                                 done = true;
    1329            0 :                                 break;
    1330              :                             }
    1331              :                             // do annotations include any f.b.?
    1332            0 :                             if (el->isFiguredBass() && el->track() == track) {
    1333            0 :                                 numSegs++;                  //yes: it counts as a step
    1334            0 :                                 break;
    1335              :                             }
    1336              :                         }
    1337            0 :                         if (done) {
    1338            0 :                             break;
    1339              :                         }
    1340            0 :                         continue;                           // segment is not relevant: no ChordRest nor f.b.
    1341            0 :                     } else {
    1342            0 :                         if (iter->second.s == seg) {
    1343            0 :                             break;
    1344              :                         }
    1345              :                     }
    1346            0 :                     numSegs++;
    1347              :                 }
    1348              :             }
    1349              :         } else {
    1350          231 :             while (seg && iter->second.s != seg) {
    1351          122 :                 seg = seg->nextCR(currTrack);
    1352          122 :                 numSegs++;
    1353              :             }
    1354              :         }
    1355          109 :         symbols.push_back({ track, tickOffset, numSegs, iter->second.e });
    1356              :     }
    1357              : 
    1358           22 :     return rw::RWRegister::writer()->writeSymbolListSelection(topTrack, bottomTrack, symbols);
    1359           11 : }
    1360              : 
    1361            1 : std::vector<EngravingItem*> Selection::elements(ElementType type) const
    1362              : {
    1363            1 :     std::vector<EngravingItem*> result;
    1364              : 
    1365           24 :     for (EngravingItem* element : m_el) {
    1366           23 :         if (element->type() == type) {
    1367           23 :             result.push_back(element);
    1368              :         }
    1369              :     }
    1370              : 
    1371            1 :     return result;
    1372            0 : }
    1373              : 
    1374          111 : std::vector<Note*> Selection::noteList(track_idx_t selTrack) const
    1375              : {
    1376          111 :     std::vector<Note*> nl;
    1377              : 
    1378          111 :     if (m_state == SelState::LIST) {
    1379          237 :         for (EngravingItem* e : m_el) {
    1380          137 :             if (e->isNote()) {
    1381          137 :                 nl.push_back(toNote(e));
    1382              :             }
    1383              :         }
    1384           11 :     } else if (m_state == SelState::RANGE) {
    1385           22 :         for (staff_idx_t staffIdx = staffStart(); staffIdx < staffEnd(); ++staffIdx) {
    1386           11 :             track_idx_t startTrack = staffIdx * VOICES;
    1387           11 :             track_idx_t endTrack   = startTrack + VOICES;
    1388          203 :             for (Segment* seg = m_startSegment; seg && seg != m_endSegment; seg = seg->next1()) {
    1389          192 :                 if (!(seg->segmentType() & (SegmentType::ChordRest))) {
    1390           40 :                     continue;
    1391              :                 }
    1392          760 :                 for (track_idx_t track = startTrack; track < endTrack; ++track) {
    1393          608 :                     if (!canSelectVoice(track)) {
    1394          476 :                         continue;
    1395              :                     }
    1396          608 :                     EngravingItem* e = seg->element(track);
    1397          152 :                     if (e == 0 || !e->isChord()
    1398          760 :                         || (selTrack != muse::nidx && selTrack != track)) {
    1399          476 :                         continue;
    1400              :                     }
    1401          132 :                     Chord* c = toChord(e);
    1402          132 :                     const std::vector<Note*> notes = c->notes();
    1403          264 :                     for (size_t noteIdx = 0; noteIdx < notes.size(); ++noteIdx) {
    1404          132 :                         Note* note = notes.at(noteIdx);
    1405          132 :                         if (selectionFilter().canSelectNoteIdx(noteIdx, notes.size(), rangeContainsMultiNoteChords())) {
    1406          132 :                             nl.push_back(note);
    1407              :                         }
    1408              :                     }
    1409          132 :                     for (Chord* g : c->graceNotes()) {
    1410            0 :                         nl.insert(nl.end(), g->notes().begin(), g->notes().end());
    1411              :                     }
    1412          132 :                 }
    1413              :             }
    1414              :         }
    1415              :     }
    1416          111 :     return nl;
    1417            0 : }
    1418              : 
    1419              : //---------------------------------------------------------
    1420              : //   checkStartForPartialCopy
    1421              : //     return no error if element is NOT a tuplet or is start of a tuplet/tremolo
    1422              : //     return error if element is part of a tuplet/tremolo, but not the start
    1423              : //---------------------------------------------------------
    1424              : 
    1425           87 : static MsError checkStartForPartialCopy(EngravingItem* e)
    1426              : {
    1427           87 :     if (!e || !e->isChordRest()) {
    1428            0 :         return MsError::MS_NO_ERROR;
    1429              :     }
    1430              : 
    1431           87 :     const ChordRest* cr = toChordRest(e);
    1432           87 :     if (cr->tuplet()) {
    1433              :         // check that complete tuplet is selected, all the way up to top level
    1434            2 :         Tuplet* tuplet = cr->tuplet();
    1435            4 :         while (tuplet) {
    1436            2 :             if (tuplet->elements().front() != e) {
    1437            0 :                 return MsError::SOURCE_PARTIAL_TUPLET;
    1438              :             }
    1439            2 :             e = tuplet;
    1440            2 :             tuplet = tuplet->tuplet();
    1441              :         }
    1442              :     }
    1443              : 
    1444           87 :     if (cr->isChord()) {
    1445           83 :         const Chord* chord = toChord(cr);
    1446           83 :         const TremoloTwoChord* ttc = chord ? chord->tremoloTwoChord() : nullptr;
    1447           83 :         if (ttc && ttc->chord2() == chord) {
    1448            0 :             return MsError::SOURCE_PARTIAL_TREMOLO;
    1449              :         }
    1450              :     }
    1451              : 
    1452           87 :     return MsError::MS_NO_ERROR;
    1453              : }
    1454              : 
    1455              : //---------------------------------------------------------
    1456              : //   checkEndForPartialCopy
    1457              : //     return no error if element is NOT a tuplet or is end of a tuplet/tremolo
    1458              : //     return error if element is part of a tuplet/tremolo, but not the end
    1459              : //---------------------------------------------------------
    1460              : 
    1461          307 : static MsError checkEndForPartialCopy(EngravingItem* e, const Fraction& endTick)
    1462              : {
    1463          307 :     if (!e || !e->isChordRest()) {
    1464          220 :         return MsError::MS_NO_ERROR;
    1465              :     }
    1466              : 
    1467           87 :     const ChordRest* cr = toChordRest(e);
    1468           87 :     if (cr->tuplet()) {
    1469              :         // check that complete tuplet is selected, all the way up to top level
    1470            1 :         Tuplet* tuplet = cr->tuplet();
    1471            2 :         while (tuplet) {
    1472            1 :             if (tuplet->elements().back() != e) {
    1473            0 :                 return MsError::SOURCE_PARTIAL_TUPLET;
    1474              :             }
    1475            1 :             e = tuplet;
    1476            1 :             tuplet = tuplet->tuplet();
    1477              :         }
    1478              :         // also check that the selection extends to the end of the top-level tuplet
    1479            1 :         tuplet = toTuplet(e);
    1480            1 :         if (tuplet->endTick() > endTick) {
    1481            0 :             return MsError::SOURCE_PARTIAL_TUPLET;
    1482              :         }
    1483              :     }
    1484              : 
    1485           87 :     if (cr->isChord()) {
    1486           79 :         const Chord* chord = toChord(cr);
    1487           79 :         const TremoloTwoChord* ttc = chord ? chord->tremoloTwoChord() : nullptr;
    1488           79 :         if (ttc && ttc->chord1() == chord) {
    1489            0 :             return MsError::SOURCE_PARTIAL_TREMOLO;
    1490              :         }
    1491              :     }
    1492              : 
    1493           87 :     return MsError::MS_NO_ERROR;
    1494              : }
    1495              : 
    1496              : //---------------------------------------------------------
    1497              : //   canCopy
    1498              : //    return false if range selection intersects a tuplet
    1499              : //    or a tremolo, or a local time signature, or only part
    1500              : //    of a measure repeat group
    1501              : //---------------------------------------------------------
    1502              : 
    1503           87 : bool Selection::canCopy() const
    1504              : {
    1505           87 :     if (m_state != SelState::RANGE) {
    1506           12 :         return true;
    1507              :     }
    1508              : 
    1509           75 :     Fraction endTick = m_endSegment ? m_endSegment->tick() : m_score->lastSegment()->tick();
    1510           75 :     Fraction timeStretch(1, 0);
    1511              : 
    1512          153 :     for (staff_idx_t staffIdx = m_staffStart; staffIdx != m_staffEnd; ++staffIdx) {
    1513          390 :         for (voice_idx_t voice = 0; voice < VOICES; ++voice) {
    1514          312 :             track_idx_t track = staffIdx * VOICES + voice;
    1515          312 :             if (!canSelectVoice(track)) {
    1516            5 :                 continue;
    1517              :             }
    1518              : 
    1519              :             // check first cr in track within selection
    1520          307 :             ChordRest* check = m_startSegment->nextChordRest(track);
    1521          307 :             if (check && check->tick() < endTick) {
    1522           87 :                 const MsError err = checkStartForPartialCopy(check);
    1523           87 :                 if (err != MsError::MS_NO_ERROR) {
    1524            0 :                     MScore::setError(err);
    1525            0 :                     return false;
    1526              :                 }
    1527              :             }
    1528              : 
    1529          307 :             if (!m_endSegment) {
    1530            0 :                 continue;
    1531              :             }
    1532              : 
    1533              :             // find last segment in the selection.
    1534              :             // Note that _endSegment is the first segment after the selection
    1535              : 
    1536          307 :             Segment* endSegmentSelection = m_startSegment;
    1537          554 :             while (endSegmentSelection->nextCR(track)
    1538          554 :                    && (endSegmentSelection->nextCR(track)->tick() < m_endSegment->tick())) {
    1539          247 :                 endSegmentSelection = endSegmentSelection->nextCR(track);
    1540              :             }
    1541              : 
    1542          307 :             const MsError err = checkEndForPartialCopy(endSegmentSelection->element(track), endTick);
    1543          307 :             if (err != MsError::MS_NO_ERROR) {
    1544            0 :                 MScore::setError(err);
    1545            0 :                 return false;
    1546              :             }
    1547              :         }
    1548              : 
    1549              :         // Check that all selected measures have the same time stretch - allows copy/paste within a local time signature,
    1550              :         // but don't yet support it between differing local time signatures.
    1551          161 :         for (Measure* m = m_startSegment->measure(); m && m->tick() < endTick; m = m->nextMeasure()) {
    1552           83 :             Fraction mTimeStretch = m_score->staff(staffIdx)->timeStretch(m->tick());
    1553           83 :             if (!timeStretch.isValid()) {
    1554           75 :                 timeStretch = mTimeStretch;
    1555            8 :             } else if (timeStretch != mTimeStretch) {
    1556            0 :                 return false;
    1557              :             }
    1558              :         }
    1559              : 
    1560              :         // check if selection starts or ends partway through measure repeat group
    1561           78 :         const ChordRest* firstCR = firstChordRest();
    1562           78 :         const ChordRest* lastCR = lastChordRest();
    1563           78 :         if ((firstCR && firstCR->measure()->isMeasureRepeatGroupWithPrevM(staffIdx))
    1564          156 :             || (lastCR && lastCR->measure()->isMeasureRepeatGroupWithNextM(staffIdx))) {
    1565            0 :             return false;
    1566              :         }
    1567              :     }
    1568           75 :     return true;
    1569              : }
    1570              : 
    1571              : //---------------------------------------------------------
    1572              : //   measureBaseRange
    1573              : //    return false if no measure range selected
    1574              : //---------------------------------------------------------
    1575              : 
    1576            0 : bool Selection::measureBaseRange(MeasureBase** mb1, MeasureBase** mb2) const
    1577              : {
    1578            0 :     if (!isRange()) {
    1579            0 :         return false;
    1580              :     }
    1581              : 
    1582            0 :     if (m_startBox) {
    1583            0 :         *mb1 = m_startBox;
    1584              :     } else {
    1585            0 :         *mb1 = startSegment()->measure();
    1586              :     }
    1587              : 
    1588            0 :     Segment* s2 = endSegment();
    1589            0 :     if (m_endBox) {
    1590            0 :         *mb2 = m_endBox;
    1591              :     } else {
    1592            0 :         *mb2 = s2 ? s2->measure() : m_score->lastMeasure();
    1593              :     }
    1594              : 
    1595            0 :     if (*mb1 == *mb2) {
    1596            0 :         return true;
    1597              :     }
    1598              : 
    1599              :     // if selection extends to last segment of a measure,
    1600              :     // then endSegment() will point to next measure
    1601              :     // this won't normally happen because end barlines are excluded from range selection
    1602              :     // but just in case, detect this and back up one measure
    1603            0 :     if (*mb2 && s2) {
    1604            0 :         if ((*mb2)->isMeasure() && (*mb2)->tick() == s2->tick()) {
    1605            0 :             *mb2 = (*mb2)->prevMeasure();
    1606              :         }
    1607              :     }
    1608            0 :     return true;
    1609              : }
    1610              : 
    1611              : //---------------------------------------------------------
    1612              : //   uniqueElements
    1613              : //    Return list of selected elements.
    1614              : //    If some elements are linked, only one of the linked
    1615              : //    elements show up in the list.
    1616              : //---------------------------------------------------------
    1617              : 
    1618            1 : const std::list<EngravingItem*> Selection::uniqueElements() const
    1619              : {
    1620            1 :     std::list<EngravingItem*> l;
    1621              : 
    1622           14 :     for (EngravingItem* e : elements()) {
    1623           13 :         bool alreadyThere = false;
    1624           91 :         for (EngravingItem* ee : l) {
    1625           78 :             if ((ee->links() && ee->links()->contains(e)) || e == ee) {
    1626            0 :                 alreadyThere = true;
    1627            0 :                 break;
    1628              :             }
    1629              :         }
    1630           13 :         if (!alreadyThere) {
    1631           13 :             l.push_back(e);
    1632              :         }
    1633              :     }
    1634            1 :     return l;
    1635            0 : }
    1636              : 
    1637              : //---------------------------------------------------------
    1638              : //   uniqueNotes
    1639              : //    Return list of selected notes.
    1640              : //    If some notes are linked, only one of the linked
    1641              : //    elements show up in the list.
    1642              : //---------------------------------------------------------
    1643              : 
    1644           21 : std::vector<Note*> Selection::uniqueNotes(track_idx_t track, bool tied) const
    1645              : {
    1646           21 :     std::vector<Note*> l;
    1647              : 
    1648          166 :     auto addNote = [&l](Note* note) {
    1649          166 :         bool alreadyThere = false;
    1650         1007 :         for (Note* n : l) {
    1651          841 :             if ((n->links() && n->links()->contains(note)) || n == note) {
    1652            0 :                 alreadyThere = true;
    1653            0 :                 break;
    1654              :             }
    1655              :         }
    1656          166 :         if (!alreadyThere) {
    1657          166 :             l.push_back(note);
    1658              :         }
    1659          166 :     };
    1660              : 
    1661          187 :     for (Note* nn : noteList(track)) {
    1662          166 :         if (!tied) {
    1663           34 :             addNote(nn);
    1664           34 :             continue;
    1665              :         }
    1666              : 
    1667          264 :         for (Note* note : nn->tiedNotes()) {
    1668          132 :             addNote(note);
    1669          132 :         }
    1670           21 :     }
    1671           21 :     return l;
    1672            0 : }
    1673              : 
    1674            0 : bool Selection::elementsSelected(const ElementTypeSet& types) const
    1675              : {
    1676            0 :     for (const EngravingItem* element : m_el) {
    1677            0 :         if (muse::contains(types, element->type())) {
    1678            0 :             return true;
    1679              :         }
    1680              :     }
    1681              : 
    1682            0 :     return false;
    1683              : }
    1684              : 
    1685              : //---------------------------------------------------------
    1686              : //   extendRangeSelection
    1687              : //    Extends the range selection to contain the given
    1688              : //    chord rest.
    1689              : //---------------------------------------------------------
    1690              : 
    1691            0 : void Selection::extendRangeSelection(ChordRest* cr)
    1692              : {
    1693            0 :     extendRangeSelection(cr->segment(),
    1694              :                          cr->nextSegmentAfterCR(SegmentType::ChordRest
    1695              :                                                 | SegmentType::EndBarLine
    1696              :                                                 | SegmentType::Clef),
    1697              :                          cr->staffIdx(),
    1698            0 :                          cr->tick(),
    1699            0 :                          cr->tick());
    1700            0 : }
    1701              : 
    1702              : //---------------------------------------------------------
    1703              : //   extendRangeSelection
    1704              : //    Extends the range selection to contain the given
    1705              : //    segment. SegAfter should represent the segment
    1706              : //    that is after seg. Tick and etick represent
    1707              : //    the start and end tick of an element. Useful when
    1708              : //    extending by a chord rest.
    1709              : //---------------------------------------------------------
    1710              : 
    1711          203 : void Selection::extendRangeSelection(Segment* seg, Segment* segAfter, staff_idx_t staffIdx, const Fraction& tick,
    1712              :                                      const Fraction& etick, Box* box)
    1713              : {
    1714          203 :     bool activeSegmentIsStart = false;
    1715          203 :     staff_idx_t activeStaff = m_activeTrack / VOICES;
    1716              : 
    1717              :     // TODO: Quite a lot of nesting - should clean this up...
    1718          203 :     if (!box) {
    1719          203 :         if (staffIdx < m_staffStart) {
    1720            0 :             m_staffStart = staffIdx;
    1721          203 :         } else if (staffIdx >= m_staffEnd) {
    1722           25 :             m_staffEnd = staffIdx + 1;
    1723          178 :         } else if (m_staffEnd - m_staffStart > 1) { // at least 2 staff selected
    1724            0 :             if (staffIdx == m_staffStart + 1 && activeStaff == m_staffStart) {   // going down
    1725            0 :                 m_staffStart = staffIdx;
    1726            0 :             } else if (staffIdx == m_staffEnd - 2 && activeStaff == m_staffEnd - 1) { // going up
    1727            0 :                 m_staffEnd = staffIdx + 1;
    1728              :             }
    1729              :         }
    1730              :     }
    1731              : 
    1732              :     // start/end tick may not have changed, but a Box at the same tick may still lie outside the range...
    1733          203 :     bool extendsBackwards = tick < tickStart();
    1734          203 :     bool extendsForwards = etick > tickEnd();
    1735          203 :     if (box) {
    1736            0 :         if (box->tick() == tickStart()) {
    1737            0 :             extendsBackwards = !m_startBox || box->isBefore(m_startBox);
    1738              :         }
    1739            0 :         if (box->tick() == tickEnd()) {
    1740            0 :             extendsForwards = !m_endBox || m_endBox->isBefore(box);
    1741              :         }
    1742              :     }
    1743              : 
    1744          203 :     if (extendsBackwards) {
    1745              :         // Range extends backwards...
    1746            0 :         m_startSegment = seg;
    1747            0 :         m_startBox = box;
    1748            0 :         activeSegmentIsStart = true;
    1749          203 :     } else if (extendsForwards) {
    1750              :         // Range extends forwards...
    1751          172 :         m_endSegment = segAfter;
    1752          172 :         m_endBox = box;
    1753              :     } else {
    1754           31 :         if (m_activeSegment == m_startSegment) {
    1755            0 :             m_startSegment = seg;
    1756            0 :             m_startBox = box;
    1757            0 :             activeSegmentIsStart = true;
    1758              :         } else {
    1759           31 :             m_endSegment = segAfter;
    1760           31 :             m_endBox = box;
    1761              :         }
    1762              :     }
    1763          203 :     validateBoxesAndSegments();
    1764              : 
    1765          203 :     m_activeSegment = activeSegmentIsStart ? m_startSegment : m_endSegment;
    1766          203 :     m_score->setSelectionChanged(true);
    1767          203 :     assert(!(m_endSegment && !m_startSegment));
    1768          203 : }
    1769              : 
    1770        36134 : SelectionFilter Selection::selectionFilter() const
    1771              : {
    1772        36134 :     return m_score->selectionFilter();
    1773              : }
    1774              : 
    1775         2335 : bool Selection::canSelectNoteIdx(size_t noteIdx, size_t totalNotesInChord, bool selectionContainsMultiNoteChords) const
    1776              : {
    1777         2335 :     return selectionFilter().canSelectNoteIdx(noteIdx, totalNotesInChord, selectionContainsMultiNoteChords);
    1778              : }
        

Generated by: LCOV version 2.0-1