diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp index a90b9e023..eedf4d0f4 100644 --- a/app/common/timecodefunctions.cpp +++ b/app/common/timecodefunctions.cpp @@ -259,7 +259,7 @@ QString Timecode::time_to_timecode(const rational &time, const rational &timebas bool Timecode::TimebaseIsDropFrame(const rational &timebase) { - return (timebase.numerator() == 1001); + return (timebase.numerator() != 1); } QString Timecode::TimeToString(int64_t ms) diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index b1a19fe5a..ff82a6b2c 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -28,6 +28,7 @@ add_subdirectory(preferences) add_subdirectory(progress) add_subdirectory(rendercancel) add_subdirectory(sequence) +add_subdirectory(speedduration) add_subdirectory(task) add_subdirectory(text) diff --git a/app/dialog/export/codec/imagesection.cpp b/app/dialog/export/codec/imagesection.cpp index caf7a0ef0..cf38fd698 100644 --- a/app/dialog/export/codec/imagesection.cpp +++ b/app/dialog/export/codec/imagesection.cpp @@ -43,9 +43,10 @@ ImageSection::ImageSection(QWidget* parent) : layout->addWidget(new QLabel(tr("Frame to Export:")), row, 0); - frame_slider_ = new TimeSlider(); + frame_slider_ = new RationalSlider(); frame_slider_->SetMinimum(0); frame_slider_->SetValue(0); + frame_slider_->SetDisplayType(RationalSlider::kTime); layout->addWidget(frame_slider_, row, 1); } diff --git a/app/dialog/export/codec/imagesection.h b/app/dialog/export/codec/imagesection.h index 933678e17..6e116b466 100644 --- a/app/dialog/export/codec/imagesection.h +++ b/app/dialog/export/codec/imagesection.h @@ -24,7 +24,7 @@ #include #include "codecsection.h" -#include "widget/slider/timeslider.h" +#include "widget/slider/rationalslider.h" namespace olive { @@ -44,12 +44,12 @@ public: frame_slider_->SetTimebase(r); } - int64_t GetTimestamp() const + rational GetTime() const { return frame_slider_->GetValue(); } - void SetTimestamp(int64_t t) + void SetTime(const rational &t) { frame_slider_->SetValue(t); } @@ -57,7 +57,7 @@ public: private: QCheckBox* image_sequence_checkbox_; - TimeSlider* frame_slider_; + RationalSlider* frame_slider_; private slots: void ImageSequenceCheckBoxToggled(bool e); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 16e0bb8ae..acf3de5ce 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -167,7 +167,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) : preview_layout->addWidget(new QLabel(tr("Preview"))); preview_viewer_ = new ViewerWidget(); preview_viewer_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - connect(preview_viewer_, &ViewerWidget::TimeChanged, video_tab_, &ExportVideoTab::SetTimestamp); + connect(preview_viewer_, &ViewerWidget::TimeChanged, video_tab_, &ExportVideoTab::SetTime); preview_layout->addWidget(preview_viewer_); splitter->addWidget(preview_area); @@ -527,7 +527,7 @@ ExportParams ExportDialog::GenerateParams() const if (ExportCodec::IsCodecAStillImage(video_tab_->GetSelectedCodec()) && !video_tab_->IsImageSequenceSet()) { // Exporting as image without exporting image sequence, only export one frame - rational export_time = Timecode::timestamp_to_time(video_tab_->GetStillImageTime(), GetSelectedTimebase()); + rational export_time = video_tab_->GetStillImageTime(); params.set_custom_range(TimeRange(export_time, export_time)); } else if (range_combobox_->currentIndex() == kRangeInToOut) { // Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index 9c5b98b57..7aa6844fe 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -252,12 +252,12 @@ void ExportVideoTab::VideoCodecChanged() qDebug() << "Set default pix fmt" << pix_fmt_; } -void ExportVideoTab::SetTimestamp(int64_t timestamp) +void ExportVideoTab::SetTime(const rational &time) { for (int i=0; icount(); i++) { ImageSection* img = dynamic_cast(codec_stack_->widget(i)); if (img) { - img->SetTimestamp(timestamp); + img->SetTime(time); } } } diff --git a/app/dialog/export/exportvideotab.h b/app/dialog/export/exportvideotab.h index 45b2d50bb..ce74d5a0a 100644 --- a/app/dialog/export/exportvideotab.h +++ b/app/dialog/export/exportvideotab.h @@ -45,9 +45,9 @@ public: bool IsImageSequenceSet() const; - int64_t GetStillImageTime() const + rational GetStillImageTime() const { - return image_section_->GetTimestamp(); + return image_section_->GetTime(); } ExportCodec::Codec GetSelectedCodec() const @@ -138,7 +138,7 @@ public: public slots: void VideoCodecChanged(); - void SetTimestamp(int64_t timestamp); + void SetTime(const rational &time); signals: void ColorSpaceChanged(const QString& colorspace); diff --git a/app/dialog/keyframeproperties/keyframeproperties.cpp b/app/dialog/keyframeproperties/keyframeproperties.cpp index 6a518d52f..9637c486f 100644 --- a/app/dialog/keyframeproperties/keyframeproperties.cpp +++ b/app/dialog/keyframeproperties/keyframeproperties.cpp @@ -43,7 +43,8 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector layout->addWidget(new QLabel("Time:"), row, 0); - time_slider_ = new TimeSlider(); + time_slider_ = new RationalSlider(); + time_slider_->SetDisplayType(RationalSlider::kTime); time_slider_->SetTimebase(timebase_); layout->addWidget(time_slider_, row, 1); @@ -146,7 +147,7 @@ KeyframePropertiesDialog::KeyframePropertiesDialog(const QVector } if (all_same_time) { - time_slider_->SetValue(Timecode::time_to_timestamp(keys_.first()->time(), timebase_)); + time_slider_->SetValue(keys_.first()->time()); } else { time_slider_->SetTristate(); } @@ -196,7 +197,7 @@ void KeyframePropertiesDialog::accept() { MultiUndoCommand* command = new MultiUndoCommand(); - rational new_time = Timecode::timestamp_to_time(time_slider_->GetValue(), timebase_); + rational new_time = time_slider_->GetValue(); int new_type = type_select_->currentData().toInt(); foreach (NodeKeyframe* key, keys_) { diff --git a/app/dialog/keyframeproperties/keyframeproperties.h b/app/dialog/keyframeproperties/keyframeproperties.h index 708d3b988..835845943 100644 --- a/app/dialog/keyframeproperties/keyframeproperties.h +++ b/app/dialog/keyframeproperties/keyframeproperties.h @@ -27,7 +27,7 @@ #include "node/keyframe.h" #include "widget/slider/floatslider.h" -#include "widget/slider/timeslider.h" +#include "widget/slider/rationalslider.h" namespace olive { @@ -47,7 +47,7 @@ private: rational timebase_; - TimeSlider* time_slider_; + RationalSlider* time_slider_; QComboBox* type_select_; diff --git a/app/dialog/speedduration/CMakeLists.txt b/app/dialog/speedduration/CMakeLists.txt new file mode 100644 index 000000000..42471bd32 --- /dev/null +++ b/app/dialog/speedduration/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# 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 3 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. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/speedduration/speeddurationdialog.cpp + dialog/speedduration/speeddurationdialog.h + PARENT_SCOPE +) diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp new file mode 100644 index 000000000..f5308e8c3 --- /dev/null +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -0,0 +1,205 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + 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 3 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. If not, see . + +***/ + +#include "speeddurationdialog.h" + +#include +#include +#include + +#include "core.h" +#include "widget/nodeparamview/nodeparamviewundo.h" +#include "widget/timelinewidget/undo/timelineundopointer.h" + +namespace olive { + +#define super QDialog + +SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, const rational &timebase, QWidget *parent) : + super(parent), + clips_(clips), + timebase_(timebase) +{ + setWindowTitle(tr("Speed/Duration")); + + QGridLayout *layout = new QGridLayout(this); + + int row = 0; + + layout->addWidget(new QLabel(tr("Speed:")), row, 0); + + speed_slider_ = new FloatSlider(); + speed_slider_->SetDisplayType(FloatSlider::kPercentage); + connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged); + layout->addWidget(speed_slider_, row, 1); + + row++; + + layout->addWidget(new QLabel(tr("Duration:")), row, 0); + + dur_slider_ = new RationalSlider(); + dur_slider_->SetTimebase(timebase); + dur_slider_->SetDisplayType(RationalSlider::kTime); + connect(dur_slider_, &RationalSlider::ValueChanged, this, &SpeedDurationDialog::DurationChanged); + layout->addWidget(dur_slider_, row, 1); + + row++; + + link_box_ = new QCheckBox(tr("Link Speed and Duration")); + link_box_->setChecked(true); + layout->addWidget(link_box_, row, 0, 1, 2); + + row++; + + ripple_box_ = new QCheckBox(tr("Ripple Trailing Clips")); + layout->addWidget(ripple_box_, row, 0, 1, 2); + + row++; + + QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + btns->setCenterButtons(true); + connect(btns, &QDialogButtonBox::accepted, this, &SpeedDurationDialog::accept); + connect(btns, &QDialogButtonBox::rejected, this, &SpeedDurationDialog::reject); + layout->addWidget(btns, row, 0, 1, 2); + + // Determine which speed value to use + start_speed_ = clips.first()->speed(); + start_duration_ = clips.first()->length(); + for (int i=1; ispeed())) { + // Speed differs per clip + start_speed_ = qSNaN(); + } + + if (start_duration_ != -1 && clips.at(i)->length() != start_duration_) { + start_duration_ = -1; + } + } + + if (qIsNaN(start_speed_)) { + speed_slider_->SetTristate(); + } else { + speed_slider_->SetValue(start_speed_); + } + + if (start_duration_ == -1) { + dur_slider_->SetTristate(); + } else { + dur_slider_->SetValue(start_duration_); + } +} + +void SpeedDurationDialog::accept() +{ + // We haven't implemented rippling yet, so warn the user + if (ripple_box_->isChecked()) { + // FIXME: Stub + if (QMessageBox::information(this, QString(), tr("Rippling is a stub and will not do anything. Do you wish to continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + return; + } + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + // Set speed values + if (speed_slider_->IsTristate()) { + if (link_box_->isChecked() && !dur_slider_->IsTristate()) { + // Automatically determine speed from duration + foreach (ClipBlock *c, clips_) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), GetSpeedAdjustment(c->speed(), c->length(), dur_slider_->GetValue()))); + } + } + } else { + // Set speeds to value of slider + foreach (ClipBlock *c, clips_) { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(NodeInput(c, ClipBlock::kSpeedInput)), speed_slider_->GetValue())); + } + } + + // Set duration values + foreach (ClipBlock *c, clips_) { + rational proposed_length = c->length(); + + if (dur_slider_->IsTristate()) { + if (link_box_->isChecked() && !speed_slider_->IsTristate()) { + proposed_length = GetLengthAdjustment(c->length(), c->speed(), speed_slider_->GetValue(), timebase_); + } + } else { + proposed_length = dur_slider_->GetValue(); + } + + if (proposed_length != c->length()) { + // Clip length should ideally change, but check if there's "room" to do so + if (proposed_length > c->length() && c->next()) { + if (GapBlock *gap = dynamic_cast(c->next())) { + proposed_length = qMin(proposed_length, gap->out() - c->in()); + } else { + proposed_length = c->length(); + } + } + + if (proposed_length != c->length()) { + command->add_child(new BlockTrimCommand(c->track(), c, proposed_length, Timeline::kTrimOut)); + } + } + } + + Core::instance()->undo_stack()->push(command); + + super::accept(); +} + +rational SpeedDurationDialog::GetLengthAdjustment(const rational &original_length, double original_speed, double new_speed, const rational &timebase) +{ + return Timecode::snap_time_to_timebase(rational::fromDouble(original_length.toDouble() / new_speed * original_speed), timebase); +} + +double SpeedDurationDialog::GetSpeedAdjustment(double original_speed, const rational &original_length, const rational &new_length) +{ + return original_speed / new_length.toDouble() * original_length.toDouble(); +} + +void SpeedDurationDialog::SpeedChanged(double s) +{ + if (!link_box_->isChecked()) { + return; + } + + if (start_duration_ == -1) { + dur_slider_->SetTristate(); + } else { + dur_slider_->SetValue(GetLengthAdjustment(start_duration_, start_speed_, s, timebase_)); + } +} + +void SpeedDurationDialog::DurationChanged(const rational &r) +{ + if (!link_box_->isChecked()) { + return; + } + + if (qIsNaN(start_speed_)) { + speed_slider_->SetTristate(); + } else { + speed_slider_->SetValue(GetSpeedAdjustment(start_speed_, start_duration_, r)); + } +} + +} diff --git a/app/dialog/speedduration/speeddurationdialog.h b/app/dialog/speedduration/speeddurationdialog.h new file mode 100644 index 000000000..135f77434 --- /dev/null +++ b/app/dialog/speedduration/speeddurationdialog.h @@ -0,0 +1,76 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + 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 3 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. If not, see . + +***/ + +#ifndef SPEEDDURATIONDIALOG_H +#define SPEEDDURATIONDIALOG_H + +#include +#include + +#include "node/block/clip/clip.h" +#include "node/block/gap/gap.h" +#include "undo/undocommand.h" +#include "widget/slider/floatslider.h" +#include "widget/slider/rationalslider.h" + +namespace olive { + +class SpeedDurationDialog : public QDialog +{ + Q_OBJECT +public: + explicit SpeedDurationDialog(const QVector &clips, const rational &timebase, QWidget *parent = nullptr); + +public slots: + virtual void accept() override; + +signals: + +private: + static rational GetLengthAdjustment(const rational &original_length, double original_speed, double new_speed, const rational &timebase); + + static double GetSpeedAdjustment(double original_speed, const rational &original_length, const rational &new_length); + + QVector clips_; + + FloatSlider *speed_slider_; + + RationalSlider *dur_slider_; + + QCheckBox *link_box_; + + QCheckBox *ripple_box_; + + double start_speed_; + + rational start_duration_; + + rational timebase_; + +private slots: + void SpeedChanged(double s); + + void DurationChanged(const rational &r); + +}; + +} + +#endif // SPEEDDURATIONDIALOG_H diff --git a/app/node/node.cpp b/app/node/node.cpp index 13ad30cf1..d4296402c 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -2343,7 +2343,7 @@ void NodeSetPositionAsChildCommand::redo() } } - sub_command_->redo(); + sub_command_->redo_now(); } void NodeSetPositionToOffsetOfAnotherNodeCommand::redo() diff --git a/app/node/node.h b/app/node/node.h index 37109178d..22c02b528 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1406,7 +1406,7 @@ protected: virtual void undo() override { - sub_command_->undo(); + sub_command_->undo_now(); } private: diff --git a/app/node/project/folder/folder.cpp b/app/node/project/folder/folder.cpp index 91fda055f..0dde22fff 100644 --- a/app/node/project/folder/folder.cpp +++ b/app/node/project/folder/folder.cpp @@ -168,7 +168,7 @@ void Folder::RemoveElementCommand::redo() } if (subcommand_) { - subcommand_->redo(); + subcommand_->redo_now(); } } diff --git a/app/node/project/folder/folder.h b/app/node/project/folder/folder.h index 502dbe14f..c1436f1c7 100644 --- a/app/node/project/folder/folder.h +++ b/app/node/project/folder/folder.h @@ -145,7 +145,7 @@ public: virtual void undo() override { if (subcommand_) { - subcommand_->undo(); + subcommand_->undo_now(); } } diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index c897add29..44cc51281 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -74,9 +74,9 @@ void TimeBasedPanel::SetTimebase(const rational &timebase) widget_->SetTimebase(timebase); } -void TimeBasedPanel::SetTimestamp(const int64_t ×tamp) +void TimeBasedPanel::SetTime(const rational &time) { - widget_->SetTimestamp(timestamp); + widget_->SetTime(time); } void TimeBasedPanel::GoToPrevCut() diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 9559d1f9f..b85657e4d 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -101,10 +101,10 @@ public: public slots: void SetTimebase(const rational& timebase); - virtual void SetTimestamp(const int64_t& timestamp); + void SetTime(const rational &time); signals: - void TimeChanged(const int64_t& time); + void TimeChanged(const rational& time); void TimebaseChanged(const rational& timebase); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 903320c3e..50b6c7508 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -174,6 +174,16 @@ void TimelinePanel::NudgeRight() timeline_widget()->NudgeRight(); } +void TimelinePanel::MoveInToPlayhead() +{ + timeline_widget()->MoveInToPlayhead(); +} + +void TimelinePanel::MoveOutToPlayhead() +{ + timeline_widget()->MoveOutToPlayhead(); +} + void TimelinePanel::InsertFootageAtPlayhead(const QVector &footage) { timeline_widget()->InsertFootageAtPlayhead(footage); diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 63a83fcff..f27726d65 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -92,13 +92,22 @@ public: virtual void NudgeRight() override; + virtual void MoveInToPlayhead() override; + + virtual void MoveOutToPlayhead() override; + + void ShowSpeedDurationDialogForSelectedClips() + { + timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); + } + void InsertFootageAtPlayhead(const QVector &footage); void OverwriteFootageAtPlayhead(const QVector &footage); const QVector& GetSelectedBlocks() const { - return static_cast(GetTimeBasedWidget())->GetSelectedBlocks(); + return timeline_widget()->GetSelectedBlocks(); } protected: diff --git a/app/undo/undocommand.h b/app/undo/undocommand.h index f63ae745e..94d4d3e8f 100644 --- a/app/undo/undocommand.h +++ b/app/undo/undocommand.h @@ -82,9 +82,6 @@ class MultiUndoCommand : public UndoCommand public: MultiUndoCommand(); - virtual void redo() override; - virtual void undo() override; - virtual Project* GetRelevantProject() const override { return nullptr; @@ -105,6 +102,10 @@ public: return children_[i]; } +protected: + virtual void redo() override; + virtual void undo() override; + private: std::vector children_; diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 7b124934e..4de5787ef 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -59,7 +59,7 @@ CurveWidget::CurveWidget(QWidget *parent) : QHBoxLayout* top_controls = new QHBoxLayout(); key_control_ = new NodeParamViewKeyframeControl(false); - connect(key_control_, &NodeParamViewKeyframeControl::RequestSetTime, this, &CurveWidget::KeyControlRequestedTimeChanged); + connect(key_control_, &NodeParamViewKeyframeControl::RequestSetTime, this, &CurveWidget::SetTimeAndSignal); top_controls->addWidget(key_control_); top_controls->addStretch(); @@ -156,12 +156,12 @@ void CurveWidget::SetNodes(const QVector &nodes) nodes_ = nodes; } -void CurveWidget::TimeChangedEvent(const int64_t ×tamp) +void CurveWidget::TimeChangedEvent(const rational &time) { - TimeBasedWidget::TimeChangedEvent(timestamp); + TimeBasedWidget::TimeChangedEvent(time); - view_->SetTime(timestamp); - UpdateBridgeTime(timestamp); + view_->SetTime(time); + UpdateBridgeTime(time); } void CurveWidget::TimebaseChangedEvent(const rational &timebase) @@ -211,9 +211,8 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type) hold_button_->setChecked(type == NodeKeyframe::kHold); } -void CurveWidget::UpdateBridgeTime(const int64_t ×tamp) +void CurveWidget::UpdateBridgeTime(const rational &time) { - rational time = Timecode::timestamp_to_time(timestamp, view_->timebase()); key_control_->SetTime(time); } @@ -355,11 +354,6 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked) Core::instance()->undo_stack()->push(command); } -void CurveWidget::KeyControlRequestedTimeChanged(const rational &time) -{ - SetTimeAndSignal(Timecode::time_to_timestamp(time, view_->timebase())); -} - void CurveWidget::NodeEnabledChanged(Node* n, bool e) { ConnectNode(n, e); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index 5e8daf8f2..7b9e01021 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -61,7 +61,7 @@ public slots: void SetNodes(const QVector &nodes); protected: - virtual void TimeChangedEvent(const int64_t &) override; + virtual void TimeChangedEvent(const rational &) override; virtual void TimebaseChangedEvent(const rational &) override; virtual void ScaleChangedEvent(const double &) override; @@ -76,7 +76,7 @@ private: void SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type); - void UpdateBridgeTime(const int64_t& timestamp); + void UpdateBridgeTime(const rational &time); void ConnectNode(Node* node, bool connect); @@ -103,8 +103,6 @@ private slots: void KeyframeTypeButtonTriggered(bool checked); - void KeyControlRequestedTimeChanged(const rational& time); - void NodeEnabledChanged(Node* n, bool e); void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index f2a7f2494..7b0506a45 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -24,6 +24,7 @@ #include "common/timecodefunctions.h" #include "panel/panelmanager.h" #include "panel/timeline/timeline.h" +#include "window/mainwindow/mainwindow.h" namespace olive { @@ -45,6 +46,7 @@ MenuShared::MenuShared() edit_delete_item_ = Menu::CreateItem(this, "delete", this, &MenuShared::DeleteSelectedTriggered, "Del"); edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, &MenuShared::RippleDeleteTriggered, "Shift+Del"); edit_split_item_ = Menu::CreateItem(this, "split", this, &MenuShared::SplitAtPlayheadTriggered, "Ctrl+K"); + edit_speedduration_item_ = Menu::CreateItem(this, "speeddur", this, &MenuShared::SpeedDurationTriggered, "Ctrl+R"); // "In/Out" menu shared items inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", this, &MenuShared::SetInTriggered, "I"); @@ -134,6 +136,7 @@ void MenuShared::AddItemsForEditMenu(Menu *m, bool for_clips) if (for_clips) { m->addAction(edit_ripple_delete_item_); m->addAction(edit_split_item_); + m->addAction(edit_speedduration_item_); } } @@ -301,6 +304,15 @@ void MenuShared::ColorLabelTriggered(int color_index) PanelManager::instance()->CurrentlyFocused()->SetColorLabel(color_index); } +void MenuShared::SpeedDurationTriggered() +{ + TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); + + if (timeline) { + timeline->ShowSpeedDurationDialogForSelectedClips(); + } +} + void MenuShared::Retranslate() { // "New" menu shared items @@ -317,6 +329,7 @@ void MenuShared::Retranslate() edit_delete_item_->setText(tr("Delete")); edit_ripple_delete_item_->setText(tr("Ripple Delete")); edit_split_item_->setText(tr("Split")); + edit_speedduration_item_->setText(tr("Speed/Duration")); // "In/Out" menu shared items inout_set_in_item_->setText(tr("Set In Point")); diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index 34d2cecb4..cdc3dcdec 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -75,6 +75,7 @@ private: QAction* edit_delete_item_; QAction* edit_ripple_delete_item_; QAction* edit_split_item_; + QAction* edit_speedduration_item_; // "In/Out" menu shared items QAction* inout_set_in_item_; @@ -145,6 +146,8 @@ private slots: void ColorLabelTriggered(int color_index); + void SpeedDurationTriggered(); + }; } diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index b85fbc69d..a97c76b3a 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -92,7 +92,7 @@ NodeParamView::NodeParamView(QWidget *parent) : // Connect ruler and keyframe view together connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime); connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime); - connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTimestamp); + connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime); connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged); // Connect keyframe view scaling to this @@ -155,7 +155,7 @@ void NodeParamView::SelectNodes(const QVector &nodes) } if (items_.size() > original_node_count ) { - UpdateItemTime(GetTimestamp()); + UpdateItemTime(GetTime()); // Re-arrange keyframes QueueKeyframePositionUpdate(); @@ -220,16 +220,16 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase) item->SetTimebase(timebase); } - UpdateItemTime(GetTimestamp()); + UpdateItemTime(GetTime()); } -void NodeParamView::TimeChangedEvent(const int64_t ×tamp) +void NodeParamView::TimeChangedEvent(const rational &time) { - super::TimeChangedEvent(timestamp); + super::TimeChangedEvent(time); - keyframe_view_->SetTime(timestamp); + keyframe_view_->SetTime(time); - UpdateItemTime(timestamp); + UpdateItemTime(time); } void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n) @@ -252,10 +252,8 @@ void NodeParamView::DeleteSelected() keyframe_view_->DeleteSelected(); } -void NodeParamView::UpdateItemTime(const int64_t ×tamp) +void NodeParamView::UpdateItemTime(const rational &time) { - rational time = Timecode::timestamp_to_time(timestamp, timebase()); - foreach (NodeParamViewItem* item, items_) { item->SetTime(time); } @@ -306,7 +304,7 @@ void NodeParamView::AddNode(Node *n) connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe); connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe); - connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged); + connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal); connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode); connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder); @@ -358,11 +356,6 @@ void NodeParamView::RemoveNode(Node *n) } } -void NodeParamView::ItemRequestedTimeChanged(const rational &time) -{ - SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase())); -} - void NodeParamView::UpdateGlobalScrollBar() { int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height(); diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 3e8f7edc4..1718f445e 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -94,12 +94,12 @@ protected: virtual void ScaleChangedEvent(const double &) override; virtual void TimebaseChangedEvent(const rational&) override; - virtual void TimeChangedEvent(const int64_t &) override; + virtual void TimeChangedEvent(const rational &time) override; virtual void ConnectedNodeChangeEvent(ViewerOutput* n) override; private: - void UpdateItemTime(const int64_t ×tamp); + void UpdateItemTime(const rational &time); void QueueKeyframePositionUpdate(); @@ -132,8 +132,6 @@ private: Node* focused_node_; private slots: - void ItemRequestedTimeChanged(const rational& time); - void UpdateGlobalScrollBar(); void PinNode(bool pin); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index ad01f6ba0..519ae141f 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -27,6 +27,7 @@ #include "common/qtutils.h" #include "core.h" +#include "dialog/speedduration/speeddurationdialog.h" #include "node/project/sequence/sequence.h" #include "nodeparamviewundo.h" @@ -35,6 +36,7 @@ namespace olive { const int NodeParamViewItemBody::kKeyControlColumn = 10; const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1; const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1; +const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1; // 0 is for the array collapse button, 1 is for the main label, widgets start at 2 const int NodeParamViewItemBody::kWidgetStartColumn = 2; @@ -221,7 +223,8 @@ void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event) } NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) : - QWidget(parent) + QWidget(parent), + node_(node) { QGridLayout* root_layout = new QGridLayout(this); @@ -313,6 +316,14 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const connect(remove_element_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayRemoveClicked); } + } else if (dynamic_cast(node) && input == ClipBlock::kSpeedInput) { + // Special behavior - this was the most preferable way to do this so we could support multiple + // nodes per item one day + QPushButton *btn = new QPushButton(tr("...")); + btn->setFixedWidth(btn->sizeHint().height()); + connect(btn, &QPushButton::clicked, this, &NodeParamViewItemBody::ShowSpeedDurationDialogForNode); + layout->addWidget(btn, row, kExtraButtonColumn); + ui_objects.extra_btn = btn; } // Create a widget/input bridge for this input @@ -541,6 +552,8 @@ void NodeParamViewItemBody::ToggleArrayExpanded() void NodeParamViewItemBody::SetTimebase(const rational& timebase) { + timebase_ = timebase; + foreach (const InputUI& ui_obj, input_ui_map_) { ui_obj.widget_bridge->SetTimebase(timebase); } @@ -552,11 +565,19 @@ void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input) PlaceWidgetsFromBridge(ui.layout, ui.widget_bridge, ui.row); } +void NodeParamViewItemBody::ShowSpeedDurationDialogForNode() +{ + // We should only get there if the node is a clip, determined by the dynamic_cast in CreateWidgets + SpeedDurationDialog sdd({static_cast(node_)}, timebase_, this); + sdd.exec(); +} + NodeParamViewItemBody::InputUI::InputUI() : main_label(nullptr), widget_bridge(nullptr), connected_label(nullptr), key_control(nullptr), + extra_btn(nullptr), array_insert_btn(nullptr), array_remove_btn(nullptr) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index b000b6282..f312fa32d 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -111,6 +111,7 @@ private: NodeParamViewKeyframeControl* key_control; QGridLayout* layout; int row; + QPushButton *extra_btn; NodeParamViewArrayButton* array_insert_btn; NodeParamViewArrayButton* array_remove_btn; @@ -124,10 +125,14 @@ private: NodeParamViewArrayButton* append_btn; }; + Node *node_; + QHash array_ui_; QHash array_collapse_buttons_; + rational timebase_; + /** * @brief The column to place the keyframe controls in * @@ -138,6 +143,7 @@ private: static const int kArrayInsertColumn; static const int kArrayRemoveColumn; + static const int kExtraButtonColumn; static const int kWidgetStartColumn; @@ -158,6 +164,8 @@ private slots: void ReplaceWidgets(const NodeInput& input); + void ShowSpeedDurationDialogForNode(); + }; class NodeParamViewItem : public QDockWidget diff --git a/app/widget/nodetableview/nodetablewidget.h b/app/widget/nodetableview/nodetablewidget.h index 558a3f2c1..13d800799 100644 --- a/app/widget/nodetableview/nodetablewidget.h +++ b/app/widget/nodetableview/nodetablewidget.h @@ -42,17 +42,12 @@ public: } protected: - virtual void TimeChangedEvent(const int64_t&) override + virtual void TimeChangedEvent(const rational &time) override { - UpdateView(); + view_->SetTime(time); } private: - void UpdateView() - { - view_->SetTime(GetTime()); - } - NodeTableView* view_; }; diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index 6b5d740e6..e804391e1 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -431,7 +431,7 @@ void NodeView::keyPressEvent(QKeyEvent *event) // We undo the last action which SHOULD be adding the node if (paste_command_) { - paste_command_->undo(); + paste_command_->undo_now(); delete paste_command_; paste_command_ = nullptr; } @@ -757,7 +757,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } } if (set_pos_command->child_count()) { - set_pos_command->redo(); + set_pos_command->redo_now(); command->add_child(set_pos_command); } else { delete set_pos_command; @@ -784,7 +784,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) drop_edge_ = nullptr; } if (drop_edge_command->child_count()) { - drop_edge_command->redo(); + drop_edge_command->redo_now(); command->add_child(drop_edge_command); } else { delete drop_edge_command; @@ -822,7 +822,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event) } if (remove_pos_command->child_count()) { - remove_pos_command->redo(); + remove_pos_command->redo_now(); command->add_child(remove_pos_command); } else { delete remove_pos_command; @@ -988,7 +988,7 @@ void NodeView::CreateNodeSlot(QAction *action) paste_command_->add_child(new NodeSetPositionCommand(new_node, context, QPointF(0, 0), false)); } paste_command_->add_child(new NodeViewAttachNodesToCursor(this, {new_node})); - paste_command_->redo(); + paste_command_->redo_now(); this->setFocus(); } @@ -1673,7 +1673,7 @@ void NodeView::PasteNodesInternal(const QVector &duplicate_nodes) // Attach nodes to cursor paste_command_->add_child(new NodeViewAttachNodesToCursor(this, new_nodes)); - paste_command_->redo(); + paste_command_->redo_now(); } NodeView::NodeViewAttachNodesToCursor::NodeViewAttachNodesToCursor(NodeView *view, const QVector &nodes) : diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index c2f5aae8e..fcf23ae92 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -117,7 +117,7 @@ protected: virtual void redo() override { - command_->redo(); + command_->redo_now(); graph_ = node_->parent(); node_->setParent(&memory_manager_); @@ -128,7 +128,7 @@ protected: node_->setParent(graph_); graph_ = nullptr; - command_->undo(); + command_->undo_now(); } private: @@ -179,12 +179,12 @@ protected: virtual void redo() override { - command_->redo(); + command_->redo_now(); } virtual void undo() override { - command_->undo(); + command_->undo_now(); } private: diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 51c989af2..95c0743ae 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -174,6 +174,10 @@ public: virtual void NudgeRight(){} + virtual void MoveInToPlayhead(){} + + virtual void MoveOutToPlayhead(){} + signals: void CloseRequested(); diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp index 6fd514207..80bae6381 100644 --- a/app/widget/playbackcontrols/playbackcontrols.cpp +++ b/app/widget/playbackcontrols/playbackcontrols.cpp @@ -53,8 +53,9 @@ PlaybackControls::PlaybackControls(QWidget *parent) : lower_left_layout->setSpacing(0); lower_left_layout->setMargin(0); - cur_tc_lbl_ = new TimeSlider(); - connect(cur_tc_lbl_, &TimeSlider::ValueChanged, this, &PlaybackControls::TimeChanged); + cur_tc_lbl_ = new RationalSlider(); + cur_tc_lbl_->SetDisplayType(RationalSlider::kTime); + connect(cur_tc_lbl_, &RationalSlider::ValueChanged, this, &PlaybackControls::TimeChanged); lower_left_layout->addWidget(cur_tc_lbl_); lower_left_layout->addStretch(); @@ -179,12 +180,12 @@ void PlaybackControls::SetAudioVideoDragButtonsVisible(bool e) audio_drag_btn_->setVisible(e); } -void PlaybackControls::SetTime(const int64_t &r) +void PlaybackControls::SetTime(const rational &r) { cur_tc_lbl_->SetValue(r); } -void PlaybackControls::SetEndTime(const int64_t &r) +void PlaybackControls::SetEndTime(const rational &r) { if (time_base_.isNull()) { return; @@ -192,9 +193,9 @@ void PlaybackControls::SetEndTime(const int64_t &r) end_time_ = r; - end_tc_lbl_->setText(Timecode::timestamp_to_timecode(end_time_, - time_base_, - Core::instance()->GetTimecodeDisplay())); + end_tc_lbl_->setText(Timecode::time_to_timecode(end_time_, + time_base_, + Core::instance()->GetTimecodeDisplay())); } void PlaybackControls::ShowPauseButton() diff --git a/app/widget/playbackcontrols/playbackcontrols.h b/app/widget/playbackcontrols/playbackcontrols.h index 7a76560e1..b1d283f56 100644 --- a/app/widget/playbackcontrols/playbackcontrols.h +++ b/app/widget/playbackcontrols/playbackcontrols.h @@ -28,7 +28,7 @@ #include "common/rational.h" #include "dragbutton.h" -#include "widget/slider/timeslider.h" +#include "widget/slider/rationalslider.h" namespace olive { @@ -53,9 +53,9 @@ public: void SetAudioVideoDragButtonsVisible(bool e); public slots: - void SetTime(const int64_t &r); + void SetTime(const rational &r); - void SetEndTime(const int64_t &r); + void SetEndTime(const rational &r); void ShowPauseButton(); @@ -100,7 +100,7 @@ signals: void VideoPressed(); - void TimeChanged(const int64_t& t); + void TimeChanged(const rational& t); protected: virtual void changeEvent(QEvent *) override; @@ -111,10 +111,10 @@ private: QWidget* lower_left_container_; QWidget* lower_right_container_; - TimeSlider* cur_tc_lbl_; + RationalSlider* cur_tc_lbl_; QLabel* end_tc_lbl_; - int64_t end_time_; + rational end_time_; rational time_base_; diff --git a/app/widget/timebased/timebasedview.cpp b/app/widget/timebased/timebasedview.cpp index d0053dd17..0156771ab 100644 --- a/app/widget/timebased/timebasedview.cpp +++ b/app/widget/timebased/timebasedview.cpp @@ -33,7 +33,6 @@ const double TimeBasedView::kMaximumScale = 8192; TimeBasedView::TimeBasedView(QWidget *parent) : HandMovableView(parent), - playhead_(0), playhead_scene_left_(-1), playhead_scene_right_(-1), dragging_playhead_(false), @@ -150,7 +149,7 @@ void TimeBasedView::SetYScale(const double &y_scale) } } -void TimeBasedView::SetTime(const int64_t time) +void TimeBasedView::SetTime(const rational &time) { playhead_ = time; @@ -194,11 +193,6 @@ void TimeBasedView::drawForeground(QPainter *painter, const QRectF &rect) } } -rational TimeBasedView::GetPlayheadTime() const -{ - return Timecode::timestamp_to_time(playhead_, timebase()); -} - bool TimeBasedView::PlayheadPress(QMouseEvent *event) { QPointF scene_pos = mapToScene(event->pos()); @@ -217,23 +211,16 @@ bool TimeBasedView::PlayheadMove(QMouseEvent *event) } QPointF scene_pos = mapToScene(event->pos()); - rational mouse_time = SceneToTime(scene_pos.x()); - - int64_t target_ts = qMax(static_cast(0), Timecode::time_to_timestamp(mouse_time, timebase())); + rational mouse_time = qMax(rational(0), SceneToTime(scene_pos.x())); if (Core::instance()->snapping() && snap_service_) { - rational target_time = Timecode::timestamp_to_time(target_ts, timebase()); rational movement; - snap_service_->SnapPoint({target_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); - - if (!movement.isNull()) { - target_ts = Timecode::time_to_timestamp(target_time + movement, timebase()); - } + snap_service_->SnapPoint({mouse_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); } - SetTime(target_ts); - emit TimeChanged(target_ts); + SetTime(mouse_time); + emit TimeChanged(mouse_time); return true; } @@ -255,7 +242,7 @@ bool TimeBasedView::PlayheadRelease(QMouseEvent*) qreal TimeBasedView::GetPlayheadX() { - return TimeToScene(Timecode::timestamp_to_time(playhead_, timebase())); + return TimeToScene(playhead_); } void TimeBasedView::SetEndTime(const rational &length) diff --git a/app/widget/timebased/timebasedview.h b/app/widget/timebased/timebasedview.h index 55a6ec6d8..d7a6f4b49 100644 --- a/app/widget/timebased/timebasedview.h +++ b/app/widget/timebased/timebasedview.h @@ -56,12 +56,12 @@ public: } public slots: - void SetTime(const int64_t time); + void SetTime(const rational &time); void SetEndTime(const rational& length); signals: - void TimeChanged(const int64_t& time); + void TimeChanged(const rational& time); void ScaleChanged(double scale); @@ -78,7 +78,10 @@ protected: virtual void ZoomIntoCursorPosition(QWheelEvent *event, double multiplier, const QPointF &cursor_pos) override; - rational GetPlayheadTime() const; + const rational &GetPlayheadTime() const + { + return playhead_; + } bool PlayheadPress(QMouseEvent* event); bool PlayheadMove(QMouseEvent* event); @@ -105,7 +108,7 @@ protected slots: private: qreal GetPlayheadX(); - int64_t playhead_; + rational playhead_; double playhead_scene_left_; double playhead_scene_right_; diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index 86befc724..2b3691884 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -57,12 +57,7 @@ void TimeBasedWidget::SetScaleAndCenterOnPlayhead(const double &scale) QTimer::singleShot(0, this, &TimeBasedWidget::CenterScrollOnPlayhead); } -rational TimeBasedWidget::GetTime() const -{ - return Timecode::timestamp_to_time(ruler()->GetTime(), timebase()); -} - -const int64_t &TimeBasedWidget::GetTimestamp() const +const rational &TimeBasedWidget::GetTime() const { return ruler_->GetTime(); } @@ -273,8 +268,7 @@ void TimeBasedWidget::resizeEvent(QResizeEvent *event) void TimeBasedWidget::ConnectTimelineView(TimeBasedView *base, bool connect_time_change_event) { if (connect_time_change_event) { - connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::SetTimestamp); - connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::TimeChanged); + connect(base, &TimeBasedView::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal); } timeline_views_.append(base); @@ -286,7 +280,7 @@ void TimeBasedWidget::PassWheelEventsToScrollBar(QObject *object) object->installEventFilter(this); } -void TimeBasedWidget::SetTimestamp(int64_t timestamp) +void TimeBasedWidget::SetTime(const rational &time) { if (UserIsDraggingPlayhead()) { // If the user is dragging the playhead, we will simply nudge over and not use autoscroll rules. @@ -306,9 +300,9 @@ void TimeBasedWidget::SetTimestamp(int64_t timestamp) } } - ruler_->SetTime(timestamp); + ruler_->SetTime(time); - TimeChangedEvent(timestamp); + TimeChangedEvent(time); } void TimeBasedWidget::SetTimebase(const rational &timebase) @@ -341,20 +335,18 @@ void TimeBasedWidget::GoToPrevCut() return; } - if (GetTimestamp() == 0) { + if (GetTime().isNull()) { return; } - int64_t closest_cut = 0; + rational closest_cut = 0; foreach (Track* track, sequence->GetTracks()) { - int64_t this_track_closest_cut = 0; + rational this_track_closest_cut = 0; foreach (Block* block, track->Blocks()) { - int64_t block_out_ts = Timecode::time_to_timestamp(block->out(), timebase()); - - if (block_out_ts < GetTimestamp()) { - this_track_closest_cut = block_out_ts; + if (block->out() < GetTime()) { + this_track_closest_cut = block->out(); } else { break; } @@ -375,20 +367,18 @@ void TimeBasedWidget::GoToNextCut() return; } - int64_t closest_cut = INT64_MAX; + rational closest_cut = RATIONAL_MAX; foreach (Track* track, sequence->GetTracks()) { - int64_t this_track_closest_cut = Timecode::time_to_timestamp(track->track_length(), timebase()); + rational this_track_closest_cut = track->track_length(); - if (this_track_closest_cut <= GetTimestamp()) { - this_track_closest_cut = INT64_MAX; + if (this_track_closest_cut <= GetTime()) { + this_track_closest_cut = RATIONAL_MAX; } foreach (Block* block, track->Blocks()) { - int64_t block_in_ts = Timecode::time_to_timestamp(block->in(), timebase()); - - if (block_in_ts > GetTimestamp()) { - this_track_closest_cut = block_in_ts; + if (block->in() > GetTime()) { + this_track_closest_cut = block->in(); break; } } @@ -396,7 +386,7 @@ void TimeBasedWidget::GoToNextCut() closest_cut = qMin(closest_cut, this_track_closest_cut); } - if (closest_cut < INT64_MAX) { + if (closest_cut < RATIONAL_MAX) { SetTimeAndSignal(closest_cut); } } @@ -411,33 +401,43 @@ void TimeBasedWidget::GoToStart() void TimeBasedWidget::PrevFrame() { if (viewer_node_) { - SetTimeAndSignal(qMax(static_cast(0), ruler()->GetTime() - 1)); + rational proposed_time = Timecode::snap_time_to_timebase(GetTime() - timebase(), timebase(), Timecode::kCeil); + if (proposed_time == GetTime()) { + // Catch rounding error, assume this time is snapped and just subtract a timebase + proposed_time -= timebase(); + } + SetTimeAndSignal(qMax(rational(0), proposed_time)); } } void TimeBasedWidget::NextFrame() { if (viewer_node_) { - SetTimeAndSignal(ruler()->GetTime() + 1); + rational proposed_time = Timecode::snap_time_to_timebase(GetTime() + timebase(), timebase(), Timecode::kFloor); + if (proposed_time == GetTime()) { + // Catch rounding error, assume this time is snapped and just add a timebase + proposed_time += timebase(); + } + SetTimeAndSignal(proposed_time); } } void TimeBasedWidget::GoToEnd() { if (viewer_node_) { - SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->GetLength(), timebase())); + SetTimeAndSignal(viewer_node_->GetLength()); } } -void TimeBasedWidget::SetTimeAndSignal(const int64_t &t) +void TimeBasedWidget::SetTimeAndSignal(const rational &t) { - SetTimestamp(t); + SetTime(t); emit TimeChanged(t); } void TimeBasedWidget::CenterScrollOnPlayhead() { - scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2); + scrollbar_->setValue(qRound(TimeToScene(ruler_->GetTime())) - scrollbar_->width()/2); } void TimeBasedWidget::SetAutoSetTimebase(bool e) @@ -639,7 +639,7 @@ void TimeBasedWidget::GoToIn() { if (GetConnectedNode()) { if (GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->in(), timebase())); + SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); } else { GoToStart(); } @@ -650,7 +650,7 @@ void TimeBasedWidget::GoToOut() { if (GetConnectedNode()) { if (GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->out(), timebase())); + SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->out()); } else { GoToEnd(); } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index e0cc79b41..2e479d84b 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -38,9 +38,7 @@ class TimeBasedWidget : public TimelineScaledWidget public: TimeBasedWidget(bool ruler_text_visible = true, bool ruler_cache_status_visible = false, QWidget* parent = nullptr); - rational GetTime() const; - - const int64_t& GetTimestamp() const; + const rational &GetTime() const; void ZoomIn(); @@ -57,7 +55,7 @@ public: virtual bool eventFilter(QObject* object, QEvent* event) override; public slots: - void SetTimestamp(int64_t timestamp); + void SetTime(const rational &time); void SetTimebase(const rational& timebase); @@ -94,14 +92,14 @@ public slots: void GoToOut(); protected slots: - void SetTimeAndSignal(const int64_t& t); + void SetTimeAndSignal(const rational& t); protected: ResizableTimelineScrollBar* scrollbar() const; virtual void TimebaseChangedEvent(const rational&) override; - virtual void TimeChangedEvent(const int64_t&){} + virtual void TimeChangedEvent(const rational&){} virtual void ScaleChangedEvent(const double &) override; @@ -134,7 +132,7 @@ protected slots: static void PageScrollInternal(QScrollBar* bar, int maximum, int screen_position, bool whole_page_scroll); signals: - void TimeChanged(const int64_t&); + void TimeChanged(const rational&); void TimebaseChanged(const rational&); diff --git a/app/widget/timebased/timescaledobject.cpp b/app/widget/timebased/timescaledobject.cpp index bdb162979..3d74ed026 100644 --- a/app/widget/timebased/timescaledobject.cpp +++ b/app/widget/timebased/timescaledobject.cpp @@ -72,12 +72,12 @@ rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, c return rational(rounded_x_mvmt * timebase.numerator(), timebase.denominator()); } -double TimeScaledObject::TimeToScene(const rational &time) +double TimeScaledObject::TimeToScene(const rational &time) const { return time.toDouble() * scale_; } -rational TimeScaledObject::SceneToTime(const double &x, bool round) +rational TimeScaledObject::SceneToTime(const double &x, bool round) const { return SceneToTime(x, scale_, timebase_, round); } diff --git a/app/widget/timebased/timescaledobject.h b/app/widget/timebased/timescaledobject.h index 60dd5eea5..c9e86eeb2 100644 --- a/app/widget/timebased/timescaledobject.h +++ b/app/widget/timebased/timescaledobject.h @@ -51,8 +51,8 @@ public: static double CalculateScaleFromDimensions(double viewport_sz, double content_sz); static double CalculatePaddingFromDimensionScale(double viewport_sz); - double TimeToScene(const rational& time); - rational SceneToTime(const double &x, bool round = false); + double TimeToScene(const rational& time) const; + rational SceneToTime(const double &x, bool round = false) const; protected: virtual void TimebaseChangedEvent(const rational&){} diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index bc2624f57..9c4890a29 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -30,6 +30,7 @@ #include "common/timecodefunctions.h" #include "dialog/nodeproperties/nodepropertiesdialog.h" #include "dialog/sequence/sequence.h" +#include "dialog/speedduration/speeddurationdialog.h" #include "node/block/transition/transition.h" #include "tool/add.h" #include "tool/beam.h" @@ -70,10 +71,11 @@ TimelineWidget::TimelineWidget(QWidget *parent) : QHBoxLayout* ruler_and_time_layout = new QHBoxLayout(); vert_layout->addLayout(ruler_and_time_layout); - timecode_label_ = new TimeSlider(); + timecode_label_ = new RationalSlider(); timecode_label_->SetAlignment(Qt::AlignCenter); + timecode_label_->SetDisplayType(RationalSlider::kTime); timecode_label_->setVisible(false); - connect(timecode_label_, &TimeSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal); + connect(timecode_label_, &RationalSlider::ValueChanged, this, &TimelineWidget::SetTimeAndSignal); ruler_and_time_layout->addWidget(timecode_label_); ruler_and_time_layout->addWidget(ruler()); @@ -135,7 +137,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) : connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll); connect(view, &TimelineView::ScaleChanged, this, &TimelineWidget::SetScale); - connect(view, &TimelineView::TimeChanged, this, &TimelineWidget::ViewTimestampChanged); + connect(view, &TimelineView::TimeChanged, this, &TimelineWidget::SetTimeAndSignal); connect(view, &TimelineView::customContextMenuRequested, this, &TimelineWidget::ShowContextMenu); connect(scrollbar(), &QScrollBar::valueChanged, view->horizontalScrollBar(), &QScrollBar::setValue); connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, scrollbar(), &QScrollBar::setValue); @@ -221,13 +223,13 @@ void TimelineWidget::resizeEvent(QResizeEvent *event) UpdateTimecodeWidthFromSplitters(views_.first()->splitter()); } -void TimelineWidget::TimeChangedEvent(const int64_t& timestamp) +void TimelineWidget::TimeChangedEvent(const rational &time) { - super::TimeChangedEvent(timestamp); + super::TimeChangedEvent(time); - SetViewTimestamp(timestamp); + SetViewTime(time); - timecode_label_->SetValue(timestamp); + timecode_label_->SetValue(time); } void TimelineWidget::ScaleChangedEvent(const double &scale) @@ -399,7 +401,7 @@ void TimelineWidget::SplitAtPlayhead() return; } - rational playhead_time = Timecode::timestamp_to_time(GetTimestamp(), timebase()); + const rational &playhead_time = GetTime(); QVector selected_blocks = GetSelectedBlocks(); @@ -692,8 +694,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) false)); if (ripple) { - SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->in(), - timebase())); + SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); } Core::instance()->undo_stack()->push(command); @@ -738,6 +739,33 @@ void TimelineWidget::NudgeRight() } } +void TimelineWidget::MoveInToPlayhead() +{ + MoveToPlayheadInternal(false); +} + +void TimelineWidget::MoveOutToPlayhead() +{ + MoveToPlayheadInternal(true); +} + +void TimelineWidget::ShowSpeedDurationDialogForSelectedClips() +{ + QVector clips; + + foreach (Block *b, selected_blocks_) { + ClipBlock *c = dynamic_cast(b); + if (c) { + clips.append(c); + } + } + + if (!clips.isEmpty()) { + SpeedDurationDialog sdd(clips, timebase(), this); + sdd.exec(); + } +} + void TimelineWidget::InsertGapsAt(const rational &earliest_point, const rational &insert_length, MultiUndoCommand *command) { for (int i=0;iview()->SetTime(Timecode::rescale_timestamp(ts, - timebase(), - GetConnectedNode()->GetAudioParams().sample_rate_as_time_base())); - } else { - view->view()->SetTime(ts); - } + view->view()->SetTime(time); } } -void TimelineWidget::ViewTimestampChanged(int64_t ts) -{ - if (GetConnectedNode() && use_audio_time_units_ && sender() == views_.at(Track::kAudio)) { - ts = Timecode::rescale_timestamp(ts, - GetConnectedNode()->GetAudioParams().sample_rate_as_time_base(), - timebase()); - } - - // Update all other views - SetTimestamp(ts); - emit TimeChanged(ts); -} - void TimelineWidget::ToolChanged() { HideSnaps(); @@ -1161,19 +1166,73 @@ void TimelineWidget::UpdateViewTimebases() void TimelineWidget::NudgeInternal(const rational &amount) { - MultiUndoCommand *command = new MultiUndoCommand(); + if (!selected_blocks_.isEmpty()) { + MultiUndoCommand *command = new MultiUndoCommand(); - foreach (Block* b, selected_blocks_) { - command->add_child(new TrackReplaceBlockWithGapCommand(b->track(), b, false)); - command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(b->track()->type()), b->track()->Index(), b, b->in() + amount)); + foreach (Block* b, selected_blocks_) { + command->add_child(new TrackReplaceBlockWithGapCommand(b->track(), b, false)); + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(b->track()->type()), b->track()->Index(), b, b->in() + amount)); + } + + // Nudge selections + TimelineWidgetSelections new_sel = GetSelections(); + new_sel.ShiftTime(amount); + command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections(), true)); + + Core::instance()->undo_stack()->push(command); } +} - // Nudge selections - TimelineWidgetSelections new_sel = GetSelections(); - new_sel.ShiftTime(amount); - command->add_child(new TimelineWidget::SetSelectionsCommand(this, new_sel, GetSelections(), true)); +void TimelineWidget::MoveToPlayheadInternal(bool out) +{ + if (GetConnectedNode() && !selected_blocks_.isEmpty()) { + MultiUndoCommand *command = new MultiUndoCommand(); - Core::instance()->undo_stack()->pushIfHasChildren(command); + // Remove each block from the graph + QHash earliest_pts; + foreach (Block *b, selected_blocks_) { + command->add_child(new TrackReplaceBlockWithGapCommand(b->track(), b, false)); + + rational r = earliest_pts.value(b->track(), out ? RATIONAL_MIN : RATIONAL_MAX); + rational compare = out ? b->out() : b->in(); + if ((compare < r) == !out) { + earliest_pts.insert(b->track(), compare); + } + } + + foreach (Block *b, selected_blocks_) { + rational shift_amt = GetTime() - earliest_pts.value(b->track()); + rational new_in = b->in() + shift_amt; + bool can_shift = true; + + if (new_in < 0) { + // Handle clips threatening to go below 0 + rational new_out = new_in + b->length(); + if (new_out <= 0) { + can_shift = false; + } else { + command->add_child(new BlockResizeWithMediaInCommand(b, new_out)); + new_in = 0; + } + } + + if (can_shift) { + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(b->track()->type()), b->track()->Index(), b, new_in)); + } + } + + // Shift selections + TimelineWidgetSelections new_sel = GetSelections(); + for (auto it=new_sel.begin(); it!=new_sel.end(); it++) { + rational track_adj = GetTime() - earliest_pts.value(GetTrackFromReference(it.key()), GetTime()); + if (!track_adj.isNull()) { + it.value().shift(track_adj); + } + } + command->add_child(new SetSelectionsCommand(this, new_sel, GetSelections(), true)); + + Core::instance()->undo_stack()->push(command); + } } void TimelineWidget::SetViewBeamCursor(const TimelineCoordinate &coord) @@ -1359,9 +1418,9 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode) // If we rippled, ump to where new cut is if applicable if (mode == Timeline::kTrimIn) { - SetTimeAndSignal(Timecode::time_to_timestamp(closest_point_to_playhead, timebase())); + SetTimeAndSignal(closest_point_to_playhead); } else if (mode == Timeline::kTrimOut && closest_point_to_playhead == GetTime()) { - SetTimeAndSignal(Timecode::time_to_timestamp(playhead_time, timebase())); + SetTimeAndSignal(playhead_time); } } @@ -1603,6 +1662,10 @@ void TimelineWidget::RemoveSelection(Block *item) void TimelineWidget::SetSelections(const TimelineWidgetSelections &s, bool process_block_changes) { + if (selections_ == s) { + return; + } + if (process_block_changes) { SignalDeselectedBlocks(GetBlocksInSelection(selections_.Subtracted(s))); SignalSelectedBlocks(GetBlocksInSelection(s.Subtracted(selections_))); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 6098fb1b4..5fcae61e5 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -31,7 +31,7 @@ #include "node/output/viewer/viewer.h" #include "timeline/timelinecommon.h" #include "timelineandtrackview.h" -#include "widget/slider/timeslider.h" +#include "widget/slider/rationalslider.h" #include "widget/snapservice/snapservice.h" #include "widget/timebased/timebasedwidget.h" #include "widget/timelinewidget/timelinewidgetselections.h" @@ -95,6 +95,12 @@ public: void NudgeRight(); + void MoveInToPlayhead(); + + void MoveOutToPlayhead(); + + void ShowSpeedDurationDialogForSelectedClips(); + /** * @brief Timelines should always be connected to sequences */ @@ -248,7 +254,7 @@ protected: virtual void resizeEvent(QResizeEvent *event) override; virtual void TimebaseChangedEvent(const rational &) override; - virtual void TimeChangedEvent(const int64_t &) override; + virtual void TimeChangedEvent(const rational &time) override; virtual void ScaleChangedEvent(const double &) override; virtual void ConnectNodeEvent(ViewerOutput* n) override; @@ -299,7 +305,7 @@ private: QVector views_; - TimeSlider* timecode_label_; + RationalSlider* timecode_label_; QVector selected_blocks_; @@ -317,6 +323,8 @@ private: void NudgeInternal(const rational &amount); + void MoveToPlayheadInternal(bool out); + private slots: void ViewMousePressed(TimelineViewMouseEvent* event); void ViewMouseMoved(TimelineViewMouseEvent* event); @@ -349,9 +357,7 @@ private slots: void SetUseAudioTimeUnits(bool use); - void SetViewTimestamp(const int64_t& ts); - - void ViewTimestampChanged(int64_t ts); + void SetViewTime(const rational &time); void ToolChanged(); diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp index ff9ee2629..163078e38 100644 --- a/app/widget/timelinewidget/tool/import.cpp +++ b/app/widget/timelinewidget/tool/import.cpp @@ -131,10 +131,9 @@ void ImportTool::DragMove(TimelineViewMouseEvent *event) // Generate tooltip (showing earliest in point of imported clip) rational tooltip_timebase = parent()->GetTimebaseForTrackType(event->GetTrack().type()); - int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, tooltip_timebase); - QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp, - tooltip_timebase, - Core::instance()->GetTimecodeDisplay()); + QString tooltip_text = Timecode::time_to_timecode(earliest_ghost, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay()); // Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way // of the cursor) diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 67dc553bd..1913e0cd6 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -504,10 +504,10 @@ void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos) rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, tooltip_timebase), - tooltip_timebase, - Core::instance()->GetTimecodeDisplay(), - true), + Timecode::time_to_timecode(time_movement, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay(), + true), parent()); } @@ -670,10 +670,10 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) QHash >::const_iterator i; for (i=slide_info.constBegin(); i!=slide_info.constEnd(); i++) { command->add_child(new TrackSlideCommand(parent()->GetTrackFromReference(i.key()), - i.value(), - in_adjacents.value(i.key()), - out_adjacents.value(i.key()), - movement)); + i.value(), + in_adjacents.value(i.key()), + out_adjacents.value(i.key()), + movement)); } // Adjust selections diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp index f3b1281b8..6dc6a6c61 100644 --- a/app/widget/timelinewidget/tool/slip.cpp +++ b/app/widget/timelinewidget/tool/slip.cpp @@ -58,10 +58,10 @@ void SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos) rational tooltip_timebase = parent()->GetTimebaseForTrackType(drag_start_.GetTrack().type()); QToolTip::hideText(); QToolTip::showText(QCursor::pos(), - Timecode::timestamp_to_timecode(Timecode::time_to_timestamp(time_movement, tooltip_timebase), - tooltip_timebase, - Core::instance()->GetTimecodeDisplay(), - true), + Timecode::time_to_timecode(time_movement, + tooltip_timebase, + Core::instance()->GetTimecodeDisplay(), + true), parent()); } diff --git a/app/widget/timelinewidget/undo/timelineundogeneral.cpp b/app/widget/timelinewidget/undo/timelineundogeneral.cpp index a44099fa6..63958f250 100644 --- a/app/widget/timelinewidget/undo/timelineundogeneral.cpp +++ b/app/widget/timelinewidget/undo/timelineundogeneral.cpp @@ -172,12 +172,12 @@ void TimelineAddTrackCommand::redo() position_command_->add_child(new NodeSetPositionAsChildCommand(track_, merge_, timeline_->parent(), 1, 2, true)); } } - position_command_->redo(); + position_command_->redo_now(); } void TimelineAddTrackCommand::undo() { - position_command_->undo(); + position_command_->undo_now(); // Remove merge if applicable if (merge_) { diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 637b1e0a7..787228442 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -39,7 +39,7 @@ void BlockSplitCommand::redo() new_block_ = static_cast(Node::CopyNodeInGraph(block_, reconnect_tree_command_)); } - reconnect_tree_command_->redo(); + reconnect_tree_command_->redo_now(); // Determine our new lengths rational new_length = point_ - block_->in(); @@ -97,7 +97,7 @@ void BlockSplitCommand::undo() track->RippleRemoveBlock(new_block()); // If we ran a reconnect command, disconnect now - reconnect_tree_command_->undo(); + reconnect_tree_command_->undo_now(); track->EndOperation(); } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 0804d1800..df0386016 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -664,12 +664,6 @@ Block *TimelineView::GetItemAtScenePos(const rational &time, int track_index) co return nullptr; } -void TimelineView::UserSetTime(const int64_t &time) -{ - SetTime(time); - emit TimeChanged(time); -} - void TimelineView::TrackListChanged() { UpdateSceneRect(); diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 7c5d9fb88..5a9f01282 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -126,8 +126,6 @@ private: int GetHeightOfAllTracks() const; - void UserSetTime(const int64_t& time); - void UpdatePlayheadRect(); QHash* selections_; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index 1b763f067..b410557d3 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -31,7 +31,6 @@ namespace olive { SeekableWidget::SeekableWidget(QWidget* parent) : TimelineScaledWidget(parent), - time_(0), timeline_points_(nullptr), scroll_(0), snap_service_(nullptr), @@ -73,11 +72,6 @@ void SeekableWidget::SetSnapService(SnapService *service) snap_service_ = service; } -const int64_t &SeekableWidget::GetTime() const -{ - return time_; -} - const int &SeekableWidget::GetScroll() const { return scroll_; @@ -119,7 +113,7 @@ TimelinePoints *SeekableWidget::timeline_points() const return timeline_points_; } -void SeekableWidget::SetTime(const int64_t &r) +void SeekableWidget::SetTime(const rational &r) { time_ = r; @@ -133,29 +127,14 @@ void SeekableWidget::SetScroll(int s) update(); } -double SeekableWidget::ScreenToUnitFloat(int screen) const -{ - return (screen + scroll_) / GetScale() / timebase_dbl(); -} - -int64_t SeekableWidget::ScreenToUnit(int screen) const -{ - return qFloor(ScreenToUnitFloat(screen)); -} - -int64_t SeekableWidget::ScreenToUnitRounded(int screen) const -{ - return qRound64(ScreenToUnitFloat(screen)); -} - -int SeekableWidget::UnitToScreen(int64_t unit) const -{ - return qFloor(static_cast(unit) * GetScale() * timebase_dbl()) - scroll_; -} - int SeekableWidget::TimeToScreen(const rational &time) const { - return qFloor(time.toDouble() * GetScale()) - scroll_; + return qFloor(TimeToScene(time)) - scroll_; +} + +rational SeekableWidget::ScreenToTime(int x) const +{ + return qMax(rational(0), SceneToTime(x + scroll_)); } void SeekableWidget::SeekToScreenPoint(int screen) @@ -164,26 +143,20 @@ void SeekableWidget::SeekToScreenPoint(int screen) return; } - int64_t timestamp = qMax(static_cast(0), ScreenToUnitRounded(screen)); + rational playhead_time = ScreenToTime(screen); if (Core::instance()->snapping() && snap_service_) { - rational playhead_time = Timecode::timestamp_to_time(timestamp, timebase()); rational movement; snap_service_->SnapPoint({playhead_time}, &movement, SnapService::kSnapAll & ~SnapService::kSnapToPlayhead); - - if (!movement.isNull()) { - timestamp = Timecode::time_to_timestamp(playhead_time + movement, - timebase()); - } } - if (timestamp != GetTime()) { - SetTime(timestamp); + if (playhead_time != GetTime()) { + SetTime(playhead_time); - emit TimeChanged(timestamp); + emit TimeChanged(playhead_time); } } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 7881e7e9f..8cccaf64b 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -34,7 +34,10 @@ class SeekableWidget : public TimelineScaledWidget public: SeekableWidget(QWidget *parent = nullptr); - const int64_t& GetTime() const; + const rational& GetTime() const + { + return time_; + } const int& GetScroll() const; @@ -48,7 +51,7 @@ public: } public slots: - void SetTime(const int64_t &r); + void SetTime(const rational &r); void SetScroll(int s); @@ -65,14 +68,8 @@ protected: TimelinePoints* timeline_points() const; - double ScreenToUnitFloat(int screen) const; - - int64_t ScreenToUnit(int screen) const; - int64_t ScreenToUnitRounded(int screen) const; - - int UnitToScreen(int64_t unit) const; - int TimeToScreen(const rational& time) const; + rational ScreenToTime(int x) const; void DrawPlayhead(QPainter* p, int x, int y); @@ -88,10 +85,10 @@ signals: /** * @brief Signal emitted whenever the time changes on this ruler, either by user or programmatically */ - void TimeChanged(int64_t); + void TimeChanged(const rational &time); private: - int64_t time_; + rational time_; TimelinePoints* timeline_points_; diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp index ccadef51b..db887fe7e 100644 --- a/app/widget/timeruler/timeruler.cpp +++ b/app/widget/timeruler/timeruler.cpp @@ -208,7 +208,7 @@ void TimeRuler::paintEvent(QPaintEvent *) if (text_visible_) { QRect text_rect; Qt::Alignment text_align; - QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase(), Core::instance()->GetTimecodeDisplay()); + QString timecode_str = Timecode::time_to_timecode(ScreenToTime(i), timebase(), Core::instance()->GetTimecodeDisplay()); int timecode_width = QtUtils::QFontMetricsWidth(fm, timecode_str); int timecode_left; @@ -287,7 +287,7 @@ void TimeRuler::paintEvent(QPaintEvent *) } // Draw the playhead if it's on screen at the moment - int playhead_pos = UnitToScreen(GetTime()); + int playhead_pos = TimeToScreen(GetTime()); p.setPen(Qt::NoPen); p.setBrush(PLAYHEAD_COLOR); DrawPlayhead(&p, playhead_pos, line_bottom); diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp index a632da463..fdc52edc4 100644 --- a/app/widget/viewer/audiowaveformview.cpp +++ b/app/widget/viewer/audiowaveformview.cpp @@ -100,7 +100,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event) // Draw playhead p.setPen(PLAYHEAD_COLOR); - int playhead_x = UnitToScreen(GetTime()); + int playhead_x = TimeToScreen(GetTime()); p.drawLine(playhead_x, 0, playhead_x, height()); } diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 983db9e0f..2a2926f85 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -44,14 +44,14 @@ void FootageViewerWidget::ConnectNodeEvent(ViewerOutput *n) { super::ConnectNodeEvent(n); - SetTimestamp(cached_timestamps_.value(n, 0)); + SetTime(cached_timestamps_.value(n, 0)); } void FootageViewerWidget::DisconnectNodeEvent(ViewerOutput *n) { // Cache timestamp in case this footage is opened again later - cached_timestamps_.insert(n, GetTimestamp()); - SetTimestamp(0); + cached_timestamps_.insert(n, GetTime()); + SetTime(0); super::DisconnectNodeEvent(n); } diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index ce765c6ff..fdc8a62ea 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -40,7 +40,7 @@ protected: private: void StartFootageDragInternal(bool enable_video, bool enable_audio); - QHash cached_timestamps_; + QHash cached_timestamps_; private slots: void StartFootageDrag(); diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 697ad204b..e85b72d3a 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -121,7 +121,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : SetScale(48.0); // Ensures that seeking on the waveform view updates the time as expected - connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::TimeChangedFromWaveform); + connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal); connect(waveform_view_, &AudioWaveformView::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu); connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate); @@ -144,30 +144,16 @@ ViewerWidget::~ViewerWidget() } } -void ViewerWidget::TimeChangedEvent(const int64_t &i) +void ViewerWidget::TimeChangedEvent(const rational &time) { if (!time_changed_from_timer_) { PauseInternal(); } - controls_->SetTime(i); - - { - // Update waveform time - qint64 waveform_time; - - if (waveform_view_->timebase() != this->timebase()) { - waveform_time = Timecode::rescale_timestamp(i, this->timebase(), waveform_view_->timebase()); - } else { - waveform_time = i; - } - - waveform_view_->SetTime(waveform_time); - } - - if (GetConnectedNode() && last_time_ != i) { - rational time_set = Timecode::timestamp_to_time(i, timebase()); + controls_->SetTime(time); + waveform_view_->SetTime(time); + if (GetConnectedNode() && last_time_ != time) { if (!IsPlaying()) { UpdateTextureFromNode(); @@ -178,13 +164,13 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i) display_widget_->ResetFPSTimer(); } - display_widget_->SetTime(time_set); + display_widget_->SetTime(time); } // Send time to auto-cacher UpdateAutoCacher(); - last_time_ = i; + last_time_ = time; } void ViewerWidget::ConnectNodeEvent(ViewerOutput *n) @@ -573,14 +559,14 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) if (speed > 0) { SetTimeAndSignal(0); } else { - SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetLength(), timebase())); + SetTimeAndSignal(GetConnectedNode()->GetLength()); } } playback_speed_ = speed; play_in_to_out_only_ = in_to_out_only; - playback_queue_next_frame_ = ruler()->GetTime(); + playback_queue_next_frame_ = GetTimestamp(); controls_->ShowPauseButton(); @@ -752,7 +738,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool prioritize) void ViewerWidget::FinishPlayPreprocess() { - int64_t playback_start_time = ruler()->GetTime(); + int64_t playback_start_time = GetTimestamp(); StartAudioOutput(); @@ -1062,7 +1048,7 @@ void ViewerWidget::Play(bool in_to_out_only) if (GetConnectedNode() && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { // Jump to in point - SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->in(), timebase())); + SetTimeAndSignal(GetConnectedNode()->GetTimelinePoints()->workarea()->in()); } else { in_to_out_only = false; } @@ -1148,21 +1134,21 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase) void ViewerWidget::PlaybackTimerUpdate() { - int64_t current_time = playback_timer_.GetTimestampNow(); + rational current_time = Timecode::timestamp_to_time(playback_timer_.GetTimestampNow(), timebase()); - int64_t min_time, max_time; + rational min_time, max_time; if (play_in_to_out_only_ && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { // If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea - min_time = Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->in(), timebase()); - max_time = Timecode::time_to_timestamp(GetConnectedNode()->GetTimelinePoints()->workarea()->out(), timebase()); + min_time = GetConnectedNode()->GetTimelinePoints()->workarea()->in(); + max_time = GetConnectedNode()->GetTimelinePoints()->workarea()->out(); } else { // Otherwise set the bounds to the range of the sequence min_time = 0; - max_time = Timecode::time_to_timestamp(GetConnectedNode()->GetLength(), timebase()); + max_time = GetConnectedNode()->GetLength(); } @@ -1170,7 +1156,7 @@ void ViewerWidget::PlaybackTimerUpdate() || (playback_speed_ > 0 && current_time >= max_time)) { // Determine which timestamp we tripped - int64_t tripped_time; + rational tripped_time; if (current_time <= min_time) { tripped_time = min_time; @@ -1181,7 +1167,7 @@ void ViewerWidget::PlaybackTimerUpdate() if (Config::Current()[QStringLiteral("Loop")].toBool()) { // If we're looping, jump to the other side of the workarea and continue - int64_t opposing_time = (tripped_time == min_time) ? max_time : min_time; + rational opposing_time = (tripped_time == min_time) ? max_time : min_time; // Cache the current speed int current_speed = playback_speed_; @@ -1211,8 +1197,7 @@ void ViewerWidget::PlaybackTimerUpdate() UpdateTextureFromNode(); } else if (!windows_.empty()) { // We still run the queue if windows are visible even if our own display widget isn't visible - rational t = GetTime(); - while (!playback_queue_.empty() && playback_queue_.front().timestamp != t) { + while (!playback_queue_.empty() && playback_queue_.front().timestamp != GetTime()) { PopOldestFrameFromPlaybackQueue(); } } @@ -1239,7 +1224,7 @@ void ViewerWidget::SetViewerPixelAspect(const rational &ratio) void ViewerWidget::LengthChangedSlot(const rational &length) { if (last_length_ != length) { - controls_->SetEndTime(Timecode::time_to_timestamp(length, timebase())); + controls_->SetEndTime(length); UpdateMinimumScale(); if (length < last_length_ && GetTime() >= length) { @@ -1296,16 +1281,6 @@ void ViewerWidget::ManualSwitchToWaveform(bool e) } } -void ViewerWidget::TimeChangedFromWaveform(qint64 t) -{ - if (waveform_view_->timebase() != this->timebase()) { - // Transform time to our timebase - t = Timecode::rescale_timestamp(t, waveform_view_->timebase(), this->timebase()); - } - - SetTimeAndSignal(t); -} - void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to) { if (GetTime() >= qMin(from, to)) { diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index 402164ee3..a7d7154ae 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -149,7 +149,7 @@ signals: protected: virtual void TimebaseChangedEvent(const rational &) override; - virtual void TimeChangedEvent(const int64_t &) override; + virtual void TimeChangedEvent(const rational &time) override; virtual void ConnectNodeEvent(ViewerOutput *) override; virtual void DisconnectNodeEvent(ViewerOutput *) override; @@ -167,6 +167,11 @@ protected: } private: + int64_t GetTimestamp() const + { + return Timecode::time_to_timestamp(GetTime(), timebase(), Timecode::kFloor); + } + void UpdateTimeInternal(int64_t i); void PlayInternal(int speed, bool in_to_out_only); @@ -215,7 +220,7 @@ private: QAtomicInt playback_speed_; - int64_t last_time_; + rational last_time_; bool color_menu_enabled_; @@ -294,8 +299,6 @@ private slots: void ManualSwitchToWaveform(bool e); - void TimeChangedFromWaveform(qint64 t); - void DragEntered(QDragEnterEvent* event); void Dropped(QDropEvent* event); diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index b53d94e63..d158904dd 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -110,6 +110,8 @@ MainMenu::MainMenu(MainWindow *parent) : edit_menu_->addSeparator(); edit_nudge_left_item_ = edit_menu_->AddItem("nudgeleft", this, &MainMenu::NudgeLeftTriggered, "Alt+Left"); edit_nudge_right_item_ = edit_menu_->AddItem("nudgeright", this, &MainMenu::NudgeRightTriggered, "Alt+Right"); + edit_move_in_to_playhead_item_ = edit_menu_->AddItem("moveintoplayhead", this, &MainMenu::MoveInToPlayheadTriggered, "["); + edit_move_out_to_playhead_item_ = edit_menu_->AddItem("moveouttoplayhead", this, &MainMenu::MoveOutToPlayheadTriggered, "]"); edit_menu_->addSeparator(); MenuShared::instance()->AddItemsForInOutMenu(edit_menu_); edit_delete_inout_item_ = edit_menu_->AddItem("deleteinout", this, &MainMenu::DeleteInOutTriggered, ";"); @@ -550,6 +552,16 @@ void MainMenu::NudgeRightTriggered() PanelManager::instance()->CurrentlyFocused()->NudgeRight(); } +void MainMenu::MoveInToPlayheadTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->MoveInToPlayhead(); +} + +void MainMenu::MoveOutToPlayheadTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->MoveOutToPlayhead(); +} + void MainMenu::ActionSearchTriggered() { ActionSearch as(parentWidget()); @@ -677,6 +689,8 @@ void MainMenu::Retranslate() edit_edit_to_out_item_->setText(tr("Edit to Out Point")); edit_nudge_left_item_->setText(tr("Nudge Left")); edit_nudge_right_item_->setText(tr("Nudge Right")); + edit_move_in_to_playhead_item_->setText(tr("Move In Point to Playhead")); + edit_move_out_to_playhead_item_->setText(tr("Move Out Point to Playhead")); edit_delete_inout_item_->setText(tr("Delete In/Out Point")); edit_ripple_delete_inout_item_->setText(tr("Ripple Delete In/Out Point")); edit_set_marker_item_->setText(tr("Set/Edit Marker")); diff --git a/app/window/mainwindow/mainmenu.h b/app/window/mainwindow/mainmenu.h index baa864b9b..4cedd3522 100644 --- a/app/window/mainwindow/mainmenu.h +++ b/app/window/mainwindow/mainmenu.h @@ -156,6 +156,8 @@ private slots: void NudgeLeftTriggered(); void NudgeRightTriggered(); + void MoveInToPlayheadTriggered(); + void MoveOutToPlayheadTriggered(); void ActionSearchTriggered(); @@ -223,6 +225,8 @@ private: QAction* edit_edit_to_out_item_; QAction* edit_nudge_left_item_; QAction* edit_nudge_right_item_; + QAction* edit_move_in_to_playhead_item_; + QAction* edit_move_out_to_playhead_item_; QAction* edit_delete_inout_item_; QAction* edit_ripple_delete_inout_item_; QAction* edit_set_marker_item_; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 7f49a1e89..fec8e5453 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -102,15 +102,15 @@ MainWindow::MainWindow(QWidget *parent) : connect(param_panel_, &ParamPanel::FocusedNodeChanged, sequence_viewer_panel_, &ViewerPanel::SetGizmos); // Connect time signals together - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTimestamp); - connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); - connect(param_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); - connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTimestamp); - connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); - connect(curve_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); - connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTimestamp); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTime); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTime); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); + connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); + connect(param_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTime); + connect(param_panel_, &ParamPanel::TimeChanged, curve_panel_, &NodeTablePanel::SetTime); + connect(curve_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); + connect(curve_panel_, &ParamPanel::TimeChanged, table_panel_, &NodeTablePanel::SetTime); + connect(curve_panel_, &ParamPanel::TimeChanged, param_panel_, &NodeTablePanel::SetTime); // Connect node order signals connect(param_panel_, &ParamPanel::NodeOrderChanged, curve_panel_, &CurvePanel::SetNodes); @@ -564,14 +564,14 @@ TimelinePanel* MainWindow::AppendTimelinePanel() TimelinePanel* panel = AppendPanelInternal(timeline_panels_); connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested); - connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTimestamp); - connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); - connect(panel, &TimelinePanel::TimeChanged, table_panel_, &NodeTablePanel::SetTimestamp); - connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); + connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &ParamPanel::SetTime); + connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); + connect(panel, &TimelinePanel::TimeChanged, table_panel_, &NodeTablePanel::SetTime); + connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(panel, &TimelinePanel::BlockSelectionChanged, this, &MainWindow::TimelinePanelSelectionChanged); - connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); - connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); - connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTimestamp); + connect(param_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); + connect(curve_panel_, &ParamPanel::TimeChanged, panel, &TimelinePanel::SetTime); + connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, panel, &TimelinePanel::SetTime); sequence_viewer_panel_->ConnectTimeBasedPanel(panel);