1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2010-2019 Werner Schweer and others
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19 
20 #include "gridcanvas.h"
21 #include "preferences.h"
22 
23 namespace Ms {
24 
25 //---------------------------------------------------------
26 //   GridCanvas
27 //---------------------------------------------------------
28 
GridCanvas(QWidget * parent)29 GridCanvas::GridCanvas(QWidget* parent)
30    : QFrame(parent)
31       {
32       setFrameStyle(QFrame::NoFrame);
33       }
34 
35 //---------------------------------------------------------
36 //   paintEvent
37 //---------------------------------------------------------
38 
paintEvent(QPaintEvent * ev)39 void GridCanvas::paintEvent(QPaintEvent* ev)
40       {
41       if (!(m_rows && m_columns)) {
42             qDebug("SqareCanvas::paintEvent: number of columns or rows set to 0.\nColumns: %i, Rows: %i", m_rows, m_columns);
43             return;
44             }
45       // not qreal here, even though elsewhere yes,
46       // because width and height return a number of pixels,
47       // hence integers.
48       const int w = width();
49       const int h = height();
50 
51       const qreal columnWidth = qreal(w) / m_columns;
52       const qreal rowHeight = qreal(h) / m_rows;
53 
54       // let half a column of margin around
55       const qreal leftPos = columnWidth * .5; // also left margin
56       const qreal topPos = rowHeight * .5;    // also top margin
57       const qreal rightPos = w - leftPos; // right end position of graph
58       const qreal bottomPos = h - topPos; // bottom end position of graph
59 
60       QPainter painter(this);
61       painter.setRenderHint(QPainter::Antialiasing, preferences.getBool(PREF_UI_CANVAS_MISC_ANTIALIASEDDRAWING));
62 
63       painter.fillRect(rect(), QApplication::palette().color(QPalette::Window).lighter());
64       QPen pen = painter.pen();
65       pen.setWidth(1);
66 
67       QColor primaryLinesColor(preferences.isThemeDark() ? Qt::white : Qt::black);
68       QColor secondaryLinesColor(Qt::gray);
69       // draw vertical lines
70       for (int i = 0; i < m_columns; ++i) {
71             qreal xpos = leftPos + i * columnWidth;
72             // lighter middle lines
73             pen.setColor(i % m_primaryColumnsInterval ? secondaryLinesColor : primaryLinesColor);
74             painter.setPen(pen);
75             painter.drawLine(xpos, topPos, xpos, bottomPos);
76             }
77 
78       // draw horizontal lines
79       for (int i = 0; i < m_rows; ++i) {
80             int ypos = topPos + i * rowHeight;
81             // lighter middle lines
82             pen.setColor(i % m_primaryRowsInterval ? secondaryLinesColor : primaryLinesColor);
83             if (m_showNegativeRows)
84                   pen.setWidth(i == (m_rows - 1) / 2 ? 3 : 1);
85             painter.setPen(pen);
86             painter.drawLine(leftPos, ypos, rightPos, ypos);
87             }
88 
89       // this lambda takes as input a pitch value, and determines where what are its x and y coordinates
90       auto getPosition = [this, columnWidth, rowHeight, leftPos, topPos, bottomPos] (const PitchValue& v) -> QPointF {
91             const qreal x = round((qreal(v.time) / 60) * (m_columns - 1)) * columnWidth + leftPos;
92             qreal y = 0;
93             if (m_showNegativeRows) // get the middle pos and add the top margin and half of the rows
94                   y = topPos + rowHeight * (m_rows - 1) * .5;
95             else // from the bottom
96                   y = bottomPos;
97             // add the offset
98             y -=  round((qreal(v.pitch) / (100 * (m_rows / m_primaryRowsInterval))) * (m_rows - 1)) * rowHeight;
99             return QPointF(x, y);
100             };
101 
102       static constexpr int GRIP_HALF_RADIUS = 5;
103       QPointF lastPoint(0, 0);
104       pen = painter.pen();
105       pen.setWidth(3);
106       pen.setColor(Qt::red); // not theme dependant
107       painter.setPen(pen);
108       // draw line between points
109       for (const PitchValue& v : qAsConst(m_points)) {
110             QPointF currentPoint = getPosition(v);
111             // draw line only if there is a point before the current one
112             if (lastPoint.x()) {
113                   painter.drawLine(lastPoint, currentPoint);
114                   }
115             lastPoint = currentPoint;
116             }
117 
118       painter.setPen(Qt::NoPen);
119       painter.setBrush(QColor::fromRgb(32, 116, 189)); // Musescore blue
120       // draw points
121       for (const PitchValue& v : qAsConst(m_points)) {
122             painter.drawEllipse(getPosition(v), GRIP_HALF_RADIUS, GRIP_HALF_RADIUS);
123             }
124 
125       QFrame::paintEvent(ev);
126       }
127 
128 //---------------------------------------------------------
129 //   mousePressEvent
130 //---------------------------------------------------------
131 
mousePressEvent(QMouseEvent * ev)132 void GridCanvas::mousePressEvent(QMouseEvent* ev)
133       {
134       if (!(m_rows && m_columns)) {
135             qDebug("GridCanvas::mousePressEvent: number of columns or rows set to 0.\nColumns: %i, Rows: %i", m_rows, m_columns);
136             return;
137             }
138       const qreal columnWidth = qreal(width()) / m_columns;
139       const qreal rowHeight = qreal(height()) / m_rows;
140 
141       // Half a column/row of margin around
142       const int x = ev->x() - columnWidth * .5;
143       const int y = ev->y() - rowHeight * .5;
144 
145       int column = round(qreal(x) / columnWidth);
146       int row = round(qreal(y) / rowHeight);
147 
148       // restrict to clickable area
149       if (column >= m_columns)
150             column = m_columns - 1;
151       else if (column < 0)
152             column = 0;
153       if (row >= m_rows)
154             row = m_rows - 1;
155       else if (row < 0)
156             row = 0;
157 
158       // invert y.
159       if (m_showNegativeRows)
160             row = (m_rows - 1) / 2 - row;
161       else
162             row = (m_rows - 1) - row;
163 
164       const int time = column * 60 / (m_columns - 1);
165       const int pitch = row * 100 / m_primaryRowsInterval;
166 
167       const int numberOfPoints = m_points.size();
168       bool found = false;
169       for (int i = 0; i < numberOfPoints; ++i) {
170             if (round(qreal(m_points[i].time) / 60 * (m_columns - 1)) > column) {
171                   m_points.insert(i, PitchValue(time, pitch, false));
172                   found = true;
173                   break;
174                   }
175             if (round(qreal(m_points[i].time) / 60 * (m_columns - 1)) == column) {
176                   if (round(qreal(m_points[i].pitch) / (100 * (m_rows / m_primaryRowsInterval)) * (m_rows - 1)) == row
177                      && i > 0 && i < (numberOfPoints - 1)) {
178                         m_points.removeAt(i);
179                         }
180                   else {
181                         m_points[i].pitch = pitch;
182                         }
183                   found = true;
184                   break;
185                   }
186             }
187       if (!found)
188             m_points.append(PitchValue(time, pitch, false));
189 
190       update();
191       emit canvasChanged();
192       }
193 
194 } // namespace Ms
195