diff --git a/app/dialog/speedduration/speeddurationdialog.cpp b/app/dialog/speedduration/speeddurationdialog.cpp index be2bc38b6..08a930c6e 100644 --- a/app/dialog/speedduration/speeddurationdialog.cpp +++ b/app/dialog/speedduration/speeddurationdialog.cpp @@ -23,18 +23,28 @@ #include #include +#include "core.h" + namespace olive { -SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, QWidget *parent) : - QDialog(parent) +#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); - FloatSlider *speed_slider_ = new FloatSlider(); + speed_slider_ = new FloatSlider(); + speed_slider_->SetDisplayType(FloatSlider::kPercentage); + connect(speed_slider_, &FloatSlider::ValueChanged, this, &SpeedDurationDialog::SpeedChanged); layout->addWidget(speed_slider_, row, 1); row++; @@ -42,11 +52,20 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, QWid 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++; - ripple_box_ = new QCheckBox(); + 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++; @@ -56,6 +75,119 @@ SpeedDurationDialog::SpeedDurationDialog(const QVector &clips, QWid 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() +{ + super::accept(); + + 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 SetSpeedCommand(c, GetSpeedAdjustment(c->speed(), c->length(), dur_slider_->GetValue()))); + } + } + } else { + // Set speeds to value of slider + foreach (ClipBlock *c, clips_) { + command->add_child(new SetSpeedCommand(c, speed_slider_->GetValue())); + } + } + + // Set duration values + if (ripple_box_->isChecked()) { + // Determine where and how much we need to ripple + foreach (ClipBlock *c, clips_) { + rational new_len = c->length(); + if (dur_slider_->IsTristate()) { + if (link_box_->isChecked() && !speed_slider_->IsTristate()) { + new_len = GetLengthAdjustment(c->length(), c->speed(), speed_slider_->GetValue(), timebase_); + } + } else { + new_len = dur_slider_->GetValue(); + } + } + } + + + Core::instance()->undo_stack()->push(command); +} + +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)); + } +} + +void SpeedDurationDialog::SetSpeedCommand::redo() +{ + old_speed_ = clip_->speed(); + clip_->set_speed(new_speed_); +} + +void SpeedDurationDialog::SetSpeedCommand::undo() +{ + clip_->set_speed(old_speed_); } } diff --git a/app/dialog/speedduration/speeddurationdialog.h b/app/dialog/speedduration/speeddurationdialog.h index aadfee411..b6428778b 100644 --- a/app/dialog/speedduration/speeddurationdialog.h +++ b/app/dialog/speedduration/speeddurationdialog.h @@ -25,6 +25,7 @@ #include #include "node/block/clip/clip.h" +#include "undo/undocommand.h" #include "widget/slider/floatslider.h" #include "widget/slider/rationalslider.h" @@ -34,17 +35,63 @@ class SpeedDurationDialog : public QDialog { Q_OBJECT public: - explicit SpeedDurationDialog(const QVector &clips, QWidget *parent = nullptr); + explicit SpeedDurationDialog(const QVector &clips, const rational &timebase, QWidget *parent = nullptr); + + class SetSpeedCommand : public UndoCommand + { + public: + SetSpeedCommand(ClipBlock *clip, double new_speed) : + clip_(clip), + new_speed_(new_speed) + {} + + virtual void redo() override; + + virtual void undo() override; + + virtual Project *GetRelevantProject() const override + { + return clip_->project(); + } + + private: + ClipBlock *clip_; + double new_speed_; + double old_speed_; + + }; + +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); + }; } diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 63a83fcff..03e77bfa0 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -92,6 +92,11 @@ public: virtual void NudgeRight() override; + void ShowSpeedDurationDialogForSelectedClips() + { + timeline_widget()->ShowSpeedDurationDialogForSelectedClips(); + } + void InsertFootageAtPlayhead(const QVector &footage); void OverwriteFootageAtPlayhead(const QVector &footage); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index 79623abbe..7b0506a45 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -22,7 +22,6 @@ #include "core.h" #include "common/timecodefunctions.h" -#include "dialog/speedduration/speeddurationdialog.h" #include "panel/panelmanager.h" #include "panel/timeline/timeline.h" #include "window/mainwindow/mainwindow.h" @@ -47,7 +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); + 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"); @@ -310,18 +309,7 @@ void MenuShared::SpeedDurationTriggered() TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); if (timeline) { - QVector sel = timeline->GetSelectedBlocks(); - QVector clips; - - foreach (Block *b, sel) { - ClipBlock *c = dynamic_cast(b); - if (c) { - clips.append(c); - } - } - - SpeedDurationDialog sdd(clips, Core::instance()->main_window()); - sdd.exec(); + timeline->ShowSpeedDurationDialogForSelectedClips(); } } diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 8dd77583f..8a6978e97 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" @@ -738,6 +739,23 @@ void TimelineWidget::NudgeRight() } } +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;i