1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2010-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 "fret.h"
14 #include "measure.h"
15 #include "system.h"
16 #include "score.h"
17 #include "stringdata.h"
18 #include "chord.h"
19 #include "note.h"
20 #include "segment.h"
21 #include "mscore.h"
22 #include "harmony.h"
23 #include "staff.h"
24 #include "undo.h"
25
26 namespace Ms {
27
28 // parent() is Segment or Box
29 //
30
31 //---------------------------------------------------------
32 // fretStyle
33 //---------------------------------------------------------
34
35 static const ElementStyle fretStyle {
36 { Sid::fretNumPos, Pid::FRET_NUM_POS },
37 { Sid::fretMag, Pid::MAG },
38 { Sid::fretPlacement, Pid::PLACEMENT },
39 { Sid::fretStrings, Pid::FRET_STRINGS },
40 { Sid::fretFrets, Pid::FRET_FRETS },
41 { Sid::fretNut, Pid::FRET_NUT },
42 { Sid::fretMinDistance, Pid::MIN_DISTANCE },
43 { Sid::fretOrientation, Pid::ORIENTATION },
44 };
45
46 //---------------------------------------------------------
47 // FretDiagram
48 //---------------------------------------------------------
49
FretDiagram(Score * score)50 FretDiagram::FretDiagram(Score* score)
51 : Element(score, ElementFlag::MOVABLE | ElementFlag::ON_STAFF)
52 {
53 font.setFamily("FreeSans");
54 font.setPointSize(4.0 * mag());
55 initElementStyle(&fretStyle);
56 }
57
FretDiagram(const FretDiagram & f)58 FretDiagram::FretDiagram(const FretDiagram& f)
59 : Element(f)
60 {
61 _strings = f._strings;
62 _frets = f._frets;
63 _fretOffset = f._fretOffset;
64 _maxFrets = f._maxFrets;
65 font = f.font;
66 _userMag = f._userMag;
67 _numPos = f._numPos;
68 _dots = f._dots;
69 _markers = f._markers;
70 _barres = f._barres;
71 _showNut = f._showNut;
72 _orientation= f._orientation;
73
74 if (f._harmony) {
75 Harmony* h = new Harmony(*f._harmony);
76 add(h);
77 }
78 }
79
~FretDiagram()80 FretDiagram::~FretDiagram()
81 {
82 if (_harmony)
83 delete _harmony;
84 }
85
86 //---------------------------------------------------------
87 // linkedClone
88 //---------------------------------------------------------
89
linkedClone()90 Element* FretDiagram::linkedClone()
91 {
92 FretDiagram* e = clone();
93 e->setAutoplace(true);
94 if (_harmony) {
95 Element* newHarmony = _harmony->linkedClone();
96 e->add(newHarmony);
97 }
98 score()->undo(new Link(e, this));
99 return e;
100 }
101
102 //---------------------------------------------------------
103 // fromString
104 /// Create diagram from string like "XO-123"
105 /// Always assume barre on the first visible fret
106 //---------------------------------------------------------
107
fromString(Score * score,const QString & s)108 FretDiagram* FretDiagram::fromString(Score* score, const QString &s)
109 {
110 FretDiagram* fd = new FretDiagram(score);
111 int strings = s.size();
112
113 fd->setStrings(strings);
114 fd->setFrets(4);
115 fd->setPropertyFlags(Pid::FRET_STRINGS, PropertyFlags::UNSTYLED);
116 fd->setPropertyFlags(Pid::FRET_FRETS, PropertyFlags::UNSTYLED);
117 int offset = 0;
118 int barreString = -1;
119 std::vector<std::pair<int, int>> dotsToAdd;
120
121 for (int i = 0; i < strings; i++) {
122 QChar c = s.at(i);
123 if (c == 'X' || c == 'O') {
124 FretMarkerType mt = (c == 'X' ? FretMarkerType::CROSS : FretMarkerType::CIRCLE);
125 fd->setMarker(i, mt);
126 }
127 else if (c == '-' && barreString == -1) {
128 barreString = i;
129 }
130 else {
131 int fret = c.digitValue();
132 if (fret != -1) {
133 dotsToAdd.push_back(std::make_pair(i, fret));
134 if (fret - 3 > 0 && offset < fret - 3)
135 offset = fret - 3;
136 }
137 }
138 }
139
140 if (offset > 0)
141 fd->setFretOffset(offset);
142
143 for (std::pair<int, int> d : dotsToAdd) {
144 fd->setDot(d.first, d.second - offset, true);
145 }
146
147 // This assumes that any barre goes to the end of the fret
148 if (barreString >= 0)
149 fd->setBarre(barreString, -1, 1);
150
151 return fd;
152 }
153
154 //---------------------------------------------------------
155 // pagePos
156 //---------------------------------------------------------
157
pagePos() const158 QPointF FretDiagram::pagePos() const
159 {
160 if (parent() == 0)
161 return pos();
162 if (parent()->isSegment()) {
163 Measure* m = toSegment(parent())->measure();
164 System* system = m->system();
165 qreal yp = y();
166 if (system)
167 yp += system->staffYpage(staffIdx());
168 return QPointF(pageX(), yp);
169 }
170 else
171 return Element::pagePos();
172 }
173
174 //---------------------------------------------------------
175 // dragAnchorLines
176 //---------------------------------------------------------
177
dragAnchorLines() const178 QVector<QLineF> FretDiagram::dragAnchorLines() const
179 {
180 return genericDragAnchorLines();
181 #if 0 // TODOxx
182 if (parent()->isSegment()) {
183 Segment* s = toSegment(parent());
184 Measure* m = s->measure();
185 System* system = m->system();
186 qreal yp = system->staff(staffIdx())->y() + system->y();
187 qreal xp = m->tick2pos(s->tick()) + m->pagePos().x();
188 QPointF p1(xp, yp);
189
190 qreal x = 0.0;
191 qreal y = 0.0;
192 qreal tw = width();
193 qreal th = height();
194 if (_align & Align::BOTTOM)
195 y = th;
196 else if (_align & Align::VCENTER)
197 y = (th * .5);
198 else if (_align & Align::BASELINE)
199 y = baseLine();
200 if (_align & Align::RIGHT)
201 x = tw;
202 else if (_align & Align::HCENTER)
203 x = (tw * .5);
204 return QLineF(p1, abbox().topLeft() + QPointF(x, y));
205 }
206 return QLineF(parent()->pagePos(), abbox().topLeft());
207 #endif
208 }
209
210 //---------------------------------------------------------
211 // setStrings
212 //---------------------------------------------------------
213
setStrings(int n)214 void FretDiagram::setStrings(int n)
215 {
216 int difference = n - _strings;
217 if (difference == 0 || n <= 0)
218 return;
219
220 // Move all dots, makers, barres to the RIGHT, so we add strings to the left
221 // This is more useful - few instruments need strings added to the right.
222 DotMap tempDots;
223 MarkerMap tempMarkers;
224
225 for (int string = 0; string < _strings; ++string) {
226 if (string + difference < 0)
227 continue;
228
229 for (auto const& d : dot(string)) {
230 if (d.exists())
231 tempDots[string + difference].push_back(FretItem::Dot(d));
232 }
233
234 if (marker(string).exists())
235 tempMarkers[string + difference] = marker(string);
236 }
237
238 _dots = tempDots;
239 _markers = tempMarkers;
240
241 for (int fret = 1; fret <= _frets; ++fret) {
242 if (barre(fret).exists()) {
243 if (_barres[fret].startString + difference <= 0) {
244 removeBarre(fret);
245 continue;
246 }
247
248 _barres[fret].startString = qMax(0, _barres[fret].startString + difference);
249 _barres[fret].endString = _barres[fret].endString == -1 ? -1 : _barres[fret].endString + difference;
250 }
251
252 }
253
254 _strings = n;
255 }
256
257 //---------------------------------------------------------
258 // init
259 //---------------------------------------------------------
260
init(StringData * stringData,Chord * chord)261 void FretDiagram::init(StringData* stringData, Chord* chord)
262 {
263 if (!stringData)
264 setStrings(6);
265 else
266 setStrings(stringData->strings());
267 if (stringData) {
268 for (int string = 0; string < _strings; ++string)
269 setMarker(string, FretMarkerType::CROSS);
270 for (const Note* note : chord->notes()) {
271 int string;
272 int fret;
273 if (stringData->convertPitch(note->pitch(), chord->staff(), chord->segment()->tick(), &string, &fret))
274 setDot(string, fret);
275 }
276 _maxFrets = stringData->frets();
277 }
278 else
279 _maxFrets = 6;
280 }
281
282 //---------------------------------------------------------
283 // draw
284 //---------------------------------------------------------
285
draw(QPainter * painter) const286 void FretDiagram::draw(QPainter* painter) const
287 {
288 QPointF translation = -QPointF(stringDist * (_strings - 1), 0);
289 if (_orientation == Orientation::HORIZONTAL) {
290 painter->save();
291 painter->rotate(-90);
292 painter->translate(translation);
293 }
294
295 // Init pen and other values
296 qreal _spatium = spatium() * _userMag;
297 QPen pen(curColor());
298 pen.setCapStyle(Qt::FlatCap);
299 painter->setBrush(QBrush(QColor(painter->pen().color())));
300
301 // x2 is the x val of the rightmost string
302 qreal x2 = (_strings-1) * stringDist;
303
304 // Draw the nut
305 pen.setWidthF(nutLw);
306 painter->setPen(pen);
307 painter->drawLine(QLineF(-stringLw * .5, 0.0, x2 + stringLw * .5, 0.0));
308
309 // Draw strings and frets
310 pen.setWidthF(stringLw);
311 painter->setPen(pen);
312
313 // y2 is the y val of the bottom fretline
314 qreal y2 = fretDist * (_frets + .5);
315 for (int i = 0; i < _strings; ++i) {
316 qreal x = stringDist * i;
317 painter->drawLine(QLineF(x, _fretOffset ? -_spatium * .2 : 0.0, x, y2));
318 }
319 for (int i = 1; i <= _frets; ++i) {
320 qreal y = fretDist * i;
321 painter->drawLine(QLineF(0.0, y, x2, y));
322 }
323
324 // dotd is the diameter of a dot
325 qreal dotd = _spatium * .49 * score()->styleD(Sid::fretDotSize);
326
327 // Draw dots, sym pen is used to draw them (and markers)
328 QPen symPen(pen);
329 symPen.setCapStyle(Qt::RoundCap);
330 qreal symPenWidth = stringLw * 1.2;
331 symPen.setWidthF(symPenWidth);
332
333 for (auto const& i : _dots) {
334 for (auto const& d : i.second) {
335 if (!d.exists())
336 continue;
337
338 int string = i.first;
339 int fret = d.fret - 1;
340
341 // Calculate coords of the top left corner of the dot
342 qreal x = stringDist * string - dotd * .5;
343 qreal y = fretDist * fret + fretDist * .5 - dotd * .5;
344
345 // Draw different symbols
346 painter->setPen(symPen);
347 switch (d.dtype) {
348 case FretDotType::CROSS:
349 // Give the cross a slightly larger width
350 symPen.setWidthF(symPenWidth * 1.5);
351 painter->setPen(symPen);
352 painter->drawLine(QLineF(x, y, x + dotd, y + dotd));
353 painter->drawLine(QLineF(x + dotd, y, x, y + dotd));
354 symPen.setWidthF(symPenWidth);
355 break;
356 case FretDotType::SQUARE:
357 painter->setBrush(Qt::NoBrush);
358 painter->drawRect(QRectF(x, y, dotd, dotd));
359 break;
360 case FretDotType::TRIANGLE:
361 painter->drawLine(QLineF(x, y + dotd, x + .5 * dotd, y));
362 painter->drawLine(QLineF(x + .5 * dotd, y, x + dotd, y + dotd));
363 painter->drawLine(QLineF(x + dotd, y + dotd, x, y + dotd));
364 break;
365 case FretDotType::NORMAL:
366 default:
367 painter->setBrush(symPen.color());
368 painter->setPen(Qt::NoPen);
369 painter->drawEllipse(QRectF(x, y, dotd, dotd));
370 break;
371 }
372 }
373 }
374
375 // Draw markers
376 symPen.setWidthF(symPenWidth * 1.2);
377 painter->setBrush(Qt::NoBrush);
378 painter->setPen(symPen);
379 for (auto const& i : _markers) {
380 int string = i.first;
381 FretItem::Marker marker = i.second;
382 if (!marker.exists())
383 continue;
384
385 qreal x = stringDist * string - markerSize * .5;
386 qreal y = -fretDist - markerSize * .5;
387 if (marker.mtype == FretMarkerType::CIRCLE) {
388 painter->drawEllipse(QRectF(x, y, markerSize, markerSize));
389 }
390 else if (marker.mtype == FretMarkerType::CROSS) {
391 painter->drawLine(QPointF(x, y), QPointF(x + markerSize, y + markerSize));
392 painter->drawLine(QPointF(x, y + markerSize), QPointF(x + markerSize, y));
393 }
394 }
395
396 // Draw barres
397 for (auto const& i : _barres) {
398 int fret = i.first;
399 int startString = i.second.startString;
400 int endString = i.second.endString;
401
402 qreal x1 = stringDist * startString;
403 qreal newX2 = endString == -1 ? x2 : stringDist * endString;
404 qreal y = fretDist * (fret - 1) + fretDist * .5;
405 pen.setWidthF(dotd * score()->styleD(Sid::barreLineWidth));
406 pen.setCapStyle(Qt::RoundCap);
407 painter->setPen(pen);
408 painter->drawLine(QLineF(x1, y, newX2, y));
409 }
410
411 // Draw fret offset number
412 if (_fretOffset > 0) {
413 qreal fretNumMag = score()->styleD(Sid::fretNumMag);
414 QFont scaledFont(font);
415 scaledFont.setPointSizeF(font.pointSize() * _userMag * (spatium() / SPATIUM20) * MScore::pixelRatio * fretNumMag);
416 painter->setFont(scaledFont);
417 QString text = QString("%1").arg(_fretOffset+1);
418
419 if (_orientation == Orientation::VERTICAL) {
420 if (_numPos == 0) {
421 painter->drawText(QRectF(-stringDist * .4, .0, .0, fretDist),
422 Qt::AlignVCenter|Qt::AlignRight|Qt::TextDontClip, text);
423 }
424 else {
425 painter->drawText(QRectF(x2 + (stringDist * .4), .0, .0, fretDist),
426 Qt::AlignVCenter|Qt::AlignLeft|Qt::TextDontClip,
427 QString("%1").arg(_fretOffset+1));
428 }
429 }
430 else if (_orientation == Orientation::HORIZONTAL) {
431 painter->save();
432 painter->translate(-translation);
433 painter->rotate(90);
434 if (_numPos == 0) {
435 painter->drawText(QRectF(.0, stringDist * (_strings - 1), .0, .0),
436 Qt::AlignLeft|Qt::TextDontClip, text);
437 }
438 else {
439 painter->drawText(QRectF(.0, .0, .0, .0),
440 Qt::AlignBottom|Qt::AlignLeft|Qt::TextDontClip, text);
441 }
442 painter->restore();
443 }
444 painter->setFont(font);
445 }
446
447 // NOTE:JT possible future todo - draw fingerings
448
449 if (_orientation == Orientation::HORIZONTAL) {
450 painter->restore();
451 }
452 }
453
454 //---------------------------------------------------------
455 // layout
456 //---------------------------------------------------------
457
layout()458 void FretDiagram::layout()
459 {
460 qreal _spatium = spatium() * _userMag;
461 stringLw = _spatium * 0.08;
462 nutLw = (_fretOffset || !_showNut) ? stringLw : _spatium * 0.2;
463 stringDist = score()->styleP(Sid::fretStringSpacing) * _userMag;
464 fretDist = score()->styleP(Sid::fretFretSpacing) * _userMag;
465 markerSize = stringDist * .8;
466
467 qreal w = stringDist * (_strings - 1) + markerSize;
468 qreal h = (_frets + 1) * fretDist + markerSize;
469 qreal y = -(markerSize * .5 + fretDist);
470 qreal x = -(markerSize * .5);
471
472 // Allocate space for fret offset number
473 if (_fretOffset > 0) {
474 QFont scaledFont(font);
475 scaledFont.setPointSize(font.pointSize() * _userMag);
476 qreal fretNumMag = score()->styleD(Sid::fretNumMag);
477 scaledFont.setPointSizeF(scaledFont.pointSizeF() * fretNumMag);
478 QFontMetricsF fm2(scaledFont, MScore::paintDevice());
479 qreal numw = fm2.width(QString("%1").arg(_fretOffset+1));
480 qreal xdiff = numw + stringDist * .4;
481 w += xdiff;
482 x += (_numPos == 0) == (_orientation == Orientation::VERTICAL) ? -xdiff : 0;
483 }
484
485 if (_orientation == Orientation::HORIZONTAL) {
486 qreal tempW = w,
487 tempX = x;
488 w = h;
489 h = tempW;
490 x = y;
491 y = tempX;
492 }
493
494 bbox().setRect(x, y, w, h);
495
496 if (!parent() || !parent()->isSegment()) {
497 setPos(QPointF());
498 return;
499 }
500
501 // We need to get the width of the notehead/rest in order to position the fret diagram correctly
502 Segment* pSeg = toSegment(parent());
503 qreal noteheadWidth = 0;
504 if (pSeg->isChordRestType()) {
505 int idx = staff()->idx();
506 for (Element* e = pSeg->firstElementOfSegment(pSeg, idx); e; e = pSeg->nextElementOfSegment(pSeg, e, idx)) {
507 if (e->isRest()) {
508 Rest* r = toRest(e);
509 noteheadWidth = symWidth(r->sym());
510 break;
511 }
512 else if (e->isNote()) {
513 Note* n = toNote(e);
514 noteheadWidth = n->headWidth();
515 break;
516 }
517 }
518 }
519
520 qreal mainWidth = 0.0;
521 if (_orientation == Orientation::VERTICAL)
522 mainWidth = stringDist * (_strings - 1);
523 else if (_orientation == Orientation::HORIZONTAL)
524 mainWidth = fretDist * (_frets + 0.5);
525 setPos((noteheadWidth - mainWidth)/2, -(h + styleP(Sid::fretY)));
526
527 autoplaceSegmentElement();
528
529 // don't display harmony in palette
530 if (!parent())
531 return;
532
533 if (_harmony)
534 _harmony->layout();
535 if (_harmony && _harmony->autoplace() && _harmony->parent()) {
536 Segment* s = toSegment(parent());
537 Measure* m = s->measure();
538 int si = staffIdx();
539
540 SysStaff* ss = m->system()->staff(si);
541 QRectF r = _harmony->bbox().translated(m->pos() + s->pos() + pos() + _harmony->pos() + QPointF(_harmony->xShapeOffset(), 0.0));
542
543 qreal minDistance = _harmony->minDistance().val() * spatium();
544 SkylineLine sk(false);
545 sk.add(r.x(), r.bottom(), r.width());
546 qreal d = sk.minDistance(ss->skyline().north());
547 if (d > -minDistance) {
548 qreal yd = d + minDistance;
549 yd *= -1.0;
550 _harmony->rypos() += yd;
551 r.translate(QPointF(0.0, yd));
552 }
553 if (_harmony->addToSkyline())
554 ss->skyline().add(r);
555 }
556 }
557
558 //---------------------------------------------------------
559 // centerX
560 /// used by harmony for layout. Keep in sync with layout, same dotd and x as above
561 // also used in Element::canvasPos().
562 //---------------------------------------------------------
563
centerX() const564 qreal FretDiagram::centerX() const
565 {
566 qreal dotd = spatium() * _userMag * .49 * score()->styleD(Sid::fretDotSize);
567 qreal x = -((dotd+stringLw) * .5);
568 return bbox().right() * .5 + x;
569 }
570
571 //---------------------------------------------------------
572 // write
573 // NOTICE: if you are looking to change how fret diagrams are
574 // written, edit the writeNew function. writeOld is purely compatibility.
575 //---------------------------------------------------------
576
577 static const std::array<Pid, 8> pids { {
578 Pid::MIN_DISTANCE,
579 Pid::FRET_OFFSET,
580 Pid::FRET_FRETS,
581 Pid::FRET_STRINGS,
582 Pid::FRET_NUT,
583 Pid::MAG,
584 Pid::FRET_NUM_POS,
585 Pid::ORIENTATION
586 } };
587
write(XmlWriter & xml) const588 void FretDiagram::write(XmlWriter& xml) const
589 {
590 if (!xml.canWrite(this))
591 return;
592 xml.stag(this);
593
594 // Write properties first and only once
595 for (Pid p : pids) {
596 writeProperty(xml, p);
597 }
598 Element::writeProperties(xml);
599
600 if (_harmony)
601 _harmony->write(xml);
602
603 // Lowercase f indicates new writing format
604 // TODO: in the next score format version (4) use only write new + props and discard
605 // the compatibility writing.
606 xml.stag("fretDiagram");
607 writeNew(xml);
608 xml.etag();
609
610 writeOld(xml);
611 xml.etag();
612 }
613
614 //---------------------------------------------------------
615 // writeOld
616 // This is the old method of writing. This is for backwards
617 // compatibility with < 3.1 versions.
618 //---------------------------------------------------------
619
writeOld(XmlWriter & xml) const620 void FretDiagram::writeOld(XmlWriter& xml) const
621 {
622 int lowestDotFret = -1;
623 int furthestLeftLowestDot = -1;
624
625 // Do some checks for details needed for checking whether to add barres
626 for (int i = 0; i < _strings; ++i) {
627 std::vector<FretItem::Dot> allDots = dot(i);
628
629 bool dotExists = false;
630 for (auto const& d : allDots) {
631 if (d.exists()) {
632 dotExists = true;
633 break;
634 }
635 }
636
637 if (!dotExists)
638 continue;
639
640 for (auto const& d : allDots) {
641 if (d.exists()) {
642 if (d.fret < lowestDotFret || lowestDotFret == -1) {
643 lowestDotFret = d.fret;
644 furthestLeftLowestDot = i;
645 }
646 else if (d.fret == lowestDotFret && (i < furthestLeftLowestDot || furthestLeftLowestDot == -1)) {
647 furthestLeftLowestDot = i;
648 }
649 }
650 }
651 }
652
653 // The old system writes a barre as a bool, which causes no problems in any way, not at all.
654 // So, only write that if the barre is on the lowest fret with a dot,
655 // and there are no other dots on its fret, and it goes all the way to the right.
656 int barreStartString = -1;
657 int barreFret = -1;
658 for (auto const& i : _barres) {
659 FretItem::Barre b = i.second;
660 if (b.exists()) {
661 int fret = i.first;
662 if (fret <= lowestDotFret && b.endString == -1 && !(fret == lowestDotFret && b.startString > furthestLeftLowestDot)) {
663 barreStartString = b.startString;
664 barreFret = fret;
665 break;
666 }
667 }
668 }
669
670 for (int i = 0; i < _strings; ++i) {
671 FretItem::Marker m = marker(i);
672 std::vector<FretItem::Dot> allDots = dot(i);
673
674 bool dotExists = false;
675 for (auto const& d : allDots) {
676 if (d.exists()) {
677 dotExists = true;
678 break;
679 }
680 }
681
682 if (!dotExists && !m.exists() && i != barreStartString)
683 continue;
684
685 xml.stag(QString("string no=\"%1\"").arg(i));
686
687 if (m.exists())
688 xml.tag("marker", FretItem::markerToChar(m.mtype).unicode());
689
690 for (auto const& d : allDots) {
691 if (d.exists() && !(i == barreStartString && d.fret == barreFret)) {
692 xml.tag("dot", d.fret);
693 }
694 }
695
696 // Add dot so barre will display in pre-3.1
697 if (barreStartString == i) {
698 xml.tag("dot", barreFret);
699 }
700
701 xml.etag();
702 }
703
704 if (barreFret > 0)
705 xml.tag("barre", 1);
706 }
707
708 //---------------------------------------------------------
709 // writeNew
710 // This is the important one for 3.1+
711 //---------------------------------------------------------
712
writeNew(XmlWriter & xml) const713 void FretDiagram::writeNew(XmlWriter& xml) const
714 {
715 for (int i = 0; i < _strings; ++i) {
716 FretItem::Marker m = marker(i);
717 std::vector<FretItem::Dot> allDots = dot(i);
718
719 bool dotExists = false;
720 for (auto const& d : allDots) {
721 if (d.exists()) {
722 dotExists = true;
723 break;
724 }
725 }
726
727 // Only write a string if we have anything to write
728 if (!dotExists && !m.exists())
729 continue;
730
731 // Start the string writing
732 xml.stag(QString("string no=\"%1\"").arg(i));
733
734 // Write marker
735 if (m.exists())
736 xml.tag("marker", FretItem::markerTypeToName(m.mtype));
737
738 // Write any dots
739 for (auto const& d : allDots) {
740 if (d.exists()) {
741 // TODO: write fingering
742 xml.tag(QString("dot fret=\"%1\"").arg(d.fret), FretItem::dotTypeToName(d.dtype));
743 }
744 }
745
746 xml.etag();
747 }
748
749 for (int f = 1; f <= _frets; ++f) {
750 FretItem::Barre b = barre(f);
751 if (!b.exists())
752 continue;
753
754 xml.tag(QString("barre start=\"%1\" end=\"%2\"").arg(b.startString).arg(b.endString), f);
755 }
756 }
757
758 //---------------------------------------------------------
759 // read
760 //---------------------------------------------------------
761
read(XmlReader & e)762 void FretDiagram::read(XmlReader& e)
763 {
764 // Read the old format first
765 bool hasBarre = false;
766 bool haveReadNew = false;
767
768 while (e.readNextStartElement()) {
769 const QStringRef& tag(e.name());
770
771 // Check for new format fret diagram
772 if (haveReadNew) {
773 e.skipCurrentElement();
774 continue;
775 }
776 if (tag == "fretDiagram") {
777 readNew(e);
778 haveReadNew = true;
779 }
780
781 // Check for new properties
782 else if (tag == "showNut")
783 readProperty(e, Pid::FRET_NUT);
784 else if (tag == "orientation")
785 readProperty(e, Pid::ORIENTATION);
786
787 // Then read the rest if there is no new format diagram (compatibility read)
788 else if (tag == "strings")
789 readProperty(e, Pid::FRET_STRINGS);
790 else if (tag == "frets")
791 readProperty(e, Pid::FRET_FRETS);
792 else if (tag == "fretOffset")
793 readProperty(e, Pid::FRET_OFFSET);
794 else if (tag == "string") {
795 int no = e.intAttribute("no");
796 while (e.readNextStartElement()) {
797 const QStringRef& t(e.name());
798 if (t == "dot")
799 setDot(no, e.readInt());
800 else if (t == "marker")
801 setMarker(no, QChar(e.readInt()) == 'X' ? FretMarkerType::CROSS : FretMarkerType::CIRCLE);
802 /*else if (t == "fingering")
803 setFingering(no, e.readInt());*/
804 else
805 e.unknown();
806 }
807 }
808 else if (tag == "barre")
809 hasBarre = e.readBool();
810 else if (tag == "mag")
811 readProperty(e, Pid::MAG);
812 else if (tag == "Harmony") {
813 Harmony* h = new Harmony(score());
814 h->read(e);
815 add(h);
816 }
817 else if (!Element::readProperties(e))
818 e.unknown();
819 }
820
821 // Old handling of barres
822 if (hasBarre) {
823 for (int s = 0; s < _strings; ++s) {
824 for (auto& d : dot(s)) {
825 if (d.exists()) {
826 setBarre(s, -1, d.fret);
827 return;
828 }
829 }
830 }
831 }
832 }
833
834 //---------------------------------------------------------
835 // readNew
836 // read the new 'fretDiagram' tag
837 //---------------------------------------------------------
838
readNew(XmlReader & e)839 void FretDiagram::readNew(XmlReader& e)
840 {
841 while (e.readNextStartElement()) {
842 const QStringRef& tag(e.name());
843
844 if (tag == "string") {
845 int no = e.intAttribute("no");
846 while (e.readNextStartElement()) {
847 const QStringRef& t(e.name());
848 if (t == "dot") {
849 int fret = e.intAttribute("fret", 0);
850 FretDotType dtype = FretItem::nameToDotType(e.readElementText());
851 setDot(no, fret, true, dtype);
852 }
853 else if (t == "marker") {
854 FretMarkerType mtype = FretItem::nameToMarkerType(e.readElementText());
855 setMarker(no, mtype);
856 }
857 else if (t == "fingering") {
858 e.readElementText();
859 /*setFingering(no, e.readInt()); NOTE:JT todo */
860 }
861 else
862 e.unknown();
863 }
864 }
865 else if (tag == "barre") {
866 int start = e.intAttribute("start", -1);
867 int end = e.intAttribute("end", -1);
868 int fret = e.readInt();
869
870 setBarre(start, end, fret);
871 }
872 else if (!Element::readProperties(e))
873 e.unknown();
874 }
875 }
876
877 //---------------------------------------------------------
878 // setDot
879 // take a fret value of 0 to mean remove the dot, except with add
880 // where we actually need to pass a fret val.
881 //---------------------------------------------------------
882
setDot(int string,int fret,bool add,FretDotType dtype)883 void FretDiagram::setDot(int string, int fret, bool add /*= false*/, FretDotType dtype /*= FretDotType::NORMAL*/)
884 {
885 if (fret == 0)
886 removeDot(string, fret);
887 else if (string >= 0 && string < _strings) {
888 // Special case - with add, if there is a dot in the position, remove it
889 // If not, add it.
890 if (add) {
891 if (dot(string, fret)[0].exists()) {
892 removeDot(string, fret);
893 return; // We are done here, all we needed to do was remove a single dot
894 }
895 }
896 else
897 _dots[string].clear();
898
899 _dots[string].push_back(FretItem::Dot(fret, dtype));
900 if (!add)
901 setMarker(string, FretMarkerType::NONE);
902 }
903 }
904
905 //---------------------------------------------------------
906 // setMarker
907 // Removal of dots and barres if "Multiple dots" is inactive
908 // is handled in FretCanvas::mousePressEvent()
909 //---------------------------------------------------------
910
setMarker(int string,FretMarkerType mtype)911 void FretDiagram::setMarker(int string, FretMarkerType mtype)
912 {
913 if (string >= 0 && string < _strings) {
914 _markers[string] = FretItem::Marker(mtype);
915 }
916 }
917
918 //---------------------------------------------------------
919 // setFingering
920 // NOTE:JT: todo possible future feature
921 //---------------------------------------------------------
922
923 #if 0
924 void FretDiagram::setFingering(int string, int finger)
925 {
926 if (_dots.find(string) != _dots.end()) {
927 _dots[string].fingering = finger;
928 qDebug("set finger: s %d finger %d", string, finger);
929 }
930 }
931 #endif
932
933 //---------------------------------------------------------
934 // setBarre
935 // We'll accept a value of -1 for the end string, to denote
936 // that the barre goes as far right as possible.
937 // Take a start string value of -1 to mean 'remove this barre'
938 //---------------------------------------------------------
939
setBarre(int startString,int endString,int fret)940 void FretDiagram::setBarre(int startString, int endString, int fret)
941 {
942 if (startString == -1)
943 removeBarre(fret);
944 else if (startString >= 0 && endString >= -1 && startString < _strings && endString < _strings)
945 _barres[fret] = FretItem::Barre(startString, endString);
946 }
947
948 //---------------------------------------------------------
949 // This version is for clicks on a dot with shift.
950 // If there is no barre at fret, then add one with the string as the start.
951 // If there is a barre with a -1 end string, set the end string to string.
952 // If there is a barre with a set start and end, remove it.
953 // Add may be used in the future if we decide to add dots as default with barres.
954 //---------------------------------------------------------
955
setBarre(int string,int fret,bool add)956 void FretDiagram::setBarre(int string, int fret, bool add /*= false*/)
957 {
958 Q_UNUSED(add);
959
960 FretItem::Barre b = barre(fret);
961 if (!b.exists()) {
962 if (string < _strings - 1) {
963 _barres[fret] = FretItem::Barre(string, -1);
964 removeDotsMarkers(string, -1, fret);
965 }
966 }
967 else if (b.endString == -1 && b.startString < string) {
968 _barres[fret].endString = string;
969 }
970 else {
971 removeDotsMarkers(b.startString, b.endString, fret);
972 removeBarre(fret);
973 }
974 }
975
976 //---------------------------------------------------------
977 // undoSetFretDot
978 //---------------------------------------------------------
979
undoSetFretDot(int _string,int _fret,bool _add,FretDotType _dtype)980 void FretDiagram::undoSetFretDot(int _string, int _fret, bool _add /*= true*/, FretDotType _dtype /*= FretDotType::NORMAl*/)
981 {
982 for (ScoreElement* e : linkList()) {
983 FretDiagram* fd = toFretDiagram(e);
984 fd->score()->undo(new FretDot(fd, _string, _fret, _add, _dtype));
985 }
986 }
987
988 //---------------------------------------------------------
989 // undoSetFretMarker
990 //---------------------------------------------------------
991
undoSetFretMarker(int _string,FretMarkerType _mtype)992 void FretDiagram::undoSetFretMarker(int _string, FretMarkerType _mtype)
993 {
994 for (ScoreElement* e : linkList()) {
995 FretDiagram* fd = toFretDiagram(e);
996 fd->score()->undo(new FretMarker(fd, _string, _mtype));
997 }
998 }
999
1000 //---------------------------------------------------------
1001 // undoSetFretBarre
1002 // add refers to using multiple dots per string when adding dots automatically
1003 //---------------------------------------------------------
1004
undoSetFretBarre(int _string,int _fret,bool _add)1005 void FretDiagram::undoSetFretBarre(int _string, int _fret, bool _add /*= false*/)
1006 {
1007 for (ScoreElement* e : linkList()) {
1008 FretDiagram* fd = toFretDiagram(e);
1009 fd->score()->undo(new FretBarre(fd, _string, _fret, _add));
1010 }
1011 }
1012
1013 //---------------------------------------------------------
1014 // removeBarre
1015 // Remove a barre on a given fret.
1016 //---------------------------------------------------------
1017
removeBarre(int f)1018 void FretDiagram::removeBarre(int f)
1019 {
1020 _barres.erase(f);
1021 }
1022
1023 //---------------------------------------------------------
1024 // removeBarres
1025 // Remove barres crossing a certain point. Fret of 0 means any point along
1026 // the string.
1027 //---------------------------------------------------------
1028
removeBarres(int string,int fret)1029 void FretDiagram::removeBarres(int string, int fret /*= 0*/)
1030 {
1031 auto iter = _barres.begin();
1032 while (iter != _barres.end()) {
1033 int bfret = iter->first;
1034 FretItem::Barre b = iter->second;
1035
1036 if (b.exists() && b.startString <= string && (b.endString >= string || b.endString == -1)) {
1037 if (fret > 0 && fret != bfret)
1038 ++iter;
1039 else
1040 iter = _barres.erase(iter);
1041 }
1042 else
1043 ++iter;
1044 }
1045 }
1046
1047 //---------------------------------------------------------
1048 // removeMarker
1049 //---------------------------------------------------------
1050
removeMarker(int s)1051 void FretDiagram::removeMarker(int s)
1052 {
1053 auto it = _markers.find(s);
1054 _markers.erase(it);
1055 }
1056
1057 //---------------------------------------------------------
1058 // removeDot
1059 // take a fret value of 0 to mean remove all dots
1060 //---------------------------------------------------------
1061
removeDot(int s,int f)1062 void FretDiagram::removeDot(int s, int f /*= 0*/)
1063 {
1064 if (f > 0) {
1065 std::vector<FretItem::Dot> tempDots;
1066 for (auto const& d : dot(s)) {
1067 if (d.exists() && d.fret != f)
1068 tempDots.push_back(FretItem::Dot(d));
1069 }
1070
1071 _dots[s] = tempDots;
1072 }
1073 else
1074 _dots[s].clear();
1075
1076 if (_dots[s].size() == 0) {
1077 auto it = _dots.find(s);
1078 _dots.erase(it);
1079 }
1080 }
1081
1082 //---------------------------------------------------------
1083 // removeDotsMarkers
1084 // removes all markers between [ss, es] and dots between [ss, es],
1085 // where the dots have a fret of fret.
1086 //---------------------------------------------------------
1087
removeDotsMarkers(int ss,int es,int fret)1088 void FretDiagram::removeDotsMarkers(int ss, int es, int fret)
1089 {
1090 if (ss == -1)
1091 return;
1092
1093 int end = es == -1 ? _strings : es;
1094 for (int string = ss; string <= end; ++string) {
1095 removeDot(string, fret);
1096
1097 if (marker(string).exists())
1098 removeMarker(string);
1099 }
1100 }
1101
1102 //---------------------------------------------------------
1103 // clear
1104 //---------------------------------------------------------
1105
clear()1106 void FretDiagram::clear()
1107 {
1108 _barres.clear();
1109 _dots.clear();
1110 _markers.clear();
1111 }
1112
1113 //---------------------------------------------------------
1114 // undoFretClear
1115 //---------------------------------------------------------
1116
undoFretClear()1117 void FretDiagram::undoFretClear()
1118 {
1119 for (ScoreElement* e : linkList()) {
1120 FretDiagram* fd = toFretDiagram(e);
1121 fd->score()->undo(new FretClear(fd));
1122 }
1123 }
1124
1125 //---------------------------------------------------------
1126 // dot
1127 // take fret value of zero to mean all dots
1128 //---------------------------------------------------------
1129
dot(int s,int f) const1130 std::vector<FretItem::Dot> FretDiagram::dot(int s, int f /*= 0*/) const
1131 {
1132 if (_dots.find(s) != _dots.end()) {
1133 if (f != 0) {
1134 for (auto const& d : _dots.at(s)) {
1135 if (d.fret == f)
1136 return std::vector<FretItem::Dot> { FretItem::Dot(d) };
1137 }
1138 }
1139 else
1140 return _dots.at(s);
1141 }
1142 return std::vector<FretItem::Dot> { FretItem::Dot(0) };
1143 }
1144
1145 //---------------------------------------------------------
1146 // marker
1147 //---------------------------------------------------------
1148
marker(int s) const1149 FretItem::Marker FretDiagram::marker(int s) const
1150 {
1151 if (_markers.find(s) != _markers.end())
1152 return _markers.at(s);
1153 return FretItem::Marker(FretMarkerType::NONE);
1154 }
1155
1156 //---------------------------------------------------------
1157 // barre
1158 //---------------------------------------------------------
1159
barre(int f) const1160 FretItem::Barre FretDiagram::barre(int f) const
1161 {
1162 if (_barres.find(f) != _barres.end())
1163 return _barres.at(f);
1164 return FretItem::Barre(-1, -1);
1165 }
1166
1167 //---------------------------------------------------------
1168 // setHarmony
1169 /// if this is being done by the user, use undoSetHarmony instead
1170 //---------------------------------------------------------
1171
setHarmony(QString harmonyText)1172 void FretDiagram::setHarmony(QString harmonyText)
1173 {
1174 if (!_harmony) {
1175 Harmony* h = new Harmony(score());
1176 add(h);
1177 }
1178
1179 _harmony->setHarmony(harmonyText);
1180 _harmony->setXmlText(_harmony->harmonyName());
1181 triggerLayout();
1182 }
1183
1184 //---------------------------------------------------------
1185 // add
1186 //---------------------------------------------------------
1187
add(Element * e)1188 void FretDiagram::add(Element* e)
1189 {
1190 e->setParent(this);
1191 if (e->isHarmony()) {
1192 _harmony = toHarmony(e);
1193 _harmony->setTrack(track());
1194 if (_harmony->propertyFlags(Pid::OFFSET) == PropertyFlags::STYLED)
1195 _harmony->resetProperty(Pid::OFFSET);
1196 _harmony->setProperty(Pid::ALIGN, int(Align::HCENTER | Align::TOP));
1197 _harmony->setPropertyFlags(Pid::ALIGN, PropertyFlags::UNSTYLED);
1198 }
1199 else {
1200 qWarning("FretDiagram: cannot add <%s>\n", e->name());
1201 }
1202 }
1203
1204 //---------------------------------------------------------
1205 // remove
1206 //---------------------------------------------------------
1207
remove(Element * e)1208 void FretDiagram::remove(Element* e)
1209 {
1210 if (e == _harmony)
1211 _harmony = 0;
1212 else
1213 qWarning("FretDiagram: cannot remove <%s>\n", e->name());
1214 }
1215
1216 //---------------------------------------------------------
1217 // acceptDrop
1218 //---------------------------------------------------------
1219
acceptDrop(EditData & data) const1220 bool FretDiagram::acceptDrop(EditData& data) const
1221 {
1222 return data.dropElement->type() == ElementType::HARMONY;
1223 }
1224
1225 //---------------------------------------------------------
1226 // drop
1227 //---------------------------------------------------------
1228
drop(EditData & data)1229 Element* FretDiagram::drop(EditData& data)
1230 {
1231 Element* e = data.dropElement;
1232 if (e->isHarmony()) {
1233 Harmony* h = toHarmony(e);
1234 h->setParent(parent());
1235 h->setTrack(track());
1236 score()->undoAddElement(h);
1237 }
1238 else {
1239 qWarning("FretDiagram: cannot drop <%s>\n", e->name());
1240 delete e;
1241 e = 0;
1242 }
1243 return e;
1244 }
1245
1246 //---------------------------------------------------------
1247 // scanElements
1248 //---------------------------------------------------------
1249
scanElements(void * data,void (* func)(void *,Element *),bool all)1250 void FretDiagram::scanElements(void* data, void (*func)(void*, Element*), bool all)
1251 {
1252 Q_UNUSED(all);
1253 func(data, this);
1254 // don't display harmony in palette
1255 if (_harmony && !!parent())
1256 func(data, _harmony);
1257 }
1258
1259 //---------------------------------------------------------
1260 // Write MusicXML
1261 //---------------------------------------------------------
1262
writeMusicXML(XmlWriter & xml) const1263 void FretDiagram::writeMusicXML(XmlWriter& xml) const
1264 {
1265 qDebug("FretDiagram::writeMusicXML() this %p harmony %p", this, _harmony);
1266 xml.stag("frame");
1267 xml.tag("frame-strings", _strings);
1268 xml.tag("frame-frets", frets());
1269
1270 for (int i = 0; i < _strings; ++i) {
1271 int mxmlString = _strings - i;
1272
1273 std::vector<int> bStarts;
1274 std::vector<int> bEnds;
1275 for (auto const& j : _barres) {
1276 FretItem::Barre b = j.second;
1277 int fret = j.first;
1278 if (!b.exists())
1279 continue;
1280
1281 if (b.startString == i)
1282 bStarts.push_back(fret);
1283 else if (b.endString == i || (b.endString == -1 && mxmlString == 1))
1284 bEnds.push_back(fret);
1285 }
1286
1287 if (marker(i).exists() && marker(i).mtype == FretMarkerType::CIRCLE) {
1288 xml.stag("frame-note");
1289 xml.tag("string", mxmlString);
1290 xml.tag("fret", "0");
1291 xml.etag();
1292 }
1293 else {
1294 // Write dots
1295 for (auto const& d : dot(i)) {
1296 if (!d.exists())
1297 continue;
1298 xml.stag("frame-note");
1299 xml.tag("string", mxmlString);
1300 xml.tag("fret", d.fret);
1301 // TODO: write fingerings
1302
1303 // Also write barre if it starts at this dot
1304 if (std::find(bStarts.begin(), bStarts.end(), d.fret) != bStarts.end()) {
1305 xml.tagE("barre type=\"start\"");
1306 bStarts.erase(std::remove(bStarts.begin(), bStarts.end(), d.fret), bStarts.end());
1307 }
1308 if (std::find(bEnds.begin(), bEnds.end(), d.fret) != bEnds.end()) {
1309 xml.tagE("barre type=\"stop\"");
1310 bEnds.erase(std::remove(bEnds.begin(), bEnds.end(), d.fret), bEnds.end());
1311 }
1312 xml.etag();
1313 }
1314 }
1315
1316 // Write unwritten barres
1317 for (int j : bStarts) {
1318 xml.stag("frame-note");
1319 xml.tag("string", mxmlString);
1320 xml.tag("fret", j);
1321 xml.tagE("barre type=\"start\"");
1322 xml.etag();
1323 }
1324
1325 for (int j : bEnds) {
1326 xml.stag("frame-note");
1327 xml.tag("string", mxmlString);
1328 xml.tag("fret", j);
1329 xml.tagE("barre type=\"stop\"");
1330 xml.etag();
1331 }
1332 }
1333
1334 xml.etag();
1335 }
1336
1337 //---------------------------------------------------------
1338 // getProperty
1339 //---------------------------------------------------------
1340
getProperty(Pid propertyId) const1341 QVariant FretDiagram::getProperty(Pid propertyId) const
1342 {
1343 switch (propertyId) {
1344 case Pid::MAG:
1345 return userMag();
1346 case Pid::FRET_STRINGS:
1347 return strings();
1348 case Pid::FRET_FRETS:
1349 return frets();
1350 case Pid::FRET_NUT:
1351 return showNut();
1352 case Pid::FRET_OFFSET:
1353 return fretOffset();
1354 case Pid::FRET_NUM_POS:
1355 return _numPos;
1356 case Pid::ORIENTATION:
1357 return int(_orientation);
1358 break;
1359 default:
1360 return Element::getProperty(propertyId);
1361 }
1362 }
1363
1364 //---------------------------------------------------------
1365 // setProperty
1366 //---------------------------------------------------------
1367
setProperty(Pid propertyId,const QVariant & v)1368 bool FretDiagram::setProperty(Pid propertyId, const QVariant& v)
1369 {
1370 switch (propertyId) {
1371 case Pid::MAG:
1372 setUserMag(v.toDouble());
1373 break;
1374 case Pid::FRET_STRINGS:
1375 setStrings(v.toInt());
1376 break;
1377 case Pid::FRET_FRETS:
1378 setFrets(v.toInt());
1379 break;
1380 case Pid::FRET_NUT:
1381 setShowNut(v.toBool());
1382 break;
1383 case Pid::FRET_OFFSET:
1384 setFretOffset(v.toInt());
1385 break;
1386 case Pid::FRET_NUM_POS:
1387 _numPos = v.toInt();
1388 break;
1389 case Pid::ORIENTATION:
1390 _orientation = Orientation(v.toInt());
1391 break;
1392 default:
1393 return Element::setProperty(propertyId, v);
1394 }
1395 triggerLayout();
1396 return true;
1397 }
1398
1399 //---------------------------------------------------------
1400 // propertyDefault
1401 //---------------------------------------------------------
1402
propertyDefault(Pid pid) const1403 QVariant FretDiagram::propertyDefault(Pid pid) const
1404 {
1405 // We shouldn't style the fret offset
1406 if (pid == Pid::FRET_OFFSET) {
1407 return QVariant(0);
1408 }
1409
1410 for (const StyledProperty& p : *styledProperties()) {
1411 if (p.pid == pid) {
1412 if (propertyType(pid) == P_TYPE::SP_REAL)
1413 return score()->styleP(p.sid);
1414 return score()->styleV(p.sid);
1415 }
1416 }
1417 return Element::propertyDefault(pid);
1418 }
1419
1420 //---------------------------------------------------------
1421 // endEditDrag
1422 //---------------------------------------------------------
1423
endEditDrag(EditData & editData)1424 void FretDiagram::endEditDrag(EditData& editData)
1425 {
1426 Element::endEditDrag(editData);
1427
1428 triggerLayout();
1429 }
1430
1431 //---------------------------------------------------------
1432 // accessibleInfo
1433 //---------------------------------------------------------
1434
accessibleInfo() const1435 QString FretDiagram::accessibleInfo() const
1436 {
1437 QString chordName = _harmony ? QObject::tr("with chord symbol %1").arg(_harmony->harmonyName()) : QObject::tr("without chord symbol");
1438 return QString("%1 %2").arg(userName(), chordName);
1439 }
1440
1441 //---------------------------------------------------------
1442 // screenReaderInfo
1443 //---------------------------------------------------------
1444
screenReaderInfo() const1445 QString FretDiagram::screenReaderInfo() const
1446 {
1447 QString detailedInfo;
1448 for (int i = 0; i < _strings; i++) {
1449 QString stringIdent = QObject::tr("string %1").arg(i + 1);
1450
1451 const FretItem::Marker& m = marker(i);
1452 QString markerName;
1453 switch (m.mtype) {
1454 case FretMarkerType::CIRCLE:
1455 markerName = QObject::tr("circle marker");
1456 break;
1457 case FretMarkerType::CROSS:
1458 markerName = QObject::tr("cross marker");
1459 break;
1460 case FretMarkerType::NONE:
1461 default:
1462 break;
1463 }
1464
1465 int dotsCount = 0;
1466 std::vector<int> fretsWithDots;
1467 for (auto const& d : dot(i)) {
1468 if (!d.exists())
1469 continue;
1470 fretsWithDots.push_back(d.fret + _fretOffset);
1471 dotsCount += 1;
1472 // TODO consider: do we need to announce what type of dot a dot is?
1473 // i.e. triangle, square, normal dot. It's mostly just information
1474 // that clutters the screenreader output and makes it harder to
1475 // understand, so leaving it out for now.
1476 }
1477
1478 if (dotsCount == 0 && markerName.length() == 0)
1479 continue;
1480
1481 QString fretInfo;
1482 if (dotsCount == 1) {
1483 fretInfo = QString("%1").arg(fretsWithDots.front());
1484 }
1485 else if (dotsCount > 1) {
1486 int max = int(fretsWithDots.size());
1487 for (int j = 0; j < max; j++) {
1488 if (j == max - 1)
1489 fretInfo = QObject::tr("%1 and %2").arg(fretInfo).arg(fretsWithDots[j]);
1490 else
1491 fretInfo = QString("%1 %2").arg(fretInfo).arg(fretsWithDots[j]);
1492 }
1493 }
1494
1495 //: Omit the "%n " for the singular translation (and the "(s)" too)
1496 QString dotsInfo = QObject::tr("%n dot(s) on fret(s) %1", "", dotsCount).arg(fretInfo);
1497
1498 detailedInfo = QString("%1 %2 %3 %4").arg(detailedInfo, stringIdent, markerName, dotsInfo);
1499 }
1500
1501 QString barreInfo;
1502 for (auto const& iter : _barres) {
1503 const FretItem::Barre& b = iter.second;
1504 if (!b.exists())
1505 continue;
1506
1507 QString fretInfo = QObject::tr("fret %1").arg(iter.first);
1508
1509 QString newBarreInfo;
1510 if (b.startString == 0 && (b.endString == -1 || b.endString == _strings - 1)) {
1511 newBarreInfo = QObject::tr("barré %1").arg(fretInfo);
1512 }
1513 else {
1514 QString startPart = QObject::tr("beginning string %1").arg(b.startString + 1);
1515 QString endPart;
1516 if (b.endString != -1)
1517 endPart = QObject::tr("and ending string %1").arg(b.endString + 1);
1518
1519 newBarreInfo = QObject::tr("partial barré %1 %2 %3").arg(fretInfo, startPart, endPart);
1520 }
1521
1522 barreInfo = QString("%1 %2").arg(barreInfo, newBarreInfo);
1523 }
1524
1525 detailedInfo = QString("%1 %2").arg(detailedInfo, barreInfo);
1526
1527 if (detailedInfo.trimmed().length() == 0)
1528 detailedInfo = QObject::tr("no content");
1529
1530 QString chordName = _harmony ? QObject::tr("with chord symbol %1").arg(_harmony->generateScreenReaderInfo()) : QObject::tr("without chord symbol");
1531 QString basicInfo = QString("%1 %2").arg(userName(), chordName);
1532
1533 QString generalInfo = QObject::tr("%n string(s) total", "", _strings);
1534
1535 QString res = QString("%1 %2 %3").arg(basicInfo, generalInfo, detailedInfo);
1536
1537 return res;
1538 }
1539
1540 //---------------------------------------------------------
1541 // markerToChar
1542 //---------------------------------------------------------
1543
markerToChar(FretMarkerType t)1544 QChar FretItem::markerToChar(FretMarkerType t)
1545 {
1546 switch (t) {
1547 case FretMarkerType::CIRCLE:
1548 return QChar('O');
1549 case FretMarkerType::CROSS:
1550 return QChar('X');
1551 case FretMarkerType::NONE:
1552 default:
1553 return QChar();
1554 }
1555 }
1556
1557 //---------------------------------------------------------
1558 // markerTypeToName
1559 //---------------------------------------------------------
1560
1561 const std::vector<FretItem::MarkerTypeNameItem> FretItem::markerTypeNameMap = {
1562 { FretMarkerType::CIRCLE, "circle" },
1563 { FretMarkerType::CROSS, "cross" },
1564 { FretMarkerType::NONE, "none" }
1565 };
1566
markerTypeToName(FretMarkerType t)1567 QString FretItem::markerTypeToName(FretMarkerType t)
1568 {
1569 for (auto i : FretItem::markerTypeNameMap) {
1570 if (i.mtype == t)
1571 return i.name;
1572 }
1573 qFatal("Unrecognised FretMarkerType!");
1574 return QString(); // prevent compiler warnings
1575 }
1576
1577 //---------------------------------------------------------
1578 // nameToMarkerType
1579 //---------------------------------------------------------
1580
nameToMarkerType(QString n)1581 FretMarkerType FretItem::nameToMarkerType(QString n)
1582 {
1583 for (auto i : FretItem::markerTypeNameMap) {
1584 if (i.name == n)
1585 return i.mtype;
1586 }
1587 qWarning("Unrecognised marker name!");
1588 return FretMarkerType::NONE; // default
1589 }
1590
1591 //---------------------------------------------------------
1592 // dotTypeToName
1593 //---------------------------------------------------------
1594
1595 const std::vector<FretItem::DotTypeNameItem> FretItem::dotTypeNameMap = {
1596 { FretDotType::NORMAL, "normal" },
1597 { FretDotType::CROSS, "cross" },
1598 { FretDotType::SQUARE, "square" },
1599 { FretDotType::TRIANGLE, "triangle" },
1600 };
1601
dotTypeToName(FretDotType t)1602 QString FretItem::dotTypeToName(FretDotType t)
1603 {
1604 for (auto i : FretItem::dotTypeNameMap) {
1605 if (i.dtype == t)
1606 return i.name;
1607 }
1608 qFatal("Unrecognised FretDotType!");
1609 return QString(); // prevent compiler warnings
1610 }
1611
1612 //---------------------------------------------------------
1613 // nameToDotType
1614 //---------------------------------------------------------
1615
nameToDotType(QString n)1616 FretDotType FretItem::nameToDotType(QString n)
1617 {
1618 for (auto i : FretItem::dotTypeNameMap) {
1619 if (i.name == n)
1620 return i.dtype;
1621 }
1622 qWarning("Unrecognised dot name!");
1623 return FretDotType::NORMAL; // default
1624 }
1625
1626 //---------------------------------------------------------
1627 // updateStored
1628 //---------------------------------------------------------
1629
FretUndoData(FretDiagram * fd)1630 FretUndoData::FretUndoData(FretDiagram* fd)
1631 {
1632 // We need to store the old barres and markers, since predicting how
1633 // adding dots, markers, barres etc. will change things is too difficult.
1634 // Update linked fret diagrams:
1635 _diagram = fd;
1636 _dots = _diagram->_dots;
1637 _markers = _diagram->_markers;
1638 _barres = _diagram->_barres;
1639 }
1640
1641 //---------------------------------------------------------
1642 // updateDiagram
1643 //---------------------------------------------------------
1644
updateDiagram()1645 void FretUndoData::updateDiagram()
1646 {
1647 if (!_diagram) {
1648 qFatal("Tried to undo fret diagram change without ever setting diagram!");
1649 return;
1650 }
1651
1652 // Reset every fret diagram property of the changed diagram
1653 // FretUndoData is a friend of FretDiagram so has access to these private members
1654 _diagram->_barres = _barres;
1655 _diagram->_markers = _markers;
1656 _diagram->_dots = _dots;
1657 }
1658
1659 }
1660
1661