1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Rosegarden
5     A MIDI and audio sequencer and musical notation editor.
6     Copyright 2000-2021 the Rosegarden development team.
7 
8     This file is Copyright 2007-2009
9         Yves Guillemot      <yc.guillemot@wanadoo.fr>
10 
11     Other copyrights also apply to some parts of this work.  Please
12     see the AUTHORS file and individual file headers for details.
13 
14     This program is free software; you can redistribute it and/or
15     modify it under the terms of the GNU General Public License as
16     published by the Free Software Foundation; either version 2 of the
17     License, or (at your option) any later version.  See the file
18     COPYING included with this distribution for more information.
19 */
20 
21 #include "StaffHeader.h"
22 #include "HeadersGroup.h"
23 #include "base/Composition.h"
24 #include "base/NotationTypes.h"
25 #include "base/StaffExportTypes.h"
26 #include "base/ColourMap.h"
27 #include "base/Track.h"
28 #include "base/Overlaps.h"
29 #include "gui/application/RosegardenMainWindow.h"
30 #include "gui/general/GUIPalette.h"
31 #include "document/RosegardenDocument.h"
32 #include "misc/Strings.h"
33 #include "misc/Debug.h"
34 #include "NotePixmapFactory.h"
35 #include "NotationScene.h"
36 #include "NotationStaff.h"
37 
38 #include "Inconsistencies.h"
39 
40 #include <vector>
41 #include <map>
42 #include <set>
43 #include <string>
44 #include <utility>
45 
46 #include <QApplication>
47 #include <QColor>
48 #include <QSize>
49 #include <QWidget>
50 #include <QHBoxLayout>
51 #include <QToolButton>
52 #include <QLabel>
53 #include <QFrame>
54 #include <QString>
55 #include <QGraphicsPixmapItem>
56 #include <QBitmap>
57 #include <QEvent>
58 #include <QMouseEvent>
59 #include <QPainter>
60 #include <QTimer>
61 #include <QTextEdit>
62 
63 
64 namespace Rosegarden
65 {
66 
67 
68 // Status bits
69 const int StaffHeader::SEGMENT_HERE                = 1 << 0;
70 const int StaffHeader::SUPERIMPOSED_SEGMENTS       = 1 << 1;
71 const int StaffHeader::INCONSISTENT_CLEFS          = 1 << 2;
72 const int StaffHeader::INCONSISTENT_KEYS           = 1 << 3;
73 const int StaffHeader::INCONSISTENT_LABELS         = 1 << 4;
74 const int StaffHeader::INCONSISTENT_TRANSPOSITIONS = 1 << 5;
75 const int StaffHeader::INCONSISTENT_COLOURS        = 1 << 6;
76 const int StaffHeader::BEFORE_FIRST_SEGMENT        = 1 << 7;
77 
78 
StaffHeader(HeadersGroup * group,TrackId trackId,int height,int ypos)79 StaffHeader::StaffHeader(HeadersGroup *group,
80                          TrackId trackId, int height, int ypos) :
81         QWidget(nullptr),
82         m_headersGroup(group),
83         m_track(trackId),
84         m_height(height),
85         m_ypos(ypos),
86         m_lastClef(Clef()),
87         m_lastKey(Rosegarden::Key()),
88         m_lastLabel(QString("")),
89         m_lastTranspose(0),
90         m_lastUpperText(QString("")),
91         m_neverUpdated(true),
92         m_lastStatusPart(0),
93         m_lastWidth(0),
94         m_clef(Clef()),
95         m_key(Rosegarden::Key()),
96         m_label(QString("")),
97         m_transpose(0),
98         m_status(0),
99         m_trackIsCurrent(false),
100         m_segmentIsCurrent(false),
101         m_upperText(QString("")),
102         m_transposeText(QString("")),
103         m_numberOfTextLines(0),
104         m_firstSeg(nullptr),
105         m_firstSegStartTime(0),
106         m_clefItem(nullptr),
107         m_keyItem(nullptr),
108         m_lineSpacing(0),
109         m_maxDelta(0),
110         m_staffLineThickness(0),
111         m_foreground(Qt::white),
112         m_background(Qt::black),
113         m_toolTipText(QString("")),
114         m_warningToolTipText(QString("")),
115         m_cursorPos(QPoint()),
116         m_toolTipTimer(nullptr),
117         m_toolTipCount(0),
118         m_colourIndex(0),
119         m_lastColourIndex(0),
120         m_clefOrKeyInconsistency(nullptr),
121         m_transposeOverlaps(nullptr),
122         m_clefOverlaps(nullptr),
123         m_keyOverlaps(nullptr),
124         m_clefOrKeyIsInconsistent(false),
125         m_clefOrKeyWasInconsistent(false),
126         m_transposeIsInconsistent(false),
127         m_transposeWasInconsistent(false),
128         m_noSegment(false)
129 {
130     // localStyle (search key)
131     //
132     // If/when we have the option to override the stylesheet, we should tweak
133     // m_foreground and m_background, which are currently hard coded to assume
134     // the stylesheet is in effect.  This may or may not make it into the first
135     // release.
136 
137     m_scene = m_headersGroup->getScene();
138 
139     //
140     // Tooltip text creation
141 
142     Composition *comp = m_headersGroup->getComposition();
143     Track *track = comp->getTrackById(m_track);
144     int trackPos = comp->getTrackPositionById(m_track);
145 
146     QString toolTipText = QString(tr("Track %1 : \"%2\"")
147                              .arg(trackPos + 1)
148                              .arg(strtoqstr(track->getLabel())));
149 
150     QString preset = strtoqstr(track->getPresetLabel());
151     if (preset != QString(""))
152         toolTipText += QString(tr("<br>Notate for: %1").arg(preset));
153 
154     QString notationSize = tr("normal");
155     switch (track->getStaffSize()) {
156         case StaffTypes::Small:
157             notationSize = tr("small");
158             break;
159         case StaffTypes::Tiny:
160             notationSize = tr("tiny");
161             break;
162     }
163 
164     QString bracketText = "--";
165     switch (track->getStaffBracket()) {
166         case Brackets::SquareOn:
167             bracketText = "[-";
168             break;
169         case Brackets::SquareOff:
170             bracketText = "-]";
171             break;
172         case Brackets::SquareOnOff:
173             bracketText = "[-]";
174             break;
175         case Brackets::CurlyOn:
176             bracketText = "{-";
177             break;
178         case Brackets::CurlyOff:
179             bracketText = "-}";
180             break;
181         case Brackets::CurlySquareOn:
182             bracketText = "{[-";
183             break;
184         case Brackets::CurlySquareOff:
185             bracketText = "-]}";
186             break;
187     }
188 
189     toolTipText += QString(tr("<br>Size: %1,  Bracket: %2 "))
190                             .arg(notationSize)
191                             .arg(bracketText);
192 
193     // Sort segments by position on the track
194     m_segments.clear();
195     std::vector<NotationStaff *> *staffs = m_scene->getStaffs();
196     for (size_t i = 0; i < staffs->size(); i++) {
197 
198         NotationStaff *notationStaff = (*staffs)[i];
199         Segment &segment = notationStaff->getSegment();
200         TrackId trackId = segment.getTrack();
201 
202         if (trackId  == m_track) {
203             m_segments.insert(&segment);
204         }
205     }
206 
207     for (SortedSegments::iterator i=m_segments.begin();
208                                      i!=m_segments.end(); ++i) {
209         timeT segStart = (*i)->getStartTime();
210         timeT segEnd = (*i)->getEndMarkerTime();
211         int barStart = comp->getBarNumber(segStart) + 1;
212         int barEnd = comp->getBarNumber(segEnd) + 1;
213 
214         int transpose = (*i)->getTranspose();
215         if (transpose) {
216             QString transposeName = transposeValueToName(transpose);
217             toolTipText += QString(tr("<br>bars [%1-%2] in %3 (tr=%4) : \"%5\""))
218                                     .arg(barStart)
219                                     .arg(barEnd)
220                                     .arg(transposeName)
221                                     .arg(transpose)
222                                     .arg(strtoqstr((*i)->getLabel()));
223         } else {
224             toolTipText += QString(tr("<br>bars [%1-%2] (tr=%3) : \"%4\""))
225                                     .arg(barStart)
226                                     .arg(barEnd)
227                                     .arg(transpose)
228                                     .arg(strtoqstr((*i)->getLabel()));
229         }
230     }
231 
232     // not translated to spare the translators the pointless effort of copying
233     // and pasting this tag for every language we support
234     m_toolTipText = "<qt><p>" + toolTipText + "</p></qt>";
235 
236     // With Qt3  "this->setToolTip(m_toolTipText);"  would have been
237     // called here.
238     // This is no more well working with the new Qt4 RG implementation because
239     // the StaffHeader widget is now embedded in a QGraphicsScene :
240     //   - The tool tip would be clipped by the QGraphicsView
241     //   - The tool tip would be zoomed whith the scene.
242     //
243     // Now the showToolTip signal, carrying the tool tip text, is emitted
244     // from the staff header main event handler when a tool tip event occurs.
245     // This signal is connected to NotationWidget::slotShowHeaderToolTip()
246     // from the headers group. The notation widget displays the tool tip,
247     // without clipping nor resizing it, when it receives this signal.
248 
249     if (m_segments.begin() == m_segments.end()) {
250         RG_WARNING << "No segments on this track";
251         m_noSegment = true;
252         return;
253     } else {
254         m_noSegment = false;
255     }
256 
257     m_firstSeg = *m_segments.begin();
258     m_firstSegStartTime = m_firstSeg->getStartTime();
259 
260     /// This may not work if two segments are superimposed
261     /// at the beginning of the track (inconsistencies are
262     /// not detected).
263     ///   TODO : Look for the first segment(s) in
264     ///          lookAtStaff() and not here.
265 
266 
267     // Create buttons to warn/inform on inconsistencies in overlapping segments
268     int size = 4 * m_scene->getNotePixmapFactory()->getSize();
269     m_clefOrKeyInconsistency = new QToolButton;
270     m_clefOrKeyInconsistency->setIcon(QIcon(":/pixmaps/misc/inconsistency.png"));
271     m_clefOrKeyInconsistency->setIconSize(QSize(size, size));
272     // Don't call setToolTip directly but use the local tooltip implementation
273     m_warningToolTipText = tr("<qt><p>Notation is not consistent</p>"
274                               "<p>Click to get more information</p></qt>");
275     // Needed to see mouseMoveEvent inside this QToolButton widget from
276     // the current StaffHeader widget and to know when to show the tooltip
277     m_clefOrKeyInconsistency->setMouseTracking(true);
278 
279     // Add a layout where to place the warning icon
280     QHBoxLayout *hbox = new QHBoxLayout;
281     hbox->addWidget(m_clefOrKeyInconsistency);
282     setLayout(hbox);
283 
284     // Icon is hidden
285     m_clefOrKeyInconsistency->hide();
286 
287     // Connect the warning icon to a show popup slot
288     connect(m_clefOrKeyInconsistency, &QAbstractButton::clicked,
289             this, &StaffHeader::slotShowInconsistencies);
290 
291 
292     // Implement a ToolTip event replacement (see enterEvent(), leaveEvent and
293     // mouseMoveEvent()).
294     m_toolTipTimer = new QTimer(this);
295     connect(m_toolTipTimer, &QTimer::timeout, this, &StaffHeader::slotToolTip);
296     m_toolTipTimer->setSingleShot(false);
297     m_toolTipTimer->setInterval(200);  // 0.2 s
298     setMouseTracking(true);
299 
300     connect(m_headersGroup, &HeadersGroup::currentSegmentChanged,
301             this, &StaffHeader::slotSetCurrent);
302 
303 
304     // Create three objects where to find possible inconsistencies
305     // when segments overlap
306 
307     // Create a vector to pass all the segments of a track to the Overlaps ctors
308     // and, in the same time, set the header as observer of these segments.
309     std::vector<Segment *> segVec;
310     for (SortedSegments::iterator i=m_segments.begin();
311                                       i!=m_segments.end(); ++i) {
312         segVec.push_back(*i);
313         (*i)->addObserver(this);
314     }
315 
316     m_clefOverlaps = new Inconsistencies<Clef>(segVec);
317     m_keyOverlaps = new Inconsistencies<Key>(segVec);
318     m_transposeOverlaps = new Inconsistencies<int>(segVec);
319 
320     // Look for the initial current track
321 /// TODO : LOOK FOR CURRENT SEGMENT
322     m_trackIsCurrent = m_headersGroup->getCurrentTrackId() == m_track;
323 }
324 
~StaffHeader()325 StaffHeader::~StaffHeader()
326 {
327     if (m_noSegment) return;   /// OK here ?
328 
329     delete m_toolTipTimer;
330     delete m_clefItem;
331     delete m_keyItem;
332 
333     delete m_clefOverlaps;
334     delete m_keyOverlaps;
335     delete m_transposeOverlaps;
336 
337     for (SortedSegments::iterator i=m_segments.begin();
338                                       i!=m_segments.end(); ++i) {
339         (*i)->removeObserver(this);
340     }
341 }
342 
343 void
paintEvent(QPaintEvent *)344 StaffHeader::paintEvent(QPaintEvent *)
345 {
346     if (m_noSegment) return;
347 
348     // avoid common random crash by brute force
349     if (!m_clefItem) {
350         RG_WARNING << "StaffHeader::paintEvent() - m_clefItem was nullptr.\n"
351                    << "  Skipping this paintEvent to avoid a crash.\n"
352                    << "  This is a BUG which should no longer occur. (rev 11137)";
353         return;
354     }
355 
356     QPainter paint(this);
357     paint.fillRect(0, 0, width(), height(), m_background);  /// ???
358 
359     int wHeight = height();
360     int wWidth = width();
361 
362     wHeight -= 4;    // Make room to the label frame :
363                      // 4 = 2 * (margin + lineWidth)
364 wWidth -= 4; /// ???
365 
366     int lw = m_lineSpacing;
367     int h;
368     QColor colour;
369     int maxDelta = m_maxDelta;
370 
371     // Staff Y position inside the whole header
372     int offset = (wHeight - 10 * lw -1) / 2;
373 
374     // Draw staff lines
375     paint.setPen(QPen(QColor(m_foreground), m_staffLineThickness));
376     for (h = 0; h <= 8; h += 2) {
377         int y = (lw * 3) + ((8 - h) * lw) / 2;
378         paint.drawLine(maxDelta/2, y + offset, wWidth - maxDelta/2, y + offset);
379     }
380 
381     if (isAClefToDraw()) {
382 
383         // Draw clef
384         QPixmap clefPixmap = m_clefItem->pixmap();
385 
386         h = m_clef.getAxisHeight();
387         int y = (lw * 3) + ((8 - h) * lw) / 2;
388         paint.drawPixmap(maxDelta, y + m_clefItem->offset().y() + offset, clefPixmap);
389 
390         // Draw key
391         QPixmap keyPixmap = m_keyItem->pixmap();
392         y = lw;   /// Why ???
393         paint.drawPixmap((maxDelta * 3) / 2+ clefPixmap.width(),
394                          y + m_keyItem->offset().y() + offset, keyPixmap);
395 
396     }
397 
398 
399     NotePixmapFactory * npf = m_scene->getNotePixmapFactory();
400     paint.setFont(npf->getTrackHeaderFont());
401 
402     QString text;
403     QString textLine;
404 
405     int charHeight = npf->getTrackHeaderFontMetrics().height();
406     int charWidth = npf->getTrackHeaderFontMetrics().maxWidth();
407 
408     const QString transposeText = getTransposeText();
409     QRect bounds = npf->getTrackHeaderBoldFontMetrics()
410                                    .boundingRect(transposeText);
411     int transposeWidth = bounds.width();
412 
413 
414     // Write upper text (track name and track label)
415 
416     int numberOfTextLines = getNumberOfTextLines();
417 
418 
419     // Limit between upper text and central part
420     // int upperTextY = charHeight
421     //               + numberOfTextLines * npf->getTrackHeaderTextLineSpacing();
422 
423     paint.setPen(m_foreground);
424     text = getUpperText();
425 
426     for (int l=1; l<=numberOfTextLines; l++) {
427         int upperTextY = charHeight
428                          + (l - 1) * npf->getTrackHeaderTextLineSpacing();
429         if (l == numberOfTextLines) {
430             int transposeSpace = transposeWidth ? transposeWidth + charWidth / 4 : 0;
431             textLine = npf->getOneLine(text, m_lastWidth - transposeSpace - charWidth / 2);
432             if (!text.isEmpty()) {
433                 // String too long : cut it and replace last character with dots
434                 int len = textLine.length();
435                 if (len > 1) textLine.replace(len - 1, 1, tr("..."));
436             }
437         } else {
438             textLine = npf->getOneLine(text, m_lastWidth - charWidth / 2);
439         }
440         if (textLine.isEmpty()) break;
441         paint.drawText(charWidth / 4, upperTextY, textLine);
442     }
443 
444 
445     // Write transposition text
446 
447     // TODO : use colours from GUIPalette
448     colour = QColor(m_foreground);;
449     paint.setFont(npf->getTrackHeaderBoldFont());
450     paint.setPen(colour);
451 
452     paint.drawText(m_lastWidth - transposeWidth - charWidth / 4,
453                    charHeight + (numberOfTextLines - 1)
454                                       * npf->getTrackHeaderTextLineSpacing(),
455                    transposeText);
456 
457 
458     // Write lower text (segment label)
459 
460     // Limit between upper text and central part
461     // int lowerTextY = wHeight - 4            // -4 : adjust
462     //               - numberOfTextLines * npf->getTrackHeaderTextLineSpacing();
463 
464     // Draw top and bottom separation line
465     paint.setPen(QColor(m_foreground));
466     paint.drawLine(0, 0, width(), 0);
467     paint.drawLine(0, height(), width(), height());
468 
469     // TODO : use colours from GUIPalette
470     colour = QColor(m_foreground);
471     paint.setFont(npf->getTrackHeaderFont());
472     paint.setPen(colour);
473     text = isLabelInconsistent() ? QString("???????") : getLowerText();
474 
475     for (int l=1; l<=numberOfTextLines; l++) {
476         int lowerTextY = wHeight - 4            // -4 : adjust
477             - (numberOfTextLines - l) * npf->getTrackHeaderTextLineSpacing();
478 
479         QString textLine = npf->getOneLine(text, m_lastWidth - charWidth / 2);
480         if (textLine.isEmpty()) break;
481 
482         if ((l == numberOfTextLines)  && !text.isEmpty()) {
483                 // String too long : cut it and replace last character by dots
484                 int len = textLine.length();
485                 if (len > 1) textLine.replace(len - 1, 1, tr("..."));
486         }
487 
488         paint.drawText(charWidth / 4, lowerTextY, textLine);
489     }
490 
491     // Draw a blue rectangle around the header if staff is the current one
492     if (m_trackIsCurrent) {
493         QPen pen;
494 
495         // Select a visible color depending of the background intensity
496         if (m_foreground == Qt::black) {
497             pen.setColor(QColor(0, 0, 255));       // Blue
498         } else {
499             pen.setColor(QColor(170, 170, 255));   // Lighter blue
500         }
501 
502         // Draw the rectangle
503         // TODO : Use strokePath() rather than drawLine()
504         pen.setWidth(4);
505         paint.setPen(pen);
506         paint.drawLine(1, 1, wWidth - 1, 1);
507         paint.drawLine(wWidth - 1, 1, wWidth - 1, height() - 1);
508         paint.drawLine(wWidth - 1, height() - 1, 1, height() - 1);
509         paint.drawLine(1, height() - 1, 1, 1);
510     }
511 }
512 
513 void
slotSetCurrent()514 StaffHeader::slotSetCurrent()
515 {
516     m_trackIsCurrent = m_headersGroup->getCurrentTrackId() == m_track;
517     if (m_trackIsCurrent && setCurrentSegmentVisible()) {
518         m_neverUpdated = true;  /// Hack to force updateHeader() working
519         updateHeader(m_lastWidth);   // Show the selected segment and track
520     } else {
521         update();                    // Show or hide the blue rectangle
522     }
523 }
524 
525 bool
setCurrentSegmentVisible()526 StaffHeader::setCurrentSegmentVisible()
527 {
528     if (!m_trackIsCurrent) return false;
529 
530     if (m_status & BEFORE_FIRST_SEGMENT) {
531         m_segmentIsCurrent = m_headersGroup
532                              ->timeIsInCurrentSegment(m_firstSegStartTime);
533     } else {
534         m_segmentIsCurrent = m_headersGroup
535                              ->timeIsInCurrentSegment(m_headersGroup
536                                                       ->getStartOfViewTime());
537     }
538 
539     if (m_segmentIsCurrent) {
540         Segment *s = m_headersGroup->getCurrentSegment();
541         m_label = strtoqstr(s->getLabel());
542         m_transpose = s->getTranspose();
543         m_colourIndex = s->getColourIndex();
544         return true;
545     }
546 
547     return false;
548 }
549 
550 QString
transposeValueToName(int transpose)551 StaffHeader::transposeValueToName(int transpose)
552 {
553 
554     /// TODO : Should be rewritten using methods from Pitch class
555 
556     int noteIndex = transpose % 12;
557     if (noteIndex < 0) noteIndex += 12;
558 
559     switch(noteIndex) {
560         case  0 : return tr("C",  "note name");
561         case  1 : return tr("C#", "note name");
562         case  2 : return tr("D",  "note name");
563         case  3 : return tr("Eb", "note name");
564         case  4 : return tr("E",  "note name");
565         case  5 : return tr("F",  "note name");
566         case  6 : return tr("F#", "note name");
567         case  7 : return tr("G",  "note name");
568         case  8 : return tr("G#", "note name");
569         case  9 : return tr("A",  "note name");
570         case 10 : return tr("Bb", "note name");
571         case 11 : return tr("B",  "note name");
572     }
573 
574     return QString("???");   // Only here to remove compiler warning
575 }
576 
577 int
lookAtStaff(double x,int maxWidth)578 StaffHeader::lookAtStaff(double x, int maxWidth)
579 {
580     // Read Clef and Key on scene at (x, m_ypos + m_height / 2)
581     // then guess the header needed width and return it
582 
583     if (m_noSegment) return 0;
584 
585     // When walking through the segments :
586     //    clef, key, label and transpose are current values
587     //    clef0, key0, label0 and transpose0 are preceding values used to look
588     //                                       for inconsistencies
589     //    key1, label1 and transpose1 are "visible" (opposed at invisible as are
590     //                                key=<C major>, label="" or transpose=0)
591     //                                preceding or current values which may be
592     //                                displayed with a red colour if some
593     //                                inconsistency occurs.
594     Clef clef, clef0;
595     Rosegarden::Key key, key0, key1 = Rosegarden::Key("C major");
596     QString label = QString(""), label0, label1 = QString("");
597     int transpose = 0, transpose0 = 0, transpose1 = 0;
598     unsigned int colourIndex = 0, colourIndex0 = 0;
599 
600     // size_t staff;
601 
602     Composition *comp = m_headersGroup->getComposition();
603     Track *track = comp->getTrackById(m_track);
604     int trackPos = comp->getTrackPositionById(m_track);
605     std::vector<NotationStaff *> *staffs = m_scene->getStaffs();
606 
607     int status = 0;
608     for (size_t i = 0; i < staffs->size(); i++) {
609         NotationStaff *notationStaff = (*staffs)[i];
610         Segment &segment = notationStaff->getSegment();
611         TrackId trackId = segment.getTrack();
612         if (trackId  == m_track) {
613             /// TODO : What if a segment has been moved ???
614             timeT xTime = notationStaff->getTimeAtSceneCoords(x, m_ypos);
615             if (xTime < m_firstSegStartTime) {
616                 status |= BEFORE_FIRST_SEGMENT;
617                 /// TODO : What if superimposed segments ???
618                 m_firstSeg->getFirstClefAndKey(clef, key);
619                 label = strtoqstr(m_firstSeg->getLabel());
620                 transpose = m_firstSeg->getTranspose();
621                 colourIndex = m_firstSeg->getColourIndex();
622                 break;
623             }
624             timeT segStart = segment.getStartTime();
625             timeT segEnd = segment.getEndMarkerTime();
626 
627             if ((xTime >= segStart) && (xTime < segEnd)) {
628 
629                 notationStaff->getClefAndKeyAtSceneCoords(x,
630                                             m_ypos + m_height / 2, clef, key);
631                 label = strtoqstr(segment.getLabel());
632                 transpose = segment.getTranspose();
633                 colourIndex = segment.getColourIndex();
634 
635                 if (status & SEGMENT_HERE) {
636                     status |= SUPERIMPOSED_SEGMENTS;
637                     if (clef != clef0)
638                         status |= INCONSISTENT_CLEFS;
639                     if (key != key0)
640                         status |= INCONSISTENT_KEYS;
641                     if (label != label0)
642                         status |= INCONSISTENT_LABELS;
643                     if (transpose != transpose0)
644                         status |= INCONSISTENT_TRANSPOSITIONS;
645                     if (colourIndex != colourIndex0)
646                         status |= INCONSISTENT_COLOURS;
647                 } else {
648                     status |= SEGMENT_HERE;
649                 }
650 
651                 // staff = i;
652 
653                 // If current value is visible, remember it
654                 if (key.getAccidentalCount()) key1 = key;
655                 if (label.trimmed().length()) label1 = label;
656                 if (transpose) transpose1 = transpose;
657 
658                 // Current values become last values
659                 clef0 = clef;
660                 key0 = key;
661                 label0 = label;
662                 transpose0 = transpose;
663                 colourIndex0 = colourIndex;
664             }                                                // if(xTime...)
665         }                                                // if(trackId...)
666     }
667 
668     // Remember current data (but only visible data if inconsistency)
669     m_status = status;
670     m_clef = clef;
671     m_key = (status & INCONSISTENT_KEYS) ? key1 : key;
672     if (!setCurrentSegmentVisible()) {
673         m_label = (status & INCONSISTENT_LABELS) ? label1 : label;
674         m_transpose = (status & INCONSISTENT_TRANSPOSITIONS) ? transpose1 : transpose;
675         m_colourIndex = colourIndex;
676     }
677 
678 /// Try to show current segment label rather than ??? when segment is current.
679 /// May be not a so good idea...
680     QString noteName = isTransposeInconsistent() && !m_segmentIsCurrent ?
681                            QString("???") : transposeValueToName(m_transpose);
682 
683     m_upperText = QString(tr("%1: %2")
684                                  .arg(trackPos + 1)
685                                  .arg(strtoqstr(track->getLabel())));
686     if (m_transpose) m_transposeText = tr(" in %1").arg(noteName);
687     else             m_transposeText = QString("");
688 
689     NotePixmapFactory * npf = m_scene->getNotePixmapFactory();
690     int clefAndKeyWidth = npf->getClefAndKeyWidth(m_key, m_clef);
691 
692     // How many text lines may be written above or under the clef
693     // in track header ?
694     m_numberOfTextLines = npf->getTrackHeaderNTL(m_height);
695 
696     int trackLabelWidth =
697              npf->getTrackHeaderTextWidth(m_upperText + m_transposeText)
698                                                         / m_numberOfTextLines;
699     int segmentNameWidth =
700              npf->getTrackHeaderTextWidth(m_label) / m_numberOfTextLines;
701 
702     // Get the max. width from upper text and lower text
703     int width = (segmentNameWidth > trackLabelWidth)
704                              ? segmentNameWidth : trackLabelWidth;
705 
706     // Text width is limited by max header Width
707     if (width > maxWidth) width = maxWidth;
708 
709     // But clef and key width may override max header width
710     if (width < clefAndKeyWidth) width = clefAndKeyWidth;
711 
712     // Is clef or key inconsistent somewhere in the current view ?
713     timeT start = m_headersGroup->getStartOfViewTime();
714     timeT end = m_headersGroup->getEndOfViewTime();
715     m_clefOrKeyIsInconsistent = !m_clefOverlaps->isConsistent(start, end)
716         || !m_keyOverlaps->isConsistent(start, end);
717 
718     // Is transposition inconsistent somewhere in the current view ?
719     m_transposeIsInconsistent = !m_transposeOverlaps->isConsistent(start, end);
720 
721     return width;
722 }
723 
724 
725 void
updateHeader(int width)726 StaffHeader::updateHeader(int width)
727 {
728     // Update the header (using given width) if necessary
729 
730     if (m_noSegment) return;
731 
732     // updateHeader() must be executed the first time it is called
733     // (ie when m_neverUpdated is true) as it initialized some data
734     // later used by paintEvent()).
735     // But to execute it when the headers are not visible is useless.
736     if (!m_headersGroup->isVisible() && !m_neverUpdated) return;
737 
738     // Filter out bits whose display doesn't depend from
739     int statusPart = m_status & ~(SUPERIMPOSED_SEGMENTS);
740 
741     // Header should be updated only if necessary
742     if (    m_neverUpdated
743          || (width != m_lastWidth)
744          || (statusPart != m_lastStatusPart)
745          || (m_key != m_lastKey)
746          || (m_clef != m_lastClef)
747          || (m_label != m_lastLabel)
748          || (m_upperText != m_lastUpperText)
749          || (m_transpose != m_lastTranspose)
750          || (m_colourIndex != m_lastColourIndex)
751          || (m_clefOrKeyIsInconsistent != m_clefOrKeyWasInconsistent)
752          || (m_transposeIsInconsistent != m_transposeWasInconsistent)) {
753 
754         m_neverUpdated = false;
755         m_lastStatusPart = statusPart;
756         m_lastKey = m_key;
757         m_lastClef = m_clef;
758         m_lastLabel = m_label;
759         m_lastTranspose = m_transpose;
760         m_lastColourIndex = m_colourIndex;
761         m_lastUpperText = m_upperText;
762         m_clefOrKeyWasInconsistent = m_clefOrKeyIsInconsistent;
763         m_transposeWasInconsistent = m_transposeIsInconsistent;
764 
765 
766         NotePixmapFactory * npf = m_scene->getNotePixmapFactory();
767 
768         // Get background colour from colour index
769         m_background = m_headersGroup->getComposition()
770                            ->getSegmentColourMap()
771                                .getColour(m_colourIndex);
772 
773         // Select foreground colour (black or white) to get the better
774         // visibility
775         int intensity = qGray(m_background.rgb());
776         if (intensity > 127) {
777             m_foreground = Qt::black;
778             m_foregroundType = NotePixmapFactory::PlainColour;
779         } else {
780             m_foreground = Qt::white;
781             m_foregroundType = NotePixmapFactory::PlainColourLight;
782         }
783 
784         // Fix staff header variant of bug #2997311 (Part 1)
785         bool selectedMode = npf->isSelected();
786         npf->setSelected(false);
787         bool shadedMode = npf->isShaded();
788         npf->setShaded(false);
789 
790         delete m_clefItem;
791         m_clefItem = npf->makeClef(m_clef, m_foregroundType);
792 
793         delete m_keyItem;
794         m_keyItem = npf->makeKey(m_key, m_clef,
795                                  Rosegarden::Key("C major"), m_foregroundType);
796 
797         // Fix staff header variant of bug #2997311 (Part 2)
798         npf->setSelected(selectedMode);
799         npf->setShaded(shadedMode);
800 
801         m_lineSpacing = npf->getLineSpacing();
802         m_maxDelta = npf->getAccidentalWidth(Accidentals::Sharp);
803         m_staffLineThickness = npf->getStaffLineThickness();
804 
805         setFixedWidth(width);
806         setFixedHeight(m_height);
807 
808         // Forced width may differ from localy computed width
809         m_lastWidth = width;
810 
811         // Show or hide the warning icons
812         if (m_clefOrKeyIsInconsistent || m_transposeIsInconsistent) {
813             m_clefOrKeyInconsistency->show();
814         } else {
815             m_clefOrKeyInconsistency->hide();
816         }
817     }
818 
819     update();
820 }
821 
822 
823 bool
operator ()(const Segment * s1,const Segment * s2) const824 StaffHeader::SegmentCmp::operator()(const Segment * s1, const Segment * s2) const
825 {
826     // Sort segments by start time, then by end time, then by address.
827     // The last comparison garantees two segments will never be equals and
828     // allows to remove easily one of them from the m_segments multiset.
829     // (Now, a set may replace the multiset.)
830     if (s1->getStartTime() < s2->getStartTime()) return true;
831     if (s1->getStartTime() > s2->getStartTime()) return false;
832     if (s1->getEndMarkerTime() < s2->getEndMarkerTime()) return true;
833     if (s1->getEndMarkerTime() > s2->getEndMarkerTime()) return false;
834     return (long) s1 < (long) s2;
835 }
836 
837 
838 
839 // bool
840 // StaffHeader::event(QEvent *event)
841 // {
842 //     if (event->type() == QEvent::ToolTip) {
843 //         emit showToolTip(m_toolTipText);
844 //         return true;
845 //     }
846 //
847 //     return QWidget::event(event);
848 // }
849 //
850 // For some reason ToolTip event is not received after a change of font size
851 // (ie after staff headers have been deleted then recreated) or when the first
852 // staff headers show() is called late (ie is called after some other action
853 // occured, but I was unable to determine what this action is).
854 // The 4 following methods and m_toolTipTimer are used to replace that
855 // ToolTip event.
856 // Moreover, QToolTip::showText() called from NotationWidget when the
857 // showToolTip() signal is emitted displays the tooltip for a short duration
858 // only. That's why m_toolTipCount is used with a multipleshot m_toolTipTimer
859 // to get a reasonable tooltip display duration (10 s).
860 
861 
862 void
863 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
enterEvent(QEnterEvent *)864 StaffHeader::enterEvent(QEnterEvent */* event */)
865 #else
866 StaffHeader::enterEvent(QEvent */* event */)
867 #endif
868 {
869     // Start timer when mouse enters
870     m_toolTipTimer->start();
871     m_toolTipCount = 50;   // Set a 10 s tooltip duration if timer is 0.2 s
872 }
873 
874 void
leaveEvent(QEvent *)875 StaffHeader::leaveEvent(QEvent */* event */)
876 {
877     // Stop timer when mouse leaves
878     m_toolTipTimer->stop();
879     m_toolTipCount = 0;
880 }
881 
882 void
mouseMoveEvent(QMouseEvent * event)883 StaffHeader::mouseMoveEvent(QMouseEvent *event)
884 {
885     // Restart timer while mouse is moving
886     m_toolTipTimer->start();
887 
888     // and remember current cursor position
889     m_cursorPos = event->pos();
890 }
891 
892 void
slotToolTip()893 StaffHeader::slotToolTip()
894 {
895     // Timeout occured : show the tool tip while countdown unfinished
896 
897     if (m_toolTipCount-- <= 0)  return;
898 
899     // First select what tool tip to display then show it
900     QRect inconIconRect = m_clefOrKeyInconsistency->frameGeometry();
901     if ((m_clefOrKeyIsInconsistent || m_transposeIsInconsistent)
902          && inconIconRect.contains(m_cursorPos)) {
903         emit showToolTip(m_warningToolTipText);
904     } else {
905         emit showToolTip(m_toolTipText);
906     }
907 }
908 
909 
910 
911 // void
912 // StaffHeader::mousePressEvent(QMouseEvent *event)
913 // {
914 //     int h = height();
915 //     int offset = (h - 10 * m_lineSpacing -1) / 2;
916 //     if (event->y() > (h - offset)) {
917 /// Here is a place to call a popup menu to select what segment is the current
918 /// one when segments are overlapping.
919 //         std::cerr << "START MENU\n";
920 //     }
921 // }
922 
923 
924 void
slotShowInconsistencies()925 StaffHeader::slotShowInconsistencies()
926 {
927     Composition *comp = m_headersGroup->getComposition();
928     Track *track = comp->getTrackById(m_track);
929     int trackPos = comp->getTrackPositionById(m_track);
930 
931     QString str = tr("<h2>Notation Inconsistencies</h2>");
932 
933     str += tr("<h3>Filename: %1 </h3>")
934              .arg(RosegardenMainWindow::self()->getDocument()->getTitle());
935 
936     str += tr("<h3>Track %1: \"%2\"</h3>").arg(trackPos + 1)
937                                           .arg(strtoqstr(track->getLabel()));
938 
939     if (!m_clefOverlaps->isConsistent()) {
940         str += QString("<br><b>");
941         str += tr("Overlapping segments with inconsistent clefs:");
942         str += QString("</b>");
943         m_clefOverlaps->display(str, comp, tr("Segment \"%1\": %2 clef"));
944     }
945 
946     if (!m_keyOverlaps->isConsistent()) {
947         str += QString("<br><b>");
948         str += tr("Overlapping segments with inconsistent keys:");
949         str += QString("</b>");
950         m_keyOverlaps->display(str, comp, tr("Segment \"%1\": %2 key"));
951     }
952 
953     if (!m_transposeOverlaps->isConsistent()) {
954         str += QString("<br><b>");
955         str += tr("Overlapping segments with inconsistent transpositions:");
956         str += QString("</b>");
957         m_transposeOverlaps->display(str, comp, tr("Segment \"%1\": %2"));
958     }
959 
960     QTextEdit *warning = new QTextEdit(str);
961     warning->setReadOnly(true);
962     warning->setAttribute(Qt::WA_DeleteOnClose);
963     warning->setWindowTitle(tr("Rosegarden")); // (in case we ever go cross-platform)
964     warning->setWindowFlags(Qt::Dialog); // Get a popup in middle of screen
965     warning->setMinimumWidth(500);
966     warning->show();
967     connect(this, &QObject::destroyed, warning, &QWidget::close);
968 }
969 
970 void
eventAdded(const Segment *,Event * ev)971 StaffHeader::eventAdded(const Segment */* seg */, Event *ev)
972 {
973    if (ev->isa(Key::EventType) || ev->isa(Clef::EventType)) {
974         emit staffModified();
975     }
976 }
977 
978 void
eventRemoved(const Segment *,Event * ev)979 StaffHeader::eventRemoved(const Segment */* seg */, Event *ev)
980 {
981     if (ev->isa(Key::EventType) || ev->isa(Clef::EventType)) {
982         emit staffModified();
983     }
984 }
985 
986 void
appearanceChanged(const Segment *)987 StaffHeader::appearanceChanged(const Segment */* seg */)
988 {
989         emit staffModified();
990 }
991 
992 void
startChanged(const Segment *,timeT)993 StaffHeader::startChanged(const Segment */* seg */, timeT)
994 {
995         emit staffModified();
996 }
997 
998 void
endMarkerTimeChanged(const Segment *,bool)999 StaffHeader::endMarkerTimeChanged(const Segment */* seg */, bool /*shorten*/)
1000 {
1001         emit staffModified();
1002 }
1003 
1004 void
transposeChanged(const Segment *,int)1005 StaffHeader::transposeChanged(const Segment */* seg */, int)
1006 {
1007         emit staffModified();
1008 }
1009 
1010 void
segmentDeleted(const Segment * seg)1011 StaffHeader::segmentDeleted(const Segment *seg)
1012 {
1013     Segment *s = const_cast<Segment *>(seg);
1014 
1015     // Remove one segment from m_segments, the right segment and only one
1016     // segment, even if the comparison operator used in SortedSegments is
1017     // not as good as it would be wished.
1018     std::pair<SortedSegments::iterator, SortedSegments::iterator> range;
1019     range = m_segments.equal_range(s);
1020     for (SortedSegments::iterator i=m_segments.begin();
1021                                       i!=m_segments.end(); ++i) {
1022         if (*i == s) {
1023             m_segments.erase(i);
1024             break;
1025         }
1026     }
1027 
1028     emit staffModified();
1029 }
1030 
1031 
1032 }
1033 
1034 
1035