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