hydrogen 1.2.6
AutomationPathView.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
23#include "AutomationPathView.h"
24
28#include "../HydrogenApp.h"
29#include "../Skin.h"
30
31using namespace H2Core;
32
34 : QWidget(parent),
36 m_nGridWidth(16),
38 m_bIsHolding(false),
39 m_fTick( 0 )
40{
41 setFocusPolicy( Qt::ClickFocus );
44
46
47 _path = nullptr;
48 autoResize();
49
50 qreal pixelRatio = devicePixelRatio();
51 m_pBackgroundPixmap = new QPixmap( width() * pixelRatio,
52 height() * pixelRatio );
53 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
55}
56
63
70
71
73{
74 if ( path == _path ) {
75 return;
76 }
77 _path = path;
78
79 if( _path ) {
80 _selectedPoint = path->end();
81 }
82
83 if ( bUpdate ) {
85 update();
86 }
87}
88
89// Make sure we have the current automation path
91{
92 auto pSong = Hydrogen::get_instance()->getSong();
93 if ( pSong != nullptr ) {
94 setAutomationPath( pSong->getVelocityAutomationPath(), false );
95 } else {
96 setAutomationPath( nullptr, false );
97 }
98}
99
101{
102 if ( ( SONG_EDITOR_MIN_GRID_WIDTH <= width ) && ( SONG_EDITOR_MAX_GRID_WIDTH >= width ) ) {
103 m_nGridWidth = width;
104 autoResize();
106 update();
107 }
108}
109
110
114QPoint AutomationPathView::translatePoint(float x,float y) const
115{
116 return translatePoint(std::make_pair(x, y));
117}
118
119
123QPoint AutomationPathView::translatePoint(const std::pair<float,float> &p) const
124{
125 int contentHeight = height() - 2* m_nMarginHeight;
126
127 return QPoint(
129 m_nMarginHeight + contentHeight * ((_path->get_max()-p.second)/(_path->get_max()-_path->get_min()))
130 );
131}
132
133
137bool AutomationPathView::checkBounds(QMouseEvent *event) const
138{
139 auto pEv = static_cast<MouseEvent*>( event );
140
141 return pEv->position().x() > SongEditor::nMargin
142 && pEv->position().y() > m_nMarginHeight
143 && pEv->position().y() < height()-m_nMarginHeight;
144}
145
146
150std::pair<const float, float> AutomationPathView::locate(QMouseEvent *event) const
151{
152 int contentHeight = height() - 2* m_nMarginHeight;
153
154 auto pEv = static_cast<MouseEvent*>( event );
155
156 float x = (pEv->position().x() - SongEditor::nMargin) / (float)m_nGridWidth;
157 float y = ((contentHeight - pEv->position().y() + m_nMarginHeight)/
158 (float)contentHeight)
159 * (_path->get_max() - _path->get_min()) + _path->get_min();
160
161 return std::pair<const float,float>(x,y);
162}
163
165 m_fTick = fTick;
166 update();
167}
168
173{
174
175 if (!isVisible()) {
176 return;
177 }
178
179 qreal pixelRatio = devicePixelRatio();
180 if ( pixelRatio != m_pBackgroundPixmap->devicePixelRatio() ||
181 width() * pixelRatio != m_pBackgroundPixmap->width() ||
182 height() * pixelRatio != m_pBackgroundPixmap->height() ) {
184 }
185
186 QPainter painter( this );
187 painter.drawPixmap( ev->rect(), *m_pBackgroundPixmap,
188 QRectF( pixelRatio * ev->rect().x(),
189 pixelRatio * ev->rect().y(),
190 pixelRatio * ev->rect().width(),
191 pixelRatio * ev->rect().height() ) );
192
193 // Draw playhead
194 //
195 // Using the grid width of the song editor over class' own one is
196 // crucial in order to keep the full-height playhead in sync.
197 auto pSongEditorPanel = HydrogenApp::get_instance()->getSongEditorPanel();
198 if ( m_fTick != -1 && pSongEditorPanel != nullptr ) {
199 int nOffset = Skin::getPlayheadShaftOffset();
200 int nX = static_cast<int>( static_cast<float>(SongEditor::nMargin) + 1 +
201 m_fTick *
202 static_cast<float>(pSongEditorPanel->getSongEditor()->
203 getGridWidth()) -
204 static_cast<float>(Skin::nPlayheadWidth) / 2 );
205 Skin::setPlayheadPen( &painter, false );
206 painter.drawLine( nX + nOffset, 0, nX + nOffset, height() );
207 }
208
209}
210
212
215
216 QColor backgroundColor =
217 pPref->getColorTheme()->m_songEditor_automationBackgroundColor;
218 QColor automationLineColor =
219 pPref->getColorTheme()->m_songEditor_automationLineColor;
220 QColor nodeColor = pPref->getColorTheme()->m_songEditor_automationNodeColor;
221 QColor textColor = pPref->getColorTheme()->m_songEditor_textColor;
222
223 // Resize pixmap if pixel ratio has changed
224 qreal pixelRatio = devicePixelRatio();
225 if ( m_pBackgroundPixmap->devicePixelRatio() != pixelRatio ||
226 width() != m_pBackgroundPixmap->width() ||
227 height() != m_pBackgroundPixmap->height() ) {
228 delete m_pBackgroundPixmap;
229 m_pBackgroundPixmap = new QPixmap( width() * pixelRatio , height() * pixelRatio );
230 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
231 }
232
233 m_pBackgroundPixmap->fill( backgroundColor );
234
235 QPainter painter( m_pBackgroundPixmap );
236 painter.setRenderHint(QPainter::Antialiasing);
237
238 // Border
239 painter.setPen( Qt::black );
240 painter.drawLine( 0, 0, width(), 0 );
241
242 QPen rulerPen(Qt::DotLine);
243 rulerPen.setColor( textColor );
244 painter.setPen(rulerPen);
245
246 /* Paint min, max */
247 painter.drawLine(0, m_nMarginHeight, width(), m_nMarginHeight);
248 painter.drawLine(0, height()-m_nMarginHeight, width(), height()-m_nMarginHeight);
249
250 if (!_path) {
251 return;
252 }
253
254 /* Paint default */
255 QPoint def = translatePoint(0, _path->get_default());
256 painter.drawLine(0, def.y(), width(), def.y());
257
258 QPen linePen( automationLineColor );
259 linePen.setWidth(2);
260 painter.setPen(linePen);
261
262 if (_path->empty()) {
263 QPoint p = translatePoint(0,_path->get_default());
264
265 painter.drawLine(0, p.y(), width(), p.y());
266 } else {
267 std::pair<float, float> firstPoint = *_path->begin();
268 QPoint lastPoint = translatePoint(0,firstPoint.second);
269 lastPoint.setX(0);
270
271 for (auto point : *_path) {
272 QPoint current = translatePoint(point);
273 painter.drawLine(lastPoint, current);
274 lastPoint = current;
275 }
276 QPoint last(width(), lastPoint.y());
277 painter.drawLine(lastPoint, last);
278 }
279
280
281 QPen circlePen( nodeColor );
282 circlePen.setWidth(1);
283 painter.setPen(circlePen);
284 painter.setBrush(QBrush( pPref->getColorTheme()->m_windowColor ));
285
286 for (auto point : *_path) {
287
288 QPoint center = translatePoint(point);
289 painter.drawEllipse(center, 3, 3);
290
291 }
292}
293
294
303{
305
306 if (! checkBounds(event) || !_path) {
307 return;
308 }
309
310 auto p = locate(event);
311 float x = p.first;
312 float y = p.second;
313
314 _selectedPoint = _path->find(x);
315 if (_selectedPoint == _path->end()) {
316 _path->add_point(x, y);
317 _selectedPoint = _path->find(x);
318
319 m_bPointAdded = true;
320 } else {
321 _selectedPoint = _path->move(_selectedPoint, x, y);
322 m_fOriginX = x;
323 m_fOriginY = y;
324 m_bPointAdded = false;
325 }
327
329 update();
330
331 m_bIsHolding = true;
332
333 emit valueChanged();
334}
335
336
343{
345 m_bIsHolding = false;
346
347 if (! checkBounds(event) || !_path) {
348 return;
349 }
350
351 auto p = locate(event);
352 float x = p.first;
353 float y = p.second;
354 if (m_bPointAdded) {
355 emit pointAdded(x, y);
356 } else {
357 emit pointMoved(m_fOriginX, m_fOriginY, x, y);
358 }
359
360 emit valueChanged();
361}
362
363
370{
372 if (! checkBounds(event) || !_path) {
373 return;
374 }
375
376 auto p = locate(event);
377 float x = p.first;
378 float y = p.second;
379
380 if ( m_bIsHolding && _path && _selectedPoint != _path->end() ) {
381 _selectedPoint = _path->move(_selectedPoint, x, y);
383 }
384
386 update();
387}
388
389
396{
398 if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace ) {
399 if ( _path && _selectedPoint != _path->end() ) {
400 float x = _selectedPoint->first;
401 float y = _selectedPoint->second;
402 _path->remove_point(_selectedPoint->first);
403 _selectedPoint = _path->end();
404
406
407 emit pointRemoved( x, y );
409 update();
410 emit valueChanged();
411
412 event->accept();
413 return;
414 }
415 }
416
417 event->ignore();
418}
419
420
static const uint SONG_EDITOR_MAX_GRID_WIDTH
Definition SongEditor.h:55
static const uint SONG_EDITOR_MIN_GRID_WIDTH
Definition SongEditor.h:54
AutomationPathView(QWidget *parent=nullptr)
float m_fTick
< Point that is being dragged
void pointMoved(float ox, float oy, float tx, float ty)
H2Core::AutomationPath * _path
bool checkBounds(QMouseEvent *event) const
Check if user clicked within area inside margins.
bool m_bPointAdded
< Whether any points are being dragged
void mouseReleaseEvent(QMouseEvent *event) override
Handler for releasing mouse button.
void pointAdded(float x, float y)
void updatePosition(float fTick)
void mouseMoveEvent(QMouseEvent *event) override
Handler for mouse moves.
void keyPressEvent(QKeyEvent *event) override
Handler for key presses.
void onPreferencesChanged(H2Core::Preferences::Changes changes)
int m_nMarginHeight
< Width of song grid cell size - in order to properly align AutomationPathView and SongEditor
void mousePressEvent(QMouseEvent *event) override
Handle mouse click.
void pointRemoved(float x, float y)
QPoint translatePoint(float x, float y) const
Locate path point on a wdiget surface.
int getGridWidth() const noexcept
H2Core::AutomationPath::iterator _selectedPoint
< Original position of selected point
void paintEvent(QPaintEvent *event) override
Repaint widget.
std::pair< const float, float > locate(QMouseEvent *) const
Locate clicked point on a path.
float m_fOriginY
< Original position of selected point
static constexpr int m_nMinimumHeight
float m_fOriginX
< Whether a new point was added during mouse move
void autoResize()
Resize widget to fit everything.
void setGridWidth(int width)
void setAutomationPath(H2Core::AutomationPath *path, bool bUpdate=true)
int m_nMaxPatternSequence
< Height of top and bottom margins
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
Manager for User Preferences File (singleton)
Definition Preferences.h:79
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
int getMaxBars() const
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ Colors
At least one of the colors has changed.
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
void preferencesChanged(H2Core::Preferences::Changes changes)
Propagates a change in the Preferences through the GUI.
SongEditorPanel * getSongEditorPanel()
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
Definition MouseEvent.h:35
QPointF position() const
static constexpr int nPlayheadWidth
Definition Skin.h:77
static void setPlayheadPen(QPainter *p, bool bHovered=false)
Definition Skin.cpp:190
static int getPlayheadShaftOffset()
Definition Skin.h:79
static constexpr int nMargin
Definition SongEditor.h:106