Skip to content

Commit

Permalink
Feature: PianoRoll Knife (LMMS#5845)
Browse files Browse the repository at this point in the history
* Initial PianoRoll razor feature

* Restore PianoRoll edit mode after focusOut and in razor mode.

* Show changes directly after cut.

* Fix hanging note after adjusting vol/pan with razor action.

* Extract the split action to a separate method

	This PR addresses some suggestions from a review, the most
important ones being:
	- Extracting the note split action to a separate method, called
Pattern::splitNotes
	- Removing getMouseTickPos method
	- Adding a variable that holds the current razor position and a
method to update it (quantizing if CTRL is not pressed)
	- Using [this] to capture "this" on the lambda function instead
of [=], since the latter doesn't work as intended from C++20 forward
	- Fixing some code style and adding comments

* Removes an extra call to noteUnderMouse

	By removing "&& noteUnderMouse()" from the mousePressEvent
conditional, we avoid an extra call to noteUnderMouse. The only
difference in the behavior of the tool is that now clicking on a place
that doesn't have a note will exit Razor mode.

* Style change suggested by @russiankumar

* Cancel razor action on SHIFT release.

* Make razor cut-line (color) themable.

* Add razor cut-line color to classic theme style.css

* Rename razor to knife.

* Change pixmap from razor to knife (from LMMS#5524)

* Remove SHIFT behavior.

* Change knife shortcut to SHIFT+K

Co-authored-by: CYBERDEViL <cyberdevil@notabug.org>
Co-authored-by: Ian Caio <iancaio_dev@hotmail.com>
  • Loading branch information
3 people authored Feb 26, 2021
1 parent 4521ec8 commit 0b354c7
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 6 deletions.
Binary file added data/themes/classic/edit_knife.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions data/themes/classic/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ PianoRoll {
qproperty-ghostNoteBorders: true;
qproperty-barColor: #4afd85;
qproperty-markedSemitoneColor: rgba( 0, 255, 200, 60 );
qproperty-knifeCutLine: rgba(255, 0, 0, 255);
/* Piano keys */
qproperty-whiteKeyWidth: 64;
qproperty-whiteKeyActiveTextColor: #000;
Expand Down
Binary file added data/themes/default/edit_knife.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions data/themes/default/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ PianoRoll {
qproperty-ghostNoteBorders: false;
qproperty-barColor: #078f3a;
qproperty-markedSemitoneColor: rgba(255, 255, 255, 30);
qproperty-knifeCutLine: rgba(255, 0, 0, 255);
/* Piano keys */
qproperty-whiteKeyWidth: 64;
qproperty-whiteKeyActiveTextColor: #000;
Expand Down
3 changes: 3 additions & 0 deletions include/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class LMMS_EXPORT Pattern : public TrackContentObject
Note * addStepNote( int step );
void setStep( int step, bool enabled );

// Split the list of notes on the given position
void splitNotes(NoteVector notes, TimePos pos);

// pattern-type stuff
inline PatternTypes type() const
{
Expand Down
15 changes: 14 additions & 1 deletion include/PianoRoll.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class PianoRoll : public QWidget
Q_PROPERTY(QColor textColorLight MEMBER m_textColorLight)
Q_PROPERTY(QColor textShadow MEMBER m_textShadow)
Q_PROPERTY(QColor markedSemitoneColor MEMBER m_markedSemitoneColor)
Q_PROPERTY(QColor knifeCutLine MEMBER m_knifeCutLineColor)
Q_PROPERTY(int noteOpacity MEMBER m_noteOpacity)
Q_PROPERTY(bool noteBorders MEMBER m_noteBorders)
Q_PROPERTY(int ghostNoteOpacity MEMBER m_ghostNoteOpacity)
Expand All @@ -95,6 +96,7 @@ class PianoRoll : public QWidget
ModeErase,
ModeSelect,
ModeEditDetuning,
ModeEditKnife
};

/*! \brief Resets settings to default when e.g. creating a new project */
Expand Down Expand Up @@ -226,7 +228,8 @@ protected slots:
ActionResizeNote,
ActionSelectNotes,
ActionChangeNoteProperty,
ActionResizeNoteEditArea
ActionResizeNoteEditArea,
ActionKnife
};

enum NoteEditMode
Expand Down Expand Up @@ -282,6 +285,9 @@ protected slots:
void playChordNotes(int key, int velocity=-1);
void pauseChordNotes(int key);

void setKnifeAction();
void cancelKnifeAction();

void updateScrollbars();
void updatePositionLineHeight();

Expand All @@ -304,6 +310,7 @@ protected slots:
static QPixmap * s_toolSelect;
static QPixmap * s_toolMove;
static QPixmap * s_toolOpen;
static QPixmap* s_toolKnife;

static PianoRollKeyTypes prKeyOrder[];

Expand Down Expand Up @@ -389,6 +396,7 @@ protected slots:

EditModes m_editMode;
EditModes m_ctrlMode; // mode they were in before they hit ctrl
EditModes m_knifeMode; // mode they where in before entering knife mode

bool m_mouseDownRight; //true if right click is being held down

Expand All @@ -408,6 +416,10 @@ protected slots:
// did we start a mouseclick with shift pressed
bool m_startedWithShift;

// Variable that holds the position in ticks for the knife action
int m_knifeTickPos;
void updateKnifePos(QMouseEvent* me);

friend class PianoRollWindow;

StepRecorderWidget m_stepRecorderWidget;
Expand All @@ -428,6 +440,7 @@ protected slots:
QColor m_textColorLight;
QColor m_textShadow;
QColor m_markedSemitoneColor;
QColor m_knifeCutLineColor;
int m_noteOpacity;
int m_ghostNoteOpacity;
bool m_noteBorders;
Expand Down
156 changes: 151 additions & 5 deletions src/gui/editors/PianoRoll.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ QPixmap * PianoRoll::s_toolErase = NULL;
QPixmap * PianoRoll::s_toolSelect = NULL;
QPixmap * PianoRoll::s_toolMove = NULL;
QPixmap * PianoRoll::s_toolOpen = NULL;
QPixmap* PianoRoll::s_toolKnife = nullptr;

TextFloat * PianoRoll::s_textFloat = NULL;

Expand Down Expand Up @@ -200,6 +201,7 @@ PianoRoll::PianoRoll() :
m_textColorLight( 0, 0, 0 ),
m_textShadow( 0, 0, 0 ),
m_markedSemitoneColor( 0, 0, 0 ),
m_knifeCutLineColor(0, 0, 0),
m_noteOpacity( 255 ),
m_ghostNoteOpacity( 255 ),
m_noteBorders( true ),
Expand Down Expand Up @@ -271,6 +273,10 @@ PianoRoll::PianoRoll() :
{
s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) );
}
if (s_toolKnife == nullptr)
{
s_toolKnife = new QPixmap(embed::getIconPixmap("edit_knife"));
}

// init text-float
if( s_textFloat == NULL )
Expand Down Expand Up @@ -1268,8 +1274,16 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
break;

case Qt::Key_Escape:
// Same as Ctrl + Shift + A
clearSelectedNotes();
// On the Knife mode, ESC cancels it
if (m_editMode == ModeEditKnife)
{
cancelKnifeAction();
}
else
{
// Same as Ctrl + Shift + A
clearSelectedNotes();
}
break;

case Qt::Key_Backspace:
Expand Down Expand Up @@ -1314,6 +1328,12 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
}

