1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2002-2011 Werner Schweer
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2
9 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "hairpin.h"
14 #include "dynamichairpingroup.h"
15 #include "style.h"
16 #include "xml.h"
17 #include "utils.h"
18 #include "score.h"
19 #include "measure.h"
20 #include "segment.h"
21 #include "system.h"
22 #include "undo.h"
23 #include "staff.h"
24 #include "mscore.h"
25 #include "chord.h"
26 #include "changeMap.h"
27
28 namespace Ms {
29
30 //---------------------------------------------------------
31 // hairpinStyle
32 //---------------------------------------------------------
33
34 static const ElementStyle hairpinStyle {
35 { Sid::hairpinFontFace, Pid::BEGIN_FONT_FACE },
36 { Sid::hairpinFontSize, Pid::BEGIN_FONT_SIZE },
37 { Sid::hairpinFontStyle, Pid::BEGIN_FONT_STYLE },
38 { Sid::hairpinText, Pid::BEGIN_TEXT },
39 { Sid::hairpinTextAlign, Pid::BEGIN_TEXT_ALIGN },
40 { Sid::hairpinFontFace, Pid::CONTINUE_FONT_FACE },
41 { Sid::hairpinFontSize, Pid::CONTINUE_FONT_SIZE },
42 { Sid::hairpinFontStyle, Pid::CONTINUE_FONT_STYLE },
43 { Sid::hairpinText, Pid::CONTINUE_TEXT },
44 { Sid::hairpinTextAlign, Pid::CONTINUE_TEXT_ALIGN },
45 { Sid::hairpinFontFace, Pid::END_FONT_FACE },
46 { Sid::hairpinFontSize, Pid::END_FONT_SIZE },
47 { Sid::hairpinFontStyle, Pid::END_FONT_STYLE },
48 { Sid::hairpinTextAlign, Pid::END_TEXT_ALIGN },
49 { Sid::hairpinLineWidth, Pid::LINE_WIDTH },
50 { Sid::hairpinHeight, Pid::HAIRPIN_HEIGHT },
51 { Sid::hairpinContHeight, Pid::HAIRPIN_CONT_HEIGHT },
52 { Sid::hairpinPlacement, Pid::PLACEMENT },
53 { Sid::hairpinPosBelow, Pid::OFFSET },
54 { Sid::hairpinLineStyle, Pid::LINE_STYLE },
55 };
56
57 //---------------------------------------------------------
58 // HairpinSegment
59 //---------------------------------------------------------
60
HairpinSegment(Spanner * sp,Score * s)61 HairpinSegment::HairpinSegment(Spanner* sp, Score* s)
62 : TextLineBaseSegment(sp, s, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
63 {
64 }
65
acceptDrop(EditData & data) const66 bool HairpinSegment::acceptDrop(EditData& data) const
67 {
68 Element* e = data.dropElement;
69 if (e->isDynamic())
70 return true;
71 return false;
72 }
73
drop(EditData & data)74 Element* HairpinSegment::drop(EditData& data)
75 {
76 Element* e = data.dropElement;
77 if (e->isDynamic()) {
78 Dynamic* d = toDynamic(e);
79 hairpin()->undoChangeProperty(Pid::END_TEXT, d->xmlText());
80 }
81 return 0;
82 }
83
84 //---------------------------------------------------------
85 // layout
86 //---------------------------------------------------------
87
layout()88 void HairpinSegment::layout()
89 {
90 const qreal _spatium = spatium();
91 const int _trck = track();
92 Dynamic* sd = nullptr;
93 Dynamic* ed = nullptr;
94 qreal dymax = hairpin()->placeBelow() ? -10000.0 : 10000.0;
95 if (autoplace() && !score()->isPalette()) {
96 Segment* start = hairpin()->startSegment();
97 Segment* end = hairpin()->endSegment();
98 // Try to fit between adjacent dynamics
99 qreal minDynamicsDistance = score()->styleP(Sid::autoplaceHairpinDynamicsDistance) * staff()->mag(tick());
100 const System* sys = system();
101 if (isSingleType() || isBeginType()) {
102 if (start && start->system() == sys) {
103 sd = toDynamic(start->findAnnotation(ElementType::DYNAMIC, _trck, _trck));
104 if (!sd) {
105 // Dynamics might have been added to the previous
106 // segment rather than exactly to hairpin start,
107 // search in that segment too.
108 start = start->prev(SegmentType::ChordRest);
109 if (start && start->system() == sys)
110 sd = toDynamic(start->findAnnotation(ElementType::DYNAMIC, _trck, _trck));
111 }
112 }
113 if (sd && sd->addToSkyline() && sd->placement() == hairpin()->placement()) {
114 const qreal sdRight = sd->bbox().right() + sd->pos().x()
115 + sd->segment()->pos().x() + sd->measure()->pos().x();
116 const qreal dist = qMax(sdRight - pos().x() + minDynamicsDistance, 0.0);
117 rxpos() += dist;
118 rxpos2() -= dist;
119 // prepare to align vertically
120 dymax = sd->pos().y();
121 }
122 }
123 if (isSingleType() || isEndType()) {
124 if (end && end->tick() < sys->endTick() && start != end) {
125 // checking ticks rather than systems
126 // systems may be unknown at layout stage.
127 ed = toDynamic(end->findAnnotation(ElementType::DYNAMIC, _trck, _trck));
128 }
129 if (ed && ed->addToSkyline() && ed->placement() == hairpin()->placement()) {
130 const qreal edLeft = ed->bbox().left() + ed->pos().x()
131 + ed->segment()->pos().x() + ed->measure()->pos().x();
132 const qreal dist = edLeft - pos2().x() - pos().x() - minDynamicsDistance;
133 const qreal extendThreshold = 3.0 * _spatium; // TODO: style setting
134 if (dist < 0.0)
135 rxpos2() += dist; // always shorten
136 else if (dist >= extendThreshold && hairpin()->endText().isEmpty() && minDynamicsDistance > 0.0)
137 rxpos2() += dist; // lengthen only if appropriate
138 // prepare to align vertically
139 if (hairpin()->placeBelow())
140 dymax = qMax(dymax, ed->pos().y());
141 else
142 dymax = qMin(dymax, ed->pos().y());
143 }
144 }
145 }
146
147 HairpinType type = hairpin()->hairpinType();
148 if (hairpin()->isLineType()) {
149 twoLines = false;
150 TextLineBaseSegment::layout();
151 drawCircledTip = false;
152 circledTipRadius = 0.0;
153 }
154 else {
155 twoLines = true;
156
157 hairpin()->setBeginTextAlign(Align::LEFT | Align::VCENTER);
158 hairpin()->setEndTextAlign(Align::RIGHT | Align::VCENTER);
159
160 qreal x1 = 0.0;
161 TextLineBaseSegment::layout();
162 if (!_text->empty())
163 x1 = _text->width() + _spatium * .5;
164
165 QTransform t;
166 qreal h1 = hairpin()->hairpinHeight().val() * _spatium * .5;
167 qreal h2 = hairpin()->hairpinContHeight().val() * _spatium * .5;
168
169 qreal x = pos2().x();
170 if (!_endText->empty())
171 x -= (_endText->width() + _spatium * .5); // 0.5 spatium distance
172 if (x < _spatium) // minimum size of hairpin
173 x = _spatium;
174 qreal y = pos2().y();
175 qreal len = sqrt(x * x + y * y);
176 t.rotateRadians(asin(y/len));
177
178 drawCircledTip = hairpin()->hairpinCircledTip();
179 circledTipRadius = drawCircledTip ? 0.6 * _spatium * .5 : 0.0;
180
181 QLine l1, l2;
182
183 switch (type) {
184 case HairpinType::CRESC_HAIRPIN: {
185 switch (spannerSegmentType()) {
186 case SpannerSegmentType::SINGLE:
187 case SpannerSegmentType::BEGIN:
188 l1.setLine(x1 + circledTipRadius * 2.0, 0.0, len, h1);
189 l2.setLine(x1 + circledTipRadius * 2.0, 0.0, len, -h1);
190 circledTip.setX(x1 + circledTipRadius );
191 circledTip.setY(0.0);
192 break;
193
194 case SpannerSegmentType::MIDDLE:
195 case SpannerSegmentType::END:
196 drawCircledTip = false;
197 l1.setLine(x1, h2, len, h1);
198 l2.setLine(x1, -h2, len, -h1);
199 break;
200 }
201 }
202 break;
203 case HairpinType::DECRESC_HAIRPIN: {
204 switch (spannerSegmentType()) {
205 case SpannerSegmentType::SINGLE:
206 case SpannerSegmentType::END:
207 l1.setLine(x1, h1, len - circledTipRadius * 2, 0.0);
208 l2.setLine(x1, -h1, len - circledTipRadius * 2, 0.0);
209 circledTip.setX(len - circledTipRadius);
210 circledTip.setY(0.0);
211 break;
212 case SpannerSegmentType::BEGIN:
213 case SpannerSegmentType::MIDDLE:
214 drawCircledTip = false;
215 l1.setLine(x1, h1, len, + h2);
216 l2.setLine(x1, -h1, len, - h2);
217 break;
218 }
219 }
220 break;
221 default:
222 break;
223 }
224
225 // Do Coord rotation
226 l1 = t.map(l1);
227 l2 = t.map(l2);
228 if (drawCircledTip )
229 circledTip = t.map(circledTip);
230
231 points[0] = l1.p1();
232 points[1] = l1.p2();
233 points[2] = l2.p1();
234 points[3] = l2.p2();
235 npoints = 4;
236
237 QRectF r = QRectF(l1.p1(), l1.p2()).normalized() | QRectF(l2.p1(), l2.p2()).normalized();
238 if (!_text->empty())
239 r |= _text->bbox();
240 if (!_endText->empty())
241 r |= _endText->bbox().translated(x + _endText->bbox().width(), 0.0);
242 qreal w = point(score()->styleS(Sid::hairpinLineWidth));
243 setbbox(r.adjusted(-w*.5, -w*.5, w, w));
244 }
245 if (!parent()) {
246 rpos() = QPointF();
247 roffset() = QPointF();
248 return;
249 }
250
251 if (isStyled(Pid::OFFSET))
252 roffset() = hairpin()->propertyDefault(Pid::OFFSET).toPointF();
253
254 // rebase vertical offset on drag
255 qreal rebase = 0.0;
256 if (offsetChanged() != OffsetChange::NONE)
257 rebase = rebaseOffset();
258
259 if (autoplace()) {
260 qreal ymax = pos().y();
261 qreal d;
262 qreal ddiff = hairpin()->isLineType() ? 0.0 : _spatium * 0.5;
263
264 qreal sp = spatium();
265 qreal md = minDistance().val() * sp;
266
267 bool above = spanner()->placeAbove();
268 SkylineLine sl(!above);
269 Shape sh = shape();
270 sl.add(sh.translated(pos()));
271 if (above) {
272 d = system()->topDistance(staffIdx(), sl);
273 if (d > -md)
274 ymax -= d + md;
275 // align hairpin with dynamics
276 if (!hairpin()->diagonal())
277 ymax = qMin(ymax, dymax - ddiff);
278 }
279 else {
280 d = system()->bottomDistance(staffIdx(), sl);
281 if (d > -md)
282 ymax += d + md;
283 // align hairpin with dynamics
284 if (!hairpin()->diagonal())
285 ymax = qMax(ymax, dymax - ddiff);
286 }
287 qreal yd = ymax - pos().y();
288 if (yd != 0.0) {
289 if (offsetChanged() != OffsetChange::NONE) {
290 // user moved element within the skyline
291 // we may need to adjust minDistance, yd, and/or offset
292 qreal adj = pos().y() + rebase;
293 bool inStaff = above ? sh.bottom() + adj > 0.0 : sh.top() + adj < staff()->height();
294 rebaseMinDistance(md, yd, sp, rebase, above, inStaff);
295 }
296 rypos() += yd;
297 }
298
299 if (hairpin()->addToSkyline() && !hairpin()->diagonal()) {
300 // align dynamics with hairpin
301 if (sd && sd->autoplace() && sd->placement() == hairpin()->placement()) {
302 qreal ny = y() + ddiff - sd->offset().y();
303 if (sd->placeAbove())
304 ny = qMin(ny, sd->ipos().y());
305 else
306 ny = qMax(ny, sd->ipos().y());
307 if (sd->ipos().y() != ny) {
308 sd->rypos() = ny;
309 if (sd->addToSkyline()) {
310 Segment* s = sd->segment();
311 Measure* m = s->measure();
312 QRectF r = sd->bbox().translated(sd->pos());
313 s->staffShape(sd->staffIdx()).add(r);
314 r = sd->bbox().translated(sd->pos() + s->pos() + m->pos());
315 m->system()->staff(sd->staffIdx())->skyline().add(r);
316 }
317 }
318 }
319 if (ed && ed->autoplace() && ed->placement() == hairpin()->placement()) {
320 qreal ny = y() + ddiff - ed->offset().y();
321 if (ed->placeAbove())
322 ny = qMin(ny, ed->ipos().y());
323 else
324 ny = qMax(ny, ed->ipos().y());
325 if (ed->ipos().y() != ny) {
326 ed->rypos() = ny;
327 if (ed->addToSkyline()) {
328 Segment* s = ed->segment();
329 Measure* m = s->measure();
330 QRectF r = ed->bbox().translated(ed->pos());
331 s->staffShape(ed->staffIdx()).add(r);
332 r = ed->bbox().translated(ed->pos() + s->pos() + m->pos());
333 m->system()->staff(ed->staffIdx())->skyline().add(r);
334 }
335 }
336 }
337 }
338 }
339 setOffsetChanged(false);
340 }
341
342 //---------------------------------------------------------
343 // shape
344 //---------------------------------------------------------
345
shape() const346 Shape HairpinSegment::shape() const
347 {
348 switch (hairpin()->hairpinType()) {
349 case HairpinType::CRESC_HAIRPIN:
350 case HairpinType::DECRESC_HAIRPIN:
351 return Shape(bbox());
352 case HairpinType::DECRESC_LINE:
353 case HairpinType::CRESC_LINE:
354 default:
355 return TextLineBaseSegment::shape();
356 }
357 }
358
359 //---------------------------------------------------------
360 // gripsPositions
361 //---------------------------------------------------------
362
gripsPositions(const EditData &) const363 std::vector<QPointF> HairpinSegment::gripsPositions(const EditData&) const
364 {
365 qreal _spatium = spatium();
366 qreal x = pos2().x();
367 if (x < _spatium) // minimum size of hairpin
368 x = _spatium;
369 qreal y = pos2().y();
370 QPointF p(x, y);
371
372 // Calc QPointF for Grip Aperture
373 QTransform doRotation;
374 QPointF gripLineAperturePoint;
375 qreal h1 = hairpin()->hairpinHeight().val() * spatium() * .5;
376 qreal len = sqrt( x * x + y * y );
377 doRotation.rotateRadians(asin(y/len));
378 qreal lineApertureX;
379 qreal offsetX = 10; // Horizontal offset for x Grip
380 if (len < offsetX * 3) // For small hairpin, offset = 30% of len
381 offsetX = len/3; // else offset is fixed to 10
382
383 if (hairpin()->hairpinType() == HairpinType::CRESC_HAIRPIN)
384 lineApertureX = len - offsetX; // End of CRESCENDO - Offset
385 else
386 lineApertureX = offsetX; // Begin of DECRESCENDO + Offset
387 qreal lineApertureH = ( len - offsetX ) * h1/len; // Vertical position for y grip
388 gripLineAperturePoint.setX( lineApertureX );
389 gripLineAperturePoint.setY( lineApertureH );
390 gripLineAperturePoint = doRotation.map(gripLineAperturePoint);
391
392 std::vector<QPointF> grips(gripsCount());
393
394 // End calc position grip aperture
395 QPointF pp(pagePos());
396 grips[int(Grip::START)] = pp;
397 grips[int(Grip::END)] = p + pp;
398 grips[int(Grip::MIDDLE)] = p * .5 + pp;
399 grips[int(Grip::APERTURE)] = gripLineAperturePoint + pp;
400
401 return grips;
402 }
403
404 //---------------------------------------------------------
405 // getDragGroup
406 //---------------------------------------------------------
407
getDragGroup(std::function<bool (const Element *)> isDragged)408 std::unique_ptr<ElementGroup> HairpinSegment::getDragGroup(std::function<bool(const Element*)> isDragged)
409 {
410 if (auto g = HairpinWithDynamicsDragGroup::detectFor(this, isDragged))
411 return g;
412 return TextLineBaseSegment::getDragGroup(isDragged);
413 }
414
415 //---------------------------------------------------------
416 // startEditDrag
417 //---------------------------------------------------------
418
startEditDrag(EditData & ed)419 void HairpinSegment::startEditDrag(EditData& ed)
420 {
421 TextLineBaseSegment::startEditDrag(ed);
422 ElementEditData* eed = ed.getData(this);
423
424 eed->pushProperty(Pid::HAIRPIN_HEIGHT);
425 eed->pushProperty(Pid::HAIRPIN_CONT_HEIGHT);
426 }
427
428 //---------------------------------------------------------
429 // editDrag
430 //---------------------------------------------------------
431
editDrag(EditData & ed)432 void HairpinSegment::editDrag(EditData& ed)
433 {
434 if (ed.curGrip == Grip::APERTURE) {
435 qreal newHeight = hairpin()->hairpinHeight().val() + ed.delta.y()/spatium()/.5;
436 if (newHeight < 0.5)
437 newHeight = 0.5;
438 hairpin()->setHairpinHeight(Spatium(newHeight));
439 triggerLayout();
440 }
441 TextLineBaseSegment::editDrag(ed);
442 }
443
444 //---------------------------------------------------------
445 // draw
446 //---------------------------------------------------------
447
draw(QPainter * painter) const448 void HairpinSegment::draw(QPainter* painter) const
449 {
450 TextLineBaseSegment::draw(painter);
451
452 #if 0
453 QColor color;
454 if ((selected() && !(score() && score()->printing())) || !hairpin()->visible())
455 color = curColor();
456 else
457 color = hairpin()->lineColor();
458 #endif
459 QColor color = curColor(hairpin()->visible(), hairpin()->lineColor());
460 qreal w = hairpin()->lineWidth();
461 if (staff())
462 w *= staff()->mag(hairpin()->tick());
463 QPen pen(color, w, hairpin()->lineStyle());
464 painter->setPen(pen);
465
466 if (drawCircledTip) {
467 painter->setBrush(Qt::NoBrush);
468 painter->drawEllipse( circledTip,circledTipRadius,circledTipRadius );
469 }
470 }
471
472 //---------------------------------------------------------
473 // propertyDelegate
474 //---------------------------------------------------------
475
propertyDelegate(Pid pid)476 Element* HairpinSegment::propertyDelegate(Pid pid)
477 {
478 if (pid == Pid::HAIRPIN_TYPE
479 || pid == Pid::VELO_CHANGE
480 || pid == Pid::VELO_CHANGE_METHOD
481 || pid == Pid::SINGLE_NOTE_DYNAMICS
482 || pid == Pid::HAIRPIN_CIRCLEDTIP
483 || pid == Pid::HAIRPIN_HEIGHT
484 || pid == Pid::HAIRPIN_CONT_HEIGHT
485 || pid == Pid::DYNAMIC_RANGE
486 || pid == Pid::LINE_STYLE
487 )
488 return spanner();
489 return TextLineBaseSegment::propertyDelegate(pid);
490 }
491
492 //---------------------------------------------------------
493 // getPropertyStyle
494 //---------------------------------------------------------
495
getPropertyStyle(Pid pid) const496 Sid HairpinSegment::getPropertyStyle(Pid pid) const
497 {
498 switch (pid) {
499 case Pid::OFFSET:
500 if (hairpin()->isLineType())
501 return spanner()->placeAbove() ? Sid::hairpinLinePosAbove : Sid::hairpinLinePosBelow;
502 return spanner()->placeAbove() ? Sid::hairpinPosAbove : Sid::hairpinPosBelow;
503 case Pid::BEGIN_TEXT:
504 switch (hairpin()->hairpinType()) {
505 default:
506 return Sid::hairpinText;
507 case HairpinType::CRESC_LINE:
508 return Sid::hairpinCrescText;
509 case HairpinType::DECRESC_LINE:
510 return Sid::hairpinDecrescText;
511 }
512 break;
513 case Pid::CONTINUE_TEXT:
514 switch (hairpin()->hairpinType()) {
515 default:
516 return Sid::hairpinText;
517 case HairpinType::CRESC_LINE:
518 return Sid::hairpinCrescContText;
519 case HairpinType::DECRESC_LINE:
520 return Sid::hairpinDecrescContText;
521 }
522 break;
523 case Pid::LINE_STYLE:
524 return hairpin()->isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
525 default:
526 break;
527 }
528 return TextLineBaseSegment::getPropertyStyle(pid);
529 }
530
getPropertyStyle(Pid pid) const531 Sid Hairpin::getPropertyStyle(Pid pid) const
532 {
533 switch (pid) {
534 case Pid::OFFSET:
535 if (isLineType())
536 return placeAbove() ? Sid::hairpinLinePosAbove : Sid::hairpinLinePosBelow;
537 return placeAbove() ? Sid::hairpinPosAbove : Sid::hairpinPosBelow;
538 case Pid::BEGIN_TEXT:
539 switch (hairpinType()) {
540 default:
541 return Sid::hairpinText;
542 case HairpinType::CRESC_LINE:
543 return Sid::hairpinCrescText;
544 case HairpinType::DECRESC_LINE:
545 return Sid::hairpinDecrescText;
546 }
547 break;
548 case Pid::CONTINUE_TEXT:
549 switch (hairpinType()) {
550 default:
551 return Sid::hairpinText;
552 case HairpinType::CRESC_LINE:
553 return Sid::hairpinCrescContText;
554 case HairpinType::DECRESC_LINE:
555 return Sid::hairpinDecrescContText;
556 }
557 break;
558 case Pid::LINE_STYLE:
559 return isLineType() ? Sid::hairpinLineLineStyle : Sid::hairpinLineStyle;
560 default:
561 break;
562 }
563 return TextLineBase::getPropertyStyle(pid);
564 }
565
566 //---------------------------------------------------------
567 // Hairpin
568 //---------------------------------------------------------
569
Hairpin(Score * s)570 Hairpin::Hairpin(Score* s)
571 : TextLineBase(s)
572 {
573 initElementStyle(&hairpinStyle);
574
575 resetProperty(Pid::BEGIN_TEXT_PLACE);
576 resetProperty(Pid::CONTINUE_TEXT_PLACE);
577 resetProperty(Pid::HAIRPIN_TYPE);
578 resetProperty(Pid::LINE_VISIBLE);
579
580 _hairpinCircledTip = false;
581 _veloChange = 0;
582 _dynRange = Dynamic::Range::PART;
583 _singleNoteDynamics = true;
584 _veloChangeMethod = ChangeMethod::NORMAL;
585 }
586
587 //---------------------------------------------------------
588 // setHairpinType
589 //---------------------------------------------------------
590
setHairpinType(HairpinType val)591 void Hairpin::setHairpinType(HairpinType val)
592 {
593 if (_hairpinType == val)
594 return;
595 _hairpinType = val;
596 #if 0
597 switch (_hairpinType) {
598 case HairpinType::CRESC_HAIRPIN:
599 case HairpinType::DECRESC_HAIRPIN:
600 setBeginText("");
601 setContinueText("");
602 setLineStyle(Qt::SolidLine);
603 break;
604 case HairpinType::CRESC_LINE:
605 setBeginText("cresc.");
606 setContinueText("(cresc.)");
607 setLineStyle(Qt::CustomDashLine);
608 break;
609 case HairpinType::DECRESC_LINE:
610 setBeginText("dim.");
611 setContinueText("(dim.)");
612 setLineStyle(Qt::CustomDashLine);
613 break;
614 case HairpinType::INVALID:
615 break;
616 };
617 #endif
618 styleChanged();
619 }
620
621 //---------------------------------------------------------
622 // layout
623 // compute segments from tick() to _tick2
624 //---------------------------------------------------------
625
layout()626 void Hairpin::layout()
627 {
628 setPos(0.0, 0.0);
629 TextLineBase::layout();
630 }
631
632 //---------------------------------------------------------
633 // createLineSegment
634 //---------------------------------------------------------
635
636 static const ElementStyle hairpinSegmentStyle {
637 { Sid::hairpinPosBelow, Pid::OFFSET },
638 { Sid::hairpinMinDistance, Pid::MIN_DISTANCE },
639 };
640
createLineSegment()641 LineSegment* Hairpin::createLineSegment()
642 {
643 HairpinSegment* h = new HairpinSegment(this, score());
644 h->setTrack(track());
645 h->initElementStyle(&hairpinSegmentStyle);
646 return h;
647 }
648
649 //---------------------------------------------------------
650 // write
651 //---------------------------------------------------------
652
write(XmlWriter & xml) const653 void Hairpin::write(XmlWriter& xml) const
654 {
655 if (!xml.canWrite(this))
656 return;
657 xml.stag(this);
658 xml.tag("subtype", int(_hairpinType));
659 writeProperty(xml, Pid::VELO_CHANGE);
660 writeProperty(xml, Pid::HAIRPIN_CIRCLEDTIP);
661 writeProperty(xml, Pid::DYNAMIC_RANGE);
662 // writeProperty(xml, Pid::BEGIN_TEXT);
663 writeProperty(xml, Pid::END_TEXT);
664 // writeProperty(xml, Pid::CONTINUE_TEXT);
665 writeProperty(xml, Pid::LINE_VISIBLE);
666 writeProperty(xml, Pid::SINGLE_NOTE_DYNAMICS);
667 writeProperty(xml, Pid::VELO_CHANGE_METHOD);
668
669 for (const StyledProperty& spp : *styledProperties()) {
670 if (!isStyled(spp.pid))
671 writeProperty(xml, spp.pid);
672 }
673 SLine::writeProperties(xml);
674 xml.etag();
675 }
676
677 //---------------------------------------------------------
678 // read
679 //---------------------------------------------------------
680
read(XmlReader & e)681 void Hairpin::read(XmlReader& e)
682 {
683 eraseSpannerSegments();
684
685 while (e.readNextStartElement()) {
686 const QStringRef& tag(e.name());
687 if (tag == "subtype")
688 setHairpinType(HairpinType(e.readInt()));
689 else if (readStyledProperty(e, tag))
690 ;
691 else if (tag == "hairpinCircledTip")
692 _hairpinCircledTip = e.readInt();
693 else if (tag == "veloChange")
694 _veloChange = e.readInt();
695 else if (tag == "dynType")
696 _dynRange = Dynamic::Range(e.readInt());
697 else if (tag == "useTextLine") { // obsolete
698 e.readInt();
699 if (hairpinType() == HairpinType::CRESC_HAIRPIN)
700 setHairpinType(HairpinType::CRESC_LINE);
701 else if (hairpinType() == HairpinType::DECRESC_HAIRPIN)
702 setHairpinType(HairpinType::DECRESC_LINE);
703 }
704 else if (tag == "singleNoteDynamics")
705 _singleNoteDynamics = e.readBool();
706 else if (tag == "veloChangeMethod")
707 _veloChangeMethod = ChangeMap::nameToChangeMethod(e.readElementText());
708 else if (!TextLineBase::readProperties(e))
709 e.unknown();
710 }
711 styleChanged();
712 }
713
714 //---------------------------------------------------------
715 // getProperty
716 //---------------------------------------------------------
717
getProperty(Pid id) const718 QVariant Hairpin::getProperty(Pid id) const
719 {
720 switch (id) {
721 case Pid::HAIRPIN_CIRCLEDTIP:
722 return _hairpinCircledTip;
723 case Pid::HAIRPIN_TYPE:
724 return int(_hairpinType);
725 case Pid::VELO_CHANGE:
726 return _veloChange;
727 case Pid::DYNAMIC_RANGE:
728 return int(_dynRange);
729 case Pid::HAIRPIN_HEIGHT:
730 return _hairpinHeight;
731 case Pid::HAIRPIN_CONT_HEIGHT:
732 return _hairpinContHeight;
733 case Pid::SINGLE_NOTE_DYNAMICS:
734 return _singleNoteDynamics;
735 case Pid::VELO_CHANGE_METHOD:
736 return int(_veloChangeMethod);
737 default:
738 return TextLineBase::getProperty(id);
739 }
740 }
741
742 //---------------------------------------------------------
743 // setProperty
744 //---------------------------------------------------------
745
setProperty(Pid id,const QVariant & v)746 bool Hairpin::setProperty(Pid id, const QVariant& v)
747 {
748 switch (id) {
749 case Pid::HAIRPIN_CIRCLEDTIP:
750 _hairpinCircledTip = v.toBool();
751 break;
752 case Pid::HAIRPIN_TYPE:
753 setHairpinType(HairpinType(v.toInt()));
754 break;
755 case Pid::VELO_CHANGE:
756 _veloChange = v.toInt();
757 break;
758 case Pid::DYNAMIC_RANGE:
759 _dynRange = Dynamic::Range(v.toInt());
760 break;
761 case Pid::HAIRPIN_HEIGHT:
762 _hairpinHeight = v.value<Spatium>();
763 break;
764 case Pid::HAIRPIN_CONT_HEIGHT:
765 _hairpinContHeight = v.value<Spatium>();
766 break;
767 case Pid::SINGLE_NOTE_DYNAMICS:
768 _singleNoteDynamics = v.toBool();
769 break;
770 case Pid::VELO_CHANGE_METHOD:
771 _veloChangeMethod = ChangeMethod(v.toInt());
772 break;
773 default:
774 return TextLineBase::setProperty(id, v);
775 }
776 triggerLayout();
777 return true;
778 }
779
780 //---------------------------------------------------------
781 // propertyDefault
782 //---------------------------------------------------------
783
propertyDefault(Pid id) const784 QVariant Hairpin::propertyDefault(Pid id) const
785 {
786 switch (id) {
787 case Pid::HAIRPIN_CIRCLEDTIP:
788 return false;
789
790 case Pid::VELO_CHANGE:
791 return 0;
792
793 case Pid::DYNAMIC_RANGE:
794 return int(Dynamic::Range::PART);
795
796 case Pid::LINE_STYLE:
797 if (isLineType())
798 return int(Qt::CustomDashLine);
799 return int(Qt::SolidLine);
800
801 case Pid::BEGIN_TEXT:
802 if (_hairpinType == HairpinType::CRESC_LINE)
803 return QString("cresc.");
804 if (_hairpinType == HairpinType::DECRESC_LINE)
805 return QString("dim.");
806 return QString();
807
808 case Pid::CONTINUE_TEXT:
809 case Pid::END_TEXT:
810 if (_hairpinType == HairpinType::CRESC_LINE)
811 return QString("(cresc.)");
812 if (_hairpinType == HairpinType::DECRESC_LINE)
813 return QString("(dim.)");
814 return QString("");
815
816 case Pid::BEGIN_TEXT_PLACE:
817 case Pid::CONTINUE_TEXT_PLACE:
818 return int(PlaceText::LEFT);
819
820 case Pid::BEGIN_TEXT_OFFSET:
821 case Pid::CONTINUE_TEXT_OFFSET:
822 case Pid::END_TEXT_OFFSET:
823 return QPointF();
824
825 case Pid::BEGIN_HOOK_TYPE:
826 case Pid::END_HOOK_TYPE:
827 return int(HookType::NONE);
828
829 case Pid::BEGIN_HOOK_HEIGHT:
830 case Pid::END_HOOK_HEIGHT:
831 return Spatium(0.0);
832
833 case Pid::LINE_VISIBLE:
834 return true;
835
836 case Pid::HAIRPIN_TYPE:
837 return int(HairpinType::CRESC_HAIRPIN);
838
839 case Pid::SINGLE_NOTE_DYNAMICS:
840 return true;
841
842 case Pid::VELO_CHANGE_METHOD:
843 return int(ChangeMethod::NORMAL);
844
845 case Pid::PLACEMENT:
846 return score()->styleV(Sid::hairpinPlacement);
847
848 default:
849 return TextLineBase::propertyDefault(id);
850 }
851 }
852
853 //---------------------------------------------------------
854 // Hairpin::propertyId
855 //---------------------------------------------------------
856
propertyId(const QStringRef & name) const857 Pid Hairpin::propertyId(const QStringRef& name) const
858 {
859 if (name == "subtype")
860 return Pid::HAIRPIN_TYPE;
861 return TextLineBase::propertyId(name);
862 }
863
864 //---------------------------------------------------------
865 // accessibleInfo
866 //---------------------------------------------------------
867
accessibleInfo() const868 QString Hairpin::accessibleInfo() const
869 {
870 QString rez = TextLineBase::accessibleInfo();
871 switch (hairpinType()) {
872 case HairpinType::CRESC_HAIRPIN:
873 rez += ": " + QObject::tr("Crescendo");
874 break;
875 case HairpinType::DECRESC_HAIRPIN:
876 rez += ": " + QObject::tr("Decrescendo");
877 break;
878 default:
879 rez += ": " + QObject::tr("Custom");
880 }
881 return rez;
882 }
883
884 }
885
886