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     Other copyrights also apply to some parts of this work.  Please
9     see the AUTHORS file and individual file headers for details.
10 
11     This program is free software; you can redistribute it and/or
12     modify it under the terms of the GNU General Public License as
13     published by the Free Software Foundation; either version 2 of the
14     License, or (at your option) any later version.  See the file
15     COPYING included with this distribution for more information.
16 */
17 
18 #define RG_MODULE_STRING "[StaffLayout]"
19 #define RG_NO_DEBUG_PRINT 1
20 
21 #include "StaffLayout.h"
22 
23 #include "base/Event.h"
24 #include "base/LayoutEngine.h"
25 #include "base/NotationTypes.h"
26 #include "base/Profiler.h"
27 #include "base/SnapGrid.h"
28 #include "base/ViewElement.h"
29 #include "base/ViewSegment.h"
30 #include "gui/editors/notation/BarLineItem.h"
31 #include "gui/general/GUIPalette.h"
32 #include "misc/Debug.h"
33 
34 #include <QColor>
35 #include <QFont>
36 #include <QFontMetrics>
37 #include <QPen>
38 #include <QRect>
39 #include <QString>
40 #include <QGraphicsScene>
41 #include <QGraphicsItem>
42 #include <QGraphicsLineItem>
43 #include <QGraphicsRectItem>
44 #include <QGraphicsSimpleTextItem>
45 #include <QtGlobal>
46 
47 #include <algorithm>
48 
49 
50 namespace Rosegarden
51 {
52 
53 const int pointerWidth = 3;
54 
StaffLayout(QGraphicsScene * scene,ViewSegment * viewSegment,SnapGrid * snapGrid,int id,int resolution,int lineThickness)55 StaffLayout::StaffLayout(QGraphicsScene *scene, ViewSegment *viewSegment,
56                          SnapGrid *snapGrid, int id,
57                          int resolution, int lineThickness) :
58     m_scene(scene),
59     m_viewSegment(viewSegment),
60     m_snapGrid(snapGrid),
61     m_id(id),
62     m_x(0.0),
63     m_y(0),
64     m_margin(0.0),
65     m_titleHeight(0),
66     m_resolution(resolution),
67     m_lineThickness(lineThickness),
68     m_pageMode(LinearMode),
69     m_pageWidth(2000.0),  // fairly arbitrary, but we need something non-zero
70     m_rowsPerPage(0),
71     m_rowSpacing(0),
72     m_connectingLineLength(0),
73     m_startLayoutX(0),
74     m_endLayoutX(0),
75     m_current(false)
76 {
77 }
78 
StaffLayout(QGraphicsScene * scene,ViewSegment * viewSegment,SnapGrid * snapGrid,int id,int resolution,int lineThickness,double pageWidth,int rowsPerPage,int rowSpacing)79 StaffLayout::StaffLayout(QGraphicsScene *scene, ViewSegment *viewSegment,
80                          SnapGrid *snapGrid,
81                          int id, int resolution, int lineThickness,
82                          double pageWidth, int rowsPerPage, int rowSpacing) :
83     m_scene(scene),
84     m_viewSegment(viewSegment),
85     m_snapGrid(snapGrid),
86     m_id(id),
87     m_x(0.0),
88     m_y(0),
89     m_margin(0.0),
90     m_titleHeight(0),
91     m_resolution(resolution),
92     m_lineThickness(lineThickness),
93     m_pageMode(rowsPerPage ? MultiPageMode : ContinuousPageMode),
94     m_pageWidth(pageWidth),
95     m_rowsPerPage(rowsPerPage),
96     m_rowSpacing(rowSpacing),
97     m_connectingLineLength(0),
98     m_startLayoutX(0),
99     m_endLayoutX(0),
100     m_current(false)
101 {
102 }
103 
StaffLayout(QGraphicsScene * scene,ViewSegment * viewSegment,SnapGrid * snapGrid,int id,int resolution,int lineThickness,PageMode pageMode,double pageWidth,int rowsPerPage,int rowSpacing)104 StaffLayout::StaffLayout(QGraphicsScene *scene, ViewSegment *viewSegment,
105                          SnapGrid *snapGrid,
106                          int id, int resolution, int lineThickness,
107                          PageMode pageMode, double pageWidth, int rowsPerPage,
108                          int rowSpacing) :
109     m_scene(scene),
110     m_viewSegment(viewSegment),
111     m_snapGrid(snapGrid),
112     m_id(id),
113     m_x(0.0),
114     m_y(0),
115     m_margin(0.0),
116     m_titleHeight(0),
117     m_resolution(resolution),
118     m_lineThickness(lineThickness),
119     m_pageMode(pageMode),
120     m_pageWidth(pageWidth),
121     m_rowsPerPage(rowsPerPage),
122     m_rowSpacing(rowSpacing),
123     m_connectingLineLength(0),
124     m_startLayoutX(0),
125     m_endLayoutX(0),
126     m_current(false)
127 {
128 }
129 
~StaffLayout()130 StaffLayout::~StaffLayout()
131 {
132     deleteBars();
133     for (int i = 0; i < (int)m_staffLines.size(); ++i) clearStaffLineRow(i);
134 }
135 
136 void
setResolution(int resolution)137 StaffLayout::setResolution(int resolution)
138 {
139     m_resolution = resolution;
140 }
141 
142 void
setLineThickness(int lineThickness)143 StaffLayout::setLineThickness(int lineThickness)
144 {
145     m_lineThickness = lineThickness;
146 }
147 
148 void
setPageMode(PageMode pageMode)149 StaffLayout::setPageMode(PageMode pageMode)
150 {
151     m_pageMode = pageMode;
152 }
153 
154 void
setPageWidth(double pageWidth)155 StaffLayout::setPageWidth(double pageWidth)
156 {
157     m_pageWidth = pageWidth;
158 }
159 
160 void
setRowsPerPage(int rowsPerPage)161 StaffLayout::setRowsPerPage(int rowsPerPage)
162 {
163     m_rowsPerPage = rowsPerPage;
164 }
165 
166 void
setRowSpacing(int rowSpacing)167 StaffLayout::setRowSpacing(int rowSpacing)
168 {
169     m_rowSpacing = rowSpacing;
170 }
171 
172 void
setConnectingLineLength(int connectingLineLength)173 StaffLayout::setConnectingLineLength(int connectingLineLength)
174 {
175     m_connectingLineLength = connectingLineLength;
176 }
177 
178 int
getId() const179 StaffLayout::getId() const
180 {
181     return m_id;
182 }
183 
184 void
setX(double x)185 StaffLayout::setX(double x)
186 {
187     m_x = x;
188 }
189 
190 double
getX() const191 StaffLayout::getX() const
192 {
193     return m_x;
194 }
195 
196 void
setY(int y)197 StaffLayout::setY(int y)
198 {
199     m_y = y;
200 }
201 
202 int
getY() const203 StaffLayout::getY() const
204 {
205     return m_y;
206 }
207 
208 void
setMargin(double margin)209 StaffLayout::setMargin(double margin)
210 {
211     m_margin = margin;
212 }
213 
214 double
getMargin() const215 StaffLayout::getMargin() const
216 {
217     if (m_pageMode != MultiPageMode)
218         return 0;
219     return m_margin;
220 }
221 
222 void
setTitleHeight(int titleHeight)223 StaffLayout::setTitleHeight(int titleHeight)
224 {
225     m_titleHeight = titleHeight;
226 }
227 
228 int
getTitleHeight() const229 StaffLayout::getTitleHeight() const
230 {
231     return m_titleHeight;
232 }
233 
234 double
getTotalWidth() const235 StaffLayout::getTotalWidth() const
236 {
237     switch (m_pageMode) {
238 
239     case ContinuousPageMode:
240         return getSceneXForRightOfRow(getRowForLayoutX(m_endLayoutX)) - m_x;
241 
242     case MultiPageMode:
243         return getSceneXForRightOfRow(getRowForLayoutX(m_endLayoutX)) + m_margin - m_x;
244 
245     case LinearMode:
246     default:
247         return getSceneXForLayoutX(m_endLayoutX) - m_x;
248     }
249 }
250 
251 int
getTotalHeight() const252 StaffLayout::getTotalHeight() const
253 {
254     switch (m_pageMode) {
255 
256     case ContinuousPageMode:
257         return getSceneYForTopOfStaff(getRowForLayoutX(m_endLayoutX)) +
258                getHeightOfRow() - m_y;
259 
260     case MultiPageMode:
261         return getSceneYForTopOfStaff(m_rowsPerPage - 1) +
262                getHeightOfRow() - m_y;
263 
264     case LinearMode:
265     default:
266         return getSceneYForTopOfStaff(0) + getHeightOfRow() - m_y;
267     }
268 }
269 
270 int
getHeightOfRow() const271 StaffLayout::getHeightOfRow() const
272 {
273     return getTopLineOffset() + getLegerLineCount() * getLineSpacing()
274            + getBarLineHeight() + m_lineThickness;
275 }
276 
277 bool
containsSceneCoords(double x,int y) const278 StaffLayout::containsSceneCoords(double x, int y) const
279 {
280 //    std::cerr << "StaffLayout::containsSceneCoords(" << x << "," << y << ")" << std::endl;
281 
282     switch (m_pageMode) {
283 
284     case ContinuousPageMode:
285 
286         for (int row = getRowForLayoutX(m_startLayoutX);
287              row <= getRowForLayoutX(m_endLayoutX); ++row) {
288 
289             if (y >= getSceneYForTopOfStaff(row) &&
290                 y  < getSceneYForTopOfStaff(row) + getHeightOfRow()) {
291                 return true;
292             }
293         }
294 
295         return false;
296 
297     case MultiPageMode:
298 
299         for (int row = getRowForLayoutX(m_startLayoutX);
300              row <= getRowForLayoutX(m_endLayoutX); ++row) {
301 
302             if (y >= getSceneYForTopOfStaff(row) &&
303                 y  < getSceneYForTopOfStaff(row) + getHeightOfRow() &&
304                 x >= getSceneXForLeftOfRow(row) &&
305                 x <= getSceneXForRightOfRow(row)) {
306                 return true;
307             }
308         }
309 
310         return false;
311 
312     case LinearMode:
313     default:
314 
315         return (y >= getSceneYForTopOfStaff() &&
316                 y < getSceneYForTopOfStaff() + getHeightOfRow());
317     }
318 }
319 
320 int
getSceneYForHeight(int h,double baseX,int baseY) const321 StaffLayout::getSceneYForHeight(int h, double baseX, int baseY) const
322 {
323     int y;
324 
325     //    RG_DEBUG << "getSceneYForHeight(" << h << "," << baseY
326     //             << ")" << endl;
327 
328     if (baseX < 0)
329         baseX = getX() + getMargin();
330 
331     if (baseY >= 0) {
332         y = getSceneYForTopLine(getRowForSceneCoords(baseX, baseY));
333     } else {
334         y = getSceneYForTopLine();
335     }
336 
337     y += getLayoutYForHeight(h);
338 
339     return y;
340 }
341 
342 int
getLayoutYForHeight(int h) const343 StaffLayout::getLayoutYForHeight(int h) const
344 {
345     int y = ((getTopLineHeight() - h) * getLineSpacing()) / getHeightPerLine();
346     if (h < getTopLineHeight() && (h % getHeightPerLine() != 0))
347         ++y;
348 
349     return y;
350 }
351 
352 int
getWeightedHeightAtSceneCoords(int originalHeight,double x,int y)353 StaffLayout::getWeightedHeightAtSceneCoords(int originalHeight, double x, int y)
354 {
355     RG_DEBUG << "getWeightedHeightAtSceneCoords: originalHeight: "
356                    << originalHeight << " non-weighted height: "
357                    << getHeightAtSceneCoords(x, y);
358 
359     // return the non-weighted height if it already matches (ie. the user
360     // clicked pretty close to the center of the note head)
361     int nonWeightedHeight = getHeightAtSceneCoords(x, y);
362 
363     if (originalHeight == nonWeightedHeight) return nonWeightedHeight;
364 
365     // if no match, calculate an approximate height
366     int row = getRowForSceneCoords(x, y);
367     int approximateHeight = (y - getSceneYForTopLine(row)) * getHeightPerLine() / getLineSpacing();
368     approximateHeight = getTopLineHeight() - approximateHeight;
369 
370     RG_DEBUG << "approximateHeight: " << approximateHeight
371                    << " originalHeight: " << originalHeight;
372 
373     int difference = approximateHeight - originalHeight;
374     if (difference < 0) difference *= -1;
375 
376     // the approximate height is very coarse, so let's try using it as a rough
377     // measure of how far the new height differs from the original, and return
378     // the original if it's inside the range of "close enough"
379     if (difference > 1) return nonWeightedHeight;
380     else return originalHeight;
381 }
382 
383 int
getHeightAtSceneCoords(double x,int y) const384 StaffLayout::getHeightAtSceneCoords(double x, int y) const
385 {
386     //!!! the lazy route: approximate, then get the right value
387     // by calling getSceneYForHeight a few times... ugh
388 
389     //    RG_DEBUG << "\nNotationStaff::heightOfYCoord: y = " << y
390     //                         << ", getTopLineOffset() = " << getTopLineOffset()
391     //                         << ", getLineSpacing() = " << m_npf->getLineSpacing()
392     //                         << endl;
393 
394     if (x < 0)
395         x = getX() + getMargin();
396 
397     int row = getRowForSceneCoords(x, y);
398     int ph = (y - getSceneYForTopLine(row)) * getHeightPerLine() /
399              getLineSpacing();
400     ph = getTopLineHeight() - ph;
401 
402     int i;
403     int mi = -2;
404     int md = getLineSpacing() * 2;
405 
406     int testi = -2;
407     int testMd = 1000;
408 
409     for (i = -1; i <= 1; ++i) {
410         int d = y - getSceneYForHeight(ph + i, x, y);
411         if (d < 0) {
412             d = -d;
413         }
414         if (d < md) {
415             md = d;
416             mi = i;
417         }
418         if (d < testMd) {
419             testMd = d;
420             testi = i;
421         }
422     }
423 
424     if (mi > -2) {
425         //         RG_DEBUG << "StaffLayout::getHeightAtSceneCoords: " << y
426         //                              << " -> " << (ph + mi) << " (mi is " << mi << ", distance "
427         //                              << md << ")" << endl;
428         //         if (mi == 0) {
429         //             RG_DEBUG << "GOOD APPROXIMATION";
430         //         } else {
431         //             RG_DEBUG << "BAD APPROXIMATION";
432         //         }
433         return ph + mi;
434     } else {
435         RG_DEBUG << "StaffLayout::getHeightAtSceneCoords: heuristic got " << ph << ", nothing within range (closest was " << (ph + testi) << " which is " << testMd << " away)";
436         return 0;
437     }
438 }
439 
440 QRect
getBarExtents(double x,int y) const441 StaffLayout::getBarExtents(double x, int y) const
442 {
443     int row = getRowForSceneCoords(x, y);
444 
445     RG_DEBUG << "getBarExtents(" << x << "," << y << "), row " << row << ", have " << m_barLines.size() << " bar records";
446 
447     for (BarLineList::const_iterator i = m_barLines.begin();
448          i != m_barLines.end(); ++i) {
449 
450         BarLineItem *line = *i;
451 
452         double layoutX = line->getLayoutX();
453         int barRow = getRowForLayoutX(layoutX);
454 
455         RG_DEBUG << "bar layoutX " << layoutX << ", row " << barRow << ", page mode " << m_pageMode << ", x " << line->x();
456 
457         if (m_pageMode != LinearMode && (barRow < row)) continue;
458 
459         if (line->x() <= x) continue;
460         if (i == m_barLines.begin()) continue;
461 
462         BarLineList::const_iterator j = i;
463         --j;
464         BarLineItem *prevline = *j;
465 
466         QRect r = QRect(int(prevline->x()),
467                         getSceneYForTopOfStaff(barRow),
468                         int(line->x() - prevline->x()),
469                         getHeightOfRow());
470         RG_DEBUG << "Returning rect " << r;
471         return r;
472     }
473 
474     // failure
475     return QRect(int(getX() + getMargin()), getSceneYForTopOfStaff(),
476                  4, getHeightOfRow());
477 }
478 
479 double
getSceneXForLayoutX(double x) const480 StaffLayout::getSceneXForLayoutX(double x) const
481 {
482     switch (m_pageMode) {
483 
484     case ContinuousPageMode:
485         return m_x + x - (m_pageWidth * getRowForLayoutX(x));
486 
487     case MultiPageMode: {
488             int pageNo = getRowForLayoutX(x) / getRowsPerPage();
489             double cx = m_x + x - (m_pageWidth * getRowForLayoutX(x));
490             cx += m_margin + (m_margin * 2 + m_pageWidth) * pageNo;
491             return cx;
492         }
493 
494     case LinearMode:
495     default:
496         return m_x + x;
497     }
498 }
499 
500 StaffLayout::StaffLayoutCoords
getLayoutCoordsForSceneCoords(double x,int y) const501 StaffLayout::getLayoutCoordsForSceneCoords(double x, int y) const
502 {
503     int row = getRowForSceneCoords(x, y);
504     return StaffLayoutCoords
505            ((row * m_pageWidth) + x - getSceneXForLeftOfRow(row),
506             y - getSceneYForTopOfStaff(row));
507 }
508 
509 StaffLayout::StaffLayoutCoords
getSceneCoordsForLayoutCoords(double x,int y) const510 StaffLayout::getSceneCoordsForLayoutCoords(double x, int y) const
511 {
512     int row = getRowForLayoutX(x);
513     return StaffLayoutCoords
514            (getSceneXForLayoutX(x), getSceneYForTopLine(row) + y);
515 }
516 
517 int
getRowForSceneCoords(double x,int y) const518 StaffLayout::getRowForSceneCoords(double x, int y) const
519 {
520     switch (m_pageMode) {
521 
522     case ContinuousPageMode:
523         return ((y - m_y) / m_rowSpacing);
524 
525     case MultiPageMode: {
526             int px = int(x - m_x - m_margin);
527             int pw = int(m_margin * 2 + m_pageWidth);
528             if (px < pw)
529                 y -= m_titleHeight;
530             return (getRowsPerPage() * (px / pw)) + ((y - m_y) / m_rowSpacing);
531         }
532 
533     case LinearMode:
534     default:
535         return (int)((x - m_x) / m_pageWidth);
536     }
537 }
538 
539 int
getSceneYForTopOfStaff(int row) const540 StaffLayout::getSceneYForTopOfStaff(int row) const
541 {
542     switch (m_pageMode) {
543 
544     case ContinuousPageMode:
545         if (row <= 0)
546             return m_y;
547         else
548             return m_y + (row * m_rowSpacing);
549 
550     case MultiPageMode:
551         if (row <= 0)
552             return m_y + m_titleHeight;
553         else if (row < getRowsPerPage())
554             return m_y + ((row % getRowsPerPage()) * m_rowSpacing) + m_titleHeight;
555         else
556             return m_y + ((row % getRowsPerPage()) * m_rowSpacing);
557 
558     case LinearMode:
559     default:
560         return m_y;
561     }
562 }
563 
564 double
getSceneXForLeftOfRow(int row) const565 StaffLayout::getSceneXForLeftOfRow(int row) const
566 {
567     switch (m_pageMode) {
568 
569     case ContinuousPageMode:
570         return m_x;
571 
572     case MultiPageMode:
573         return m_x + m_margin +
574                (m_margin*2 + m_pageWidth) * (row / getRowsPerPage());
575 
576     case LinearMode:
577     default:
578         return m_x + (row * m_pageWidth);
579     }
580 }
581 
582 void
sizeStaff(HorizontalLayoutEngine & layout)583 StaffLayout::sizeStaff(HorizontalLayoutEngine &layout)
584 {
585     Profiler profiler("StaffLayout::sizeStaff", true);
586 
587     deleteBars();
588     deleteRepeatedClefsAndKeys();
589     deleteTimeSignatures();
590 
591     //    RG_DEBUG << "StaffLayout::sizeStaff";
592 
593     int lastBar = layout.getLastVisibleBarOnViewSegment(*m_viewSegment);
594 
595     double xleft = 0, xright = 0;
596     bool haveXLeft = false;
597 
598     xright = layout.getBarPosition(lastBar) - 1;
599 
600     TimeSignature currentTimeSignature;
601 
602     for (int barNo = layout.getFirstVisibleBarOnViewSegment(*m_viewSegment);
603             barNo <= lastBar; ++barNo) {
604 
605         double x = layout.getBarPosition(barNo);
606 
607         if (!haveXLeft) {
608             xleft = x;
609             haveXLeft = true;
610         }
611 
612         double timeSigX = 0;
613         TimeSignature timeSig;
614         bool isNew = layout.getTimeSignaturePosition
615             (*m_viewSegment, barNo, timeSig, timeSigX);
616 
617 
618         if (isNew && barNo < lastBar) {
619             currentTimeSignature = timeSig;
620             // Ask for a gray color if the segment is a repetition
621             bool grayed = m_viewSegment->getSegment().isTmp();
622             insertTimeSignature(timeSigX, currentTimeSignature, grayed);
623             RG_DEBUG << "StaffLayout[" << this << "]::sizeStaff: bar no " << barNo << " has time signature at " << timeSigX;
624         }
625 
626         RG_DEBUG << "StaffLayout::sizeStaff: inserting bar at " << x << " on staff " << this << " (isNew " << isNew << ", timeSigX " << timeSigX << ")";
627 
628         bool showBarNo =
629             (showBarNumbersEvery() > 0 &&
630              ((barNo + 1) % showBarNumbersEvery()) == 0);
631 
632         insertBar(x,
633                   ((barNo == lastBar) ? 0 :
634                    (layout.getBarPosition(barNo + 1) - x)),
635                   layout.isBarCorrectOnViewSegment(*m_viewSegment, barNo - 1),
636                   currentTimeSignature,
637                   barNo,
638                   showBarNo);
639     }
640 
641     m_startLayoutX = xleft;
642     m_endLayoutX = xright;
643 
644     drawStaffName();
645     resizeStaffLines();
646 }
647 
648 void
deleteBars()649 StaffLayout::deleteBars()
650 {
651     for (BarLineList::iterator i = m_barLines.begin();
652          i != m_barLines.end(); ++i) {
653         delete *i;
654     }
655 
656     for (LineRecList::iterator i = m_beatLines.begin();
657          i != m_beatLines.end(); ++i) {
658         delete i->second;
659     }
660 
661     for (LineRecList::iterator i = m_barConnectingLines.begin();
662          i != m_barConnectingLines.end(); ++i) {
663         delete i->second;
664     }
665 
666     for (ItemList::iterator i = m_barNumbers.begin();
667          i != m_barNumbers.end(); ++i) {
668         delete *i;
669     }
670 
671     m_barLines.clear();
672     m_beatLines.clear();
673     m_barConnectingLines.clear();
674     m_barNumbers.clear();
675 }
676 
677 void
insertBar(double layoutX,double width,bool isCorrect,const TimeSignature & timeSig,int barNo,bool showBarNo)678 StaffLayout::insertBar(double layoutX, double width, bool isCorrect,
679                       const TimeSignature &timeSig,
680                       int barNo, bool showBarNo)
681 {
682 //    RG_DEBUG << "insertBar: " << layoutX << ", " << width
683 //                   << ", " << isCorrect << endl;
684 
685     int barThickness = m_lineThickness * 5 / 4;
686 
687     // hack to ensure the bar line appears on the correct row in
688     // notation page layouts, with a conditional to prevent us from
689     // moving the bar and beat lines in the matrix
690     if (!showBeatLines()) {
691         if (width > 0.01) { // not final bar in staff
692             layoutX += 1;
693         } else {
694             layoutX -= 1;
695         }
696     }
697 
698     int row = getRowForLayoutX(layoutX);
699     double x = getSceneXForLayoutX(layoutX);
700     int y = getSceneYForTopLine(row);
701 
702     bool firstBarInRow = false, lastBarInRow = false;
703 
704     if (m_pageMode != LinearMode &&
705             (getRowForLayoutX(layoutX) >
706              getRowForLayoutX(layoutX - getMargin() - 2)))
707         firstBarInRow = true;
708 
709     if (m_pageMode != LinearMode &&
710             width > 0.01 &&  // width == 0 for final bar in staff
711             (getRowForLayoutX(layoutX) <
712              getRowForLayoutX(layoutX + width + getMargin() + 2)))
713         lastBarInRow = true;
714 
715     BarStyle style = getBarStyle(barNo);
716 
717     if (style == RepeatBothBar && firstBarInRow)
718         style = RepeatStartBar;
719 
720     if (firstBarInRow)
721         insertRepeatedClefAndKey(layoutX, barNo);
722 
723     // If we're supposed to be hiding bar lines, we do just that --
724     // create them as normal, then hide them.  We can't simply not
725     // create them because we rely on this to find bar extents for
726     // things like double-click selection in notation.
727     bool hidden = false;
728     if (style == PlainBar && timeSig.hasHiddenBars())
729         hidden = true;
730 
731     double inset = 0.0;
732     if (style == RepeatStartBar || style == RepeatBothBar) {
733         inset = getBarInset(barNo, firstBarInRow);
734     }
735 
736     BarLineItem *line = new BarLineItem(layoutX, getBarLineHeight(),
737                                         barThickness, getLineSpacing(),
738                                         (int)inset, style);
739 
740     m_scene->addItem(line);
741     line->setPos(int(x), y);
742 
743     if (isCorrect) {
744         line->setColour(GUIPalette::getColour(GUIPalette::BarLine));
745     } else {
746         line->setColour(GUIPalette::getColour(GUIPalette::BarLineIncorrect));
747     }
748 
749     line->setZValue( -1);
750     if (hidden)
751         line->hide();
752     else
753         line->show();
754 
755     // The bar lines have to be in order of layout-x (there's no
756     // such interesting stipulation for beat or connecting lines)
757     BarLineList::iterator insertPoint = lower_bound
758         (m_barLines.begin(), m_barLines.end(), line, compareBars);
759     m_barLines.insert(insertPoint, line);
760 
761     if (lastBarInRow) {
762 
763         double xe = x + width - barThickness;
764         style = getBarStyle(barNo + 1);
765         if (style == RepeatBothBar)
766             style = RepeatEndBar;
767 
768         BarLineItem *eline = new BarLineItem(layoutX, getBarLineHeight(),
769                                              barThickness, getLineSpacing(),
770                                              0, style);
771         m_scene->addItem(eline);
772         eline->setPos(int(xe) + .5, y + .5);
773 
774         eline->setColour(GUIPalette::getColour(GUIPalette::BarLine));
775 
776         eline->setZValue( -1);
777         if (hidden)
778             eline->hide();
779         else
780             eline->show();
781 
782         BarLineList::iterator insertPoint = lower_bound
783             (m_barLines.begin(), m_barLines.end(), eline, compareBars);
784         m_barLines.insert(insertPoint, eline);
785     }
786 
787     if (showBarNo) {
788 
789         QFont font;
790         font.setPixelSize(m_resolution * 3 / 2);
791         QFontMetrics metrics(font);
792         QString text = QString("%1").arg(barNo + 1);
793 
794         QGraphicsSimpleTextItem *barNoText = new QGraphicsSimpleTextItem(text);
795         barNoText->setFont(font);
796         barNoText->setPos(x, y - metrics.height() - m_resolution * 2);
797         barNoText->setZValue( -1);
798         m_scene->addItem(barNoText);
799         if (hidden)
800             barNoText->hide();
801         else
802             barNoText->show();
803 
804         m_barNumbers.push_back(barNoText);
805     }
806 
807     QGraphicsRectItem *rect = nullptr;
808 
809     if (showBeatLines()) {
810 
811         double gridLines; // number of grid lines per bar may be fractional
812 
813         // If the snap time is zero we default to beat markers
814         //
815         if (m_snapGrid && m_snapGrid->getSnapTime(x))
816             gridLines = double(timeSig.getBarDuration()) /
817                         double(m_snapGrid->getSnapTime(x));
818         else
819             gridLines = timeSig.getBeatsPerBar();
820 
821         double dx = width / gridLines;
822 
823         for (int gridLine = hidden ? 0 : 1; gridLine < gridLines; ++gridLine) {
824 
825             rect = new QGraphicsRectItem
826                 (0, 0, barThickness, getBarLineHeight());
827             m_scene->addItem(rect);
828 
829             rect->setPos(x + gridLine * dx + .5, y + .5);
830 
831             double currentGrid = gridLines / double(timeSig.getBeatsPerBar());
832 
833             rect->setPen(GUIPalette::getColour(GUIPalette::BeatLine));
834             rect->setBrush(GUIPalette::getColour(GUIPalette::BeatLine));
835 
836             // Reset to SubBeatLine colour if we're not a beat line - avoid div by zero!
837             //
838             if (currentGrid > 1.0 && double(gridLine) / currentGrid != gridLine / int(currentGrid)) {
839                 rect->setPen(GUIPalette::getColour(GUIPalette::SubBeatLine));
840                 rect->setBrush(GUIPalette::getColour(GUIPalette::SubBeatLine));
841             }
842 
843             rect->setZValue( -1);
844             rect->show(); // show beat lines even if the bar lines are hidden
845 
846             LineRec beatLine(layoutX + gridLine * dx, rect);
847             m_beatLines.push_back(beatLine);
848         }
849     }
850 
851     if (m_connectingLineLength > 0) {
852 
853         rect = new QGraphicsRectItem
854                (0, 0, barThickness, m_connectingLineLength);
855         m_scene->addItem(rect);
856 
857         rect->setPos(x + .5, y + .5);
858 
859         rect->setPen(GUIPalette::getColour(GUIPalette::StaffConnectingLine));
860         rect->setBrush(GUIPalette::getColour(GUIPalette::StaffConnectingLine));
861         rect->setZValue( -3);
862         if (hidden)
863             rect->hide();
864         else
865             rect->show();
866 
867         LineRec connectingLine(layoutX, rect);
868         m_barConnectingLines.push_back(connectingLine);
869     }
870 }
871 
872 bool
compareBars(const BarLineItem * barLine1,const BarLineItem * barLine2)873 StaffLayout::compareBars(const BarLineItem *barLine1, const BarLineItem *barLine2)
874 {
875     return (barLine1->getLayoutX() < barLine2->getLayoutX());
876 }
877 
878 bool
compareBarToLayoutX(const BarLineItem * barLine1,int x)879 StaffLayout::compareBarToLayoutX(const BarLineItem *barLine1, int x)
880 {
881     return (barLine1->getLayoutX() < x);
882 }
883 
884 void
deleteTimeSignatures()885 StaffLayout::deleteTimeSignatures()
886 {
887     // default implementation is empty
888 }
889 
890 void
insertTimeSignature(double,const TimeSignature &,bool)891 StaffLayout::insertTimeSignature(double, const TimeSignature &, bool)
892 {
893     // default implementation is empty
894 }
895 
896 void
deleteRepeatedClefsAndKeys()897 StaffLayout::deleteRepeatedClefsAndKeys()
898 {
899     // default implementation is empty
900 }
901 
902 void
insertRepeatedClefAndKey(double,int)903 StaffLayout::insertRepeatedClefAndKey(double, int)
904 {
905     // default implementation is empty
906 }
907 
908 void
drawStaffName()909 StaffLayout::drawStaffName()
910 {
911     // default implementation is empty
912 }
913 
914 void
resizeStaffLines()915 StaffLayout::resizeStaffLines()
916 {
917     Profiler profiler("StaffLayout::resizeStaffLines");
918 
919     int firstRow = getRowForLayoutX(m_startLayoutX);
920     int lastRow = getRowForLayoutX(m_endLayoutX);
921 
922     RG_DEBUG << "StaffLayout::resizeStaffLines: firstRow "
923              << firstRow << ", lastRow " << lastRow
924              << " (startLayoutX " << m_startLayoutX
925              << ", endLayoutX " << m_endLayoutX << ")";
926 
927     Q_ASSERT(lastRow >= firstRow);
928 
929     int i;
930     while ((int)m_staffLines.size() <= lastRow) {
931         m_staffLines.push_back(ItemList());
932         m_staffConnectingLines.push_back(nullptr);
933     }
934 
935     // Remove all the staff lines that precede the start of the staff
936 
937     for (i = 0; i < firstRow; ++i) clearStaffLineRow(i);
938 
939     // now i == firstRow
940 
941     while (i <= lastRow) {
942 
943         double x0;
944         double x1;
945 
946         if (i == firstRow) {
947             x0 = getSceneXForLayoutX(m_startLayoutX);
948         } else {
949             x0 = getSceneXForLeftOfRow(i);
950         }
951 
952         if (i == lastRow) {
953             x1 = getSceneXForLayoutX(m_endLayoutX);
954         } else {
955             x1 = getSceneXForRightOfRow(i);
956         }
957 
958         resizeStaffLineRow(i, x0, x1 - x0);
959 
960         ++i;
961     }
962 
963     // now i == lastRow + 1
964 
965     while (i < (int)m_staffLines.size()) clearStaffLineRow(i++);
966 }
967 
968 void
clearStaffLineRow(int row)969 StaffLayout::clearStaffLineRow(int row)
970 {
971     for (int h = 0; h < (int)m_staffLines[row].size(); ++h) {
972         delete m_staffLines[row][h];
973     }
974     m_staffLines[row].clear();
975 
976     delete m_staffConnectingLines[row];
977     m_staffConnectingLines[row] = nullptr;
978 }
979 
980 void
resizeStaffLineRow(int row,double x,double length)981 StaffLayout::resizeStaffLineRow(int row, double x, double length)
982 {
983     Profiler profiler("StaffLayout::resizeStaffLineRow");
984 
985     //    RG_DEBUG << "StaffLayout::resizeStaffLineRow: row "
986     //       << row << ", x " << x << ", length "
987     //       << length << endl;
988 
989 
990     // If the resolution is 8 or less, we want to reduce the blackness
991     // of the staff lines somewhat to make them less intrusive
992 
993     int level = 0;
994     // int z = 2;
995     if (m_resolution < 6) {
996         // z = -1;
997         level = (9 - m_resolution) * 32;
998         if (level > 200)
999             level = 200;
1000     }
1001 
1002     // As a result of a bug Michael reported where the staff changes color after
1003     // bar N, I decided to check the values. For m_resolution of 4 the level = 160.
1004     // However, if the previous value was < 12, the line remained black, i.e. level = 0.
1005     // If the previous value was 12 and it was reduced to 4, the line indeed was
1006     // gray, but it was UGLY. The reason for this is the notes remain black and
1007     // there are these gray lines through the notes. Since it doesn't always work,
1008     // and since it is ugly when it does work - kill it. 6 Nov 2009, Ilan
1009     level = 0;
1010     QColor lineColour(level, level, level);
1011 
1012     int h;
1013 
1014     /*!!! No longer really good enough. But we could potentially use the
1015       bar positions to sort this out
1016 
1017         if (m_pageMode && row > 0 && offset == 0.0) {
1018             offset = (double)m_npf->getBarMargin() / 2;
1019             length -= offset;
1020         }
1021     */
1022 
1023     int y;
1024 
1025     delete m_staffConnectingLines[row];
1026 
1027     if (m_pageMode != LinearMode && m_connectingLineLength > 0) {
1028 
1029         // rather arbitrary (dup in insertBar)
1030         int barThickness = m_resolution / 12 + 1;
1031         y = getSceneYForTopLine(row);
1032         QGraphicsRectItem *line = new QGraphicsRectItem
1033             (int(x + length) + .5, y + .5, barThickness, m_connectingLineLength);
1034         m_scene->addItem(line);
1035         line->setPen(GUIPalette::getColour(GUIPalette::StaffConnectingTerminatingLine));
1036         line->setBrush(GUIPalette::getColour(GUIPalette::StaffConnectingTerminatingLine));
1037         line->setZValue( -2);
1038         line->show();
1039         m_staffConnectingLines[row] = line;
1040 
1041     } else {
1042         m_staffConnectingLines[row] = nullptr;
1043     }
1044 
1045     while ((int)m_staffLines[row].size() <= getLineCount() * m_lineThickness) {
1046         m_staffLines[row].push_back(nullptr);
1047     }
1048 
1049     int lineIndex = 0;
1050 
1051     for (h = 0; h < getLineCount(); ++h) {
1052 
1053         y = getSceneYForHeight
1054             (getBottomLineHeight() + getHeightPerLine() * h,
1055              x, getSceneYForTopLine(row));
1056 
1057         if (elementsInSpaces()) {
1058             y -= getLineSpacing() / 2 + 1;
1059         }
1060 
1061         //      RG_DEBUG << "StaffLayout: drawing line from ("
1062         //                           << x << "," << y << ") to (" << (x+length-1)
1063         //                           << "," << y << ")" << endl;
1064 
1065         if (m_lineThickness > 1) {
1066 
1067             QGraphicsRectItem *line = dynamic_cast<QGraphicsRectItem *>
1068                 (m_staffLines[row][lineIndex]);
1069 
1070             if (!line) {
1071                 delete m_staffLines[row][lineIndex];
1072                 m_staffLines[row][lineIndex] = line = new QGraphicsRectItem;
1073                 line->setPen(QPen(lineColour, 0));
1074                 line->setBrush(lineColour);
1075                 m_scene->addItem(line);
1076             }
1077 
1078             line->setRect(int(x) + .5, y + .5, int(length), m_lineThickness);
1079             line->show();
1080 
1081         } else {
1082 
1083             QGraphicsLineItem *line = dynamic_cast<QGraphicsLineItem *>
1084                 (m_staffLines[row][lineIndex]);
1085 
1086             if (!line) {
1087                 delete m_staffLines[row][lineIndex];
1088                 m_staffLines[row][lineIndex] = line = new QGraphicsLineItem;
1089                 line->setPen(QPen(lineColour, 0));
1090                 m_scene->addItem(line);
1091             }
1092 
1093             line->setLine(int(x) + .5, y + .5, int(x + length) + .5, y + .5);
1094             line->show();
1095         }
1096 
1097         ++lineIndex;
1098     }
1099 
1100     while (lineIndex < (int)m_staffLines[row].size()) {
1101         delete m_staffLines[row][lineIndex];
1102         m_staffLines[row][lineIndex] = nullptr;
1103         ++lineIndex;
1104     }
1105 }
1106 
1107 void
setCurrent(bool current)1108 StaffLayout::setCurrent(bool current)
1109 {
1110     m_current = current;
1111 }
1112 
1113 void
renderElements(ViewElementList::iterator,ViewElementList::iterator)1114 StaffLayout::renderElements(ViewElementList::iterator,
1115                             ViewElementList::iterator)
1116 {
1117     // nothing -- we assume rendering will be done by the implementation
1118     // of positionElements
1119 }
1120 
1121 QRectF
getSceneArea()1122 StaffLayout::getSceneArea()
1123 {
1124     double top, bottom;
1125     double left, right;
1126     int firstRow, lastRow;
1127 
1128     switch (m_pageMode) {
1129 
1130     case ContinuousPageMode:
1131 
1132         firstRow = getRowForLayoutX(m_startLayoutX);
1133         lastRow = getRowForLayoutX(m_endLayoutX);
1134 
1135         if (lastRow != firstRow) {
1136             left = m_x;
1137             right = m_x + m_pageWidth;
1138         } else {
1139             left = getSceneXForLayoutX(m_startLayoutX);
1140             right = getSceneXForLayoutX(m_endLayoutX);
1141         }
1142 
1143         top = getSceneYForTopOfStaff(firstRow);
1144         bottom = getSceneYForTopOfStaff(lastRow) + getHeightOfRow();
1145 
1146         break;
1147 
1148     case MultiPageMode:
1149 
1150         firstRow = getRowForLayoutX(m_startLayoutX);
1151         lastRow = getRowForLayoutX(m_endLayoutX);
1152 
1153         if (lastRow == firstRow) {
1154             left = getSceneXForLayoutX(m_startLayoutX);
1155             right = getSceneXForLayoutX(m_endLayoutX);
1156             top = getSceneYForTopOfStaff(firstRow);
1157             bottom = getSceneYForTopOfStaff(lastRow) + getHeightOfRow();
1158         } else {
1159 
1160             int firstPage = firstRow / getRowsPerPage();
1161             int lastPage = lastRow / getRowsPerPage();
1162 
1163             if (lastPage == firstPage) {
1164                 left = getSceneXForLeftOfRow(firstRow);
1165                 right = getSceneXForRightOfRow(lastRow);
1166 
1167                 top = getSceneYForTopOfStaff(firstRow);
1168                 bottom = getSceneYForTopOfStaff(lastRow) + getHeightOfRow();
1169             } else {
1170 
1171               /// TODO : Two special cases should be processed here
1172               ///          1 - Only one row on the first page
1173               ///          2 - Only one row on the last page
1174 
1175                 left = getSceneXForLeftOfRow(firstRow);
1176                 right = getSceneXForRightOfRow(lastRow);
1177 
1178                 top = m_y;
1179                 bottom = m_y + getHeightOfRow()
1180                          + getRowsPerPage() * m_rowSpacing;
1181             }
1182         }
1183 
1184         break;
1185 
1186     case LinearMode:
1187     default:
1188 
1189         left = m_startLayoutX;
1190         right = m_endLayoutX;
1191         top = getSceneYForTopOfStaff();
1192         bottom = top + getHeightOfRow();
1193     }
1194 
1195     return QRectF(left, top, right - left, bottom - top);
1196 }
1197 
1198 }
1199