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