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