Knife Tool for Sample Clips (Again) (#5524)

* Rebase BaraMGB's Knife
Co-authored-by: Steffen Baranowsky <BaraMGB@freenet.de>

* Draw marker

* Refactoring and shift mode

* Allow resizing

* Add Icon

* Fix stuck marker on RMB, remove unnecessary cast

* Remove redundant line, more const

* Fix

* Review fixes

* Only perform split logic for SampleTCO

* Add unquantizedModHeld function

* missed one

* Don't use copy/paste

* Don't use copy/paste

* More git troubles

* Fix undo

* git dammit

* Cleaner solution?

* Set cursor, add copy assignment to SampleBuffer

* Add TODO comment

* Make it build

* Fixes from review

* Make splitTCO virtual

* Make splitTCO more generic

Co-authored-by: IanCaio <iancaio_dev@hotmail.com>

* Prevent resizing of MIDI clips in knife mode

* Fix move/resize and rework box select via ctrl

* Apply suggestions from code review.

Co-authored-by: IanCaio <iancaio_dev@hotmail.com>

* Don't show inaccurate/useless/empty text float in knife mode

* Addresses Github review

	- Fixes a typo where QWidget::mousePressEvent was being called
inside mouseReleaseEvent.
	- Avoids unnecessarily disabling journalling on the Split
action, since it doesn't require it.

* Revert format changes in Track

* Revert format changes in Track.h

* Revert formatting changes in Track.cpp

Co-authored-by: Hyunjin Song <tteu.ingog@gmail.com>
Co-authored-by: IanCaio <iancaio_dev@hotmail.com>
This commit is contained in:
Spekular
2021-03-12 22:49:54 +01:00
committed by GitHub
parent 266ad8d45e
commit 8acb9222fd
14 changed files with 276 additions and 98 deletions

View File

@@ -308,6 +308,14 @@ bool TrackContainerView::allowRubberband() const
bool TrackContainerView::knifeMode() const
{
return false;
}
void TrackContainerView::setPixelsPerBar( int ppb )
{
m_ppb = ppb;
@@ -374,7 +382,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" )
{

View File

@@ -77,13 +77,13 @@ TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco,
TrackView * tv ) :
selectableObject( tv->getTrackContentWidget() ),
ModelView( NULL, this ),
m_tco( tco ),
m_trackView( tv ),
m_initialTCOPos( TimePos(0) ),
m_initialTCOEnd( TimePos(0) ),
m_tco( tco ),
m_action( NoAction ),
m_initialMousePos( QPoint( 0, 0 ) ),
m_initialMouseGlobalPos( QPoint( 0, 0 ) ),
m_initialTCOPos( TimePos(0) ),
m_initialTCOEnd( TimePos(0) ),
m_initialOffsets( QVector<TimePos>() ),
m_hint( NULL ),
m_mutedColor( 0, 0, 0 ),
@@ -94,6 +94,9 @@ TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco,
m_BBPatternBackground( 0, 0, 0 ),
m_gradient( true ),
m_mouseHotspotHand( 0, 0 ),
m_mouseHotspotKnife( 0, 0 ),
m_cursorHand( QCursor( embed::getIconPixmap( "hand" ) ) ),
m_cursorKnife( QCursor( embed::getIconPixmap( "cursor_knife" ) ) ),
m_cursorSetYet( false ),
m_needsUpdate( true )
{
@@ -106,7 +109,7 @@ TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco,
setAttribute( Qt::WA_OpaquePaintEvent, true );
setAttribute( Qt::WA_DeleteOnClose, true );
setFocusPolicy( Qt::StrongFocus );
setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
setCursor( m_cursorHand );
move( 0, 0 );
show();
@@ -159,7 +162,9 @@ void TrackContentObjectView::update()
{
if( !m_cursorSetYet )
{
setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
m_cursorHand = QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() );
m_cursorKnife = QCursor( embed::getIconPixmap( "cursor_knife" ), m_mouseHotspotKnife.width(), m_mouseHotspotKnife.height() );
setCursor( m_cursorHand );
m_cursorSetYet = true;
}
@@ -248,6 +253,11 @@ void TrackContentObjectView::setMouseHotspotHand(const QSize & s)
m_mouseHotspotHand = s;
}
void TrackContentObjectView::setMouseHotspotKnife(const QSize & s)
{
m_mouseHotspotKnife = s;
}
// access needsUpdate member variable
bool TrackContentObjectView::needsUpdate()
{ return m_needsUpdate; }
@@ -439,22 +449,32 @@ void TrackContentObjectView::dropEvent( QDropEvent * de )
/*! \brief Handle a dragged selection leaving our 'airspace'.
/* @brief Chooses the correct cursor to be displayed on the widget
*
* \param e The QEvent to watch.
* @param me The QMouseEvent that is triggering the cursor change
*/
void TrackContentObjectView::leaveEvent( QEvent * e )
void TrackContentObjectView::updateCursor(QMouseEvent * me)
{
if( cursor().shape() != Qt::BitmapCursor )
SampleTCO * sTco = dynamic_cast<SampleTCO*>(m_tco);
// If we are at the edges, use the resize cursor
if ((me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize())
|| (me->x() < RESIZE_GRIP_WIDTH && !me->buttons() && sTco && !m_tco->getAutoResize()))
{
setCursor( QCursor( embed::getIconPixmap( "hand" ), m_mouseHotspotHand.width(), m_mouseHotspotHand.height() ) );
setCursor(Qt::SizeHorCursor);
}
if( e != NULL )
// If we are in the middle on knife mode, use the knife cursor
else if (sTco && m_trackView->trackContainerView()->knifeMode())
{
QWidget::leaveEvent( e );
setCursor(m_cursorKnife);
}
// If we are in the middle in any other mode, use the hand cursor
else { setCursor(m_cursorHand); }
}
/*! \brief Create a DataFile suitable for copying multiple trackContentObjects.
*
* trackContentObjects in the vector are written to the "tcos" node in the
@@ -568,7 +588,10 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
setInitialOffsets();
if( !fixedTCOs() && me->button() == Qt::LeftButton )
{
if( me->modifiers() & Qt::ControlModifier )
SampleTCO * sTco = dynamic_cast<SampleTCO*>( m_tco );
const bool knifeMode = m_trackView->trackContainerView()->knifeMode();
if ( me->modifiers() & Qt::ControlModifier && !(sTco && knifeMode) )
{
if( isSelected() )
{
@@ -579,9 +602,7 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
m_action = ToggleSelected;
}
}
else if( !me->modifiers()
|| (me->modifiers() & Qt::AltModifier)
|| (me->modifiers() & Qt::ShiftModifier) )
else
{
if( isSelected() )
{
@@ -592,28 +613,43 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
gui->songEditor()->m_editor->selectAllTcos( false );
m_tco->addJournalCheckPoint();
// move or resize
m_tco->setJournalling( false );
// Move, Resize and ResizeLeft
// Split action doesn't disable TCO journalling
if (m_action == Move || m_action == Resize || m_action == ResizeLeft)
{
m_tco->setJournalling(false);
}
setInitialPos( me->pos() );
setInitialOffsets();
SampleTCO * sTco = dynamic_cast<SampleTCO*>( m_tco );
if( me->x() < RESIZE_GRIP_WIDTH && sTco
&& !m_tco->getAutoResize() )
if( m_tco->getAutoResize() )
{ // Always move clips that can't be manually resized
m_action = Move;
setCursor( Qt::SizeAllCursor );
}
else if( me->x() >= width() - RESIZE_GRIP_WIDTH )
{
m_action = Resize;
setCursor( Qt::SizeHorCursor );
}
else if( me->x() < RESIZE_GRIP_WIDTH && sTco )
{
m_action = ResizeLeft;
setCursor( Qt::SizeHorCursor );
}
else if( m_tco->getAutoResize() || me->x() < width() - RESIZE_GRIP_WIDTH )
else if( sTco && knifeMode )
{
m_action = Move;
setCursor( Qt::SizeAllCursor );
m_action = Split;
setCursor( m_cursorKnife );
setMarkerPos( knifeMarkerPos( me ) );
setMarkerEnabled( true );
update();
}
else
{
m_action = Resize;
setCursor( Qt::SizeHorCursor );
m_action = Move;
setCursor( Qt::SizeAllCursor );
}
if( m_action == Move )
@@ -641,7 +677,7 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
// s_textFloat->reparent( this );
// setup text-float as if TCO was already moved/resized
s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) );
s_textFloat->show();
if ( m_action != Split) { s_textFloat->show(); }
}
delete m_hint;
@@ -662,6 +698,16 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
{
remove( active );
}
if (m_action == Split)
{
m_action = NoAction;
SampleTCO * sTco = dynamic_cast<SampleTCO*>( m_tco );
if (sTco)
{
setMarkerEnabled( false );
update();
}
}
}
else if( me->button() == Qt::MidButton )
{
@@ -790,8 +836,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 TimePos snapLength = TimePos( (int)(snapSize * TimePos::ticksPerBar()) );
@@ -801,7 +845,8 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
// The clip's new length
TimePos l = static_cast<int>( me->x() * TimePos::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 );
@@ -837,7 +882,7 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
m_trackView->trackContainerView()->currentPosition() +
static_cast<int>( x * TimePos::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 );
@@ -882,19 +927,17 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
TimePos::ticksPerBar() ) );
s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) );
}
else
else if( m_action == Split )
{
SampleTCO * sTco = dynamic_cast<SampleTCO*>( m_tco );
if( ( me->x() > width() - RESIZE_GRIP_WIDTH && !me->buttons() && !m_tco->getAutoResize() )
|| ( me->x() < RESIZE_GRIP_WIDTH && !me->buttons() && sTco && !m_tco->getAutoResize() ) )
{
setCursor( Qt::SizeHorCursor );
}
else
{
leaveEvent( NULL );
if (sTco) {
setCursor( m_cursorKnife );
setMarkerPos( knifeMarkerPos( me ) );
}
update();
}
// None of the actions above, we will just handle the cursor
else { updateCursor(me); }
}
@@ -918,17 +961,26 @@ 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 )
{
const float ppb = m_trackView->trackContainerView()->pixelsPerBar();
const TimePos relPos = me->pos().x() * TimePos::ticksPerBar() / ppb;
splitTCO(unquantizedModHeld(me) ?
relPos :
quantizeSplitPos(relPos, me->modifiers() & Qt::ShiftModifier)
);
}
m_action = NoAction;
delete m_hint;
m_hint = NULL;
s_textFloat->hide();
leaveEvent( NULL );
updateCursor(me);
selectableObject::mouseReleaseEvent( me );
}
@@ -1271,6 +1323,15 @@ 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
*
*
@@ -1285,12 +1346,8 @@ TimePos TrackContentObjectView::draggedTCOPos( QMouseEvent * me )
TimePos newPos = m_initialTCOPos + mouseOff * TimePos::ticksPerBar() / ppb;
TimePos 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 )
@@ -1313,6 +1370,50 @@ TimePos 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 TimePos
const float ppb = m_trackView->trackContainerView()->pixelsPerBar();
TimePos midiPos = markerPos * TimePos::ticksPerBar() / ppb;
//2: Snap to the correct position, based on modifier keys
midiPos = quantizeSplitPos( midiPos, me->modifiers() & Qt::ShiftModifier );
//3: Convert back to a pixel position
return midiPos * ppb / TimePos::ticksPerBar();
}
}
TimePos TrackContentObjectView::quantizeSplitPos( TimePos 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 TimePos leftPos = midiPos.quantize( snapSize );
//...or right clip...
const TimePos rightOff = m_tco->length() - midiPos;
const TimePos 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 TimePos(midiPos + m_initialTCOPos).quantize( snapSize ) - m_initialTCOPos;
}
}
// Return the color that the TCO's background should be
QColor TrackContentObjectView::getColorForDisplay( QColor defaultColor )
{
@@ -1355,4 +1456,3 @@ QColor TrackContentObjectView::getColorForDisplay( QColor defaultColor )
// Return color to caller
return c;
}

View File

@@ -449,6 +449,11 @@ void SongEditor::setEditModeDraw()
setEditMode(DrawMode);
}
void SongEditor::setEditModeKnife()
{
setEditMode(KnifeMode);
}
void SongEditor::setEditModeSelect()
{
setEditMode(SelectMode);
@@ -860,6 +865,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());
@@ -944,13 +957,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"));
@@ -1031,6 +1047,13 @@ void SongEditorWindow::updateSnapLabel(){
void SongEditorWindow::syncEditMode(){
m_editModeGroup->checkedAction()->trigger();
}
void SongEditorWindow::resizeEvent(QResizeEvent *event)
{
emit resized();
@@ -1108,31 +1131,3 @@ void SongEditorWindow::adjustUiAfterProjectLoad()
connect( qobject_cast<SubWindow *>( parentWidget() ), SIGNAL( focusLost() ), this, SLOT( lostFocus() ) );
m_editor->scrolled(0);
}
void SongEditorWindow::keyPressEvent( QKeyEvent *ke )
{
if( ke->key() == Qt::Key_Control )
{
m_crtlAction = m_editModeGroup->checkedAction();
m_selectModeAction->setChecked( true );
m_selectModeAction->trigger();
}
}
void SongEditorWindow::keyReleaseEvent( QKeyEvent *ke )
{
if( ke->key() == Qt::Key_Control )
{
if( m_crtlAction )
{
m_crtlAction->setChecked( true );
m_crtlAction->trigger();
}
}
}

View File

@@ -547,14 +547,22 @@ void TrackContentWidget::dropEvent( QDropEvent * de )
*/
void TrackContentWidget::mousePressEvent( QMouseEvent * me )
{
// Enable box select if control is held when clicking an empty space
// (If we had clicked a TCO it would have intercepted the mouse event)
if( me->modifiers() & Qt::ControlModifier ){
gui->songEditor()->m_editor->setEditMode(SongEditor::EditMode::SelectMode);
}
// Forward event to allow box select if the editor supports it and is in that mode
if( m_trackView->trackContainerView()->allowRubberband() == true )
{
QWidget::mousePressEvent( me );
}
// Forward shift clicks so tracks can be resized
else if( me->modifiers() & Qt::ShiftModifier )
{
QWidget::mousePressEvent( me );
}
// For an unmodified click, create a new TCO
else if( me->button() == Qt::LeftButton &&
!m_trackView->trackContainerView()->fixedTCOs() )
{
@@ -573,6 +581,15 @@ void TrackContentWidget::mousePressEvent( QMouseEvent * me )
void TrackContentWidget::mouseReleaseEvent( QMouseEvent * me )
{
gui->songEditor()->syncEditMode();
QWidget::mouseReleaseEvent(me);
}
/*! \brief Repaint the trackContentWidget on command
*
* \param pe the Paint Event to respond to
@@ -707,4 +724,3 @@ void TrackContentWidget::setGridColor( const QBrush & c )
//! \brief CSS theming qproperty access method
void TrackContentWidget::setEmbossColor( const QBrush & c )
{ m_embossColor = c; }

View File

@@ -106,9 +106,6 @@ SampleTCO::SampleTCO( Track * _track ) :
updateTrackTcos();
}
SampleTCO::SampleTCO(const SampleTCO& orig) :
SampleTCO(orig.getTrack())
{
@@ -634,6 +631,10 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
embed::getIconPixmap( "muted", size, size ) );
}
if ( m_marker )
{
p.drawLine(m_markerPos, rect().bottom(), m_markerPos, rect().top());
}
// recording sample tracks is not possible at the moment
/* if( m_tco->isRecord() )
@@ -667,6 +668,39 @@ void SampleTCOView::reverseSample()
//! Split this TCO.
/*! \param pos the position of the split, relative to the start of the clip */
bool SampleTCOView::splitTCO( const TimePos pos )
{
setMarkerEnabled( false );
const TimePos splitPos = m_initialTCOPos + pos;
//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 )
{
m_tco->getTrack()->addJournalCheckPoint();
m_tco->getTrack()->saveJournallingState( false );
SampleTCO * rightTCO = new SampleTCO ( *m_tco );
m_tco->changeLength( splitPos - m_initialTCOPos );
rightTCO->movePosition( splitPos );
rightTCO->changeLength( m_initialTCOEnd - splitPos );
rightTCO->setStartTimeOffset( m_tco->startTimeOffset() - m_tco->length() );
m_tco->getTrack()->restoreJournallingState();
return true;
}
else { return false; }
}
SampleTrack::SampleTrack(TrackContainer* tc) :