Refactor to move positionChanged signal to Timeline (#7454)
Previously, this PR simply added a new signal to the Timeline class and had it emitted by the TimeLineWidget class in order to solve #7351. However, as messmerd pointed out in his review comments, that was quite a hacky solution, and ideally the positionChanged signal would be solely managed by the backend Timeline class, while the frontend TimeLineWidget class would connect to those signals, instead of the other way around. This PR is no longer a simple bugfix, but instead a refactoring of the Timeline/TimeLineWidget signal/slots. Changes - The positionChanged signal and updatePosition slot were removed from TimeLineWidget and moved to Timeline. - Removed PlayPos, and instead store the timeline position in a TimePos along with a separate frame offset counter variable. The functions to set the ticks/timepos in Timeline emit the positionChanged signal. (Also, may emit positionJumped signal if the ticks were forcefully set--see below) - The pos() method and PlayPos m_pos were removed from TimeLineWidget; - The constructor for TimeLineWidget no longer requires a PlayPos reference. - Since each TimeLineWidget stores a reference to its Timeline, a new method was added, timeline(), for other classes to access it. - Removed array of PlayPos'es in Song. Now each Timeline holds their respective TimePos. - Song's methods for getPlayPos were changed to return the TimePos of the respective Timeline. The non-const versions of the methods were removed because Timeline does not expose its TimePos to write. - All of the places where Timelines are used were updated, along with their calls to access/modify the PlayPos. For example, occurrences of m_timeline->pos() were replaced with m_timeline->timeline()->pos(), and calls to m_timeline->pos().setTicks(ticks) were changed to m_timeline->timeline()->setTicks(ticks). - ALSO: Removed m_elapsedMilliseconds, m_elapsedBars, and m_elapsedTicks from Song. The elapsed milliseconds is now handled individually by each Timeline. - NEW: The m_jumped variable has been removed from Timeline. Now jumped events emit Timeline::positionJumped automatically whenever the ticks are forcefully set. This means it is no longer necessary to call timeline->setJumped(true) or timeline->setFrameOffset(0) whenever the playhead position is forcefully set. Many places in the codebase were already missing these calls, so this may fix some unknown bugs. --------- Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com> Co-authored-by: saker <sakertooth@gmail.com> Co-authored-by: Alex <allejok96@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@ set(LMMS_TESTS
|
||||
src/core/MathTest.cpp
|
||||
src/core/ProjectVersionTest.cpp
|
||||
src/core/RelativePathsTest.cpp
|
||||
src/core/TimelineTest.cpp
|
||||
src/tracks/AutomationTrackTest.cpp
|
||||
)
|
||||
|
||||
|
||||
119
tests/src/core/TimelineTest.cpp
Normal file
119
tests/src/core/TimelineTest.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* TimelineTest.cpp
|
||||
*
|
||||
* Copyright (c) 2025 Keratin
|
||||
*
|
||||
* 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 <QtTest>
|
||||
#include "Timeline.h"
|
||||
|
||||
#include "Song.h"
|
||||
|
||||
class TimelineTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
bool positionChangedReceived;
|
||||
bool positionJumpedReceived;
|
||||
void resetReceived()
|
||||
{
|
||||
positionChangedReceived = false;
|
||||
positionJumpedReceived = false;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onPositionChanged() { positionChangedReceived = true; }
|
||||
void onPositionJumped() { positionJumpedReceived = true; }
|
||||
|
||||
private slots:
|
||||
|
||||
void initTestCase()
|
||||
{
|
||||
using namespace lmms;
|
||||
Engine::init(true);
|
||||
}
|
||||
|
||||
void cleanupTestCase()
|
||||
{
|
||||
using namespace lmms;
|
||||
Engine::destroy();
|
||||
}
|
||||
|
||||
void JumpedTests()
|
||||
{
|
||||
using namespace lmms;
|
||||
|
||||
Timeline timeline = Timeline();
|
||||
connect(&timeline, &Timeline::positionChanged, this, &TimelineTest::onPositionChanged);
|
||||
connect(&timeline, &Timeline::positionJumped, this, &TimelineTest::onPositionJumped);
|
||||
|
||||
// By default, setting the ticks is treated as a forceful jump
|
||||
resetReceived();
|
||||
timeline.setTicks(10);
|
||||
QCOMPARE(timeline.ticks(), 10);
|
||||
QVERIFY(positionChangedReceived);
|
||||
QVERIFY(positionJumpedReceived);
|
||||
|
||||
// However, using incrementTicks will not emit positionJumped.
|
||||
resetReceived();
|
||||
timeline.incrementTicks(10);
|
||||
QCOMPARE(timeline.ticks(), 20);
|
||||
QVERIFY(positionChangedReceived);
|
||||
QVERIFY(!positionJumpedReceived);
|
||||
}
|
||||
|
||||
void ElapsedTimeTests()
|
||||
{
|
||||
using namespace lmms;
|
||||
|
||||
Timeline timeline = Timeline();
|
||||
connect(&timeline, &Timeline::positionChanged, this, &TimelineTest::onPositionChanged);
|
||||
connect(&timeline, &Timeline::positionJumped, this, &TimelineTest::onPositionJumped);
|
||||
|
||||
// Forecefully setting the ticks to 0 should reset the elapsed time
|
||||
timeline.setTicks(0);
|
||||
QCOMPARE(timeline.getElapsedSeconds(), 0);
|
||||
|
||||
// Setting the ticks to a nonzero value should reset the elapsed time to that tick's time based on the current tempo
|
||||
Engine::getSong()->setTempo(240);
|
||||
double secondsPerTick = 60.0f / Engine::getSong()->getTempo() * 4 / DefaultTicksPerBar;
|
||||
timeline.setTicks(10);
|
||||
double initialElapsedSeconds = timeline.getElapsedSeconds();
|
||||
QCOMPARE(static_cast<int>(timeline.getElapsedSeconds() * 1000), static_cast<int>(10 * secondsPerTick * 1000)); // Rouding to milliseconds to prevent double comparison issues
|
||||
|
||||
// Changing the tempo and then non-forcefully incrementing the ticks will increase the elapsed time based on the new tempo
|
||||
Engine::getSong()->setTempo(60);
|
||||
secondsPerTick = 60.0f / Engine::getSong()->getTempo() * 4 / DefaultTicksPerBar;
|
||||
timeline.incrementTicks(5);
|
||||
QCOMPARE(static_cast<int>(timeline.getElapsedSeconds() * 1000), static_cast<int>((initialElapsedSeconds + 5 * secondsPerTick) * 1000));
|
||||
|
||||
// Forcefully setting the ticks (such as dragging the playhead with the mouse) will reset the elapsed time based on the global position and current tempo
|
||||
Engine::getSong()->setTempo(180);
|
||||
secondsPerTick = 60.0f / Engine::getSong()->getTempo() * 4 / DefaultTicksPerBar;
|
||||
timeline.setTicks(25);
|
||||
QCOMPARE(static_cast<int>(timeline.getElapsedSeconds() * 1000), static_cast<int>(25 * secondsPerTick * 1000));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TimelineTest)
|
||||
#include "TimelineTest.moc"
|
||||
Reference in New Issue
Block a user