Split TimeLineWidget into core and GUI parts (#7004)

This commit is contained in:
Dominic Clark
2023-12-16 14:19:36 +00:00
committed by GitHub
parent 8136b7002c
commit f3d3a1421e
15 changed files with 334 additions and 380 deletions

View File

@@ -248,7 +248,6 @@ private slots:
void onExportProject();
void onExportProjectTracks();
void onImportProject();
void onSongStopped();
void onSongModified();
void onProjectFileNameChanged();

View File

@@ -55,25 +55,20 @@ public:
public slots:
void changeState( int _n );
void changeState(int state);
signals:
void changedState( int _n );
void changedState(int state);
protected:
void mousePressEvent( QMouseEvent * _me ) override;
void mousePressEvent(QMouseEvent* me) override;
private:
QVector<QPair<QPixmap, QString> > m_states;
QVector<QPair<QPixmap, QString>> m_states;
QString m_generalToolTip;
int m_curState;
} ;
};
} // namespace lmms::gui

View File

@@ -25,16 +25,18 @@
#ifndef LMMS_SONG_H
#define LMMS_SONG_H
#include <array>
#include <memory>
#include <QHash>
#include <QString>
#include "TrackContainer.h"
#include "AudioEngine.h"
#include "Controller.h"
#include "lmms_constants.h"
#include "MeterModel.h"
#include "Timeline.h"
#include "TrackContainer.h"
#include "VstSyncController.h"
namespace lmms
@@ -105,7 +107,6 @@ public:
public:
PlayPos( const int abs = 0 ) :
TimePos( abs ),
m_timeLine( nullptr ),
m_currentFrame( 0.0f )
{
}
@@ -125,13 +126,11 @@ public:
{
return m_jumped;
}
gui::TimeLineWidget * m_timeLine;
private:
float m_currentFrame;
bool m_jumped;
} ;
};
void processNextBuffer();
@@ -274,6 +273,11 @@ public:
return getPlayPos(m_playMode);
}
auto getTimeline(PlayMode mode) -> Timeline& { return m_timelines[static_cast<std::size_t>(mode)]; }
auto getTimeline(PlayMode mode) const -> const Timeline& { return m_timelines[static_cast<std::size_t>(mode)]; }
auto getTimeline() -> Timeline& { return getTimeline(m_playMode); }
auto getTimeline() const -> const Timeline& { return getTimeline(m_playMode); }
void updateLength();
bar_t length() const
{
@@ -402,7 +406,7 @@ private slots:
void masterVolumeChanged();
void savePos();
void savePlayStartPosition();
void updateFramesPerTick();
@@ -481,6 +485,8 @@ private:
QHash<QString, int> m_errors;
std::array<Timeline, PlayModeCount> m_timelines;
PlayMode m_playMode;
PlayPos m_playPos[PlayModeCount];
bar_t m_length;

View File

@@ -34,6 +34,12 @@
class QPixmap;
class QToolBar;
namespace lmms {
class Timeline;
} // namespace lmms
namespace lmms::gui
{
@@ -42,7 +48,7 @@ class TextFloat;
class SongEditor;
class TimeLineWidget : public QWidget, public JournallingObject
class TimeLineWidget : public QWidget
{
Q_OBJECT
public:
@@ -60,24 +66,10 @@ public:
{
Enabled,
Disabled
} ;
};
enum class LoopPointState
{
Disabled,
Enabled
} ;
enum class BehaviourAtStopState
{
BackToZero,
BackToStart,
KeepStopPosition
} ;
TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos & pos,
const TimePos & begin, Song::PlayMode mode, QWidget * parent);
TimeLineWidget(int xoff, int yoff, float ppb, Song::PlayPos& pos, Timeline& timeline,
const TimePos& begin, Song::PlayMode mode, QWidget* parent);
~TimeLineWidget() override;
inline QColor const & getBarLineColor() const { return m_barLineColor; }
@@ -117,42 +109,6 @@ public:
return m_autoScroll;
}
BehaviourAtStopState behaviourAtStop() const
{
return m_behaviourAtStop;
}
void setBehaviourAtStop (int state)
{
emit loadBehaviourAtStop (state);
}
bool loopPointsEnabled() const
{
return m_loopPoints == LoopPointState::Enabled;
}
inline const TimePos & loopBegin() const
{
return ( m_loopPos[0] < m_loopPos[1] ) ?
m_loopPos[0] : m_loopPos[1];
}
inline const TimePos & loopEnd() const
{
return ( m_loopPos[0] > m_loopPos[1] ) ?
m_loopPos[0] : m_loopPos[1];
}
inline void savePos( const TimePos & pos )
{
m_savedPos = pos;
}
inline const TimePos & savedPos() const
{
return m_savedPos;
}
inline void setPixelsPerBar( float ppb )
{
m_ppb = ppb;
@@ -163,14 +119,6 @@ public:
void addToolButtons(QToolBar* _tool_bar );
void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
void loadSettings( const QDomElement & _this ) override;
inline QString nodeName() const override
{
return "timeline";
}
inline int markerX( const TimePos & _t ) const
{
return m_xOffset + static_cast<int>( ( _t - m_begin ) *
@@ -178,25 +126,17 @@ public:
}
signals:
void positionChanged(const lmms::TimePos& postion);
void regionSelectedFromPixels( int, int );
void selectionFinished();
public slots:
void updatePosition( const lmms::TimePos & );
void updatePosition()
{
updatePosition( TimePos() );
}
void updatePosition();
void setSnapSize( const float snapSize )
{
m_snapSize = snapSize;
}
void toggleAutoScroll( int _n );
void toggleLoopPoints( int _n );
void toggleBehaviourAtStop( int _n );
protected:
void paintEvent( QPaintEvent * _pe ) override;
@@ -222,8 +162,6 @@ private:
QColor m_barNumberColor;
AutoScrollState m_autoScroll;
LoopPointState m_loopPoints;
BehaviourAtStopState m_behaviourAtStop;
bool m_changedPosition;
@@ -232,12 +170,9 @@ private:
float m_ppb;
float m_snapSize;
Song::PlayPos & m_pos;
Timeline* m_timeline;
const TimePos & m_begin;
const Song::PlayMode m_mode;
TimePos m_loopPos[2];
TimePos m_savedPos;
TextFloat * m_hint;
int m_initalXSelect;
@@ -253,17 +188,7 @@ private:
} m_action;
int m_moveXOff;
signals:
void positionChanged( const lmms::TimePos & _t );
void loopPointStateLoaded( int _n );
void positionMarkerMoved();
void loadBehaviourAtStop( int _n );
} ;
};
} // namespace lmms::gui

