Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "notationselection.h"
23 :
24 : #include <QMimeData>
25 :
26 : #include "engraving/dom/score.h"
27 : #include "engraving/dom/segment.h"
28 : #include "engraving/dom/measure.h"
29 : #include "engraving/dom/keysig.h"
30 : #include "engraving/dom/timesig.h"
31 : #include "engraving/dom/box.h"
32 : #include "engraving/dom/staff.h"
33 : #include "engraving/dom/part.h"
34 : #include "engraving/dom/stringdata.h"
35 : #include "engraving/dom/stafftype.h"
36 :
37 : #include "engraving/editing/edittie.h"
38 : #include "engraving/editing/navigation.h"
39 :
40 : #include "notationselectionrange.h"
41 : #include "notationerrors.h"
42 :
43 : #include "log.h"
44 : #include "thirdparty/kors_logger/src/log_base.h"
45 :
46 : using namespace muse;
47 : using namespace mu::notation;
48 : using namespace mu::engraving;
49 :
50 0 : NotationSelection::NotationSelection(IGetScore* getScore, IInteractionForSelection* interaction)
51 0 : : m_getScore(getScore), m_interaction(interaction)
52 : {
53 0 : m_range = std::make_shared<NotationSelectionRange>(getScore);
54 0 : }
55 :
56 0 : bool NotationSelection::isNone() const
57 : {
58 0 : return score()->selection().isNone();
59 : }
60 :
61 0 : bool NotationSelection::isSingle() const
62 : {
63 0 : return score()->selection().isSingle();
64 : }
65 :
66 0 : bool NotationSelection::isRange() const
67 : {
68 0 : return score()->selection().isRange();
69 : }
70 :
71 0 : SelectionState NotationSelection::state() const
72 : {
73 0 : return score()->selection().state();
74 : }
75 :
76 0 : Ret NotationSelection::canCopy() const
77 : {
78 0 : if (isNone()) {
79 0 : return make_ret(Err::EmptySelection);
80 : }
81 :
82 0 : if (!score()->selection().canCopy()) {
83 0 : return make_ret(Err::SelectCompleteTupletOrTremolo);
84 : }
85 :
86 0 : return muse::make_ok();
87 : }
88 :
89 0 : muse::ByteArray NotationSelection::mimeData() const
90 : {
91 0 : return score()->selection().mimeData();
92 : }
93 :
94 0 : QMimeData* NotationSelection::qMimeData() const
95 : {
96 0 : QString mimeType = score()->selection().mimeType();
97 0 : if (mimeType.isEmpty()) {
98 0 : return nullptr;
99 : }
100 :
101 0 : QMimeData* mimeData = new QMimeData();
102 0 : mimeData->setData(mimeType, score()->selection().mimeData().toQByteArray());
103 :
104 0 : return mimeData;
105 0 : }
106 :
107 0 : EngravingItem* NotationSelection::element() const
108 : {
109 0 : return score()->selection().element();
110 : }
111 :
112 0 : const std::vector<EngravingItem*>& NotationSelection::elements() const
113 : {
114 0 : return score()->selection().elements();
115 : }
116 :
117 0 : std::vector<Note*> NotationSelection::notes(NoteFilter filter) const
118 : {
119 0 : switch (filter) {
120 0 : case NoteFilter::All: return score()->selection().noteList();
121 0 : case NoteFilter::WithTie: return mu::engraving::EditTie::cmdTieNoteList(score()->selection(), false);
122 0 : case NoteFilter::WithSlur: {
123 0 : NOT_IMPLEMENTED;
124 0 : return {};
125 : }
126 : }
127 :
128 0 : return {};
129 : }
130 :
131 0 : muse::RectF NotationSelection::canvasBoundingRect() const
132 : {
133 0 : if (isNone()) {
134 0 : return RectF();
135 : }
136 :
137 0 : if (const mu::engraving::EngravingItem* element = score()->selection().element()) {
138 0 : return element->canvasBoundingRect();
139 : }
140 :
141 0 : RectF result;
142 :
143 0 : for (const RectF& rect : range()->boundingArea()) {
144 0 : result = result.united(rect);
145 0 : }
146 :
147 0 : return result;
148 : }
149 :
150 0 : INotationSelectionRangePtr NotationSelection::range() const
151 : {
152 0 : return m_range;
153 : }
154 :
155 0 : Score* NotationSelection::score() const
156 : {
157 0 : return m_getScore->score();
158 : }
159 :
160 0 : void NotationSelection::onElementHit(EngravingItem* el)
161 : {
162 0 : m_lastElementHit = el;
163 0 : }
164 :
165 0 : MeasureBase* NotationSelection::startMeasureBase() const
166 : {
167 0 : return score()->selection().startMeasureBase();
168 : }
169 :
170 0 : MeasureBase* NotationSelection::endMeasureBase() const
171 : {
172 0 : return score()->selection().endMeasureBase();
173 : }
174 :
175 0 : std::vector<MeasureBase*> NotationSelection::measureBasesInSelection() const
176 : {
177 0 : return score()->selection().measureBasesInSelection();
178 : }
179 :
180 0 : std::vector<System*> NotationSelection::selectedSystems() const
181 : {
182 0 : return score()->selection().selectedSystems();
183 : }
184 :
185 0 : std::vector<mu::engraving::Page*> NotationSelection::pagesContainingSelection() const
186 : {
187 0 : return score()->selection().pagesContainingSelection();
188 : }
189 :
190 0 : EngravingItem* NotationSelection::lastElementHit() const
191 : {
192 0 : return m_lastElementHit;
193 : }
194 :
195 0 : bool NotationSelection::elementsSelected(const mu::engraving::ElementTypeSet& types) const
196 : {
197 0 : return score()->selection().elementsSelected(types);
198 : }
199 :
200 0 : void NotationSelection::select(SelectionTarget target)
201 : {
202 : //! TODO It's better to change the implementation,
203 : // instead of calling different methods, it's better to do this:
204 : // 1. get target elements
205 : // 2. call select method with the elements
206 0 : switch (target) {
207 0 : case SelectionTarget::Undefined:
208 0 : break;
209 0 : case SelectionTarget::FirstItem:
210 0 : selectFirstElement();
211 0 : break;
212 0 : case SelectionTarget::LastItem:
213 0 : selectLastElement();
214 0 : break;
215 0 : case SelectionTarget::NextItem:
216 0 : moveSelection(MoveDirection::Right, MoveSelectionType::EngravingItem);
217 0 : break;
218 0 : case SelectionTarget::PrevItem:
219 0 : moveSelection(MoveDirection::Left, MoveSelectionType::EngravingItem);
220 0 : break;
221 0 : case SelectionTarget::NextSegmentItem:
222 0 : moveSegmentSelection(MoveDirection::Right);
223 0 : break;
224 0 : case SelectionTarget::PrevSegmentItem:
225 0 : moveSegmentSelection(MoveDirection::Left);
226 0 : break;
227 0 : case SelectionTarget::NextChord:
228 0 : moveSelection(MoveDirection::Right, MoveSelectionType::Chord);
229 0 : break;
230 0 : case SelectionTarget::PrevChord:
231 0 : moveSelection(MoveDirection::Left, MoveSelectionType::Chord);
232 0 : break;
233 0 : case SelectionTarget::NextMeasure:
234 0 : moveSelection(MoveDirection::Right, MoveSelectionType::Measure);
235 0 : break;
236 0 : case SelectionTarget::PrevMeasure:
237 0 : moveSelection(MoveDirection::Left, MoveSelectionType::Measure);
238 0 : break;
239 0 : case SelectionTarget::NextTrack:
240 0 : moveSelection(MoveDirection::Right, MoveSelectionType::Track);
241 0 : break;
242 0 : case SelectionTarget::PrevTrack:
243 0 : moveSelection(MoveDirection::Left, MoveSelectionType::Track);
244 0 : break;
245 0 : case SelectionTarget::NextFrame:
246 0 : moveSelection(MoveDirection::Right, MoveSelectionType::Frame);
247 0 : break;
248 0 : case SelectionTarget::PrevFrame:
249 0 : moveSelection(MoveDirection::Left, MoveSelectionType::Frame);
250 0 : break;
251 0 : case SelectionTarget::NextSystem:
252 0 : moveSelection(MoveDirection::Right, MoveSelectionType::System);
253 0 : break;
254 0 : case SelectionTarget::PrevSystem:
255 0 : moveSelection(MoveDirection::Left, MoveSelectionType::System);
256 0 : break;
257 0 : case SelectionTarget::AboveStaff:
258 0 : moveSelection(MoveDirection::Up, MoveSelectionType::Track);
259 0 : break;
260 0 : case SelectionTarget::BelowStaff:
261 0 : moveSelection(MoveDirection::Down, MoveSelectionType::Track);
262 0 : break;
263 0 : case SelectionTarget::UpNoteInChord:
264 0 : moveChordNoteSelection(MoveDirection::Up);
265 0 : break;
266 0 : case SelectionTarget::DownNoteInChord:
267 0 : moveChordNoteSelection(MoveDirection::Down);
268 0 : break;
269 0 : case SelectionTarget::TopNoteInChord:
270 0 : selectTopOrBottomOfChord(MoveDirection::Up);
271 0 : break;
272 0 : case SelectionTarget::BottomNoteInChord:
273 0 : selectTopOrBottomOfChord(MoveDirection::Down);
274 0 : break;
275 0 : case SelectionTarget::TopStaff:
276 0 : selectTopStaff();
277 0 : break;
278 0 : case SelectionTarget::EmptyTrailingMeasure:
279 0 : selectEmptyTrailingMeasure();
280 0 : break;
281 0 : case SelectionTarget::Similar:
282 0 : selectAllSimilarElements();
283 0 : break;
284 0 : case SelectionTarget::SimilarInStaff:
285 0 : selectAllSimilarElementsInStaff();
286 0 : break;
287 0 : case SelectionTarget::SimilarInRange:
288 0 : selectAllSimilarElementsInRange();
289 0 : break;
290 0 : case SelectionTarget::NotesInChord:
291 0 : selectAllNotesInChord();
292 0 : break;
293 0 : case SelectionTarget::All:
294 0 : selectAll();
295 0 : break;
296 0 : case SelectionTarget::Section:
297 0 : selectSection();
298 0 : break;
299 : }
300 0 : }
301 :
302 0 : void NotationSelection::addToSelection(SelectionTarget target)
303 : {
304 0 : switch (target) {
305 0 : case SelectionTarget::NextChord:
306 0 : addToSelection(MoveDirection::Right, MoveSelectionType::Chord);
307 0 : break;
308 0 : case SelectionTarget::PrevChord:
309 0 : addToSelection(MoveDirection::Left, MoveSelectionType::Chord);
310 0 : break;
311 0 : case SelectionTarget::NextMeasure:
312 0 : addToSelection(MoveDirection::Right, MoveSelectionType::Measure);
313 0 : break;
314 0 : case SelectionTarget::PrevMeasure:
315 0 : addToSelection(MoveDirection::Left, MoveSelectionType::Measure);
316 0 : break;
317 0 : case SelectionTarget::AboveStaff:
318 0 : addToSelection(MoveDirection::Up, MoveSelectionType::Track);
319 0 : break;
320 0 : case SelectionTarget::BelowStaff:
321 0 : addToSelection(MoveDirection::Down, MoveSelectionType::Track);
322 0 : break;
323 0 : default:
324 0 : NOT_SUPPORTED << "SelectionTarget: " << (int)target;
325 0 : break;
326 : }
327 0 : }
328 :
329 0 : void NotationSelection::select(const std::vector<EngravingItem*>& elements, SelectType type, staff_idx_t staffIndex)
330 : {
331 0 : TRACEFUNC;
332 :
333 0 : const mu::engraving::Selection& selection = score()->selection();
334 0 : const std::vector<EngravingItem*> oldSelectedElements = selection.elements();
335 0 : const mu::engraving::SelState oldSelectionState = selection.state();
336 :
337 0 : const Fraction oldStartTick = selection.tickStart();
338 0 : const Fraction oldEndTick = selection.tickEnd();
339 :
340 0 : const staff_idx_t oldStartStaff = selection.staffStart();
341 0 : const staff_idx_t oldEndStaff = selection.staffEnd();
342 :
343 0 : const Box* oldStartBox = selection.startBox();
344 0 : const Box* oldEndBox = selection.endBox();
345 :
346 0 : doSelect(elements, type, staffIndex);
347 :
348 0 : bool rangeChanged = false;
349 0 : if (selection.isRange()) {
350 0 : const bool ticksChanged = oldStartTick != selection.tickStart() || oldEndTick != selection.tickEnd();
351 0 : const bool stavesChanged = oldStartStaff != selection.staffStart() || oldEndStaff != selection.staffEnd();
352 0 : const bool boxesChanged = oldStartBox != selection.startBox() || oldEndBox != selection.endBox();
353 0 : rangeChanged = ticksChanged || stavesChanged || boxesChanged;
354 : }
355 :
356 0 : if (rangeChanged || oldSelectionState != selection.state() || oldSelectedElements != selection.elements()) {
357 0 : notifyAboutSelectionChangedIfNeed();
358 : } else {
359 0 : score()->setSelectionChanged(false);
360 : }
361 0 : }
362 :
363 0 : void NotationSelection::doSelect(const std::vector<EngravingItem*>& elements, SelectType type, staff_idx_t staffIndex)
364 : {
365 0 : TRACEFUNC;
366 :
367 0 : if (elements.size() == 1 && type == SelectType::ADD && QGuiApplication::keyboardModifiers() == Qt::KeyboardModifier::ControlModifier) {
368 0 : if (score()->selection().isRange()) {
369 0 : score()->selection().setState(mu::engraving::SelState::LIST);
370 0 : score()->setUpdateAll();
371 : }
372 :
373 0 : if (elements.front()->selected()) {
374 0 : score()->deselect(elements.front());
375 0 : return;
376 : }
377 : }
378 :
379 0 : if (type == SelectType::REPLACE) {
380 0 : score()->deselectAll();
381 0 : type = SelectType::ADD;
382 : }
383 :
384 0 : if (type == SelectType::SINGLE && elements.size() == 1) {
385 0 : const mu::engraving::EngravingItem* element = elements.front();
386 0 : mu::engraving::Segment* segment = nullptr;
387 :
388 0 : if (element->isKeySig()) {
389 0 : segment = mu::engraving::toKeySig(element)->segment();
390 0 : } else if (element->isTimeSig()) {
391 0 : segment = mu::engraving::toTimeSig(element)->segment();
392 : }
393 :
394 0 : if (segment) {
395 0 : selectElementsWithSameTypeOnSegment(element->type(), segment);
396 0 : return;
397 : }
398 : }
399 :
400 0 : score()->select(elements, type, staffIndex);
401 0 : }
402 :
403 0 : void NotationSelection::clearSelection()
404 : {
405 0 : score()->deselectAll();
406 :
407 0 : notifyAboutSelectionChangedIfNeed();
408 0 : }
409 :
410 0 : void NotationSelection::notifyAboutSelectionChangedIfNeed()
411 : {
412 0 : if (!score()->selectionChanged()) {
413 0 : return;
414 : }
415 :
416 0 : TRACEFUNC;
417 :
418 0 : score()->setSelectionChanged(false);
419 :
420 0 : m_selectionChanged.notify();
421 0 : }
422 :
423 0 : muse::async::Notification NotationSelection::selectionChanged() const
424 : {
425 0 : return m_selectionChanged;
426 : }
427 :
428 0 : void NotationSelection::selectElementsWithSameTypeOnSegment(mu::engraving::ElementType elementType, mu::engraving::Segment* segment)
429 : {
430 0 : TRACEFUNC;
431 :
432 0 : IF_ASSERT_FAILED(segment) {
433 0 : return;
434 : }
435 :
436 0 : score()->deselectAll();
437 :
438 0 : std::vector<EngravingItem*> elementsToSelect;
439 :
440 0 : for (track_idx_t track = 0; track < score()->ntracks(); track += VOICES) {
441 0 : EngravingItem* element = segment->element(track);
442 0 : if (element && element->type() == elementType) {
443 0 : elementsToSelect.push_back(element);
444 : }
445 : }
446 :
447 0 : score()->select(elementsToSelect, SelectType::ADD);
448 0 : }
449 :
450 0 : void NotationSelection::selectFirstElement(bool frame)
451 : {
452 0 : if (EngravingItem* element = Navigation::firstElement(score(), frame)) {
453 0 : select({ element }, SelectType::SINGLE, element->staffIdx());
454 0 : m_interaction->showItem(element);
455 : }
456 0 : }
457 :
458 0 : void NotationSelection::selectLastElement()
459 : {
460 0 : if (EngravingItem* element = Navigation::lastElement(score())) {
461 0 : select({ element }, SelectType::SINGLE, element->staffIdx());
462 0 : m_interaction->showItem(element);
463 : }
464 0 : }
465 :
466 0 : void NotationSelection::moveSelection(MoveDirection d, MoveSelectionType type)
467 : {
468 0 : IF_ASSERT_FAILED(MoveSelectionType::Undefined != type) {
469 0 : return;
470 : }
471 :
472 0 : if (type != MoveSelectionType::String) {
473 0 : IF_ASSERT_FAILED(MoveDirection::Left == d || MoveDirection::Right == d) {
474 0 : return;
475 : }
476 : } else {
477 0 : IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
478 0 : return;
479 : }
480 : }
481 :
482 0 : if (MoveSelectionType::EngravingItem == type) {
483 0 : moveElementSelection(d);
484 0 : return;
485 : }
486 :
487 0 : if (MoveSelectionType::String == type) {
488 0 : moveStringSelection(d);
489 0 : return;
490 : }
491 :
492 : // TODO: rewrite, Score::move( ) logic needs to be here
493 :
494 0 : auto typeToString = [](MoveSelectionType type) {
495 0 : switch (type) {
496 0 : case MoveSelectionType::Undefined: return QString();
497 0 : case MoveSelectionType::EngravingItem: return QString();
498 0 : case MoveSelectionType::Chord: return QString("chord");
499 0 : case MoveSelectionType::Measure: return QString("measure");
500 0 : case MoveSelectionType::Track: return QString("track");
501 0 : case MoveSelectionType::Frame: return QString("frame");
502 0 : case MoveSelectionType::System: return QString("system");
503 0 : case MoveSelectionType::String: return QString();
504 : }
505 0 : return QString();
506 : };
507 :
508 0 : QString cmd;
509 0 : if (MoveDirection::Left == d) {
510 0 : cmd = "prev-";
511 0 : } else if (MoveDirection::Right == d) {
512 0 : cmd = "next-";
513 : }
514 :
515 0 : cmd += typeToString(type);
516 :
517 0 : mu::engraving::EngravingItem* item = Navigation::move(score(), cmd);
518 0 : m_interaction->resetHitElementContext();
519 :
520 0 : notifyAboutSelectionChangedIfNeed();
521 0 : m_interaction->showItem(item);
522 0 : }
523 :
524 0 : ChordRest* NotationSelection::activeCr(const mu::engraving::Score* score) const
525 : {
526 0 : ChordRest* cr = score->selection().activeCR();
527 0 : if (!cr) {
528 0 : cr = score->selection().lastChordRest();
529 0 : if (!cr && score->noteEntryMode()) {
530 0 : cr = score->inputState().cr();
531 : }
532 : }
533 0 : return cr;
534 : }
535 :
536 0 : void NotationSelection::addToSelection(MoveDirection d, MoveSelectionType type)
537 : {
538 0 : ChordRest* cr = activeCr(score());
539 0 : if (!cr) {
540 0 : return;
541 : }
542 0 : ChordRest* el = 0;
543 0 : switch (type) {
544 0 : case MoveSelectionType::Chord: {
545 0 : ChordRestNavigateOptions options;
546 0 : options.skipGrace = true;
547 0 : if (d == MoveDirection::Right) {
548 0 : el = mu::engraving::Navigation::nextChordRest(cr, options);
549 : } else {
550 0 : el = mu::engraving::Navigation::prevChordRest(cr, options);
551 : }
552 0 : break;
553 : }
554 0 : case MoveSelectionType::Measure:
555 0 : if (d == MoveDirection::Right) {
556 0 : el = mu::engraving::Navigation::nextMeasure(score(), cr, true, true);
557 : } else {
558 0 : el = mu::engraving::Navigation::prevMeasure(score(), cr, true);
559 : }
560 0 : break;
561 0 : case MoveSelectionType::Track:
562 0 : if (d == MoveDirection::Up) {
563 0 : el = mu::engraving::Navigation::upStaff(cr);
564 : } else {
565 0 : el = mu::engraving::Navigation::downStaff(score(), cr);
566 : }
567 : case MoveSelectionType::EngravingItem:
568 : case MoveSelectionType::Frame:
569 : case MoveSelectionType::System:
570 : case MoveSelectionType::String:
571 : case MoveSelectionType::Undefined:
572 0 : break;
573 : }
574 :
575 0 : if (el) {
576 0 : select({ el }, SelectType::RANGE, el->staffIdx());
577 0 : m_interaction->showItem(el);
578 0 : m_interaction->resetHitElementContext();
579 : }
580 : }
581 :
582 0 : void NotationSelection::moveElementSelection(MoveDirection d)
583 : {
584 0 : EngravingItem* el = score()->selection().element();
585 0 : if (!el && !score()->selection().elements().empty()) {
586 0 : el = score()->selection().elements().back();
587 : }
588 :
589 0 : if (m_interaction->isTextEditingStarted() && el && el->isTextBase()) {
590 0 : m_interaction->navigateToNearText(d);
591 0 : return;
592 : }
593 :
594 0 : const bool isLeftDirection = MoveDirection::Left == d;
595 0 : const bool isHorizontalLayout = score()->isLayoutMode(LayoutMode::LINE) || score()->isLayoutMode(LayoutMode::HORIZONTAL_FIXED);
596 :
597 : // VBoxes are not included in horizontal layouts - skip over them (and their contents) when moving selections...
598 0 : const auto nextNonVBox = [this, isLeftDirection](EngravingItem* currElem) -> EngravingItem* {
599 0 : IF_ASSERT_FAILED(currElem) {
600 0 : return nullptr;
601 : }
602 :
603 0 : while (const EngravingItem* vBox = currElem->findAncestor(ElementType::VBOX)) {
604 0 : currElem = isLeftDirection ? toVBox(vBox)->prevMM() : toVBox(vBox)->nextMM();
605 0 : if (currElem && currElem->isMeasure()) {
606 0 : const ChordRest* cr = score()->selection().currentCR();
607 0 : const staff_idx_t si = cr ? cr->staffIdx() : 0;
608 0 : Measure* mb = toMeasure(currElem);
609 0 : currElem = isLeftDirection ? mb->prevElementStaff(si, currElem) : mb->nextElementStaff(si, currElem);
610 : }
611 :
612 0 : if (!currElem) {
613 0 : break;
614 : }
615 0 : }
616 :
617 0 : return currElem;
618 0 : };
619 :
620 0 : EngravingItem* toEl = nullptr;
621 :
622 0 : if (el) {
623 0 : toEl = isLeftDirection ? score()->prevElement() : score()->nextElement();
624 0 : if (isHorizontalLayout) {
625 0 : toEl = nextNonVBox(toEl);
626 : }
627 : } else {
628 : // Nothing currently selected (e.g. because user pressed Esc or clicked on
629 : // an empty region of the page). Try to restore previous selection.
630 0 : if (ChordRest* cr = score()->selection().currentCR()) {
631 0 : el = cr->isChord() ? toChord(cr)->upNote() : toEngravingItem(cr);
632 : }
633 0 : if (el) {
634 0 : toEl = el; // Restoring previous selection.
635 : } else {
636 0 : toEl = isLeftDirection ? Navigation::lastElement(score()) : Navigation::firstElement(score());
637 0 : if (isHorizontalLayout) {
638 0 : toEl = nextNonVBox(toEl);
639 : }
640 : }
641 : }
642 :
643 0 : if (!toEl) {
644 0 : return;
645 : }
646 :
647 0 : if (m_interaction->isEditingElement()) {
648 0 : m_interaction->endEditElement();
649 : }
650 :
651 0 : select({ toEl }, SelectType::REPLACE);
652 0 : m_interaction->resetHitElementContext();
653 0 : m_interaction->showItem(toEl);
654 :
655 0 : if (toEl->isNote() || toEl->isHarmony()) {
656 0 : score()->setPlayNote(true);
657 : }
658 :
659 0 : if (toEl->needStartEditingAfterSelecting()) {
660 0 : m_interaction->startEditElement(toEl);
661 : }
662 : }
663 :
664 0 : void NotationSelection::moveStringSelection(MoveDirection d)
665 : {
666 0 : mu::engraving::InputState& is = score()->inputState();
667 0 : mu::engraving::Staff* staff = score()->staff(track2staff(is.track()));
668 0 : int instrStrgs = static_cast<int>(staff->part()->stringData(is.tick(), staff->idx())->strings());
669 0 : int delta = (staff->staffType(is.tick())->upsideDown() ? -1 : 1);
670 :
671 0 : if (MoveDirection::Up == d) {
672 0 : delta = -delta;
673 : }
674 :
675 0 : int strg = is.string() + delta;
676 0 : if (strg >= 0 && strg < instrStrgs && strg != is.string()) {
677 0 : is.setString(strg);
678 :
679 0 : const ChordRest* chordRest = is.cr();
680 0 : if (chordRest && chordRest->isChord()) {
681 0 : const Chord* chord = toChord(chordRest);
682 :
683 0 : for (Note* note : chord->notes()) {
684 0 : if (note->string() == strg) {
685 0 : select({ note }, SelectType::SINGLE);
686 : }
687 : }
688 : }
689 : }
690 0 : }
691 :
692 0 : void NotationSelection::moveSegmentSelection(MoveDirection d)
693 : {
694 0 : IF_ASSERT_FAILED(MoveDirection::Left == d || MoveDirection::Right == d) {
695 0 : return;
696 : }
697 :
698 0 : EngravingItem* e = this->element();
699 0 : if (!e && !this->elements().empty()) {
700 0 : e = d == MoveDirection::Left ? this->elements().front() : this->elements().back();
701 : }
702 :
703 0 : if (!e || (e = d == MoveDirection::Left ? e->prevSegmentElement() : e->nextSegmentElement()) == nullptr) {
704 0 : e = d == MoveDirection::Left ? Navigation::firstElement(score()) : Navigation::lastElement(score());
705 : }
706 :
707 0 : select({ e }, SelectType::SINGLE);
708 0 : m_interaction->showItem(e);
709 : }
710 :
711 0 : void NotationSelection::moveChordNoteSelection(MoveDirection d)
712 : {
713 0 : IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
714 0 : return;
715 : }
716 :
717 0 : EngravingItem* current = this->element();
718 0 : if (!current || !(current->isNote() || current->isRest())) {
719 0 : return;
720 : }
721 :
722 : EngravingItem* chordElem;
723 0 : if (d == MoveDirection::Up) {
724 0 : chordElem = Navigation::chordNoteAbove(score(), current);
725 : } else {
726 0 : chordElem = Navigation::chordNoteBelow(score(), current);
727 : }
728 :
729 0 : if (chordElem == current) {
730 0 : return;
731 : }
732 :
733 0 : select({ chordElem }, SelectType::SINGLE, chordElem->staffIdx());
734 0 : m_interaction->showItem(chordElem);
735 : }
736 :
737 0 : void NotationSelection::selectTopOrBottomOfChord(MoveDirection d)
738 : {
739 0 : IF_ASSERT_FAILED(MoveDirection::Up == d || MoveDirection::Down == d) {
740 0 : return;
741 : }
742 :
743 0 : EngravingItem* current = this->element();
744 0 : if (!current || !current->isNote()) {
745 0 : return;
746 : }
747 :
748 : EngravingItem* target = d == MoveDirection::Up
749 0 : ? Navigation::topNoteInChord(toNote(current)) : Navigation::bottomNoteInChord(toNote(current));
750 :
751 0 : if (target == current) {
752 0 : return;
753 : }
754 :
755 0 : select({ target }, SelectType::SINGLE);
756 0 : m_interaction->showItem(target);
757 : }
758 :
759 0 : void NotationSelection::selectTopStaff()
760 : {
761 0 : EngravingItem* el = Navigation::topStaff(score(), activeCr(score()));
762 0 : if (score()->noteEntryMode()) {
763 0 : score()->inputState().moveInputPos(el);
764 : }
765 :
766 0 : if (el->isChord()) {
767 0 : el = mu::engraving::toChord(el)->upNote();
768 : }
769 :
770 0 : select({ el }, SelectType::SINGLE, 0);
771 0 : m_interaction->showItem(el);
772 0 : m_interaction->resetHitElementContext();
773 0 : }
774 :
775 0 : void NotationSelection::selectEmptyTrailingMeasure()
776 : {
777 0 : ChordRest* cr = activeCr(score());
778 0 : const Measure* ftm = score()->firstTrailingMeasure(cr ? &cr : nullptr);
779 0 : if (!ftm) {
780 0 : ftm = score()->lastMeasure();
781 : }
782 0 : if (ftm) {
783 0 : if (score()->style().styleB(mu::engraving::Sid::createMultiMeasureRests) && ftm->hasMMRest()) {
784 0 : ftm = ftm->coveringMMRestOrThis();
785 : }
786 : EngravingItem* el
787 0 : = !cr ? ftm->first()->nextChordRest(0, false) : ftm->first()->nextChordRest(mu::engraving::trackZeroVoice(cr->track()), false);
788 0 : score()->inputState().moveInputPos(el);
789 0 : select({ el }, SelectType::SINGLE);
790 0 : m_interaction->resetHitElementContext();
791 : }
792 0 : }
793 :
794 0 : FilterElementsOptions NotationSelection::elementsFilterOptions(const EngravingItem* element) const
795 : {
796 0 : TRACEFUNC;
797 0 : FilterElementsOptions options;
798 0 : options.elementType = element->type();
799 :
800 0 : if (element->isNote()) {
801 0 : const mu::engraving::Note* note = dynamic_cast<const mu::engraving::Note*>(element);
802 0 : if (note->chord()->isGrace()) {
803 0 : options.subtype = -1;
804 : } else {
805 0 : options.subtype = element->subtype();
806 : }
807 0 : } else if (element->isHairpinSegment() || element->isHarmony()) {
808 0 : options.subtype = element->subtype();
809 0 : options.bySubtype = true;
810 : }
811 :
812 0 : return options;
813 0 : }
814 :
815 0 : void NotationSelection::selectAllSimilarElements()
816 : {
817 0 : TRACEFUNC;
818 0 : auto notationElements = m_interaction->elements();
819 0 : if (!notationElements) {
820 0 : return;
821 : }
822 :
823 0 : EngravingItem* selectedElement = this->element();
824 0 : if (!selectedElement) {
825 0 : return;
826 : }
827 :
828 0 : FilterElementsOptions options = elementsFilterOptions(selectedElement);
829 0 : std::vector<EngravingItem*> elements = notationElements->elements(options);
830 0 : if (elements.empty()) {
831 0 : return;
832 : }
833 :
834 0 : clearSelection();
835 :
836 0 : select(elements, SelectType::ADD);
837 0 : }
838 :
839 0 : void NotationSelection::selectAllSimilarElementsInStaff()
840 : {
841 0 : TRACEFUNC;
842 0 : auto notationElements = m_interaction->elements();
843 0 : if (!notationElements) {
844 0 : return;
845 : }
846 :
847 0 : EngravingItem* selectedElement = this->element();
848 0 : if (!selectedElement) {
849 0 : return;
850 : }
851 :
852 0 : FilterElementsOptions options = elementsFilterOptions(selectedElement);
853 0 : options.staffStart = static_cast<int>(selectedElement->staffIdx());
854 0 : options.staffEnd = options.staffStart + 1;
855 :
856 0 : std::vector<EngravingItem*> elements = notationElements->elements(options);
857 0 : if (elements.empty()) {
858 0 : return;
859 : }
860 :
861 0 : clearSelection();
862 :
863 0 : select(elements, SelectType::ADD);
864 0 : }
865 :
866 0 : void NotationSelection::selectAllSimilarElementsInRange()
867 : {
868 0 : auto elements = m_interaction->elements();
869 0 : if (!elements) {
870 0 : return;
871 : }
872 :
873 0 : mu::engraving::EngravingItem* lastHit = this->lastElementHit();
874 0 : if (!lastHit) {
875 0 : return;
876 : }
877 :
878 0 : mu::engraving::Score* score = elements->msScore();
879 0 : score->selectSimilarInRange(lastHit);
880 0 : notifyAboutSelectionChangedIfNeed();
881 0 : }
882 :
883 0 : void NotationSelection::selectAllNotesInChord()
884 : {
885 0 : TRACEFUNC;
886 :
887 0 : const std::vector<EngravingItem*>& selectedElements = this->elements();
888 0 : if (selectedElements.empty()) {
889 0 : return;
890 : }
891 :
892 0 : std::set<const Chord*> chords;
893 0 : for (const EngravingItem* item : selectedElements) {
894 0 : if (item->isNote()) {
895 0 : chords.insert(toNote(item)->chord());
896 : }
897 : }
898 :
899 0 : if (chords.empty()) {
900 0 : return;
901 : }
902 :
903 0 : std::vector<EngravingItem*> allNotes;
904 0 : for (const Chord* chord : chords) {
905 0 : for (Note* note : chord->notes()) {
906 0 : allNotes.push_back(note);
907 : }
908 : }
909 :
910 0 : clearSelection();
911 0 : select(allNotes, SelectType::ADD);
912 0 : }
913 :
914 0 : void NotationSelection::selectAll()
915 : {
916 0 : if (m_interaction->isTextEditingStarted()) {
917 0 : m_interaction->selectAllText();
918 : } else {
919 0 : score()->cmdSelectAll();
920 : }
921 :
922 0 : notifyAboutSelectionChangedIfNeed();
923 0 : }
924 :
925 0 : void NotationSelection::selectSection()
926 : {
927 0 : score()->cmdSelectSection();
928 :
929 0 : notifyAboutSelectionChangedIfNeed();
930 0 : }
|