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