From 2acfa44246f0455acd8fa2088a9e3c3be3974b7b Mon Sep 17 00:00:00 2001 From: WeakOstra Date: Thu, 8 Jan 2026 13:23:33 +0100 Subject: [PATCH 1/3] Fixed scrolling notes to adjust their volume doesn't reflect in Pattern editor #7806 --- src/gui/editors/PianoRoll.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index a0e1ac4cf45..9c37e56d457 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -4087,6 +4087,8 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) // same volume showVolTextFloat(nv[0]->getVolume(), pos, 1000); } + // Emit MIDI clip has changed + m_midiClip->dataChanged(); } else if( m_noteEditMode == NoteEditMode::Panning ) { From ba9973538c6d6a3effd0a216429a69d48ffdbdb3 Mon Sep 17 00:00:00 2001 From: WeakOstra Date: Wed, 18 Mar 2026 11:45:56 +0100 Subject: [PATCH 2/3] =?UTF-8?q?A=C3=B1ade=20nueva=20funcionalidad=20Pianor?= =?UTF-8?q?ollpainter=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/PianoRoll.h | 10 +- include/PianoRollConstants.h | 47 ++ include/PianoRollPainter.h | 36 ++ src/gui/CMakeLists.txt | 2 +- src/gui/editors/PianoRoll.cpp | 747 +-------------------------- src/gui/editors/PianoRollPainter.cpp | 647 +++++++++++++++++++++++ 6 files changed, 740 insertions(+), 749 deletions(-) create mode 100644 include/PianoRollConstants.h create mode 100644 include/PianoRollPainter.h create mode 100644 src/gui/editors/PianoRollPainter.cpp diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 7b316b46dce..6c105f8fb31 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -56,6 +56,7 @@ namespace gui { class ComboBox; +class PianoRollPainter; class PositionLine; class SimpleTextFloat; class TimeLineWidget; @@ -500,13 +501,14 @@ protected slots: int m_strumCurrentVertical = 0; float m_strumHeightRatio = 0.0f; bool m_strumEnabled = false; - //! Handles updating all of the note positions when performing a strum - void updateStrumPos(QMouseEvent* me, bool initial, bool warp); + //! Handles updating all of the note positions when performing a strum + void updateStrumPos(QMouseEvent* me, bool initial, bool warp); - friend class PianoRollWindow; + friend class PianoRollPainter; + friend class PianoRollWindow; - StepRecorderWidget m_stepRecorderWidget; + StepRecorderWidget m_stepRecorderWidget; StepRecorder m_stepRecorder; // qproperty fields diff --git a/include/PianoRollConstants.h b/include/PianoRollConstants.h new file mode 100644 index 00000000000..f44510229d3 --- /dev/null +++ b/include/PianoRollConstants.h @@ -0,0 +1,47 @@ +/* + * PianoRollConstants.h - shared constants for PianoRoll rendering and layout + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + */ + +#ifndef LMMS_GUI_PIANO_ROLL_CONSTANTS_H +#define LMMS_GUI_PIANO_ROLL_CONSTANTS_H + +namespace lmms::gui +{ + +inline constexpr int INITIAL_PIANOROLL_WIDTH = 970; +inline constexpr int INITIAL_PIANOROLL_HEIGHT = 485; + +inline constexpr int SCROLLBAR_SIZE = 12; +inline constexpr int PIANO_X = 0; + +inline constexpr int WHITE_KEY_WIDTH = 64; +inline constexpr int BLACK_KEY_WIDTH = 41; + +inline constexpr int DEFAULT_KEY_LINE_HEIGHT = 12; +inline constexpr int DEFAULT_CELL_WIDTH = 12; + +inline constexpr int NOTE_EDIT_RESIZE_BAR = 6; +inline constexpr int NOTE_EDIT_MIN_HEIGHT = 50; +inline constexpr int KEY_AREA_MIN_HEIGHT = DEFAULT_KEY_LINE_HEIGHT * 10; +inline constexpr int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE; +inline constexpr int PR_TOP_MARGIN = 18; +inline constexpr int PR_RIGHT_MARGIN = SCROLLBAR_SIZE; + +inline constexpr int RESIZE_AREA_WIDTH = 9; +inline constexpr int NOTE_EDIT_LINE_WIDTH = 3; + +inline constexpr int NUM_EVEN_LENGTHS = 6; +inline constexpr int NUM_TRIPLET_LENGTHS = 5; +inline constexpr int DETUNING_HANDLE_RADIUS = 3; + +} // namespace lmms::gui + +#endif // LMMS_GUI_PIANO_ROLL_CONSTANTS_H diff --git a/include/PianoRollPainter.h b/include/PianoRollPainter.h new file mode 100644 index 00000000000..c84bd84f101 --- /dev/null +++ b/include/PianoRollPainter.h @@ -0,0 +1,36 @@ +/* + * PianoRollPainter.h - rendering helper for PianoRoll + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + */ + +#ifndef LMMS_GUI_PIANO_ROLL_PAINTER_H +#define LMMS_GUI_PIANO_ROLL_PAINTER_H + +class QPaintEvent; + +namespace lmms::gui +{ + +class PianoRoll; + +class PianoRollPainter +{ +public: + explicit PianoRollPainter(PianoRoll& pianoRoll); + + void paint(QPaintEvent* event); + +private: + PianoRoll& m_pianoRoll; +}; + +} // namespace lmms::gui + +#endif // LMMS_GUI_PIANO_ROLL_PAINTER_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 061d05eb407..3d2b995735c 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -54,6 +54,7 @@ SET(LMMS_SRCS gui/editors/Editor.cpp gui/editors/PatternEditor.cpp gui/editors/PianoRoll.cpp + gui/editors/PianoRollPainter.cpp gui/editors/PositionLine.cpp gui/editors/Rubberband.cpp gui/editors/SongEditor.cpp @@ -133,4 +134,3 @@ SET(LMMS_SRCS PARENT_SCOPE ) - diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 2b40d0af989..2e9ae1893db 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -25,6 +25,7 @@ */ #include "PianoRoll.h" +#include "PianoRollConstants.h" #include // IWYU pragma: keep #include @@ -64,6 +65,7 @@ #include "MidiClip.h" #include "PatternStore.h" #include "PianoView.h" +#include "PianoRollPainter.h" #include "PositionLine.h" #include "SimpleTextFloat.h" #include "SongEditor.h" @@ -83,44 +85,9 @@ using timeMap = AutomationClip::timeMap; namespace gui { -// some constants... -const int INITIAL_PIANOROLL_WIDTH = 970; -const int INITIAL_PIANOROLL_HEIGHT = 485; - -const int SCROLLBAR_SIZE = 12; -const int PIANO_X = 0; - -const int WHITE_KEY_WIDTH = 64; -const int BLACK_KEY_WIDTH = 41; - -const int DEFAULT_KEY_LINE_HEIGHT = 12; -const int DEFAULT_CELL_WIDTH = 12; - - -const int NOTE_EDIT_RESIZE_BAR = 6; -const int NOTE_EDIT_MIN_HEIGHT = 50; -const int KEY_AREA_MIN_HEIGHT = DEFAULT_KEY_LINE_HEIGHT * 10; -const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE; -const int PR_TOP_MARGIN = 18; -const int PR_RIGHT_MARGIN = SCROLLBAR_SIZE; - - -// width of area used for resizing (the grip at the end of a note) -const int RESIZE_AREA_WIDTH = 9; - -// width of line for setting volume/panning of note -const int NOTE_EDIT_LINE_WIDTH = 3; - // key where to start const int INITIAL_START_KEY = Octave::Octave_4 + Key::C; -// number of each note to provide in quantization and note lengths -const int NUM_EVEN_LENGTHS = 6; -const int NUM_TRIPLET_LENGTHS = 5; - -// Radius of the automation node circles which appear when pitchbending a note -const int DETUNING_HANDLE_RADIUS = 3; - SimpleTextFloat * PianoRoll::s_textFloat = nullptr; static std::array s_noteStrings { @@ -3282,715 +3249,7 @@ void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl) void PianoRoll::paintEvent(QPaintEvent * pe ) { - bool drawNoteNames = ConfigManager::inst()->value( "ui", "printnotelabels").toInt(); - - QStyleOption opt; - opt.initFrom( this ); - QPainter p( this ); - style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); - - QBrush bgColor = p.background(); - - // fill with bg color - p.fillRect( 0, 0, width(), height(), bgColor ); - - if (!hasValidMidiClip()) - { - const auto icon = embed::getIconPixmap("pr_no_clip"); - const int x = (width() - icon.width()) / 2; - const int y = (height() - icon.height()) / 2; - p.drawPixmap(x, y, icon); - - p.setPen(QApplication::palette().color(QPalette::Active, QPalette::Text)); - QRect textRect(0, y + icon.height() + 5, width(), 30); - p.drawText(textRect, Qt::AlignHCenter | Qt::AlignTop, - tr("Double-click on an instrument clip in Song Editor to open it here")); - return; - } - - // set font-size to 80% of key line height - QFont f = p.font(); - int keyFontSize = m_keyLineHeight * 0.8; - p.setFont(adjustedToPixelSize(f, keyFontSize)); - QFontMetrics fontMetrics(p.font()); - // G-1 is one of the widest; plus one pixel margin for the shadow - QRect const boundingRect = fontMetrics.boundingRect(QString("G-1")) + QMargins(0, 0, 1, 0); - - auto xCoordOfTick = [this](int tick) { - return m_whiteKeyWidth + ( - (tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar() - ); - }; - - // Order of drawing - // - vertical quantization lines - // - piano roll + horizontal key lines - // - alternating bar colors - // - vertical beat lines - // - vertical bar lines - // - marked semitones - // - note editing - // - notes - // - selection frame - // - highlight hovered note - // - note edit area resize bar - // - cursor mode icon - - if (hasValidMidiClip()) - { - int pianoAreaHeight = keyAreaBottom() - keyAreaTop(); - m_pianoKeysVisible = pianoAreaHeight / m_keyLineHeight; - int partialKeyVisible = pianoAreaHeight % m_keyLineHeight; - // check if we're below the minimum key area size - if (m_pianoKeysVisible * m_keyLineHeight < KEY_AREA_MIN_HEIGHT) - { - m_pianoKeysVisible = KEY_AREA_MIN_HEIGHT / m_keyLineHeight; - partialKeyVisible = KEY_AREA_MIN_HEIGHT % m_keyLineHeight; - // if we have a partial key, just show it - if (partialKeyVisible > 0) - { - m_pianoKeysVisible += 1; - partialKeyVisible = 0; - } - // have to modify the notes edit area height instead - m_notesEditHeight = height() - (m_pianoKeysVisible * m_keyLineHeight) - - PR_TOP_MARGIN - PR_BOTTOM_MARGIN; - } - // check if we're trying to show more keys than available - else if (m_pianoKeysVisible >= NumKeys) - { - m_pianoKeysVisible = NumKeys; - // have to modify the notes edit area height instead - m_notesEditHeight = height() - (NumKeys * m_keyLineHeight) - - PR_TOP_MARGIN - PR_BOTTOM_MARGIN; - partialKeyVisible = 0; - } - int topKey = std::clamp(m_startKey + m_pianoKeysVisible - 1, 0, NumKeys - 1); - int topNote = topKey % KeysPerOctave; - // if not resizing the note edit area, we can change m_notesEditHeight - if (m_action != Action::ResizeNoteEditArea && partialKeyVisible != 0) - { - // calculate the height change adding and subtracting the partial key - int noteAreaPlus = (m_notesEditHeight + partialKeyVisible) - m_userSetNotesEditHeight; - int noteAreaMinus = m_userSetNotesEditHeight - (m_notesEditHeight - partialKeyVisible); - // if adding the partial key to height is more distant from the set height - // we want to subtract the partial key - if (noteAreaPlus > noteAreaMinus) - { - m_notesEditHeight -= partialKeyVisible; - // since we're adding a partial key, we add one to the number visible - m_pianoKeysVisible += 1; - } - // otherwise we add height - else { m_notesEditHeight += partialKeyVisible; } - } - int x, q = quantization(), tick; - - // draw vertical quantization lines - // If we're over 100% zoom, we allow all quantization level grids - if (m_zoomingModel.value() <= 3) - { - // we're under 100% zoom - // allow quantization grid up to 1/24 for triplets - if (q % 3 != 0 && q < 8) { q = 8; } - // allow quantization grid up to 1/32 for normal notes - else if (q < 6) { q = 6; } - } - - p.setPen(m_lineColor); - for (tick = m_currentPosition - m_currentPosition % q, - x = xCoordOfTick(tick); - x <= width(); - tick += q, x = xCoordOfTick(tick)) - { - p.drawLine(x, keyAreaTop(), x, noteEditBottom()); - } - - // draw horizontal grid lines and piano notes - p.setClipRect(0, keyAreaTop(), width(), keyAreaBottom() - keyAreaTop()); - // the first grid line from the top Y position - int grid_line_y = keyAreaTop() + m_keyLineHeight - 1; - - // lambda function for returning the height of a key - auto keyHeight = [&]( - const int key - ) -> int - { - switch (prKeyOrder[key % KeysPerOctave]) - { - case KeyType::WhiteBig: - return m_whiteKeyBigHeight; - case KeyType::WhiteSmall: - return m_whiteKeySmallHeight; - case KeyType::Black: - return m_blackKeyHeight; - } - return 0; // should never happen - }; - // lambda function for returning the distance to the top of a key - auto gridCorrection = [&]( - const int key - ) -> int - { - const int keyCode = key % KeysPerOctave; - switch (prKeyOrder[keyCode]) - { - case KeyType::WhiteBig: - return m_whiteKeySmallHeight; - case KeyType::WhiteSmall: - // These two keys need to adjust up small height instead of only key line height - if (static_cast(keyCode) == Key::C || static_cast(keyCode) == Key::F) - { - return m_whiteKeySmallHeight; - } - case KeyType::Black: - return m_blackKeyHeight; - } - return 0; // should never happen - }; - auto keyWidth = [&]( - const int key - ) -> int - { - switch (prKeyOrder[key % KeysPerOctave]) - { - case KeyType::WhiteSmall: - case KeyType::WhiteBig: - return m_whiteKeyWidth; - case KeyType::Black: - return m_blackKeyWidth; - } - return 0; // should never happen - }; - // lambda function to draw a key - auto drawKey = [&]( - const int key, - const int yb) - { - const bool mapped = m_midiClip->instrumentTrack()->isKeyMapped(key); - const bool pressed = m_midiClip->instrumentTrack()->pianoModel()->isKeyPressed(key); - const int keyCode = key % KeysPerOctave; - const int yt = yb - gridCorrection(key); - const int kh = keyHeight(key); - const int kw = keyWidth(key); - // set key colors - p.setPen(QColor(0, 0, 0)); - switch (prKeyOrder[keyCode]) - { - case KeyType::WhiteSmall: - case KeyType::WhiteBig: - if (mapped) - { - if (pressed) { p.setBrush(m_whiteKeyActiveBackground); } - else { p.setBrush(m_whiteKeyInactiveBackground); } - } - else - { - p.setBrush(m_whiteKeyDisabledBackground); - } - break; - case KeyType::Black: - if (mapped) - { - if (pressed) { p.setBrush(m_blackKeyActiveBackground); } - else { p.setBrush(m_blackKeyInactiveBackground); } - } - else - { - p.setBrush(m_blackKeyDisabledBackground); - } - } - // draw key - p.drawRect(PIANO_X, yt, kw, kh); - // draw note name - if (static_cast(keyCode) == Key::C || (drawNoteNames && Piano::isWhiteKey(key))) - { - // small font sizes have 1 pixel offset instead of 2 - auto zoomOffset = m_zoomYLevels[m_zoomingYModel.value()] > 1.0f ? 2 : 1; - QString noteString = getNoteString(key); - QRect textRect( - m_whiteKeyWidth - boundingRect.width() - 2, - yb - m_keyLineHeight + zoomOffset, - boundingRect.width(), - boundingRect.height() - ); - p.setPen(pressed ? m_whiteKeyActiveTextShadow : m_whiteKeyInactiveTextShadow); - p.drawText(textRect.adjusted(0, 1, 1, 0), Qt::AlignRight | Qt::AlignHCenter, noteString); - p.setPen(pressed ? m_whiteKeyActiveTextColor : m_whiteKeyInactiveTextColor); - // if (static_cast(keyCode) == Key::C) { p.setPen(textColor()); } - // else { p.setPen(textColorLight()); } - p.drawText(textRect, Qt::AlignRight | Qt::AlignHCenter, noteString); - } - }; - // lambda for drawing the horizontal grid line - auto drawHorizontalLine = [&]( - const int key, - const int y - ) - { - if (static_cast(key % KeysPerOctave) == Key::C) { p.setPen(m_beatLineColor); } - else { p.setPen(m_lineColor); } - p.drawLine(m_whiteKeyWidth, y, width(), y); - }; - // correct y offset of the top key - switch (prKeyOrder[topNote]) - { - case KeyType::WhiteSmall: - case KeyType::WhiteBig: - break; - case KeyType::Black: - // draw extra white key - drawKey(topKey + 1, grid_line_y - m_keyLineHeight); - } - // loop through visible keys - const int lastKey = qMax(0, topKey - m_pianoKeysVisible); - for (int key = topKey; key > lastKey; --key) - { - bool whiteKey = Piano::isWhiteKey(key); - if (whiteKey) - { - drawKey(key, grid_line_y); - drawHorizontalLine(key, grid_line_y); - grid_line_y += m_keyLineHeight; - } - else - { - // draw next white key - drawKey(key - 1, grid_line_y + m_keyLineHeight); - drawHorizontalLine(key - 1, grid_line_y + m_keyLineHeight); - // draw black key over previous and next white key - drawKey(key, grid_line_y); - drawHorizontalLine(key, grid_line_y); - // drew two grid keys so skip ahead properly - grid_line_y += m_keyLineHeight + m_keyLineHeight; - // capture double key draw - --key; - } - } - - // don't draw over keys - p.setClipRect(m_whiteKeyWidth, keyAreaTop(), width(), noteEditBottom() - keyAreaTop()); - - // draw alternating shading on bars - float timeSignature = - static_cast(Engine::getSong()->getTimeSigModel().getNumerator()) / - static_cast(Engine::getSong()->getTimeSigModel().getDenominator()); - float zoomFactor = m_zoomLevels[m_zoomingModel.value()]; - //the bars which disappears at the left side by scrolling - int leftBars = m_currentPosition * zoomFactor / TimePos::ticksPerBar(); - //iterates the visible bars and draw the shading on uneven bars - for (int x = m_whiteKeyWidth, barCount = leftBars; - x < width() + m_currentPosition * zoomFactor / timeSignature; - x += m_ppb, ++barCount) - { - if ((barCount + leftBars) % 2 != 0) - { - p.fillRect(x - m_currentPosition * zoomFactor / timeSignature, - PR_TOP_MARGIN, - m_ppb, - height() - (PR_BOTTOM_MARGIN + PR_TOP_MARGIN), - m_backgroundShade); - } - } - - // draw vertical beat lines - int ticksPerBeat = DefaultTicksPerBar / - Engine::getSong()->getTimeSigModel().getDenominator(); - p.setPen(m_beatLineColor); - for(tick = m_currentPosition - m_currentPosition % ticksPerBeat, - x = xCoordOfTick( tick ); - x <= width(); - tick += ticksPerBeat, x = xCoordOfTick(tick)) - { - p.drawLine(x, PR_TOP_MARGIN, x, noteEditBottom()); - } - - // draw vertical bar lines - p.setPen(m_barLineColor); - for(tick = m_currentPosition - m_currentPosition % TimePos::ticksPerBar(), - x = xCoordOfTick( tick ); - x <= width(); - tick += TimePos::ticksPerBar(), x = xCoordOfTick(tick)) - { - p.drawLine(x, PR_TOP_MARGIN, x, noteEditBottom()); - } - - // draw marked semitones after the grid - for(x = 0; x < m_markedSemiTones.size(); ++x) - { - const int key_num = m_markedSemiTones.at(x); - const int y = yCoordOfKey(key_num); - if(y >= keyAreaBottom() - 1) { break; } - p.fillRect(m_whiteKeyWidth + 1, - y, - width() - 10, - m_keyLineHeight, - m_markedSemitoneColor); - } - } - - // reset MIDI clip - p.setClipRect(0, 0, width(), height()); - - // erase the area below the piano, because there might be keys that - // should be only half-visible - p.fillRect( QRect( 0, keyAreaBottom(), - m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), bgColor); - - // display note editing info - f.setBold(false); - p.setFont(adjustedToPixelSize(f, SMALL_FONT_SIZE)); - p.setPen(m_noteModeColor); - p.drawText( QRect( 0, keyAreaBottom(), - m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), - Qt::AlignCenter | Qt::TextWordWrap, - m_nemStr.at(static_cast(m_noteEditMode)) + ":" ); - - // set clipping area, because we are not allowed to paint over - // keyboard... - p.setClipRect( - m_whiteKeyWidth, - PR_TOP_MARGIN, - width() - m_whiteKeyWidth, - height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN); - - // following code draws all notes in visible area - // and the note editing stuff (volume, panning, etc) - - // setup selection-vars - int sel_pos_start = m_selectStartTick; - int sel_pos_end = m_selectStartTick+m_selectedTick; - if( sel_pos_start > sel_pos_end ) - { - qSwap( sel_pos_start, sel_pos_end ); - } - - int sel_key_start = m_selectStartKey - m_startKey + 1; - int sel_key_end = sel_key_start + m_selectedKeys; - if( sel_key_start > sel_key_end ) - { - qSwap( sel_key_start, sel_key_end ); - } - - int y_base = keyAreaBottom() - 1; - if( hasValidMidiClip() ) - { - p.setClipRect( - m_whiteKeyWidth, - PR_TOP_MARGIN, - width() - m_whiteKeyWidth, - height() - PR_TOP_MARGIN); - - const int topKey = qBound(0, m_startKey + m_pianoKeysVisible - 1, NumKeys - 1); - const int bottomKey = topKey - m_pianoKeysVisible; - - QPolygonF editHandles; - - // Return a note's Y position on the grid - auto noteYPos = [&](const int key) - { - return (topKey - key) * m_keyLineHeight + keyAreaTop() - 1; - }; - - // -- Begin ghost MIDI clip - if( !m_ghostNotes.empty() ) - { - for( const Note *note : m_ghostNotes ) - { - int len_ticks = note->length(); - - if( len_ticks == 0 ) - { - continue; - } - else if( len_ticks < 0 ) - { - len_ticks = 4; - } - - int pos_ticks = note->pos(); - - int note_width = len_ticks * m_ppb / TimePos::ticksPerBar(); - const int x = ( pos_ticks - m_currentPosition ) * - m_ppb / TimePos::ticksPerBar(); - // skip this note if not in visible area at all - if (!(x + note_width >= 0 && x <= width() - m_whiteKeyWidth)) - { - continue; - } - - // is the note in visible area? - if (note->key() > bottomKey && note->key() <= topKey) - { - - // we've done and checked all, let's draw the note - drawNoteRect( - p, x + m_whiteKeyWidth, noteYPos(note->key()), note_width, - note, m_ghostNoteColor, m_ghostNoteTextColor, m_selectedNoteColor, - m_ghostNoteOpacity, m_ghostNoteBorders, drawNoteNames); - } - - } - } - // -- End ghost MIDI clip - - for( const Note *note : m_midiClip->notes() ) - { - int len_ticks = note->length(); - - if( len_ticks == 0 ) - { - continue; - } - else if( len_ticks < 0 ) - { - len_ticks = 4; - } - - int pos_ticks = note->pos(); - int note_width = len_ticks * m_ppb / TimePos::ticksPerBar(); - - int detuningLength = !note->detuning()->automationClip()->getTimeMap().isEmpty() - ? note->detuning()->automationClip()->getTimeMap().lastKey() * m_ppb / TimePos::ticksPerBar() - : note_width; - - const int x = ( pos_ticks - m_currentPosition ) * - m_ppb / TimePos::ticksPerBar(); - // Skip this note if not in visible area at all - // But still draw the note if the detuning curve extends past the end of it. - if (!(x + std::max(note_width, detuningLength) >= 0 && x <= width() - m_whiteKeyWidth)) - { - continue; - } - - // is the note in visible area? - if (note->key() > bottomKey && note->key() <= topKey) - { - // We've done and checked all, let's draw the note with - // the appropriate color - const auto fillColor = note->type() == Note::Type::Regular ? m_noteColor : m_stepNoteColor; - - drawNoteRect( - p, x + m_whiteKeyWidth, noteYPos(note->key()), note_width, - note, fillColor, m_noteTextColor, m_selectedNoteColor, - m_noteOpacity, m_noteBorders, drawNoteNames - ); - } - - // draw note editing stuff - int editHandleTop = 0; - if( m_noteEditMode == NoteEditMode::Volume ) - { - QColor color = m_barColor.lighter(30 + (note->getVolume() * 90 / MaxVolume)); - if( note->selected() ) - { - color = m_selectedNoteColor; - } - p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) ); - - editHandleTop = noteEditBottom() - - ( (float)( note->getVolume() - MinVolume ) ) / - ( (float)( MaxVolume - MinVolume ) ) * - ( (float)( noteEditBottom() - noteEditTop() ) ); - - p.drawLine( QLineF ( noteEditLeft() + x + 0.5, editHandleTop + 0.5, - noteEditLeft() + x + 0.5, noteEditBottom() + 0.5 ) ); - - } - else if( m_noteEditMode == NoteEditMode::Panning ) - { - QColor color = m_noteColor; - if( note->selected() ) - { - color = m_selectedNoteColor; - } - - p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) ); - - editHandleTop = noteEditBottom() - - ( (float)( note->getPanning() - PanningLeft ) ) / - ( (float)( (PanningRight - PanningLeft ) ) ) * - ( (float)( noteEditBottom() - noteEditTop() ) ); - - p.drawLine( QLine( noteEditLeft() + x, noteEditTop() + - ( (float)( noteEditBottom() - noteEditTop() ) ) / 2.0f, - noteEditLeft() + x , editHandleTop ) ); - } - editHandles << QPoint ( x + noteEditLeft(), - editHandleTop ); - - if( note->hasDetuningInfo() ) - { - drawDetuningInfo(p, note, x + m_whiteKeyWidth, noteYPos(note->key())); - p.setClipRect( - m_whiteKeyWidth, - PR_TOP_MARGIN, - width() - m_whiteKeyWidth, - height() - PR_TOP_MARGIN); - } - } - - // draw clip bounds - p.fillRect( - xCoordOfTick(m_midiClip->length() - m_midiClip->startTimeOffset()), - PR_TOP_MARGIN, - width() - 10, - noteEditBottom(), - m_outOfBoundsShade - ); - p.fillRect( - 0, - PR_TOP_MARGIN, - xCoordOfTick(-m_midiClip->startTimeOffset()), - noteEditBottom(), - m_outOfBoundsShade - ); - - // -- Knife tool (draw cut line) - if (m_action == Action::Knife && m_knifeDown) - { - int x1 = xCoordOfTick(m_knifeStartTickPos); - int y1 = y_base - (m_knifeStartKey - m_startKey + 1) * m_keyLineHeight; - int x2 = xCoordOfTick(m_knifeEndTickPos); - int y2 = y_base - (m_knifeEndKey - m_startKey + 1) * m_keyLineHeight; - - p.setPen(QPen(m_knifeCutLineColor, 1)); - p.drawLine(x1, y1, x2, y2); - } - // -- End knife tool - - //draw current step recording notes - for( const Note *note : m_stepRecorder.getCurStepNotes() ) - { - int len_ticks = note->length(); - - if( len_ticks == 0 ) - { - continue; - } - - - int pos_ticks = note->pos(); - - int note_width = len_ticks * m_ppb / TimePos::ticksPerBar(); - const int x = ( pos_ticks - m_currentPosition ) * - m_ppb / TimePos::ticksPerBar(); - // skip this note if not in visible area at all - if (!(x + note_width >= 0 && x <= width() - m_whiteKeyWidth)) - { - continue; - } - - // is the note in visible area? - if (note->key() > bottomKey && note->key() <= topKey) - { - - // we've done and checked all, let's draw the note - drawNoteRect( - p, x + m_whiteKeyWidth, noteYPos(note->key()), note_width, - note, m_currentStepNoteColor, m_noteTextColor, m_selectedNoteColor, - m_noteOpacity, m_noteBorders, drawNoteNames); - } - } - - p.setPen(QPen(m_noteColor, NOTE_EDIT_LINE_WIDTH + 2)); - p.drawPoints( editHandles ); - - } - - p.setClipRect( - m_whiteKeyWidth, - PR_TOP_MARGIN, - width() - m_whiteKeyWidth, - height() - PR_TOP_MARGIN - m_notesEditHeight - PR_BOTTOM_MARGIN); - - // now draw selection-frame - int x = ( ( sel_pos_start - m_currentPosition ) * m_ppb ) / - TimePos::ticksPerBar(); - int w = ( ( ( sel_pos_end - m_currentPosition ) * m_ppb ) / - TimePos::ticksPerBar() ) - x; - int y = (int) y_base - sel_key_start * m_keyLineHeight; - int h = (int) y_base - sel_key_end * m_keyLineHeight - y; - p.setPen(m_selectedNoteColor); - p.setBrush( Qt::NoBrush ); - p.drawRect(x + m_whiteKeyWidth, y, w, h); - - // TODO: Get this out of paint event - int l = ( hasValidMidiClip() )? (int) m_midiClip->length() - m_midiClip->startTimeOffset() : 0; - - // reset scroll-range - if( m_leftRightScroll->maximum() != l ) - { - m_leftRightScroll->setRange( 0, l ); - m_leftRightScroll->setPageStep( l ); - } - - // set line colors - auto editAreaCol = QColor(m_lineColor); - auto currentKeyCol = QColor(m_beatLineColor); - - editAreaCol.setAlpha( 64 ); - currentKeyCol.setAlpha( 64 ); - - // horizontal line for the key under the cursor - if(hasValidMidiClip() && getGUI()->pianoRoll()->hasFocus()) - { - int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() ); - p.fillRect( - 10, - yCoordOfKey(key_num) + 3, - width() - 10, - m_keyLineHeight - 7, - currentKeyCol); - } - - // bar to resize note edit area - p.setClipRect( 0, 0, width(), height() ); - p.fillRect( QRect( 0, keyAreaBottom(), - width()-PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR ), editAreaCol ); - - if (getGUI()->pianoRoll()->hasFocus()) - { - const QPixmap * cursor = nullptr; - // draw current edit-mode-icon below the cursor - switch( m_editMode ) - { - case EditMode::Draw: - if( m_mouseDownRight ) - { - cursor = &m_toolErase; - } - else if( m_action == Action::MoveNote ) - { - cursor = &m_toolMove; - } - else - { - cursor = &m_toolDraw; - } - break; - case EditMode::Erase: - cursor = &m_toolErase; - break; - case EditMode::Select: - cursor = &m_toolSelect; - break; - case EditMode::Detuning: - cursor = &m_toolOpen; - break; - case EditMode::Knife: - cursor = &m_toolKnife; - break; - case EditMode::Strum: - cursor = &m_toolStrum; - break; - } - QPoint mousePosition = mapFromGlobal( QCursor::pos() ); - if( cursor != nullptr && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft()) - { - p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor ); - } - } + PianoRollPainter(*this).paint(pe); } diff --git a/src/gui/editors/PianoRollPainter.cpp b/src/gui/editors/PianoRollPainter.cpp new file mode 100644 index 00000000000..3853f2af095 --- /dev/null +++ b/src/gui/editors/PianoRollPainter.cpp @@ -0,0 +1,647 @@ +/* + * PianoRollPainter.cpp - rendering helper for PianoRoll + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + */ + +#include "PianoRollPainter.h" + +#include +#include +#include +#include +#include +#include + +#include "ConfigManager.h" +#include "DetuningHelper.h" +#include "embed.h" +#include "FontHelper.h" +#include "GuiApplication.h" +#include "InstrumentTrack.h" +#include "MidiClip.h" +#include "Piano.h" +#include "PianoRoll.h" +#include "PianoRollConstants.h" + +namespace lmms::gui +{ + +namespace +{ + +std::array s_noteStrings { + "C", "C\u266F / D\u266D", "D", "D\u266F / E\u266D", "E", "F", "F\u266F / G\u266D", + "G", "G\u266F / A\u266D", "A", "A\u266F / B\u266D", "B" +}; + +QString getNoteString(int key) +{ + return s_noteStrings[key % 12] + QString::number(static_cast(FirstOctave + key / KeysPerOctave)); +} + +} // namespace + +PianoRollPainter::PianoRollPainter(PianoRoll& pianoRoll) : + m_pianoRoll(pianoRoll) +{ +} + +void PianoRollPainter::paint(QPaintEvent* event) +{ + Q_UNUSED(event) + + const bool drawNoteNames = ConfigManager::inst()->value("ui", "printnotelabels").toInt(); + + QStyleOption opt; + opt.initFrom(&m_pianoRoll); + QPainter p(&m_pianoRoll); + m_pianoRoll.style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, &m_pianoRoll); + + QBrush bgColor = p.background(); + + p.fillRect(0, 0, m_pianoRoll.width(), m_pianoRoll.height(), bgColor); + + if (!m_pianoRoll.hasValidMidiClip()) + { + const auto icon = embed::getIconPixmap("pr_no_clip"); + const int x = (m_pianoRoll.width() - icon.width()) / 2; + const int y = (m_pianoRoll.height() - icon.height()) / 2; + p.drawPixmap(x, y, icon); + + p.setPen(QApplication::palette().color(QPalette::Active, QPalette::Text)); + QRect textRect(0, y + icon.height() + 5, m_pianoRoll.width(), 30); + p.drawText(textRect, Qt::AlignHCenter | Qt::AlignTop, + PianoRoll::tr("Double-click on an instrument clip in Song Editor to open it here")); + return; + } + + QFont f = p.font(); + const int keyFontSize = m_pianoRoll.m_keyLineHeight * 0.8; + p.setFont(adjustedToPixelSize(f, keyFontSize)); + QFontMetrics fontMetrics(p.font()); + QRect const boundingRect = fontMetrics.boundingRect(QString("G-1")) + QMargins(0, 0, 1, 0); + + auto xCoordOfTick = [this](int tick) + { + return m_pianoRoll.m_whiteKeyWidth + + ((tick - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb / TimePos::ticksPerBar()); + }; + + if (m_pianoRoll.hasValidMidiClip()) + { + int pianoAreaHeight = m_pianoRoll.keyAreaBottom() - m_pianoRoll.keyAreaTop(); + m_pianoRoll.m_pianoKeysVisible = pianoAreaHeight / m_pianoRoll.m_keyLineHeight; + int partialKeyVisible = pianoAreaHeight % m_pianoRoll.m_keyLineHeight; + if (m_pianoRoll.m_pianoKeysVisible * m_pianoRoll.m_keyLineHeight < KEY_AREA_MIN_HEIGHT) + { + m_pianoRoll.m_pianoKeysVisible = KEY_AREA_MIN_HEIGHT / m_pianoRoll.m_keyLineHeight; + partialKeyVisible = KEY_AREA_MIN_HEIGHT % m_pianoRoll.m_keyLineHeight; + if (partialKeyVisible > 0) + { + m_pianoRoll.m_pianoKeysVisible += 1; + partialKeyVisible = 0; + } + m_pianoRoll.m_notesEditHeight = m_pianoRoll.height() - (m_pianoRoll.m_pianoKeysVisible * m_pianoRoll.m_keyLineHeight) + - PR_TOP_MARGIN - PR_BOTTOM_MARGIN; + } + else if (m_pianoRoll.m_pianoKeysVisible >= NumKeys) + { + m_pianoRoll.m_pianoKeysVisible = NumKeys; + m_pianoRoll.m_notesEditHeight = m_pianoRoll.height() - (NumKeys * m_pianoRoll.m_keyLineHeight) - + PR_TOP_MARGIN - PR_BOTTOM_MARGIN; + partialKeyVisible = 0; + } + int topKey = std::clamp(m_pianoRoll.m_startKey + m_pianoRoll.m_pianoKeysVisible - 1, 0, NumKeys - 1); + int topNote = topKey % KeysPerOctave; + if (m_pianoRoll.m_action != PianoRoll::Action::ResizeNoteEditArea && partialKeyVisible != 0) + { + int noteAreaPlus = (m_pianoRoll.m_notesEditHeight + partialKeyVisible) - m_pianoRoll.m_userSetNotesEditHeight; + int noteAreaMinus = m_pianoRoll.m_userSetNotesEditHeight - (m_pianoRoll.m_notesEditHeight - partialKeyVisible); + if (noteAreaPlus > noteAreaMinus) + { + m_pianoRoll.m_notesEditHeight -= partialKeyVisible; + m_pianoRoll.m_pianoKeysVisible += 1; + } + else + { + m_pianoRoll.m_notesEditHeight += partialKeyVisible; + } + } + int x, q = m_pianoRoll.quantization(), tick; + + if (m_pianoRoll.m_zoomingModel.value() <= 3) + { + if (q % 3 != 0 && q < 8) { q = 8; } + else if (q < 6) { q = 6; } + } + + p.setPen(m_pianoRoll.m_lineColor); + for (tick = m_pianoRoll.m_currentPosition - m_pianoRoll.m_currentPosition % q, + x = xCoordOfTick(tick); + x <= m_pianoRoll.width(); + tick += q, x = xCoordOfTick(tick)) + { + p.drawLine(x, m_pianoRoll.keyAreaTop(), x, m_pianoRoll.noteEditBottom()); + } + + p.setClipRect(0, m_pianoRoll.keyAreaTop(), m_pianoRoll.width(), m_pianoRoll.keyAreaBottom() - m_pianoRoll.keyAreaTop()); + int gridLineY = m_pianoRoll.keyAreaTop() + m_pianoRoll.m_keyLineHeight - 1; + + auto keyHeight = [&](const int key) -> int + { + switch (PianoRoll::prKeyOrder[key % KeysPerOctave]) + { + case PianoRoll::KeyType::WhiteBig: + return m_pianoRoll.m_whiteKeyBigHeight; + case PianoRoll::KeyType::WhiteSmall: + return m_pianoRoll.m_whiteKeySmallHeight; + case PianoRoll::KeyType::Black: + return m_pianoRoll.m_blackKeyHeight; + } + return 0; + }; + auto gridCorrection = [&](const int key) -> int + { + const int keyCode = key % KeysPerOctave; + switch (PianoRoll::prKeyOrder[keyCode]) + { + case PianoRoll::KeyType::WhiteBig: + return m_pianoRoll.m_whiteKeySmallHeight; + case PianoRoll::KeyType::WhiteSmall: + if (static_cast(keyCode) == Key::C || static_cast(keyCode) == Key::F) + { + return m_pianoRoll.m_whiteKeySmallHeight; + } + [[fallthrough]]; + case PianoRoll::KeyType::Black: + return m_pianoRoll.m_blackKeyHeight; + } + return 0; + }; + auto keyWidth = [&](const int key) -> int + { + switch (PianoRoll::prKeyOrder[key % KeysPerOctave]) + { + case PianoRoll::KeyType::WhiteSmall: + case PianoRoll::KeyType::WhiteBig: + return m_pianoRoll.m_whiteKeyWidth; + case PianoRoll::KeyType::Black: + return m_pianoRoll.m_blackKeyWidth; + } + return 0; + }; + auto drawKey = [&](const int key, const int yb) + { + const bool mapped = m_pianoRoll.m_midiClip->instrumentTrack()->isKeyMapped(key); + const bool pressed = m_pianoRoll.m_midiClip->instrumentTrack()->pianoModel()->isKeyPressed(key); + const int keyCode = key % KeysPerOctave; + const int yt = yb - gridCorrection(key); + const int kh = keyHeight(key); + const int kw = keyWidth(key); + p.setPen(QColor(0, 0, 0)); + switch (PianoRoll::prKeyOrder[keyCode]) + { + case PianoRoll::KeyType::WhiteSmall: + case PianoRoll::KeyType::WhiteBig: + if (mapped) + { + p.setBrush(pressed ? m_pianoRoll.m_whiteKeyActiveBackground : m_pianoRoll.m_whiteKeyInactiveBackground); + } + else + { + p.setBrush(m_pianoRoll.m_whiteKeyDisabledBackground); + } + break; + case PianoRoll::KeyType::Black: + if (mapped) + { + p.setBrush(pressed ? m_pianoRoll.m_blackKeyActiveBackground : m_pianoRoll.m_blackKeyInactiveBackground); + } + else + { + p.setBrush(m_pianoRoll.m_blackKeyDisabledBackground); + } + } + p.drawRect(PIANO_X, yt, kw, kh); + if (static_cast(keyCode) == Key::C || (drawNoteNames && Piano::isWhiteKey(key))) + { + auto zoomOffset = m_pianoRoll.m_zoomYLevels[m_pianoRoll.m_zoomingYModel.value()] > 1.0f ? 2 : 1; + QString noteString = getNoteString(key); + QRect textRect( + m_pianoRoll.m_whiteKeyWidth - boundingRect.width() - 2, + yb - m_pianoRoll.m_keyLineHeight + zoomOffset, + boundingRect.width(), + boundingRect.height() + ); + p.setPen(pressed ? m_pianoRoll.m_whiteKeyActiveTextShadow : m_pianoRoll.m_whiteKeyInactiveTextShadow); + p.drawText(textRect.adjusted(0, 1, 1, 0), Qt::AlignRight | Qt::AlignHCenter, noteString); + p.setPen(pressed ? m_pianoRoll.m_whiteKeyActiveTextColor : m_pianoRoll.m_whiteKeyInactiveTextColor); + p.drawText(textRect, Qt::AlignRight | Qt::AlignHCenter, noteString); + } + }; + auto drawHorizontalLine = [&](const int key, const int y) + { + p.setPen(static_cast(key % KeysPerOctave) == Key::C ? m_pianoRoll.m_beatLineColor : m_pianoRoll.m_lineColor); + p.drawLine(m_pianoRoll.m_whiteKeyWidth, y, m_pianoRoll.width(), y); + }; + switch (PianoRoll::prKeyOrder[topNote]) + { + case PianoRoll::KeyType::WhiteSmall: + case PianoRoll::KeyType::WhiteBig: + break; + case PianoRoll::KeyType::Black: + drawKey(topKey + 1, gridLineY - m_pianoRoll.m_keyLineHeight); + } + const int lastKey = qMax(0, topKey - m_pianoRoll.m_pianoKeysVisible); + for (int key = topKey; key > lastKey; --key) + { + if (Piano::isWhiteKey(key)) + { + drawKey(key, gridLineY); + drawHorizontalLine(key, gridLineY); + gridLineY += m_pianoRoll.m_keyLineHeight; + } + else + { + drawKey(key - 1, gridLineY + m_pianoRoll.m_keyLineHeight); + drawHorizontalLine(key - 1, gridLineY + m_pianoRoll.m_keyLineHeight); + drawKey(key, gridLineY); + drawHorizontalLine(key, gridLineY); + gridLineY += m_pianoRoll.m_keyLineHeight + m_pianoRoll.m_keyLineHeight; + --key; + } + } + + p.setClipRect(m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.keyAreaTop(), m_pianoRoll.width(), m_pianoRoll.noteEditBottom() - m_pianoRoll.keyAreaTop()); + + float timeSignature = + static_cast(Engine::getSong()->getTimeSigModel().getNumerator()) / + static_cast(Engine::getSong()->getTimeSigModel().getDenominator()); + float zoomFactor = PianoRoll::m_zoomLevels[m_pianoRoll.m_zoomingModel.value()]; + int leftBars = m_pianoRoll.m_currentPosition * zoomFactor / TimePos::ticksPerBar(); + for (int x = m_pianoRoll.m_whiteKeyWidth, barCount = leftBars; + x < m_pianoRoll.width() + m_pianoRoll.m_currentPosition * zoomFactor / timeSignature; + x += m_pianoRoll.m_ppb, ++barCount) + { + if ((barCount + leftBars) % 2 != 0) + { + p.fillRect(x - m_pianoRoll.m_currentPosition * zoomFactor / timeSignature, + PR_TOP_MARGIN, + m_pianoRoll.m_ppb, + m_pianoRoll.height() - (PR_BOTTOM_MARGIN + PR_TOP_MARGIN), + m_pianoRoll.m_backgroundShade); + } + } + + int ticksPerBeat = DefaultTicksPerBar / Engine::getSong()->getTimeSigModel().getDenominator(); + p.setPen(m_pianoRoll.m_beatLineColor); + for (tick = m_pianoRoll.m_currentPosition - m_pianoRoll.m_currentPosition % ticksPerBeat, + x = xCoordOfTick(tick); + x <= m_pianoRoll.width(); + tick += ticksPerBeat, x = xCoordOfTick(tick)) + { + p.drawLine(x, PR_TOP_MARGIN, x, m_pianoRoll.noteEditBottom()); + } + + p.setPen(m_pianoRoll.m_barLineColor); + for (tick = m_pianoRoll.m_currentPosition - m_pianoRoll.m_currentPosition % TimePos::ticksPerBar(), + x = xCoordOfTick(tick); + x <= m_pianoRoll.width(); + tick += TimePos::ticksPerBar(), x = xCoordOfTick(tick)) + { + p.drawLine(x, PR_TOP_MARGIN, x, m_pianoRoll.noteEditBottom()); + } + + for (x = 0; x < m_pianoRoll.m_markedSemiTones.size(); ++x) + { + const int keyNum = m_pianoRoll.m_markedSemiTones.at(x); + const int y = m_pianoRoll.yCoordOfKey(keyNum); + if (y >= m_pianoRoll.keyAreaBottom() - 1) { break; } + p.fillRect(m_pianoRoll.m_whiteKeyWidth + 1, + y, + m_pianoRoll.width() - 10, + m_pianoRoll.m_keyLineHeight, + m_pianoRoll.m_markedSemitoneColor); + } + } + + p.setClipRect(0, 0, m_pianoRoll.width(), m_pianoRoll.height()); + p.fillRect(QRect(0, m_pianoRoll.keyAreaBottom(), + m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.noteEditBottom() - m_pianoRoll.keyAreaBottom()), bgColor); + + f.setBold(false); + p.setFont(adjustedToPixelSize(f, SMALL_FONT_SIZE)); + p.setPen(m_pianoRoll.m_noteModeColor); + p.drawText(QRect(0, m_pianoRoll.keyAreaBottom(), + m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.noteEditBottom() - m_pianoRoll.keyAreaBottom()), + Qt::AlignCenter | Qt::TextWordWrap, + m_pianoRoll.m_nemStr.at(static_cast(m_pianoRoll.m_noteEditMode)) + ":"); + + p.setClipRect( + m_pianoRoll.m_whiteKeyWidth, + PR_TOP_MARGIN, + m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth, + m_pianoRoll.height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN); + + int selPosStart = m_pianoRoll.m_selectStartTick; + int selPosEnd = m_pianoRoll.m_selectStartTick + m_pianoRoll.m_selectedTick; + if (selPosStart > selPosEnd) + { + qSwap(selPosStart, selPosEnd); + } + + int selKeyStart = m_pianoRoll.m_selectStartKey - m_pianoRoll.m_startKey + 1; + int selKeyEnd = selKeyStart + m_pianoRoll.m_selectedKeys; + if (selKeyStart > selKeyEnd) + { + qSwap(selKeyStart, selKeyEnd); + } + + int yBase = m_pianoRoll.keyAreaBottom() - 1; + if (m_pianoRoll.hasValidMidiClip()) + { + p.setClipRect( + m_pianoRoll.m_whiteKeyWidth, + PR_TOP_MARGIN, + m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth, + m_pianoRoll.height() - PR_TOP_MARGIN); + + const int topKey = qBound(0, m_pianoRoll.m_startKey + m_pianoRoll.m_pianoKeysVisible - 1, NumKeys - 1); + const int bottomKey = topKey - m_pianoRoll.m_pianoKeysVisible; + + QPolygonF editHandles; + + auto noteYPos = [&](const int key) + { + return (topKey - key) * m_pianoRoll.m_keyLineHeight + m_pianoRoll.keyAreaTop() - 1; + }; + + if (!m_pianoRoll.m_ghostNotes.empty()) + { + for (const Note* note : m_pianoRoll.m_ghostNotes) + { + int lenTicks = note->length(); + if (lenTicks == 0) + { + continue; + } + else if (lenTicks < 0) + { + lenTicks = 4; + } + + int posTicks = note->pos(); + int noteWidth = lenTicks * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + const int x = (posTicks - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + if (!(x + noteWidth >= 0 && x <= m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth)) + { + continue; + } + + if (note->key() > bottomKey && note->key() <= topKey) + { + m_pianoRoll.drawNoteRect( + p, x + m_pianoRoll.m_whiteKeyWidth, noteYPos(note->key()), noteWidth, + note, m_pianoRoll.m_ghostNoteColor, m_pianoRoll.m_ghostNoteTextColor, m_pianoRoll.m_selectedNoteColor, + m_pianoRoll.m_ghostNoteOpacity, m_pianoRoll.m_ghostNoteBorders, drawNoteNames); + } + } + } + + for (const Note* note : m_pianoRoll.m_midiClip->notes()) + { + int lenTicks = note->length(); + + if (lenTicks == 0) + { + continue; + } + else if (lenTicks < 0) + { + lenTicks = 4; + } + + int posTicks = note->pos(); + int noteWidth = lenTicks * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + + int detuningLength = !note->detuning()->automationClip()->getTimeMap().isEmpty() + ? note->detuning()->automationClip()->getTimeMap().lastKey() * m_pianoRoll.m_ppb / TimePos::ticksPerBar() + : noteWidth; + + const int x = (posTicks - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + if (!(x + std::max(noteWidth, detuningLength) >= 0 && x <= m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth)) + { + continue; + } + + if (note->key() > bottomKey && note->key() <= topKey) + { + const auto fillColor = note->type() == Note::Type::Regular ? m_pianoRoll.m_noteColor : m_pianoRoll.m_stepNoteColor; + + m_pianoRoll.drawNoteRect( + p, x + m_pianoRoll.m_whiteKeyWidth, noteYPos(note->key()), noteWidth, + note, fillColor, m_pianoRoll.m_noteTextColor, m_pianoRoll.m_selectedNoteColor, + m_pianoRoll.m_noteOpacity, m_pianoRoll.m_noteBorders, drawNoteNames + ); + } + + int editHandleTop = 0; + if (m_pianoRoll.m_noteEditMode == PianoRoll::NoteEditMode::Volume) + { + QColor color = m_pianoRoll.m_barColor.lighter(30 + (note->getVolume() * 90 / MaxVolume)); + if (note->selected()) + { + color = m_pianoRoll.m_selectedNoteColor; + } + p.setPen(QPen(color, NOTE_EDIT_LINE_WIDTH)); + + editHandleTop = m_pianoRoll.noteEditBottom() - + (static_cast(note->getVolume() - MinVolume)) / + (static_cast(MaxVolume - MinVolume)) * + (static_cast(m_pianoRoll.noteEditBottom() - m_pianoRoll.noteEditTop())); + + p.drawLine(QLineF(m_pianoRoll.noteEditLeft() + x + 0.5, editHandleTop + 0.5, + m_pianoRoll.noteEditLeft() + x + 0.5, m_pianoRoll.noteEditBottom() + 0.5)); + } + else if (m_pianoRoll.m_noteEditMode == PianoRoll::NoteEditMode::Panning) + { + QColor color = m_pianoRoll.m_noteColor; + if (note->selected()) + { + color = m_pianoRoll.m_selectedNoteColor; + } + + p.setPen(QPen(color, NOTE_EDIT_LINE_WIDTH)); + + editHandleTop = m_pianoRoll.noteEditBottom() - + (static_cast(note->getPanning() - PanningLeft)) / + (static_cast(PanningRight - PanningLeft)) * + (static_cast(m_pianoRoll.noteEditBottom() - m_pianoRoll.noteEditTop())); + + p.drawLine(QLine(m_pianoRoll.noteEditLeft() + x, m_pianoRoll.noteEditTop() + + (static_cast(m_pianoRoll.noteEditBottom() - m_pianoRoll.noteEditTop())) / 2.0f, + m_pianoRoll.noteEditLeft() + x, editHandleTop)); + } + editHandles << QPoint(x + m_pianoRoll.noteEditLeft(), editHandleTop); + + if (note->hasDetuningInfo()) + { + m_pianoRoll.drawDetuningInfo(p, note, x + m_pianoRoll.m_whiteKeyWidth, noteYPos(note->key())); + p.setClipRect( + m_pianoRoll.m_whiteKeyWidth, + PR_TOP_MARGIN, + m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth, + m_pianoRoll.height() - PR_TOP_MARGIN); + } + } + + p.fillRect( + xCoordOfTick(m_pianoRoll.m_midiClip->length() - m_pianoRoll.m_midiClip->startTimeOffset()), + PR_TOP_MARGIN, + m_pianoRoll.width() - 10, + m_pianoRoll.noteEditBottom(), + m_pianoRoll.m_outOfBoundsShade + ); + p.fillRect( + 0, + PR_TOP_MARGIN, + xCoordOfTick(-m_pianoRoll.m_midiClip->startTimeOffset()), + m_pianoRoll.noteEditBottom(), + m_pianoRoll.m_outOfBoundsShade + ); + + if (m_pianoRoll.m_action == PianoRoll::Action::Knife && m_pianoRoll.m_knifeDown) + { + int x1 = xCoordOfTick(m_pianoRoll.m_knifeStartTickPos); + int y1 = yBase - (m_pianoRoll.m_knifeStartKey - m_pianoRoll.m_startKey + 1) * m_pianoRoll.m_keyLineHeight; + int x2 = xCoordOfTick(m_pianoRoll.m_knifeEndTickPos); + int y2 = yBase - (m_pianoRoll.m_knifeEndKey - m_pianoRoll.m_startKey + 1) * m_pianoRoll.m_keyLineHeight; + + p.setPen(QPen(m_pianoRoll.m_knifeCutLineColor, 1)); + p.drawLine(x1, y1, x2, y2); + } + + for (const Note* note : m_pianoRoll.m_stepRecorder.getCurStepNotes()) + { + int lenTicks = note->length(); + + if (lenTicks == 0) + { + continue; + } + + int posTicks = note->pos(); + int noteWidth = lenTicks * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + const int x = (posTicks - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb / TimePos::ticksPerBar(); + if (!(x + noteWidth >= 0 && x <= m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth)) + { + continue; + } + + if (note->key() > bottomKey && note->key() <= topKey) + { + m_pianoRoll.drawNoteRect( + p, x + m_pianoRoll.m_whiteKeyWidth, noteYPos(note->key()), noteWidth, + note, m_pianoRoll.m_currentStepNoteColor, m_pianoRoll.m_noteTextColor, m_pianoRoll.m_selectedNoteColor, + m_pianoRoll.m_noteOpacity, m_pianoRoll.m_noteBorders, drawNoteNames); + } + } + + p.setPen(QPen(m_pianoRoll.m_noteColor, NOTE_EDIT_LINE_WIDTH + 2)); + p.drawPoints(editHandles); + } + + p.setClipRect( + m_pianoRoll.m_whiteKeyWidth, + PR_TOP_MARGIN, + m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth, + m_pianoRoll.height() - PR_TOP_MARGIN - m_pianoRoll.m_notesEditHeight - PR_BOTTOM_MARGIN); + + int x = ((selPosStart - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb) / TimePos::ticksPerBar(); + int w = ((((selPosEnd - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb) / TimePos::ticksPerBar())) - x; + int y = yBase - selKeyStart * m_pianoRoll.m_keyLineHeight; + int h = yBase - selKeyEnd * m_pianoRoll.m_keyLineHeight - y; + p.setPen(m_pianoRoll.m_selectedNoteColor); + p.setBrush(Qt::NoBrush); + p.drawRect(x + m_pianoRoll.m_whiteKeyWidth, y, w, h); + + int l = m_pianoRoll.hasValidMidiClip() ? static_cast(m_pianoRoll.m_midiClip->length()) - m_pianoRoll.m_midiClip->startTimeOffset() : 0; + + if (m_pianoRoll.m_leftRightScroll->maximum() != l) + { + m_pianoRoll.m_leftRightScroll->setRange(0, l); + m_pianoRoll.m_leftRightScroll->setPageStep(l); + } + + auto editAreaCol = QColor(m_pianoRoll.m_lineColor); + auto currentKeyCol = QColor(m_pianoRoll.m_beatLineColor); + + editAreaCol.setAlpha(64); + currentKeyCol.setAlpha(64); + + if (m_pianoRoll.hasValidMidiClip() && getGUI()->pianoRoll()->hasFocus()) + { + int keyNum = m_pianoRoll.getKey(m_pianoRoll.mapFromGlobal(QCursor::pos()).y()); + p.fillRect( + 10, + m_pianoRoll.yCoordOfKey(keyNum) + 3, + m_pianoRoll.width() - 10, + m_pianoRoll.m_keyLineHeight - 7, + currentKeyCol); + } + + p.setClipRect(0, 0, m_pianoRoll.width(), m_pianoRoll.height()); + p.fillRect(QRect(0, m_pianoRoll.keyAreaBottom(), + m_pianoRoll.width() - PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR), editAreaCol); + + if (getGUI()->pianoRoll()->hasFocus()) + { + const QPixmap* cursor = nullptr; + switch (m_pianoRoll.m_editMode) + { + case PianoRoll::EditMode::Draw: + if (m_pianoRoll.m_mouseDownRight) + { + cursor = &m_pianoRoll.m_toolErase; + } + else if (m_pianoRoll.m_action == PianoRoll::Action::MoveNote) + { + cursor = &m_pianoRoll.m_toolMove; + } + else + { + cursor = &m_pianoRoll.m_toolDraw; + } + break; + case PianoRoll::EditMode::Erase: + cursor = &m_pianoRoll.m_toolErase; + break; + case PianoRoll::EditMode::Select: + cursor = &m_pianoRoll.m_toolSelect; + break; + case PianoRoll::EditMode::Detuning: + cursor = &m_pianoRoll.m_toolOpen; + break; + case PianoRoll::EditMode::Knife: + cursor = &m_pianoRoll.m_toolKnife; + break; + case PianoRoll::EditMode::Strum: + cursor = &m_pianoRoll.m_toolStrum; + break; + } + QPoint mousePosition = m_pianoRoll.mapFromGlobal(QCursor::pos()); + if (cursor != nullptr && mousePosition.y() > m_pianoRoll.keyAreaTop() && mousePosition.x() > m_pianoRoll.noteEditLeft()) + { + p.drawPixmap(mousePosition + QPoint(8, 8), *cursor); + } + } +} + +} // namespace lmms::gui From 3a6167c7643c00f1fa763920a5c972ce329529d9 Mon Sep 17 00:00:00 2001 From: WeakOstra Date: Wed, 18 Mar 2026 12:02:56 +0100 Subject: [PATCH 3/3] Restored comments in PianoRollPainter --- src/gui/editors/PianoRollPainter.cpp | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/gui/editors/PianoRollPainter.cpp b/src/gui/editors/PianoRollPainter.cpp index 3853f2af095..21f2ef02212 100644 --- a/src/gui/editors/PianoRollPainter.cpp +++ b/src/gui/editors/PianoRollPainter.cpp @@ -94,51 +94,81 @@ void PianoRollPainter::paint(QPaintEvent* event) ((tick - m_pianoRoll.m_currentPosition) * m_pianoRoll.m_ppb / TimePos::ticksPerBar()); }; + // Order of drawing + // - vertical quantization lines + // - piano roll + horizontal key lines + // - alternating bar colors + // - vertical beat lines + // - vertical bar lines + // - marked semitones + // - note editing + // - notes + // - selection frame + // - highlight hovered note + // - note edit area resize bar + // - cursor mode icon + if (m_pianoRoll.hasValidMidiClip()) { int pianoAreaHeight = m_pianoRoll.keyAreaBottom() - m_pianoRoll.keyAreaTop(); m_pianoRoll.m_pianoKeysVisible = pianoAreaHeight / m_pianoRoll.m_keyLineHeight; int partialKeyVisible = pianoAreaHeight % m_pianoRoll.m_keyLineHeight; + // check if we're below the minimum key area size if (m_pianoRoll.m_pianoKeysVisible * m_pianoRoll.m_keyLineHeight < KEY_AREA_MIN_HEIGHT) { m_pianoRoll.m_pianoKeysVisible = KEY_AREA_MIN_HEIGHT / m_pianoRoll.m_keyLineHeight; partialKeyVisible = KEY_AREA_MIN_HEIGHT % m_pianoRoll.m_keyLineHeight; + // if we have a partial key, just show it if (partialKeyVisible > 0) { m_pianoRoll.m_pianoKeysVisible += 1; partialKeyVisible = 0; } + // have to modify the notes edit area height instead m_pianoRoll.m_notesEditHeight = m_pianoRoll.height() - (m_pianoRoll.m_pianoKeysVisible * m_pianoRoll.m_keyLineHeight) - PR_TOP_MARGIN - PR_BOTTOM_MARGIN; } + // check if we're trying to show more keys than available else if (m_pianoRoll.m_pianoKeysVisible >= NumKeys) { m_pianoRoll.m_pianoKeysVisible = NumKeys; + // have to modify the notes edit area height instead m_pianoRoll.m_notesEditHeight = m_pianoRoll.height() - (NumKeys * m_pianoRoll.m_keyLineHeight) - PR_TOP_MARGIN - PR_BOTTOM_MARGIN; partialKeyVisible = 0; } int topKey = std::clamp(m_pianoRoll.m_startKey + m_pianoRoll.m_pianoKeysVisible - 1, 0, NumKeys - 1); int topNote = topKey % KeysPerOctave; + // if not resizing the note edit area, we can change m_notesEditHeight if (m_pianoRoll.m_action != PianoRoll::Action::ResizeNoteEditArea && partialKeyVisible != 0) { + // calculate the height change adding and subtracting the partial key int noteAreaPlus = (m_pianoRoll.m_notesEditHeight + partialKeyVisible) - m_pianoRoll.m_userSetNotesEditHeight; int noteAreaMinus = m_pianoRoll.m_userSetNotesEditHeight - (m_pianoRoll.m_notesEditHeight - partialKeyVisible); + // if adding the partial key to height is more distant from the set height + // we want to subtract the partial key if (noteAreaPlus > noteAreaMinus) { m_pianoRoll.m_notesEditHeight -= partialKeyVisible; + // since we're adding a partial key, we add one to the number visible m_pianoRoll.m_pianoKeysVisible += 1; } else { + // otherwise we add height m_pianoRoll.m_notesEditHeight += partialKeyVisible; } } int x, q = m_pianoRoll.quantization(), tick; + // draw vertical quantization lines + // If we're over 100% zoom, we allow all quantization level grids if (m_pianoRoll.m_zoomingModel.value() <= 3) { + // we're under 100% zoom + // allow quantization grid up to 1/24 for triplets if (q % 3 != 0 && q < 8) { q = 8; } + // allow quantization grid up to 1/32 for normal notes else if (q < 6) { q = 6; } } @@ -151,9 +181,12 @@ void PianoRollPainter::paint(QPaintEvent* event) p.drawLine(x, m_pianoRoll.keyAreaTop(), x, m_pianoRoll.noteEditBottom()); } + // draw horizontal grid lines and piano notes p.setClipRect(0, m_pianoRoll.keyAreaTop(), m_pianoRoll.width(), m_pianoRoll.keyAreaBottom() - m_pianoRoll.keyAreaTop()); + // the first grid line from the top Y position int gridLineY = m_pianoRoll.keyAreaTop() + m_pianoRoll.m_keyLineHeight - 1; + // lambda function for returning the height of a key auto keyHeight = [&](const int key) -> int { switch (PianoRoll::prKeyOrder[key % KeysPerOctave]) @@ -167,6 +200,7 @@ void PianoRollPainter::paint(QPaintEvent* event) } return 0; }; + // lambda function for returning the distance to the top of a key auto gridCorrection = [&](const int key) -> int { const int keyCode = key % KeysPerOctave; @@ -197,6 +231,7 @@ void PianoRollPainter::paint(QPaintEvent* event) } return 0; }; + // lambda function to draw a key auto drawKey = [&](const int key, const int yb) { const bool mapped = m_pianoRoll.m_midiClip->instrumentTrack()->isKeyMapped(key); @@ -246,11 +281,13 @@ void PianoRollPainter::paint(QPaintEvent* event) p.drawText(textRect, Qt::AlignRight | Qt::AlignHCenter, noteString); } }; + // lambda for drawing the horizontal grid line auto drawHorizontalLine = [&](const int key, const int y) { p.setPen(static_cast(key % KeysPerOctave) == Key::C ? m_pianoRoll.m_beatLineColor : m_pianoRoll.m_lineColor); p.drawLine(m_pianoRoll.m_whiteKeyWidth, y, m_pianoRoll.width(), y); }; + // correct y offset of the top key switch (PianoRoll::prKeyOrder[topNote]) { case PianoRoll::KeyType::WhiteSmall: @@ -259,6 +296,7 @@ void PianoRollPainter::paint(QPaintEvent* event) case PianoRoll::KeyType::Black: drawKey(topKey + 1, gridLineY - m_pianoRoll.m_keyLineHeight); } + // loop through visible keys const int lastKey = qMax(0, topKey - m_pianoRoll.m_pianoKeysVisible); for (int key = topKey; key > lastKey; --key) { @@ -279,13 +317,17 @@ void PianoRollPainter::paint(QPaintEvent* event) } } + // don't draw over keys p.setClipRect(m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.keyAreaTop(), m_pianoRoll.width(), m_pianoRoll.noteEditBottom() - m_pianoRoll.keyAreaTop()); + // draw alternating shading on bars float timeSignature = static_cast(Engine::getSong()->getTimeSigModel().getNumerator()) / static_cast(Engine::getSong()->getTimeSigModel().getDenominator()); float zoomFactor = PianoRoll::m_zoomLevels[m_pianoRoll.m_zoomingModel.value()]; + // the bars which disappear at the left side by scrolling int leftBars = m_pianoRoll.m_currentPosition * zoomFactor / TimePos::ticksPerBar(); + // iterates the visible bars and draw the shading on uneven bars for (int x = m_pianoRoll.m_whiteKeyWidth, barCount = leftBars; x < m_pianoRoll.width() + m_pianoRoll.m_currentPosition * zoomFactor / timeSignature; x += m_pianoRoll.m_ppb, ++barCount) @@ -300,6 +342,7 @@ void PianoRollPainter::paint(QPaintEvent* event) } } + // draw vertical beat lines int ticksPerBeat = DefaultTicksPerBar / Engine::getSong()->getTimeSigModel().getDenominator(); p.setPen(m_pianoRoll.m_beatLineColor); for (tick = m_pianoRoll.m_currentPosition - m_pianoRoll.m_currentPosition % ticksPerBeat, @@ -310,6 +353,7 @@ void PianoRollPainter::paint(QPaintEvent* event) p.drawLine(x, PR_TOP_MARGIN, x, m_pianoRoll.noteEditBottom()); } + // draw vertical bar lines p.setPen(m_pianoRoll.m_barLineColor); for (tick = m_pianoRoll.m_currentPosition - m_pianoRoll.m_currentPosition % TimePos::ticksPerBar(), x = xCoordOfTick(tick); @@ -319,6 +363,7 @@ void PianoRollPainter::paint(QPaintEvent* event) p.drawLine(x, PR_TOP_MARGIN, x, m_pianoRoll.noteEditBottom()); } + // draw marked semitones after the grid for (x = 0; x < m_pianoRoll.m_markedSemiTones.size(); ++x) { const int keyNum = m_pianoRoll.m_markedSemiTones.at(x); @@ -332,10 +377,14 @@ void PianoRollPainter::paint(QPaintEvent* event) } } + // reset MIDI clip p.setClipRect(0, 0, m_pianoRoll.width(), m_pianoRoll.height()); + // erase the area below the piano, because there might be keys that + // should be only half-visible p.fillRect(QRect(0, m_pianoRoll.keyAreaBottom(), m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.noteEditBottom() - m_pianoRoll.keyAreaBottom()), bgColor); + // display note editing info f.setBold(false); p.setFont(adjustedToPixelSize(f, SMALL_FONT_SIZE)); p.setPen(m_pianoRoll.m_noteModeColor); @@ -344,12 +393,15 @@ void PianoRollPainter::paint(QPaintEvent* event) Qt::AlignCenter | Qt::TextWordWrap, m_pianoRoll.m_nemStr.at(static_cast(m_pianoRoll.m_noteEditMode)) + ":"); + // set clipping area, because we are not allowed to paint over + // keyboard... p.setClipRect( m_pianoRoll.m_whiteKeyWidth, PR_TOP_MARGIN, m_pianoRoll.width() - m_pianoRoll.m_whiteKeyWidth, m_pianoRoll.height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN); + // setup selection-vars int selPosStart = m_pianoRoll.m_selectStartTick; int selPosEnd = m_pianoRoll.m_selectStartTick + m_pianoRoll.m_selectedTick; if (selPosStart > selPosEnd) @@ -367,6 +419,8 @@ void PianoRollPainter::paint(QPaintEvent* event) int yBase = m_pianoRoll.keyAreaBottom() - 1; if (m_pianoRoll.hasValidMidiClip()) { + // following code draws all notes in visible area + // and the note editing stuff (volume, panning, etc) p.setClipRect( m_pianoRoll.m_whiteKeyWidth, PR_TOP_MARGIN, @@ -378,11 +432,13 @@ void PianoRollPainter::paint(QPaintEvent* event) QPolygonF editHandles; + // Return a note's Y position on the grid auto noteYPos = [&](const int key) { return (topKey - key) * m_pianoRoll.m_keyLineHeight + m_pianoRoll.keyAreaTop() - 1; }; + // -- Begin ghost MIDI clip if (!m_pianoRoll.m_ghostNotes.empty()) { for (const Note* note : m_pianoRoll.m_ghostNotes) @@ -415,6 +471,7 @@ void PianoRollPainter::paint(QPaintEvent* event) } } + // -- End ghost MIDI clip for (const Note* note : m_pianoRoll.m_midiClip->notes()) { int lenTicks = note->length(); @@ -502,6 +559,7 @@ void PianoRollPainter::paint(QPaintEvent* event) } } + // draw clip bounds p.fillRect( xCoordOfTick(m_pianoRoll.m_midiClip->length() - m_pianoRoll.m_midiClip->startTimeOffset()), PR_TOP_MARGIN, @@ -517,6 +575,7 @@ void PianoRollPainter::paint(QPaintEvent* event) m_pianoRoll.m_outOfBoundsShade ); + // -- Knife tool (draw cut line) if (m_pianoRoll.m_action == PianoRoll::Action::Knife && m_pianoRoll.m_knifeDown) { int x1 = xCoordOfTick(m_pianoRoll.m_knifeStartTickPos); @@ -527,7 +586,9 @@ void PianoRollPainter::paint(QPaintEvent* event) p.setPen(QPen(m_pianoRoll.m_knifeCutLineColor, 1)); p.drawLine(x1, y1, x2, y2); } + // -- End knife tool + // draw current step recording notes for (const Note* note : m_pianoRoll.m_stepRecorder.getCurStepNotes()) { int lenTicks = note->length(); @@ -558,6 +619,7 @@ void PianoRollPainter::paint(QPaintEvent* event) p.drawPoints(editHandles); } + // now draw selection-frame p.setClipRect( m_pianoRoll.m_whiteKeyWidth, PR_TOP_MARGIN, @@ -572,20 +634,24 @@ void PianoRollPainter::paint(QPaintEvent* event) p.setBrush(Qt::NoBrush); p.drawRect(x + m_pianoRoll.m_whiteKeyWidth, y, w, h); + // TODO: Get this out of paint event int l = m_pianoRoll.hasValidMidiClip() ? static_cast(m_pianoRoll.m_midiClip->length()) - m_pianoRoll.m_midiClip->startTimeOffset() : 0; + // reset scroll-range if (m_pianoRoll.m_leftRightScroll->maximum() != l) { m_pianoRoll.m_leftRightScroll->setRange(0, l); m_pianoRoll.m_leftRightScroll->setPageStep(l); } + // set line colors auto editAreaCol = QColor(m_pianoRoll.m_lineColor); auto currentKeyCol = QColor(m_pianoRoll.m_beatLineColor); editAreaCol.setAlpha(64); currentKeyCol.setAlpha(64); + // horizontal line for the key under the cursor if (m_pianoRoll.hasValidMidiClip() && getGUI()->pianoRoll()->hasFocus()) { int keyNum = m_pianoRoll.getKey(m_pianoRoll.mapFromGlobal(QCursor::pos()).y()); @@ -597,6 +663,7 @@ void PianoRollPainter::paint(QPaintEvent* event) currentKeyCol); } + // bar to resize note edit area p.setClipRect(0, 0, m_pianoRoll.width(), m_pianoRoll.height()); p.fillRect(QRect(0, m_pianoRoll.keyAreaBottom(), m_pianoRoll.width() - PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR), editAreaCol); @@ -604,6 +671,7 @@ void PianoRollPainter::paint(QPaintEvent* event) if (getGUI()->pianoRoll()->hasFocus()) { const QPixmap* cursor = nullptr; + // draw current edit-mode-icon below the cursor switch (m_pianoRoll.m_editMode) { case PianoRoll::EditMode::Draw: