|
|
|
@@ -31,6 +31,7 @@
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QInputDialog>
|
|
|
|
|
#include <QProcess>
|
|
|
|
|
#include <QSlider>
|
|
|
|
|
#include <QSplitter>
|
|
|
|
|
#include <QUrl>
|
|
|
|
|
#include <QVBoxLayout>
|
|
|
|
@@ -80,6 +81,7 @@
|
|
|
|
|
#include "widget/menu/menushared.h"
|
|
|
|
|
#include "widget/nodeparamview/nodeparamview.h"
|
|
|
|
|
#include "widget/timeruler/timeruler.h"
|
|
|
|
|
#include "widget/toolbar/toolbar.h"
|
|
|
|
|
|
|
|
|
|
#include "widget/viewer/vieweroutpututils.h"
|
|
|
|
|
namespace olive
|
|
|
|
@@ -159,6 +161,26 @@ QVector<Footage *> get_selected_proxy_footage(const QVector<Block *> &blocks)
|
|
|
|
|
return footage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Zoom slider <-> scale mapping (logarithmic: slider 0..1000 -> 0.1..10000 px/s)
|
|
|
|
|
constexpr double k_zoom_scale_min = 0.1;
|
|
|
|
|
constexpr double k_zoom_scale_max = 10000.0;
|
|
|
|
|
|
|
|
|
|
double zoom_slider_to_scale(int v)
|
|
|
|
|
{
|
|
|
|
|
return k_zoom_scale_min *
|
|
|
|
|
std::pow(k_zoom_scale_max / k_zoom_scale_min, v / 1000.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int scale_to_zoom_slider(double s)
|
|
|
|
|
{
|
|
|
|
|
if (s <= k_zoom_scale_min)
|
|
|
|
|
return 0;
|
|
|
|
|
if (s >= k_zoom_scale_max)
|
|
|
|
|
return 1000;
|
|
|
|
|
return qRound(1000.0 * std::log(s / k_zoom_scale_min) /
|
|
|
|
|
std::log(k_zoom_scale_max / k_zoom_scale_min));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
TimelineWidget::TimelineWidget(QWidget *parent)
|
|
|
|
@@ -173,6 +195,66 @@ TimelineWidget::TimelineWidget(QWidget *parent)
|
|
|
|
|
vert_layout->setSpacing(0);
|
|
|
|
|
vert_layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
// Application toolbar row (31px) — replaces the dockable ToolPanel by
|
|
|
|
|
// default. The dockable ToolPanel remains available via the Window menu.
|
|
|
|
|
QHBoxLayout *toolbar_row = new QHBoxLayout();
|
|
|
|
|
toolbar_row->setContentsMargins(0, 0, 0, 0);
|
|
|
|
|
toolbar_row->setSpacing(4);
|
|
|
|
|
|
|
|
|
|
Toolbar *toolbar = new Toolbar(this);
|
|
|
|
|
toolbar->setFixedHeight(31);
|
|
|
|
|
toolbar->set_tool(Core::instance()->tool());
|
|
|
|
|
toolbar->set_snapping(Core::instance()->snapping());
|
|
|
|
|
toolbar_row->addWidget(toolbar);
|
|
|
|
|
|
|
|
|
|
connect(toolbar, &Toolbar::tool_changed, Core::instance(),
|
|
|
|
|
&Core::set_tool);
|
|
|
|
|
connect(Core::instance(), &Core::tool_changed, toolbar,
|
|
|
|
|
&Toolbar::set_tool);
|
|
|
|
|
connect(toolbar, &Toolbar::snapping_changed, Core::instance(),
|
|
|
|
|
&Core::set_snapping);
|
|
|
|
|
connect(Core::instance(), &Core::snapping_changed, toolbar,
|
|
|
|
|
&Toolbar::set_snapping);
|
|
|
|
|
connect(toolbar, &Toolbar::selected_transition_changed, Core::instance(),
|
|
|
|
|
&Core::set_selected_transition_object);
|
|
|
|
|
|
|
|
|
|
toolbar_row->addStretch();
|
|
|
|
|
|
|
|
|
|
// Zoom slider (logarithmic)
|
|
|
|
|
zoom_slider_ = new QSlider(Qt::Horizontal, this);
|
|
|
|
|
zoom_slider_->setRange(0, 1000);
|
|
|
|
|
zoom_slider_->setFixedWidth(120);
|
|
|
|
|
zoom_slider_->setToolTip(tr("Zoom"));
|
|
|
|
|
zoom_slider_->setValue(scale_to_zoom_slider(get_scale()));
|
|
|
|
|
toolbar_row->addWidget(zoom_slider_);
|
|
|
|
|
|
|
|
|
|
connect(zoom_slider_, &QSlider::valueChanged, this, [this](int v) {
|
|
|
|
|
set_scale(zoom_slider_to_scale(v));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Track height slider
|
|
|
|
|
track_height_slider_ = new QSlider(Qt::Horizontal, this);
|
|
|
|
|
track_height_slider_->setRange(0, 8);
|
|
|
|
|
track_height_slider_->setFixedWidth(80);
|
|
|
|
|
track_height_slider_->setToolTip(tr("Track Height"));
|
|
|
|
|
track_height_slider_->setValue(3);
|
|
|
|
|
toolbar_row->addWidget(track_height_slider_);
|
|
|
|
|
|
|
|
|
|
connect(track_height_slider_, &QSlider::valueChanged, this, [this](int v) {
|
|
|
|
|
if (!get_connected_node()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
double h = oakengine_track_height_minimum() +
|
|
|
|
|
v * oakengine_track_height_interval();
|
|
|
|
|
foreach (Track *t, sequence()->get_tracks()) {
|
|
|
|
|
oakengine_track_set_height(
|
|
|
|
|
reinterpret_cast<OakEngineSequence *>(sequence()), t->type(),
|
|
|
|
|
t->index(), h);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
vert_layout->addLayout(toolbar_row);
|
|
|
|
|
|
|
|
|
|
QHBoxLayout *ruler_and_time_layout = new QHBoxLayout();
|
|
|
|
|
vert_layout->addLayout(ruler_and_time_layout);
|
|
|
|
|
|
|
|
|
@@ -396,6 +478,11 @@ void TimelineWidget::ScaleChangedEvent(const double &scale)
|
|
|
|
|
view->view()->set_scale(scale);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep zoom slider in sync when scale changes elsewhere (ctrl+wheel etc.)
|
|
|
|
|
zoom_slider_->blockSignals(true);
|
|
|
|
|
zoom_slider_->setValue(scale_to_zoom_slider(scale));
|
|
|
|
|
zoom_slider_->blockSignals(false);
|
|
|
|
|
|
|
|
|
|
if (rubberband_.isVisible()) {
|
|
|
|
|
QMetaObject::invokeMethod(this, &TimelineWidget::force_update_rubber_band,
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
@@ -448,17 +535,20 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n)
|
|
|
|
|
// Subscribe to track-level events via bridge (subscriptions in add_track)
|
|
|
|
|
connect(bridge_, &EngineEventBridge::track_index_changed, this,
|
|
|
|
|
[this](OakEngineTrack *source, int old_index, int new_index) {
|
|
|
|
|
Track *track = reinterpret_cast<Track *>(source);
|
|
|
|
|
track_updated(track->type());
|
|
|
|
|
track_index_changed(track, old_index, new_index);
|
|
|
|
|
track_updated(static_cast<Track::Type>(
|
|
|
|
|
oakengine_track_type(source)));
|
|
|
|
|
track_index_changed(reinterpret_cast<Track *>(source),
|
|
|
|
|
old_index, new_index);
|
|
|
|
|
});
|
|
|
|
|
connect(bridge_, &EngineEventBridge::track_height_changed, this,
|
|
|
|
|
[this](OakEngineTrack *source, double) {
|
|
|
|
|
track_updated(reinterpret_cast<Track *>(source)->type());
|
|
|
|
|
track_updated(static_cast<Track::Type>(
|
|
|
|
|
oakengine_track_type(source)));
|
|
|
|
|
});
|
|
|
|
|
connect(bridge_, &EngineEventBridge::track_blocks_refreshed, this,
|
|
|
|
|
[this](OakEngineTrack *source) {
|
|
|
|
|
track_updated(reinterpret_cast<Track *>(source)->type());
|
|
|
|
|
track_updated(static_cast<Track::Type>(
|
|
|
|
|
oakengine_track_type(source)));
|
|
|
|
|
});
|
|
|
|
|
connect(bridge_, &EngineEventBridge::track_block_added, this,
|
|
|
|
|
[this](OakEngineBlock *block, qint64, qint64) {
|
|
|
|
@@ -519,7 +609,10 @@ void TimelineWidget::ConnectNodeEvent(ViewerOutput *n)
|
|
|
|
|
{
|
|
|
|
|
oak_video_params vp;
|
|
|
|
|
oakengine_viewer_get_video_params(handle, 0, &vp);
|
|
|
|
|
SetTimebase(Rational(vp.time_base_den, vp.time_base_num));
|
|
|
|
|
// time_base is the frame duration (e.g. 1/25); reconstruct it as
|
|
|
|
|
// Rational(num, den). (Previously num/den were swapped here, which
|
|
|
|
|
// produced the frame rate and triggered "INVALID TIMEBASE".)
|
|
|
|
|
SetTimebase(Rational(vp.time_base_num, vp.time_base_den));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < views_.size(); i++) {
|
|
|
|
@@ -1954,9 +2047,15 @@ void TimelineWidget::track_updated(Track::Type type)
|
|
|
|
|
update_viewports(type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TimelineWidget::block_updated()
|
|
|
|
|
void TimelineWidget::block_updated(OakEngineBlock *)
|
|
|
|
|
{
|
|
|
|
|
update_viewports(static_cast<Block *>(sender())->track()->type());
|
|
|
|
|
// The old implementation used sender() to obtain the Block and then
|
|
|
|
|
// queried its track type to update only one viewport. With the bridge
|
|
|
|
|
// approach sender() is the EngineEventBridge (not a Block), and the
|
|
|
|
|
// OakEngineBlock handle is opaque -- there is no C API to retrieve its
|
|
|
|
|
// track type. Simply refresh all viewports; the cost is negligible
|
|
|
|
|
// (just a repaint request).
|
|
|
|
|
update_viewports();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TimelineWidget::update_horizontal_splitters()
|
|
|
|
|