diff --git a/data/themes/classic/edit_knife.png b/data/themes/classic/edit_knife.png new file mode 100644 index 00000000000..e6d5bb1c8ed Binary files /dev/null and b/data/themes/classic/edit_knife.png differ diff --git a/data/themes/default/edit_knife.png b/data/themes/default/edit_knife.png new file mode 100644 index 00000000000..e6d5bb1c8ed Binary files /dev/null and b/data/themes/default/edit_knife.png differ diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 2bad4d91042..d368771dbd0 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -73,6 +73,15 @@ class SampleTCO : public TrackContentObject bool isPlaying() const; void setIsPlaying(bool isPlaying); + void inline setMarkerPos( int x ) + { + m_markerPos = x; + } + void inline setMarkerEnabled( bool e ) + { + m_marker = e; + } + public slots: void setSampleBuffer( SampleBuffer* sb ); void setSampleFile( const QString & _sf ); @@ -87,6 +96,8 @@ public slots: BoolModel m_recordModel; bool m_isPlaying; + bool m_marker = false; + int m_markerPos = 0; friend class SampleTCOView; diff --git a/include/SongEditor.h b/include/SongEditor.h index 9621bcc23d8..973fb266440 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -63,6 +63,7 @@ class SongEditor : public TrackContainerView enum EditMode { DrawMode, + KnifeMode, SelectMode }; @@ -85,6 +86,7 @@ public slots: void setEditMode( EditMode mode ); void setEditModeDraw(); + void setEditModeKnife(); void setEditModeSelect(); void toggleProportionalSnap(); @@ -120,6 +122,7 @@ private slots: void wheelEvent( QWheelEvent * we ) override; bool allowRubberband() const override; + bool knifeMode() const override; int trackIndexFromSelectionPoint(int yPos); int indexOfTrackView(const TrackView* tv); @@ -209,6 +212,7 @@ protected slots: ActionGroup * m_editModeGroup; QAction* m_drawModeAction; + QAction* m_knifeModeAction; QAction* m_selectModeAction; QAction* m_crtlAction; diff --git a/include/Track.h b/include/Track.h index de622d0fb64..c750fd32edc 100644 --- a/include/Track.h +++ b/include/Track.h @@ -289,6 +289,7 @@ protected slots: MoveSelection, Resize, ResizeLeft, + Split, CopySelection, ToggleSelected } ; @@ -327,7 +328,10 @@ protected slots: void setInitialOffsets(); bool mouseMovedDistance( QMouseEvent * me, int distance ); + bool unquantizedModHeld( QMouseEvent * me ); MidiTime draggedTCOPos( QMouseEvent * me ); + int knifeMarkerPos( QMouseEvent * me ); + MidiTime quantizeMarkerPos( MidiTime, bool shiftMode ); } ; diff --git a/include/TrackContainerView.h b/include/TrackContainerView.h index 6e952189b01..f0bb20b04b4 100644 --- a/include/TrackContainerView.h +++ b/include/TrackContainerView.h @@ -77,6 +77,7 @@ class TrackContainerView : public QWidget, public ModelView, const TrackView * trackViewAt( const int _y ) const; virtual bool allowRubberband() const; + virtual bool knifeMode() const; inline bool rubberBandActive() const { diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 0c046a504e2..771f59e95f0 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -722,9 +722,37 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { setInitialPos( me->pos() ); setInitialOffsets(); + if( !fixedTCOs() && me->button() == Qt::LeftButton ) { - if( me->modifiers() & Qt::ControlModifier ) + if( m_trackView->trackContainerView()->knifeMode() ) + { + SampleTCO * sTco = dynamic_cast( m_tco ); + + if( me->x() < RESIZE_GRIP_WIDTH && sTco + && !m_tco->getAutoResize() ) + { + m_action = ResizeLeft; + setCursor( Qt::SizeHorCursor ); + } + else if( me->x() >= width() - RESIZE_GRIP_WIDTH ) + { + m_action = Resize; + setCursor( Qt::SizeHorCursor ); + } + else if (sTco) + { + m_action = Split; + sTco->setMarkerPos( knifeMarkerPos( me ) ); + sTco->setMarkerEnabled( true ); + update(); + } + // We can't split anything except samples right now, so disable the + // action to avoid entering if statements we don't need to. This + // also saves us a few 'if(sTco)' checks + else { m_action = NoAction; } + } + else if ( me->modifiers() & Qt::ControlModifier ) { if( isSelected() ) { @@ -818,6 +846,16 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { remove(); } + if (m_action == Split) + { + m_action = NoAction; + SampleTCO * sTco = dynamic_cast( m_tco ); + if (sTco) + { + sTco->setMarkerEnabled( false ); + update(); + } + } } else if( me->button() == Qt::MidButton ) { @@ -947,8 +985,6 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) } else if( m_action == Resize || m_action == ResizeLeft ) { - // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize - const bool unquantized = (me->modifiers() & Qt::ControlModifier) || (me->modifiers() & Qt::AltModifier); const float snapSize = gui->songEditor()->m_editor->getSnapSize(); // Length in ticks of one snap increment const MidiTime snapLength = MidiTime( (int)(snapSize * MidiTime::ticksPerBar()) ); @@ -958,7 +994,8 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) // The clip's new length MidiTime l = static_cast( me->x() * MidiTime::ticksPerBar() / ppb ); - if ( unquantized ) + // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize + if ( unquantizedModHeld(me) ) { // We want to preserve this adjusted offset, // even if the user switches to snapping later setInitialPos( m_initialMousePos ); @@ -994,7 +1031,7 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) m_trackView->trackContainerView()->currentPosition() + static_cast( x * MidiTime::ticksPerBar() / ppb ) ); - if( unquantized ) + if( unquantizedModHeld(me) ) { // We want to preserve this adjusted offset, // even if the user switches to snapping later setInitialPos( m_initialMousePos ); @@ -1040,6 +1077,12 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) MidiTime::ticksPerBar() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } + else if( m_action == Split ) + { + SampleTCO * sTco = dynamic_cast( m_tco ); + if (sTco) { sTco->setMarkerPos( knifeMarkerPos( me ) ); } + update(); + } else { SampleTCO * sTco = dynamic_cast( m_tco ); @@ -1076,12 +1119,53 @@ void TrackContentObjectView::mouseReleaseEvent( QMouseEvent * me ) { setSelected( !isSelected() ); } - - if( m_action == Move || m_action == Resize || m_action == ResizeLeft ) + else if( m_action == Move || m_action == Resize || m_action == ResizeLeft ) { // TODO: Fix m_tco->setJournalling() consistency m_tco->setJournalling( true ); } + else if( m_action == Split ) + { + SampleTCO * leftTCO = dynamic_cast( m_tco ); + + if (leftTCO) + { + leftTCO->setMarkerEnabled( false ); + + const int relativePixelPos = me->pos().x(); + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + MidiTime splitPos = relativePixelPos * MidiTime::ticksPerBar() / ppb; + + if ( !unquantizedModHeld(me) ) + { + splitPos = quantizeMarkerPos( splitPos, me->modifiers() & Qt::ShiftModifier ); + } + + splitPos += m_initialTCOPos; + + //Don't split if we slid off the TCO or if we're on the clip's start/end + //Cutting at exactly the start/end position would create a zero length + //clip (bad), and a clip the same length as the original one (pointless). + if ( splitPos > m_initialTCOPos && splitPos < m_initialTCOEnd ) + { + leftTCO->getTrack()->addJournalCheckPoint(); + leftTCO->getTrack()->saveJournallingState( false ); + + SampleTCO * rightTCO = new SampleTCO ( leftTCO->getTrack() ); + rightTCO->setSampleBuffer( leftTCO->sampleBuffer() ); + rightTCO->setIsPlaying(leftTCO->isPlaying()); + + leftTCO->changeLength( splitPos - m_initialTCOPos ); + + rightTCO->movePosition( splitPos ); + rightTCO->changeLength( m_initialTCOEnd - splitPos ); + rightTCO->setStartTimeOffset( leftTCO->startTimeOffset() - leftTCO->length() ); + + leftTCO->getTrack()->restoreJournallingState(); + } + } + } + m_action = NoAction; delete m_hint; m_hint = NULL; @@ -1180,6 +1264,14 @@ bool TrackContentObjectView::mouseMovedDistance( QMouseEvent * me, int distance + +bool TrackContentObjectView::unquantizedModHeld( QMouseEvent * me ) +{ + return me->modifiers() & Qt::ControlModifier || me->modifiers() & Qt::AltModifier; +} + + + /*! \brief Calculate the new position of a dragged TCO from a mouse event * * @@ -1194,12 +1286,8 @@ MidiTime TrackContentObjectView::draggedTCOPos( QMouseEvent * me ) MidiTime newPos = m_initialTCOPos + mouseOff * MidiTime::ticksPerBar() / ppb; MidiTime offset = newPos - m_initialTCOPos; // If the user is holding alt, or pressed ctrl after beginning the drag, don't quantize - if ( me->button() != Qt::NoButton - || (me->modifiers() & Qt::ControlModifier) - || (me->modifiers() & Qt::AltModifier) ) - { - // We want to preserve this adjusted offset, - // even if the user switches to snapping + if ( me->button() != Qt::NoButton || unquantizedModHeld(me) ) + { // We want to preserve this adjusted offset, even if the user switches to snapping setInitialPos( m_initialMousePos ); } else if ( me->modifiers() & Qt::ShiftModifier ) @@ -1224,6 +1312,50 @@ MidiTime TrackContentObjectView::draggedTCOPos( QMouseEvent * me ) +int TrackContentObjectView::knifeMarkerPos( QMouseEvent * me ) +{ + //Position relative to start of clip + const int markerPos = me->pos().x(); + + //In unquantized mode, we don't have to mess with the position at all + if ( unquantizedModHeld(me) ) { return markerPos; } + else + { //Otherwise we... + //1: Convert the position to a MidiTime + const float ppb = m_trackView->trackContainerView()->pixelsPerBar(); + MidiTime midiPos = markerPos * MidiTime::ticksPerBar() / ppb; + //2: Snap to the correct position, based on modifier keys + midiPos = quantizeMarkerPos( midiPos, me->modifiers() & Qt::ShiftModifier ); + //3: Convert back to a pixel position + return midiPos * ppb / MidiTime::ticksPerBar(); + } +} + + + + +MidiTime TrackContentObjectView::quantizeMarkerPos( MidiTime midiPos, bool shiftMode ) +{ + const float snapSize = gui->songEditor()->m_editor->getSnapSize(); + if ( shiftMode ) + { //If shift is held we quantize the length of the new left clip... + const MidiTime leftPos = midiPos.quantize( snapSize ); + //...or right clip... + const MidiTime rightOff = m_tco->length() - midiPos; + const MidiTime rightPos = m_tco->length() - rightOff.quantize( snapSize ); + //...whichever gives a position closer to the cursor + if ( abs(leftPos - midiPos) < abs(rightPos - midiPos) ) { return leftPos; } + else { return rightPos; } + } + else + { + return (MidiTime(midiPos + m_initialTCOPos).quantize( snapSize ) - m_initialTCOPos); + } +} + + + + // =========================================================================== // trackContentWidget // =========================================================================== diff --git a/src/gui/TrackContainerView.cpp b/src/gui/TrackContainerView.cpp index 9b51c76f272..0da962fd9f0 100644 --- a/src/gui/TrackContainerView.cpp +++ b/src/gui/TrackContainerView.cpp @@ -305,6 +305,14 @@ bool TrackContainerView::allowRubberband() const +bool TrackContainerView::knifeMode() const +{ + return false; +} + + + + void TrackContainerView::setPixelsPerBar( int ppb ) { m_ppb = ppb; @@ -371,7 +379,7 @@ void TrackContainerView::dropEvent( QDropEvent * _de ) //it->toggledInstrumentTrackButton( true ); _de->accept(); } - else if( type == "samplefile" || type == "pluginpresetfile" + else if( type == "samplefile" || type == "pluginpresetfile" || type == "soundfontfile" || type == "vstpluginfile" || type == "patchfile" ) { diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index ba7cd6f824b..442f4c01f4b 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -458,6 +458,11 @@ void SongEditor::setEditModeDraw() setEditMode(DrawMode); } +void SongEditor::setEditModeKnife() +{ + setEditMode(KnifeMode); +} + void SongEditor::setEditModeSelect() { setEditMode(SelectMode); @@ -863,6 +868,14 @@ bool SongEditor::allowRubberband() const +bool SongEditor::knifeMode() const +{ + return m_mode == KnifeMode; +} + + + + int SongEditor::trackIndexFromSelectionPoint(int yPos) { const TrackView * tv = trackViewAt(yPos - m_timeLine->height()); @@ -947,13 +960,16 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_editModeGroup = new ActionGroup(this); m_drawModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode")); + m_knifeModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_knife"), tr("Knife mode (split sample clips)")); m_selectModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_select"), tr("Edit mode (select and move)")); m_drawModeAction->setChecked(true); connect(m_drawModeAction, SIGNAL(triggered()), m_editor, SLOT(setEditModeDraw())); + connect(m_knifeModeAction, SIGNAL(triggered()), m_editor, SLOT(setEditModeKnife())); connect(m_selectModeAction, SIGNAL(triggered()), m_editor, SLOT(setEditModeSelect())); editActionsToolBar->addAction( m_drawModeAction ); + editActionsToolBar->addAction( m_knifeModeAction ); editActionsToolBar->addAction( m_selectModeAction ); DropToolBar *timeLineToolBar = addDropToolBarToTop(tr("Timeline controls")); diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 72f63bb05b5..331ca86e598 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -560,6 +560,10 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) embed::getIconPixmap( "muted", size, size ) ); } + if ( m_tco->m_marker ) + { + p.drawLine(m_tco->m_markerPos, rect().bottom(), m_tco->m_markerPos, rect().top()); + } // recording sample tracks is not possible at the moment /* if( m_tco->isRecord() )