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 "slur.h"
23 :
24 : #include "../editing/editspanner.h"
25 : #include "../editing/mscoreview.h"
26 : #include "../editing/navigation.h"
27 : #include "../editing/transaction/transaction.h"
28 : #include "../rendering/iscorerenderer.h"
29 :
30 : #include "arpeggio.h"
31 : #include "beam.h"
32 : #include "chord.h"
33 : #include "masterscore.h"
34 : #include "measure.h"
35 : #include "note.h"
36 : #include "part.h"
37 : #include "score.h"
38 : #include "staff.h"
39 : #include "stafftype.h"
40 : #include "stem.h"
41 : #include "system.h"
42 : #include "tremolotwochord.h"
43 :
44 : #include "log.h"
45 :
46 : using namespace mu;
47 : using namespace mu::engraving;
48 : using namespace muse::draw;
49 :
50 : namespace mu::engraving {
51 533 : SlurSegment::SlurSegment(Slur* sp, ElementType type)
52 533 : : SlurTieSegment(type, sp)
53 : {
54 533 : }
55 :
56 34 : SlurSegment::SlurSegment(const SlurSegment& ss)
57 34 : : SlurTieSegment(ss)
58 : {
59 34 : }
60 :
61 : //---------------------------------------------------------
62 : // searchCR
63 : //---------------------------------------------------------
64 :
65 0 : static ChordRest* searchCR(Segment* segment, track_idx_t startTrack, track_idx_t endTrack)
66 : {
67 0 : for (Segment* s = segment; s; s = s->next(SegmentType::ChordRest)) { // restrict search to measure
68 0 : if (startTrack > endTrack) {
69 0 : for (int t = static_cast<int>(startTrack) - 1; t >= static_cast<int>(endTrack); --t) {
70 0 : if (s->element(t)) {
71 0 : return toChordRest(s->element(t));
72 : }
73 : }
74 : } else {
75 0 : for (track_idx_t t = startTrack; t < endTrack; ++t) {
76 0 : if (s->element(t)) {
77 0 : return toChordRest(s->element(t));
78 : }
79 : }
80 : }
81 : }
82 0 : return 0;
83 : }
84 :
85 0 : bool SlurSegment::isEditAllowed(EditData& ed) const
86 : {
87 0 : if (SlurTieSegment::isEditAllowed(ed)) {
88 0 : return true;
89 : }
90 :
91 0 : const bool moveStart = ed.curGrip == Grip::START;
92 0 : const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::DRAG;
93 :
94 0 : if (!((ed.modifiers & ShiftModifier) && (isSingleType()
95 0 : || (isBeginType() && moveStart) || (isEndType() && moveEnd)))) {
96 0 : return false;
97 : }
98 :
99 : static const std::set<int> navigationKeys {
100 : Key_Left,
101 : Key_Up,
102 : Key_Down,
103 : Key_Right
104 0 : };
105 :
106 0 : return muse::contains(navigationKeys, ed.key);
107 : }
108 :
109 : //---------------------------------------------------------
110 : // edit
111 : // return true if event is accepted
112 : //---------------------------------------------------------
113 :
114 0 : bool SlurSegment::edit(EditData& ed)
115 : {
116 0 : if (!isEditAllowed(ed)) {
117 0 : return false;
118 : }
119 :
120 0 : if (SlurTieSegment::edit(ed)) {
121 0 : return true;
122 : }
123 :
124 0 : Slur* sl = slur();
125 :
126 0 : ChordRest* cr = nullptr;
127 : ChordRest* e;
128 : ChordRest* e1;
129 0 : const bool start = ed.curGrip == Grip::START;
130 0 : if (start) {
131 0 : e = sl->startCR();
132 0 : e1 = sl->endCR();
133 : } else {
134 0 : e = sl->endCR();
135 0 : e1 = sl->startCR();
136 : }
137 :
138 0 : const bool altMod = ed.modifiers & AltModifier;
139 0 : const bool shiftMod = ed.modifiers & ShiftModifier;
140 0 : const bool ctrlMod = ed.modifiers & ControlModifier;
141 0 : const bool extendToBarLine = shiftMod && altMod;
142 0 : const bool isPartialSlur = sl->isIncoming() || sl->isOutgoing();
143 :
144 0 : ChordRestNavigateOptions options;
145 0 : options.disableOverRepeats = true;
146 :
147 0 : if (ed.key == Key_Left) {
148 0 : if (extendToBarLine) {
149 0 : const Measure* measure = e->measure();
150 0 : if (start) {
151 0 : cr = measure->firstChordRest(e->track());
152 0 : if (!cr->hasPrecedingJumpItem()) {
153 0 : return false;
154 : }
155 0 : sl->undoSetIncoming(true);
156 0 : } else if (e->hasFollowingJumpItem()) {
157 0 : sl->undoSetOutgoing(false);
158 : }
159 : } else {
160 0 : if (!start && sl->isOutgoing()) {
161 0 : sl->undoSetOutgoing(false);
162 : } else {
163 0 : if (start && sl->isIncoming()) {
164 0 : sl->undoSetIncoming(false);
165 : }
166 0 : if (ctrlMod) {
167 0 : cr = Navigation::prevMeasure(score(), cr, true);
168 : } else {
169 0 : cr = Navigation::prevChordRest(e, options);
170 : }
171 : }
172 : }
173 0 : } else if (ed.key == Key_Right) {
174 0 : if (extendToBarLine) {
175 0 : const Measure* measure = e->measure();
176 0 : if (start && e->hasPrecedingJumpItem()) {
177 0 : sl->undoSetIncoming(false);
178 0 : } else if (!start) {
179 0 : cr = measure->lastChordRest(e->track());
180 0 : if (!cr->hasFollowingJumpItem()) {
181 0 : return false;
182 : }
183 0 : sl->undoSetOutgoing(true);
184 : }
185 : } else {
186 0 : if (start && sl->isIncoming()) {
187 0 : sl->undoSetIncoming(false);
188 : } else {
189 0 : if (!start && sl->isOutgoing()) {
190 0 : sl->undoSetOutgoing(false);
191 : }
192 0 : if (ctrlMod) {
193 0 : cr = Navigation::nextMeasure(score(), cr, false, true);
194 : } else {
195 0 : cr = Navigation::nextChordRest(e, options);
196 : }
197 : }
198 : }
199 0 : } else if (ed.key == Key_Up) {
200 0 : track_idx_t startTrack = e->part()->trackRange().startTrack;
201 0 : track_idx_t endTrack = e->track();
202 0 : cr = searchCR(e->segment(), endTrack, startTrack);
203 0 : } else if (ed.key == Key_Down) {
204 0 : track_idx_t startTrack = e->track() + 1;
205 0 : track_idx_t endTrack = e->part()->trackRange().endTrack;
206 0 : cr = searchCR(e->segment(), startTrack, endTrack);
207 : } else {
208 0 : return false;
209 : }
210 0 : if (cr && (cr != e1 || isPartialSlur)) {
211 0 : if (cr->staff() != e->staff() && (cr->staffType()->isTabStaff() || e->staffType()->isTabStaff())) {
212 0 : return false; // Cross-staff slurs don't make sense for TAB staves
213 : }
214 0 : if (cr->staff()->isLinked(e->staff())) {
215 0 : return false; // Don't allow slur to cross into staff that's linked to this
216 : }
217 0 : changeAnchor(ed, cr);
218 : }
219 0 : return true;
220 : }
221 :
222 : //---------------------------------------------------------
223 : // changeAnchor
224 : //---------------------------------------------------------
225 :
226 0 : void SlurSegment::changeAnchor(EditData& ed, EngravingItem* element)
227 : {
228 0 : Transaction& tx = masterScore()->transactionManager()->currentOrDummyTransaction();
229 :
230 0 : ChordRest* cr = element->isChordRest() ? toChordRest(element) : nullptr;
231 0 : ChordRest* scr = spanner()->startCR();
232 0 : ChordRest* ecr = spanner()->endCR();
233 0 : if (!cr || !scr || !ecr) {
234 0 : return;
235 : }
236 :
237 : // save current start/end elements
238 0 : for (EngravingObject* e : spanner()->linkList()) {
239 0 : Spanner* sp = toSpanner(e);
240 0 : tx.pushWithoutPerforming(new ChangeStartEndSpanner(sp, sp->startElement(), sp->endElement()));
241 0 : }
242 :
243 0 : if (ed.curGrip == Grip::START) {
244 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICK, cr->tick());
245 0 : Fraction ticks = ecr->tick() - cr->tick();
246 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICKS, ticks);
247 0 : int diff = static_cast<int>(cr->track() - spanner()->track());
248 0 : for (auto e : spanner()->linkList()) {
249 0 : Spanner* s = toSpanner(e);
250 0 : s->undoChangeProperty(Pid::TRACK, s->track() + diff);
251 0 : }
252 0 : scr = cr;
253 : } else {
254 0 : Fraction ticks = cr->tick() - scr->tick();
255 0 : spanner()->undoChangeProperty(Pid::SPANNER_TICKS, ticks);
256 0 : int diff = static_cast<int>(cr->track() - spanner()->track());
257 0 : for (auto e : spanner()->linkList()) {
258 0 : Spanner* s = toSpanner(e);
259 0 : s->undoChangeProperty(Pid::SPANNER_TRACK2, s->track() + diff);
260 0 : }
261 0 : ecr = cr;
262 : }
263 :
264 : // update start/end elements (which could be grace notes)
265 0 : slur()->undoChangeStartEndElements(scr, ecr);
266 :
267 0 : const size_t segments = spanner()->spannerSegments().size();
268 0 : ups(ed.curGrip).off = PointF();
269 0 : renderer()->layoutItem(spanner());
270 0 : if (spanner()->spannerSegments().size() != segments) {
271 0 : const std::vector<SpannerSegment*>& ss = spanner()->spannerSegments();
272 0 : const bool moveEnd = ed.curGrip == Grip::END || ed.curGrip == Grip::DRAG;
273 0 : SlurSegment* newSegment = toSlurSegment(moveEnd ? ss.back() : ss.front());
274 0 : ed.view()->changeEditElement(newSegment);
275 0 : triggerLayout();
276 : }
277 : }
278 :
279 0 : void SlurSegment::dragGrip(EditData& ed)
280 : {
281 0 : Grip g = ed.curGrip;
282 :
283 0 : switch (g) {
284 0 : case Grip::START:
285 : case Grip::END:
286 0 : ups(g).off += ed.delta;
287 : //
288 : // move anchor for slurs/ties
289 : //
290 0 : if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) {
291 0 : Slur* slr = slur();
292 0 : KeyboardModifiers km = ed.modifiers;
293 0 : EngravingItem* e = ed.view()->elementNear(ed.pos);
294 0 : if (e && e->isNote()) {
295 0 : Note* note = toNote(e);
296 0 : Fraction tick = note->chord()->tick();
297 0 : if ((g == Grip::END && tick > slr->tick()) || (g == Grip::START && tick < slr->tick2())) {
298 0 : if (km != (ShiftModifier | ControlModifier)) {
299 0 : Chord* c = note->chord();
300 0 : ed.view()->setDropTarget(note);
301 0 : if (c->part() == slr->part() && c != slr->endCR()) {
302 0 : changeAnchor(ed, c);
303 : }
304 : }
305 : }
306 : } else {
307 0 : ed.view()->setDropTarget(0);
308 : }
309 : }
310 0 : renderer()->computeBezier(this);
311 0 : break;
312 0 : case Grip::BEZIER1:
313 : case Grip::BEZIER2:
314 0 : ups(g).off += ed.delta;
315 0 : renderer()->computeBezier(this);
316 0 : break;
317 0 : case Grip::SHOULDER:
318 0 : ups(g).off = PointF();
319 0 : renderer()->computeBezier(this, ed.delta);
320 0 : break;
321 0 : case Grip::DRAG:
322 0 : ups(Grip::DRAG).off = PointF();
323 0 : roffset() += ed.delta;
324 0 : break;
325 0 : default:
326 0 : UNREACHABLE;
327 : return;
328 : }
329 :
330 0 : triggerLayout();
331 0 : }
332 :
333 : //---------------------------------------------------------
334 : // isEdited
335 : //---------------------------------------------------------
336 :
337 0 : bool SlurSegment::isEdited() const
338 : {
339 0 : for (int i = 0; i < int(Grip::GRIPS); ++i) {
340 0 : if (!m_ups[i].off.isNull()) {
341 0 : return true;
342 : }
343 : }
344 0 : return false;
345 : }
346 :
347 2539 : bool SlurSegment::isEndPointsEdited() const
348 : {
349 2539 : return !(m_ups[int(Grip::START)].off.isNull() && m_ups[int(Grip::END)].off.isNull());
350 : }
351 :
352 924 : double SlurSegment::endWidth() const
353 : {
354 924 : return style().styleAbsolute(Sid::slurEndWidth);
355 : }
356 :
357 924 : double SlurSegment::midWidth() const
358 : {
359 924 : return style().styleAbsolute(Sid::slurMidWidth);
360 : }
361 :
362 29 : double SlurSegment::dottedWidth() const
363 : {
364 29 : return style().styleAbsolute(Sid::slurDottedWidth);
365 : }
366 :
367 0 : Color SlurSegment::curColor(const rendering::PaintOptions& opt) const
368 : {
369 0 : return EngravingItem::curColor(getProperty(Pid::VISIBLE).toBool(), getProperty(Pid::COLOR).value<Color>(), opt);
370 : }
371 :
372 39 : Slur::Slur(const Slur& s)
373 39 : : SlurTie(s)
374 : {
375 39 : _connectedElement = s._connectedElement;
376 39 : _partialSpannerDirection = s._partialSpannerDirection;
377 39 : }
378 :
379 : //---------------------------------------------------------
380 : // Slur
381 : //---------------------------------------------------------
382 :
383 528 : Slur::Slur(DummyParentOr<EngravingItem> parent, ElementType type)
384 528 : : SlurTie(type, parent)
385 : {
386 528 : }
387 :
388 953 : double Slur::scalingFactor() const
389 : {
390 953 : Chord* startC = startElement() && startElement()->isChord() ? toChord(startElement()) : nullptr;
391 953 : Chord* endC = endElement() && endElement()->isChord() ? toChord(endElement()) : nullptr;
392 :
393 953 : if (!startC || !endC) {
394 13 : return 1.0;
395 : }
396 :
397 987 : if ((startC->isGraceBefore() && startC->parent() == endC)
398 987 : || (endC->isGraceAfter() && endC->parent() == startC)) {
399 34 : return style().styleD(Sid::graceNoteMag);
400 : }
401 :
402 906 : if (startC->isGrace()) {
403 29 : startC = toChord(startC->parent());
404 : }
405 906 : if (endC->isGrace()) {
406 28 : endC = toChord(endC->parent());
407 : }
408 :
409 906 : return 0.5 * (startC->intrinsicMag() + endC->intrinsicMag());
410 : }
411 :
412 0 : void Slur::undoSetIncoming(bool incoming)
413 : {
414 0 : if (incoming == isIncoming()) {
415 0 : return;
416 : }
417 :
418 0 : undoChangeProperty(Pid::PARTIAL_SPANNER_DIRECTION, calcIncomingDirection(incoming), PropertyFlags::UNSTYLED);
419 : }
420 :
421 0 : void Slur::undoSetOutgoing(bool outgoing)
422 : {
423 0 : if (outgoing == isOutgoing()) {
424 0 : return;
425 : }
426 :
427 0 : undoChangeProperty(Pid::PARTIAL_SPANNER_DIRECTION, calcOutgoingDirection(outgoing), PropertyFlags::UNSTYLED);
428 : }
429 :
430 0 : void Slur::setIncoming(bool incoming)
431 : {
432 0 : if (incoming == isIncoming()) {
433 0 : return;
434 : }
435 :
436 0 : _partialSpannerDirection = calcIncomingDirection(incoming);
437 : }
438 :
439 1 : void Slur::setOutgoing(bool outgoing)
440 : {
441 1 : if (outgoing == isOutgoing()) {
442 0 : return;
443 : }
444 :
445 1 : _partialSpannerDirection = calcOutgoingDirection(outgoing);
446 : }
447 :
448 0 : PartialSpannerDirection Slur::calcIncomingDirection(bool incoming)
449 : {
450 0 : PartialSpannerDirection dir = PartialSpannerDirection::INCOMING;
451 0 : if (incoming) {
452 0 : SlurSegment* firstSeg = nsegments() > 0 ? frontSegment() : nullptr;
453 0 : if (firstSeg) {
454 0 : firstSeg->setSlurOffset(Grip::START, PointF(0, 0));
455 : }
456 0 : dir = _partialSpannerDirection
457 0 : == PartialSpannerDirection::OUTGOING ? PartialSpannerDirection::BOTH : PartialSpannerDirection::INCOMING;
458 : } else {
459 0 : dir = _partialSpannerDirection == PartialSpannerDirection::BOTH ? PartialSpannerDirection::OUTGOING : PartialSpannerDirection::NONE;
460 : }
461 0 : return dir;
462 : }
463 :
464 1 : PartialSpannerDirection Slur::calcOutgoingDirection(bool outgoing)
465 : {
466 1 : PartialSpannerDirection dir = PartialSpannerDirection::OUTGOING;
467 1 : if (outgoing) {
468 0 : SlurSegment* lastSeg = nsegments() > 0 ? backSegment() : nullptr;
469 0 : if (lastSeg) {
470 0 : lastSeg->setSlurOffset(Grip::END, PointF(0, 0));
471 : }
472 0 : dir = _partialSpannerDirection
473 0 : == PartialSpannerDirection::INCOMING ? PartialSpannerDirection::BOTH : PartialSpannerDirection::OUTGOING;
474 : } else {
475 1 : dir = _partialSpannerDirection == PartialSpannerDirection::BOTH ? PartialSpannerDirection::INCOMING : PartialSpannerDirection::NONE;
476 : }
477 1 : return dir;
478 : }
479 :
480 1311 : bool Slur::isIncoming() const
481 : {
482 1311 : return _partialSpannerDirection == PartialSpannerDirection::BOTH || _partialSpannerDirection == PartialSpannerDirection::INCOMING;
483 : }
484 :
485 1289 : bool Slur::isOutgoing() const
486 : {
487 1289 : return _partialSpannerDirection == PartialSpannerDirection::BOTH || _partialSpannerDirection == PartialSpannerDirection::OUTGOING;
488 : }
489 :
490 0 : void Slur::undoChangeStartEndElements(ChordRest* scr, ChordRest* ecr)
491 : {
492 0 : for (EngravingObject* lsp : linkList()) {
493 0 : Spanner* sp = toSpanner(lsp);
494 0 : if (sp == this) {
495 0 : score()->undo(new ChangeSpannerElements(this, scr, ecr));
496 : } else {
497 0 : EngravingItem* se = 0;
498 0 : EngravingItem* ee = 0;
499 0 : if (scr) {
500 0 : std::list<EngravingObject*> sel = scr->linkList();
501 0 : for (EngravingObject* lcr : sel) {
502 0 : EngravingItem* le = toEngravingItem(lcr);
503 0 : if (le->score() == sp->score() && le->track() == sp->track()) {
504 0 : se = le;
505 0 : break;
506 : }
507 : }
508 0 : }
509 0 : if (ecr) {
510 0 : std::list<EngravingObject*> sel = ecr->linkList();
511 0 : for (EngravingObject* lcr : sel) {
512 0 : EngravingItem* le = toEngravingItem(lcr);
513 0 : if (le->score() == sp->score() && le->track() == sp->track2()) {
514 0 : ee = le;
515 0 : break;
516 : }
517 : }
518 0 : }
519 0 : if (se && ee) {
520 0 : score()->undo(new ChangeStartEndSpanner(sp, se, ee));
521 0 : renderer()->layoutItem(sp);
522 : }
523 : }
524 0 : }
525 0 : }
526 :
527 : //---------------------------------------------------------
528 : // setTrack
529 : //---------------------------------------------------------
530 :
531 954 : void Slur::setTrack(track_idx_t n)
532 : {
533 954 : EngravingItem::setTrack(n);
534 1082 : for (SpannerSegment* ss : spannerSegments()) {
535 128 : ss->setTrack(n);
536 : }
537 954 : }
538 :
539 6650 : bool Slur::isCrossStaff()
540 : {
541 13300 : return startCR() && endCR()
542 19786 : && (startCR()->staffMove() != 0 || endCR()->staffMove() != 0
543 13136 : || startCR()->vStaffIdx() != endCR()->vStaffIdx());
544 : }
545 :
546 1751 : bool Slur::hasCrossBeams()
547 : {
548 1751 : return (startCR() && startCR()->beam() && startCR()->beam()->cross()) || (endCR() && endCR()->beam() && endCR()->beam()->cross());
549 : }
550 :
551 5708 : PropertyValue Slur::getProperty(Pid propertyId) const
552 : {
553 5708 : switch (propertyId) {
554 303 : case Pid::PARTIAL_SPANNER_DIRECTION:
555 303 : return partialSpannerDirection();
556 5405 : default:
557 5405 : return SlurTie::getProperty(propertyId);
558 : }
559 : }
560 :
561 4630 : PropertyValue Slur::propertyDefault(Pid id) const
562 : {
563 4630 : switch (id) {
564 303 : case Pid::PARTIAL_SPANNER_DIRECTION:
565 606 : return PartialSpannerDirection::NONE;
566 4327 : default:
567 4327 : return SlurTie::propertyDefault(id);
568 : }
569 : }
570 :
571 184 : bool Slur::setProperty(Pid propertyId, const PropertyValue& v)
572 : {
573 184 : switch (propertyId) {
574 11 : case Pid::PARTIAL_SPANNER_DIRECTION:
575 11 : setPartialSpannerDirection(v.value<PartialSpannerDirection>());
576 11 : break;
577 173 : default:
578 173 : return SlurTie::setProperty(propertyId, v);
579 : }
580 11 : triggerLayout();
581 11 : return true;
582 : }
583 : }
|