hydrogen 1.2.6
NotePropertiesRuler.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 <core/Hydrogen.h>
26#include <core/Basics/Pattern.h>
28using namespace H2Core;
29
30#include <cassert>
31
34#include "../HydrogenApp.h"
35
36#include "UndoActions.h"
37#include "NotePropertiesRuler.h"
38#include "PatternEditorPanel.h"
39#include "PatternEditorRuler.h"
40#include "DrumPatternEditor.h"
41#include "PianoRollEditor.h"
42#include "../Skin.h"
43
47
48
50 : PatternEditor( parent, pPatternEditorPanel )
51 , m_bEntered( false )
52{
53
55 m_mode = mode;
56
57 m_fGridWidth = (Preferences::get_instance())->getPatternEditorGridWidth();
59
60 m_fLastSetValue = 0.0;
61 m_bValueHasBeenSet = false;
62
65 }
66 else {
68 }
69
71 setMinimumHeight( m_nEditorHeight );
72
74 show();
75
78
79 setFocusPolicy( Qt::StrongFocus );
80
81 // Generic pattern editor menu contains some operations that don't apply here, and we will want to add
82 // menu options specific to this later.
83 delete m_pPopupMenu;
84 m_pPopupMenu = new QMenu( this );
85 m_pPopupMenu->addAction( tr( "Select &all" ), this, SLOT( selectAll() ) );
86 m_pPopupMenu->addAction( tr( "Clear selection" ), this, SLOT( selectNone() ) );
87
88 setMouseTracking( true );
89}
90
91
92
93
97
98
103{
104 Hydrogen *pHydrogen = Hydrogen::get_instance();
105 if ( m_pPattern == nullptr ) {
106 return;
107 }
108
109 auto pEv = static_cast<WheelEvent*>( ev );
110
111 prepareUndoAction( pEv->position().x() ); //get all old values
112
113 float fDelta;
114 if ( ev->modifiers() == Qt::ControlModifier ||
115 ev->modifiers() == Qt::AltModifier ) {
116 fDelta = 0.01; // fine control
117 } else {
118 fDelta = 0.05; // coarse control
119 }
120 if ( ev->angleDelta().y() < 0 ) {
121 fDelta = fDelta * -1.0;
122 }
123
124 int nColumn = getColumn( pEv->position().x() );
125
126 m_pPatternEditorPanel->setCursorPosition( nColumn );
127
128 auto pHydrogenApp = HydrogenApp::get_instance();
129 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
130 pHydrogenApp->setHideKeyboardCursor( true );
131
132 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
133 if ( pSelectedInstrument == nullptr ) {
134 ERRORLOG( "No instrument selected" );
135 return;
136 }
137
138 // Gather notes to act on: selected or under the mouse cursor
139 std::list< Note *> notes;
140 if ( m_selection.begin() != m_selection.end() ) {
141 for ( Note *pNote : m_selection ) {
142 notes.push_back( pNote );
143 }
144 } else {
145 FOREACH_NOTE_CST_IT_BOUND_LENGTH( m_pPattern->get_notes(), it, nColumn, m_pPattern ) {
146 notes.push_back( it->second );
147 }
148 }
149
150 bool bValueChanged = false;
151 for ( Note *pNote : notes ) {
152 assert( pNote );
153 if ( pNote->get_instrument() != pSelectedInstrument && !m_selection.isSelected( pNote ) ) {
154 continue;
155 }
156 bValueChanged = true;
157 adjustNotePropertyDelta( pNote, fDelta, /* bMessage=*/ true );
158 }
159
160 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
161 // Immediate update to prevent visual delay.
162 m_pPatternEditorPanel->getPatternEditorRuler()->update();
163 if ( ! bValueChanged ) {
164 update();
165 }
166 }
167
168 if ( bValueChanged ) {
171 update();
172 }
173}
174
175
177 auto pEv = static_cast<MouseEvent*>( ev );
178
179 if ( ev->button() == Qt::RightButton ) {
180 m_pPopupMenu->popup( pEv->globalPosition().toPoint() );
181
182 } else {
183 // Treat single click as an instantaneous drag
184 propertyDragStart( ev );
185 propertyDragUpdate( ev );
187 }
188}
189
191 auto pEv = static_cast<MouseEvent*>( ev );
192
193 if ( pEv->position().x() > m_nActiveWidth ) {
194 return;
195 }
196
198
199 auto pHydrogenApp = HydrogenApp::get_instance();
200
201 // Hide cursor in case this behavior was selected in the
202 // Preferences.
203 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
204 pHydrogenApp->setHideKeyboardCursor( true );
205
206 // Cursor just got hidden.
207 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
208 // Immediate update to prevent visual delay.
209 m_pPatternEditorPanel->getPatternEditorRuler()->update();
210 update();
211 }
212
213 // Update cursor position
214 if ( ! pHydrogenApp->hideKeyboardCursor() ) {
215 int nColumn = getColumn( pEv->position().x(), /* bUseFineGrained=*/ true );
216 if ( ( m_pPattern != nullptr &&
217 nColumn >= (int)m_pPattern->get_length() ) ||
218 nColumn >= MAX_INSTRUMENTS ) {
219 return;
220 }
221
222 m_pPatternEditorPanel->setCursorPosition( nColumn );
223
224 update();
225 m_pPatternEditorPanel->getPatternEditorRuler()->update();
226 }
227}
228
230 auto pEv = static_cast<MouseEvent*>( ev );
231
232 if ( m_selection.isMoving() ) {
233 prepareUndoAction( pEv->position().x() );
235 } else {
236 propertyDragStart( ev );
237 propertyDragUpdate( ev );
238 }
239}
240
242 propertyDragUpdate( ev );
243}
244
247}
248
249
251 if ( m_pPattern == nullptr ) {
252 return;
253 }
254 Hydrogen *pHydrogen = Hydrogen::get_instance();
255
256 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
257 if ( pSelectedInstrument == nullptr ) {
258 ERRORLOG( "No instrument selected" );
259 return;
260 }
261
262 float fDelta;
263
264 QPoint movingOffset = m_selection.movingOffset();
266 fDelta = (float)-movingOffset.y() /
267 static_cast<float>(NotePropertiesRuler::nNoteKeyLineHeight);
268 } else {
269 fDelta = (float)-movingOffset.y() / height();
270 }
271
272 // Only send a status message for the update in case a single note
273 // was selected.
274 bool bSendStatusMsg = false;
275 int nNotes = 0;
276 for ( Note *pNote : m_selection ) {
277 ++nNotes;
278 }
279 if ( nNotes == 1 ) {
280 bSendStatusMsg = true;
281 }
282
283 bool bValueChanged = false;
284 for ( Note *pNote : m_selection ) {
285 if ( pNote->get_instrument() == pSelectedInstrument || m_selection.isSelected( pNote ) ) {
286
287 // Record original note if not already recorded
288 if ( m_oldNotes.find( pNote ) == m_oldNotes.end() ) {
289 m_oldNotes[ pNote ] = new Note( pNote );
290 }
291
292 adjustNotePropertyDelta( pNote, fDelta, bSendStatusMsg );
293 bValueChanged = true;
294 }
295 }
296
297 if ( bValueChanged ) {
299 update();
300 }
301}
302
307 update();
308}
309
311 for ( auto it : m_oldNotes ) {
312 delete it.second;
313 }
314 m_oldNotes.clear();
315}
316
319 for ( auto it : m_oldNotes ) {
320 Note *pNote = it.first, *pOldNote = it.second;
321 switch ( m_mode ) {
323 pNote->set_velocity( pOldNote->get_velocity() );
324 break;
326 pNote->setPan( pOldNote->getPan() );
327 break;
329 pNote->set_lead_lag( pOldNote->get_lead_lag() );
330 break;
332 pNote->set_key_octave( pOldNote->get_key(), pOldNote->get_octave() );
333 break;
335 pNote->set_probability( pOldNote->get_probability() );
336 break;
337 default:
338 break;
339 }
340 }
341
342 if ( m_oldNotes.size() == 0 ) {
343 for ( const auto& it : m_oldNotes ){
345 }
346 }
347
349}
350
351
353{
354 if ( m_pPattern == nullptr ) {
355 return;
356 }
357
358 auto pEv = static_cast<MouseEvent*>( ev );
359
360 if ( ev->buttons() == Qt::NoButton ) {
361 int nColumn = getColumn( pEv->position().x() );
362 bool bFound = false;
363 FOREACH_NOTE_CST_IT_BOUND_LENGTH( m_pPattern->get_notes(), it, nColumn, m_pPattern ) {
364 bFound = true;
365 break;
366 }
367 if ( bFound ) {
368 setCursor( Qt::PointingHandCursor );
369 } else {
370 unsetCursor();
371 }
372
373 } else {
375 }
376}
377
378
380{
381 auto pEv = static_cast<MouseEvent*>( ev );
382
383 setCursor( Qt::CrossCursor );
384 prepareUndoAction( pEv->position().x() );
386 update();
387}
388
389
392{
393 if ( m_pPattern == nullptr ) {
394 return;
395 }
396 Hydrogen *pHydrogen = Hydrogen::get_instance();
397
399
400 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
401 if ( pSelectedInstrument == nullptr ) {
402 ERRORLOG( "No instrument selected" );
403 return;
404 }
405 const int nColumn = getColumn( x );
406
407 if ( m_selection.begin() != m_selection.end() ) {
408 // If there is a selection, preserve the initial state of all the selected notes.
409 for ( Note *pNote : m_selection ) {
410 if ( pNote->get_instrument() == pSelectedInstrument || m_selection.isSelected( pNote ) ) {
411 m_oldNotes[ pNote ] = new Note( pNote );
412 }
413 }
414
415 } else {
416 // No notes are selected. The target notes to adjust are all those at
417 // column given by 'x', so we preserve these.
419 nColumn, m_pPattern ) {
420 Note *pNote = it->second;
421 if ( pNote->get_instrument() == pSelectedInstrument ) {
422 m_oldNotes[ pNote ] = new Note( pNote );
423 }
424 }
425 }
426
427 m_nDragPreviousColumn = nColumn;
428}
429
434{
435 if (m_pPattern == nullptr) {
436 return;
437 }
438
439 auto pEv = static_cast<MouseEvent*>( ev );
440
441 int nColumn = getColumn( pEv->position().x() );
442
443 m_pPatternEditorPanel->setCursorPosition( nColumn );
444
445 auto pHydrogenApp = HydrogenApp::get_instance();
446 auto pHydrogen = Hydrogen::get_instance();
447
448 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
449 pHydrogenApp->setHideKeyboardCursor( true );
450
451 if ( m_nDragPreviousColumn != nColumn ) {
452 // Complete current undo action, and start a new one.
454 prepareUndoAction( pEv->position().x() );
455 }
456
457 float val = height() - pEv->position().y();
458 if (val > height()) {
459 val = height();
460 }
461 else if (val < 0.0) {
462 val = 0.0;
463 }
464 val = val / height(); // val is normalized, in [0;1]
465 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
466 if ( pSelectedInstrument == nullptr ) {
467 ERRORLOG( "No instrument selected" );
468 return;
469 }
470
471 bool bValueSet = false;
472
473 FOREACH_NOTE_CST_IT_BOUND_LENGTH( m_pPattern->get_notes(), it, nColumn, m_pPattern ) {
474 Note *pNote = it->second;
475
476 if ( pNote->get_instrument() != pSelectedInstrument &&
477 !m_selection.isSelected( pNote ) ) {
478 continue;
479 }
480 if ( m_mode == PatternEditor::Mode::Velocity && !pNote->get_note_off() ) {
481 pNote->set_velocity( val );
482 m_fLastSetValue = val;
483 bValueSet = true;
484 }
485 else if ( m_mode == PatternEditor::Mode::Pan && !pNote->get_note_off() ){
486 if ( (ev->button() == Qt::MiddleButton)
487 || (ev->modifiers() == Qt::ControlModifier && ev->button() == Qt::LeftButton) ) {
488 val = 0.5; // central pan
489 }
490 pNote->setPanWithRangeFrom0To1( val ); // checks the boundaries
492 bValueSet = true;
493
494 }
496 if ( (ev->button() == Qt::MiddleButton) ||
497 (ev->modifiers() == Qt::ControlModifier &&
498 ev->button() == Qt::LeftButton) ) {
499 pNote->set_lead_lag(0.0);
500 m_fLastSetValue = 0.0;
501 bValueSet = true;
502 }
503 else {
504 m_fLastSetValue = val * -2.0 + 1.0;
505 bValueSet = true;
507 }
508 }
510 if ( ev->button() != Qt::MiddleButton &&
511 ! ( ev->modifiers() == Qt::ControlModifier &&
512 ev->button() == Qt::LeftButton ) ) {
513 int nKey = 666;
514 int nOctave = 666;
515 if ( pEv->position().y() > 0 &&
516 pEv->position().y() <= NotePropertiesRuler::nNoteKeyOctaveHeight ) {
517 nOctave = std::round(
520 pEv->position().y() -
523 nOctave = std::clamp( nOctave, OCTAVE_MIN, OCTAVE_MAX );
524 }
525 else if ( pEv->position().y() >= NotePropertiesRuler::nNoteKeyOctaveHeight &&
526 pEv->position().y() < NotePropertiesRuler::nNoteKeyHeight ) {
527 nKey = ( height() - pEv->position().y() -
530 nKey = std::clamp( nKey, KEY_MIN, KEY_MAX );
531 }
532
533 if ( nKey != 666 || nOctave != 666 ) {
534 m_fLastSetValue = nOctave * KEYS_PER_OCTAVE + nKey;
535 bValueSet = true;
536 pNote->set_key_octave((Note::Key)nKey,(Note::Octave)nOctave); // won't set wrong values see Note::set_key_octave
537 }
538 }
539 }
540 else if ( m_mode == PatternEditor::Mode::Probability && !pNote->get_note_off() ) {
541 m_fLastSetValue = val;
542 bValueSet = true;
543 pNote->set_probability( val );
544 }
545
546 if ( bValueSet ) {
548 m_bValueHasBeenSet = true;
550 }
551 }
552
553 // Cursor just got hidden.
554 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
555 // Immediate update to prevent visual delay.
556 m_pPatternEditorPanel->getPatternEditorRuler()->update();
557 }
558
559 m_nDragPreviousColumn = nColumn;
561 update();
562
563 m_pPatternEditorPanel->getPianoRollEditor()->updateEditor();
564 m_pPatternEditorPanel->getDrumPatternEditor()->updateEditor();
565}
566
568{
570 unsetCursor();
572 update();
573}
574
577void NotePropertiesRuler::adjustNotePropertyDelta( Note *pNote, float fDelta, bool bMessage )
578{
579 Note *pOldNote = m_oldNotes[ pNote ];
580 assert( pOldNote );
581
582 bool bValueSet = false;
583
584 switch (m_mode) {
586 if ( !pNote->get_note_off() ) {
587 float fVelocity = qBound( VELOCITY_MIN, (pOldNote->get_velocity() + fDelta), VELOCITY_MAX );
588 pNote->set_velocity( fVelocity );
589 m_fLastSetValue = fVelocity;
590 bValueSet = true;
591 }
592 break;
594 if ( !pNote->get_note_off() ) {
595 float fVal = pOldNote->getPanWithRangeFrom0To1() + fDelta; // value in [0,1] or slight out of boundaries
596 pNote->setPanWithRangeFrom0To1( fVal ); // checks the boundaries as well
598 bValueSet = true;
599 }
600 break;
602 {
603 float fLeadLag = qBound( LEAD_LAG_MIN, pOldNote->get_lead_lag() - fDelta, LEAD_LAG_MAX );
604 pNote->set_lead_lag( fLeadLag );
605 m_fLastSetValue = fLeadLag;
606 bValueSet = true;
607 }
608 break;
610 if ( !pNote->get_note_off() ) {
611 float fProbability = qBound( 0.0f, pOldNote->get_probability() + fDelta, 1.0f );
612 pNote->set_probability( fProbability );
613 m_fLastSetValue = fProbability;
614 bValueSet = true;
615 }
616 break;
618 int nPitch = qBound( KEYS_PER_OCTAVE * OCTAVE_MIN, (int)( pOldNote->get_notekey_pitch() + fDelta ),
620 Note::Octave octave;
621 if ( nPitch >= 0 ) {
622 octave = (Note::Octave)( nPitch / KEYS_PER_OCTAVE );
623 } else {
624 octave = (Note::Octave)( (nPitch-11) / KEYS_PER_OCTAVE );
625 }
626 Note::Key key = (Note::Key)( nPitch - KEYS_PER_OCTAVE * (int)octave );
627
628 pNote->set_key_octave( key, octave );
629 m_fLastSetValue = KEYS_PER_OCTAVE * octave + key;
630
631 bValueSet = true;
632 break;
633 }
634
635 if ( bValueSet ) {
637 m_bValueHasBeenSet = true;
638 if ( bMessage ) {
640 }
641 }
642}
643
645{
646 if ( m_pPattern == nullptr ) {
647 return;
648 }
649
650 auto pHydrogenApp = HydrogenApp::get_instance();
651 auto pHydrogen = Hydrogen::get_instance();
652 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
653
654 const int nWordSize = 5;
655 bool bIsSelectionKey = m_selection.keyPressEvent( ev );
656 bool bUnhideCursor = true;
657
658 bool bValueChanged = false;
659
660 if ( bIsSelectionKey ) {
661 // Key was claimed by selection
662 } else if ( ev->matches( QKeySequence::MoveToNextChar ) || ev->matches( QKeySequence::SelectNextChar ) ) {
663 // ->
664 m_pPatternEditorPanel->moveCursorRight();
665
666 } else if ( ev->matches( QKeySequence::MoveToNextWord ) || ev->matches( QKeySequence::SelectNextWord ) ) {
667 // ->
668 m_pPatternEditorPanel->moveCursorRight( nWordSize );
669
670 } else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) {
671 // -->|
672 m_pPatternEditorPanel->setCursorPosition( m_pPattern->get_length() );
673
674 } else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) {
675 // <-
676 m_pPatternEditorPanel->moveCursorLeft();
677
678 } else if ( ev->matches( QKeySequence::MoveToPreviousWord ) || ev->matches( QKeySequence::SelectPreviousWord ) ) {
679 // <-
680 m_pPatternEditorPanel->moveCursorLeft( nWordSize );
681
682 } else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) {
683 // |<--
684 m_pPatternEditorPanel->setCursorPosition(0);
685
686 } else if ( ev->key() == Qt::Key_Delete ) {
687 // Key: Delete / Backspace: delete selected notes, or note under keyboard cursor
688 bUnhideCursor = false;
689 if ( m_selection.begin() != m_selection.end() ) {
690 // Delete selected notes if any
691 m_pPatternEditorPanel->getDrumPatternEditor()->
693 } else {
694 // Delete note under the keyboard cursor.
695 m_pPatternEditorPanel->getDrumPatternEditor()->
696 addOrRemoveNote( m_pPatternEditorPanel->getCursorPosition(), -1,
697 pHydrogen->getSelectedInstrumentNumber(),
698 /*bDoAdd=*/false, /*bDoDelete=*/true );
699 }
700
701 } else {
702
703 // Value adjustments
704 float fDelta = 0.0;
705 bool bRepeatLastValue = false;
706
707 if ( ev->matches( QKeySequence::MoveToPreviousLine ) ) {
708 // Key: Up: increase note parameter value
709 fDelta = 0.1;
710
711 } else if ( ev->key() == Qt::Key_Up && ev->modifiers() & Qt::AltModifier ) {
712 // Key: Alt+Up: increase parameter slightly
713 fDelta = 0.01;
714
715 } else if ( ev->matches( QKeySequence::MoveToNextLine ) ) {
716 // Key: Down: decrease note parameter value
717 fDelta = -0.1;
718
719 } else if ( ev->key() == Qt::Key_Down && ev->modifiers() & Qt::AltModifier ) {
720 // Key: Alt+Up: decrease parameter slightly
721 fDelta = -0.01;
722
723 } else if ( ev->matches( QKeySequence::MoveToStartOfDocument ) ) {
724 // Key: MoveToStartOfDocument: increase parameter to maximum value
725 fDelta = 1.0;
726
727 } else if ( ev->matches( QKeySequence::MoveToEndOfDocument ) ) {
728 // Key: MoveEndOfDocument: decrease parameter to minimum value
729 fDelta = -1.0;
730
731 } else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) {
732 // Key: Enter/Return: repeat last parameter value set.
733 if ( m_bValueHasBeenSet ) {
734 bRepeatLastValue = true;
735 }
736
737 } else if ( ev->matches( QKeySequence::SelectAll ) ) {
738 // Key: Ctrl + A: Select all
739 bUnhideCursor = false;
740 selectAll();
741
742 } else if ( ev->matches( QKeySequence::Deselect ) ) {
743 // Key: Shift + Ctrl + A: clear selection
744 bUnhideCursor = false;
745 selectNone();
746
747 }
748
749 if ( fDelta != 0.0 || bRepeatLastValue ) {
750 int column = m_pPatternEditorPanel->getCursorPosition();
751
752 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
753 if ( pSelectedInstrument == nullptr ) {
754 ERRORLOG( "No instrument selected" );
755 return;
756 }
757
758 int nNotes = 0;
759
760 // Collect notes to apply the change to
761 std::list< Note *> notes;
762 if ( m_selection.begin() != m_selection.end() ) {
763 for ( Note *pNote : m_selection ) {
764 nNotes++;
765 notes.push_back( pNote );
766 }
767 } else {
768 FOREACH_NOTE_CST_IT_BOUND_LENGTH( m_pPattern->get_notes(), it, column, m_pPattern ) {
769 Note *pNote = it->second;
770 assert( pNote );
771 assert( pNote->get_position() == column );
772 if ( pNote->get_instrument() == pSelectedInstrument ) {
773 nNotes++;
774 notes.push_back( pNote );
775 }
776 }
777 }
778
779 // For the NoteKeyEditor, adjust the pitch by a whole semitone
781 if ( fDelta > 0.0 ) {
782 fDelta = 1;
783 } else if ( fDelta < 0.0 ) {
784 fDelta = -1;
785 }
786 }
787
789
790 for ( Note *pNote : notes ) {
791 bValueChanged = true;
792
793 if ( !bRepeatLastValue ) {
794
795 // Apply delta to the property
796 adjustNotePropertyDelta( pNote, fDelta, nNotes == 1 );
797
798 } else {
799
800 bool bValueSet = false;
801
802 // Repeating last value
803 switch (m_mode) {
805 if ( !pNote->get_note_off() ) {
806 pNote->set_velocity( m_fLastSetValue );
807 bValueSet = true;
808 }
809 break;
811 if ( !pNote->get_note_off() ) {
812 if ( m_fLastSetValue > 1. ) { // TODO whats this for? is it ever reached?
813 printf( "reached m_fLastSetValue > 1 in NotePropertiesRuler.cpp\n" );
814 pNote->setPanWithRangeFrom0To1( m_fLastSetValue );
815 }
816 bValueSet = true;
817 }
818 break;
820 pNote->set_lead_lag( m_fLastSetValue );
821 bValueSet = true;
822 break;
824 if ( !pNote->get_note_off() ) {
825 pNote->set_probability( m_fLastSetValue );
826 bValueSet = true;
827 }
828 break;
830 pNote->set_key_octave( (Note::Key)( (int)m_fLastSetValue % 12 ),
831 (Note::Octave)( (int)m_fLastSetValue / 12 ) );
832 bValueSet = true;
833 break;
834 }
835
836 if ( bValueSet ) {
837 if ( nNotes == 1 ) {
839 }
841 }
842 }
843 }
845 } else {
846 pHydrogenApp->setHideKeyboardCursor( true );
847 ev->ignore();
848
849 // Cursor either just got hidden.
850 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
851 // Immediate update to prevent visual delay.
852 m_pPatternEditorPanel->getPatternEditorRuler()->update();
853 update();
854 }
855 return;
856 }
857 }
858 if ( bUnhideCursor ) {
859 pHydrogenApp->setHideKeyboardCursor( false );
860 }
861
862 // Cursor either just got hidden or was moved.
863 if ( ! HydrogenApp::get_instance()->hideKeyboardCursor() ||
864 bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
865 // Immediate update to prevent visual delay.
866 m_pPatternEditorPanel->getPatternEditorRuler()->update();
867 }
868
869 m_selection.updateKeyboardCursorPosition( getKeyboardCursorRect() );
870
871 if ( bValueChanged ) {
873 }
874 update();
875
876 ev->accept();
877
878}
879
881{
882 if ( m_nSelectedPatternNumber == -1 ) {
883 // No pattern selected.
884 return;
885 }
886
887 auto pInstrumentList = Hydrogen::get_instance()->getSong()->getInstrumentList();
888 int nSize = m_oldNotes.size();
889 if ( nSize != 0 ) {
890 QUndoStack *pUndoStack = HydrogenApp::get_instance()->m_pUndoStack;
891
892 if ( nSize != 1 ) {
893 pUndoStack->beginMacro( QString( tr( "Edit [%1] property of [%2] notes" ) )
895 .arg( nSize ) );
896 }
897 for ( auto it : m_oldNotes ) {
898 Note *pNewNote = it.first, *pOldNote = it.second;
899 pUndoStack->push( new SE_editNotePropertiesVolumeAction( pNewNote->get_position(),
900 m_mode,
902 pInstrumentList->index( pNewNote->get_instrument() ),
903 pNewNote->get_velocity(),
904 pOldNote->get_velocity(),
905 pNewNote->getPan(),
906 pOldNote->getPan(),
907 pNewNote->get_lead_lag(),
908 pOldNote->get_lead_lag(),
909 pNewNote->get_probability(),
910 pOldNote->get_probability(),
911 pNewNote->get_key(),
912 pOldNote->get_key(),
913 pNewNote->get_octave(),
914 pOldNote->get_octave() ) );
915 }
916 if ( nSize != 1 ) {
917 pUndoStack->endMacro();
918 }
919 }
921}
922
924{
925 if (!isVisible()) {
926 return;
927 }
928
929 auto pPref = Preferences::get_instance();
930
931 qreal pixelRatio = devicePixelRatio();
932 if ( pixelRatio != m_pBackgroundPixmap->devicePixelRatio() ||
935 }
936
937 QPainter painter(this);
938 painter.drawPixmap( ev->rect(), *m_pBackgroundPixmap,
939 QRectF( pixelRatio * ev->rect().x(),
940 pixelRatio * ev->rect().y(),
941 pixelRatio * ev->rect().width(),
942 pixelRatio * ev->rect().height() ) );
943
944 // Draw playhead
945 if ( m_nTick != -1 ) {
946
947 int nOffset = Skin::getPlayheadShaftOffset();
948 int nX = static_cast<int>(static_cast<float>(PatternEditor::nMargin) +
949 static_cast<float>(m_nTick) *
950 m_fGridWidth );
951 Skin::setPlayheadPen( &painter, false );
952 painter.drawLine( nX, 0, nX, height() );
953 }
954
955 drawFocus( painter );
956
957 m_selection.paintSelection( &painter );
958
959 // cursor
960 if ( hasFocus() && ! HydrogenApp::get_instance()->hideKeyboardCursor() ) {
961 uint x = PatternEditor::nMargin + m_pPatternEditorPanel->getCursorPosition() * m_fGridWidth;
962
963 QPen pen( pPref->getColorTheme()->m_cursorColor );
964 pen.setWidth( 2 );
965 painter.setPen( pen );
966 painter.setBrush( Qt::NoBrush );
967 painter.setRenderHint( QPainter::Antialiasing );
968 painter.drawRoundedRect( QRect( x-m_fGridWidth*3, 0 + 3, m_fGridWidth*6, height() - 6 ), 4, 4 );
969 }
970}
971
972void NotePropertiesRuler::drawFocus( QPainter& painter ) {
973
974 if ( ! m_bEntered && ! hasFocus() ) {
975 return;
976 }
977
979
980 QColor color = pPref->getColorTheme()->m_highlightColor;
981
982 // If the mouse is placed on the widget but the user hasn't
983 // clicked it yet, the highlight will be done more transparent to
984 // indicate that keyboard inputs are not accepted yet.
985 if ( ! hasFocus() ) {
986 color.setAlpha( 125 );
987 }
988
989 const QScrollArea* pScrollArea;
990
991 switch ( m_mode ) {
994 break;
997 break;
1000 break;
1003 break;
1006 break;
1007 default:
1008 return;
1009 }
1010 int nStartY = pScrollArea->verticalScrollBar()->value();
1011 int nStartX = pScrollArea->horizontalScrollBar()->value();
1012 int nEndY = nStartY + pScrollArea->viewport()->size().height();
1013 // In order to match the width used in the DrumPatternEditor.
1014 int nEndX = std::min( nStartX + pScrollArea->viewport()->size().width(),
1015 static_cast<int>( m_nEditorWidth ) );
1016
1017 int nMargin;
1018 if ( nEndX == static_cast<int>( m_nEditorWidth ) ) {
1019 nEndX = nEndX - 2;
1020 nMargin = 1;
1021 } else {
1022 nMargin = 0;
1023 }
1024
1025 QPen pen( color );
1026 pen.setWidth( 4 );
1027 painter.setPen( pen );
1028 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nEndX, nStartY ) );
1029 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nStartX, nEndY ) );
1030 painter.drawLine( QPoint( nEndX, nEndY ), QPoint( nStartX, nEndY ) );
1031
1032 if ( nMargin != 0 ) {
1033 // Since for all other lines we are drawing at a border with just
1034 // half of the line being painted in the visual viewport, there
1035 // has to be some tweaking since the NotePropertiesRuler is
1036 // paintable to the right.
1037 pen.setWidth( 2 );
1038 painter.setPen( pen );
1039 }
1040 painter.drawLine( QPoint( nEndX + nMargin, nStartY ), QPoint( nEndX + nMargin, nEndY ) );
1041
1042}
1043
1045 UNUSED( nValue );
1046 update();
1047}
1048
1049#ifdef H2CORE_HAVE_QT6
1050void NotePropertiesRuler::enterEvent( QEnterEvent *ev ) {
1051#else
1053#endif
1054 UNUSED( ev );
1055 m_bEntered = true;
1056 update();
1057}
1058
1060 UNUSED( ev );
1061 m_bEntered = false;
1062 update();
1063}
1064
1065void NotePropertiesRuler::drawDefaultBackground( QPainter& painter, int nHeight, int nIncrement ) {
1066
1067 auto pPref = H2Core::Preferences::get_instance();
1068
1069 const QColor borderColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1070 const QColor lineColor( pPref->getColorTheme()->m_patternEditor_line5Color );
1071 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
1072 const QColor backgroundColor( pPref->getColorTheme()->m_patternEditor_backgroundColor );
1073 const QColor backgroundInactiveColor( pPref->getColorTheme()->m_windowColor );
1074
1075 if ( nHeight == 0 ) {
1076 nHeight = height();
1077 }
1078 if ( nIncrement == 0 ) {
1079 nIncrement = nHeight / 10;
1080 }
1081
1082 painter.fillRect( 0, 0, m_nActiveWidth, height(), backgroundColor );
1083 painter.fillRect( m_nActiveWidth, 0, m_nEditorWidth - m_nActiveWidth,
1084 height(), backgroundInactiveColor );
1085
1086 drawGridLines( painter, Qt::DotLine );
1087
1088 painter.setPen( lineColor );
1089 for (unsigned y = 0; y < nHeight; y += nIncrement ) {
1090 painter.drawLine( PatternEditor::nMargin, y, m_nActiveWidth, y );
1091 }
1092
1093 painter.setPen( borderColor );
1094 painter.drawLine( 0, 0, m_nActiveWidth, 0 );
1095 painter.drawLine( 0, m_nEditorHeight - 1, m_nActiveWidth, m_nEditorHeight - 1 );
1096
1097 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1098 painter.setPen( lineInactiveColor );
1099 for (unsigned y = 0; y < nHeight; y += nIncrement ) {
1100 painter.drawLine( m_nActiveWidth, y, m_nEditorWidth, y );
1101 }
1102
1103 painter.drawLine( m_nActiveWidth, 0, m_nEditorWidth, 0 );
1104 painter.drawLine( m_nActiveWidth, m_nEditorHeight - 1,
1106 }
1107}
1108
1110{
1111 auto pPref = H2Core::Preferences::get_instance();
1112 auto pHydrogen = Hydrogen::get_instance();
1113
1114 QColor borderColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1115 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
1116 QPainter p( pixmap );
1117
1119
1120 // draw velocity lines
1121 if ( m_pPattern != nullptr ) {
1122 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
1123 if ( pSelectedInstrument == nullptr ) {
1124 ERRORLOG( "No instrument selected" );
1125 return;
1126 }
1127
1128 QPen selectedPen( selectedNoteColor() );
1129 selectedPen.setWidth( 2 );
1130
1131 const Pattern::notes_t* notes = m_pPattern->get_notes();
1133 Note *pposNote = it->second;
1134 assert( pposNote );
1135 uint pos = pposNote->get_position();
1136 int xoffset = 0;
1138 Note *pNote = coit->second;
1139 assert( pNote );
1140 if ( pNote->get_instrument() != pSelectedInstrument
1141 && !m_selection.isSelected( pNote ) ) {
1142 continue;
1143 }
1144 uint x_pos = PatternEditor::nMargin + pos * m_fGridWidth;
1145 uint line_end = height();
1146
1147
1148 uint value = 0;
1150 value = (uint)(pNote->get_velocity() * height());
1151 }
1153 value = (uint)(pNote->get_probability() * height());
1154 }
1155 uint line_start = line_end - value;
1156 QColor noteColor = DrumPatternEditor::computeNoteColor( pNote->get_velocity() );
1157 int nLineWidth = 3;
1158
1159 p.fillRect( x_pos - 1 + xoffset, line_start,
1160 nLineWidth, line_end - line_start,
1161 noteColor );
1162 p.setPen( QPen( Qt::black, 1 ) );
1163 p.setRenderHint( QPainter::Antialiasing );
1164 p.drawRoundedRect( x_pos - 1 - 1 + xoffset, line_start - 1,
1165 nLineWidth + 2, line_end - line_start + 2, 2, 2 );
1166
1167 if ( m_selection.isSelected( pNote ) ) {
1168 p.setPen( selectedPen );
1169 p.setRenderHint( QPainter::Antialiasing );
1170 p.drawRoundedRect( x_pos - 1 -2 + xoffset, line_start - 2,
1171 nLineWidth + 4, line_end - line_start + 4 ,
1172 4, 4 );
1173 }
1174 xoffset++;
1175 }
1176 }
1177 }
1178
1179 p.setPen( borderColor );
1180 p.setRenderHint( QPainter::Antialiasing );
1181 p.drawLine( 0, 0, m_nEditorWidth, 0 );
1182 p.setPen( QPen( borderColor, 2 ) );
1184
1185 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1186 p.setPen( lineInactiveColor );
1187 p.drawLine( m_nActiveWidth, 0, m_nEditorWidth, 0 );
1188 p.setPen( QPen( lineInactiveColor, 2 ) );
1189 p.drawLine( m_nActiveWidth, m_nEditorHeight,
1191 }
1192}
1193
1195{
1196 auto pPref = H2Core::Preferences::get_instance();
1197 auto pHydrogen = Hydrogen::get_instance();
1198
1199 QColor baseLineColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1200 QColor borderColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1201 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
1202
1203 QPainter p( pixmap );
1204
1206
1207 // central line
1208 p.setPen( baseLineColor );
1209 p.drawLine(0, height() / 2.0, m_nActiveWidth, height() / 2.0);
1210 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1211 p.setPen( lineInactiveColor );
1212 p.drawLine( m_nActiveWidth, height() / 2.0,
1213 m_nEditorWidth, height() / 2.0);
1214 }
1215
1216 if ( m_pPattern != nullptr ) {
1217 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
1218 if ( pSelectedInstrument == nullptr ) {
1219 ERRORLOG( "No instrument selected" );
1220 return;
1221 }
1222
1223 QPen selectedPen( selectedNoteColor() );
1224 selectedPen.setWidth( 2 );
1225
1226 const Pattern::notes_t* notes = m_pPattern->get_notes();
1228 Note *pposNote = it->second;
1229 assert( pposNote );
1230 uint pos = pposNote->get_position();
1231 int xoffset = 0;
1233 Note *pNote = coit->second;
1234 assert( pNote );
1235 if ( pNote->get_note_off() || (pNote->get_instrument()
1236 != pSelectedInstrument
1237 && !m_selection.isSelected( pNote ) ) ) {
1238 continue;
1239 }
1240 uint x_pos = PatternEditor::nMargin + pNote->get_position() * m_fGridWidth;
1241 QColor noteColor = DrumPatternEditor::computeNoteColor( pNote->get_velocity() );
1242
1243 p.setPen( Qt::NoPen );
1244
1245 float fValue = 0;
1247 fValue = pNote->getPan();
1248 } else if ( m_mode == PatternEditor::Mode::LeadLag ) {
1249 fValue = -1 * pNote->get_lead_lag();
1250 }
1251
1252 // Rounding in order to not miss the center due to
1253 // rounding errors introduced in the Note class
1254 // internals.
1255 fValue *= 100;
1256 fValue = std::round( fValue );
1257 fValue /= 100;
1258
1259 int nLineWidth = 3;
1260 p.setPen( QPen( Qt::black, 1 ) );
1261 p.setRenderHint( QPainter::Antialiasing );
1262 if ( fValue == 0.f ) {
1263 // value is centered - draw circle
1264 int y_pos = (int)( height() * 0.5 );
1265 p.setBrush(QColor( noteColor ));
1266 p.drawEllipse( x_pos-4 + xoffset, y_pos-4, 8, 8);
1267 p.setBrush( Qt::NoBrush );
1268
1269 if ( m_selection.isSelected( pNote ) ) {
1270 p.setPen( selectedPen );
1271 p.setRenderHint( QPainter::Antialiasing );
1272 p.drawEllipse( x_pos - 6 + xoffset, y_pos - 6,
1273 12, 12);
1274 }
1275 }
1276 else {
1277 // value was altered - draw a rectangle
1278 int nHeight = 0.5 * height() * std::abs( fValue ) + 5;
1279 int nStartY = height() * 0.5 - 2;
1280 if ( fValue >= 0 ) {
1281 nStartY = nStartY - nHeight + 5;
1282 }
1283
1284 p.fillRect( x_pos - 1 + xoffset, nStartY,
1285 nLineWidth, nHeight, QColor( noteColor ) );
1286 p.drawRoundedRect( x_pos - 1 + xoffset - 1, nStartY - 1,
1287 nLineWidth + 2, nHeight + 2, 2, 2 );
1288
1289 if ( m_selection.isSelected( pNote ) ) {
1290 p.setPen( selectedPen );
1291 p.drawRoundedRect( x_pos - 1 - 2 + xoffset, nStartY - 2,
1292 nLineWidth + 4, nHeight + 4,
1293 4, 4 );
1294 }
1295 }
1296 xoffset++;
1297 }
1298 }
1299 }
1300
1301
1302 p.setPen( borderColor );
1303 p.setRenderHint( QPainter::Antialiasing );
1304 p.drawLine( 0, 0, m_nEditorWidth, 0 );
1305 p.setPen( QPen( borderColor, 2 ) );
1307
1308 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1309 p.setPen( lineInactiveColor );
1310 p.drawLine( m_nActiveWidth, 0, m_nEditorWidth, 0 );
1311 p.setPen( QPen( lineInactiveColor, 2 ) );
1312 p.drawLine( m_nActiveWidth, m_nEditorHeight,
1314 }
1315}
1316
1318{
1319 auto pPref = H2Core::Preferences::get_instance();
1320 QColor backgroundColor = pPref->getColorTheme()->m_patternEditor_backgroundColor;
1321 const QColor backgroundInactiveColor( pPref->getColorTheme()->m_windowColor );
1322 QColor alternateRowColor = pPref->getColorTheme()->m_patternEditor_alternateRowColor;
1323 QColor octaveColor = pPref->getColorTheme()->m_patternEditor_octaveRowColor;
1324 QColor lineColor( pPref->getColorTheme()->m_patternEditor_lineColor );
1325 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
1326 QColor textColor( pPref->getColorTheme()->m_patternEditor_textColor );
1327
1328 QPainter p( pixmap );
1329 p.fillRect( 0, 0, m_nEditorWidth, m_nEditorHeight, backgroundInactiveColor );
1333
1334 // fill the background of the key region;
1338
1339 const int nRow = ( y - NotePropertiesRuler::nNoteKeyOctaveHeight ) /
1341 if ( nRow == 1 || nRow == 3 || nRow == 5 || nRow == 8 || nRow == 10 ) {
1342 // Draw rows of semi tones in a different color.
1343 p.setPen( QPen( alternateRowColor,
1345 Qt::SolidLine, Qt::FlatCap ) );
1346 }
1347 else {
1348 p.setPen( QPen( octaveColor,
1350 Qt::SolidLine, Qt::FlatCap ) );
1351 }
1352
1353 p.drawLine( PatternEditor::nMargin, y, m_nActiveWidth, y );
1354 }
1355
1356 drawGridLines( p, Qt::DotLine );
1357
1358 // Annotate with note class names
1359 static QString noteNames[] = { tr( "B" ), tr( "A#" ), tr( "A" ), tr( "G#" ), tr( "G" ), tr( "F#" ),
1360 tr( "F" ), tr( "E" ), tr( "D#" ), tr( "D" ), tr( "C#" ), tr( "C" ) };
1361
1362 QFont font( pPref->getApplicationFontFamily(), getPointSize( pPref->getFontSize() ) );
1363
1364 p.setFont( font );
1365 p.setPen( textColor );
1366 for ( int n = 0; n < KEYS_PER_OCTAVE; n++ ) {
1369 noteNames[n] );
1370 }
1371
1372 // Horizontal grid lines in the key region
1373 p.setPen( QPen( lineColor, 1, Qt::SolidLine));
1377 p.drawLine( PatternEditor::nMargin,
1381 }
1382
1383 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1384 p.setPen( lineInactiveColor );
1388 p.drawLine( m_nActiveWidth,
1392 }
1393 }
1394
1395 if ( m_pPattern != nullptr ) {
1396 auto pSelectedInstrument = Hydrogen::get_instance()->getSelectedInstrument();
1397 if ( pSelectedInstrument == nullptr ) {
1398 DEBUGLOG( "No instrument selected" );
1399 return;
1400 }
1401 QPen selectedPen( selectedNoteColor() );
1402 selectedPen.setWidth( 2 );
1403
1404 const Pattern::notes_t* notes = m_pPattern->get_notes();
1406 Note *pNote = it->second;
1407 assert( pNote );
1408 if ( pNote->get_instrument() != pSelectedInstrument
1409 && !m_selection.isSelected( pNote ) ) {
1410 continue;
1411 }
1412 if ( !pNote->get_note_off() ) {
1413 // paint the octave
1414 const int nRadiusOctave = 3;
1415 const int nX = PatternEditor::nMargin +
1416 pNote->get_position() * m_fGridWidth;
1417 const int nOctaveY = ( 4 - pNote->get_octave() ) *
1419 p.setPen( QPen( Qt::black, 1 ) );
1421 pNote->get_velocity() ) );
1422 p.drawEllipse( QPoint( nX, nOctaveY ), nRadiusOctave,
1423 nRadiusOctave );
1424
1425 // paint note
1426 const int nRadiusKey = 5;
1427 const int nKeyY = NotePropertiesRuler::nNoteKeyHeight -
1428 ( ( pNote->get_key() + 1 ) *
1430
1432 pNote->get_velocity() ) );
1433 p.drawEllipse( QPoint( nX, nKeyY ), nRadiusKey, nRadiusKey);
1434
1435 // Paint selection outlines
1436 if ( m_selection.isSelected( pNote ) ) {
1437 p.setPen( selectedPen );
1438 p.setBrush( Qt::NoBrush );
1439 p.setRenderHint( QPainter::Antialiasing );
1440 // Octave
1441 p.drawEllipse( QPoint( nX, nOctaveY ), nRadiusOctave + 1,
1442 nRadiusOctave + 1 );
1443
1444 // Key
1445 p.drawEllipse( QPoint( nX, nKeyY ), nRadiusKey + 1,
1446 nRadiusKey + 1 );
1447 }
1448 }
1449 }
1450 }
1451
1452 p.setPen( lineColor );
1453 p.setRenderHint( QPainter::Antialiasing );
1454 p.drawLine( 0, 0, m_nEditorWidth, 0 );
1455 p.setPen( QPen( lineColor, 2 ) );
1457
1458 if ( m_nActiveWidth + 1 < m_nEditorWidth ) {
1459 p.setPen( lineInactiveColor );
1460 p.drawLine( m_nActiveWidth, 0, m_nEditorWidth, 0 );
1461 p.setPen( QPen( lineInactiveColor, 2 ) );
1462 p.drawLine( m_nActiveWidth, m_nEditorHeight,
1464 }
1465}
1466
1467
1469{
1470 Hydrogen *pHydrogen = Hydrogen::get_instance();
1471 PatternList *pPatternList = pHydrogen->getSong()->getPatternList();
1472 int nSelectedPatternNumber = pHydrogen->getSelectedPatternNumber();
1473 if ( (nSelectedPatternNumber != -1) && ( (uint)nSelectedPatternNumber < pPatternList->size() ) ) {
1474 m_pPattern = pPatternList->get( nSelectedPatternNumber );
1475 }
1476 else {
1477 m_pPattern = nullptr;
1478 }
1479 m_nSelectedPatternNumber = nSelectedPatternNumber;
1480
1481 updateWidth();
1482 resize( m_nEditorWidth, height() );
1483
1485 update();
1486}
1487
1489{
1490 qreal pixelRatio = devicePixelRatio();
1491 if ( m_pBackgroundPixmap->width() != m_nEditorWidth ||
1492 m_pBackgroundPixmap->height() != m_nEditorHeight ||
1493 m_pBackgroundPixmap->devicePixelRatio() != pixelRatio ) {
1494 delete m_pBackgroundPixmap;
1495 m_pBackgroundPixmap = new QPixmap( m_nEditorWidth * pixelRatio ,
1496 m_nEditorHeight * pixelRatio );
1497 m_pBackgroundPixmap->setDevicePixelRatio( pixelRatio );
1498 }
1499
1503 }
1504 else if ( m_mode == PatternEditor::Mode::Pan ||
1507 }
1508 else if ( m_mode == PatternEditor::Mode::NoteKey ) {
1510 }
1511
1512 m_bBackgroundInvalid = false;
1513}
1514
1515
1520
1525
1529
1530std::vector<NotePropertiesRuler::SelectionIndex> NotePropertiesRuler::elementsIntersecting( QRect r ) {
1531 std::vector<SelectionIndex> result;
1532 if ( m_pPattern == nullptr ) {
1533 return std::move( result );
1534 }
1535
1536 auto pHydrogen = Hydrogen::get_instance();
1537
1538 const Pattern::notes_t* notes = m_pPattern->get_notes();
1539 auto pSelectedInstrument = pHydrogen->getSelectedInstrument();
1540 if ( pSelectedInstrument == nullptr ) {
1541 ERRORLOG( "No instrument selected" );
1542 return std::move( result );
1543 }
1544
1545 // Account for the notional active area of the slider. We allow a
1546 // width of 8 as this is the size of the circle used for the zero
1547 // position on the lead/lag editor.
1548 r = r.normalized();
1549 if ( r.top() == r.bottom() && r.left() == r.right() ) {
1550 r += QMargins( 2, 2, 2, 2 );
1551 }
1552 r += QMargins( 4, 4, 4, 4 );
1553
1555 if ( it->second->get_instrument() != pSelectedInstrument
1556 && !m_selection.isSelected( it->second ) ) {
1557 continue;
1558 }
1559
1560 int pos = it->first;
1561 uint x_pos = PatternEditor::nMargin + pos * m_fGridWidth;
1562 if ( r.intersects( QRect( x_pos, 0, 1, height() ) ) ) {
1563 result.push_back( it->second );
1564 }
1565 }
1566
1567 // Updating selection, we may need to repaint the whole widget.
1569 update();
1570
1571 return std::move(result);
1572}
1573
1578{
1579 uint x = PatternEditor::nMargin +
1580 m_pPatternEditorPanel->getCursorPosition() * m_fGridWidth;
1581 return QRect( x-m_fGridWidth*3, 3, m_fGridWidth*6, height()-6 );
1582}
1583
1585{
1586 selectInstrumentNotes( Hydrogen::get_instance()->getSelectedInstrumentNumber() );
1587}
1588
#define VELOCITY_MAX
Definition Note.h:46
#define KEYS_PER_OCTAVE
Definition Note.h:40
#define LEAD_LAG_MIN
Definition Note.h:49
#define KEY_MAX
Definition Note.h:35
#define OCTAVE_MIN
Definition Note.h:36
#define OCTAVE_MAX
Definition Note.h:37
#define KEY_MIN
Definition Note.h:34
#define VELOCITY_MIN
Definition Note.h:45
#define LEAD_LAG_MAX
Definition Note.h:50
#define ERRORLOG(x)
Definition Object.h:242
#define DEBUGLOG(x)
Definition Object.h:239
#define FOREACH_NOTE_CST_IT_BOUND_LENGTH(_notes, _it, _bound, _pattern)
Iterate over all notes in column _bound in an immutable way if it is contained in _pattern.
Definition Pattern.h:290
#define FOREACH_NOTE_CST_IT_BEGIN_LENGTH(_notes, _it, _pattern)
Iterate over all accessible notes between position 0 and length of _pattern in an immutable way.
Definition Pattern.h:285
Hydrogen Audio Engine.
Definition Hydrogen.h:54
std::shared_ptr< Song > getSong() const
Get the current song.
Definition Hydrogen.h:123
int getSelectedPatternNumber() const
Definition Hydrogen.h:674
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:84
std::shared_ptr< Instrument > getSelectedInstrument() const
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
A note plays an associated instrument with a velocity left and right pan.
Definition Note.h:101
int get_position() const
__position accessor
Definition Note.h:534
float getPanWithRangeFrom0To1() const
get pan of the note, scaling and translating the range from [-1;1] to [0;1]
Definition Note.h:196
void set_lead_lag(float value)
__lead_lag setter
Definition Note.cpp:148
void set_key_octave(const QString &str)
parse str and set __key and __octave
Definition Note.cpp:202
Octave get_octave()
__octave accessor
Definition Note.h:676
void set_probability(float value)
Definition Note.h:614
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
Definition Note.h:499
float get_lead_lag() const
__lead_lag accessor
Definition Note.h:549
float get_notekey_pitch() const
note key pitch accessor
Definition Note.h:695
float get_velocity() const
__velocity accessor
Definition Note.h:539
void setPanWithRangeFrom0To1(float fVal)
set pan of the note, assuming the input range in [0;1]
Definition Note.h:190
Key
possible keys
Definition Note.h:105
bool get_note_off() const
__note_off accessor
Definition Note.h:579
void setPan(float val)
set pan of the note.
Definition Note.cpp:153
void set_velocity(float value)
__velocity setter
Definition Note.cpp:143
Key get_key()
__key accessor
Definition Note.h:671
float get_probability() const
Definition Note.h:609
Octave
possible octaves
Definition Note.h:109
float getPan() const
get pan of the note.
Definition Note.h:544
PatternList is a collection of patterns.
Definition PatternList.h:43
Pattern * get(int idx)
get a pattern from the list
std::multimap< int, Note * > notes_t
< multimap note type
Definition Pattern.h:50
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
Changes
Bitwise or-able options showing which part of the Preferences were altered using the PreferencesDialo...
@ Font
Either the font size or font family have changed.
@ Colors
At least one of the colors has changed.
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
void preferencesChanged(H2Core::Preferences::Changes changes)
Propagates a change in the Preferences through the GUI.
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
Definition MouseEvent.h:35
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
std::map< H2Core::Note *, H2Core::Note * > m_oldNotes
Map of notes currently in the pattern -> old notes with their properties.
virtual void mouseMoveEvent(QMouseEvent *ev) override
virtual void updateEditor(bool bPatternOnly=false) override
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
virtual void selectionMoveEndEvent(QInputEvent *ev) override
virtual QRect getKeyboardCursorRect() override
The screen area occupied by the keyboard cursor.
static constexpr int nNoteKeyOctaveHeight
Height of the whole octave section.
void prepareUndoAction(int x)
Preserve current note properties at position x (or in selection, if any) for use in later UndoAction.
static constexpr int nNoteKeyLineHeight
Height of a single line in the key section.
void adjustNotePropertyDelta(H2Core::Note *pNote, float fDelta, bool bMessage=false)
Adjust a note's property by applying a delta to the current value, and clipping to the appropriate ra...
virtual void selectionMoveUpdateEvent(QMouseEvent *ev) override
static constexpr int nDefaultHeight
Height of all editors except the NoteKey one.
virtual void deleteSelection() override
virtual void selectAll() override
void onPreferencesChanged(H2Core::Preferences::Changes changes)
virtual void songModeActivationEvent() override
void leaveEvent(QEvent *ev) override
void wheelEvent(QWheelEvent *ev) override
Scroll wheel gestures will adjust the property of notes under the mouse cursor (or selected notes,...
void createNoteKeyBackground(QPixmap *pixmap)
void keyPressEvent(QKeyEvent *ev) override
void createNormalizedBackground(QPixmap *pixmap)
virtual void mouseDragStartEvent(QMouseEvent *ev) override
void propertyDragUpdate(QMouseEvent *ev)
Update notes for a property adjust drag, in response to the mouse moving.
void createBackground() override
Updates m_pBackgroundPixmap to show the latest content.
virtual void selectedInstrumentChangedEvent() override
static int nNoteKeyHeight
The height of the overall NoteKey Editor.
void drawDefaultBackground(QPainter &painter, int nHeight=0, int nIncrement=0)
virtual void mouseDragEndEvent(QMouseEvent *ev) override
virtual void mouseClickEvent(QMouseEvent *ev) override
void paintEvent(QPaintEvent *ev) override
void createCenteredBackground(QPixmap *pixmap)
virtual void selectionMoveCancelEvent() override
Move of selection is cancelled. Revert notes to preserved state.
static constexpr int nNoteKeySpaceHeight
Height of the non-interactive space in NoteKey editor between octave and key section.
virtual void selectedPatternChangedEvent() override
void drawFocus(QPainter &painter)
void propertyDragStart(QMouseEvent *ev)
virtual void enterEvent(QEvent *ev) override
virtual std::vector< SelectionIndex > elementsIntersecting(QRect r) override
Find list of elements which intersect a rectangular area.
Pattern Editor Panel.
const QScrollArea * getNotePanScrollArea() const
const QScrollArea * getNoteLeadLagScrollArea() const
const QScrollArea * getNoteNoteKeyScrollArea() const
const QScrollArea * getNoteVelocityScrollArea() const
const QScrollArea * getNoteProbabilityScrollArea() const
static QString modeToQString(Mode mode)
virtual void selectInstrumentNotes(int nInstrument)
virtual void mouseMoveEvent(QMouseEvent *ev) override
static QColor computeNoteColor(float velocity)
Calculate colour to use for note representation based on note velocity.
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
virtual void selectNone()
QColor selectedNoteColor() const
Colour to use for outlining selected notes.
static void triggerStatusMessage(H2Core::Note *pNote, Mode mode)
static constexpr int nMargin
PatternEditorPanel * m_pPatternEditorPanel
QMenu * m_pPopupMenu
QPixmap * m_pBackgroundPixmap
PatternEditor(QWidget *pParent, PatternEditorPanel *panel)
int getColumn(int x, bool bUseFineGrained=false) const
H2Core::Pattern * m_pPattern
void invalidateBackground()
void updateWidth()
Adjusts m_nActiveWidth and m_nEditorWidth to the current state of the editor.
Selection< SelectionIndex > m_selection
The Selection object.
bool m_bBackgroundInvalid
void drawGridLines(QPainter &p, Qt::PenStyle style=Qt::SolidLine) const
Draw lines for note grid.
static void setPlayheadPen(QPainter *p, bool bHovered=false)
Definition Skin.cpp:190
static int getPlayheadShaftOffset()
Definition Skin.h:79
Compatibility class to support QWheelEvent more esily in Qt5 and Qt6.
Definition WheelEvent.h:35
constexpr int getPointSize(H2Core::FontTheme::FontSize fontSize) const
#define MAX_INSTRUMENTS
Maximum number of instruments allowed in Hydrogen.
Definition config.dox:70
#define MAX_NOTES
Maximum number of notes.
Definition config.dox:79
#define UNUSED(v)
Definition Globals.h:42