1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2016 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 "global/log.h"
14
15 #include "measure.h"
16 #include "score.h"
17 #include "system.h"
18 #include "undo.h"
19 #include "slurtie.h"
20 #include "tie.h"
21 #include "chord.h"
22 #include "page.h"
23
24 namespace Ms {
25
26 //---------------------------------------------------------
27 // SlurTieSegment
28 //---------------------------------------------------------
29
SlurTieSegment(Score * score)30 SlurTieSegment::SlurTieSegment(Score* score)
31 : SpannerSegment(score)
32 {
33 setFlag(ElementFlag::ON_STAFF, true);
34 }
35
SlurTieSegment(const SlurTieSegment & b)36 SlurTieSegment::SlurTieSegment(const SlurTieSegment& b)
37 : SpannerSegment(b)
38 {
39 for (int i = 0; i < int(Grip::GRIPS); ++i) {
40 _ups[i] = b._ups[i];
41 _ups[i].p = QPointF();
42 }
43 path = b.path;
44 }
45
46 //---------------------------------------------------------
47 // gripAnchorLines
48 //---------------------------------------------------------
49
gripAnchorLines(Grip grip) const50 QVector<QLineF> SlurTieSegment::gripAnchorLines(Grip grip) const
51 {
52 QVector<QLineF> result;
53
54 if (!system() || (grip != Grip::START && grip != Grip::END))
55 return result;
56
57 QPointF sp(system()->pagePos());
58 QPointF pp(pagePos());
59 QPointF p1(ups(Grip::START).p + pp);
60 QPointF p2(ups(Grip::END).p + pp);
61
62 QPointF anchorPosition;
63 int gripIndex = static_cast<int>(grip);
64
65 switch (spannerSegmentType()) {
66 case SpannerSegmentType::SINGLE:
67 anchorPosition = (grip == Grip::START ? p1 : p2);
68 break;
69
70 case SpannerSegmentType::BEGIN:
71 anchorPosition = (grip == Grip::START ? p1 : system()->abbox().topRight());
72 break;
73
74 case SpannerSegmentType::MIDDLE:
75 anchorPosition = (grip == Grip::START ? sp : system()->abbox().topRight());
76 break;
77
78 case SpannerSegmentType::END:
79 anchorPosition = (grip == Grip::START ? sp : p2);
80 break;
81 }
82
83 const Page* p = system()->page();
84 const QPointF pageOffset = p ? p->pos() : QPointF();
85
86 result << QLineF(anchorPosition, gripsPositions().at(gripIndex)).translated(pageOffset);
87
88 return result;
89 }
90
91 //---------------------------------------------------------
92 // move
93 //---------------------------------------------------------
94
move(const QPointF & s)95 void SlurTieSegment::move(const QPointF& s)
96 {
97 Element::move(s);
98 for (int k = 0; k < int(Grip::GRIPS); ++k)
99 _ups[k].p += s;
100 }
101
102 //---------------------------------------------------------
103 // spatiumChanged
104 //---------------------------------------------------------
105
spatiumChanged(qreal oldValue,qreal newValue)106 void SlurTieSegment::spatiumChanged(qreal oldValue, qreal newValue)
107 {
108 Element::spatiumChanged(oldValue, newValue);
109 qreal diff = newValue / oldValue;
110 for (UP& u : _ups)
111 u.off *= diff;
112 }
113
114 //---------------------------------------------------------
115 // gripsPositions
116 //---------------------------------------------------------
117
gripsPositions(const EditData &) const118 std::vector<QPointF> SlurTieSegment::gripsPositions(const EditData&) const
119 {
120 const int ngrips = gripsCount();
121 std::vector<QPointF> grips(ngrips);
122
123 const QPointF p(pagePos());
124 for (int i = 0; i < ngrips; ++i)
125 grips[i] = _ups[i].p + _ups[i].off + p;
126
127 return grips;
128 }
129
130 //---------------------------------------------------------
131 // startEditDrag
132 //---------------------------------------------------------
133
startEditDrag(EditData & ed)134 void SlurTieSegment::startEditDrag(EditData& ed)
135 {
136 ElementEditData* eed = ed.getData(this);
137 IF_ASSERT_FAILED(eed) {
138 return;
139 }
140 for (auto i : { Pid::SLUR_UOFF1, Pid::SLUR_UOFF2, Pid::SLUR_UOFF3, Pid::SLUR_UOFF4, Pid::OFFSET })
141 eed->pushProperty(i);
142 }
143
144 //---------------------------------------------------------
145 // endEditDrag
146 //---------------------------------------------------------
147
endEditDrag(EditData & ed)148 void SlurTieSegment::endEditDrag(EditData& ed)
149 {
150 Element::endEditDrag(ed);
151 triggerLayout();
152 }
153
154 //---------------------------------------------------------
155 // editDrag
156 //---------------------------------------------------------
157
editDrag(EditData & ed)158 void SlurTieSegment::editDrag(EditData& ed)
159 {
160 Grip g = ed.curGrip;
161 ups(g).off += ed.delta;
162
163 QPointF delta;
164
165 switch (g) {
166 case Grip::START:
167 case Grip::END:
168 //
169 // move anchor for slurs/ties
170 //
171 if ((g == Grip::START && isSingleBeginType()) || (g == Grip::END && isSingleEndType())) {
172 Spanner* spanner = slurTie();
173 Qt::KeyboardModifiers km = qApp->keyboardModifiers();
174 Element* e = ed.view->elementNear(ed.pos);
175 if (e && e->isNote()) {
176 Note* note = toNote(e);
177 Fraction tick = note->chord()->tick();
178 if ((g == Grip::END && tick > slurTie()->tick()) || (g == Grip::START && tick < slurTie()->tick2())) {
179 if (km != (Qt::ShiftModifier | Qt::ControlModifier)) {
180 Chord* c = note->chord();
181 ed.view->setDropTarget(note);
182 if (c->part() == spanner->part() && c != spanner->endCR())
183 changeAnchor(ed, c);
184 }
185 }
186 }
187 else
188 ed.view->setDropTarget(0);
189 }
190 break;
191 case Grip::BEZIER1:
192 break;
193 case Grip::BEZIER2:
194 break;
195 case Grip::SHOULDER:
196 ups(g).off = QPointF();
197 delta = ed.delta;
198 break;
199 case Grip::DRAG:
200 ups(g).off = QPointF();
201 setOffset(offset() + ed.delta);
202 break;
203 case Grip::NO_GRIP:
204 case Grip::GRIPS:
205 break;
206 }
207 computeBezier(delta);
208 }
209
210 //---------------------------------------------------------
211 // getProperty
212 //---------------------------------------------------------
213
getProperty(Pid propertyId) const214 QVariant SlurTieSegment::getProperty(Pid propertyId) const
215 {
216 switch (propertyId) {
217 case Pid::LINE_TYPE:
218 case Pid::SLUR_DIRECTION:
219 return slurTie()->getProperty(propertyId);
220 case Pid::SLUR_UOFF1:
221 return ups(Grip::START).off;
222 case Pid::SLUR_UOFF2:
223 return ups(Grip::BEZIER1).off;
224 case Pid::SLUR_UOFF3:
225 return ups(Grip::BEZIER2).off;
226 case Pid::SLUR_UOFF4:
227 return ups(Grip::END).off;
228 default:
229 return SpannerSegment::getProperty(propertyId);
230 }
231 }
232
233 //---------------------------------------------------------
234 // setProperty
235 //---------------------------------------------------------
236
setProperty(Pid propertyId,const QVariant & v)237 bool SlurTieSegment::setProperty(Pid propertyId, const QVariant& v)
238 {
239 switch(propertyId) {
240 case Pid::LINE_TYPE:
241 case Pid::SLUR_DIRECTION:
242 return slurTie()->setProperty(propertyId, v);
243 case Pid::SLUR_UOFF1:
244 ups(Grip::START).off = v.toPointF();
245 break;
246 case Pid::SLUR_UOFF2:
247 ups(Grip::BEZIER1).off = v.toPointF();
248 break;
249 case Pid::SLUR_UOFF3:
250 ups(Grip::BEZIER2).off = v.toPointF();
251 break;
252 case Pid::SLUR_UOFF4:
253 ups(Grip::END).off = v.toPointF();
254 break;
255 default:
256 return SpannerSegment::setProperty(propertyId, v);
257 }
258 triggerLayoutAll();
259 return true;
260 }
261
262 //---------------------------------------------------------
263 // propertyDefault
264 //---------------------------------------------------------
265
propertyDefault(Pid id) const266 QVariant SlurTieSegment::propertyDefault(Pid id) const
267 {
268 switch (id) {
269 case Pid::LINE_TYPE:
270 case Pid::SLUR_DIRECTION:
271 return slurTie()->propertyDefault(id);
272 case Pid::SLUR_UOFF1:
273 case Pid::SLUR_UOFF2:
274 case Pid::SLUR_UOFF3:
275 case Pid::SLUR_UOFF4:
276 return QPointF();
277 default:
278 return SpannerSegment::propertyDefault(id);
279 }
280 }
281
282 //---------------------------------------------------------
283 // reset
284 //---------------------------------------------------------
285
reset()286 void SlurTieSegment::reset()
287 {
288 Element::reset();
289 undoResetProperty(Pid::SLUR_UOFF1);
290 undoResetProperty(Pid::SLUR_UOFF2);
291 undoResetProperty(Pid::SLUR_UOFF3);
292 undoResetProperty(Pid::SLUR_UOFF4);
293 slurTie()->reset();
294 }
295
296 //---------------------------------------------------------
297 // undoChangeProperty
298 //---------------------------------------------------------
299
undoChangeProperty(Pid pid,const QVariant & val,PropertyFlags ps)300 void SlurTieSegment::undoChangeProperty(Pid pid, const QVariant& val, PropertyFlags ps)
301 {
302 if (pid == Pid::AUTOPLACE && (val.toBool() == true && !autoplace())) {
303 // Switching autoplacement on. Save user-defined
304 // placement properties to undo stack.
305 undoPushProperty(Pid::SLUR_UOFF1);
306 undoPushProperty(Pid::SLUR_UOFF2);
307 undoPushProperty(Pid::SLUR_UOFF3);
308 undoPushProperty(Pid::SLUR_UOFF4);
309 // other will be saved in base classes.
310 }
311 SpannerSegment::undoChangeProperty(pid, val, ps);
312 }
313
314 //---------------------------------------------------------
315 // writeProperties
316 //---------------------------------------------------------
317
writeSlur(XmlWriter & xml,int no) const318 void SlurTieSegment::writeSlur(XmlWriter& xml, int no) const
319 {
320 if (visible() && autoplace()
321 && (color() == Qt::black)
322 && offset().isNull()
323 && ups(Grip::START).off.isNull()
324 && ups(Grip::BEZIER1).off.isNull()
325 && ups(Grip::BEZIER2).off.isNull()
326 && ups(Grip::END).off.isNull()
327 )
328 return;
329
330 xml.stag(this, QString("no=\"%1\"").arg(no));
331
332 qreal _spatium = score()->spatium();
333 if (!ups(Grip::START).off.isNull())
334 xml.tag("o1", ups(Grip::START).off / _spatium);
335 if (!ups(Grip::BEZIER1).off.isNull())
336 xml.tag("o2", ups(Grip::BEZIER1).off / _spatium);
337 if (!ups(Grip::BEZIER2).off.isNull())
338 xml.tag("o3", ups(Grip::BEZIER2).off / _spatium);
339 if (!ups(Grip::END).off.isNull())
340 xml.tag("o4", ups(Grip::END).off / _spatium);
341 Element::writeProperties(xml);
342 xml.etag();
343 }
344
345 //---------------------------------------------------------
346 // readSegment
347 //---------------------------------------------------------
348
read(XmlReader & e)349 void SlurTieSegment::read(XmlReader& e)
350 {
351 qreal _spatium = score()->spatium();
352 while (e.readNextStartElement()) {
353 const QStringRef& tag(e.name());
354 if (tag == "o1")
355 ups(Grip::START).off = e.readPoint() * _spatium;
356 else if (tag == "o2")
357 ups(Grip::BEZIER1).off = e.readPoint() * _spatium;
358 else if (tag == "o3")
359 ups(Grip::BEZIER2).off = e.readPoint() * _spatium;
360 else if (tag == "o4")
361 ups(Grip::END).off = e.readPoint() * _spatium;
362 else if (!Element::readProperties(e))
363 e.unknown();
364 }
365 }
366
367 //---------------------------------------------------------
368 // drawEditMode
369 //---------------------------------------------------------
370
drawEditMode(QPainter * p,EditData & ed)371 void SlurTieSegment::drawEditMode(QPainter* p, EditData& ed)
372 {
373 QPolygonF polygon(7);
374 polygon[0] = QPointF(ed.grip[int(Grip::START)].center());
375 polygon[1] = QPointF(ed.grip[int(Grip::BEZIER1)].center());
376 polygon[2] = QPointF(ed.grip[int(Grip::SHOULDER)].center());
377 polygon[3] = QPointF(ed.grip[int(Grip::BEZIER2)].center());
378 polygon[4] = QPointF(ed.grip[int(Grip::END)].center());
379 polygon[5] = QPointF(ed.grip[int(Grip::DRAG)].center());
380 polygon[6] = QPointF(ed.grip[int(Grip::START)].center());
381 p->setPen(QPen(MScore::frameMarginColor, 0.0));
382 p->drawPolyline(polygon);
383
384 p->setPen(QPen(MScore::defaultColor, 0.0));
385 for (int i = 0; i < ed.grips; ++i) {
386 // This must be done with an if-else statement rather than a ternary operator.
387 // This is because there are two setBrush methods that take different types
388 // of argument, either a Qt::BrushStyle or a QBrush. Since a QBrush can be
389 // constructed from a QColour, passing Mscore::frameMarginColor works.
390 // Qt::NoBrush is a Qt::BrushStyle, however, so if it is passed in a ternary
391 // operator with a QColor, a new QColor will be created from it, and from that
392 // a QBrush. Instead, what we really want to do is pass Qt::NoBrush as a
393 // Qt::BrushStyle, therefore this requires two separate function calls:
394 if (Grip(i) == ed.curGrip)
395 p->setBrush(MScore::frameMarginColor);
396 else
397 p->setBrush(Qt::NoBrush);
398 p->drawRect(ed.grip[i]);
399 }
400 }
401
402 //---------------------------------------------------------
403 // SlurTie
404 //---------------------------------------------------------
405
SlurTie(Score * s)406 SlurTie::SlurTie(Score* s)
407 : Spanner(s)
408 {
409 _slurDirection = Direction::AUTO;
410 _up = true;
411 _lineType = 0; // default is solid
412 }
413
SlurTie(const SlurTie & t)414 SlurTie::SlurTie(const SlurTie& t)
415 : Spanner(t)
416 {
417 _up = t._up;
418 _slurDirection = t._slurDirection;
419 _lineType = t._lineType;
420 }
421
422 //---------------------------------------------------------
423 // SlurTie
424 //---------------------------------------------------------
425
~SlurTie()426 SlurTie::~SlurTie()
427 {
428 }
429
430 //---------------------------------------------------------
431 // writeProperties
432 //---------------------------------------------------------
433
writeProperties(XmlWriter & xml) const434 void SlurTie::writeProperties(XmlWriter& xml) const
435 {
436 Element::writeProperties(xml);
437 int idx = 0;
438 for (const SpannerSegment* ss : spannerSegments())
439 ((SlurTieSegment*)ss)->writeSlur(xml, idx++);
440 writeProperty(xml, Pid::SLUR_DIRECTION);
441 writeProperty(xml, Pid::LINE_TYPE);
442 }
443
444 //---------------------------------------------------------
445 // readProperties
446 //---------------------------------------------------------
447
readProperties(XmlReader & e)448 bool SlurTie::readProperties(XmlReader& e)
449 {
450 const QStringRef& tag(e.name());
451
452 if (readProperty(tag, e, Pid::SLUR_DIRECTION))
453 ;
454 else if (tag == "lineType")
455 _lineType = e.readInt();
456 else if (tag == "SlurSegment" || tag == "TieSegment") {
457 const int idx = e.intAttribute("no", 0);
458 const int n = int(spannerSegments().size());
459 for (int i = n; i < idx; ++i)
460 add(newSlurTieSegment());
461 SlurTieSegment* s = newSlurTieSegment();
462 s->read(e);
463 add(s);
464 }
465 else if (!Element::readProperties(e))
466 return false;
467 return true;
468 }
469
470 //---------------------------------------------------------
471 // read
472 //---------------------------------------------------------
473
read(XmlReader & e)474 void SlurTie::read(XmlReader& e)
475 {
476 while (e.readNextStartElement()) {
477 if (!SlurTie::readProperties(e))
478 e.unknown();
479 }
480 }
481
482 //---------------------------------------------------------
483 // undoSetLineType
484 //---------------------------------------------------------
485
undoSetLineType(int t)486 void SlurTie::undoSetLineType(int t)
487 {
488 undoChangeProperty(Pid::LINE_TYPE, t);
489 }
490
491 //---------------------------------------------------------
492 // undoSetSlurDirection
493 //---------------------------------------------------------
494
undoSetSlurDirection(Direction d)495 void SlurTie::undoSetSlurDirection(Direction d)
496 {
497 undoChangeProperty(Pid::SLUR_DIRECTION, QVariant::fromValue<Direction>(d));
498 }
499
500 //---------------------------------------------------------
501 // getProperty
502 //---------------------------------------------------------
503
getProperty(Pid propertyId) const504 QVariant SlurTie::getProperty(Pid propertyId) const
505 {
506 switch (propertyId) {
507 case Pid::LINE_TYPE:
508 return lineType();
509 case Pid::SLUR_DIRECTION:
510 return QVariant::fromValue<Direction>(slurDirection());
511 default:
512 return Spanner::getProperty(propertyId);
513 }
514 }
515
516 //---------------------------------------------------------
517 // setProperty
518 //---------------------------------------------------------
519
setProperty(Pid propertyId,const QVariant & v)520 bool SlurTie::setProperty(Pid propertyId, const QVariant& v)
521 {
522 switch (propertyId) {
523 case Pid::LINE_TYPE:
524 setLineType(v.toInt());
525 break;
526 case Pid::SLUR_DIRECTION:
527 setSlurDirection(v.value<Direction>());
528 break;
529 default:
530 return Spanner::setProperty(propertyId, v);
531 }
532 triggerLayoutAll();
533 return true;
534 }
535
536 //---------------------------------------------------------
537 // propertyDefault
538 //---------------------------------------------------------
539
propertyDefault(Pid id) const540 QVariant SlurTie::propertyDefault(Pid id) const
541 {
542 switch (id) {
543 case Pid::LINE_TYPE:
544 return 0;
545 case Pid::SLUR_DIRECTION:
546 return QVariant::fromValue<Direction>(Direction::AUTO);
547 default:
548 return Spanner::propertyDefault(id);
549 }
550 }
551
552 //---------------------------------------------------------
553 // fixupSegments
554 //---------------------------------------------------------
555
fixupSegments(unsigned nsegs)556 void SlurTie::fixupSegments(unsigned nsegs)
557 {
558 Spanner::fixupSegments(nsegs, [this]() { return newSlurTieSegment(); });
559 }
560
561 //---------------------------------------------------------
562 // reset
563 //---------------------------------------------------------
564
reset()565 void SlurTie::reset()
566 {
567 Element::reset();
568 undoResetProperty(Pid::SLUR_DIRECTION);
569 undoResetProperty(Pid::LINE_TYPE);
570 }
571
572 }
573
574