Files
oak-editor/app/dialog/speedduration/speedduration.cpp
T
itsmattkc f73c41d3fa various: implemented multiple project support
This required various changes to various parts of the infrastructure (mostly
using paths to finding the "root project" of any given object throughout).
Now theoretically infinite projects can be opened and accounted for at any
given time.
2020-04-18 01:50:47 +10:00

380 lines
12 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
***/
#include "speedduration.h"
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QVBoxLayout>
#include "core.h"
#include "common/timecodefunctions.h"
#include "widget/nodeview/nodeviewundo.h"
#include "widget/timelinewidget/undo/undo.h"
OLIVE_NAMESPACE_ENTER
SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QList<ClipBlock *> &clips, QWidget *parent) :
QDialog(parent),
clips_(clips),
timebase_(timebase)
{
setWindowTitle(tr("Speed/Duration"));
QVBoxLayout* layout = new QVBoxLayout(this);
{
// Create groupbox for the speed/duration
QGroupBox* speed_groupbox = new QGroupBox(tr("Speed/Duration"));
layout->addWidget(speed_groupbox);
QGridLayout* speed_layout = new QGridLayout(speed_groupbox);
int row = 0;
// For any other clips that are selected, determine if they share speeds and lengths. If they don't, the UI can't
// show them all as having the same parameters
bool same_speed = true;
bool same_duration = true;
bool all_reversed = true;
for (int i=1;i<clips.size();i++) {
ClipBlock* prev_clip = clips_.at(i-1);
ClipBlock* this_clip = clips_.at(i);
// Check if the speeds are different
if (same_speed
&& prev_clip->speed() == this_clip->speed()) {
same_speed = false;
}
// Check if the durations are different
if (same_duration
&& prev_clip->length() != this_clip->length()) {
same_duration = false;
}
// Check if all are reversed
if (all_reversed
&& prev_clip->is_reversed() != this_clip->is_reversed()) {
all_reversed = false;
}
// If we've already determined both are different, no need to continue
if (!same_speed
&& !same_duration
&& !all_reversed) {
break;
}
}
speed_layout->addWidget(new QLabel(tr("Speed:")), row, 0);
// Create "Speed" slider
speed_slider_ = new FloatSlider();
speed_slider_->SetMinimum(0);
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
speed_layout->addWidget(speed_slider_, row, 1);
if (same_speed) {
// All clips share the same speed so we can show the value
speed_slider_->SetValue(clips_.first()->speed().toDouble());
} else {
// Else, we show an invalid initial state
speed_slider_->SetTristate();
}
row++;
speed_layout->addWidget(new QLabel(tr("Duration:")), row, 0);
// Create "Duration" slider
duration_slider_ = new TimeSlider();
duration_slider_->SetTimebase(timebase_);
duration_slider_->SetMinimum(1);
speed_layout->addWidget(duration_slider_, row, 1);
if (same_duration) {
duration_slider_->SetValue(Timecode::time_to_timestamp(clips_.first()->length(), timebase_));
} else {
duration_slider_->SetTristate();
}
row++;
link_speed_and_duration_ = new QCheckBox(tr("Link Speed and Duration"));
link_speed_and_duration_->setChecked(true);
speed_layout->addWidget(link_speed_and_duration_, row, 0, 1, 2);
// Pick up when the speed or duration slider changes so we can programmatically link them
connect(speed_slider_, SIGNAL(ValueChanged(double)), this, SLOT(SpeedChanged()));
connect(duration_slider_, SIGNAL(ValueChanged(int64_t)), this, SLOT(DurationChanged()));
reverse_speed_checkbox_ = new QCheckBox(tr("Reverse Speed"));
if (all_reversed) {
reverse_speed_checkbox_->setChecked(clips_.first()->is_reversed());
} else {
reverse_speed_checkbox_->setTristate();
}
layout->addWidget(reverse_speed_checkbox_);
}
maintain_audio_pitch_checkbox_ = new QCheckBox(tr("Maintain Audio Pitch"));
layout->addWidget(maintain_audio_pitch_checkbox_);
ripple_clips_checkbox_ = new QCheckBox(tr("Ripple Clips"));
layout->addWidget(ripple_clips_checkbox_);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons->setCenterButtons(true);
layout->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
}
void SpeedDurationDialog::accept()
{
if (duration_slider_->IsTristate() && speed_slider_->IsTristate()) {
// Nothing to be done
QDialog::accept();
return;
}
QUndoCommand* command = new QUndoCommand();
foreach (ClipBlock* clip, clips_) {
bool change_duration = !duration_slider_->IsTristate() || link_speed_and_duration_->isChecked();
bool change_speed = !speed_slider_->IsTristate() || link_speed_and_duration_->isChecked();
double new_speed = speed_slider_->GetValue();
rational new_clip_length = clip->length();
if (change_duration) {
// Change the duration
int64_t current_duration = Timecode::time_to_timestamp(clip->length(), timebase_);
int64_t new_duration = current_duration;
// Check if we're getting the duration value directly from the slider or calculating it from the speed
if (duration_slider_->IsTristate()) {
// Calculate duration from speed
new_duration = GetAdjustedDuration(clip, new_speed);
} else {
// Get duration directly from slider
new_duration = duration_slider_->GetValue();
// Check if we're calculating the speed from this duration
if (speed_slider_->IsTristate() && change_speed) {
// If we're here, the duration is going to override the speed
new_speed = GetAdjustedSpeed(clip, new_duration);
}
}
if (new_duration != current_duration) {
new_clip_length = Timecode::timestamp_to_time(new_duration, timebase_);
Block* next_block = clip->next();
// If "ripple clips" isn't checked, we need to calculate around the timeline as-is
if (!ripple_clips_checkbox_->isChecked()
&& next_block) {
if (new_clip_length > clip->length()) {
// Check if next clip is a gap, and if so we can take it all up
if (next_block->type() == Block::kGap) {
new_clip_length = qMin(next_block->out(), clip->in() + new_clip_length);
// If we're taking up the entire clip, we'll just remove it
if (new_clip_length == next_block->out()) {
new TrackRippleRemoveBlockCommand(TrackOutput::TrackFromBlock(next_block), next_block, command);
// Delete node and its exclusive deps
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(next_block->parent()), next_block, command);
} else {
// Otherwise we can just resize it
new BlockResizeCommand(next_block, next_block->out() - new_clip_length, command);
}
} else {
// Otherwise we can't extend any further
new_clip_length = clip->length();
}
} else if (new_clip_length < clip->length()) {
// If we're not rippling these clips, we'll need to insert a gap (unless the clip is already at the end)
rational gap_length = clip->length() - new_clip_length;
if (next_block->type() == Block::kGap) {
// If we've already got a gap here, we can just resize it
new BlockResizeCommand(next_block, next_block->length() + gap_length, command);
} else {
// Otherwise we have to create a new gap
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(gap_length);
new NodeAddCommand(static_cast<NodeGraph*>(clip->parent()), gap, command);
new TrackInsertBlockAfterCommand(TrackOutput::TrackFromBlock(clip), gap, clip, command);
}
}
}
if (new_clip_length != clip->length()) {
new BlockResizeCommand(clip, new_clip_length, command);
}
}
}
if (change_speed) {
rational new_block_speed = rational::fromDouble(new_speed);
if (clip->is_reversed()) {
new_block_speed = -new_block_speed;
}
// Change the speed by calculating the appropriate media out point for this clip
new BlockSetSpeedCommand(clip, new_block_speed, command);
}
if (!reverse_speed_checkbox_->isTristate() && clip->is_reversed() != reverse_speed_checkbox_->isChecked()) {
new BlockReverseCommand(clip, command);
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
QDialog::accept();
}
double SpeedDurationDialog::GetUnadjustedLengthTimestamp(ClipBlock *clip) const
{
double duration = static_cast<double>(Timecode::time_to_timestamp(clip->length(), timebase_));
// Convert duration to non-speed adjusted duration
duration *= clip->speed().toDouble();
return duration;
}
int64_t SpeedDurationDialog::GetAdjustedDuration(ClipBlock *clip, const double &new_speed) const
{
double duration = GetUnadjustedLengthTimestamp(clip);
// Re-adjust by new speed
duration /= new_speed;
// Return rounded time
return qRound64(duration);
}
double SpeedDurationDialog::GetAdjustedSpeed(ClipBlock *clip, const int64_t &new_duration) const
{
double duration = GetUnadjustedLengthTimestamp(clip);
// Create a fraction of the original duration over the new duration
duration /= static_cast<double>(new_duration);
return duration;
}
void SpeedDurationDialog::SpeedChanged()
{
if (link_speed_and_duration_->isChecked()) {
double new_speed = speed_slider_->GetValue();
if (qIsNull(new_speed)) {
// A speed of 0 is considered a still frame. Since we can't divide by zero and a still frame could be any length,
// we don't bother updating the
return;
}
bool same_durations = true;
int64_t new_duration = GetAdjustedDuration(clips_.first(), new_speed);
for (int i=1;i<clips_.size();i++) {
// Calculate what this clip's duration will be
int64_t this_clip_duration = GetAdjustedDuration(clips_.at(i), new_speed);
// If they won't be the same, we won't show the value
if (this_clip_duration != new_duration) {
same_durations = false;
break;
}
}
if (same_durations) {
duration_slider_->SetValue(new_duration);
} else {
duration_slider_->SetTristate();
}
}
}
void SpeedDurationDialog::DurationChanged()
{
if (link_speed_and_duration_->isChecked()) {
int64_t new_duration = duration_slider_->GetValue();
bool same_speeds = true;
double new_speed = GetAdjustedSpeed(clips_.first(), new_duration);
for (int i=1;i<clips_.size();i++) {
double this_clip_speed = GetAdjustedSpeed(clips_.at(i), new_duration);
if (!qFuzzyCompare(this_clip_speed, new_speed)) {
same_speeds = false;
break;
}
}
if (same_speeds) {
speed_slider_->SetValue(new_speed);
} else {
speed_slider_->SetTristate();
}
}
}
BlockReverseCommand::BlockReverseCommand(Block *block, QUndoCommand *parent) :
UndoCommand(parent),
block_(block)
{
}
Project *BlockReverseCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(block_->parent())->project();
}
void BlockReverseCommand::redo_internal()
{
block_->set_media_in(block_->media_out());
block_->set_speed(-block_->speed());
}
void BlockReverseCommand::undo_internal()
{
redo_internal();
}
OLIVE_NAMESPACE_EXIT