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 "hairpin.h"
24 :
25 : #include <cmath>
26 :
27 : #include "draw/types/transform.h"
28 :
29 : #include "../editing/elementeditdata.h"
30 : #include "types/typesconv.h"
31 :
32 : #include "dynamic.h"
33 : #include "dynamichairpingroup.h"
34 : #include "measure.h"
35 : #include "score.h"
36 : #include "segment.h"
37 : #include "system.h"
38 : #include "text.h"
39 :
40 : using namespace mu;
41 : using namespace muse::draw;
42 : using namespace mu::engraving;
43 :
44 : namespace mu::engraving {
45 : //---------------------------------------------------------
46 : // hairpinStyle
47 : //---------------------------------------------------------
48 :
49 : static const ElementStyle hairpinStyle {
50 : { Sid::hairpinFontFace, Pid::BEGIN_FONT_FACE },
51 : { Sid::hairpinFontSize, Pid::BEGIN_FONT_SIZE },
52 : { Sid::hairpinFontStyle, Pid::BEGIN_FONT_STYLE },
53 : { Sid::hairpinText, Pid::BEGIN_TEXT },
54 : { Sid::hairpinTextAlign, Pid::BEGIN_TEXT_ALIGN },
55 : { Sid::hairpinPosition, Pid::BEGIN_TEXT_POSITION },
56 : { Sid::hairpinFontFace, Pid::CONTINUE_FONT_FACE },
57 : { Sid::hairpinFontSize, Pid::CONTINUE_FONT_SIZE },
58 : { Sid::hairpinFontStyle, Pid::CONTINUE_FONT_STYLE },
59 : { Sid::hairpinText, Pid::CONTINUE_TEXT },
60 : { Sid::hairpinTextAlign, Pid::CONTINUE_TEXT_ALIGN },
61 : { Sid::hairpinPosition, Pid::CONTINUE_TEXT_POSITION },
62 : { Sid::hairpinFontFace, Pid::END_FONT_FACE },
63 : { Sid::hairpinFontSize, Pid::END_FONT_SIZE },
64 : { Sid::hairpinFontStyle, Pid::END_FONT_STYLE },
65 : { Sid::hairpinTextAlign, Pid::END_TEXT_ALIGN },
66 : { Sid::hairpinPosition, Pid::END_TEXT_POSITION },
67 : { Sid::hairpinLineWidth, Pid::LINE_WIDTH },
68 : { Sid::hairpinHeight, Pid::HAIRPIN_HEIGHT },
69 : { Sid::hairpinContHeight, Pid::HAIRPIN_CONT_HEIGHT },
70 : { Sid::hairpinLineStyle, Pid::LINE_STYLE },
71 : { Sid::hairpinLineDashLineLen, Pid::DASH_LINE_LEN },
72 : { Sid::hairpinLineDashGapLen, Pid::DASH_GAP_LEN },
73 : { Sid::hairpinFontSpatiumDependent, Pid::TEXT_SIZE_SPATIUM_DEPENDENT, },
74 : { Sid::hairpinEndLineArrowHeight, Pid::END_LINE_ARROW_HEIGHT },
75 : { Sid::hairpinEndLineArrowWidth, Pid::END_LINE_ARROW_WIDTH },
76 : { Sid::hairpinBeginLineArrowHeight, Pid::BEGIN_LINE_ARROW_HEIGHT },
77 : { Sid::hairpinBeginLineArrowWidth, Pid::BEGIN_LINE_ARROW_WIDTH },
78 : { Sid::hairpinEndFilledArrowHeight, Pid::END_FILLED_ARROW_HEIGHT },
79 : { Sid::hairpinEndFilledArrowWidth, Pid::END_FILLED_ARROW_WIDTH },
80 : { Sid::hairpinBeginFilledArrowHeight, Pid::BEGIN_FILLED_ARROW_HEIGHT },
81 : { Sid::hairpinBeginFilledArrowWidth, Pid::BEGIN_FILLED_ARROW_WIDTH },
82 : { Sid::hairpinMusicalSymbolSize, Pid::BEGIN_TEXT_MUSIC_SYMBOLS_SIZE },
83 : { Sid::hairpinMusicalSymbolSize, Pid::CONTINUE_TEXT_MUSIC_SYMBOLS_SIZE },
84 : { Sid::hairpinMusicalSymbolSize, Pid::END_TEXT_MUSIC_SYMBOLS_SIZE },
85 : { Sid::dummyMusicalSymbolsScale, Pid::BEGIN_TEXT_MUSICAL_SYMBOLS_SCALE },
86 : { Sid::dummyMusicalSymbolsScale, Pid::CONTINUE_TEXT_MUSICAL_SYMBOLS_SCALE },
87 : { Sid::dummyMusicalSymbolsScale, Pid::END_TEXT_MUSICAL_SYMBOLS_SCALE },
88 : };
89 :
90 : //---------------------------------------------------------
91 : // HairpinSegment
92 : //---------------------------------------------------------
93 :
94 283 : HairpinSegment::HairpinSegment(Hairpin* sp)
95 283 : : TextLineBaseSegment(ElementType::HAIRPIN_SEGMENT, sp, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
96 : {
97 283 : m_text->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
98 283 : m_endText->setTextStyleType(propertyDefault(Pid::TEXT_STYLE).value<TextStyleType>());
99 283 : }
100 :
101 0 : bool HairpinSegment::acceptDrop(EditData& data) const
102 : {
103 0 : EngravingItem* e = data.dropElement;
104 0 : if (e->isDynamic()) {
105 0 : return true;
106 : }
107 0 : return false;
108 : }
109 :
110 0 : EngravingItem* HairpinSegment::drop(Transaction& tx, EditData& data)
111 : {
112 0 : EngravingItem* e = data.dropElement;
113 0 : if (!e->isDynamic()) {
114 0 : return nullptr;
115 : }
116 :
117 0 : if (EngravingItem* item = ldata()->itemSnappedAfter()) {
118 0 : if (item->isDynamic()) {
119 0 : return item->drop(tx, data);
120 : }
121 : }
122 :
123 0 : Fraction endTick = hairpin()->tick2();
124 0 : Measure* measure = score()->tick2measure(endTick);
125 0 : Segment* segment = measure->getChordRestOrTimeTickSegment(endTick);
126 :
127 0 : Dynamic* d = toDynamic(e->clone());
128 0 : d->setTrack(hairpin()->track());
129 0 : d->setOwnershipParent(segment);
130 0 : d->setVoiceAssignment(hairpin()->voiceAssignment());
131 0 : score()->undoAddElement(d);
132 :
133 0 : return d;
134 : }
135 :
136 0 : void HairpinSegment::setPropertyFlags(Pid id, PropertyFlags f)
137 : {
138 0 : int i = getPropertyFlagsIdx(id);
139 0 : if (i == -1) {
140 : // Can happen (for example when edit-dragging the aperture) that we're editing
141 : // the HairpinSegment but the relevant property belongs to the Hairpin.
142 0 : hairpin()->setPropertyFlags(id, f);
143 0 : return;
144 : }
145 0 : m_propertyFlagsList[i] = f;
146 : }
147 :
148 : //---------------------------------------------------------
149 : // gripsPositions
150 : //---------------------------------------------------------
151 :
152 0 : int HairpinSegment::gripsCount() const
153 : {
154 0 : return hairpin()->isLineType() ? 3 : 4;
155 : }
156 :
157 0 : std::vector<PointF> HairpinSegment::gripsPositions(const EditData&) const
158 : {
159 0 : double _spatium = spatium();
160 0 : double x = pos2().x();
161 0 : if (x < _spatium) { // minimum size of hairpin
162 0 : x = _spatium;
163 : }
164 0 : double y = pos2().y();
165 0 : PointF p(x, y);
166 :
167 0 : std::vector<PointF> grips(gripsCount());
168 0 : PointF pp(pagePos());
169 0 : grips[int(Grip::START)] = pp;
170 0 : grips[int(Grip::END)] = p + pp;
171 0 : grips[int(Grip::MIDDLE)] = p * .5 + pp;
172 :
173 0 : if (!hairpin()->isLineType()) {
174 : // Calc PointF for Grip Aperture
175 0 : Transform doRotation;
176 0 : PointF gripLineAperturePoint;
177 0 : double h1 = hairpin()->hairpinHeight().val() * spatium() * .5;
178 0 : double len = sqrt(x * x + y * y);
179 0 : doRotation.rotateRadians(asin(y / len));
180 : double lineApertureX;
181 0 : double offsetX = 10; // Horizontal offset for x Grip
182 0 : if (len < offsetX * 3) { // For small hairpin, offset = 30% of len
183 0 : offsetX = len / 3; // else offset is fixed to 10
184 : }
185 0 : if (hairpin()->hairpinType() == HairpinType::CRESC_HAIRPIN) {
186 0 : lineApertureX = len - offsetX; // End of CRESCENDO - Offset
187 : } else {
188 0 : lineApertureX = offsetX; // Begin of DIMINUENDO + Offset
189 : }
190 0 : double lineApertureH = (len - offsetX) * h1 / len; // Vertical position for y grip
191 0 : gripLineAperturePoint.setX(lineApertureX);
192 0 : gripLineAperturePoint.setY(lineApertureH);
193 0 : gripLineAperturePoint = doRotation.map(gripLineAperturePoint);
194 :
195 : // End calc position grip aperture
196 0 : grips[int(Grip::APERTURE)] = gripLineAperturePoint + pp;
197 : }
198 :
199 0 : return grips;
200 0 : }
201 :
202 : //---------------------------------------------------------
203 : // getDragGroup
204 : //---------------------------------------------------------
205 :
206 0 : std::unique_ptr<ElementGroup> HairpinSegment::getDragGroup(std::function<bool(const EngravingItem*)> isDragged)
207 : {
208 0 : if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged)) {
209 0 : return g;
210 0 : }
211 0 : return TextLineBaseSegment::getDragGroup(isDragged);
212 : }
213 :
214 : //---------------------------------------------------------
215 : // startDragGrip
216 : //---------------------------------------------------------
217 :
218 0 : void HairpinSegment::startDragGrip(EditData& ed)
219 : {
220 0 : TextLineBaseSegment::startDragGrip(ed);
221 0 : ElementEditDataPtr eed = ed.getData(this);
222 :
223 0 : eed->pushProperty(Pid::HAIRPIN_HEIGHT);
224 0 : eed->pushProperty(Pid::HAIRPIN_CONT_HEIGHT);
225 0 : }
226 :
227 : //---------------------------------------------------------
228 : // dragGrip
229 : //---------------------------------------------------------
230 :
231 0 : void HairpinSegment::dragGrip(EditData& ed)
232 : {
233 0 : if (ed.curGrip == Grip::APERTURE) {
234 0 : double newHeight = hairpin()->hairpinHeight().val() + ed.delta.y() / spatium() / .5;
235 0 : if (newHeight < 0.5) {
236 0 : newHeight = 0.5;
237 : }
238 0 : hairpin()->setHairpinHeight(Spatium(newHeight));
239 0 : triggerLayout();
240 : }
241 0 : TextLineBaseSegment::dragGrip(ed);
242 0 : }
243 :
244 : //---------------------------------------------------------
245 : // propertyDelegate
246 : //---------------------------------------------------------
247 :
248 2401 : EngravingObject* HairpinSegment::propertyDelegate(Pid pid) const
249 : {
250 2401 : if (pid == Pid::HAIRPIN_TYPE
251 2401 : || pid == Pid::VELO_CHANGE
252 2401 : || pid == Pid::VELO_CHANGE_METHOD
253 2401 : || pid == Pid::SINGLE_NOTE_DYNAMICS
254 2401 : || pid == Pid::HAIRPIN_CIRCLEDTIP
255 2401 : || pid == Pid::HAIRPIN_HEIGHT
256 2401 : || pid == Pid::HAIRPIN_CONT_HEIGHT
257 2401 : || pid == Pid::LINE_STYLE
258 2401 : || pid == Pid::VOICE_ASSIGNMENT
259 2241 : || pid == Pid::DIRECTION
260 2241 : || pid == Pid::CENTER_BETWEEN_STAVES
261 : ) {
262 356 : return spanner();
263 : }
264 2045 : return TextLineBaseSegment::propertyDelegate(pid);
265 : }
266 :
267 : //---------------------------------------------------------
268 : // getPropertyStyle
269 : //---------------------------------------------------------
270 :
271 385 : Sid HairpinSegment::getPropertyStyle(Pid pid) const
272 : {
273 385 : switch (pid) {
274 0 : case Pid::BEGIN_TEXT:
275 0 : switch (hairpin()->hairpinType()) {
276 0 : default:
277 0 : return Sid::hairpinText;
278 0 : case HairpinType::CRESC_LINE:
279 0 : return Sid::hairpinCrescText;
280 0 : case HairpinType::DIM_LINE:
281 0 : return Sid::hairpinDecrescText;
282 : }
283 : break;
284 0 : case Pid::CONTINUE_TEXT:
285 0 : switch (hairpin()->hairpinType()) {
286 0 : default:
287 0 : return Sid::hairpinText;
288 0 : case HairpinType::CRESC_LINE:
289 0 : return Sid::hairpinCrescContText;
290 0 : case HairpinType::DIM_LINE:
291 0 : return Sid::hairpinDecrescContText;
292 : }
293 : break;
294 0 : case Pid::LINE_STYLE:
295 0 : return hairpin()->isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
296 0 : case Pid::DASH_LINE_LEN:
297 0 : return hairpin()->isLineType() ? Sid::hairpinLineDashLineLen : Sid::hairpinDashLineLen;
298 0 : case Pid::DASH_GAP_LEN:
299 0 : return hairpin()->isLineType() ? Sid::hairpinLineDashGapLen : Sid::hairpinDashGapLen;
300 385 : default:
301 385 : break;
302 : }
303 385 : return TextLineBaseSegment::getPropertyStyle(pid);
304 : }
305 :
306 467 : EngravingItem* HairpinSegment::findElementToSnapBefore(bool ignoreInvisible) const
307 : {
308 467 : TextBase* startDynOrExpr = findStartDynamicOrExpression(ignoreInvisible);
309 467 : if (startDynOrExpr) {
310 106 : return startDynOrExpr;
311 : }
312 :
313 361 : Hairpin* thisHairpin = hairpin();
314 361 : Fraction startTick = hairpin()->tick();
315 :
316 361 : auto intervals = score()->spannerMap().findOverlapping(startTick.ticks(), startTick.ticks());
317 1398 : for (auto interval : intervals) {
318 1066 : Spanner* spanner = interval.value;
319 1989 : bool isValidHairpin = spanner->isHairpin() && !spanner->segmentsEmpty() && spanner != thisHairpin
320 1989 : && (spanner->addToSkyline() || !ignoreInvisible);
321 1066 : if (!isValidHairpin) {
322 610 : continue;
323 : }
324 456 : Hairpin* precedingHairpin = toHairpin(spanner);
325 456 : bool endsMatch = precedingHairpin->track() == thisHairpin->track()
326 33 : && precedingHairpin->tick2() == startTick
327 31 : && precedingHairpin->placeAbove() == thisHairpin->placeAbove()
328 489 : && toHairpin(spanner)->voiceAssignment() == thisHairpin->voiceAssignment();
329 456 : if (endsMatch && precedingHairpin->snapToItemAfter()) {
330 29 : return precedingHairpin->backSegment();
331 : }
332 : }
333 :
334 332 : return nullptr;
335 361 : }
336 :
337 465 : EngravingItem* HairpinSegment::findElementToSnapAfter(bool ignoreInvisible, bool requirePlayable) const
338 : {
339 : // Note: we don't need to look for a hairpin after.
340 : // It is the next hairpin which looks for a hairpin before.
341 465 : return findEndDynamicOrExpression(ignoreInvisible, requirePlayable);
342 : }
343 :
344 0 : void HairpinSegment::endDragGrip(EditData& ed)
345 : {
346 0 : if (ed.isHairpinDragCreatedFromDynamic) {
347 0 : undoResetProperty(Pid::OFFSET);
348 0 : undoResetProperty(Pid::OFFSET2);
349 : }
350 :
351 0 : LineSegment::endDragGrip(ed);
352 0 : }
353 :
354 467 : TextBase* HairpinSegment::findStartDynamicOrExpression(bool ignoreInvisible) const
355 : {
356 467 : Fraction refTick = hairpin()->tick();
357 467 : Measure* measure = score()->tick2measure(refTick);
358 467 : if (!measure) {
359 0 : return nullptr;
360 : }
361 :
362 467 : std::vector<TextBase*> dynamicsAndExpr;
363 467 : dynamicsAndExpr.reserve(2);
364 :
365 2976 : for (Segment* segment = measure->last(); segment; segment = segment->prev1()) {
366 2907 : Fraction segmentTick = segment->tick();
367 2907 : if (segmentTick > refTick) {
368 1739 : continue;
369 : }
370 1168 : if (segmentTick < refTick) {
371 398 : break;
372 : }
373 1222 : for (EngravingItem* item : segment->annotations()) {
374 346 : if (!item->isDynamic() && !item->isExpression()) {
375 103 : continue;
376 : }
377 243 : if (ignoreInvisible && !item->addToSkyline()) {
378 2 : continue;
379 : }
380 241 : bool endsMatch = item->track() == hairpin()->track()
381 107 : && item->placement() == placement()
382 589 : && item->getProperty(Pid::VOICE_ASSIGNMENT) == hairpin()->getProperty(Pid::VOICE_ASSIGNMENT);
383 241 : if (endsMatch) {
384 106 : dynamicsAndExpr.push_back(toTextBase(item));
385 : }
386 : }
387 876 : if (dynamicsAndExpr.size() > 0) {
388 106 : break;
389 : }
390 : }
391 :
392 467 : if (dynamicsAndExpr.size() == 0) {
393 361 : return nullptr;
394 : }
395 :
396 106 : if (dynamicsAndExpr.size() > 1) {
397 0 : std::sort(dynamicsAndExpr.begin(), dynamicsAndExpr.end(), [](TextBase* item1, TextBase* item2) {
398 0 : bool dynamicBeforeExpression = item1->isDynamic() && item2->isExpression();
399 0 : bool oneIsAnchorToPrevious = item1->isDynamic() && toDynamic(item1)->anchorToEndOfPrevious()
400 0 : && item2->isDynamic() && !toDynamic(item2)->anchorToEndOfPrevious();
401 0 : return dynamicBeforeExpression || oneIsAnchorToPrevious;
402 : });
403 : }
404 :
405 106 : return dynamicsAndExpr.back();
406 467 : }
407 :
408 465 : TextBase* HairpinSegment::findEndDynamicOrExpression(bool ignoreInvisible, bool requirePlayable) const
409 : {
410 465 : Fraction refTick = hairpin()->tick2();
411 465 : Measure* measure = score()->tick2measure(refTick - Fraction::eps());
412 465 : if (!measure) {
413 0 : return nullptr;
414 : }
415 :
416 465 : std::vector<TextBase*> dynamicsAndExpr;
417 465 : dynamicsAndExpr.reserve(2);
418 :
419 3249 : for (Segment* segment = measure->first(); segment; segment = segment->next1()) {
420 3236 : Fraction segmentTick = segment->tick();
421 3236 : if (segmentTick < refTick) {
422 1968 : continue;
423 : }
424 1268 : if (segmentTick > refTick) {
425 452 : break;
426 : }
427 1028 : for (EngravingItem* item : segment->annotations()) {
428 141 : if (!item->isDynamic() && !item->isExpression()) {
429 64 : continue;
430 : }
431 77 : if (ignoreInvisible && !item->addToSkyline()) {
432 3 : continue;
433 : }
434 74 : if (requirePlayable && (!item->isDynamic() || !toDynamic(item)->playDynamic())) {
435 0 : continue;
436 : }
437 74 : bool endsMatch = item->track() == hairpin()->track()
438 74 : && item->placement() == placement()
439 222 : && item->getProperty(Pid::VOICE_ASSIGNMENT) == hairpin()->getProperty(Pid::VOICE_ASSIGNMENT);
440 74 : if (endsMatch) {
441 71 : dynamicsAndExpr.push_back(toTextBase(item));
442 : }
443 : }
444 887 : if (dynamicsAndExpr.size() > 0) {
445 71 : break;
446 : }
447 : }
448 :
449 465 : if (dynamicsAndExpr.size() == 0) {
450 394 : return nullptr;
451 : }
452 :
453 71 : if (dynamicsAndExpr.size() > 1) {
454 0 : std::sort(dynamicsAndExpr.begin(), dynamicsAndExpr.end(), [](TextBase* item1, TextBase* item2) {
455 0 : bool dynamicBeforeExpression = item1->isDynamic() && item2->isExpression();
456 0 : bool oneIsAnchorToPrevious = item1->isDynamic() && toDynamic(item1)->anchorToEndOfPrevious()
457 0 : && item2->isDynamic() && !toDynamic(item2)->anchorToEndOfPrevious();
458 0 : return dynamicBeforeExpression || oneIsAnchorToPrevious;
459 : });
460 : }
461 :
462 71 : return dynamicsAndExpr.front();
463 465 : }
464 :
465 41926 : Sid Hairpin::getPropertyStyle(Pid pid) const
466 : {
467 41926 : switch (pid) {
468 1052 : case Pid::BEGIN_TEXT:
469 1052 : switch (hairpinType()) {
470 1035 : default:
471 1035 : return Sid::hairpinText;
472 10 : case HairpinType::CRESC_LINE:
473 10 : return Sid::hairpinCrescText;
474 7 : case HairpinType::DIM_LINE:
475 7 : return Sid::hairpinDecrescText;
476 : }
477 : break;
478 1076 : case Pid::CONTINUE_TEXT:
479 1076 : switch (hairpinType()) {
480 1058 : default:
481 1058 : return Sid::hairpinText;
482 11 : case HairpinType::CRESC_LINE:
483 11 : return Sid::hairpinCrescContText;
484 7 : case HairpinType::DIM_LINE:
485 7 : return Sid::hairpinDecrescContText;
486 : }
487 : break;
488 1093 : case Pid::LINE_STYLE:
489 1093 : return isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
490 1090 : case Pid::DASH_LINE_LEN:
491 1090 : return isLineType() ? Sid::hairpinLineDashLineLen : Sid::hairpinDashLineLen;
492 1090 : case Pid::DASH_GAP_LEN:
493 1090 : return isLineType() ? Sid::hairpinLineDashGapLen : Sid::hairpinDashGapLen;
494 0 : case Pid::PLACEMENT:
495 0 : return Sid::hairpinPlacement;
496 36525 : default:
497 36525 : break;
498 : }
499 36525 : return TextLineBase::getPropertyStyle(pid);
500 : }
501 :
502 : //---------------------------------------------------------
503 : // Hairpin
504 : //---------------------------------------------------------
505 :
506 362 : Hairpin::Hairpin(DummyParentOr<EngravingItem> parent)
507 362 : : TextLineBase(ElementType::HAIRPIN, parent)
508 : {
509 362 : initElementStyle(&hairpinStyle);
510 :
511 362 : resetProperty(Pid::BEGIN_TEXT_PLACE);
512 362 : resetProperty(Pid::END_TEXT_PLACE);
513 362 : resetProperty(Pid::CONTINUE_TEXT_PLACE);
514 362 : resetProperty(Pid::BEGIN_HOOK_HEIGHT);
515 362 : resetProperty(Pid::END_HOOK_HEIGHT);
516 362 : resetProperty(Pid::HAIRPIN_TYPE);
517 362 : resetProperty(Pid::LINE_VISIBLE);
518 :
519 362 : m_hairpinCircledTip = false;
520 362 : m_veloChange = 0;
521 362 : m_singleNoteDynamics = true;
522 362 : m_veloChangeMethod = ChangeMethod::NORMAL;
523 362 : }
524 :
525 38 : DynamicType Hairpin::dynamicTypeFrom() const
526 : {
527 38 : if (m_hairpinType == HairpinType::CRESC_HAIRPIN && hairpinCircledTip()) {
528 1 : return DynamicType::N;
529 : }
530 :
531 37 : muse::ByteArray ba = beginText().toAscii();
532 37 : return TConv::dynamicType(ba.constChar());
533 37 : }
534 :
535 38 : DynamicType Hairpin::dynamicTypeTo() const
536 : {
537 38 : if (m_hairpinType == HairpinType::DIM_HAIRPIN && hairpinCircledTip()) {
538 1 : return DynamicType::N;
539 : }
540 :
541 37 : muse::ByteArray ba = endText().toAscii();
542 37 : return TConv::dynamicType(ba.constChar());
543 37 : }
544 :
545 0 : const Dynamic* Hairpin::dynamicSnappedBefore() const
546 : {
547 0 : const LineSegment* seg = frontSegment();
548 0 : if (!seg) {
549 0 : return nullptr;
550 : }
551 :
552 0 : const EngravingItem* item = seg->ldata()->itemSnappedBefore();
553 0 : if (!item || !item->isDynamic()) {
554 0 : return nullptr;
555 : }
556 :
557 0 : return toDynamic(item);
558 : }
559 :
560 0 : const Dynamic* Hairpin::dynamicSnappedAfter() const
561 : {
562 0 : const LineSegment* seg = backSegment();
563 0 : if (!seg) {
564 0 : return nullptr;
565 : }
566 :
567 0 : const EngravingItem* item = seg->ldata()->itemSnappedAfter();
568 0 : if (!item || !item->isDynamic()) {
569 0 : return nullptr;
570 : }
571 :
572 0 : return toDynamic(item);
573 : }
574 :
575 : //---------------------------------------------------------
576 : // setHairpinType
577 : //---------------------------------------------------------
578 :
579 687 : void Hairpin::setHairpinType(HairpinType val)
580 : {
581 687 : if (m_hairpinType == val) {
582 251 : return;
583 : }
584 436 : m_hairpinType = val;
585 436 : styleChanged();
586 : }
587 :
588 : //---------------------------------------------------------
589 : // createLineSegment
590 : //---------------------------------------------------------
591 :
592 : static const ElementStyle hairpinSegmentStyle {
593 : { Sid::hairpinMinDistance, Pid::MIN_DISTANCE },
594 : };
595 :
596 283 : LineSegment* Hairpin::createLineSegment()
597 : {
598 283 : HairpinSegment* h = new HairpinSegment(this);
599 283 : h->setTrack(track());
600 283 : h->initElementStyle(&hairpinSegmentStyle);
601 283 : return h;
602 : }
603 :
604 : //---------------------------------------------------------
605 : // getProperty
606 : //---------------------------------------------------------
607 :
608 7779 : PropertyValue Hairpin::getProperty(Pid id) const
609 : {
610 7779 : switch (id) {
611 148 : case Pid::HAIRPIN_CIRCLEDTIP:
612 148 : return m_hairpinCircledTip;
613 0 : case Pid::HAIRPIN_TYPE:
614 0 : return int(m_hairpinType);
615 148 : case Pid::VELO_CHANGE:
616 148 : return m_veloChange;
617 6 : case Pid::HAIRPIN_HEIGHT:
618 6 : return m_hairpinHeight;
619 6 : case Pid::HAIRPIN_CONT_HEIGHT:
620 6 : return m_hairpinContHeight;
621 148 : case Pid::SINGLE_NOTE_DYNAMICS:
622 148 : return m_singleNoteDynamics;
623 148 : case Pid::VELO_CHANGE_METHOD:
624 148 : return m_veloChangeMethod;
625 950 : case Pid::VOICE_ASSIGNMENT:
626 950 : return voiceAssignment();
627 814 : case Pid::CENTER_BETWEEN_STAVES:
628 814 : return centerBetweenStaves();
629 619 : case Pid::DIRECTION:
630 619 : return direction();
631 148 : case Pid::SNAP_BEFORE:
632 148 : return snapToItemBefore();
633 148 : case Pid::SNAP_AFTER:
634 148 : return snapToItemAfter();
635 :
636 4496 : default:
637 4496 : return TextLineBase::getProperty(id);
638 : }
639 : }
640 :
641 : //---------------------------------------------------------
642 : // setProperty
643 : //---------------------------------------------------------
644 :
645 44473 : bool Hairpin::setProperty(Pid id, const PropertyValue& v)
646 : {
647 44473 : switch (id) {
648 0 : case Pid::HAIRPIN_CIRCLEDTIP:
649 0 : m_hairpinCircledTip = v.toBool();
650 0 : break;
651 362 : case Pid::HAIRPIN_TYPE:
652 362 : setHairpinType(HairpinType(v.toInt()));
653 362 : break;
654 0 : case Pid::VELO_CHANGE:
655 0 : m_veloChange = v.toInt();
656 0 : break;
657 1091 : case Pid::HAIRPIN_HEIGHT:
658 1091 : m_hairpinHeight = v.value<Spatium>();
659 1091 : break;
660 1091 : case Pid::HAIRPIN_CONT_HEIGHT:
661 1091 : m_hairpinContHeight = v.value<Spatium>();
662 1091 : break;
663 0 : case Pid::SINGLE_NOTE_DYNAMICS:
664 0 : m_singleNoteDynamics = v.toBool();
665 0 : break;
666 0 : case Pid::VELO_CHANGE_METHOD:
667 0 : m_veloChangeMethod = v.value<ChangeMethod>();
668 0 : break;
669 5 : case Pid::VOICE_ASSIGNMENT:
670 5 : setVoiceAssignment(v.value<VoiceAssignment>());
671 5 : break;
672 9 : case Pid::CENTER_BETWEEN_STAVES:
673 9 : setCenterBetweenStaves(v.value<AutoOnOff>());
674 9 : break;
675 55 : case Pid::DIRECTION:
676 55 : setDirection(v.value<DirectionV>());
677 55 : break;
678 0 : case Pid::SNAP_BEFORE:
679 0 : setSnapToItemBefore(v.toBool());
680 0 : break;
681 0 : case Pid::SNAP_AFTER:
682 0 : setSnapToItemAfter(v.toBool());
683 0 : break;
684 41860 : default:
685 41860 : return TextLineBase::setProperty(id, v);
686 : }
687 2613 : triggerLayout();
688 2613 : return true;
689 : }
690 :
691 : //---------------------------------------------------------
692 : // propertyDefault
693 : //---------------------------------------------------------
694 :
695 8380 : PropertyValue Hairpin::propertyDefault(Pid id) const
696 : {
697 8380 : switch (id) {
698 148 : case Pid::HAIRPIN_CIRCLEDTIP:
699 148 : return false;
700 :
701 148 : case Pid::VELO_CHANGE:
702 148 : return 0;
703 :
704 19 : case Pid::BEGIN_TEXT:
705 19 : if (m_hairpinType == HairpinType::CRESC_LINE) {
706 1 : return style().styleV(Sid::hairpinCrescText);
707 : }
708 18 : if (m_hairpinType == HairpinType::DIM_LINE) {
709 2 : return style().styleV(Sid::hairpinDecrescText);
710 : }
711 32 : return String();
712 :
713 16 : case Pid::CONTINUE_TEXT:
714 16 : if (m_hairpinType == HairpinType::CRESC_LINE) {
715 0 : return style().styleV(Sid::hairpinCrescContText);
716 : }
717 16 : if (m_hairpinType == HairpinType::DIM_LINE) {
718 0 : return style().styleV(Sid::hairpinDecrescContText);
719 : }
720 32 : return String();
721 :
722 148 : case Pid::END_TEXT:
723 296 : return String();
724 :
725 1530 : case Pid::BEGIN_TEXT_PLACE:
726 : case Pid::CONTINUE_TEXT_PLACE:
727 : case Pid::END_TEXT_PLACE:
728 1530 : return TextPlace::LEFT;
729 :
730 444 : case Pid::BEGIN_TEXT_OFFSET:
731 : case Pid::CONTINUE_TEXT_OFFSET:
732 : case Pid::END_TEXT_OFFSET:
733 444 : return PropertyValue::fromValue(PointF());
734 :
735 296 : case Pid::BEGIN_HOOK_TYPE:
736 : case Pid::END_HOOK_TYPE:
737 296 : return HookType::NONE;
738 :
739 1020 : case Pid::BEGIN_HOOK_HEIGHT:
740 : case Pid::END_HOOK_HEIGHT:
741 1020 : return 1.9_sp;
742 :
743 510 : case Pid::LINE_VISIBLE:
744 510 : return true;
745 :
746 362 : case Pid::HAIRPIN_TYPE:
747 362 : return int(HairpinType::CRESC_HAIRPIN);
748 :
749 148 : case Pid::SINGLE_NOTE_DYNAMICS:
750 148 : return true;
751 :
752 148 : case Pid::VELO_CHANGE_METHOD:
753 148 : return ChangeMethod::NORMAL;
754 :
755 0 : case Pid::PLACEMENT:
756 0 : return style().styleV(Sid::hairpinPlacement);
757 :
758 148 : case Pid::VOICE_ASSIGNMENT:
759 296 : return VoiceAssignment::ALL_VOICE_IN_INSTRUMENT;
760 :
761 148 : case Pid::CENTER_BETWEEN_STAVES:
762 296 : return AutoOnOff::AUTO;
763 :
764 148 : case Pid::DIRECTION:
765 148 : return DirectionV::AUTO;
766 :
767 148 : case Pid::SNAP_BEFORE:
768 148 : return true;
769 148 : case Pid::SNAP_AFTER:
770 148 : return true;
771 :
772 566 : case Pid::TEXT_STYLE:
773 566 : return TextStyleType::HAIRPIN;
774 :
775 2137 : default:
776 2137 : return TextLineBase::propertyDefault(id);
777 : }
778 : }
779 :
780 : //---------------------------------------------------------
781 : // accessibleInfo
782 : //---------------------------------------------------------
783 :
784 0 : String Hairpin::accessibleInfo() const
785 : {
786 0 : return String(u"%1: %2").arg(TextLineBase::accessibleInfo(), translatedSubtypeUserName());
787 : }
788 :
789 935 : PointF Hairpin::linePos(Grip grip, System** system) const
790 : {
791 935 : bool start = grip == Grip::START;
792 :
793 935 : Segment* segment = start ? startSegment() : endSegment();
794 935 : if (!segment) {
795 0 : return PointF();
796 : }
797 :
798 935 : if (!start) {
799 467 : Fraction curTick = segment->tick();
800 467 : Segment* prevSeg = segment->prev1(SegmentType::EndBarLine);
801 467 : if (prevSeg && prevSeg->tick() == curTick) {
802 228 : segment = prevSeg;
803 : }
804 : }
805 :
806 935 : *system = segment->measure()->system();
807 935 : double x = segment->x() + segment->measure()->x();
808 935 : if (!start) {
809 467 : x -= spatium();
810 : }
811 :
812 935 : return PointF(x, 0.0);
813 : }
814 :
815 0 : void Hairpin::reset()
816 : {
817 0 : undoResetProperty(Pid::DIRECTION);
818 0 : undoResetProperty(Pid::CENTER_BETWEEN_STAVES);
819 0 : TextLineBase::reset();
820 0 : }
821 :
822 1 : muse::TranslatableString Hairpin::subtypeUserName() const
823 : {
824 1 : switch (hairpinType()) {
825 1 : case HairpinType::CRESC_HAIRPIN:
826 1 : return TranslatableString("engraving/hairpintype", "Crescendo hairpin");
827 0 : case HairpinType::DIM_HAIRPIN:
828 0 : return TranslatableString("engraving/hairpintype", "Diminuendo hairpin");
829 0 : case HairpinType::CRESC_LINE:
830 0 : return TranslatableString("engraving/hairpintype", "Crescendo line");
831 0 : case HairpinType::DIM_LINE:
832 0 : return TranslatableString("engraving/hairpintype", "Diminuendo line");
833 0 : default:
834 0 : return TranslatableString("engraving/hairpintype", "Custom");
835 : }
836 : }
837 :
838 489 : Sid Hairpin::defaultPosSid() const
839 : {
840 489 : if (isLineType()) {
841 26 : return placeAbove() ? Sid::hairpinLinePosAbove : Sid::hairpinLinePosBelow;
842 : }
843 463 : return placeAbove() ? Sid::hairpinPosAbove : Sid::hairpinPosBelow;
844 : }
845 : }
|