case Qt::Key_Control:
// Ctrl will not enter selection mode if we are
// in Knife mode, but unquantize it
if (m_editMode == ModeEditKnife)
{
break;
}
// Enter selection mode if:
// -> this window is active
// -> shift is not pressed
Expand Down Expand Up @@ -1353,6 +1373,10 @@ void PianoRoll::keyReleaseEvent(QKeyEvent* ke )
switch( ke->key() )
{
case Qt::Key_Control:
if (m_editMode == ModeEditKnife)
{
break;
}
computeSelectedNotes( ke->modifiers() & Qt::ShiftModifier);
m_editMode = m_ctrlMode;
update();
Expand Down Expand Up @@ -1441,6 +1465,26 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
return;
}

// -- Knife
if (m_editMode == ModeEditKnife && me->button() == Qt::LeftButton)
{
NoteVector n;
Note* note = noteUnderMouse();

if (note)
{
n.append(note);

updateKnifePos(me);

// Call splitNotes for the note
m_pattern->splitNotes(n, TimePos(m_knifeTickPos));
}

update();
return;
}

if( m_editMode == ModeEditDetuning && noteUnderMouse() )
{
static QPointer<AutomationPattern> detuningPattern = nullptr;
Expand Down Expand Up @@ -1946,6 +1990,24 @@ void PianoRoll::pauseChordNotes(int key)
}
}

void PianoRoll::setKnifeAction()
{
if (m_editMode != ModeEditKnife)
{
m_knifeMode = m_editMode;
m_editMode = ModeEditKnife;
m_action = ActionKnife;
setCursor(Qt::ArrowCursor);
update();
}
}

void PianoRoll::cancelKnifeAction()
{
m_editMode = m_knifeMode;
m_action = ActionNone;
update();
}



Expand Down Expand Up @@ -2047,6 +2109,12 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me )

s_textFloat->hide();

// Quit knife mode if we pressed and released the right mouse button
if (m_editMode == ModeEditKnife && me->button() == Qt::RightButton)
{
cancelKnifeAction();
}

if( me->button() & Qt::LeftButton )
{
mustRepaint = true;
Expand Down Expand Up @@ -2105,7 +2173,11 @@ void PianoRoll::mouseReleaseEvent( QMouseEvent * me )
}

m_currentNote = NULL;
m_action = ActionNone;

