/***
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 .
***/
#include "timebased.h"
#include
#include
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "core.h"
#include "project/item/sequence/sequence.h"
#include "widget/timelinewidget/undo/undo.h"
OLIVE_NAMESPACE_ENTER
TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_status_visible, QWidget *parent) :
TimelineScaledWidget(parent),
viewer_node_(nullptr),
auto_max_scrollbar_(false),
points_(nullptr),
toggle_show_all_(false),
auto_set_timebase_(true)
{
ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this);
connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal);
scrollbar_ = new ResizableScrollBar(Qt::Horizontal, this);
connect(scrollbar_, &ResizableScrollBar::RequestScale, this, &TimeBasedWidget::ScrollBarResized);
}
void TimeBasedWidget::SetScaleAndCenterOnPlayhead(const double &scale)
{
SetScale(scale);
// Zoom towards the playhead
// (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated)
QTimer::singleShot(0, this, &TimeBasedWidget::CenterScrollOnPlayhead);
}
rational TimeBasedWidget::GetTime() const
{
return Timecode::timestamp_to_time(ruler()->GetTime(), timebase());
}
const int64_t &TimeBasedWidget::GetTimestamp() const
{
return ruler_->GetTime();
}
ViewerOutput *TimeBasedWidget::GetConnectedNode() const
{
return viewer_node_;
}
void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
{
if (viewer_node_ == node) {
return;
}
if (viewer_node_) {
DisconnectNodeInternal(viewer_node_);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
disconnect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase);
if (auto_set_timebase_) {
SetTimebase(rational());
}
points_ = nullptr;
ruler()->ConnectTimelinePoints(nullptr);
}
viewer_node_ = node;
ConnectedNodeChanged(viewer_node_);
if (viewer_node_) {
connect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
if ((points_ = ConnectTimelinePoints())) {
ruler()->ConnectTimelinePoints(points_);
}
if (auto_set_timebase_) {
if (!viewer_node_->video_params().time_base().isNull()) {
SetTimebase(viewer_node_->video_params().time_base());
} else if (viewer_node_->audio_params().sample_rate() > 0) {
SetTimebase(viewer_node_->audio_params().time_base());
} else {
SetTimebase(rational());
}
connect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase);
}
ConnectNodeInternal(viewer_node_);
}
UpdateMaximumScroll();
}
void TimeBasedWidget::UpdateMaximumScroll()
{
rational length = (viewer_node_) ? viewer_node_->GetLength() : rational();
if (auto_max_scrollbar_) {
scrollbar_->setMaximum(qMax(0, qCeil(TimeToScene(length)) - width()));
}
foreach (TimelineViewBase* base, timeline_views_) {
base->SetEndTime(length);
}
}
void TimeBasedWidget::ScrollBarResized(const double &multiplier)
{
QScrollBar* bar = static_cast(sender());
// Our extension area (represented by a TimelineViewEndItem) is NOT scaled, but the ResizableScrollBar doesn't know
// this. Here we re-calculate the requested scale knowing that the end item is not affected by scale.
int current_max = bar->maximum();
double proposed_max = static_cast(current_max) * multiplier;
proposed_max = proposed_max - (bar->width() * 0.5 / multiplier) + (bar->width() * 0.5);
double corrected_scale;
if (current_max == 0) {
corrected_scale = multiplier;
} else {
corrected_scale = (proposed_max / static_cast(current_max));
}
SetScale(GetScale() * corrected_scale);
}
TimeRuler *TimeBasedWidget::ruler() const
{
return ruler_;
}
ResizableScrollBar *TimeBasedWidget::scrollbar() const
{
return scrollbar_;
}
void TimeBasedWidget::TimebaseChangedEvent(const rational &timebase)
{
TimelineScaledWidget::TimebaseChangedEvent(timebase);
ruler_->SetTimebase(timebase);
emit TimebaseChanged(timebase);
}
void TimeBasedWidget::ScaleChangedEvent(const double &scale)
{
TimelineScaledWidget::ScaleChangedEvent(scale);
ruler_->SetScale(scale);
UpdateMaximumScroll();
toggle_show_all_ = false;
}
void TimeBasedWidget::SetAutoMaxScrollBar(bool e)
{
auto_max_scrollbar_ = e;
}
void TimeBasedWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
// Update horizontal scrollbar's page step to the width of the panel
scrollbar()->setPageStep(scrollbar()->width());
UpdateMaximumScroll();
}
TimelinePoints *TimeBasedWidget::ConnectTimelinePoints()
{
return static_cast(viewer_node_->parent());
}
Project *TimeBasedWidget::GetTimelinePointsProject()
{
return static_cast(viewer_node_->parent())->project();
}
TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const
{
return points_;
}
void TimeBasedWidget::ConnectTimelineView(TimelineViewBase *base)
{
timeline_views_.append(base);
}
void TimeBasedWidget::SetTimestamp(int64_t timestamp)
{
ruler_->SetTime(timestamp);
TimeChangedEvent(timestamp);
}
void TimeBasedWidget::SetTimebase(const rational &timebase)
{
TimelineScaledWidget::SetTimebase(timebase);
}
void TimeBasedWidget::SetScale(const double &scale)
{
// Simple QObject slot wrapper around TimelineScaledWidget::SetScale()
TimelineScaledWidget::SetScale(scale);
}
void TimeBasedWidget::ZoomIn()
{
SetScaleAndCenterOnPlayhead(GetScale() * 2);
}
void TimeBasedWidget::ZoomOut()
{
SetScaleAndCenterOnPlayhead(GetScale() * 0.5);
}
void TimeBasedWidget::GoToPrevCut()
{
if (!GetConnectedNode()) {
return;
}
if (GetTimestamp() == 0) {
return;
}
int64_t closest_cut = 0;
foreach (TrackOutput* track, viewer_node_->GetTracks()) {
int64_t 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;
} else {
break;
}
}
closest_cut = qMax(closest_cut, this_track_closest_cut);
}
SetTimeAndSignal(closest_cut);
}
void TimeBasedWidget::GoToNextCut()
{
if (!GetConnectedNode()) {
return;
}
int64_t closest_cut = INT64_MAX;
foreach (TrackOutput* track, GetConnectedNode()->GetTracks()) {
int64_t this_track_closest_cut = Timecode::time_to_timestamp(track->track_length(), timebase());
if (this_track_closest_cut <= GetTimestamp()) {
this_track_closest_cut = INT64_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;
break;
}
}
closest_cut = qMin(closest_cut, this_track_closest_cut);
}
if (closest_cut < INT64_MAX) {
SetTimeAndSignal(closest_cut);
}
}
void TimeBasedWidget::GoToStart()
{
if (viewer_node_) {
SetTimeAndSignal(0);
}
}
void TimeBasedWidget::PrevFrame()
{
if (viewer_node_) {
SetTimeAndSignal(qMax(static_cast(0), ruler()->GetTime() - 1));
}
}
void TimeBasedWidget::NextFrame()
{
if (viewer_node_) {
SetTimeAndSignal(ruler()->GetTime() + 1);
}
}
void TimeBasedWidget::GoToEnd()
{
if (viewer_node_) {
SetTimeAndSignal(Timecode::time_to_timestamp(viewer_node_->GetLength(), timebase()));
}
}
void TimeBasedWidget::SetTimeAndSignal(const int64_t &t)
{
SetTimestamp(t);
emit TimeChanged(t);
}
void TimeBasedWidget::CenterScrollOnPlayhead()
{
scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2);
}
void TimeBasedWidget::SetAutoSetTimebase(bool e)
{
auto_set_timebase_ = e;
}
void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time)
{
if (!points_) {
return;
}
QUndoCommand* command = new QUndoCommand();
// Enable workarea if it isn't already enabled
if (!points_->workarea()->enabled()) {
new WorkareaSetEnabledCommand(GetTimelinePointsProject(), points_, true, command);
}
// Determine our new range
rational in_point, out_point;
if (m == Timeline::kTrimIn) {
in_point = time;
if (!points_->workarea()->enabled() || points_->workarea()->out() < in_point) {
out_point = TimelineWorkArea::kResetOut;
} else {
out_point = points_->workarea()->out();
}
} else {
out_point = time;
if (!points_->workarea()->enabled() || points_->workarea()->in() > out_point) {
in_point = TimelineWorkArea::kResetIn;
} else {
in_point = points_->workarea()->in();
}
}
// Set workarea
new WorkareaSetRangeCommand(GetTimelinePointsProject(), points_, TimeRange(in_point, out_point), command);
Core::instance()->undo_stack()->push(command);
}
void TimeBasedWidget::ResetPoint(Timeline::MovementMode m)
{
if (!points_ || !points_->workarea()->enabled()) {
return;
}
TimeRange r = points_->workarea()->range();
if (m == Timeline::kTrimIn) {
r.set_in(TimelineWorkArea::kResetIn);
} else {
r.set_out(TimelineWorkArea::kResetOut);
}
Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(GetTimelinePointsProject(), points_, r));
}
void TimeBasedWidget::SetInAtPlayhead()
{
SetPoint(Timeline::kTrimIn, GetTime());
}
void TimeBasedWidget::SetOutAtPlayhead()
{
SetPoint(Timeline::kTrimOut, GetTime());
}
void TimeBasedWidget::ResetIn()
{
ResetPoint(Timeline::kTrimIn);
}
void TimeBasedWidget::ResetOut()
{
ResetPoint(Timeline::kTrimOut);
}
void TimeBasedWidget::ClearInOutPoints()
{
if (!points_) {
return;
}
Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(GetTimelinePointsProject(), points_, false));
}
void TimeBasedWidget::SetMarker()
{
if (!points_) {
return;
}
bool ok;
QString marker_name;
if (Config::Current()["SetNameWithMarker"].toBool()) {
marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok);
} else {
ok = true;
}
if (ok) {
points_->markers()->AddMarker(TimeRange(GetTime(), GetTime()), marker_name);
}
}
void TimeBasedWidget::ToggleShowAll()
{
if (!GetConnectedNode()) {
return;
}
if (toggle_show_all_) {
SetScale(toggle_show_all_old_scale_);
scrollbar_->setValue(toggle_show_all_old_scroll_);
// Don't have to set toggle_show_all_ because SetScale() will automatically set it to false
} else {
int w;
if (timeline_views_.isEmpty()) {
w = width();
} else {
w = timeline_views_.first()->width();
}
toggle_show_all_old_scale_ = GetScale();
toggle_show_all_old_scroll_ = scrollbar_->value();
SetScaleFromDimensions(w, GetConnectedNode()->GetLength().toDouble());
scrollbar_->setValue(0);
// Must explicitly do this because SetScale() will automatically set this to false
toggle_show_all_ = true;
}
}
void TimeBasedWidget::GoToIn()
{
if (viewer_node_) {
if (points_ && points_->workarea()->enabled()) {
SetTimeAndSignal(Timecode::time_to_timestamp(points_->workarea()->in(), timebase()));
} else {
GoToStart();
}
}
}
void TimeBasedWidget::GoToOut()
{
if (viewer_node_) {
if (points_ && points_->workarea()->enabled()) {
SetTimeAndSignal(Timecode::time_to_timestamp(points_->workarea()->out(), timebase()));
} else {
GoToEnd();
}
}
}
OLIVE_NAMESPACE_EXIT