moved some files around for better organization
This commit is contained in:
@@ -0,0 +1,594 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2020 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 "timebasedwidget.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "common/autoscroll.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "project/item/sequence/sequence.h"
|
||||
#include "widget/timelinewidget/undo/undo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
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 ResizableTimelineScrollBar(Qt::Horizontal, this);
|
||||
connect(scrollbar_, &ResizableScrollBar::RequestScale, this, &TimeBasedWidget::ScrollBarResized);
|
||||
|
||||
PassWheelEventsToScrollBar(ruler_);
|
||||
}
|
||||
|
||||
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);
|
||||
scrollbar_->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_);
|
||||
scrollbar_->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 (TimeBasedView* base, timeline_views_) {
|
||||
base->SetEndTime(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);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::PageScrollToPlayhead()
|
||||
{
|
||||
int playhead_pos = qRound(TimeToScene(GetTime()));
|
||||
|
||||
int viewport_width = ruler()->width();
|
||||
int viewport_padding = viewport_width / 16;
|
||||
|
||||
if (playhead_pos < scrollbar()->value()) {
|
||||
// Anchor the playhead to the RIGHT of where we scroll to
|
||||
scrollbar()->setValue(playhead_pos - viewport_width + viewport_padding);
|
||||
} else if (playhead_pos > scrollbar()->value() + viewport_width) {
|
||||
// Anchor the playhead to the LEFT of where we scroll to
|
||||
scrollbar()->setValue(playhead_pos - viewport_padding);
|
||||
}
|
||||
}
|
||||
|
||||
TimeRuler *TimeBasedWidget::ruler() const
|
||||
{
|
||||
return ruler_;
|
||||
}
|
||||
|
||||
ResizableTimelineScrollBar *TimeBasedWidget::scrollbar() const
|
||||
{
|
||||
return scrollbar_;
|
||||
}
|
||||
|
||||
void TimeBasedWidget::TimebaseChangedEvent(const rational &timebase)
|
||||
{
|
||||
TimelineScaledWidget::TimebaseChangedEvent(timebase);
|
||||
|
||||
ruler_->SetTimebase(timebase);
|
||||
scrollbar_->SetTimebase(timebase);
|
||||
|
||||
emit TimebaseChanged(timebase);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
TimelineScaledWidget::ScaleChangedEvent(scale);
|
||||
|
||||
ruler_->SetScale(scale);
|
||||
scrollbar_->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(TimeBasedView *base)
|
||||
{
|
||||
timeline_views_.append(base);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::PassWheelEventsToScrollBar(QObject *object)
|
||||
{
|
||||
wheel_passthrough_objects_.append(object);
|
||||
object->installEventFilter(this);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::SetTimestamp(int64_t timestamp)
|
||||
{
|
||||
ruler_->SetTime(timestamp);
|
||||
|
||||
switch (static_cast<AutoScroll::Method>(Config::Current()["Autoscroll"].toInt())) {
|
||||
case AutoScroll::kNone:
|
||||
// Do nothing
|
||||
break;
|
||||
case AutoScroll::kPage:
|
||||
QMetaObject::invokeMethod(this, "PageScrollToPlayhead", Qt::QueuedConnection);
|
||||
break;
|
||||
case AutoScroll::kSmooth:
|
||||
QMetaObject::invokeMethod(this, "CenterScrollOnPlayhead", Qt::QueuedConnection);
|
||||
break;
|
||||
}
|
||||
|
||||
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<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_->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) {
|
||||
Core::instance()->undo_stack()->push(new MarkerAddCommand(static_cast<Sequence*>(GetConnectedNode()->parent())->project(),
|
||||
points_->markers(), 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeBasedWidget::MarkerAddCommand::MarkerAddCommand(Project *project, TimelineMarkerList *marker_list, const TimeRange &range, const QString &name) :
|
||||
project_(project),
|
||||
marker_list_(marker_list),
|
||||
range_(range),
|
||||
name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
Project *TimeBasedWidget::MarkerAddCommand::GetRelevantProject() const
|
||||
{
|
||||
return project_;
|
||||
}
|
||||
|
||||
void TimeBasedWidget::MarkerAddCommand::redo_internal()
|
||||
{
|
||||
added_marker_ = marker_list_->AddMarker(range_, name_);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::MarkerAddCommand::undo_internal()
|
||||
{
|
||||
marker_list_->RemoveMarker(added_marker_);
|
||||
}
|
||||
|
||||
bool TimeBasedWidget::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if (wheel_passthrough_objects_.contains(object) && event->type() == QEvent::Wheel) {
|
||||
QCoreApplication::sendEvent(scrollbar(), event);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user