46 QScrollArea *pScrollView)
55 setAttribute(Qt::WA_OpaquePaintEvent);
89 if ( !bPatternOnly ) {
140 qreal pixelRatio = devicePixelRatio();
145 QPainter painter(
this );
149 painter.drawPixmap( ev->rect(), *
m_pTemp,
150 QRectF( pixelRatio * ev->rect().x(),
151 pixelRatio * ev->rect().y(),
152 pixelRatio * ev->rect().width(),
153 pixelRatio * ev->rect().height() ) );
163 painter.drawLine( nX, 2, nX, height() - 2 );
174 QPen pen( pPref->getColorTheme()->m_cursorColor );
176 painter.setPen( pen );
177 painter.setBrush( Qt::NoBrush );
178 painter.setRenderHint( QPainter::Antialiasing );
191 QColor color = pPref->getColorTheme()->m_highlightColor;
196 if ( ! hasFocus() ) {
197 color.setAlpha( 125 );
203 int nEndX = std::min( nStartX +
HydrogenApp::get_instance()->getPatternEditorPanel()->getPianoRollEditorScrollArea()->viewport()->size().width(), width() );
207 painter.setPen( pen );
208 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nEndX, nStartY ) );
209 painter.drawLine( QPoint( nStartX, nStartY ), QPoint( nStartX, nEndY ) );
210 painter.drawLine( QPoint( nEndX, nStartY ), QPoint( nEndX, nEndY ) );
211 painter.drawLine( QPoint( nEndX, nEndY ), QPoint( nStartX, nEndY ) );
218 const QColor backgroundColor = pPref->getColorTheme()->m_patternEditor_backgroundColor;
219 const QColor backgroundInactiveColor = pPref->getColorTheme()->m_windowColor;
220 const QColor alternateRowColor = pPref->getColorTheme()->m_patternEditor_alternateRowColor;
221 const QColor octaveColor = pPref->getColorTheme()->m_patternEditor_octaveRowColor;
224 const QColor baseNoteColor = octaveColor.lighter( 119 );
225 const QColor lineColor( pPref->getColorTheme()->m_patternEditor_lineColor );
226 const QColor lineInactiveColor( pPref->getColorTheme()->m_windowTextColor.darker( 170 ) );
228 unsigned start_x = 0;
232 qreal pixelRatio = devicePixelRatio();
240 m_pTemp =
new QPixmap( width() * pixelRatio , height() * pixelRatio );
241 m_pTemp->setDevicePixelRatio( pixelRatio );
248 for ( uint ooctave = 0; ooctave <
m_nOctaves; ++ooctave ) {
251 for (
int ii = 0; ii < 12; ++ii ) {
252 if ( ii == 0 || ii == 2 || ii == 4 || ii == 6 || ii == 7 ||
253 ii == 9 || ii == 11 ) {
254 if ( ooctave % 2 != 0 ) {
271 if ( ooctave == 3 ) {
280 p.setPen( lineColor );
281 for ( uint row = 0; row < ( 12 *
m_nOctaves ); ++row ) {
283 p.drawLine( start_x, y, end_x, y );
287 p.setPen( lineInactiveColor );
288 for ( uint row = 0; row < ( 12 *
m_nOctaves ); ++row ) {
295 QFont font( pPref->getApplicationFontFamily(),
getPointSize( pPref->getFontSize() ) );
297 p.setPen( pPref->getColorTheme()->m_patternEditor_textColor );
301 for (
int oct = 0; oct < (int)
m_nOctaves; oct++ ){
337 p.setPen( QPen( lineColor, 2, Qt::SolidLine ) );
350 qreal pixelRatio = devicePixelRatio();
355 QRectF( pixelRatio * rect().x(),
356 pixelRatio * rect().y(),
357 pixelRatio * rect().width(),
358 pixelRatio * rect().height() ) );
362 bool bIsForeground = ( pPattern ==
m_pPattern );
365 Note *note = it->second;
367 drawNote( note, &p, bIsForeground );
386 int nNotekey,
int nOctave,
387 bool bDoAdd,
bool bDoDelete )
399 if ( pSelectedInstrument ==
nullptr ) {
400 DEBUGLOG(
"No instrument selected" );
404 Note* pOldNote =
m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument,
408 float fVelocity = 0.8f;
410 float fLeadLag = 0.0f;
411 float fProbability = 1.0f;
413 if ( pOldNote && !bDoDelete ) {
416 }
else if ( !pOldNote && !bDoAdd ) {
424 fPan = pOldNote->
getPan();
431 if ( pOldNote ==
nullptr ) {
435 Note *pNote2 =
new Note( pSelectedInstrument );
444 nSelectedInstrumentnumber,
452 pOldNote !=
nullptr );
469 int nPressedLine = ((int) pEv->position().y()) / ((int)
m_nGridHeight);
470 if ( nPressedLine >= (
int)
m_nOctaves * 12 ) {
474 int nColumn =
getColumn( pEv->position().x(),
true );
476 if ( nColumn >= (
int)
m_pPattern->get_length() ) {
477 update( 0, 0, width(), height() );
485 if ( pSelectedInstrument ==
nullptr ) {
486 ERRORLOG(
"No instrument selected" );
495 if (ev->button() == Qt::LeftButton ) {
497 unsigned nRealColumn = 0;
502 if ( ev->modifiers() & Qt::ShiftModifier ) {
503 H2Core::Note *pNote =
m_pPattern->find_note( nColumn, nRealColumn, pSelectedInstrument, pressednotekey, pressedoctave );
504 if ( pNote !=
nullptr ) {
508 nSelectedInstrumentnumber,
517 pHydrogenApp->m_pUndoStack->push( action );
520 pHydrogenApp->m_pUndoStack->push( action );
525 addOrRemoveNote( nColumn, nRealColumn, nPressedLine, pressednotekey, pressedoctave );
527 }
else if ( ev->button() == Qt::RightButton ) {
548 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
549 pHydrogenApp->setHideKeyboardCursor(
true );
552 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
559 if ( ! pHydrogenApp->hideKeyboardCursor() ) {
560 int nPressedLine = ((int) pEv->position().y()) / ((int)
m_nGridHeight);
561 if ( nPressedLine >= (
int)
m_nOctaves * 12 ) {
566 int nColumn =
getColumn( pEv->position().x(),
true );
594 int nColumn =
getColumn( pEv->position().x() );
596 if ( pSelectedInstrument ==
nullptr ) {
597 DEBUGLOG(
"No instrument selected" );
601 int nRow = std::floor(
static_cast<float>(pEv->position().y()) /
608 if (ev->button() == Qt::RightButton ) {
613 static_cast<int>(std::floor(
621 pressedOctave,
false );
634 int nRow = std::floor(
static_cast<float>(pEv->position().y()) /
645 int selectedPatternNumber,
646 int selectedinstrument,
662 std::shared_ptr<Song> pSong = pHydrogen->
getSong();
665 auto pSelectedInstrument = pSong->getInstrumentList()->get( selectedinstrument );
666 if ( pSelectedInstrument ==
nullptr ) {
667 ERRORLOG( QString(
"Instrument [%1] could not be found" )
668 .arg( selectedinstrument ) );
673 if ( ( selectedPatternNumber != -1 ) && ( (uint)selectedPatternNumber < pPatternList->size() ) ) {
674 pPattern = pPatternList->
get( selectedPatternNumber );
683 Note* note =
m_pPattern->find_note( nColumn, -1, pSelectedInstrument, pressednotekey, pressedoctave );
689 ERRORLOG(
"Could not find note to delete" );
693 unsigned nPosition = nColumn;
694 float fVelocity = oldVelocity;
695 float fPan = fOldPan;
696 int nLength = oldLength;
704 if ( pPattern !=
nullptr ) {
705 Note *pNote =
new Note( pSelectedInstrument, nPosition, fVelocity, fPan, nLength );
736 std::shared_ptr<Song> pSong = pHydrogen->
getSong();
739 PatternList *pPatternList = pSong->getPatternList();
740 Note *pFoundNote =
nullptr;
742 if ( nPattern < 0 || nPattern > pPatternList->
size() ) {
743 ERRORLOG(
"Invalid pattern number" );
748 Pattern *pPattern = pPatternList->
get( nPattern );
751 Note *pCandidateNote = it->second;
754 && pCandidateNote->
get_key() == key
759 pFoundNote = pCandidateNote;
766 if ( pFoundNote ==
nullptr ) {
767 ERRORLOG(
"Couldn't find note to move" );
810 if ( pSelectedInstrument ==
nullptr ) {
811 DEBUGLOG(
"No instrument selected" );
816 std::list< QUndoCommand * > actions;
819 if ( pNote->get_instrument() == pSelectedInstrument ) {
820 int nLine =
pitchToLine( pNote->get_notekey_pitch() );
824 nSelectedInstrumentNumber,
826 pNote->get_velocity(),
828 pNote->get_lead_lag(),
831 pNote->get_probability(),
838 pUndo->beginMacro(
"delete notes");
839 for ( QUndoCommand * pAction : actions ) {
840 pUndo->push( pAction );
859 QClipboard *clipboard = QApplication::clipboard();
864 int nDeltaPos = 0, nDeltaPitch = 0;
868 if ( ! doc.setContent( clipboard->text() ) ) {
873 XMLNode selection = doc.firstChildElement(
"noteSelection" );
874 if ( ! selection.isNull() ) {
880 noteList = selection.firstChildElement(
"noteList" );
881 if ( noteList.isNull() ) {
885 XMLNode positionNode = selection.firstChildElement(
"sourcePosition" );
890 if ( !positionNode.isNull() ) {
893 nDeltaPos = nCurrentPos -
894 positionNode.
read_int(
"minColumn", nCurrentPos );
900 XMLNode instrumentLine = doc.firstChildElement(
"instrument_line" );
901 if ( ! instrumentLine.isNull() ) {
908 XMLNode patternList = instrumentLine.firstChildElement(
"patternList" );
909 if ( patternList.isNull() ) {
912 XMLNode pattern = patternList.firstChildElement(
"pattern" );
913 if ( pattern.isNull() ) {
917 if ( ! pattern.nextSiblingElement(
"pattern" ).isNull() ) {
918 QMessageBox::information(
this,
"Hydrogen", tr(
"Cannot paste multi-pattern selection" ) );
921 noteList = pattern.firstChildElement(
"noteList" );
922 if ( noteList.isNull() ) {
931 if ( noteList.hasChildNodes() ) {
933 pUndo->beginMacro(
"paste notes" );
934 for (
XMLNode n = noteList.firstChildElement(
"note" ); ! n.isNull(); n = n.nextSiblingElement() ) {
939 if ( nPos >= 0 && nPos < m_pPattern->get_length() && nPitch >= 12 *
OCTAVE_MIN && nPitch < 12 * (
OCTAVE_MAX+1) ) {
970 bool bOldCursorHidden = pHydrogenApp->hideKeyboardCursor();
972 const int nBlockSize = 5, nWordSize = 5;
973 bool bIsSelectionKey =
m_selection.keyPressEvent( ev );
974 bool bUnhideCursor =
true;
977 if ( bIsSelectionKey ) {
979 }
else if ( ev->matches( QKeySequence::MoveToNextChar ) || ev->matches( QKeySequence::SelectNextChar ) ) {
983 }
else if ( ev->matches( QKeySequence::MoveToNextWord ) || ev->matches( QKeySequence::SelectNextWord ) ) {
987 }
else if ( ev->matches( QKeySequence::MoveToEndOfLine ) || ev->matches( QKeySequence::SelectEndOfLine ) ) {
991 }
else if ( ev->matches( QKeySequence::MoveToPreviousChar ) || ev->matches( QKeySequence::SelectPreviousChar ) ) {
995 }
else if ( ev->matches( QKeySequence::MoveToPreviousWord ) || ev->matches( QKeySequence::SelectPreviousWord ) ) {
999 }
else if ( ev->matches( QKeySequence::MoveToStartOfLine ) || ev->matches( QKeySequence::SelectStartOfLine ) ) {
1003 }
else if ( ev->matches( QKeySequence::MoveToNextLine ) || ev->matches( QKeySequence::SelectNextLine ) ) {
1008 }
else if ( ev->matches( QKeySequence::MoveToEndOfBlock ) || ev->matches( QKeySequence::SelectEndOfBlock ) ) {
1012 }
else if ( ev->matches( QKeySequence::MoveToNextPage ) || ev->matches( QKeySequence::SelectNextPage ) ) {
1020 }
else if ( ev->matches( QKeySequence::MoveToEndOfDocument ) || ev->matches( QKeySequence::SelectEndOfDocument ) ) {
1023 }
else if ( ev->matches( QKeySequence::MoveToPreviousLine ) || ev->matches( QKeySequence::SelectPreviousLine ) ) {
1028 }
else if ( ev->matches( QKeySequence::MoveToStartOfBlock ) || ev->matches( QKeySequence::SelectStartOfBlock ) ) {
1032 }
else if ( ev->matches( QKeySequence::MoveToPreviousPage ) || ev->matches( QKeySequence::SelectPreviousPage ) ) {
1039 }
else if ( ev->matches( QKeySequence::MoveToStartOfDocument ) || ev->matches( QKeySequence::SelectStartOfDocument ) ) {
1042 }
else if ( ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return ) {
1049 }
else if ( ev->matches( QKeySequence::SelectAll ) ) {
1051 bUnhideCursor =
false;
1054 }
else if ( ev->matches( QKeySequence::Deselect ) ) {
1056 bUnhideCursor =
false;
1059 }
else if ( ev->key() == Qt::Key_Delete ) {
1061 bUnhideCursor =
false;
1073 }
else if ( ev->matches( QKeySequence::Copy ) ) {
1074 bUnhideCursor =
false;
1077 }
else if ( ev->matches( QKeySequence::Paste ) ) {
1078 bUnhideCursor =
false;
1081 }
else if ( ev->matches( QKeySequence::Cut ) ) {
1082 bUnhideCursor =
false;
1087 pHydrogenApp->setHideKeyboardCursor(
true );
1089 if ( bOldCursorHidden != pHydrogenApp->hideKeyboardCursor() ) {
1098 if ( bUnhideCursor ) {
1119 if ( offset.x() == 0 && offset.y() == 0 ) {
1130 if (
m_pPattern ==
nullptr || nSelectedPatternNumber == -1 ) {
1138 pUndo->beginMacro(
"copy notes" );
1140 pUndo->beginMacro(
"move notes" );
1142 std::list< Note * > selectedNotes;
1144 selectedNotes.push_back( pNote );
1154 for (
auto pNote : selectedNotes ) {
1155 int nPosition = pNote->get_position();
1156 int nNewPosition = nPosition + offset.x();
1161 int nNewPitch = pNote->get_notekey_pitch() - offset.y();
1165 bool bNoteInRange = ( newOctave >=
OCTAVE_MIN && newOctave <= OCTAVE_MAX && nNewPosition >= 0
1166 && nNewPosition <
m_pPattern->get_length() );
1169 if ( bNoteInRange ) {
1172 nSelectedPatternNumber,
1173 nSelectedInstrumentNumber,
1174 pNote->get_length(),
1175 pNote->get_velocity(),
1177 pNote->get_lead_lag(),
1180 pNote->get_probability(),
1184 if ( bNoteInRange ) {
1185 pUndo->push(
new SE_moveNotePianoRollAction( nPosition, octave, key, nSelectedPatternNumber, nNewPosition, newOctave, newKey, pNote ) );
1189 nSelectedPatternNumber,
1190 nSelectedInstrumentNumber,
1191 pNote->get_length(),
1192 pNote->get_velocity(),
1194 pNote->get_lead_lag(),
1197 pNote->get_probability(),
1209 std::vector<SelectionIndex> result;
1211 return std::move( result );
1217 if ( pSelectedInstrument ==
nullptr ) {
1218 DEBUGLOG(
"No instrument selected" );
1219 return std::move( result );
1223 if ( r.top() == r.bottom() && r.left() == r.right() ) {
1224 r += QMargins( 2, 2, 2, 2 );
1233 for (
auto it = pNotes->lower_bound( x_min ); it != pNotes->end() && it->first <= x_max; ++it ) {
1234 Note *pNote = it->second;
1239 if ( r.intersects( QRect( start_x -4 , start_y, w, h ) ) ) {
1240 result.push_back( pNote );
1245 return std::move( result );
1263 return QRect( pos.x() - fHalfWidth, pos.y()-2,
#define RIGHT_HERE
Macro intended to be used for the logging of the locking of the H2Core::AudioEngine.
#define FOREACH_NOTE_IT_BOUND_END(_notes, _it, _bound)
Iterate over all notes in column _bound in a mutable way.
#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.
std::shared_ptr< Song > getSong() const
Get the current song.
int getSelectedInstrumentNumber() const
int getSelectedPatternNumber() const
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
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.
int get_position() const
__position accessor
static int octaveKeyToPitch(Octave octave, Key key)
static Key pitchToKey(int nPitch)
void set_lead_lag(float value)
__lead_lag setter
void set_key_octave(const QString &str)
parse str and set __key and __octave
static Octave pitchToOctave(int nPitch)
static Note * load_from(XMLNode *node, std::shared_ptr< InstrumentList > instruments, bool bSilent=false)
load a note from an XMLNode
Octave get_octave()
__octave accessor
void set_probability(float value)
void set_position(int value)
__position setter
std::shared_ptr< Instrument > get_instrument()
__instrument accessor
float get_lead_lag() const
__lead_lag accessor
int get_length() const
__length accessor
float get_notekey_pitch() const
note key pitch accessor
float get_velocity() const
__velocity accessor
bool get_note_off() const
__note_off accessor
void set_note_off(bool value)
__note_off setter
Key get_key()
__key accessor
float get_probability() const
float getPan() const
get pan of the note.
PatternList is a collection of patterns.
int size() const
returns the numbers of patterns
Pattern * get(int idx)
get a pattern from the list
Pattern class is a Note container.
void remove_note(Note *note)
removes a given note from __notes, it's not deleted
const notes_t * get_notes() const
get the virtual pattern set
std::multimap< int, Note * > notes_t
< multimap note type
void insert_note(Note *note)
insert a new note within __notes
Manager for User Preferences File (singleton)
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.
XMLDoc is a subclass of QDomDocument with read and write methods.
XMLNode is a subclass of QDomNode with read and write values methods.
int read_int(const QString &node, int default_value, bool inexistent_ok=true, bool empty_ok=true, bool bSilent=false)
reads an integer stored into a child node
void setHideKeyboardCursor(bool bHidden)
void addEventListener(EventListener *pListener)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
QUndoStack * m_pUndoStack
PatternEditorPanel * getPatternEditorPanel()
Compatibility class to support QMouseEvent more esily in Qt5 and Qt6.
const QScrollArea * getPianoRollEditorScrollArea() const
virtual void selectInstrumentNotes(int nInstrument)
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
int lineToPitch(int nLine)
virtual void validateSelection() override
Ensure that the Selection contains only valid elements.
int m_nSelectedPatternNumber
virtual void mousePressEvent(QMouseEvent *ev) override
Raw Qt mouse events are passed to the Selection.
virtual void updateModifiers(QInputEvent *ev)
Update the status of modifier keys in response to input events.
virtual void selectNone()
int pitchToLine(int nPitch)
void storeNoteProperties(const H2Core::Note *pNote)
Stores the properties of pNote in member variables.
void drawNoteSymbol(QPainter &p, QPoint pos, H2Core::Note *pNote, bool bIsForeground=true) const
Draw a note.
static constexpr int nMargin
PatternEditorPanel * m_pPatternEditorPanel
H2Core::AudioEngine * m_pAudioEngine
H2Core::Note * m_pDraggedNote
void updatePatternInfo()
Update current pattern information.
virtual void mouseDragStartEvent(QMouseEvent *ev) override
virtual void copy()
Copy selection to clipboard in XML.
std::vector< H2Core::Pattern * > getPatternsToShow(void)
Get notes to show in pattern editor.
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.
QPoint movingGridOffset() const
bool m_bEntered
Indicates whether the mouse pointer entered the widget.
bool m_bBackgroundInvalid
void drawGridLines(QPainter &p, Qt::PenStyle style=Qt::SolidLine) const
Draw lines for note grid.
virtual std::vector< SelectionIndex > elementsIntersecting(QRect r) override
Selections are indexed by Note pointers.
virtual void mouseDragUpdateEvent(QMouseEvent *ev) override
void moveNoteAction(int nColumn, H2Core::Note::Octave octave, H2Core::Note::Key key, int nPattern, int nNewColumn, H2Core::Note::Octave newOctave, H2Core::Note::Key newKey, H2Core::Note *pNote)
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
Position of keyboard input cursor on screen.
void addOrDeleteNoteAction(int nColumn, int pressedLine, int selectedPatternNumber, int selectedinstrument, int oldLength, float oldVelocity, float fOldPan, float oldLeadLag, int oldNoteKeyVal, int oldOctaveKeyVal, float fProbability, bool noteOff, bool isDelete)
void addOrRemoveNote(int nColumn, int nRealColumn, int nLine, int nNotekey, int nOctave, bool bDoAdd=true, bool bDoDelete=true)
virtual void selectAll() override
void onPreferencesChanged(H2Core::Preferences::Changes changes)
virtual void songModeActivationEvent() override
virtual void keyPressEvent(QKeyEvent *ev) override
void finishUpdateEditor()
PianoRollEditor(QWidget *pParent, PatternEditorPanel *panel, QScrollArea *pScrollView)
QScrollArea * m_pScrollView
virtual void paste() override
Paste selection.
virtual void mouseDragStartEvent(QMouseEvent *ev) override
void createBackground() override
Updates m_pBackgroundPixmap to show the latest content.
virtual void selectedInstrumentChangedEvent() override
void drawNote(H2Core::Note *pNote, QPainter *pPainter, bool bIsForeground)
Draw a note.
bool m_bNeedsBackgroundUpdate
virtual void mouseClickEvent(QMouseEvent *ev) override
virtual void paintEvent(QPaintEvent *ev) override
virtual void deleteSelection() override
virtual void selectedPatternChangedEvent() override
void drawFocus(QPainter &painter)
static void setPlayheadPen(QPainter *p, bool bHovered=false)
static int getPlayheadShaftOffset()
#define MAX_INSTRUMENTS
Maximum number of instruments allowed in Hydrogen.
#define MAX_NOTES
Maximum number of notes.