1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2015 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 "scoreElement.h"
14 #include "score.h"
15 #include "undo.h"
16 #include "xml.h"
17 #include "bracket.h"
18 #include "bracketItem.h"
19 #include "measure.h"
20 #include "spanner.h"
21 #include "musescoreCore.h"
22 
23 namespace Ms {
24 
25 ElementStyle const ScoreElement::emptyStyle;
26 
27 //
28 // list has to be synchronized with ElementType enum
29 //
30 static const ElementName elementNames[] = {
31       { ElementType::INVALID,              "invalid",              QT_TRANSLATE_NOOP("elementName", "Invalid") },
32       { ElementType::BRACKET_ITEM,         "BracketItem",          QT_TRANSLATE_NOOP("elementName", "Bracket") },
33       { ElementType::PART,                 "Part",                 QT_TRANSLATE_NOOP("elementName", "Part") },
34       { ElementType::STAFF,                "Staff",                QT_TRANSLATE_NOOP("elementName", "Staff") },
35       { ElementType::SCORE,                "Score",                QT_TRANSLATE_NOOP("elementName", "Score") },
36       { ElementType::SYMBOL,               "Symbol",               QT_TRANSLATE_NOOP("elementName", "Symbol") },
37       { ElementType::TEXT,                 "Text",                 QT_TRANSLATE_NOOP("elementName", "Text") },
38       { ElementType::MEASURE_NUMBER,       "MeasureNumber",        QT_TRANSLATE_NOOP("elementName", "Measure Number") },
39       { ElementType::MMREST_RANGE,         "MMRestRange",          QT_TRANSLATE_NOOP("elementName", "Multimeasure Rest Range") },
40       { ElementType::INSTRUMENT_NAME,      "InstrumentName",       QT_TRANSLATE_NOOP("elementName", "Instrument Name") },
41       { ElementType::SLUR_SEGMENT,         "SlurSegment",          QT_TRANSLATE_NOOP("elementName", "Slur Segment") },
42       { ElementType::TIE_SEGMENT,          "TieSegment",           QT_TRANSLATE_NOOP("elementName", "Tie Segment") },
43       { ElementType::BAR_LINE,             "BarLine",              QT_TRANSLATE_NOOP("elementName", "Barline") },
44       { ElementType::STAFF_LINES,          "StaffLines",           QT_TRANSLATE_NOOP("elementName", "Staff Lines") },
45       { ElementType::SYSTEM_DIVIDER,       "SystemDivider",        QT_TRANSLATE_NOOP("elementName", "System Divider") },
46       { ElementType::STEM_SLASH,           "StemSlash",            QT_TRANSLATE_NOOP("elementName", "Stem Slash") },
47       { ElementType::ARPEGGIO,             "Arpeggio",             QT_TRANSLATE_NOOP("elementName", "Arpeggio") },
48       { ElementType::ACCIDENTAL,           "Accidental",           QT_TRANSLATE_NOOP("elementName", "Accidental") },
49       { ElementType::LEDGER_LINE,          "LedgerLine",           QT_TRANSLATE_NOOP("elementName", "Ledger Line") },
50       { ElementType::STEM,                 "Stem",                 QT_TRANSLATE_NOOP("elementName", "Stem") },
51       { ElementType::NOTE,                 "Note",                 QT_TRANSLATE_NOOP("elementName", "Note") },
52       { ElementType::CLEF,                 "Clef",                 QT_TRANSLATE_NOOP("elementName", "Clef") },
53       { ElementType::KEYSIG,               "KeySig",               QT_TRANSLATE_NOOP("elementName", "Key Signature") },
54       { ElementType::AMBITUS,              "Ambitus",              QT_TRANSLATE_NOOP("elementName", "Ambitus") },
55       { ElementType::TIMESIG,              "TimeSig",              QT_TRANSLATE_NOOP("elementName", "Time Signature") },
56       { ElementType::REST,                 "Rest",                 QT_TRANSLATE_NOOP("elementName", "Rest") },
57       { ElementType::BREATH,               "Breath",               QT_TRANSLATE_NOOP("elementName", "Breath") },
58       { ElementType::REPEAT_MEASURE,       "RepeatMeasure",        QT_TRANSLATE_NOOP("elementName", "Repeat Measure") },
59       { ElementType::TIE,                  "Tie",                  QT_TRANSLATE_NOOP("elementName", "Tie") },
60       { ElementType::ARTICULATION,         "Articulation",         QT_TRANSLATE_NOOP("elementName", "Articulation") },
61       { ElementType::FERMATA,              "Fermata",              QT_TRANSLATE_NOOP("elementName", "Fermata") },
62       { ElementType::CHORDLINE,            "ChordLine",            QT_TRANSLATE_NOOP("elementName", "Chord Line") },
63       { ElementType::DYNAMIC,              "Dynamic",              QT_TRANSLATE_NOOP("elementName", "Dynamic") },
64       { ElementType::BEAM,                 "Beam",                 QT_TRANSLATE_NOOP("elementName", "Beam") },
65       { ElementType::HOOK,                 "Hook",                 QT_TRANSLATE_NOOP("elementName", "Flag") }, // internally called "Hook", but "Flag" in SMuFL, so here externally too
66       { ElementType::LYRICS,               "Lyrics",               QT_TRANSLATE_NOOP("elementName", "Lyrics") },
67       { ElementType::FIGURED_BASS,         "FiguredBass",          QT_TRANSLATE_NOOP("elementName", "Figured Bass") },
68       { ElementType::MARKER,               "Marker",               QT_TRANSLATE_NOOP("elementName", "Marker") },
69       { ElementType::JUMP,                 "Jump",                 QT_TRANSLATE_NOOP("elementName", "Jump") },
70       { ElementType::FINGERING,            "Fingering",            QT_TRANSLATE_NOOP("elementName", "Fingering") },
71       { ElementType::TUPLET,               "Tuplet",               QT_TRANSLATE_NOOP("elementName", "Tuplet") },
72       { ElementType::TEMPO_TEXT,           "Tempo",                QT_TRANSLATE_NOOP("elementName", "Tempo") },
73       { ElementType::STAFF_TEXT,           "StaffText",            QT_TRANSLATE_NOOP("elementName", "Staff Text") },
74       { ElementType::SYSTEM_TEXT,          "SystemText",           QT_TRANSLATE_NOOP("elementName", "System Text") },
75       { ElementType::REHEARSAL_MARK,       "RehearsalMark",        QT_TRANSLATE_NOOP("elementName", "Rehearsal Mark") },
76       { ElementType::INSTRUMENT_CHANGE,    "InstrumentChange",     QT_TRANSLATE_NOOP("elementName", "Instrument Change") },
77       { ElementType::STAFFTYPE_CHANGE,     "StaffTypeChange",      QT_TRANSLATE_NOOP("elementName", "Staff Type Change") },
78       { ElementType::HARMONY,              "Harmony",              QT_TRANSLATE_NOOP("elementName", "Chord Symbol") },
79       { ElementType::FRET_DIAGRAM,         "FretDiagram",          QT_TRANSLATE_NOOP("elementName", "Fretboard Diagram") },
80       { ElementType::BEND,                 "Bend",                 QT_TRANSLATE_NOOP("elementName", "Bend") },
81       { ElementType::TREMOLOBAR,           "TremoloBar",           QT_TRANSLATE_NOOP("elementName", "Tremolo Bar") },
82       { ElementType::VOLTA,                "Volta",                QT_TRANSLATE_NOOP("elementName", "Volta") },
83       { ElementType::HAIRPIN_SEGMENT,      "HairpinSegment",       QT_TRANSLATE_NOOP("elementName", "Hairpin Segment") },
84       { ElementType::OTTAVA_SEGMENT,       "OttavaSegment",        QT_TRANSLATE_NOOP("elementName", "Ottava Segment") },
85       { ElementType::TRILL_SEGMENT,        "TrillSegment",         QT_TRANSLATE_NOOP("elementName", "Trill Segment") },
86       { ElementType::LET_RING_SEGMENT,     "LetRingSegment",       QT_TRANSLATE_NOOP("elementName", "Let Ring Segment") },
87       { ElementType::VIBRATO_SEGMENT,      "VibratoSegment",       QT_TRANSLATE_NOOP("elementName", "Vibrato Segment") },
88       { ElementType::PALM_MUTE_SEGMENT,    "PalmMuteSegment",      QT_TRANSLATE_NOOP("elementName", "Palm Mute Segment") },
89       { ElementType::TEXTLINE_SEGMENT,     "TextLineSegment",      QT_TRANSLATE_NOOP("elementName", "Text Line Segment") },
90       { ElementType::VOLTA_SEGMENT,        "VoltaSegment",         QT_TRANSLATE_NOOP("elementName", "Volta Segment") },
91       { ElementType::PEDAL_SEGMENT,        "PedalSegment",         QT_TRANSLATE_NOOP("elementName", "Pedal Segment") },
92       { ElementType::LYRICSLINE_SEGMENT,   "LyricsLineSegment",    QT_TRANSLATE_NOOP("elementName", "Melisma Line Segment") },
93       { ElementType::GLISSANDO_SEGMENT,    "GlissandoSegment",     QT_TRANSLATE_NOOP("elementName", "Glissando Segment") },
94       { ElementType::LAYOUT_BREAK,         "LayoutBreak",          QT_TRANSLATE_NOOP("elementName", "Layout Break") },
95       { ElementType::SPACER,               "Spacer",               QT_TRANSLATE_NOOP("elementName", "Spacer") },
96       { ElementType::STAFF_STATE,          "StaffState",           QT_TRANSLATE_NOOP("elementName", "Staff State") },
97       { ElementType::NOTEHEAD,             "NoteHead",             QT_TRANSLATE_NOOP("elementName", "Notehead") },
98       { ElementType::NOTEDOT,              "NoteDot",              QT_TRANSLATE_NOOP("elementName", "Note Dot") },
99       { ElementType::TREMOLO,              "Tremolo",              QT_TRANSLATE_NOOP("elementName", "Tremolo") },
100       { ElementType::IMAGE,                "Image",                QT_TRANSLATE_NOOP("elementName", "Image") },
101       { ElementType::MEASURE,              "Measure",              QT_TRANSLATE_NOOP("elementName", "Measure") },
102       { ElementType::SELECTION,            "Selection",            QT_TRANSLATE_NOOP("elementName", "Selection") },
103       { ElementType::LASSO,                "Lasso",                QT_TRANSLATE_NOOP("elementName", "Lasso") },
104       { ElementType::SHADOW_NOTE,          "ShadowNote",           QT_TRANSLATE_NOOP("elementName", "Shadow Note") },
105       { ElementType::TAB_DURATION_SYMBOL,  "TabDurationSymbol",    QT_TRANSLATE_NOOP("elementName", "Tab Duration Symbol") },
106       { ElementType::FSYMBOL,              "FSymbol",              QT_TRANSLATE_NOOP("elementName", "Font Symbol") },
107       { ElementType::PAGE,                 "Page",                 QT_TRANSLATE_NOOP("elementName", "Page") },
108       { ElementType::HAIRPIN,              "HairPin",              QT_TRANSLATE_NOOP("elementName", "Hairpin") },
109       { ElementType::OTTAVA,               "Ottava",               QT_TRANSLATE_NOOP("elementName", "Ottava") },
110       { ElementType::PEDAL,                "Pedal",                QT_TRANSLATE_NOOP("elementName", "Pedal") },
111       { ElementType::TRILL,                "Trill",                QT_TRANSLATE_NOOP("elementName", "Trill") },
112       { ElementType::LET_RING,             "LetRing",              QT_TRANSLATE_NOOP("elementName", "Let Ring") },
113       { ElementType::VIBRATO,              "Vibrato",              QT_TRANSLATE_NOOP("elementName", "Vibrato") },
114       { ElementType::PALM_MUTE,            "PalmMute",             QT_TRANSLATE_NOOP("elementName", "Palm Mute") },
115       { ElementType::TEXTLINE,             "TextLine",             QT_TRANSLATE_NOOP("elementName", "Text Line") },
116       { ElementType::TEXTLINE_BASE,        "TextLineBase",         QT_TRANSLATE_NOOP("elementName", "Text Line Base") },  // remove
117       { ElementType::NOTELINE,             "NoteLine",             QT_TRANSLATE_NOOP("elementName", "Note Line") },
118       { ElementType::LYRICSLINE,           "LyricsLine",           QT_TRANSLATE_NOOP("elementName", "Melisma Line") },
119       { ElementType::GLISSANDO,            "Glissando",            QT_TRANSLATE_NOOP("elementName", "Glissando") },
120       { ElementType::BRACKET,              "Bracket",              QT_TRANSLATE_NOOP("elementName", "Bracket") },
121       { ElementType::SEGMENT,              "Segment",              QT_TRANSLATE_NOOP("elementName", "Segment") },
122       { ElementType::SYSTEM,               "System",               QT_TRANSLATE_NOOP("elementName", "System") },
123       { ElementType::COMPOUND,             "Compound",             QT_TRANSLATE_NOOP("elementName", "Compound") },
124       { ElementType::CHORD,                "Chord",                QT_TRANSLATE_NOOP("elementName", "Chord") },
125       { ElementType::SLUR,                 "Slur",                 QT_TRANSLATE_NOOP("elementName", "Slur") },
126       { ElementType::ELEMENT,              "Element",              QT_TRANSLATE_NOOP("elementName", "Element") },
127       { ElementType::ELEMENT_LIST,         "ElementList",          QT_TRANSLATE_NOOP("elementName", "Element List") },
128       { ElementType::STAFF_LIST,           "StaffList",            QT_TRANSLATE_NOOP("elementName", "Staff List") },
129       { ElementType::MEASURE_LIST,         "MeasureList",          QT_TRANSLATE_NOOP("elementName", "Measure List") },
130       { ElementType::HBOX,                 "HBox",                 QT_TRANSLATE_NOOP("elementName", "Horizontal Frame") },
131       { ElementType::VBOX,                 "VBox",                 QT_TRANSLATE_NOOP("elementName", "Vertical Frame") },
132       { ElementType::TBOX,                 "TBox",                 QT_TRANSLATE_NOOP("elementName", "Text Frame") },
133       { ElementType::FBOX,                 "FBox",                 QT_TRANSLATE_NOOP("elementName", "Fretboard Diagram Frame") },
134       { ElementType::ICON,                 "Icon",                 QT_TRANSLATE_NOOP("elementName", "Icon") },
135       { ElementType::OSSIA,                "Ossia",                QT_TRANSLATE_NOOP("elementName", "Ossia") },
136       { ElementType::BAGPIPE_EMBELLISHMENT,"BagpipeEmbellishment", QT_TRANSLATE_NOOP("elementName", "Bagpipe Embellishment") },
137       { ElementType::STICKING,             "Sticking",             QT_TRANSLATE_NOOP("elementName", "Sticking") }
138       };
139 
140 //---------------------------------------------------------
141 //   ScoreElement
142 //---------------------------------------------------------
143 
ScoreElement(const ScoreElement & se)144 ScoreElement::ScoreElement(const ScoreElement& se)
145       {
146       _score        = se._score;
147       _elementStyle = se._elementStyle;
148       if (_elementStyle) {
149             size_t n = _elementStyle->size();
150             _propertyFlagsList = new PropertyFlags[n];
151             for (size_t i = 0; i < n; ++i)
152                   _propertyFlagsList[i] = se._propertyFlagsList[i];
153             }
154       _links = 0;
155       }
156 
157 //---------------------------------------------------------
158 //   ~Element
159 //---------------------------------------------------------
160 
~ScoreElement()161 ScoreElement::~ScoreElement()
162       {
163       if (_links) {
164             _links->removeOne(this);
165             if (_links->empty()) {
166                   delete _links;
167                   _links = 0;
168                   }
169             }
170       delete[] _propertyFlagsList;
171       }
172 
173 //---------------------------------------------------------
174 //   propertyDefault
175 //---------------------------------------------------------
176 
propertyDefault(Pid pid,Tid tid) const177 QVariant ScoreElement::propertyDefault(Pid pid, Tid tid) const
178       {
179       for (const StyledProperty& spp : *textStyle(tid)) {
180             if (spp.pid == pid)
181                   return styleValue(pid, spp.sid);
182             }
183       return QVariant();
184       }
185 
186 //---------------------------------------------------------
187 //   propertyDefault
188 //---------------------------------------------------------
189 
propertyDefault(Pid pid) const190 QVariant ScoreElement::propertyDefault(Pid pid) const
191       {
192       Sid sid = getPropertyStyle(pid);
193       if (sid != Sid::NOSTYLE)
194             return styleValue(pid, sid);
195 //      qDebug("<%s>(%d) not found in <%s>", propertyQmlName(pid), int(pid), name());
196       return QVariant();
197       }
198 
199 //---------------------------------------------------------
200 //   initElementStyle
201 //---------------------------------------------------------
202 
initElementStyle(const ElementStyle * ss)203 void ScoreElement::initElementStyle(const ElementStyle* ss)
204       {
205       _elementStyle = ss;
206       size_t n      = _elementStyle->size();
207       delete[] _propertyFlagsList;
208       _propertyFlagsList = new PropertyFlags[n];
209       for (size_t i = 0; i < n; ++i)
210             _propertyFlagsList[i] = PropertyFlags::STYLED;
211       for (const StyledProperty& spp : *_elementStyle)
212 //            setProperty(spp.pid, styleValue(spp.pid, spp.sid));
213             setProperty(spp.pid, styleValue(spp.pid, getPropertyStyle(spp.pid)));
214       }
215 
216 //---------------------------------------------------------
217 //   resetProperty
218 //---------------------------------------------------------
219 
resetProperty(Pid pid)220 void ScoreElement::resetProperty(Pid pid)
221       {
222       QVariant v = propertyDefault(pid);
223       if (v.isValid()) {
224             setProperty(pid, v);
225             PropertyFlags p = propertyFlags(pid);
226             if (p == PropertyFlags::UNSTYLED)
227                   setPropertyFlags(pid, PropertyFlags::STYLED);
228             }
229       }
230 
231 //---------------------------------------------------------
232 //   undoResetProperty
233 //---------------------------------------------------------
234 
undoResetProperty(Pid id)235 void ScoreElement::undoResetProperty(Pid id)
236       {
237       PropertyFlags f = propertyFlags(id);
238       if (f == PropertyFlags::UNSTYLED)
239             f = PropertyFlags::STYLED;
240       undoChangeProperty(id, propertyDefault(id), f);
241       }
242 
243 //---------------------------------------------------------
244 //   isStyled
245 //---------------------------------------------------------
246 
isStyled(Pid pid) const247 bool ScoreElement::isStyled(Pid pid) const
248       {
249       PropertyFlags f = propertyFlags(pid);
250       return f == PropertyFlags::STYLED;
251       }
252 
253 //---------------------------------------------------------
254 //   changeProperty
255 //---------------------------------------------------------
256 
changeProperty(ScoreElement * e,Pid t,const QVariant & st,PropertyFlags ps)257 static void changeProperty(ScoreElement* e, Pid t, const QVariant& st, PropertyFlags ps)
258       {
259       if (e->getProperty(t) != st || e->propertyFlags(t) != ps) {
260             if (e->isBracketItem()) {
261                   BracketItem* bi = toBracketItem(e);
262                   e->score()->undo(new ChangeBracketProperty(bi->staff(), bi->column(), t, st, ps));
263                   }
264             else
265                   e->score()->undo(new ChangeProperty(e, t, st, ps));
266             }
267       }
268 
269 //---------------------------------------------------------
270 //   changeProperties
271 //---------------------------------------------------------
272 
changeProperties(ScoreElement * e,Pid t,const QVariant & st,PropertyFlags ps)273 static void changeProperties(ScoreElement* e, Pid t, const QVariant& st, PropertyFlags ps)
274       {
275       if (propertyLink(t)) {
276             for (ScoreElement* ee : e->linkList())
277                   changeProperty(ee, t, st, ps);
278             }
279       else
280             changeProperty(e, t, st, ps);
281       }
282 
283 //---------------------------------------------------------
284 //   undoChangeProperty
285 //---------------------------------------------------------
286 
undoChangeProperty(Pid id,const QVariant & v)287 void ScoreElement::undoChangeProperty(Pid id, const QVariant& v)
288       {
289       undoChangeProperty(id, v, propertyFlags(id));
290       }
291 
undoChangeProperty(Pid id,const QVariant & v,PropertyFlags ps)292 void ScoreElement::undoChangeProperty(Pid id, const QVariant& v, PropertyFlags ps)
293       {
294       if ((getProperty(id) == v) && (propertyFlags(id) == ps))
295             return;
296       bool doUpdateInspector = false;
297       if (id == Pid::PLACEMENT || id == Pid::HAIRPIN_TYPE) {
298             // first set property, then set offset for above/below if styled
299             changeProperties(this, id, v, ps);
300 
301             if (isStyled(Pid::OFFSET)) {
302                   // TODO: maybe it just makes more sense to do this in Element::undoChangeProperty,
303                   // but some of the overrides call ScoreElement explicitly
304                   qreal sp;
305                   if (isElement())
306                         sp = toElement(this)->spatium();
307                   else
308                         sp = score()->spatium();
309                   ScoreElement::undoChangeProperty(Pid::OFFSET, score()->styleV(getPropertyStyle(Pid::OFFSET)).toPointF() * sp);
310                   Element* e = toElement(this);
311                   e->setOffsetChanged(false);
312                   }
313             doUpdateInspector = true;
314             }
315       else if (id == Pid::SUB_STYLE) {
316             //
317             // change a list of properties
318             //
319             auto l = textStyle(Tid(v.toInt()));
320             // Change to ElementStyle defaults
321             for (const StyledProperty& p : *l) {
322                   if (p.sid == Sid::NOSTYLE)
323                         break;
324                   changeProperties(this, p.pid, score()->styleV(p.sid), PropertyFlags::STYLED);
325                   }
326             }
327       else if (id == Pid::OFFSET) {
328             // TODO: do this in caller?
329             if (isElement()) {
330                   Element* e = toElement(this);
331                   if (e->offset().y() != v.toPointF().y())
332                         e->setOffsetChanged(true, false, v.toPointF() - e->offset());
333                   }
334             }
335       changeProperties(this, id, v, ps);
336       if (id == Pid::VISIBLE) {
337             if (isNote())
338                   toNote(this)->undoChangeDotsVisible(v.toBool());
339             else if (isRest())
340                   toRest(this)->undoChangeDotsVisible(v.toBool());
341             }
342       if (id != Pid::GENERATED)
343             changeProperties(this, Pid::GENERATED, QVariant(false), PropertyFlags::NOSTYLE);
344       if (doUpdateInspector)
345             MuseScoreCore::mscoreCore->updateInspector();
346       }
347 
348 //---------------------------------------------------------
349 //   undoPushProperty
350 //---------------------------------------------------------
351 
undoPushProperty(Pid id)352 void ScoreElement::undoPushProperty(Pid id)
353       {
354       QVariant val = getProperty(id);
355       score()->undoStack()->push1(new ChangeProperty(this, id, val));
356       }
357 
358 //---------------------------------------------------------
359 //   readProperty
360 //---------------------------------------------------------
361 
readProperty(XmlReader & e,Pid id)362 void ScoreElement::readProperty(XmlReader& e, Pid id)
363       {
364       QVariant v = Ms::readProperty(id, e);
365       switch (propertyType(id)) {
366             case P_TYPE::SP_REAL:
367                   v = v.toReal() * score()->spatium();
368                   break;
369             case P_TYPE::POINT_SP:
370                   v = v.toPointF() * score()->spatium();
371                   break;
372             case P_TYPE::POINT_SP_MM:
373                   if (offsetIsSpatiumDependent())
374                         v = v.toPointF() * score()->spatium();
375                   else
376                         v = v.toPointF() * DPMM;
377                   break;
378             default:
379                   break;
380             }
381       setProperty(id, v);
382       if (isStyled(id))
383             setPropertyFlags(id, PropertyFlags::UNSTYLED);
384       }
385 
readProperty(const QStringRef & s,XmlReader & e,Pid id)386 bool ScoreElement::readProperty(const QStringRef& s, XmlReader& e, Pid id)
387       {
388       if (s == propertyName(id)) {
389             readProperty(e, id);
390             return true;
391             }
392       return false;
393       }
394 
395 //-----------------------------------------------------------------------------
396 //   writeProperty
397 //
398 //    - styled properties are never written
399 //    - unstyled properties are always written regardless of value,
400 //    - properties without style are written if different from default value
401 //-----------------------------------------------------------------------------
402 
writeProperty(XmlWriter & xml,Pid pid) const403 void ScoreElement::writeProperty(XmlWriter& xml, Pid pid) const
404       {
405       if (isStyled(pid))
406             return;
407       QVariant p = getProperty(pid);
408       if (!p.isValid()) {
409             qDebug("%s invalid property %d <%s>", name(), int(pid), propertyName(pid));
410             return;
411             }
412       PropertyFlags f = propertyFlags(pid);
413       QVariant d = (f != PropertyFlags::STYLED) ? propertyDefault(pid) : QVariant();
414 
415       if (pid == Pid::FONT_STYLE) {
416             FontStyle ds = FontStyle(d.isValid() ? d.toInt() : 0);
417             FontStyle fs = FontStyle(p.toInt());
418             if ((fs & FontStyle::Bold) != (ds & FontStyle::Bold))
419                   xml.tag("bold", fs & FontStyle::Bold);
420             if ((fs & FontStyle::Italic) != (ds & FontStyle::Italic))
421                   xml.tag("italic", fs & FontStyle::Italic);
422             if ((fs & FontStyle::Underline) != (ds & FontStyle::Underline))
423                   xml.tag("underline", fs & FontStyle::Underline);
424             return;
425             }
426 
427       if (propertyType(pid) == P_TYPE::SP_REAL) {
428             qreal f1 = p.toReal();
429             if (d.isValid() && qAbs(f1 - d.toReal()) < 0.0001)          // fuzzy compare
430                   return;
431             p = QVariant(f1/score()->spatium());
432             d = QVariant();
433             }
434       else if (propertyType(pid) == P_TYPE::POINT_SP) {
435             QPointF p1 = p.toPointF();
436             if (d.isValid()) {
437                   QPointF p2 = d.toPointF();
438                   if ( (qAbs(p1.x() - p2.x()) < 0.0001) && (qAbs(p1.y() - p2.y()) < 0.0001))
439                         return;
440                   }
441             p = QVariant(p1/score()->spatium());
442             d = QVariant();
443             }
444       else if (propertyType(pid) == P_TYPE::POINT_SP_MM) {
445             QPointF p1 = p.toPointF();
446             if (d.isValid()) {
447                   QPointF p2 = d.toPointF();
448                   if ((qAbs(p1.x() - p2.x()) < 0.0001) && (qAbs(p1.y() - p2.y()) < 0.0001))
449                         return;
450                   }
451             qreal q = offsetIsSpatiumDependent() ? score()->spatium() : DPMM;
452             p = QVariant(p1/q);
453             d = QVariant();
454             }
455       xml.tag(pid, p, d);
456       }
457 
458 //---------------------------------------------------------
459 //   propertyId
460 //---------------------------------------------------------
461 
propertyId(const QStringRef & xmlName) const462 Pid ScoreElement::propertyId(const QStringRef& xmlName) const
463       {
464       return Ms::propertyId(xmlName);
465       }
466 
467 //---------------------------------------------------------
468 //   propertyUserValue
469 //---------------------------------------------------------
470 
propertyUserValue(Pid id) const471 QString ScoreElement::propertyUserValue(Pid id) const
472       {
473       QVariant val = getProperty(id);
474       switch (propertyType(id)) {
475             case P_TYPE::POINT_SP:
476                   {
477                   QPointF p = val.toPointF();
478                   return QString("(%1, %2)").arg(p.x()).arg(p.y());
479                   }
480             case P_TYPE::DIRECTION:
481                   return toUserString(val.value<Direction>());
482             case P_TYPE::SYMID:
483                   return Sym::id2userName(val.value<SymId>());
484             default:
485                   break;
486             }
487       return val.toString();
488       }
489 
490 //---------------------------------------------------------
491 //   readStyledProperty
492 //---------------------------------------------------------
493 
readStyledProperty(XmlReader & e,const QStringRef & tag)494 bool ScoreElement::readStyledProperty(XmlReader& e, const QStringRef& tag)
495       {
496       for (const StyledProperty& spp : *styledProperties()) {
497             if (readProperty(tag, e, spp.pid))
498                   return true;
499             }
500       return false;
501       }
502 
503 //---------------------------------------------------------
504 //   writeStyledProperties
505 //---------------------------------------------------------
506 
writeStyledProperties(XmlWriter & xml) const507 void ScoreElement::writeStyledProperties(XmlWriter& xml) const
508       {
509       for (const StyledProperty& spp : *styledProperties())
510             writeProperty(xml, spp.pid);
511       }
512 
513 //---------------------------------------------------------
514 //   reset
515 //---------------------------------------------------------
516 
reset()517 void ScoreElement::reset()
518       {
519       for (const StyledProperty& spp : *styledProperties())
520             undoResetProperty(spp.pid);
521       }
522 
523 //---------------------------------------------------------
524 //   readAddConnector
525 //---------------------------------------------------------
526 
readAddConnector(ConnectorInfoReader * info,bool pasteMode)527 void ScoreElement::readAddConnector(ConnectorInfoReader* info, bool pasteMode)
528       {
529       Q_UNUSED(pasteMode);
530       qDebug("Cannot add connector %s to %s", info->connector()->name(), name());
531       }
532 
533 //---------------------------------------------------------
534 //   linkTo
535 //    link this to element
536 //---------------------------------------------------------
537 
linkTo(ScoreElement * element)538 void ScoreElement::linkTo(ScoreElement* element)
539       {
540       Q_ASSERT(element != this);
541       Q_ASSERT(!_links);
542 
543       if (element->links()) {
544             _links = element->_links;
545             Q_ASSERT(_links->contains(element));
546             }
547       else {
548             if (isStaff())
549                   _links = new LinkedElements(score(), -1); // don’t use lid
550             else
551                   _links = new LinkedElements(score());
552             _links->append(element);
553             element->_links = _links;
554             }
555       Q_ASSERT(!_links->contains(this));
556       _links->append(this);
557       }
558 
559 //---------------------------------------------------------
560 //   unlink
561 //---------------------------------------------------------
562 
unlink()563 void ScoreElement::unlink()
564       {
565       Q_ASSERT(_links);
566       Q_ASSERT(_links->contains(this));
567       _links->removeOne(this);
568 
569       // if link list is empty, remove list
570       if (_links->size() <= 1) {
571             if (!_links->empty())
572                   _links->front()->_links = 0;
573             delete _links;
574             }
575       _links = 0; // this element is not linked anymore
576       }
577 
578 //---------------------------------------------------------
579 //   isLinked
580 ///  return true if se is different and
581 ///  linked to this element
582 //---------------------------------------------------------
583 
isLinked(ScoreElement * se)584 bool ScoreElement::isLinked(ScoreElement* se)
585       {
586       return se != this && _links && _links->contains(se);
587       }
588 
589 //---------------------------------------------------------
590 //   undoUnlink
591 //---------------------------------------------------------
592 
undoUnlink()593 void ScoreElement::undoUnlink()
594       {
595       if (_links)
596             _score->undo(new Unlink(this));
597       }
598 
599 //---------------------------------------------------------
600 //   linkList
601 //---------------------------------------------------------
602 
linkList() const603 QList<ScoreElement*> ScoreElement::linkList() const
604       {
605       QList<ScoreElement*> el;
606       if (_links)
607             el = *_links;
608       else
609             el.append(const_cast<ScoreElement*>(this));
610       return el;
611       }
612 
613 //---------------------------------------------------------
614 //   LinkedElements
615 //---------------------------------------------------------
616 
LinkedElements(Score * score)617 LinkedElements::LinkedElements(Score* score)
618       {
619       _lid = score->linkId(); // create new unique id
620       }
621 
LinkedElements(Score * score,int id)622 LinkedElements::LinkedElements(Score* score, int id)
623       {
624       _lid = id;
625       if (_lid != -1)
626             score->linkId(id);      // remember used id
627       }
628 
629 //---------------------------------------------------------
630 //   setLid
631 //---------------------------------------------------------
632 
setLid(Score * score,int id)633 void LinkedElements::setLid(Score* score, int id)
634       {
635       _lid = id;
636       score->linkId(id);
637       }
638 
639 //---------------------------------------------------------
640 //   mainElement
641 //    Returns "main" linked element which is expected to
642 //    be written to the file prior to others.
643 //---------------------------------------------------------
644 
mainElement()645 ScoreElement* LinkedElements::mainElement()
646       {
647       if (isEmpty())
648             return nullptr;
649       MasterScore* ms = at(0)->masterScore();
650       const bool elements = at(0)->isElement();
651       const bool staves = at(0)->isStaff();
652       return *std::min_element(begin(), end(), [ms, elements, staves](ScoreElement* s1, ScoreElement* s2) {
653             if (s1->score() == ms && s2->score() != ms)
654                   return true;
655             if (s1->score() != s2->score())
656                   return false;
657             if (staves)
658                   return toStaff(s1)->idx() < toStaff(s2)->idx();
659             if (elements) {
660                   // Now we compare either two elements from master score
661                   // or two elements from excerpt.
662                   Element* e1 = toElement(s1);
663                   Element* e2 = toElement(s2);
664                   const int tr1 = e1->track();
665                   const int tr2 = e2->track();
666                   if (tr1 == tr2) {
667                         const Fraction tick1 = e1->tick();
668                         const Fraction tick2 = e2->tick();
669                         if (tick1 == tick2) {
670                               Measure* m1 = e1->findMeasure();
671                               Measure* m2 = e2->findMeasure();
672                               if (!m1 || !m2)
673                                     return false;
674 
675                               // MM rests are written to MSCX in the following order:
676                               // 1) first measure of MM rest (m->hasMMRest() == true);
677                               // 2) MM rest itself (m->isMMRest() == true);
678                               // 3) other measures of MM rest (m->hasMMRest() == false).
679                               //
680                               // As mainElement() must find the first element that
681                               // is going to be written to a file, MM rest writing
682                               // order should also be considered.
683 
684                               if (m1->isMMRest() == m2->isMMRest()) {
685                                     // no difference if both are MM rests or both are usual measures
686                                     return false;
687                                     }
688 
689                               // MM rests may be generated but not written (e.g. if
690                               // saving a file right after disabling MM rests)
691                               const bool mmRestsWritten = e1->score()->styleB(Sid::createMultiMeasureRests);
692 
693                               if (m1->isMMRest()) {
694                                     // m1 is earlier if m2 is *not* the first MM rest measure
695                                     return mmRestsWritten && !m2->hasMMRest();
696                                     }
697                               if (m2->isMMRest()) {
698                                     // m1 is earlier if it *is* the first MM rest measure
699                                     return !mmRestsWritten || m1->hasMMRest();
700                                     }
701                               return false;
702                               }
703                         return tick1 < tick2;
704                         }
705                   return tr1 < tr2;
706                   }
707             return false;
708             });
709       }
710 
711 //---------------------------------------------------------
712 //   masterScore
713 //---------------------------------------------------------
714 
masterScore() const715 MasterScore* ScoreElement::masterScore() const
716       {
717       return _score->masterScore();
718       }
719 
720 //---------------------------------------------------------
721 //   getPropertyFlagsIdx
722 //---------------------------------------------------------
723 
getPropertyFlagsIdx(Pid id) const724 int ScoreElement::getPropertyFlagsIdx(Pid id) const
725       {
726       int i = 0;
727       for (const StyledProperty& p : *_elementStyle) {
728             if (p.pid == id)
729                   return i;
730             ++i;
731             }
732       return -1;
733       }
734 
735 //---------------------------------------------------------
736 //   propertyFlags
737 //---------------------------------------------------------
738 
propertyFlags(Pid id) const739 PropertyFlags ScoreElement::propertyFlags(Pid id) const
740       {
741       static PropertyFlags f = PropertyFlags::NOSTYLE;
742 
743       int i = getPropertyFlagsIdx(id);
744       if (i == -1)
745             return f;
746       return _propertyFlagsList[i];
747       }
748 
749 //---------------------------------------------------------
750 //   setPropertyFlags
751 //---------------------------------------------------------
752 
setPropertyFlags(Pid id,PropertyFlags f)753 void ScoreElement::setPropertyFlags(Pid id, PropertyFlags f)
754       {
755       int i = getPropertyFlagsIdx(id);
756       if (i == -1)
757             return;
758       _propertyFlagsList[i] = f;
759       }
760 
761 //---------------------------------------------------------
762 //   getPropertyStyle
763 //---------------------------------------------------------
764 
getPropertyStyle(Pid id) const765 Sid ScoreElement::getPropertyStyle(Pid id) const
766       {
767       for (const StyledProperty& p : *_elementStyle) {
768             if (p.pid == id)
769                   return p.sid;
770             }
771       return Sid::NOSTYLE;
772       }
773 
774 //---------------------------------------------------------
775 //   styleChanged
776 //---------------------------------------------------------
777 
styleChanged()778 void ScoreElement::styleChanged()
779       {
780       for (const StyledProperty& spp : *_elementStyle) {
781             PropertyFlags f = propertyFlags(spp.pid);
782             if (f == PropertyFlags::STYLED)
783                   setProperty(spp.pid, styleValue(spp.pid, getPropertyStyle(spp.pid)));
784             }
785       }
786 
787 //---------------------------------------------------------
788 //   name
789 //---------------------------------------------------------
790 
name() const791 const char* ScoreElement::name() const
792       {
793       return name(type());
794       }
795 
796 //---------------------------------------------------------
797 //   name
798 //---------------------------------------------------------
799 
name(ElementType type)800 const char* ScoreElement::name(ElementType type)
801       {
802       return elementNames[int(type)].name;
803       }
804 
805 //---------------------------------------------------------
806 //   userName
807 //---------------------------------------------------------
808 
userName() const809 QString ScoreElement::userName() const
810       {
811       return qApp->translate("elementName", elementNames[int(type())].userName);
812       }
813 
814 //---------------------------------------------------------
815 //   name2type
816 //---------------------------------------------------------
817 
name2type(const QStringRef & s,bool silent)818 ElementType ScoreElement::name2type(const QStringRef& s, bool silent)
819       {
820       for (int i = 0; i < int(ElementType::MAXTYPE); ++i) {
821             if (s == elementNames[i].name)
822                   return ElementType(i);
823             }
824       if (!silent)
825             qDebug("unknown type <%s>", qPrintable(s.toString()));
826       return ElementType::INVALID;
827       }
828 
829 //---------------------------------------------------------
830 //   isSLineSegment
831 //---------------------------------------------------------
832 
isSLineSegment() const833 bool ScoreElement::isSLineSegment() const
834       {
835       return isHairpinSegment() || isOttavaSegment() || isPedalSegment()
836          || isTrillSegment() || isVoltaSegment() || isTextLineSegment()
837          || isGlissandoSegment() || isLetRingSegment() || isVibratoSegment() || isPalmMuteSegment();
838       }
839 
840 //---------------------------------------------------------
841 //   isText
842 //---------------------------------------------------------
843 
isTextBase() const844 bool ScoreElement::isTextBase() const
845       {
846       return type()  == ElementType::TEXT
847          || type() == ElementType::LYRICS
848          || type() == ElementType::DYNAMIC
849          || type() == ElementType::FINGERING
850          || type() == ElementType::HARMONY
851          || type() == ElementType::MARKER
852          || type() == ElementType::JUMP
853          || type() == ElementType::STAFF_TEXT
854          || type() == ElementType::SYSTEM_TEXT
855          || type() == ElementType::REHEARSAL_MARK
856          || type() == ElementType::INSTRUMENT_CHANGE
857          || type() == ElementType::FIGURED_BASS
858          || type() == ElementType::TEMPO_TEXT
859          || type() == ElementType::INSTRUMENT_NAME
860          || type() == ElementType::MEASURE_NUMBER
861          || type() == ElementType::MMREST_RANGE
862          || type() == ElementType::STICKING
863          ;
864       }
865 
866 //---------------------------------------------------------
867 //   styleValue
868 //---------------------------------------------------------
869 
styleValue(Pid pid,Sid sid) const870 QVariant ScoreElement::styleValue(Pid pid, Sid sid) const
871       {
872       switch (propertyType(pid)) {
873             case P_TYPE::SP_REAL:
874                   return score()->styleP(sid);
875             case P_TYPE::POINT_SP: {
876                   QPointF val = score()->styleV(sid).toPointF() * score()->spatium();
877                   if (isElement()) {
878                         const Element* e = toElement(this);
879                         if (e->staff() && !e->systemFlag())
880                               val *= e->staff()->mag(e->tick());
881                         }
882                   return val;
883                   }
884             case P_TYPE::POINT_SP_MM: {
885                   QPointF val = score()->styleV(sid).toPointF();
886                   if (offsetIsSpatiumDependent()) {
887                         val *= score()->spatium();
888                         if (isElement()) {
889                               const Element* e = toElement(this);
890                               if (e->staff() && !e->systemFlag())
891                                     val *= e->staff()->mag(e->tick());
892                               }
893                         }
894                   else {
895                         val *= DPMM;
896                         }
897                   return val;
898                   }
899             default:
900                   return score()->styleV(sid);
901             }
902       }
903 }
904