Files
lmms/include/TimeLineWidget.h
regulus79 0fe7d6f679 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>
2025-12-17 06:31:36 -05:00

249 lines
8.4 KiB
C++

/*
* TimeLineWidget.h - class timeLine, representing a time-line with position marker
*
* Copyright (c) 2004-2008 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
* 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_GUI_TIMELINE_WIDGET_H
#define LMMS_GUI_TIMELINE_WIDGET_H
#include <array>
#include <QSize>
#include <QWidget>
#include "Song.h"
#include "embed.h"
class QToolBar;
namespace lmms {
class Timeline;
} // namespace lmms
namespace lmms::gui
{
class TextFloat;
class TimeLineWidget : public QWidget
{
Q_OBJECT
public:
Q_PROPERTY( QColor barLineColor READ getBarLineColor WRITE setBarLineColor )
Q_PROPERTY( QColor barNumberColor READ getBarNumberColor WRITE setBarNumberColor )
Q_PROPERTY( QColor inactiveLoopColor READ getInactiveLoopColor WRITE setInactiveLoopColor )
Q_PROPERTY( QBrush inactiveLoopBrush READ getInactiveLoopBrush WRITE setInactiveLoopBrush )
Q_PROPERTY( QColor inactiveLoopInnerColor READ getInactiveLoopInnerColor WRITE setInactiveLoopInnerColor )
Q_PROPERTY(QColor inactiveLoopHandleColor MEMBER m_inactiveLoopHandleColor)
Q_PROPERTY( QColor activeLoopColor READ getActiveLoopColor WRITE setActiveLoopColor )
Q_PROPERTY( QBrush activeLoopBrush READ getActiveLoopBrush WRITE setActiveLoopBrush )
Q_PROPERTY( QColor activeLoopInnerColor READ getActiveLoopInnerColor WRITE setActiveLoopInnerColor )
Q_PROPERTY(QColor activeLoopHandleColor MEMBER m_activeLoopHandleColor)
Q_PROPERTY( int loopRectangleVerticalPadding READ getLoopRectangleVerticalPadding WRITE setLoopRectangleVerticalPadding )
Q_PROPERTY(int loopHandleWidth MEMBER m_loopHandleWidth)
Q_PROPERTY(QSize mouseHotspotSelLeft READ mouseHotspotSelLeft WRITE setMouseHotspotSelLeft)
Q_PROPERTY(QSize mouseHotspotSelRight READ mouseHotspotSelRight WRITE setMouseHotspotSelRight)
static constexpr const char* AutoScrollDisabledString = "disabled";
static constexpr const char* AutoScrollSteppedString = "stepped";
static constexpr const char* AutoScrollContinuousString = "continuous";
enum class AutoScrollState
{
Stepped,
Continuous,
Disabled
};
TimeLineWidget(int xoff, int yoff, float ppb, Timeline& timeline,
const TimePos& begin, QWidget* parent);
~TimeLineWidget() override;
inline QColor const & getBarLineColor() const { return m_barLineColor; }
inline void setBarLineColor(QColor const & barLineColor) { m_barLineColor = barLineColor; }
inline QColor const & getBarNumberColor() const { return m_barNumberColor; }
inline void setBarNumberColor(QColor const & barNumberColor) { m_barNumberColor = barNumberColor; }
inline QColor const & getInactiveLoopColor() const { return m_inactiveLoopColor; }
inline void setInactiveLoopColor(QColor const & inactiveLoopColor) { m_inactiveLoopColor = inactiveLoopColor; }
inline QBrush const & getInactiveLoopBrush() const { return m_inactiveLoopBrush; }
inline void setInactiveLoopBrush(QBrush const & inactiveLoopBrush) { m_inactiveLoopBrush = inactiveLoopBrush; }
inline QColor const & getInactiveLoopInnerColor() const { return m_inactiveLoopInnerColor; }
inline void setInactiveLoopInnerColor(QColor const & inactiveLoopInnerColor) { m_inactiveLoopInnerColor = inactiveLoopInnerColor; }
inline QColor const & getActiveLoopColor() const { return m_activeLoopColor; }
inline void setActiveLoopColor(QColor const & activeLoopColor) { m_activeLoopColor = activeLoopColor; }
inline QBrush const & getActiveLoopBrush() const { return m_activeLoopBrush; }
inline void setActiveLoopBrush(QBrush const & activeLoopBrush) { m_activeLoopBrush = activeLoopBrush; }
inline QColor const & getActiveLoopInnerColor() const { return m_activeLoopInnerColor; }
inline void setActiveLoopInnerColor(QColor const & activeLoopInnerColor) { m_activeLoopInnerColor = activeLoopInnerColor; }
inline int const & getLoopRectangleVerticalPadding() const { return m_loopRectangleVerticalPadding; }
inline void setLoopRectangleVerticalPadding(int const & loopRectangleVerticalPadding) { m_loopRectangleVerticalPadding = loopRectangleVerticalPadding; }
auto mouseHotspotSelLeft() const -> QSize
{
const auto point = m_cursorSelectLeft.hotSpot();
return QSize{point.x(), point.y()};
}
void setMouseHotspotSelLeft(const QSize& s)
{
m_cursorSelectLeft = QCursor{m_cursorSelectLeft.pixmap(), s.width(), s.height()};
}
auto mouseHotspotSelRight() const -> QSize
{
const auto point = m_cursorSelectRight.hotSpot();
return QSize{point.x(), point.y()};
}
void setMouseHotspotSelRight(const QSize& s)
{
m_cursorSelectRight = QCursor{m_cursorSelectRight.pixmap(), s.width(), s.height()};
}
static AutoScrollState defaultAutoScrollState();
AutoScrollState autoScroll() const { return m_autoScroll; }
void setAutoScroll(AutoScrollState state) { m_autoScroll = state; }
inline void setPixelsPerBar( float ppb )
{
m_ppb = ppb;
update();
}
void setXOffset(const int x);
void addToolButtons(QToolBar* _tool_bar );
inline int markerX( const TimePos & _t ) const
{
return m_xOffset + static_cast<int>( ( _t - m_begin ) *
m_ppb / TimePos::ticksPerBar() );
}
Timeline* timeline()
{
return m_timeline;
}
bool isRecording() const { return m_isRecording; }
void setRecording(bool recording) { m_isRecording = recording; }
bool isPlayheadVisible() const { return m_isPlayheadVisible; }
void setPlayheadVisible(bool visible) { m_isPlayheadVisible = visible; }
signals:
void regionSelectedFromPixels( int, int );
void selectionFinished();
public slots:
void setSnapSize( const float snapSize )
{
m_snapSize = snapSize;
}
void toggleAutoScroll( int _n );
protected:
void paintEvent( QPaintEvent * _pe ) override;
void mousePressEvent( QMouseEvent * _me ) override;
void mouseMoveEvent( QMouseEvent * _me ) override;
void mouseReleaseEvent( QMouseEvent * _me ) override;
void contextMenuEvent(QContextMenuEvent* event) override;
private:
enum class Action
{
NoAction,
MovePositionMarker,
MoveLoopBegin,
MoveLoopEnd,
MoveLoop,
SelectSongClip,
};
auto getClickedTime(int xPosition) const -> TimePos;
auto getLoopAction(QMouseEvent* event) const -> Action;
auto actionCursor(Action action) const -> QCursor;
QPixmap m_posMarkerPixmap = embed::getIconPixmap("playpos_marker");
QPixmap m_recordingPosMarkerPixmap = embed::getIconPixmap("recording_playpos_marker");
QColor m_inactiveLoopColor = QColor{52, 63, 53, 64};
QBrush m_inactiveLoopBrush = QColor{255, 255, 255, 32};
QColor m_inactiveLoopInnerColor = QColor{255, 255, 255, 32};
QColor m_inactiveLoopHandleColor = QColor{255, 255, 255, 32};
QColor m_activeLoopColor = QColor{52, 63, 53, 255};
QBrush m_activeLoopBrush = QColor{55, 141, 89};
QColor m_activeLoopInnerColor = QColor{74, 155, 100, 255};
QColor m_activeLoopHandleColor = QColor{74, 155, 100, 255};
int m_loopRectangleVerticalPadding = 1;
int m_loopHandleWidth = 5;
QColor m_barLineColor = QColor{192, 192, 192};
QColor m_barNumberColor = m_barLineColor.darker(120);
QCursor m_cursorSelectLeft = QCursor{embed::getIconPixmap("cursor_select_left"), 0, 16};
QCursor m_cursorSelectRight = QCursor{embed::getIconPixmap("cursor_select_right"), 32, 16};
// Width of the unused region on the widget's left (above track labels or piano)
int m_xOffset;
float m_ppb;
float m_snapSize = 1.f;
Timeline* m_timeline;
// Leftmost position visible in parent editor
const TimePos & m_begin;
AutoScrollState m_autoScroll = defaultAutoScrollState();
// When in MoveLoop mode we need the initial positions. Storing only the latest
// position allows for unquantized drag but fails when toggling quantization.
std::array<TimePos, 2> m_oldLoopPos;
TimePos m_dragStartPos;
bool m_isRecording = false;
bool m_isPlayheadVisible = true;
TextFloat* m_hint = nullptr;
int m_initalXSelect;
Action m_action = Action::NoAction;
};
} // namespace lmms::gui
#endif // LMMS_GUI_TIMELINE_WIDGET_H