Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include "ottava.h"
24 :
25 : #include "types/translatablestring.h"
26 :
27 : #include "chordrest.h"
28 : #include "measure.h"
29 : #include "score.h"
30 : #include "staff.h"
31 : #include "system.h"
32 : #include "text.h"
33 :
34 : #include "log.h"
35 :
36 : using namespace mu::engraving;
37 :
38 : //---------------------------------------------------------
39 : // ottavaStyle
40 : //---------------------------------------------------------
41 :
42 : static const ElementStyle ottavaStyle {
43 : { Sid::ottavaNumbersOnly, Pid::NUMBERS_ONLY },
44 : { Sid::ottava8VAPlacement, Pid::PLACEMENT },
45 : { Sid::ottava8VAText, Pid::BEGIN_TEXT },
46 : { Sid::ottava8VAContinueText, Pid::CONTINUE_TEXT },
47 : { Sid::ottavaHookAbove, Pid::END_HOOK_HEIGHT },
48 : { Sid::ottavaFontFace, Pid::BEGIN_FONT_FACE },
49 : { Sid::ottavaFontFace, Pid::CONTINUE_FONT_FACE },
50 : { Sid::ottavaFontFace, Pid::END_FONT_FACE },
51 : { Sid::ottavaFontSize, Pid::BEGIN_FONT_SIZE },
52 : { Sid::ottavaFontSize, Pid::CONTINUE_FONT_SIZE },
53 : { Sid::ottavaFontSize, Pid::END_FONT_SIZE },
54 : { Sid::ottavaFontStyle, Pid::BEGIN_FONT_STYLE },
55 : { Sid::ottavaFontStyle, Pid::CONTINUE_FONT_STYLE },
56 : { Sid::ottavaFontStyle, Pid::END_FONT_STYLE },
57 : { Sid::ottavaMusicalSymbolsScale, Pid::BEGIN_TEXT_MUSICAL_SYMBOLS_SCALE },
58 : { Sid::ottavaMusicalSymbolsScale, Pid::CONTINUE_TEXT_MUSICAL_SYMBOLS_SCALE },
59 : { Sid::ottavaMusicalSymbolsScale, Pid::END_TEXT_MUSICAL_SYMBOLS_SCALE },
60 : { Sid::dummyMusicalSymbolSize, Pid::BEGIN_TEXT_MUSIC_SYMBOLS_SIZE },
61 : { Sid::dummyMusicalSymbolSize, Pid::CONTINUE_TEXT_MUSIC_SYMBOLS_SIZE },
62 : { Sid::dummyMusicalSymbolSize, Pid::END_TEXT_MUSIC_SYMBOLS_SIZE },
63 : { Sid::ottavaTextAlignAbove, Pid::BEGIN_TEXT_ALIGN },
64 : { Sid::ottavaTextAlignAbove, Pid::CONTINUE_TEXT_ALIGN },
65 : { Sid::ottavaTextAlignAbove, Pid::END_TEXT_ALIGN },
66 : { Sid::ottavaPosition, Pid::BEGIN_TEXT_POSITION },
67 : { Sid::ottavaPosition, Pid::CONTINUE_TEXT_POSITION },
68 : { Sid::ottavaPosition, Pid::END_TEXT_POSITION },
69 : { Sid::ottavaLineWidth, Pid::LINE_WIDTH },
70 : { Sid::ottavaLineStyle, Pid::LINE_STYLE },
71 : { Sid::ottavaDashLineLen, Pid::DASH_LINE_LEN },
72 : { Sid::ottavaDashGapLen, Pid::DASH_GAP_LEN },
73 : { Sid::ottavaFontSpatiumDependent, Pid::TEXT_SIZE_SPATIUM_DEPENDENT },
74 : { Sid::ottavaEndLineArrowHeight, Pid::END_LINE_ARROW_HEIGHT },
75 : { Sid::ottavaEndLineArrowWidth, Pid::END_LINE_ARROW_WIDTH },
76 : { Sid::ottavaBeginLineArrowHeight, Pid::BEGIN_LINE_ARROW_HEIGHT },
77 : { Sid::ottavaBeginLineArrowWidth, Pid::BEGIN_LINE_ARROW_WIDTH },
78 : { Sid::ottavaEndFilledArrowHeight, Pid::END_FILLED_ARROW_HEIGHT },
79 : { Sid::ottavaEndFilledArrowWidth, Pid::END_FILLED_ARROW_WIDTH },
80 : { Sid::ottavaBeginFilledArrowHeight, Pid::BEGIN_FILLED_ARROW_HEIGHT },
81 : { Sid::ottavaBeginFilledArrowWidth, Pid::BEGIN_FILLED_ARROW_WIDTH },
82 : };
83 :
84 110 : OttavaSegment::OttavaSegment(Ottava* sp)
85 110 : : TextLineBaseSegment(ElementType::OTTAVA_SEGMENT, sp, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
86 : {
87 110 : m_text->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
88 110 : m_endText->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
89 110 : }
90 :
91 : //---------------------------------------------------------
92 : // propertyDelegate
93 : //---------------------------------------------------------
94 :
95 883 : EngravingObject* OttavaSegment::propertyDelegate(Pid pid) const
96 : {
97 883 : if (pid == Pid::OTTAVA_TYPE || pid == Pid::NUMBERS_ONLY) {
98 0 : return spanner();
99 : }
100 883 : return TextLineBaseSegment::propertyDelegate(pid);
101 : }
102 :
103 : //---------------------------------------------------------
104 : // setOttavaType
105 : //---------------------------------------------------------
106 :
107 252 : void Ottava::setOttavaType(OttavaType val)
108 : {
109 252 : if (m_ottavaType == val) {
110 213 : return;
111 : }
112 39 : m_ottavaType = val;
113 39 : styleChanged();
114 : }
115 :
116 : //---------------------------------------------------------
117 : // setNumbersOnly
118 : //---------------------------------------------------------
119 :
120 0 : void Ottava::setNumbersOnly(bool val)
121 : {
122 0 : m_numbersOnly = val;
123 0 : }
124 :
125 : //---------------------------------------------------------
126 : // setPlacement
127 : //---------------------------------------------------------
128 :
129 0 : void Ottava::setPlacement(PlacementV p)
130 : {
131 0 : TextLineBase::setPlacement(p);
132 0 : }
133 :
134 : //---------------------------------------------------------
135 : // undoChangeProperty
136 : //---------------------------------------------------------
137 :
138 0 : void OttavaSegment::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
139 : {
140 0 : if (id == Pid::OTTAVA_TYPE || id == Pid::NUMBERS_ONLY) {
141 0 : EngravingObject::undoChangeProperty(id, v, ps);
142 : } else {
143 0 : EngravingObject::undoChangeProperty(id, v, ps);
144 : }
145 0 : }
146 :
147 5 : void Ottava::undoChangeProperty(Pid id, const PropertyValue& v, PropertyFlags ps)
148 : {
149 5 : if (id == Pid::OTTAVA_TYPE || id == Pid::NUMBERS_ONLY) {
150 0 : TextLineBase::undoChangeProperty(id, v, ps);
151 0 : styleChanged(); // these properties may change style settings
152 : } else {
153 5 : TextLineBase::undoChangeProperty(id, v, ps);
154 : }
155 5 : }
156 :
157 9491 : Sid Ottava::getPropertyStyle(Pid pid) const
158 : {
159 : static_assert(int(OttavaType::OTTAVA_22MB) - int(OttavaType::OTTAVA_8VA) == 5);
160 :
161 : static const std::vector<Sid> ss = {
162 : Sid::ottava8VAPlacement,
163 : Sid::ottava8VAnoText,
164 : Sid::ottava8VAnoContinueText,
165 : Sid::ottava8VBPlacement,
166 : Sid::ottava8VBnoText,
167 : Sid::ottava8VBnoContinueText,
168 : Sid::ottava15MAPlacement,
169 : Sid::ottava15MAnoText,
170 : Sid::ottava15MAnoContinueText,
171 : Sid::ottava15MBPlacement,
172 : Sid::ottava15MBnoText,
173 : Sid::ottava15MBnoContinueText,
174 : Sid::ottava22MAPlacement,
175 : Sid::ottava22MAnoText,
176 : Sid::ottava22MAnoContinueText,
177 : Sid::ottava22MBPlacement,
178 : Sid::ottava22MBnoText,
179 : Sid::ottava22MBnoContinueText,
180 :
181 : Sid::ottava8VAPlacement,
182 : Sid::ottava8VAText,
183 : Sid::ottava8VAContinueText,
184 : Sid::ottava8VBPlacement,
185 : Sid::ottava8VBText,
186 : Sid::ottava8VBContinueText,
187 : Sid::ottava15MAPlacement,
188 : Sid::ottava15MAText,
189 : Sid::ottava15MAContinueText,
190 : Sid::ottava15MBPlacement,
191 : Sid::ottava15MBText,
192 : Sid::ottava15MBContinueText,
193 : Sid::ottava22MAPlacement,
194 : Sid::ottava22MAText,
195 : Sid::ottava22MAContinueText,
196 : Sid::ottava22MBPlacement,
197 : Sid::ottava22MBText,
198 : Sid::ottava22MBContinueText,
199 9501 : };
200 :
201 9491 : size_t idx = size_t(m_ottavaType) * 3 + (m_numbersOnly ? 0 : ss.size() / 2);
202 9491 : switch (pid) {
203 243 : case Pid::PLACEMENT:
204 243 : return ss[idx];
205 239 : case Pid::BEGIN_TEXT:
206 239 : return ss[idx + 1]; // BEGIN_TEXT
207 239 : case Pid::CONTINUE_TEXT:
208 239 : return ss[idx + 2]; // CONTINUE_TEXT
209 239 : case Pid::END_HOOK_HEIGHT:
210 239 : if (isStyled(Pid::PLACEMENT)) {
211 238 : return style().styleV(ss[idx]).value<PlacementV>() == PlacementV::ABOVE
212 238 : ? Sid::ottavaHookAbove
213 238 : : Sid::ottavaHookBelow;
214 : } else {
215 1 : return placeAbove() ? Sid::ottavaHookAbove : Sid::ottavaHookBelow;
216 : }
217 714 : case Pid::BEGIN_TEXT_ALIGN:
218 : case Pid::CONTINUE_TEXT_ALIGN:
219 : case Pid::END_TEXT_ALIGN:
220 714 : return placeAbove() ? Sid::ottavaTextAlignAbove : Sid::ottavaTextAlignBelow;
221 7817 : default:
222 7817 : return TextLineBase::getPropertyStyle(pid);
223 : }
224 : }
225 :
226 : //---------------------------------------------------------
227 : // Ottava
228 : //---------------------------------------------------------
229 :
230 187 : Ottava::Ottava(DummyParentOr<EngravingItem> parent)
231 187 : : TextLineBase(ElementType::OTTAVA, parent, ElementFlag::ON_STAFF | ElementFlag::MOVABLE)
232 : {
233 187 : m_ottavaType = OttavaType::OTTAVA_8VA;
234 187 : m_numbersOnly = false;
235 187 : setBeginTextPlace(TextPlace::LEFT);
236 187 : setContinueTextPlace(TextPlace::LEFT);
237 187 : setEndHookType(HookType::HOOK_90);
238 187 : setLineVisible(true);
239 187 : setBeginHookHeight(0_sp);
240 187 : setEndText(u"");
241 :
242 187 : initElementStyle(&ottavaStyle);
243 187 : }
244 :
245 4 : Ottava::Ottava(const Ottava& o)
246 4 : : TextLineBase(o)
247 : {
248 4 : setOttavaType(o.m_ottavaType);
249 4 : m_numbersOnly = o.m_numbersOnly;
250 4 : }
251 :
252 : //---------------------------------------------------------
253 : // pitchShift
254 : //---------------------------------------------------------
255 :
256 223 : int Ottava::pitchShift() const
257 : {
258 223 : return ottavaDefault[int(m_ottavaType)].shift;
259 : }
260 :
261 : //---------------------------------------------------------
262 : // createLineSegment
263 : //---------------------------------------------------------
264 :
265 : static const ElementStyle ottavaSegmentStyle {
266 : { Sid::ottavaMinDistance, Pid::MIN_DISTANCE },
267 : };
268 :
269 110 : LineSegment* Ottava::createLineSegment()
270 : {
271 110 : OttavaSegment* os = new OttavaSegment(this);
272 110 : os->setTrack(track());
273 110 : os->initElementStyle(&ottavaSegmentStyle);
274 110 : return os;
275 : }
276 :
277 : //---------------------------------------------------------
278 : // getProperty
279 : //---------------------------------------------------------
280 :
281 2814 : PropertyValue Ottava::getProperty(Pid propertyId) const
282 : {
283 2814 : switch (propertyId) {
284 108 : case Pid::OTTAVA_TYPE:
285 108 : return int(ottavaType());
286 :
287 6 : case Pid::NUMBERS_ONLY:
288 6 : return m_numbersOnly;
289 :
290 108 : case Pid::END_TEXT_PLACE: // HACK
291 108 : return TextPlace::LEFT;
292 :
293 2592 : default:
294 2592 : break;
295 : }
296 2592 : return TextLineBase::getProperty(propertyId);
297 : }
298 :
299 : //---------------------------------------------------------
300 : // setProperty
301 : //---------------------------------------------------------
302 :
303 9359 : bool Ottava::setProperty(Pid propertyId, const PropertyValue& val)
304 : {
305 9359 : switch (propertyId) {
306 0 : case Pid::PLAY:
307 0 : setPlaySpanner(val.toBool());
308 0 : staff()->updateOttava();
309 0 : break;
310 :
311 4 : case Pid::OTTAVA_TYPE:
312 4 : setOttavaType(OttavaType(val.toInt()));
313 4 : break;
314 :
315 238 : case Pid::NUMBERS_ONLY:
316 238 : m_numbersOnly = val.toBool();
317 238 : break;
318 :
319 4 : case Pid::SPANNER_TICKS:
320 4 : setTicks(val.value<Fraction>());
321 4 : staff()->updateOttava();
322 4 : break;
323 :
324 2 : case Pid::SPANNER_TICK:
325 2 : setTick(val.value<Fraction>());
326 2 : staff()->updateOttava();
327 2 : break;
328 :
329 9111 : default:
330 9111 : if (!TextLineBase::setProperty(propertyId, val)) {
331 0 : return false;
332 : }
333 9111 : break;
334 : }
335 9359 : triggerLayout();
336 9359 : return true;
337 : }
338 :
339 : //---------------------------------------------------------
340 : // propertyDefault
341 : //---------------------------------------------------------
342 :
343 2902 : PropertyValue Ottava::propertyDefault(Pid pid) const
344 : {
345 2902 : switch (pid) {
346 108 : case Pid::OTTAVA_TYPE:
347 108 : return PropertyValue();
348 108 : case Pid::END_HOOK_TYPE:
349 108 : return HookType::HOOK_90;
350 108 : case Pid::LINE_VISIBLE:
351 108 : return true;
352 324 : case Pid::BEGIN_TEXT_OFFSET:
353 : case Pid::CONTINUE_TEXT_OFFSET:
354 : case Pid::END_TEXT_OFFSET:
355 324 : return PropertyValue::fromValue(PointF());
356 324 : case Pid::BEGIN_TEXT_PLACE:
357 : case Pid::CONTINUE_TEXT_PLACE:
358 : case Pid::END_TEXT_PLACE:
359 324 : return TextPlace::LEFT;
360 108 : case Pid::BEGIN_HOOK_TYPE:
361 108 : return HookType::NONE;
362 108 : case Pid::BEGIN_HOOK_HEIGHT:
363 108 : return 0_sp;
364 108 : case Pid::END_TEXT:
365 216 : return String();
366 7 : case Pid::PLACEMENT:
367 7 : return styleValue(Pid::PLACEMENT, getPropertyStyle(Pid::PLACEMENT));
368 220 : case Pid::TEXT_STYLE:
369 220 : return TextStyleType::OTTAVA;
370 :
371 1379 : default:
372 1379 : return TextLineBase::propertyDefault(pid);
373 : }
374 : }
375 :
376 : //---------------------------------------------------------
377 : // accessibleInfo
378 : //---------------------------------------------------------
379 :
380 0 : String Ottava::accessibleInfo() const
381 : {
382 0 : return String(u"%1: %2").arg(EngravingItem::accessibleInfo(), String::fromUtf8(ottavaDefault[static_cast<int>(ottavaType())].name));
383 : }
384 :
385 : //---------------------------------------------------------
386 : // subtypeUserName
387 : //---------------------------------------------------------
388 :
389 0 : muse::TranslatableString Ottava::subtypeUserName() const
390 : {
391 0 : return ottavaDefault[int(ottavaType())].userName;
392 : }
393 :
394 0 : void OttavaSegment::rebaseOffsetsOnAnchorChanged(Grip grip, const PointF& oldPos, System* sys)
395 : {
396 0 : if (grip == Grip::MIDDLE || grip == Grip::END) {
397 0 : ottava()->computeEndElement();
398 : }
399 0 : LineSegment::rebaseOffsetsOnAnchorChanged(grip, oldPos, sys);
400 0 : }
401 :
402 : //---------------------------------------------------------
403 : // ottavaTypeName
404 : //---------------------------------------------------------
405 :
406 108 : const char* Ottava::ottavaTypeName(OttavaType type)
407 : {
408 108 : return ottavaDefault[int(type)].name;
409 : }
410 :
411 308 : PointF Ottava::linePos(Grip grip, System** system) const
412 : {
413 308 : if (grip == Grip::START) {
414 155 : return TextLineBase::linePos(grip, system);
415 : }
416 :
417 153 : bool extendToEndOfDuration = false; // TODO: style
418 153 : if (extendToEndOfDuration) {
419 0 : return SLine::linePos(grip, system);
420 : }
421 :
422 153 : ChordRest* endCr = endElement() && endElement()->isChordRest() ? toChordRest(endElement()) : nullptr;
423 153 : if (!endCr) {
424 0 : return PointF();
425 : }
426 :
427 153 : Segment* seg = endCr->segment();
428 :
429 153 : *system = seg->measure()->system();
430 :
431 : // End 1sp after the right edge of the end chord, but don't overlap followig segments
432 153 : Shape staffShape = seg->staffShape(endCr->vStaffIdx());
433 556 : staffShape.remove_if([](ShapeElement& el) { return el.height() == 0; });
434 153 : double x = staffShape.right() + seg->x() + seg->measure()->x() + spatium();
435 153 : Segment* followingCRseg = score()->tick2segment(endCr->endTick(), true, SegmentType::ChordRest);
436 153 : if (followingCRseg && followingCRseg->system() == seg->system()) {
437 141 : x = std::min(x, followingCRseg->x() + followingCRseg->measure()->x());
438 : }
439 :
440 153 : x -= 0.5 * absoluteFromSpatium(lineWidth());
441 :
442 153 : return PointF(x, 0.0);
443 153 : }
444 :
445 313 : void Ottava::doComputeEndElement()
446 : {
447 313 : setEndElement(score()->findChordRestEndingBeforeTickInStaff(tick2(), track2staff(track())));
448 313 : }
449 :
450 165 : Sid Ottava::defaultPosSid() const
451 : {
452 165 : return placeAbove() ? Sid::ottavaPosAbove : Sid::ottavaPosBelow;
453 : }
|