82
include/Timeline.h Normal file
View File

@@ -0,0 +1,82 @@
/*
* Timeline.h
*
* Copyright (c) 2023 Dominic Clark
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#ifndef LMMS_TIMELINE_H
#define LMMS_TIMELINE_H
#include <QObject>
#include "JournallingObject.h"
#include "TimePos.h"
namespace lmms {
class Timeline : public QObject, public JournallingObject
{
Q_OBJECT
public:
enum class StopBehaviour
{
BackToZero,
BackToStart,
KeepPosition
};
auto loopBegin() const -> TimePos { return m_loopBegin; }
auto loopEnd() const -> TimePos { return m_loopEnd; }
auto loopEnabled() const -> bool { return m_loopEnabled; }
void setLoopBegin(TimePos begin);
void setLoopEnd(TimePos end);
void setLoopPoints(TimePos begin, TimePos end);
void setLoopEnabled(bool enabled);
auto playStartPosition() const -> TimePos { return m_playStartPosition; }
auto stopBehaviour() const -> StopBehaviour { return m_stopBehaviour; }
void setPlayStartPosition(TimePos position) { m_playStartPosition = position; }
void setStopBehaviour(StopBehaviour behaviour);
auto nodeName() const -> QString override { return "timeline"; }
signals:
void loopEnabledChanged(bool enabled);
void stopBehaviourChanged(lmms::Timeline::StopBehaviour behaviour);
protected:
void saveSettings(QDomDocument& doc, QDomElement& element) override;
void loadSettings(const QDomElement& element) override;
private:
TimePos m_loopBegin = TimePos{0};
TimePos m_loopEnd = TimePos{DefaultTicksPerBar};
bool m_loopEnabled = false;
StopBehaviour m_stopBehaviour = StopBehaviour::BackToStart;
TimePos m_playStartPosition = TimePos{-1};
};
} // namespace lmms
#endif // LMMS_TIMELINE_H

View File

@@ -74,6 +74,7 @@ set(LMMS_SRCS
core/SerializingObject.cpp
core/Song.cpp
core/TempoSyncKnobModel.cpp
core/Timeline.cpp
core/TimePos.cpp
core/ToolPlugin.cpp
core/Track.cpp

View File

@@ -52,16 +52,10 @@ SampleClip::SampleClip( Track * _track ) :
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
this, SLOT(updateLength()));
//care about positionmarker
gui::TimeLineWidget* timeLine = Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine;
if( timeLine )
{
connect( timeLine, SIGNAL(positionMarkerMoved()), this, SLOT(playbackPositionChanged()));
}
//playbutton clicked or space key / on Export Song set isPlaying to false
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
//care about loops
//care about loops and jumps
connect( Engine::getSong(), SIGNAL(updateSampleTracks()),
this, SLOT(playbackPositionChanged()), Qt::DirectConnection );
//care about mute Clips

View File

@@ -184,14 +184,9 @@ void Song::setTimeSignature()
void Song::savePos()
void Song::savePlayStartPosition()
{
gui::TimeLineWidget* tl = getPlayPos().m_timeLine;
if( tl != nullptr )
{
tl->savePos( getPlayPos() );
}
getTimeline().setPlayStartPosition(getPlayPos());
}
@@ -258,16 +253,17 @@ void Song::processNextBuffer()
return false;
};
const auto timeline = getPlayPos().m_timeLine;
const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled();
const auto& timeline = getTimeline();
const auto loopEnabled = !m_exporting && timeline.loopEnabled();
// Ensure playback begins within the loop if it is enabled
if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); }
if (loopEnabled) { enforceLoop(timeline.loopBegin(), timeline.loopEnd()); }
// Inform VST plugins if the user moved the play head
// Inform VST plugins and sample tracks if the user moved the play head
if (getPlayPos().jumped())
{
m_vstSyncController.setPlaybackJumped(true);
emit updateSampleTracks();
getPlayPos().setJumped(false);
}
@@ -301,13 +297,13 @@ void Song::processNextBuffer()
}
// Handle loop points, and inform VST plugins of the loop status
if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline->loopBegin()))
if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline.loopBegin()))
{
m_vstSyncController.startCycle(
timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks());
timeline.loopBegin().getTicks(), timeline.loopEnd().getTicks());
// Loop if necessary, and decrement the remaining loops if we did
if (enforceLoop(timeline->loopBegin(), timeline->loopEnd())
if (enforceLoop(timeline.loopBegin(), timeline.loopEnd())
&& m_loopRenderRemaining > 1)
{
m_loopRenderRemaining--;
@@ -492,7 +488,7 @@ void Song::playSong()
m_vstSyncController.setPlaybackState( true );
savePos();
savePlayStartPosition();
emit playbackStateChanged();
}
@@ -531,7 +527,7 @@ void Song::playPattern()
m_vstSyncController.setPlaybackState( true );
savePos();
savePlayStartPosition();
emit playbackStateChanged();
}
@@ -556,7 +552,7 @@ void Song::playMidiClip( const MidiClip* midiClipToPlay, bool loop )
m_paused = false;
}
savePos();
savePlayStartPosition();
emit playbackStateChanged();
}
@@ -644,40 +640,32 @@ void Song::stop()
// To avoid race conditions with the processing threads
Engine::audioEngine()->requestChangeInModel();
TimeLineWidget * tl = getPlayPos().m_timeLine;
auto& timeline = getTimeline();
m_paused = false;
m_recording = true;
if( tl )
{
switch( tl->behaviourAtStop() )
{
case TimeLineWidget::BehaviourAtStopState::BackToZero:
getPlayPos().setTicks(0);
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
break;
case TimeLineWidget::BehaviourAtStopState::BackToStart:
if( tl->savedPos() >= 0 )
{
getPlayPos().setTicks(tl->savedPos().getTicks());
setToTime(tl->savedPos());
tl->savePos( -1 );
}
break;
case TimeLineWidget::BehaviourAtStopState::KeepStopPosition:
break;
}
}
else
{
getPlayPos().setTicks( 0 );
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
}
m_playing = false;
switch (timeline.stopBehaviour())
{
case Timeline::StopBehaviour::BackToZero:
getPlayPos().setTicks(0);
m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)] = 0;
break;
case Timeline::StopBehaviour::BackToStart:
if (timeline.playStartPosition() >= 0)
{
getPlayPos().setTicks(timeline.playStartPosition().getTicks());
setToTime(timeline.playStartPosition());
timeline.setPlayStartPosition(-1);
}
break;
case Timeline::StopBehaviour::KeepPosition:
break;
}
m_elapsedMilliSeconds[static_cast<std::size_t>(PlayMode::None)] = m_elapsedMilliSeconds[static_cast<std::size_t>(m_playMode)];
getPlayPos(PlayMode::None).setTicks(getPlayPos().getTicks());
@@ -719,37 +707,35 @@ void Song::startExport()
m_exporting = true;
updateLength();
const auto& timeline = getTimeline(PlayMode::Song);
if (m_renderBetweenMarkers)
{
m_exportSongBegin = m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin();
m_exportSongEnd = m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd();
m_exportSongBegin = m_exportLoopBegin = timeline.loopBegin();
m_exportSongEnd = m_exportLoopEnd = timeline.loopEnd();
getPlayPos(PlayMode::Song).setTicks( getPlayPos(PlayMode::Song).m_timeLine->loopBegin().getTicks() );
getPlayPos(PlayMode::Song).setTicks(timeline.loopBegin().getTicks());
}
else
{
m_exportSongEnd = TimePos(m_length, 0);
// Handle potentially ridiculous loop points gracefully.
if (m_loopRenderCount > 1 && getPlayPos(PlayMode::Song).m_timeLine->loopEnd() > m_exportSongEnd)
if (m_loopRenderCount > 1 && timeline.loopEnd() > m_exportSongEnd)
{
m_exportSongEnd = getPlayPos(PlayMode::Song).m_timeLine->loopEnd();
m_exportSongEnd = timeline.loopEnd();
}
if (!m_exportLoop)
m_exportSongEnd += TimePos(1,0);
m_exportSongBegin = TimePos(0,0);
// FIXME: remove this check once we load timeline in headless mode
if (getPlayPos(PlayMode::Song).m_timeLine)
{
m_exportLoopBegin = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd &&
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ?
getPlayPos(PlayMode::Song).m_timeLine->loopBegin() : TimePos(0,0);
m_exportLoopEnd = getPlayPos(PlayMode::Song).m_timeLine->loopBegin() < m_exportSongEnd &&
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() <= m_exportSongEnd ?
getPlayPos(PlayMode::Song).m_timeLine->loopEnd() : TimePos(0,0);
}
m_exportLoopBegin = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd
? timeline.loopBegin()
: TimePos{0};
m_exportLoopEnd = timeline.loopBegin() < m_exportSongEnd && timeline.loopEnd() <= m_exportSongEnd
? timeline.loopEnd()
: TimePos{0};
getPlayPos(PlayMode::Song).setTicks( 0 );
}
@@ -1080,11 +1066,7 @@ void Song::loadProject( const QString & fileName )
m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" );
m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" );
if( getPlayPos(PlayMode::Song).m_timeLine )
{
// reset loop-point-state
getPlayPos(PlayMode::Song).m_timeLine->toggleLoopPoints( 0 );
}
getTimeline(PlayMode::Song).setLoopEnabled(false);
if( !dataFile.content().firstChildElement( "track" ).isNull() )
{
@@ -1167,9 +1149,9 @@ void Song::loadProject( const QString & fileName )
{
getGUI()->getProjectNotes()->SerializingObject::restoreState( node.toElement() );
}
else if( node.nodeName() == getPlayPos(PlayMode::Song).m_timeLine->nodeName() )
else if (node.nodeName() == getTimeline(PlayMode::Song).nodeName())
{
getPlayPos(PlayMode::Song).m_timeLine->restoreState( node.toElement() );
getTimeline(PlayMode::Song).restoreState(node.toElement());
}
}
}
@@ -1253,7 +1235,7 @@ bool Song::saveProjectFile(const QString & filename, bool withResources)
getGUI()->pianoRoll()->saveState( dataFile, dataFile.content() );
getGUI()->automationEditor()->m_editor->saveState( dataFile, dataFile.content() );
getGUI()->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() );
getPlayPos(PlayMode::Song).m_timeLine->saveState( dataFile, dataFile.content() );
getTimeline(PlayMode::Song).saveState(dataFile, dataFile.content());
}
saveControllerStates( dataFile, dataFile.content() );

83
src/core/Timeline.cpp Normal file
View File

@@ -0,0 +1,83 @@
/*
* Timeline.cpp
*
* Copyright (c) 2023 Dominic Clark
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*/
#include "Timeline.h"
#include <algorithm>
#include <tuple>
#include <QDomDocument>
#include <QDomElement>
namespace lmms {
void Timeline::setLoopBegin(TimePos begin)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, TimePos{m_loopEnd});
}
void Timeline::setLoopEnd(TimePos end)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(TimePos{m_loopBegin}, end);
}
void Timeline::setLoopPoints(TimePos begin, TimePos end)
{
std::tie(m_loopBegin, m_loopEnd) = std::minmax(begin, end);
}
void Timeline::setLoopEnabled(bool enabled)
{
if (enabled != m_loopEnabled) {
m_loopEnabled = enabled;
emit loopEnabledChanged(m_loopEnabled);
}
}
void Timeline::setStopBehaviour(StopBehaviour behaviour)
{
if (behaviour != m_stopBehaviour) {
m_stopBehaviour = behaviour;
emit stopBehaviourChanged(m_stopBehaviour);
}
}
void Timeline::saveSettings(QDomDocument& doc, QDomElement& element)
{
element.setAttribute("lp0pos", static_cast<int>(loopBegin()));
element.setAttribute("lp1pos", static_cast<int>(loopEnd()));
element.setAttribute("lpstate", static_cast<int>(loopEnabled()));
element.setAttribute("stopbehaviour", static_cast<int>(stopBehaviour()));
}
void Timeline::loadSettings(const QDomElement& element)
{
setLoopPoints(
static_cast<TimePos>(element.attribute("lp0pos").toInt()),
static_cast<TimePos>(element.attribute("lp1pos").toInt())
);
setLoopEnabled(static_cast<bool>(element.attribute("lpstate").toInt()));
setStopBehaviour(static_cast<StopBehaviour>(element.attribute("stopbehaviour", "1").toInt()));
}
} // namespace lmms

