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