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