View File

@@ -226,8 +226,6 @@ MainWindow::MainWindow() :
connect( Engine::getSong(), SIGNAL(playbackStateChanged()),
this, SLOT(updatePlayPauseIcons()));
connect(Engine::getSong(), SIGNAL(stopped()), SLOT(onSongStopped()));
connect(Engine::getSong(), SIGNAL(modified()), SLOT(onSongModified()));
connect(Engine::getSong(), SIGNAL(projectFileNameChanged()), SLOT(onProjectFileNameChanged()));
@@ -1606,42 +1604,6 @@ void MainWindow::onImportProject()
}
}
void MainWindow::onSongStopped()
{
Song * song = Engine::getSong();
Song::PlayPos const & playPos = song->getPlayPos();
TimeLineWidget * tl = playPos.m_timeLine;
if( tl )
{
SongEditorWindow* songEditor = getGUI()->songEditor();
switch( tl->behaviourAtStop() )
{
case TimeLineWidget::BehaviourAtStopState::BackToZero:
if( songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) )
{
songEditor->m_editor->updatePosition(0);
}
break;
case TimeLineWidget::BehaviourAtStopState::BackToStart:
if( tl->savedPos() >= 0 )
{
if(songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollState::Enabled ) )
{
songEditor->m_editor->updatePosition( TimePos(tl->savedPos().getTicks() ) );
}
tl->savePos( -1 );
}
break;
case TimeLineWidget::BehaviourAtStopState::KeepStopPosition:
break;
}
}
}
void MainWindow::onSongModified()
{
// Only update the window title if the code is executed from the GUI main thread.

View File

@@ -132,13 +132,12 @@ AutomationEditor::AutomationEditor() :
m_quantizeModel.setValue( m_quantizeModel.findText( "1/8" ) );
// add time-line
m_timeLine = new TimeLineWidget( VALUES_WIDTH, 0, m_ppb,
Engine::getSong()->getPlayPos(
Song::PlayMode::AutomationClip ),
m_currentPosition,
Song::PlayMode::AutomationClip, this );
connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ),
m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) );
m_timeLine = new TimeLineWidget(VALUES_WIDTH, 0, m_ppb,
Engine::getSong()->getPlayPos(Song::PlayMode::AutomationClip),
Engine::getSong()->getTimeline(Song::PlayMode::AutomationClip),
m_currentPosition, Song::PlayMode::AutomationClip, this
);
connect(this, &AutomationEditor::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
@@ -1602,11 +1601,7 @@ void AutomationEditor::resizeEvent(QResizeEvent * re)
}
centerTopBottomScroll();
if( Engine::getSong() )
{
Engine::getSong()->getPlayPos( Song::PlayMode::AutomationClip
).m_timeLine->setFixedWidth( width() );
}
m_timeLine->setFixedWidth(width());
updateTopBottomLevels();
update();

