From ef961e53de0cc7beac0175c17ec8316cb0890d6e Mon Sep 17 00:00:00 2001 From: Kevin Zander Date: Sun, 9 Aug 2020 18:01:35 -0500 Subject: [PATCH] Refactor PianoRoll (#5253) * Rework PianoRoll paintEvent + some extras * Split out PositionLine class to own file * Refactor PianoRoll Q_PROPERTYs * Reduce code by using Q_PROPERTY's MEMBER function and removing getter/setter functions After looking at the getters and setters, they did nothing different than what direct access would allow. Nothing outside of PianoRoll used the public functions as well. Considering these factors we can reduce the number of functions by 2x the number of Q_PROPERTIES, and go with direct access instead. * Remove need for keyboard pixmaps With the recent change to allow zooming vertically, aligning pixmaps is a PITA. Since we have themes which can take brushes and colors, it would be simpler to take a solid color or a gradient with some extra style properties to resize the keys and text colors. While it will slightly be a downgrade from pixmaps since they can be anything really, this will allow us to customize the piano roll further moving forward. * Added the ability to update margins for TimeLineWidget and StepRecorderWidget These take a X coordinate, which was hardcoded to WHITE_KEY_WIDTH, and never looked back. Now we can adjust on the fly if we need to. Currently this just allows us to shift the left margin to the style-defined white key width. * Fix phantom pixmaps when PianoRoll not focused * Update PositionLine class changes related to #5543 --- data/themes/classic/style.css | 13 +- data/themes/default/style.css | 13 +- include/PianoRoll.h | 121 ++- include/PositionLine.h | 49 ++ include/SongEditor.h | 28 +- include/StepRecorderWidget.h | 2 + include/TimeLineWidget.h | 2 + src/gui/CMakeLists.txt | 1 + src/gui/TimeLineWidget.cpp | 8 + src/gui/editors/PianoRoll.cpp | 1120 +++++++++++------------- src/gui/editors/SongEditor.cpp | 82 +- src/gui/widgets/PositionLine.cpp | 98 +++ src/gui/widgets/StepRecorderWidget.cpp | 13 + 13 files changed, 757 insertions(+), 793 deletions(-) create mode 100644 include/PositionLine.h create mode 100644 src/gui/widgets/PositionLine.cpp diff --git a/data/themes/classic/style.css b/data/themes/classic/style.css index 6ae93b498..3e64c9da5 100644 --- a/data/themes/classic/style.css +++ b/data/themes/classic/style.css @@ -118,7 +118,7 @@ QMenu::indicator:selected { background-color: #747474; } -positionLine { +PositionLine { qproperty-tailGradient: false; qproperty-lineColor: rgb(255, 255, 255); } @@ -138,6 +138,17 @@ PianoRoll { qproperty-ghostNoteBorders: true; qproperty-barColor: #4afd85; qproperty-markedSemitoneColor: rgba( 0, 255, 200, 60 ); + /* Piano keys */ + qproperty-whiteKeyWidth: 64; + qproperty-whiteKeyActiveTextColor: #000; + qproperty-whiteKeyActiveTextShadow: rgb( 240, 240, 240 ); + qproperty-whiteKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); + qproperty-whiteKeyInactiveTextColor: rgb( 128, 128, 128); + qproperty-whiteKeyInactiveTextShadow: rgb( 240, 240, 240 ); + qproperty-whiteKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #eeeeee, stop:1 #ffffff); + qproperty-blackKeyWidth: 48; + qproperty-blackKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); + qproperty-blackKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #333, stop:1 #000); /* Grid colors */ qproperty-lineColor: rgba( 128, 128, 128, 80 ); qproperty-beatLineColor: rgba( 128, 128, 128, 160 ); diff --git a/data/themes/default/style.css b/data/themes/default/style.css index fc6495921..4dee86788 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -150,7 +150,7 @@ QMenu::indicator:selected { background-color: #101213; } -positionLine { +PositionLine { qproperty-tailGradient: true; qproperty-lineColor: rgb(255, 255, 255); } @@ -170,6 +170,17 @@ PianoRoll { qproperty-ghostNoteBorders: false; qproperty-barColor: #078f3a; qproperty-markedSemitoneColor: rgba(255, 255, 255, 30); + /* Piano keys */ + qproperty-whiteKeyWidth: 64; + qproperty-whiteKeyActiveTextColor: #000; + qproperty-whiteKeyActiveTextShadow: #fff; + qproperty-whiteKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); + qproperty-whiteKeyInactiveTextColor: #000; + qproperty-whiteKeyInactiveTextShadow: #fff; + qproperty-whiteKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #eeeeee, stop:1 #ffffff); + qproperty-blackKeyWidth: 48; + qproperty-blackKeyActiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #43e97b, stop:1 #3bcd6c); + qproperty-blackKeyInactiveBackground: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #333, stop:1 #000); /* Grid colors */ qproperty-lineColor: #292929; qproperty-beatLineColor: #2d6b45; diff --git a/include/PianoRoll.h b/include/PianoRoll.h index 86a65050c..7a99e7b1f 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -40,6 +40,7 @@ #include "ToolTip.h" #include "StepRecorder.h" #include "StepRecorderWidget.h" +#include "PositionLine.h" class QPainter; class QPixmap; @@ -55,25 +56,38 @@ class TimeLineWidget; class PianoRoll : public QWidget { Q_OBJECT - Q_PROPERTY( QColor barLineColor READ barLineColor WRITE setBarLineColor ) - Q_PROPERTY( QColor beatLineColor READ beatLineColor WRITE setBeatLineColor ) - Q_PROPERTY( QColor lineColor READ lineColor WRITE setLineColor ) - Q_PROPERTY( QColor noteModeColor READ noteModeColor WRITE setNoteModeColor ) - Q_PROPERTY( QColor noteColor READ noteColor WRITE setNoteColor ) - Q_PROPERTY( QColor ghostNoteColor READ ghostNoteColor WRITE setGhostNoteColor ) - Q_PROPERTY( QColor noteTextColor READ noteTextColor WRITE setNoteTextColor ) - Q_PROPERTY( QColor ghostNoteTextColor READ ghostNoteTextColor WRITE setGhostNoteTextColor ) - Q_PROPERTY( QColor barColor READ barColor WRITE setBarColor ) - Q_PROPERTY( QColor selectedNoteColor READ selectedNoteColor WRITE setSelectedNoteColor ) - Q_PROPERTY( QColor textColor READ textColor WRITE setTextColor ) - Q_PROPERTY( QColor textColorLight READ textColorLight WRITE setTextColorLight ) - Q_PROPERTY( QColor textShadow READ textShadow WRITE setTextShadow ) - Q_PROPERTY( QColor markedSemitoneColor READ markedSemitoneColor WRITE setMarkedSemitoneColor ) - Q_PROPERTY( int noteOpacity READ noteOpacity WRITE setNoteOpacity ) - Q_PROPERTY( bool noteBorders READ noteBorders WRITE setNoteBorders ) - Q_PROPERTY( int ghostNoteOpacity READ ghostNoteOpacity WRITE setGhostNoteOpacity ) - Q_PROPERTY( bool ghostNoteBorders READ ghostNoteBorders WRITE setGhostNoteBorders ) - Q_PROPERTY( QColor backgroundShade READ backgroundShade WRITE setBackgroundShade ) + Q_PROPERTY(QColor barLineColor MEMBER m_barLineColor) + Q_PROPERTY(QColor beatLineColor MEMBER m_beatLineColor) + Q_PROPERTY(QColor lineColor MEMBER m_lineColor) + Q_PROPERTY(QColor noteModeColor MEMBER m_noteModeColor) + Q_PROPERTY(QColor noteColor MEMBER m_noteColor) + Q_PROPERTY(QColor ghostNoteColor MEMBER m_ghostNoteColor) + Q_PROPERTY(QColor noteTextColor MEMBER m_noteTextColor) + Q_PROPERTY(QColor ghostNoteTextColor MEMBER m_ghostNoteTextColor) + Q_PROPERTY(QColor barColor MEMBER m_barColor) + Q_PROPERTY(QColor selectedNoteColor MEMBER m_selectedNoteColor) + Q_PROPERTY(QColor textColor MEMBER m_textColor) + Q_PROPERTY(QColor textColorLight MEMBER m_textColorLight) + Q_PROPERTY(QColor textShadow MEMBER m_textShadow) + Q_PROPERTY(QColor markedSemitoneColor MEMBER m_markedSemitoneColor) + Q_PROPERTY(int noteOpacity MEMBER m_noteOpacity) + Q_PROPERTY(bool noteBorders MEMBER m_noteBorders) + Q_PROPERTY(int ghostNoteOpacity MEMBER m_ghostNoteOpacity) + Q_PROPERTY(bool ghostNoteBorders MEMBER m_ghostNoteBorders) + Q_PROPERTY(QColor backgroundShade MEMBER m_backgroundShade) + + /* white key properties */ + Q_PROPERTY(int whiteKeyWidth MEMBER m_whiteKeyWidth) + Q_PROPERTY(QColor whiteKeyInactiveTextColor MEMBER m_whiteKeyInactiveTextColor) + Q_PROPERTY(QColor whiteKeyInactiveTextShadow MEMBER m_whiteKeyInactiveTextShadow) + Q_PROPERTY(QBrush whiteKeyInactiveBackground MEMBER m_whiteKeyInactiveBackground) + Q_PROPERTY(QColor whiteKeyActiveTextColor MEMBER m_whiteKeyActiveTextColor) + Q_PROPERTY(QColor whiteKeyActiveTextShadow MEMBER m_whiteKeyActiveTextShadow) + Q_PROPERTY(QBrush whiteKeyActiveBackground MEMBER m_whiteKeyActiveBackground) + /* black key properties */ + Q_PROPERTY(int blackKeyWidth MEMBER m_blackKeyWidth) + Q_PROPERTY(QBrush blackKeyInactiveBackground MEMBER m_blackKeyInactiveBackground) + Q_PROPERTY(QBrush blackKeyActiveBackground MEMBER m_blackKeyActiveBackground) public: enum EditModes { @@ -125,47 +139,6 @@ public: int quantization() const; - // qproperty access functions - QColor barLineColor() const; - void setBarLineColor( const QColor & c ); - QColor beatLineColor() const; - void setBeatLineColor( const QColor & c ); - QColor lineColor() const; - void setLineColor( const QColor & c ); - QColor noteModeColor() const; - void setNoteModeColor( const QColor & c ); - QColor noteColor() const; - void setNoteColor( const QColor & c ); - QColor noteTextColor() const; - void setNoteTextColor( const QColor & c ); - QColor barColor() const; - void setBarColor( const QColor & c ); - QColor selectedNoteColor() const; - void setSelectedNoteColor( const QColor & c ); - QColor textColor() const; - void setTextColor( const QColor & c ); - QColor textColorLight() const; - void setTextColorLight( const QColor & c ); - QColor textShadow() const; - void setTextShadow( const QColor & c ); - QColor markedSemitoneColor() const; - void setMarkedSemitoneColor( const QColor & c ); - int noteOpacity() const; - void setNoteOpacity( const int i ); - bool noteBorders() const; - void setNoteBorders( const bool b ); - QColor ghostNoteColor() const; - void setGhostNoteColor( const QColor & c ); - QColor ghostNoteTextColor() const; - void setGhostNoteTextColor( const QColor & c ); - int ghostNoteOpacity() const; - void setGhostNoteOpacity( const int i ); - bool ghostNoteBorders() const; - void setGhostNoteBorders( const bool b ); - QColor backgroundShade() const; - void setBackgroundShade( const QColor & c ); - - protected: void keyPressEvent( QKeyEvent * ke ) override; void keyReleaseEvent( QKeyEvent * ke ) override; @@ -188,7 +161,6 @@ protected: void selectAll(); NoteVector getSelectedNotes() const; void selectNotesOnKey(); - int xCoordOfTick( int tick ); // for entering values with dblclick in the vol/pan bars void enterValue( NoteVector* nv ); @@ -279,6 +251,8 @@ private: PR_BLACK_KEY }; + PositionLine * m_positionLine; + QVector m_nemStr; // gui names of each edit mode QMenu * m_noteEditMenu; // when you right click below the key area @@ -306,6 +280,9 @@ private: void playChordNotes(int key, int velocity=-1); void pauseChordNotes(int key); + void updateScrollbars(); + void updatePositionLineHeight(); + QList getAllOctavesForKey( int keyToMirror ) const; int noteEditTop() const; @@ -320,12 +297,6 @@ private: static const int cm_scrollAmtHoriz = 10; static const int cm_scrollAmtVert = 1; - static QPixmap * s_whiteKeyBigPm; - static QPixmap * s_whiteKeyBigPressedPm; - static QPixmap * s_whiteKeySmallPm; - static QPixmap * s_whiteKeySmallPressedPm; - static QPixmap * s_blackKeyPm; - static QPixmap * s_blackKeyPressedPm; static QPixmap * s_toolDraw; static QPixmap * s_toolErase; static QPixmap * s_toolSelect; @@ -389,10 +360,11 @@ private: int m_moveStartX; int m_moveStartY; - int m_oldNotesEditHeight; int m_notesEditHeight; + int m_userSetNotesEditHeight; int m_ppb; // pixels per bar int m_totalKeysToScroll; + int m_pianoKeysVisible; int m_keyLineHeight; int m_octaveHeight; @@ -458,6 +430,18 @@ private: bool m_noteBorders; bool m_ghostNoteBorders; QColor m_backgroundShade; + /* white key properties */ + int m_whiteKeyWidth; + QColor m_whiteKeyActiveTextColor; + QColor m_whiteKeyActiveTextShadow; + QBrush m_whiteKeyActiveBackground; + QColor m_whiteKeyInactiveTextColor; + QColor m_whiteKeyInactiveTextShadow; + QBrush m_whiteKeyInactiveBackground; + /* black key properties */ + int m_blackKeyWidth; + QBrush m_blackKeyActiveBackground; + QBrush m_blackKeyInactiveBackground; signals: void positionChanged( const MidiTime & ); @@ -501,6 +485,7 @@ public: } QSize sizeHint() const override; + bool hasFocus() const; signals: void currentPatternChanged(); diff --git a/include/PositionLine.h b/include/PositionLine.h new file mode 100644 index 000000000..d48fd3df1 --- /dev/null +++ b/include/PositionLine.h @@ -0,0 +1,49 @@ +/* + * PositionLine.h - declaration of class PositionLine, a simple widget that + * draws a line, mainly works with TimeLineWidget + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef POSITION_LINE_H +#define POSITION_LINE_H + +#include + +class PositionLine : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool tailGradient MEMBER m_hasTailGradient) + Q_PROPERTY(QColor lineColor MEMBER m_lineColor) +public: + PositionLine(QWidget* parent); + +public slots: + void zoomChange(double zoom); + +private: + void paintEvent(QPaintEvent* pe) override; + + bool m_hasTailGradient; + QColor m_lineColor; +}; + +#endif \ No newline at end of file diff --git a/include/SongEditor.h b/include/SongEditor.h index c4c25d7d0..7e0fe986a 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -33,6 +33,7 @@ #include "ActionGroup.h" #include "Editor.h" #include "TrackContainerView.h" +#include "PositionLine.h" class QLabel; class QScrollBar; @@ -46,31 +47,6 @@ class Song; class TextFloat; class TimeLineWidget; -class positionLine : public QWidget -{ - Q_OBJECT - Q_PROPERTY ( bool tailGradient READ hasTailGradient WRITE setHasTailGradient ) - Q_PROPERTY ( QColor lineColor READ lineColor WRITE setLineColor ) -public: - positionLine ( QWidget* parent ); - - // qproperty access functions - bool hasTailGradient () const; - void setHasTailGradient ( const bool g ); - QColor lineColor () const; - void setLineColor ( const QColor & c ); - -public slots: - void zoomChange (double zoom); - -private: - void paintEvent( QPaintEvent* pe ) override; - - bool m_hasTailGradient; - QColor m_lineColor; - -}; - class SongEditor : public TrackContainerView { @@ -156,7 +132,7 @@ private: TextFloat * m_mvsStatus; TextFloat * m_mpsStatus; - positionLine * m_positionLine; + PositionLine * m_positionLine; ComboBoxModel* m_zoomingModel; ComboBoxModel* m_snappingModel; diff --git a/include/StepRecorderWidget.h b/include/StepRecorderWidget.h index 14cfc2eed..67f2a4b65 100644 --- a/include/StepRecorderWidget.h +++ b/include/StepRecorderWidget.h @@ -45,7 +45,9 @@ public: //API used by PianoRoll void setPixelsPerBar(int ppb); void setCurrentPosition(MidiTime currentPosition); + void setMargins(const QMargins &qm); void setBottomMargin(const int marginBottom); + QMargins margins(); //API used by StepRecorder void setStepsLength(MidiTime stepsLength); diff --git a/include/TimeLineWidget.h b/include/TimeLineWidget.h index 5b33bd989..8a9dc5044 100644 --- a/include/TimeLineWidget.h +++ b/include/TimeLineWidget.h @@ -150,6 +150,8 @@ public: update(); } + void setXOffset(const int x); + void addToolButtons(QToolBar* _tool_bar ); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 81e588c66..3c6529e0b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -81,6 +81,7 @@ SET(LMMS_SRCS gui/widgets/NStateButton.cpp gui/widgets/Oscilloscope.cpp gui/widgets/PixmapButton.cpp + gui/widgets/PositionLine.cpp gui/widgets/ProjectNotes.cpp gui/widgets/RenameDialog.cpp gui/widgets/Rubberband.cpp diff --git a/src/gui/TimeLineWidget.cpp b/src/gui/TimeLineWidget.cpp index 8e79410b8..070ac0a40 100644 --- a/src/gui/TimeLineWidget.cpp +++ b/src/gui/TimeLineWidget.cpp @@ -109,6 +109,14 @@ TimeLineWidget::~TimeLineWidget() +void TimeLineWidget::setXOffset(const int x) +{ + m_xOffset = x; + if (s_posMarkerPixmap != nullptr) { m_xOffset -= s_posMarkerPixmap->width() / 2; } +} + + + void TimeLineWidget::addToolButtons( QToolBar * _tool_bar ) { diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index b519fb9e5..3d35c9c51 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #ifndef __USE_XOPEN #define __USE_XOPEN @@ -72,7 +73,7 @@ typedef AutomationPattern::timeMap timeMap; // some constants... const int INITIAL_PIANOROLL_WIDTH = 860; -const int INITIAL_PIANOROLL_HEIGHT = 480; +const int INITIAL_PIANOROLL_HEIGHT = 485; const int SCROLLBAR_SIZE = 12; const int PIANO_X = 0; @@ -86,9 +87,9 @@ 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 = 100; +const int KEY_AREA_MIN_HEIGHT = DEFAULT_KEY_LINE_HEIGHT * 10; const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE; -const int PR_TOP_MARGIN = 16; +const int PR_TOP_MARGIN = 18; const int PR_RIGHT_MARGIN = SCROLLBAR_SIZE; @@ -107,12 +108,6 @@ const int NUM_TRIPLET_LENGTHS = 5; -QPixmap * PianoRoll::s_whiteKeySmallPm = NULL; -QPixmap * PianoRoll::s_whiteKeySmallPressedPm = NULL; -QPixmap * PianoRoll::s_whiteKeyBigPm = NULL; -QPixmap * PianoRoll::s_whiteKeyBigPressedPm = NULL; -QPixmap * PianoRoll::s_blackKeyPm = NULL; -QPixmap * PianoRoll::s_blackKeyPressedPm = NULL; QPixmap * PianoRoll::s_toolDraw = NULL; QPixmap * PianoRoll::s_toolErase = NULL; QPixmap * PianoRoll::s_toolSelect = NULL; @@ -170,14 +165,14 @@ PianoRoll::PianoRoll() : m_mouseDownTick( 0 ), m_lastMouseX( 0 ), m_lastMouseY( 0 ), - m_oldNotesEditHeight( 100 ), m_notesEditHeight( 100 ), + m_userSetNotesEditHeight(100), m_ppb( DEFAULT_PR_PPB ), m_keyLineHeight(DEFAULT_KEY_LINE_HEIGHT), m_octaveHeight(m_keyLineHeight * KeysPerOctave), - m_whiteKeySmallHeight(round(m_keyLineHeight * 1.5)), + m_whiteKeySmallHeight(qFloor(m_keyLineHeight * 1.5)), m_whiteKeyBigHeight(m_keyLineHeight * 2), - m_blackKeyHeight(round(m_keyLineHeight * 1.3333)), + m_blackKeyHeight(m_keyLineHeight), m_lenOfNewNotes( MidiTime( 0, DefaultTicksPerBar/4 ) ), m_lastNoteVolume( DefaultVolume ), m_lastNotePanning( DefaultPanning ), @@ -207,7 +202,9 @@ PianoRoll::PianoRoll() : m_ghostNoteOpacity( 255 ), m_noteBorders( true ), m_ghostNoteBorders( true ), - m_backgroundShade( 0, 0, 0 ) + m_backgroundShade( 0, 0, 0 ), + m_whiteKeyWidth(WHITE_KEY_WIDTH), + m_blackKeyWidth(BLACK_KEY_WIDTH) { // gui names of edit modes m_nemStr.push_back( tr( "Note Velocity" ) ); @@ -252,36 +249,6 @@ PianoRoll::PianoRoll() : m_semiToneMarkerMenu->addAction( copyAllNotesAction ); // init pixmaps - if( s_whiteKeySmallPm == NULL ) - { - s_whiteKeySmallPm = new QPixmap( embed::getIconPixmap( - "pr_white_key_small" ) ); - } - if( s_whiteKeySmallPressedPm == NULL ) - { - s_whiteKeySmallPressedPm = new QPixmap( embed::getIconPixmap( - "pr_white_key_small_pressed" ) ); - } - if( s_whiteKeyBigPm == NULL ) - { - s_whiteKeyBigPm = new QPixmap( embed::getIconPixmap( - "pr_white_key_big" ) ); - } - if( s_whiteKeyBigPressedPm == NULL ) - { - s_whiteKeyBigPressedPm = new QPixmap( embed::getIconPixmap( - "pr_white_key_big_pressed" ) ); - } - if( s_blackKeyPm == NULL ) - { - s_blackKeyPm = new QPixmap( embed::getIconPixmap( - "pr_black_key" ) ); - } - if( s_blackKeyPressedPm == NULL ) - { - s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( - "pr_black_key_pressed" ) ); - } if( s_toolDraw == NULL ) { s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); @@ -312,7 +279,7 @@ PianoRoll::PianoRoll() : setAttribute( Qt::WA_OpaquePaintEvent, true ); // add time-line - m_timeLine = new TimeLineWidget( WHITE_KEY_WIDTH, 0, m_ppb, + m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb, Engine::getSong()->getPlayPos( Song::Mode_PlayPattern ), m_currentPosition, @@ -322,6 +289,9 @@ PianoRoll::PianoRoll() : connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); + // white position line follows timeline marker + m_positionLine = new PositionLine(this); + //update timeline when in step-recording mode connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePositionStepRecording( const MidiTime & ) ) ); @@ -768,8 +738,8 @@ void PianoRoll::hidePattern( Pattern* pattern ) void PianoRoll::selectRegionFromPixels( int xStart, int xEnd ) { - xStart -= WHITE_KEY_WIDTH; - xEnd -= WHITE_KEY_WIDTH; + xStart -= m_whiteKeyWidth; + xEnd -= m_whiteKeyWidth; // select an area of notes int posTicks = xStart * MidiTime::ticksPerBar() / m_ppb + @@ -804,127 +774,6 @@ void PianoRoll::selectRegionFromPixels( int xStart, int xEnd ) - - -/** \brief qproperty access implementation */ - -QColor PianoRoll::barLineColor() const -{ return m_barLineColor; } - -void PianoRoll::setBarLineColor( const QColor & c ) -{ m_barLineColor = c; } - -QColor PianoRoll::beatLineColor() const -{ return m_beatLineColor; } - -void PianoRoll::setBeatLineColor( const QColor & c ) -{ m_beatLineColor = c; } - -QColor PianoRoll::lineColor() const -{ return m_lineColor; } - -void PianoRoll::setLineColor( const QColor & c ) -{ m_lineColor = c; } - -QColor PianoRoll::noteModeColor() const -{ return m_noteModeColor; } - -void PianoRoll::setNoteModeColor( const QColor & c ) -{ m_noteModeColor = c; } - -QColor PianoRoll::noteColor() const -{ return m_noteColor; } - -void PianoRoll::setNoteColor( const QColor & c ) -{ m_noteColor = c; } - -QColor PianoRoll::noteTextColor() const -{ return m_noteTextColor; } - -void PianoRoll::setNoteTextColor( const QColor & c ) -{ m_noteTextColor = c; } - -QColor PianoRoll::barColor() const -{ return m_barColor; } - -void PianoRoll::setBarColor( const QColor & c ) -{ m_barColor = c; } - -QColor PianoRoll::selectedNoteColor() const -{ return m_selectedNoteColor; } - -void PianoRoll::setSelectedNoteColor( const QColor & c ) -{ m_selectedNoteColor = c; } - -QColor PianoRoll::textColor() const -{ return m_textColor; } - -void PianoRoll::setTextColor( const QColor & c ) -{ m_textColor = c; } - -QColor PianoRoll::textColorLight() const -{ return m_textColorLight; } - -void PianoRoll::setTextColorLight( const QColor & c ) -{ m_textColorLight = c; } - -QColor PianoRoll::textShadow() const -{ return m_textShadow; } - -void PianoRoll::setTextShadow( const QColor & c ) -{ m_textShadow = c; } - -QColor PianoRoll::markedSemitoneColor() const -{ return m_markedSemitoneColor; } - -void PianoRoll::setMarkedSemitoneColor( const QColor & c ) -{ m_markedSemitoneColor = c; } - -int PianoRoll::noteOpacity() const -{ return m_noteOpacity; } - -void PianoRoll::setNoteOpacity( const int i ) -{ m_noteOpacity = i; } - -bool PianoRoll::noteBorders() const -{ return m_noteBorders; } - -void PianoRoll::setNoteBorders( const bool b ) -{ m_noteBorders = b; } - -QColor PianoRoll::ghostNoteColor() const -{ return m_ghostNoteColor; } - -void PianoRoll::setGhostNoteColor( const QColor & c ) -{ m_ghostNoteColor = c; } - -QColor PianoRoll::ghostNoteTextColor() const -{ return m_ghostNoteTextColor; } - -void PianoRoll::setGhostNoteTextColor( const QColor & c ) -{ m_ghostNoteTextColor = c; } - -int PianoRoll::ghostNoteOpacity() const -{ return m_ghostNoteOpacity; } - -void PianoRoll::setGhostNoteOpacity( const int i ) -{ m_ghostNoteOpacity = i; } - -bool PianoRoll::ghostNoteBorders() const -{ return m_ghostNoteBorders; } - -void PianoRoll::setGhostNoteBorders( const bool b ) -{ m_ghostNoteBorders = b; } - -QColor PianoRoll::backgroundShade() const -{ return m_backgroundShade; } - -void PianoRoll::setBackgroundShade( const QColor & c ) -{ m_backgroundShade = c; } - - - - void PianoRoll::drawNoteRect( QPainter & p, int x, int y, int width, const Note * n, const QColor & noteCol, const QColor & noteTextColor, const QColor & selCol, const int noteOpc, const bool borders, bool drawNoteName ) @@ -1039,9 +888,11 @@ void PianoRoll::drawDetuningInfo( QPainter & _p, const Note * _n, int _x, int _y ) const { int middle_y = _y + m_keyLineHeight / 2; - _p.setPen( noteColor() ); - _p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN, - width() - WHITE_KEY_WIDTH, + _p.setPen(m_noteColor); + _p.setClipRect( + m_whiteKeyWidth, + PR_TOP_MARGIN, + width() - m_whiteKeyWidth, keyAreaBottom() - PR_TOP_MARGIN); int old_x = 0; @@ -1449,8 +1300,7 @@ void PianoRoll::leaveEvent(QEvent * e ) int PianoRoll::noteEditTop() const { - return height() - PR_BOTTOM_MARGIN - - m_notesEditHeight + NOTE_EDIT_RESIZE_BAR; + return keyAreaBottom() + NOTE_EDIT_RESIZE_BAR; } @@ -1474,7 +1324,7 @@ int PianoRoll::noteEditRight() const int PianoRoll::noteEditLeft() const { - return WHITE_KEY_WIDTH; + return m_whiteKeyWidth; } @@ -1539,11 +1389,11 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) m_moveStartY = me->y(); } - if( me->y() > keyAreaBottom() && me->y() < noteEditTop() ) + if(me->button() == Qt::LeftButton && + me->y() > keyAreaBottom() && me->y() < noteEditTop()) { // resizing the note edit area m_action = ActionResizeNoteEditArea; - m_oldNotesEditHeight = m_notesEditHeight; return; } @@ -1556,11 +1406,11 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) int x = me->x(); - if( x > WHITE_KEY_WIDTH ) + if (x > m_whiteKeyWidth) { // set, move or resize note - x -= WHITE_KEY_WIDTH; + x -= m_whiteKeyWidth; // get tick in which the user clicked int pos_ticks = x * MidiTime::ticksPerBar() / m_ppb + @@ -1827,7 +1677,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me ) else if( me->buttons() == Qt::LeftButton ) { // left click - play the note - int v = ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity; + int v = ((float) x) / ((float) m_whiteKeyWidth) * MidiDefaultVelocity; m_pattern->instrumentTrack()->pianoModel()->handleKeyPress(key_num, v); // if a chord is set, play the chords notes as well: playChordNotes(key_num, v); @@ -1870,7 +1720,7 @@ void PianoRoll::mouseDoubleClickEvent(QMouseEvent * me ) { // get values for going through notes int pixel_range = 4; - int x = me->x() - WHITE_KEY_WIDTH; + int x = me->x() - m_whiteKeyWidth; const int ticks_start = ( x-pixel_range/2 ) * MidiTime::ticksPerBar() / m_ppb + m_currentPosition; const int ticks_end = ( x+pixel_range/2 ) * @@ -2203,14 +2053,23 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) } else if( m_action == ActionResizeNoteEditArea ) { + // Don't try to show more keys than the full keyboard, bail if trying to + if (m_pianoKeysVisible == NumKeys && me->y() > m_moveStartY) + { + return; + } + int newHeight = height() - me->y(); + if (me->y() < KEY_AREA_MIN_HEIGHT) + { + newHeight = height() - KEY_AREA_MIN_HEIGHT - + PR_TOP_MARGIN - PR_BOTTOM_MARGIN; // - NOTE_EDIT_RESIZE_BAR + } // change m_notesEditHeight and then repaint - m_notesEditHeight = qBound( - NOTE_EDIT_MIN_HEIGHT, - m_oldNotesEditHeight - ( me->y() - m_moveStartY ), - height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR - - PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT ); - + m_notesEditHeight = qMax(NOTE_EDIT_MIN_HEIGHT, newHeight); + m_userSetNotesEditHeight = m_notesEditHeight; m_stepRecorderWidget.setBottomMargin(PR_BOTTOM_MARGIN + m_notesEditHeight); + updateScrollbars(); + updatePositionLineHeight(); repaint(); return; } @@ -2225,17 +2084,17 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) int x = me->x(); // see if they clicked on the keyboard on the left - if( x < WHITE_KEY_WIDTH && m_action == ActionNone + if (x < m_whiteKeyWidth && m_action == ActionNone && ! edit_note && key_num != m_lastKey && me->buttons() & Qt::LeftButton ) { // clicked on a key, play the note - testPlayKey( key_num, ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity, 0 ); + testPlayKey(key_num, ((float) x) / ((float) m_whiteKeyWidth) * MidiDefaultVelocity, 0); update(); return; } - x -= WHITE_KEY_WIDTH; + x -= m_whiteKeyWidth; if( me->buttons() & Qt::LeftButton && m_editMode == ModeDraw @@ -2508,12 +2367,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) m_action == ActionSelectNotes ) { - int x = me->x() - WHITE_KEY_WIDTH; + int x = me->x() - m_whiteKeyWidth; if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( - WHITE_KEY_WIDTH, + m_whiteKeyWidth, me->y() ) ) ); if( m_currentPosition >= 4 ) { @@ -2525,9 +2384,9 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me ) m_leftRightScroll->setValue( 0 ); } } - else if( x > width() - WHITE_KEY_WIDTH ) + else if (x > width() - m_whiteKeyWidth) { - x = width() - WHITE_KEY_WIDTH; + x = width() - m_whiteKeyWidth; QCursor::setPos( mapToGlobal( QPoint( width(), me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + @@ -2774,11 +2633,8 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl ) Engine::getSong()->setModified(); } -int PianoRoll::xCoordOfTick( int tick ) -{ - return WHITE_KEY_WIDTH + ( ( tick - m_currentPosition ) - * m_ppb / MidiTime::ticksPerBar() ); -} + + void PianoRoll::paintEvent(QPaintEvent * pe ) { @@ -2794,352 +2650,336 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) // fill with bg color p.fillRect( 0, 0, width(), height(), bgColor ); - // set font-size to 8 - p.setFont( pointSize<8>( p.font() ) ); + // set font-size to 80% of key line height + QFont f = p.font(); + f.setPixelSize(m_keyLineHeight * 0.8); + p.setFont(f); // font size doesn't change without this for some reason QFontMetrics fontMetrics(p.font()); - QRect const boundingRect = fontMetrics.boundingRect(QChar::fromLatin1('H')); - // This is two times of the y coordinate of the center of the bounding rectangle - // (-(top+bottom)=-2(center)) but labelHeight is more intuitive/describing name - int const labelHeight = - boundingRect.top() - boundingRect.bottom(); + // G4 is one of the widest + QRect const boundingRect = fontMetrics.boundingRect(QString("G4")); - // y_offset is used to align the piano-keys on the key-lines - int y_offset = 0; + // 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 - // calculate y_offset according to first key - switch( prKeyOrder[m_startKey % KeysPerOctave] ) + if (hasValidPattern()) { - case PR_BLACK_KEY: y_offset = m_keyLineHeight / 4; break; - case PR_WHITE_KEY_BIG: y_offset = m_keyLineHeight / 2; break; + int pianoAreaHeight, partialKeyVisible, topKey, topNote; + pianoAreaHeight = keyAreaBottom() - keyAreaTop(); + m_pianoKeysVisible = pianoAreaHeight / m_keyLineHeight; + 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 modifiy 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; + } + topKey = qBound(0, m_startKey + m_pianoKeysVisible - 1, NumKeys - 1); + topNote = topKey % KeysPerOctave; + // if not resizing the note edit area, we can change m_notesEditHeight + if (m_action != ActionResizeNoteEditArea && 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; } + } + updatePositionLineHeight(); + 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; } + } + auto xCoordOfTick = [=](int tick) { + return m_whiteKeyWidth + ( + (tick - m_currentPosition) * m_ppb / MidiTime::ticksPerBar() + ); + }; + 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 PR_WHITE_KEY_BIG: + return m_whiteKeyBigHeight; + case PR_WHITE_KEY_SMALL: + return m_whiteKeySmallHeight; + case PR_BLACK_KEY: + 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 PR_WHITE_KEY_BIG: + return m_whiteKeySmallHeight; + case PR_WHITE_KEY_SMALL: + // These two keys need to adjust up small height instead of only key line height + if (keyCode == Key_C || keyCode == Key_F) + { + return m_whiteKeySmallHeight; + } + case PR_BLACK_KEY: + return m_blackKeyHeight; + } + return 0; // should never happen + }; + auto keyWidth = [&]( + const int key + ) -> int + { + switch (prKeyOrder[key % KeysPerOctave]) + { + case PR_WHITE_KEY_SMALL: + case PR_WHITE_KEY_BIG: + return m_whiteKeyWidth; + case PR_BLACK_KEY: + return m_blackKeyWidth; + } + return 0; // should never happen + }; + // lambda function to draw a key + auto drawKey = [&]( + const int key, + const int yb) + { + const bool pressed = m_pattern->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 PR_WHITE_KEY_SMALL: + case PR_WHITE_KEY_BIG: + p.setBrush(pressed ? m_whiteKeyActiveBackground : m_whiteKeyInactiveBackground); + break; + case PR_BLACK_KEY: + p.setBrush(pressed ? m_blackKeyActiveBackground : m_blackKeyInactiveBackground); + } + // draw key + p.drawRect(PIANO_X, yt, kw, kh); + // draw note name + if (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 (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 (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 PR_WHITE_KEY_SMALL: - if( prKeyOrder[( ( m_startKey + 1 ) % - KeysPerOctave)] != PR_BLACK_KEY ) - { - y_offset = m_keyLineHeight / 2; - } + case PR_WHITE_KEY_BIG: break; - } - // start drawing at the bottom - int key_line_y = qMin(keyAreaBottom() - 1, m_keyLineHeight * NumKeys); - // we need to set m_notesEditHeight here because it needs to fill in the - // rest of the window if key_line_y is bound to m_keyLineHeight * NumKeys - if (key_line_y == m_keyLineHeight * NumKeys) { - m_notesEditHeight = height() - (PR_TOP_MARGIN + m_keyLineHeight * NumKeys); - } - // used for aligning black-keys later - int first_white_key_height = m_whiteKeySmallHeight; - // key-counter - only needed for finding out whether the processed - // key is the first one - int keys_processed = 0; - - int key = m_startKey; - - // draw all white keys... - for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN; - key_line_y -= m_keyLineHeight, ++keys_processed ) - { - // check for white key that is only half visible on the - // bottom of piano-roll - if( keys_processed == 0 && - prKeyOrder[m_startKey % KeysPerOctave] == - PR_BLACK_KEY ) - { - // draw it! - p.drawPixmap( PIANO_X, y - m_whiteKeySmallHeight, WHITE_KEY_WIDTH, m_whiteKeySmallHeight, - *s_whiteKeySmallPm ); - // update y-pos - y -= m_whiteKeySmallHeight / 2; - // move first black key down (we didn't draw whole - // white key so black key needs to be lifted down) - // (default for first_white_key_height = - // m_whiteKeySmallHeight, so m_whiteKeySmallHeight/2 - // is smaller) - first_white_key_height = m_whiteKeySmallHeight / 2; + case PR_BLACK_KEY: + // draw extra white key + drawKey(topKey + 1, grid_line_y - m_keyLineHeight); } - // check whether to draw a big or a small white key - if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_SMALL ) + // loop through visible keys + const int lastKey = qMax(0, topKey - m_pianoKeysVisible); + for (int key = topKey; key > lastKey; --key) { - // draw a small one while checking if it is pressed or not - if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) + bool whiteKey = Piano::isWhiteKey(key); + if (whiteKey) { - p.drawPixmap(PIANO_X, y - m_whiteKeySmallHeight, WHITE_KEY_WIDTH, m_whiteKeySmallHeight, - *s_whiteKeySmallPressedPm); + drawKey(key, grid_line_y); + drawHorizontalLine(key, grid_line_y); + grid_line_y += m_keyLineHeight; } else { - p.drawPixmap(PIANO_X, y - m_whiteKeySmallHeight, WHITE_KEY_WIDTH, m_whiteKeySmallHeight, - *s_whiteKeySmallPm); + // 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; } - // update y-pos - y -= m_whiteKeySmallHeight; - } - else if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_BIG ) + + // 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 / MidiTime::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) { - // draw a big one while checking if it is pressed or not - if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) + if ((barCount + leftBars) % 2 != 0) { - p.drawPixmap(PIANO_X, y - m_whiteKeyBigHeight, WHITE_KEY_WIDTH, m_whiteKeyBigHeight, - *s_whiteKeyBigPressedPm); + p.fillRect(x - m_currentPosition * zoomFactor / timeSignature, + PR_TOP_MARGIN, + m_ppb, + height() - (PR_BOTTOM_MARGIN + PR_TOP_MARGIN), + m_backgroundShade); } - else - { - p.drawPixmap(PIANO_X, y-m_whiteKeyBigHeight, WHITE_KEY_WIDTH, m_whiteKeyBigHeight, - *s_whiteKeyBigPm); - } - // if a big white key has been the first key, - // black keys needs to be lifted up - if( keys_processed == 0 ) - { - first_white_key_height = m_whiteKeyBigHeight; - } - // update y-pos - y -= m_whiteKeyBigHeight; } - // Compute the corrections for the note names - int yCorrectionForNoteLabels = 0; - - int keyCode = key % KeysPerOctave; - switch (keyCode) + // 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)) { - case 0: // C - case 5: // F - yCorrectionForNoteLabels = (m_whiteKeySmallHeight - labelHeight + 1) / -2; - break; - case 2: // D - case 7: // G - case 9: // A - yCorrectionForNoteLabels = (m_whiteKeyBigHeight / 2 - labelHeight + 1) / -2; - break; - case 4: // E - case 11: // B - // calculate center point of key and move half of text - yCorrectionForNoteLabels = -(((m_whiteKeySmallHeight - (m_whiteKeySmallHeight * 2 + 3) / 6) / 4) - - labelHeight / 2); - break; + p.drawLine(x, PR_TOP_MARGIN, x, noteEditBottom()); } - if( Piano::isWhiteKey( key ) ) + // draw vertical bar lines + p.setPen(m_barLineColor); + for(tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerBar(), + x = xCoordOfTick( tick ); + x <= width(); + tick += MidiTime::ticksPerBar(), x = xCoordOfTick(tick)) { - // Draw note names if activated in the preferences, C notes are always drawn - if ( (key % 12 == 0 || drawNoteNames) && m_keyLineHeight > 10 ) - { - QString noteString = getNoteString( key ); - - QPoint textStart( WHITE_KEY_WIDTH - 18, key_line_y ); - textStart += QPoint( 0, yCorrectionForNoteLabels ); - - p.setPen( textShadow() ); - p.drawText( textStart + QPoint( 1, 1 ), noteString ); - // The C key is painted darker than the other ones - if ( key % 12 == 0 ) - { - p.setPen( textColor() ); - } - else - { - p.setPen( textColorLight() ); - } - p.drawText( textStart, noteString ); - } + 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 = keyAreaBottom() + 5 - m_keyLineHeight * + (key_num - m_startKey + 1); + if(y > keyAreaBottom()) { break; } + p.fillRect(m_whiteKeyWidth + 1, + y - m_keyLineHeight / 2, + width() - 10, + m_keyLineHeight + 1, + m_markedSemitoneColor); } - ++key; - } - - // reset all values, because now we're going to draw all black keys - key = m_startKey; - keys_processed = 0; - int white_cnt = 0; - key_line_y = qMin(keyAreaBottom(), m_keyLineHeight * NumKeys); - - // and go! - for( int y = key_line_y + y_offset; - y > PR_TOP_MARGIN; ++keys_processed ) - { - // check for black key that is only half visible on the bottom - // of piano-roll - if( keys_processed == 0 - // current key may not be a black one - && prKeyOrder[key % KeysPerOctave] != PR_BLACK_KEY - // but the previous one must be black (we must check this - // because there might be two white keys (E-F) - && prKeyOrder[( key - 1 ) % KeysPerOctave] == - PR_BLACK_KEY ) - { - // draw the black key! - p.drawPixmap( PIANO_X, y - m_blackKeyHeight / 2, BLACK_KEY_WIDTH, m_blackKeyHeight, - *s_blackKeyPm ); - // is the one after the start-note a black key?? - if( prKeyOrder[( key + 1 ) % KeysPerOctave] != - PR_BLACK_KEY ) - { - // no, then move it up! - y -= m_keyLineHeight / 2; - } - } - // current key black? - if( prKeyOrder[key % KeysPerOctave] == PR_BLACK_KEY) - { - // then draw it (calculation of y very complicated, - // but that's the only working solution, sorry...) - // check if the key is pressed or not - if( hasValidPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) - { - p.drawPixmap( PIANO_X, y - ( first_white_key_height - - m_whiteKeySmallHeight ) - - m_whiteKeySmallHeight/2 - 1 - - m_blackKeyHeight, BLACK_KEY_WIDTH, m_blackKeyHeight, *s_blackKeyPressedPm ); - } - else - { - p.drawPixmap( PIANO_X, y - ( first_white_key_height - - m_whiteKeySmallHeight ) - - m_whiteKeySmallHeight/2 - 1 - - m_blackKeyHeight, BLACK_KEY_WIDTH, m_blackKeyHeight, *s_blackKeyPm ); - } - // update y-pos - y -= m_whiteKeyBigHeight; - // reset white-counter - white_cnt = 0; - } - else - { - // simple workaround for increasing x if there were - // two white keys (e.g. between E and F) - ++white_cnt; - if( white_cnt > 1 ) - { - y -= m_whiteKeyBigHeight/2; - } - } - - ++key; } + // reset 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, key_line_y, - WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ), bgColor ); + p.fillRect( QRect( 0, keyAreaBottom(), + m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), bgColor); // display note editing info - QFont f = p.font(); + //QFont f = p.font(); f.setBold( false ); p.setFont( pointSize<10>( f ) ); - p.setPen( noteModeColor() ); - p.drawText( QRect( 0, key_line_y, - WHITE_KEY_WIDTH, noteEditBottom() - key_line_y ), + p.setPen(m_noteModeColor); + p.drawText( QRect( 0, keyAreaBottom(), + m_whiteKeyWidth, noteEditBottom() - keyAreaBottom()), Qt::AlignCenter | Qt::TextWordWrap, m_nemStr.at( m_noteEditMode ) + ":" ); // set clipping area, because we are not allowed to paint over // keyboard... - p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, - width() - WHITE_KEY_WIDTH, - height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN ); - - // draw the grid - if( hasValidPattern() ) - { - int q, x, tick; - - if( m_zoomingModel.value() > 3 ) - { - // If we're over 100% zoom, we allow all quantization level grids - q = quantization(); - } - else if( quantization() % 3 != 0 ) - { - // If we're under 100% zoom, we allow quantization grid up to 1/24 for triplets - // to ensure a dense doesn't fill out the background - q = quantization() < 8 ? 8 : quantization(); - } - else { - // If we're under 100% zoom, we allow quantization grid up to 1/32 for normal notes - q = quantization() < 6 ? 6 : quantization(); - } - - // First we draw the vertical quantization lines - for( tick = m_currentPosition - m_currentPosition % q, x = xCoordOfTick( tick ); - x <= width(); tick += q, x = xCoordOfTick( tick ) ) - { - p.setPen( lineColor() ); - p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN ); - } - - // Draw horizontal lines - key = m_startKey; - for( int y = key_line_y - 1; y > PR_TOP_MARGIN; - y -= m_keyLineHeight ) - { - if( static_cast( key % KeysPerOctave ) == Key_C ) - { - // C note gets accented - p.setPen( beatLineColor() ); - } - else - { - p.setPen( lineColor() ); - } - p.drawLine( WHITE_KEY_WIDTH, y, width(), y ); - ++key; - } - - - // Draw alternating shades 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 / MidiTime::ticksPerBar(); - - //iterates the visible bars and draw the shading on uneven bars - for( int x = WHITE_KEY_WIDTH, 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 ), backgroundShade() ); - } - } - - // Draw the vertical beat lines - int ticksPerBeat = DefaultTicksPerBar / - Engine::getSong()->getTimeSigModel().getDenominator(); - - for( tick = m_currentPosition - m_currentPosition % ticksPerBeat, - x = xCoordOfTick( tick ); x <= width(); - tick += ticksPerBeat, x = xCoordOfTick( tick ) ) - { - p.setPen( beatLineColor() ); - p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN ); - } - - // Draw the vertical bar lines - for( tick = m_currentPosition - m_currentPosition % MidiTime::ticksPerBar(), - x = xCoordOfTick( tick ); x <= width(); - tick += MidiTime::ticksPerBar(), x = xCoordOfTick( tick ) ) - { - p.setPen( barLineColor() ); - p.drawLine( x, PR_TOP_MARGIN, x, height() - PR_BOTTOM_MARGIN ); - } - - // draw marked semitones after the grid - for( int i = 0; i < m_markedSemiTones.size(); i++ ) - { - const int key_num = m_markedSemiTones.at( i ); - const int y = key_line_y + 5 - - m_keyLineHeight * ( key_num - m_startKey + 1 ); - - if( y > key_line_y ) - { - break; - } - - p.fillRect( WHITE_KEY_WIDTH + 1, y - m_keyLineHeight / 2, width() - 10, m_keyLineHeight + 1, - markedSemitoneColor() ); - } - } + 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) @@ -3159,15 +2999,17 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) qSwap( sel_key_start, sel_key_end ); } - int y_base = key_line_y - 1; + int y_base = keyAreaBottom() - 1; if( hasValidPattern() ) { - p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, - width() - WHITE_KEY_WIDTH, - height() - PR_TOP_MARGIN ); + p.setClipRect( + m_whiteKeyWidth, + PR_TOP_MARGIN, + width() - m_whiteKeyWidth, + height() - PR_TOP_MARGIN); - const int visible_keys = ( key_line_y-keyAreaTop() ) / - m_keyLineHeight + 2; + const int topKey = qBound(0, m_startKey + m_pianoKeysVisible - 1, NumKeys - 1); + const int bottomKey = topKey - m_pianoKeysVisible; QPolygonF editHandles; @@ -3194,21 +3036,20 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int x = ( pos_ticks - m_currentPosition ) * m_ppb / MidiTime::ticksPerBar(); // skip this note if not in visible area at all - if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) + if (!(x + note_width >= 0 && x <= width() - m_whiteKeyWidth)) { continue; } // is the note in visible area? - if( key > 0 && key <= visible_keys ) + if (note->key() > bottomKey && note->key() <= topKey) { - // we've done and checked all, let's draw the - // note - drawNoteRect( p, x + WHITE_KEY_WIDTH, - y_base - key * m_keyLineHeight, - note_width, note, ghostNoteColor(), ghostNoteTextColor(), selectedNoteColor(), - ghostNoteOpacity(), ghostNoteBorders(), drawNoteNames ); + // we've done and checked all, let's draw the note + drawNoteRect( + p, x + m_whiteKeyWidth, y_base - key * m_keyLineHeight, note_width, + note, m_ghostNoteColor, m_ghostNoteTextColor, m_selectedNoteColor, + m_ghostNoteOpacity, m_ghostNoteBorders, drawNoteNames); } } @@ -3236,31 +3077,30 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int x = ( pos_ticks - m_currentPosition ) * m_ppb / MidiTime::ticksPerBar(); // skip this note if not in visible area at all - if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) + if (!(x + note_width >= 0 && x <= width() - m_whiteKeyWidth)) { continue; } // is the note in visible area? - if( key > 0 && key <= visible_keys ) + if (note->key() > bottomKey && note->key() <= topKey) { - // we've done and checked all, let's draw the - // note - drawNoteRect( p, x + WHITE_KEY_WIDTH, - y_base - key * m_keyLineHeight, - note_width, note, noteColor(), noteTextColor(), selectedNoteColor(), - noteOpacity(), noteBorders(), drawNoteNames ); + // we've done and checked all, let's draw the note + drawNoteRect( + p, x + m_whiteKeyWidth, y_base - key * m_keyLineHeight, note_width, + note, m_noteColor, m_noteTextColor, m_selectedNoteColor, + m_noteOpacity, m_noteBorders, drawNoteNames); } // draw note editing stuff int editHandleTop = 0; if( m_noteEditMode == NoteEditVolume ) { - QColor color = barColor().lighter( 30 + ( note->getVolume() * 90 / MaxVolume ) ); + QColor color = m_barColor.lighter(30 + (note->getVolume() * 90 / MaxVolume)); if( note->selected() ) { - color = selectedNoteColor(); + color = m_selectedNoteColor; } p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) ); @@ -3275,10 +3115,10 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } else if( m_noteEditMode == NoteEditPanning ) { - QColor color = noteColor(); + QColor color = m_noteColor; if( note->selected() ) { - color = selectedNoteColor(); + color = m_selectedNoteColor; } p.setPen( QPen( color, NOTE_EDIT_LINE_WIDTH ) ); @@ -3297,11 +3137,11 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) if( note->hasDetuningInfo() ) { - drawDetuningInfo( p, note, - x + WHITE_KEY_WIDTH, - y_base - key * m_keyLineHeight ); - p.setClipRect(WHITE_KEY_WIDTH, PR_TOP_MARGIN, - width() - WHITE_KEY_WIDTH, + drawDetuningInfo(p, note, x + m_whiteKeyWidth, y_base - key * m_keyLineHeight); + p.setClipRect( + m_whiteKeyWidth, + PR_TOP_MARGIN, + width() - m_whiteKeyWidth, height() - PR_TOP_MARGIN); } } @@ -3324,24 +3164,24 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) const int x = ( pos_ticks - m_currentPosition ) * m_ppb / MidiTime::ticksPerBar(); // skip this note if not in visible area at all - if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) + if (!(x + note_width >= 0 && x <= width() - m_whiteKeyWidth)) { continue; } // is the note in visible area? - if( key > 0 && key <= visible_keys ) + if (note->key() > bottomKey && note->key() <= topKey) { // we've done and checked all, let's draw the note - drawNoteRect( p, x + WHITE_KEY_WIDTH, - y_base - key * m_keyLineHeight, - note_width, note, m_stepRecorder.curStepNoteColor(), noteTextColor(), selectedNoteColor(), - noteOpacity(), noteBorders(), drawNoteNames ); + drawNoteRect( + p, x + m_whiteKeyWidth, y_base - key * m_keyLineHeight, note_width, + note, m_stepRecorder.curStepNoteColor(), m_noteTextColor, m_selectedNoteColor, + m_noteOpacity, m_noteBorders, drawNoteNames); } } - p.setPen( QPen( noteColor(), NOTE_EDIT_LINE_WIDTH + 2 ) ); + p.setPen(QPen(m_noteColor, NOTE_EDIT_LINE_WIDTH + 2)); p.drawPoints( editHandles ); } @@ -3352,14 +3192,16 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) p.setFont( pointSize<14>( f ) ); p.setPen( QApplication::palette().color( QPalette::Active, QPalette::BrightText ) ); - p.drawText( WHITE_KEY_WIDTH + 20, PR_TOP_MARGIN + 40, + p.drawText(m_whiteKeyWidth + 20, PR_TOP_MARGIN + 40, tr( "Please open a pattern by double-clicking " "on it!" ) ); } - p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN - - m_notesEditHeight - PR_BOTTOM_MARGIN ); + 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 ) / @@ -3368,9 +3210,9 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) MidiTime::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( selectedNoteColor() ); + p.setPen(m_selectedNoteColor); p.setBrush( Qt::NoBrush ); - p.drawRect( x + WHITE_KEY_WIDTH, y, w, h ); + p.drawRect(x + m_whiteKeyWidth, y, w, h); // TODO: Get this out of paint event int l = ( hasValidPattern() )? (int) m_pattern->length() : 0; @@ -3383,70 +3225,92 @@ void PianoRoll::paintEvent(QPaintEvent * pe ) } // set line colors - QColor editAreaCol = QColor( lineColor() ); - QColor currentKeyCol = QColor( beatLineColor() ); + QColor editAreaCol = QColor(m_lineColor); + QColor currentKeyCol = QColor(m_beatLineColor); editAreaCol.setAlpha( 64 ); currentKeyCol.setAlpha( 64 ); // horizontal line for the key under the cursor - if( hasValidPattern() ) + if(hasValidPattern() && gui->pianoRoll()->hasFocus()) { int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() ); - p.fillRect( 10, key_line_y + 3 - m_keyLineHeight * + p.fillRect( 10, keyAreaBottom() + 3 - m_keyLineHeight * ( key_num - m_startKey + 1 ), width() - 10, m_keyLineHeight - 7, currentKeyCol ); } // bar to resize note edit area p.setClipRect( 0, 0, width(), height() ); - p.fillRect( QRect( 0, key_line_y, + p.fillRect( QRect( 0, keyAreaBottom(), width()-PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR ), editAreaCol ); - const QPixmap * cursor = NULL; - // draw current edit-mode-icon below the cursor - switch( m_editMode ) + if (gui->pianoRoll()->hasFocus()) { - case ModeDraw: - if( m_mouseDownRight ) - { - cursor = s_toolErase; - } - else if( m_action == ActionMoveNote ) - { - cursor = s_toolMove; - } - else - { - cursor = s_toolDraw; - } - break; - case ModeErase: cursor = s_toolErase; break; - case ModeSelect: cursor = s_toolSelect; break; - case ModeEditDetuning: cursor = s_toolOpen; break; - } - QPoint mousePosition = mapFromGlobal( QCursor::pos() ); - if( cursor != NULL && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft()) - { - p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor ); + const QPixmap * cursor = NULL; + // draw current edit-mode-icon below the cursor + switch( m_editMode ) + { + case ModeDraw: + if( m_mouseDownRight ) + { + cursor = s_toolErase; + } + else if( m_action == ActionMoveNote ) + { + cursor = s_toolMove; + } + else + { + cursor = s_toolDraw; + } + break; + case ModeErase: cursor = s_toolErase; break; + case ModeSelect: cursor = s_toolSelect; break; + case ModeEditDetuning: cursor = s_toolOpen; break; + } + QPoint mousePosition = mapFromGlobal( QCursor::pos() ); + if( cursor != NULL && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft()) + { + p.drawPixmap( mousePosition + QPoint( 8, 8 ), *cursor ); + } } } +void PianoRoll::updateScrollbars() +{ + m_leftRightScroll->setGeometry( + m_whiteKeyWidth, + height() - SCROLLBAR_SIZE, + width() - m_whiteKeyWidth, + SCROLLBAR_SIZE + ); + m_topBottomScroll->setGeometry( + width() - SCROLLBAR_SIZE, + PR_TOP_MARGIN, + SCROLLBAR_SIZE, + height() - PR_TOP_MARGIN - SCROLLBAR_SIZE + ); + int pianoAreaHeight = keyAreaBottom() - PR_TOP_MARGIN; + int numKeysVisible = pianoAreaHeight / m_keyLineHeight; + m_totalKeysToScroll = qMax(0, NumKeys - numKeysVisible); + m_topBottomScroll->setRange(0, m_totalKeysToScroll); + if (m_startKey > m_totalKeysToScroll) + { + m_startKey = qMax(0, m_totalKeysToScroll); + } + m_topBottomScroll->setValue(m_totalKeysToScroll - m_startKey); +} + // responsible for moving/resizing scrollbars after window-resizing void PianoRoll::resizeEvent(QResizeEvent * re) { - m_leftRightScroll->setGeometry( WHITE_KEY_WIDTH, - height() - - SCROLLBAR_SIZE, - width()-WHITE_KEY_WIDTH, - SCROLLBAR_SIZE ); - updateYScroll(); - - Engine::getSong()->getPlayPos( Song::Mode_PlayPattern - ).m_timeLine->setFixedWidth( width() ); - + updatePositionLineHeight(); + updateScrollbars(); + Engine::getSong()->getPlayPos(Song::Mode_PlayPattern) + .m_timeLine->setFixedWidth(width()); update(); } @@ -3463,7 +3327,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) if (!hasValidPattern()) {return;} // get values for going through notes int pixel_range = 8; - int x = we->x() - WHITE_KEY_WIDTH; + int x = we->x() - m_whiteKeyWidth; int ticks_start = ( x - pixel_range / 2 ) * MidiTime::ticksPerBar() / m_ppb + m_currentPosition; int ticks_end = ( x + pixel_range / 2 ) * @@ -3568,7 +3432,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we ) } z = qBound( 0, z, m_zoomingModel.size() - 1 ); - int x = (we->x() - WHITE_KEY_WIDTH)* MidiTime::ticksPerBar(); + int x = (we->x() - m_whiteKeyWidth)* MidiTime::ticksPerBar(); // ticks based on the mouse x-position where the scroll wheel was used int ticks = x / m_ppb; // what would be the ticks in the new zoom level on the very same mouse x @@ -3619,24 +3483,16 @@ void PianoRoll::focusInEvent( QFocusEvent * ) -int PianoRoll::getKey(int y ) const +int PianoRoll::getKey(int y) const { - int key_line_y = keyAreaBottom() - 1; - // pressed key on piano - int key_num = ( key_line_y - y ) / m_keyLineHeight; - key_num += m_startKey; - - // some range-checking-stuff - if( key_num < 0 ) - { - key_num = 0; - } - - if( key_num >= KeysPerOctave * NumOctaves ) - { - key_num = KeysPerOctave * NumOctaves - 1; - } - + // handle case that very top pixel maps to next key above + if (y - keyAreaTop() <= 1) { y = keyAreaTop() + 2; } + int key_num = qBound( + 0, + // add + 1 to stay within the grid lines + ((keyAreaBottom() - y + 1) / m_keyLineHeight) + m_startKey, + NumKeys - 1 + ); return key_num; } @@ -3854,7 +3710,7 @@ void PianoRoll::horScrolled(int new_pos ) void PianoRoll::verScrolled( int new_pos ) { // revert value - m_startKey = m_totalKeysToScroll - new_pos; + m_startKey = qMax(0, m_totalKeysToScroll - new_pos); update(); } @@ -4008,13 +3864,13 @@ void PianoRoll::updateYScroll() int total_pixels = m_octaveHeight * NumOctaves - (height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight); - m_totalKeysToScroll = total_pixels * KeysPerOctave / m_octaveHeight; + m_totalKeysToScroll = qMax(0, total_pixels * KeysPerOctave / m_octaveHeight); m_topBottomScroll->setRange(0, m_totalKeysToScroll); if(m_startKey > m_totalKeysToScroll) { - m_startKey = m_totalKeysToScroll; + m_startKey = qMax(0, m_totalKeysToScroll); } m_topBottomScroll->setValue(m_totalKeysToScroll - m_startKey); } @@ -4161,7 +4017,7 @@ bool PianoRoll::deleteSelectedNotes() void PianoRoll::autoScroll( const MidiTime & t ) { - const int w = width() - WHITE_KEY_WIDTH; + const int w = width() - m_whiteKeyWidth; if( t > m_currentPosition + w * MidiTime::ticksPerBar() / m_ppb ) { m_leftRightScroll->setValue( t.getBar() * MidiTime::ticksPerBar() ); @@ -4187,6 +4043,22 @@ void PianoRoll::updatePosition( const MidiTime & t ) { autoScroll( t ); } + const int pos = m_timeLine->pos() * m_ppb / MidiTime::ticksPerBar(); + if (pos >= m_currentPosition && pos <= m_currentPosition + width() - m_whiteKeyWidth) + { + m_positionLine->show(); + m_positionLine->move(pos - (m_positionLine->width() - 1) - m_currentPosition + m_whiteKeyWidth, keyAreaTop()); + } + else + { + m_positionLine->hide(); + } +} + + +void PianoRoll::updatePositionLineHeight() +{ + m_positionLine->setFixedHeight(keyAreaBottom() - keyAreaTop()); } @@ -4230,6 +4102,7 @@ void PianoRoll::zoomingChanged() m_timeLine->setPixelsPerBar( m_ppb ); m_stepRecorderWidget.setPixelsPerBar( m_ppb ); + m_positionLine->zoomChange(m_zoomLevels[m_zoomingModel.value()]); update(); } @@ -4239,9 +4112,9 @@ void PianoRoll::zoomingYChanged() { m_keyLineHeight = m_zoomYLevels[m_zoomingYModel.value()] * DEFAULT_KEY_LINE_HEIGHT; m_octaveHeight = m_keyLineHeight * KeysPerOctave; - m_whiteKeySmallHeight = round(m_keyLineHeight * 1.5); + m_whiteKeySmallHeight = qFloor(m_keyLineHeight * 1.5); m_whiteKeyBigHeight = m_keyLineHeight * 2; - m_blackKeyHeight = round(m_keyLineHeight * 1.3333); + m_blackKeyHeight = m_keyLineHeight; //round(m_keyLineHeight * 1.3333); updateYScroll(); update(); @@ -4360,7 +4233,7 @@ Note * PianoRoll::noteUnderMouse() { QPoint pos = mapFromGlobal( QCursor::pos() ); - if( pos.x() <= WHITE_KEY_WIDTH + if (pos.x() <= m_whiteKeyWidth || pos.x() > width() - SCROLLBAR_SIZE || pos.y() < PR_TOP_MARGIN || pos.y() > keyAreaBottom() ) @@ -4369,7 +4242,7 @@ Note * PianoRoll::noteUnderMouse() } int key_num = getKey( pos.y() ); - int pos_ticks = ( pos.x() - WHITE_KEY_WIDTH ) * + int pos_ticks = (pos.x() - m_whiteKeyWidth) * MidiTime::ticksPerBar() / m_ppb + m_currentPosition; // loop through whole note-vector... @@ -4721,6 +4594,14 @@ void PianoRollWindow::loadSettings( const QDomElement & de ) m_editor->loadMarkedSemiTones(de.firstChildElement("markedSemiTones")); MainWindow::restoreWidgetState( this, de ); + + // update margins here because we're later in the startup process + // We can't earlier because everything is still starting with the + // WHITE_KEY_WIDTH default + QMargins qm = m_editor->m_stepRecorderWidget.margins(); + qm.setLeft(m_editor->m_whiteKeyWidth); + m_editor->m_stepRecorderWidget.setMargins(qm); + m_editor->m_timeLine->setXOffset(m_editor->m_whiteKeyWidth); } @@ -4733,6 +4614,13 @@ QSize PianoRollWindow::sizeHint() const +bool PianoRollWindow::hasFocus() const +{ + return m_editor->hasFocus(); +} + + + void PianoRollWindow::updateAfterPatternChange() { patternRenamed(); diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index b6647d44a..d60b986d8 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -52,86 +52,6 @@ #include "PianoRoll.h" #include "Track.h" -positionLine::positionLine( QWidget* parent ) : - QWidget( parent ), - m_hasTailGradient ( false ), - m_lineColor (0, 0, 0, 0) -{ - resize( 8, height() ); - - setAttribute( Qt::WA_NoSystemBackground, true ); - setAttribute( Qt::WA_TransparentForMouseEvents ); -} - -void positionLine::paintEvent( QPaintEvent* pe ) -{ - QPainter p( this ); - - // If width is 1, we don't need a gradient - if (width() == 1) - { - p.fillRect( rect(), - QColor( m_lineColor.red(), m_lineColor.green(), m_lineColor.blue(), 153) ); - } - - // If width > 1, we need the gradient - else - { - // Create the gradient trail behind the line - QLinearGradient gradient( rect().bottomLeft(), rect().bottomRight() ); - - // If gradient is enabled, we're in focus and we're playing, enable gradient - if (Engine::getSong()->isPlaying() && m_hasTailGradient && - Engine::getSong()->playMode() == Song::Mode_PlaySong) - { - gradient.setColorAt(( ( width() - 1.0 )/width() ), - QColor( m_lineColor.red(), m_lineColor.green(), m_lineColor.blue(), 60) ); - } - else - { - gradient.setColorAt(( ( width() - 1.0 )/width() ), - QColor( m_lineColor.red(), m_lineColor.green(), m_lineColor.blue(), 0) ); - } - - // Fill in the remaining parts - gradient.setColorAt(0, - QColor( m_lineColor.red(), m_lineColor.green(), m_lineColor.blue(), 0) ); - gradient.setColorAt(1, - QColor( m_lineColor.red(), m_lineColor.green(), m_lineColor.blue(), 153) ); - - // Fill line - p.fillRect( rect(), gradient ); - } -} - -// QProperty handles -bool positionLine::hasTailGradient() const -{ return m_hasTailGradient; } - -void positionLine::setHasTailGradient( const bool g ) -{ m_hasTailGradient = g; } - -QColor positionLine::lineColor() const -{ return m_lineColor; } - -void positionLine::setLineColor( const QColor & c ) -{ m_lineColor = c; } - -// NOTE: the move() implementation fixes a bug where the position line would appear -// in an unexpected location when positioned at the start of the track -void positionLine::zoomChange( double zoom ) -{ - int playHeadPos = x() + width() - 1; - - resize( 8.0 * zoom, height() ); - move( playHeadPos - width() + 1, y() ); - - update(); -} - - - - const QVector SongEditor::m_zoomLevels = { 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f }; @@ -172,7 +92,7 @@ SongEditor::SongEditor( Song * song ) : connect( m_timeLine, SIGNAL( selectionFinished() ), this, SLOT( stopRubberBand() ) ); - m_positionLine = new positionLine( this ); + m_positionLine = new PositionLine(this); static_cast( layout() )->insertWidget( 1, m_timeLine ); connect( m_song, SIGNAL( playbackStateChanged() ), diff --git a/src/gui/widgets/PositionLine.cpp b/src/gui/widgets/PositionLine.cpp new file mode 100644 index 000000000..ef003c5eb --- /dev/null +++ b/src/gui/widgets/PositionLine.cpp @@ -0,0 +1,98 @@ +/* + * PositionLine.cpp + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "PositionLine.h" + +#include + +#include "GuiApplication.h" +#include "Song.h" + + +PositionLine::PositionLine(QWidget* parent) : + QWidget(parent), + m_hasTailGradient(false), + m_lineColor(0, 0, 0, 0) +{ + resize(8, height()); + + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TransparentForMouseEvents); +} + +void PositionLine::paintEvent(QPaintEvent* pe) +{ + QPainter p(this); + QColor c = QColor(m_lineColor); + + // If width is 1, we don't need a gradient + if (width() == 1) + { + c.setAlpha(153); + p.fillRect(rect(), c); + } + // If width > 1, we need the gradient + else + { + // Create the gradient trail behind the line + QLinearGradient gradient(rect().bottomLeft(), rect().bottomRight()); + qreal w = (width() - 1.0) / width(); + + // If gradient is enabled, we're in focus and we're playing, enable gradient + if (m_hasTailGradient && + Engine::getSong()->isPlaying() && + (Engine::getSong()->playMode() == Song::Mode_PlaySong || + Engine::getSong()->playMode() == Song::Mode_PlayPattern)) + { + c.setAlpha(60); + gradient.setColorAt(w, c); + } + else + { + c.setAlpha(0); + gradient.setColorAt(w, c); + } + + // Fill in the remaining parts + c.setAlpha(0); + gradient.setColorAt(0, c); + c.setAlpha(153); + gradient.setColorAt(1, c); + + // Fill line + p.fillRect(rect(), gradient); + } +} + +// NOTE: the move() implementation fixes a bug where the position line would appear +// in an unexpected location when positioned at the start of the track +void PositionLine::zoomChange(double zoom) +{ + int playHeadPos = x() + width() - 1; + + resize(8.0 * zoom, height()); + move(playHeadPos - width() + 1, y()); + + update(); +} \ No newline at end of file diff --git a/src/gui/widgets/StepRecorderWidget.cpp b/src/gui/widgets/StepRecorderWidget.cpp index a546c2a2c..d2157ef66 100644 --- a/src/gui/widgets/StepRecorderWidget.cpp +++ b/src/gui/widgets/StepRecorderWidget.cpp @@ -58,6 +58,19 @@ void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition) m_currentPosition = currentPosition; } +void StepRecorderWidget::setMargins(const QMargins &qm) +{ + m_left = qm.left(); + m_right = qm.right(); + m_top = qm.top(); + m_bottom = qm.bottom(); +} + +QMargins StepRecorderWidget::margins() +{ + return QMargins(m_left, m_top, m_right, m_bottom); +} + void StepRecorderWidget::setBottomMargin(const int marginBottom) { m_marginBottom = marginBottom;