From efabbad40c016b3c1b32ac2d349aee178d01d322 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sat, 18 Apr 2026 20:08:15 +0200 Subject: [PATCH 01/10] BaseDockWidget: Allow adding a child widget to the titlebar --- app/src/basedockwidget.cpp | 23 +++++++++++++++++- app/src/basedockwidget.h | 7 ++++++ app/src/titlebarwidget.cpp | 50 +++++++++++++++++++++++++++----------- app/src/titlebarwidget.h | 17 ++++++++++++- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/app/src/basedockwidget.cpp b/app/src/basedockwidget.cpp index 3336b71706..83a1de0068 100644 --- a/app/src/basedockwidget.cpp +++ b/app/src/basedockwidget.cpp @@ -17,6 +17,7 @@ GNU General Public License for more details. #include #include +#include #include "basedockwidget.h" #include "platformhandler.h" @@ -63,9 +64,14 @@ void BaseDockWidget::lock(bool locked) // nullptr means removing the custom title bar and restoring the default one if (locked) { - setTitleBarWidget(mNoTitleBarWidget); + if (mTitleBarWidget->hasChildWidget()) { + mTitleBarWidget->lock(locked); + } else { + setTitleBarWidget(mNoTitleBarWidget); + } } else { setTitleBarWidget(mTitleBarWidget); + mTitleBarWidget->lock(locked); } mLocked = locked; @@ -77,6 +83,21 @@ void BaseDockWidget::setTitle(const QString& title) mTitleBarWidget->setTitle(title); } +void BaseDockWidget::setWidgetInTitleBarArea(QWidget* toolbar) +{ + if (mTitleBarWidget) { + mTitleBarWidget->setChildWidget(toolbar); + } +} + +QWidget* BaseDockWidget::titleBarWidget() const +{ + if (mTitleBarWidget) { + return mTitleBarWidget; + } + return nullptr; +} + void BaseDockWidget::resizeEvent(QResizeEvent *event) { QDockWidget::resizeEvent(event); diff --git a/app/src/basedockwidget.h b/app/src/basedockwidget.h index fd4323c4a1..6557831e5a 100644 --- a/app/src/basedockwidget.h +++ b/app/src/basedockwidget.h @@ -22,6 +22,7 @@ GNU General Public License for more details. class Editor; class TitleBarWidget; +class QToolBar; class BaseDockWidget : public QDockWidget { @@ -42,6 +43,12 @@ class BaseDockWidget : public QDockWidget Editor* editor() const { return mEditor; } void setEditor( Editor* e ) { mEditor = e; } + /// Sets a widget in addition to the normal titlebar buttons. + /// If the titlebar already has one widget, the previous one will be deleted. + void setWidgetInTitleBarArea(QWidget* toolbar); + + QWidget* titleBarWidget() const; + protected: void resizeEvent(QResizeEvent* event) override; diff --git a/app/src/titlebarwidget.cpp b/app/src/titlebarwidget.cpp index d7aabb95da..b2ad1feeb1 100644 --- a/app/src/titlebarwidget.cpp +++ b/app/src/titlebarwidget.cpp @@ -33,12 +33,15 @@ TitleBarWidget::TitleBarWidget(QWidget* parent) : QWidget(parent) { - QVBoxLayout* vLayout = new QVBoxLayout(); + QHBoxLayout* vLayout = new QHBoxLayout(); - vLayout->setContentsMargins(3,4,3,4); + vLayout->setContentsMargins(2,0,2,0); vLayout->setSpacing(0); - vLayout->addWidget(createCustomTitleBarWidget(this)); + QWidget* contentWidget = createCustomTitleBarWidget(this); + vLayout->addWidget(contentWidget); + + contentWidget->setMinimumHeight(28); setLayout(vLayout); } @@ -52,7 +55,7 @@ QWidget* TitleBarWidget::createCustomTitleBarWidget(QWidget* parent) bool isDarkmode = PlatformHandler::isDarkMode(); QWidget* containerWidget = new QWidget(parent); - QHBoxLayout* containerLayout = new QHBoxLayout(parent); + mContainerLayout = new QHBoxLayout(parent); mCloseButton = new QToolButton(parent); @@ -104,25 +107,34 @@ QWidget* TitleBarWidget::createCustomTitleBarWidget(QWidget* parent) mTitleLabel->setAlignment(Qt::AlignVCenter); #ifdef __APPLE__ - containerLayout->addWidget(mCloseButton); - containerLayout->addWidget(mDockButton); - containerLayout->addWidget(mTitleLabel); + mContainerLayout->addWidget(mCloseButton); + mContainerLayout->addWidget(mDockButton); + mContainerLayout->addWidget(mTitleLabel); #else - containerLayout->addWidget(mTitleLabel); - containerLayout->addWidget(mDockButton); - containerLayout->addWidget(mCloseButton); + mContainerLayout->addWidget(mTitleLabel); + mContainerLayout->addWidget(mDockButton); + mContainerLayout->addWidget(mCloseButton); #endif - containerLayout->setSpacing(3); - containerLayout->setContentsMargins(0,0,0,0); + mContainerLayout->setSpacing(3); + mContainerLayout->setContentsMargins(0,0,0,0); - containerWidget->setLayout(containerLayout); + containerWidget->setLayout(mContainerLayout); containerWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); containerWidget->setMinimumSize(QSize(1,1)); return containerWidget; } +void TitleBarWidget::setChildWidget(QWidget* widget) +{ + if (mHasChildWidget) { + mContainerLayout->removeWidget(widget); + } + mContainerLayout->addWidget(widget); + mHasChildWidget = true; +} + QString TitleBarWidget::flatButtonStylesheet() const { return "QToolButton { border: 0; }"; @@ -133,6 +145,16 @@ void TitleBarWidget::setTitle(const QString &title) mTitleLabel->setText(title); } +void TitleBarWidget::lock(bool locked) +{ + if (locked) { + hideButtons(true); + } else { + hideButtonsIfNeeded(this->width()); + } + mIsLocked = locked; +} + void TitleBarWidget::hideButtons(bool hide) { mCloseButton->setHidden(hide); @@ -150,7 +172,7 @@ void TitleBarWidget::hideButtonsIfNeeded(int width) { if (width <= mWidthOfFullLayout) { hideButtons(true); - } else { + } else if (!mIsLocked) { hideButtons(false); } } diff --git a/app/src/titlebarwidget.h b/app/src/titlebarwidget.h index 70f1482725..4130d67277 100644 --- a/app/src/titlebarwidget.h +++ b/app/src/titlebarwidget.h @@ -23,6 +23,8 @@ GNU General Public License for more details. class QLabel; class QToolButton; +class QToolBar; +class QHBoxLayout; class TitleBarWidget : public QWidget { @@ -37,14 +39,23 @@ class TitleBarWidget : public QWidget void setIsFloating(bool floating) { mIsFloating = floating; } + /// Add a child widget next to the bar buttons + void setChildWidget(QWidget* widget); + + /// Locking the title area hides the title bar buttons + void lock(bool locked); + + /// Returns true if a custom child widget has been added, otherwise false + bool hasChildWidget() const { return mHasChildWidget; } + signals: void closeButtonPressed(); void undockButtonPressed(); private: + void hideButtons(bool hide); QString flatButtonStylesheet() const; void showEvent(QShowEvent* event) override; - void hideButtons(bool hide); void hideButtonsIfNeeded(int width); QWidget* createCustomTitleBarWidget(QWidget* parent); @@ -53,7 +64,11 @@ class TitleBarWidget : public QWidget QToolButton* mCloseButton = nullptr; QToolButton* mDockButton = nullptr; + QHBoxLayout* mContainerLayout = nullptr; + + bool mIsLocked = false; bool mIsFloating = false; + bool mHasChildWidget = false; int mWidthOfFullLayout = 0; }; From fd78a51cb8d72c4ccaa800353a70eca737e718fa Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sat, 18 Apr 2026 20:09:39 +0200 Subject: [PATCH 02/10] Timeline: Put time control buttons in the titlebar - TimeControls are not centered - New time code icon - Buttons changed to QToolButton --- app/data/app.qrc | 1 + .../playful/controls/control-timecode.svg | 1 + app/src/timecontrols.cpp | 70 +++++++++++-------- app/src/timecontrols.h | 15 ++-- app/src/timeline.cpp | 6 +- 5 files changed, 54 insertions(+), 39 deletions(-) create mode 100644 app/data/icons/themes/playful/controls/control-timecode.svg diff --git a/app/data/app.qrc b/app/data/app.qrc index 5b9116dab2..2a144aa9db 100644 --- a/app/data/app.qrc +++ b/app/data/app.qrc @@ -100,6 +100,7 @@ icons/themes/playful/window/window-float-button-normal-darkm.svg icons/themes/playful/window/window-float-button-normal.svg icons/themes/playful/window/window-close-button-normal.svg + icons/themes/playful/controls/control-timecode.svg pencil2d_quick_guide.pdf diff --git a/app/data/icons/themes/playful/controls/control-timecode.svg b/app/data/icons/themes/playful/controls/control-timecode.svg new file mode 100644 index 0000000000..1e6983cbec --- /dev/null +++ b/app/data/icons/themes/playful/controls/control-timecode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index 2dc7748f15..c62f91d1d8 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -21,6 +21,7 @@ GNU General Public License for more details. #include #include #include +#include #include "editor.h" #include "playbackmanager.h" #include "layermanager.h" @@ -30,7 +31,9 @@ GNU General Public License for more details. #include "timeline.h" #include "pencildef.h" -TimeControls::TimeControls(TimeLine* parent) : QToolBar(parent) +#include + +TimeControls::TimeControls(TimeLine* parent) : QWidget(parent) { mTimeline = parent; } @@ -39,8 +42,12 @@ void TimeControls::initUI() { QSettings settings(PENCIL2D, PENCIL2D); + QHBoxLayout* hBoxLayout = new QHBoxLayout(); + hBoxLayout->setContentsMargins(0,0,0,0); + + setLayout(hBoxLayout); + mFpsBox = new QSpinBox(this); - mFpsBox->setFixedHeight(24); mFpsBox->setValue(settings.value("Fps").toInt()); mFpsBox->setMinimum(1); mFpsBox->setMaximum(90); @@ -49,10 +56,12 @@ void TimeControls::initUI() mFpsBox->setFocusPolicy(Qt::WheelFocus); mFps = mFpsBox->value(); - mTimecodeSelect = new QToolButton(this); QMenu* timeSelectMenu = new QMenu(tr("Display timecode", "Timeline menu for choose a timecode"), this); - mTimecodeSelect->setIcon(QIcon(":/icons/themes/playful/misc/more-options.svg")); + + mTimecodeSelect = new QToolButton(this); + mTimecodeSelect->setIconSize(QSize(22,22)); + mTimecodeSelect->setIcon(QIcon(":/icons/themes/playful/controls/control-timecode.svg")); timeSelectMenu->addAction(mNoTimecodeAction = new QAction(tr("No text"), this)); timeSelectMenu->addAction(mOnlyFramesAction = new QAction(tr("Frames"), this)); @@ -64,7 +73,11 @@ void TimeControls::initUI() mTimecodeLabelEnum = mEditor->preference()->getInt(SETTING::TIMECODE_TEXT); mTimecodeLabel = new QLabel(this); mTimecodeLabel->setContentsMargins(2, 0, 0, 0); - mTimecodeLabel->setText(""); + mTimecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); + + updateTimecodeLabel(mEditor->currentFrame()); + + mTimecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); switch (mTimecodeLabelEnum) { @@ -85,7 +98,6 @@ void TimeControls::initUI() } mLoopStartSpinBox = new QSpinBox(this); - mLoopStartSpinBox->setFixedHeight(24); mLoopStartSpinBox->setValue(settings.value("loopStart").toInt()); mLoopStartSpinBox->setMinimum(1); mLoopStartSpinBox->setEnabled(false); @@ -93,7 +105,6 @@ void TimeControls::initUI() mLoopStartSpinBox->setFocusPolicy(Qt::WheelFocus); mLoopEndSpinBox = new QSpinBox(this); - mLoopEndSpinBox->setFixedHeight(24); mLoopEndSpinBox->setValue(settings.value("loopEnd").toInt()); mLoopEndSpinBox->setMinimum(2); mLoopEndSpinBox->setEnabled(false); @@ -101,20 +112,19 @@ void TimeControls::initUI() mLoopEndSpinBox->setFocusPolicy(Qt::WheelFocus); mPlaybackRangeCheckBox = new QCheckBox(tr("Range")); - mPlaybackRangeCheckBox->setFixedHeight(24); mPlaybackRangeCheckBox->setToolTip(tr("Playback range")); - mPlayButton = new QPushButton(this); + mPlayButton = new QToolButton(this); mPlayButton->setIconSize(QSize(22,22)); - mLoopButton = new QPushButton(this); + mLoopButton = new QToolButton(this); mLoopButton->setIconSize(QSize(22,22)); - mSoundButton = new QPushButton(this); + mSoundButton = new QToolButton(this); mSoundButton->setIconSize(QSize(22,22)); - mSoundScrubButton = new QPushButton(this); + mSoundScrubButton = new QToolButton(this); mSoundScrubButton->setIconSize(QSize(22,22)); - mJumpToEndButton = new QPushButton(this); + mJumpToEndButton = new QToolButton(this); mJumpToEndButton->setIconSize(QSize(22,22)); - mJumpToStartButton = new QPushButton(this); + mJumpToStartButton = new QToolButton(this); mJumpToStartButton->setIconSize(QSize(22,22)); mLoopIcon = QIcon(":icons/themes/playful/controls/control-loop.svg"); @@ -144,19 +154,21 @@ void TimeControls::initUI() mSoundScrubButton->setCheckable(true); mSoundScrubButton->setChecked(mEditor->preference()->isOn(SETTING::SOUND_SCRUB_ACTIVE)); - - addWidget(mJumpToStartButton); - addWidget(mPlayButton); - addWidget(mJumpToEndButton); - addWidget(mLoopButton); - addWidget(mFpsBox); - addWidget(mPlaybackRangeCheckBox); - addWidget(mLoopStartSpinBox); - addWidget(mLoopEndSpinBox); - addWidget(mSoundButton); - addWidget(mSoundScrubButton); - addWidget(mTimecodeSelect); - mTimecodeLabelAction = addWidget(mTimecodeLabel); + layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding)); + layout()->addWidget(mTimecodeLabel); + layout()->addWidget(mTimecodeSelect); + layout()->addWidget(mJumpToStartButton); + layout()->addWidget(mPlayButton); + layout()->addWidget(mJumpToEndButton); + layout()->addWidget(mLoopButton); + + layout()->addWidget(mFpsBox); + layout()->addWidget(mPlaybackRangeCheckBox); + layout()->addWidget(mLoopStartSpinBox); + layout()->addWidget(mLoopEndSpinBox); + layout()->addWidget(mSoundButton); + layout()->addWidget(mSoundScrubButton); + layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding)); makeConnections(); @@ -364,7 +376,7 @@ void TimeControls::onFpsEditingFinished() void TimeControls::updateTimecodeLabel(int frame) { - mTimecodeLabelAction->setVisible(true); + mTimecodeLabel->setVisible(true); switch (mTimecodeLabelEnum) { @@ -384,7 +396,7 @@ void TimeControls::updateTimecodeLabel(int frame) break; case TimecodeTextLevel::NOTEXT: default: - mTimecodeLabelAction->setVisible(false); + mTimecodeLabel->setVisible(false); break; } diff --git a/app/src/timecontrols.h b/app/src/timecontrols.h index d2901085c9..ad020d6d3d 100644 --- a/app/src/timecontrols.h +++ b/app/src/timecontrols.h @@ -29,7 +29,7 @@ class Editor; class PreferenceManager; class TimeLine; -class TimeControls : public QToolBar +class TimeControls : public QWidget { Q_OBJECT @@ -77,12 +77,12 @@ public slots: void smpteText(); private: - QPushButton* mPlayButton = nullptr; - QPushButton* mJumpToEndButton = nullptr; - QPushButton* mJumpToStartButton = nullptr; - QPushButton* mLoopButton = nullptr; - QPushButton* mSoundButton = nullptr; - QPushButton* mSoundScrubButton = nullptr; + QToolButton* mPlayButton = nullptr; + QToolButton* mJumpToEndButton = nullptr; + QToolButton* mJumpToStartButton = nullptr; + QToolButton* mLoopButton = nullptr; + QToolButton* mSoundButton = nullptr; + QToolButton* mSoundScrubButton = nullptr; QSpinBox* mFpsBox = nullptr; QCheckBox* mPlaybackRangeCheckBox = nullptr; QSpinBox* mLoopStartSpinBox = nullptr; @@ -93,7 +93,6 @@ public slots: QAction* mOnlyFramesAction = nullptr; QAction* mSmpteAction = nullptr; QAction* mSffAction = nullptr; - QAction* mTimecodeLabelAction = nullptr; QIcon mStartIcon; QIcon mStopIcon; diff --git a/app/src/timeline.cpp b/app/src/timeline.cpp index 92a5544e9c..1acc9c325d 100644 --- a/app/src/timeline.cpp +++ b/app/src/timeline.cpp @@ -156,15 +156,17 @@ void TimeLine::initUI() // --------- Time controls --------- mTimeControls = new TimeControls(this); - mTimeControls->setIconSize(QSize(22,22)); + // Needs to be slightly larger than the titlebar to prevent the icons from becoming smaller. + mTimeControls->setFixedHeight(this->titleBarWidget()->height() + 1); mTimeControls->setEditor(editor()); mTimeControls->initUI(); updateLength(); + this->setWidgetInTitleBarArea(mTimeControls); + QHBoxLayout* rightToolBarLayout = new QHBoxLayout(); rightToolBarLayout->addWidget(timelineButtons); rightToolBarLayout->setAlignment(Qt::AlignLeft); - rightToolBarLayout->addWidget(mTimeControls); rightToolBarLayout->setContentsMargins(0, 0, 0, 0); rightToolBarLayout->setSpacing(0); rightToolBar->setLayout(rightToolBarLayout); From 7b6f9032148cc989a6a4d010f0e84d1041efe604 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 10:10:50 +0200 Subject: [PATCH 03/10] TimeControls: Simplify timecode logic --- app/src/timecontrols.cpp | 188 ++++++++++++++++++---------------- app/src/timecontrols.h | 27 +++-- core_lib/src/util/pencildef.h | 2 +- 3 files changed, 122 insertions(+), 95 deletions(-) diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index c62f91d1d8..3a0beaf83c 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -57,45 +57,7 @@ void TimeControls::initUI() mFps = mFpsBox->value(); - QMenu* timeSelectMenu = new QMenu(tr("Display timecode", "Timeline menu for choose a timecode"), this); - - mTimecodeSelect = new QToolButton(this); - mTimecodeSelect->setIconSize(QSize(22,22)); - mTimecodeSelect->setIcon(QIcon(":/icons/themes/playful/controls/control-timecode.svg")); - - timeSelectMenu->addAction(mNoTimecodeAction = new QAction(tr("No text"), this)); - timeSelectMenu->addAction(mOnlyFramesAction = new QAction(tr("Frames"), this)); - timeSelectMenu->addAction(mSmpteAction = new QAction(tr("SMPTE Timecode"), this)); - timeSelectMenu->addAction(mSffAction = new QAction(tr("SFF Timecode"), this)); - mTimecodeSelect->setMenu(timeSelectMenu); - mTimecodeSelect->setPopupMode(QToolButton::InstantPopup); - mTimecodeSelect->setStyleSheet("::menu-indicator{ image: none; }"); - mTimecodeLabelEnum = mEditor->preference()->getInt(SETTING::TIMECODE_TEXT); - mTimecodeLabel = new QLabel(this); - mTimecodeLabel->setContentsMargins(2, 0, 0, 0); - mTimecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); - - updateTimecodeLabel(mEditor->currentFrame()); - - mTimecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - switch (mTimecodeLabelEnum) - { - case NOTEXT: - mTimecodeLabel->setToolTip(""); - break; - case FRAMES: - mTimecodeLabel->setToolTip(tr("Actual frame number")); - break; - case SMPTE: - mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); - break; - case SFF: - mTimecodeLabel->setToolTip(tr("Timecode format S:FF")); - break; - default: - mTimecodeLabel->setToolTip(""); - } + setupTimeCodeMenu(); mLoopStartSpinBox = new QSpinBox(this); mLoopStartSpinBox->setValue(settings.value("loopStart").toInt()); @@ -155,8 +117,8 @@ void TimeControls::initUI() mSoundScrubButton->setChecked(mEditor->preference()->isOn(SETTING::SOUND_SCRUB_ACTIVE)); layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding)); - layout()->addWidget(mTimecodeLabel); - layout()->addWidget(mTimecodeSelect); + layout()->addWidget(mTimeCode.timecodeLabel); + layout()->addWidget(mTimeCode.timecodeButton); layout()->addWidget(mJumpToStartButton); layout()->addWidget(mPlayButton); layout()->addWidget(mJumpToEndButton); @@ -200,6 +162,78 @@ void TimeControls::setEditor(Editor* editor) mEditor = editor; } +void TimeControls::setupTimeCodeMenu() +{ + QMenu* timeSelectMenu = new QMenu(tr("Display timecode", "Timeline menu for choose a timecode"), this); + + mTimeCode.timecodeButton = new QToolButton(this); + mTimeCode.timecodeButton->setIconSize(QSize(22,22)); + mTimeCode.timecodeButton->setIcon(QIcon(":/icons/themes/playful/controls/control-timecode.svg")); + + timeSelectMenu->addAction(mTimeCode.hideAction = new QAction(tr("Hide"), this)); + timeSelectMenu->addSeparator(); + timeSelectMenu->addAction(mTimeCode.framesAction = new QAction(tr("Frames"), this)); + timeSelectMenu->addSeparator(); + timeSelectMenu->addAction(mTimeCode.smpteAction = new QAction(tr("SMPTE Timecode"), this)); + timeSelectMenu->addAction(mTimeCode.sffAction = new QAction(tr("SFF Timecode"), this)); + mTimeCode.timecodeButton->setMenu(timeSelectMenu); + mTimeCode.timecodeButton->setPopupMode(QToolButton::InstantPopup); + mTimeCode.timecodeButton->setStyleSheet("::menu-indicator{ image: none; }"); + mTimeCode.timecodeKind = timecodeKindFromPreference(); + mTimeCode.timecodeLabel = new QLabel(this); + mTimeCode.timecodeLabel->setContentsMargins(2, 0, 0, 0); + mTimeCode.timecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); + mTimeCode.timecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + updateTimecodeLabel(mEditor->currentFrame()); + updateTimecodeToolTip(mTimeCode.timecodeKind); +} + +void TimeControls::updateTimecodeToolTip(TimecodeKind kind) +{ + switch (kind) + { + case TimecodeKind::NOTEXT: + mTimeCode.timecodeLabel->setToolTip(""); + break; + case TimecodeKind::FRAMES: + mTimeCode.timecodeLabel->setToolTip(tr("Actual frame number")); + break; + case TimecodeKind::SMPTE: + mTimeCode.timecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); + break; + case TimecodeKind::SFF: + mTimeCode.timecodeLabel->setToolTip(tr("Timecode format S:FF")); + break; + default: + mTimeCode.timecodeLabel->setToolTip(""); + } +} + +TimecodeKind TimeControls::timecodeKindFromPreference() const +{ + int timecodePreference = mEditor->preference()->getInt(SETTING::TIMECODE_TEXT); + + switch (timecodePreference) + { + case 0: + return TimecodeKind::NOTEXT; + case 1: + return TimecodeKind::FRAMES; + case 2: + return TimecodeKind::SMPTE; + case 3: + return TimecodeKind::SFF; + default: + return TimecodeKind::NOTEXT; + } +} + +int TimeControls::timecodeKindToInt(TimecodeKind kind) const +{ + return static_cast(kind); +} + void TimeControls::setFps(int value) { QSignalBlocker blocker(mFpsBox); @@ -246,10 +280,18 @@ void TimeControls::makeConnections() connect(mFpsBox, spinBoxValueChanged, this, &TimeControls::setFps); connect(mEditor, &Editor::fpsChanged, this, &TimeControls::setFps); - connect(mNoTimecodeAction, &QAction::triggered, this, &TimeControls::noTimecodeText); - connect(mOnlyFramesAction, &QAction::triggered, this, &TimeControls::onlyFramesText); - connect(mSmpteAction, &QAction::triggered, this, &TimeControls::smpteText); - connect(mSffAction, &QAction::triggered, this, &TimeControls::sffText); + connect(mTimeCode.hideAction, &QAction::triggered, this, [this]() { + setTimecode(TimecodeKind::NOTEXT); + }); + connect(mTimeCode.framesAction, &QAction::triggered, this, [this]() { + setTimecode(TimecodeKind::FRAMES); + }); + connect(mTimeCode.smpteAction, &QAction::triggered, this, [this]() { + setTimecode(TimecodeKind::SMPTE); + }); + connect(mTimeCode.sffAction, &QAction::triggered, this, [this]() { + setTimecode(TimecodeKind::SFF); + }); } void TimeControls::playButtonClicked() @@ -331,40 +373,13 @@ void TimeControls::updateSoundScrubIcon(bool soundScrubEnabled) mEditor->preference()->set(SETTING::SOUND_SCRUB_ACTIVE, soundScrubEnabled); } -void TimeControls::noTimecodeText() +void TimeControls::setTimecode(TimecodeKind kind) { QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_TIMECODE_TEXT, NOTEXT); - mTimecodeLabelEnum = NOTEXT; - mTimecodeLabel->setToolTip(tr("")); - updateTimecodeLabel(mEditor->currentFrame()); -} - -void TimeControls::onlyFramesText() -{ - QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_TIMECODE_TEXT, FRAMES); - mTimecodeLabelEnum = FRAMES; - mTimecodeLabel->setToolTip(tr("Actual frame number")); - updateTimecodeLabel(mEditor->currentFrame()); -} - -void TimeControls::sffText() -{ - QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_TIMECODE_TEXT, SFF); - mTimecodeLabelEnum = SFF; - mTimecodeLabel->setToolTip(tr("Timecode format S:FF")); - updateTimecodeLabel(mEditor->currentFrame()); -} - -void TimeControls::smpteText() -{ - QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_TIMECODE_TEXT, SMPTE); - mTimecodeLabelEnum = SMPTE; - mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); + settings.setValue(SETTING_TIMECODE_TEXT, timecodeKindToInt(kind)); + mTimeCode.timecodeKind = kind; updateTimecodeLabel(mEditor->currentFrame()); + updateTimecodeToolTip(kind); } void TimeControls::onFpsEditingFinished() @@ -376,30 +391,29 @@ void TimeControls::onFpsEditingFinished() void TimeControls::updateTimecodeLabel(int frame) { - mTimecodeLabel->setVisible(true); + mTimeCode.timecodeLabel->setVisible(true); - switch (mTimecodeLabelEnum) + switch (mTimeCode.timecodeKind) { - case TimecodeTextLevel::SMPTE: - mTimecodeLabel->setText(QString("%1:%2:%3") + case TimecodeKind::SMPTE: + mTimeCode.timecodeLabel->setText(QString("%1:%2:%3") .arg(frame / (60 * mFps) % 60, 2, 10, QLatin1Char('0')) .arg(frame / mFps % 60, 2, 10, QLatin1Char('0')) .arg(frame % mFps, 2, 10, QLatin1Char('0'))); break; - case TimecodeTextLevel::SFF: - mTimecodeLabel->setText(QString("%1:%2") + case TimecodeKind::SFF: + mTimeCode.timecodeLabel->setText(QString("%1:%2") .arg(frame / mFps) .arg(frame % mFps, 2, 10, QLatin1Char('0'))); break; - case TimecodeTextLevel::FRAMES: - mTimecodeLabel->setText(QString::number(frame).rightJustified(4, '0')); + case TimecodeKind::FRAMES: + mTimeCode.timecodeLabel->setText(QString::number(frame).rightJustified(4, '0')); break; - case TimecodeTextLevel::NOTEXT: + case TimecodeKind::NOTEXT: default: - mTimecodeLabel->setVisible(false); + mTimeCode.timecodeLabel->setVisible(false); break; } - } void TimeControls::updateLength(int frameLength) diff --git a/app/src/timecontrols.h b/app/src/timecontrols.h index ad020d6d3d..1ed88a5332 100644 --- a/app/src/timecontrols.h +++ b/app/src/timecontrols.h @@ -25,6 +25,8 @@ GNU General Public License for more details. #include #include +#include "pencildef.h" + class Editor; class PreferenceManager; class TimeLine; @@ -33,6 +35,17 @@ class TimeControls : public QWidget { Q_OBJECT + struct TimeCode { + QToolButton* timecodeButton = nullptr; + QLabel* timecodeLabel = nullptr; + QAction* hideAction = nullptr; + QAction* framesAction = nullptr; + QAction* smpteAction = nullptr; + QAction* sffAction = nullptr; + + TimecodeKind timecodeKind = TimecodeKind::NOTEXT; + }; + public: TimeControls(TimeLine* parent = nullptr); void initUI(); @@ -62,6 +75,7 @@ public slots: private: void makeConnections(); + void setupTimeCodeMenu(); void playButtonClicked(); void jumpToStartButtonClicked(); void jumpToEndButtonClicked(); @@ -71,6 +85,10 @@ public slots: void loopEndValueChanged(int); void updateSoundScrubIcon(bool soundScrubEnabled); + void setTimecode(TimecodeKind kind); + int timecodeKindToInt(TimecodeKind kind) const; + TimecodeKind timecodeKindFromPreference() const; + void updateTimecodeToolTip(TimecodeKind kind); void noTimecodeText(); void onlyFramesText(); void sffText(); @@ -87,12 +105,8 @@ public slots: QCheckBox* mPlaybackRangeCheckBox = nullptr; QSpinBox* mLoopStartSpinBox = nullptr; QSpinBox* mLoopEndSpinBox = nullptr; - QToolButton* mTimecodeSelect = nullptr; - QLabel* mTimecodeLabel = nullptr; - QAction* mNoTimecodeAction = nullptr; - QAction* mOnlyFramesAction = nullptr; - QAction* mSmpteAction = nullptr; - QAction* mSffAction = nullptr; + + TimeCode mTimeCode; QIcon mStartIcon; QIcon mStopIcon; @@ -105,7 +119,6 @@ public slots: TimeLine* mTimeline = nullptr; Editor* mEditor = nullptr; int mFps = 12; - int mTimecodeLabelEnum; }; #endif diff --git a/core_lib/src/util/pencildef.h b/core_lib/src/util/pencildef.h index 00efcb73fd..1f8816bb60 100644 --- a/core_lib/src/util/pencildef.h +++ b/core_lib/src/util/pencildef.h @@ -72,7 +72,7 @@ enum StabilizationLevel STRONG }; -enum TimecodeTextLevel +enum class TimecodeKind { NOTEXT, FRAMES, // FF From 21e4ce4815982b1080b0a8ab8c64c8eb64479755 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 12:43:27 +0200 Subject: [PATCH 04/10] TimeControls: Ensure timecodes are mono spaced To prevent micro jitter. --- app/src/timecontrols.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index 3a0beaf83c..8d8fa6077b 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -22,6 +22,7 @@ GNU General Public License for more details. #include #include #include +#include #include "editor.h" #include "playbackmanager.h" #include "layermanager.h" @@ -181,6 +182,10 @@ void TimeControls::setupTimeCodeMenu() mTimeCode.timecodeButton->setStyleSheet("::menu-indicator{ image: none; }"); mTimeCode.timecodeKind = timecodeKindFromPreference(); mTimeCode.timecodeLabel = new QLabel(this); + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + // The default mono font is smaller, so we restore the font here + font.setPointSize(mTimeCode.timecodeLabel->font().pointSize()); + mTimeCode.timecodeLabel->setFont(font); mTimeCode.timecodeLabel->setContentsMargins(2, 0, 0, 0); mTimeCode.timecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); mTimeCode.timecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); From 05b2d89a85910e61a19431d17ea0affacd93799d Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 18:31:47 +0200 Subject: [PATCH 05/10] TimeControls: Implement new time code controls widget - Possible to enable/disable timecodes in general - Possible to show frames next to time code --- app/app.pro | 2 + .../playful/controls/control-timecode.svg | 2 +- app/src/timecodecontrolwidget.cpp | 116 +++++++++++ app/src/timecodecontrolwidget.h | 66 +++++++ app/src/timecontrols.cpp | 182 +++++++----------- app/src/timecontrols.h | 34 ++-- core_lib/src/managers/preferencemanager.cpp | 14 +- core_lib/src/util/pencildef.h | 10 +- core_lib/src/util/preferencesdef.h | 4 +- 9 files changed, 293 insertions(+), 137 deletions(-) create mode 100644 app/src/timecodecontrolwidget.cpp create mode 100644 app/src/timecodecontrolwidget.h diff --git a/app/app.pro b/app/app.pro index e1cf3eb823..5695aab33b 100644 --- a/app/app.pro +++ b/app/app.pro @@ -93,6 +93,7 @@ HEADERS += \ src/generalpage.h \ src/shortcutspage.h \ src/strokeoptionswidget.h \ + src/timecodecontrolwidget.h \ src/timelinepage.h \ src/toolboxwidget.h \ src/toolspage.h \ @@ -151,6 +152,7 @@ SOURCES += \ src/generalpage.cpp \ src/shortcutspage.cpp \ src/strokeoptionswidget.cpp \ + src/timecodecontrolwidget.cpp \ src/timelinepage.cpp \ src/toolboxwidget.cpp \ src/toolspage.cpp \ diff --git a/app/data/icons/themes/playful/controls/control-timecode.svg b/app/data/icons/themes/playful/controls/control-timecode.svg index 1e6983cbec..2db7b5d2ff 100644 --- a/app/data/icons/themes/playful/controls/control-timecode.svg +++ b/app/data/icons/themes/playful/controls/control-timecode.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/timecodecontrolwidget.cpp b/app/src/timecodecontrolwidget.cpp new file mode 100644 index 0000000000..957e9ebc57 --- /dev/null +++ b/app/src/timecodecontrolwidget.cpp @@ -0,0 +1,116 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ +#include "timecodecontrolwidget.h" + +#include "preferencemanager.h" +#include "pencildef.h" +#include "pencilsettings.h" + +#include +#include +#include +#include +#include +#include + +TimeCodeControlWidget::TimeCodeControlWidget(TimeCodeControls* controls, QWidget* parent) + : QWidget(parent, Qt::Popup), mControls(controls) +{ + QVBoxLayout* vboxLayout = new QVBoxLayout(); + vboxLayout->setContentsMargins(4,4,4,4); + setLayout(vboxLayout); + + QLabel* titlelabel = new QLabel(tr("Timecode controls")); + + vboxLayout->addWidget(titlelabel); + + QCheckBox* showTimeCodeCheckBox = new QCheckBox(this); + showTimeCodeCheckBox->setText(tr("Show")); + showTimeCodeCheckBox->setChecked(controls->enabled); + + QGroupBox* optionsGroupBox = new QGroupBox(this); + optionsGroupBox->setTitle(tr("Options")); + optionsGroupBox->setEnabled(controls->enabled); + + QCheckBox* showFramesCheckBox = new QCheckBox(this); + showFramesCheckBox->setText(tr("Frames")); + showFramesCheckBox->setChecked(controls->showFrames); + + QComboBox* timecodeComboBox = new QComboBox(this); + + QVBoxLayout* groupBoxVBoxLayout = new QVBoxLayout(); + optionsGroupBox->setLayout(groupBoxVBoxLayout); + + groupBoxVBoxLayout->addWidget(showFramesCheckBox); + groupBoxVBoxLayout->addWidget(timecodeComboBox); + + timecodeComboBox->addItem(tr("No Timecode"), timecodeKindToInt(TimecodeKind::NONE)); + timecodeComboBox->addItem(tr("SMPTE Timecode"), timecodeKindToInt(TimecodeKind::SMPTE)); + timecodeComboBox->addItem(tr("SFF Timecode"), timecodeKindToInt(TimecodeKind::SFF)); + timecodeComboBox->setCurrentIndex(timecodeKindToInt(controls->kind)); + + vboxLayout->addWidget(showTimeCodeCheckBox); + vboxLayout->addWidget(optionsGroupBox); + + connect(showTimeCodeCheckBox, &QCheckBox::toggled, this, [this, optionsGroupBox](bool toggled) { + showTimecode(toggled); + optionsGroupBox->setEnabled(toggled); + }); + + connect(showFramesCheckBox, &QCheckBox::toggled, this, [this](bool toggled) { + showFrames(toggled); + }); + + connect(timecodeComboBox, &QComboBox::currentIndexChanged, this, [this, controls](int index) { + auto timecodeKind = controls->timecodeKindFromInt(index); + + setTimecodeTimerKind(timecodeKind); + }); +} + +void TimeCodeControlWidget::showFrames(bool shown) +{ + QSettings settings(PENCIL2D, PENCIL2D); + settings.setValue(SETTING_TIMECODE_FRAMES_ON, shown); + + mControls->showFrames = shown; + emit timecodeUpdated(); +} + +int TimeCodeControlWidget::timecodeKindToInt(TimecodeKind kind) const +{ + return static_cast(kind); +} + +void TimeCodeControlWidget::showTimecode(bool shown) +{ + QSettings settings(PENCIL2D, PENCIL2D); + settings.setValue(SETTING_TIMECODE_ON, shown); + + mControls->enabled = shown; + emit timecodeUpdated(); +} + +void TimeCodeControlWidget::setTimecodeTimerKind(TimecodeKind kind) +{ + QSettings settings(PENCIL2D, PENCIL2D); + settings.setValue(SETTING_TIMECODE_KIND, timecodeKindToInt(kind)); + settings.remove(SETTING_TIMECODE_TEXT); + mControls->kind = kind; + + emit timecodeUpdated(); +} diff --git a/app/src/timecodecontrolwidget.h b/app/src/timecodecontrolwidget.h new file mode 100644 index 0000000000..de639d4cd3 --- /dev/null +++ b/app/src/timecodecontrolwidget.h @@ -0,0 +1,66 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +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; version 2 of the License. + +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. + +*/ +#ifndef TIMECODECONTROLWIDGET_H +#define TIMECODECONTROLWIDGET_H + +#include +#include "pencildef.h" + +struct TimeCodeControls { + bool enabled = false; + bool showFrames = false; + + TimecodeKind kind = TimecodeKind::NONE; + + TimecodeKind timecodeKindFromInt(int value) const + { + switch (value) + { + case 0: + return TimecodeKind::NONE; + case 1: + return TimecodeKind::SMPTE; + case 2: + return TimecodeKind::SFF; + default: + return TimecodeKind::NONE; + } + } +}; + +class TimeCodeControlWidget : public QWidget +{ + Q_OBJECT + +public: + TimeCodeControlWidget(TimeCodeControls* controls, QWidget* parent = nullptr); + +signals: + void timecodeUpdated(); + +private: + + int timecodeKindToInt(TimecodeKind kind) const; + + void setTimecodeTimerKind(TimecodeKind kind); + void showFrames(bool shown); + void showTimecode(bool shown); + + TimeCodeControls* mControls = nullptr; +}; + +#endif // TIMECODECONTROLWIDGET_H diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index 8d8fa6077b..1cca56143c 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -58,7 +58,22 @@ void TimeControls::initUI() mFps = mFpsBox->value(); - setupTimeCodeMenu(); + mTimecodeButton = new QToolButton(this); + mTimecodeButton->setIconSize(QSize(22,22)); + mTimecodeButton->setIcon(QIcon(":/icons/themes/playful/controls/control-timecode.svg")); + + mTimecodeLabel = new QLabel(this); + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + // The default mono font is smaller, so we restore the font here + font.setPointSize(mTimecodeLabel->font().pointSize()); + mTimecodeLabel->setFont(font); + mTimecodeLabel->setContentsMargins(2, 0, 0, 0); + mTimecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); + mTimecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + mTimecodeControls.enabled = mEditor->preference()->isOn(SETTING::TIMECODE_ON); + mTimecodeControls.kind = mTimecodeControls.timecodeKindFromInt(mEditor->preference()->getInt(SETTING::TIMECODE_KIND)); + mTimecodeControls.showFrames = mEditor->preference()->isOn(SETTING::TIMECODE_FRAMES_ON); mLoopStartSpinBox = new QSpinBox(this); mLoopStartSpinBox->setValue(settings.value("loopStart").toInt()); @@ -118,8 +133,8 @@ void TimeControls::initUI() mSoundScrubButton->setChecked(mEditor->preference()->isOn(SETTING::SOUND_SCRUB_ACTIVE)); layout()->addItem(new QSpacerItem(1,1, QSizePolicy::Expanding)); - layout()->addWidget(mTimeCode.timecodeLabel); - layout()->addWidget(mTimeCode.timecodeButton); + layout()->addWidget(mTimecodeLabel); + layout()->addWidget(mTimecodeButton); layout()->addWidget(mJumpToStartButton); layout()->addWidget(mPlayButton); layout()->addWidget(mJumpToEndButton); @@ -138,6 +153,12 @@ void TimeControls::initUI() updateUI(); } +void TimeControls::updateTimecode() +{ + updateTimecodeLabel(mEditor->currentFrame()); + updateTimecodeToolTip(mTimecodeControls.kind); +} + void TimeControls::updateUI() { PlaybackManager* playback = mEditor->playback(); @@ -155,6 +176,8 @@ void TimeControls::updateUI() QSignalBlocker b4(mLoopButton); mLoopButton->setChecked(playback->isLooping()); + + updateTimecode(); } void TimeControls::setEditor(Editor* editor) @@ -163,82 +186,57 @@ void TimeControls::setEditor(Editor* editor) mEditor = editor; } -void TimeControls::setupTimeCodeMenu() -{ - QMenu* timeSelectMenu = new QMenu(tr("Display timecode", "Timeline menu for choose a timecode"), this); - - mTimeCode.timecodeButton = new QToolButton(this); - mTimeCode.timecodeButton->setIconSize(QSize(22,22)); - mTimeCode.timecodeButton->setIcon(QIcon(":/icons/themes/playful/controls/control-timecode.svg")); - - timeSelectMenu->addAction(mTimeCode.hideAction = new QAction(tr("Hide"), this)); - timeSelectMenu->addSeparator(); - timeSelectMenu->addAction(mTimeCode.framesAction = new QAction(tr("Frames"), this)); - timeSelectMenu->addSeparator(); - timeSelectMenu->addAction(mTimeCode.smpteAction = new QAction(tr("SMPTE Timecode"), this)); - timeSelectMenu->addAction(mTimeCode.sffAction = new QAction(tr("SFF Timecode"), this)); - mTimeCode.timecodeButton->setMenu(timeSelectMenu); - mTimeCode.timecodeButton->setPopupMode(QToolButton::InstantPopup); - mTimeCode.timecodeButton->setStyleSheet("::menu-indicator{ image: none; }"); - mTimeCode.timecodeKind = timecodeKindFromPreference(); - mTimeCode.timecodeLabel = new QLabel(this); - QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); - // The default mono font is smaller, so we restore the font here - font.setPointSize(mTimeCode.timecodeLabel->font().pointSize()); - mTimeCode.timecodeLabel->setFont(font); - mTimeCode.timecodeLabel->setContentsMargins(2, 0, 0, 0); - mTimeCode.timecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); - mTimeCode.timecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - updateTimecodeLabel(mEditor->currentFrame()); - updateTimecodeToolTip(mTimeCode.timecodeKind); -} - void TimeControls::updateTimecodeToolTip(TimecodeKind kind) { switch (kind) { - case TimecodeKind::NOTEXT: - mTimeCode.timecodeLabel->setToolTip(""); - break; - case TimecodeKind::FRAMES: - mTimeCode.timecodeLabel->setToolTip(tr("Actual frame number")); - break; case TimecodeKind::SMPTE: - mTimeCode.timecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); + mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); break; case TimecodeKind::SFF: - mTimeCode.timecodeLabel->setToolTip(tr("Timecode format S:FF")); + mTimecodeLabel->setToolTip(tr("Timecode format S:FF")); break; default: - mTimeCode.timecodeLabel->setToolTip(""); + mTimecodeLabel->setToolTip(""); } } -TimecodeKind TimeControls::timecodeKindFromPreference() const +void TimeControls::updateTimecodeLabel(int frame) { - int timecodePreference = mEditor->preference()->getInt(SETTING::TIMECODE_TEXT); - - switch (timecodePreference) - { - case 0: - return TimecodeKind::NOTEXT; - case 1: - return TimecodeKind::FRAMES; - case 2: - return TimecodeKind::SMPTE; - case 3: - return TimecodeKind::SFF; + mTimecodeLabel->setVisible(mTimecodeControls.enabled); + + if (mTimecodeControls.enabled) { + const bool showFrames = mTimecodeControls.showFrames; + const QString frameSuffix = showFrames + ? QString(" | %1").arg(QString::number(frame).rightJustified(4, '0')) + : QString(); + + QString timecode; + switch (mTimecodeControls.kind) + { + case TimecodeKind::SMPTE: { + timecode = QString("%1:%2:%3") + .arg(frame / (60 * mFps) % 60, 2, 10, QLatin1Char('0')) + .arg(frame / mFps % 60, 2, 10, QLatin1Char('0')) + .arg(frame % mFps, 2, 10, QLatin1Char('0')); + mTimecodeLabel->setText(timecode + frameSuffix); + break; + } + case TimecodeKind::SFF: { + timecode = QString("%1:%2") + .arg(frame / mFps) + .arg(frame % mFps, 2, 10, QLatin1Char('0')); + mTimecodeLabel->setText(timecode + frameSuffix); + break; + } default: - return TimecodeKind::NOTEXT; + timecode = showFrames ? QString::number(frame).rightJustified(4, '0') : QString(); + mTimecodeLabel->setText(timecode); + break; + } } } -int TimeControls::timecodeKindToInt(TimecodeKind kind) const -{ - return static_cast(kind); -} - void TimeControls::setFps(int value) { QSignalBlocker blocker(mFpsBox); @@ -285,17 +283,20 @@ void TimeControls::makeConnections() connect(mFpsBox, spinBoxValueChanged, this, &TimeControls::setFps); connect(mEditor, &Editor::fpsChanged, this, &TimeControls::setFps); - connect(mTimeCode.hideAction, &QAction::triggered, this, [this]() { - setTimecode(TimecodeKind::NOTEXT); - }); - connect(mTimeCode.framesAction, &QAction::triggered, this, [this]() { - setTimecode(TimecodeKind::FRAMES); - }); - connect(mTimeCode.smpteAction, &QAction::triggered, this, [this]() { - setTimecode(TimecodeKind::SMPTE); - }); - connect(mTimeCode.sffAction, &QAction::triggered, this, [this]() { - setTimecode(TimecodeKind::SFF); + + connect(mTimecodeButton, &QToolButton::clicked, this, &TimeControls::showTimecodePanel); +} + +void TimeControls::showTimecodePanel() +{ + mTimeCodeWidget = new TimeCodeControlWidget(&mTimecodeControls, this); + mTimeCodeWidget->setAttribute(Qt::WA_DeleteOnClose); + mTimeCodeWidget->setPalette(palette()); + mTimeCodeWidget->show(); + mTimeCodeWidget->move(mapToGlobal(pos())); + + connect(mTimeCodeWidget, &TimeCodeControlWidget::timecodeUpdated, this, [this] { + updateTimecode(); }); } @@ -378,15 +379,6 @@ void TimeControls::updateSoundScrubIcon(bool soundScrubEnabled) mEditor->preference()->set(SETTING::SOUND_SCRUB_ACTIVE, soundScrubEnabled); } -void TimeControls::setTimecode(TimecodeKind kind) -{ - QSettings settings(PENCIL2D, PENCIL2D); - settings.setValue(SETTING_TIMECODE_TEXT, timecodeKindToInt(kind)); - mTimeCode.timecodeKind = kind; - updateTimecodeLabel(mEditor->currentFrame()); - updateTimecodeToolTip(kind); -} - void TimeControls::onFpsEditingFinished() { mFpsBox->clearFocus(); @@ -394,32 +386,6 @@ void TimeControls::onFpsEditingFinished() mFps = mFpsBox->value(); } -void TimeControls::updateTimecodeLabel(int frame) -{ - mTimeCode.timecodeLabel->setVisible(true); - - switch (mTimeCode.timecodeKind) - { - case TimecodeKind::SMPTE: - mTimeCode.timecodeLabel->setText(QString("%1:%2:%3") - .arg(frame / (60 * mFps) % 60, 2, 10, QLatin1Char('0')) - .arg(frame / mFps % 60, 2, 10, QLatin1Char('0')) - .arg(frame % mFps, 2, 10, QLatin1Char('0'))); - break; - case TimecodeKind::SFF: - mTimeCode.timecodeLabel->setText(QString("%1:%2") - .arg(frame / mFps) - .arg(frame % mFps, 2, 10, QLatin1Char('0'))); - break; - case TimecodeKind::FRAMES: - mTimeCode.timecodeLabel->setText(QString::number(frame).rightJustified(4, '0')); - break; - case TimecodeKind::NOTEXT: - default: - mTimeCode.timecodeLabel->setVisible(false); - break; - } -} void TimeControls::updateLength(int frameLength) { diff --git a/app/src/timecontrols.h b/app/src/timecontrols.h index 1ed88a5332..17bbbe332f 100644 --- a/app/src/timecontrols.h +++ b/app/src/timecontrols.h @@ -27,6 +27,8 @@ GNU General Public License for more details. #include "pencildef.h" +#include "timecodecontrolwidget.h" + class Editor; class PreferenceManager; class TimeLine; @@ -35,17 +37,6 @@ class TimeControls : public QWidget { Q_OBJECT - struct TimeCode { - QToolButton* timecodeButton = nullptr; - QLabel* timecodeLabel = nullptr; - QAction* hideAction = nullptr; - QAction* framesAction = nullptr; - QAction* smpteAction = nullptr; - QAction* sffAction = nullptr; - - TimecodeKind timecodeKind = TimecodeKind::NOTEXT; - }; - public: TimeControls(TimeLine* parent = nullptr); void initUI(); @@ -73,6 +64,12 @@ public slots: void updateTimecodeLabel(int frame); private: + void showTimecodePanel(); + void updateTimecode(); + void updateTimecodeToolTip(TimecodeKind kind); + + void showFramesInTimecode(bool shown); + void showTimecode(bool shown); void makeConnections(); void setupTimeCodeMenu(); @@ -85,15 +82,6 @@ public slots: void loopEndValueChanged(int); void updateSoundScrubIcon(bool soundScrubEnabled); - void setTimecode(TimecodeKind kind); - int timecodeKindToInt(TimecodeKind kind) const; - TimecodeKind timecodeKindFromPreference() const; - void updateTimecodeToolTip(TimecodeKind kind); - void noTimecodeText(); - void onlyFramesText(); - void sffText(); - void smpteText(); - private: QToolButton* mPlayButton = nullptr; QToolButton* mJumpToEndButton = nullptr; @@ -106,7 +94,11 @@ public slots: QSpinBox* mLoopStartSpinBox = nullptr; QSpinBox* mLoopEndSpinBox = nullptr; - TimeCode mTimeCode; + QToolButton* mTimecodeButton = nullptr; + QLabel* mTimecodeLabel = nullptr; + + TimeCodeControls mTimecodeControls; + TimeCodeControlWidget* mTimeCodeWidget = nullptr; QIcon mStartIcon; QIcon mStopIcon; diff --git a/core_lib/src/managers/preferencemanager.cpp b/core_lib/src/managers/preferencemanager.cpp index 8cb247aa63..08bc9b2413 100644 --- a/core_lib/src/managers/preferencemanager.cpp +++ b/core_lib/src/managers/preferencemanager.cpp @@ -98,7 +98,9 @@ void PreferenceManager::loadPrefs() set(SETTING::FPS, settings.value(SETTING_FPS, 12).toInt()); set(SETTING::FIELD_W, settings.value(SETTING_FIELD_W, 800).toInt()); set(SETTING::FIELD_H, settings.value(SETTING_FIELD_H, 600).toInt()); - set(SETTING::TIMECODE_TEXT, settings.value(SETTING_TIMECODE_TEXT, 1).toInt()); + set(SETTING::TIMECODE_ON, settings.value(SETTING_TIMECODE_ON, true).toBool()); + set(SETTING::TIMECODE_FRAMES_ON, settings.value(SETTING_TIMECODE_FRAMES_ON, true).toBool()); + set(SETTING::TIMECODE_KIND, settings.value(SETTING_TIMECODE_KIND, static_cast(TimecodeKind::SMPTE)).toInt()); // Files set(SETTING::AUTO_SAVE, settings.value(SETTING_AUTO_SAVE, false).toBool()); @@ -298,8 +300,8 @@ void PreferenceManager::set(SETTING option, int value) case SETTING::GRID_SIZE_W: settings.setValue(SETTING_GRID_SIZE_W, value); break; - case SETTING::TIMECODE_TEXT: - settings.setValue(SETTING_TIMECODE_TEXT, value); + case SETTING::TIMECODE_KIND: + settings.setValue(SETTING_TIMECODE_KIND, value); break; case SETTING::GRID_SIZE_H: settings.setValue(SETTING_GRID_SIZE_H, value); @@ -471,6 +473,12 @@ void PreferenceManager::set(SETTING option, bool value) case SETTING::NEW_UNDO_REDO_SYSTEM_ON: settings.setValue(SETTING_NEW_UNDO_REDO_ON, value); break; + case SETTING::TIMECODE_ON: + settings.setValue(SETTING_TIMECODE_ON, value); + break; + case SETTING::TIMECODE_FRAMES_ON: + settings.setValue(SETTING_TIMECODE_FRAMES_ON, value); + break; default: Q_ASSERT(false); break; diff --git a/core_lib/src/util/pencildef.h b/core_lib/src/util/pencildef.h index 1f8816bb60..9e907625d1 100644 --- a/core_lib/src/util/pencildef.h +++ b/core_lib/src/util/pencildef.h @@ -74,8 +74,7 @@ enum StabilizationLevel enum class TimecodeKind { - NOTEXT, - FRAMES, // FF + NONE, SMPTE, // HH:MM:SS:FF SFF // S:FF }; @@ -288,7 +287,12 @@ const static int MaxFramesBound = 9999; #define SETTING_ACTION_SAFE_ON "ActionSafeOn" #define SETTING_ACTION_SAFE "ActionSafe" #define SETTING_OVERLAY_SAFE_HELPER_TEXT_ON "OverlaySafeHelperTextOn" -#define SETTING_TIMECODE_TEXT "TimecodeText" + +#define SETTING_TIMECODE_TEXT "TimecodeText" + +#define SETTING_TIMECODE_ON "TimecodeEnabled" +#define SETTING_TIMECODE_KIND "TimecodeKind" +#define SETTING_TIMECODE_FRAMES_ON "TimecodeFramesEnabled" #define SETTING_ONION_MAX_OPACITY "OnionMaxOpacity" #define SETTING_ONION_MIN_OPACITY "OnionMinOpacity" diff --git a/core_lib/src/util/preferencesdef.h b/core_lib/src/util/preferencesdef.h index 66683c2e5a..d60177b56d 100644 --- a/core_lib/src/util/preferencesdef.h +++ b/core_lib/src/util/preferencesdef.h @@ -73,7 +73,9 @@ enum class SETTING OVERLAY_SAFE_HELPER_TEXT_ON, ACTION_SAFE_ON, ACTION_SAFE, - TIMECODE_TEXT, + TIMECODE_ON, + TIMECODE_KIND, + TIMECODE_FRAMES_ON, TITLE_SAFE_ON, TITLE_SAFE, NEW_UNDO_REDO_SYSTEM_ON, From 68251713df9af6614738c78ddff48825a82a5292 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 18:36:18 +0200 Subject: [PATCH 06/10] TimeControls: simplify time code tooltip --- app/src/timecodecontrolwidget.cpp | 1 + app/src/timecontrols.cpp | 17 +---------------- app/src/timecontrols.h | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/app/src/timecodecontrolwidget.cpp b/app/src/timecodecontrolwidget.cpp index 957e9ebc57..a513c0b56a 100644 --- a/app/src/timecodecontrolwidget.cpp +++ b/app/src/timecodecontrolwidget.cpp @@ -53,6 +53,7 @@ TimeCodeControlWidget::TimeCodeControlWidget(TimeCodeControls* controls, QWidget QComboBox* timecodeComboBox = new QComboBox(this); QVBoxLayout* groupBoxVBoxLayout = new QVBoxLayout(); + groupBoxVBoxLayout->setContentsMargins(4,4,4,4); optionsGroupBox->setLayout(groupBoxVBoxLayout); groupBoxVBoxLayout->addWidget(showFramesCheckBox); diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index 1cca56143c..1d4ac8882e 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -70,6 +70,7 @@ void TimeControls::initUI() mTimecodeLabel->setContentsMargins(2, 0, 0, 0); mTimecodeLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); mTimecodeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + mTimecodeLabel->setToolTip(tr("Shows the current time code")); mTimecodeControls.enabled = mEditor->preference()->isOn(SETTING::TIMECODE_ON); mTimecodeControls.kind = mTimecodeControls.timecodeKindFromInt(mEditor->preference()->getInt(SETTING::TIMECODE_KIND)); @@ -156,7 +157,6 @@ void TimeControls::initUI() void TimeControls::updateTimecode() { updateTimecodeLabel(mEditor->currentFrame()); - updateTimecodeToolTip(mTimecodeControls.kind); } void TimeControls::updateUI() @@ -186,21 +186,6 @@ void TimeControls::setEditor(Editor* editor) mEditor = editor; } -void TimeControls::updateTimecodeToolTip(TimecodeKind kind) -{ - switch (kind) - { - case TimecodeKind::SMPTE: - mTimecodeLabel->setToolTip(tr("Timecode format MM:SS:FF")); - break; - case TimecodeKind::SFF: - mTimecodeLabel->setToolTip(tr("Timecode format S:FF")); - break; - default: - mTimecodeLabel->setToolTip(""); - } -} - void TimeControls::updateTimecodeLabel(int frame) { mTimecodeLabel->setVisible(mTimecodeControls.enabled); diff --git a/app/src/timecontrols.h b/app/src/timecontrols.h index 17bbbe332f..a612587e3d 100644 --- a/app/src/timecontrols.h +++ b/app/src/timecontrols.h @@ -66,7 +66,6 @@ public slots: private: void showTimecodePanel(); void updateTimecode(); - void updateTimecodeToolTip(TimecodeKind kind); void showFramesInTimecode(bool shown); void showTimecode(bool shown); From 4c314d7ba73b87b45e887e5fe75b3dee8a4fceed Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 18:53:50 +0200 Subject: [PATCH 07/10] Fix Qt5 compiler error --- app/src/timecodecontrolwidget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/timecodecontrolwidget.cpp b/app/src/timecodecontrolwidget.cpp index a513c0b56a..b0cd67ecbb 100644 --- a/app/src/timecodecontrolwidget.cpp +++ b/app/src/timecodecontrolwidget.cpp @@ -76,7 +76,8 @@ TimeCodeControlWidget::TimeCodeControlWidget(TimeCodeControls* controls, QWidget showFrames(toggled); }); - connect(timecodeComboBox, &QComboBox::currentIndexChanged, this, [this, controls](int index) { + auto comboBoxValueChanged = static_cast(&QComboBox::currentIndexChanged); + connect(timecodeComboBox, comboBoxValueChanged, this, [this, controls](int index) { auto timecodeKind = controls->timecodeKindFromInt(index); setTimecodeTimerKind(timecodeKind); From 9d886f96908b2ea52f7a29be002247cb66797a29 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 19:01:50 +0200 Subject: [PATCH 08/10] Apply SonarQube suggestions --- app/src/timecodecontrolwidget.cpp | 14 +++++++------- app/src/timecodecontrolwidget.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/timecodecontrolwidget.cpp b/app/src/timecodecontrolwidget.cpp index b0cd67ecbb..1d49f8b9e8 100644 --- a/app/src/timecodecontrolwidget.cpp +++ b/app/src/timecodecontrolwidget.cpp @@ -30,29 +30,29 @@ GNU General Public License for more details. TimeCodeControlWidget::TimeCodeControlWidget(TimeCodeControls* controls, QWidget* parent) : QWidget(parent, Qt::Popup), mControls(controls) { - QVBoxLayout* vboxLayout = new QVBoxLayout(); + auto vboxLayout = new QVBoxLayout(); vboxLayout->setContentsMargins(4,4,4,4); setLayout(vboxLayout); - QLabel* titlelabel = new QLabel(tr("Timecode controls")); + auto titlelabel = new QLabel(tr("Timecode controls")); vboxLayout->addWidget(titlelabel); - QCheckBox* showTimeCodeCheckBox = new QCheckBox(this); + auto showTimeCodeCheckBox = new QCheckBox(this); showTimeCodeCheckBox->setText(tr("Show")); showTimeCodeCheckBox->setChecked(controls->enabled); - QGroupBox* optionsGroupBox = new QGroupBox(this); + auto optionsGroupBox = new QGroupBox(this); optionsGroupBox->setTitle(tr("Options")); optionsGroupBox->setEnabled(controls->enabled); - QCheckBox* showFramesCheckBox = new QCheckBox(this); + auto showFramesCheckBox = new QCheckBox(this); showFramesCheckBox->setText(tr("Frames")); showFramesCheckBox->setChecked(controls->showFrames); - QComboBox* timecodeComboBox = new QComboBox(this); + auto timecodeComboBox = new QComboBox(this); - QVBoxLayout* groupBoxVBoxLayout = new QVBoxLayout(); + auto groupBoxVBoxLayout = new QVBoxLayout(); groupBoxVBoxLayout->setContentsMargins(4,4,4,4); optionsGroupBox->setLayout(groupBoxVBoxLayout); diff --git a/app/src/timecodecontrolwidget.h b/app/src/timecodecontrolwidget.h index de639d4cd3..670f845e67 100644 --- a/app/src/timecodecontrolwidget.h +++ b/app/src/timecodecontrolwidget.h @@ -47,7 +47,7 @@ class TimeCodeControlWidget : public QWidget Q_OBJECT public: - TimeCodeControlWidget(TimeCodeControls* controls, QWidget* parent = nullptr); + explicit TimeCodeControlWidget(TimeCodeControls* controls, QWidget* parent = nullptr); signals: void timecodeUpdated(); From f1c5ac55a263a7ce69cc965e5d34e2f9b01ae743 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 19:10:52 +0200 Subject: [PATCH 09/10] Show time code controls next to the cursor --- app/src/timecontrols.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/timecontrols.cpp b/app/src/timecontrols.cpp index 1d4ac8882e..a38bff1eef 100644 --- a/app/src/timecontrols.cpp +++ b/app/src/timecontrols.cpp @@ -276,9 +276,8 @@ void TimeControls::showTimecodePanel() { mTimeCodeWidget = new TimeCodeControlWidget(&mTimecodeControls, this); mTimeCodeWidget->setAttribute(Qt::WA_DeleteOnClose); - mTimeCodeWidget->setPalette(palette()); mTimeCodeWidget->show(); - mTimeCodeWidget->move(mapToGlobal(pos())); + mTimeCodeWidget->move(QCursor::pos()); connect(mTimeCodeWidget, &TimeCodeControlWidget::timecodeUpdated, this, [this] { updateTimecode(); From 00d25bca1aa5a9fd99d864953463d81f8c9ac348 Mon Sep 17 00:00:00 2001 From: MrStevns Date: Sun, 19 Apr 2026 19:15:41 +0200 Subject: [PATCH 10/10] Cleanup --- app/src/basedockwidget.cpp | 13 ++----------- app/src/basedockwidget.h | 5 +---- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/app/src/basedockwidget.cpp b/app/src/basedockwidget.cpp index 83a1de0068..057b0b6b16 100644 --- a/app/src/basedockwidget.cpp +++ b/app/src/basedockwidget.cpp @@ -17,7 +17,6 @@ GNU General Public License for more details. #include #include -#include #include "basedockwidget.h" #include "platformhandler.h" @@ -83,21 +82,13 @@ void BaseDockWidget::setTitle(const QString& title) mTitleBarWidget->setTitle(title); } -void BaseDockWidget::setWidgetInTitleBarArea(QWidget* toolbar) +void BaseDockWidget::setWidgetInTitleBarArea(QWidget* widget) { if (mTitleBarWidget) { - mTitleBarWidget->setChildWidget(toolbar); + mTitleBarWidget->setChildWidget(widget); } } -QWidget* BaseDockWidget::titleBarWidget() const -{ - if (mTitleBarWidget) { - return mTitleBarWidget; - } - return nullptr; -} - void BaseDockWidget::resizeEvent(QResizeEvent *event) { QDockWidget::resizeEvent(event); diff --git a/app/src/basedockwidget.h b/app/src/basedockwidget.h index 6557831e5a..a509c3e78e 100644 --- a/app/src/basedockwidget.h +++ b/app/src/basedockwidget.h @@ -22,7 +22,6 @@ GNU General Public License for more details. class Editor; class TitleBarWidget; -class QToolBar; class BaseDockWidget : public QDockWidget { @@ -45,9 +44,7 @@ class BaseDockWidget : public QDockWidget /// Sets a widget in addition to the normal titlebar buttons. /// If the titlebar already has one widget, the previous one will be deleted. - void setWidgetInTitleBarArea(QWidget* toolbar); - - QWidget* titleBarWidget() const; + void setWidgetInTitleBarArea(QWidget* widget); protected: void resizeEvent(QResizeEvent* event) override;