1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2010-2012 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 "chordline.h"
14 #include "xml.h"
15 #include "chord.h"
16 #include "measure.h"
17 #include "system.h"
18 #include "note.h"
19 
20 namespace Ms {
21 
22 const char* scorelineNames[] = {
23       QT_TRANSLATE_NOOP("Ms", "Fall"),
24       QT_TRANSLATE_NOOP("Ms", "Doit"),
25       QT_TRANSLATE_NOOP("Ms", "Plop"),
26       QT_TRANSLATE_NOOP("Ms", "Scoop"),
27       };
28 
29 //---------------------------------------------------------
30 //   ChordLine
31 //---------------------------------------------------------
32 
ChordLine(Score * s)33 ChordLine::ChordLine(Score* s)
34    : Element(s, ElementFlag::MOVABLE)
35       {
36       modified = false;
37       _chordLineType = ChordLineType::NOTYPE;
38       _straight = false;
39       _lengthX = 0.0;
40       _lengthY = 0.0;
41       }
42 
ChordLine(const ChordLine & cl)43 ChordLine::ChordLine(const ChordLine& cl)
44    : Element(cl)
45       {
46       path     = cl.path;
47       modified = cl.modified;
48       _chordLineType = cl._chordLineType;
49       _straight = cl._straight;
50       _lengthX = cl._lengthX;
51       _lengthY = cl._lengthY;
52       }
53 
54 //---------------------------------------------------------
55 //   setChordLineType
56 //---------------------------------------------------------
57 
setChordLineType(ChordLineType st)58 void ChordLine::setChordLineType(ChordLineType st)
59       {
60       _chordLineType = st;
61       }
62 
63 //---------------------------------------------------------
64 //   layout
65 //---------------------------------------------------------
66 
layout()67 void ChordLine::layout()
68       {
69       if (!modified) {
70             qreal x2 = 0;
71             qreal y2 = 0;
72             switch(_chordLineType) {
73                   case ChordLineType::NOTYPE:
74                         break;
75                   case ChordLineType::FALL:
76                         x2 = _initialLength;
77                         y2 = _initialLength;
78                         break;
79                   case ChordLineType::PLOP:
80                         x2 = -_initialLength;
81                         y2 = -_initialLength;
82                         break;
83                   case ChordLineType::SCOOP:
84                         x2 = -_initialLength;
85                         y2 = _initialLength;
86                         break;
87                   default:
88                   case ChordLineType::DOIT:
89                         x2 = _initialLength;
90                         y2 = -_initialLength;
91                         break;
92                   }
93             if (_chordLineType != ChordLineType::NOTYPE) {
94                   path = QPainterPath();
95                   // chordlines to the right of the note
96                   if (_chordLineType == ChordLineType::FALL || _chordLineType == ChordLineType::DOIT) {
97                         if (_straight)
98                               path.lineTo(x2, y2);
99                         else
100                               path.cubicTo(x2/2, 0.0, x2, y2/2, x2, y2);
101                         }
102                   // chordlines to the left of the note
103                   else if (_chordLineType == ChordLineType::PLOP || _chordLineType == ChordLineType::SCOOP) {
104                         if (_straight)
105                               path.lineTo(x2, y2);
106                         else
107                               path.cubicTo(0.0, y2/2, x2/2, y2, x2, y2);
108                         }
109                   }
110             }
111 
112       qreal _spatium = spatium();
113       if (parent()) {
114             Note* note = chord()->upNote();
115             QPointF p(note->pos());
116             // chordlines to the right of the note
117             if (_chordLineType == ChordLineType::FALL || _chordLineType == ChordLineType::DOIT)
118                   setPos(p.x() + note->bboxRightPos() + _spatium * .2, p.y());
119             // chordlines to the left of the note
120             if (_chordLineType == ChordLineType::PLOP)
121                   setPos(p.x() + note->bboxRightPos() * .25, p.y() - note->headHeight() * .75);
122             if (_chordLineType == ChordLineType::SCOOP) {
123                   qreal x = p.x() + (chord()->up() ? note->bboxRightPos() * .25 : _spatium * -.2);
124                   setPos(x, p.y() + note->headHeight() * .75);
125                   }
126             }
127       else
128             setPos(0.0, 0.0);
129       QRectF r(path.boundingRect());
130       int x1, y1, width, height = 0;
131 
132       x1 = r.x() * _spatium;
133       y1 = r.y() * _spatium;
134       width = r.width() * _spatium;
135       height = r.height() * _spatium;
136       bbox().setRect(x1, y1, width, height);
137       }
138 
139 //---------------------------------------------------------
140 //   read
141 //---------------------------------------------------------
142 
read(XmlReader & e)143 void ChordLine::read(XmlReader& e)
144       {
145       path = QPainterPath();
146       while (e.readNextStartElement()) {
147             const QStringRef& tag(e.name());
148             if (tag == "Path") {
149                   path = QPainterPath();
150                   QPointF curveTo;
151                   QPointF p1;
152                   int state = 0;
153                   while (e.readNextStartElement()) {
154                         const QStringRef& nextTag(e.name());
155                         if (nextTag == "Element") {
156                               int type = e.intAttribute("type");
157                               qreal x  = e.doubleAttribute("x");
158                               qreal y  = e.doubleAttribute("y");
159                               switch(QPainterPath::ElementType(type)) {
160                                     case QPainterPath::MoveToElement:
161                                           path.moveTo(x, y);
162                                           break;
163                                     case QPainterPath::LineToElement:
164                                           path.lineTo(x, y);
165                                           break;
166                                     case QPainterPath::CurveToElement:
167                                           curveTo.rx() = x;
168                                           curveTo.ry() = y;
169                                           state = 1;
170                                           break;
171                                     case QPainterPath::CurveToDataElement:
172                                           if (state == 1) {
173                                                 p1.rx() = x;
174                                                 p1.ry() = y;
175                                                 state = 2;
176                                                 }
177                                           else if (state == 2) {
178                                                 path.cubicTo(curveTo, p1, QPointF(x, y));
179                                                 state = 0;
180                                                 }
181                                           break;
182                                     }
183                               e.skipCurrentElement(); //needed to go to next Element in Path
184                               }
185                         else
186                               e.unknown();
187                         }
188                   modified = true;
189                   }
190             else if (tag == "subtype")
191                   setChordLineType(ChordLineType(e.readInt()));
192              else if (tag == "straight")
193                   setStraight(e.readInt());
194              else if (tag == "lengthX")
195                   setLengthX(e.readInt());
196              else if (tag == "lengthY")
197                   setLengthY(e.readInt());
198 
199             else if (!Element::readProperties(e))
200                   e.unknown();
201             }
202       }
203 
204 //---------------------------------------------------------
205 //   write
206 //---------------------------------------------------------
207 
write(XmlWriter & xml) const208 void ChordLine::write(XmlWriter& xml) const
209       {
210       xml.stag(this);
211       writeProperty(xml, Pid::CHORD_LINE_TYPE);
212       writeProperty(xml, Pid::CHORD_LINE_STRAIGHT);
213       xml.tag("lengthX", _lengthX, 0.0);
214       xml.tag("lengthY", _lengthY, 0.0);
215       Element::writeProperties(xml);
216       if (modified) {
217             int n = path.elementCount();
218             xml.stag("Path");
219             for (int i = 0; i < n; ++i) {
220                   const QPainterPath::Element& e = path.elementAt(i);
221                   xml.tagE(QString("Element type=\"%1\" x=\"%2\" y=\"%3\"")
222                      .arg(int(e.type)).arg(e.x).arg(e.y));
223                   }
224             xml.etag();
225             }
226       xml.etag();
227       }
228 
229 //---------------------------------------------------------
230 //   Symbol::draw
231 //---------------------------------------------------------
232 
draw(QPainter * painter) const233 void ChordLine::draw(QPainter* painter) const
234       {
235       qreal _spatium = spatium();
236 
237       if (this->isStraight()) {
238             painter->scale(_spatium, _spatium);
239             painter->setPen(QPen(curColor(), .15, Qt::SolidLine));
240             painter->setBrush(Qt::NoBrush);
241 
242             QPainterPath pathOffset = path;
243             qreal offset = 0.5;
244 
245             if (_chordLineType == ChordLineType::FALL)
246                   pathOffset.translate(offset, -offset);
247             else if (_chordLineType == ChordLineType::DOIT)
248                   pathOffset.translate(offset, offset);
249             else if (_chordLineType == ChordLineType::SCOOP)
250                   pathOffset.translate(-offset, offset);
251             else if (_chordLineType == ChordLineType::PLOP)
252                   pathOffset.translate(-offset, -offset);
253 
254             painter->drawPath(pathOffset);
255             painter->scale(1.0/_spatium, 1.0/_spatium);
256             }
257       else  {
258             painter->scale(_spatium, _spatium);
259             painter->setPen(QPen(curColor(), .15, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
260             painter->setBrush(Qt::NoBrush);
261             painter->drawPath(path);
262             painter->scale(1.0/_spatium, 1.0/_spatium);
263             }
264       }
265 
266 //---------------------------------------------------------
267 //   startEditDrag
268 //---------------------------------------------------------
269 
startEditDrag(EditData & ed)270 void ChordLine::startEditDrag(EditData& ed)
271       {
272       Element::startEditDrag(ed);
273       ElementEditData* eed = ed.getData(this);
274 
275       eed->pushProperty(Pid::PATH);
276       }
277 
278 //---------------------------------------------------------
279 //   editDrag
280 //---------------------------------------------------------
281 
editDrag(EditData & ed)282 void ChordLine::editDrag(EditData& ed)
283       {
284       int n = path.elementCount();
285       QPainterPath p;
286       qreal sp = spatium();
287       _lengthX += ed.delta.x();
288       _lengthY += ed.delta.y();
289 
290       // used to limit how grips can affect the slide, stops the user from being able to turn one kind of slide into another
291       int slideBoundary = 5;
292       if ((_chordLineType == ChordLineType::PLOP || _chordLineType == ChordLineType::FALL) && _lengthY < -slideBoundary)
293             _lengthY = -slideBoundary;
294       else if ((_chordLineType == ChordLineType::FALL || _chordLineType == ChordLineType::DOIT) && _lengthX < -slideBoundary)
295             _lengthX = -slideBoundary;
296       else if ((_chordLineType == ChordLineType::DOIT || _chordLineType == ChordLineType::SCOOP) && _lengthY > slideBoundary)
297             _lengthY = slideBoundary;
298       else if ((_chordLineType == ChordLineType::SCOOP || _chordLineType == ChordLineType::PLOP)  && _lengthX > slideBoundary)
299             _lengthX = slideBoundary;
300 
301       qreal dx = ed.delta.x() / sp;
302       qreal dy = ed.delta.y() / sp;
303       for (int i = 0; i < n; ++i) {
304             const QPainterPath::Element& e = (_straight ? path.elementAt(1) : path.elementAt(i));
305             if (_straight) {
306                   if (i > 0)
307                         break;
308                   // check the gradient of the line
309                   const QPainterPath::Element& startPoint = path.elementAt(0);
310                   if ( (_chordLineType == ChordLineType::FALL && (e.x + dx < startPoint.x || e.y + dy < startPoint.y))  ||
311                        (_chordLineType == ChordLineType::DOIT && (e.x + dx < startPoint.x || e.y + dy > startPoint.y))  ||
312                        (_chordLineType == ChordLineType::SCOOP && (e.x + dx > startPoint.x || e.y + dy < startPoint.y)) ||
313                        (_chordLineType == ChordLineType::PLOP && (e.x + dx > startPoint.x || e.y + dy > startPoint.y)) )
314                               return;
315                   }
316 
317             qreal x = e.x;
318             qreal y = e.y;
319             if (ed.curGrip == Grip(i)) {
320                   x += dx;
321                   y += dy;
322                   }
323             switch(e.type) {
324                   case QPainterPath::CurveToDataElement:
325                         break;
326                   case QPainterPath::MoveToElement:
327                         p.moveTo(x, y);
328                         break;
329                   case QPainterPath::LineToElement:
330                         p.lineTo(x, y);
331                         break;
332                   case QPainterPath::CurveToElement:
333                         {
334                         qreal x2 = path.elementAt(i+1).x;
335                         qreal y2 = path.elementAt(i+1).y;
336                         qreal x3 = path.elementAt(i+2).x;
337                         qreal y3 = path.elementAt(i+2).y;
338                         if (Grip(i + 1) == ed.curGrip) {
339                               x2 += dx;
340                               y2 += dy;
341                               }
342                         else if (Grip(i + 2) == ed.curGrip) {
343                               x3 += dx;
344                               y3 += dy;
345                               }
346                         p.cubicTo(x, y, x2, y2, x3, y3);
347                         i += 2;
348                         }
349                         break;
350                   }
351             }
352       path = p;
353       modified = true;
354       }
355 
356 //---------------------------------------------------------
357 //   gripsPositions
358 //---------------------------------------------------------
359 
gripsPositions(const EditData &) const360 std::vector<QPointF> ChordLine::gripsPositions(const EditData&) const
361       {
362       qreal sp = spatium();
363       int n    = path.elementCount();
364       QPointF cp(pagePos());
365       if (_straight) {
366             // limit the number of grips to one
367             qreal offset = 0.5 * sp;
368             QPointF p;
369 
370             if (_chordLineType == ChordLineType::FALL)
371                   p = QPointF(offset, -offset);
372             else if (_chordLineType == ChordLineType::DOIT)
373                   p = QPointF(offset, offset);
374             else if (_chordLineType == ChordLineType::SCOOP)
375                   p = QPointF(-offset, offset);
376             else if (_chordLineType == ChordLineType::PLOP)
377                   p = QPointF(-offset, -offset);
378 
379             // translate on the length and height - stops the grips from going past boundaries of slide
380             p += (cp + QPointF(path.elementAt(1).x * sp, path.elementAt(1).y * sp));
381             return { p };
382             }
383       else  {
384             std::vector<QPointF> grips(n);
385             for (int i = 0; i < n; ++i)
386                   grips[i] = cp + QPointF(path.elementAt(i).x * sp, path.elementAt(i).y * sp);
387             return grips;
388             }
389       }
390 
391 //---------------------------------------------------------
392 //   accessibleInfo
393 //---------------------------------------------------------
394 
accessibleInfo() const395 QString ChordLine::accessibleInfo() const
396       {
397       QString rez = Element::accessibleInfo();
398       if(chordLineType() != ChordLineType::NOTYPE)
399             rez = QString("%1: %2").arg(rez, scorelineNames[static_cast<int>(chordLineType()) - 1]);
400       return rez;
401       }
402 
403 //---------------------------------------------------------
404 //   getProperty
405 //---------------------------------------------------------
406 
getProperty(Pid propertyId) const407 QVariant ChordLine::getProperty(Pid propertyId) const
408       {
409       switch(propertyId) {
410             case Pid::PATH:
411                   return QVariant::fromValue(path);
412             case Pid::CHORD_LINE_TYPE:
413                   return int(_chordLineType);
414             case Pid::CHORD_LINE_STRAIGHT:
415                   return _straight;
416             default:
417                   break;
418             }
419       return Element::getProperty(propertyId);
420       }
421 
422 //---------------------------------------------------------
423 //   setProperty
424 //---------------------------------------------------------
425 
setProperty(Pid propertyId,const QVariant & val)426 bool ChordLine::setProperty(Pid propertyId, const QVariant& val)
427       {
428       switch(propertyId) {
429             case Pid::PATH:
430                   path = val.value<QPainterPath>();
431                   break;
432             case Pid::CHORD_LINE_TYPE:
433                   setChordLineType(ChordLineType(val.toInt()));
434                   break;
435             case Pid::CHORD_LINE_STRAIGHT:
436                   setStraight(val.toBool());
437                   break;
438             default:
439                   return Element::setProperty(propertyId, val);
440             }
441       triggerLayout();
442       return true;
443       }
444 
445 //---------------------------------------------------------
446 //   propertyDefault
447 //---------------------------------------------------------
448 
propertyDefault(Pid pid) const449 QVariant ChordLine::propertyDefault(Pid pid) const
450       {
451       switch (pid) {
452             case Pid::CHORD_LINE_STRAIGHT:
453                   return false;
454             default:
455                   break;
456             }
457       return Element::propertyDefault(pid);
458       }
459 
460 //---------------------------------------------------------
461 //   propertyId
462 //---------------------------------------------------------
463 
propertyId(const QStringRef & name) const464 Pid ChordLine::propertyId(const QStringRef& name) const
465       {
466       if (name == "subtype")
467             return Pid::CHORD_LINE_TYPE;
468       else if (name == "straight")
469             return Pid::CHORD_LINE_STRAIGHT;
470       return Element::propertyId(name);
471       }
472 }
473 
474