View File

@@ -265,12 +265,11 @@ PianoRoll::PianoRoll() :
// add time-line
m_timeLine = new TimeLineWidget(m_whiteKeyWidth, 0, m_ppb,
Engine::getSong()->getPlayPos(
Song::PlayMode::MidiClip ),
m_currentPosition,
Song::PlayMode::MidiClip, this );
connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ),
m_timeLine, SLOT( updatePosition( const lmms::TimePos& ) ) );
Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip),
Engine::getSong()->getTimeline(Song::PlayMode::MidiClip),
m_currentPosition, Song::PlayMode::MidiClip, this
);
connect(this, &PianoRoll::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
@@ -282,10 +281,7 @@ PianoRoll::PianoRoll() :
this, SLOT( updatePositionStepRecording( const lmms::TimePos& ) ) );
// update timeline when in record-accompany mode
connect( Engine::getSong()->getPlayPos( Song::PlayMode::Song ).m_timeLine,
SIGNAL( positionChanged( const lmms::TimePos& ) ),
this,
SLOT( updatePositionAccompany( const lmms::TimePos& ) ) );
connect(m_timeLine, &TimeLineWidget::positionChanged, this, &PianoRoll::updatePositionAccompany);
// TODO
/* connect( engine::getSong()->getPlayPos( Song::PlayMode::Pattern ).m_timeLine,
SIGNAL( positionChanged( const lmms::TimePos& ) ),
@@ -3734,8 +3730,7 @@ void PianoRoll::resizeEvent(QResizeEvent* re)
{
updatePositionLineHeight();
updateScrollbars();
Engine::getSong()->getPlayPos(Song::PlayMode::MidiClip)
.m_timeLine->setFixedWidth(width());
m_timeLine->setFixedWidth(width());
update();
}
@@ -5167,7 +5162,8 @@ void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de )
de.appendChild(markedSemiTonesRoot);
}
de.setAttribute("stopbehaviour", static_cast<int>(m_editor->m_timeLine->behaviourAtStop()));
de.setAttribute("stopbehaviour", static_cast<int>(
Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).stopBehaviour()));
MainWindow::saveWidgetState( this, de );
}
@@ -5182,7 +5178,8 @@ void PianoRollWindow::loadSettings( const QDomElement & de )
MainWindow::restoreWidgetState( this, de );
m_editor->m_timeLine->setBehaviourAtStop(de.attribute("stopbehaviour").toInt());
Engine::getSong()->getTimeline(Song::PlayMode::MidiClip).setStopBehaviour(
static_cast<Timeline::StopBehaviour>(de.attribute("stopbehaviour").toInt()));
// update margins here because we're later in the startup process
// We can't earlier because everything is still starting with the

View File

@@ -97,14 +97,12 @@ SongEditor::SongEditor( Song * song ) :
m_zoomingModel->setParent(this);
m_snappingModel->setParent(this);
m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32,
pixelsPerBar(),
m_song->getPlayPos(Song::PlayMode::Song),
m_currentPosition,
Song::PlayMode::Song, this );
connect( this, SIGNAL( positionChanged( const lmms::TimePos& ) ),
m_song->getPlayPos(Song::PlayMode::Song).m_timeLine,
SLOT( updatePosition( const lmms::TimePos& ) ) );
m_timeLine = new TimeLineWidget(m_trackHeadWidth, 32, pixelsPerBar(),
m_song->getPlayPos(Song::PlayMode::Song),
m_song->getTimeline(Song::PlayMode::Song),
m_currentPosition, Song::PlayMode::Song, this
);
connect(this, &TrackContainerView::positionChanged, m_timeLine, &TimeLineWidget::updatePosition);
connect( m_timeLine, SIGNAL( positionChanged( const lmms::TimePos& ) ),
this, SLOT( updatePosition( const lmms::TimePos& ) ) );
connect( m_timeLine, SIGNAL(regionSelectedFromPixels(int,int)),
@@ -560,7 +558,7 @@ void SongEditor::wheelEvent( QWheelEvent * we )
m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar);
// update timeline
m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(pixelsPerBar());
m_timeLine->setPixelsPerBar(pixelsPerBar());
// and make sure, all Clip's are resized and relocated
realignTracks();
}
@@ -808,8 +806,7 @@ void SongEditor::updatePosition( const TimePos & t )
m_scrollBack = false;
}
const int x = m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->
markerX( t ) + 8;
const int x = m_timeLine->markerX(t) + 8;
if( x >= trackOpWidth + widgetWidth -1 )
{
m_positionLine->show();
@@ -872,7 +869,7 @@ void SongEditor::zoomingChanged()
int ppb = calculatePixelsPerBar();
setPixelsPerBar(ppb);
m_song->getPlayPos(Song::PlayMode::Song).m_timeLine->setPixelsPerBar(ppb);
m_timeLine->setPixelsPerBar(ppb);
realignTracks();
updateRubberband();
m_timeLine->setSnapSize(getSnapSize());

View File

@@ -45,9 +45,8 @@ namespace
constexpr int MIN_BAR_LABEL_DISTANCE = 35;
}
TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb,
Song::PlayPos & pos, const TimePos & begin, Song::PlayMode mode,
QWidget * parent ) :
TimeLineWidget::TimeLineWidget(const int xoff, const int yoff, const float ppb, Song::PlayPos& pos, Timeline& timeline,
const TimePos& begin, Song::PlayMode mode, QWidget* parent) :
QWidget( parent ),
m_inactiveLoopColor( 52, 63, 53, 64 ),
m_inactiveLoopBrush( QColor( 255, 255, 255, 32 ) ),
@@ -59,35 +58,28 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb,
m_barLineColor( 192, 192, 192 ),
m_barNumberColor( m_barLineColor.darker( 120 ) ),
m_autoScroll( AutoScrollState::Enabled ),
m_loopPoints( LoopPointState::Disabled ),
m_behaviourAtStop( BehaviourAtStopState::BackToZero ),
m_changedPosition( true ),
m_xOffset( xoff ),
m_posMarkerX( 0 ),
m_ppb( ppb ),
m_snapSize( 1.0 ),
m_pos( pos ),
m_timeline{&timeline},
m_begin( begin ),
m_mode( mode ),
m_savedPos( -1 ),
m_hint( nullptr ),
m_action( Action::NoAction ),
m_moveXOff( 0 )
{
m_loopPos[0] = 0;
m_loopPos[1] = DefaultTicksPerBar;
setAttribute( Qt::WA_OpaquePaintEvent, true );
move( 0, yoff );
m_xOffset -= m_posMarkerPixmap.width() / 2;
setMouseTracking(true);
m_pos.m_timeLine = this;
auto updateTimer = new QTimer(this);
connect( updateTimer, SIGNAL(timeout()),
this, SLOT(updatePosition()));
connect(updateTimer, &QTimer::timeout, this, &TimeLineWidget::updatePosition);
updateTimer->start( 1000 / 60 ); // 60 fps
connect( Engine::getSong(), SIGNAL(timeSignatureChanged(int,int)),
this, SLOT(update()));
@@ -98,10 +90,6 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppb,
TimeLineWidget::~TimeLineWidget()
{
if( getGUI()->songEditor() )
{
m_pos.m_timeLine = nullptr;
}
delete m_hint;
}
@@ -129,10 +117,10 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar )
loopPoints->setGeneralToolTip( tr( "Loop points" ) );
loopPoints->addState( embed::getIconPixmap( "loop_points_off" ) );
loopPoints->addState( embed::getIconPixmap( "loop_points_on" ) );
connect( loopPoints, SIGNAL(changedState(int)), this,
SLOT(toggleLoopPoints(int)));
connect( this, SIGNAL(loopPointStateLoaded(int)), loopPoints,
SLOT(changeState(int)));
connect(loopPoints, &NStateButton::changedState, m_timeline, &Timeline::setLoopEnabled);
connect(m_timeline, &Timeline::loopEnabledChanged, loopPoints, &NStateButton::changeState);
connect(m_timeline, &Timeline::loopEnabledChanged, this, static_cast<void (QWidget::*)()>(&QWidget::update));
loopPoints->changeState(static_cast<int>(m_timeline->loopEnabled()));
auto behaviourAtStop = new NStateButton(_tool_bar);
behaviourAtStop->addState( embed::getIconPixmap( "back_to_zero" ),
@@ -144,50 +132,24 @@ void TimeLineWidget::addToolButtons( QToolBar * _tool_bar )
"started" ) );
behaviourAtStop->addState( embed::getIconPixmap( "keep_stop_position" ),
tr( "After stopping keep position" ) );
connect( behaviourAtStop, SIGNAL(changedState(int)), this,
SLOT(toggleBehaviourAtStop(int)));
connect( this, SIGNAL(loadBehaviourAtStop(int)), behaviourAtStop,
SLOT(changeState(int)));
behaviourAtStop->changeState( static_cast<int>(BehaviourAtStopState::BackToStart) );
connect(behaviourAtStop, &NStateButton::changedState, m_timeline,
[timeline = m_timeline](int value) {
timeline->setStopBehaviour(static_cast<Timeline::StopBehaviour>(value));
}
);
connect(m_timeline, &Timeline::stopBehaviourChanged, behaviourAtStop,
[button = behaviourAtStop](Timeline::StopBehaviour value) {
button->changeState(static_cast<int>(value));
}
);
behaviourAtStop->changeState(static_cast<int>(m_timeline->stopBehaviour()));
_tool_bar->addWidget( autoScroll );
_tool_bar->addWidget( loopPoints );
_tool_bar->addWidget( behaviourAtStop );
}
void TimeLineWidget::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
_this.setAttribute( "lp0pos", (int) loopBegin() );
_this.setAttribute( "lp1pos", (int) loopEnd() );
_this.setAttribute( "lpstate", static_cast<int>(m_loopPoints) );
_this.setAttribute( "stopbehaviour", static_cast<int>(m_behaviourAtStop) );
}
void TimeLineWidget::loadSettings( const QDomElement & _this )
{
m_loopPos[0] = _this.attribute( "lp0pos" ).toInt();
m_loopPos[1] = _this.attribute( "lp1pos" ).toInt();
m_loopPoints = static_cast<LoopPointState>(
_this.attribute( "lpstate" ).toInt() );
update();
emit loopPointStateLoaded( static_cast<int>(m_loopPoints) );
if( _this.hasAttribute( "stopbehaviour" ) )
{
emit loadBehaviourAtStop( _this.attribute( "stopbehaviour" ).toInt() );
}
}
void TimeLineWidget::updatePosition( const TimePos & )
void TimeLineWidget::updatePosition()
{
const int new_x = markerX( m_pos );
@@ -200,34 +162,11 @@ void TimeLineWidget::updatePosition( const TimePos & )
}
}
void TimeLineWidget::toggleAutoScroll( int _n )
{
m_autoScroll = static_cast<AutoScrollState>( _n );
}
void TimeLineWidget::toggleLoopPoints( int _n )
{
m_loopPoints = static_cast<LoopPointState>( _n );
update();
}
void TimeLineWidget::toggleBehaviourAtStop( int _n )
{
m_behaviourAtStop = static_cast<BehaviourAtStopState>( _n );
}
void TimeLineWidget::paintEvent( QPaintEvent * )
{
QPainter p( this );
@@ -242,11 +181,11 @@ void TimeLineWidget::paintEvent( QPaintEvent * )
// Draw the loop rectangle
int const & loopRectMargin = getLoopRectangleVerticalPadding();
int const loopRectHeight = this->height() - 2 * loopRectMargin;
int const loopStart = markerX( loopBegin() ) + 8;
int const loopEndR = markerX( loopEnd() ) + 9;
int const loopStart = markerX(m_timeline->loopBegin()) + 8;
int const loopEndR = markerX(m_timeline->loopEnd()) + 9;
int const loopRectWidth = loopEndR - loopStart;
bool const loopPointsActive = loopPointsEnabled();
bool const loopPointsActive = m_timeline->loopEnabled();
// Draw the main rectangle (inner fill only)
QRect outerRectangle( loopStart, loopRectMargin, loopRectWidth - 1, loopRectHeight - 1 );
@@ -336,12 +275,12 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event )
else if( event->button() == Qt::RightButton )
{
m_moveXOff = m_posMarkerPixmap.width() / 2;
const TimePos t = m_begin + static_cast<int>( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb );
const TimePos loopMid = ( m_loopPos[0] + m_loopPos[1] ) / 2;
m_action = t < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd;
std::sort(std::begin(m_loopPos), std::end(m_loopPos));
m_loopPos[( m_action == Action::MoveLoopBegin ) ? 0 : 1] = t;
const auto cursorXOffset = std::max(event->x() - m_xOffset - m_moveXOff, 0);
const TimePos timeAtCursor = m_begin + static_cast<int>(cursorXOffset * TimePos::ticksPerBar() / m_ppb);
const TimePos loopMid = (m_timeline->loopBegin() + m_timeline->loopEnd()) / 2;
m_action = timeAtCursor < loopMid ? Action::MoveLoopBegin : Action::MoveLoopEnd;
}
if( m_action == Action::MoveLoopBegin || m_action == Action::MoveLoopEnd )
@@ -360,49 +299,53 @@ void TimeLineWidget::mousePressEvent( QMouseEvent* event )
void TimeLineWidget::mouseMoveEvent( QMouseEvent* event )
{
parentWidget()->update(); // essential for widgets that this timeline had taken their mouse move event from.
const TimePos t = m_begin + static_cast<int>( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * TimePos::ticksPerBar() / m_ppb );
const auto cursorXOffset = std::max(event->x() - m_xOffset - m_moveXOff, 0);
TimePos timeAtCursor = m_begin + static_cast<int>(cursorXOffset * TimePos::ticksPerBar() / m_ppb);
switch( m_action )
{
case Action::MovePositionMarker:
m_pos.setTicks(t.getTicks());
Engine::getSong()->setToTime(t, m_mode);
m_pos.setTicks(timeAtCursor.getTicks());
Engine::getSong()->setToTime(timeAtCursor, m_mode);
if (!( Engine::getSong()->isPlaying()))
{
//Song::PlayMode::None is used when nothing is being played.
Engine::getSong()->setToTime(t, Song::PlayMode::None);
Engine::getSong()->setToTime(timeAtCursor, Song::PlayMode::None);
}
m_pos.setCurrentFrame( 0 );
m_pos.setJumped( true );
updatePosition();
positionMarkerMoved();
break;
case Action::MoveLoopBegin:
case Action::MoveLoopEnd:
{
const int i = m_action == Action::MoveLoopBegin ? 0 : 1;
const auto otherPoint = m_action == Action::MoveLoopBegin
? m_timeline->loopEnd()
: m_timeline->loopBegin();
const bool control = event->modifiers() & Qt::ControlModifier;
if (control)
{
// no ctrl-press-hint when having ctrl pressed
delete m_hint;
m_hint = nullptr;
m_loopPos[i] = t;
}
else
{
m_loopPos[i] = t.quantize(m_snapSize);
timeAtCursor = timeAtCursor.quantize(m_snapSize);
}
// Catch begin == end
if (m_loopPos[0] == m_loopPos[1])
if (timeAtCursor == otherPoint)
{
const int offset = control ? 1 : m_snapSize * TimePos::ticksPerBar();
// Note, swap 1 and 0 below and the behavior "skips" the other
// marking instead of pushing it.
if (m_action == Action::MoveLoopBegin) { m_loopPos[0] -= offset; }
else { m_loopPos[1] += offset; }
if (m_action == Action::MoveLoopBegin) { timeAtCursor -= offset; }
else { timeAtCursor += offset; }
}
// Update m_action so we still move the correct point even if it is
// dragged past the other.
m_action = timeAtCursor < otherPoint ? Action::MoveLoopBegin : Action::MoveLoopEnd;
m_timeline->setLoopPoints(timeAtCursor, otherPoint);
update();
break;
}

View File

@@ -67,34 +67,27 @@ void NStateButton::addState( const QPixmap & _pm, const QString & _tooltip )
void NStateButton::changeState( int _n )
void NStateButton::changeState(int state)
{
if( _n >= 0 && _n < (int) m_states.size() )
if (state >= 0 && state < m_states.size() && state != m_curState)
{
m_curState = _n;
m_curState = state;
const QString & _tooltip =
( m_states[m_curState].second != "" ) ?
m_states[m_curState].second :
m_generalToolTip;
setToolTip(_tooltip);
const auto& [icon, tooltip] = m_states[m_curState];
setToolTip(tooltip.isEmpty() ? m_generalToolTip : tooltip);
setIcon(icon);
setIcon( m_states[m_curState].first );
emit changedState( m_curState );
emit changedState(m_curState);
}
}
void NStateButton::mousePressEvent( QMouseEvent * _me )
void NStateButton::mousePressEvent(QMouseEvent* me)
{
if( _me->button() == Qt::LeftButton && m_states.size() )
if (me->button() == Qt::LeftButton && !m_states.empty())
{
changeState( ( ++m_curState ) % m_states.size() );
changeState((m_curState + 1) % m_states.size());
}
ToolButton::mousePressEvent( _me );
ToolButton::mousePressEvent(me);
}
} // namespace lmms::gui
} // namespace lmms::gui