Files
oak-editor/app/widget/timebased/timebased.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

493 lines
11 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 "timebased.h"
#include <QInputDialog>
#include <QUndoCommand>
#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)
{
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);
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_);
}
ConnectNodeInternal(viewer_node_);
}
UpdateMaximumScroll();
}
void TimeBasedWidget::UpdateMaximumScroll()
{
rational length = (viewer_node_) ? viewer_node_->Length() : rational();
if (auto_max_scrollbar_) {
scrollbar_->setMaximum(qMax(0, qCeil(TimeToScene(length)) - width()));
}
foreach (TimelineViewBase* base, timeline_views_) {
base->SetEndTime(length);
}
ruler()->SetCacheStatusLength(length);
}
void TimeBasedWidget::ScrollBarResized(const double &multiplier)
{
QScrollBar* bar = static_cast<QScrollBar*>(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<double>(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<double>(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<Sequence*>(viewer_node_->parent());
}
Project *TimeBasedWidget::GetTimelinePointsProject()
{
return static_cast<Sequence*>(viewer_node_->parent())->project();
}
TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const
{
return points_;
}
void TimeBasedWidget::ConnectTimelineView(TimelineViewBase *base)
{
timeline_views_.append(base);
}
void TimeBasedWidget::SetTime(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_->Tracks()) {
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()->Tracks()) {
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<int64_t>(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_->Length(), timebase()));
}
}
void TimeBasedWidget::SetTimeAndSignal(const int64_t &t)
{
SetTime(t);
emit TimeChanged(t);
}
void TimeBasedWidget::CenterScrollOnPlayhead()
{
scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2);
}
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_);
toggle_show_all_ = false;
} else {
int w;
if (timeline_views_.isEmpty()) {
w = width();
} else {
w = timeline_views_.first()->width();
}
w = w / 10 * 9;
toggle_show_all_old_scale_ = GetScale();
SetScale(w / GetConnectedNode()->Length().toDouble());
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