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 "tie.h"
23 :
24 : #include "../editing/mscoreview.h"
25 : #include "../rendering/iscorerenderer.h"
26 :
27 : #include "accidental.h"
28 : #include "barline.h"
29 : #include "chord.h"
30 : #include "factory.h"
31 : #include "hook.h"
32 : #include "ledgerline.h"
33 : #include "measure.h"
34 : #include "note.h"
35 : #include "notedot.h"
36 : #include "part.h"
37 : #include "partialtie.h"
38 : #include "repeatlist.h"
39 : #include "score.h"
40 : #include "staff.h"
41 : #include "stafftype.h"
42 : #include "stem.h"
43 : #include "system.h"
44 : #include "tiejumppointlist.h"
45 : #include "utils.h"
46 : #include "volta.h"
47 :
48 : #include "log.h"
49 :
50 : using namespace mu;
51 : using namespace muse::draw;
52 :
53 : namespace mu::engraving {
54 : //---------------------------------------------------------
55 : // TieSegment
56 : //---------------------------------------------------------
57 :
58 1907 : TieSegment::TieSegment(Tie* sp)
59 1907 : : SlurTieSegment(ElementType::TIE_SEGMENT, sp)
60 : {
61 1907 : }
62 :
63 80 : TieSegment::TieSegment(const ElementType& type, Tie* sp)
64 80 : : SlurTieSegment(type, sp)
65 : {
66 80 : }
67 :
68 447 : TieSegment::TieSegment(const TieSegment& s)
69 447 : : SlurTieSegment(s)
70 : {
71 447 : }
72 :
73 0 : void TieSegment::changeAnchor(EditData& ed, EngravingItem* element)
74 : {
75 0 : if (ed.curGrip == Grip::START) {
76 0 : spanner()->setStartElement(element);
77 0 : Note* note = toNote(element);
78 0 : if (note->chord()->tick() <= tie()->endNote()->chord()->tick()) {
79 0 : tie()->startNote()->setTieFor(0);
80 0 : tie()->setStartNote(note);
81 0 : note->setTieFor(tie());
82 : }
83 : } else {
84 0 : spanner()->setEndElement(element);
85 0 : Note* note = toNote(element);
86 : // do not allow backward ties
87 0 : if (note->chord()->tick() >= tie()->startNote()->chord()->tick()) {
88 0 : tie()->endNote()->setTieBack(0);
89 0 : tie()->setEndNote(note);
90 0 : note->setTieBack(tie());
91 : }
92 : }
93 :
94 0 : const size_t segments = spanner()->spannerSegments().size();
95 0 : ups(ed.curGrip).off = PointF();
96 0 : renderer()->layoutItem(spanner());
97 0 : if (spanner()->spannerSegments().size() != segments) {
98 0 : const std::vector<SpannerSegment*>& ss = spanner()->spannerSegments();
99 :
100 0 : TieSegment* newSegment = toTieSegment(ed.curGrip == Grip::END ? ss.back() : ss.front());
101 0 : ed.view()->changeEditElement(newSegment);
102 0 : triggerLayout();
103 : }
104 0 : }
105 :
106 0 : RectF TieSegment::drag(EditData& ed)
107 : {
108 0 : consolidateAdjustmentOffsetIntoUserOffset();
109 0 : return SlurTieSegment::drag(ed);
110 : }
111 :
112 0 : void TieSegment::dragGrip(EditData& ed)
113 : {
114 0 : consolidateAdjustmentOffsetIntoUserOffset();
115 0 : Grip g = ed.curGrip;
116 :
117 0 : switch (g) {
118 0 : case Grip::START:
119 : case Grip::END:
120 0 : ups(g).off += ed.delta;
121 0 : if (!isPartialTieSegment()) {
122 : //
123 : // move anchor for slurs/ties
124 : //
125 0 : if ((g == Grip::START && isSingleBeginType())
126 0 : || (g == Grip::END && isSingleEndType())) {
127 0 : Spanner* spanner = tie();
128 0 : EngravingItem* e = ed.view()->elementNear(ed.pos);
129 0 : Note* note = (e && e->isNote()) ? toNote(e) : nullptr;
130 0 : if (note && ((g == Grip::END && note->tick() > tie()->tick())
131 0 : || (g == Grip::START && note->tick() < tie()->tick2()))) {
132 0 : if (g == Grip::END) {
133 0 : Tie* tie = toTie(spanner);
134 0 : if (tie->startNote()->pitch() == note->pitch()
135 0 : && tie->startNote()->chord()->tick() < note->chord()->tick()) {
136 0 : ed.view()->setDropTarget(note);
137 0 : if (note != tie->endNote()) {
138 0 : changeAnchor(ed, note);
139 : }
140 : }
141 : }
142 : } else {
143 0 : ed.view()->setDropTarget(0);
144 : }
145 : }
146 : }
147 0 : renderer()->computeBezier(this);
148 0 : break;
149 0 : case Grip::BEZIER1:
150 : case Grip::BEZIER2:
151 0 : ups(g).off += ed.delta;
152 0 : renderer()->computeBezier(this);
153 0 : break;
154 0 : case Grip::SHOULDER:
155 0 : ups(g).off = PointF();
156 0 : ups(Grip::BEZIER1).off += ed.delta;
157 0 : ups(Grip::BEZIER2).off += ed.delta;
158 0 : renderer()->computeBezier(this);
159 0 : break;
160 0 : case Grip::DRAG:
161 0 : ups(Grip::DRAG).off = PointF();
162 0 : roffset() += ed.delta;
163 0 : break;
164 0 : default:
165 0 : UNREACHABLE;
166 : return;
167 : }
168 :
169 0 : triggerLayout();
170 0 : }
171 :
172 0 : void TieSegment::consolidateAdjustmentOffsetIntoUserOffset()
173 : {
174 0 : for (size_t i = 0; i < m_adjustmentOffsets.size(); ++i) {
175 0 : Grip grip = static_cast<Grip>(i);
176 0 : PointF adjustOffset = m_adjustmentOffsets[i];
177 0 : if (!adjustOffset.isNull()) {
178 0 : ups(grip).p -= adjustOffset;
179 0 : ups(grip).off = adjustOffset;
180 : }
181 : }
182 0 : resetAdjustmentOffset();
183 0 : }
184 :
185 10702 : bool TieSegment::isEdited() const
186 : {
187 74914 : for (int i = 0; i < int(Grip::GRIPS); ++i) {
188 64212 : if (!m_ups[i].off.isNull()) {
189 0 : return true;
190 : }
191 : }
192 10702 : return false;
193 : }
194 :
195 5170 : double TieSegment::minShoulderHeight() const
196 : {
197 5170 : return style().styleAbsolute(Sid::tieMinShoulderHeight);
198 : }
199 :
200 5170 : double TieSegment::maxShoulderHeight() const
201 : {
202 5170 : return style().styleAbsolute(Sid::tieMaxShoulderHeight);
203 : }
204 :
205 5142 : double TieSegment::endWidth() const
206 : {
207 5142 : return style().styleAbsolute(Sid::tieEndWidth);
208 : }
209 :
210 5142 : double TieSegment::midWidth() const
211 : {
212 5142 : return style().styleAbsolute(Sid::tieMidWidth);
213 : }
214 :
215 28 : double TieSegment::dottedWidth() const
216 : {
217 28 : return style().styleAbsolute(Sid::tieDottedWidth);
218 : }
219 :
220 : //---------------------------------------------------------
221 : // Tie
222 : //---------------------------------------------------------
223 :
224 84 : Tie::Tie(const ElementType& type, EngravingObject* parent)
225 84 : : SlurTie(type, parent)
226 : {
227 84 : }
228 :
229 15800 : TieJumpPointList* Tie::startTieJumpPoints() const
230 : {
231 15800 : return m_jumpPoint ? m_jumpPoint->jumpPointList() : nullptr;
232 : }
233 :
234 16071 : void Tie::updatePossibleJumpPoints()
235 : {
236 16071 : if (!tieJumpPoints()) {
237 120 : return;
238 : }
239 :
240 15951 : tieJumpPoints()->clear();
241 :
242 15951 : const Note* note = toNote(ownershipParent());
243 15951 : const Chord* chord = note ? note->chord() : nullptr;
244 15951 : const Measure* measure = chord ? chord->measure() : nullptr;
245 15951 : if (!measure) {
246 30 : return;
247 : }
248 :
249 15921 : const Segment* segment = chord ? chord->segment() : nullptr;
250 :
251 : // Check ties starting in this measure and ending in another
252 : // If they cross a repeat, add jump points
253 15921 : const bool hasFollowingJumpItem = chord->hasFollowingJumpItem();
254 :
255 15921 : if (!hasFollowingJumpItem) {
256 15818 : const Note* tieEndNote = endNote();
257 15818 : const Chord* endChord = tieEndNote ? tieEndNote->chord() : nullptr;
258 15818 : if (!endChord) {
259 1404 : return;
260 : }
261 14414 : const Segment* endNoteSegment = endChord ? endChord->segment() : nullptr;
262 14414 : const ChordRest* finalCROfMeasure = measure->lastChordRest(track());
263 14414 : const bool finalCRHasFollowingJump = finalCROfMeasure ? finalCROfMeasure->hasFollowingJumpItem() : false;
264 14414 : const bool segsAreAdjacent = segmentsAreAdjacent(segment, endNoteSegment);
265 14414 : const bool segsAreInDifferentRepeatSegments = segmentsAreInDifferentRepeatSegments(segment, endNoteSegment);
266 :
267 14414 : if (!(finalCRHasFollowingJump && segsAreAdjacent) || !segsAreInDifferentRepeatSegments) {
268 14404 : return;
269 : }
270 : }
271 :
272 113 : int jumpPointIdx = 0;
273 :
274 113 : Note* nextNote = searchTieNote(note);
275 113 : nextNote = nextNote ? nextNote : endNote();
276 :
277 113 : if (nextNote) {
278 59 : const bool hasTie = nextNote->tieBack();
279 59 : TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasTie, jumpPointIdx, true);
280 59 : tieJumpPoints()->add(jumpPoint);
281 59 : jumpPointIdx++;
282 : }
283 :
284 242 : for (Measure* jumpMeasure : findFollowingRepeatMeasures(measure)) {
285 129 : const Segment* firstCrSeg = jumpMeasure ? jumpMeasure->first(SegmentType::ChordRest) : nullptr;
286 129 : if (!firstCrSeg) {
287 0 : continue;
288 : }
289 :
290 129 : nextNote = searchTieNote(note, firstCrSeg, false);
291 :
292 129 : if (nextNote) {
293 124 : bool hasIncomingTie = nextNote->tieBack();
294 124 : TieJumpPoint* jumpPoint = new TieJumpPoint(nextNote, hasIncomingTie, jumpPointIdx, false);
295 124 : tieJumpPoints()->add(jumpPoint);
296 124 : jumpPointIdx++;
297 : }
298 113 : }
299 :
300 113 : if (jumpPointIdx < 2 && !isPartialTie()) {
301 6 : tieJumpPoints()->clear();
302 : }
303 : }
304 :
305 11 : void Tie::addTiesToJumpPoints()
306 : {
307 11 : updatePossibleJumpPoints();
308 11 : TieJumpPointList* jumpPoints = tieJumpPoints();
309 11 : if (!jumpPoints) {
310 0 : return;
311 : }
312 :
313 27 : for (TieJumpPoint* jumpPoint : *jumpPoints) {
314 16 : if (jumpPoint->followingNote()) {
315 6 : jumpPoint->undoSetActive(true);
316 6 : continue;
317 : }
318 10 : jumpPoints->undoAddTieToScore(jumpPoint);
319 : }
320 :
321 : // Update jump points for linked ties
322 22 : for (EngravingObject* linkedTie : linkList()) {
323 11 : if (!linkedTie || !linkedTie->isTie() || linkedTie == this) {
324 11 : continue;
325 : }
326 0 : toTie(linkedTie)->updatePossibleJumpPoints();
327 11 : }
328 : }
329 :
330 6 : void Tie::undoRemoveTiesFromJumpPoints()
331 : {
332 6 : TieJumpPointList* jumpPoints = tieJumpPoints();
333 6 : if (!jumpPoints) {
334 0 : return;
335 : }
336 18 : for (TieJumpPoint* jumpPoint : *jumpPoints) {
337 12 : if (jumpPoint->followingNote() || !jumpPoint->active()) {
338 4 : jumpPoint->undoSetActive(false);
339 4 : continue;
340 : }
341 :
342 8 : jumpPoints->undoRemoveTieFromScore(jumpPoint);
343 : }
344 : }
345 :
346 4494 : bool Tie::allJumpPointsInactive() const
347 : {
348 4494 : if (endNote()) {
349 4356 : return false;
350 : }
351 138 : if (!tieJumpPoints()) {
352 0 : return true;
353 : }
354 :
355 153 : for (const TieJumpPoint* jumpPoint : *tieJumpPoints()) {
356 148 : if (jumpPoint->active()) {
357 133 : return false;
358 : }
359 : }
360 :
361 5 : return true;
362 : }
363 :
364 34750 : TieJumpPointList* Tie::tieJumpPoints()
365 : {
366 34750 : return startNote() ? startNote()->tieJumpPoints() : nullptr;
367 : }
368 :
369 276 : const TieJumpPointList* Tie::tieJumpPoints() const
370 : {
371 276 : return startNote() ? startNote()->tieJumpPoints() : nullptr;
372 : }
373 :
374 1746 : Tie::Tie(EngravingObject* parent)
375 1746 : : SlurTie(ElementType::TIE, parent)
376 : {
377 1746 : }
378 :
379 467 : Tie::Tie(const Tie& t)
380 467 : : SlurTie(t)
381 : {
382 467 : m_isInside = t.m_isInside;
383 467 : m_tiePlacement = t.m_tiePlacement;
384 : // Jump points must be recalculated for this tie
385 467 : m_jumpPoint = nullptr;
386 467 : }
387 :
388 11728 : PropertyValue Tie::getProperty(Pid propertyId) const
389 : {
390 11728 : switch (propertyId) {
391 663 : case Pid::TIE_PLACEMENT:
392 663 : return tiePlacement();
393 11065 : default:
394 11065 : return SlurTie::getProperty(propertyId);
395 : }
396 : }
397 :
398 9282 : PropertyValue Tie::propertyDefault(Pid id) const
399 : {
400 9282 : switch (id) {
401 663 : case Pid::TIE_PLACEMENT:
402 663 : return TiePlacement::AUTO;
403 8619 : default:
404 8619 : return SlurTie::propertyDefault(id);
405 : }
406 : }
407 :
408 23 : bool Tie::setProperty(Pid propertyId, const PropertyValue& v)
409 : {
410 23 : switch (propertyId) {
411 0 : case Pid::TIE_PLACEMENT:
412 0 : setTiePlacement(v.value<TiePlacement>());
413 0 : break;
414 23 : default:
415 23 : return SlurTie::setProperty(propertyId, v);
416 : }
417 0 : triggerLayout();
418 0 : return true;
419 : }
420 :
421 5170 : double Tie::scalingFactor() const
422 : {
423 5170 : const bool hasBothNotes = startNote() && endNote();
424 :
425 5170 : const Note* primaryNote = startNote() ? startNote() : endNote();
426 5170 : const Note* secondaryNote = hasBothNotes ? endNote() : nullptr;
427 :
428 5170 : if (!primaryNote) {
429 0 : return 1.0;
430 : }
431 :
432 5170 : if (primaryNote->isGrace()) {
433 25 : return style().styleD(Sid::graceNoteMag);
434 : }
435 :
436 5145 : if (hasBothNotes) {
437 4865 : return 0.5 * (primaryNote->chord()->intrinsicMag() + secondaryNote->chord()->intrinsicMag());
438 : }
439 :
440 280 : return primaryNote->chord()->intrinsicMag();
441 : }
442 :
443 2926 : void Tie::setStartNote(Note* note)
444 : {
445 2926 : setStartElement(note);
446 2926 : setOwnershipParent(note);
447 2926 : }
448 :
449 197024 : Note* Tie::startNote() const
450 : {
451 197024 : assert(!startElement() || startElement()->isNote());
452 197024 : return toNote(startElement());
453 : }
454 :
455 120073 : Note* Tie::endNote() const
456 : {
457 120073 : return toNote(endElement());
458 : }
459 :
460 12854 : bool Tie::isOuterTieOfChord(Grip startOrEnd) const
461 : {
462 12854 : if (m_isInside) {
463 0 : return false;
464 : }
465 :
466 12854 : const bool start = startOrEnd == Grip::START;
467 12854 : const Note* note = start ? startNote() : endNote();
468 12854 : if (!note) {
469 9 : return false;
470 : }
471 :
472 12845 : const Chord* chord = note->chord();
473 :
474 12845 : return (note == chord->upNote() && up()) || (note == chord->downNote() && !up());
475 : }
476 :
477 2354 : bool Tie::hasTiedSecondInside() const
478 : {
479 2354 : const Note* note = startNote();
480 2354 : if (!note) {
481 7 : return false;
482 : }
483 :
484 2347 : const Chord* chord = note->chord();
485 2347 : const int line = note->line();
486 2347 : const int secondInsideLine = up() ? line + 1 : line - 1;
487 :
488 5751 : for (const Note* otherNote : chord->notes()) {
489 3404 : if (otherNote->line() == secondInsideLine && otherNote->tieFor() && otherNote->tieFor()->up() == up()) {
490 0 : return true;
491 : }
492 : }
493 :
494 2347 : return false;
495 : }
496 :
497 2331 : bool Tie::isCrossStaff() const
498 : {
499 2331 : const Note* startN = startNote();
500 2331 : const Note* endN = endNote();
501 2331 : const Chord* startChord = startN ? startN->chord() : nullptr;
502 2331 : const Chord* endChord = endN ? endN->chord() : nullptr;
503 2331 : const staff_idx_t staff = staffIdx();
504 :
505 2331 : return (startChord && (startChord->staffMove() != 0 || startChord->vStaffIdx() != staff))
506 4662 : || (endChord && (endChord->staffMove() != 0 || endChord->vStaffIdx() != staff));
507 : }
508 :
509 5 : void Tie::changeTieType(Tie* oldTie, Note* endNote)
510 : {
511 : // Replaces oldTie with an outgoing partial tie if no endNote is specified. Otherwise replaces oldTie with a regular tie
512 5 : Note* startNote = oldTie->startNote();
513 5 : bool addPartialTie = !endNote;
514 5 : Score* score = startNote ? startNote->score() : nullptr;
515 5 : if (!score) {
516 0 : return;
517 : }
518 :
519 5 : Tie* newTie = addPartialTie ? Factory::createPartialTie(startNote) : Factory::createTie(startNote);
520 :
521 5 : score->undoRemoveElement(oldTie);
522 :
523 5 : newTie->setStartNote(startNote);
524 5 : newTie->setTick(startNote->tick());
525 5 : newTie->setTrack(startNote->track());
526 5 : startNote->setTieFor(newTie);
527 5 : if (!addPartialTie) {
528 0 : newTie->setEndNote(endNote);
529 0 : endNote->setTieBack(newTie);
530 : }
531 :
532 5 : newTie->setStyleType(oldTie->styleType());
533 5 : newTie->setTiePlacement(oldTie->tiePlacement());
534 5 : newTie->setSlurDirection(oldTie->slurDirection());
535 :
536 5 : newTie->setVisible(oldTie->visible());
537 5 : newTie->setOffset(oldTie->offset());
538 :
539 5 : score->undoAddElement(newTie);
540 : }
541 :
542 8 : void Tie::updateStartTieOnRemoval()
543 : {
544 8 : if (!jumpPoint() || !startTie() || !startTieJumpPoints()) {
545 0 : return;
546 : }
547 8 : jumpPoint()->undoSetActive(false);
548 8 : Tie* _startTie = startTie();
549 8 : if (startTieJumpPoints()->size() <= 1 || _startTie->allJumpPointsInactive()) {
550 0 : score()->undoRemoveElement(_startTie);
551 : }
552 : }
553 :
554 7998 : Tie* Tie::startTie() const
555 : {
556 7998 : return startTieJumpPoints() ? startTieJumpPoints()->startTie() : nullptr;
557 : }
558 : }
|