Enhanced snapping in song editor (#4973)
* New default behavior: Preserves offsets when moving clips, resizes in fixed increments. * Adds shift + drag: Snaps move start position (like current behavior) or end position (new), based on which is closest to the real position. When moving a selection, the grabbed clip snaps into position and the rest move relative to it. * Adds alt + drag: Allows fine adjustment of a clip's position or size, as an alternative to ctrl + drag. * Adds a Q dropdown in the song editor to allow finer or coarser snapping (8 bars to 1/16th bar) * Adds a proportional snap toggle. When enabled, snapping size/Q adjusts based on zoom, and a label appears showing the current snap size. This is disabled by default.
This commit is contained in:
BIN
data/themes/classic/proportional_snap.png
Normal file
BIN
data/themes/classic/proportional_snap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 263 B |
BIN
data/themes/default/proportional_snap.png
Normal file
BIN
data/themes/default/proportional_snap.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 263 B |
@@ -63,7 +63,7 @@ public:
|
||||
MidiTime( const tact_t tact, const tick_t ticks );
|
||||
MidiTime( const tick_t ticks = 0 );
|
||||
|
||||
MidiTime toNearestTact() const;
|
||||
MidiTime quantize(float) const;
|
||||
MidiTime toAbsoluteTact() const;
|
||||
|
||||
MidiTime& operator+=( const MidiTime& time );
|
||||
@@ -110,4 +110,3 @@ private:
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ public:
|
||||
void loadSettings( const QDomElement& element );
|
||||
|
||||
ComboBoxModel *zoomingModel() const;
|
||||
ComboBoxModel *snappingModel() const;
|
||||
float getSnapSize() const;
|
||||
QString getSnapSizeString() const;
|
||||
|
||||
public slots:
|
||||
void scrolled( int new_pos );
|
||||
@@ -80,6 +83,7 @@ public slots:
|
||||
void setEditMode( EditMode mode );
|
||||
void setEditModeDraw();
|
||||
void setEditModeSelect();
|
||||
void toggleProportionalSnap();
|
||||
|
||||
void updatePosition( const MidiTime & t );
|
||||
void updatePositionLine();
|
||||
@@ -130,6 +134,8 @@ private:
|
||||
positionLine * m_positionLine;
|
||||
|
||||
ComboBoxModel* m_zoomingModel;
|
||||
ComboBoxModel* m_snappingModel;
|
||||
bool m_proportionalSnap;
|
||||
|
||||
static const QVector<double> m_zoomLevels;
|
||||
|
||||
@@ -141,7 +147,6 @@ private:
|
||||
EditMode m_ctrlMode; // mode they were in before they hit ctrl
|
||||
|
||||
friend class SongEditorWindow;
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
@@ -170,6 +175,8 @@ protected slots:
|
||||
void lostFocus();
|
||||
void adjustUiAfterProjectLoad();
|
||||
|
||||
void updateSnapLabel();
|
||||
|
||||
signals:
|
||||
void playTriggered();
|
||||
void resized();
|
||||
@@ -181,6 +188,7 @@ private:
|
||||
QAction* m_addBBTrackAction;
|
||||
QAction* m_addSampleTrackAction;
|
||||
QAction* m_addAutomationTrackAction;
|
||||
QAction* m_setProportionalSnapAction;
|
||||
|
||||
ActionGroup * m_editModeGroup;
|
||||
QAction* m_drawModeAction;
|
||||
@@ -188,6 +196,8 @@ private:
|
||||
QAction* m_crtlAction;
|
||||
|
||||
ComboBox * m_zoomingComboBox;
|
||||
ComboBox * m_snappingComboBox;
|
||||
QLabel* m_snapSizeLabel;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -236,7 +236,7 @@ public:
|
||||
// access needsUpdate member variable
|
||||
bool needsUpdate();
|
||||
void setNeedsUpdate( bool b );
|
||||
|
||||
|
||||
public slots:
|
||||
virtual bool close();
|
||||
void cut();
|
||||
@@ -297,6 +297,9 @@ private:
|
||||
Actions m_action;
|
||||
QPoint m_initialMousePos;
|
||||
QPoint m_initialMouseGlobalPos;
|
||||
MidiTime m_initialTCOPos;
|
||||
MidiTime m_initialTCOEnd;
|
||||
QVector<MidiTime> m_initialOffsets;
|
||||
|
||||
TextFloat * m_hint;
|
||||
|
||||
@@ -311,14 +314,17 @@ private:
|
||||
bool m_gradient;
|
||||
|
||||
bool m_needsUpdate;
|
||||
inline void setInitialMousePos( QPoint pos )
|
||||
inline void setInitialPos( QPoint pos )
|
||||
{
|
||||
m_initialMousePos = pos;
|
||||
m_initialMouseGlobalPos = mapToGlobal( pos );
|
||||
m_initialTCOPos = m_tco->startPosition();
|
||||
m_initialTCOEnd = m_initialTCOPos + m_tco->length();
|
||||
}
|
||||
void setInitialOffsets();
|
||||
|
||||
bool mouseMovedDistance( QMouseEvent * me, int distance );
|
||||
|
||||
MidiTime draggedTCOPos( QMouseEvent * me );
|
||||
} ;
|
||||
|
||||
|
||||
@@ -564,13 +570,13 @@ public:
|
||||
|
||||
using Model::dataChanged;
|
||||
|
||||
inline int getHeight()
|
||||
inline int getHeight()
|
||||
{
|
||||
return m_height >= MINIMAL_TRACK_HEIGHT
|
||||
? m_height
|
||||
? m_height
|
||||
: DEFAULT_TRACK_HEIGHT;
|
||||
}
|
||||
inline void setHeight( int height )
|
||||
inline void setHeight( int height )
|
||||
{
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
@@ -267,6 +267,9 @@ TrackContentObjectView::TrackContentObjectView( TrackContentObject * tco,
|
||||
m_action( NoAction ),
|
||||
m_initialMousePos( QPoint( 0, 0 ) ),
|
||||
m_initialMouseGlobalPos( QPoint( 0, 0 ) ),
|
||||
m_initialTCOPos( MidiTime(0) ),
|
||||
m_initialTCOEnd( MidiTime(0) ),
|
||||
m_initialOffsets( QVector<MidiTime>() ),
|
||||
m_hint( NULL ),
|
||||
m_mutedColor( 0, 0, 0 ),
|
||||
m_mutedBackgroundColor( 0, 0, 0 ),
|
||||
@@ -524,7 +527,7 @@ void TrackContentObjectView::updatePosition()
|
||||
void TrackContentObjectView::dragEnterEvent( QDragEnterEvent * dee )
|
||||
{
|
||||
TrackContentWidget * tcw = getTrackView()->getTrackContentWidget();
|
||||
MidiTime tcoPos = MidiTime( m_tco->startPosition().getTact(), 0 );
|
||||
MidiTime tcoPos = MidiTime( m_tco->startPosition() );
|
||||
if( tcw->canPasteSelection( tcoPos, dee ) == false )
|
||||
{
|
||||
dee->ignore();
|
||||
@@ -563,7 +566,7 @@ void TrackContentObjectView::dropEvent( QDropEvent * de )
|
||||
if( m_trackView->trackContainerView()->allowRubberband() == true )
|
||||
{
|
||||
TrackContentWidget * tcw = getTrackView()->getTrackContentWidget();
|
||||
MidiTime tcoPos = MidiTime( m_tco->startPosition().getTact(), 0 );
|
||||
MidiTime tcoPos = MidiTime( m_tco->startPosition() );
|
||||
if( tcw->pasteSelection( tcoPos, de ) == true )
|
||||
{
|
||||
de->accept();
|
||||
@@ -711,7 +714,8 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai
|
||||
*/
|
||||
void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
|
||||
{
|
||||
setInitialMousePos( me->pos() );
|
||||
setInitialPos( me->pos() );
|
||||
setInitialOffsets();
|
||||
if( !fixedTCOs() && me->button() == Qt::LeftButton )
|
||||
{
|
||||
if( me->modifiers() & Qt::ControlModifier )
|
||||
@@ -725,7 +729,9 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
|
||||
m_action = ToggleSelected;
|
||||
}
|
||||
}
|
||||
else if( !me->modifiers() )
|
||||
else if( !me->modifiers()
|
||||
|| (me->modifiers() & Qt::AltModifier)
|
||||
|| (me->modifiers() & Qt::ShiftModifier) )
|
||||
{
|
||||
if( isSelected() )
|
||||
{
|
||||
@@ -739,7 +745,8 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me )
|
||||
// move or resize
|
||||
m_tco->setJournalling( false );
|
||||
|
||||
setInitialMousePos( me->pos() );
|
||||
setInitialPos( me->pos() );
|
||||
setInitialOffsets();
|
||||
|
||||
SampleTCO * sTco = dynamic_cast<SampleTCO*>( m_tco );
|
||||
if( me->x() < RESIZE_GRIP_WIDTH && sTco
|
||||
@@ -889,76 +896,86 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
|
||||
const float ppt = m_trackView->trackContainerView()->pixelsPerTact();
|
||||
if( m_action == Move )
|
||||
{
|
||||
const int x = mapToParent( me->pos() ).x() - m_initialMousePos.x();
|
||||
MidiTime t = qMax( 0, (int)
|
||||
m_trackView->trackContainerView()->currentPosition()+
|
||||
static_cast<int>( x * MidiTime::ticksPerTact() /
|
||||
ppt ) );
|
||||
if( ! ( me->modifiers() & Qt::ControlModifier )
|
||||
&& me->button() == Qt::NoButton )
|
||||
{
|
||||
t = t.toNearestTact();
|
||||
}
|
||||
m_tco->movePosition( t );
|
||||
MidiTime newPos = draggedTCOPos( me );
|
||||
|
||||
// Don't go left of bar zero
|
||||
newPos = max( 0, newPos.getTicks() );
|
||||
m_tco->movePosition( newPos );
|
||||
m_trackView->getTrackContentWidget()->changePosition();
|
||||
s_textFloat->setText( QString( "%1:%2" ).
|
||||
arg( m_tco->startPosition().getTact() + 1 ).
|
||||
arg( m_tco->startPosition().getTicks() %
|
||||
arg( newPos.getTact() + 1 ).
|
||||
arg( newPos.getTicks() %
|
||||
MidiTime::ticksPerTact() ) );
|
||||
s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) );
|
||||
}
|
||||
else if( m_action == MoveSelection )
|
||||
{
|
||||
const int dx = me->x() - m_initialMousePos.x();
|
||||
const bool snap = !(me->modifiers() & Qt::ControlModifier) &&
|
||||
me->button() == Qt::NoButton;
|
||||
// 1: Find the position we want to move the grabbed TCO to
|
||||
MidiTime newPos = draggedTCOPos( me );
|
||||
|
||||
// 2: Handle moving the other selected TCOs the same distance
|
||||
QVector<selectableObject *> so =
|
||||
m_trackView->trackContainerView()->selectedObjects();
|
||||
QVector<TrackContentObject *> tcos;
|
||||
int smallestPos = 0;
|
||||
MidiTime dtick = MidiTime( static_cast<int>( dx *
|
||||
MidiTime::ticksPerTact() / ppt ) );
|
||||
if( snap )
|
||||
{
|
||||
dtick = dtick.toNearestTact();
|
||||
}
|
||||
// find out smallest position of all selected objects for not
|
||||
// moving an object before zero
|
||||
QVector<TrackContentObject *> tcos; // List of selected clips
|
||||
int leftmost = 0; // Leftmost clip's offset from grabbed clip
|
||||
// Populate tcos, find leftmost
|
||||
for( QVector<selectableObject *>::iterator it = so.begin();
|
||||
it != so.end(); ++it )
|
||||
{
|
||||
TrackContentObjectView * tcov =
|
||||
dynamic_cast<TrackContentObjectView *>( *it );
|
||||
if( tcov == NULL )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
TrackContentObject * tco = tcov->m_tco;
|
||||
tcos.push_back( tco );
|
||||
smallestPos = qMin<int>( smallestPos,
|
||||
(int)tco->startPosition() + dtick );
|
||||
}
|
||||
dtick -= smallestPos;
|
||||
if( snap )
|
||||
{
|
||||
dtick = dtick.toAbsoluteTact(); // round toward 0
|
||||
if( tcov == NULL ) { continue; }
|
||||
tcos.push_back( tcov->m_tco );
|
||||
int index = std::distance( so.begin(), it );
|
||||
leftmost = min (leftmost, m_initialOffsets[index].getTicks() );
|
||||
}
|
||||
// Make sure the leftmost clip doesn't get moved to a negative position
|
||||
if ( newPos.getTicks() + leftmost < 0 ) { newPos = -leftmost; }
|
||||
|
||||
for( QVector<TrackContentObject *>::iterator it = tcos.begin();
|
||||
it != tcos.end(); ++it )
|
||||
{
|
||||
( *it )->movePosition( ( *it )->startPosition() + dtick );
|
||||
int index = std::distance( tcos.begin(), it );
|
||||
( *it )->movePosition( newPos + m_initialOffsets[index] );
|
||||
}
|
||||
}
|
||||
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::ticksPerTact()) );
|
||||
|
||||
if( m_action == Resize )
|
||||
{
|
||||
MidiTime t = qMax( MidiTime::ticksPerTact() / 16, static_cast<int>( me->x() * MidiTime::ticksPerTact() / ppt ) );
|
||||
if( ! ( me->modifiers() & Qt::ControlModifier ) && me->button() == Qt::NoButton )
|
||||
{
|
||||
t = qMax<int>( MidiTime::ticksPerTact(), t.toNearestTact() );
|
||||
// The clip's new length
|
||||
MidiTime l = static_cast<int>( me->x() * MidiTime::ticksPerTact() / ppt );
|
||||
|
||||
if ( unquantized )
|
||||
{ // We want to preserve this adjusted offset,
|
||||
// even if the user switches to snapping later
|
||||
setInitialPos( m_initialMousePos );
|
||||
// Don't resize to less than 1 tick
|
||||
m_tco->changeLength( qMax<int>( 1, l ) );
|
||||
}
|
||||
else if ( me->modifiers() & Qt::ShiftModifier )
|
||||
{ // If shift is held, quantize clip's end position
|
||||
MidiTime end = MidiTime( m_initialTCOPos + l ).quantize( snapSize );
|
||||
// The end position has to be after the clip's start
|
||||
MidiTime min = m_initialTCOPos.quantize( snapSize );
|
||||
if ( min <= m_initialTCOPos ) min += snapLength;
|
||||
m_tco->changeLength( qMax<int>(min - m_initialTCOPos, end - m_initialTCOPos) );
|
||||
}
|
||||
else
|
||||
{ // Otherwise, resize in fixed increments
|
||||
MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos;
|
||||
MidiTime offset = MidiTime( l - initialLength ).quantize( snapSize );
|
||||
// Don't resize to less than 1 tick
|
||||
MidiTime min = MidiTime( initialLength % snapLength );
|
||||
if (min < 1) min += snapLength;
|
||||
m_tco->changeLength( qMax<int>( min, initialLength + offset) );
|
||||
}
|
||||
m_tco->changeLength( t );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -969,15 +986,34 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
|
||||
|
||||
MidiTime t = qMax( 0, (int)
|
||||
m_trackView->trackContainerView()->currentPosition()+
|
||||
static_cast<int>( x * MidiTime::ticksPerTact() /
|
||||
ppt ) );
|
||||
if( ! ( me->modifiers() & Qt::ControlModifier )
|
||||
&& me->button() == Qt::NoButton )
|
||||
{
|
||||
t = t.toNearestTact();
|
||||
static_cast<int>( x * MidiTime::ticksPerTact() / ppt ) );
|
||||
|
||||
if( unquantized )
|
||||
{ // We want to preserve this adjusted offset,
|
||||
// even if the user switches to snapping later
|
||||
setInitialPos( m_initialMousePos );
|
||||
//Don't resize to less than 1 tick
|
||||
t = qMin<int>( m_initialTCOEnd - 1, t);
|
||||
}
|
||||
else if( me->modifiers() & Qt::ShiftModifier )
|
||||
{ // If shift is held, quantize clip's start position
|
||||
// Don't let the start position move past the end position
|
||||
MidiTime max = m_initialTCOEnd.quantize( snapSize );
|
||||
if ( max >= m_initialTCOEnd ) max -= snapLength;
|
||||
t = qMin<int>( max, t.quantize( snapSize ) );
|
||||
}
|
||||
else
|
||||
{ // Otherwise, resize in fixed increments
|
||||
// Don't resize to less than 1 tick
|
||||
MidiTime initialLength = m_initialTCOEnd - m_initialTCOPos;
|
||||
MidiTime minLength = MidiTime( initialLength % snapLength );
|
||||
if (minLength < 1) minLength += snapLength;
|
||||
MidiTime offset = MidiTime(t - m_initialTCOPos).quantize( snapSize );
|
||||
t = qMin<int>( m_initialTCOEnd - minLength, m_initialTCOPos + offset );
|
||||
}
|
||||
|
||||
MidiTime oldPos = m_tco->startPosition();
|
||||
if( m_tco->length() + ( oldPos - t ) >= MidiTime::ticksPerTact() )
|
||||
if( m_tco->length() + ( oldPos - t ) >= 1 )
|
||||
{
|
||||
m_tco->movePosition( t );
|
||||
m_trackView->getTrackContentWidget()->changePosition();
|
||||
@@ -1091,7 +1127,6 @@ void TrackContentObjectView::contextMenuEvent( QContextMenuEvent * cme )
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief How many pixels a tact (bar) takes for this trackContentObjectView.
|
||||
*
|
||||
* \return the number of pixels per tact (bar).
|
||||
@@ -1102,6 +1137,27 @@ float TrackContentObjectView::pixelsPerTact()
|
||||
}
|
||||
|
||||
|
||||
/*! \brief Save the offsets between all selected tracks and a clicked track */
|
||||
void TrackContentObjectView::setInitialOffsets()
|
||||
{
|
||||
QVector<selectableObject *> so = m_trackView->trackContainerView()->selectedObjects();
|
||||
QVector<MidiTime> offsets;
|
||||
for( QVector<selectableObject *>::iterator it = so.begin();
|
||||
it != so.end(); ++it )
|
||||
{
|
||||
TrackContentObjectView * tcov =
|
||||
dynamic_cast<TrackContentObjectView *>( *it );
|
||||
if( tcov == NULL )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
offsets.push_back( tcov->m_tco->startPosition() - m_initialTCOPos );
|
||||
}
|
||||
|
||||
m_initialOffsets = offsets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*! \brief Detect whether the mouse moved more than n pixels on screen.
|
||||
@@ -1118,6 +1174,49 @@ bool TrackContentObjectView::mouseMovedDistance( QMouseEvent * me, int distance
|
||||
|
||||
|
||||
|
||||
/*! \brief Calculate the new position of a dragged TCO from a mouse event
|
||||
*
|
||||
*
|
||||
* \param me The QMouseEvent
|
||||
*/
|
||||
MidiTime TrackContentObjectView::draggedTCOPos( QMouseEvent * me )
|
||||
{
|
||||
//Pixels per tact
|
||||
const float ppt = m_trackView->trackContainerView()->pixelsPerTact();
|
||||
// The pixel distance that the mouse has moved
|
||||
const int mouseOff = mapToGlobal(me->pos()).x() - m_initialMouseGlobalPos.x();
|
||||
MidiTime newPos = m_initialTCOPos + mouseOff * MidiTime::ticksPerTact() / ppt;
|
||||
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
|
||||
setInitialPos( m_initialMousePos );
|
||||
}
|
||||
else if ( me->modifiers() & Qt::ShiftModifier )
|
||||
{ // If shift is held, quantize position (Default in 1.2.0 and earlier)
|
||||
// or end position, whichever is closest to the actual position
|
||||
MidiTime startQ = newPos.quantize( gui->songEditor()->m_editor->getSnapSize() );
|
||||
// Find start position that gives snapped clip end position
|
||||
MidiTime endQ = ( newPos + m_tco->length() );
|
||||
endQ = endQ.quantize( gui->songEditor()->m_editor->getSnapSize() );
|
||||
endQ = endQ - m_tco->length();
|
||||
// Select the position closest to actual position
|
||||
if ( abs(newPos - startQ) < abs(newPos - endQ) ) newPos = startQ;
|
||||
else newPos = endQ;
|
||||
}
|
||||
else
|
||||
{ // Otherwise, quantize moved distance (preserves user offsets)
|
||||
newPos = m_initialTCOPos + offset.quantize( gui->songEditor()->m_editor->getSnapSize() );
|
||||
}
|
||||
return newPos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ===========================================================================
|
||||
// trackContentWidget
|
||||
@@ -1496,7 +1595,6 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de )
|
||||
int initialTrackIndex = tiAttr.value().toInt();
|
||||
QDomAttr tcoPosAttr = metadata.attributeNode( "grabbedTCOPos" );
|
||||
MidiTime grabbedTCOPos = tcoPosAttr.value().toInt();
|
||||
MidiTime grabbedTCOTact = MidiTime( grabbedTCOPos.getTact(), 0 );
|
||||
|
||||
// Snap the mouse position to the beginning of the dropped tact, in ticks
|
||||
const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks();
|
||||
@@ -1517,6 +1615,10 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de )
|
||||
// TODO -- Need to draw the hovericon either way, or ghost the TCOs
|
||||
// onto their final position.
|
||||
|
||||
// All patterns should be offset the same amount as the grabbed pattern
|
||||
// The offset is quantized (rather than the positions) to preserve fine adjustments
|
||||
int offset = MidiTime(tcoPos - grabbedTCOPos).quantize(gui->songEditor()->m_editor->getSnapSize());
|
||||
|
||||
for( int i = 0; i<tcoNodes.length(); i++ )
|
||||
{
|
||||
QDomElement outerTCOElement = tcoNodes.item( i ).toElement();
|
||||
@@ -1526,13 +1628,11 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de )
|
||||
int finalTrackIndex = trackIndex + ( currentTrackIndex - initialTrackIndex );
|
||||
Track * t = tracks.at( finalTrackIndex );
|
||||
|
||||
// Compute the final position by moving the tco's pos by
|
||||
// the number of tacts between the first TCO and the mouse drop TCO
|
||||
MidiTime oldPos = tcoElement.attributeNode( "pos" ).value().toInt();
|
||||
MidiTime offset = oldPos - MidiTime( oldPos.getTact(), 0 );
|
||||
MidiTime oldTact = MidiTime( oldPos.getTact(), 0 );
|
||||
MidiTime delta = offset + ( oldTact - grabbedTCOTact );
|
||||
MidiTime pos = tcoPos + delta;
|
||||
// The new position is the old position plus the offset.
|
||||
MidiTime pos = tcoElement.attributeNode( "pos" ).value().toInt() + offset;
|
||||
// If we land on ourselves, offset by one snap
|
||||
MidiTime shift = MidiTime::ticksPerTact() * gui->songEditor()->m_editor->getSnapSize();
|
||||
if (offset == 0) { pos += shift; }
|
||||
|
||||
TrackContentObject * tco = t->createTCO( pos );
|
||||
tco->restoreState( tcoElement );
|
||||
@@ -1562,7 +1662,7 @@ bool TrackContentWidget::pasteSelection( MidiTime tcoPos, QDropEvent * de )
|
||||
*/
|
||||
void TrackContentWidget::dropEvent( QDropEvent * de )
|
||||
{
|
||||
MidiTime tcoPos = MidiTime( getPosition( de->pos().x() ).getTact(), 0 );
|
||||
MidiTime tcoPos = MidiTime( getPosition( de->pos().x() ) );
|
||||
if( pasteSelection( tcoPos, de ) == true )
|
||||
{
|
||||
de->accept();
|
||||
|
||||
@@ -63,13 +63,18 @@ MidiTime::MidiTime( const tick_t ticks ) :
|
||||
{
|
||||
}
|
||||
|
||||
MidiTime MidiTime::toNearestTact() const
|
||||
MidiTime MidiTime::quantize(float bars) const
|
||||
{
|
||||
if( m_ticks % s_ticksPerTact >= s_ticksPerTact/2 )
|
||||
{
|
||||
return ( getTact() + 1 ) * s_ticksPerTact;
|
||||
}
|
||||
return getTact() * s_ticksPerTact;
|
||||
//The intervals we should snap to, our new position should be a factor of this
|
||||
int interval = s_ticksPerTact * bars;
|
||||
//The lower position we could snap to
|
||||
int lowPos = m_ticks / interval;
|
||||
//Offset from the lower position
|
||||
int offset = m_ticks % interval;
|
||||
//1 if we should snap up, 0 if we shouldn't
|
||||
int snapUp = offset / (interval / 2);
|
||||
|
||||
return (lowPos + snapUp) * interval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* TimeLineWidget.cpp - class timeLine, representing a time-line with position marker
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -384,14 +384,14 @@ void TimeLineWidget::mouseMoveEvent( QMouseEvent* event )
|
||||
}
|
||||
else
|
||||
{
|
||||
m_loopPos[i] = t.toNearestTact();
|
||||
m_loopPos[i] = t.quantize(1.0);
|
||||
}
|
||||
// Catch begin == end
|
||||
if( m_loopPos[0] == m_loopPos[1] )
|
||||
{
|
||||
// Note, swap 1 and 0 below and the behavior "skips" the other
|
||||
// marking instead of pushing it.
|
||||
if( m_action == MoveLoopBegin )
|
||||
if( m_action == MoveLoopBegin )
|
||||
m_loopPos[0] -= MidiTime::ticksPerTact();
|
||||
else
|
||||
m_loopPos[1] += MidiTime::ticksPerTact();
|
||||
|
||||
@@ -76,11 +76,14 @@ SongEditor::SongEditor( Song * song ) :
|
||||
TrackContainerView( song ),
|
||||
m_song( song ),
|
||||
m_zoomingModel(new ComboBoxModel()),
|
||||
m_snappingModel(new ComboBoxModel()),
|
||||
m_proportionalSnap( false ),
|
||||
m_scrollBack( false ),
|
||||
m_smoothScroll( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() ),
|
||||
m_mode(DrawMode)
|
||||
{
|
||||
m_zoomingModel->setParent(this);
|
||||
m_snappingModel->setParent(this);
|
||||
// create time-line
|
||||
m_widgetWidthTotal = ConfigManager::inst()->value( "ui",
|
||||
"compacttrackbuttons" ).toInt()==1 ?
|
||||
@@ -230,7 +233,7 @@ SongEditor::SongEditor( Song * song ) :
|
||||
connect( m_song, SIGNAL( lengthChanged( int ) ),
|
||||
this, SLOT( updateScrollBar( int ) ) );
|
||||
|
||||
// Set up zooming model
|
||||
//Set up zooming model
|
||||
for( float const & zoomLevel : m_zoomLevels )
|
||||
{
|
||||
m_zoomingModel->addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
|
||||
@@ -240,6 +243,24 @@ SongEditor::SongEditor( Song * song ) :
|
||||
connect( m_zoomingModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( zoomingChanged() ) );
|
||||
|
||||
//Set up snapping model, 2^i
|
||||
for ( int i = 3; i >= -4; i-- )
|
||||
{
|
||||
if ( i > 0 )
|
||||
{
|
||||
m_snappingModel->addItem( QString( "%1 Bars").arg( 1 << i ) );
|
||||
}
|
||||
else if ( i == 0 )
|
||||
{
|
||||
m_snappingModel->addItem( "1 Bar" );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_snappingModel->addItem( QString( "1/%1 Bar" ).arg( 1 << (-i) ) );
|
||||
}
|
||||
}
|
||||
m_snappingModel->setInitValue( m_snappingModel->findText( "1 Bar" ) );
|
||||
|
||||
setFocusPolicy( Qt::StrongFocus );
|
||||
setFocus();
|
||||
}
|
||||
@@ -264,6 +285,48 @@ void SongEditor::loadSettings( const QDomElement& element )
|
||||
|
||||
|
||||
|
||||
float SongEditor::getSnapSize() const
|
||||
{
|
||||
// 1 Bar is the third value in the snapping dropdown
|
||||
int val = -m_snappingModel->value() + 3;
|
||||
// If proportional snap is on, we snap to finer values when zoomed in
|
||||
if (m_proportionalSnap)
|
||||
{
|
||||
val = val - m_zoomingModel->value() + 3;
|
||||
}
|
||||
val = max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.
|
||||
|
||||
if ( val >= 0 ){
|
||||
return 1 << val;
|
||||
}
|
||||
else {
|
||||
return 1.0 / ( 1 << -val );
|
||||
}
|
||||
}
|
||||
|
||||
QString SongEditor::getSnapSizeString() const
|
||||
{
|
||||
int val = -m_snappingModel->value() + 3;
|
||||
val = val - m_zoomingModel->value() + 3;
|
||||
val = max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.
|
||||
|
||||
if ( val >= 0 ){
|
||||
int bars = 1 << val;
|
||||
if ( bars == 1 ) { return QString("1 Bar"); }
|
||||
else
|
||||
{
|
||||
return QString( "%1 Bars" ).arg(bars);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int div = ( 1 << -val );
|
||||
return QString( "1/%1 Bar" ).arg(div);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void SongEditor::setHighQuality( bool hq )
|
||||
{
|
||||
Engine::mixer()->changeQuality( Mixer::qualitySettings(
|
||||
@@ -298,6 +361,11 @@ void SongEditor::setEditModeSelect()
|
||||
setEditMode(SelectMode);
|
||||
}
|
||||
|
||||
void SongEditor::toggleProportionalSnap()
|
||||
{
|
||||
m_proportionalSnap = !m_proportionalSnap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -653,10 +721,19 @@ ComboBoxModel *SongEditor::zoomingModel() const
|
||||
|
||||
|
||||
|
||||
ComboBoxModel *SongEditor::snappingModel() const
|
||||
{
|
||||
return m_snappingModel;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SongEditorWindow::SongEditorWindow(Song* song) :
|
||||
Editor(Engine::mixer()->audioDev()->supportsCapture(), false),
|
||||
m_editor(new SongEditor(song)),
|
||||
m_crtlAction( NULL )
|
||||
m_crtlAction( NULL ),
|
||||
m_snapSizeLabel( new QLabel( m_toolBar ) )
|
||||
{
|
||||
setWindowTitle( tr( "Song-Editor" ) );
|
||||
setWindowIcon( embed::getIconPixmap( "songeditor" ) );
|
||||
@@ -718,23 +795,63 @@ SongEditorWindow::SongEditorWindow(Song* song) :
|
||||
QLabel * zoom_lbl = new QLabel( m_toolBar );
|
||||
zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );
|
||||
|
||||
// setup zooming-stuff
|
||||
//Set up zooming-stuff
|
||||
m_zoomingComboBox = new ComboBox( m_toolBar );
|
||||
m_zoomingComboBox->setFixedSize( 80, 22 );
|
||||
m_zoomingComboBox->move( 580, 4 );
|
||||
m_zoomingComboBox->setModel(m_editor->m_zoomingModel);
|
||||
m_zoomingComboBox->setToolTip(tr("Horizontal zooming"));
|
||||
connect(m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));
|
||||
|
||||
zoomToolBar->addWidget( zoom_lbl );
|
||||
zoomToolBar->addWidget( m_zoomingComboBox );
|
||||
|
||||
DropToolBar *snapToolBar = addDropToolBarToTop(tr("Snap controls"));
|
||||
QLabel * snap_lbl = new QLabel( m_toolBar );
|
||||
snap_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );
|
||||
|
||||
//Set up quantization/snapping selector
|
||||
m_snappingComboBox = new ComboBox( m_toolBar );
|
||||
m_snappingComboBox->setFixedSize( 80, 22 );
|
||||
m_snappingComboBox->setModel(m_editor->m_snappingModel);
|
||||
m_snappingComboBox->setToolTip(tr("Clip snapping size"));
|
||||
connect(m_editor->snappingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));
|
||||
|
||||
m_setProportionalSnapAction = new QAction(embed::getIconPixmap("proportional_snap"),
|
||||
tr("Toggle proportional snap on/off"), this);
|
||||
m_setProportionalSnapAction->setCheckable(true);
|
||||
m_setProportionalSnapAction->setChecked(false);
|
||||
connect(m_setProportionalSnapAction, SIGNAL(triggered()), m_editor, SLOT(toggleProportionalSnap()));
|
||||
connect(m_setProportionalSnapAction, SIGNAL(triggered()), this, SLOT(updateSnapLabel()) );
|
||||
|
||||
snapToolBar->addWidget( snap_lbl );
|
||||
snapToolBar->addWidget( m_snappingComboBox );
|
||||
snapToolBar->addSeparator();
|
||||
snapToolBar->addAction( m_setProportionalSnapAction );
|
||||
|
||||
snapToolBar->addSeparator();
|
||||
snapToolBar->addWidget( m_snapSizeLabel );
|
||||
|
||||
connect(song, SIGNAL(projectLoaded()), this, SLOT(adjustUiAfterProjectLoad()));
|
||||
connect(this, SIGNAL(resized()), m_editor, SLOT(updatePositionLine()));
|
||||
}
|
||||
|
||||
QSize SongEditorWindow::sizeHint() const
|
||||
{
|
||||
return {600, 300};
|
||||
return {720, 300};
|
||||
}
|
||||
|
||||
void SongEditorWindow::updateSnapLabel(){
|
||||
if (m_setProportionalSnapAction->isChecked())
|
||||
{
|
||||
m_snapSizeLabel->setText(QString("Snap: ") + m_editor->getSnapSizeString());
|
||||
m_snappingComboBox->setToolTip(tr("Base snapping size"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_snappingComboBox->setToolTip(tr("Clip snapping size"));
|
||||
m_snapSizeLabel->clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
|
||||
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
|
||||
float den = Engine::getSong()->getTimeSigModel().getDenominator();
|
||||
float ticksPerTact = DefaultTicksPerTact * nom / den;
|
||||
|
||||
|
||||
float offset = m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact();
|
||||
QRect r = QRect( TCO_BORDER_WIDTH + offset, spacing,
|
||||
qMax( static_cast<int>( m_tco->sampleLength() * ppt / ticksPerTact ), 1 ), rect().bottom() - 2 * spacing );
|
||||
@@ -931,7 +931,7 @@ void SampleTrackView::dropEvent(QDropEvent *de)
|
||||
? MidiTime(0)
|
||||
: MidiTime(((xPos - trackHeadWidth) / trackContainerView()->pixelsPerTact()
|
||||
* MidiTime::ticksPerTact()) + trackContainerView()->currentPosition()
|
||||
).toNearestTact();
|
||||
).quantize(1.0);
|
||||
|
||||
SampleTCO * sTco = static_cast<SampleTCO*>(getTrack()->createTCO(tcoPos));
|
||||
if (sTco) { sTco->setSampleFile(value); }
|
||||
@@ -1192,4 +1192,3 @@ void SampleTrackWindow::loadSettings(const QDomElement& element)
|
||||
m_stv->m_tlb->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user