diff --git a/data/themes/default/gridmode.png b/data/themes/default/gridmode.png new file mode 100644 index 00000000000..f018b6c2f21 Binary files /dev/null and b/data/themes/default/gridmode.png differ diff --git a/include/PianoRoll.h b/include/PianoRoll.h index ea243e541d0..ef61962aa70 100644 --- a/include/PianoRoll.h +++ b/include/PianoRoll.h @@ -214,6 +214,8 @@ protected slots: void fitNoteLengths(bool fill); void constrainNoteLengths(bool constrainMax); + void changeSnapMode(); + signals: void currentPatternChanged(); @@ -258,6 +260,13 @@ protected slots: PR_BLACK_KEY }; + enum GridMode + { + gridNudge, + gridSnap + // gridFree + }; + PositionLine * m_positionLine; QVector m_nemStr; // gui names of each edit mode @@ -302,7 +311,7 @@ protected slots: 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 @@ protected slots: ComboBoxModel m_keyModel; ComboBoxModel m_scaleModel; ComboBoxModel m_chordModel; + ComboBoxModel m_snapModel; static const QVector m_zoomLevels; static const QVector m_zoomYLevels; @@ -347,6 +357,7 @@ protected slots: Note * m_currentNote; Actions m_action; NoteEditMode m_noteEditMode; + GridMode m_gridMode; int m_selectStartTick; int m_selectedTick; @@ -528,6 +539,7 @@ private slots: ComboBox * m_keyComboBox; ComboBox * m_scaleComboBox; ComboBox * m_chordComboBox; + ComboBox* m_snapComboBox; QPushButton * m_clearGhostButton; }; diff --git a/src/gui/editors/PianoRoll.cpp b/src/gui/editors/PianoRoll.cpp index 1d8ad8bfd73..5dcb6a06fc6 100644 --- a/src/gui/editors/PianoRoll.cpp +++ b/src/gui/editors/PianoRoll.cpp @@ -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; + // get note-vector of current pattern + const NoteVector & notes = m_pattern->notes(); - // if they're not holding alt, quantize the offset - if( ! alt ) + if (m_action == ActionMoveNote) { - off_ticks = floor( off_ticks / quantization() ) - * quantization(); - } + // 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); - // make sure notes won't go outside boundary conditions - if( m_action == ActionMoveNote && ! ( shift && ! m_startedWithShift ) ) - { - if( m_moveBoundaryLeft + off_ticks < 0 ) + // 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(quantization()) / DefaultTicksPerBar); + TimePos mousePosEndQ = mousePosEnd.quantize(static_cast(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) { - off_ticks -= (off_ticks + m_moveBoundaryLeft); + // if they're not holding alt, quantize the offset + if (!alt) + { + noteOffset = floor(off_ticks / quantization()) * quantization(); + } } - if( m_moveBoundaryTop + off_key > NumKeys ) + + // Make sure notes won't go outside boundary conditions + if (m_moveBoundaryLeft + noteOffset < 0) { - off_key -= NumKeys - (m_moveBoundaryTop + off_key); + noteOffset = -m_moveBoundaryLeft; } - if( m_moveBoundaryBottom + off_key < 0 ) + if (m_moveBoundaryTop + off_key >= NumKeys) { - off_key -= (m_moveBoundaryBottom + off_key); + off_key = -m_moveBoundaryTop + NumKeys - 1; + } + if (m_moveBoundaryBottom + off_key < 0) + { + off_key = -m_moveBoundaryBottom; } - } - - - // get note-vector of current pattern - const NoteVector & notes = m_pattern->notes(); - if (m_action == ActionMoveNote) - { + // 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; - 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); + // Final position of the note + TimePos posTicks(note->oldPos().getTicks() + noteOffset); + int key_num = note->oldKey() + off_key; - 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(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();