Pianoroll: nudge/snap while dragging notes (#5848)
* pianoroll: nudge/snap while dragging notes combobox for switching behavior old behavior (nudge) is default * snap note start or note end to nearest grid line * fix member variable name * change default width of pianoroll * remove trailing white space * use mouse position instead of m_draggednote * Uses enum instead of text for GridMode selection To avoid problems with translations breaking the conditional, PianoRoll::changeSnapMode now uses the value of the ComboBox model, casted to the GridMode enum instead of the text. * Fixes last code style issues A few code style issues were left, those are fixed in this commit. * Removes forgotten comma on enum Since the last enum value was commented out, there's an unnecessary comma left that was removed. * Rewrites some of the grid logic Separates Nudge logic and Snap logic more explicitly. Avoids recalculation of mouse conversion to time line ticks. Disables shift resizing for Snap mode. Removes unnecessary range checks. Fixes bug with note key boundary checks (which is present in master). * Make noteOffset an int instead of TimePos Co-authored-by: Ian Caio <iancaio_dev@hotmail.com>
This commit is contained in:
BIN
data/themes/default/gridmode.png
Normal file
BIN
data/themes/default/gridmode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 482 B |
@@ -214,6 +214,8 @@ protected slots:
|
||||
void fitNoteLengths(bool fill);
|
||||
void constrainNoteLengths(bool constrainMax);
|
||||
|
||||
void changeSnapMode();
|
||||
|
||||
|
||||
signals:
|
||||
void currentPatternChanged();
|
||||
@@ -258,6 +260,13 @@ private:
|
||||
PR_BLACK_KEY
|
||||
};
|
||||
|
||||
enum GridMode
|
||||
{
|
||||
gridNudge,
|
||||
gridSnap
|
||||
// gridFree
|
||||
};
|
||||
|
||||
PositionLine * m_positionLine;
|
||||
|
||||
QVector<QString> m_nemStr; // gui names of each edit mode
|
||||
@@ -302,7 +311,7 @@ private:
|
||||
int noteEditRight() const;
|
||||
int noteEditLeft() const;
|
||||
|
||||
void dragNotes( int x, int y, bool alt, bool shift, bool ctrl );
|
||||
void dragNotes(int x, int y, bool alt, bool shift, bool ctrl);
|
||||
|
||||
static const int cm_scrollAmtHoriz = 10;
|
||||
static const int cm_scrollAmtVert = 1;
|
||||
@@ -325,6 +334,7 @@ private:
|
||||
ComboBoxModel m_keyModel;
|
||||
ComboBoxModel m_scaleModel;
|
||||
ComboBoxModel m_chordModel;
|
||||
ComboBoxModel m_snapModel;
|
||||
|
||||
static const QVector<double> m_zoomLevels;
|
||||
static const QVector<double> m_zoomYLevels;
|
||||
@@ -347,6 +357,7 @@ private:
|
||||
Note * m_currentNote;
|
||||
Actions m_action;
|
||||
NoteEditMode m_noteEditMode;
|
||||
GridMode m_gridMode;
|
||||
|
||||
int m_selectStartTick;
|
||||
int m_selectedTick;
|
||||
@@ -528,6 +539,7 @@ private:
|
||||
ComboBox * m_keyComboBox;
|
||||
ComboBox * m_scaleComboBox;
|
||||
ComboBox * m_chordComboBox;
|
||||
ComboBox* m_snapComboBox;
|
||||
QPushButton * m_clearGhostButton;
|
||||
|
||||
};
|
||||
|
||||
@@ -74,7 +74,7 @@ typedef AutomationPattern::timeMap timeMap;
|
||||
|
||||
|
||||
// some constants...
|
||||
const int INITIAL_PIANOROLL_WIDTH = 860;
|
||||
const int INITIAL_PIANOROLL_WIDTH = 970;
|
||||
const int INITIAL_PIANOROLL_HEIGHT = 485;
|
||||
|
||||
const int SCROLLBAR_SIZE = 12;
|
||||
@@ -438,6 +438,13 @@ PianoRoll::PianoRoll() :
|
||||
connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
|
||||
this, SLOT( selectRegionFromPixels( int, int ) ) );
|
||||
|
||||
// Set up snap model
|
||||
m_snapModel.addItem(tr("Nudge"));
|
||||
m_snapModel.addItem(tr("Snap"));
|
||||
m_snapModel.setValue(0);
|
||||
connect(&m_snapModel, SIGNAL(dataChanged()),
|
||||
this, SLOT(changeSnapMode()));
|
||||
|
||||
m_stepRecorder.initialize();
|
||||
}
|
||||
|
||||
@@ -1300,10 +1307,13 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
|
||||
if( m_action == ActionMoveNote ||
|
||||
m_action == ActionResizeNote )
|
||||
{
|
||||
dragNotes( m_lastMouseX, m_lastMouseY,
|
||||
ke->modifiers() & Qt::AltModifier,
|
||||
ke->modifiers() & Qt::ShiftModifier,
|
||||
ke->modifiers() & Qt::ControlModifier );
|
||||
dragNotes(
|
||||
m_lastMouseX,
|
||||
m_lastMouseY,
|
||||
ke->modifiers() & Qt::AltModifier,
|
||||
ke->modifiers() & Qt::ShiftModifier,
|
||||
ke->modifiers() & Qt::ControlModifier
|
||||
);
|
||||
}
|
||||
}
|
||||
ke->accept();
|
||||
@@ -1356,10 +1366,13 @@ void PianoRoll::keyPressEvent(QKeyEvent* ke)
|
||||
if( m_action == ActionMoveNote ||
|
||||
m_action == ActionResizeNote )
|
||||
{
|
||||
dragNotes( m_lastMouseX, m_lastMouseY,
|
||||
ke->modifiers() & Qt::AltModifier,
|
||||
ke->modifiers() & Qt::ShiftModifier,
|
||||
ke->modifiers() & Qt::ControlModifier );
|
||||
dragNotes(
|
||||
m_lastMouseX,
|
||||
m_lastMouseY,
|
||||
ke->modifiers() & Qt::AltModifier,
|
||||
ke->modifiers() & Qt::ShiftModifier,
|
||||
ke->modifiers() & Qt::ControlModifier
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2387,10 +2400,13 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
|
||||
pauseTestNotes();
|
||||
}
|
||||
|
||||
dragNotes( me->x(), me->y(),
|
||||
dragNotes(
|
||||
me->x(),
|
||||
me->y(),
|
||||
me->modifiers() & Qt::AltModifier,
|
||||
me->modifiers() & Qt::ShiftModifier,
|
||||
me->modifiers() & Qt::ControlModifier );
|
||||
me->modifiers() & Qt::ControlModifier
|
||||
);
|
||||
|
||||
if( replay_note && m_action == ActionMoveNote && ! ( ( me->modifiers() & Qt::ShiftModifier ) && ! m_startedWithShift ) )
|
||||
{
|
||||
@@ -2745,7 +2761,7 @@ void PianoRoll::updateKnifePos(QMouseEvent* me)
|
||||
|
||||
|
||||
|
||||
void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
|
||||
void PianoRoll::dragNotes(int x, int y, bool alt, bool shift, bool ctrl)
|
||||
{
|
||||
// dragging one or more notes around
|
||||
|
||||
@@ -2758,43 +2774,67 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
|
||||
off_ticks -= m_mouseDownTick - m_currentPosition;
|
||||
off_key -= m_mouseDownKey - m_startKey;
|
||||
|
||||
|
||||
// if they're not holding alt, quantize the offset
|
||||
if( ! alt )
|
||||
{
|
||||
off_ticks = floor( off_ticks / quantization() )
|
||||
* quantization();
|
||||
}
|
||||
|
||||
// make sure notes won't go outside boundary conditions
|
||||
if( m_action == ActionMoveNote && ! ( shift && ! m_startedWithShift ) )
|
||||
{
|
||||
if( m_moveBoundaryLeft + off_ticks < 0 )
|
||||
{
|
||||
off_ticks -= (off_ticks + m_moveBoundaryLeft);
|
||||
}
|
||||
if( m_moveBoundaryTop + off_key > NumKeys )
|
||||
{
|
||||
off_key -= NumKeys - (m_moveBoundaryTop + off_key);
|
||||
}
|
||||
if( m_moveBoundaryBottom + off_key < 0 )
|
||||
{
|
||||
off_key -= (m_moveBoundaryBottom + off_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get note-vector of current pattern
|
||||
const NoteVector & notes = m_pattern->notes();
|
||||
|
||||
if (m_action == ActionMoveNote)
|
||||
{
|
||||
// Calculate the offset for either Nudge or Snap modes
|
||||
int noteOffset = off_ticks;
|
||||
if (m_gridMode == gridSnap && quantization () > 1)
|
||||
{
|
||||
// Get the mouse timeline absolute position
|
||||
TimePos mousePos(m_currentNote->oldPos().getTicks() + off_ticks);
|
||||
|
||||
// We create a mousePos that is relative to the end of the note instead
|
||||
// of the beginning. That's to see if we will snap the beginning or end
|
||||
// of the note
|
||||
TimePos mousePosEnd(mousePos);
|
||||
mousePosEnd += m_currentNote->oldLength();
|
||||
|
||||
// Now we quantize the mouse position to snap it to the grid
|
||||
TimePos mousePosQ = mousePos.quantize(static_cast<float>(quantization()) / DefaultTicksPerBar);
|
||||
TimePos mousePosEndQ = mousePosEnd.quantize(static_cast<float>(quantization()) / DefaultTicksPerBar);
|
||||
|
||||
bool snapEnd = abs(mousePosEndQ - mousePosEnd) < abs(mousePosQ - mousePos);
|
||||
|
||||
// Set the offset
|
||||
noteOffset = snapEnd
|
||||
? mousePosEndQ.getTicks() - m_currentNote->oldPos().getTicks() - m_currentNote->oldLength().getTicks()
|
||||
: mousePosQ.getTicks() - m_currentNote->oldPos().getTicks();
|
||||
}
|
||||
else if (m_gridMode == gridNudge)
|
||||
{
|
||||
// if they're not holding alt, quantize the offset
|
||||
if (!alt)
|
||||
{
|
||||
noteOffset = floor(off_ticks / quantization()) * quantization();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure notes won't go outside boundary conditions
|
||||
if (m_moveBoundaryLeft + noteOffset < 0)
|
||||
{
|
||||
noteOffset = -m_moveBoundaryLeft;
|
||||
}
|
||||
if (m_moveBoundaryTop + off_key >= NumKeys)
|
||||
{
|
||||
off_key = -m_moveBoundaryTop + NumKeys - 1;
|
||||
}
|
||||
if (m_moveBoundaryBottom + off_key < 0)
|
||||
{
|
||||
off_key = -m_moveBoundaryBottom;
|
||||
}
|
||||
|
||||
// Apply offset to all selected notes
|
||||
for (Note *note : getSelectedNotes())
|
||||
{
|
||||
if (shift && ! m_startedWithShift)
|
||||
// Quick resize is only enabled on Nudge mode, since resizing the note
|
||||
// while in Snap mode breaks the calculation of the note offset
|
||||
if (shift && ! m_startedWithShift && m_gridMode == gridNudge)
|
||||
{
|
||||
// quick resize, toggled by holding shift after starting a note move, but not before
|
||||
int ticks_new = note->oldLength().getTicks() + off_ticks;
|
||||
int ticks_new = note->oldLength().getTicks() + noteOffset;
|
||||
if( ticks_new <= 0 )
|
||||
{
|
||||
ticks_new = 1;
|
||||
@@ -2805,17 +2845,13 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
|
||||
else
|
||||
{
|
||||
// moving note
|
||||
int pos_ticks = note->oldPos().getTicks() + off_ticks;
|
||||
|
||||
// Final position of the note
|
||||
TimePos posTicks(note->oldPos().getTicks() + noteOffset);
|
||||
int key_num = note->oldKey() + off_key;
|
||||
|
||||
// ticks can't be negative
|
||||
pos_ticks = qMax(0, pos_ticks);
|
||||
// upper/lower bound checks on key_num
|
||||
key_num = qMax(0, key_num);
|
||||
key_num = qMin(key_num, NumKeys);
|
||||
|
||||
note->setPos( TimePos( pos_ticks ) );
|
||||
note->setKey( key_num );
|
||||
note->setPos(posTicks);
|
||||
note->setKey(key_num);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2827,6 +2863,12 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
|
||||
// If shift + ctrl then we also rearrange all posterior notes (sticky)
|
||||
// If shift is pressed but only one note is selected, apply sticky
|
||||
|
||||
// Quantize the resizing if alt is not pressed
|
||||
if (!alt)
|
||||
{
|
||||
off_ticks = floor(off_ticks / quantization()) * quantization();
|
||||
}
|
||||
|
||||
auto selectedNotes = getSelectedNotes();
|
||||
|
||||
if (shift)
|
||||
@@ -2914,6 +2956,16 @@ void PianoRoll::dragNotes( int x, int y, bool alt, bool shift, bool ctrl )
|
||||
// shift is not pressed; stretch length of selected notes but not their position
|
||||
int minLength = alt ? 1 : m_minResizeLen.getTicks();
|
||||
|
||||
if (m_gridMode == gridSnap)
|
||||
{
|
||||
// Calculate the end point of the note being dragged
|
||||
TimePos oldEndPoint = m_currentNote->oldPos() + m_currentNote->oldLength();
|
||||
// Quantize that position
|
||||
TimePos quantizedEndPoint = Note::quantized(oldEndPoint, quantization());
|
||||
// Add that difference to the offset from the resize
|
||||
off_ticks += quantizedEndPoint - oldEndPoint;
|
||||
}
|
||||
|
||||
for (Note *note : selectedNotes)
|
||||
{
|
||||
int newLength = qMax(minLength, note->oldLength() + off_ticks);
|
||||
@@ -4611,8 +4663,14 @@ Note * PianoRoll::noteUnderMouse()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PianoRoll::changeSnapMode()
|
||||
{
|
||||
// gridNudge,
|
||||
// gridSnap,
|
||||
// gridFree - to be implemented
|
||||
|
||||
|
||||
m_gridMode = static_cast<GridMode>(m_snapModel.value());
|
||||
}
|
||||
|
||||
PianoRollWindow::PianoRollWindow() :
|
||||
Editor(true, true),
|
||||
@@ -4783,6 +4841,15 @@ PianoRollWindow::PianoRollWindow() :
|
||||
m_chordComboBox->setFixedSize( 105, ComboBox::DEFAULT_HEIGHT );
|
||||
m_chordComboBox->setToolTip( tr( "Chord" ) );
|
||||
|
||||
// setup snap-stuff
|
||||
QLabel* snapLbl = new QLabel(m_toolBar);
|
||||
snapLbl->setPixmap(embed::getIconPixmap("gridmode"));
|
||||
|
||||
m_snapComboBox = new ComboBox(m_toolBar);
|
||||
m_snapComboBox->setModel(&m_editor->m_snapModel);
|
||||
m_snapComboBox->setFixedSize(105, ComboBox::DEFAULT_HEIGHT);
|
||||
m_snapComboBox->setToolTip(tr("Snap mode"));
|
||||
|
||||
// -- Clear ghost pattern button
|
||||
m_clearGhostButton = new QPushButton( m_toolBar );
|
||||
m_clearGhostButton->setIcon( embed::getIconPixmap( "clear_ghost_note" ) );
|
||||
@@ -4850,6 +4917,15 @@ PianoRollWindow::PianoRollWindow() :
|
||||
zoomAndNotesToolBar->addSeparator();
|
||||
zoomAndNotesToolBar->addWidget( m_clearGhostButton );
|
||||
|
||||
QWidget* snapWidget = new QWidget();
|
||||
QHBoxLayout* snapHbox = new QHBoxLayout();
|
||||
snapHbox->setContentsMargins(0, 0, 0, 0);
|
||||
snapHbox->addWidget(snapLbl);
|
||||
snapHbox->addWidget(m_snapComboBox);
|
||||
snapWidget->setLayout(snapHbox);
|
||||
zoomAndNotesToolBar->addSeparator();
|
||||
zoomAndNotesToolBar->addWidget(snapWidget);
|
||||
|
||||
// setup our actual window
|
||||
setFocusPolicy( Qt::StrongFocus );
|
||||
setFocus();
|
||||
|
||||
Reference in New Issue
Block a user