if (m_action != ActionKnife)
{
m_action = ActionNone;
}

if( m_editMode == ModeDraw )
{
Expand All @@ -2131,6 +2203,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )

if( m_action == ActionNone && me->buttons() == 0 )
{
// When cursor is between note editing area and volume/panning
// area show vertical size cursor.
if( me->y() > keyAreaBottom() && me->y() < noteEditTop() )
{
setCursor( Qt::SizeVerCursor );
Expand Down Expand Up @@ -2160,6 +2234,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
return;
}

// Update Knife position if we are on knife mode
if (m_editMode == ModeEditKnife)
{
updateKnifePos(me);
}

if( me->y() > PR_TOP_MARGIN || m_action != ActionNone )
{
bool edit_note = ( me->y() > noteEditTop() )
Expand Down Expand Up @@ -2439,7 +2519,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
}
}
}
else if (me->buttons() == Qt::NoButton && m_editMode != ModeDraw)
else if (me->buttons() == Qt::NoButton && m_editMode != ModeDraw && m_editMode != ModeEditKnife)
{
// Is needed to restore cursor when it previously was set to
// Qt::SizeVerCursor (between keyAreaBottom and noteEditTop)
Expand Down Expand Up @@ -2535,6 +2615,24 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )



void PianoRoll::updateKnifePos(QMouseEvent* me)
{
// Calculate the TimePos from the mouse
int mouseViewportPos = me->x() - m_whiteKeyWidth;
int mouseTickPos = mouseViewportPos / (m_ppb / TimePos::ticksPerBar()) + m_currentPosition;

// If ctrl is not pressed, quantize the position
if (!(me->modifiers() & Qt::ControlModifier))
{
mouseTickPos = floor(mouseTickPos / quantization()) * quantization();
}

m_knifeTickPos = mouseTickPos;
}




void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
{
// dragging one or more notes around
Expand Down Expand Up @@ -3232,6 +3330,41 @@ void PianoRoll::paintEvent(QPaintEvent * pe )
}
}

// -- Knife tool (draw cut line)
if (m_action == ActionKnife)
{
auto xCoordOfTick = [this](int tick) {
return m_whiteKeyWidth + (
(tick - m_currentPosition) * m_ppb / TimePos::ticksPerBar());
};
Note* n = noteUnderMouse();
if (n)
{
const int key = n->key() - m_startKey + 1;
int y = y_base - key * m_keyLineHeight;

int x = xCoordOfTick(m_knifeTickPos);

if (x > xCoordOfTick(n->pos()) &&
x < xCoordOfTick(n->pos() + n->length()))
{
p.setPen(QPen(m_knifeCutLineColor, 1));
p.drawLine(x, y, x, y + m_keyLineHeight);

setCursor(Qt::BlankCursor);
}
else
{
setCursor(Qt::ArrowCursor);
}
}
else
{
setCursor(Qt::ArrowCursor);
}
}
// -- End knife tool

//draw current step recording notes
for( const Note *note : m_stepRecorder.getCurStepNotes() )
{
Expand Down Expand Up @@ -3353,6 +3486,7 @@ void PianoRoll::paintEvent(QPaintEvent * pe )
case ModeErase: cursor = s_toolErase; break;
case ModeSelect: cursor = s_toolSelect; break;
case ModeEditDetuning: cursor = s_toolOpen; break;
case ModeEditKnife: cursor = s_toolKnife; break;
}
QPoint mousePosition = mapFromGlobal( QCursor::pos() );
if( cursor != NULL && mousePosition.y() > keyAreaTop() && mousePosition.x() > noteEditLeft())
Expand Down Expand Up @@ -3560,7 +3694,12 @@ void PianoRoll::focusOutEvent( QFocusEvent * )
m_pattern->instrumentTrack()->pianoModel()->setKeyState( i, false );
}
}
m_editMode = m_ctrlMode;
if (m_editMode == ModeEditKnife) {
m_editMode = m_knifeMode;
m_action = ActionNone;
} else {
m_editMode = m_ctrlMode;
}
update();
}

Expand Down Expand Up @@ -4443,7 +4582,14 @@ PianoRollWindow::PianoRollWindow() :
connect(glueAction, SIGNAL(triggered()), m_editor, SLOT(glueNotes()));
glueAction->setShortcut( Qt::SHIFT | Qt::Key_G );

// Knife
QAction * knifeAction = new QAction(embed::getIconPixmap("edit_knife"),
tr("Knife"), noteToolsButton);
connect(knifeAction, &QAction::triggered, m_editor, &PianoRoll::setKnifeAction);
knifeAction->setShortcut( Qt::SHIFT | Qt::Key_K );

noteToolsButton->addAction(glueAction);
noteToolsButton->addAction(knifeAction);

notesActionsToolBar->addWidget(noteToolsButton);

Expand Down
Loading

0 comments on commit 0b354c7

Please sign in to comment.