1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2010-2011 Werner Schweer
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2
9 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "connector.h"
14 #include "score.h"
15 #include "spanner.h"
16 #include "system.h"
17 #include "chordrest.h"
18 #include "chord.h"
19 #include "segment.h"
20 #include "measure.h"
21 #include "undo.h"
22 #include "staff.h"
23 #include "lyrics.h"
24 #include "musescoreCore.h"
25
26 namespace Ms {
27
28 //-----------------------------------------------------------------------------
29 // @@ SpannerWriter
30 /// Helper class for writing Spanners
31 //-----------------------------------------------------------------------------
32
33 class SpannerWriter : public ConnectorInfoWriter {
34 protected:
tagName() const35 const char* tagName() const override { return "Spanner"; }
36 public:
37 SpannerWriter(XmlWriter& xml, const Element* current, const Spanner* spanner, int track, Fraction frac, bool start);
38
39 static void fillSpannerPosition(Location& l, const MeasureBase* endpoint, const Fraction& tick, bool clipboardmode);
40 };
41
42 //---------------------------------------------------------
43 // SpannerSegment
44 //---------------------------------------------------------
45
SpannerSegment(Spanner * sp,Score * s,ElementFlags f)46 SpannerSegment::SpannerSegment(Spanner* sp, Score* s, ElementFlags f)
47 : Element(s, f)
48 {
49 _spanner = sp;
50 setSpannerSegmentType(SpannerSegmentType::SINGLE);
51 }
52
SpannerSegment(Score * s,ElementFlags f)53 SpannerSegment::SpannerSegment(Score* s, ElementFlags f)
54 : Element(s, f)
55 {
56 setSpannerSegmentType(SpannerSegmentType::SINGLE);
57 _spanner = 0;
58 }
59
SpannerSegment(const SpannerSegment & s)60 SpannerSegment::SpannerSegment(const SpannerSegment& s)
61 : Element(s)
62 {
63 _spanner = s._spanner;
64 _spannerSegmentType = s._spannerSegmentType;
65 _p2 = s._p2;
66 _offset2 = s._offset2;
67 }
68
69 //---------------------------------------------------------
70 // mag
71 //---------------------------------------------------------
72
mag() const73 qreal SpannerSegment::mag() const
74 {
75 if (spanner()->systemFlag())
76 return 1.0;
77 return staff() ? staff()->mag(spanner()->tick()) : 1.0;
78 }
79
tick() const80 Fraction SpannerSegment::tick() const
81 {
82 return _spanner ? _spanner->tick() : Fraction(0, 1);
83 }
84
85 //---------------------------------------------------------
86 // setSystem
87 //---------------------------------------------------------
88
setSystem(System * s)89 void SpannerSegment::setSystem(System* s)
90 {
91 if (system() != s) {
92 if (system())
93 system()->remove(this);
94 if (s)
95 s->add(this);
96 else
97 setParent(0);
98 }
99 }
100
101 //---------------------------------------------------------
102 // spatiumChanged
103 //---------------------------------------------------------
104
spatiumChanged(qreal ov,qreal nv)105 void SpannerSegment::spatiumChanged(qreal ov, qreal nv)
106 {
107 Element::spatiumChanged(ov, nv);
108 if (offsetIsSpatiumDependent())
109 _offset2 *= (nv / ov);
110 }
111
112 //---------------------------------------------------------
113 // mimeData
114 //---------------------------------------------------------
115
mimeData(const QPointF & dragOffset) const116 QByteArray SpannerSegment::mimeData(const QPointF& dragOffset) const
117 {
118 if (dragOffset.isNull()) // where is dragOffset used?
119 return spanner()->mimeData(dragOffset);
120 return Element::mimeData(dragOffset);
121 }
122
123 //---------------------------------------------------------
124 // propertyDelegate
125 //---------------------------------------------------------
126
propertyDelegate(Pid pid)127 Element* SpannerSegment::propertyDelegate(Pid pid)
128 {
129 if (pid == Pid::COLOR || pid == Pid::VISIBLE || pid == Pid::PLACEMENT)
130 return spanner();
131 return 0;
132 }
133
134 //---------------------------------------------------------
135 // getProperty
136 //---------------------------------------------------------
137
getProperty(Pid pid) const138 QVariant SpannerSegment::getProperty(Pid pid) const
139 {
140 if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
141 return e->getProperty(pid);
142 switch (pid) {
143 case Pid::OFFSET2:
144 return _offset2;
145 default:
146 return Element::getProperty(pid);
147 }
148 }
149
150 //---------------------------------------------------------
151 // setProperty
152 //---------------------------------------------------------
153
setProperty(Pid pid,const QVariant & v)154 bool SpannerSegment::setProperty(Pid pid, const QVariant& v)
155 {
156 if (Element* e = propertyDelegate(pid))
157 return e->setProperty(pid, v);
158 switch (pid) {
159 case Pid::OFFSET2:
160 _offset2 = v.toPointF();
161 triggerLayoutAll();
162 break;
163 default:
164 return Element::setProperty(pid, v);
165 }
166 return true;
167 }
168
169 //---------------------------------------------------------
170 // propertyDefault
171 //---------------------------------------------------------
172
propertyDefault(Pid pid) const173 QVariant SpannerSegment::propertyDefault(Pid pid) const
174 {
175 if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
176 return e->propertyDefault(pid);
177 switch (pid) {
178 case Pid::OFFSET2:
179 return QVariant();
180 default:
181 return Element::propertyDefault(pid);
182 }
183 }
184
185 //---------------------------------------------------------
186 // getPropertyStyle
187 //---------------------------------------------------------
188
getPropertyStyle(Pid pid) const189 Sid SpannerSegment::getPropertyStyle(Pid pid) const
190 {
191 if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
192 return e->getPropertyStyle(pid);
193 return Element::getPropertyStyle(pid);
194 }
195
196 //---------------------------------------------------------
197 // propertyFlags
198 //---------------------------------------------------------
199
propertyFlags(Pid pid) const200 PropertyFlags SpannerSegment::propertyFlags(Pid pid) const
201 {
202 if (Element* e = const_cast<SpannerSegment*>(this)->propertyDelegate(pid))
203 return e->propertyFlags(pid);
204 return Element::propertyFlags(pid);
205 }
206
207 //---------------------------------------------------------
208 // resetProperty
209 //---------------------------------------------------------
210
resetProperty(Pid pid)211 void SpannerSegment::resetProperty(Pid pid)
212 {
213 if (Element* e = propertyDelegate(pid))
214 return e->resetProperty(pid);
215 return Element::resetProperty(pid);
216 }
217
218 //---------------------------------------------------------
219 // styleChanged
220 //---------------------------------------------------------
221
styleChanged()222 void SpannerSegment::styleChanged()
223 {
224 spanner()->styleChanged();
225 }
226
227 //---------------------------------------------------------
228 // reset
229 //---------------------------------------------------------
230
reset()231 void SpannerSegment::reset()
232 {
233 undoChangeProperty(Pid::OFFSET2, QPointF());
234 Element::reset();
235 spanner()->reset();
236 }
237
238 //---------------------------------------------------------
239 // undoChangeProperty
240 //---------------------------------------------------------
241
undoChangeProperty(Pid pid,const QVariant & val,PropertyFlags ps)242 void SpannerSegment::undoChangeProperty(Pid pid, const QVariant& val, PropertyFlags ps)
243 {
244 if (pid == Pid::AUTOPLACE && (val.toBool() == true && !autoplace())) {
245 // Switching autoplacement on. Save user-defined
246 // placement properties to undo stack.
247 undoPushProperty(Pid::OFFSET2);
248 // other will be saved in Element::undoChangeProperty
249 }
250 Element::undoChangeProperty(pid, val, ps);
251 }
252
253 //---------------------------------------------------------
254 // setSelected
255 //---------------------------------------------------------
256
setSelected(bool f)257 void SpannerSegment::setSelected(bool f)
258 {
259 for (SpannerSegment* ss : _spanner->spannerSegments())
260 ss->Element::setSelected(f);
261 _spanner->setSelected(f);
262 }
263
264 //---------------------------------------------------------
265 // setVisible
266 //---------------------------------------------------------
267
setVisible(bool f)268 void SpannerSegment::setVisible(bool f)
269 {
270 if (_spanner) {
271 for (SpannerSegment* ss : _spanner->spannerSegments())
272 ss->Element::setVisible(f);
273 _spanner->setVisible(f);
274 }
275 else
276 Element::setVisible(f);
277 }
278
279 //---------------------------------------------------------
280 // setColor
281 //---------------------------------------------------------
282
setColor(const QColor & col)283 void SpannerSegment::setColor(const QColor& col)
284 {
285 if (_spanner) {
286 for (SpannerSegment* ss : _spanner->spannerSegments())
287 ss->_color = col;
288 _spanner->_color = col;
289 }
290 else
291 _color = col;
292 }
293
294 //---------------------------------------------------------
295 // nextSegmentElement
296 //---------------------------------------------------------
297
nextSegmentElement()298 Element* SpannerSegment::nextSegmentElement()
299 {
300 return spanner()->nextSegmentElement();
301 }
302
303 //---------------------------------------------------------
304 // prevSegmentElement
305 //---------------------------------------------------------
306
prevSegmentElement()307 Element* SpannerSegment::prevSegmentElement()
308 {
309 return spanner()->prevSegmentElement();
310 }
311
312 //---------------------------------------------------------
313 // accessibleInfo
314 //---------------------------------------------------------
315
accessibleInfo() const316 QString SpannerSegment::accessibleInfo() const
317 {
318 return spanner()->accessibleInfo();
319 }
320
321 //---------------------------------------------------------
322 // triggerLayout
323 //---------------------------------------------------------
324
triggerLayout() const325 void SpannerSegment::triggerLayout() const
326 {
327 if (_spanner)
328 _spanner->triggerLayout();
329 }
330
331 //---------------------------------------------------------
332 // Spanner
333 //---------------------------------------------------------
334
Spanner(Score * s,ElementFlags f)335 Spanner::Spanner(Score* s, ElementFlags f)
336 : Element(s, f)
337 {
338 }
339
Spanner(const Spanner & s)340 Spanner::Spanner(const Spanner& s)
341 : Element(s)
342 {
343 _anchor = s._anchor;
344 _startElement = s._startElement;
345 _endElement = s._endElement;
346 _tick = s._tick;
347 _ticks = s._ticks;
348 _track2 = s._track2;
349 }
350
~Spanner()351 Spanner::~Spanner()
352 {
353 qDeleteAll(segments);
354 qDeleteAll(unusedSegments);
355 }
356
357 //---------------------------------------------------------
358 // mag
359 //---------------------------------------------------------
360
mag() const361 qreal Spanner::mag() const
362 {
363 if (systemFlag())
364 return 1.0;
365 return staff() ? staff()->mag(tick()) : 1.0;
366 }
367
368 //---------------------------------------------------------
369 // add
370 //---------------------------------------------------------
371
add(Element * e)372 void Spanner::add(Element* e)
373 {
374 SpannerSegment* ls = toSpannerSegment(e);
375 ls->setSpanner(this);
376 ls->setSelected(selected());
377 ls->setTrack(track());
378 // ls->setAutoplace(autoplace());
379 segments.push_back(ls);
380 }
381
382 //---------------------------------------------------------
383 // remove
384 //---------------------------------------------------------
385
remove(Element * e)386 void Spanner::remove(Element* e)
387 {
388 SpannerSegment* ss = toSpannerSegment(e);
389 if (ss->system())
390 ss->system()->remove(ss);
391 segments.erase(std::remove(segments.begin(), segments.end(), ss), segments.end());
392 }
393
394 //---------------------------------------------------------
395 // removeUnmanaged
396 //
397 // Remove the Spanner and its segments from objects which may know about them
398 //
399 // This method and the following are used for spanners which are contained within compound elements
400 // which manage their parts themselves without using the standard management supplied by Score;
401 // Example can be the LyricsLine within a Lyrics element or the FiguredBassLine within a FiguredBass
402 // (not implemented yet).
403 //---------------------------------------------------------
404
removeUnmanaged()405 void Spanner::removeUnmanaged()
406 {
407 for (SpannerSegment* ss : spannerSegments())
408 if (ss->system()) {
409 // ss->system()->remove(ss);
410 ss->setSystem(nullptr);
411 }
412 score()->removeUnmanagedSpanner(this);
413 }
414
415 //---------------------------------------------------------
416 // insertTimeUnmanaged
417 //---------------------------------------------------------
418
insertTimeUnmanaged(const Fraction & fromTick,const Fraction & len)419 void Spanner::insertTimeUnmanaged(const Fraction& fromTick, const Fraction& len)
420 {
421 Fraction newTick1 = tick();
422 Fraction newTick2 = tick2();
423
424 // check spanner start and end point
425 if (len > Fraction(0,1)) { // adding time
426 if (tick() > fromTick) // start after insertion point: shift start to right
427 newTick1 += len;
428 if (tick2() > fromTick) // end after insertion point: shift end to right
429 newTick2 += len;
430 }
431 if (len < Fraction(0,1)) { // removing time
432 Fraction toTick = fromTick - len;
433 if (tick() > fromTick) { // start after beginning of removed time
434 if (tick() < toTick) { // start within removed time: bring start at removing point
435 if (parent()) {
436 parent()->remove(this);
437 return;
438 }
439 else
440 newTick1 = fromTick;
441 }
442 else // start after removed time: shift start to left
443 newTick1 += len;
444 }
445 if (tick2() > fromTick) { // end after start of removed time
446 if (tick2() < toTick) // end within removed time: bring end at removing point
447 newTick2 = fromTick;
448 else // end after removed time: shift end to left
449 newTick2 += len;
450 }
451 }
452
453 // update properties as required
454 if (newTick2 <= newTick1) { // if no longer any span: remove it
455 if (parent())
456 parent()->remove(this);
457 }
458 else { // if either TICKS or TICK did change, update property
459 if (newTick2-newTick1 != tick2()- tick())
460 setProperty(Pid::SPANNER_TICKS, newTick2-newTick1);
461 if (newTick1 != tick())
462 setProperty(Pid::SPANNER_TICK, newTick1);
463 }
464 }
465
466 //---------------------------------------------------------
467 // scanElements
468 // used in palettes
469 //---------------------------------------------------------
470
scanElements(void * data,void (* func)(void *,Element *),bool all)471 void Spanner::scanElements(void* data, void (*func)(void*, Element*), bool all)
472 {
473 Q_UNUSED(all);
474 for (SpannerSegment* seg : segments)
475 seg->scanElements(data, func, true);
476 }
477
478 //---------------------------------------------------------
479 // setScore
480 //---------------------------------------------------------
481
setScore(Score * s)482 void Spanner::setScore(Score* s)
483 {
484 Element::setScore(s);
485 foreach(SpannerSegment* seg, segments)
486 seg->setScore(s);
487 }
488
489 //---------------------------------------------------------
490 // getProperty
491 //---------------------------------------------------------
492
getProperty(Pid propertyId) const493 QVariant Spanner::getProperty(Pid propertyId) const
494 {
495 switch (propertyId) {
496 case Pid::SPANNER_TICK:
497 return _tick;
498 case Pid::SPANNER_TICKS:
499 return _ticks;
500 case Pid::SPANNER_TRACK2:
501 return track2();
502 case Pid::ANCHOR:
503 return int(anchor());
504 case Pid::LOCATION_STAVES:
505 return (track2() / VOICES) - (track() / VOICES);
506 case Pid::LOCATION_VOICES:
507 return (track2() % VOICES) - (track() / VOICES);
508 case Pid::LOCATION_FRACTIONS:
509 return _ticks;
510 case Pid::LOCATION_MEASURES:
511 case Pid::LOCATION_GRACE:
512 case Pid::LOCATION_NOTE:
513 return Location::getLocationProperty(propertyId, startElement(), endElement());
514 default:
515 break;
516 }
517 return Element::getProperty(propertyId);
518 }
519
520 //---------------------------------------------------------
521 // setProperty
522 //---------------------------------------------------------
523
setProperty(Pid propertyId,const QVariant & v)524 bool Spanner::setProperty(Pid propertyId, const QVariant& v)
525 {
526 switch (propertyId) {
527 case Pid::SPANNER_TICK:
528 triggerLayout(); // spanner may have moved to another system
529 setTick(v.value<Fraction>());
530 setStartElement(0); // invalidate
531 setEndElement(0); //
532 if (score() && score()->spannerMap().removeSpanner(this))
533 score()->addSpanner(this);
534 break;
535 case Pid::SPANNER_TICKS:
536 triggerLayout(); // spanner may now span for a smaller number of systems
537 setTicks(v.value<Fraction>());
538 setEndElement(0); // invalidate
539 break;
540 case Pid::TRACK:
541 setTrack(v.toInt());
542 setStartElement(0); // invalidate
543 break;
544 case Pid::SPANNER_TRACK2:
545 setTrack2(v.toInt());
546 setEndElement(0); // invalidate
547 break;
548 case Pid::ANCHOR:
549 setAnchor(Anchor(v.toInt()));
550 break;
551 default:
552 return Element::setProperty(propertyId, v);
553 }
554 triggerLayout();
555 return true;
556 }
557
558 //---------------------------------------------------------
559 // propertyDefault
560 //---------------------------------------------------------
561
propertyDefault(Pid propertyId) const562 QVariant Spanner::propertyDefault(Pid propertyId) const
563 {
564 switch (propertyId) {
565 case Pid::ANCHOR:
566 return int(Anchor::SEGMENT);
567 default:
568 break;
569 }
570 return Element::propertyDefault(propertyId);
571 }
572
573 //---------------------------------------------------------
574 // computeStartElement
575 //---------------------------------------------------------
576
computeStartElement()577 void Spanner::computeStartElement()
578 {
579 switch (_anchor) {
580 case Anchor::SEGMENT: {
581 Segment* seg = score()->tick2segmentMM(tick(), false, SegmentType::ChordRest);
582 int strack = (track() / VOICES) * VOICES;
583 int etrack = strack + VOICES;
584 _startElement = 0;
585 if (seg) {
586 for (int t = strack; t < etrack; ++t) {
587 if (seg->element(t)) {
588 _startElement = seg->element(t);
589 break;
590 }
591 }
592 }
593 }
594 break;
595
596 case Anchor::MEASURE:
597 _startElement = score()->tick2measure(tick());
598 break;
599
600 case Anchor::CHORD:
601 case Anchor::NOTE:
602 return;
603 }
604 }
605
606 //---------------------------------------------------------
607 // computeEndElement
608 //---------------------------------------------------------
609
computeEndElement()610 void Spanner::computeEndElement()
611 {
612 if (score()->isPalette()) {
613 // return immediately to prevent lots of
614 // "no element found" messages from appearing
615 _endElement = nullptr;
616 return;
617 }
618
619 switch (_anchor) {
620 case Anchor::SEGMENT: {
621 if (track2() == -1)
622 setTrack2(track());
623 if (ticks().isZero() && isTextLine() && parent()) // special case palette
624 setTicks(score()->lastSegment()->tick() - _tick);
625
626 if (isLyricsLine() && toLyricsLine(this)->isEndMelisma()) {
627 // lyrics endTick should already indicate the segment we want
628 // except for TEMP_MELISMA_TICKS case
629 Lyrics* l = toLyricsLine(this)->lyrics();
630 Fraction tick = (l->ticks().ticks() == Lyrics::TEMP_MELISMA_TICKS) ? l->tick() : l->endTick();
631 Segment* s = score()->tick2segment(tick, true, SegmentType::ChordRest);
632 if (!s) {
633 qDebug("%s no end segment for tick %d", name(), tick.ticks());
634 return;
635 }
636 int t = trackZeroVoice(track2());
637 // take the first chordrest we can find;
638 // linePos will substitute one in current voice if available
639 for (int v = 0; v < VOICES; ++v) {
640 _endElement = s->element(t + v);
641 if (_endElement)
642 break;
643 }
644 }
645 else {
646 // find last cr on this staff that ends before tick2
647 _endElement = score()->findCRinStaff(tick2(), track2() / VOICES);
648 }
649 if (!_endElement) {
650 qDebug("%s no end element for tick %d", name(), tick2().ticks());
651 return;
652 }
653
654 if (!endCR()->measure()->isMMRest()) {
655 ChordRest* cr = endCR();
656 Fraction nticks = cr->tick() + cr->actualTicks() - _tick;
657 if ((_ticks - nticks).isNotZero()) {
658 qDebug("%s ticks changed, %d -> %d", name(), _ticks.ticks(), nticks.ticks());
659 setTicks(nticks);
660 if (isOttava())
661 staff()->updateOttava();
662 }
663 }
664 }
665 break;
666
667 case Anchor::MEASURE:
668 _endElement = score()->tick2measure(tick2() - Fraction(1, 1920));
669 if (!_endElement) {
670 qDebug("Spanner::computeEndElement(), measure not found for tick %d\n", tick2().ticks()-1);
671 _endElement = score()->lastMeasure();
672 }
673 break;
674
675 case Anchor::CHORD:
676 case Anchor::NOTE:
677 break;
678 }
679 }
680
681 //---------------------------------------------------------
682 // startElementFromSpanner
683 //
684 // Given a Spanner and an end element, determines a start element suitable for the end
685 // element of a new Spanner, so that it is 'parallel' to the old one.
686 // Can be used while cloning a linked Spanner, to update the cloned spanner start and end elements
687 // (Spanner(const Spanner&) copies start and end elements from the original to the copy).
688 // NOTES: Only spanners with Anchor::NOTE are currently supported.
689 // Going back from end to start ensures the 'other' anchor of this is already set up
690 // (for instance, while cloning staves)
691 //---------------------------------------------------------
692
startElementFromSpanner(Spanner * sp,Element * newEnd)693 Note* Spanner::startElementFromSpanner(Spanner* sp, Element* newEnd)
694 {
695 if (sp->anchor() != Anchor::NOTE)
696 return nullptr;
697
698 Note* oldStart = toNote(sp->startElement());
699 Note* oldEnd = toNote(sp->endElement());
700 if (oldStart == nullptr || oldEnd == nullptr)
701 return nullptr;
702 Note* newStart = nullptr;
703 Score* score = newEnd->score();
704 // determine the track where to expect the 'parallel' start element
705 int newTrack = (newEnd->track() - oldEnd->track()) + oldStart->track();
706 // look in notes linked to oldStart for a note with the
707 // same score as new score and appropriate track
708 for (ScoreElement* newEl : oldStart->linkList())
709 if (toNote(newEl)->score() == score && toNote(newEl)->track() == newTrack) {
710 newStart = toNote(newEl);
711 break;
712 }
713 return newStart;
714 }
715
716 //---------------------------------------------------------
717 // endElementFromSpanner
718 //
719 // Given a Spanner and a start element, determines an end element suitable for the start
720 // element of a new Spanner, so that it is 'parallel' to the old one.
721 // Can be used while cloning a linked Spanner, to update the cloned spanner start and end elements
722 // (Spanner(const Spanner&) copies start and end elements from the original to the copy).
723 // NOTES: Only spanners with Anchor::NOTE are currently supported.
724 //---------------------------------------------------------
725
endElementFromSpanner(Spanner * sp,Element * newStart)726 Note* Spanner::endElementFromSpanner(Spanner* sp, Element* newStart)
727 {
728 if (sp->anchor() != Anchor::NOTE)
729 return nullptr;
730
731 Note* oldStart = toNote(sp->startElement());
732 Note* oldEnd = toNote(sp->endElement());
733 if (oldStart == nullptr || oldEnd == nullptr)
734 return nullptr;
735 Note* newEnd = nullptr;
736 Score* score = newStart->score();
737 // determine the track where to expect the 'parallel' start element
738 int newTrack = newStart->track() + (oldEnd->track() - oldStart->track());
739 // look in notes linked to oldEnd for a note with the
740 // same score as new score and appropriate track
741 for (ScoreElement* newEl : oldEnd->linkList())
742 if (toNote(newEl)->score() == score && toNote(newEl)->track() == newTrack) {
743 newEnd = toNote(newEl);
744 break;
745 }
746 return newEnd;
747 }
748
749 //---------------------------------------------------------
750 // setNoteSpan
751 //
752 // Sets up all the variables congruent with given start and end note anchors.
753 //---------------------------------------------------------
754
setNoteSpan(Note * startNote,Note * endNote)755 void Spanner::setNoteSpan(Note* startNote, Note* endNote)
756 {
757 if (_anchor != Anchor::NOTE)
758 return;
759
760 setScore(startNote->score());
761 setParent(startNote);
762 setStartElement(startNote);
763 setEndElement(endNote);
764 setTick(startNote->chord()->tick());
765 setTick2(endNote->chord()->tick());
766 setTrack(startNote->track());
767 setTrack2(endNote->track());
768 }
769
770 //---------------------------------------------------------
771 // startChord
772 //---------------------------------------------------------
773
startChord()774 Chord* Spanner::startChord()
775 {
776 Q_ASSERT(_anchor == Anchor::CHORD);
777 if (!_startElement)
778 _startElement = score()->findCR(tick(), track());
779 return toChord(_startElement);
780 }
781
782 //---------------------------------------------------------
783 // endChord
784 //---------------------------------------------------------
785
endChord()786 Chord* Spanner::endChord()
787 {
788 Q_ASSERT(_anchor == Anchor::CHORD);
789
790 if (!_endElement && type() == ElementType::SLUR) {
791 Segment* s = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
792 _endElement = s ? toChordRest(s->element(track2())) : nullptr;
793 if (_endElement && !_endElement->isChord())
794 _endElement = nullptr;
795 }
796 return toChord(_endElement);
797 }
798
799 //---------------------------------------------------------
800 // startCR
801 //---------------------------------------------------------
802
startCR()803 ChordRest* Spanner::startCR()
804 {
805 Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
806 if (!_startElement || _startElement->score() != score())
807 _startElement = score()->findCR(tick(), track());
808 return toChordRest(_startElement);
809 }
810
811 //---------------------------------------------------------
812 // endCR
813 //---------------------------------------------------------
814
endCR()815 ChordRest* Spanner::endCR()
816 {
817 Q_ASSERT(_anchor == Anchor::SEGMENT || _anchor == Anchor::CHORD);
818 if ((!_endElement || _endElement->score() != score())) {
819 Segment* s = score()->tick2segmentMM(tick2(), false, SegmentType::ChordRest);
820 const int tr2 = effectiveTrack2();
821 _endElement = s ? toChordRest(s->element(tr2)) : nullptr;
822 }
823 return toChordRest(_endElement);
824 }
825
826 //---------------------------------------------------------
827 // startSegment
828 //---------------------------------------------------------
829
startSegment() const830 Segment* Spanner::startSegment() const
831 {
832 Q_ASSERT(score() != NULL);
833 return score()->tick2rightSegment(tick());
834 }
835
836 //---------------------------------------------------------
837 // endSegment
838 //---------------------------------------------------------
839
endSegment() const840 Segment* Spanner::endSegment() const
841 {
842 return score()->tick2leftSegment(tick2());
843 }
844
845 //---------------------------------------------------------
846 // startMeasure
847 //---------------------------------------------------------
848
startMeasure() const849 Measure* Spanner::startMeasure() const
850 {
851 return toMeasure(_startElement);
852 }
853
854 //---------------------------------------------------------
855 // endMeasure
856 //---------------------------------------------------------
857
endMeasure() const858 Measure* Spanner::endMeasure() const
859 {
860 return toMeasure(_endElement);
861 }
862
863 //---------------------------------------------------------
864 // setSelected
865 //---------------------------------------------------------
866
setSelected(bool f)867 void Spanner::setSelected(bool f)
868 {
869 for (SpannerSegment* ss : spannerSegments())
870 ss->Element::setSelected(f);
871 Element::setSelected(f);
872 }
873
874 //---------------------------------------------------------
875 // setVisible
876 //---------------------------------------------------------
877
setVisible(bool f)878 void Spanner::setVisible(bool f)
879 {
880 for (SpannerSegment* ss : spannerSegments())
881 ss->Element::setVisible(f);
882 Element::setVisible(f);
883 }
884
885 //---------------------------------------------------------
886 // setAutoplace
887 //---------------------------------------------------------
888
setAutoplace(bool f)889 void Spanner::setAutoplace(bool f)
890 {
891 for (SpannerSegment* ss : spannerSegments())
892 ss->Element::setAutoplace(f);
893 Element::setAutoplace(f);
894 }
895
896 //---------------------------------------------------------
897 // setColor
898 //---------------------------------------------------------
899
setColor(const QColor & col)900 void Spanner::setColor(const QColor& col)
901 {
902 for (SpannerSegment* ss : spannerSegments())
903 ss->setColor(col);
904 _color = col;
905 }
906
907 //---------------------------------------------------------
908 // setStartElement
909 //---------------------------------------------------------
910
setStartElement(Element * e)911 void Spanner::setStartElement(Element* e)
912 {
913 #ifndef NDEBUG
914 if (_anchor == Anchor::NOTE)
915 Q_ASSERT(!e || e->type() == ElementType::NOTE);
916 #endif
917 _startElement = e;
918 }
919
920 //---------------------------------------------------------
921 // setEndElement
922 //---------------------------------------------------------
923
setEndElement(Element * e)924 void Spanner::setEndElement(Element* e)
925 {
926 #ifndef NDEBUG
927 if (_anchor == Anchor::NOTE)
928 Q_ASSERT(!e || e->type() == ElementType::NOTE);
929 #endif
930 _endElement = e;
931 }
932
933 //---------------------------------------------------------
934 // nextSpanner
935 //---------------------------------------------------------
936
nextSpanner(Element * e,int activeStaff)937 Spanner* Spanner::nextSpanner(Element* e, int activeStaff)
938 {
939 std::multimap<int, Spanner*> mmap = score()->spanner();
940 auto range = mmap.equal_range(tick().ticks());
941 if (range.first != range.second) { // range not empty
942 for (auto i = range.first; i != range.second; ++i) {
943 if (i->second == e) {
944 while (i != range.second) {
945 ++i;
946 if (i == range.second)
947 return nullptr;
948 Spanner* s = i->second;
949 Element* st = s->startElement();
950 if (!st)
951 continue;
952 if (s->startSegment() == toSpanner(e)->startSegment()) {
953 if (st->staffIdx() == activeStaff)
954 return s;
955 #if 1
956 else if (st->isMeasure() && activeStaff == 0)
957 return s;
958 #else
959 // TODO: when navigating system spanners, check firstVisibleStaff()?
960 // currently, information about which staves are hidden
961 // is not exposed through navigation,
962 // so it may make more sense to continue to navigate systems elements
963 // only when actually on staff 0
964 // see also https://musescore.org/en/node/301496
965 // and https://github.com/musescore/MuseScore/pull/5755
966 else if (st->isMeasure()) {
967 SpannerSegment* ss = s->frontSegment();
968 int top = ss && ss->system() ? ss->system()->firstVisibleStaff() : 0;
969 if (activeStaff == top)
970 return s;
971 }
972 #endif
973 }
974 //else
975 //return nullptr;
976 }
977 break;
978 /* else {
979 break;
980 }*/
981 }
982 }
983 }
984 return nullptr;
985 }
986
987 //---------------------------------------------------------
988 // prevSpanner
989 //---------------------------------------------------------
990
prevSpanner(Element * e,int activeStaff)991 Spanner* Spanner::prevSpanner(Element* e, int activeStaff)
992 {
993 std::multimap<int, Spanner*> mmap = score()->spanner();
994 auto range = mmap.equal_range(tick().ticks());
995 if (range.first != range.second) { // range not empty
996 for (auto i = range.first; i != range.second; ++i) {
997 if (i->second == e) {
998 if (i == range.first)
999 return nullptr;
1000 while (i != range.first) {
1001 --i;
1002 Spanner* s = i->second;
1003 Element* st = s->startElement();
1004 if (s->startSegment() == toSpanner(e)->startSegment()) {
1005 if (st->staffIdx() == activeStaff)
1006 return s;
1007 #if 1
1008 else if (st->isMeasure() && activeStaff == 0)
1009 return s;
1010 #else
1011 // TODO: see nextSpanner()
1012 else if (st->isMeasure()) {
1013 SpannerSegment* ss = s->frontSegment();
1014 int top = ss && ss->system() ? ss->system()->firstVisibleStaff() : 0;
1015 if (activeStaff == top)
1016 return s;
1017 }
1018 #endif
1019 }
1020 }
1021 break;
1022 }
1023 }
1024 }
1025 return nullptr;
1026 }
1027
1028 //---------------------------------------------------------
1029 // nextSegmentElement
1030 //---------------------------------------------------------
1031
nextSegmentElement()1032 Element* Spanner::nextSegmentElement()
1033 {
1034 Segment* s = startSegment();
1035 if (s)
1036 return s->firstElement(staffIdx());
1037 return score()->lastElement();
1038 }
1039
1040 //---------------------------------------------------------
1041 // prevSegmentElement
1042 //---------------------------------------------------------
1043
prevSegmentElement()1044 Element* Spanner::prevSegmentElement()
1045 {
1046 Segment* s = endSegment();
1047 if (s)
1048 return s->lastElement(staffIdx());
1049 return score()->firstElement();
1050 }
1051
1052 //---------------------------------------------------------
1053 // setTick
1054 //---------------------------------------------------------
1055
setTick(const Fraction & v)1056 void Spanner::setTick(const Fraction& v)
1057 {
1058 _tick = v;
1059 if (score())
1060 score()->spannerMap().setDirty();
1061 }
1062
1063 //---------------------------------------------------------
1064 // setTick2
1065 //---------------------------------------------------------
1066
setTick2(const Fraction & f)1067 void Spanner::setTick2(const Fraction& f)
1068 {
1069 setTicks(f - _tick);
1070 }
1071
1072 //---------------------------------------------------------
1073 // setTicks
1074 //---------------------------------------------------------
1075
setTicks(const Fraction & f)1076 void Spanner::setTicks(const Fraction& f)
1077 {
1078 _ticks = f;
1079 if (score())
1080 score()->spannerMap().setDirty();
1081 }
1082
1083 //---------------------------------------------------------
1084 // triggerLayout
1085 //---------------------------------------------------------
1086
triggerLayout() const1087 void Spanner::triggerLayout() const
1088 {
1089 // Spanners do not have parent even when added to a score, so can't check parent here
1090 const int tr2 = effectiveTrack2();
1091 score()->setLayout(_tick, _tick + _ticks, staffIdx(), track2staff(tr2), this);
1092 }
1093
triggerLayoutAll() const1094 void Spanner::triggerLayoutAll() const
1095 {
1096 // Spanners do not have parent even when added to a score, so can't check parent here
1097 score()->setLayoutAll(staffIdx(), this);
1098
1099 const int tr2 = track2();
1100 if (tr2 != -1 && tr2 != track())
1101 score()->setLayoutAll(track2staff(tr2), this);
1102 }
1103
1104 //---------------------------------------------------------
1105 // pushUnusedSegment
1106 //---------------------------------------------------------
1107
pushUnusedSegment(SpannerSegment * seg)1108 void Spanner::pushUnusedSegment(SpannerSegment* seg)
1109 {
1110 if (!seg)
1111 return;
1112 seg->setSystem(nullptr);
1113 unusedSegments.push_back(seg);
1114 }
1115
1116 //---------------------------------------------------------
1117 // popUnusedSegment
1118 // Take the next unused segment for reusing it.
1119 // If there is no unused segments left returns nullptr.
1120 //---------------------------------------------------------
1121
popUnusedSegment()1122 SpannerSegment* Spanner::popUnusedSegment()
1123 {
1124 if (unusedSegments.empty())
1125 return nullptr;
1126 SpannerSegment* seg = unusedSegments.front();
1127 unusedSegments.pop_front();
1128 return seg;
1129 }
1130
1131 //---------------------------------------------------------
1132 // reuse
1133 // called when segment from unusedSegments is added
1134 // back to the spanner.
1135 //---------------------------------------------------------
1136
reuse(SpannerSegment * seg)1137 void Spanner::reuse(SpannerSegment* seg)
1138 {
1139 add(seg);
1140 }
1141
1142 //---------------------------------------------------------
1143 // reuseSegments
1144 // Adds \p number segments from unusedSegments to this
1145 // spanner via reuse() call. Returns number of new
1146 // segments that still need to be created, that is,
1147 // returns (number - nMovedSegments).
1148 //---------------------------------------------------------
1149
reuseSegments(int number)1150 int Spanner::reuseSegments(int number)
1151 {
1152 while (number > 0) {
1153 SpannerSegment* seg = popUnusedSegment();
1154 if (!seg)
1155 break;
1156 reuse(seg);
1157 --number;
1158 }
1159 return number;
1160 }
1161
1162 //---------------------------------------------------------
1163 // fixupSegments
1164 // Makes number of segments match targetNumber.
1165 // Tries to reuse unused segments. If there are no
1166 // unused segments left, uses \p createSegment to create
1167 // the needed segments.
1168 // Previously unused segments are added via reuse() call
1169 //---------------------------------------------------------
1170
fixupSegments(unsigned int targetNumber,std::function<SpannerSegment * ()> createSegment)1171 void Spanner::fixupSegments(unsigned int targetNumber, std::function<SpannerSegment*()> createSegment)
1172 {
1173 const int diff = targetNumber - int(nsegments());
1174 if (diff == 0)
1175 return;
1176 if (diff > 0) {
1177 const int ncreate = reuseSegments(diff);
1178 for (int i = 0; i < ncreate; ++i)
1179 add(createSegment());
1180 }
1181 else { // diff < 0
1182 const int nremove = -diff;
1183 for (int i = 0; i < nremove; ++i) {
1184 SpannerSegment* seg = segments.back();
1185 segments.pop_back();
1186 pushUnusedSegment(seg);
1187 }
1188 }
1189 }
1190
1191 //---------------------------------------------------------
1192 // eraseSpannerSegments
1193 // Completely erase all spanner segments, both used and
1194 // unused.
1195 //---------------------------------------------------------
1196
eraseSpannerSegments()1197 void Spanner::eraseSpannerSegments()
1198 {
1199 qDeleteAll(segments);
1200 qDeleteAll(unusedSegments);
1201 segments.clear();
1202 unusedSegments.clear();
1203 }
1204
1205 //---------------------------------------------------------
1206 // layoutSystem
1207 //---------------------------------------------------------
1208
layoutSystem(System *)1209 SpannerSegment* Spanner::layoutSystem(System*)
1210 {
1211 qDebug(" %s", name());
1212 return 0;
1213 }
1214
1215 //---------------------------------------------------------
1216 // getNextLayoutSystemSegment
1217 //---------------------------------------------------------
1218
getNextLayoutSystemSegment(System * system,std::function<SpannerSegment * ()> createSegment)1219 SpannerSegment* Spanner::getNextLayoutSystemSegment(System* system, std::function<SpannerSegment*()> createSegment)
1220 {
1221 SpannerSegment* seg = nullptr;
1222 for (SpannerSegment* ss : spannerSegments()) {
1223 if (!ss->system()) {
1224 seg = ss;
1225 break;
1226 }
1227 }
1228 if (!seg) {
1229 if ((seg = popUnusedSegment()))
1230 reuse(seg);
1231 else {
1232 seg = createSegment();
1233 Q_ASSERT(seg);
1234 add(seg);
1235 }
1236 }
1237 seg->setSystem(system);
1238 seg->setSpanner(this);
1239 seg->setTrack(track());
1240 seg->setVisible(visible());
1241 return seg;
1242 }
1243
1244 //---------------------------------------------------------
1245 // layoutSystemsDone
1246 // Called after layout of all systems is done so precise
1247 // number of systems for this spanner becomes available.
1248 //---------------------------------------------------------
1249
layoutSystemsDone()1250 void Spanner::layoutSystemsDone()
1251 {
1252 std::vector<SpannerSegment*> validSegments;
1253 for (SpannerSegment* seg : segments) {
1254 if (seg->system())
1255 validSegments.push_back(seg);
1256 else // TODO: score()->selection().remove(ss); needed?
1257 pushUnusedSegment(seg);
1258 }
1259 segments = std::move(validSegments);
1260 }
1261
1262 //--------------------------------------------------
1263 // fraction
1264 //---------------------------------------------------------
1265
fraction(const XmlWriter & xml,const Element * current,const Fraction & t)1266 static Fraction fraction(const XmlWriter& xml, const Element* current, const Fraction& t)
1267 {
1268 Fraction tick(t);
1269 if (!xml.clipboardmode()) {
1270 const Measure* m = toMeasure(current->findMeasure());
1271 if (m)
1272 tick -= m->tick();
1273 }
1274 return tick;
1275 }
1276
1277 //---------------------------------------------------------
1278 // Spanner::readProperties
1279 //---------------------------------------------------------
1280
readProperties(XmlReader & e)1281 bool Spanner::readProperties(XmlReader& e)
1282 {
1283 const QStringRef tag(e.name());
1284 if (e.pasteMode()) {
1285 if (tag == "ticks_f") {
1286 setTicks(e.readFraction());
1287 return true;
1288 }
1289 }
1290 return Element::readProperties(e);
1291 }
1292
1293 //---------------------------------------------------------
1294 // Spanner::writeProperties
1295 //---------------------------------------------------------
1296
writeProperties(XmlWriter & xml) const1297 void Spanner::writeProperties(XmlWriter& xml) const
1298 {
1299 if (xml.clipboardmode())
1300 xml.tag("ticks_f", ticks());
1301 Element::writeProperties(xml);
1302 }
1303
1304 //--------------------------------------------------
1305 // Spanner::writeSpannerStart
1306 //---------------------------------------------------------
1307
writeSpannerStart(XmlWriter & xml,const Element * current,int track,Fraction tick) const1308 void Spanner::writeSpannerStart(XmlWriter& xml, const Element* current, int track, Fraction tick) const
1309 {
1310 Fraction frac = fraction(xml, current, tick);
1311 SpannerWriter w(xml, current, this, track, frac, true);
1312 w.write();
1313 }
1314
1315 //--------------------------------------------------
1316 // Spanner::writeSpannerEnd
1317 //---------------------------------------------------------
1318
writeSpannerEnd(XmlWriter & xml,const Element * current,int track,Fraction tick) const1319 void Spanner::writeSpannerEnd(XmlWriter& xml, const Element* current, int track, Fraction tick) const
1320 {
1321 Fraction frac = fraction(xml, current, tick);
1322 SpannerWriter w(xml, current, this, track, frac, false);
1323 w.write();
1324 }
1325
1326 //--------------------------------------------------
1327 // Spanner::readSpanner
1328 //---------------------------------------------------------
1329
readSpanner(XmlReader & e,Element * current,int track)1330 void Spanner::readSpanner(XmlReader& e, Element* current, int track)
1331 {
1332 std::unique_ptr<ConnectorInfoReader> info(new ConnectorInfoReader(e, current, track));
1333 ConnectorInfoReader::readConnector(std::move(info), e);
1334 }
1335
1336 //--------------------------------------------------
1337 // Spanner::readSpanner
1338 //---------------------------------------------------------
1339
readSpanner(XmlReader & e,Score * current,int track)1340 void Spanner::readSpanner(XmlReader& e, Score* current, int track)
1341 {
1342 std::unique_ptr<ConnectorInfoReader> info(new ConnectorInfoReader(e, current, track));
1343 ConnectorInfoReader::readConnector(std::move(info), e);
1344 }
1345
1346 //---------------------------------------------------------
1347 // SpannerWriter::fillSpannerPosition
1348 //---------------------------------------------------------
1349
fillSpannerPosition(Location & l,const MeasureBase * m,const Fraction & tick,bool clipboardmode)1350 void SpannerWriter::fillSpannerPosition(Location& l, const MeasureBase* m, const Fraction& tick, bool clipboardmode)
1351 {
1352 if (clipboardmode) {
1353 l.setMeasure(0);
1354 l.setFrac(tick);
1355 }
1356 else {
1357 if (!m) {
1358 qWarning("fillSpannerPosition: couldn't find spanner's endpoint's measure");
1359 l.setMeasure(0);
1360 l.setFrac(tick);
1361 return;
1362 }
1363 l.setMeasure(m->measureIndex());
1364 l.setFrac(tick - m->tick());
1365 }
1366 }
1367
1368 //---------------------------------------------------------
1369 // SpannerWriter::SpannerWriter
1370 //---------------------------------------------------------
1371
SpannerWriter(XmlWriter & xml,const Element * current,const Spanner * sp,int track,Fraction frac,bool start)1372 SpannerWriter::SpannerWriter(XmlWriter& xml, const Element* current, const Spanner* sp, int track, Fraction frac, bool start)
1373 : ConnectorInfoWriter(xml, current, sp, track, frac)
1374 {
1375 const bool clipboardmode = xml.clipboardmode();
1376 if (!sp->startElement() || !sp->endElement()) {
1377 qWarning("SpannerWriter: spanner (%s) doesn't have an endpoint!", sp->name());
1378 return;
1379 }
1380 if (current->isMeasure() || current->isSegment() || (sp->startElement()->type() != current->type())) {
1381 // (The latter is the hairpins' case, for example, though they are
1382 // covered by the other checks too.)
1383 // We cannot determine position of the spanner from its start/end
1384 // elements and will try to obtain this info from the spanner itself.
1385 if (!start) {
1386 _prevLoc.setTrack(sp->track());
1387 Measure* m = sp->score()->tick2measure(sp->tick());
1388 fillSpannerPosition(_prevLoc, m, sp->tick(), clipboardmode);
1389 }
1390 else {
1391 const int track2 = (sp->track2() != -1) ? sp->track2() : sp->track();
1392 _nextLoc.setTrack(track2);
1393 Measure* m = sp->score()->tick2measure(sp->tick2());
1394 fillSpannerPosition(_nextLoc, m, sp->tick2(), clipboardmode);
1395 }
1396 }
1397 else {
1398 // We can obtain the spanner position info from its start/end
1399 // elements and will prefer this source of information.
1400 // Reason: some spanners contain no or wrong information (e.g. Ties).
1401 if (!start)
1402 updateLocation(sp->startElement(), _prevLoc, clipboardmode);
1403 else
1404 updateLocation(sp->endElement(), _nextLoc, clipboardmode);
1405 }
1406 }
1407
1408 //---------------------------------------------------------
1409 // autoplaceSpannerSegment
1410 //---------------------------------------------------------
1411
autoplaceSpannerSegment()1412 void SpannerSegment::autoplaceSpannerSegment()
1413 {
1414 if (!parent()) {
1415 setOffset(QPointF());
1416 return;
1417 }
1418 if (isStyled(Pid::OFFSET))
1419 setOffset(spanner()->propertyDefault(Pid::OFFSET).toPointF());
1420
1421 if (spanner()->anchor() == Spanner::Anchor::NOTE)
1422 return;
1423
1424 // rebase vertical offset on drag
1425 qreal rebase = 0.0;
1426 if (offsetChanged() != OffsetChange::NONE)
1427 rebase = rebaseOffset();
1428
1429 if (autoplace()) {
1430 qreal sp = score()->spatium();
1431 if (!systemFlag() && !spanner()->systemFlag())
1432 sp *= staff()->mag(spanner()->tick());
1433 qreal md = minDistance().val() * sp;
1434 bool above = spanner()->placeAbove();
1435 SkylineLine sl(!above);
1436 Shape sh = shape();
1437 sl.add(sh.translated(pos()));
1438 qreal yd = 0.0;
1439 if (above) {
1440 qreal d = system()->topDistance(staffIdx(), sl);
1441 if (d > -md)
1442 yd = -(d + md);
1443 }
1444 else {
1445 qreal d = system()->bottomDistance(staffIdx(), sl);
1446 if (d > -md)
1447 yd = d + md;
1448 }
1449 if (yd != 0.0) {
1450 if (offsetChanged() != OffsetChange::NONE) {
1451 // user moved element within the skyline
1452 // we may need to adjust minDistance, yd, and/or offset
1453 qreal adj = pos().y() + rebase;
1454 bool inStaff = above ? sh.bottom() + adj > 0.0 : sh.top() + adj < staff()->height();
1455 rebaseMinDistance(md, yd, sp, rebase, above, inStaff);
1456 }
1457 rypos() += yd;
1458 }
1459 }
1460 setOffsetChanged(false);
1461 }
1462
1463 //---------------------------------------------------------
1464 // undoChangeProperty
1465 //---------------------------------------------------------
1466
undoChangeProperty(Pid id,const QVariant & v,PropertyFlags ps)1467 void Spanner::undoChangeProperty(Pid id, const QVariant& v, PropertyFlags ps)
1468 {
1469 if (id == Pid::PLACEMENT) {
1470 ScoreElement::undoChangeProperty(id, v, ps);
1471 // change offset of all segments if styled
1472
1473 for (SpannerSegment* s : segments) {
1474 if (s->isStyled(Pid::OFFSET)) {
1475 s->setOffset(s->propertyDefault(Pid::OFFSET).toPointF());
1476 s->triggerLayout();
1477 }
1478 }
1479 MuseScoreCore::mscoreCore->updateInspector();
1480 return;
1481 }
1482 Element::undoChangeProperty(id, v, ps);
1483 }
1484
1485 }
1486
1487