R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+40 -13
View File
@@ -25,10 +25,9 @@
#include <QDebug>
#include <QPainter>
#include "audio/audiolevelmeter.h"
#include "audio/audiomanager.h"
#include "oakutil/decibel.h"
#include "oakengine/preview.h"
#include "oakengine/viewer.h"
#include "oakutil/qtutils.h"
namespace olive
@@ -39,6 +38,10 @@ const int k_decibel_minimum =
-198; // Must be divisible by kDecibelStep for infinity to appear
const int k_maximum_smoothness = 8;
/// Channel capacity of the summary buffers passed to
/// oakengine_waveform_cache_get_summary()
const int k_max_summary_channels = 64;
QVector<AudioMonitor *> AudioMonitor::instances;
AudioMonitor::AudioMonitor(QWidget *parent)
@@ -118,9 +121,15 @@ void AudioMonitor::start_waveform(const void *waveform,
{
stop();
const AudioWaveformCache *cache =
static_cast<const AudioWaveformCache *>(waveform);
waveform_length_ = cache->length();
// The waveform cache C ABI reports the length in sample frames at the
// cache's own sample rate; convert to seconds (AudioWaveformCache::length())
const int sample_rate = oakengine_waveform_cache_sample_rate(waveform);
if (sample_rate <= 0) {
return;
}
waveform_length_ =
Rational(static_cast<int>(oakengine_waveform_cache_length(waveform)),
sample_rate);
if (start >= waveform_length_) {
return;
}
@@ -455,21 +464,39 @@ void AudioMonitor::update_values_from_waveform(QVector<double> &v,
// Delta time is provided in milliseconds, so we convert to seconds in Rational
Rational length(delta_time, 1000);
const AudioWaveformCache *cache =
static_cast<const AudioWaveformCache *>(waveform_);
AudioVisualWaveform::Sample sum =
cache->get_summary_from_time(waveform_time_, length);
const int sample_rate = oakengine_waveform_cache_sample_rate(waveform_);
if (sample_rate <= 0) {
return;
}
audio_visual_waveform_sample_to_internal_values(sum, v);
// The summary C ABI works in sample frames at the cache's sample rate
// (AudioWaveformCache::get_summary_from_time() equivalent)
const Rational sample_tb(1, sample_rate);
const int64_t start_ts = core::Timecode::time_to_timestamp(
waveform_time_, sample_tb, core::Timecode::k_round);
const int64_t end_ts = core::Timecode::time_to_timestamp(
waveform_time_ + length, sample_tb, core::Timecode::k_round);
double min_vals[k_max_summary_channels];
double max_vals[k_max_summary_channels];
int channels = 0;
if (oakengine_waveform_cache_get_summary(waveform_, start_ts, end_ts,
min_vals, max_vals,
k_max_summary_channels,
&channels) == OAKENGINE_OK) {
audio_visual_waveform_sample_to_internal_values(min_vals, max_vals,
channels, v);
}
waveform_time_ += length;
}
void AudioMonitor::audio_visual_waveform_sample_to_internal_values(
const AudioVisualWaveform::Sample &in, QVector<double> &out)
const double *min_vals, const double *max_vals, int channels,
QVector<double> &out)
{
for (size_t i = 0; i < in.size(); i++) {
float max = qMax(qAbs(in.at(i).min), qAbs(in.at(i).max));
for (int i = 0; i < channels; i++) {
double max = qMax(qAbs(min_vals[i]), qAbs(max_vals[i]));
int output_index = i % out.size();
if (max > out.at(output_index)) {
+12 -3
View File
@@ -26,13 +26,17 @@
#include <QOpenGLWidget>
#include <QTimer>
#include "audio/audiovisualwaveform.h"
#include <olive/core/core.h>
#include "oakutil/define.h"
#include "render/audiowaveformcache.h"
namespace olive
{
// Same namespace bridge the engine audio headers used to provide
// (unqualified Rational/AudioParams/SampleBuffer inside namespace olive).
using namespace core;
class AudioMonitor : public QOpenGLWidget {
Q_OBJECT
public:
@@ -87,8 +91,13 @@ private:
void update_values_from_waveform(QVector<double> &v, qint64 delta_time);
/**
* @brief Fold a per-channel min/max summary (POD arrays from
* oakengine_waveform_cache_get_summary()) into the internal values.
*/
void audio_visual_waveform_sample_to_internal_values(
const AudioVisualWaveform::Sample &in, QVector<double> &out);
const double *min_vals, const double *max_vals, int channels,
QVector<double> &out);
void push_value(const QVector<double> &v);
+4 -4
View File
@@ -61,7 +61,7 @@ void ColorButton::set_color(const ManagedColor &c)
color_.set_color_input(compliant_in);
QByteArray out_name = color_.color_output().output().toUtf8();
ColorTransform cs_out = color_.color_output();
oak::ColorTransform cs_out = color_.color_output();
QByteArray o, v, l;
oak_color_transform pod = oak_to_transform(cs_out, &o, &v, &l);
int out_is_display = 0;
@@ -70,11 +70,11 @@ void ColorButton::set_color(const ManagedColor &c)
color_manager_, &pod, 0, &out_is_display, out_buf, sizeof(out_buf),
view_buf, sizeof(view_buf), look_buf, sizeof(look_buf));
if (out_is_display) {
color_.set_color_output(ColorTransform(QString::fromUtf8(out_buf),
color_.set_color_output(oak::ColorTransform(QString::fromUtf8(out_buf),
QString::fromUtf8(view_buf),
QString::fromUtf8(look_buf)));
} else {
color_.set_color_output(ColorTransform(QString::fromUtf8(out_buf)));
color_.set_color_output(oak::ColorTransform(QString::fromUtf8(out_buf)));
}
update_color();
@@ -113,7 +113,7 @@ void ColorButton::color_dialog_finished(int e)
void ColorButton::update_color()
{
QByteArray in_cs = color_.color_input().toUtf8();
ColorTransform out = color_.color_output();
oak::ColorTransform out = color_.color_output();
QByteArray o, v, l;
oak_color_transform out_pod = oak_to_transform(out, &o, &v, &l);
color_processor_ = ColorProcessorHandlePtr(
@@ -21,7 +21,7 @@
#include "colorcodingcombobox.h"
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
namespace olive
{
@@ -48,7 +48,7 @@ void ColorCodingComboBox::showPopup()
void ColorCodingComboBox::set_color(int index)
{
clear();
addItem(ColorCoding::get_color_name(index));
addItem(AppColorCoding::get_color_name(index));
index_ = index;
}
+5 -5
View File
@@ -26,7 +26,7 @@
#include <QWidgetAction>
#include "oakutil/qtutils.h"
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
namespace olive
{
@@ -37,14 +37,14 @@ ColorLabelMenu::ColorLabelMenu(QWidget *parent)
// Used for size calculations
int box_size = fontMetrics().height();
color_items_.resize(ColorCoding::standard_colors().size());
for (int i = 0; i < ColorCoding::standard_colors().size(); i++) {
color_items_.resize(AppColorCoding::standard_colors().size());
for (int i = 0; i < AppColorCoding::standard_colors().size(); i++) {
QPixmap p(box_size, box_size);
QPainter painter(&p);
painter.setPen(Qt::black);
painter.setBrush(
QtUtils::to_q_color(ColorCoding::standard_colors().at(i)));
QtUtils::to_q_color(AppColorCoding::standard_colors().at(i)));
painter.drawRect(p.rect().adjusted(0, 0, -1, -1));
QAction *a = add_item(QStringLiteral("colorlabel%1").arg(i), this,
@@ -71,7 +71,7 @@ void ColorLabelMenu::retranslate()
this->setTitle(tr("Color"));
for (int i = 0; i < color_items_.size(); i++) {
color_items_.at(i)->setText(ColorCoding::get_color_name(i));
color_items_.at(i)->setText(AppColorCoding::get_color_name(i));
}
}
@@ -24,7 +24,7 @@
#include <QPainter>
#include "oakutil/lerp.h"
#include "node/node.h"
#include "oakutil/qtutils.h"
namespace olive
{
+3 -3
View File
@@ -173,9 +173,9 @@ QString ColorSpaceChooser::input() const
}
}
ColorTransform ColorSpaceChooser::output() const
oak::ColorTransform ColorSpaceChooser::output() const
{
return ColorTransform(display_combobox_->currentText(),
return oak::ColorTransform(display_combobox_->currentText(),
view_combobox_->currentText(),
look_combobox_->currentIndex() == 0 ?
QString() :
@@ -192,7 +192,7 @@ void ColorSpaceChooser::set_input(const QString &s)
input_combobox_->setCurrentText(compliant);
}
void ColorSpaceChooser::set_output(const ColorTransform &out)
void ColorSpaceChooser::set_output(const oak::ColorTransform &out)
{
QByteArray o, v, l;
oak_color_transform pod = oak_to_transform(out, &o, &v, &l);
+4 -4
View File
@@ -40,17 +40,17 @@ public:
QWidget *parent = nullptr);
QString input() const;
ColorTransform output() const;
oak::ColorTransform output() const;
void set_input(const QString &s);
void set_output(const ColorTransform &out);
void set_output(const oak::ColorTransform &out);
signals:
void input_color_space_changed(const QString &input);
void output_color_space_changed(const ColorTransform &out);
void output_color_space_changed(const oak::ColorTransform &out);
void color_space_changed(const QString &input, const ColorTransform &out);
void color_space_changed(const QString &input, const oak::ColorTransform &out);
private slots:
void update_views(const QString &display);
+3 -3
View File
@@ -144,7 +144,7 @@ void ColorSwatchChooser::load_swatches()
Color::DataType r;
QString s;
ManagedColor c;
ColorTransform t;
oak::ColorTransform t;
bool is_display;
c.set_alpha(1.0);
@@ -167,10 +167,10 @@ void ColorSwatchChooser::load_swatches()
d >> display;
d >> view;
d >> look;
c.set_color_output(ColorTransform(display, view, look));
c.set_color_output(oak::ColorTransform(display, view, look));
} else {
d >> s;
c.set_color_output(ColorTransform(s));
c.set_color_output(oak::ColorTransform(s));
}
buttons_[index]->set_color(c);
+2 -2
View File
@@ -23,7 +23,7 @@
#include <QMouseEvent>
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
namespace olive
{
@@ -80,7 +80,7 @@ void ColorSwatchWidget::SelectedColorChangedEvent(const Color &, bool)
Qt::GlobalColor ColorSwatchWidget::get_ui_selector_color() const
{
return ColorCoding::get_ui_selector_color(get_selected_color());
return AppColorCoding::get_ui_selector_color(get_selected_color());
}
Color ColorSwatchWidget::get_managed_color(const Color &input) const
+1 -1
View File
@@ -24,7 +24,7 @@
#include <QPainter>
#include <QtMath>
#include "node/node.h"
#include "oakutil/qtutils.h"
namespace olive
{
+137 -145
View File
@@ -29,7 +29,6 @@
#include <QtMath>
#include "oakutil/decibel.h"
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "oakutil/qtutils.h"
#include "oakengine/node.h"
@@ -44,44 +43,45 @@ namespace
{
// Map a keyframe track's scalar QVariant into the facade POD for the
// input's declared type (the curve view drags numeric tracks).
void track_value_to_c(NodeValue::Type declared, const QVariant &value,
// input's declared type (the curve view drags numeric tracks). `c_type`
// is the oak_node_value_type of the input (oakengine_node_input_get_type()).
void track_value_to_c(int c_type, const QVariant &value,
oak_node_value *out)
{
memset(out, 0, sizeof(*out));
switch (declared) {
case NodeValue::k_int:
switch (c_type) {
case OAK_NODE_VALUE_INT:
out->type = OAK_NODE_VALUE_INT;
out->num = value.toLongLong();
break;
case NodeValue::k_combo:
case OAK_NODE_VALUE_COMBO:
out->type = OAK_NODE_VALUE_COMBO;
out->num = value.toLongLong();
break;
case NodeValue::k_boolean:
case OAK_NODE_VALUE_BOOL:
out->type = OAK_NODE_VALUE_BOOL;
out->num = value.toBool() ? 1 : 0;
break;
case NodeValue::k_rational: {
case OAK_NODE_VALUE_RATIONAL: {
const Rational r = value.value<Rational>();
out->type = OAK_NODE_VALUE_RATIONAL;
out->num = r.numerator();
out->den = r.denominator();
break;
}
case NodeValue::k_color:
case OAK_NODE_VALUE_COLOR:
out->type = OAK_NODE_VALUE_COLOR;
out->f[0] = value.toDouble();
break;
case NodeValue::k_vec2:
case OAK_NODE_VALUE_VEC2:
out->type = OAK_NODE_VALUE_VEC2;
out->f[0] = value.toDouble();
break;
case NodeValue::k_vec3:
case OAK_NODE_VALUE_VEC3:
out->type = OAK_NODE_VALUE_VEC3;
out->f[0] = value.toDouble();
break;
case NodeValue::k_vec4:
case OAK_NODE_VALUE_VEC4:
out->type = OAK_NODE_VALUE_VEC4;
out->f[0] = value.toDouble();
break;
@@ -92,6 +92,14 @@ void track_value_to_c(NodeValue::Type declared, const QVariant &value,
}
}
// The oak_node_value_type of the input that owns `key`.
int key_input_c_type(OakEngineKeyframe *key)
{
const QByteArray input = key_input_id(key).toUtf8();
return oakengine_node_input_get_type(oakengine_keyframe_get_node(key),
input.constData());
}
} // namespace
CurveView::CurveView(QWidget *parent)
@@ -109,7 +117,7 @@ CurveView::CurveView(QWidget *parent)
QtUtils::q_font_metrics_width(fontMetrics(), QStringLiteral("00000"));
}
void CurveView::connect_input(const NodeKeyframeTrackReference &ref)
void CurveView::connect_input(const oak::KeyframeTrackRef &ref)
{
if (connected_inputs_.contains(ref)) {
// Input wasn't connected, do nothing
@@ -129,7 +137,7 @@ void CurveView::connect_input(const NodeKeyframeTrackReference &ref)
connected_inputs_.append(ref);
}
void CurveView::disconnect_input(const NodeKeyframeTrackReference &ref)
void CurveView::disconnect_input(const oak::KeyframeTrackRef &ref)
{
if (!connected_inputs_.contains(ref)) {
// Input wasn't connected, do nothing
@@ -143,18 +151,18 @@ void CurveView::disconnect_input(const NodeKeyframeTrackReference &ref)
connected_inputs_.removeOne(ref);
}
void CurveView::select_keyframes_of_input(const NodeKeyframeTrackReference &ref)
void CurveView::select_keyframes_of_input(const oak::KeyframeTrackRef &ref)
{
deselect_all();
if (KeyframeViewInputConnection *con = track_connections_.value(ref)) {
foreach (NodeKeyframe *key, con->get_keyframes()) {
foreach (const oak::Keyframe &key, con->get_keyframes()) {
select_keyframe(key);
}
}
}
void CurveView::set_keyframe_track_color(const NodeKeyframeTrackReference &ref,
void CurveView::set_keyframe_track_color(const oak::KeyframeTrackRef &ref,
const QColor &color)
{
// Insert color into hashmap
@@ -221,15 +229,9 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
painter->drawLines(lines);
// Draw keyframe lines
foreach (const NodeKeyframeTrackReference &ref, connected_inputs_) {
Node *node = ref.input().node();
const QString &input = ref.input().input();
if (node->is_input_keyframing(input, ref.input().element())) {
const QVector<NodeKeyframeTrack> &tracks =
node->get_keyframe_tracks(ref.input());
const NodeKeyframeTrack &track = tracks.at(ref.track());
foreach (const oak::KeyframeTrackRef &ref, connected_inputs_) {
if (ref.input().is_keyframing()) {
const QVector<oak::Keyframe> track = ref.keyframes();
if (!track.isEmpty()) {
painter->setPen(QPen(keyframe_colors_.value(ref),
@@ -245,46 +247,46 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
// Draw lines between each keyframe
for (int i = 1; i < track.size(); i++) {
NodeKeyframe *before = track.at(i - 1);
NodeKeyframe *after = track.at(i);
const oak::Keyframe &before = track.at(i - 1);
const oak::Keyframe &after = track.at(i);
QPointF before_pos = get_keyframe_position(before);
QPointF after_pos = get_keyframe_position(after);
if (before->type() == NodeKeyframe::k_hold) {
if (before.type() == KeyframeTypes::k_facade_hold) {
// Draw a hold keyframe (basically a right angle)
path.lineTo(after_pos.x(), before_pos.y());
path.lineTo(after_pos.x(), after_pos.y());
} else if (before->type() == NodeKeyframe::k_bezier &&
after->type() == NodeKeyframe::k_bezier) {
} else if (before.type() == KeyframeTypes::k_facade_bezier &&
after.type() == KeyframeTypes::k_facade_bezier) {
// Draw a cubic bezier
// Cubic beziers have two control points, so we can just use both
QPointF before_control_point =
before_pos +
ScalePoint(before->valid_bezier_control_out());
ScalePoint(before.valid_bezier_point(1));
QPointF after_control_point =
after_pos +
ScalePoint(after->valid_bezier_control_in());
ScalePoint(after.valid_bezier_point(0));
path.cubicTo(before_control_point, after_control_point,
after_pos);
} else if (before->type() == NodeKeyframe::k_bezier ||
after->type() == NodeKeyframe::k_bezier) {
} else if (before.type() == KeyframeTypes::k_facade_bezier ||
after.type() == KeyframeTypes::k_facade_bezier) {
// Draw a quadratic bezier
// Quadratic beziers have a single control point, we just have to determine which it is
QPointF key_anchor;
QPointF control_point;
if (before->type() == NodeKeyframe::k_bezier) {
if (before.type() == KeyframeTypes::k_facade_bezier) {
key_anchor = before_pos;
control_point = before->valid_bezier_control_out();
control_point = before.valid_bezier_point(1);
} else {
key_anchor = after_pos;
control_point = after->valid_bezier_control_in();
control_point = after.valid_bezier_point(0);
}
// Scale control point
@@ -327,8 +329,7 @@ void CurveView::ContextMenuEvent(Menu &m)
&CurveView::zoom_to_fit_selected);
QAction *reset_zoom_action = m.addAction(tr("Reset Zoom"));
connect(reset_zoom_action, &QAction::triggered, this,
&CurveView::reset_zoom);
connect(reset_zoom_action, &QAction::triggered, this, &CurveView::reset_zoom);
}
void CurveView::SceneRectUpdateEvent(QRectF &r)
@@ -337,7 +338,7 @@ void CurveView::SceneRectUpdateEvent(QRectF &r)
bool got_val = false;
foreach (KeyframeViewInputConnection *con, track_connections_) {
foreach (NodeKeyframe *key, con->get_keyframes()) {
foreach (const oak::Keyframe &key, con->get_keyframes()) {
qreal key_y = get_item_y_from_keyframe_value(key);
if (got_val) {
@@ -358,16 +359,17 @@ void CurveView::SceneRectUpdateEvent(QRectF &r)
}
qreal CurveView::get_keyframe_scene_y(KeyframeViewInputConnection *track,
NodeKeyframe *key)
const oak::Keyframe &key)
{
return get_item_y_from_keyframe_value(key);
}
void CurveView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
void CurveView::draw_keyframe(QPainter *painter, const oak::Keyframe &key,
KeyframeViewInputConnection *track,
const QRectF &key_rect)
{
if (is_keyframe_selected(key) && key->type() == NodeKeyframe::k_bezier) {
if (is_keyframe_selected(key) &&
key.type() == KeyframeTypes::k_facade_bezier) {
// Draw bezier control points if keyframe is selected
int control_point_size = QtUtils::q_font_metrics_width(fontMetrics(), "o");
int half_sz = control_point_size / 2;
@@ -378,9 +380,9 @@ void CurveView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
painter->setBrush(Qt::NoBrush);
QRectF cp_in = control_point_rect.translated(
key_rect.center() + ScalePoint(key->bezier_control_in()));
key_rect.center() + ScalePoint(key.bezier_point(0)));
QRectF cp_out = control_point_rect.translated(
key_rect.center() + ScalePoint(key->bezier_control_out()));
key_rect.center() + ScalePoint(key.bezier_point(1)));
painter->drawLine(key_rect.center(), cp_in.center());
painter->drawLine(key_rect.center(), cp_out.center());
@@ -388,8 +390,8 @@ void CurveView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
painter->drawEllipse(cp_in);
painter->drawEllipse(cp_out);
bezier_pts_.append({ cp_in, key, NodeKeyframe::k_in_handle });
bezier_pts_.append({ cp_out, key, NodeKeyframe::k_out_handle });
bezier_pts_.append({ cp_in, key.handle(), KeyframeTypes::k_in_handle });
bezier_pts_.append({ cp_out, key.handle(), KeyframeTypes::k_out_handle });
}
super::draw_keyframe(painter, key, track, key_rect);
@@ -407,15 +409,15 @@ bool CurveView::first_chance_mouse_press(QMouseEvent *event)
}
if (dragging_bezier_pt_) {
NodeKeyframe *key = dragging_bezier_pt_->keyframe;
OakEngineKeyframe *key = dragging_bezier_pt_->keyframe;
dragging_bezier_point_start_ =
(dragging_bezier_pt_->type == NodeKeyframe::k_in_handle) ?
key->bezier_control_in() :
key->bezier_control_out();
(dragging_bezier_pt_->type == KeyframeTypes::k_in_handle) ?
key_bezier_point(key, 0) :
key_bezier_point(key, 1);
dragging_bezier_point_opposing_start_ =
(dragging_bezier_pt_->type == NodeKeyframe::k_in_handle) ?
key->bezier_control_out() :
key->bezier_control_in();
(dragging_bezier_pt_->type == KeyframeTypes::k_in_handle) ?
key_bezier_point(key, 1) :
key_bezier_point(key, 0);
drag_start_ = mapToScene(event->pos());
return true;
@@ -449,7 +451,7 @@ void CurveView::first_chance_mouse_move(QMouseEvent *event)
if (!(event->modifiers() & Qt::ControlModifier)) {
new_opposing_pos = generate_bezier_control_position(
static_cast<NodeKeyframe::BezierType>(opposing_type),
static_cast<KeyframeTypes::BezierType>(opposing_type),
dragging_bezier_point_opposing_start_,
-mouse_diff_scaled);
} else {
@@ -457,12 +459,12 @@ void CurveView::first_chance_mouse_move(QMouseEvent *event)
}
oakengine_keyframe_set_bezier_point_live(
reinterpret_cast<OakEngineKeyframe *>(dragging_bezier_pt_->keyframe),
dragging_bezier_pt_->keyframe,
dragging_bezier_pt_->type,
new_bezier_pos.x(), new_bezier_pos.y());
oakengine_keyframe_set_bezier_point_live(
reinterpret_cast<OakEngineKeyframe *>(dragging_bezier_pt_->keyframe),
dragging_bezier_pt_->keyframe,
opposing_type,
new_opposing_pos.x(), new_opposing_pos.y());
@@ -475,29 +477,29 @@ void CurveView::first_chance_mouse_release(QMouseEvent *event)
// as the explicit old values (the drag already live-set the new
// ones); one undoable command per handle, same as the old
// KeyframeSetBezierControlPoint children.
NodeKeyframe *key = dragging_bezier_pt_->keyframe;
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(key->parent());
OakEngineKeyframe *key = dragging_bezier_pt_->keyframe;
OakEngineNode *handle = oakengine_keyframe_get_node(key);
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
const int64_t ts = Timecode::time_to_timestamp(
key->time(), Rational(tbn, tbd), Timecode::k_round);
key_time(key), Rational(tbn, tbd), Timecode::k_round);
const QPointF current =
key->bezier_control(dragging_bezier_pt_->type);
key_bezier_point(key, dragging_bezier_pt_->type);
const QByteArray input = key_input_id(key).toUtf8();
oakengine_node_keyframe_set_bezier_point(
handle, key->input().toUtf8().constData(), key->element(), ts,
key->track(),
(dragging_bezier_pt_->type == NodeKeyframe::k_in_handle) ? 0 : 1,
handle, input.constData(), key_element(key), ts,
key_track(key),
(dragging_bezier_pt_->type == KeyframeTypes::k_in_handle) ? 0 : 1,
current.x(), current.y(), dragging_bezier_point_start_.x(),
dragging_bezier_point_start_.y());
if (!(event->modifiers() & Qt::ControlModifier)) {
int opposing_type =
oakengine_keyframe_opposing_bezier_type(dragging_bezier_pt_->type);
const QPointF opposing_current = key->bezier_control(static_cast<NodeKeyframe::BezierType>(opposing_type));
const QPointF opposing_current = key_bezier_point(key, opposing_type);
oakengine_node_keyframe_set_bezier_point(
handle, key->input().toUtf8().constData(), key->element(), ts,
key->track(),
handle, input.constData(), key_element(key), ts,
key_track(key),
opposing_type,
opposing_current.x(), opposing_current.y(),
dragging_bezier_point_opposing_start_.x(),
@@ -511,8 +513,8 @@ void CurveView::keyframe_drag_start(QMouseEvent *event)
{
drag_keyframe_values_.resize(get_selected_keyframes().size());
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *key = get_selected_keyframes().at(i);
drag_keyframe_values_[i] = key->value();
OakEngineKeyframe *key = get_selected_keyframes().at(i);
drag_keyframe_values_[i] = OakNodeValueToQVariant(key_value(key));
}
drag_start_ = mapToScene(event->pos());
@@ -523,9 +525,9 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
if (event->modifiers() & Qt::ShiftModifier) {
// Lock to X axis only and set original values on all keys
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *key = get_selected_keyframes().at(i);
OakEngineKeyframe *key = get_selected_keyframes().at(i);
oak_node_value v;
track_value_to_c(key->parent()->get_input_data_type(key->input()),
track_value_to_c(key_input_c_type(key),
drag_keyframe_values_.at(i), &v);
key_set_value_live(key, v);
}
@@ -538,27 +540,26 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
// Validate movement - ensure no keyframe goes above its max point or below its min point
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *key = get_selected_keyframes().at(i);
OakEngineKeyframe *key = get_selected_keyframes().at(i);
FloatSlider::DisplayType display = get_float_display_type_from_keyframe(key);
Node *node = key->parent();
FloatSlider::DisplayType display = get_float_display_type_from_keyframe(oak::Keyframe(key));
OakEngineNode *node = oakengine_keyframe_get_node(key);
const QByteArray input = key_input_id(key).toUtf8();
double original_val = FloatSlider::transform_value_to_display(
drag_keyframe_values_.at(i).toDouble(), display);
const QString &input = key->input();
double new_val = FloatSlider::transform_display_to_value(
original_val - scaled_diff, display);
double limited = new_val;
if (node->has_input_property(input, QStringLiteral("min"))) {
limited = qMax(
limited,
node->get_input_property(input, QStringLiteral("min")).toDouble());
double prop = 0;
if (oakengine_node_input_get_property_number(
node, input.constData(), "min", -1, &prop) == OAKENGINE_OK) {
limited = qMax(limited, prop);
}
if (node->has_input_property(input, QStringLiteral("max"))) {
limited = qMin(
limited,
node->get_input_property(input, QStringLiteral("max")).toDouble());
if (oakengine_node_input_get_property_number(
node, input.constData(), "max", -1, &prop) == OAKENGINE_OK) {
limited = qMin(limited, prop);
}
if (limited != new_val) {
@@ -568,11 +569,11 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
// Set values
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *key = get_selected_keyframes().at(i);
FloatSlider::DisplayType display = get_float_display_type_from_keyframe(key);
OakEngineKeyframe *key = get_selected_keyframes().at(i);
FloatSlider::DisplayType display = get_float_display_type_from_keyframe(oak::Keyframe(key));
oak_node_value v;
track_value_to_c(
key->parent()->get_input_data_type(key->input()),
key_input_c_type(key),
FloatSlider::transform_display_to_value(
FloatSlider::transform_value_to_display(
drag_keyframe_values_.at(i).toDouble(), display) -
@@ -582,16 +583,16 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
key_set_value_live(key, v);
}
NodeKeyframe *tip_item = get_selected_keyframes().front();
OakEngineKeyframe *tip_item = get_selected_keyframes().front();
bool ok;
double num_value = tip_item->value().toDouble(&ok);
double num_value = OakNodeValueToQVariant(key_value(tip_item)).toDouble(&ok);
if (ok) {
tip = QStringLiteral("%1\n");
tip.append(FloatSlider::value_to_string(
num_value + get_offset_from_keyframe(tip_item),
get_float_display_type_from_keyframe(tip_item), 2, true));
num_value + get_offset_from_keyframe(oak::Keyframe(tip_item)),
get_float_display_type_from_keyframe(oak::Keyframe(tip_item)), 2, true));
}
}
@@ -605,7 +606,7 @@ void CurveView::keyframe_drag_release(QMouseEvent *event,
// drag-start values as the explicit undo values (the drag already
// live-set the new ones).
struct ValueGroup {
Node *node;
OakEngineNode *node;
QString input;
int element;
QVector<int64_t> times;
@@ -615,45 +616,45 @@ void CurveView::keyframe_drag_release(QMouseEvent *event,
};
QVector<ValueGroup> groups;
for (size_t i = 0; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *k = get_selected_keyframes().at(i);
if (qFuzzyCompare(k->value().toDouble(),
OakEngineKeyframe *k = get_selected_keyframes().at(i);
if (qFuzzyCompare(key_value_as_double(k),
drag_keyframe_values_.at(i).toDouble())) {
continue;
}
OakEngineNode *node = oakengine_keyframe_get_node(k);
const QString input = key_input_id(k);
const int element = key_element(k);
int g = 0;
for (; g < groups.size(); g++) {
if (groups.at(g).node == k->parent() &&
groups.at(g).input == k->input() &&
groups.at(g).element == k->element()) {
if (groups.at(g).node == node &&
groups.at(g).input == input &&
groups.at(g).element == element) {
break;
}
}
if (g == groups.size()) {
groups.append(
{ k->parent(), k->input(), k->element(), {}, {}, {}, {} });
{ node, input, element, {}, {}, {}, {} });
}
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(k->parent());
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
oakengine_node_frame_time_base(node, &tbn, &tbd);
groups[g].times.append(Timecode::time_to_timestamp(
k->time(), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(k->track());
key_time(k), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(key_track(k));
const NodeValue::Type declared =
k->parent()->get_input_data_type(k->input());
oak_node_value new_v, old_v;
track_value_to_c(declared, k->value(), &new_v);
track_value_to_c(declared, drag_keyframe_values_.at(i), &old_v);
groups[g].values.push_back(new_v);
oak_node_value old_v;
track_value_to_c(key_input_c_type(k), drag_keyframe_values_.at(i),
&old_v);
groups[g].values.push_back(key_value(k));
groups[g].olds.push_back(old_v);
}
foreach (const ValueGroup &g, groups) {
oakengine_node_keyframes_set_value_many(
reinterpret_cast<OakEngineNode *>(g.node),
g.node,
g.input.toUtf8().constData(), g.element, g.times.constData(),
g.tracks.data(), g.times.size(), g.values.data(),
g.olds.data());
@@ -661,7 +662,7 @@ void CurveView::keyframe_drag_release(QMouseEvent *event,
}
QPointF
CurveView::generate_bezier_control_position(const NodeKeyframe::BezierType mode,
CurveView::generate_bezier_control_position(const KeyframeTypes::BezierType mode,
const QPointF &start_point,
const QPointF &scaled_cursor_diff)
{
@@ -670,7 +671,7 @@ CurveView::generate_bezier_control_position(const NodeKeyframe::BezierType mode,
new_bezier_pos += scaled_cursor_diff;
// LIMIT bezier handles from overlapping each other
if (mode == NodeKeyframe::k_in_handle) {
if (mode == KeyframeTypes::k_in_handle) {
if (new_bezier_pos.x() > 0) {
new_bezier_pos.setX(0);
}
@@ -696,11 +697,12 @@ void CurveView::zoom_to_fit_internal(bool selected_only)
double min_val, max_val;
foreach (KeyframeViewInputConnection *con, track_connections_) {
foreach (NodeKeyframe *key, con->get_keyframes()) {
foreach (const oak::Keyframe &key, con->get_keyframes()) {
if (!selected_only || is_keyframe_selected(key)) {
Rational transformed_time =
get_adjusted_time(key->parent(), get_time_target(), key->time(),
Node::k_transform_towards_output);
get_adjusted_time(key.node().handle(),
get_time_target(), key_time(key.handle()),
k_transform_towards_output);
qreal key_y = get_unscaled_item_y_from_keyframe_value(key);
@@ -762,14 +764,14 @@ void CurveView::zoom_to_fit_internal(bool selected_only)
}
}
qreal CurveView::get_item_y_from_keyframe_value(NodeKeyframe *key)
qreal CurveView::get_item_y_from_keyframe_value(const oak::Keyframe &key)
{
return get_unscaled_item_y_from_keyframe_value(key) * get_y_scale();
}
qreal CurveView::get_unscaled_item_y_from_keyframe_value(NodeKeyframe *key)
qreal CurveView::get_unscaled_item_y_from_keyframe_value(const oak::Keyframe &key)
{
double val = key->value().toDouble();
double val = key_value_as_double(key.handle());
val = FloatSlider::transform_value_to_display(
val, get_float_display_type_from_keyframe(key));
@@ -786,45 +788,35 @@ QPointF CurveView::ScalePoint(const QPointF &point)
}
FloatSlider::DisplayType
CurveView::get_float_display_type_from_keyframe(NodeKeyframe *key)
CurveView::get_float_display_type_from_keyframe(const oak::Keyframe &key)
{
Node *node = key->parent();
const QString &input = key->input();
if (node->has_input_property(input, QStringLiteral("view"))) {
// Try to get view from input (which will be normal if unset)
return static_cast<FloatSlider::DisplayType>(
node->get_input_property(input, QStringLiteral("view")).toInt());
// Try to get view from input (which will be normal if unset)
const QByteArray input = key.input_id().toUtf8();
double view_type = 0;
if (oakengine_node_input_get_property_number(
key.node().handle(), input.constData(), "view", -1,
&view_type) == OAKENGINE_OK) {
return static_cast<FloatSlider::DisplayType>(int(view_type));
}
// Fallback to normal
return slider::k_normal;
}
double CurveView::get_offset_from_keyframe(NodeKeyframe *key)
double CurveView::get_offset_from_keyframe(const oak::Keyframe &key)
{
Node *node = key->parent();
const QString &input = key->input();
if (node->has_input_property(input, QStringLiteral("offset"))) {
QVariant v = node->get_input_property(input, QStringLiteral("offset"));
const NodeValue::Type dt = node->get_input_data_type(input);
const int c_type = node_value_type_to_c(dt);
oak_node_value normal;
const int tc = oakengine_node_value_keyframe_track_count(c_type);
QVector<oak_node_value> track_vals(tc);
if (QVariantToOakNodeValue(dt, v, &normal) &&
oakengine_node_value_split_to_tracks(
c_type, &normal, track_vals.data(), tc) == OAKENGINE_OK &&
key->track() >= 0 && key->track() < tc) {
return track_vals.at(key->track()).f[0];
}
return 0;
const QByteArray input = key.input_id().toUtf8();
double offset = 0;
if (oakengine_node_input_get_property_number(
key.node().handle(), input.constData(), "offset", -1,
&offset) == OAKENGINE_OK) {
return offset;
}
return 0;
}
QPointF CurveView::get_keyframe_position(NodeKeyframe *key)
QPointF CurveView::get_keyframe_position(const oak::Keyframe &key)
{
return QPointF(get_keyframe_scene_x(key), get_item_y_from_keyframe_value(key));
}
+19 -19
View File
@@ -22,7 +22,7 @@
#ifndef OAK_CURVEVIEW_H
#define OAK_CURVEVIEW_H
#include "node/keyframe.h"
#include "common/keyframetypes.h"
#include "widget/keyframeview/keyframeview.h"
#include "widget/slider/floatslider.h"
@@ -34,16 +34,16 @@ class CurveView : public KeyframeView {
public:
CurveView(QWidget *parent = nullptr);
void connect_input(const NodeKeyframeTrackReference &ref);
void connect_input(const oak::KeyframeTrackRef &ref);
void disconnect_input(const NodeKeyframeTrackReference &ref);
void disconnect_input(const oak::KeyframeTrackRef &ref);
void select_keyframes_of_input(const NodeKeyframeTrackReference &ref);
void select_keyframes_of_input(const oak::KeyframeTrackRef &ref);
void set_keyframe_track_color(const NodeKeyframeTrackReference &ref,
void set_keyframe_track_color(const oak::KeyframeTrackRef &ref,
const QColor &color);
const QHash<NodeKeyframeTrackReference, KeyframeViewInputConnection *> &
const QHash<oak::KeyframeTrackRef, KeyframeViewInputConnection *> &
get_connections() const
{
return track_connections_;
@@ -65,9 +65,9 @@ protected:
virtual void SceneRectUpdateEvent(QRectF &r) override;
virtual qreal get_keyframe_scene_y(KeyframeViewInputConnection *track,
NodeKeyframe *key) override;
const oak::Keyframe &key) override;
virtual void draw_keyframe(QPainter *painter, NodeKeyframe *key,
virtual void draw_keyframe(QPainter *painter, const oak::Keyframe &key,
KeyframeViewInputConnection *track,
const QRectF &key_rect) override;
@@ -83,41 +83,41 @@ protected:
private:
void zoom_to_fit_internal(bool selected_only);
qreal get_item_y_from_keyframe_value(NodeKeyframe *key);
qreal get_unscaled_item_y_from_keyframe_value(NodeKeyframe *key);
qreal get_item_y_from_keyframe_value(const oak::Keyframe &key);
qreal get_unscaled_item_y_from_keyframe_value(const oak::Keyframe &key);
QPointF ScalePoint(const QPointF &point);
static FloatSlider::DisplayType
get_float_display_type_from_keyframe(NodeKeyframe *key);
get_float_display_type_from_keyframe(const oak::Keyframe &key);
static double get_offset_from_keyframe(NodeKeyframe *key);
static double get_offset_from_keyframe(const oak::Keyframe &key);
void adjust_lines();
QPointF get_keyframe_position(NodeKeyframe *key);
QPointF get_keyframe_position(const oak::Keyframe &key);
static QPointF
generate_bezier_control_position(const NodeKeyframe::BezierType mode,
generate_bezier_control_position(const KeyframeTypes::BezierType mode,
const QPointF &start_point,
const QPointF &scaled_cursor_diff);
QPointF get_scaled_cursor_pos(const QPointF &cursor_pos);
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
QHash<NodeKeyframeTrackReference, KeyframeViewInputConnection *>
QHash<oak::KeyframeTrackRef, QColor> keyframe_colors_;
QHash<oak::KeyframeTrackRef, KeyframeViewInputConnection *>
track_connections_;
int text_padding_;
int minimum_grid_space_;
QVector<NodeKeyframeTrackReference> connected_inputs_;
QVector<oak::KeyframeTrackRef> connected_inputs_;
struct BezierPoint {
QRectF rect;
NodeKeyframe *keyframe;
NodeKeyframe::BezierType type;
OakEngineKeyframe *keyframe;
KeyframeTypes::BezierType type;
};
QVector<BezierPoint> bezier_pts_;
+67 -69
View File
@@ -29,9 +29,9 @@
#include "core.h"
#include "oakutil/qtutils.h"
#include "node/node.h"
#include "common/nodevaluehandle.h"
#include "common/keyframetypes.h"
#include "oakengine/node.h"
#include "olive/core/util/timecodefunctions.h"
#include "widget/timeruler/timeruler.h"
namespace olive
@@ -135,17 +135,17 @@ void CurveWidget::DeleteSelected()
view_->delete_selected();
}
Node *CurveWidget::get_selected_node_with_id(const QString &id)
oak::Node CurveWidget::get_selected_node_with_id(const QString &id)
{
for (auto it = view_->get_connections().cbegin();
it != view_->get_connections().cend(); it++) {
Node *n = it.key().input().node();
if (n->id() == id) {
oak::Node n = it.key().input().node();
if (n.id() == id) {
return n;
}
}
return nullptr;
return oak::Node();
}
bool CurveWidget::copy_selected(bool cut)
@@ -167,7 +167,7 @@ bool CurveWidget::paste()
std::placeholders::_1));
}
void CurveWidget::set_nodes(const QVector<Node *> &nodes)
void CurveWidget::set_nodes(const QVector<oak::Node> &nodes)
{
tree_view_->set_nodes(nodes);
@@ -175,19 +175,19 @@ void CurveWidget::set_nodes(const QVector<Node *> &nodes)
nodes_ = nodes;
// Generate colors
foreach (Node *node, nodes_) {
foreach (const QString &input, node->inputs()) {
if (node->is_input_keyframable(input) &&
!node->is_input_hidden(input)) {
int arr_sz = node->input_array_size(input);
foreach (const oak::Node &node, nodes_) {
foreach (const oak::Input &input, node.inputs()) {
if (input.is_keyframable() && !input.is_hidden()) {
const int arr_sz = input.array_size();
for (int i = -1; i < arr_sz; i++) {
// Generate a random color for this input
const QVector<NodeKeyframeTrack> &tracks =
node->get_keyframe_tracks(input, i);
const oak::Input element_input(node.handle(),
input.input_id(), i);
const int track_count =
element_input.keyframe_track_count();
for (int j = 0; j < tracks.size(); j++) {
NodeKeyframeTrackReference ref(
NodeInput(node, input, i), j);
for (int j = 0; j < track_count; j++) {
oak::KeyframeTrackRef ref(element_input, j);
if (!keyframe_colors_.contains(ref)) {
QColor c =
@@ -218,7 +218,7 @@ void CurveWidget::ScaleChangedEvent(const double &scale)
view_->set_scale(scale);
}
void CurveWidget::TimeTargetChangedEvent(ViewerOutput *target)
void CurveWidget::TimeTargetChangedEvent(OakEngineNode *target)
{
TimeTargetObject::TimeTargetChangedEvent(target);
@@ -227,7 +227,7 @@ void CurveWidget::TimeTargetChangedEvent(ViewerOutput *target)
view_->set_time_target(target);
}
void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
void CurveWidget::ConnectedNodeChangeEvent(OakEngineNode *n)
{
super::ConnectedNodeChangeEvent(n);
@@ -250,18 +250,20 @@ void CurveWidget::set_keyframe_button_checked(bool checked)
hold_button_->setChecked(checked);
}
void CurveWidget::set_keyframe_button_checked_from_type(NodeKeyframe::Type type)
void CurveWidget::set_keyframe_button_checked_from_type(int facade_type)
{
linear_button_->setChecked(type == NodeKeyframe::k_linear);
bezier_button_->setChecked(type == NodeKeyframe::k_bezier);
hold_button_->setChecked(type == NodeKeyframe::k_hold);
linear_button_->setChecked(facade_type == KeyframeTypes::k_facade_linear);
bezier_button_->setChecked(facade_type == KeyframeTypes::k_facade_bezier);
hold_button_->setChecked(facade_type == KeyframeTypes::k_facade_hold);
}
void CurveWidget::connect_input(Node *node, const QString &input, int element)
void CurveWidget::connect_input(const oak::Node &node, const QString &input,
int element)
{
if (element == -1 && node->input_is_array(input)) {
const oak::Input root_input(node.handle(), input);
if (element == -1 && root_input.is_array()) {
// This is the root element, connect all elements (if applicable)
int arr_sz = node->input_array_size(input);
int arr_sz = root_input.array_size();
for (int i = -1; i < arr_sz; i++) {
connect_input_internal(node, input, i);
}
@@ -271,14 +273,13 @@ void CurveWidget::connect_input(Node *node, const QString &input, int element)
}
}
void CurveWidget::connect_input_internal(Node *node, const QString &input,
int element)
void CurveWidget::connect_input_internal(const oak::Node &node,
const QString &input, int element)
{
NodeInput input_ref(node, input, element);
int track_count =
oakengine_node_value_keyframe_track_count(node_value_type_to_c(input_ref.get_data_type()));
const oak::Input input_ref(node.handle(), input, element);
const int track_count = input_ref.keyframe_track_count();
for (int i = 0; i < track_count; i++) {
NodeKeyframeTrackReference track_ref(input_ref, i);
oak::KeyframeTrackRef track_ref(input_ref, i);
view_->connect_input(track_ref);
selected_tracks_.append(track_ref);
}
@@ -286,20 +287,21 @@ void CurveWidget::connect_input_internal(Node *node, const QString &input,
void CurveWidget::selection_changed()
{
const std::vector<NodeKeyframe *> &selected = view_->get_selected_keyframes();
const std::vector<OakEngineKeyframe *> &selected = view_->get_selected_keyframes();
set_keyframe_button_checked(false);
set_keyframe_button_enabled(!selected.empty());
if (!selected.empty()) {
bool all_same_type = true;
NodeKeyframe::Type type = selected.front()->type();
const int type = oakengine_keyframe_get_type(selected.front());
for (size_t i = 1; i < selected.size(); i++) {
NodeKeyframe *prev_item = selected.at(i - 1);
NodeKeyframe *this_item = selected.at(i);
OakEngineKeyframe *prev_item = selected.at(i - 1);
OakEngineKeyframe *this_item = selected.at(i);
if (prev_item->type() != this_item->type()) {
if (oakengine_keyframe_get_type(prev_item) !=
oakengine_keyframe_get_type(this_item)) {
all_same_type = false;
break;
}
@@ -322,21 +324,21 @@ void CurveWidget::keyframe_type_button_triggered(bool checked)
}
// Get selected items and do nothing if there are none
const std::vector<NodeKeyframe *> &selected = view_->get_selected_keyframes();
const std::vector<OakEngineKeyframe *> &selected = view_->get_selected_keyframes();
if (selected.empty()) {
return;
}
// Set all selected keyframes to this type
NodeKeyframe::Type new_type;
int new_type;
// Determine which type to set
if (key_btn == bezier_button_) {
new_type = NodeKeyframe::k_bezier;
new_type = KeyframeTypes::k_facade_bezier;
} else if (key_btn == hold_button_) {
new_type = NodeKeyframe::k_hold;
new_type = KeyframeTypes::k_facade_hold;
} else {
new_type = NodeKeyframe::k_linear;
new_type = KeyframeTypes::k_facade_linear;
}
// Ensure only the appropriate button is checked
@@ -346,53 +348,49 @@ void CurveWidget::keyframe_type_button_triggered(bool checked)
// distinct input (usually just one), with the same batch semantics as
// the old per-keyframe commands.
struct TypeGroup {
Node *node;
OakEngineNode *node;
QString input;
int element;
QVector<int64_t> times;
QVector<int> tracks;
};
QVector<TypeGroup> groups;
foreach (NodeKeyframe *item, selected) {
foreach (OakEngineKeyframe *item, selected) {
const oak::Keyframe key(item);
OakEngineNode *node = key.node().handle();
int g = 0;
for (; g < groups.size(); g++) {
if (groups.at(g).node == item->parent() &&
groups.at(g).input == item->input() &&
groups.at(g).element == item->element()) {
if (groups.at(g).node == node &&
groups.at(g).input == key.input_id() &&
groups.at(g).element == key.element()) {
break;
}
}
if (g == groups.size()) {
groups.append({ item->parent(), item->input(), item->element(),
groups.append({ node, key.input_id(), key.element(),
{}, {} });
}
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(item->parent());
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
oakengine_node_frame_time_base(node, &tbn, &tbd);
int64_t num = 0, den = 1;
key.time(&num, &den);
groups[g].times.append(Timecode::time_to_timestamp(
item->time(), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(item->track());
}
int facade_type = 0;
if (new_type == NodeKeyframe::k_bezier) {
facade_type = 1;
} else if (new_type == NodeKeyframe::k_hold) {
facade_type = 2;
Rational(int(num), int(den)), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(key.track());
}
foreach (const TypeGroup &g, groups) {
oakengine_node_keyframes_set_type_many(
reinterpret_cast<OakEngineNode *>(g.node),
g.node,
g.input.toUtf8().constData(), g.element, g.times.constData(),
g.tracks.data(), g.times.size(), facade_type);
g.tracks.data(), g.times.size(), new_type);
}
}
void CurveWidget::input_selection_changed(const NodeKeyframeTrackReference &ref)
void CurveWidget::input_selection_changed(const oak::KeyframeTrackRef &ref)
{
key_control_->set_input(ref.input());
foreach (const NodeKeyframeTrackReference &c, selected_tracks_) {
foreach (const oak::KeyframeTrackRef &c, selected_tracks_) {
view_->disconnect_input(c);
}
@@ -404,14 +402,14 @@ void CurveWidget::input_selection_changed(const NodeKeyframeTrackReference &ref)
selected_tracks_.append(ref);
} else if (ref.input().is_valid()) {
// This reference is a input, connect all tracks
connect_input(ref.input().node(), ref.input().input(),
connect_input(ref.input().node(), ref.input().input_id(),
ref.input().element());
} else if (Node *node = ref.input().node()) {
} else if (!ref.input().node().is_null()) {
// This is a node, add all inputs
foreach (const QString &input, node->inputs()) {
if (node->is_input_keyframable(input) &&
!node->is_input_hidden(input)) {
connect_input(node, input, -1);
const oak::Node node = ref.input().node();
foreach (const oak::Input &input, node.inputs()) {
if (input.is_keyframable() && !input.is_hidden()) {
connect_input(node, input.input_id(), -1);
}
}
}
+12 -15
View File
@@ -56,25 +56,22 @@ public:
view_->deselect_all();
}
Node *get_selected_node_with_id(const QString &id);
oak::Node get_selected_node_with_id(const QString &id);
virtual bool copy_selected(bool cut) override;
virtual bool paste() override;
public:
// Not a slot: signature uses the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void set_nodes(const QVector<Node *> &nodes);
void set_nodes(const QVector<oak::Node> &nodes);
protected:
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void ScaleChangedEvent(const double &) override;
virtual void TimeTargetChangedEvent(ViewerOutput *target) override;
virtual void TimeTargetChangedEvent(OakEngineNode *target) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput *n) override;
virtual void ConnectedNodeChangeEvent(OakEngineNode *n) override;
virtual const QVector<KeyframeViewInputConnection *> *
get_snap_keyframes() const override
@@ -87,7 +84,7 @@ protected:
return view_;
}
virtual const std::vector<NodeKeyframe *> *
virtual const std::vector<OakEngineKeyframe *> *
get_snap_ignore_keyframes() const override
{
return &view_->get_selected_keyframes();
@@ -98,13 +95,13 @@ private:
void set_keyframe_button_checked(bool checked);
void set_keyframe_button_checked_from_type(NodeKeyframe::Type type);
void set_keyframe_button_checked_from_type(int facade_type);
void connect_input(Node *node, const QString &input, int element);
void connect_input(const oak::Node &node, const QString &input, int element);
void connect_input_internal(Node *node, const QString &input, int element);
void connect_input_internal(const oak::Node &node, const QString &input, int element);
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
QHash<oak::KeyframeTrackRef, QColor> keyframe_colors_;
NodeTreeView *tree_view_;
@@ -118,16 +115,16 @@ private:
NodeParamViewKeyframeControl *key_control_;
QVector<Node *> nodes_;
QVector<oak::Node> nodes_;
QVector<NodeKeyframeTrackReference> selected_tracks_;
QVector<oak::KeyframeTrackRef> selected_tracks_;
private slots:
void selection_changed();
void keyframe_type_button_triggered(bool checked);
void input_selection_changed(const NodeKeyframeTrackReference &ref);
void input_selection_changed(const oak::KeyframeTrackRef &ref);
void keyframe_view_dragged(int x, int y);
void keyframe_view_released();
+1 -1
View File
@@ -25,7 +25,7 @@
#include <QGraphicsView>
#include <QMenu>
#include "tool/tool.h"
#include "common/tooltypes.h"
namespace olive
{
+92 -3
View File
@@ -21,19 +21,108 @@
#include "historywidget.h"
#include "core.h"
#include "oakengine/events.h"
#include "oakengine/undo.h"
namespace olive
{
HistoryModel::HistoryModel(QObject *parent)
: QAbstractItemModel(parent)
{
sub_ = oakengine_event_subscribe(
oakengine_undo_handle(), OAKENGINE_EVENT_UNDO_INDEX_CHANGED,
[](const oakengine_event *event, void *userdata) {
Q_UNUSED(event)
auto *self = static_cast<HistoryModel *>(userdata);
self->beginResetModel();
self->endResetModel();
},
this);
}
HistoryModel::~HistoryModel()
{
if (sub_ > 0) {
oakengine_event_unsubscribe(sub_);
}
}
QModelIndex HistoryModel::index(int row, int column,
const QModelIndex &parent) const
{
Q_UNUSED(parent)
return createIndex(row, column, nullptr);
}
QModelIndex HistoryModel::parent(const QModelIndex &index) const
{
Q_UNUSED(index)
return QModelIndex();
}
int HistoryModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return static_cast<int>(oakengine_undo_count());
}
int HistoryModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return 2;
}
QVariant HistoryModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
switch (index.column()) {
case 0:
return index.row() + 1;
case 1: {
char buf[1024];
buf[0] = '\0';
oakengine_undo_command_text(index.row(), buf, sizeof(buf));
const QString name = QString::fromUtf8(buf);
return name.isEmpty() ? tr("Command") : name;
}
}
} else if (role == Qt::ForegroundRole) {
// Rows at/after the current stack index are undone commands
if (index.row() >= oakengine_undo_index()) {
return QVariant(QColor(Qt::gray));
}
}
return QVariant();
}
QVariant HistoryModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
Q_UNUSED(orientation)
if (role == Qt::DisplayRole) {
switch (section) {
case 0:
return QStringLiteral("Number");
case 1:
return QStringLiteral("Action");
}
}
return QVariant();
}
HistoryWidget::HistoryWidget(QWidget *parent)
: QTreeView(parent)
{
stack_ = Core::instance()->undo_stack();
model_ = new HistoryModel(this);
this->setModel(stack_);
this->setModel(model_);
this->setRootIsDecorated(false);
undo_sub_ = oakengine_event_subscribe(
oakengine_undo_handle(), OAKENGINE_EVENT_UNDO_INDEX_CHANGED,
+30 -3
View File
@@ -22,15 +22,42 @@
#ifndef OAK_HISTORYWIDGET_H
#define OAK_HISTORYWIDGET_H
#include <QAbstractItemModel>
#include <QTreeView>
#include <cstdint>
#include "undo/undostack.h"
namespace olive
{
/**
* @brief App-side undo history model over the engine C ABI.
*
* Replaces the direct use of the engine's UndoStack as a Qt item model.
* Semantics mirror engine/undo/undostack.cpp: two columns (Number, Action),
* rows are all commands on the stack (done first, then undone), undone rows
* are shown gray. Refreshes itself on OAKENGINE_EVENT_UNDO_INDEX_CHANGED.
*/
class HistoryModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit HistoryModel(QObject *parent = nullptr);
~HistoryModel() override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
private:
int64_t sub_ = 0;
};
class HistoryWidget : public QTreeView {
Q_OBJECT
public:
@@ -38,7 +65,7 @@ public:
~HistoryWidget() override;
private:
UndoStack *stack_;
HistoryModel *model_;
int64_t undo_sub_ = 0;
+55 -72
View File
@@ -31,74 +31,45 @@
namespace olive
{
class Node;
class NodeKeyframe;
using olive::core::Rational;
/**
* @brief Facade accessors for keyframe pointers held by the keyframe
* @brief Facade accessors for keyframe handles held by the keyframe
* views.
*
* The keyframe/curve views keep olive::NodeKeyframe* as opaque identity
* pointers (selection, drawing, hit-testing). All engine data and
* The keyframe/curve views keep OakEngineKeyframe* as opaque identity
* handles (selection, drawing, hit-testing). All engine data and
* mutations go through the liboakengine C ABI (oakengine/node.h); the
* pointer itself is only a handle. Easing types use the facade order:
* handle itself is only an identity. Easing types use the facade order:
* 0 = linear, 1 = bezier, 2 = hold.
*/
inline OakEngineKeyframe *keyhandle(NodeKeyframe *key)
inline OakEngineNode *key_node(const OakEngineKeyframe *key)
{
return reinterpret_cast<OakEngineKeyframe *>(key);
return oakengine_keyframe_get_node(key);
}
inline const OakEngineKeyframe *keyhandle(const NodeKeyframe *key)
{
return reinterpret_cast<const OakEngineKeyframe *>(key);
}
inline NodeKeyframe *keyhandle(OakEngineKeyframe *key)
{
return reinterpret_cast<NodeKeyframe *>(key);
}
inline OakEngineNode *nodehandle(Node *node)
{
return reinterpret_cast<OakEngineNode *>(node);
}
inline const OakEngineNode *nodehandle(const Node *node)
{
return reinterpret_cast<const OakEngineNode *>(node);
}
inline Node *key_node(const NodeKeyframe *key)
{
return reinterpret_cast<Node *>(
oakengine_keyframe_get_node(keyhandle(key)));
}
inline Rational key_time(const NodeKeyframe *key)
inline Rational key_time(const OakEngineKeyframe *key)
{
int64_t num = 0, den = 1;
oakengine_keyframe_get_time(keyhandle(key), &num, &den);
oakengine_keyframe_get_time(key, &num, &den);
return Rational(int(num), int(den));
}
inline int key_easing(const NodeKeyframe *key)
inline int key_easing(const OakEngineKeyframe *key)
{
return oakengine_keyframe_get_type(keyhandle(key));
return oakengine_keyframe_get_type(key);
}
inline oak_node_value key_value(const NodeKeyframe *key)
inline oak_node_value key_value(const OakEngineKeyframe *key)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
oakengine_keyframe_get_value(keyhandle(key), &v);
oakengine_keyframe_get_value(key, &v);
return v;
}
inline double key_value_as_double(const NodeKeyframe *key)
inline double key_value_as_double(const OakEngineKeyframe *key)
{
const oak_node_value v = key_value(key);
switch (v.type) {
@@ -113,93 +84,105 @@ inline double key_value_as_double(const NodeKeyframe *key)
}
}
inline void key_set_value_live(NodeKeyframe *key, const oak_node_value &v)
inline void key_set_value_live(OakEngineKeyframe *key, const oak_node_value &v)
{
oakengine_keyframe_set_value_live(keyhandle(key), &v);
oakengine_keyframe_set_value_live(key, &v);
}
inline QPointF key_bezier_point(const NodeKeyframe *key, int point_index)
inline QPointF key_bezier_point(const OakEngineKeyframe *key, int point_index)
{
double x = 0, y = 0;
oakengine_keyframe_get_bezier_point(keyhandle(key), point_index, &x, &y);
oakengine_keyframe_get_bezier_point(key, point_index, &x, &y);
return QPointF(x, y);
}
inline QPointF key_valid_bezier_point(const NodeKeyframe *key,
inline QPointF key_valid_bezier_point(const OakEngineKeyframe *key,
int point_index)
{
double x = 0, y = 0;
oakengine_keyframe_get_valid_bezier_point(keyhandle(key), point_index,
&x, &y);
oakengine_keyframe_get_valid_bezier_point(key, point_index, &x, &y);
return QPointF(x, y);
}
inline void key_set_bezier_point_live(NodeKeyframe *key, int point_index,
inline void key_set_bezier_point_live(OakEngineKeyframe *key, int point_index,
const QPointF &point)
{
oakengine_keyframe_set_bezier_point_live(keyhandle(key), point_index,
oakengine_keyframe_set_bezier_point_live(key, point_index,
point.x(), point.y());
}
inline void key_set_time_live(NodeKeyframe *key, const Rational &time)
inline void key_set_time_live(OakEngineKeyframe *key, const Rational &time)
{
oakengine_keyframe_set_time_live(keyhandle(key), time.numerator(),
oakengine_keyframe_set_time_live(key, time.numerator(),
time.denominator());
}
inline bool key_has_sibling_at_time(const NodeKeyframe *key,
/**
* @brief NodeKeyframe::has_sibling_at_time() equivalent: true when another
* keyframe sits at `time` on the same input/track/element.
*
* NOTE: oakengine_keyframe_has_sibling_at_time() is NOT used here — its
* facade contract (whole-second time, ignored track) does not match the
* engine semantics, so the check is done with an exact rational lookup.
*/
inline bool key_has_sibling_at_time(const OakEngineKeyframe *key,
const Rational &time)
{
return oakengine_keyframe_has_sibling_at_time(
keyhandle(key), time.numerator(), time.denominator()) != 0;
char input_id[256];
input_id[0] = '\0';
oakengine_keyframe_get_input_id(key, input_id, sizeof(input_id));
OakEngineKeyframe *sibling = oakengine_node_keyframe_handle_at_time(
oakengine_keyframe_get_node(key), input_id,
oakengine_keyframe_get_element(key), oakengine_keyframe_get_track(key),
time.numerator(), time.denominator());
return sibling && sibling != key;
}
inline QString key_input_id(const NodeKeyframe *key)
inline QString key_input_id(const OakEngineKeyframe *key)
{
const int size =
oakengine_keyframe_get_input_id(keyhandle(key), nullptr, 0);
oakengine_keyframe_get_input_id(key, nullptr, 0);
QByteArray buf(size + 1, '\0');
oakengine_keyframe_get_input_id(keyhandle(key), buf.data(),
oakengine_keyframe_get_input_id(key, buf.data(),
int(buf.size()));
return QString::fromUtf8(buf.constData());
}
inline int key_track(const NodeKeyframe *key)
inline int key_track(const OakEngineKeyframe *key)
{
return oakengine_keyframe_get_track(keyhandle(key));
return oakengine_keyframe_get_track(key);
}
inline int key_element(const NodeKeyframe *key)
inline int key_element(const OakEngineKeyframe *key)
{
return oakengine_keyframe_get_element(keyhandle(key));
return oakengine_keyframe_get_element(key);
}
/**
* @brief ADL customization points for
* TimeBasedViewSelectionManager<NodeKeyframe>.
* TimeBasedViewSelectionManager<OakEngineKeyframe>.
*
* The selection manager template calls these unqualified; the generic
* member-forwarding templates in timebasedviewselectionmanager.h cover
* other object types (e.g. TimelineMarker), while these overloads route
* keyframe access through the facade.
* The selection manager template calls these unqualified; the overloads in
* timeruler/markerhandle.h cover TimelineMarker, while these route keyframe
* access through the facade.
*/
inline Rational selection_time(NodeKeyframe *key)
inline Rational selection_time(OakEngineKeyframe *key)
{
return key_time(key);
}
inline void selection_set_time(NodeKeyframe *key, const Rational &time)
inline void selection_set_time(OakEngineKeyframe *key, const Rational &time)
{
key_set_time_live(key, time);
}
inline bool selection_has_sibling_at_time(NodeKeyframe *key,
inline bool selection_has_sibling_at_time(OakEngineKeyframe *key,
const Rational &time)
{
return key_has_sibling_at_time(key, time);
}
inline Node *selection_time_target_parent(NodeKeyframe *key)
inline OakEngineNode *selection_time_target_parent(OakEngineKeyframe *key)
{
return key_node(key);
}
+167 -153
View File
@@ -25,17 +25,15 @@
#include <QToolTip>
#include <QVBoxLayout>
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "common/keyframetypes.h"
#include "oakutil/qtutils.h"
#include "dialog/keyframeproperties/keyframeproperties.h"
#include "keyframehandle.h"
#include "node/node.h"
#include "node/value.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/undo.h"
#include "widget/menu/menu.h"
#include "widget/viewer/vieweroutpututils.h"
#include "widget/menu/menushared.h"
namespace olive
@@ -43,24 +41,24 @@ namespace olive
#define super TimeBasedView
static bool KeyframeToOakNodeValue(Node *node, NodeKeyframe *key,
oak_node_value *out)
/**
* @brief Node::get_keyframes_at_time() equivalent through the C ABI: all
* keyframes at the key's time across every track of its input.
*/
static QVector<OakEngineKeyframe *> GetKeyframesAtTime(const oak::Keyframe &key)
{
const NodeValue::Type type = node->get_input_data_type(key->input());
QVector<QVariant> split = node->get_split_value_at_time(
NodeInput(node, key->input(), key->element()), key->time());
if (key->track() >= 0 && key->track() < split.size()) {
split[key->track()] = key->value();
}
QVector<oak_node_value> tracks(split.size());
for (int i = 0; i < split.size(); i++) {
if (!NodeTrackComponentToOakNodeValue(type, split.at(i), &tracks[i])) {
return false;
}
}
return oakengine_node_value_combine_tracks(
node_value_type_to_c(type), tracks.constData(), tracks.size(),
out) == OAKENGINE_OK;
OakEngineNode *node = oakengine_keyframe_get_node(key.handle());
const QByteArray input = key.input_id().toUtf8();
int64_t num = 0, den = 1;
key.time(&num, &den);
QVector<OakEngineKeyframe *> out(
qMax(1, oakengine_node_keyframe_track_count(node, input.constData(),
key.element())));
const int filled = oakengine_node_keyframes_at_time(
node, input.constData(), key.element(), num, den, out.data(),
out.size());
out.resize(filled);
return out;
}
KeyframeView::KeyframeView(QWidget *parent)
@@ -81,9 +79,12 @@ KeyframeView::KeyframeView(QWidget *parent)
void KeyframeView::delete_selected()
{
if (!selection_manager_.is_dragging()) {
const std::vector<OakEngineKeyframe *> &selected =
get_selected_keyframes();
QVector<OakEngineKeyframe *> keys;
foreach (NodeKeyframe *key, get_selected_keyframes()) {
keys.append(reinterpret_cast<OakEngineKeyframe *>(key));
keys.reserve(int(selected.size()));
foreach (OakEngineKeyframe *key, selected) {
keys.append(key);
}
oakengine_keyframes_remove_many(
keys.data(), keys.size(),
@@ -94,19 +95,22 @@ void KeyframeView::delete_selected()
}
}
KeyframeView::NodeConnections KeyframeView::add_keyframes_of_node(Node *n)
KeyframeView::NodeConnections
KeyframeView::add_keyframes_of_node(const oak::Node &n)
{
NodeConnections map;
foreach (const QString &i, n->inputs()) {
map.insert(i, add_keyframes_of_input(n, i));
const int input_count = n.input_count();
for (int i = 0; i < input_count; i++) {
const QString input = n.input_id(i);
map.insert(input, add_keyframes_of_input(n, input));
}
return map;
}
KeyframeView::InputConnections
KeyframeView::add_keyframes_of_input(Node *on, const QString &oinput)
KeyframeView::add_keyframes_of_input(const oak::Node &on, const QString &oinput)
{
InputConnections vec;
@@ -114,19 +118,18 @@ KeyframeView::add_keyframes_of_input(Node *on, const QString &oinput)
char resolved_input[256];
int resolved_element = 0;
oakengine_group_resolve_input(
reinterpret_cast<OakEngineNode *>(on), oinput.toUtf8().constData(), -1,
on.handle(), oinput.toUtf8().constData(), -1,
&resolved_node, resolved_input, sizeof(resolved_input),
&resolved_element);
NodeInput resolved(reinterpret_cast<Node *>(resolved_node),
QString::fromUtf8(resolved_input), resolved_element);
Node *n = resolved.node();
const QString &input = resolved.input();
oak::Input resolved(resolved_node, QString::fromUtf8(resolved_input),
resolved_element);
if (n->is_input_keyframable(input)) {
int arr_sz = n->input_array_size(input);
if (resolved.is_keyframable()) {
int arr_sz = resolved.array_size();
vec.resize(arr_sz + 1);
for (int i = -1; i < arr_sz; i++) {
vec[i + 1] = add_keyframes_of_element(NodeInput(n, input, i));
vec[i + 1] = add_keyframes_of_element(
oak::Input(resolved_node, resolved.input_id(), i));
}
}
@@ -134,21 +137,22 @@ KeyframeView::add_keyframes_of_input(Node *on, const QString &oinput)
}
KeyframeView::ElementConnections
KeyframeView::add_keyframes_of_element(const NodeInput &input)
KeyframeView::add_keyframes_of_element(const oak::Input &input)
{
const QVector<NodeKeyframeTrack> &tracks =
input.node()->get_keyframe_tracks(input);
ElementConnections vec(tracks.size());
const int track_count = oakengine_node_keyframe_track_count(
input.node_handle(), input.input_id().toUtf8().constData(),
input.element());
ElementConnections vec(track_count);
for (int i = 0; i < tracks.size(); i++) {
vec[i] = add_keyframes_of_track(NodeKeyframeTrackReference(input, i));
for (int i = 0; i < track_count; i++) {
vec[i] = add_keyframes_of_track(oak::KeyframeTrackRef(input, i));
}
return vec;
}
KeyframeViewInputConnection *
KeyframeView::add_keyframes_of_track(const NodeKeyframeTrackReference &ref)
KeyframeView::add_keyframes_of_track(const oak::KeyframeTrackRef &ref)
{
KeyframeViewInputConnection *track =
new KeyframeViewInputConnection(ref, this);
@@ -163,8 +167,8 @@ void KeyframeView::remove_keyframes_of_track(
KeyframeViewInputConnection *connection)
{
if (tracks_.removeOne(connection)) {
foreach (NodeKeyframe *key, connection->get_keyframes()) {
selection_manager_.deselect(key);
foreach (const oak::Keyframe &key, connection->get_keyframes()) {
selection_manager_.deselect(key.handle());
}
delete connection;
redraw();
@@ -175,7 +179,7 @@ void KeyframeView::remove_keyframes_of_track(
void KeyframeView::select_all()
{
foreach (KeyframeViewInputConnection *track, tracks_) {
foreach (NodeKeyframe *key, track->get_keyframes()) {
foreach (const oak::Keyframe &key, track->get_keyframes()) {
select_keyframe(key);
}
}
@@ -202,12 +206,10 @@ void KeyframeView::clear()
void KeyframeView::SelectionManagerSelectEvent(void *obj)
{
if (autoselect_siblings_) {
NodeKeyframe *key = static_cast<NodeKeyframe *>(obj);
QVector<NodeKeyframe *> keys = key->parent()->get_keyframes_at_time(
key->input(), key->time(), key->element());
foreach (NodeKeyframe *k, keys) {
OakEngineKeyframe *key = static_cast<OakEngineKeyframe *>(obj);
foreach (OakEngineKeyframe *k, GetKeyframesAtTime(oak::Keyframe(key))) {
if (k != key) {
select_keyframe(k);
select_keyframe(oak::Keyframe(k));
}
}
}
@@ -218,12 +220,10 @@ void KeyframeView::SelectionManagerSelectEvent(void *obj)
void KeyframeView::SelectionManagerDeselectEvent(void *obj)
{
if (autoselect_siblings_) {
NodeKeyframe *key = static_cast<NodeKeyframe *>(obj);
QVector<NodeKeyframe *> keys = key->parent()->get_keyframes_at_time(
key->input(), key->time(), key->element());
foreach (NodeKeyframe *k, keys) {
OakEngineKeyframe *key = static_cast<OakEngineKeyframe *>(obj);
foreach (OakEngineKeyframe *k, GetKeyframesAtTime(oak::Keyframe(key))) {
if (k != key) {
deselect_keyframe(k);
deselect_keyframe(oak::Keyframe(k));
}
}
}
@@ -257,7 +257,7 @@ bool KeyframeView::copy_selected(bool cut)
}
bool KeyframeView::paste(
std::function<Node *(const QString &)> find_node_function)
std::function<oak::Node(const QString &)> find_node_function)
{
if (!get_viewer_node()) {
return false;
@@ -290,14 +290,13 @@ bool KeyframeView::paste(
cb,
[](const char *, OakEngineKeyframe *kf, void *userdata) -> int {
auto *ctx = static_cast<PasteCtx *>(userdata);
NodeKeyframe *key = reinterpret_cast<NodeKeyframe *>(kf);
ctx->min = std::min(ctx->min, key->time());
ctx->min = std::min(ctx->min, key_time(kf));
ctx->total++;
return 0;
},
&ctx);
ctx.min -= ctx.self->get_viewer_node()->get_playhead();
ctx.min -= viewer_output_playhead(ctx.self->get_viewer_node());
// Second pass: process keyframes
oakengine_clipboard_foreach_keyframe(
@@ -305,50 +304,62 @@ bool KeyframeView::paste(
[](const char *node_id, OakEngineKeyframe *kf,
void *userdata) -> int {
auto *ctx = static_cast<PasteCtx *>(userdata);
NodeKeyframe *key = reinterpret_cast<NodeKeyframe *>(kf);
auto &find_fn =
*static_cast<std::function<Node *(const QString &)> *>(
*static_cast<std::function<oak::Node(const QString &)> *>(
ctx->find_fn);
Node *node_with_id =
oak::Node node_with_id =
find_fn(QString::fromUtf8(node_id));
if (node_with_id) {
Rational t = key->time() - ctx->min;
if (!node_with_id.is_null()) {
Rational t = key_time(kf) - ctx->min;
t = ctx->self->get_adjusted_time(
ctx->self->get_time_target(), node_with_id, t,
Node::k_transform_towards_input);
key_set_time_live(key, t);
ctx->self->get_time_target(),
node_with_id.handle(), t,
k_transform_towards_input);
key_set_time_live(kf, t);
if (NodeKeyframe *existing =
node_with_id->get_keyframe_at_time_on_track(
key->input(), key->time(), key->track(),
key->element())) {
char kf_input[256];
kf_input[0] = '\0';
oakengine_keyframe_get_input_id(kf, kf_input,
sizeof(kf_input));
int64_t kn = 0, kd = 1;
oakengine_keyframe_get_time(kf, &kn, &kd);
if (OakEngineKeyframe *existing =
oakengine_node_keyframe_handle_at_time(
node_with_id.handle(), kf_input,
oakengine_keyframe_get_element(kf),
oakengine_keyframe_get_track(kf), kn, kd)) {
void *rm = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(existing));
existing);
oakengine_undo_command_multi_add_child(
ctx->command, rm);
}
oak_node_value v;
KeyframeToOakNodeValue(node_with_id, key, &v);
oakengine_keyframe_compute_paste_value(
node_with_id.handle(), kf, &v);
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(node_with_id),
node_with_id.handle(),
&tbn, &tbd);
const int64_t time_ts = Timecode::time_to_timestamp(
key->time(), Rational(tbn, tbd), Timecode::k_round);
key_time(kf), Rational(tbn, tbd), Timecode::k_round);
const QPointF cp_in = key_bezier_point(kf, 0);
const QPointF cp_out = key_bezier_point(kf, 1);
void *cmd = oakengine_node_insert_keyframe_command(
reinterpret_cast<OakEngineNode *>(node_with_id),
key->input().toUtf8().constData(),
key->element(), key->track(), time_ts, &v,
NodeKeyframeTypeToFacade(key->type()),
static_cast<float>(key->bezier_control_in().x()),
static_cast<float>(key->bezier_control_in().y()),
static_cast<float>(key->bezier_control_out().x()),
static_cast<float>(key->bezier_control_out().y()));
node_with_id.handle(),
kf_input,
oakengine_keyframe_get_element(kf),
oakengine_keyframe_get_track(kf), time_ts, &v,
oakengine_keyframe_get_type(kf),
static_cast<float>(cp_in.x()),
static_cast<float>(cp_in.y()),
static_cast<float>(cp_out.x()),
static_cast<float>(cp_out.y()));
oakengine_undo_command_multi_add_child(ctx->command, cmd);
} else {
delete key;
oakengine_keyframe_dispose(kf);
}
return 0;
@@ -377,7 +388,7 @@ void KeyframeView::CatchUpScrollEvent()
void KeyframeView::mousePressEvent(QMouseEvent *event)
{
NodeKeyframe *key_under_cursor =
OakEngineKeyframe *key_under_cursor =
selection_manager_.get_object_at_point(event->pos());
if (hand_press(event) || (!key_under_cursor && playhead_press(event))) {
@@ -387,7 +398,7 @@ void KeyframeView::mousePressEvent(QMouseEvent *event)
// Do mouse press things
if (first_chance_mouse_press(event)) {
first_chance_mouse_event_ = true;
} else if (NodeKeyframe *initial_key =
} else if (OakEngineKeyframe *initial_key =
selection_manager_.mouse_press(event)) {
selection_manager_.drag_start(initial_key, event, this);
keyframe_drag_start(event);
@@ -447,7 +458,7 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
emit released();
}
int binary_search_first_keyframe_after_or_at(const QVector<NodeKeyframe *> &keys,
int binary_search_first_keyframe_after_or_at(const QVector<oak::Keyframe> &keys,
const Rational &time)
{
int low = 0;
@@ -455,13 +466,13 @@ int binary_search_first_keyframe_after_or_at(const QVector<NodeKeyframe *> &keys
while (low <= high) {
int mid = low + (high - low) / 2;
NodeKeyframe *test_key = keys.at(mid);
const oak::Keyframe &test_key = keys.at(mid);
if (test_key->time() == time ||
(test_key->time() > time &&
(mid == 0 || keys.at(mid - 1)->time() < time))) {
if (key_time(test_key.handle()) == time ||
(key_time(test_key.handle()) > time &&
(mid == 0 || key_time(keys.at(mid - 1).handle()) < time))) {
return mid;
} else if (test_key->time() < time) {
} else if (key_time(test_key.handle()) < time) {
low = mid + 1;
} else {
high = mid - 1;
@@ -481,7 +492,7 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect)
painter->setRenderHint(QPainter::Antialiasing);
foreach (KeyframeViewInputConnection *track, tracks_) {
const QVector<NodeKeyframe *> &keys = track->get_keyframes();
const QVector<oak::Keyframe> keys = track->get_keyframes();
if (keys.isEmpty()) {
continue;
@@ -501,11 +512,11 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect)
int using_index = binary_search_first_keyframe_after_or_at(keys, left_time);
Rational next_key = RATIONAL_MIN;
NodeKeyframe::Type last_type = NodeKeyframe::k_invalid;
int last_type = KeyframeTypes::k_facade_invalid;
for (int i = using_index; i < keys.size(); i++) {
NodeKeyframe *key = keys.at(i);
oak::Keyframe key = keys.at(i);
if (key->time() < next_key && key->type() == last_type) {
if (key_time(key.handle()) < next_key && key.type() == last_type) {
// This key will be drawn at exactly the same location as the last one and therefore
// doesn't need to be drawn. See if the next one will be drawn.
i++;
@@ -515,7 +526,7 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect)
key = keys.at(i);
if (key->time() < next_key) {
if (key_time(key.handle()) < next_key) {
// Next key still won't be drawn, so we'll switch to a binary search
i = binary_search_first_keyframe_after_or_at(keys, next_key);
@@ -539,14 +550,14 @@ void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect)
draw_keyframe(painter, key, track, key_rect);
next_key = get_unadjusted_keyframe_time(key, scene_to_time(key_x + 1));
last_type = key->type();
last_type = key.type();
}
}
super::drawForeground(painter, rect);
}
void KeyframeView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
void KeyframeView::draw_keyframe(QPainter *painter, const oak::Keyframe &key,
KeyframeViewInputConnection *track,
const QRectF &key_rect)
{
@@ -558,12 +569,10 @@ void KeyframeView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
painter->setBrush(track->get_brush());
}
selection_manager_.declare_drawn_object(key, key_rect);
selection_manager_.declare_drawn_object(key.handle(), key_rect);
switch (key->type()) {
case NodeKeyframe::k_invalid:
break;
case NodeKeyframe::k_linear: {
switch (key.type()) {
case KeyframeTypes::k_facade_linear: {
QPointF points[] = { QPointF(key_rect.center().x(), key_rect.top()),
QPointF(key_rect.right(), key_rect.center().y()),
QPointF(key_rect.center().x(), key_rect.bottom()),
@@ -572,12 +581,14 @@ void KeyframeView::draw_keyframe(QPainter *painter, NodeKeyframe *key,
painter->drawPolygon(points, 4);
break;
}
case NodeKeyframe::k_bezier:
case KeyframeTypes::k_facade_bezier:
painter->drawEllipse(key_rect);
break;
case NodeKeyframe::k_hold:
case KeyframeTypes::k_facade_hold:
painter->drawRect(key_rect);
break;
default:
break;
}
}
@@ -588,7 +599,7 @@ void KeyframeView::ScaleChangedEvent(const double &scale)
redraw();
}
void KeyframeView::TimeTargetChangedEvent(ViewerOutput *v)
void KeyframeView::TimeTargetChangedEvent(OakEngineNode *v)
{
redraw();
}
@@ -605,44 +616,46 @@ void KeyframeView::ContextMenuEvent(Menu &m)
Q_UNUSED(m)
}
void KeyframeView::select_keyframe(NodeKeyframe *key)
void KeyframeView::select_keyframe(const oak::Keyframe &key)
{
if (selection_manager_.select(key)) {
if (selection_manager_.select(key.handle())) {
redraw();
emit selection_changed();
}
}
void KeyframeView::deselect_keyframe(NodeKeyframe *key)
void KeyframeView::deselect_keyframe(const oak::Keyframe &key)
{
if (selection_manager_.deselect(key)) {
if (selection_manager_.deselect(key.handle())) {
redraw();
emit selection_changed();
}
}
Rational KeyframeView::get_unadjusted_keyframe_time(NodeKeyframe *key,
Rational KeyframeView::get_unadjusted_keyframe_time(const oak::Keyframe &key,
const Rational &time)
{
return get_adjusted_time(get_time_target(), key->parent(), time,
Node::k_transform_towards_input);
return get_adjusted_time(get_time_target(),
key.node().handle(), time,
k_transform_towards_input);
}
Rational KeyframeView::get_adjusted_keyframe_time(NodeKeyframe *key)
Rational KeyframeView::get_adjusted_keyframe_time(const oak::Keyframe &key)
{
return get_adjusted_time(key->parent(), get_time_target(), key->time(),
Node::k_transform_towards_output);
return get_adjusted_time(key.node().handle(),
get_time_target(), key_time(key.handle()),
k_transform_towards_output);
}
double KeyframeView::get_keyframe_scene_x(NodeKeyframe *key)
double KeyframeView::get_keyframe_scene_x(const oak::Keyframe &key)
{
return time_to_scene(get_adjusted_keyframe_time(key));
}
qreal KeyframeView::get_keyframe_scene_y(KeyframeViewInputConnection *track,
NodeKeyframe *key)
const oak::Keyframe &key)
{
return mapFromGlobal(QPoint(0, track->get_keyframe_y())).y();
}
@@ -671,13 +684,15 @@ void KeyframeView::show_context_menu()
if (!get_selected_keyframes().empty()) {
bool all_keys_are_same_type = true;
NodeKeyframe::Type type = get_selected_keyframes().front()->type();
const int type = oakengine_keyframe_get_type(
get_selected_keyframes().front());
for (size_t i = 1; i < get_selected_keyframes().size(); i++) {
NodeKeyframe *key_item = get_selected_keyframes().at(i);
NodeKeyframe *prev_item = get_selected_keyframes().at(i - 1);
OakEngineKeyframe *key_item = get_selected_keyframes().at(i);
OakEngineKeyframe *prev_item = get_selected_keyframes().at(i - 1);
if (key_item->type() != prev_item->type()) {
if (oakengine_keyframe_get_type(key_item) !=
oakengine_keyframe_get_type(prev_item)) {
all_keys_are_same_type = false;
break;
}
@@ -691,17 +706,17 @@ void KeyframeView::show_context_menu()
if (all_keys_are_same_type) {
switch (type) {
case NodeKeyframe::k_invalid:
break;
case NodeKeyframe::k_linear:
case KeyframeTypes::k_facade_linear:
linear_key_action->setChecked(true);
break;
case NodeKeyframe::k_bezier:
case KeyframeTypes::k_facade_bezier:
bezier_key_action->setChecked(true);
break;
case NodeKeyframe::k_hold:
case KeyframeTypes::k_facade_hold:
hold_key_action->setChecked(true);
break;
default:
break;
}
}
}
@@ -724,60 +739,54 @@ void KeyframeView::show_context_menu()
if (selected) {
if (selected == linear_key_action || selected == bezier_key_action ||
selected == hold_key_action) {
NodeKeyframe::Type new_type;
int new_type;
if (selected == hold_key_action) {
new_type = NodeKeyframe::k_hold;
new_type = KeyframeTypes::k_facade_hold;
} else if (selected == bezier_key_action) {
new_type = NodeKeyframe::k_bezier;
new_type = KeyframeTypes::k_facade_bezier;
} else {
new_type = NodeKeyframe::k_linear;
new_type = KeyframeTypes::k_facade_linear;
}
// Through the liboakengine C ABI facade: one undoable command
// per distinct input (usually just one), with the same batch
// semantics as the old per-keyframe commands.
struct TypeGroup {
Node *node;
OakEngineNode *node;
QString input;
int element;
QVector<int64_t> times;
QVector<int> tracks;
};
QVector<TypeGroup> groups;
foreach (NodeKeyframe *item, get_selected_keyframes()) {
foreach (OakEngineKeyframe *item, get_selected_keyframes()) {
const oak::Keyframe key(item);
OakEngineNode *node = key.node().handle();
int g = 0;
for (; g < groups.size(); g++) {
if (groups.at(g).node == item->parent() &&
groups.at(g).input == item->input() &&
groups.at(g).element == item->element()) {
if (groups.at(g).node == node &&
groups.at(g).input == key.input_id() &&
groups.at(g).element == key.element()) {
break;
}
}
if (g == groups.size()) {
groups.append({ item->parent(), item->input(),
item->element(), {}, {} });
groups.append({ node, key.input_id(),
key.element(), {}, {} });
}
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(item->parent());
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(handle, &tbn, &tbd);
oakengine_node_frame_time_base(node, &tbn, &tbd);
groups[g].times.append(Timecode::time_to_timestamp(
item->time(), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(item->track());
}
int facade_type = 0;
if (new_type == NodeKeyframe::k_bezier) {
facade_type = 1;
} else if (new_type == NodeKeyframe::k_hold) {
facade_type = 2;
key_time(item), Rational(tbn, tbd), Timecode::k_round));
groups[g].tracks.append(key.track());
}
foreach (const TypeGroup &g, groups) {
oakengine_node_keyframes_set_type_many(
reinterpret_cast<OakEngineNode *>(g.node),
g.node,
g.input.toUtf8().constData(), g.element,
g.times.constData(), g.tracks.data(), g.times.size(),
facade_type);
new_type);
}
}
}
@@ -786,7 +795,12 @@ void KeyframeView::show_context_menu()
void KeyframeView::show_keyframe_properties_dialog()
{
if (!get_selected_keyframes().empty()) {
KeyframePropertiesDialog kd(get_selected_keyframes(), timebase(), this);
QVector<oak::Keyframe> keys;
keys.reserve(int(get_selected_keyframes().size()));
foreach (OakEngineKeyframe *key, get_selected_keyframes()) {
keys.append(oak::Keyframe(key));
}
KeyframePropertiesDialog kd(keys, timebase(), this);
kd.exec();
}
}
+23 -20
View File
@@ -24,8 +24,9 @@
#include <functional>
#include "keyframehandle.h"
#include "keyframeviewinputconnection.h"
#include "node/keyframe.h"
#include "oakutil/oaknode.h"
#include "widget/menu/menu.h"
#include "widget/timebased/timebasedview.h"
#include "widget/timebased/timebasedviewselectionmanager.h"
@@ -45,14 +46,15 @@ public:
using InputConnections = QVector<ElementConnections>;
using NodeConnections = QMap<QString, InputConnections>;
NodeConnections add_keyframes_of_node(Node *n);
NodeConnections add_keyframes_of_node(const oak::Node &n);
InputConnections add_keyframes_of_input(Node *n, const QString &input);
InputConnections add_keyframes_of_input(const oak::Node &n,
const QString &input);
ElementConnections add_keyframes_of_element(const NodeInput &input);
ElementConnections add_keyframes_of_element(const oak::Input &input);
KeyframeViewInputConnection *
add_keyframes_of_track(const NodeKeyframeTrackReference &ref);
add_keyframes_of_track(const oak::KeyframeTrackRef &ref);
void remove_keyframes_of_track(KeyframeViewInputConnection *connection);
@@ -62,7 +64,7 @@ public:
void clear();
const std::vector<NodeKeyframe *> &get_selected_keyframes() const
const std::vector<OakEngineKeyframe *> &get_selected_keyframes() const
{
return selection_manager_.get_selected_objects();
}
@@ -83,7 +85,7 @@ public:
bool copy_selected(bool cut);
bool paste(std::function<Node *(const QString &)> find_node_function);
bool paste(std::function<oak::Node(const QString &)> find_node_function);
virtual void CatchUpScrollEvent() override;
@@ -101,13 +103,13 @@ protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void draw_keyframe(QPainter *painter, NodeKeyframe *key,
virtual void draw_keyframe(QPainter *painter, const oak::Keyframe &key,
KeyframeViewInputConnection *track,
const QRectF &key_rect);
virtual void ScaleChangedEvent(const double &scale) override;
virtual void TimeTargetChangedEvent(ViewerOutput *v) override;
virtual void TimeTargetChangedEvent(OakEngineNode *v) override;
virtual void TimebaseChangedEvent(const Rational &timebase) override;
@@ -135,27 +137,28 @@ protected:
{
}
void select_keyframe(NodeKeyframe *key);
void select_keyframe(const oak::Keyframe &key);
void deselect_keyframe(NodeKeyframe *key);
void deselect_keyframe(const oak::Keyframe &key);
bool is_keyframe_selected(NodeKeyframe *key) const
bool is_keyframe_selected(const oak::Keyframe &key) const
{
return selection_manager_.is_selected(key);
return selection_manager_.is_selected(key.handle());
}
Rational get_unadjusted_keyframe_time(NodeKeyframe *key, const Rational &time);
Rational get_unadjusted_keyframe_time(NodeKeyframe *key)
Rational get_unadjusted_keyframe_time(const oak::Keyframe &key,
const Rational &time);
Rational get_unadjusted_keyframe_time(const oak::Keyframe &key)
{
return get_unadjusted_keyframe_time(key, key->time());
return get_unadjusted_keyframe_time(key, key_time(key.handle()));
}
Rational get_adjusted_keyframe_time(NodeKeyframe *key);
Rational get_adjusted_keyframe_time(const oak::Keyframe &key);
double get_keyframe_scene_x(NodeKeyframe *key);
double get_keyframe_scene_x(const oak::Keyframe &key);
virtual qreal get_keyframe_scene_y(KeyframeViewInputConnection *track,
NodeKeyframe *key);
const oak::Keyframe &key);
void set_auto_select_siblings(bool e)
{
@@ -173,7 +176,7 @@ private:
QVector<KeyframeViewInputConnection *> tracks_;
TimeBasedViewSelectionManager<NodeKeyframe> selection_manager_;
TimeBasedViewSelectionManager<OakEngineKeyframe> selection_manager_;
bool autoselect_siblings_;
@@ -26,8 +26,23 @@
namespace olive
{
static bool keyframe_matches_ref(OakEngineKeyframe *key,
const oak::KeyframeTrackRef &ref)
{
const oak::Keyframe k(key);
if (k.node().handle() != ref.input().node_handle())
return false;
if (k.input_id() != ref.input().input_id())
return false;
if (k.element() != ref.input().element())
return false;
if (k.track() != ref.track())
return false;
return true;
}
KeyframeViewInputConnection::KeyframeViewInputConnection(
const NodeKeyframeTrackReference &input, KeyframeView *parent)
const oak::KeyframeTrackRef &input, KeyframeView *parent)
: QObject(parent)
, keyframe_view_(parent)
, input_(input)
@@ -36,7 +51,7 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(
, brush_(Qt::white)
, bridge_(new EngineEventBridge(this))
{
Node *n = input.input().node();
OakEngineNode *n = input.input().node_handle();
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_ADDED);
@@ -104,34 +119,35 @@ void KeyframeViewInputConnection::set_brush(const QBrush &brush)
}
}
QVector<oak::Keyframe> KeyframeViewInputConnection::get_keyframes() const
{
return input_.keyframes();
}
void KeyframeViewInputConnection::add_keyframe(OakEngineKeyframe *key)
{
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
if (keyframe_matches_ref(key, input_)) {
emit require_update();
}
}
void KeyframeViewInputConnection::remove_keyframe(OakEngineKeyframe *key)
{
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
if (keyframe_matches_ref(key, input_)) {
emit require_update();
}
}
void KeyframeViewInputConnection::keyframe_changed(OakEngineKeyframe *key)
{
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
if (keyframe_matches_ref(key, input_)) {
emit require_update();
}
}
void KeyframeViewInputConnection::keyframe_type_changed(OakEngineKeyframe *key)
{
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
if (keyframe_matches_ref(key, input_)) {
emit type_changed();
}
}
@@ -22,13 +22,12 @@
#ifndef OAK_KEYFRAMEVIEWINPUTCONNECTION_H
#define OAK_KEYFRAMEVIEWINPUTCONNECTION_H
#include <QBrush>
#include <QObject>
#include <QVector>
#include "engineeventbridge.h"
#include "node/node.h"
#include "node/param.h"
struct OakEngineKeyframe;
#include "oakutil/oaknode.h"
namespace olive
{
@@ -38,7 +37,7 @@ class KeyframeView;
class KeyframeViewInputConnection : public QObject {
Q_OBJECT
public:
KeyframeViewInputConnection(const NodeKeyframeTrackReference &input,
KeyframeViewInputConnection(const oak::KeyframeTrackRef &input,
KeyframeView *parent);
const int &get_keyframe_y() const
@@ -52,20 +51,14 @@ public:
void set_y_behavior(YBehavior e);
const QVector<NodeKeyframe *> &get_keyframes() const
{
return input_.input()
.node()
->get_keyframe_tracks(input_.input())
.at(input_.track());
}
QVector<oak::Keyframe> get_keyframes() const;
const QBrush &get_brush() const
{
return brush_;
}
const NodeKeyframeTrackReference &get_reference() const
const oak::KeyframeTrackRef &get_reference() const
{
return input_;
}
@@ -80,7 +73,7 @@ signals:
private:
KeyframeView *keyframe_view_;
NodeKeyframeTrackReference input_;
oak::KeyframeTrackRef input_;
int y_;
@@ -29,11 +29,16 @@
#include <oakengine/color.h>
#include <olive/core/core.h>
#include "render/colortransform.h"
#include "oakutil/oakvideo.h"
#include "oakutil/qtutils.h"
namespace olive
{
// Same namespace bridge the engine headers used to provide (unqualified
// Color/Rational/... inside namespace olive).
using namespace core;
class ColorManager;
/**
@@ -72,11 +77,11 @@ public:
color_input_ = color_input;
}
const ColorTransform &color_output() const
const oak::ColorTransform &color_output() const
{
return color_transform_;
}
void set_color_output(const ColorTransform &color_output)
void set_color_output(const oak::ColorTransform &color_output)
{
color_transform_ = color_output;
}
@@ -84,7 +89,7 @@ public:
private:
QString color_input_;
ColorTransform color_transform_;
oak::ColorTransform color_transform_;
};
/**
@@ -149,10 +154,10 @@ QStringList oak_query_string_list(CountFn &&count_fn, AtFn &&at_fn)
}
/**
* @brief Convert an olive::ColorTransform to the facade POD. The QByteArray
* @brief Convert an oak::ColorTransform to the facade POD. The QByteArray
* outputs back the POD's pointers and must outlive its use.
*/
inline oak_color_transform oak_to_transform(const ColorTransform &t,
inline oak_color_transform oak_to_transform(const oak::ColorTransform &t,
QByteArray *output,
QByteArray *view,
QByteArray *look)
@@ -172,8 +177,8 @@ inline oak_color_transform oak_to_transform(const ColorTransform &t,
* @brief ColorManager::get_compliant_color_space(ColorTransform) through
* the facade.
*/
inline ColorTransform oak_compliant_transform(ColorManager *mgr,
const ColorTransform &in,
inline oak::ColorTransform oak_compliant_transform(ColorManager *mgr,
const oak::ColorTransform &in,
bool force_display = false)
{
QByteArray o, v, l;
@@ -187,10 +192,11 @@ inline ColorTransform oak_compliant_transform(ColorManager *mgr,
return in;
}
if (is_display) {
return ColorTransform(QString::fromUtf8(out), QString::fromUtf8(view),
QString::fromUtf8(look));
return oak::ColorTransform(QString::fromUtf8(out),
QString::fromUtf8(view),
QString::fromUtf8(look));
}
return ColorTransform(QString::fromUtf8(out));
return oak::ColorTransform(QString::fromUtf8(out));
}
/**
@@ -212,7 +218,7 @@ inline QString oak_compliant_color_space(ColorManager *mgr, const QString &s)
*/
inline ColorProcessorHandlePtr
oak_make_color_processor(ColorManager *mgr, const QString &input,
const ColorTransform &dest,
const oak::ColorTransform &dest,
int direction = OAKENGINE_COLOR_PROCESSOR_NORMAL)
{
QByteArray o, v, l;
+13 -13
View File
@@ -169,7 +169,7 @@ void ManagedDisplayWidget::disconnect_color_manager()
connect_color_manager(nullptr);
}
const ColorTransform &ManagedDisplayWidget::get_color_transform() const
const oak::ColorTransform &ManagedDisplayWidget::get_color_transform() const
{
return color_transform_;
}
@@ -227,7 +227,7 @@ void ManagedDisplayWidget::color_config_changed()
});
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(display, view, QString()), true));
oak::ColorTransform(display, view, QString()), true));
} else {
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
@@ -260,11 +260,11 @@ void ManagedDisplayWidget::show_default_context_menu()
void ManagedDisplayWidget::menu_display_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
const oak::ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = oak_compliant_transform(
oak::ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(action->data().toString(), old_transform.view(),
oak::ColorTransform(action->data().toString(), old_transform.view(),
old_transform.look()));
set_color_transform(new_transform);
@@ -272,11 +272,11 @@ void ManagedDisplayWidget::menu_display_select(QAction *action)
void ManagedDisplayWidget::menu_view_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
const oak::ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = oak_compliant_transform(
oak::ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(old_transform.display(), action->data().toString(),
oak::ColorTransform(old_transform.display(), action->data().toString(),
old_transform.look()));
set_color_transform(new_transform);
@@ -284,11 +284,11 @@ void ManagedDisplayWidget::menu_view_select(QAction *action)
void ManagedDisplayWidget::menu_look_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
const oak::ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = oak_compliant_transform(
oak::ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(old_transform.display(), old_transform.view(),
oak::ColorTransform(old_transform.display(), old_transform.view(),
action->data().toString()));
set_color_transform(new_transform);
@@ -298,7 +298,7 @@ void ManagedDisplayWidget::menu_colorspace_select(QAction *action)
{
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(action->data().toString())));
oak::ColorTransform(action->data().toString())));
}
void ManagedDisplayWidget::on_destroy()
@@ -306,7 +306,7 @@ void ManagedDisplayWidget::on_destroy()
oakengine_display_renderer_destroy(attached_renderer_);
}
void ManagedDisplayWidget::set_color_transform(const ColorTransform &transform)
void ManagedDisplayWidget::set_color_transform(const oak::ColorTransform &transform)
{
color_transform_ = transform;
+3 -5
View File
@@ -35,8 +35,6 @@
#include "oakengine/color.h"
#include "oakengine/display.h"
#include "oakengine/events.h"
#include "render/colorprocessor.h"
#include "render/colortransform.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/menu/menu.h"
@@ -151,7 +149,7 @@ public:
/**
* @brief Get current color transform
*/
const ColorTransform &get_color_transform() const;
const oak::ColorTransform &get_color_transform() const;
/**
* @brief Get menu that can be used to select the colorspace
@@ -184,7 +182,7 @@ public slots:
/**
* @brief Replaces the color transform with a new one
*/
void set_color_transform(const ColorTransform &transform);
void set_color_transform(const oak::ColorTransform &transform);
/**
* @brief Connect a ColorManager (ColorManagers usually belong to the Project)
@@ -318,7 +316,7 @@ private:
/**
* @brief Internal color transform storage
*/
ColorTransform color_transform_;
oak::ColorTransform color_transform_;
bool is_backend_neutral_ = false;
+24 -27
View File
@@ -29,39 +29,35 @@ namespace olive
{
Menu *create_node_menu(QWidget *parent, bool create_none_item,
Node::CategoryID restrict_to, uint64_t restrict_flags)
oak::NodeCategory restrict_to, uint64_t restrict_flags)
{
const int library_size = oakengine_node_factory_id_count();
const int library_size = oak::Node::factory_count();
Menu *menu = new Menu(parent);
menu->setToolTipsVisible(true);
for (int i = 0; i < library_size; i++) {
olive::Node *n = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(i));
oak::Node n = oak::Node::factory_node_at(i);
if (restrict_to != Node::k_category_unknown &&
!n->category().contains(restrict_to)) {
// Skip this node
if (restrict_to != oak::k_category_unknown &&
!n.in_category(restrict_to)) {
continue;
}
if (restrict_flags && !(n->get_flags() & restrict_flags)) {
const uint64_t flags = n.flags();
if (restrict_flags && !(flags & restrict_flags)) {
continue;
}
if (n->get_flags() & Node::k_dont_show_in_create_menu) {
if (flags & oak::Node::flag_dont_show_in_create_menu()) {
continue;
}
// Make sure nodes are up-to-date with the current translation
n->retranslate();
n.retranslate();
char cat_buf[256];
oakengine_node_category_name(
n->category().isEmpty() ? 0 : n->category().first(),
cat_buf, sizeof(cat_buf));
QString category_name = QString::fromUtf8(cat_buf);
const int cat_count = n.category_count();
QString category_name = oak::Node::category_name(cat_count == 0 ? 0 : n.category_at(0));
// Find or create top-level category menu
Menu *top_menu = nullptr;
@@ -79,8 +75,9 @@ Menu *create_node_menu(QWidget *parent, bool create_none_item,
// Determine final destination (support secondary grouping)
Menu *destination = top_menu;
QString sub = n->sub_category();
if (!sub.isEmpty() && n->category().contains(Node::k_category_open_fx)) {
QString sub = n.sub_category();
bool is_openfx = n.in_category(oak::k_category_open_fx);
if (!sub.isEmpty() && is_openfx) {
QList<QAction *> sub_actions = top_menu->actions();
foreach (QAction *action, sub_actions) {
if (action->menu() && action->menu()->title() == sub) {
@@ -95,9 +92,9 @@ Menu *create_node_menu(QWidget *parent, bool create_none_item,
}
// Add entry to menu
QAction *a = destination->insert_alphabetically(n->name());
QAction *a = destination->insert_alphabetically(n.name());
a->setData(i);
a->setToolTip(n->description());
a->setToolTip(n.description());
}
if (create_none_item) {
@@ -117,17 +114,15 @@ Menu *create_node_menu(QWidget *parent, bool create_none_item,
return menu;
}
Node *create_node_from_menu_action(QAction *action)
oak::Node create_node_from_menu_action(QAction *action)
{
int index = action->data().toInt();
if (index == -1) {
return nullptr;
return oak::Node();
}
olive::Node *proto = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(index));
return proto ? proto->copy() : nullptr;
return oak::Node::factory_node_at(index).create_copy();
}
QString get_node_id_from_menu_action(QAction *action)
@@ -138,9 +133,11 @@ QString get_node_id_from_menu_action(QAction *action)
return QString();
}
olive::Node *proto = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(index));
return proto ? proto->id() : QString();
oak::Node proto = oak::Node::factory_node_at(index);
if (proto.is_null()) {
return QString();
}
return proto.id();
}
}
+7 -5
View File
@@ -24,8 +24,7 @@
#include <QAction>
#include "oakengine/node.h"
#include "node/node.h"
#include "oakutil/oaknode.h"
#include "widget/menu/menu.h"
namespace olive
@@ -41,15 +40,18 @@ namespace olive
* get_node_id_from_menu_action().
*/
Menu *create_node_menu(QWidget *parent, bool create_none_item = false,
Node::CategoryID restrict_to = Node::k_category_unknown,
oak::NodeCategory restrict_to = oak::k_category_unknown,
uint64_t restrict_flags = 0);
/**
* @brief Create a node from an action of a menu built by create_node_menu()
*
* Returns nullptr for the "None" item.
* Returns a null node for the "None" item.
*
* NOTE: the returned handle is OWNED by the caller (not added to any
* project); hand it on to a project/undo command.
*/
Node *create_node_from_menu_action(QAction *action);
oak::Node create_node_from_menu_action(QAction *action);
/**
* @brief Get the node ID from an action of a menu built by create_node_menu()
+5 -3
View File
@@ -21,6 +21,8 @@
#include "multicamdisplay.h"
#include <QPainter>
#include "oakengine/display.h"
#include "oakengine/node.h"
@@ -51,7 +53,7 @@ void MulticamDisplay::on_paint()
int rows, cols;
oakengine_multicam_get_rows_and_columns(
oakengine_multicam_get_source_count(
reinterpret_cast<OakEngineNode *>(node_)),
node_),
&rows, &cols);
int multi = std::max(rows, cols);
@@ -60,7 +62,7 @@ void MulticamDisplay::on_paint()
int col, row;
int current_source = oakengine_multicam_get_current_source(
reinterpret_cast<OakEngineNode *>(node_));
node_);
oakengine_multicam_index_to_row_cols(
current_source, rows, cols, &row, &col);
@@ -211,7 +213,7 @@ QString MulticamDisplay::generate_shader_code(int rows, int cols)
return shader.join('\n');
}
void MulticamDisplay::set_multicam_node(MultiCamNode *n)
void MulticamDisplay::set_multicam_node(OakEngineNode *n)
{
node_ = n;
}
+2 -3
View File
@@ -22,7 +22,6 @@
#ifndef OAK_MULTICAMDISPLAY_H
#define OAK_MULTICAMDISPLAY_H
#include "node/input/multicam/multicamnode.h"
#include "widget/viewer/viewerdisplay.h"
namespace olive
@@ -33,7 +32,7 @@ class MulticamDisplay : public ViewerDisplayWidget {
public:
explicit MulticamDisplay(QWidget *parent = nullptr);
void set_multicam_node(MultiCamNode *n);
void set_multicam_node(OakEngineNode *n);
protected:
virtual void on_paint() override;
@@ -45,7 +44,7 @@ protected:
private:
static QString generate_shader_code(int rows, int cols);
MultiCamNode *node_;
OakEngineNode *node_;
void *shader_;
int rows_;
+49 -34
View File
@@ -29,7 +29,6 @@
#include "oakengine/viewer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelineundosplit.h"
#include "widget/timeruler/timeruler.h"
namespace olive
@@ -71,8 +70,9 @@ MulticamWidget::MulticamWidget(QWidget *parent)
}
}
void MulticamWidget::set_multicam_node_internal(ViewerOutput *viewer,
MultiCamNode *n, ClipBlock *clip)
void MulticamWidget::set_multicam_node_internal(OakEngineNode *viewer,
OakEngineNode *n,
OakEngineBlock *clip)
{
if (get_connected_node() != viewer) {
connect_viewer_node(viewer);
@@ -88,11 +88,12 @@ void MulticamWidget::set_multicam_node_internal(ViewerOutput *viewer,
}
}
void MulticamWidget::set_multicam_node(ViewerOutput *viewer, MultiCamNode *n,
ClipBlock *clip, const Rational &time)
void MulticamWidget::set_multicam_node(OakEngineNode *viewer,
OakEngineNode *n, OakEngineBlock *clip,
const Rational &time)
{
if (time.isNaN() || !get_connected_node() ||
time == get_connected_node()->get_playhead()) {
time == viewer_output_playhead(get_connected_node())) {
set_multicam_node_internal(viewer, n, clip);
play_queue_.clear();
} else {
@@ -101,10 +102,8 @@ void MulticamWidget::set_multicam_node(ViewerOutput *viewer, MultiCamNode *n,
}
}
void MulticamWidget::ConnectNodeEvent(ViewerOutput *n)
void MulticamWidget::ConnectNodeEvent(OakEngineNode *handle)
{
OakEngineNode *handle = reinterpret_cast<OakEngineNode *>(n);
viewer_sub_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_VIEWER_SIZE_CHANGED,
[](const oakengine_event *event, void *userdata) {
@@ -128,7 +127,7 @@ void MulticamWidget::ConnectNodeEvent(ViewerOutput *n)
Rational(vp.pixel_aspect_num, vp.pixel_aspect_den));
}
void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n)
void MulticamWidget::DisconnectNodeEvent(OakEngineNode *n)
{
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
@@ -159,37 +158,53 @@ void MulticamWidget::Switch(int source, bool split_clip)
return;
}
MultiCamNode *cam = node_;
ClipBlock *clip = clip_;
OakEngineNode *cam = node_;
OakEngineBlock *clip = clip_;
const QByteArray undo_name = tr("Switched Multi-Camera Source").toUtf8();
oakengine_undo_group_begin(undo_name.constData());
// Block range via the C ABI (clip is an opaque block handle; the facade
// returns rational seconds, comparable to get_playhead()).
int clip_in_num = 0, clip_in_den = 1, clip_out_num = 0, clip_out_den = 1;
if (clip_) {
oakengine_block_get_in_rational(
reinterpret_cast<OakEngineNode *>(clip_), &clip_in_num,
&clip_in_den);
oakengine_block_get_out_rational(
reinterpret_cast<OakEngineNode *>(clip_), &clip_out_num,
&clip_out_den);
}
if (clip_ && split_clip &&
clip_->in() < get_connected_node()->get_playhead() &&
clip_->out() > get_connected_node()->get_playhead()) {
QVector<Block *> blocks;
Rational(clip_in_num, clip_in_den) <
viewer_output_playhead(get_connected_node()) &&
Rational(clip_out_num, clip_out_den) >
viewer_output_playhead(get_connected_node())) {
QVector<OakEngineBlock *> blocks;
blocks.append(clip_);
blocks.append(clip_->block_links());
// ClipBlock::block_links() via the C ABI link enumeration
const int link_count = oakengine_block_link_count(clip_);
for (int i = 0; i < link_count; i++) {
blocks.append(oakengine_block_link_at(clip_, i));
}
int split_tbn = 0, split_tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_node_frame_time_base(get_connected_node(),
&split_tbn, &split_tbd);
void *split = oakengine_block_split_preserving_links_command(
reinterpret_cast<void *const *>(blocks.data()), blocks.size(),
olive::core::Timecode::time_to_timestamp(
get_connected_node()->get_playhead(),
viewer_output_playhead(get_connected_node()),
olive::Rational(split_tbn, split_tbd),
olive::core::Timecode::k_round));
oakengine_undo_push(split, undo_name.constData());
clip = reinterpret_cast<ClipBlock *>(
oakengine_block_split_get_split(
split, reinterpret_cast<void *>(clip_), 0));
clip = reinterpret_cast<OakEngineBlock *>(
oakengine_block_split_get_split(split, clip_, 0));
cam = reinterpret_cast<MultiCamNode *>(
oakengine_clip_find_multicam(reinterpret_cast<OakEngineNode *>(clip)));
cam = oakengine_clip_find_multicam(
reinterpret_cast<OakEngineNode *>(clip));
}
oak_node_value val;
@@ -199,18 +214,19 @@ void MulticamWidget::Switch(int source, bool split_clip)
if (cam) {
oakengine_node_set_input(
reinterpret_cast<OakEngineNode *>(cam),
oakengine_multicam_input_current(), &val);
cam, oakengine_multicam_input_current(), &val);
}
if (clip) {
for (Block *link : clip->block_links()) {
if (ClipBlock *clink = dynamic_cast<ClipBlock *>(link)) {
if (MultiCamNode *mlink = reinterpret_cast<MultiCamNode *>(
oakengine_clip_find_multicam(reinterpret_cast<OakEngineNode *>(clink)))) {
const int link_count = oakengine_block_link_count(clip);
for (int i = 0; i < link_count; i++) {
OakEngineNode *link = reinterpret_cast<OakEngineNode *>(
oakengine_block_link_at(clip, i));
if (oakengine_node_is_clip(link)) {
if (OakEngineNode *mlink =
oakengine_clip_find_multicam(link)) {
oakengine_node_set_input(
reinterpret_cast<OakEngineNode *>(mlink),
oakengine_multicam_input_current(), &val);
mlink, oakengine_multicam_input_current(), &val);
}
}
}
@@ -240,8 +256,7 @@ void MulticamWidget::display_clicked(const QPoint &p)
int rows, cols;
oakengine_multicam_get_rows_and_columns(
oakengine_multicam_get_source_count(
reinterpret_cast<OakEngineNode *>(node_)),
oakengine_multicam_get_source_count(node_),
&rows, &cols);
int multi = std::max(cols, rows);
+12 -12
View File
@@ -25,7 +25,7 @@
#include "multicamdisplay.h"
#include <cstdint>
#include "node/input/multicam/multicamnode.h"
#include "oakengine/timeline.h"
#include "widget/viewer/viewer.h"
namespace olive
@@ -41,20 +41,20 @@ public:
return display_;
}
void set_multicam_node(ViewerOutput *viewer, MultiCamNode *n, ClipBlock *clip,
const Rational &time);
void set_multicam_node(OakEngineNode *viewer, OakEngineNode *n,
OakEngineBlock *clip, const Rational &time);
protected:
virtual void ConnectNodeEvent(ViewerOutput *n) override;
virtual void DisconnectNodeEvent(ViewerOutput *n) override;
virtual void ConnectNodeEvent(OakEngineNode *n) override;
virtual void DisconnectNodeEvent(OakEngineNode *n) override;
virtual void TimeChangedEvent(const Rational &t) override;
signals:
void switched();
private:
void set_multicam_node_internal(ViewerOutput *viewer, MultiCamNode *n,
ClipBlock *clip);
void set_multicam_node_internal(OakEngineNode *viewer, OakEngineNode *n,
OakEngineBlock *clip);
void Switch(int source, bool split_clip);
@@ -65,15 +65,15 @@ private:
MulticamDisplay *display_;
MultiCamNode *node_;
OakEngineNode *node_;
ClipBlock *clip_;
OakEngineBlock *clip_;
struct MulticamNodeQueue {
Rational time;
ViewerOutput *viewer;
MultiCamNode *node;
ClipBlock *clip;
OakEngineNode *viewer;
OakEngineNode *node;
OakEngineBlock *clip;
};
std::list<MulticamNodeQueue> play_queue_;
-2
View File
@@ -24,8 +24,6 @@
#include <QComboBox>
#include "node/node.h"
namespace olive
{
@@ -19,7 +19,6 @@
#ifndef OAK_NODEPARAMBUTTON_H
#define OAK_NODEPARAMBUTTON_H
#include "node/plugins/plugin.h"
#include <QPushButton>
+187 -153
View File
@@ -28,10 +28,11 @@
#include <QSet>
#include <QSplitter>
#include "node/output/viewer/viewer.h"
#include "oakengine/footage.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "oakutil/oaknode.h"
#include "widget/timelinewidget/cliphandle.h"
#include "widget/timeruler/timeruler.h"
namespace olive
@@ -78,7 +79,7 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent)
param_widget_container_layout->addStretch(INT_MAX);
// Create contexts for three different types
context_items_.resize(Track::k_count + 1);
context_items_.resize(TrackReference::k_count + 1);
for (int i = 0; i < context_items_.size(); i++) {
NodeParamViewContext *c = new NodeParamViewContext(param_widget_area_);
c->setVisible(false);
@@ -88,8 +89,8 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent)
NodeParamViewItemTitleBar *title_bar =
static_cast<NodeParamViewItemTitleBar *>(c->titleBarWidget());
if (i == Track::k_video || i == Track::k_audio) {
c->set_effect_type(static_cast<Track::Type>(i));
if (i == TrackReference::k_video || i == TrackReference::k_audio) {
c->set_effect_type(static_cast<TrackReference::Type>(i));
title_bar->set_add_effect_button_visible(true);
title_bar->set_text(tr("%1 Nodes")
.arg(QString::fromUtf8(
@@ -189,23 +190,21 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent)
[this](OakEngineNode *source, OakEngineNode *node,
const QString &input, int element) {
group_input_passthrough_added(source,
NodeInput(reinterpret_cast<Node *>(node), input, element));
oak::Input(node, input, element));
});
connect(bridge_, &EngineEventBridge::group_input_passthrough_removed, this,
[this](OakEngineNode *source, OakEngineNode *node,
const QString &input, int element) {
group_input_passthrough_removed(source,
NodeInput(reinterpret_cast<Node *>(node), input, element));
oak::Input(node, input, element));
});
connect(bridge_, &EngineEventBridge::node_node_added_to_context, this,
[this](OakEngineNode *source, OakEngineNode *node) {
node_added_to_context(reinterpret_cast<Node *>(node),
reinterpret_cast<Node *>(source));
node_added_to_context(node, source);
}, Qt::QueuedConnection);
connect(bridge_, &EngineEventBridge::node_node_removed_from_context, this,
[this](OakEngineNode *source, OakEngineNode *node) {
node_removed_from_context(reinterpret_cast<Node *>(node),
reinterpret_cast<Node *>(source));
node_removed_from_context(node, source);
}, Qt::QueuedConnection);
}
@@ -214,12 +213,12 @@ NodeParamView::~NodeParamView()
qDeleteAll(context_items_);
}
void NodeParamView::close_contexts_belonging_to_project(Project *p)
void NodeParamView::close_contexts_belonging_to_project(oak::Project p)
{
QVector<Node *> new_contexts = contexts_;
QVector<oak::Node> new_contexts = contexts_;
for (int i = 0; i < new_contexts.size(); i++) {
if (new_contexts.at(i)->project() == p) {
if (new_contexts.at(i).project().handle() == p.handle()) {
new_contexts.removeAt(i);
i--;
}
@@ -228,12 +227,12 @@ void NodeParamView::close_contexts_belonging_to_project(Project *p)
set_contexts(new_contexts);
}
/*void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
/*void NodeParamView::SelectNodes(const QVector<OakEngineNode *> &nodes)
{
return;
int original_node_count = items_.size();
foreach (Node* n, nodes) {
foreach (OakEngineNode* n, nodes) {
// If we've already added this node (either a duplicate or a pinned node), don't add another
if (items_.contains(n)) {
continue;
@@ -256,13 +255,13 @@ void NodeParamView::close_contexts_belonging_to_project(Project *p)
}
}
void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
void NodeParamView::DeselectNodes(const QVector<OakEngineNode *> &nodes)
{
return;
// Remove item from map and delete the widget
int original_node_count = items_.size();
foreach (Node* n, nodes) {
foreach (OakEngineNode* n, nodes) {
// Filter out duplicates
if (!items_.contains(n)) {
continue;
@@ -291,7 +290,7 @@ void NodeParamView::update_contexts()
{
bool changes_made = false;
foreach (Node *ctx, current_contexts_) {
foreach (const oak::Node &ctx, current_contexts_) {
if (!contexts_.contains(ctx)) {
// Context is being removed
remove_context(ctx);
@@ -299,7 +298,7 @@ void NodeParamView::update_contexts()
}
}
foreach (Node *ctx, contexts_) {
foreach (const oak::Node &ctx, contexts_) {
if (!current_contexts_.contains(ctx)) {
// Context is being added
add_context(ctx);
@@ -324,28 +323,30 @@ void NodeParamView::update_contexts()
if (is_group_mode()) {
// Check inputs that have been passed through
Node *group = contexts_.first();
const int pt_count = oakengine_group_input_passthrough_count(
reinterpret_cast<OakEngineNode *>(group));
oak::Node group = contexts_.first();
// WRAPPER-GAP: oakengine_group_input_passthrough_* (group API
// has no oak:: wrapper)
const int pt_count =
oakengine_group_input_passthrough_count(group.handle());
for (int i = 0; i < pt_count; i++) {
OakEngineNode *inner_node = nullptr;
char inner_input[256];
int inner_element = 0;
char id[256];
if (oakengine_group_input_passthrough_at(
reinterpret_cast<OakEngineNode *>(group), i,
group.handle(), i,
id, sizeof(id), &inner_node, inner_input,
sizeof(inner_input), &inner_element) ==
OAKENGINE_OK) {
group_input_passthrough_added(
reinterpret_cast<OakEngineNode *>(group),
NodeInput(reinterpret_cast<Node *>(inner_node),
group.handle(),
oak::Input(inner_node,
QString::fromUtf8(inner_input),
inner_element));
}
}
OakEngineNode *group_handle = reinterpret_cast<OakEngineNode *>(group);
OakEngineNode *group_handle = group.handle();
group_passthrough_added_sub_ = bridge_->subscribe(
group_handle, OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_ADDED);
group_passthrough_removed_sub_ = bridge_->subscribe(
@@ -391,7 +392,7 @@ void NodeParamView::select_node_from_connected_link(OakEngineNode *node)
NodeParamViewItem *item = static_cast<NodeParamViewItem *>(sender());
QPair<OakEngineNode *, OakEngineNode *> p = qMakePair(
node, reinterpret_cast<OakEngineNode *>(item->get_context()));
node, item->get_context().handle());
set_selected_nodes({ p });
}
@@ -403,7 +404,7 @@ void NodeParamView::request_edit_text_in_viewer()
emit request_viewer_to_start_editing_text();
}
void NodeParamView::set_contexts(const QVector<Node *> &contexts)
void NodeParamView::set_contexts(const QVector<oak::Node> &contexts)
{
// Setting contexts is expensive, so we queue it here to prevent multiple calls in a short timespan
contexts_ = contexts;
@@ -439,7 +440,7 @@ void NodeParamView::TimebaseChangedEvent(const Rational &timebase)
}
}
void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n)
void NodeParamView::ConnectedNodeChangeEvent(OakEngineNode *n)
{
if (keyframe_view_) {
// Set viewer as a time target
@@ -474,23 +475,27 @@ struct ReconnectEdgeList {
}
};
void collect_reconnect_edges(QSet<Node *> &deleted_nodes, Node *output,
Node *deleting, ReconnectEdgeList &edges)
void collect_reconnect_edges(QSet<OakEngineNode *> &deleted_nodes,
OakEngineNode *output,
OakEngineNode *deleting, ReconnectEdgeList &edges)
{
for (auto it = deleting->output_connections().cbegin();
it != deleting->output_connections().cend(); it++) {
const NodeInput &proposed_reconnect = it->second;
// Output-connection enumeration goes through the oak:: wrapper (C ABI);
// replaces the engine Node::output_connections() map iteration.
const oak::Node deleting_handle(deleting);
const int connection_count = deleting_handle.output_connection_count();
for (int i = 0; i < connection_count; i++) {
const oak::NodeConnection proposed_reconnect =
deleting_handle.output_connection_at_ex(i);
OakEngineNode *proposed_node = proposed_reconnect.node.handle();
if (deleted_nodes.contains(proposed_reconnect.node())) {
if (deleted_nodes.contains(proposed_node)) {
// Uh-oh we're deleting this node too, instead connect to its outputs
collect_reconnect_edges(deleted_nodes, output,
proposed_reconnect.node(), edges);
collect_reconnect_edges(deleted_nodes, output, proposed_node, edges);
} else {
edges.outputs.append(reinterpret_cast<OakEngineNode *>(output));
edges.input_nodes.append(
reinterpret_cast<OakEngineNode *>(proposed_reconnect.node()));
edges.ids_storage.append(proposed_reconnect.input().toUtf8());
edges.elements.append(proposed_reconnect.element());
edges.outputs.append(output);
edges.input_nodes.append(proposed_reconnect.node.handle());
edges.ids_storage.append(proposed_reconnect.input_id.toUtf8());
edges.elements.append(proposed_reconnect.element);
}
}
}
@@ -503,13 +508,13 @@ void NodeParamView::DeleteSelected()
QVector<OakEngineNode *> nodes;
QVector<OakEngineNode *> contexts;
QSet<Node *> deleted_nodes_set;
QSet<OakEngineNode *> deleted_nodes_set;
// Collect all nodes to delete
foreach (NodeParamViewItem *item, selected_nodes_) {
Node *n = item->get_node();
nodes.append(reinterpret_cast<OakEngineNode *>(n));
contexts.append(reinterpret_cast<OakEngineNode *>(item->get_context()));
OakEngineNode *n = item->get_node().handle();
nodes.append(n);
contexts.append(item->get_context().handle());
deleted_nodes_set.insert(n);
}
@@ -517,16 +522,18 @@ void NodeParamView::DeleteSelected()
// facade, inside the same undoable command)
ReconnectEdgeList edges;
foreach (NodeParamViewItem *item, selected_nodes_) {
Node *n = item->get_node();
OakEngineNode *n = item->get_node().handle();
Node *node_being_deleted = n;
Node *connected_to_effect_input = nullptr;
OakEngineNode *node_being_deleted = n;
OakEngineNode *connected_to_effect_input = nullptr;
while (true) {
if (node_being_deleted->get_effect_input().is_valid()) {
if ((connected_to_effect_input =
node_being_deleted->get_effect_input()
.get_connected_output())) {
oak::Input effect_input =
oak::Node(node_being_deleted).effect_input();
if (effect_input.is_valid()) {
oak::Node connected = effect_input.connected_node();
if (!connected.is_null()) {
connected_to_effect_input = connected.handle();
if (deleted_nodes_set.contains(connected_to_effect_input)) {
// Node's getting deleted, recurse
node_being_deleted = connected_to_effect_input;
@@ -581,8 +588,8 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
n->set_highlighted(true);
if (emit_signal) {
p[i] = qMakePair(reinterpret_cast<OakEngineNode *>(n->get_node()),
reinterpret_cast<OakEngineNode *>(n->get_context()));
p[i] = qMakePair(n->get_node().handle(),
n->get_context().handle());
}
}
@@ -590,14 +597,15 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
focused_node_ = nullptr;
foreach (NodeParamViewItem *n, selected_nodes_) {
if (n->get_node()->has_gizmos()) {
if (oakengine_node_has_gizmos(n->get_node().handle())) {
focused_node_ = n;
break;
}
}
Node *n = focused_node_ ? focused_node_->get_node() : nullptr;
emit focused_node_changed(reinterpret_cast<OakEngineNode *>(n));
OakEngineNode *n = focused_node_ ? focused_node_->get_node().handle() :
nullptr;
emit focused_node_changed(n);
}
if (emit_signal) {
@@ -613,13 +621,12 @@ void NodeParamView::set_selected_nodes(
NodeParamViewContext *scrolled_ctx = nullptr;
foreach (const auto &n, nodes) {
Node *node = reinterpret_cast<Node *>(n.first);
Node *context = reinterpret_cast<Node *>(n.second);
for (auto it = context_items_.cbegin(); it != context_items_.cend();
it++) {
NodeParamViewContext *ctx = *it;
NodeParamViewItem *item = ctx->get_item(node, context);
NodeParamViewItem *item =
ctx->get_item(oak::Node(n.first), oak::Node(n.second));
if (item) {
items.append(item);
@@ -649,25 +656,26 @@ void NodeParamView::set_selected_nodes(
}
}
Node *NodeParamView::get_node_with_id(const QString &id)
OakEngineNode *NodeParamView::get_node_with_id(const QString &id)
{
return get_node_with_id_and_ignore_list(id, QVector<Node *>());
return get_node_with_id_and_ignore_list(id, QVector<OakEngineNode *>());
}
Node *NodeParamView::get_node_with_id_and_ignore_list(const QString &id,
const QVector<Node *> &ignore)
OakEngineNode *NodeParamView::get_node_with_id_and_ignore_list(const QString &id,
const QVector<OakEngineNode *> &ignore)
{
for (NodeParamViewItem *item : selected_nodes_) {
if (item->get_node()->id() == id && !ignore.contains(item->get_node())) {
return item->get_node();
OakEngineNode *n = item->get_node().handle();
if (item->get_node().id() == id && !ignore.contains(n)) {
return n;
}
}
for (NodeParamViewContext *ctx : context_items_) {
for (NodeParamViewItem *item : ctx->get_items()) {
if (item->get_node()->id() == id &&
!ignore.contains(item->get_node())) {
return item->get_node();
OakEngineNode *n = item->get_node().handle();
if (item->get_node().id() == id && !ignore.contains(n)) {
return n;
}
}
}
@@ -693,33 +701,34 @@ bool NodeParamView::copy_selected(bool cut)
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_NODES, nullptr, nullptr);
QVector<Node *> nodes;
QVector<OakEngineNode *> nodes;
for (NodeParamViewItem *item : selected_nodes_) {
Node *n = item->get_node();
OakEngineNode *n = item->get_node().handle();
if (!nodes.contains(n)) {
nodes.append(n);
Node::Position pos =
item->get_context()->get_node_position_data_in_context(n);
QPointF pos;
bool expanded = false;
item->get_context().context_position_of(
item->get_node(), &pos, &expanded);
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "x",
QByteArray::number(pos.position.x()).constData());
cb, n, "x",
QByteArray::number(pos.x()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "y",
QByteArray::number(pos.position.y()).constData());
cb, n, "y",
QByteArray::number(pos.y()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "expanded",
QByteArray::number(pos.expanded).constData());
cb, n, "expanded",
QByteArray::number(expanded).constData());
}
}
oakengine_clipboard_set_nodes(
cb,
reinterpret_cast<const OakEngineNode *const *>(
nodes.constData()),
nodes.constData(),
nodes.size());
oakengine_clipboard_copy(cb);
@@ -735,8 +744,9 @@ bool NodeParamView::copy_selected(bool cut)
bool NodeParamView::paste()
{
if (keyframe_view_) {
if (keyframe_view_->paste(std::bind(&NodeParamView::get_node_with_id, this,
std::placeholders::_1))) {
if (keyframe_view_->paste([this](const QString &id) {
return oak::Node(get_node_with_id(id));
})) {
return true;
}
}
@@ -747,7 +757,7 @@ bool NodeParamView::paste()
bool NodeParamView::paste(
QWidget *parent,
std::function<QHash<Node *, Node *>(void *)>
std::function<QHash<OakEngineNode *, OakEngineNode *>(void *)>
get_existing_map_function)
{
OakEngineClipboard *cb = oakengine_clipboard_create(
@@ -762,12 +772,11 @@ bool NodeParamView::paste(
}
// Collect pasted nodes
QVector<Node *> pasted_nodes;
QVector<OakEngineNode *> pasted_nodes;
const int node_count = oakengine_clipboard_get_loaded_node_count(cb);
pasted_nodes.reserve(node_count);
for (int i = 0; i < node_count; i++) {
pasted_nodes.append(reinterpret_cast<Node *>(
oakengine_clipboard_get_loaded_node_at(cb, i)));
pasted_nodes.append(oakengine_clipboard_get_loaded_node_at(cb, i));
}
if (pasted_nodes.isEmpty()) {
@@ -776,9 +785,9 @@ bool NodeParamView::paste(
}
// Determine if any nodes of this type are already in the editor
QHash<Node *, Node *> existing_nodes = get_existing_map_function(cb);
QHash<OakEngineNode *, OakEngineNode *> existing_nodes = get_existing_map_function(cb);
QVector<Node *> nodes_to_paste_as_new = pasted_nodes;
QVector<OakEngineNode *> nodes_to_paste_as_new = pasted_nodes;
void *command = oakengine_undo_command_create_multi();
if (!existing_nodes.empty()) {
@@ -788,7 +797,7 @@ bool NodeParamView::paste(
QStringList node_names;
for (auto it = existing_nodes.cbegin(); it != existing_nodes.cend();
it++) {
node_names.append(it.key()->get_label_and_name());
node_names.append(oak::Node(it.key()).label_and_name());
}
b.setText(
@@ -806,7 +815,12 @@ bool NodeParamView::paste(
b.exec();
if (b.clickedButton() == cancel_btn) {
qDeleteAll(nodes_to_paste_as_new);
// Caller-owned pasted nodes that will not be inserted: free them
// synchronously through the facade (replaces qDeleteAll on engine
// Node*).
for (OakEngineNode *n : nodes_to_paste_as_new) {
oakengine_node_free(n);
}
nodes_to_paste_as_new.clear();
} else if (b.clickedButton() == as_vals) {
@@ -815,8 +829,8 @@ bool NodeParamView::paste(
// NOTE: the C ABI oakengine_node_copy_inputs pushes its own
// undo entry rather than becoming a child of `command`.
oakengine_node_copy_inputs(
reinterpret_cast<OakEngineNode*>(it.key()),
reinterpret_cast<const OakEngineNode*>(it.value()));
it.key(),
it.value());
nodes_to_paste_as_new.removeOne(it.value());
}
}
@@ -840,9 +854,10 @@ void NodeParamView::queue_keyframe_position_update()
Qt::QueuedConnection);
}
void NodeParamView::add_context(Node *ctx)
void NodeParamView::add_context(oak::Node ctx)
{
NodeParamViewContext *item = get_context_item_from_context(ctx);
NodeParamViewContext *item =
get_context_item_from_context(ctx.handle());
// TEMP: Creating many NPV items is EXTREMELY slow so limit to one item per context for now.
// I have a better solution in the works to use one UI for several nodes, but I haven't
@@ -854,22 +869,22 @@ void NodeParamView::add_context(Node *ctx)
// Queued so that if any further work is done in connecting this node to the context, it'll be
// done before our sorting function is called
context_subs_[ctx].first = bridge_->subscribe(
reinterpret_cast<void *>(ctx),
reinterpret_cast<void *>(ctx.handle()),
OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT);
context_subs_[ctx].second = bridge_->subscribe(
reinterpret_cast<void *>(ctx),
reinterpret_cast<void *>(ctx.handle()),
OAKENGINE_EVENT_NODE_NODE_REMOVED_FROM_CONTEXT);
item->add_context(ctx);
item->setVisible(true);
for (auto it = ctx->get_context_positions().cbegin();
it != ctx->get_context_positions().cend(); it++) {
add_node(it.key(), ctx, item);
const int context_node_count = ctx.context_node_count();
for (int i = 0; i < context_node_count; i++) {
add_node(ctx.context_node_at(i).node.handle(), ctx.handle(), item);
}
}
void NodeParamView::remove_context(Node *ctx)
void NodeParamView::remove_context(oak::Node ctx)
{
auto subs = context_subs_.take(ctx);
bridge_->unsubscribe(subs.first);
@@ -885,15 +900,16 @@ void NodeParamView::remove_context(Node *ctx)
}
}
void NodeParamView::add_node(Node *n, Node *ctx, NodeParamViewContext *context)
void NodeParamView::add_node(OakEngineNode *n, OakEngineNode *ctx, NodeParamViewContext *context)
{
if ((n->get_flags() & Node::k_dont_show_in_param_view) && !is_group_mode() &&
!show_all_nodes_) {
if ((oak::Node(n).flags() & oakengine_node_flag_dont_show_in_param_view()) &&
!is_group_mode() && !show_all_nodes_) {
return;
}
NodeParamViewItem *item = new NodeParamViewItem(
n, is_group_mode() ? k_check_boxes_on_non_connected : k_no_check_boxes,
oak::Node(n),
is_group_mode() ? k_check_boxes_on_non_connected : k_no_check_boxes,
context->get_dock_area());
connect(item, &NodeParamViewItem::request_select_node, this,
@@ -907,13 +923,13 @@ void NodeParamView::add_node(Node *n, Node *ctx, NodeParamViewContext *context)
connect(item, &NodeParamViewItem::request_edit_text_in_viewer, this,
&NodeParamView::request_edit_text_in_viewer);
item->set_context(ctx);
item->set_context(oak::Node(ctx));
item->set_time_target(get_connected_node());
item->set_timebase(timebase());
context->add_node(item);
if (!focused_node_ && n->has_gizmos()) {
if (!focused_node_ && oakengine_node_has_gizmos(n)) {
// We'll focus this node now
set_selected_nodes({ item });
}
@@ -930,19 +946,23 @@ void NodeParamView::add_node(Node *n, Node *ctx, NodeParamViewContext *context)
connect(item, &NodeParamViewItem::input_array_size_changed, this,
&NodeParamView::input_array_size_changed);
item->set_keyframe_connections(keyframe_view_->add_keyframes_of_node(n));
item->set_keyframe_connections(keyframe_view_->add_keyframes_of_node(
oak::Node(n)));
}
}
int get_distance_between_nodes(Node *start, Node *end)
int get_distance_between_nodes(OakEngineNode *start, OakEngineNode *end)
{
if (start == end) {
return 0;
}
for (auto it = start->input_connections().cbegin();
it != start->input_connections().cend(); it++) {
int this_node_dist = get_distance_between_nodes(it->second, end);
const oak::Node start_node(start);
const int connection_count = start_node.input_connection_count_all();
for (int i = 0; i < connection_count; i++) {
OakEngineNode *upstream =
start_node.input_connection_at_all(i).node.handle();
int this_node_dist = get_distance_between_nodes(upstream, end);
if (this_node_dist != -1) {
return 1 + this_node_dist;
}
@@ -960,9 +980,11 @@ void NodeParamView::sort_items_in_context(NodeParamViewContext *context_item)
NodeParamViewItem *item = *it;
int distance = -1;
foreach (Node *ctx, context_item->get_contexts()) {
foreach (const oak::Node &ctx, context_item->get_contexts()) {
distance =
qMax(distance, get_distance_between_nodes(ctx, item->get_node()));
qMax(distance, get_distance_between_nodes(
ctx.handle(),
item->get_node().handle()));
}
if (distance == -1) {
@@ -990,19 +1012,23 @@ void NodeParamView::sort_items_in_context(NodeParamViewContext *context_item)
}
}
NodeParamViewContext *NodeParamView::get_context_item_from_context(Node *ctx)
NodeParamViewContext *NodeParamView::get_context_item_from_context(OakEngineNode *ctx)
{
Track::Type ctx_type = Track::k_count;
TrackReference::Type ctx_type = TrackReference::k_count;
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(ctx)) {
if (clip->track()) {
if (clip->track()->type() != Track::k_none) {
ctx_type = clip->track()->type();
if (oakengine_node_is_clip(ctx)) {
OakEngineNode *track = block_track_handle(
reinterpret_cast<OakEngineBlock *>(ctx));
if (track) {
int type = oakengine_track_get_type(track);
if (type != TrackReference::k_none) {
ctx_type = static_cast<TrackReference::Type>(type);
}
}
} else if (Track *track = dynamic_cast<Track *>(ctx)) {
if (track->type() != Track::k_none) {
ctx_type = track->type();
} else if (oakengine_node_is_track(ctx)) {
int type = oakengine_track_get_type(ctx);
if (type != TrackReference::k_none) {
ctx_type = static_cast<TrackReference::Type>(type);
}
}
@@ -1032,24 +1058,23 @@ void NodeParamView::toggle_select(NodeParamViewItem *item)
// no gizmos
focused_node_ = item;
emit focused_node_changed(focused_node_ ? reinterpret_cast<OakEngineNode *>(focused_node_->get_node()) :
emit focused_node_changed(focused_node_ ? focused_node_->get_node().handle() :
nullptr);
}
}
}
QHash<Node *, Node *>
QHash<OakEngineNode *, OakEngineNode *>
NodeParamView::generate_existing_paste_map(void *clipboard)
{
QVector<Node *> ignore_nodes;
QHash<Node *, Node *> existing_nodes;
QVector<OakEngineNode *> ignore_nodes;
QHash<OakEngineNode *, OakEngineNode *> existing_nodes;
OakEngineClipboard *cb = static_cast<OakEngineClipboard *>(clipboard);
const int node_count = oakengine_clipboard_get_loaded_node_count(cb);
for (int i = 0; i < node_count; i++) {
Node *n = reinterpret_cast<Node *>(
oakengine_clipboard_get_loaded_node_at(cb, i));
if (Node *existing =
get_node_with_id_and_ignore_list(n->id(), ignore_nodes)) {
OakEngineNode *n = oakengine_clipboard_get_loaded_node_at(cb, i);
if (OakEngineNode *existing =
get_node_with_id_and_ignore_list(oak::Node(n).id(), ignore_nodes)) {
existing_nodes.insert(existing, n);
ignore_nodes.append(existing);
}
@@ -1068,7 +1093,7 @@ void NodeParamView::update_global_scroll_bar()
void NodeParamView::pin_node(bool pin)
{
NodeParamViewItem *item = static_cast<NodeParamViewItem *>(sender());
Node *node = item->get_node();
OakEngineNode *node = item->get_node().handle();
if (pin) {
pinned_nodes_.append(node);
@@ -1118,28 +1143,33 @@ void NodeParamView::update_element_y()
for (auto it = ctx->get_items().cbegin(); it != ctx->get_items().cend();
it++) {
NodeParamViewItem *item = *it;
Node *node = item->get_node();
oak::Node node = item->get_node();
const KeyframeView::NodeConnections &connections =
item->get_keyframe_connections();
if (!connections.isEmpty()) {
for (const QString &input : node->inputs()) {
if (!(node->get_input_flags(input) & k_input_flag_hidden)) {
const int node_input_count = node.input_count();
for (int input_index = 0; input_index < node_input_count;
input_index++) {
const QString input = node.input_id(input_index);
if (!oak::Input(node.handle(), input).is_hidden()) {
OakEngineNode *out_node = nullptr;
char out_input[256];
int out_element = 0;
int arr_sz = 0;
// WRAPPER-GAP: oakengine_group_resolve_input (group
// API has no oak:: wrapper)
if (oakengine_group_resolve_input(
reinterpret_cast<OakEngineNode *>(
contexts_.first()),
contexts_.first().handle(),
input.toUtf8().constData(), -1, &out_node,
out_input, sizeof(out_input),
&out_element) == OAKENGINE_OK && out_node) {
arr_sz = oakengine_node_input_array_size(
out_node, out_input);
arr_sz = oak::Input(out_node,
QString::fromUtf8(out_input))
.array_size();
}
for (int i = -1; i < arr_sz; i++) {
NodeInput ic = { node, input, i };
oak::Input ic(node.handle(), input, i);
int y = item->get_element_y(ic);
@@ -1165,7 +1195,7 @@ void NodeParamView::update_element_y()
}
}
void NodeParamView::node_added_to_context(Node *n, Node *ctx)
void NodeParamView::node_added_to_context(OakEngineNode *n, OakEngineNode *ctx)
{
NodeParamViewContext *item = get_context_item_from_context(ctx);
@@ -1178,10 +1208,10 @@ void NodeParamView::node_added_to_context(Node *n, Node *ctx)
}
}
void NodeParamView::node_removed_from_context(Node *n, Node *ctx)
void NodeParamView::node_removed_from_context(OakEngineNode *n, OakEngineNode *ctx)
{
foreach (NodeParamViewContext *ctx_item, context_items_) {
ctx_item->remove_node(n, ctx);
ctx_item->remove_node(oak::Node(n), oak::Node(ctx));
}
if (keyframe_view_) {
@@ -1189,25 +1219,29 @@ void NodeParamView::node_removed_from_context(Node *n, Node *ctx)
}
}
void NodeParamView::input_check_box_changed(const NodeInput &input, bool e)
void NodeParamView::input_check_box_changed(const oak::Input &input, bool e)
{
Node *group = contexts_.first();
oak::Node group = contexts_.first();
if (e) {
char out_id[256];
// WRAPPER-GAP: oakengine_group_add_input_passthrough (group API has
// no oak:: wrapper)
oakengine_group_add_input_passthrough(
reinterpret_cast<OakEngineNode *>(group),
nullptr, input.input().toUtf8().constData(), input.element(),
group.handle(),
nullptr, input.input_id().toUtf8().constData(), input.element(),
nullptr, out_id, sizeof(out_id));
} else {
// WRAPPER-GAP: oakengine_group_remove_input_passthrough (group API
// has no oak:: wrapper)
oakengine_group_remove_input_passthrough(
reinterpret_cast<OakEngineNode *>(group),
nullptr, input.input().toUtf8().constData(), input.element());
group.handle(),
nullptr, input.input_id().toUtf8().constData(), input.element());
}
}
void NodeParamView::group_input_passthrough_added(OakEngineNode *group,
const NodeInput &input)
const oak::Input &input)
{
foreach (NodeParamViewContext *pvctx, context_items_) {
pvctx->set_input_checked(input, true);
@@ -1215,7 +1249,7 @@ void NodeParamView::group_input_passthrough_added(OakEngineNode *group,
}
void NodeParamView::group_input_passthrough_removed(OakEngineNode *group,
const NodeInput &input)
const oak::Input &input)
{
foreach (NodeParamViewContext *pvctx, context_items_) {
pvctx->set_input_checked(input, false);
@@ -1256,7 +1290,7 @@ void NodeParamView::input_array_size_changed(const QString &input, int,
// Fill in extra elements
for (int i = old_size; i < inputs.size(); i++) {
inputs[i] = keyframe_view_->add_keyframes_of_element(
NodeInput(sender->get_node(), input, i - 1));
oak::Input(sender->get_node().handle(), input, i - 1));
}
}
}
+30 -34
View File
@@ -26,8 +26,8 @@
#include <QVBoxLayout>
#include <QWidget>
#include "node/node.h"
#include "oakengine/serializer.h"
#include "oakutil/oaknode.h"
#include "nodeparamviewcontext.h"
#include "nodeparamviewdockarea.h"
#include "nodeparamviewitem.h"
@@ -48,7 +48,7 @@ public:
virtual ~NodeParamView() override;
void close_contexts_belonging_to_project(Project *p);
void close_contexts_belonging_to_project(oak::Project p);
void DeleteSelected();
@@ -69,11 +69,11 @@ public:
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes,
bool emit_signal = true);
Node *get_node_with_id(const QString &id);
Node *get_node_with_id_and_ignore_list(const QString &id,
const QVector<Node *> &ignore);
OakEngineNode *get_node_with_id(const QString &id);
OakEngineNode *get_node_with_id_and_ignore_list(const QString &id,
const QVector<OakEngineNode *> &ignore);
const QVector<Node *> &get_contexts() const
const QVector<oak::Node> &get_contexts() const
{
return contexts_;
}
@@ -83,14 +83,13 @@ public:
virtual bool paste() override;
static bool paste(
QWidget *parent,
std::function<QHash<Node *, Node *>(void *)>
std::function<QHash<OakEngineNode *, OakEngineNode *>(void *)>
get_existing_map_function);
public:
// Not a slot: signature uses the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void set_contexts(const QVector<Node *> &contexts);
// Not a slot: mirrors NodeView::set_contexts(), which is also a plain
// member function. All connections use new-style member-function syntax.
void set_contexts(const QVector<oak::Node> &contexts);
public slots:
void update_element_y();
@@ -109,7 +108,7 @@ protected:
virtual void ScaleChangedEvent(const double &) override;
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput *n) override;
virtual void ConnectedNodeChangeEvent(OakEngineNode *n) override;
virtual const QVector<KeyframeViewInputConnection *> *
get_snap_keyframes() const override
@@ -117,7 +116,7 @@ protected:
return keyframe_view_ ? &keyframe_view_->get_keyframe_tracks() : nullptr;
}
virtual const std::vector<NodeKeyframe *> *
virtual const std::vector<OakEngineKeyframe *> *
get_snap_ignore_keyframes() const override
{
return keyframe_view_ ? &keyframe_view_->get_selected_keyframes() :
@@ -132,33 +131,30 @@ protected:
private:
void queue_keyframe_position_update();
void add_context(Node *context);
void add_context(oak::Node context);
void remove_context(Node *context);
void remove_context(oak::Node context);
// Ordinary member functions (NOT slots): their signatures use Node*, which
// must not be exposed to MOC. They are only invoked from lambdas inside
// this class, never used as connect() targets.
void node_added_to_context(Node *n, Node *ctx);
// Ordinary member functions (NOT slots): they are only invoked from
// lambdas inside this class, never used as connect() targets.
void node_added_to_context(OakEngineNode *n, OakEngineNode *ctx);
void node_removed_from_context(Node *n, Node *ctx);
void node_removed_from_context(OakEngineNode *n, OakEngineNode *ctx);
void add_node(Node *n, Node *ctx, NodeParamViewContext *context);
void add_node(OakEngineNode *n, OakEngineNode *ctx, NodeParamViewContext *context);
void sort_items_in_context(NodeParamViewContext *context);
NodeParamViewContext *get_context_item_from_context(Node *context);
NodeParamViewContext *get_context_item_from_context(OakEngineNode *context);
bool is_group_mode() const
{
return contexts_.size() == 1 &&
oakengine_node_is_group(
reinterpret_cast<OakEngineNode *>(contexts_.first()));
return contexts_.size() == 1 && contexts_.first().is_group();
}
void toggle_select(NodeParamViewItem *item);
QHash<Node *, Node *>
QHash<OakEngineNode *, OakEngineNode *>
generate_existing_paste_map(void *clipboard);
KeyframeView *keyframe_view_;
@@ -175,15 +171,15 @@ private:
NodeParamViewDockArea *param_widget_area_;
QVector<Node *> pinned_nodes_;
QVector<OakEngineNode *> pinned_nodes_;
QVector<Node *> active_nodes_;
QVector<OakEngineNode *> active_nodes_;
NodeParamViewItem *focused_node_;
QVector<NodeParamViewItem *> selected_nodes_;
QVector<Node *> contexts_;
QVector<Node *> current_contexts_;
QVector<oak::Node> contexts_;
QVector<oak::Node> current_contexts_;
bool show_all_nodes_;
@@ -192,7 +188,7 @@ private:
int64_t group_passthrough_added_sub_ = 0;
int64_t group_passthrough_removed_sub_ = 0;
QHash<Node *, QPair<int64_t, int64_t>> context_subs_;
QHash<oak::Node, QPair<int64_t, int64_t>> context_subs_;
private slots:
void update_global_scroll_bar();
@@ -201,13 +197,13 @@ private slots:
//void FocusChanged(QWidget *old, QWidget *now);
void input_check_box_changed(const NodeInput &input, bool e);
void input_check_box_changed(const oak::Input &input, bool e);
void group_input_passthrough_added(OakEngineNode *group,
const olive::NodeInput &input);
const oak::Input &input);
void group_input_passthrough_removed(OakEngineNode *group,
const olive::NodeInput &input);
const oak::Input &input);
void update_contexts();
@@ -24,12 +24,10 @@
#include <QEvent>
#include <QHBoxLayout>
#include "node/node.h"
namespace olive
{
NodeParamViewArrayWidget::NodeParamViewArrayWidget(Node *node,
NodeParamViewArrayWidget::NodeParamViewArrayWidget(oak::Node node,
const QString &input,
QWidget *parent)
: QWidget(parent)
@@ -42,7 +40,7 @@ NodeParamViewArrayWidget::NodeParamViewArrayWidget(Node *node,
count_lbl_ = new QLabel();
layout->addWidget(count_lbl_);
bridge_->subscribe(reinterpret_cast<void *>(node_),
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
[this](OakEngineNode *, const QString &input, int old_size,
@@ -50,7 +48,7 @@ NodeParamViewArrayWidget::NodeParamViewArrayWidget(Node *node,
update_counter(input, old_size, new_size);
});
update_counter(input_, 0, node_->input_array_size(input_));
update_counter(input_, 0, oak::Input(node_.handle(), input_).array_size());
}
void NodeParamViewArrayWidget::mouseDoubleClickEvent(QMouseEvent *event)
@@ -27,7 +27,7 @@
#include <QWidget>
#include "engineeventbridge.h"
#include "node/param.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -51,7 +51,7 @@ private:
class NodeParamViewArrayWidget : public QWidget {
Q_OBJECT
public:
NodeParamViewArrayWidget(Node *node, const QString &input,
NodeParamViewArrayWidget(oak::Node node, const QString &input,
QWidget *parent = nullptr);
signals:
@@ -61,7 +61,7 @@ protected:
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
Node *node_;
oak::Node node_;
QString input_;
@@ -25,7 +25,6 @@
#include "oakutil/qtutils.h"
#include "core.h"
#include "node/node.h"
#include "oakengine/node.h"
#include "widget/collapsebutton/collapsebutton.h"
#include "widget/menu/menu.h"
@@ -36,11 +35,11 @@
namespace olive
{
NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const oak::Input &input,
QWidget *parent)
: QWidget(parent)
, input_(input)
, connected_node_(nullptr)
, connected_node_()
, viewer_(nullptr)
, bridge_(new EngineEventBridge(this))
{
@@ -83,28 +82,26 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
connected_to_lbl_->setFont(link_font);
if (input_.is_connected()) {
input_connected(reinterpret_cast<OakEngineNode *>(input_.get_connected_output()), input_);
input_connected(input_.connected_node(), input_);
} else {
input_disconnected(nullptr, input_);
input_disconnected(oak::Node(), input_);
}
bridge_->subscribe(reinterpret_cast<void *>(input_.node()),
bridge_->subscribe(reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_INPUT_CONNECTED);
bridge_->subscribe(reinterpret_cast<void *>(input_.node()),
bridge_->subscribe(reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED);
connect(bridge_, &EngineEventBridge::node_input_connected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
input_connected(output,
NodeInput(reinterpret_cast<Node *>(source), input,
element));
oak::Input(source, input, element));
});
connect(bridge_, &EngineEventBridge::node_input_disconnected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
input_disconnected(output,
NodeInput(reinterpret_cast<Node *>(source), input,
element));
oak::Input(source, input, element));
});
// Creating the tree is expensive, hold off until the user specifically requests it
@@ -113,7 +110,7 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
&NodeParamViewConnectedLabel::set_value_tree_visible);
}
void NodeParamViewConnectedLabel::set_viewer_node(ViewerOutput *viewer)
void NodeParamViewConnectedLabel::set_viewer_node(OakEngineNode *viewer)
{
if (viewer_) {
oakengine_event_unsubscribe(viewer_sub_);
@@ -124,7 +121,7 @@ void NodeParamViewConnectedLabel::set_viewer_node(ViewerOutput *viewer)
if (viewer_) {
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(viewer_),
viewer_,
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<NodeParamViewConnectedLabel *>(userdata)
@@ -142,20 +139,20 @@ void NodeParamViewConnectedLabel::create_tree()
layout()->addWidget(value_tree_);
}
void NodeParamViewConnectedLabel::input_connected(OakEngineNode *output,
const NodeInput &input)
void NodeParamViewConnectedLabel::input_connected(oak::Node output,
const oak::Input &input)
{
if (input_ != input) {
return;
}
connected_node_ = reinterpret_cast<Node *>(output);
connected_node_ = output;
update_label();
}
void NodeParamViewConnectedLabel::input_disconnected(OakEngineNode *output,
const NodeInput &input)
void NodeParamViewConnectedLabel::input_disconnected(oak::Node output,
const oak::Input &input)
{
if (input_ != input) {
return;
@@ -163,7 +160,7 @@ void NodeParamViewConnectedLabel::input_disconnected(OakEngineNode *output,
Q_UNUSED(output)
connected_node_ = nullptr;
connected_node_ = oak::Node();
update_label();
}
@@ -176,9 +173,7 @@ void NodeParamViewConnectedLabel::show_label_context_menu()
connect(disconnect_action, &QAction::triggered, this, [this]() {
// Through the liboakengine C ABI facade (one undoable command,
// array element included, same as the old NodeEdgeRemoveCommand).
oakengine_node_disconnect_ex(
reinterpret_cast<OakEngineNode *>(input_.node()),
input_.input().toUtf8().constData(), input_.element());
input_.disconnect();
});
m.exec(QCursor::pos());
@@ -186,8 +181,8 @@ void NodeParamViewConnectedLabel::show_label_context_menu()
void NodeParamViewConnectedLabel::connection_clicked()
{
if (connected_node_) {
emit request_select_node(reinterpret_cast<OakEngineNode *>(connected_node_));
if (!connected_node_.is_null()) {
emit request_select_node(connected_node_.handle());
}
}
@@ -195,8 +190,8 @@ void NodeParamViewConnectedLabel::update_label()
{
QString s;
if (connected_node_) {
s = connected_node_->name();
if (!connected_node_.is_null()) {
s = connected_node_.name();
} else {
s = tr("Nothing");
}
@@ -23,7 +23,7 @@
#define OAK_NODEPARAMVIEWCONNECTEDLABEL_H
#include "engineeventbridge.h"
#include "node/param.h"
#include "oakutil/oaknode.h"
#include "widget/clickablelabel/clickablelabel.h"
#include "widget/nodevaluetree/nodevaluetree.h"
@@ -35,18 +35,18 @@ namespace olive
class NodeParamViewConnectedLabel : public QWidget {
Q_OBJECT
public:
NodeParamViewConnectedLabel(const NodeInput &input,
NodeParamViewConnectedLabel(const oak::Input &input,
QWidget *parent = nullptr);
void set_viewer_node(ViewerOutput *viewer);
void set_viewer_node(OakEngineNode *viewer);
signals:
void request_select_node(OakEngineNode *n);
private slots:
void input_connected(OakEngineNode *output, const NodeInput &input);
void input_connected(oak::Node output, const oak::Input &input);
void input_disconnected(OakEngineNode *output, const NodeInput &input);
void input_disconnected(oak::Node output, const oak::Input &input);
void show_label_context_menu();
@@ -61,13 +61,13 @@ private:
ClickableLabel *connected_to_lbl_;
NodeInput input_;
oak::Input input_;
Node *connected_node_;
oak::Node connected_node_;
NodeValueTree *value_tree_;
ViewerOutput *viewer_;
OakEngineNode *viewer_;
EngineEventBridge *bridge_ = nullptr;
@@ -23,7 +23,6 @@
#include <QMessageBox>
#include "node/block/clip/clip.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "widget/menu/factorymenu.h"
@@ -35,7 +34,7 @@ namespace olive
NodeParamViewContext::NodeParamViewContext(QWidget *parent)
: super(parent)
, type_(Track::k_none)
, type_(TrackReference::k_none)
{
QWidget *body = new QWidget();
QHBoxLayout *body_layout = new QHBoxLayout(body);
@@ -52,7 +51,7 @@ NodeParamViewContext::NodeParamViewContext(QWidget *parent)
this, &NodeParamViewContext::add_effect_button_clicked);
}
NodeParamViewItem *NodeParamViewContext::get_item(Node *node, Node *ctx)
NodeParamViewItem *NodeParamViewContext::get_item(oak::Node node, oak::Node ctx)
{
for (auto it = items_.begin(); it != items_.end(); it++) {
NodeParamViewItem *item = *it;
@@ -71,7 +70,7 @@ void NodeParamViewContext::add_node(NodeParamViewItem *item)
dock_area_->add_item(item);
}
void NodeParamViewContext::remove_node(Node *node, Node *ctx)
void NodeParamViewContext::remove_node(oak::Node node, oak::Node ctx)
{
for (auto it = items_.begin(); it != items_.end();) {
NodeParamViewItem *item = *it;
@@ -86,7 +85,7 @@ void NodeParamViewContext::remove_node(Node *node, Node *ctx)
}
}
void NodeParamViewContext::remove_nodes_with_context(Node *ctx)
void NodeParamViewContext::remove_nodes_with_context(oak::Node ctx)
{
for (auto it = items_.begin(); it != items_.end();) {
NodeParamViewItem *item = *it;
@@ -101,7 +100,7 @@ void NodeParamViewContext::remove_nodes_with_context(Node *ctx)
}
}
void NodeParamViewContext::set_input_checked(const NodeInput &input, bool e)
void NodeParamViewContext::set_input_checked(const oak::Input &input, bool e)
{
foreach (NodeParamViewItem *item, items_) {
if (item->get_node() == input.node()) {
@@ -117,14 +116,14 @@ void NodeParamViewContext::set_timebase(const Rational &timebase)
}
}
void NodeParamViewContext::set_time_target(ViewerOutput *n)
void NodeParamViewContext::set_time_target(OakEngineNode *n)
{
foreach (NodeParamViewItem *item, items_) {
item->set_time_target(n);
}
}
void NodeParamViewContext::set_effect_type(Track::Type type)
void NodeParamViewContext::set_effect_type(TrackReference::Type type)
{
type_ = type;
}
@@ -135,20 +134,16 @@ void NodeParamViewContext::retranslate()
void NodeParamViewContext::add_effect_button_clicked()
{
Node::Flag flag = Node::k_none;
uint64_t flag = (type_ == TrackReference::k_video)
? oakengine_node_flag_video_effect()
: oakengine_node_flag_audio_effect();
if (type_ == Track::k_video) {
flag = Node::k_video_effect;
} else {
flag = Node::k_audio_effect;
}
if (flag == Node::k_none) {
if (flag == 0) {
return;
}
Menu *m =
create_node_menu(this, false, Node::k_category_unknown, flag);
create_node_menu(this, false, oak::k_category_unknown, flag);
connect(m, &Menu::triggered, this,
&NodeParamViewContext::add_effect_menu_item_triggered);
m->exec(QCursor::pos());
@@ -157,63 +152,68 @@ void NodeParamViewContext::add_effect_button_clicked()
void NodeParamViewContext::add_effect_menu_item_triggered(QAction *a)
{
Node *n = create_node_from_menu_action(a);
// Owned handle: handed to the add-to-project undo command below
oak::Node n = create_node_from_menu_action(a);
if (n) {
NodeInput new_node_input = n->get_effect_input();
if (!n.is_null()) {
oak::Input new_node_input = n.effect_input();
// WRAPPER-GAP: oakengine_undo_* / oakengine_node_*_command (undo
// command assembly has no oak:: wrapper)
void *command = oakengine_undo_command_create_multi();
QVector<Project *> graphs_added_to;
QVector<OakEngineProject *> graphs_added_to;
foreach (Node *ctx, contexts_) {
NodeInput ctx_input = ctx->get_effect_input();
foreach (oak::Node ctx, contexts_) {
oak::Input ctx_input = ctx.effect_input();
if (!graphs_added_to.contains(ctx->parent())) {
OakEngineProject *ctx_project = ctx.project().handle();
if (!graphs_added_to.contains(ctx_project)) {
oakengine_undo_command_multi_add_child(
command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(ctx->parent()),
reinterpret_cast<OakEngineNode *>(n)));
graphs_added_to.append(ctx->parent());
ctx_project, n.handle()));
graphs_added_to.append(ctx_project);
}
QPointF ctx_pos;
ctx.context_position_of(ctx, &ctx_pos);
oakengine_undo_command_multi_add_child(
command, oakengine_node_set_position_command(reinterpret_cast<void *>(n), reinterpret_cast<void *>(ctx), ctx->get_node_position_in_context(ctx).x(), ctx->get_node_position_in_context(ctx).y(), 0));
command, oakengine_node_set_position_command(n.handle(), ctx.handle(), ctx_pos.x(), ctx_pos.y(), 0));
oakengine_undo_command_multi_add_child(
command, oakengine_node_set_position_command(
reinterpret_cast<void *>(ctx), reinterpret_cast<void *>(ctx),
ctx->get_node_position_in_context(ctx).x() + 1,
ctx->get_node_position_in_context(ctx).y(), 0));
ctx.handle(), ctx.handle(),
ctx_pos.x() + 1,
ctx_pos.y(), 0));
if (ctx_input.is_connected()) {
Node *prev_output = ctx_input.get_connected_output();
oak::Node prev_output = ctx_input.connected_node();
oakengine_undo_command_multi_add_child(
command,
oakengine_node_disconnect_command(
reinterpret_cast<OakEngineNode *>(ctx_input.node()),
ctx_input.input().toUtf8().constData(),
ctx_input.node_handle(),
ctx_input.input_id().toUtf8().constData(),
ctx_input.element()));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(prev_output),
reinterpret_cast<OakEngineNode *>(new_node_input.node()),
new_node_input.input().toUtf8().constData(),
prev_output.handle(),
new_node_input.node_handle(),
new_node_input.input_id().toUtf8().constData(),
new_node_input.element()));
}
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(n),
reinterpret_cast<OakEngineNode *>(ctx_input.node()),
ctx_input.input().toUtf8().constData(),
n.handle(),
ctx_input.node_handle(),
ctx_input.input_id().toUtf8().constData(),
ctx_input.element()));
}
oakengine_undo_push(
command, tr("Added %1 to Node Chain").arg(n->name()).toUtf8().constData());
command, tr("Added %1 to Node Chain").arg(n.name()).toUtf8().constData());
}
}
+14 -14
View File
@@ -25,6 +25,7 @@
#include "nodeparamviewdockarea.h"
#include "nodeparamviewitembase.h"
#include "nodeparamviewitem.h"
#include "common/trackreferencehandle.h"
namespace olive
{
@@ -39,7 +40,7 @@ public:
return dock_area_;
}
const QVector<Node *> &get_contexts() const
const QVector<oak::Node> &get_contexts() const
{
return contexts_;
}
@@ -49,35 +50,34 @@ public:
return items_;
}
NodeParamViewItem *get_item(Node *node, Node *ctx);
NodeParamViewItem *get_item(oak::Node node, oak::Node ctx);
void add_node(NodeParamViewItem *item);
void remove_node(Node *node, Node *ctx);
void remove_node(oak::Node node, oak::Node ctx);
void remove_nodes_with_context(Node *ctx);
void remove_nodes_with_context(oak::Node ctx);
void set_input_checked(const NodeInput &input, bool e);
void set_input_checked(const oak::Input &input, bool e);
void set_timebase(const Rational &timebase);
void set_time_target(ViewerOutput *n);
void set_time_target(OakEngineNode *n);
void set_effect_type(Track::Type type);
void set_effect_type(TrackReference::Type type);
signals:
void about_to_delete_item(NodeParamViewItem *item);
public:
// Not slots: signatures use the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). They are called directly, never used as connect() targets.
void add_context(Node *node)
// Ordinary member functions (NOT slots): they are called directly, never
// used as connect() targets.
void add_context(oak::Node node)
{
contexts_.append(node);
}
void remove_context(Node *node)
void remove_context(oak::Node node)
{
contexts_.removeOne(node);
}
@@ -88,11 +88,11 @@ protected slots:
private:
NodeParamViewDockArea *dock_area_;
QVector<Node *> contexts_;
QVector<oak::Node> contexts_;
QVector<NodeParamViewItem *> items_;
Track::Type type_;
TrackReference::Type type_;
private slots:
void add_effect_button_clicked();
+122 -106
View File
@@ -26,28 +26,37 @@
#include "oakutil/qtutils.h"
#include "dialog/speedduration/speeddurationdialog.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "pluginSupport/oliveplugininstance.h"
namespace olive
{
static NodeInput ResolveGroupInput(const NodeInput &input)
static oak::Input ResolveGroupInput(const oak::Input &input)
{
OakEngineNode *node = reinterpret_cast<OakEngineNode *>(input.node());
OakEngineNode *node = input.node_handle();
char input_id[256];
int element = input.element();
const QByteArray utf = input.input().toUtf8();
const QByteArray utf = input.input_id().toUtf8();
memcpy(input_id, utf.constData(), qMin<int>(sizeof(input_id) - 1, utf.size()));
input_id[sizeof(input_id) - 1] = '\0';
// WRAPPER-GAP: oakengine_group_resolve_input (group API has no oak:: wrapper)
if (oakengine_group_resolve_input(
node, input_id, element,
&node, input_id, sizeof(input_id), &element) != OAKENGINE_OK) {
return input;
}
return NodeInput(reinterpret_cast<Node *>(node),
QString::fromUtf8(input_id), element);
return oak::Input(node, QString::fromUtf8(input_id), element);
}
static QString input_property_string(OakEngineNode *node, const QString &input,
const QString &key)
{
char buf[256];
buf[0] = '\0';
oakengine_node_input_get_property_string(
node, input.toUtf8().constData(), key.toUtf8().constData(), buf,
sizeof(buf));
return QString::fromUtf8(buf);
}
const int NodeParamViewItemBody::k_key_control_column = 10;
@@ -64,7 +73,7 @@ const int NodeParamViewItemBody::k_max_widget_column = k_array_remove_column;
#define super NodeParamViewItemBase
NodeParamViewItem::NodeParamViewItem(
Node *node, NodeParamViewCheckBoxBehavior create_checkboxes,
oak::Node node, NodeParamViewCheckBoxBehavior create_checkboxes,
QWidget *parent)
: super(parent)
, body_(nullptr)
@@ -73,22 +82,22 @@ NodeParamViewItem::NodeParamViewItem(
, message_container_(nullptr)
, node_(node)
, create_checkboxes_(create_checkboxes)
, ctx_(nullptr)
, ctx_()
, time_target_(nullptr)
, bridge_(new EngineEventBridge(this))
{
node_->retranslate();
node_.retranslate();
// Create and add contents widget
recreate_body();
bridge_->subscribe(reinterpret_cast<void *>(node_),
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
OAKENGINE_EVENT_NODE_LABEL_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
bridge_->subscribe(reinterpret_cast<void *>(node_.handle()),
OAKENGINE_EVENT_NODE_INPUT_FLAGS_CHANGED);
connect(bridge_, &EngineEventBridge::node_label_changed, this,
@@ -115,7 +124,7 @@ NodeParamViewItem::NodeParamViewItem(
void NodeParamViewItem::retranslate()
{
node_->retranslate();
node_.retranslate();
title_bar()->set_text(get_title_bar_text_from_node(node_));
@@ -182,10 +191,7 @@ void NodeParamViewItem::update_message_panel()
return;
}
auto *instance = node_->getPluginInstance();
auto *olive_instance =
dynamic_cast<plugin::OlivePluginInstance *>(instance);
if (!olive_instance || olive_instance->persistent_message_count() == 0) {
if (!node_.has_plugin() || node_.plugin_message_count() == 0) {
message_label_->setVisible(false);
if (message_clear_button_) {
message_clear_button_->setVisible(false);
@@ -194,20 +200,25 @@ void NodeParamViewItem::update_message_panel()
}
QStringList lines;
for (const auto &msg : olive_instance->persistent_messages()) {
for (int i = 0; i < node_.plugin_message_count(); i++) {
// Message type ordinals mirror plugin::ErrorType (0=error,
// 1=warning, 2=message); must stay in sync with the engine enum,
// values cross the C ABI as int.
int type = 0;
const QString message = node_.plugin_message_at(i, &type);
QString prefix;
switch (msg.type) {
case plugin::ErrorType::error:
switch (type) {
case 0:
prefix = QStringLiteral("Error");
break;
case plugin::ErrorType::warning:
case 1:
prefix = QStringLiteral("Warning");
break;
case plugin::ErrorType::message:
default:
prefix = QStringLiteral("Message");
break;
}
lines.append(QStringLiteral("%1: %2").arg(prefix, msg.message));
lines.append(QStringLiteral("%1: %2").arg(prefix, message));
}
message_label_->setText(lines.join('\n'));
@@ -217,7 +228,7 @@ void NodeParamViewItem::update_message_panel()
}
}
int NodeParamViewItem::get_element_y(const NodeInput &c) const
int NodeParamViewItem::get_element_y(const oak::Input &c) const
{
if (is_expanded()) {
return body_->get_element_y(c);
@@ -227,25 +238,18 @@ int NodeParamViewItem::get_element_y(const NodeInput &c) const
}
}
void NodeParamViewItem::set_input_checked(const NodeInput &input, bool e)
void NodeParamViewItem::set_input_checked(const oak::Input &input, bool e)
{
body_->set_input_checked(input, e);
}
void NodeParamViewItem::clear_messages()
{
auto *instance = node_->getPluginInstance();
auto *olive_instance =
dynamic_cast<plugin::OlivePluginInstance *>(instance);
if (!olive_instance) {
return;
}
olive_instance->clearPersistentMessage();
node_.clear_plugin_messages();
}
NodeParamViewItemBody::NodeParamViewItemBody(
Node *node, NodeParamViewCheckBoxBehavior create_checkboxes,
oak::Node node, NodeParamViewCheckBoxBehavior create_checkboxes,
QWidget *parent)
: QWidget(parent)
, node_(node)
@@ -259,7 +263,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(
QString current_page;
QString current_group;
QVector<Node *> connected_signals;
QVector<oak::Node> connected_signals;
connect(bridge_, &EngineEventBridge::node_input_array_size_changed,
this, &NodeParamViewItemBody::input_array_size_changed);
@@ -267,42 +271,43 @@ NodeParamViewItemBody::NodeParamViewItemBody(
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
edge_changed(output,
NodeInput(reinterpret_cast<Node *>(source), input,
element));
oak::Input(source, input, element));
});
connect(bridge_, &EngineEventBridge::node_input_disconnected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
edge_changed(output,
NodeInput(reinterpret_cast<Node *>(source), input,
element));
oak::Input(source, input, element));
});
// Create widgets all root level components
foreach (QString input, node->inputs()) {
Node *n = node;
const int node_input_count = node.input_count();
for (int input_index = 0; input_index < node_input_count; input_index++) {
QString input = node.input_id(input_index);
oak::Node n = node;
NodeInput resolved = ResolveGroupInput(NodeInput(n, input));
oak::Input resolved = ResolveGroupInput(oak::Input(n.handle(), input));
if (!connected_signals.contains(resolved.node())) {
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
bridge_->subscribe(reinterpret_cast<void *>(resolved.node_handle()),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
bridge_->subscribe(reinterpret_cast<void *>(resolved.node_handle()),
OAKENGINE_EVENT_NODE_INPUT_CONNECTED);
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
bridge_->subscribe(reinterpret_cast<void *>(resolved.node_handle()),
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED);
connected_signals.append(resolved.node());
}
input_group_lookup_.insert({ resolved.node(), resolved.input() },
{ n, input });
input_group_lookup_.insert({ resolved.node_handle(), resolved.input_id() },
{ n.handle(), input });
if (!(n->get_input_flags(input) & k_input_flag_hidden)) {
QString page_label =
n->get_input_property(input, QStringLiteral("ui_page")).toString();
QString group_label =
n->get_input_property(input, QStringLiteral("ui_group"))
.toString();
if (!oak::Input(n.handle(), input).is_hidden()) {
// ui_page / ui_group grouping labels via the C ABI string
// property getter (replaces Node::get_input_property).
QString page_label = input_property_string(
n.handle(), input, QStringLiteral("ui_page"));
QString group_label = input_property_string(
n.handle(), input, QStringLiteral("ui_group"));
if (!page_label.isEmpty() && page_label != current_page) {
QLabel *page_title = new QLabel(page_label, this);
QFont f = page_title->font();
@@ -326,7 +331,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(
insert_row++;
if (n->input_is_array(input)) {
if (oak::Input(n.handle(), input).is_array()) {
// Insert here
QWidget *array_widget = new QWidget(this);
@@ -352,7 +357,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(
array_widget->setVisible(false);
array_ui_.insert({ n, input },
array_ui_.insert({ n.handle(), input },
{ array_widget, arr_sz, append_btn });
insert_row++;
@@ -361,11 +366,11 @@ NodeParamViewItemBody::NodeParamViewItemBody(
}
}
void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
void NodeParamViewItemBody::create_widgets(QGridLayout *layout, oak::Node node,
const QString &input, int element,
int row)
{
NodeInput input_ref(node, input, element);
oak::Input input_ref(node.handle(), input, element);
InputUI ui_objects;
@@ -392,7 +397,7 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
// Create input label
layout->addWidget(ui_objects.main_label, row, k_label_column);
if (node->input_is_array(input)) {
if (oak::Input(node.handle(), input).is_array()) {
if (element == -1) {
// Create a collapse toggle for expanding/collapsing the array
CollapseButton *array_collapse_btn = new CollapseButton(this);
@@ -407,7 +412,7 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
connect(array_collapse_btn, &CollapseButton::toggled, this,
&NodeParamViewItemBody::array_collapse_btn_pressed);
array_collapse_buttons_.insert({ node, input }, array_collapse_btn);
array_collapse_buttons_.insert({ node.handle(), input }, array_collapse_btn);
} else {
NodeParamViewArrayButton *insert_element_btn =
@@ -432,7 +437,9 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
// Create a widget/input bridge for this input
ui_objects.widget_bridge =
new NodeParamViewWidgetBridge(NodeInput(node, input, element), this);
new NodeParamViewWidgetBridge(
oak::Input(node.handle(), input, element),
this);
connect(ui_objects.widget_bridge,
&NodeParamViewWidgetBridge::widgets_recreated, this,
&NodeParamViewItemBody::replace_widgets);
@@ -447,9 +454,9 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
place_widgets_from_bridge(layout, ui_objects.widget_bridge, row);
// In case this input is a group, resolve that actual input to use for connected labels
NodeInput resolved = ResolveGroupInput(input_ref);
oak::Input resolved = ResolveGroupInput(input_ref);
if (node->is_input_connectable(input)) {
if (oak::Input(node.handle(), input).is_connectable()) {
// Create clickable label used when an input is connected
ui_objects.connected_label =
new NodeParamViewConnectedLabel(resolved, this);
@@ -461,7 +468,7 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
}
// Add keyframe control to this layout if parameter is keyframable
if (node->is_input_keyframable(input)) {
if (oak::Input(node.handle(), input).is_keyframable()) {
ui_objects.key_control = new NodeParamViewKeyframeControl(this);
ui_objects.key_control->set_input(resolved);
layout->addWidget(ui_objects.key_control, row, k_key_control_column);
@@ -469,7 +476,7 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
input_ui_map_.insert(input_ref, ui_objects);
if (node->is_input_connectable(input)) {
if (oak::Input(node.handle(), input).is_connectable()) {
update_ui_for_edge_connection(input_ref);
}
@@ -477,7 +484,7 @@ void NodeParamViewItemBody::create_widgets(QGridLayout *layout, Node *node,
set_timebase_on_input_ui(ui_objects);
}
void NodeParamViewItemBody::set_time_target(ViewerOutput *target)
void NodeParamViewItemBody::set_time_target(OakEngineNode *target)
{
time_target_ = target;
@@ -501,13 +508,15 @@ void NodeParamViewItemBody::set_time_target_on_input_ui(const InputUI &ui_obj)
void NodeParamViewItemBody::retranslate()
{
for (auto i = input_ui_map_.begin(); i != input_ui_map_.end(); i++) {
const NodeInput &ic = i.key();
const oak::Input &ic = i.key();
if (ic.is_array() && ic.element() >= 0) {
// Make the label the array index
i.value().main_label->setText(tr("%1:").arg(
ic.element() +
ic.get_property(QStringLiteral("arraystart")).toInt()));
// Make the label the array index ("arraystart" property via the
// C ABI integer property getter)
int64_t arraystart = 0;
ic.property_int("arraystart", &arraystart);
i.value().main_label->setText(
tr("%1:").arg(ic.element() + static_cast<int>(arraystart)));
} else {
// Set to the input's name
i.value().main_label->setText(tr("%1:").arg(ic.name()));
@@ -515,9 +524,11 @@ void NodeParamViewItemBody::retranslate()
}
}
int NodeParamViewItemBody::get_element_y(NodeInput c) const
int NodeParamViewItemBody::get_element_y(oak::Input c) const
{
if (c.is_array() && !array_ui_.value(c.input_pair()).widget->isVisible()) {
if (c.is_array() &&
!array_ui_.value(oak::InputPair(c.node_handle(), c.input_id()))
.widget->isVisible()) {
// Array is collapsed, so we'll return the Y of its root
c.set_element(-1);
}
@@ -537,18 +548,18 @@ int NodeParamViewItemBody::get_element_y(NodeInput c) const
return lbl_center.y();
}
void NodeParamViewItemBody::edge_changed(OakEngineNode *output, const NodeInput &input)
void NodeParamViewItemBody::edge_changed(OakEngineNode *output, const oak::Input &input)
{
Q_UNUSED(output)
const NodeInputPair &pair =
input_group_lookup_.value({ input.node(), input.input() });
NodeInput resolved(pair.node, pair.input, input.element());
const oak::InputPair &pair =
input_group_lookup_.value({ input.node_handle(), input.input_id() });
oak::Input resolved(pair.node_handle(), pair.input_id(), input.element());
update_ui_for_edge_connection(resolved);
}
void NodeParamViewItemBody::update_ui_for_edge_connection(const NodeInput &input)
void NodeParamViewItemBody::update_ui_for_edge_connection(const oak::Input &input)
{
// Show/hide bridge widgets
if (input_ui_map_.contains(input)) {
@@ -595,11 +606,11 @@ void NodeParamViewItemBody::place_widgets_from_bridge(
}
}
void NodeParamViewItemBody::input_array_size_changed_internal(Node *node,
void NodeParamViewItemBody::input_array_size_changed_internal(oak::Node node,
const QString &input,
int size)
{
NodeInputPair nip = { node, input };
oak::InputPair nip(node.handle(), input);
if (!array_ui_.contains(nip)) {
return;
@@ -621,7 +632,7 @@ void NodeParamViewItemBody::input_array_size_changed_internal(Node *node,
} else {
for (int i = array_ui.count - 1; i >= size; i--) {
// Our UI count is larger than the size, delete
InputUI input_ui = input_ui_map_.take({ node, input, i });
InputUI input_ui = input_ui_map_.take({ node.handle(), input, i });
delete input_ui.main_label;
qDeleteAll(input_ui.widget_bridge->widgets());
delete input_ui.widget_bridge;
@@ -642,16 +653,16 @@ void NodeParamViewItemBody::input_array_size_changed_internal(Node *node,
void NodeParamViewItemBody::array_collapse_btn_pressed(bool checked)
{
const NodeInputPair &input =
const oak::InputPair &input =
array_collapse_buttons_.key(static_cast<CollapseButton *>(sender()));
array_ui_.value(input).widget->setVisible(checked);
if (checked) {
// Ensure widgets are created (the signal will be ignored if they are)
NodeInput resolved =
ResolveGroupInput(NodeInput(input.node, input.input));
input_array_size_changed_internal(input.node, input.input,
resolved.get_array_size());
oak::Input resolved =
ResolveGroupInput(oak::Input(input.node_handle(), input.input_id()));
input_array_size_changed_internal(input.node(), input.input_id(),
resolved.array_size());
}
emit array_expanded_changed(checked);
@@ -663,24 +674,25 @@ void NodeParamViewItemBody::input_array_size_changed(OakEngineNode *source,
{
Q_UNUSED(old_sz)
NodeInputPair nip =
input_group_lookup_.value({ reinterpret_cast<Node *>(source), input });
oak::InputPair nip =
input_group_lookup_.value({ source, input });
input_array_size_changed_internal(nip.node, nip.input, size);
input_array_size_changed_internal(nip.node(), nip.input_id(), size);
}
void NodeParamViewItemBody::array_append_clicked()
{
for (auto it = array_ui_.cbegin(); it != array_ui_.cend(); it++) {
if (it.value().append_btn == sender()) {
NodeInput real_input = ResolveGroupInput(
NodeInput(it.key().node, it.key().input));
oak::Input real_input = ResolveGroupInput(
oak::Input(it.key().node_handle(), it.key().input_id()));
// Through the liboakengine C ABI facade (one undoable command,
// same as the old NodeArrayInsertCommand push).
// WRAPPER-GAP: oakengine_node_array_insert_at
oakengine_node_array_insert_at(
reinterpret_cast<OakEngineNode *>(real_input.node()),
real_input.input().toUtf8().constData(),
real_input.get_array_size());
real_input.node_handle(),
real_input.input_id().toUtf8().constData(),
real_input.array_size());
break;
}
}
@@ -691,11 +703,12 @@ void NodeParamViewItemBody::array_insert_clicked()
for (auto it = input_ui_map_.cbegin(); it != input_ui_map_.cend(); it++) {
if (it.value().array_insert_btn == sender()) {
// Found our input and element
NodeInput ic = ResolveGroupInput(it.key());
oak::Input ic = ResolveGroupInput(it.key());
// Through the liboakengine C ABI facade (one undoable command).
// WRAPPER-GAP: oakengine_node_array_insert_at
oakengine_node_array_insert_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
ic.input().toUtf8().constData(), ic.element());
ic.node_handle(),
ic.input_id().toUtf8().constData(), ic.element());
break;
}
}
@@ -706,11 +719,12 @@ void NodeParamViewItemBody::array_remove_clicked()
for (auto it = input_ui_map_.cbegin(); it != input_ui_map_.cend(); it++) {
if (it.value().array_remove_btn == sender()) {
// Found our input and element
NodeInput ic = ResolveGroupInput(it.key());
oak::Input ic = ResolveGroupInput(it.key());
// Through the liboakengine C ABI facade (one undoable command).
// WRAPPER-GAP: oakengine_node_array_remove_at
oakengine_node_array_remove_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
ic.input().toUtf8().constData(), ic.element());
ic.node_handle(),
ic.input_id().toUtf8().constData(), ic.element());
break;
}
}
@@ -724,7 +738,8 @@ void NodeParamViewItemBody::toggle_array_expanded()
for (auto it = input_ui_map_.cbegin(); it != input_ui_map_.cend(); it++) {
if (it.value().widget_bridge == bridge) {
CollapseButton *b =
array_collapse_buttons_.value(it.key().input_pair());
array_collapse_buttons_.value(
oak::InputPair(it.key().node_handle(), it.key().input_id()));
b->setChecked(!b->isChecked());
return;
}
@@ -745,7 +760,7 @@ void NodeParamViewItemBody::set_timebase_on_input_ui(const InputUI &ui_obj)
ui_obj.widget_bridge->set_timebase(timebase_);
}
void NodeParamViewItemBody::set_input_checked(const NodeInput &input, bool e)
void NodeParamViewItemBody::set_input_checked(const oak::Input &input, bool e)
{
if (input_ui_map_.contains(input)) {
QCheckBox *cb = input_ui_map_.value(input).optional_checkbox;
@@ -755,7 +770,7 @@ void NodeParamViewItemBody::set_input_checked(const NodeInput &input, bool e)
}
}
void NodeParamViewItemBody::replace_widgets(const NodeInput &input)
void NodeParamViewItemBody::replace_widgets(const oak::Input &input)
{
InputUI ui = input_ui_map_.value(input);
place_widgets_from_bridge(ui.layout, ui.widget_bridge, ui.row);
@@ -764,8 +779,9 @@ void NodeParamViewItemBody::replace_widgets(const NodeInput &input)
void NodeParamViewItemBody::show_speed_duration_dialog_for_node()
{
// We should only get there if the node is a clip, determined by the dynamic_cast in CreateWidgets
SpeedDurationDialog sdd({ static_cast<ClipBlock *>(node_) }, timebase_,
this);
SpeedDurationDialog sdd(
{ reinterpret_cast<OakEngineBlock *>(node_.handle()) },
timebase_, this);
sdd.exec();
}
+28 -28
View File
@@ -29,13 +29,13 @@
#include <QVBoxLayout>
#include <QWidget>
#include "node/node.h"
#include "engineeventbridge.h"
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewconnectedlabel.h"
#include "nodeparamviewkeyframecontrol.h"
#include "nodeparamviewitembase.h"
#include "nodeparamviewwidgetbridge.h"
#include "oakutil/oaknode.h"
#include "widget/clickablelabel/clickablelabel.h"
#include "widget/collapsebutton/collapsebutton.h"
#include "widget/keyframeview/keyframeview.h"
@@ -52,40 +52,40 @@ enum NodeParamViewCheckBoxBehavior {
class NodeParamViewItemBody : public QWidget {
Q_OBJECT
public:
NodeParamViewItemBody(Node *node,
NodeParamViewItemBody(oak::Node node,
NodeParamViewCheckBoxBehavior create_checkboxes,
QWidget *parent = nullptr);
void set_time_target(ViewerOutput *target);
void set_time_target(OakEngineNode *target);
void retranslate();
int get_element_y(NodeInput c) const;
int get_element_y(oak::Input c) const;
// Set the timebase of any timebased widgets contained here
void set_timebase(const Rational &timebase);
void set_input_checked(const NodeInput &input, bool e);
void set_input_checked(const oak::Input &input, bool e);
signals:
void request_select_node(OakEngineNode *node);
void array_expanded_changed(bool e);
void input_checked_changed(const NodeInput &input, bool e);
void input_checked_changed(const oak::Input &input, bool e);
void request_edit_text_in_viewer();
private:
void create_widgets(QGridLayout *layout, Node *node, const QString &input,
void create_widgets(QGridLayout *layout, oak::Node node, const QString &input,
int element, int row_index);
void update_ui_for_edge_connection(const NodeInput &input);
void update_ui_for_edge_connection(const oak::Input &input);
void place_widgets_from_bridge(QGridLayout *layout,
NodeParamViewWidgetBridge *bridge, int row);
void input_array_size_changed_internal(Node *node, const QString &input,
void input_array_size_changed_internal(oak::Node node, const QString &input,
int size);
struct InputUI {
@@ -104,7 +104,7 @@ private:
NodeParamViewArrayButton *array_remove_btn;
};
QHash<NodeInput, InputUI> input_ui_map_;
QHash<oak::Input, InputUI> input_ui_map_;
struct ArrayUI {
QWidget *widget;
@@ -115,19 +115,19 @@ private:
void set_time_target_on_input_ui(const InputUI &ui);
void set_timebase_on_input_ui(const InputUI &ui);
Node *node_;
oak::Node node_;
QHash<NodeInputPair, ArrayUI> array_ui_;
QHash<oak::InputPair, ArrayUI> array_ui_;
QHash<NodeInputPair, CollapseButton *> array_collapse_buttons_;
QHash<oak::InputPair, CollapseButton *> array_collapse_buttons_;
Rational timebase_;
ViewerOutput *time_target_;
OakEngineNode *time_target_;
NodeParamViewCheckBoxBehavior create_checkboxes_;
QHash<NodeInputPair, NodeInputPair> input_group_lookup_;
QHash<oak::InputPair, oak::InputPair> input_group_lookup_;
EngineEventBridge *bridge_ = nullptr;
@@ -150,7 +150,7 @@ private:
static const int k_max_widget_column;
private slots:
void edge_changed(OakEngineNode *output, const NodeInput &input);
void edge_changed(OakEngineNode *output, const oak::Input &input);
void array_collapse_btn_pressed(bool checked);
@@ -165,7 +165,7 @@ private slots:
void toggle_array_expanded();
void replace_widgets(const NodeInput &input);
void replace_widgets(const oak::Input &input);
void show_speed_duration_dialog_for_node();
@@ -175,11 +175,11 @@ private slots:
class NodeParamViewItem : public NodeParamViewItemBase {
Q_OBJECT
public:
NodeParamViewItem(Node *node,
NodeParamViewItem(oak::Node node,
NodeParamViewCheckBoxBehavior create_checkboxes,
QWidget *parent = nullptr);
void set_time_target(ViewerOutput *target)
void set_time_target(OakEngineNode *target)
{
time_target_ = target;
@@ -193,24 +193,24 @@ public:
body_->set_timebase(timebase);
}
Node *get_context() const
oak::Node get_context() const
{
return ctx_;
}
void set_context(Node *ctx)
void set_context(oak::Node ctx)
{
ctx_ = ctx;
}
Node *get_node() const
oak::Node get_node() const
{
return node_;
}
int get_element_y(const NodeInput &c) const;
int get_element_y(const oak::Input &c) const;
void set_input_checked(const NodeInput &input, bool e);
void set_input_checked(const oak::Input &input, bool e);
KeyframeView::NodeConnections &get_keyframe_connections()
{
@@ -227,7 +227,7 @@ signals:
void array_expanded_changed(bool e);
void input_checked_changed(const NodeInput &input, bool e);
void input_checked_changed(const oak::Input &input, bool e);
void request_edit_text_in_viewer();
@@ -243,13 +243,13 @@ private:
QPushButton *message_clear_button_;
QWidget *message_container_;
Node *node_;
oak::Node node_;
NodeParamViewCheckBoxBehavior create_checkboxes_;
Node *ctx_;
oak::Node ctx_;
ViewerOutput *time_target_;
OakEngineNode *time_target_;
Rational timebase_;
@@ -65,13 +65,9 @@ bool NodeParamViewItemBase::is_expanded() const
return title_bar_->is_expanded();
}
QString NodeParamViewItemBase::get_title_bar_text_from_node(Node *n)
QString NodeParamViewItemBase::get_title_bar_text_from_node(oak::Node n)
{
if (n->get_label().isEmpty()) {
return n->name();
} else {
return tr("%1 (%2)").arg(n->get_label(), n->name());
}
return n.label_and_name();
}
void NodeParamViewItemBase::set_body(QWidget *body)
@@ -25,7 +25,7 @@
#include <QDockWidget>
#include "nodeparamviewitemtitlebar.h"
#include "node/node.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -49,7 +49,7 @@ public:
bool is_expanded() const;
static QString get_title_bar_text_from_node(Node *n);
static QString get_title_bar_text_from_node(oak::Node n);
public slots:
void set_expanded(bool e);
@@ -24,10 +24,7 @@
#include <QHBoxLayout>
#include <QMessageBox>
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "core.h"
#include "node/value.h"
#include "oakengine/events.h"
#include "oakengine/undo.h"
#include "oakengine/viewer.h"
@@ -37,14 +34,67 @@
namespace olive
{
static int64_t rational_to_node_ts(Node *node, const Rational &time)
static int64_t rational_to_node_ts(OakEngineNode *node, const Rational &time)
{
int num = 0, den = 1;
oakengine_node_frame_time_base(reinterpret_cast<OakEngineNode *>(node),
&num, &den);
oakengine_node_frame_time_base(node, &num, &den);
return core::Timecode::time_to_timestamp(time, Rational(num, den));
}
// Exact-rational, all-tracks equivalent of Node::has_keyframe_at_time().
// The facade's oakengine_node_has_keyframe_at_time() uses a lossy
// whole-second/single-track contract, so the tracks are walked through
// the handle API instead.
static bool input_has_keyframe_at_time(OakEngineNode *node,
const char *input_id, int element,
const Rational &time)
{
const int tracks =
oakengine_node_keyframe_track_count(node, input_id, element);
for (int t = 0; t < tracks; t++) {
if (oakengine_node_keyframe_handle_at_time(
node, input_id, element, t, time.numerator(),
time.denominator())) {
return true;
}
}
return false;
}
// All-tracks equivalent of Node::get_closest_keyframe_before/after_time()
// (same lossy-contract caveat as above). Returns false when none.
static bool closest_keyframe_time(OakEngineNode *node, const char *input_id,
int element, const Rational &time, bool after,
Rational *out)
{
const int tracks =
oakengine_node_keyframe_track_count(node, input_id, element);
bool found = false;
Rational best;
for (int t = 0; t < tracks; t++) {
const int count =
oakengine_node_keyframe_count_on_track(node, input_id, element, t);
for (int i = 0; i < count; i++) {
OakEngineKeyframe *key = oakengine_node_keyframe_handle_on_track(
node, input_id, element, t, i);
int64_t num = 0, den = 1;
oakengine_keyframe_get_time(key, &num, &den);
const Rational kt{int(num), int(den)};
if ((after && kt > time) || (!after && kt < time)) {
if (!found || (after && kt < best) ||
(!after && kt > best)) {
best = kt;
found = true;
}
}
}
}
if (found) {
*out = best;
}
return found;
}
NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
QWidget *parent)
: QWidget(parent)
@@ -92,8 +142,7 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
[this](OakEngineNode *source, const QString &input, int element,
bool enabled) {
keyframe_enable_changed(
NodeInput(reinterpret_cast<Node *>(source), input,
element),
oak::Input(source, input, element),
enabled);
});
connect(bridge_, &EngineEventBridge::node_keyframe_added, this,
@@ -104,11 +153,11 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
&NodeParamViewKeyframeControl::update_state);
// Set defaults
set_input(NodeInput());
set_input(oak::Input());
show_buttons_from_keyframe_enable(false);
}
void NodeParamViewKeyframeControl::set_input(const NodeInput &input)
void NodeParamViewKeyframeControl::set_input(const oak::Input &input)
{
if (input_.is_valid()) {
bridge_->unsubscribe(keyframe_enable_sub_);
@@ -132,21 +181,21 @@ void NodeParamViewKeyframeControl::set_input(const NodeInput &input)
if (input_.is_valid()) {
keyframe_enable_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_KEYFRAME_ENABLE_CHANGED);
keyframe_added_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_KEYFRAME_ADDED);
keyframe_removed_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_KEYFRAME_REMOVED);
keyframe_time_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
reinterpret_cast<void *>(input_.node_handle()),
OAKENGINE_EVENT_NODE_KEYFRAME_TIME_CHANGED);
}
}
void NodeParamViewKeyframeControl::TimeTargetDisconnectEvent(ViewerOutput *v)
void NodeParamViewKeyframeControl::TimeTargetDisconnectEvent(OakEngineNode *v)
{
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
@@ -154,10 +203,10 @@ void NodeParamViewKeyframeControl::TimeTargetDisconnectEvent(ViewerOutput *v)
}
}
void NodeParamViewKeyframeControl::TimeTargetConnectEvent(ViewerOutput *v)
void NodeParamViewKeyframeControl::TimeTargetConnectEvent(OakEngineNode *v)
{
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(v),
v,
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<NodeParamViewKeyframeControl *>(userdata)
@@ -187,16 +236,20 @@ void NodeParamViewKeyframeControl::set_buttons_enabled(bool e)
Rational NodeParamViewKeyframeControl::get_current_time_as_node_time() const
{
return get_adjusted_time(get_time_target(), input_.node(),
get_time_target()->get_playhead(),
Node::k_transform_towards_input);
int64_t pn = 0, pd = 1;
oakengine_viewer_get_playhead(get_time_target(), &pn, &pd);
return get_adjusted_time(get_time_target(),
input_.node_handle(),
Rational(pn, pd),
k_transform_towards_input);
}
Rational
NodeParamViewKeyframeControl::convert_to_viewer_time(const Rational &r) const
{
return get_adjusted_time(input_.node(), get_time_target(), r,
Node::k_transform_towards_output);
return get_adjusted_time(input_.node_handle(),
get_time_target(), r,
k_transform_towards_output);
}
void NodeParamViewKeyframeControl::show_buttons_from_keyframe_enable(bool e)
@@ -210,27 +263,33 @@ void NodeParamViewKeyframeControl::toggle_keyframe(bool e)
{
Rational node_time = get_current_time_as_node_time();
QVector<NodeKeyframe *> keys =
input_.node()->get_keyframes_at_time(input_, node_time);
OakEngineNode *node = input_.node_handle();
void *command = oakengine_undo_command_create_multi();
Node *node = input_.node();
const NodeValue::Type declared = node->get_input_data_type(input_.input());
int nb_tracks = oakengine_node_value_keyframe_track_count(
node_value_type_to_c(declared));
const QByteArray input_utf8 = input_.input().toUtf8();
const QByteArray input_utf8 = input_.input_id().toUtf8();
const char *input_id = input_utf8.constData();
const int element = input_.element();
const int64_t time_ts = rational_to_node_ts(node, node_time);
// Node::get_keyframes_at_time() across all tracks
QVector<OakEngineKeyframe *> keys(
qMax(1, oakengine_node_keyframe_track_count(node, input_id, element)));
keys.resize(oakengine_node_keyframes_at_time(
node, input_id, element, node_time.numerator(),
node_time.denominator(), keys.data(), keys.size()));
// WRAPPER-GAP: oakengine_undo_* / oakengine_node_*_keyframe_command
// (undo command assembly has no oak:: wrapper)
void *command = oakengine_undo_command_create_multi();
const int c_type = input_.c_type();
int nb_tracks = oakengine_node_value_keyframe_track_count(c_type);
if (e && keys.isEmpty()) {
// Add a keyframe here (one for each track)
oak_node_value v;
if (oakengine_node_get_input_at_time(
reinterpret_cast<OakEngineNode *>(node), input_id, element, -1,
node, input_id, element, -1,
time_ts, 1, &v) != OAKENGINE_OK) {
oakengine_undo_command_free(command);
return;
@@ -238,34 +297,38 @@ void NodeParamViewKeyframeControl::toggle_keyframe(bool e)
for (int i = 0; i < nb_tracks; i++) {
void *cmd = oakengine_node_insert_keyframe_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, i,
node, input_id, element, i,
time_ts, &v,
NodeKeyframeTypeToFacade(
node->get_best_keyframe_type_for_time_on_track(input_,
node_time, i)),
oakengine_node_keyframe_best_type_at_time(
node, input_id, element, time_ts, i,
oakengine_keyframe_default_type()),
0, 0, 0, 0);
oakengine_undo_command_multi_add_child(command, cmd);
}
} else if (!e && !keys.isEmpty()) {
// Remove all keyframes at this time
foreach (NodeKeyframe *key, keys) {
void *cmd = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(key));
foreach (OakEngineKeyframe *key, keys) {
void *cmd = oakengine_node_remove_keyframe_command(key);
oakengine_undo_command_multi_add_child(command, cmd);
if (node->get_keyframe_tracks(input_).size() == 1) {
if (oakengine_node_keyframe_track_count(node, input_id, element) == 1) {
// If this was the last keyframe on this track, set the standard value
// to the value at this time too.
oak_node_value v;
NodeTrackComponentToOakNodeValue(
declared,
node->get_split_value_at_time_on_track(input_, node_time,
key->track()),
&v);
void *sv = oakengine_node_set_standard_value_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element,
key->track(), &v);
oakengine_undo_command_multi_add_child(command, sv);
oak_node_value normal;
const int track = oakengine_keyframe_get_track(key);
QVector<oak_node_value> track_vals(nb_tracks);
if (oakengine_node_get_input_at_time(
node, input_id, element, -1, time_ts, 1,
&normal) == OAKENGINE_OK &&
oakengine_node_value_split_to_tracks(
c_type, &normal, track_vals.data(),
nb_tracks) == OAKENGINE_OK &&
track >= 0 && track < nb_tracks) {
void *sv = oakengine_node_set_standard_value_command(
node, input_id, element,
track, &track_vals[track]);
oakengine_undo_command_multi_add_child(command, sv);
}
}
}
}
@@ -275,30 +338,45 @@ void NodeParamViewKeyframeControl::toggle_keyframe(bool e)
void NodeParamViewKeyframeControl::update_state()
{
if (!input_.is_valid() || !input_.is_keyframing() || !get_time_target()) {
if (!input_.is_valid() || !input_.is_keyframing() ||
!get_time_target()) {
return;
}
NodeKeyframe *earliest_key = input_.node()->get_earliest_keyframe(input_);
NodeKeyframe *latest_key = input_.node()->get_latest_keyframe(input_);
OakEngineNode *node = input_.node_handle();
const QByteArray input_utf8 = input_.input_id().toUtf8();
const char *input_id = input_utf8.constData();
const int element = input_.element();
int64_t earliest_num = 0, earliest_den = 1;
int64_t latest_num = 0, latest_den = 1;
const bool has_earliest = oakengine_node_keyframe_earliest_time(
node, input_id, element, &earliest_num, &earliest_den);
const bool has_latest = oakengine_node_keyframe_latest_time(
node, input_id, element, &latest_num, &latest_den);
Rational node_time = get_current_time_as_node_time();
prev_key_btn_->setEnabled(earliest_key && node_time > earliest_key->time());
next_key_btn_->setEnabled(latest_key && node_time < latest_key->time());
prev_key_btn_->setEnabled(
has_earliest &&
node_time > Rational(int(earliest_num), int(earliest_den)));
next_key_btn_->setEnabled(
has_latest && node_time < Rational(int(latest_num), int(latest_den)));
toggle_key_btn_->setChecked(
input_.node()->has_keyframe_at_time(input_, node_time));
input_has_keyframe_at_time(node, input_id, element, node_time));
}
void NodeParamViewKeyframeControl::go_to_previous_key()
{
Rational node_time = get_current_time_as_node_time();
NodeKeyframe *previous_key =
input_.node()->get_closest_keyframe_before_time(input_, node_time);
if (previous_key && get_time_target()) {
Rational key_time = convert_to_viewer_time(previous_key->time());
Rational previous_time;
if (closest_keyframe_time(input_.node_handle(),
input_.input_id().toUtf8().constData(),
input_.element(), node_time, false,
&previous_time) &&
get_time_target()) {
Rational key_time = convert_to_viewer_time(previous_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_time_target()),
key_time.numerator(), key_time.denominator());
@@ -309,11 +387,13 @@ void NodeParamViewKeyframeControl::go_to_next_key()
{
Rational node_time = get_current_time_as_node_time();
NodeKeyframe *next_key =
input_.node()->get_closest_keyframe_after_time(input_, node_time);
if (next_key && get_time_target()) {
Rational key_time = convert_to_viewer_time(next_key->time());
Rational next_time;
if (closest_keyframe_time(input_.node_handle(),
input_.input_id().toUtf8().constData(),
input_.element(), node_time, true,
&next_time) &&
get_time_target()) {
Rational key_time = convert_to_viewer_time(next_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_time_target()),
key_time.numerator(), key_time.denominator());
@@ -327,9 +407,8 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
return;
}
Node *node = input_.node();
const NodeValue::Type declared = node->get_input_data_type(input_.input());
const QByteArray input_utf8 = input_.input().toUtf8();
OakEngineNode *node = input_.node_handle();
const QByteArray input_utf8 = input_.input_id().toUtf8();
const char *input_id = input_utf8.constData();
const int element = input_.element();
@@ -337,38 +416,28 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
if (e) {
// Enable keyframing
// WRAPPER-GAP: oakengine_undo_* / oakengine_node_*_command (undo
// command assembly has no oak:: wrapper)
void *command = oakengine_undo_command_create_multi();
void *kf = oakengine_node_set_input_keyframing_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, 1);
node, input_id, element, 1);
oakengine_undo_command_multi_add_child(command, kf);
// Create one keyframe across all tracks here
const QVector<QVariant> &key_vals = node->get_split_standard_value(input_);
if (!key_vals.isEmpty()) {
QVector<oak_node_value> tracks(key_vals.size());
bool converted = true;
for (int i = 0; i < key_vals.size(); i++) {
if (!NodeTrackComponentToOakNodeValue(declared, key_vals.at(i),
&tracks[i])) {
converted = false;
break;
}
}
oak_node_value v;
memset(&v, 0, sizeof(v));
if (converted) {
oakengine_node_value_combine_tracks(
node_value_type_to_c(declared), tracks.constData(),
tracks.size(), &v);
}
const int64_t time_ts =
rational_to_node_ts(node, get_current_time_as_node_time());
const int type = NodeKeyframeTypeToFacade(static_cast<NodeKeyframe::Type>(oakengine_keyframe_default_type()));
for (int i = 0; i < key_vals.size(); i++) {
// Create one keyframe across all tracks here. Keyframing is still
// off at this point, so the value at the current time is the
// input's standard value.
const int64_t time_ts =
rational_to_node_ts(node, get_current_time_as_node_time());
oak_node_value v;
memset(&v, 0, sizeof(v));
if (oakengine_node_get_input_at_time(node, input_id, element, -1,
time_ts, 1, &v) == OAKENGINE_OK) {
const int nb_tracks = input_.keyframe_track_count();
const int type = oakengine_keyframe_default_type();
for (int i = 0; i < nb_tracks; i++) {
void *cmd = oakengine_node_insert_keyframe_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, i,
node, input_id, element, i,
time_ts, &v, type, 0, 0, 0, 0);
oakengine_undo_command_multi_add_child(command, cmd);
}
@@ -376,7 +445,8 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
command_name =
tr("Enabled Keyframing On %1 - %2")
.arg(node->get_label_and_name(), input_.get_input_name());
.arg(oak::Node(node).label_and_name(),
oak::Input(node, input_id).name());
oakengine_undo_push(command, command_name.toUtf8().constData());
} else {
@@ -385,41 +455,56 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
this, tr("Warning"),
tr("Are you sure you want to disable keyframing on this value? This will clear all existing keyframes."),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
// WRAPPER-GAP: oakengine_undo_* / oakengine_node_*_command (undo
// command assembly has no oak:: wrapper)
void *command = oakengine_undo_command_create_multi();
// Store value at this time, we'll set this as the persistent value later
const QVector<QVariant> &stored_vals =
node->get_split_value_at_time(input_,
get_current_time_as_node_time());
const int64_t time_ts =
rational_to_node_ts(node, get_current_time_as_node_time());
const int nb_tracks = input_.keyframe_track_count();
QVector<oak_node_value> track_vals(nb_tracks);
bool have_vals = false;
oak_node_value normal;
if (oakengine_node_get_input_at_time(
node, input_id, element, -1, time_ts, 1,
&normal) == OAKENGINE_OK &&
oakengine_node_value_split_to_tracks(
input_.c_type(), &normal, track_vals.data(),
nb_tracks) == OAKENGINE_OK) {
have_vals = true;
}
// Delete all keyframes
foreach (const NodeKeyframeTrack &track,
node->get_keyframe_tracks(input_)) {
for (int i = track.size() - 1; i >= 0; i--) {
for (int t = 0; t < nb_tracks; t++) {
const int count = oakengine_node_keyframe_count_on_track(
node, input_id, element, t);
for (int i = count - 1; i >= 0; i--) {
void *cmd = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(track.at(i)));
oakengine_node_keyframe_handle_on_track(
node, input_id, element, t, i));
oakengine_undo_command_multi_add_child(command, cmd);
}
}
// Update standard value
for (int i = 0; i < stored_vals.size(); i++) {
oak_node_value v;
NodeTrackComponentToOakNodeValue(declared, stored_vals.at(i), &v);
void *cmd = oakengine_node_set_standard_value_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, i,
&v);
oakengine_undo_command_multi_add_child(command, cmd);
if (have_vals) {
for (int i = 0; i < nb_tracks; i++) {
void *cmd = oakengine_node_set_standard_value_command(
node, input_id, element, i,
&track_vals[i]);
oakengine_undo_command_multi_add_child(command, cmd);
}
}
// Disable keyframing
void *kf = oakengine_node_set_input_keyframing_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, 0);
node, input_id, element, 0);
oakengine_undo_command_multi_add_child(command, kf);
command_name = tr("Disabled Keyframing On %1 - %2")
.arg(node->get_label_and_name(),
input_.get_input_name());
.arg(oak::Node(node).label_and_name(),
oak::Input(node, input_id).name());
oakengine_undo_push(command, command_name.toUtf8().constData());
} else {
@@ -429,7 +514,7 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
}
}
void NodeParamViewKeyframeControl::keyframe_enable_changed(const NodeInput &input,
void NodeParamViewKeyframeControl::keyframe_enable_changed(const oak::Input &input,
bool e)
{
if (input_ == input) {
@@ -27,7 +27,7 @@
#include <cstdint>
#include "engineeventbridge.h"
#include "node/param.h"
#include "oakutil/oaknode.h"
#include "widget/timetarget/timetarget.h"
namespace olive
@@ -42,16 +42,16 @@ public:
{
}
const NodeInput &get_connected_input() const
const oak::Input &get_connected_input() const
{
return input_;
}
void set_input(const NodeInput &input);
void set_input(const oak::Input &input);
protected:
virtual void TimeTargetDisconnectEvent(ViewerOutput *v) override;
virtual void TimeTargetConnectEvent(ViewerOutput *v) override;
virtual void TimeTargetDisconnectEvent(OakEngineNode *v) override;
virtual void TimeTargetConnectEvent(OakEngineNode *v) override;
private:
QPushButton *create_new_tool_button(const QIcon &icon) const;
@@ -67,7 +67,7 @@ private:
QPushButton *next_key_btn_;
QPushButton *enable_key_btn_;
NodeInput input_;
oak::Input input_;
EngineEventBridge *bridge_ = nullptr;
@@ -91,7 +91,7 @@ private slots:
void keyframe_enable_btn_clicked(bool e);
void keyframe_enable_changed(const NodeInput &input, bool e);
void keyframe_enable_changed(const oak::Input &input, bool e);
};
}
File diff suppressed because it is too large Load Diff
@@ -26,7 +26,9 @@
#include <cstdint>
#include "engineeventbridge.h"
#include "common/nodevaluehandle.h"
#include "oakengine/node.h"
#include "oakutil/oaknode.h"
#include "widget/slider/base/numericsliderbase.h"
#include "widget/timetarget/timetarget.h"
@@ -42,7 +44,7 @@ public:
class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject {
Q_OBJECT
public:
NodeParamViewWidgetBridge(NodeInput input, QObject *parent);
NodeParamViewWidgetBridge(const oak::Input &input, QObject *parent);
~NodeParamViewWidgetBridge() override;
const QVector<QWidget *> &widgets() const
@@ -56,13 +58,13 @@ public:
signals:
void array_widget_double_clicked();
void widgets_recreated(const NodeInput &input);
void widgets_recreated(const oak::Input &input);
void request_edit_text_in_viewer();
protected:
virtual void TimeTargetDisconnectEvent(ViewerOutput *v) override;
virtual void TimeTargetConnectEvent(ViewerOutput *v) override;
virtual void TimeTargetDisconnectEvent(OakEngineNode *v) override;
virtual void TimeTargetConnectEvent(OakEngineNode *v) override;
private:
void create_widgets();
@@ -90,26 +92,26 @@ private:
Rational get_current_time_as_node_time() const;
const NodeInput &get_outer_input() const
const oak::Input &get_outer_input() const
{
return input_hierarchy_.first();
}
const NodeInput &get_inner_input() const
const oak::Input &get_inner_input() const
{
return input_hierarchy_.last();
}
QString get_command_name() const;
NodeValue::Type get_data_type() const
NodeValueType::Type get_data_type() const
{
return get_outer_input().get_data_type();
return static_cast<NodeValueType::Type>(get_outer_input().data_type());
}
void update_properties();
QVector<NodeInput> input_hierarchy_;
QVector<oak::Input> input_hierarchy_;
QVector<QWidget *> widgets_;
+28 -17
View File
@@ -26,7 +26,7 @@
#include "oakengine/traverse.h"
#include "oakengine/node.h"
#include "node/value.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -39,11 +39,11 @@ NodeTableView::NodeTableView(QWidget *parent)
tr("A/W") });
}
void NodeTableView::select_nodes(const QVector<Node *> &nodes)
void NodeTableView::select_nodes(const QVector<OakEngineNode *> &nodes)
{
foreach (Node *n, nodes) {
foreach (OakEngineNode *n, nodes) {
QTreeWidgetItem *top_item = new QTreeWidgetItem();
top_item->setText(0, n->get_label_and_name());
top_item->setText(0, oak::Node(n).label_and_name());
top_item->setFirstColumnSpanned(true);
this->addTopLevelItem(top_item);
top_level_item_map_.insert(n, top_item);
@@ -52,9 +52,9 @@ void NodeTableView::select_nodes(const QVector<Node *> &nodes)
set_time(last_time_);
}
void NodeTableView::deselect_nodes(const QVector<Node *> &nodes)
void NodeTableView::deselect_nodes(const QVector<OakEngineNode *> &nodes)
{
foreach (Node *n, nodes) {
foreach (OakEngineNode *n, nodes) {
delete top_level_item_map_.take(n);
}
}
@@ -65,11 +65,12 @@ void NodeTableView::set_time(const Rational &time)
for (auto i = top_level_item_map_.constBegin();
i != top_level_item_map_.constEnd(); i++) {
Node *node = i.key();
OakEngineNode *node = i.key();
QTreeWidgetItem *item = i.value();
// WRAPPER-GAP: oakengine_traverse_* (traverse API has no oak:: wrapper)
OakEngineTraverseDb *db = oakengine_traverse_generate_database(
reinterpret_cast<OakEngineNode *>(node), time.numerator(),
node, time.numerator(),
time.denominator(), time.numerator(), time.denominator());
int input_count = oakengine_traverse_db_input_count(db);
@@ -98,9 +99,22 @@ void NodeTableView::set_time(const Rational &time)
const char *input_id_c = oakengine_traverse_db_input_id(db, l);
QString input_id = QString::fromUtf8(input_id_c);
if (!node->has_input_with_id(input_id)) {
if (!oak::Node(node).input_count()) {
continue;
}
{
bool found_input = false;
const int nic = oak::Node(node).input_count();
for (int ni = 0; ni < nic; ni++) {
if (input_id == oak::Node(node).input_id(ni)) {
found_input = true;
break;
}
}
if (!found_input) {
continue;
}
}
int row_count = oakengine_traverse_db_row_count(db, l);
@@ -117,7 +131,7 @@ void NodeTableView::set_time(const Rational &time)
if (!input_item) {
input_item = new QTreeWidgetItem();
input_item->setText(0, node->get_input_name(input_id));
input_item->setText(0, oak::Input(node, input_id).name());
input_item->setData(0, Qt::UserRole, input_id);
input_item->setFirstColumnSpanned(true);
item->addChild(input_item);
@@ -156,21 +170,18 @@ void NodeTableView::set_time(const Rational &time)
oakengine_traverse_row_source(db, l, actual_row);
QString source_name;
if (source) {
char label_buf[256];
oakengine_node_get_label_and_name(
source, label_buf, sizeof(label_buf));
source_name = QString(label_buf);
source_name = oak::Node(source).label_and_name();
} else {
source_name = tr("(unknown)");
}
sub_item->setText(1, source_name);
switch (type) {
case NodeValue::k_video_params:
case NodeValue::k_audio_params:
case OAK_NODE_VALUE_VIDEO_PARAMS:
case OAK_NODE_VALUE_AUDIO_PARAMS:
// These types have no string representation
break;
case NodeValue::k_texture: {
case OAK_NODE_VALUE_TEXTURE: {
for (int k = 0; k < 4; k++) {
this->setItemWidget(sub_item, 2 + k,
new QCheckBox());
+7 -4
View File
@@ -24,24 +24,27 @@
#include <QTreeWidget>
#include "node/node.h"
#include "olive/core/util/rational.h"
#include "oakengine/node.h"
namespace olive
{
using core::Rational;
class NodeTableView : public QTreeWidget {
Q_OBJECT
public:
NodeTableView(QWidget *parent = nullptr);
void select_nodes(const QVector<Node *> &nodes);
void select_nodes(const QVector<OakEngineNode *> &nodes);
void deselect_nodes(const QVector<Node *> &nodes);
void deselect_nodes(const QVector<OakEngineNode *> &nodes);
void set_time(const Rational &time);
private:
QMap<Node *, QTreeWidgetItem *> top_level_item_map_;
QMap<OakEngineNode *, QTreeWidgetItem *> top_level_item_map_;
Rational last_time_;
};
+2 -2
View File
@@ -32,12 +32,12 @@ class NodeTableWidget : public TimeBasedWidget {
public:
NodeTableWidget(QWidget *parent = nullptr);
void select_nodes(const QVector<Node *> &nodes)
void select_nodes(const QVector<OakEngineNode *> &nodes)
{
view_->select_nodes(nodes);
}
void deselect_nodes(const QVector<Node *> &nodes)
void deselect_nodes(const QVector<OakEngineNode *> &nodes)
{
view_->deselect_nodes(nodes);
}
+42 -41
View File
@@ -23,9 +23,8 @@
#include <QEvent>
#include "common/nodevaluehandle.h"
#include "oakengine/node.h"
#include "oakutil/qtutils.h"
namespace olive
{
@@ -43,17 +42,17 @@ NodeTreeView::NodeTreeView(QWidget *parent)
retranslate();
}
bool NodeTreeView::is_node_enabled(Node *n) const
bool NodeTreeView::is_node_enabled(const oak::Node &n) const
{
return !disabled_nodes_.contains(n);
}
bool NodeTreeView::is_input_enabled(const NodeKeyframeTrackReference &ref) const
bool NodeTreeView::is_input_enabled(const oak::KeyframeTrackRef &ref) const
{
return !disabled_inputs_.contains(ref);
}
void NodeTreeView::set_keyframe_track_color(const NodeKeyframeTrackReference &ref,
void NodeTreeView::set_keyframe_track_color(const oak::KeyframeTrackRef &ref,
const QColor &color)
{
// Insert into hashmap
@@ -66,42 +65,45 @@ void NodeTreeView::set_keyframe_track_color(const NodeKeyframeTrackReference &re
}
}
void NodeTreeView::set_nodes(const QVector<Node *> &nodes)
void NodeTreeView::set_nodes(const QVector<oak::Node> &nodes)
{
nodes_ = nodes;
this->clear();
item_map_.clear();
foreach (Node *n, nodes_) {
foreach (const oak::Node &n, nodes_) {
QTreeWidgetItem *node_item = new QTreeWidgetItem();
node_item->setText(0, n->name());
node_item->setText(0, n.name());
if (checkboxes_enabled_) {
node_item->setCheckState(
0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
}
node_item->setData(0, k_item_type, k_item_type_node);
node_item->setData(0, k_item_node_pointer, QtUtils::ptr_to_value(n));
node_item->setData(0, k_item_node_pointer, QtUtils::ptr_to_value(n.handle()));
foreach (const QString &input, n->inputs()) {
if (n->is_input_hidden(input) ||
(only_show_keyframable_ && !n->is_input_keyframable(input))) {
const int input_count = n.input_count();
for (int idx = 0; idx < input_count; idx++) {
const QString input_id = n.input_id(idx);
oak::Input probe(n.handle(), input_id);
if (probe.is_hidden() ||
(only_show_keyframable_ && !probe.is_keyframable())) {
continue;
}
QTreeWidgetItem *input_item = nullptr;
int arr_sz = n->input_array_size(input);
const int arr_sz = probe.array_size();
for (int i = -1; i < arr_sz; i++) {
NodeInput input_ref(n, input, i);
const QVector<NodeKeyframeTrack> &key_tracks =
n->get_keyframe_tracks(input_ref);
oak::Input input_ref(n.handle(), input_id, i);
const int track_count = input_ref.keyframe_track_count();
int this_element_track;
if (show_keyframe_tracks_as_rows_ &&
(key_tracks.size() == 1 ||
(i == -1 && n->input_is_array(input)))) {
(track_count == 1 ||
(i == -1 && probe.is_array()))) {
this_element_track = 0;
} else {
this_element_track = -1;
@@ -111,19 +113,19 @@ void NodeTreeView::set_nodes(const QVector<Node *> &nodes)
if (input_item) {
element_item = create_item(
input_item, NodeKeyframeTrackReference(
input_item, oak::KeyframeTrackRef(
input_ref, this_element_track));
} else {
input_item = create_item(node_item,
NodeKeyframeTrackReference(
oak::KeyframeTrackRef(
input_ref, this_element_track));
element_item = input_item;
}
if (show_keyframe_tracks_as_rows_ && key_tracks.size() > 1 &&
(!n->input_is_array(input) || i >= 0)) {
if (show_keyframe_tracks_as_rows_ && track_count > 1 &&
(!probe.is_array() || i >= 0)) {
create_items_for_tracks(element_item, input_ref,
key_tracks.size());
track_count);
}
}
}
@@ -152,7 +154,7 @@ void NodeTreeView::mouseDoubleClickEvent(QMouseEvent *e)
{
QTreeWidget::mouseDoubleClickEvent(e);
NodeKeyframeTrackReference ref = get_selected_input();
oak::KeyframeTrackRef ref = get_selected_input();
if (ref.input().is_valid()) {
emit input_double_clicked(ref);
@@ -164,21 +166,21 @@ void NodeTreeView::retranslate()
setHeaderLabel(tr("Nodes"));
}
NodeKeyframeTrackReference NodeTreeView::get_selected_input()
oak::KeyframeTrackRef NodeTreeView::get_selected_input()
{
QList<QTreeWidgetItem *> sel = selectedItems();
NodeKeyframeTrackReference selected_ref;
oak::KeyframeTrackRef selected_ref;
if (!sel.isEmpty()) {
QTreeWidgetItem *item = sel.first();
if (item->data(0, k_item_type).toInt() == k_item_type_input) {
selected_ref = item->data(0, k_item_input_reference)
.value<NodeKeyframeTrackReference>();
.value<oak::KeyframeTrackRef>();
} else {
selected_ref = NodeKeyframeTrackReference(NodeInput(
QtUtils::value_to_ptr<Node>(item->data(0, k_item_node_pointer)),
selected_ref = oak::KeyframeTrackRef(oak::Input(
QtUtils::value_to_ptr<OakEngineNode>(item->data(0, k_item_node_pointer)),
QString()));
}
}
@@ -187,14 +189,13 @@ NodeKeyframeTrackReference NodeTreeView::get_selected_input()
}
QTreeWidgetItem *NodeTreeView::create_item(QTreeWidgetItem *parent,
const NodeKeyframeTrackReference &ref)
const oak::KeyframeTrackRef &ref)
{
QTreeWidgetItem *input_item = new QTreeWidgetItem(parent);
QString item_name;
if (ref.track() == -1 ||
oakengine_node_value_keyframe_track_count(node_value_type_to_c(ref.input().get_data_type())) ==
1 ||
ref.input().keyframe_track_count() == 1 ||
(ref.input().is_array() && ref.input().element() == -1)) {
if (ref.input().element() == -1) {
item_name = ref.input().name();
@@ -238,16 +239,16 @@ QTreeWidgetItem *NodeTreeView::create_item(QTreeWidgetItem *parent,
}
void NodeTreeView::create_items_for_tracks(QTreeWidgetItem *parent,
const NodeInput &input, int track_count)
const oak::Input &input, int track_count)
{
for (int j = 0; j < track_count; j++) {
create_item(parent, NodeKeyframeTrackReference(input, j));
create_item(parent, oak::KeyframeTrackRef(input, j));
}
}
bool NodeTreeView::use_rgba_over_xyzw(const NodeKeyframeTrackReference &ref)
bool NodeTreeView::use_rgba_over_xyzw(const oak::KeyframeTrackRef &ref)
{
return ref.input().get_data_type() == NodeValue::k_color;
return ref.input().c_type() == OAK_NODE_VALUE_COLOR;
}
void NodeTreeView::item_check_state_changed(QTreeWidgetItem *item, int column)
@@ -256,22 +257,22 @@ void NodeTreeView::item_check_state_changed(QTreeWidgetItem *item, int column)
switch (item->data(0, k_item_type).toInt()) {
case k_item_type_node: {
Node *n = QtUtils::value_to_ptr<Node>(item->data(0, k_item_node_pointer));
oak::Node n(QtUtils::value_to_ptr<OakEngineNode>(item->data(0, k_item_node_pointer)));
if (item->checkState(0) == Qt::Checked) {
if (disabled_nodes_.contains(n)) {
disabled_nodes_.removeOne(n);
emit node_enable_changed(reinterpret_cast<OakEngineNode *>(n), true);
emit node_enable_changed(n.handle(), true);
}
} else if (!disabled_nodes_.contains(n)) {
disabled_nodes_.append(n);
emit node_enable_changed(reinterpret_cast<OakEngineNode *>(n), false);
emit node_enable_changed(n.handle(), false);
}
break;
}
case k_item_type_input: {
NodeKeyframeTrackReference i = item->data(0, k_item_input_reference)
.value<NodeKeyframeTrackReference>();
oak::KeyframeTrackRef i = item->data(0, k_item_input_reference)
.value<oak::KeyframeTrackRef>();
if (item->checkState(0) == Qt::Checked) {
if (disabled_inputs_.contains(i)) {
+17 -21
View File
@@ -24,8 +24,7 @@
#include <QTreeWidget>
#include "node/node.h"
#include "oakengine/node.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -35,16 +34,16 @@ class NodeTreeView : public QTreeWidget {
public:
NodeTreeView(QWidget *parent = nullptr);
bool is_node_enabled(Node *n) const;
bool is_node_enabled(const oak::Node &n) const;
bool is_input_enabled(const NodeKeyframeTrackReference &ref) const;
bool is_input_enabled(const oak::KeyframeTrackRef &ref) const;
void set_check_boxes_enabled(bool e)
{
checkboxes_enabled_ = e;
}
void set_keyframe_track_color(const NodeKeyframeTrackReference &ref,
void set_keyframe_track_color(const oak::KeyframeTrackRef &ref,
const QColor &color);
void set_only_show_keyframable(bool e)
@@ -58,19 +57,16 @@ public:
}
public:
// Not a slot: signature uses the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void set_nodes(const QVector<Node *> &nodes);
void set_nodes(const QVector<oak::Node> &nodes);
signals:
void node_enable_changed(OakEngineNode *n, bool e);
void input_enable_changed(const NodeKeyframeTrackReference &ref, bool e);
void input_enable_changed(const oak::KeyframeTrackRef &ref, bool e);
void input_selection_changed(const NodeKeyframeTrackReference &ref);
void input_selection_changed(const oak::KeyframeTrackRef &ref);
void input_double_clicked(const NodeKeyframeTrackReference &ref);
void input_double_clicked(const oak::KeyframeTrackRef &ref);
protected:
virtual void changeEvent(QEvent *e) override;
@@ -80,15 +76,15 @@ protected:
private:
void retranslate();
NodeKeyframeTrackReference get_selected_input();
oak::KeyframeTrackRef get_selected_input();
QTreeWidgetItem *create_item(QTreeWidgetItem *parent,
const NodeKeyframeTrackReference &ref);
const oak::KeyframeTrackRef &ref);
void create_items_for_tracks(QTreeWidgetItem *parent, const NodeInput &input,
void create_items_for_tracks(QTreeWidgetItem *parent, const oak::Input &input,
int track_count);
static bool use_rgba_over_xyzw(const NodeKeyframeTrackReference &ref);
static bool use_rgba_over_xyzw(const oak::KeyframeTrackRef &ref);
enum ItemType { k_item_type_node, k_item_type_input };
@@ -96,19 +92,19 @@ private:
static const int k_item_input_reference = Qt::UserRole + 1;
static const int k_item_node_pointer = Qt::UserRole + 1;
QVector<Node *> nodes_;
QVector<oak::Node> nodes_;
QVector<Node *> disabled_nodes_;
QVector<oak::Node> disabled_nodes_;
QVector<NodeKeyframeTrackReference> disabled_inputs_;
QVector<oak::KeyframeTrackRef> disabled_inputs_;
QHash<NodeKeyframeTrackReference, QTreeWidgetItem *> item_map_;
QHash<oak::KeyframeTrackRef, QTreeWidgetItem *> item_map_;
bool only_show_keyframable_;
bool show_keyframe_tracks_as_rows_;
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
QHash<oak::KeyframeTrackRef, QColor> keyframe_colors_;
bool checkboxes_enabled_;
+36 -25
View File
@@ -20,10 +20,25 @@
#include <QEvent>
#include "common/nodevaluehandle.h"
#include "oakengine/traverse.h"
#include "oakengine/node.h"
#include "node/value.h"
namespace olive
{
/**
* @brief Local mirror of the engine's value-hint triple (type, index, tag)
* carried in the radio buttons' "hint" property.
*/
struct ValueHint {
int type = 0;
int index = -1;
QString tag;
};
}
Q_DECLARE_METATYPE(olive::ValueHint)
namespace olive
{
@@ -46,23 +61,25 @@ NodeValueTree::NodeValueTree(QWidget *parent)
retranslate();
}
void NodeValueTree::set_node(const NodeInput &input, const Rational &time)
void NodeValueTree::set_node(const oak::Input &input, const Rational &time)
{
clear();
Node *connected_node = input.get_connected_output();
oak::Node connected_node = input.connected_node();
// WRAPPER-GAP: oakengine_traverse_* (traverse API has no oak:: wrapper)
OakEngineTraverseDb *table_db = oakengine_traverse_generate_table(
reinterpret_cast<OakEngineNode *>(connected_node),
connected_node.handle(),
time.numerator(), time.denominator(), time.numerator(),
time.denominator());
int db_index = 0;
int row_count = oakengine_traverse_db_row_count(table_db, db_index);
// WRAPPER-GAP: oakengine_traverse_table_element_index_for_hint
int index = oakengine_traverse_table_element_index_for_hint(
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(), table_db);
input.node_handle(),
input.input_id().toUtf8().constData(), input.element(), table_db);
for (int i = 0; i < row_count; i++) {
QTreeWidgetItem *item = new QTreeWidgetItem(this);
@@ -72,8 +89,10 @@ void NodeValueTree::set_node(const NodeInput &input, const Rational &time)
i);
const char *tag = oakengine_traverse_row_tag(table_db, db_index, i);
Node::ValueHint hint({ static_cast<NodeValue::Type>(type) },
row_count - 1 - i, QString(tag));
ValueHint hint;
hint.type = type;
hint.index = row_count - 1 - i;
hint.tag = QString(tag);
QRadioButton *radio = new QRadioButton(this);
radio->setProperty("input", QVariant::fromValue(input));
@@ -98,10 +117,7 @@ void NodeValueTree::set_node(const NodeInput &input, const Rational &time)
item->setText(2, vs ? QString(vs) : QString());
if (source) {
char label_buf[256];
oakengine_node_get_label_and_name(source, label_buf,
sizeof(label_buf));
item->setText(3, QString(label_buf));
item->setText(3, oak::Node(source).label_and_name());
} else {
item->setText(3, QString());
}
@@ -128,20 +144,15 @@ void NodeValueTree::radio_button_checked(bool e)
{
if (e) {
QRadioButton *btn = static_cast<QRadioButton *>(sender());
Node::ValueHint hint = btn->property("hint").value<Node::ValueHint>();
NodeInput input = btn->property("input").value<NodeInput>();
ValueHint hint = btn->property("hint").value<ValueHint>();
oak::Input input = btn->property("input").value<oak::Input>();
// Map the full hint through the facade: type (single, or -1 to keep
// the input's declared type), index and tag must not be dropped.
int c_type = -1;
if (!hint.types().isEmpty()) {
c_type = node_value_type_to_c(hint.types().first());
}
// WRAPPER-GAP: oakengine_node_set_value_hint
oakengine_node_set_value_hint(
reinterpret_cast<OakEngineNode*>(input.node()),
input.input().toUtf8().constData(), input.element(),
c_type, hint.index(),
hint.tag().isEmpty() ? nullptr : hint.tag().toUtf8().constData());
input.node_handle(),
input.input_id().toUtf8().constData(), input.element(),
hint.type, hint.index,
hint.tag.isEmpty() ? nullptr : hint.tag.toUtf8().constData());
}
}
+5 -2
View File
@@ -22,17 +22,20 @@
#include <QRadioButton>
#include <QTreeWidget>
#include "node/node.h"
#include "olive/core/util/rational.h"
#include "oakutil/oaknode.h"
namespace olive
{
using core::Rational;
class NodeValueTree : public QTreeWidget {
Q_OBJECT
public:
NodeValueTree(QWidget *parent = nullptr);
void set_node(const NodeInput &input, const Rational &time);
void set_node(const oak::Input &input, const Rational &time);
protected:
virtual void changeEvent(QEvent *event) override;
File diff suppressed because it is too large Load Diff
+22 -22
View File
@@ -51,9 +51,9 @@ public:
virtual ~NodeView() override;
void set_contexts(const QVector<Node *> &nodes);
void set_contexts(const QVector<oak::Node> &nodes);
const QVector<Node *> &get_contexts() const
const QVector<oak::Node> &get_contexts() const
{
if (overlay_view_) {
return overlay_view_->get_contexts();
@@ -67,7 +67,7 @@ public:
return overlay_view_;
}
void close_contexts_belonging_to_project(Project *project);
void close_contexts_belonging_to_project(oak::Project project);
void clear_graph();
@@ -94,7 +94,7 @@ public:
void zoom_out();
const QVector<Node *> &get_current_contexts() const
const QVector<oak::Node> &get_current_contexts() const
{
return contexts_;
}
@@ -163,10 +163,10 @@ private:
void move_attached_nodes_to_cursor(const QPoint &p);
void process_moving_attached_nodes(const QPoint &pos);
QVector<Node *> process_dropping_attached_nodes(void *command,
Node *select_context,
QVector<oak::Node> process_dropping_attached_nodes(void *command,
oak::Node select_context,
const QPoint &pos);
Node *get_context_at_mouse_pos(const QPoint &p);
oak::Node get_context_at_mouse_pos(const QPoint &p);
void connect_selection_changed_signal();
void disconnect_selection_changed_signal();
@@ -176,18 +176,18 @@ private:
void clear_create_edge_input_if_necessary();
QPointF get_estimated_position_for_context(NodeViewItem *item,
Node *context) const;
oak::Node context) const;
NodeViewItem *get_assumed_item_for_selected_node(Node *node);
bool get_assumed_position_for_selected_node(Node *node, Node::Position *pos);
NodeViewItem *get_assumed_item_for_selected_node(oak::Node node);
bool get_assumed_position_for_selected_node(oak::Node node, NodeViewItemPosition *pos);
Menu *create_add_menu(Menu *parent);
void position_new_edge(const QPoint &pos);
void add_context(Node *n);
void add_context(oak::Node n);
void remove_context(Node *n);
void remove_context(oak::Node n);
bool is_item_attached_to_cursor(NodeViewItem *item) const;
@@ -197,8 +197,8 @@ private:
void end_edge_drag(bool cancel = false);
void post_paste(const QVector<Node *> &new_nodes,
const Node::PositionMap &map);
void post_paste(const QVector<oak::Node> &new_nodes,
const QHash<oak::Node, NodeViewItemPosition> &map);
void resize_overlay();
@@ -208,7 +208,7 @@ private:
struct AttachedItem {
NodeViewItem *item;
Node *node;
oak::Node node;
QPointF original_pos;
};
@@ -216,12 +216,12 @@ private:
QVector<AttachedItem> attached_items_;
NodeViewEdge *drop_edge_;
NodeInput drop_input_;
oak::Input drop_input_;
NodeViewEdge *create_edge_;
NodeViewItem *create_edge_output_item_;
NodeViewItem *create_edge_input_item_;
NodeInput create_edge_input_;
oak::Input create_edge_input_;
bool create_edge_already_exists_;
bool create_edge_from_output_;
@@ -229,11 +229,11 @@ private:
NodeViewScene scene_;
QVector<Node *> selected_nodes_;
QVector<oak::Node> selected_nodes_;
QVector<Node *> contexts_;
QVector<Node *> last_set_filter_nodes_;
QMap<Node *, QPointF> context_offsets_;
QVector<oak::Node> contexts_;
QVector<oak::Node> last_set_filter_nodes_;
QHash<oak::Node, QPointF> context_offsets_;
QMap<NodeViewItem *, QPointF> dragging_items_;
@@ -245,7 +245,7 @@ private:
EngineEventBridge *bridge_ = nullptr;
QHash<Node *, int64_t> removed_from_graph_subs_;
QHash<oak::Node, int64_t> removed_from_graph_subs_;
QAction *show_in_param_editor_action_;
+114 -89
View File
@@ -24,16 +24,17 @@
#include <QCoreApplication>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QPen>
#include <QStyleOptionGraphicsItem>
#include "core.h"
#include "node/block/block.h"
#include "node/output/track/track.h"
#include "node/project.h"
#include "node/project/sequence/sequence.h"
#include "common/trackreferencehandle.h"
#include "nodeviewitem.h"
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
#include "oakengine/timeline.h"
#include "oakutil/qtutils.h"
#include "widget/timelinewidget/cliphandle.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
@@ -41,95 +42,109 @@ namespace olive
#define super QGraphicsRectItem
NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item)
NodeViewContext::NodeViewContext(oak::Node context, QGraphicsItem *item)
: super(item)
, context_(context)
, bridge_(new EngineEventBridge(this))
{
Block *block = dynamic_cast<Block *>(context_);
if (block && block->track() && block->track()->sequence()) {
Rational timebase = viewer_output_video_params(block->track()->sequence())
// Block contexts (clips/transitions nested in the node graph) show their
// track placement in the title. All engine queries go through the C ABI:
// oakengine_node_is_block() replaces dynamic_cast<Block*> (Block is
// abstract and carries no own type id), and the track type ordinals are
// the TrackReference mirror values (== engine Track::Type, pinned by the
// static_asserts in trackreferencehandle.h).
OakEngineNode *track_node =
oakengine_node_is_block(context_.handle()) ?
block_track_handle(
reinterpret_cast<OakEngineBlock *>(context_.handle())) :
nullptr;
OakEngineNode *track_seq =
track_node ? oakengine_track_get_sequence(track_node) : nullptr;
if (track_seq) {
Rational timebase = viewer_output_video_params(track_seq)
.frame_rate_as_time_base();
QString type_label;
switch (block->track()->type()) {
case Track::k_video:
switch (oakengine_track_get_type(track_node)) {
case TrackReference::k_video:
type_label = QCoreApplication::translate("NodeViewContext", "V");
break;
case Track::k_audio:
case TrackReference::k_audio:
type_label = QCoreApplication::translate("NodeViewContext", "A");
break;
case Track::k_subtitle:
case TrackReference::k_subtitle:
type_label = QCoreApplication::translate("NodeViewContext", "S");
break;
default:
break;
}
int in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_block_get_in_rational(context_.handle(), &in_num, &in_den);
oakengine_block_get_out_rational(context_.handle(), &out_num, &out_den);
lbl_ =
QCoreApplication::translate("NodeViewContext", "%1 [%2] :: %3 - %4")
.arg(block->get_label_and_name(),
.arg(context_.label_and_name(),
type_label,
QString::fromStdString(Timecode::time_to_timecode(
block->in(), timebase,
Rational(in_num, in_den), timebase,
Core::instance()->get_timecode_display())),
QString::fromStdString(Timecode::time_to_timecode(
block->out(), timebase,
Rational(out_num, out_den), timebase,
Core::instance()->get_timecode_display())));
} else {
lbl_ = context_->get_label_and_name();
lbl_ = context_.label_and_name();
}
connect(bridge_, &EngineEventBridge::node_node_added_to_context, this,
[this](OakEngineNode *source, OakEngineNode *node) {
Node *src = reinterpret_cast<Node *>(source);
if (src == context_) {
add_child(reinterpret_cast<Node *>(node));
if (source == context_.handle()) {
add_child(node);
} else {
group_added_node(reinterpret_cast<Node *>(node), src);
group_added_node(node, source);
}
});
connect(bridge_, &EngineEventBridge::node_node_removed_from_context, this,
[this](OakEngineNode *source, OakEngineNode *node) {
Node *src = reinterpret_cast<Node *>(source);
if (src == context_) {
remove_child(reinterpret_cast<Node *>(node));
if (source == context_.handle()) {
remove_child(node);
} else {
group_removed_node(reinterpret_cast<Node *>(node), src);
group_removed_node(node, source);
}
});
connect(bridge_, &EngineEventBridge::node_context_position_changed, this,
[this](OakEngineNode *, OakEngineNode *node, double x, double y) {
set_child_position(reinterpret_cast<Node *>(node),
QPointF(x, y));
set_child_position(node, QPointF(x, y));
});
connect(bridge_, &EngineEventBridge::node_input_connected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
child_input_connected(reinterpret_cast<Node *>(output),
NodeInput(reinterpret_cast<Node *>(source), input,
element));
child_input_connected(output,
oak::Input(source, input, element));
});
connect(bridge_, &EngineEventBridge::node_input_disconnected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
child_input_disconnected(reinterpret_cast<Node *>(output),
NodeInput(reinterpret_cast<Node *>(source), input,
element));
child_input_disconnected(output,
oak::Input(source, input, element));
});
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
node_subs_[context_.handle()].append(bridge_->subscribe(
context_.handle(),
OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT));
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
node_subs_[context_.handle()].append(bridge_->subscribe(
context_.handle(),
OAKENGINE_EVENT_NODE_CONTEXT_POSITION_CHANGED));
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
node_subs_[context_.handle()].append(bridge_->subscribe(
context_.handle(),
OAKENGINE_EVENT_NODE_NODE_REMOVED_FROM_CONTEXT));
const Node::PositionMap &map = context_->get_context_positions();
for (auto it = map.cbegin(); it != map.cend(); it++) {
add_child(it.key());
const int ctx_count = context_.context_node_count();
for (int i = 0; i < ctx_count; i++) {
oak::Node child = context_.context_node_at(i).node;
if (!child.is_null()) {
add_child(child.handle());
}
}
}
@@ -140,22 +155,26 @@ NodeViewContext::~NodeViewContext()
edges_.clear();
}
void NodeViewContext::add_child(Node *node)
void NodeViewContext::add_child(OakEngineNode *node)
{
if (!context_) {
if (context_.is_null()) {
return;
}
NodeViewItem *item = new NodeViewItem(node, context_, this);
NodeViewItem *item = new NodeViewItem(oak::Node(node), context_, this);
item->set_flow_direction(flow_dir_);
add_node_internal(node, item);
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(node))) {
for (auto it = node->get_context_positions().cbegin();
it != node->get_context_positions().cend(); it++) {
// Use this item as the representative for all of these nodes too
add_node_internal(it.key(), item);
oak::Node group_node(node);
if (group_node.is_group()) {
const int grp_count = group_node.context_node_count();
for (int i = 0; i < grp_count; i++) {
oak::Node grp_child = group_node.context_node_at(i).node;
if (!grp_child.is_null()) {
// Use this item as the representative for all of these nodes too
add_node_internal(grp_child.handle(), item);
}
}
node_subs_[node].append(bridge_->subscribe(
@@ -169,12 +188,12 @@ void NodeViewContext::add_child(Node *node)
update_rect();
}
void NodeViewContext::set_child_position(Node *node, const QPointF &pos)
void NodeViewContext::set_child_position(OakEngineNode *node, const QPointF &pos)
{
item_map_.value(node)->set_node_position(pos);
}
void NodeViewContext::remove_child(Node *node)
void NodeViewContext::remove_child(OakEngineNode *node)
{
foreach (int64_t id, node_subs_.take(node)) {
bridge_->unsubscribe(id);
@@ -192,17 +211,16 @@ void NodeViewContext::remove_child(Node *node)
// be changed...)
QVector<NodeViewEdge *> edges_to_remove = item->get_all_edges_recursively();
foreach (NodeViewEdge *edge, edges_to_remove) {
if (node == item->get_node() || edge->output() == node ||
edge->input().node() == node) {
child_input_disconnected(edge->output(), edge->input());
if (item->get_node() == oak::Node(node) || edge->output() == oak::Node(node) ||
edge->input().node_handle() == node) {
child_input_disconnected(edge->output().handle(), edge->input());
}
}
// Check if this item is specifically for this node and the node is a group. If so, remove it for
// all other entries in the map.
if (item->get_node() == node) {
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(
item->get_node()))) {
if (item->get_node() == oak::Node(node)) {
if (item->get_node().is_group()) {
for (auto it = item_map_.begin(); it != item_map_.end();) {
if (it.value() == item) {
it = item_map_.erase(it);
@@ -218,25 +236,25 @@ void NodeViewContext::remove_child(Node *node)
update_rect();
}
void NodeViewContext::child_input_connected(Node *output, const NodeInput &input)
void NodeViewContext::child_input_connected(OakEngineNode *output, const oak::Input &input)
{
// Add edge
if (!input.is_hidden()) {
if (NodeViewItem *output_item = item_map_.value(output)) {
add_edge_internal(
output, input, output_item,
item_map_.value(input.node())->get_item_for_input(input));
item_map_.value(input.node_handle())->get_item_for_input(input));
}
}
}
bool NodeViewContext::child_input_disconnected(Node *output,
const NodeInput &input)
bool NodeViewContext::child_input_disconnected(OakEngineNode *output,
const oak::Input &input)
{
// Remove edge
for (int i = 0; i < edges_.size(); i++) {
NodeViewEdge *e = edges_.at(i);
if (e->output() == output && e->input() == input) {
if (e->output() == oak::Node(output) && e->input() == input) {
delete e;
edges_.removeAt(i);
return true;
@@ -284,8 +302,8 @@ void NodeViewContext::set_curved_edges(bool e)
}
}
void NodeViewContext::get_selected_for_deletion(QVector<Node *> &nodes,
QVector<Node *> &contexts,
void NodeViewContext::get_selected_for_deletion(QVector<OakEngineNode *> &nodes,
QVector<OakEngineNode *> &contexts,
QVector<NodeViewEdge *> &edges) const
{
// Collect any selected edges
@@ -298,15 +316,15 @@ void NodeViewContext::get_selected_for_deletion(QVector<Node *> &nodes,
// Collect any selected nodes
foreach (NodeViewItem *node, item_map_) {
if (node->isSelected()) {
nodes.append(node->get_node());
contexts.append(context_);
nodes.append(node->get_node().handle());
contexts.append(context_.handle());
}
}
}
void NodeViewContext::select(const QVector<Node *> &nodes)
void NodeViewContext::select(const QVector<OakEngineNode *> &nodes)
{
foreach (Node *n, nodes) {
foreach (OakEngineNode *n, nodes) {
if (NodeViewItem *item = item_map_.value(n)) {
item->setSelected(true);
}
@@ -343,7 +361,7 @@ void NodeViewContext::paint(QPainter *painter,
QWidget *widget)
{
// Set pen and brush
Color color = context_->color();
Color color = AppColorCoding::get_color(context_.effective_color_label());
QColor c = QtUtils::to_q_color(color);
QPen pen(c, 2);
if (option->state & QStyle::State_Selected) {
@@ -368,7 +386,7 @@ void NodeViewContext::paint(QPainter *painter,
painter->setClipping(false);
// Draw titlebar text
painter->setPen(ColorCoding::get_ui_selector_color(color));
painter->setPen(AppColorCoding::get_ui_selector_color(color));
int offset = get_text_offset(painter->fontMetrics());
@@ -393,7 +411,7 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event)
super::mousePressEvent(event);
}
void NodeViewContext::add_node_internal(Node *node, NodeViewItem *item)
void NodeViewContext::add_node_internal(OakEngineNode *node, NodeViewItem *item)
{
node_subs_[node].append(bridge_->subscribe(
reinterpret_cast<void *>(node),
@@ -404,39 +422,46 @@ void NodeViewContext::add_node_internal(Node *node, NodeViewItem *item)
item_map_.insert(node, item);
if (node == context_) {
if (node == context_.handle()) {
item->set_label_as_output(true);
}
for (auto it = node->output_connections().cbegin();
it != node->output_connections().cend(); it++) {
if (!it->second.is_hidden()) {
if (NodeViewItem *other_item = item_map_.value(it->second.node())) {
add_edge_internal(node, it->second, item,
other_item->get_item_for_input(it->second));
// Iterate output connections via the wrapper
oak::Node wrapped(node);
const int out_count = wrapped.output_connection_count();
for (int i = 0; i < out_count; i++) {
oak::NodeConnection conn = wrapped.output_connection_at_ex(i);
if (!conn.hidden) {
if (NodeViewItem *other_item = item_map_.value(conn.node.handle())) {
oak::Input ai(conn.node.handle(), conn.input_id, conn.element);
add_edge_internal(node, ai, item,
other_item->get_item_for_input(ai));
}
}
}
for (auto it = node->input_connections().cbegin();
it != node->input_connections().cend(); it++) {
if (!it->first.is_hidden()) {
if (NodeViewItem *other_item = item_map_.value(it->second)) {
add_edge_internal(it->second, it->first, other_item,
item->get_item_for_input(it->first));
// Iterate input connections via the wrapper
const int in_count = wrapped.input_connection_count_all();
for (int i = 0; i < in_count; i++) {
oak::NodeConnection conn = wrapped.input_connection_at_all(i);
if (!conn.hidden) {
if (NodeViewItem *other_item = item_map_.value(conn.source_node.handle())) {
oak::Input ai(conn.node.handle(), conn.input_id, conn.element);
add_edge_internal(conn.source_node.handle(), ai, other_item,
item->get_item_for_input(ai));
}
}
}
}
void NodeViewContext::add_edge_internal(Node *output, const NodeInput &input,
void NodeViewContext::add_edge_internal(OakEngineNode *output, const oak::Input &input,
NodeViewItem *from, NodeViewItem *to)
{
if (from == to) {
return;
}
NodeViewEdge *edge_ui = new NodeViewEdge(output, input, from, to, this);
NodeViewEdge *edge_ui = new NodeViewEdge(oak::Node(output), input, from, to, this);
edge_ui->adjust();
edge_ui->set_curved(curved_edges_);
@@ -444,12 +469,12 @@ void NodeViewContext::add_edge_internal(Node *output, const NodeInput &input,
edges_.append(edge_ui);
}
void NodeViewContext::group_added_node(Node *node, Node *group)
void NodeViewContext::group_added_node(OakEngineNode *node, OakEngineNode *group)
{
add_node_internal(node, item_map_.value(group));
}
void NodeViewContext::group_removed_node(Node *node, Node *group)
void NodeViewContext::group_removed_node(OakEngineNode *node, OakEngineNode *group)
{
if (item_map_.value(node) == item_map_.value(group)) {
item_map_.remove(node);
+22 -25
View File
@@ -24,7 +24,7 @@
#include <QHash>
#include "engineeventbridge.h"
#include "node/node.h"
#include "oakutil/oaknode.h"
#include "nodeviewcommon.h"
#include "nodeviewedge.h"
@@ -34,11 +34,11 @@ namespace olive
class NodeViewContext : public QObject, public QGraphicsRectItem {
Q_OBJECT
public:
NodeViewContext(Node *context, QGraphicsItem *item = nullptr);
NodeViewContext(oak::Node context, QGraphicsItem *item = nullptr);
virtual ~NodeViewContext() override;
Node *get_context() const
oak::Node get_context() const
{
return context_;
}
@@ -49,17 +49,17 @@ public:
void set_curved_edges(bool e);
void get_selected_for_deletion(QVector<Node *> &nodes,
QVector<Node *> &contexts,
void get_selected_for_deletion(QVector<OakEngineNode *> &nodes,
QVector<OakEngineNode *> &contexts,
QVector<NodeViewEdge *> &edges) const;
void select(const QVector<Node *> &nodes);
void select(const QVector<OakEngineNode *> &nodes);
QVector<NodeViewItem *> get_selected_items() const;
QPointF map_scene_pos_to_node_pos_in_context(const QPointF &pos) const;
NodeViewItem *get_item_from_map(Node *node) const
NodeViewItem *get_item_from_map(OakEngineNode *node) const
{
return item_map_.value(node);
}
@@ -69,19 +69,17 @@ public:
QWidget *widget = nullptr) override;
public:
// Not slots: signatures use the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). They are invoked from lambdas / directly, never as connect()
// targets.
void add_child(Node *node);
// Not slots: they are invoked from EngineEventBridge lambdas / directly,
// never as connect() targets.
void add_child(OakEngineNode *node);
void set_child_position(Node *node, const QPointF &pos);
void set_child_position(OakEngineNode *node, const QPointF &pos);
void remove_child(Node *node);
void remove_child(OakEngineNode *node);
void child_input_connected(Node *output, const NodeInput &input);
void child_input_connected(OakEngineNode *output, const oak::Input &input);
bool child_input_disconnected(Node *output, const NodeInput &input);
bool child_input_disconnected(OakEngineNode *output, const oak::Input &input);
signals:
void item_about_to_be_deleted(NodeViewItem *item);
@@ -93,12 +91,12 @@ protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
private:
void add_node_internal(Node *node, NodeViewItem *item);
void add_node_internal(OakEngineNode *node, NodeViewItem *item);
void add_edge_internal(Node *output, const NodeInput &input,
void add_edge_internal(OakEngineNode *output, const oak::Input &input,
NodeViewItem *from, NodeViewItem *to);
Node *context_;
oak::Node context_;
QString lbl_;
@@ -108,20 +106,19 @@ private:
int last_titlebar_height_;
QMap<Node *, NodeViewItem *> item_map_;
QMap<OakEngineNode *, NodeViewItem *> item_map_;
QVector<NodeViewEdge *> edges_;
EngineEventBridge *bridge_ = nullptr;
QHash<Node *, QVector<int64_t>> node_subs_;
QHash<OakEngineNode *, QVector<int64_t>> node_subs_;
private:
// Ordinary member functions (NOT slots): signatures use Node*, which must
// not be exposed to MOC. Invoked from lambdas only.
void group_added_node(Node *node, Node *group);
// Ordinary member functions (NOT slots): invoked from lambdas only.
void group_added_node(OakEngineNode *node, OakEngineNode *group);
void group_removed_node(Node *node, Node *group);
void group_removed_node(OakEngineNode *node, OakEngineNode *group);
};
}
+1 -1
View File
@@ -36,7 +36,7 @@ namespace olive
#define super QGraphicsPathItem
NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input,
NodeViewEdge::NodeViewEdge(oak::Node output, const oak::Input &input,
NodeViewItem *from_item, NodeViewItem *to_item,
QGraphicsItem *parent)
: super(parent)
+7 -6
View File
@@ -26,7 +26,7 @@
#include <QPalette>
#include "nodeviewcommon.h"
#include "node/node.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -40,19 +40,20 @@ class NodeViewItem;
*/
class NodeViewEdge : public QGraphicsPathItem {
public:
NodeViewEdge(Node *output, const NodeInput &input, NodeViewItem *from_item,
NodeViewEdge(oak::Node output, const oak::Input &input,
NodeViewItem *from_item,
NodeViewItem *to_item, QGraphicsItem *parent = nullptr);
NodeViewEdge(QGraphicsItem *parent = nullptr);
virtual ~NodeViewEdge() override;
Node *output() const
oak::Node output() const
{
return output_;
}
const NodeInput &input() const
const oak::Input &input() const
{
return input_;
}
@@ -122,9 +123,9 @@ private:
void update_curve();
Node *output_;
oak::Node output_;
NodeInput input_;
oak::Input input_;
int element_;
+71 -58
View File
@@ -32,19 +32,30 @@
#include "oakutil/qtutils.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/value.h"
#include "pluginSupport/oliveplugininstance.h"
#include "common/nodevaluehandle.h"
#include "nodeview.h"
#include "nodeviewscene.h"
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
#include "ui/icons/icons.h"
#include "window/mainwindow/mainwindow.h"
namespace olive
{
NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
Node *context, QGraphicsItem *parent)
static QVector<QString> node_input_list(oak::Node n)
{
const int count = n.input_count();
QVector<QString> result;
result.reserve(count);
for (int i = 0; i < count; i++) {
result.append(n.input_id(i));
}
return result;
}
NodeViewItem::NodeViewItem(oak::Node node, const QString &input,
int element, oak::Node context,
QGraphicsItem *parent)
: QGraphicsRectItem(parent)
, node_(node)
, input_(input)
@@ -71,9 +82,9 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
output_connector_ = new NodeViewItemConnector(true, this);
bridge_ = new EngineEventBridge(this);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_LABEL_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_COLOR_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_LABEL_CHANGED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_COLOR_CHANGED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED);
connect(bridge_, &EngineEventBridge::node_label_changed, this,
&NodeViewItem::node_appearance_changed);
connect(bridge_, &EngineEventBridge::node_color_changed, this,
@@ -82,8 +93,8 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
&NodeViewItem::node_appearance_changed);
if (is_output_item()) {
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_INPUT_ADDED);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_INPUT_REMOVED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_ADDED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_REMOVED);
connect(bridge_, &EngineEventBridge::node_input_added, this,
&NodeViewItem::repopulate_inputs);
connect(bridge_, &EngineEventBridge::node_input_removed, this,
@@ -95,13 +106,17 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
if (context_) {
set_node_position(context_->get_node_position_data_in_context(node_));
if (!context_.is_null()) {
QPointF ctx_pos;
bool ctx_expanded = false;
context_.context_position_of(node_, &ctx_pos, &ctx_expanded);
set_node_position(ctx_pos);
set_expanded(ctx_expanded);
}
} else {
output_connector_->setVisible(false);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
bridge_->subscribe(node_.handle(), OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
[this](OakEngineNode *, const QString &input, int, int) {
input_array_size_changed(input);
@@ -117,9 +132,9 @@ NodeViewItem::~NodeViewItem()
Q_ASSERT(edges_.isEmpty());
}
Node::Position NodeViewItem::get_node_position_data() const
NodeViewItemPosition NodeViewItem::get_node_position_data() const
{
return Node::Position(get_node_position(), is_expanded());
return NodeViewItemPosition{get_node_position(), is_expanded()};
}
QPointF NodeViewItem::get_node_position() const
@@ -134,7 +149,7 @@ void NodeViewItem::set_node_position(const QPointF &pos)
update_node_position();
}
void NodeViewItem::set_node_position(const Node::Position &pos)
void NodeViewItem::set_node_position(const NodeViewItemPosition &pos)
{
set_node_position(pos.position);
set_expanded(pos.expanded);
@@ -280,8 +295,8 @@ void NodeViewItem::set_expanded(bool e, bool hide_titlebar)
expanded_ = e;
if (context_) {
context_->set_node_expanded_in_context(node_, e);
if (!context_.is_null()) {
context_.set_context_expanded_of(node_, e);
}
if (is_output_item()) {
@@ -290,11 +305,11 @@ void NodeViewItem::set_expanded(bool e, bool hide_titlebar)
}
if (expanded_) {
node_->retranslate();
node_.retranslate();
if (is_output_item()) {
// Create items for each input of the node
foreach (const QString &input, node_->inputs()) {
foreach (const QString &input, node_input_list(node_)) {
if (is_input_valid(input)) {
NodeViewItem *item =
new NodeViewItem(node_, input, -1, context_, this);
@@ -310,7 +325,7 @@ void NodeViewItem::set_expanded(bool e, bool hide_titlebar)
}
} else {
// Create items for each element of the input array
int arr_sz = node_->input_array_size(input_);
int arr_sz = oak::Input(node_, input_).array_size();
children_.resize(arr_sz);
for (int i = 0; i < arr_sz; i++) {
NodeViewItem *item =
@@ -368,8 +383,7 @@ void NodeViewItem::paint(QPainter *painter,
if (is_output_item()) {
// Set output item colors
painter->setPen(Qt::black);
painter->setBrush(
node_->brush(single_unit_rect.top(), single_unit_rect.bottom()));
painter->setBrush(node_.brush(single_unit_rect.top(), single_unit_rect.bottom()));
} else {
// Set input item colors
painter->setPen(Qt::NoPen);
@@ -394,17 +408,16 @@ void NodeViewItem::paint(QPainter *painter,
if (label_as_output_) {
node_name = QCoreApplication::translate("NodeViewItem", "Output");
} else {
node_label = node_->get_label();
node_name = node_->short_name();
node_label = node_.get_label();
node_name = node_.short_name();
}
} else {
if (element_ == -1) {
node_name = node_->get_input_name(input_);
node_name = oak::Input(node_, input_).name();
} else {
node_name = QString::number(
element_ +
node_->get_input_property(input_, QStringLiteral("arraystart"))
.toInt());
int64_t arraystart = 0;
oak::Input(node_, input_).property_int("arraystart", &arraystart);
node_name = QString::number(element_ + static_cast<int>(arraystart));
}
}
@@ -413,7 +426,8 @@ void NodeViewItem::paint(QPainter *painter,
if (is_output_item()) {
// Determine the text color (automatically calculate from node background color)
painter->setPen(ColorCoding::get_ui_selector_color(node_->color()));
painter->setPen(AppColorCoding::get_ui_selector_color(
AppColorCoding::get_color(node_.effective_color_label())));
} else {
// Just use text item
painter->setPen(app_pal.text().color());
@@ -444,11 +458,7 @@ void NodeViewItem::paint(QPainter *painter,
}
if (is_output_item()) {
auto *instance = node_->getPluginInstance();
auto *olive_instance =
dynamic_cast<plugin::OlivePluginInstance *>(instance);
int message_count =
olive_instance ? olive_instance->persistent_message_count() : 0;
int message_count = node_.plugin_message_count();
if (message_count > 0) {
QString badge_text = QString::number(message_count);
@@ -537,15 +547,17 @@ void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change,
const QVariant &value)
{
if (node_) {
if (change == ItemPositionHasChanged) {
readjust_all_edges();
if (node_.is_null()) {
return QGraphicsItem::itemChange(change, value);
}
update_context_rect();
} else if (change == ItemSelectedHasChanged) {
if (value.toBool()) {
qDebug() << "Selected node:" << node_;
}
if (change == ItemPositionHasChanged) {
readjust_all_edges();
update_context_rect();
} else if (change == ItemSelectedHasChanged) {
if (value.toBool()) {
qDebug() << "Selected node:" << node_.handle();
}
}
@@ -761,14 +773,14 @@ void NodeViewItem::update_output_connector_position()
bool NodeViewItem::is_input_valid(const QString &input)
{
if (!node_->is_input_connectable(input) || node_->is_input_hidden(input)) {
oak::Input in(node_, input);
if (!in.is_connectable() || in.is_hidden()) {
return false;
}
// For OFX plugin nodes, only show texture inputs in the node graph
// to avoid excessively tall nodes with dozens of scalar parameters.
// Scalar parameters are still visible in the parameter panel.
if (node_->getPluginInstance() != nullptr &&
node_->get_input_data_type(input) != NodeValue::k_texture) {
if (node_.has_plugin() && in.data_type() != NodeValueType::k_texture) {
return false;
}
return true;
@@ -789,8 +801,8 @@ bool NodeViewItem::can_be_expanded() const
if (is_output_item()) {
return has_connectable_inputs_;
} else {
return node_->get_input_flags(input_) & k_input_flag_array &&
element_ == -1 && !node_->is_input_connected(input_);
return oak::Input(node_, input_).flags() & k_input_flag_array &&
element_ == -1 && !oak::Input(node_, input_).is_connected();
}
}
@@ -845,7 +857,7 @@ void NodeViewItem::repopulate_inputs()
if (is_output_item()) {
has_connectable_inputs_ = false;
foreach (const QString &input, node_->inputs()) {
foreach (const QString &input, node_input_list(node_)) {
if (is_input_valid(input)) {
has_connectable_inputs_ = true;
break;
@@ -881,19 +893,20 @@ void NodeViewItem::set_highlighted(bool e)
update();
}
NodeViewItem *NodeViewItem::get_item_for_input(NodeInput input)
NodeViewItem *NodeViewItem::get_item_for_input(oak::Input input)
{
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(node_))) {
if (node_.is_group()) {
if (input.node() != node_) {
// Translate input to group input
// WRAPPER-GAP: oakengine_group_get_id_of_passthrough (group passthrough lookup has no wrapper)
char id[256];
if (oakengine_group_get_id_of_passthrough(
reinterpret_cast<OakEngineNode *>(node_),
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(),
node_.handle(),
input.node_handle(),
input.input_id().toUtf8().constData(), input.element(),
id, sizeof(id)) > 0) {
input.set_node(node_);
input.set_input(QString::fromUtf8(id));
input.set_node_handle(node_.handle());
input.set_input_id(QString::fromUtf8(id));
}
}
}
@@ -902,7 +915,7 @@ NodeViewItem *NodeViewItem::get_item_for_input(NodeInput input)
if (input_.isEmpty()) {
// Look for the input in our children
foreach (NodeViewItem *i, children_) {
if (i->input_ == input.input()) {
if (i->input_ == input.input_id()) {
return i->get_item_for_input(input);
}
}
+22 -13
View File
@@ -27,7 +27,7 @@
#include <QLinearGradient>
#include <QWidget>
#include "node/node.h"
#include "oakutil/oaknode.h"
#include "nodeviewcommon.h"
#include "nodeviewitemconnector.h"
#include "engineeventbridge.h"
@@ -38,6 +38,14 @@ namespace olive
class NodeViewItem;
class NodeViewEdge;
/**
* @brief Local UI-only position aggregate (replaces engine Node::Position).
*/
struct NodeViewItemPosition {
QPointF position;
bool expanded = false;
};
/**
* @brief A visual widget representation of a Node object to be used in a NodeView
*
@@ -48,36 +56,37 @@ class NodeViewEdge;
class NodeViewItem : public QObject, public QGraphicsRectItem {
Q_OBJECT
public:
NodeViewItem(Node *node, const QString &input, int element, Node *context,
QGraphicsItem *parent = nullptr);
NodeViewItem(Node *node, Node *context, QGraphicsItem *parent = nullptr)
NodeViewItem(oak::Node node, const QString &input, int element,
oak::Node context, QGraphicsItem *parent = nullptr);
NodeViewItem(oak::Node node, oak::Node context,
QGraphicsItem *parent = nullptr)
: NodeViewItem(node, QString(), -1, context, parent)
{
}
virtual ~NodeViewItem() override;
Node::Position get_node_position_data() const;
NodeViewItemPosition get_node_position_data() const;
QPointF get_node_position() const;
void set_node_position(const QPointF &pos);
void set_node_position(const Node::Position &pos);
void set_node_position(const NodeViewItemPosition &pos);
QVector<NodeViewEdge *> get_all_edges_recursively() const;
/**
* @brief Get currently attached node
*/
Node *get_node() const
oak::Node get_node() const
{
return node_;
}
NodeInput get_input() const
oak::Input get_input() const
{
return NodeInput(node_, input_, element_);
return oak::Input(node_, input_, element_);
}
Node *get_context() const
oak::Node get_context() const
{
return context_;
}
@@ -145,7 +154,7 @@ public:
void set_highlighted(bool e);
NodeViewItem *get_item_for_input(NodeInput input);
NodeViewItem *get_item_for_input(oak::Input input);
bool is_output_item() const
{
@@ -197,11 +206,11 @@ private:
/**
* @brief Reference to attached Node
*/
Node *node_;
oak::Node node_;
QString input_;
int element_;
Node *context_;
oak::Node context_;
/**
* @brief Cached list of node inputs
+2 -3
View File
@@ -22,7 +22,6 @@
#include "nodeviewscene.h"
#include "core.h"
#include "node/project/sequence/sequence.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
@@ -70,7 +69,7 @@ QVector<NodeViewItem *> NodeViewScene::get_selected_items() const
return items;
}
NodeViewContext *NodeViewScene::add_context(Node *node)
NodeViewContext *NodeViewScene::add_context(oak::Node node)
{
NodeViewContext *context_item = context_map_.value(node);
@@ -96,7 +95,7 @@ NodeViewContext *NodeViewScene::add_context(Node *node)
return context_item;
}
void NodeViewScene::remove_context(Node *node)
void NodeViewScene::remove_context(oak::Node node)
{
delete context_map_.take(node);
}
+5 -11
View File
@@ -25,11 +25,9 @@
#include <QGraphicsScene>
#include <QTimer>
#include "node/project.h"
#include "nodeviewcontext.h"
#include "nodeviewedge.h"
#include "nodeviewitem.h"
#include "undo/undostack.h"
namespace olive
{
@@ -44,7 +42,7 @@ public:
QVector<NodeViewItem *> get_selected_items() const;
const QHash<Node *, NodeViewContext *> &context_map() const
const QHash<oak::Node, NodeViewContext *> &context_map() const
{
return context_map_;
}
@@ -64,11 +62,9 @@ public:
}
public:
// Not slots: signatures use the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). They are called directly, never used as connect() targets.
NodeViewContext *add_context(Node *node);
void remove_context(Node *node);
// Not slots: they are called directly, never used as connect() targets.
NodeViewContext *add_context(oak::Node node);
void remove_context(oak::Node node);
public slots:
/**
@@ -77,9 +73,7 @@ public slots:
void set_edges_are_curved(bool curved);
private:
QHash<Node *, NodeViewContext *> context_map_;
Project *graph_;
QHash<oak::Node, NodeViewContext *> context_map_;
NodeViewCommon::FlowDirection direction_;
+1 -1
View File
@@ -40,7 +40,7 @@ public:
return node_view_;
}
void set_contexts(const QVector<Node *> &nodes)
void set_contexts(const QVector<oak::Node> &nodes)
{
node_view_->set_contexts(nodes);
toolbar_->setEnabled(!nodes.isEmpty());
+145 -147
View File
@@ -53,20 +53,19 @@ namespace olive
namespace
{
QVector<Footage *> get_selected_proxy_footage(const QVector<Node *> &items)
QVector<oak::Node> get_selected_proxy_footage(const QVector<oak::Node> &items)
{
QVector<Footage *> footage;
for (Node *node : items) {
Footage *candidate = dynamic_cast<Footage *>(node);
QVector<oak::Node> footage;
for (const oak::Node &node : items) {
oak_video_params _vp;
if (!candidate ||
if (!node.is_footage() ||
oakengine_viewer_get_first_enabled_video_stream(
reinterpret_cast<OakEngineNode *>(candidate), &_vp) < 0 ||
node.handle(), &_vp) < 0 ||
!oakengine_video_params_is_valid(&_vp) ||
footage.contains(candidate)) {
footage.contains(node)) {
continue;
}
footage.append(candidate);
footage.append(node);
}
return footage;
}
@@ -161,7 +160,7 @@ void ProjectExplorer::set_view_type(ProjectToolbar::ViewType type)
void ProjectExplorer::edit(OakEngineNode *item)
{
current_view()->edit(
sort_model_.mapFromSource(model_.create_index_from_item(reinterpret_cast<Node *>(item))));
sort_model_.mapFromSource(model_.create_index_from_item(oak::Node(item))));
}
void ProjectExplorer::add_view(QAbstractItemView *view)
@@ -190,18 +189,18 @@ void ProjectExplorer::browse_to_folder(const QModelIndex &index)
nav_bar_->set_dir_up_enabled(index.isValid());
}
int ProjectExplorer::confirm_item_deletion(Node *item)
int ProjectExplorer::confirm_item_deletion(oak::Node item)
{
QMessageBox msgbox(this);
msgbox.setWindowTitle(tr("Confirm Item Deletion"));
msgbox.setIcon(QMessageBox::Warning);
QStringList connected_nodes_names;
foreach (const Node::OutputConnection &connected,
item->output_connections()) {
if (!dynamic_cast<Folder *>(connected.second.node())) {
for (int i = 0; i < item.output_connection_count(); i++) {
oak::Node connected_node = item.output_connection_node(i);
if (!connected_node.is_folder()) {
connected_nodes_names.append(
get_human_readable_node_name(connected.second.node()));
get_human_readable_node_name(connected_node));
}
}
@@ -222,21 +221,20 @@ int ProjectExplorer::confirm_item_deletion(Node *item)
return msgbox.exec();
}
bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
bool ProjectExplorer::delete_items_internal(const QVector<oak::Node> &selected,
bool &check_if_item_is_in_use,
void *command)
{
for (int i = 0; i < selected.size(); i++) {
// Delete sequences first
Node *node = selected.at(i);
oak::Node node = selected.at(i);
bool can_delete_item = true;
if (check_if_item_is_in_use) {
foreach (const Node::OutputConnection &oc,
node->output_connections()) {
Folder *folder_test = dynamic_cast<Folder *>(oc.second.node());
if (!folder_test) {
for (int j = 0; j < node.output_connection_count(); j++) {
oak::Node oc_node = node.output_connection_node(j);
if (!oc_node.is_folder()) {
// This sequence outputs to SOMETHING, confirm the user if they want to delete this
int r = confirm_item_deletion(node);
@@ -255,30 +253,31 @@ bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
}
if (can_delete_item) {
Sequence *sequence = dynamic_cast<Sequence *>(node);
if (sequence &&
Core::instance()->main_window()->is_sequence_open(sequence)) {
oakengine_undo_command_multi_add_child(command, make_close_sequence_command(sequence));
if (node.is_sequence() &&
Core::instance()->main_window()->is_sequence_open(node.handle())) {
oakengine_undo_command_multi_add_child(command, make_close_sequence_command(node.handle()));
}
if (node->folder()) {
oak::Node parent_folder = node.folder();
if (parent_folder) {
oakengine_undo_command_multi_add_child(
command,
oakengine_folder_remove_element_command(
reinterpret_cast<OakEngineNode *>(node->folder()),
reinterpret_cast<OakEngineNode *>(node)));
parent_folder.handle(),
node.handle()));
}
void *remove_cmd = oakengine_undo_command_create_multi();
oakengine_undo_command_multi_add_child(
remove_cmd,
oakengine_node_remove_and_disconnect_command(
reinterpret_cast<void *>(node)));
for (Node *dep : node->get_exclusive_dependencies()) {
node.handle()));
for (int d = 0; d < node.exclusive_dependency_count(); d++) {
oak::Node dep = node.exclusive_dependency_at(d);
oakengine_undo_command_multi_add_child(
remove_cmd,
oakengine_node_remove_and_disconnect_command(
reinterpret_cast<void *>(dep)));
dep.handle()));
}
oakengine_undo_command_multi_add_child(command, remove_cmd);
}
@@ -287,12 +286,12 @@ bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
return true;
}
QString ProjectExplorer::get_human_readable_node_name(Node *node)
QString ProjectExplorer::get_human_readable_node_name(const oak::Node &node)
{
if (node->get_label().isEmpty()) {
return node->name();
if (node.get_label().isEmpty()) {
return node.name();
} else {
return tr("%1 (%2)").arg(node->get_label(), node->name());
return tr("%1 (%2)").arg(node.get_label(), node.name());
}
}
@@ -300,11 +299,11 @@ void ProjectExplorer::update_nav_bar_text()
{
QString absolute;
Folder *f = static_cast<Folder *>(
sort_model_.mapToSource(list_view_->rootIndex()).internalPointer());
while (f && f != project()->root()) {
absolute.prepend(QStringLiteral("%1 / ").arg(f->get_label()));
f = f->folder();
oak::Node f(static_cast<OakEngineNode *>(
sort_model_.mapToSource(list_view_->rootIndex()).internalPointer()));
while (f && f != project().root()) {
absolute.prepend(QStringLiteral("%1 / ").arg(f.get_label()));
f = f.folder();
}
absolute.prepend(QStringLiteral("/ "));
@@ -325,18 +324,18 @@ void ProjectExplorer::view_empty_area_double_clicked_slot()
void ProjectExplorer::item_double_clicked_slot(const QModelIndex &index)
{
// Retrieve source item from index
Node *i =
static_cast<Node *>(sort_model_.mapToSource(index).internalPointer());
oak::Node i(
static_cast<OakEngineNode *>(sort_model_.mapToSource(index).internalPointer()));
// If the item is a folder, browse to it
if (dynamic_cast<Folder *>(i) &&
if (i.is_folder() &&
(view_type() == ProjectToolbar::list_view ||
view_type() == ProjectToolbar::icon_view)) {
browse_to_folder(index);
}
// Emit a signal
emit double_clicked_item(reinterpret_cast<OakEngineNode *>(i));
emit double_clicked_item(i.handle());
}
void ProjectExplorer::size_changed_slot(int s)
@@ -392,9 +391,9 @@ void ProjectExplorer::show_context_menu()
} else {
// Actions to add when only one item is selected
if (context_menu_items_.size() == 1) {
Node *context_menu_item = context_menu_items_.first();
oak::Node context_menu_item = context_menu_items_.first();
if (dynamic_cast<Folder *>(context_menu_item)) {
if (context_menu_item.is_folder()) {
QAction *open_in_new_tab =
menu.addAction(tr("Open in New Tab"));
connect(open_in_new_tab, &QAction::triggered, this,
@@ -405,7 +404,7 @@ void ProjectExplorer::show_context_menu()
connect(open_in_new_window, &QAction::triggered, this,
&ProjectExplorer::open_context_menu_item_in_new_window);
} else if (dynamic_cast<Footage *>(context_menu_item)) {
} else if (context_menu_item.is_footage()) {
QString reveal_text;
#if defined(Q_OS_WINDOWS)
@@ -432,28 +431,28 @@ void ProjectExplorer::show_context_menu()
bool all_items_have_video_streams = true;
bool all_items_are_footage_or_sequence = true;
foreach (Node *i, context_menu_items_) {
Footage *footage_cast_test = dynamic_cast<Footage *>(i);
Sequence *sequence_cast_test = dynamic_cast<Sequence *>(i);
foreach (const oak::Node &i, context_menu_items_) {
bool is_footage = i.is_footage();
bool is_sequence = i.is_sequence();
if (footage_cast_test &&
if (is_footage &&
!oakengine_viewer_has_enabled_streams(
reinterpret_cast<OakEngineNode *>(footage_cast_test),
i.handle(),
OAKENGINE_TRACK_TYPE_VIDEO)) {
all_items_have_video_streams = false;
}
if (!footage_cast_test) {
if (!is_footage) {
all_items_are_footage = false;
}
if (!footage_cast_test && !sequence_cast_test) {
if (!is_footage && !is_sequence) {
all_items_are_footage_or_sequence = false;
}
}
if (all_items_are_footage && all_items_have_video_streams) {
const QVector<Footage *> proxy_footage =
const QVector<oak::Node> proxy_footage =
get_selected_proxy_footage(context_menu_items_);
Menu *proxy_menu = new Menu(tr("Proxy"), &menu);
@@ -471,8 +470,9 @@ void ProjectExplorer::show_context_menu()
use_proxy->setChecked(
!proxy_footage.isEmpty() &&
std::all_of(proxy_footage.cbegin(), proxy_footage.cend(),
[](const Footage *footage) {
return footage->proxy_enabled();
[](const oak::Node &node) {
oak::Footage f = node.as_footage();
return f && f.proxy_enabled();
}));
connect(use_proxy, &QAction::triggered, this,
&ProjectExplorer::set_selected_footage_proxy_enabled);
@@ -480,8 +480,9 @@ void ProjectExplorer::show_context_menu()
QAction *reveal_proxy = proxy_menu->addAction(tr("Reveal Proxy"));
reveal_proxy->setEnabled(
std::any_of(proxy_footage.cbegin(), proxy_footage.cend(),
[](const Footage *footage) {
return !footage->proxy_path().isEmpty();
[](const oak::Node &node) {
oak::Footage f = node.as_footage();
return f && !f.proxy_path().isEmpty();
}));
connect(reveal_proxy, &QAction::triggered, this,
&ProjectExplorer::reveal_proxy_for_selected_footage);
@@ -489,8 +490,9 @@ void ProjectExplorer::show_context_menu()
QAction *delete_proxy = proxy_menu->addAction(tr("Delete Proxy"));
delete_proxy->setEnabled(
std::any_of(proxy_footage.cbegin(), proxy_footage.cend(),
[](const Footage *footage) {
return !footage->proxy_path().isEmpty();
[](const oak::Node &node) {
oak::Footage f = node.as_footage();
return f && !f.proxy_path().isEmpty();
}));
connect(delete_proxy, &QAction::triggered, this,
&ProjectExplorer::delete_proxies_for_selected_footage);
@@ -529,19 +531,23 @@ void ProjectExplorer::show_context_menu()
void ProjectExplorer::show_item_properties_dialog()
{
Node *sel = context_menu_items_.first();
oak::Node sel = context_menu_items_.first();
// FIXME: Support for multiple items
if (dynamic_cast<Footage *>(sel)) {
FootagePropertiesDialog fpd(this, static_cast<Footage *>(sel));
if (sel.is_footage()) {
FootagePropertiesDialog fpd(this, sel.handle());
fpd.exec();
} else if (dynamic_cast<Folder *>(sel)) {
Core::instance()->label_nodes(
reinterpret_cast<const QVector<OakEngineNode *> &>(context_menu_items_));
} else if (sel.is_folder()) {
QVector<OakEngineNode *> handles;
handles.reserve(context_menu_items_.size());
for (const oak::Node &n : context_menu_items_) {
handles.append(n.handle());
}
Core::instance()->label_nodes(handles);
} else if (dynamic_cast<Sequence *>(sel)) {
SequenceDialog sd(static_cast<Sequence *>(sel),
} else if (sel.is_sequence()) {
SequenceDialog sd(sel.handle(),
SequenceDialog::k_existing, this);
sd.exec();
}
@@ -549,12 +555,12 @@ void ProjectExplorer::show_item_properties_dialog()
void ProjectExplorer::reveal_selected_footage()
{
Footage *footage = static_cast<Footage *>(context_menu_items_.first());
oak::Footage footage = context_menu_items_.first().as_footage();
#if defined(Q_OS_WINDOWS)
// Explorer
QStringList args;
args << "/select," << QDir::toNativeSeparators(footage->filename());
args << "/select," << QDir::toNativeSeparators(footage.filename());
QProcess::startDetached("explorer", args);
#elif defined(Q_OS_MAC)
QStringList args;
@@ -563,19 +569,20 @@ void ProjectExplorer::reveal_selected_footage()
args << "-e";
args << "activate";
args << "-e";
args << "select POSIX file \"" + footage->filename() + "\"";
args << "select POSIX file \"" + footage.filename() + "\"";
args << "-e";
args << "end tell";
QProcess::startDetached("osascript", args);
#else
QDesktopServices::openUrl(QUrl::fromLocalFile(
QFileInfo(footage->filename()).dir().absolutePath()));
QFileInfo(footage.filename()).dir().absolutePath()));
#endif
}
void ProjectExplorer::replace_selected_footage()
{
Footage *footage = static_cast<Footage *>(context_menu_items_.first());
oak::Node node = context_menu_items_.first();
oak::Footage footage = node.as_footage();
QString file =
QFileDialog::getOpenFileName(this, tr("Replace Footage"), QString(),
@@ -591,28 +598,20 @@ void ProjectExplorer::replace_selected_footage()
// Change the filename through the facade relink (reprobes the new
// file and resets proxy/stream state); the label policy stays here.
OakEngineFootage *facade_handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(footage));
const int relink_rc = oakengine_footage_relink(
facade_handle, file.toUtf8().constData());
oakengine_footage_free(facade_handle);
const int relink_rc = footage.relink(file);
if (relink_rc != OAKENGINE_OK) {
char err[512];
err[0] = '\0';
oakengine_footage_last_error(err, sizeof(err));
const QString err = oak::Footage::last_error();
QMessageBox::warning(
this, tr("Cannot replace footage"),
err[0] ? QString::fromUtf8(err) :
tr("The file could not be used as media."));
!err.isEmpty() ? err :
tr("The file could not be used as media."));
return;
}
if (QFileInfo(footage->filename()).fileName() ==
footage->get_label()) {
if (QFileInfo(footage.filename()).fileName() ==
node.get_label()) {
// Footage label == filename, change label too
oakengine_node_set_label(
reinterpret_cast<OakEngineNode *>(footage),
QFileInfo(file).fileName().toUtf8().constData());
node.set_label(QFileInfo(file).fileName());
}
}
}
@@ -620,13 +619,13 @@ void ProjectExplorer::replace_selected_footage()
void ProjectExplorer::open_context_menu_item_in_new_tab()
{
Core::instance()->main_window()->open_folder(
static_cast<Folder *>(context_menu_items_.first()), false);
context_menu_items_.first().handle(), false);
}
void ProjectExplorer::open_context_menu_item_in_new_window()
{
Core::instance()->main_window()->open_folder(
static_cast<Folder *>(context_menu_items_.first()), true);
context_menu_items_.first().handle(), true);
}
void ProjectExplorer::generate_proxies_for_selected_footage()
@@ -636,69 +635,65 @@ void ProjectExplorer::generate_proxies_for_selected_footage()
return;
}
const QVector<Footage *> footage =
const QVector<oak::Node> footage =
get_selected_proxy_footage(context_menu_items_);
qDebug()
<< "GenerateProxiesForSelectedFootage: starting proxy generation for"
<< footage.size() << "footage item(s)";
for (Footage *item : footage) {
for (const oak::Node &item : footage) {
oak_video_params _vp;
if (oakengine_viewer_get_first_enabled_video_stream(
reinterpret_cast<OakEngineNode *>(item), &_vp) < 0 ||
item.handle(), &_vp) < 0 ||
!oakengine_video_params_is_valid(&_vp)) {
oak::Footage f = item.as_footage();
qWarning()
<< "GenerateProxiesForSelectedFootage: skipping item with no valid video stream"
<< item->filename();
<< (f ? f.filename() : QString());
continue;
}
// Queue one facade-backed task per footage item (same queueing
// semantics as the old per-footage proxy tasks).
OakEngineTask *proxy_task = oakengine_task_create_proxy(
reinterpret_cast<OakEngineNode *>(item));
item.handle());
oakengine_task_manager_add(proxy_task);
}
}
void ProjectExplorer::set_selected_footage_proxy_enabled(bool enabled)
{
const QVector<Footage *> footage =
const QVector<oak::Node> footage =
get_selected_proxy_footage(context_menu_items_);
qDebug() << "ProjectExplorer::SetSelectedFootageProxyEnabled:" << enabled
<< "footage count=" << footage.size();
for (Footage *item : footage) {
if (item->proxy_path().isEmpty()) {
for (const oak::Node &item : footage) {
oak::Footage f = item.as_footage();
if (!f || f.proxy_path().isEmpty()) {
qDebug()
<< " skipping item with empty proxy path" << item->filename();
<< " skipping item with empty proxy path" << (f ? f.filename() : QString());
continue;
}
OakEngineFootage *handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(item));
oakengine_footage_proxy_set_enabled(handle, enabled ? 1 : 0);
f.set_proxy_enabled(enabled);
// The facade call toggles the flag; cache invalidation for the UI
// stays here.
oakengine_footage_invalidate(handle);
oakengine_footage_free(handle);
f.invalidate();
}
}
void ProjectExplorer::reveal_proxy_for_selected_footage()
{
const QVector<Footage *> footage =
const QVector<oak::Node> footage =
get_selected_proxy_footage(context_menu_items_);
for (Footage *item : footage) {
char proxy_path[4096];
proxy_path[0] = '\0';
OakEngineFootage *handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(item));
oakengine_footage_proxy_get_path(handle, proxy_path,
sizeof(proxy_path));
oakengine_footage_free(handle);
if (proxy_path[0] == '\0') {
for (const oak::Node &item : footage) {
oak::Footage f = item.as_footage();
if (!f) {
continue;
}
const QString path = f.proxy_path();
if (path.isEmpty()) {
continue;
}
const QString path = QString::fromUtf8(proxy_path);
#if defined(Q_OS_WINDOWS)
QStringList args;
@@ -724,25 +719,29 @@ void ProjectExplorer::reveal_proxy_for_selected_footage()
void ProjectExplorer::delete_proxies_for_selected_footage()
{
const QVector<Footage *> footage =
const QVector<oak::Node> footage =
get_selected_proxy_footage(context_menu_items_);
for (Footage *item : footage) {
if (item->proxy_path().isEmpty()) {
for (const oak::Node &item : footage) {
oak::Footage f = item.as_footage();
if (!f || f.proxy_path().isEmpty()) {
continue;
}
// Facade delete: removes the file, clears the proxy state and
// invalidates the footage.
OakEngineFootage *handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(item));
oakengine_footage_proxy_delete(handle);
oakengine_footage_free(handle);
f.proxy_delete();
}
}
void ProjectExplorer::show_proxy_dialog_for_selected_footage()
{
ProxyDialog d(this, get_selected_proxy_footage(context_menu_items_));
QVector<OakEngineNode *> handles;
const QVector<oak::Node> footage = get_selected_proxy_footage(context_menu_items_);
handles.reserve(footage.size());
for (const oak::Node &n : footage) {
handles.append(n.handle());
}
ProxyDialog d(this, handles);
d.exec();
}
@@ -755,43 +754,42 @@ void ProjectExplorer::view_selection_changed()
QVector<OakEngineNode *> nodes;
foreach (const QModelIndex &index, selection) {
Node *sel = static_cast<Node *>(
auto handle = static_cast<OakEngineNode *>(
sort_model_.mapToSource(index).internalPointer());
auto handle = reinterpret_cast<OakEngineNode *>(sel);
if (!nodes.contains(handle)) {
nodes.append(handle);
}
}
if (nodes.isEmpty()) {
nodes.append(reinterpret_cast<OakEngineNode *>(get_root()));
nodes.append(get_root().handle());
}
emit selection_changed(nodes);
}
Project *ProjectExplorer::project() const
oak::Project ProjectExplorer::project() const
{
return model_.project();
}
void ProjectExplorer::set_project(Project *p)
void ProjectExplorer::set_project(oak::Project p)
{
model_.set_project(p);
}
Folder *ProjectExplorer::get_root() const
oak::Node ProjectExplorer::get_root() const
{
QModelIndex root_index = sort_model_.mapToSource(tree_view_->rootIndex());
if (!root_index.isValid()) {
return project()->root();
return project().root();
}
return static_cast<Folder *>(root_index.internalPointer());
return oak::Node(static_cast<OakEngineNode *>(root_index.internalPointer()));
}
void ProjectExplorer::set_root(Folder *item)
void ProjectExplorer::set_root(oak::Node item)
{
QModelIndex index =
sort_model_.mapFromSource(model_.create_index_from_item(item));
@@ -800,19 +798,19 @@ void ProjectExplorer::set_root(Folder *item)
tree_view_->setRootIndex(index);
}
QVector<Node *> ProjectExplorer::selected_items() const
QVector<oak::Node> ProjectExplorer::selected_items() const
{
// Determine which view is active and get its selected indexes
QModelIndexList index_list =
current_view()->selectionModel()->selectedRows();
// Convert indexes to item objects
QVector<Node *> selected_items;
QVector<oak::Node> selected_items;
for (int i = 0; i < index_list.size(); i++) {
QModelIndex index = sort_model_.mapToSource(index_list.at(i));
Node *item = static_cast<Node *>(index.internalPointer());
oak::Node item(static_cast<OakEngineNode *>(index.internalPointer()));
selected_items.append(item);
}
@@ -820,16 +818,16 @@ QVector<Node *> ProjectExplorer::selected_items() const
return selected_items;
}
Folder *ProjectExplorer::get_selected_folder() const
oak::Node ProjectExplorer::get_selected_folder() const
{
if (project() == nullptr) {
return nullptr;
return oak::Node();
}
Folder *folder = nullptr;
oak::Node folder;
// Get the selected items from the panel
QVector<Node *> selected_nodes = selected_items();
QVector<oak::Node> selected_nodes = selected_items();
// Heuristic for finding the selected folder:
//
@@ -839,27 +837,27 @@ Folder *ProjectExplorer::get_selected_folder() const
// - If more than one folder is found, we play it safe and import into the root folder
for (int i = 0; i < selected_nodes.size(); i++) {
Node *sel_item = selected_nodes.at(i);
oak::Node sel_item = selected_nodes.at(i);
// If this item is not a folder, presumably it's parent is
if (!dynamic_cast<Folder *>(sel_item)) {
sel_item = sel_item->folder();
if (!sel_item.is_folder()) {
sel_item = sel_item.folder();
}
if (folder == nullptr) {
// If the folder is nullptr, cache it as this folder
folder = static_cast<Folder *>(sel_item);
folder = sel_item;
} else if (folder != sel_item) {
// If not, we've already cached a folder so we check if it's the same
// If it isn't, we "play it safe" and use the root folder
folder = nullptr;
folder = oak::Node();
break;
}
}
// If we didn't pick up a folder from the heuristic above for whatever reason, use root
if (folder == nullptr) {
folder = project()->root();
folder = project().root();
}
return folder;
@@ -882,7 +880,7 @@ void ProjectExplorer::deselect_all()
void ProjectExplorer::delete_selected()
{
QVector<Node *> selected = selected_items();
QVector<oak::Node> selected = selected_items();
if (selected.isEmpty()) {
return;
@@ -900,7 +898,7 @@ void ProjectExplorer::delete_selected()
}
}
bool ProjectExplorer::select_item(Node *n, bool deselect_all_first)
bool ProjectExplorer::select_item(oak::Node n, bool deselect_all_first)
{
if (deselect_all_first) {
deselect_all();
+11 -19
View File
@@ -27,7 +27,6 @@
#include <QTimer>
#include <QTreeView>
#include "node/project.h"
#include "projectviewmodel.h"
#include "widget/projectexplorer/projectexplorericonview.h"
#include "widget/projectexplorer/projectexplorerlistview.h"
@@ -55,13 +54,13 @@ public:
const ProjectToolbar::ViewType &view_type() const;
Project *project() const;
void set_project(Project *p);
oak::Project project() const;
void set_project(oak::Project p);
Folder *get_root() const;
void set_root(Folder *item);
oak::Node get_root() const;
void set_root(oak::Node item);
QVector<Node *> selected_items() const;
QVector<oak::Node> selected_items() const;
/**
* @brief Use a heuristic to determine which (if any) folder is selected
@@ -75,7 +74,7 @@ public:
* A folder that's heuristically been determined as "selected", or the root directory if none, or nullptr if no
* project is open.
*/
Folder *get_selected_folder() const;
oak::Node get_selected_folder() const;
/**
* @brief Access the ViewModel model of the project
@@ -88,7 +87,7 @@ public:
void delete_selected();
bool select_item(Node *n, bool deselect_all_first = true);
bool select_item(oak::Node n, bool deselect_all_first = true);
public slots:
void set_view_type(ProjectToolbar::ViewType type);
@@ -112,13 +111,6 @@ signals:
void selection_changed(const QVector<OakEngineNode *> &selected);
private:
/**
* @brief Get all the blocks that solely rely on an input node
*
* Ignores blocks that depend on multiple inputs
*/
QList<Block *> get_footage_blocks(QList<Node *> nodes);
/**
* @brief Simple convenience function for adding a view to this stacked widget
*
@@ -141,13 +133,13 @@ private:
*/
void browse_to_folder(const QModelIndex &index);
int confirm_item_deletion(Node *item);
int confirm_item_deletion(oak::Node item);
bool delete_items_internal(const QVector<Node *> &selected,
bool delete_items_internal(const QVector<oak::Node> &selected,
bool &check_if_item_is_in_use,
void *command);
static QString get_human_readable_node_name(Node *node);
static QString get_human_readable_node_name(const oak::Node &node);
void update_nav_bar_text();
@@ -169,7 +161,7 @@ private:
QSortFilterProxyModel sort_model_;
ProjectViewModel model_;
QVector<Node *> context_menu_items_;
QVector<oak::Node> context_menu_items_;
private slots:
void view_empty_area_double_clicked_slot();
+92 -101
View File
@@ -28,9 +28,6 @@
#include "oakutil/qtutils.h"
#include "core.h"
#include "oakengine/project.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
namespace olive
{
@@ -47,9 +44,7 @@ void ProjectViewModel::connect_bridge_signals()
{
connect(bridge_, &EngineEventBridge::folder_begin_insert_item, this,
[this](OakEngineNode *folder, OakEngineNode *child, int index) {
this->folder_begin_insert_item(
reinterpret_cast<Folder *>(folder),
reinterpret_cast<Node *>(child), index);
this->folder_begin_insert_item(folder, child, index);
});
connect(bridge_, &EngineEventBridge::folder_end_insert_item, this,
[this](OakEngineNode *) {
@@ -57,9 +52,7 @@ void ProjectViewModel::connect_bridge_signals()
});
connect(bridge_, &EngineEventBridge::folder_begin_remove_item, this,
[this](OakEngineNode *folder, OakEngineNode *child, int index) {
this->folder_begin_remove_item(
reinterpret_cast<Folder *>(folder),
reinterpret_cast<Node *>(child), index);
this->folder_begin_remove_item(folder, child, index);
});
connect(bridge_, &EngineEventBridge::folder_end_remove_item, this,
[this](OakEngineNode *) {
@@ -69,17 +62,17 @@ void ProjectViewModel::connect_bridge_signals()
&ProjectViewModel::item_renamed);
}
Project *ProjectViewModel::project() const
oak::Project ProjectViewModel::project() const
{
return project_;
}
void ProjectViewModel::set_project(Project *p)
void ProjectViewModel::set_project(oak::Project p)
{
beginResetModel();
if (project_) {
disconnect_item(project_->root());
disconnect_item(project_.root());
// Recreate bridge to clear all folder subscriptions
delete bridge_;
bridge_ = new EngineEventBridge(this);
@@ -89,7 +82,7 @@ void ProjectViewModel::set_project(Project *p)
project_ = p;
if (project_) {
connect_item(project_->root());
connect_item(project_.root());
}
endResetModel();
@@ -104,22 +97,22 @@ QModelIndex ProjectViewModel::index(int row, int column,
}
// Get the parent object, we assume it's a folder since only folders can have children
Folder *item_parent = static_cast<Folder *>(get_item_object_from_index(parent));
oak::Node item_parent = get_item_object_from_index(parent);
// Return an index to this object
return createIndex(row, column, item_parent->item_child(row));
return createIndex(row, column, item_parent.item_child(row).handle());
}
QModelIndex ProjectViewModel::parent(const QModelIndex &child) const
{
// Get the Item object from the index
Node *item = get_item_object_from_index(child);
oak::Node item = get_item_object_from_index(child);
// Get Item's parent object
Folder *par = item->folder();
oak::Node par = item.folder();
// If the parent is the root, return an empty index
if (par == project_->root()) {
if (par == project_.root()) {
return QModelIndex();
}
@@ -130,7 +123,7 @@ QModelIndex ProjectViewModel::parent(const QModelIndex &child) const
Q_ASSERT(parent_index > -1);
// Return an index to the parent
return createIndex(parent_index, 0, par);
return createIndex(parent_index, 0, par.handle());
}
int ProjectViewModel::rowCount(const QModelIndex &parent) const
@@ -142,12 +135,11 @@ int ProjectViewModel::rowCount(const QModelIndex &parent) const
// If the index is the root, return the root child count
if (parent == QModelIndex()) {
return project_->root()->item_child_count();
return project_.root().item_child_count();
}
// Otherwise, the index must contain a valid pointer, so we just return its child count
return static_cast<Folder *>(get_item_object_from_index(parent))
->item_child_count();
return get_item_object_from_index(parent).item_child_count();
}
int ProjectViewModel::columnCount(const QModelIndex &parent) const
@@ -164,7 +156,7 @@ int ProjectViewModel::columnCount(const QModelIndex &parent) const
QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
{
Node *internal_item = get_item_object_from_index(index);
oak::Node internal_item = get_item_object_from_index(index);
ColumnType column_type = static_cast<ColumnType>(index.column());
@@ -175,17 +167,15 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
switch (column_type) {
case k_name:
return internal_item->get_label();
return internal_item.get_label();
case k_duration:
return internal_item->data(Node::duration);
return internal_item.data(1);
case k_rate:
return internal_item->data(Node::frequency_rate);
return internal_item.data(4);
case k_last_modified:
case k_created_time: {
qint64 using_time =
(column_type == k_last_modified) ?
internal_item->data(Node::modified_time).toLongLong() :
internal_item->data(Node::created_time).toLongLong();
const int data_role = (column_type == k_last_modified) ? 3 : 2;
qint64 using_time = internal_item.data(data_role).toLongLong();
if (using_time == 0) {
// 0 is the null value, return nothing
@@ -211,17 +201,17 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
} break;
case Qt::EditRole:
if (column_type == k_name) {
return internal_item->get_label();
return internal_item.get_label();
}
break;
case Qt::DecorationRole:
// If this is the first column, return the Item's icon
if (column_type == k_name) {
return icon::from_name(internal_item->data(Node::icon).toString());
return icon::from_name(internal_item.data(0).toString());
}
break;
case Qt::ToolTipRole:
return internal_item->data(Node::tooltip);
return internal_item.data(5);
}
return QVariant();
@@ -259,9 +249,7 @@ bool ProjectViewModel::hasChildren(const QModelIndex &parent) const
{
// If it's a folder, we always return TRUE in order to always show the "expand triangle" icon,
// even when there are no "physical" children
Node *item = get_item_object_from_index(parent);
return dynamic_cast<Folder *>(item);
return get_item_object_from_index(parent).is_folder();
}
bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
@@ -269,18 +257,17 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
{
// The name is editable
if (index.isValid() && index.column() == k_name && role == Qt::EditRole) {
Node *item = get_item_object_from_index(index);
oak::Node item = get_item_object_from_index(index);
QString new_name = value.toString();
if (!new_name.isEmpty()) {
void *nrc = oakengine_node_rename_command(
reinterpret_cast<OakEngineNode *>(item),
new_name.toUtf8().constData());
item.handle(), new_name.toUtf8().constData());
oakengine_undo_push(
nrc,
tr("Renamed Item \"%1\" to \"%2\"").arg(item->get_label(), new_name).toUtf8().constData());
tr("Renamed Item \"%1\" to \"%2\"").arg(item.get_label(), new_name).toUtf8().constData());
return true;
}
@@ -304,7 +291,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const
Qt::ItemFlags f = Qt::ItemIsDragEnabled | QAbstractItemModel::flags(index);
if (dynamic_cast<Folder *>(get_item_object_from_index(index))) {
if (get_item_object_from_index(index).is_folder()) {
f |= Qt::ItemIsDropEnabled;
}
@@ -345,17 +332,20 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
// Check if we've dragged this item before
if (!dragged_items.contains(index.internalPointer())) {
// If not, add it to the stream (and also keep track of it in the vector)
Node *item = static_cast<Node *>(index.internalPointer());
QVector<Track::Reference> streams;
oak::Node item(static_cast<OakEngineNode *>(index.internalPointer()));
if (ViewerOutput *footage =
dynamic_cast<ViewerOutput *>(item)) {
streams = footage->get_enabled_streams_as_references();
// Serialize the enabled streams (type/index pairs) for viewer items
const QVector<QPair<int, int>> streams =
item.is_viewer_output() ? item.enabled_streams()
: QVector<QPair<int, int>>();
stream << streams.size();
for (const QPair<int, int> &s : streams) {
stream << s.first << s.second;
}
stream << reinterpret_cast<quintptr>(item.handle());
stream << streams << reinterpret_cast<quintptr>(item);
dragged_items.append(item);
dragged_items.append(item.handle());
}
}
}
@@ -390,34 +380,38 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
QDataStream stream(&model_data, QIODevice::ReadOnly);
// Get the Item object that the items were dropped on
Folder *drop_location =
dynamic_cast<Folder *>(get_item_object_from_index(drop));
oak::Node drop_location = get_item_object_from_index(drop);
// If this is not a folder, we cannot drop these items here
if (!drop_location) {
if (!drop_location.is_folder()) {
return false;
}
// Variables to deserialize into
quintptr item_ptr;
QList<Track::Reference> streams;
// Loop through all data, collecting the items to move
QVector<OakEngineNode *> items_to_move;
while (!stream.atEnd()) {
stream >> streams >> item_ptr;
// Written as qsizetype (8 bytes) by mimeData(); must match
qint64 stream_count = 0;
stream >> stream_count;
for (qint64 si = 0; si < stream_count; si++) {
int st = 0, sidx = 0;
stream >> st >> sidx;
}
Node *item = reinterpret_cast<Node *>(item_ptr);
quintptr item_ptr;
stream >> item_ptr;
oak::Node item(reinterpret_cast<OakEngineNode *>(item_ptr));
// Check if Item is already the drop location or if its parent is the drop location, in which case this is a
// no-op
if (item != drop_location && item->folder() != drop_location &&
(!dynamic_cast<Folder *>(item) ||
!item_is_parent_of_child(static_cast<Folder *>(item),
drop_location))) {
items_to_move.append(reinterpret_cast<OakEngineNode *>(item));
if (item != drop_location &&
item.folder() != drop_location &&
(!item.is_folder() ||
!item_is_parent_of_child(item, drop_location))) {
items_to_move.append(item.handle());
}
}
@@ -426,7 +420,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
// item from its old folder, then adds it to the drop location)
oakengine_folder_move_children(
items_to_move.constData(), items_to_move.size(),
reinterpret_cast<OakEngineNode *>(drop_location),
drop_location.handle(),
tr("Move %1 Item(s)").arg(items_to_move.size()).toUtf8().constData());
}
@@ -450,11 +444,11 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
}
// Get folder dropped onto
Node *drop_item = get_item_object_from_index(drop);
oak::Node drop_item = get_item_object_from_index(drop);
// If we didn't drop onto an item, find the nearest parent folder (should eventually terminate at root either way)
if (!dynamic_cast<Folder *>(drop_item)) {
drop_item = drop_item->folder();
if (!drop_item.is_folder()) {
drop_item = drop_item.folder();
if (!drop_item) {
// Failed to find folder to place this in
@@ -463,7 +457,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
}
// Trigger an import
Core::instance()->import_files(urls, reinterpret_cast<OakEngineNode *>(drop_item));
Core::instance()->import_files(urls, drop_item.handle());
return true;
}
@@ -471,32 +465,32 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
return false;
}
int ProjectViewModel::index_of_child(Node *item) const
int ProjectViewModel::index_of_child(oak::Node item) const
{
// Find parent's index within its own parent
Folder *parent = item->folder();
oak::Node parent = item.folder();
if (parent) {
return parent->index_of_child(item);
return parent.index_of_child(item);
}
return -1;
}
Node *ProjectViewModel::get_item_object_from_index(const QModelIndex &index) const
oak::Node ProjectViewModel::get_item_object_from_index(const QModelIndex &index) const
{
if (index.isValid()) {
return static_cast<Node *>(index.internalPointer());
return oak::Node(static_cast<OakEngineNode *>(index.internalPointer()));
}
return project_ ? project_->root() : nullptr;
return project_ ? project_.root() : oak::Node();
}
bool ProjectViewModel::item_is_parent_of_child(Folder *parent, Node *child) const
bool ProjectViewModel::item_is_parent_of_child(oak::Node parent, oak::Node child) const
{
// Loop through parent hierarchy checking if `parent` is one of its parents
do {
child = child->folder();
child = child.folder();
if (parent == child) {
return true;
@@ -506,49 +500,48 @@ bool ProjectViewModel::item_is_parent_of_child(Folder *parent, Node *child) cons
return false;
}
void ProjectViewModel::connect_item(Node *n)
void ProjectViewModel::connect_item(oak::Node n)
{
label_changed_subs_[n] = bridge_->subscribe(
reinterpret_cast<void *>(n), OAKENGINE_EVENT_NODE_LABEL_CHANGED);
n.handle(), OAKENGINE_EVENT_NODE_LABEL_CHANGED);
Folder *f = dynamic_cast<Folder *>(n);
if (f) {
OakEngineNode *handle = reinterpret_cast<OakEngineNode *>(f);
bridge_->subscribe(handle, OAKENGINE_EVENT_FOLDER_BEGIN_INSERT_ITEM);
bridge_->subscribe(handle, OAKENGINE_EVENT_FOLDER_END_INSERT_ITEM);
bridge_->subscribe(handle, OAKENGINE_EVENT_FOLDER_BEGIN_REMOVE_ITEM);
bridge_->subscribe(handle, OAKENGINE_EVENT_FOLDER_END_REMOVE_ITEM);
if (n.is_folder()) {
bridge_->subscribe(n.handle(), OAKENGINE_EVENT_FOLDER_BEGIN_INSERT_ITEM);
bridge_->subscribe(n.handle(), OAKENGINE_EVENT_FOLDER_END_INSERT_ITEM);
bridge_->subscribe(n.handle(), OAKENGINE_EVENT_FOLDER_BEGIN_REMOVE_ITEM);
bridge_->subscribe(n.handle(), OAKENGINE_EVENT_FOLDER_END_REMOVE_ITEM);
foreach (Node *c, f->children()) {
connect_item(c);
const int count = n.item_child_count();
for (int i = 0; i < count; i++) {
connect_item(n.item_child(i));
}
}
}
void ProjectViewModel::disconnect_item(Node *n)
void ProjectViewModel::disconnect_item(oak::Node n)
{
int64_t sub = label_changed_subs_.take(n);
if (sub > 0) {
bridge_->unsubscribe(sub);
}
Folder *f = dynamic_cast<Folder *>(n);
if (f) {
if (n.is_folder()) {
// Bridge subscriptions are cleaned up by recreating the bridge in set_project
foreach (Node *c, f->children()) {
disconnect_item(c);
const int count = n.item_child_count();
for (int i = 0; i < count; i++) {
disconnect_item(n.item_child(i));
}
}
}
void ProjectViewModel::folder_begin_insert_item(Folder *folder, Node *n,
void ProjectViewModel::folder_begin_insert_item(oak::Node folder, oak::Node n,
int insert_index)
{
connect_item(n);
QModelIndex index;
if (folder != project_->root()) {
if (folder != project_.root()) {
index = create_index_from_item(folder);
}
@@ -560,14 +553,14 @@ void ProjectViewModel::folder_end_insert_item()
endInsertRows();
}
void ProjectViewModel::folder_begin_remove_item(Folder *folder, Node *n,
void ProjectViewModel::folder_begin_remove_item(oak::Node folder, oak::Node n,
int child_index)
{
disconnect_item(n);
QModelIndex index;
if (folder != project_->root()) {
if (folder != project_.root()) {
index = create_index_from_item(folder);
}
@@ -581,16 +574,14 @@ void ProjectViewModel::folder_end_remove_item()
void ProjectViewModel::item_renamed(OakEngineNode *source)
{
Node *item = reinterpret_cast<Node *>(source);
QModelIndex index = create_index_from_item(item);
QModelIndex index = create_index_from_item(oak::Node(source));
emit dataChanged(index, index, { Qt::DisplayRole, Qt::EditRole });
}
QModelIndex ProjectViewModel::create_index_from_item(Node *item, int column)
QModelIndex ProjectViewModel::create_index_from_item(oak::Node item, int column)
{
return createIndex(index_of_child(item), column, item);
return createIndex(index_of_child(item), column, item.handle());
}
}
+18 -16
View File
@@ -26,8 +26,7 @@
#include <QHash>
#include "engineeventbridge.h"
#include "node/block/block.h"
#include "node/project.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -39,6 +38,9 @@ namespace olive
* a ProjectViewModel), it may be better to make modifications (e.g. additions/removals/renames) through the
* ProjectViewModel so that the views can be efficiently and correctly updated. ProjectViewModel contains several
* "wrapper" functions for Project and Item functions that also signal any connected views to update accordingly.
*
* Engine access goes through the oak:: C++ wrapper layer (oakutil/oaknode.h),
* which re-wraps the engine's pure C ABI into object form.
*/
class ProjectViewModel : public QAbstractItemModel {
Q_OBJECT
@@ -78,9 +80,9 @@ public:
*
* @return
*
* Currently active project or nullptr if there is none
* Currently active project or a null handle if there is none
*/
Project *project() const;
oak::Project project() const;
/**
* @brief Set the project to adapt
@@ -89,9 +91,9 @@ public:
*
* @param p
*
* Project to adapt, can be set to nullptr to "close" the project (will show an empty model that cannot be modified)
* Project to adapt, can be set to null to "close" the project (will show an empty model that cannot be modified)
*/
void set_project(Project *p);
void set_project(oak::Project p);
/** Compulsory Qt QAbstractItemModel overrides */
virtual QModelIndex
@@ -125,7 +127,7 @@ public:
/**
* @brief Convenience function for creating QModelIndexes from an Item object
*/
QModelIndex create_index_from_item(Node *item, int column = 0);
QModelIndex create_index_from_item(oak::Node item, int column = 0);
private:
/**
@@ -138,25 +140,25 @@ private:
*
* Index of the specified item, or -1 if the item is root (in which case it has no parent).
*/
int index_of_child(Node *item) const;
int index_of_child(oak::Node item) const;
/**
* @brief Retrieves the Item object from a given index
*
* A convenience function for retrieving Item objects. If the index is not valid, this returns the root Item.
*/
Node *get_item_object_from_index(const QModelIndex &index) const;
oak::Node get_item_object_from_index(const QModelIndex &index) const;
/**
* @brief Check if an Item is a parent of a Child
*
* Checks entire "parent hierarchy" of `child` to see if `parent` is one of its parents.
*/
bool item_is_parent_of_child(Folder *parent, Node *child) const;
bool item_is_parent_of_child(oak::Node parent, oak::Node child) const;
void connect_item(Node *n);
void connect_item(oak::Node n);
void disconnect_item(Node *n);
void disconnect_item(oak::Node n);
/**
* @brief Wire the bridge's folder signals to our handlers.
@@ -166,19 +168,19 @@ private:
*/
void connect_bridge_signals();
void folder_begin_insert_item(Folder *folder, Node *n, int insert_index);
void folder_begin_insert_item(oak::Node folder, oak::Node n, int insert_index);
void folder_end_insert_item();
void folder_begin_remove_item(Folder *folder, Node *n, int child_index);
void folder_begin_remove_item(oak::Node folder, oak::Node n, int child_index);
void folder_end_remove_item();
Project *project_;
oak::Project project_;
EngineEventBridge *bridge_;
QHash<Node *, int64_t> label_changed_subs_;
QHash<oak::Node, int64_t> label_changed_subs_;
private slots:
void item_renamed(OakEngineNode *source);
@@ -26,7 +26,10 @@
#include <QStyleOptionSlider>
#include <QtMath>
#include "ui/colorcoding.h"
#include "common/colorcodingapp.h"
#include "oakengine/timeline.h"
#include "oakutil/qtutils.h"
#include "widget/timeruler/markerhandle.h"
namespace olive
{
@@ -63,7 +66,7 @@ ResizableTimelineScrollBar::~ResizableTimelineScrollBar()
}
}
void ResizableTimelineScrollBar::connect_markers(TimelineMarkerList *markers)
void ResizableTimelineScrollBar::connect_markers(OakEngineMarkerList *markers)
{
if (markers_) {
if (marker_sub_add_)
@@ -78,14 +81,11 @@ void ResizableTimelineScrollBar::connect_markers(TimelineMarkerList *markers)
if (markers_) {
marker_sub_add_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED);
markers_, OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED);
marker_sub_rem_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED);
markers_, OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED);
marker_sub_mod_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED);
markers_, OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED);
connect(bridge_, &EngineEventBridge::marker_list_marker_added, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
@@ -104,7 +104,7 @@ void ResizableTimelineScrollBar::connect_markers(TimelineMarkerList *markers)
update();
}
void ResizableTimelineScrollBar::connect_work_area(TimelineWorkArea *workarea)
void ResizableTimelineScrollBar::connect_work_area(OakEngineWorkarea *workarea)
{
if (workarea_) {
if (workarea_range_sub_ > 0) {
@@ -120,7 +120,7 @@ void ResizableTimelineScrollBar::connect_work_area(TimelineWorkArea *workarea)
workarea_ = workarea;
if (workarea_) {
void *handle = reinterpret_cast<void *>(workarea_);
void *handle = workarea_;
workarea_range_sub_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED,
[](const oakengine_event *, void *userdata) {
@@ -147,8 +147,20 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event)
{
ResizableScrollBar::paintEvent(event);
if (!timebase().isNull() && ((workarea_ && workarea_->enabled()) ||
(markers_ && !markers_->empty()))) {
// Fetch workarea/marker state through the C ABI (the engine types are
// opaque identity pointers on this side).
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled_flag = 0;
const bool wa_enabled =
workarea_ &&
oakengine_workarea_get(workarea_, &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled_flag) == OAKENGINE_OK &&
wa_enabled_flag;
const int marker_count =
markers_ ? oakengine_marker_list_count(markers_) : 0;
if (!timebase().isNull() && (wa_enabled || marker_count > 0)) {
// Draw workarea
QStyleOptionSlider opt;
initStyleOption(&opt);
@@ -160,19 +172,22 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event)
scale_ * double(gr.width()) / double(this->maximum() + gr.width());
QPainter p(this);
if (workarea_ && workarea_->enabled()) {
if (wa_enabled) {
const Rational wa_in{int(wa_in_num), int(wa_in_den)};
const Rational wa_out{int(wa_out_num), int(wa_out_den)};
QColor workarea_color(this->palette().highlight().color());
workarea_color.setAlpha(128);
qint64 in =
qMax(qint64(0), qRound64(ratio * time_to_scene(workarea_->in())));
qMax(qint64(0), qRound64(ratio * time_to_scene(wa_in)));
qint64 out;
if (workarea_->out() == RATIONAL_MAX) {
if (wa_out == RATIONAL_MAX) {
out = gr.width();
} else {
out = qMin(qint64(gr.width()),
qRound64(ratio * time_to_scene(workarea_->out())));
qRound64(ratio * time_to_scene(wa_out)));
}
qint64 length = qMax(qint64(1), out - in);
@@ -181,18 +196,19 @@ void ResizableTimelineScrollBar::paintEvent(QPaintEvent *event)
}
// Draw markers
if (markers_ && !markers_->empty()) {
for (auto it = markers_->cbegin(); it != markers_->cend(); it++) {
TimelineMarker *marker = *it;
if (marker_count > 0) {
for (int i = 0; i < marker_count; i++) {
OakEngineMarker *marker =
oakengine_marker_list_at(markers_, i);
const TimeRange range = marker_time(marker);
QColor marker_color =
QtUtils::to_q_color(ColorCoding::get_color(marker->color()));
int64_t in = qRound64(ratio * time_to_scene(marker->time().in()));
int64_t out =
qRound64(ratio * time_to_scene(marker->time().out()));
QColor marker_qcolor = QtUtils::to_q_color(
AppColorCoding::get_color(marker_color(marker)));
int64_t in = qRound64(ratio * time_to_scene(range.in()));
int64_t out = qRound64(ratio * time_to_scene(range.out()));
int64_t length = qMax(int64_t(1), out - in);
p.fillRect(gr.x() + in, 0, length, height(), marker_color);
p.fillRect(gr.x() + in, 0, length, height(), marker_qcolor);
}
}
}
@@ -23,11 +23,10 @@
#define OAK_RESIZABLETIMELINESCROLLBAR_H
#include "resizablescrollbar.h"
#include "timeline/timelinemarker.h"
#include "timeline/timelineworkarea.h"
#include "widget/timebased/timescaledobject.h"
#include "engineeventbridge.h"
#include "oakengine/events.h"
#include "oakengine/timeline.h"
namespace olive
{
@@ -41,8 +40,8 @@ public:
QWidget *parent = nullptr);
~ResizableTimelineScrollBar() override;
void connect_markers(TimelineMarkerList *markers);
void connect_work_area(TimelineWorkArea *workarea);
void connect_markers(OakEngineMarkerList *markers);
void connect_work_area(OakEngineWorkarea *workarea);
void SetScale(double d);
@@ -50,9 +49,9 @@ protected:
virtual void paintEvent(QPaintEvent *event) override;
private:
TimelineMarkerList *markers_;
OakEngineMarkerList *markers_;
TimelineWorkArea *workarea_;
OakEngineWorkarea *workarea_;
// Workarea signal subscriptions (event 141/142-style, but workarea is a
// timeline-level concept tracked via OakEngineEvents).
+4 -3
View File
@@ -23,7 +23,7 @@
#define OAK_FLOATSLIDER_H
#include "base/decimalsliderbase.h"
#include "node/sliderdisplaytype.h"
#include "sliderdisplaytypeapp.h"
namespace olive
{
@@ -33,8 +33,9 @@ class FloatSlider : public DecimalSliderBase {
public:
FloatSlider(QWidget *parent = nullptr);
// The canonical definition lives in the engine layer
// (node/sliderdisplaytype.h); this alias keeps existing call sites
// The canonical definition lives in the app-side mirror
// (widget/slider/sliderdisplaytypeapp.h, ordinals synced with engine
// node/sliderdisplaytype.h); this alias keeps existing call sites
// source-compatible. Use slider::k_normal etc. for the enumerators.
using DisplayType = slider::FloatDisplayType;
+4 -3
View File
@@ -26,7 +26,7 @@
#include <QMouseEvent>
#include "base/decimalsliderbase.h"
#include "node/sliderdisplaytype.h"
#include "sliderdisplaytypeapp.h"
namespace olive
{
@@ -45,8 +45,9 @@ public:
/**
* @brief enum containing the possibly display types
*
* The canonical definition lives in the engine layer
* (node/sliderdisplaytype.h); this alias keeps existing call sites
* The canonical definition lives in the app-side mirror
* (widget/slider/sliderdisplaytypeapp.h, ordinals synced with engine
* node/sliderdisplaytype.h); this alias keeps existing call sites
* source-compatible. Use slider::k_time etc. for the enumerators.
*/
using DisplayType = slider::RationalDisplayType;
+54
View File
@@ -0,0 +1,54 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak 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/>.
***/
#ifndef OAK_SLIDERDISPLAYTYPEAPP_H
#define OAK_SLIDERDISPLAYTYPEAPP_H
namespace olive
{
/**
* @brief App-side mirror of engine node/sliderdisplaytype.h
*
* Nodes reference these enums in their input properties ("view"); the
* slider widgets in app/widget/slider use the same values to render.
* Ordinals MUST stay in sync with the engine definitions: the values cross
* the C ABI as ints inside node input properties. Enumerator order is
* ABI/feature compatible with the previous FloatSlider::DisplayType and
* RationalSlider::DisplayType. Update both sides together.
*/
namespace slider
{
enum FloatDisplayType { k_normal, k_decibel, k_percentage };
enum RationalDisplayType { k_time, k_float, k_rational };
} // namespace slider
// Ordinal sync guards against engine node/sliderdisplaytype.h.
static_assert(slider::k_percentage == 2,
"slider::FloatDisplayType out of sync with engine");
static_assert(slider::k_rational == 2,
"slider::RationalDisplayType out of sync with engine");
} // namespace olive
#endif // OAK_SLIDERDISPLAYTYPEAPP_H
+1 -1
View File
@@ -155,7 +155,7 @@ void TimeBasedView::set_y_scale(const double &y_scale)
}
}
void TimeBasedView::set_viewer_node(ViewerOutput *v)
void TimeBasedView::set_viewer_node(OakEngineNode *v)
{
if (viewer_) {
oakengine_event_unsubscribe(viewer_sub_);
+3 -3
View File
@@ -72,12 +72,12 @@ public:
{
}
ViewerOutput *get_viewer_node() const
OakEngineNode *get_viewer_node() const
{
return viewer_;
}
void set_viewer_node(ViewerOutput *v);
void set_viewer_node(OakEngineNode *v);
QPointF scale_point(const QPointF &p) const;
QPointF unscale_point(const QPointF &p) const;
@@ -146,7 +146,7 @@ private:
double y_scale_;
ViewerOutput *viewer_;
OakEngineNode *viewer_;
int64_t viewer_sub_ = 0;
};
@@ -183,7 +183,7 @@ public:
dragging_.resize(selected_.size());
if constexpr (std::is_same_v<T, TimelineMarker>) {
if constexpr (std::is_same_v<T, OakEngineMarker>) {
snap_points_.resize(selected_.size() * 2);
} else {
snap_points_.resize(selected_.size());
@@ -192,7 +192,7 @@ public:
if (target) {
time_targets_.resize(snap_points_.size());
memset(time_targets_.data(), 0,
time_targets_.size() * sizeof(Node *));
time_targets_.size() * sizeof(OakEngineNode *));
} else {
time_targets_.clear();
}
@@ -200,21 +200,21 @@ public:
for (size_t i = 0; i < selected_.size(); i++) {
T *obj = selected_.at(i);
if constexpr (std::is_same_v<T, TimelineMarker>) {
dragging_[i] = obj->time().in();
snap_points_[i] = obj->time().in();
snap_points_[i + selected_.size()] = obj->time().out();
if constexpr (std::is_same_v<T, OakEngineMarker>) {
dragging_[i] = selection_time(obj);
snap_points_[i] = selection_time(obj);
snap_points_[i + selected_.size()] = selection_time_end(obj);
if (target) {
time_targets_[i] = time_targets_[i + selected_.size()] =
QtUtils::get_parent_of_type<Node>(obj);
selection_time_target_parent(obj);
}
} else {
dragging_[i] = obj->time();
snap_points_[i] = obj->time();
dragging_[i] = selection_time(obj);
snap_points_[i] = selection_time(obj);
if (target) {
time_targets_[i] = QtUtils::get_parent_of_type<Node>(obj);
time_targets_[i] = selection_time_target_parent(obj);
}
}
}
@@ -229,10 +229,11 @@ public:
if (time_target_) {
for (size_t i = 0; i < copy.size(); i++) {
if (Node *parent = time_targets_[i]) {
if (OakEngineNode *parent = time_targets_[i]) {
copy[i] = time_target_->get_adjusted_time(
parent, time_target_->get_time_target(), copy[i],
Node::k_transform_towards_output);
parent,
time_target_->get_time_target(), copy[i],
k_transform_towards_output);
}
}
}
@@ -266,7 +267,7 @@ public:
Rational proposed_time = dragging_.at(i) + time_diff;
T *sel = selected_.at(i);
if (sel->has_sibling_at_time(proposed_time)) {
if (selection_has_sibling_at_time(sel, proposed_time)) {
// Unsnap
time_diff = presnap_time_diff;
if (view_->get_snap_service()) {
@@ -292,7 +293,7 @@ public:
bool loop;
do {
loop = false;
while (sel->has_sibling_at_time(proposed_time)) {
while (selection_has_sibling_at_time(sel, proposed_time)) {
proposed_time += adj;
unsnap();
}
@@ -316,22 +317,17 @@ public:
// Apply movement
for (size_t i = 0; i < selected_.size(); i++) {
if constexpr (std::is_same_v<T, NodeKeyframe>) {
key_set_time_live(selected_.at(i),
dragging_.at(i) + time_diff);
} else {
selection_set_time(selected_.at(i),
dragging_.at(i) + time_diff);
}
selection_set_time(selected_.at(i),
dragging_.at(i) + time_diff);
}
// Show information about this keyframe
Rational display_time;
if constexpr (std::is_same_v<T, TimelineMarker>) {
display_time = initial_drag_item_->time().in();
if constexpr (std::is_same_v<T, OakEngineMarker>) {
display_time = selection_time(initial_drag_item_);
} else {
display_time = initial_drag_item_->time();
display_time = selection_time(initial_drag_item_);
}
QString tip = QString::fromStdString(Timecode::time_to_timecode(
@@ -352,10 +348,10 @@ public:
QToolTip::hideText();
for (size_t i = 0; i < selected_.size(); i++) {
if constexpr (std::is_same_v<T, NodeKeyframe>) {
if constexpr (std::is_same_v<T, OakEngineKeyframe>) {
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(selected_.at(i)->parent()),
oakengine_keyframe_get_node(selected_.at(i)),
&tbn, &tbd);
const int64_t new_ts = olive::core::Timecode::time_to_timestamp(
dragging_.at(i), olive::Rational(tbn, tbd),
@@ -363,13 +359,13 @@ public:
oakengine_undo_command_multi_add_child(
command,
oakengine_keyframe_set_time_command(
reinterpret_cast<OakEngineKeyframe *>(selected_.at(i)),
selected_.at(i),
new_ts));
} else if constexpr (std::is_same_v<T, TimelineMarker>) {
} else if constexpr (std::is_same_v<T, OakEngineMarker>) {
oakengine_undo_command_multi_add_child(
command,
oakengine_marker_set_time_command(
reinterpret_cast<OakEngineMarker *>(selected_.at(i)),
selected_.at(i),
dragging_.at(i).numerator(),
dragging_.at(i).denominator()));
}
@@ -452,7 +448,7 @@ TimeBasedView *view_;
std::vector<Rational> dragging_;
std::vector<Rational> snap_points_;
std::vector<Node *> time_targets_;
std::vector<OakEngineNode *> time_targets_;
T *initial_drag_item_;
+303 -170
View File
@@ -30,11 +30,13 @@
#include "engineeventbridge.h"
#include "common/current.h"
#include "dialog/markerproperties/markerpropertiesdialog.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "widget/keyframeview/keyframehandle.h"
#include "widget/timeruler/markerhandle.h"
#include "widget/timeruler/timeruler.h"
#include "widget/timelinewidget/cliphandle.h"
@@ -86,21 +88,29 @@ void TimeBasedWidget::set_scale_and_center_on_playhead(const double &scale)
QTimer::singleShot(0, this, &TimeBasedWidget::center_scroll_on_playhead);
}
ViewerOutput *TimeBasedWidget::get_connected_node() const
OakEngineNode *TimeBasedWidget::get_connected_node() const
{
return viewer_node_.data();
// The handle is the same object the QPointer tracks (the documented
// QPointer<ViewerOutput> exception in timebasedwidget.h); bridge once
// here so the rest of the app only ever sees the C ABI handle.
return reinterpret_cast<OakEngineNode *>(viewer_node_.data());
}
void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
void TimeBasedWidget::connect_viewer_node(OakEngineNode *node)
{
// viewer_node_ is the documented QPointer<ViewerOutput> exception (see
// timebasedwidget.h): the incoming handle is the same engine object,
// bridged once here for QPointer's null-on-destroy semantics.
ViewerOutput *viewer = reinterpret_cast<ViewerOutput *>(node);
// Ignore no-op
if (viewer_node_ == node) {
if (viewer_node_ == viewer) {
return;
}
// Set viewer node
ViewerOutput *old = viewer_node_.data();
viewer_node_ = node;
OakEngineNode *old = get_connected_node();
viewer_node_ = viewer;
// Disconnect old bridge subscriptions and connections
disconnect(bridge_, nullptr, this, nullptr);
@@ -108,15 +118,23 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
if (viewer_node_) {
oak_video_params vp;
oakengine_viewer_get_video_params(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()), 0, &vp);
// We still need Current class - keep using it for now
oakengine_viewer_get_video_params(node, 0, &vp);
// We still need Current class - keep using it for now. It stores an
// engine-side olive::VideoParams, so bridge through the C ABI
// create/free pair (setCurrentVideoParams copies the value, the
// engine object is released immediately).
void *engine_vp =
viewer_output_video_params(viewer_node_).create_engine_params();
Current::getInstance().setCurrentVideoParams(
viewer_output_video_params(viewer_node_));
*static_cast<olive::VideoParams *>(engine_vp));
oakengine_video_params_free(engine_vp);
Current::getInstance().setCurrentAudioParams(
viewer_output_audio_params(viewer_node_));
} else {
Current::getInstance().setCurrentVideoParams(empty_video_params());
void *engine_vp = empty_video_params().create_engine_params();
Current::getInstance().setCurrentVideoParams(
*static_cast<olive::VideoParams *>(engine_vp));
oakengine_video_params_free(engine_vp);
Current::getInstance().setCurrentAudioParams(AudioParams());
}
if (old) {
@@ -133,18 +151,15 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
// Call derivatives
for (TimeBasedView *view : timeline_views_) {
view->set_viewer_node(viewer_node_.data());
view->set_viewer_node(reinterpret_cast<OakEngineNode *>(viewer_node_.data()));
}
ConnectedNodeChangeEvent(viewer_node_.data());
ConnectedNodeChangeEvent(node);
if (viewer_node_) {
OakEngineNode *handle =
reinterpret_cast<OakEngineNode *>(viewer_node_.data());
// Subscribe to viewer events via bridge
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH);
bridge_->subscribe(node, OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED);
bridge_->subscribe(node, OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED);
bridge_->subscribe(node, OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH);
connect(bridge_, &EngineEventBridge::viewer_length_changed, this,
[this](OakEngineNode *, qint64, qint64) {
@@ -162,14 +177,14 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
});
// Connect ruler and scrollbar to timeline points
connect_work_area(viewer_node_->get_work_area());
connect_markers(viewer_node_->get_markers());
connect_work_area(oakengine_viewer_get_workarea_handle(node));
connect_markers(oakengine_viewer_get_marker_list(node));
// If we're setting the timebase, set it automatically based on the video and audio parameters
if (auto_set_timebase_) {
auto_update_timebase();
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED);
bridge_->subscribe(node, OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED);
bridge_->subscribe(node, OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED);
connect(bridge_, &EngineEventBridge::viewer_frame_rate_changed, this,
[this](OakEngineNode *, qint64, qint64) {
auto_update_timebase();
@@ -181,23 +196,22 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
}
// Call derivatives
ConnectNodeEvent(viewer_node_.data());
ConnectNodeEvent(node);
}
update_maximum_scroll();
emit connected_node_changed(reinterpret_cast<OakEngineNode *>(old),
reinterpret_cast<OakEngineNode *>(node));
emit connected_node_changed(old, node);
}
void TimeBasedWidget::connect_work_area(TimelineWorkArea *workarea)
void TimeBasedWidget::connect_work_area(OakEngineWorkarea *workarea)
{
workarea_ = workarea;
ruler()->set_work_area(workarea);
scrollbar_->connect_work_area(workarea);
}
void TimeBasedWidget::connect_markers(TimelineMarkerList *markers)
void TimeBasedWidget::connect_markers(OakEngineMarkerList *markers)
{
markers_ = markers;
ruler()->set_markers(markers);
@@ -206,7 +220,7 @@ void TimeBasedWidget::connect_markers(TimelineMarkerList *markers)
void TimeBasedWidget::update_maximum_scroll()
{
Rational length = (viewer_node_) ? viewer_node_->get_length() : 0;
Rational length = (viewer_node_) ? viewer_output_length(viewer_node_.data()) : 0;
if (auto_max_scrollbar_) {
scrollbar_->setMaximum(
@@ -267,7 +281,7 @@ void TimeBasedWidget::page_scroll_to_playhead()
{
if (get_connected_node()) {
page_scroll_internal(
qRound(time_to_scene(get_connected_node()->get_playhead())), true);
qRound(time_to_scene(viewer_output_playhead(get_connected_node()))), true);
}
}
@@ -275,7 +289,7 @@ void TimeBasedWidget::catch_up_scroll_to_playhead()
{
if (get_connected_node()) {
catch_up_scroll_to_point(
qRound(time_to_scene(get_connected_node()->get_playhead())));
qRound(time_to_scene(viewer_output_playhead(get_connected_node()))));
}
}
@@ -486,68 +500,125 @@ void TimeBasedWidget::zoom_out()
void TimeBasedWidget::go_to_prev_cut()
{
// Cuts are only possible in sequences
Sequence *sequence = dynamic_cast<Sequence *>(viewer_node_.data());
OakEngineNode *sequence_node = get_connected_node();
if (!sequence) {
if (!oakengine_node_is_sequence(sequence_node)) {
return;
}
if (get_connected_node()->get_playhead().isNull()) {
const Rational playhead = viewer_output_playhead(sequence_node);
if (playhead.isNull()) {
return;
}
OakEngineSequence *sequence =
reinterpret_cast<OakEngineSequence *>(sequence_node);
Rational closest_cut = 0;
for (Track *track : sequence->get_tracks()) {
Rational this_track_closest_cut = 0;
// Iterate all track lists (video, audio, subtitle), mirroring
// Sequence::get_tracks(). The per-type counts line up with the
// OAKENGINE_TRACK_TYPE_* ordinals (0..2).
int track_counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(sequence, &track_counts[0],
&track_counts[1], &track_counts[2]);
for (Block *block : track->blocks()) {
if (block->out() < get_connected_node()->get_playhead()) {
this_track_closest_cut = block->out();
} else {
break;
for (int type = 0; type < 3; type++) {
for (int ti = 0; ti < track_counts[type]; ti++) {
OakEngineTrack *track =
oakengine_sequence_track_at(sequence, type, ti);
if (!track) {
continue;
}
}
closest_cut = qMax(closest_cut, this_track_closest_cut);
Rational this_track_closest_cut = 0;
const int block_count = oakengine_track_block_count(track);
for (int bi = 0; bi < block_count; bi++) {
OakEngineBlock *block = oakengine_track_block_at(track, bi);
int out_num = 0, out_den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block), &out_num,
&out_den);
const Rational block_out(out_num, out_den);
if (block_out < playhead) {
this_track_closest_cut = block_out;
} else {
break;
}
}
closest_cut = qMax(closest_cut, this_track_closest_cut);
}
}
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(sequence_node,
closest_cut.numerator(), closest_cut.denominator());
}
void TimeBasedWidget::go_to_next_cut()
{
// Cuts are only possible in sequences
Sequence *sequence = dynamic_cast<Sequence *>(viewer_node_.data());
OakEngineNode *sequence_node = get_connected_node();
if (!sequence) {
if (!oakengine_node_is_sequence(sequence_node)) {
return;
}
OakEngineSequence *sequence =
reinterpret_cast<OakEngineSequence *>(sequence_node);
const Rational playhead = viewer_output_playhead(sequence_node);
Rational closest_cut = RATIONAL_MAX;
for (Track *track : sequence->get_tracks()) {
Rational this_track_closest_cut = track->track_length();
// See go_to_prev_cut() for the track iteration convention.
int track_counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(sequence, &track_counts[0],
&track_counts[1], &track_counts[2]);
if (this_track_closest_cut <= get_connected_node()->get_playhead()) {
this_track_closest_cut = RATIONAL_MAX;
}
for (Block *block : track->blocks()) {
if (block->in() > get_connected_node()->get_playhead()) {
this_track_closest_cut = block->in();
break;
for (int type = 0; type < 3; type++) {
for (int ti = 0; ti < track_counts[type]; ti++) {
OakEngineTrack *track =
oakengine_sequence_track_at(sequence, type, ti);
if (!track) {
continue;
}
}
closest_cut = qMin(closest_cut, this_track_closest_cut);
const int block_count = oakengine_track_block_count(track);
// Track::track_length(): out of the last block (0 when empty)
Rational this_track_closest_cut = 0;
if (block_count > 0) {
OakEngineBlock *last =
oakengine_track_block_at(track, block_count - 1);
int out_num = 0, out_den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(last), &out_num,
&out_den);
this_track_closest_cut = Rational(out_num, out_den);
}
if (this_track_closest_cut <= playhead) {
this_track_closest_cut = RATIONAL_MAX;
}
for (int bi = 0; bi < block_count; bi++) {
OakEngineBlock *block = oakengine_track_block_at(track, bi);
int in_num = 0, in_den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(block), &in_num,
&in_den);
const Rational block_in(in_num, in_den);
if (block_in > playhead) {
this_track_closest_cut = block_in;
break;
}
}
closest_cut = qMin(closest_cut, this_track_closest_cut);
}
}
if (closest_cut < RATIONAL_MAX) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(sequence_node,
closest_cut.numerator(), closest_cut.denominator());
}
}
@@ -555,8 +626,7 @@ void TimeBasedWidget::go_to_next_cut()
void TimeBasedWidget::go_to_start()
{
if (viewer_node_) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()), 0, 1);
oakengine_viewer_set_playhead(get_connected_node(), 0, 1);
}
}
@@ -564,16 +634,15 @@ void TimeBasedWidget::prev_frame()
{
if (viewer_node_) {
Rational proposed_time = Timecode::snap_time_to_timebase(
get_connected_node()->get_playhead() - timebase(), timebase(),
viewer_output_playhead(viewer_node_.data()) - timebase(), timebase(),
Timecode::k_ceil);
if (proposed_time == get_connected_node()->get_playhead()) {
if (proposed_time == viewer_output_playhead(viewer_node_.data())) {
// Catch rounding error, assume this time is snapped and just subtract a timebase
proposed_time -= timebase();
}
{
Rational _pt = qMax(Rational(0), proposed_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
oakengine_viewer_set_playhead(get_connected_node(),
_pt.numerator(), _pt.denominator());
}
}
@@ -583,14 +652,13 @@ void TimeBasedWidget::next_frame()
{
if (viewer_node_) {
Rational proposed_time = Timecode::snap_time_to_timebase(
get_connected_node()->get_playhead() + timebase(), timebase(),
viewer_output_playhead(viewer_node_.data()) + timebase(), timebase(),
Timecode::k_floor);
if (proposed_time == get_connected_node()->get_playhead()) {
if (proposed_time == viewer_output_playhead(viewer_node_.data())) {
// Catch rounding error, assume this time is snapped and just add a timebase
proposed_time += timebase();
}
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
oakengine_viewer_set_playhead(get_connected_node(),
proposed_time.numerator(), proposed_time.denominator());
}
}
@@ -598,10 +666,9 @@ void TimeBasedWidget::next_frame()
void TimeBasedWidget::go_to_end()
{
if (viewer_node_) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
viewer_node_->get_length().numerator(),
viewer_node_->get_length().denominator());
const Rational length = viewer_output_length(viewer_node_.data());
oakengine_viewer_set_playhead(get_connected_node(),
length.numerator(), length.denominator());
}
}
@@ -609,7 +676,7 @@ void TimeBasedWidget::center_scroll_on_playhead()
{
if (get_connected_node()) {
scrollbar_->setValue(
qRound(time_to_scene(get_connected_node()->get_playhead())) -
qRound(time_to_scene(viewer_output_playhead(get_connected_node()))) -
scrollbar_->width() / 2);
}
}
@@ -619,39 +686,48 @@ void TimeBasedWidget::set_auto_set_timebase(bool e)
auto_set_timebase_ = e;
}
void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
void TimeBasedWidget::set_point(TimelineApp::MovementMode m, const Rational &time)
{
if (!viewer_node_) {
return;
}
void *command = oakengine_undo_command_create_multi();
TimelineWorkArea *points = viewer_node_->get_work_area();
OakEngineWorkarea *points =
oakengine_viewer_get_workarea_handle(get_connected_node());
// Enable workarea if it isn't already enabled
if (!points->enabled()) {
oakengine_workarea_set_enabled_undoable(
reinterpret_cast<OakEngineWorkarea *>(points), 1, command);
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
oakengine_workarea_get(points, &wa_in_num, &wa_in_den, &wa_out_num,
&wa_out_den, &wa_enabled);
// Enable workarea if it isn't already enabled. Note the enable is only
// queued on `command` (applied on push below), so the in/out logic keeps
// using this pre-enable snapshot, mirroring the original undo semantics.
if (!wa_enabled) {
oakengine_workarea_set_enabled_undoable(points, 1, command);
}
// Determine our new range
Rational in_point, out_point;
if (m == Timeline::k_trim_in) {
if (m == TimelineApp::k_trim_in) {
in_point = time;
if (!points->enabled() || points->out() < in_point) {
if (!wa_enabled ||
Rational(int(wa_out_num), int(wa_out_den)) < in_point) {
out_point = RATIONAL_MAX;
} else {
out_point = points->out();
out_point = Rational(int(wa_out_num), int(wa_out_den));
}
} else {
out_point = time;
if (!points->enabled() || points->in() > out_point) {
if (!wa_enabled ||
Rational(int(wa_in_num), int(wa_in_den)) > out_point) {
in_point = Rational(0, 1);
} else {
in_point = points->in();
in_point = Rational(int(wa_in_num), int(wa_in_den));
}
}
@@ -659,12 +735,11 @@ void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
{
int64_t old_in_num, old_in_den, old_out_num, old_out_den;
int old_enabled;
oakengine_workarea_get(
reinterpret_cast<OakEngineWorkarea *>(points),
oakengine_workarea_get(points,
&old_in_num, &old_in_den, &old_out_num, &old_out_den,
&old_enabled);
oakengine_workarea_set_range_undoable(
reinterpret_cast<OakEngineWorkarea *>(points),
points,
in_point.numerator(), in_point.denominator(),
out_point.numerator(), out_point.denominator(),
old_in_num, old_in_den, old_out_num, old_out_den, command);
@@ -673,21 +748,28 @@ void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
oakengine_undo_push(command, tr("Set In/Out Point").toUtf8().constData());
}
void TimeBasedWidget::reset_point(Timeline::MovementMode m)
void TimeBasedWidget::reset_point(TimelineApp::MovementMode m)
{
if (!get_connected_node()) {
return;
}
TimelineWorkArea *points = get_connected_node()->get_work_area();
OakEngineWorkarea *points =
oakengine_viewer_get_workarea_handle(get_connected_node());
if (!points->enabled()) {
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
oakengine_workarea_get(points, &wa_in_num, &wa_in_den, &wa_out_num,
&wa_out_den, &wa_enabled);
if (!wa_enabled) {
return;
}
TimeRange r = points->range();
TimeRange r{Rational(int(wa_in_num), int(wa_in_den)),
Rational(int(wa_out_num), int(wa_out_den))};
if (m == Timeline::k_trim_in) {
if (m == TimelineApp::k_trim_in) {
r.set_in(Rational(0, 1));
} else {
r.set_out(RATIONAL_MAX);
@@ -697,12 +779,11 @@ void TimeBasedWidget::reset_point(Timeline::MovementMode m)
auto reset_cmd = oakengine_undo_command_create_multi();
int64_t old_in_num, old_in_den, old_out_num, old_out_den;
int old_enabled;
oakengine_workarea_get(
reinterpret_cast<OakEngineWorkarea *>(points),
oakengine_workarea_get(points,
&old_in_num, &old_in_den, &old_out_num, &old_out_den,
&old_enabled);
oakengine_workarea_set_range_undoable(
reinterpret_cast<OakEngineWorkarea *>(points),
points,
r.in().numerator(), r.in().denominator(),
r.out().numerator(), r.out().denominator(),
old_in_num, old_in_den, old_out_num, old_out_den, reset_cmd);
@@ -756,22 +837,24 @@ bool TimeBasedWidget::user_is_dragging_playhead() const
void TimeBasedWidget::set_in_at_playhead()
{
set_point(Timeline::k_trim_in, get_connected_node()->get_playhead());
set_point(TimelineApp::k_trim_in,
viewer_output_playhead(get_connected_node()));
}
void TimeBasedWidget::set_out_at_playhead()
{
set_point(Timeline::k_trim_out, get_connected_node()->get_playhead());
set_point(TimelineApp::k_trim_out,
viewer_output_playhead(get_connected_node()));
}
void TimeBasedWidget::reset_in()
{
reset_point(Timeline::k_trim_in);
reset_point(TimelineApp::k_trim_in);
}
void TimeBasedWidget::reset_out()
{
reset_point(Timeline::k_trim_out);
reset_point(TimelineApp::k_trim_out);
}
void TimeBasedWidget::clear_in_out_points()
@@ -783,53 +866,83 @@ void TimeBasedWidget::clear_in_out_points()
{
auto clear_cmd = oakengine_undo_command_create_multi();
oakengine_workarea_set_enabled_undoable(
reinterpret_cast<OakEngineWorkarea *>(
get_connected_node()->get_work_area()),
oakengine_viewer_get_workarea_handle(get_connected_node()),
0, clear_cmd);
oakengine_undo_push(clear_cmd,
tr("Cleared In/Out Points").toUtf8().constData());
}
}
/**
* @brief TimelineMarkerList::get_closest_marker_to_time() through the C ABI:
* the list is sorted by time, so the walk stops as soon as the distance
* starts increasing.
*/
static OakEngineMarker *marker_list_closest_to_time(
OakEngineMarkerList *markers, const Rational &t)
{
OakEngineMarker *closest = nullptr;
const int count = oakengine_marker_list_count(markers);
for (int i = 0; i < count; i++) {
OakEngineMarker *m = oakengine_marker_list_at(markers, i);
Rational this_diff = qAbs(marker_time(m).in() - t);
if (closest) {
Rational stored_diff = qAbs(marker_time(closest).in() - t);
if (this_diff > stored_diff) {
// Since the list is organized by time, if the diff increases, assume we are only going
// to move further away from here and there's no need to check
break;
}
}
closest = m;
}
return closest;
}
void TimeBasedWidget::set_marker()
{
if (!get_connected_node()) {
OakEngineNode *viewer = get_connected_node();
if (!viewer) {
return;
}
TimelineMarkerList *markers = get_connected_node()->get_markers();
OakEngineMarkerList *markers = oakengine_viewer_get_marker_list(viewer);
const Rational playhead = viewer_output_playhead(viewer);
if (TimelineMarker *existing =
markers->get_marker_at_time(get_connected_node()->get_playhead())) {
if (OakEngineMarker *existing =
oakengine_marker_list_marker_at_time(
markers, playhead.numerator(), playhead.denominator())) {
// We already have a marker here, so pop open the edit dialog
MarkerPropertiesDialog mpd({ existing }, timebase(), this);
mpd.exec();
} else {
// Create a new marker and place it here
int color;
if (TimelineMarker *closest = markers->get_closest_marker_to_time(
get_connected_node()->get_playhead())) {
if (OakEngineMarker *closest =
marker_list_closest_to_time(markers, playhead)) {
// Copy color of closest marker to this time
color = closest->color();
color = marker_color(closest);
} else {
// Fallback to default color in preferences
color = OAK_CONFIG("MarkerColor").toInt();
}
const Rational playhead = get_connected_node()->get_playhead();
OakEngineMarker *marker = oakengine_marker_create(
color, playhead.numerator(), playhead.denominator(),
playhead.numerator(), playhead.denominator(), "");
TimelineMarker *cpp_marker =
reinterpret_cast<TimelineMarker *>(marker);
bool edited_in_dialog = false;
if (OAK_CONFIG("SetNameWithMarker").toBool()) {
MarkerPropertiesDialog mpd({ cpp_marker }, timebase(), this);
MarkerPropertiesDialog mpd({ marker }, timebase(), this);
if (mpd.exec() != QDialog::Accepted) {
oakengine_marker_free(marker);
marker = nullptr;
cpp_marker = nullptr;
} else {
edited_in_dialog = true;
}
@@ -839,18 +952,16 @@ void TimeBasedWidget::set_marker()
if (edited_in_dialog) {
// The dialog pushed undo commands referencing this exact
// marker object, so it must be the one added to the list.
oakengine_marker_list_add_existing(
reinterpret_cast<OakEngineMarkerList *>(markers), marker);
oakengine_marker_list_add_existing(markers, marker);
} else {
// Pristine marker: add through the liboakengine C ABI
// facade (one undoable command) and drop the temporary.
oakengine_sequence_marker_add_ex(
reinterpret_cast<OakEngineSequence *>(
get_connected_node()),
Timecode::time_to_timestamp(cpp_marker->time().in(),
reinterpret_cast<OakEngineSequence *>(viewer),
Timecode::time_to_timestamp(marker_time(marker).in(),
timebase(),
Timecode::k_round),
"", cpp_marker->color());
"", marker_color(marker));
oakengine_marker_free(marker);
}
}
@@ -880,7 +991,7 @@ void TimeBasedWidget::toggle_show_all()
toggle_show_all_old_scale_ = get_scale();
toggle_show_all_old_scroll_ = scrollbar_->value();
set_scale_from_dimensions(w, get_connected_node()->get_length().to_double());
set_scale_from_dimensions(w, viewer_output_length(get_connected_node()).to_double());
scrollbar_->setValue(0);
// Must explicitly do this because SetScale() will automatically set this to false
@@ -891,11 +1002,11 @@ void TimeBasedWidget::toggle_show_all()
void TimeBasedWidget::go_to_in()
{
if (get_connected_node()) {
if (get_connected_node()->get_work_area()->enabled()) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
get_connected_node()->get_work_area()->in().numerator(),
get_connected_node()->get_work_area()->in().denominator());
oakengine_viewer_workarea wa;
oakengine_viewer_get_workarea(get_connected_node(), &wa);
if (wa.enabled) {
oakengine_viewer_set_playhead(get_connected_node(),
wa.in_num, wa.in_den);
} else {
go_to_start();
}
@@ -905,11 +1016,11 @@ void TimeBasedWidget::go_to_in()
void TimeBasedWidget::go_to_out()
{
if (get_connected_node()) {
if (get_connected_node()->get_work_area()->enabled()) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
get_connected_node()->get_work_area()->out().numerator(),
get_connected_node()->get_work_area()->out().denominator());
oakengine_viewer_workarea wa;
oakengine_viewer_get_workarea(get_connected_node(), &wa);
if (wa.enabled) {
oakengine_viewer_set_playhead(get_connected_node(),
wa.out_num, wa.out_den);
} else {
go_to_end();
}
@@ -956,7 +1067,8 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
std::vector<SnapData> potential_snaps;
if (snap_points & k_snap_to_playhead) {
Rational playhead_abs_time = get_connected_node()->get_playhead();
Rational playhead_abs_time =
viewer_output_playhead(get_connected_node());
qreal playhead_pos = time_to_scene(playhead_abs_time);
attempt_snap(potential_snaps, screen_pt, playhead_pos, start_times,
playhead_abs_time);
@@ -965,31 +1077,43 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
if ((snap_points & k_snap_to_clips) && get_snap_blocks()) {
for (auto it = get_snap_blocks()->cbegin(); it != get_snap_blocks()->cend();
it++) {
Block *b = *it;
OakEngineBlock *b = *it;
qreal rect_left = time_to_scene(b->in());
qreal rect_right = time_to_scene(b->out());
int in_num = 0, in_den = 1, out_num = 0, out_den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(b), &in_num, &in_den);
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(b), &out_num, &out_den);
const Rational block_in(in_num, in_den);
const Rational block_out(out_num, out_den);
qreal rect_left = time_to_scene(block_in);
qreal rect_right = time_to_scene(block_out);
// Attempt snapping to clip in point
attempt_snap(potential_snaps, screen_pt, rect_left, start_times,
b->in());
block_in);
// Attempt snapping to clip out point
attempt_snap(potential_snaps, screen_pt, rect_right, start_times,
b->out());
block_out);
if (snap_points & k_snap_to_markers) {
// Snap to clip markers too
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(b)) {
if (clip->connected_viewer()) {
TimelineMarkerList *markers =
clip->connected_viewer()->get_markers();
for (auto jt = markers->cbegin(); jt != markers->cend();
jt++) {
TimelineMarker *marker = *jt;
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(b))) {
if (OakEngineNode *clip_viewer =
oakengine_clip_get_connected_viewer(b)) {
OakEngineMarkerList *markers =
oakengine_viewer_get_marker_list(clip_viewer);
const int marker_count =
oakengine_marker_list_count(markers);
for (int mi = 0; mi < marker_count; mi++) {
OakEngineMarker *marker =
oakengine_marker_list_at(markers, mi);
TimeRange marker_range =
marker->time() + clip->in() - clip_media_in(clip);
marker_time(marker) + block_in - clip_media_in(b);
qreal marker_in_screen =
time_to_scene(marker_range.in());
@@ -1010,9 +1134,10 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
}
if ((snap_points & k_snap_to_markers) && ruler()->get_markers()) {
for (auto it = ruler()->get_markers()->cbegin();
it != ruler()->get_markers()->cend(); it++) {
TimelineMarker *m = *it;
OakEngineMarkerList *ruler_markers = ruler()->get_markers();
const int marker_count = oakengine_marker_list_count(ruler_markers);
for (int mi = 0; mi < marker_count; mi++) {
OakEngineMarker *m = oakengine_marker_list_at(ruler_markers, mi);
// Ignore selected markers
if (std::find(ruler()->get_selected_markers().cbegin(),
@@ -1021,35 +1146,43 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
continue;
}
qreal marker_pos = time_to_scene(m->time().in());
attempt_snap(potential_snaps, screen_pt, marker_pos, start_times,
m->time().in());
const TimeRange m_time = marker_time(m);
if (m->time().in() != m->time().out()) {
marker_pos = time_to_scene(m->time().out());
qreal marker_pos = time_to_scene(m_time.in());
attempt_snap(potential_snaps, screen_pt, marker_pos, start_times,
m_time.in());
if (m_time.in() != m_time.out()) {
marker_pos = time_to_scene(m_time.out());
attempt_snap(potential_snaps, screen_pt, marker_pos, start_times,
m->time().out());
m_time.out());
}
}
}
if ((snap_points & k_snap_to_workarea) && ruler()->get_work_area() &&
ruler()->get_work_area()->enabled()) {
const Rational &workarea_in = ruler()->get_work_area()->in();
const Rational &workarea_out = ruler()->get_work_area()->out();
if ((snap_points & k_snap_to_workarea) && ruler()->get_work_area()) {
int64_t wa_in_num = 0, wa_in_den = 1, wa_out_num = 0, wa_out_den = 1;
int wa_enabled = 0;
oakengine_workarea_get(ruler()->get_work_area(), &wa_in_num,
&wa_in_den, &wa_out_num, &wa_out_den,
&wa_enabled);
if (wa_enabled) {
const Rational workarea_in{int(wa_in_num), int(wa_in_den)};
const Rational workarea_out{int(wa_out_num), int(wa_out_den)};
attempt_snap(potential_snaps, screen_pt, time_to_scene(workarea_in),
start_times, workarea_in);
attempt_snap(potential_snaps, screen_pt, time_to_scene(workarea_out),
start_times, workarea_out);
attempt_snap(potential_snaps, screen_pt, time_to_scene(workarea_in),
start_times, workarea_in);
attempt_snap(potential_snaps, screen_pt, time_to_scene(workarea_out),
start_times, workarea_out);
}
}
if ((snap_points & k_snap_to_keyframes) && get_snap_keyframes()) {
for (auto it = get_snap_keyframes()->cbegin();
it != get_snap_keyframes()->cend(); it++) {
const QVector<NodeKeyframe *> &keys = (*it)->get_keyframes();
const QVector<oak::Keyframe> &keys = (*it)->get_keyframes();
for (auto jt = keys.cbegin(); jt != keys.cend(); jt++) {
NodeKeyframe *key = *jt;
OakEngineKeyframe *key = jt->handle();
auto ignore = get_snap_ignore_keyframes();
if (ignore && std::find(ignore->cbegin(), ignore->cend(),
@@ -1057,12 +1190,12 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
continue;
}
Rational time = key->time();
Rational time = key_time(key);
if (const TimeTargetObject *target = get_keyframe_time_target()) {
if (Node *parent = key->parent()) {
if (OakEngineNode *parent = key_node(key)) {
time = target->get_adjusted_time(
parent, target->get_time_target(), time,
Node::k_transform_towards_output);
k_transform_towards_output);
}
}
+36 -19
View File
@@ -25,8 +25,25 @@
#include <QPointer>
#include <QWidget>
// Must precede the engine viewer.h include below: engine headers inline-
// instantiate QMetaTypeId for core value types (Rational/Color), and the
// Q_DECLARE_METATYPE specializations in oakutil/qtutils.h are only legal
// if they come first.
#include "oakutil/qtutils.h"
// WAVE3: this engine include is intentionally retained. The QPointer below
// needs the complete QObject-derived ViewerOutput type, and QPointer's
// null-on-destroy semantics cannot be replicated through the facade: the C
// ABI exposes no node-destruction event (OAKENGINE_EVENT_* tops out at
// REMOVED_FROM_GRAPH, which does not fire on every teardown path, e.g.
// project destruction via the QObject parent chain) and no accessor that
// hands the node back as a QObject* for a QPointer<QObject>. The queued
// playhead-scroll slots in the .cpp dereference the pointer after engine
// teardown, so a raw OakEngineNode* would be a use-after-free there. engine/
// is frozen for R8, so the include stays until the facade grows a
// destruction notification (WRAPPER-GAP).
#include "node/output/viewer/viewer.h"
#include "timeline/timelinecommon.h"
#include "timeline/timelinecommonapp.h"
#include "widget/keyframeview/keyframeviewinputconnection.h"
#include "widget/resizablescrollbar/resizabletimelinescrollbar.h"
#include "widget/timebased/timescaledobject.h"
@@ -50,20 +67,20 @@ public:
void zoom_out();
ViewerOutput *get_connected_node() const;
OakEngineNode *get_connected_node() const;
void connect_viewer_node(ViewerOutput *node);
void connect_viewer_node(OakEngineNode *node);
TimelineWorkArea *get_connected_work_area() const
OakEngineWorkarea *get_connected_work_area() const
{
return workarea_;
}
TimelineMarkerList *get_connected_markers() const
OakEngineMarkerList *get_connected_markers() const
{
return markers_;
}
void connect_work_area(TimelineWorkArea *workarea);
void connect_markers(TimelineMarkerList *markers);
void connect_work_area(OakEngineWorkarea *workarea);
void connect_markers(OakEngineMarkerList *markers);
void set_scale_and_center_on_playhead(const double &scale);
@@ -139,24 +156,24 @@ protected:
virtual void ScaleChangedEvent(const double &) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput *)
virtual void ConnectedNodeChangeEvent(OakEngineNode *)
{
}
virtual void ConnectedWorkAreaChangeEvent(TimelineWorkArea *)
virtual void ConnectedWorkAreaChangeEvent(OakEngineWorkarea *)
{
}
virtual void ConnectedMarkersChangeEvent(TimelineMarkerList *)
virtual void ConnectedMarkersChangeEvent(OakEngineMarkerList *)
{
}
EngineEventBridge *bridge_ = nullptr;
virtual void ConnectNodeEvent(ViewerOutput *)
virtual void ConnectNodeEvent(OakEngineNode *)
{
}
virtual void DisconnectNodeEvent(ViewerOutput *)
virtual void DisconnectNodeEvent(OakEngineNode *)
{
}
@@ -169,7 +186,7 @@ protected:
void set_catch_up_scroll_value(QScrollBar *b, int v, int maximum);
void stop_catch_up_scroll_timer(QScrollBar *b);
virtual const QVector<Block *> *get_snap_blocks() const
virtual const QVector<OakEngineBlock *> *get_snap_blocks() const
{
return nullptr;
}
@@ -182,11 +199,11 @@ protected:
{
return nullptr;
}
virtual const std::vector<NodeKeyframe *> *get_snap_ignore_keyframes() const
virtual const std::vector<OakEngineKeyframe *> *get_snap_ignore_keyframes() const
{
return nullptr;
}
virtual const std::vector<TimelineMarker *> *get_snap_ignore_markers() const
virtual const std::vector<OakEngineMarker *> *get_snap_ignore_markers() const
{
return nullptr;
}
@@ -229,7 +246,7 @@ private:
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void set_point(Timeline::MovementMode m, const Rational &time);
void set_point(TimelineApp::MovementMode m, const Rational &time);
/**
* @brief Reset either the in or out point
@@ -240,7 +257,7 @@ private:
*
* Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
*/
void reset_point(Timeline::MovementMode m);
void reset_point(TimelineApp::MovementMode m);
void page_scroll_internal(int screen_position, bool whole_page_scroll);
@@ -268,8 +285,8 @@ private:
double scrollbar_start_scale_;
bool scrollbar_top_handle_;
TimelineWorkArea *workarea_;
TimelineMarkerList *markers_;
OakEngineWorkarea *workarea_;
OakEngineMarkerList *markers_;
QTimer *catchup_scroll_timer_;
struct CatchUpScrollData {
@@ -24,8 +24,6 @@
#include <cfloat>
#include <QtMath>
#include "audio/audiovisualwaveform.h"
#include "oakengine/node.h"
namespace olive
{
+5 -2
View File
@@ -25,11 +25,14 @@
#include <olive/core/core.h>
#include <QWidget>
#include "node/block/block.h"
namespace olive
{
// Forward declaration for consumers of this header (e.g. timebasedview.h)
// that previously got ViewerOutput transitively through engine block.h.
class ViewerOutput;
using olive::core::Rational;
/**
* @brief Provides base functionality for any object that uses time and scale
*/
+71 -43
View File
@@ -23,60 +23,87 @@
#include <cstring>
#include "node/block/clip/clip.h"
#include <olive/core/core.h>
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
class FrameHashCache;
class AudioWaveformCache;
using olive::core::Rational;
using olive::core::TimeRange;
/**
* @brief Facade accessors for ClipBlock pointers held by the timeline UI.
* @brief Facade accessors for clip blocks held by the timeline UI.
*
* ClipBlock's header-inline convenience accessors (speed()/loop_mode()/
* The engine's header-inline clip convenience accessors (speed()/loop_mode()/
* thumbnails()/waveform()/connected_video_cache()/...) reference the
* input-id statics (k_speed_input/k_buffer_in/...), which are engine
* symbols the app must no longer pull across the liboakengine boundary.
* These helpers route the same queries through the C ABI instead (same
* pattern as app/widget/keyframeview/keyframehandle.h). The ClipBlock*
* itself stays an opaque identity pointer.
* pattern as app/widget/keyframeview/keyframehandle.h). Clips are passed
* as OakEngineBlock* handles; the engine cache types
* (FrameHashCache/AudioWaveformCache) are forward-declared here and only
* dereferenced by callers that still see the engine class definition.
*/
inline OakEngineClip *cliphandle(ClipBlock *clip)
inline OakEngineClip *cliphandle(OakEngineBlock *clip)
{
return reinterpret_cast<OakEngineClip *>(clip);
}
/** @brief The node feeding the clip's buffer input (ClipBlock's inline
/** @brief The node feeding the clip's buffer input (the inline
* get_connected_output(k_buffer_in) uses; borrowed, may be null). */
inline Node *clip_connected_node(ClipBlock *clip)
inline OakEngineNode *clip_connected_node(OakEngineBlock *clip)
{
return reinterpret_cast<Node *>(
oakengine_node_input_get_connected_node(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(), -1));
return oakengine_node_input_get_connected_node(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(), -1);
}
inline FrameHashCache *clip_thumbnails(ClipBlock *clip)
inline FrameHashCache *clip_thumbnails(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->thumbnail_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<FrameHashCache *>(
oakengine_node_get_thumbnail_cache(n)) :
nullptr;
}
inline AudioWaveformCache *clip_waveform(ClipBlock *clip)
inline AudioWaveformCache *clip_waveform(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->waveform_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<AudioWaveformCache *>(
oakengine_node_get_waveform_cache(n)) :
nullptr;
}
inline FrameHashCache *clip_connected_video_cache(ClipBlock *clip)
inline FrameHashCache *clip_connected_video_cache(OakEngineBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->video_frame_cache() : nullptr;
OakEngineNode *n = clip_connected_node(clip);
return n ? reinterpret_cast<FrameHashCache *>(
oakengine_node_get_video_frame_cache(n)) :
nullptr;
}
/** @brief ClipBlock::speed() through the facade input getter. */
inline double clip_speed(ClipBlock *clip)
/**
* @brief Owning track handle of a block of any kind (the block's track).
*
* Wraps the generic oakengine_block_get_track(); the timeline family's
* OakEngineTrack* is reinterpreted to the node family's OakEngineNode*
* (same underlying track object; the track accessors in node.h take the
* latter). NULL when the block is not on a track.
*/
inline OakEngineNode *block_track_handle(OakEngineBlock *block)
{
return reinterpret_cast<OakEngineNode *>(oakengine_block_get_track(block));
}
/** @brief The clip's speed through the facade input getter. */
inline double clip_speed(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -88,8 +115,8 @@ inline double clip_speed(ClipBlock *clip)
return v.f[0];
}
/** @brief ClipBlock::loop_mode() value (an olive::LoopMode int). */
inline int clip_loop_mode(ClipBlock *clip)
/** @brief The clip's loop mode value (an OAKENGINE_LOOP_MODE_* int). */
inline int clip_loop_mode(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -101,8 +128,8 @@ inline int clip_loop_mode(ClipBlock *clip)
return int(v.num);
}
/** @brief ClipBlock::is_reversed() through the facade input getter. */
inline bool clip_is_reversed(ClipBlock *clip)
/** @brief The clip's reverse flag through the facade input getter. */
inline bool clip_is_reversed(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -112,8 +139,8 @@ inline bool clip_is_reversed(ClipBlock *clip)
v.num != 0;
}
/** @brief ClipBlock::maintain_audio_pitch() through the facade. */
inline bool clip_maintain_audio_pitch(ClipBlock *clip)
/** @brief The clip's maintain-audio-pitch flag through the facade. */
inline bool clip_maintain_audio_pitch(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -124,14 +151,14 @@ inline bool clip_maintain_audio_pitch(ClipBlock *clip)
v.num != 0;
}
/** @brief Create a new empty ClipBlock through the C ABI. */
inline ClipBlock *clip_create_empty(const char *label = nullptr)
/** @brief Create a new empty clip block through the C ABI. */
inline OakEngineBlock *clip_create_empty(const char *label = nullptr)
{
return reinterpret_cast<ClipBlock *>(oakengine_clip_create_empty(label));
return reinterpret_cast<OakEngineBlock *>(oakengine_clip_create_empty(label));
}
/** @brief ClipBlock::media_in() through the facade. */
inline Rational clip_media_in(ClipBlock *clip)
/** @brief The clip's media in-point through the facade. */
inline Rational clip_media_in(OakEngineBlock *clip)
{
int64_t num = 0, den = 1;
if (oakengine_clip_get_media_in_rational(cliphandle(clip), &num, &den) ==
@@ -141,23 +168,23 @@ inline Rational clip_media_in(ClipBlock *clip)
return Rational(0, 1);
}
/** @brief ClipBlock::media_range() through the facade. */
inline TimeRange clip_media_range(ClipBlock *clip)
/** @brief The clip's media range through the facade. */
inline TimeRange clip_media_range(OakEngineBlock *clip)
{
int64_t in_num = 0, in_den = 1, out_num = 0, out_den = 1;
if (oakengine_clip_get_media_range_rational(
cliphandle(clip), &in_num, &in_den, &out_num, &out_den) ==
OAKENGINE_OK) {
return TimeRange(Rational(static_cast<int>(in_num),
static_cast<int>(in_den)),
static_cast<int>(in_den)),
Rational(static_cast<int>(out_num),
static_cast<int>(out_den)));
static_cast<int>(out_den)));
}
return TimeRange(0, 0);
}
/** @brief Set the clip's media in-point directly (rational seconds). */
inline void clip_set_media_in(ClipBlock *clip, const Rational &media_in,
inline void clip_set_media_in(OakEngineBlock *clip, const Rational &media_in,
bool undoable = false)
{
if (!clip) {
@@ -169,8 +196,8 @@ inline void clip_set_media_in(ClipBlock *clip, const Rational &media_in,
undoable ? 1 : 0);
}
/** @brief ClipBlock::is_autocaching() through the facade. */
inline bool clip_is_autocaching(ClipBlock *clip)
/** @brief The clip's auto-cache flag through the facade. */
inline bool clip_is_autocaching(OakEngineBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
@@ -181,9 +208,10 @@ inline bool clip_is_autocaching(ClipBlock *clip)
v.num != 0;
}
/** @brief ClipBlock::request_invalidated_from_connected() through the facade. */
/** @brief Request invalidation from the clip's connected node, through the
* facade. */
inline void clip_request_invalidate_connected(
ClipBlock *clip, bool force_all = false,
OakEngineBlock *clip, bool force_all = false,
const TimeRange &intersect = TimeRange())
{
if (!clip) {
File diff suppressed because it is too large Load Diff
+50 -42
View File
@@ -29,13 +29,12 @@
#include <QWidget>
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/output/viewer/viewer.h"
#include "oakengine/events.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelinecommon.h"
#include "timeline/timelinecommonapp.h"
#include "timelineandtrackview.h"
#include "widget/slider/rationalslider.h"
#include "widget/timebased/timebasedwidget.h"
@@ -127,7 +126,7 @@ public:
void show_proxy_dialog_for_selected_clips();
void recording_callback(const QString &filename, const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void enable_recording_overlay(const TimelineCoordinate &coord);
@@ -140,12 +139,21 @@ public:
/**
* @brief Timelines should always be connected to sequences
*/
Sequence *sequence() const
OakEngineSequence *sequence() const
{
return static_cast<Sequence *>(get_connected_node());
// R8: the type check goes through the C ABI facade predicate; the
// Sequence handle is shared with the node handle, so no engine C++
// definition is needed here (replaces the old static_cast, which
// required the complete engine type).
auto *connected = get_connected_node();
return (connected &&
oakengine_node_is_sequence(
reinterpret_cast<OakEngineNode *>(connected)))
? reinterpret_cast<OakEngineSequence *>(connected)
: nullptr;
}
const QVector<Block *> &get_selected_blocks() const
const QVector<OakEngineBlock *> &get_selected_blocks() const
{
return selected_blocks_;
}
@@ -154,7 +162,7 @@ public:
void restore_splitter_state(const QByteArray &state);
static void replace_blocks_with_gaps(const QVector<Block *> &blocks,
static void replace_blocks_with_gaps(const QVector<OakEngineBlock *> &blocks,
bool remove_from_graph,
void *command,
bool handle_transitions = true);
@@ -165,13 +173,13 @@ public:
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
Block *get_item_at_scene_pos(const TimelineCoordinate &coord);
OakEngineBlock *get_item_at_scene_pos(const TimelineCoordinate &coord);
void add_selection(const TimeRange &time, const Track::Reference &track);
void add_selection(Block *item);
void add_selection(const TimeRange &time, const TrackReference &track);
void add_selection(OakEngineBlock *item);
void remove_selection(const TimeRange &time, const Track::Reference &track);
void remove_selection(Block *item);
void remove_selection(const TimeRange &time, const TrackReference &track);
void remove_selection(OakEngineBlock *item);
const TimelineWidgetSelections &get_selections() const
{
@@ -181,10 +189,10 @@ public:
void set_selections(const TimelineWidgetSelections &s,
bool process_block_changes);
Track *get_track_from_reference(const Track::Reference &ref) const;
OakEngineTrack *get_track_from_reference(const TrackReference &ref) const;
void set_view_beam_cursor(const TimelineCoordinate &coord);
void set_view_transition_overlay(ClipBlock *out, ClipBlock *in);
void set_view_transition_overlay(OakEngineClip *out, OakEngineClip *in);
const QVector<TimelineViewGhostItem *> &get_ghost_items() const
{
@@ -198,8 +206,8 @@ public:
void move_rubber_band_select(bool enable_selecting, bool select_links);
void end_rubber_band_select();
int get_track_y(const Track::Reference &ref);
int get_track_height(const Track::Reference &ref);
int get_track_y(const TrackReference &ref);
int get_track_height(const TrackReference &ref);
void add_ghost(TimelineViewGhostItem *ghost);
@@ -210,18 +218,18 @@ public:
return !ghost_items_.isEmpty();
}
bool is_block_selected(Block *b) const
bool is_block_selected(OakEngineBlock *b) const
{
return selected_blocks_.contains(b);
}
void set_block_links_selected(ClipBlock *block, bool selected);
void set_block_links_selected(OakEngineClip *block, bool selected);
void queue_scroll(int value);
TimelineView *get_first_timeline_view();
Rational get_timebase_for_track_type(Track::Type type);
Rational get_timebase_for_track_type(TrackReference::Type type);
const QRect &get_rubber_band_geometry() const;
@@ -241,13 +249,13 @@ public:
* this is preferable and should only be set to FALSE if the list is guaranteed not to contain
* already selected blocks (and therefore filtering can be skipped to save time).
*/
void signal_selected_blocks(QVector<Block *> selected_blocks,
void signal_selected_blocks(QVector<OakEngineBlock *> selected_blocks,
bool filter = true);
/**
* @brief Track blocks that have been newly deselected
*/
void signal_deselected_blocks(const QVector<Block *> &deselected_blocks);
void signal_deselected_blocks(const QVector<OakEngineBlock *> &deselected_blocks);
/**
* @brief Convenience function to deselect all blocks and signal them
@@ -285,7 +293,7 @@ signals:
void block_selection_changed(const QVector<OakEngineBlock *> &selected_blocks);
void request_capture_start(const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void reveal_viewer_in_footage_viewer(OakEngineNode *r, const TimeRange &range);
void reveal_viewer_in_project(OakEngineNode *r);
@@ -297,10 +305,10 @@ protected:
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void ScaleChangedEvent(const double &) override;
virtual void ConnectNodeEvent(ViewerOutput *n) override;
virtual void DisconnectNodeEvent(ViewerOutput *n) override;
virtual void ConnectNodeEvent(OakEngineNode *n) override;
virtual void DisconnectNodeEvent(OakEngineNode *n) override;
virtual const QVector<Block *> *get_snap_blocks() const override
virtual const QVector<OakEngineBlock *> *get_snap_blocks() const override
{
return &added_blocks_;
}
@@ -309,14 +317,14 @@ protected slots:
virtual void SendCatchUpScrollEvent() override;
private:
QVector<Timeline::EditToInfo> get_edit_to_info(const Rational &playhead_time,
Timeline::MovementMode mode);
QVector<TimelineApp::EditToInfo> get_edit_to_info(const Rational &playhead_time,
TimelineApp::MovementMode mode);
void ripple_to(Timeline::MovementMode mode);
void ripple_to(TimelineApp::MovementMode mode);
void edit_to(Timeline::MovementMode mode);
void edit_to(TimelineApp::MovementMode mode);
void update_viewports(const Track::Type &type = Track::k_none);
void update_viewports(const TrackReference::Type &type = TrackReference::k_none);
bool paste_internal(bool insert);
@@ -324,13 +332,13 @@ private:
TimelineAndTrackView *add_timeline_and_track_view(Qt::Alignment alignment);
QHash<Node *, Node *>
QHash<OakEngineNode *, OakEngineNode *>
generate_existing_paste_map(void *clipboard);
QRubberBand rubberband_;
QVector<QPointF> rubberband_scene_pos_;
TimelineWidgetSelections rubberband_old_selections_;
QVector<Block *> rubberband_now_selected_;
QVector<OakEngineBlock *> rubberband_now_selected_;
bool rubberband_enable_selecting_;
bool rubberband_select_links_;
@@ -350,11 +358,11 @@ private:
RationalSlider *timecode_label_;
QVector<Block *> selected_blocks_;
QVector<OakEngineBlock *> selected_blocks_;
QVector<Block *> added_blocks_;
QVector<OakEngineBlock *> added_blocks_;
QHash<Block *, QVector<int64_t>> block_subscriptions_;
QHash<OakEngineBlock *, QVector<int64_t>> block_subscriptions_;
int deferred_scroll_value_;
@@ -424,7 +432,7 @@ private slots:
void view_drag_left(QDragLeaveEvent *event);
void view_drag_dropped(TimelineViewMouseEvent *event);
void track_updated(Track::Type type);
void track_updated(TrackReference::Type type);
void block_updated(OakEngineBlock *block = nullptr);
@@ -468,13 +476,13 @@ private slots:
void force_update_rubber_band();
private:
void add_block(Block *block);
void remove_block(Block *blocks);
void add_block(OakEngineBlock *block);
void remove_block(OakEngineBlock *blocks);
void add_track(Track *track);
void remove_track(Track *track);
void add_track(OakEngineTrack *track);
void remove_track(OakEngineTrack *track);
void track_index_changed(Track *track, int old, int now);
void track_index_changed(OakEngineTrack *track, int old, int now);
void track_about_to_be_deleted(OakEngineTrack *track);
};
@@ -31,7 +31,7 @@ void TimelineWidgetSelections::shift_time(const Rational &diff)
}
}
void TimelineWidgetSelections::shift_tracks(Track::Type type, int diff)
void TimelineWidgetSelections::shift_tracks(TrackReference::Type type, int diff)
{
TimelineWidgetSelections cached_selections;
@@ -51,7 +51,7 @@ void TimelineWidgetSelections::shift_tracks(Track::Type type, int diff)
// Then re-insert them with the diff applied
for (auto it = cached_selections.cbegin(); it != cached_selections.cend();
it++) {
Track::Reference ref(it.key().type(), it.key().index() + diff);
TrackReference ref(it.key().type(), it.key().index() + diff);
this->insert(ref, it.value());
}
@@ -75,7 +75,7 @@ void TimelineWidgetSelections::subtract(
const TimelineWidgetSelections &selections)
{
for (auto it = selections.cbegin(); it != selections.cend(); it++) {
const Track::Reference &track = it.key();
const TrackReference &track = it.key();
const TimeRangeList &their_list = it.value();
if (this->contains(track)) {
@@ -24,18 +24,22 @@
#include <QHash>
#include "node/output/track/track.h"
#include <olive/core/util/timerange.h>
#include "common/trackreferencehandle.h"
namespace olive
{
class TimelineWidgetSelections : public QHash<Track::Reference, TimeRangeList> {
using namespace core;
class TimelineWidgetSelections : public QHash<TrackReference, TimeRangeList> {
public:
TimelineWidgetSelections() = default;
void shift_time(const Rational &diff);
void shift_tracks(Track::Type type, int diff);
void shift_tracks(TrackReference::Type type, int diff);
void trim_in(const Rational &diff);
@@ -23,8 +23,8 @@
#include <cmath>
#include "node/block/clip/clip.h"
#include "render/audiowaveformcache.h"
#include "olive/core/util/timecodefunctions.h"
#include "oakengine/viewer.h"
#include "widget/timelinewidget/cliphandle.h"
namespace olive
@@ -33,10 +33,81 @@ namespace olive
namespace timeline_waveform_sync
{
bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
namespace
{
ClipBlock *clip = dynamic_cast<ClipBlock *>(block);
if (!clip || !clip_waveform(clip)) {
/**
* @brief Validated ranges of a waveform cache as a TimeRangeList.
*
* WRAPPER-GAP: the C ABI has no waveform-specific validated-ranges accessor;
* oakengine_playback_cache_valid_ranges() is reused instead. That function
* reinterprets the handle as PlaybackCache, which is sound here because
* AudioWaveformCache derives (single inheritance) from PlaybackCache.
*/
TimeRangeList waveform_validated_ranges(const void *waveform)
{
TimeRangeList list;
QVector<int64_t> quads(4 * 64);
int count;
while ((count = oakengine_playback_cache_valid_ranges(
static_cast<OakEnginePlaybackCache *>(
const_cast<void *>(waveform)),
quads.data(), quads.size() / 4)) == quads.size() / 4) {
quads.resize(quads.size() * 2);
}
for (int i = 0; i < count; i++) {
list.insert(TimeRange(Rational(static_cast<int>(quads.at(i * 4 + 0)),
static_cast<int>(quads.at(i * 4 + 1))),
Rational(static_cast<int>(quads.at(i * 4 + 2)),
static_cast<int>(quads.at(i * 4 + 3)))));
}
return list;
}
/**
* @brief Peak over [t, t + length) across all channels
* (AudioWaveformCache::get_summary_from_time() equivalent). 0 when the
* summary is unavailable.
*/
double waveform_window_peak(const void *waveform, const Rational &t,
const Rational &length, int sample_rate)
{
// The summary C ABI works in sample frames at the cache's sample rate
const Rational sample_tb(1, sample_rate);
const int64_t start_ts = core::Timecode::time_to_timestamp(
t, sample_tb, core::Timecode::k_round);
const int64_t end_ts = core::Timecode::time_to_timestamp(
t + length, sample_tb, core::Timecode::k_round);
double min_vals[64], max_vals[64];
int channels = 0;
if (oakengine_waveform_cache_get_summary(waveform, start_ts, end_ts,
min_vals, max_vals, 64,
&channels) != OAKENGINE_OK) {
return 0.0;
}
double peak = 0.0;
for (int i = 0; i < channels; i++) {
const double channel_peak =
std::max(std::abs(min_vals[i]), std::abs(max_vals[i]));
peak = std::max(peak, channel_peak);
}
return peak;
}
} // namespace
bool get_waveform_sync_clip(OakEngineBlock *block, WaveformSyncClip *out)
{
if (!block ||
!oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block))) {
return false;
}
OakEngineBlock *clip = block;
const void *waveform = clip_waveform(clip);
if (!waveform) {
return false;
}
@@ -45,8 +116,8 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
return false;
}
const AudioWaveformCache *waveform = clip_waveform(clip);
if (waveform->get_parameters().sample_rate() <= 0) {
const int sample_rate = oakengine_waveform_cache_sample_rate(waveform);
if (sample_rate <= 0) {
return false;
}
@@ -55,7 +126,7 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
// validated makes the menu item stay disabled for long clips and gives
// the appearance that "nothing happens" when the user tries to sync.
const TimeRangeList validated_ranges =
waveform->get_validated_ranges().intersects(media_range);
waveform_validated_ranges(waveform).intersects(media_range);
if (validated_ranges.isEmpty()) {
return false;
}
@@ -63,15 +134,15 @@ bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
out->clip = clip;
out->waveform = waveform;
out->media_range = media_range;
out->sample_rate = waveform->get_parameters().sample_rate();
out->sample_rate = sample_rate;
return true;
}
QVector<WaveformSyncClip>
get_selected_waveform_sync_clips(const QVector<Block *> &blocks)
get_selected_waveform_sync_clips(const QVector<OakEngineBlock *> &blocks)
{
QVector<WaveformSyncClip> clips;
for (Block *block : blocks) {
for (OakEngineBlock *block : blocks) {
WaveformSyncClip sync_clip;
if (get_waveform_sync_clip(block, &sync_clip)) {
clips.append(sync_clip);
@@ -103,7 +174,7 @@ QVector<double> extract_waveform_cache_envelope(const WaveformSyncClip &clip,
// absolute timeline, while the validity mask lets the correlation skip
// those placeholders entirely.
const TimeRangeList validated_ranges =
clip.waveform->get_validated_ranges().intersects(clip.media_range);
waveform_validated_ranges(clip.waveform).intersects(clip.media_range);
for (Rational t = clip.media_range.in(); t < clip.media_range.out();
t += window_time) {
@@ -114,16 +185,7 @@ QVector<double> extract_waveform_cache_envelope(const WaveformSyncClip &clip,
double peak = 0.0;
if (window_valid) {
const AudioVisualWaveform::Sample summary =
clip.waveform->get_summary_from_time(t, length);
for (const AudioVisualWaveform::SamplePerChannel &channel :
summary) {
const double channel_peak =
std::max(std::abs(static_cast<double>(channel.min)),
std::abs(static_cast<double>(channel.max)));
peak = std::max(peak, channel_peak);
}
peak = waveform_window_peak(clip.waveform, t, length, sample_rate);
}
envelope.append(peak);
@@ -24,22 +24,25 @@
#include <QVector>
#include "node/block/block.h"
#include "olive/core/util/rational.h"
#include "olive/core/util/timerange.h"
#include "oakengine/timeline.h"
namespace olive
{
class AudioWaveformCache;
class ClipBlock;
// Same namespace bridge the engine headers used to provide (unqualified
// Rational/TimeRange/TimeRangeList inside namespace olive).
using namespace core;
/**
* @brief Data required to synchronize a clip using its cached audio waveform.
*/
struct WaveformSyncClip {
ClipBlock *clip = nullptr;
const AudioWaveformCache *waveform = nullptr;
OakEngineBlock *clip = nullptr;
/// Opaque engine AudioWaveformCache handle, only ever passed to the
/// oakengine_waveform_cache_* C ABI (never dereferenced).
const void *waveform = nullptr;
TimeRange media_range;
int sample_rate = 0;
};
@@ -59,13 +62,13 @@ namespace timeline_waveform_sync
* has been validated in the waveform cache. Previously the whole range had to
* be validated, which made the context-menu action unavailable for long clips.
*/
bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out);
bool get_waveform_sync_clip(OakEngineBlock *block, WaveformSyncClip *out);
/**
* @brief Return all selected blocks that can be synchronized by waveform.
*/
QVector<WaveformSyncClip>
get_selected_waveform_sync_clips(const QVector<Block *> &blocks);
get_selected_waveform_sync_clips(const QVector<OakEngineBlock *> &blocks);
/**
* @brief Extract a peak envelope from the validated regions of a waveform cache.
+38 -31
View File
@@ -21,17 +21,12 @@
#include "add.h"
#include "core.h"
#include "node/block/subtitle/subtitle.h"
#include "node/factory.h"
#include "node/generator/shape/shapenode.h"
#include "node/generator/solid/solid.h"
#include "node/generator/text/textv3.h"
#include "oakengine/node.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelineundopointer.h"
#include "widget/timelinewidget/cliphandle.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
@@ -45,28 +40,28 @@ AddTool::AddTool(TimelineWidget *parent)
void AddTool::mouse_press(TimelineViewMouseEvent *event)
{
const Track::Reference &track = event->get_track();
const TrackReference &track = event->get_track();
// Check if track is locked
Track *t = parent()->get_track_from_reference(track);
if (t && t->is_locked()) {
OakEngineTrack *t = parent()->get_track_from_reference(track);
if (track_is_locked(t)) {
return;
}
Track::Type add_type = Track::k_none;
TrackReference::Type add_type = TrackReference::k_none;
switch (Core::instance()->get_selected_addable_object()) {
case Tool::k_addable_bars:
case Tool::k_addable_solid:
case Tool::k_addable_title:
case Tool::k_addable_shape:
add_type = Track::k_video;
add_type = TrackReference::k_video;
break;
case Tool::k_addable_tone:
add_type = Track::k_audio;
add_type = TrackReference::k_audio;
break;
case Tool::k_addable_subtitle:
add_type = Track::k_subtitle;
add_type = TrackReference::k_subtitle;
break;
case Tool::k_addable_empty:
// Leave as "none", which means this block can be placed on any track
@@ -76,7 +71,7 @@ void AddTool::mouse_press(TimelineViewMouseEvent *event)
return;
}
if (add_type == Track::k_none || add_type == track.type()) {
if (add_type == TrackReference::k_none || add_type == track.type()) {
drag_start_point_ =
validated_coordinate(event->get_coordinates(true)).get_frame();
@@ -111,12 +106,12 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
oakengine_undo_command_multi_add_child(command, subtitle_section_command);
}
Sequence *s = parent()->sequence();
OakEngineSequence *s = sequence();
QRectF r;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_title) {
VideoParams svp = viewer_output_video_params(s);
oak::VideoParams svp = viewer_output_video_params(s);
r = QRectF(0, 0, svp.width(), svp.height());
r.adjust(svp.width() / 10, svp.height() / 10, -svp.width() / 10,
-svp.height() / 10);
@@ -136,44 +131,56 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
}
}
Node *AddTool::create_addable_clip(void *command, Sequence *sequence,
const Track::Reference &track,
OakEngineNode *AddTool::create_addable_clip(void *command, OakEngineSequence *sequence,
const TrackReference &track,
const Rational &in, const Rational &length,
const QRectF &rect)
{
ClipBlock *clip;
OakEngineBlock *clip;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_subtitle) {
clip = reinterpret_cast<SubtitleBlock*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.subtitle"));
clip = reinterpret_cast<OakEngineBlock*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.subtitle"));
} else {
clip = clip_create_empty(olive::Tool::get_addable_object_name(
Core::instance()->get_selected_addable_object()).toUtf8().constData());
}
clip->set_length_and_media_out(length);
Project *graph = sequence->parent();
OakEngineProject *graph = oakengine_node_get_project(
reinterpret_cast<OakEngineNode *>(sequence));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(graph),
graph,
reinterpret_cast<OakEngineNode *>(clip)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(clip), reinterpret_cast<void *>(clip), 0, 0, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence->track_list(track.type())), track.index(), reinterpret_cast<void *>(clip), core::Timecode::time_to_timestamp(in, sequence_timebase(sequence))));
// Set the clip's length before placement through a trim command child
// (children redo in order): oakengine_block_set_length_and_media_out()
// requires the block to already be on a track (OAKENGINE_E_STATE), and
// pre-placement there are no adjacent blocks, so a trim-out command
// reduces to Block::set_length_and_media_out().
oakengine_undo_command_multi_add_child(command,
oakengine_block_trim_command(
reinterpret_cast<void *>(oakengine_sequence_track_at(
sequence,
track.type(), track.index())),
reinterpret_cast<void *>(clip),
length.numerator(), length.denominator(),
OAKENGINE_MOVEMENT_MODE_TRIM_OUT, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence, track.type())), track.index(), reinterpret_cast<void *>(clip), core::Timecode::time_to_timestamp(in, sequence_timebase(sequence))));
Node *node_to_add = nullptr;
OakEngineNode *node_to_add = nullptr;
switch (Core::instance()->get_selected_addable_object()) {
case Tool::k_addable_empty:
// Empty, nothing to be done
break;
case Tool::k_addable_solid:
node_to_add = reinterpret_cast<SolidGenerator*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.solidgenerator"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.solidgenerator");
break;
case Tool::k_addable_shape:
node_to_add = reinterpret_cast<ShapeNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.shape"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.shape");
break;
case Tool::k_addable_title:
node_to_add = reinterpret_cast<TextGeneratorV3*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.text3"));
node_to_add = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.text3");
break;
case Tool::k_addable_bars:
case Tool::k_addable_tone:
@@ -205,7 +212,7 @@ Node *AddTool::create_addable_clip(void *command, Sequence *sequence,
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(node_to_add), reinterpret_cast<void *>(clip), extra_node_offset.x(), extra_node_offset.y(), 0));
if (!rect.isNull()) {
const VideoParams vp = viewer_output_video_params(sequence);
const oak::VideoParams vp = viewer_output_video_params(sequence);
oak_video_params pod = {};
pod.width = vp.width();
pod.height = vp.height();
+3 -3
View File
@@ -35,9 +35,9 @@ public:
virtual void mouse_move(TimelineViewMouseEvent *event) override;
virtual void mouse_release(TimelineViewMouseEvent *event) override;
static Node *create_addable_clip(void *command,
Sequence *sequence,
const Track::Reference &track,
static OakEngineNode *create_addable_clip(void *command,
OakEngineSequence *sequence,
const TrackReference &track,
const Rational &in, const Rational &length,
const QRectF &rect = QRectF());
+4 -2
View File
@@ -21,6 +21,7 @@
#include "edit.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -84,9 +85,10 @@ void EditTool::mouse_release(TimelineViewMouseEvent *event)
void EditTool::mouse_double_click(TimelineViewMouseEvent *event)
{
Block *item = parent()->get_item_at_scene_pos(event->get_coordinates());
OakEngineBlock *item =
parent()->get_item_at_scene_pos(event->get_coordinates());
if (item && !item->track()->is_locked()) {
if (item && !track_is_locked(oakengine_block_get_track(item))) {
parent()->add_selection(item);
}
}
+112 -71
View File
@@ -27,21 +27,16 @@
#include <QToolTip>
#include "common/configwrapper.h"
#include "common/subtitleapp.h"
#include "oakutil/oaknode.h"
#include "oakutil/qtutils.h"
#include "core.h"
#include "dialog/sequence/sequence.h"
#include "node/audio/volume/volume.h"
#include "node/block/subtitle/subtitle.h"
#include "node/distort/transform/transformdistortnode.h"
#include "node/generator/matrix/matrix.h"
#include "node/math/math/math.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "oakengine/viewer.h"
#include "oakengine/project.h"
#include "timeline/timelineundopointer.h"
#include "widget/timelinewidget/cliphandle.h"
#include "window/mainwindow/mainwindow.h"
#include "window/mainwindow/mainwindowundo.h"
@@ -73,7 +68,7 @@ void ImportTool::drag_enter(TimelineViewMouseEvent *event)
// Variables to deserialize into
quintptr item_ptr;
QVector<Track::Reference> enabled_streams;
QVector<TrackReference> enabled_streams;
// Set drag start position
drag_start_ = event->get_coordinates();
@@ -84,14 +79,16 @@ void ImportTool::drag_enter(TimelineViewMouseEvent *event)
stream >> enabled_streams >> item_ptr;
// Get Item object
Node *item = reinterpret_cast<Node *>(item_ptr);
OakEngineNode *item = reinterpret_cast<OakEngineNode *>(item_ptr);
// Check if Item is Footage
ViewerOutput *f = dynamic_cast<ViewerOutput *>(item);
if (f && f->get_total_stream_count()) {
// Check if Item is a viewer (Footage or Sequence) with streams
if (oakengine_node_is_viewer_output(item) &&
oakengine_viewer_get_video_stream_count(item) +
oakengine_viewer_get_audio_stream_count(item) +
oakengine_viewer_get_subtitle_stream_count(item) >
0) {
// If the Item is Footage, we can create a Ghost from it
dragged_footage_.append({ f, enabled_streams });
dragged_footage_.append({ item, enabled_streams });
}
}
@@ -207,15 +204,26 @@ void ImportTool::drag_drop(TimelineViewMouseEvent *event)
}
}
void ImportTool::place_at(const QVector<ViewerOutput *> &footage,
void ImportTool::place_at(const QVector<OakEngineNode *> &footage,
const Rational &start, bool insert,
void *command, int track_offset,
bool jump_to_end)
{
DraggedFootageData refs;
foreach (ViewerOutput *f, footage) {
refs.append({ f, f->get_enabled_streams_as_references() });
foreach (OakEngineNode *f, footage) {
// ViewerOutput::get_enabled_streams_as_references() via the oak::
// wrapper (C ABI): (track_type, index) pairs with ordinals matching
// TrackReference::Type (see common/trackreferencehandle.h)
QVector<TrackReference> enabled_streams;
const QVector<QPair<int, int>> streams =
oak::Node(f).enabled_streams();
enabled_streams.reserve(streams.size());
for (const QPair<int, int> &s : streams) {
enabled_streams.append(TrackReference(
static_cast<TrackReference::Type>(s.first), s.second));
}
refs.append({ f, enabled_streams });
}
place_at(refs, start, insert, command, track_offset, jump_to_end);
@@ -256,27 +264,39 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
const int &track_start)
{
for (auto it = sorted.cbegin(); it != sorted.cend(); it++) {
ViewerOutput *footage = it->first;
OakEngineNode *footage = it->first;
if (footage == sequence() ||
(sequence() && footage->inputs_from(sequence(), true))) {
if (footage ==
reinterpret_cast<OakEngineNode *>(sequence()) ||
(sequence() &&
oakengine_node_inputs_from(
footage,
reinterpret_cast<OakEngineNode *>(sequence()), 1))) {
// Prevent cyclical dependency
continue;
}
// Each stream is offset by one track per track "type", we keep track of them in this vector
QVector<int> track_offsets(Track::k_count);
QVector<int> track_offsets(TrackReference::k_count);
track_offsets.fill(track_start);
Rational footage_duration;
Rational ghost_in;
TimelineWorkArea *wk = footage->get_work_area();
if (wk->enabled()) {
footage_duration = wk->length();
ghost_in = wk->in();
oakengine_viewer_workarea wk;
oakengine_viewer_get_workarea(
footage, &wk);
if (wk.enabled) {
footage_duration =
Rational(int(wk.out_num), int(wk.out_den)) -
Rational(int(wk.in_num), int(wk.in_den));
ghost_in = Rational(int(wk.in_num), int(wk.in_den));
} else {
footage_duration = footage->get_length();
int64_t len_num = 0, len_den = 1;
oakengine_viewer_get_length(
footage, &len_num,
&len_den);
footage_duration = Rational(int(len_num), int(len_den));
if (footage_duration.isNull()) {
// Fallback to still length if legngth was 0
@@ -293,12 +313,13 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
}
// Create ghosts
foreach (const Track::Reference &ref, it->second) {
Track::Type track_type = ref.type();
Track::Reference dest_track(track_type,
track_offsets.at(track_type));
foreach (const TrackReference &ref, it->second) {
TrackReference::Type track_type = ref.type();
TrackReference dest_track(track_type,
track_offsets.at(track_type));
if (track_type == Track::k_video || track_type == Track::k_audio) {
if (track_type == TrackReference::k_video ||
track_type == TrackReference::k_audio) {
auto ghost = create_ghost(
TimeRange(ghost_start, ghost_start + footage_duration),
ghost_in, dest_track);
@@ -310,15 +331,17 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
ref.to_string() };
ghost->set_data(TimelineViewGhostItem::k_attached_footage,
QVariant::fromValue(af));
} else if (track_type == Track::k_subtitle) {
} else if (track_type == TrackReference::k_subtitle) {
int sub_count = oakengine_viewer_get_subtitle_count(
reinterpret_cast<const OakEngineNode *>(footage),
footage,
ref.index());
for (int si = 0; si < sub_count; si++) {
const Subtitle *sub = static_cast<const Subtitle *>(
// Points at an engine Subtitle; read through the
// layout-identical app mirror (common/subtitleapp.h)
const SubtitleApp *sub = static_cast<const SubtitleApp *>(
oakengine_viewer_get_subtitle_at(
reinterpret_cast<const OakEngineNode *>(footage),
footage,
ref.index(), si));
auto ghost =
create_ghost(sub->time() + ghost_start, 0, dest_track);
@@ -354,12 +377,13 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, c);
}
Project *dst_graph = nullptr;
Sequence *sequence = this->sequence();
OakEngineProject *dst_graph = nullptr;
OakEngineSequence *sequence = this->sequence();
bool open_sequence = false;
if (sequence) {
dst_graph = sequence->parent();
dst_graph = oakengine_node_parent(
reinterpret_cast<OakEngineNode *>(sequence));
} else {
// There's no active timeline here, ask the user what to do
@@ -412,9 +436,9 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
OakEngineProject *active_project = Core::instance()->get_active_project();
if (active_project) {
Sequence *new_sequence = reinterpret_cast<Sequence *>(
OakEngineSequence *new_sequence =
Core::instance()->create_new_sequence_for_project(
active_project));
active_project);
oakengine_viewer_set_default_parameters(
reinterpret_cast<OakEngineNode *>(new_sequence));
@@ -423,7 +447,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
// Even if the user selected manual, set from footage anyway so the user has a useful
// starting point
QVector<ViewerOutput *> footage_only;
QVector<OakEngineNode *> footage_only;
for (auto it = dragged_footage_.cbegin();
it != dragged_footage_.cend(); it++) {
@@ -434,8 +458,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
QVector<OakEngineNode *> _footage_nodes;
for (auto *f : footage_only) {
_footage_nodes.append(
reinterpret_cast<OakEngineNode *>(f));
_footage_nodes.append(f);
}
oakengine_viewer_set_parameters_from_footage(
reinterpret_cast<OakEngineNode *>(new_sequence),
@@ -443,7 +466,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
// If the user selected manual, show them a dialog with parameters
if (behavior == k_dws_manual) {
SequenceDialog sd(new_sequence, SequenceDialog::k_new,
SequenceDialog sd(reinterpret_cast<OakEngineNode *>(new_sequence), SequenceDialog::k_new,
parent());
sd.set_undoable(false);
@@ -453,11 +476,11 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
}
if (sequence_is_valid) {
dst_graph = reinterpret_cast<Project *>(Core::instance()->get_active_project());
dst_graph = Core::instance()->get_active_project();
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
dst_graph,
reinterpret_cast<OakEngineNode *>(new_sequence)));
oakengine_folder_add_child(
reinterpret_cast<OakEngineNode *>(
@@ -465,7 +488,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
reinterpret_cast<OakEngineNode *>(new_sequence));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(new_sequence), reinterpret_cast<void *>(new_sequence), 0, 0, 0));
oakengine_sequence_add_default_nodes(
reinterpret_cast<OakEngineSequence *>(new_sequence));
new_sequence);
footage_to_ghosts(0, dragged_footage_,
viewer_output_video_params(new_sequence).time_base(),
@@ -483,16 +506,17 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
} else {
// If the sequence is valid, ownership is passed to AddItemCommand.
// Otherwise, we're responsible for deleting it.
delete new_sequence;
oakengine_node_free(
reinterpret_cast<OakEngineNode *>(new_sequence));
}
}
}
}
std::list<ClipBlock *> imported_clips;
std::list<OakEngineBlock *> imported_clips;
if (dst_graph) {
QVector<Block *> block_items(parent()->get_ghost_items().size());
QVector<OakEngineBlock *> block_items(parent()->get_ghost_items().size());
// Check if we're inserting (only valid if we're not creating this sequence ourselves)
if (insert && !open_sequence) {
@@ -501,15 +525,16 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
for (int i = 0; i < parent()->get_ghost_items().size(); i++) {
TimelineViewGhostItem *ghost = parent()->get_ghost_items().at(i);
Block *block = nullptr;
OakEngineBlock *block = nullptr;
Track::Type track_type = ghost->get_adjusted_track().type();
if (track_type == Track::k_video || track_type == Track::k_audio) {
TrackReference::Type track_type = ghost->get_adjusted_track().type();
if (track_type == TrackReference::k_video ||
track_type == TrackReference::k_audio) {
TimelineViewGhostItem::AttachedFootage footage_stream =
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<TimelineViewGhostItem::AttachedFootage>();
ClipBlock *clip = clip_create_empty();
OakEngineBlock *clip = clip_create_empty();
block = clip;
clip_set_media_in(clip, ghost->get_media_in());
oakengine_undo_command_multi_add_child(command,
@@ -528,10 +553,10 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
dep_pos++;
switch (
Track::Reference::type_from_string(footage_stream.output)) {
case Track::k_video: {
TransformDistortNode *transform =
reinterpret_cast<TransformDistortNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.transformdistort"));
TrackReference::type_from_string(footage_stream.output)) {
case TrackReference::k_video: {
OakEngineNode *transform =
oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.transformdistort");
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
@@ -548,7 +573,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
footage_stream.footage,
reinterpret_cast<OakEngineNode *>(transform),
QLatin1String(oakengine_transform_texture_input_id()).toUtf8().constData(),
-1));
@@ -562,8 +587,8 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(transform), reinterpret_cast<void *>(clip), dep_pos, 0, 0));
break;
}
case Track::k_audio: {
VolumeNode *volume_node = reinterpret_cast<VolumeNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.volume"));
case TrackReference::k_audio: {
OakEngineNode *volume_node = oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.volume");
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
@@ -580,7 +605,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
footage_stream.footage,
reinterpret_cast<OakEngineNode *>(volume_node),
QLatin1String(oakengine_volume_samples_input_id()).toUtf8().constData(),
-1));
@@ -615,11 +640,11 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
}
imported_clips.push_back(clip);
} else if (track_type == Track::k_subtitle) {
Subtitle src =
} else if (track_type == TrackReference::k_subtitle) {
SubtitleApp src =
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<Subtitle>();
SubtitleBlock *sub = reinterpret_cast<SubtitleBlock *>(
.value<SubtitleApp>();
OakEngineBlock *sub = reinterpret_cast<OakEngineBlock *>(
oakengine_node_factory_create_from_id(
"org.olivevideoeditor.Olive.subtitle"));
oakengine_subtitle_set_text(
@@ -634,16 +659,32 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(sub), reinterpret_cast<void *>(sub), 0, 0, 0));
}
block->set_length_and_media_out(ghost->get_length());
// Set the block's length before placement through a trim command
// child (children redo in order, so the length is set before
// the block is placed): oakengine_block_set_length_and_media_out()
// itself requires the block to already be on a track
// (OAKENGINE_E_STATE), and pre-placement there are no adjacent
// blocks, so a trim-out command reduces to
// Block::set_length_and_media_out().
oakengine_undo_command_multi_add_child(command,
oakengine_block_trim_command(
reinterpret_cast<void *>(oakengine_sequence_track_at(
sequence,
ghost->get_adjusted_track().type(),
ghost->get_adjusted_track().index())),
reinterpret_cast<void *>(block),
ghost->get_length().numerator(),
ghost->get_length().denominator(),
OAKENGINE_MOVEMENT_MODE_TRIM_OUT, 0));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence->track_list(ghost->get_adjusted_track().type())), ghost->get_adjusted_track().index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(ghost->get_adjusted_in(), parent()->timebase())));
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence, static_cast<int>(ghost->get_adjusted_track().type()))), ghost->get_adjusted_track().index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(ghost->get_adjusted_in(), parent()->timebase())));
block_items.replace(i, block);
}
}
if (open_sequence) {
oakengine_undo_command_multi_add_child(command, make_open_sequence_command(sequence));
oakengine_undo_command_multi_add_child(command, make_open_sequence_command(reinterpret_cast<OakEngineNode *>(sequence)));
}
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
@@ -662,7 +703,7 @@ void ImportTool::drop_ghosts(bool insert, void *parent_command)
TimelineViewGhostItem *ImportTool::create_ghost(const TimeRange &range,
const Rational &media_in,
const Track::Reference &track)
const TrackReference &track)
{
TimelineViewGhostItem *ghost = new TimelineViewGhostItem();
@@ -674,7 +715,7 @@ TimelineViewGhostItem *ImportTool::create_ghost(const TimeRange &range,
snap_points_.push_back(ghost->get_in());
snap_points_.push_back(ghost->get_out());
ghost->set_mode(Timeline::k_move);
ghost->set_mode(TimelineApp::k_move);
parent()->add_ghost(ghost);
+3 -3
View File
@@ -38,9 +38,9 @@ public:
virtual void drag_drop(TimelineViewMouseEvent *event) override;
using DraggedFootageData =
QVector<QPair<ViewerOutput *, QVector<Track::Reference>>>;
QVector<QPair<OakEngineNode *, QVector<TrackReference>>>;
void place_at(const QVector<ViewerOutput *> &footage, const Rational &start,
void place_at(const QVector<OakEngineNode *> &footage, const Rational &start,
bool insert, void *command, int track_offset = 0,
bool jump_to_end = false);
void place_at(const DraggedFootageData &footage, const Rational &start,
@@ -65,7 +65,7 @@ private:
TimelineViewGhostItem *create_ghost(const TimeRange &range,
const Rational &media_in,
const Track::Reference &track);
const TrackReference &track);
DraggedFootageData dragged_footage_;
+292 -207
View File
@@ -28,18 +28,72 @@
#include "oakutil/range.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "oakengine/timeline.h"
#include "pointer.h"
#include "timeline/timelineundopointer.h"
#include "widget/timeruler/timeruler.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
namespace
{
/// Block::in() as rational seconds.
Rational block_in_rational(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_in_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/// Block::out() as rational seconds.
Rational block_out_rational(const OakEngineBlock *block)
{
int num = 0, den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block), &num, &den);
return Rational(num, den);
}
/**
* @brief ClipBlock::block_links() through the engine C ABI
* (oakengine_block_link_count/at: Node::links() filtered to blocks, same
* content and ordering for a ClipBlock).
*/
QVector<OakEngineBlock *> block_links_of(OakEngineBlock *block)
{
QVector<OakEngineBlock *> links;
const int n = oakengine_block_link_count(block);
links.reserve(n);
for (int i = 0; i < n; i++) {
links.append(oakengine_block_link_at(block, i));
}
return links;
}
/**
* @brief Track::to_reference() facade: the app TrackReference mirror of an
* engine Track, through the C ABI (same pattern as
* ghost_block_track_reference()). Type ordinals are pinned to the engine
* Track::Type ordinals by the static_asserts in trackreferencehandle.h.
*/
TrackReference track_reference_of(OakEngineTrack *track)
{
if (!track) {
return TrackReference();
}
auto *h = reinterpret_cast<OakEngineNode *>(track);
return TrackReference(
static_cast<TrackReference::Type>(oakengine_track_get_type(h)),
oakengine_track_get_index(h));
}
} // namespace
PointerTool::PointerTool(TimelineWidget *parent)
: TimelineTool(parent)
, movement_allowed_(true)
@@ -53,17 +107,20 @@ PointerTool::PointerTool(TimelineWidget *parent)
void PointerTool::mouse_press(TimelineViewMouseEvent *event)
{
const Track::Reference &track_ref = event->get_track();
const TrackReference &track_ref = event->get_track();
// Determine if item clicked on is selectable
clicked_item_ = parent()->get_item_at_scene_pos(event->get_coordinates());
ClipBlock *clip_clicked_item = dynamic_cast<ClipBlock *>(clicked_item_);
OakEngineClip *clip_clicked_item =
oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(clicked_item_)) ?
reinterpret_cast<OakEngineClip *>(clicked_item_) :
nullptr;
can_rubberband_select_ = false;
bool selectable_item =
(clicked_item_ &&
!parent()->get_track_from_reference(track_ref)->is_locked());
!track_is_locked(parent()->get_track_from_reference(track_ref)));
if (selectable_item) {
// Cache the clip's type for use later
@@ -79,15 +136,16 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
// If we're not in a trim mode, we must be in a move mode (provided the tool allows movement and
// the block is not a gap)
if (drag_movement_mode_ == Timeline::k_none && movement_allowed_ &&
!dynamic_cast<GapBlock *>(clicked_item_)) {
drag_movement_mode_ = Timeline::k_move;
if (drag_movement_mode_ == TimelineApp::k_none && movement_allowed_ &&
!oakengine_block_is_gap(
reinterpret_cast<OakEngineBlock *>(clicked_item_))) {
drag_movement_mode_ = TimelineApp::k_move;
}
// If this item is already selected, no further selection needs to be made
if (parent()->is_block_selected(clicked_item_)) {
// Collect item deselections
QVector<Block *> deselected_blocks;
QVector<OakEngineBlock *> deselected_blocks;
// If shift is held, deselect it
if (event->get_modifiers() & Qt::ShiftModifier) {
@@ -98,7 +156,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
if (clip_clicked_item &&
!(event->get_modifiers() & Qt::AltModifier)) {
parent()->set_block_links_selected(clip_clicked_item, false);
deselected_blocks.append(clip_clicked_item->block_links());
deselected_blocks.append(block_links_of(reinterpret_cast<OakEngineBlock *>(clip_clicked_item)));
}
}
@@ -115,7 +173,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
if (selectable_item) {
// Collect item selections
QVector<Block *> selected_blocks;
QVector<OakEngineBlock *> selected_blocks;
// Select this item
parent()->add_selection(clicked_item_);
@@ -124,7 +182,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
// If not holding alt, select all links as well
if (clip_clicked_item && !(event->get_modifiers() & Qt::AltModifier)) {
parent()->set_block_links_selected(clip_clicked_item, true);
selected_blocks.append(clip_clicked_item->block_links());
selected_blocks.append(block_links_of(reinterpret_cast<OakEngineBlock *>(clip_clicked_item)));
}
parent()->signal_selected_blocks(selected_blocks);
@@ -136,7 +194,7 @@ void PointerTool::mouse_press(TimelineViewMouseEvent *event)
&&
(!selectable_item ||
drag_movement_mode_ ==
Timeline::
TimelineApp::
k_none)); // And if no item was selected OR the item isn't draggable
if (can_rubberband_select_) {
@@ -176,7 +234,7 @@ void PointerTool::mouse_move(TimelineViewMouseEvent *event)
snap_points_.clear();
// If we're performing an action, we can initiate ghosts
if (drag_movement_mode_ != Timeline::k_none) {
if (drag_movement_mode_ != TimelineApp::k_none) {
initiate_drag(clicked_item_, drag_movement_mode_,
event->get_modifiers());
}
@@ -219,15 +277,15 @@ void PointerTool::hover_move(TimelineViewMouseEvent *event)
{
if (trimming_allowed_) {
// No dragging, but we still want to process cursors
Block *block_at_cursor =
OakEngineBlock *block_at_cursor =
parent()->get_item_at_scene_pos(event->get_coordinates());
if (block_at_cursor) {
switch (is_cursor_in_trim_handle(block_at_cursor, event->get_scene_x())) {
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
parent()->setCursor(Qt::SizeHorCursor);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
parent()->setCursor(Qt::SizeHorCursor);
break;
default:
@@ -247,20 +305,20 @@ void set_ghost_to_slide_mode(TimelineViewGhostItem *g)
g->set_data(TimelineViewGhostItem::k_ghost_is_sliding, true);
}
void PointerTool::initiate_drag_internal(Block *clicked_item,
Timeline::MovementMode trim_mode,
void PointerTool::initiate_drag_internal(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers,
bool dont_roll_trims,
bool allow_nongap_rolling,
bool slide_instead_of_moving)
{
// Get list of selected blocks
QVector<Block *> clips = parent()->get_selected_blocks();
QVector<OakEngineBlock *> clips = parent()->get_selected_blocks();
if (trim_mode == Timeline::k_move) {
if (trim_mode == TimelineApp::k_move) {
// Gaps are not allowed to move, and since we only allow moving one block type at a time,
// dragging a gap is a no-op
if (dynamic_cast<GapBlock *>(clicked_item)) {
if (oakengine_block_is_gap(clicked_item)) {
return;
}
@@ -269,18 +327,23 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
if (!slide_instead_of_moving) {
// If the user tries to move a transition without moving the clip it belongs to, we turn
// this into a slide
foreach (Block *block, clips) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(block)) {
if (!can_transition_move(transit, clips)) {
foreach (OakEngineBlock *block, clips) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(block))) {
if (!can_transition_move(block, clips)) {
slide_instead_of_moving = true;
break;
}
} else if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
if ((clip->in_transition() &&
!can_transition_move(clip->in_transition(), clips)) ||
(clip->out_transition() &&
!can_transition_move(clip->out_transition(), clips))) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
OakEngineBlock *in_transit =
oakengine_clip_in_transition(block);
OakEngineBlock *out_transit =
oakengine_clip_out_transition(block);
if ((in_transit &&
!can_transition_move(in_transit, clips)) ||
(out_transit &&
!can_transition_move(out_transit, clips))) {
slide_instead_of_moving = true;
break;
}
@@ -298,117 +361,129 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// For slides to be legal, we make all blocks "contiguous". This means that only one series
// of blocks can move at a time and prevents.
QHash<Track *, Block *> earliest_block_on_track;
QHash<Track *, Block *> latest_block_on_track;
QHash<OakEngineTrack *, OakEngineBlock *> earliest_block_on_track;
QHash<OakEngineTrack *, OakEngineBlock *> latest_block_on_track;
foreach (Block *this_block, clips) {
Block *current_earliest =
earliest_block_on_track.value(this_block->track(), nullptr);
foreach (OakEngineBlock *this_block, clips) {
OakEngineTrack *this_track =
oakengine_block_get_track(this_block);
OakEngineBlock *current_earliest =
earliest_block_on_track.value(this_track, nullptr);
if (!current_earliest ||
this_block->in() < current_earliest->in()) {
earliest_block_on_track.insert(this_block->track(),
this_block);
block_in_rational(this_block) <
block_in_rational(current_earliest)) {
earliest_block_on_track.insert(this_track, this_block);
}
Block *current_latest =
latest_block_on_track.value(this_block->track(), nullptr);
OakEngineBlock *current_latest =
latest_block_on_track.value(this_track, nullptr);
if (!current_latest ||
this_block->out() > current_earliest->out()) {
latest_block_on_track.insert(this_block->track(),
this_block);
block_out_rational(this_block) >
block_out_rational(current_earliest)) {
latest_block_on_track.insert(this_track, this_block);
}
}
for (auto i = earliest_block_on_track.constBegin();
i != earliest_block_on_track.constEnd(); i++) {
// Make a contiguous stream
Track *track = i.key();
Block *earliest = i.value();
Block *latest = latest_block_on_track.value(i.key());
OakEngineTrack *track = i.key();
OakEngineBlock *earliest = i.value();
OakEngineBlock *latest = latest_block_on_track.value(i.key());
OakEngineBlock *earliest_previous =
oakengine_block_prev(earliest);
OakEngineBlock *latest_next = oakengine_block_next(latest);
// First we add the block that's out trimming, the one prior to the earliest
{
TimelineViewGhostItem *earliest_ghost;
bool slide_with_earliest_previous = true;
if (sliding_due_to_transition && earliest->previous()) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(earliest)) {
if (earliest->previous() !=
transit->connected_out_block()) {
if (sliding_due_to_transition && earliest_previous) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(earliest))) {
if (earliest_previous !=
oakengine_transition_connected_out_block(
earliest)) {
slide_with_earliest_previous = false;
}
} else if (ClipBlock *clip =
dynamic_cast<ClipBlock *>(earliest)) {
if (earliest->previous() != clip->in_transition()) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(
earliest))) {
if (earliest_previous !=
oakengine_clip_in_transition(earliest)) {
slide_with_earliest_previous = false;
}
}
}
if (earliest->previous() && slide_with_earliest_previous) {
earliest_ghost = add_ghost_from_block(earliest->previous(),
Timeline::k_trim_out);
if (earliest_previous && slide_with_earliest_previous) {
earliest_ghost = add_ghost_from_block(earliest_previous,
TimelineApp::k_trim_out);
} else {
earliest_ghost = add_ghost_from_null(earliest->in(),
earliest->in(),
track->to_reference(),
Timeline::k_trim_out);
earliest_ghost = add_ghost_from_null(block_in_rational(earliest),
block_in_rational(earliest),
track_reference_of(track),
TimelineApp::k_trim_out);
}
set_ghost_to_slide_mode(earliest_ghost);
}
// Then we add the block that's in trimming, the one after the latest
if (latest->next()) {
if (latest_next) {
TimelineViewGhostItem *latest_ghost;
bool slide_with_latest_next = true;
if (sliding_due_to_transition) {
if (TransitionBlock *transit =
dynamic_cast<TransitionBlock *>(latest)) {
if (latest->next() !=
transit->connected_in_block()) {
if (oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(latest))) {
if (latest_next !=
oakengine_transition_connected_in_block(
latest)) {
slide_with_latest_next = false;
}
} else if (ClipBlock *clip =
dynamic_cast<ClipBlock *>(latest)) {
if (latest->next() != clip->out_transition()) {
} else if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(
latest))) {
if (latest_next !=
oakengine_clip_out_transition(latest)) {
slide_with_latest_next = false;
}
}
}
if (slide_with_latest_next) {
latest_ghost = add_ghost_from_block(latest->next(),
Timeline::k_trim_in);
latest_ghost = add_ghost_from_block(latest_next,
TimelineApp::k_trim_in);
} else {
latest_ghost = add_ghost_from_null(latest->out(),
latest->out(),
track->to_reference(),
Timeline::k_trim_in);
latest_ghost = add_ghost_from_null(block_out_rational(latest),
block_out_rational(latest),
track_reference_of(track),
TimelineApp::k_trim_in);
}
set_ghost_to_slide_mode(latest_ghost);
}
// Finally, we add all of the moving blocks in between
Block *b = nullptr;
OakEngineBlock *b = nullptr;
do {
// On first run-through, set to earliest only. From then on, set to the next of the last
// in the loop.
if (b) {
b = b->next();
b = oakengine_block_next(b);
} else {
b = earliest;
}
TimelineViewGhostItem *between_ghost =
add_ghost_from_block(b, Timeline::k_move);
add_ghost_from_block(b, TimelineApp::k_move);
set_ghost_to_slide_mode(between_ghost);
} while (b != latest);
}
} else {
// Prepare for a standard pointer move by creating ghosts for them and any related blocks
foreach (Block *block, clips) {
if (dynamic_cast<GapBlock *>(block)) {
foreach (OakEngineBlock *block, clips) {
if (oakengine_block_is_gap(block)) {
continue;
}
@@ -416,14 +491,15 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
auto ghost = add_ghost_from_block(block, trim_mode, true);
Q_UNUSED(ghost)
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(block)) {
if (clip->out_transition()) {
add_ghost_from_block(clip->out_transition(), trim_mode,
true);
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
if (OakEngineBlock *out_transit =
oakengine_clip_out_transition(block)) {
add_ghost_from_block(out_transit, trim_mode, true);
}
if (clip->in_transition()) {
add_ghost_from_block(clip->in_transition(), trim_mode,
true);
if (OakEngineBlock *in_transit =
oakengine_clip_in_transition(block)) {
add_ghost_from_block(in_transit, trim_mode, true);
}
}
}
@@ -437,7 +513,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
is_clip_trimmable(clicked_item, clips, trim_mode);
// Create ghosts for trimming
for (Block *clip_item : clips) {
for (OakEngineBlock *clip_item : clips) {
if (clip_item != clicked_item &&
(!multitrim_enabled ||
!is_clip_trimmable(clip_item, clips, trim_mode))) {
@@ -446,7 +522,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
continue;
}
Block *block = clip_item;
OakEngineBlock *block = clip_item;
// Create ghost for this block
TimelineViewGhostItem *ghost = add_ghost_from_block(block, trim_mode);
@@ -455,22 +531,21 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// transition than a trim/roll
bool treat_trim_as_slide = false;
ClipBlock *cb = dynamic_cast<ClipBlock *>(block);
if (cb) {
if (oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(block))) {
// See if this clip has a transition attached, and move it with the trim if so
TransitionBlock *connected_transition;
OakEngineBlock *connected_transition;
// Get appropriate transition for the side of the clip
if (trim_mode == Timeline::k_trim_in) {
connected_transition = cb->in_transition();
if (trim_mode == TimelineApp::k_trim_in) {
connected_transition = oakengine_clip_in_transition(block);
} else {
connected_transition = cb->out_transition();
connected_transition = oakengine_clip_out_transition(block);
}
if (connected_transition) {
// We found a transition, we'll make this a "slide" action
TimelineViewGhostItem *transition_ghost = add_ghost_from_block(
connected_transition, Timeline::k_move);
connected_transition, TimelineApp::k_move);
// This will in effect be a slide with the transition moving between two other blocks
set_ghost_to_slide_mode(ghost);
@@ -485,29 +560,32 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// Standard pointer trimming in reality is a "roll" edit with an adjacent gap (one that may
// or may not exist already)
if (!dont_roll_trims) {
Block *adjacent = nullptr;
OakEngineBlock *adjacent = nullptr;
// Determine which block is adjacent
if (trim_mode == Timeline::k_trim_in) {
adjacent = block->previous();
if (trim_mode == TimelineApp::k_trim_in) {
adjacent = oakengine_block_prev(block);
} else {
adjacent = block->next();
adjacent = oakengine_block_next(block);
}
// See if we can roll the adjacent or if we'll need to create our own gap
if (!dynamic_cast<GapBlock *>(block) && !allow_nongap_rolling &&
adjacent && !dynamic_cast<GapBlock *>(adjacent) &&
!(dynamic_cast<TransitionBlock *>(block) &&
((trim_mode == Timeline::k_trim_in &&
static_cast<TransitionBlock *>(block)
->connected_out_block() == adjacent) ||
(trim_mode == Timeline::k_trim_out &&
static_cast<TransitionBlock *>(block)
->connected_in_block() == adjacent)))) {
bool block_is_transition = oakengine_node_is_transition(
reinterpret_cast<OakEngineNode *>(block));
if (!oakengine_block_is_gap(block) &&
!allow_nongap_rolling && adjacent &&
!oakengine_block_is_gap(adjacent) &&
!(block_is_transition &&
((trim_mode == TimelineApp::k_trim_in &&
oakengine_transition_connected_out_block(block) ==
adjacent) ||
(trim_mode == TimelineApp::k_trim_out &&
oakengine_transition_connected_in_block(block) ==
adjacent)))) {
adjacent = nullptr;
}
Timeline::MovementMode flipped_mode = flip_trim_mode(trim_mode);
TimelineApp::MovementMode flipped_mode = flip_trim_mode(trim_mode);
QVector<TimelineViewGhostItem *> adjacent_ghosts;
if (adjacent) {
@@ -518,23 +596,26 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
// FIXME: The check for `clips.size() == 1` may not be necessary, but I don't know yet.
// I'm only including it to prevent any potentially unintended behavior.
if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) {
if (ClipBlock *adjacent_clip =
dynamic_cast<ClipBlock *>(adjacent)) {
for (Block *adjacent_link :
adjacent_clip->block_links()) {
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(adjacent))) {
for (OakEngineBlock *adjacent_link :
block_links_of(adjacent)) {
adjacent_ghosts.append(add_ghost_from_block(
adjacent_link, flipped_mode));
}
}
}
} else if (trim_mode == Timeline::k_trim_in || block->next()) {
Rational null_ghost_pos = (trim_mode == Timeline::k_trim_in) ?
block->in() :
block->out();
} else if (trim_mode == TimelineApp::k_trim_in ||
oakengine_block_next(block)) {
Rational null_ghost_pos = (trim_mode == TimelineApp::k_trim_in) ?
block_in_rational(block) :
block_out_rational(block);
adjacent_ghosts.append(add_ghost_from_null(
null_ghost_pos, null_ghost_pos,
clip_item->track()->to_reference(), flipped_mode));
track_reference_of(
oakengine_block_get_track(clip_item)),
flipped_mode));
}
// If we have an adjacent block (for any reason), this is a roll edit and the adjacent is
@@ -547,7 +628,7 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
if (treat_trim_as_slide) {
// We're sliding a transition rather than a pure trim/roll
set_ghost_to_slide_mode(adjacent_ghost);
} else if (dynamic_cast<GapBlock *>(block)) {
} else if (oakengine_block_is_gap(block)) {
ghost->set_data(
TimelineViewGhostItem::k_trim_should_be_ignored,
true);
@@ -563,11 +644,11 @@ void PointerTool::initiate_drag_internal(Block *clicked_item,
}
}
bool PointerTool::can_transition_move(TransitionBlock *transit,
const QVector<Block *> &clips)
bool PointerTool::can_transition_move(OakEngineBlock *transit,
const QVector<OakEngineBlock *> &clips)
{
Block *out = transit->connected_out_block();
Block *in = transit->connected_in_block();
OakEngineBlock *out = oakengine_transition_connected_out_block(transit);
OakEngineBlock *in = oakengine_transition_connected_in_block(transit);
if ((out && !clips.contains(out)) || (in && !clips.contains(in))) {
return false;
@@ -619,16 +700,16 @@ void PointerTool::process_drag(const TimelineCoordinate &mouse_pos)
// Perform movement
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
switch (ghost->get_mode()) {
case Timeline::k_none:
case TimelineApp::k_none:
break;
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
ghost->set_in_adjustment(time_movement);
ghost->set_media_in_adjustment(time_movement);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
ghost->set_out_adjustment(time_movement);
break;
case Timeline::k_move: {
case TimelineApp::k_move: {
ghost->set_in_adjustment(time_movement);
ghost->set_out_adjustment(time_movement);
@@ -656,7 +737,7 @@ void PointerTool::process_drag(const TimelineCoordinate &mouse_pos)
struct GhostBlockPair {
TimelineViewGhostItem *ghost;
Block *block;
OakEngineBlock *block;
};
void PointerTool::finish_drag(TimelineViewMouseEvent *event)
@@ -668,14 +749,14 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// Sort ghosts depending on which ones are trimming, which are moving, and which are sliding
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->has_been_adjusted()) {
Block *b = QtUtils::value_to_ptr<Block>(
OakEngineBlock *b = QtUtils::value_to_ptr<OakEngineBlock>(
ghost->get_data(TimelineViewGhostItem::k_attached_block));
if (ghost->get_data(TimelineViewGhostItem::k_ghost_is_sliding).toBool()) {
blocks_sliding.append({ ghost, b });
} else if (ghost->get_mode() == Timeline::k_move) {
} else if (ghost->get_mode() == TimelineApp::k_move) {
blocks_moving.append({ ghost, b });
} else if (Timeline::is_a_trim_mode(ghost->get_mode())) {
} else if (TimelineApp::is_a_trim_mode(ghost->get_mode())) {
blocks_trimming.append({ ghost, b });
}
}
@@ -700,7 +781,8 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
command,
oakengine_block_trim_command(
reinterpret_cast<void *>(
parent()->get_track_from_reference(ghost->get_adjusted_track())),
parent()->get_track_from_reference(
ghost->get_adjusted_track())),
reinterpret_cast<void *>(p.block),
ghost->get_adjusted_length().numerator(),
ghost->get_adjusted_length().denominator(),
@@ -717,7 +799,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
TimelineWidgetSelections new_sel = parent()->get_selections();
TimelineViewGhostItem *reference_ghost =
blocks_trimming.first().ghost;
if (reference_ghost->get_mode() == Timeline::k_trim_in) {
if (reference_ghost->get_mode() == TimelineApp::k_trim_in) {
new_sel.trim_in(reference_ghost->get_in_adjustment());
} else {
new_sel.trim_out(reference_ghost->get_out_adjustment());
@@ -733,7 +815,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// If we're not duplicating, "remove" the clips and replace them with gaps
if (!duplicate_clips) {
QVector<Block *> blocks_to_delete(blocks_moving.size());
QVector<OakEngineBlock *> blocks_to_delete(blocks_moving.size());
for (int i = 0; i < blocks_moving.size(); i++) {
blocks_to_delete[i] = blocks_moving.at(i).block;
@@ -748,39 +830,39 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
insert_gaps_at_ghost_destination(command);
}
QMap<Node *, Node *> relinks;
QMap<OakEngineBlock *, OakEngineBlock *> relinks;
// Now we can re-add each clip
foreach (const GhostBlockPair &p, blocks_moving) {
Block *block = p.block;
OakEngineBlock *block = p.block;
if (duplicate_clips) {
// Duplicate rather than move
// Place the copy instead of the original block
Block *new_block =
reinterpret_cast<Block *>(oakengine_node_copy_in_graph(
OakEngineBlock *new_block =
reinterpret_cast<OakEngineBlock *>(oakengine_node_copy_in_graph(
reinterpret_cast<OakEngineNode*>(block), command));
relinks.insert(block, new_block);
block = new_block;
if (ClipBlock *new_clip = dynamic_cast<ClipBlock *>(block)) {
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block))) {
oakengine_clip_add_cache_passthrough(
reinterpret_cast<OakEngineClip *>(new_clip),
reinterpret_cast<OakEngineClip *>(block),
reinterpret_cast<OakEngineClip *>(p.block));
}
}
const Track::Reference &track_ref = p.ghost->get_adjusted_track();
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(sequence()->track_list(track_ref.type())), track_ref.index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(p.ghost->get_adjusted_in(), parent()->timebase())));
const TrackReference track_ref = p.ghost->get_adjusted_track();
oakengine_undo_command_multi_add_child(command, oakengine_track_place_block_command(reinterpret_cast<void *>(oakengine_sequence_track_list(sequence(), track_ref.type())), track_ref.index(), reinterpret_cast<void *>(block), core::Timecode::time_to_timestamp(p.ghost->get_adjusted_in(), parent()->timebase())));
}
if (!relinks.empty()) {
for (auto it = relinks.cbegin(); it != relinks.cend(); it++) {
// Re-connect links on duplicate clips
for (auto jt = it.key()->links().cbegin();
jt != it.key()->links().cend(); jt++) {
Node *link = *jt;
Node *copy_link = relinks.value(link);
// Re-connect links on duplicate clips (block links, same
// content as Node::links() for a ClipBlock)
for (OakEngineBlock *link : block_links_of(it.key())) {
OakEngineBlock *copy_link = relinks.value(link);
if (copy_link) {
oakengine_undo_command_multi_add_child(command, (void *)(oakengine_node_link_command(
reinterpret_cast<OakEngineNode*>(it.value()),
@@ -789,23 +871,21 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
}
// Re-connect transitions where applicable
if (ClipBlock *og_clip = dynamic_cast<ClipBlock *>(it.key())) {
ClipBlock *cp_clip = static_cast<ClipBlock *>(it.value());
TransitionBlock *og_in_transition =
og_clip->in_transition();
TransitionBlock *og_out_transition =
og_clip->out_transition();
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(it.key()))) {
OakEngineBlock *og_in_transition =
oakengine_clip_in_transition(it.key());
OakEngineBlock *og_out_transition =
oakengine_clip_out_transition(it.key());
if (og_in_transition &&
relinks.contains(og_in_transition)) {
TransitionBlock *cp_in_transition =
static_cast<TransitionBlock *>(
relinks.value(og_in_transition));
OakEngineBlock *cp_in_transition =
relinks.value(og_in_transition);
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(it.value()),
reinterpret_cast<OakEngineNode *>(cp_in_transition),
QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(),
-1));
@@ -813,13 +893,12 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (og_out_transition &&
relinks.contains(og_out_transition)) {
TransitionBlock *cp_out_transition =
static_cast<TransitionBlock *>(
relinks.value(og_out_transition));
OakEngineBlock *cp_out_transition =
relinks.value(og_out_transition);
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(it.value()),
reinterpret_cast<OakEngineNode *>(cp_out_transition),
QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(),
-1));
@@ -840,26 +919,27 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// Assume that the blocks are contiguous per track as set up in InitiateGhostsInternal()
// All we need to do is sort them by track and order them
QHash<Track::Reference, QList<Block *>> slide_info;
QHash<Track::Reference, Block *> in_adjacents;
QHash<Track::Reference, Block *> out_adjacents;
QHash<TrackReference, QList<OakEngineBlock *>> slide_info;
QHash<TrackReference, OakEngineBlock *> in_adjacents;
QHash<TrackReference, OakEngineBlock *> out_adjacents;
Rational movement;
foreach (const GhostBlockPair &p, blocks_sliding) {
const Track::Reference &track = p.ghost->get_track();
const TrackReference &track = p.ghost->get_track();
switch (p.ghost->get_mode()) {
case Timeline::k_none:
case TimelineApp::k_none:
break;
case Timeline::k_move: {
case TimelineApp::k_move: {
// These all should have moved uniformly, so as long as this is set, it should be fine
movement = p.ghost->get_in_adjustment();
QList<Block *> &blocks_on_this_track = slide_info[track];
QList<OakEngineBlock *> &blocks_on_this_track = slide_info[track];
bool inserted = false;
for (int i = 0; i < blocks_on_this_track.size(); i++) {
if (blocks_on_this_track.at(i)->in() > p.block->in()) {
if (block_in_rational(blocks_on_this_track.at(i)) >
block_in_rational(p.block)) {
blocks_on_this_track.insert(i, p.block);
inserted = true;
break;
@@ -871,10 +951,10 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
}
break;
}
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
out_adjacents.insert(track, p.block);
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
in_adjacents.insert(track, p.block);
break;
}
@@ -883,16 +963,17 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (!movement.isNull()) {
for (auto i = slide_info.constBegin(); i != slide_info.constEnd();
i++) {
const QList<Block *> &moving_blocks = i.value();
const QList<OakEngineBlock *> &moving_blocks = i.value();
QVector<void *> slide_blocks;
slide_blocks.reserve(moving_blocks.size());
for (Block *b : moving_blocks) {
for (OakEngineBlock *b : moving_blocks) {
slide_blocks.append(reinterpret_cast<void *>(b));
}
oakengine_undo_command_multi_add_child(
command,
oakengine_track_slide_command(
reinterpret_cast<void *>(parent()->get_track_from_reference(i.key())),
reinterpret_cast<void *>(
parent()->get_track_from_reference(i.key())),
slide_blocks.constData(), slide_blocks.size(),
reinterpret_cast<void *>(in_adjacents.value(i.key())),
reinterpret_cast<void *>(out_adjacents.value(i.key())),
@@ -910,42 +991,42 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
command, qApp->translate("PointerTool", "Moved Clips").toUtf8().constData());
}
Timeline::MovementMode PointerTool::is_cursor_in_trim_handle(Block *block,
TimelineApp::MovementMode PointerTool::is_cursor_in_trim_handle(OakEngineBlock *block,
qreal cursor_x)
{
const double k_trim_handle =
QtUtils::q_font_metrics_width(parent()->fontMetrics(), "H");
double block_left = parent()->time_to_scene(block->in());
double block_right = parent()->time_to_scene(block->out());
double block_left = parent()->time_to_scene(block_in_rational(block));
double block_right = parent()->time_to_scene(block_out_rational(block));
double block_width = block_right - block_left;
// Block is too narrow, no trimming allowed
if (block_width <= k_trim_handle * 2) {
return Timeline::k_none;
return TimelineApp::k_none;
}
if (trimming_allowed_ && cursor_x <= block_left + k_trim_handle) {
return Timeline::k_trim_in;
return TimelineApp::k_trim_in;
} else if (trimming_allowed_ && cursor_x >= block_right - k_trim_handle) {
return Timeline::k_trim_out;
return TimelineApp::k_trim_out;
} else {
return Timeline::k_none;
return TimelineApp::k_none;
}
}
void PointerTool::initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
void PointerTool::initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers)
{
initiate_drag_internal(clicked_item, trim_mode, modifiers, false, false,
false);
}
TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(Block *block)
TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(OakEngineBlock *block)
{
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (QtUtils::value_to_ptr<Block>(ghost->get_data(
if (QtUtils::value_to_ptr<OakEngineBlock>(ghost->get_data(
TimelineViewGhostItem::k_attached_block)) == block) {
return ghost;
}
@@ -957,12 +1038,12 @@ TimelineViewGhostItem *PointerTool::get_existing_ghost_from_block(Block *block)
//#define HIDE_GAP_GHOSTS
TimelineViewGhostItem *
PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
PointerTool::add_ghost_from_block(OakEngineBlock *block, TimelineApp::MovementMode mode,
bool check_if_exists)
{
// Ignore null blocks or blocks that aren't attached to a track because there's nothing we can
// do with either of those
if (!block || !block->track()) {
if (!block || !oakengine_block_get_track(block)) {
return nullptr;
}
@@ -979,8 +1060,8 @@ PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
ghost = TimelineViewGhostItem::from_block(block);
#ifdef HIDE_GAP_GHOSTS
if (block->type() == Block::kGap) {
ghost->SetInvisible(true);
if (oakengine_block_is_gap(block)) {
ghost->set_invisible(true);
}
#endif
@@ -991,8 +1072,8 @@ PointerTool::add_ghost_from_block(Block *block, Timeline::MovementMode mode,
TimelineViewGhostItem *
PointerTool::add_ghost_from_null(const Rational &in, const Rational &out,
const Track::Reference &track,
Timeline::MovementMode mode)
const TrackReference &track,
TimelineApp::MovementMode mode)
{
TimelineViewGhostItem *ghost = new TimelineViewGhostItem();
@@ -1010,20 +1091,20 @@ PointerTool::add_ghost_from_null(const Rational &in, const Rational &out,
}
void PointerTool::add_ghost_internal(TimelineViewGhostItem *ghost,
Timeline::MovementMode mode)
TimelineApp::MovementMode mode)
{
ghost->set_mode(mode);
// Prepare snap points (optimizes snapping for later)
switch (mode) {
case Timeline::k_move:
case TimelineApp::k_move:
snap_points_.push_back(ghost->get_in());
snap_points_.push_back(ghost->get_out());
break;
case Timeline::k_trim_in:
case TimelineApp::k_trim_in:
snap_points_.push_back(ghost->get_in());
break;
case Timeline::k_trim_out:
case TimelineApp::k_trim_out:
snap_points_.push_back(ghost->get_out());
break;
default:
@@ -1033,13 +1114,17 @@ void PointerTool::add_ghost_internal(TimelineViewGhostItem *ghost,
parent()->add_ghost(ghost);
}
bool PointerTool::is_clip_trimmable(Block *clip, const QVector<Block *> &items,
const Timeline::MovementMode &mode)
bool PointerTool::is_clip_trimmable(OakEngineBlock *clip, const QVector<OakEngineBlock *> &items,
const TimelineApp::MovementMode &mode)
{
foreach (Block *compare, items) {
if (clip->track() == compare->track() && clip != compare &&
((compare->in() < clip->in() && mode == Timeline::k_trim_in) ||
(compare->out() > clip->out() && mode == Timeline::k_trim_out))) {
foreach (OakEngineBlock *compare, items) {
if (oakengine_block_get_track(clip) ==
oakengine_block_get_track(compare) &&
clip != compare &&
((block_in_rational(compare) < block_in_rational(clip) &&
mode == TimelineApp::k_trim_in) ||
(block_out_rational(compare) > block_out_rational(clip) &&
mode == TimelineApp::k_trim_out))) {
return false;
}
}
@@ -1052,7 +1137,7 @@ Rational PointerTool::validate_in_trimming(Rational movement)
bool first_ghost = true;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->get_mode() != Timeline::k_trim_in) {
if (ghost->get_mode() != TimelineApp::k_trim_in) {
continue;
}
@@ -1090,7 +1175,7 @@ Rational PointerTool::validate_out_trimming(Rational movement)
bool first_ghost = true;
foreach (TimelineViewGhostItem *ghost, parent()->get_ghost_items()) {
if (ghost->get_mode() != Timeline::k_trim_out) {
if (ghost->get_mode() != TimelineApp::k_trim_out) {
continue;
}
+21 -21
View File
@@ -40,20 +40,20 @@ public:
protected:
virtual void finish_drag(TimelineViewMouseEvent *event);
virtual void initiate_drag(Block *clicked_item,
Timeline::MovementMode trim_mode,
virtual void initiate_drag(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers);
TimelineViewGhostItem *get_existing_ghost_from_block(Block *block);
TimelineViewGhostItem *get_existing_ghost_from_block(OakEngineBlock *block);
TimelineViewGhostItem *add_ghost_from_block(Block *block,
Timeline::MovementMode mode,
TimelineViewGhostItem *add_ghost_from_block(OakEngineBlock *block,
TimelineApp::MovementMode mode,
bool check_if_exists = false);
TimelineViewGhostItem *add_ghost_from_null(const Rational &in,
const Rational &out,
const Track::Reference &track,
Timeline::MovementMode mode);
const TrackReference &track,
TimelineApp::MovementMode mode);
/**
* @brief Validates Ghosts that are getting their in points trimmed
@@ -73,23 +73,23 @@ protected:
virtual void process_drag(const TimelineCoordinate &mouse_pos);
void initiate_drag_internal(Block *clicked_item,
Timeline::MovementMode trim_mode,
void initiate_drag_internal(OakEngineBlock *clicked_item,
TimelineApp::MovementMode trim_mode,
Qt::KeyboardModifiers modifiers,
bool dont_roll_trims, bool allow_nongap_rolling,
bool slide_instead_of_moving);
const Timeline::MovementMode &drag_movement_mode() const
const TimelineApp::MovementMode &drag_movement_mode() const
{
return drag_movement_mode_;
}
void set_drag_movement_mode(const Timeline::MovementMode &d)
void set_drag_movement_mode(const TimelineApp::MovementMode &d)
{
drag_movement_mode_ = d;
}
static bool can_transition_move(TransitionBlock *transit,
const QVector<Block *> &clips);
static bool can_transition_move(OakEngineBlock *transit,
const QVector<OakEngineBlock *> &clips);
void set_movement_allowed(bool e)
{
@@ -111,19 +111,19 @@ protected:
gap_trimming_allowed_ = e;
}
void set_clicked_item(Block *b)
void set_clicked_item(OakEngineBlock *b)
{
clicked_item_ = b;
}
private:
Timeline::MovementMode is_cursor_in_trim_handle(Block *block, qreal cursor_x);
TimelineApp::MovementMode is_cursor_in_trim_handle(OakEngineBlock *block, qreal cursor_x);
void add_ghost_internal(TimelineViewGhostItem *ghost,
Timeline::MovementMode mode);
TimelineApp::MovementMode mode);
bool is_clip_trimmable(Block *clip, const QVector<Block *> &items,
const Timeline::MovementMode &mode);
bool is_clip_trimmable(OakEngineBlock *clip, const QVector<OakEngineBlock *> &items,
const TimelineApp::MovementMode &mode);
void process_ghosts_for_sliding();
@@ -136,10 +136,10 @@ private:
bool can_rubberband_select_;
bool rubberband_selecting_;
Track::Type drag_track_type_;
Timeline::MovementMode drag_movement_mode_;
TrackReference::Type drag_track_type_;
TimelineApp::MovementMode drag_movement_mode_;
Block *clicked_item_;
OakEngineBlock *clicked_item_;
QPoint drag_global_start_;
};
+35 -20
View File
@@ -21,8 +21,10 @@
#include "razor.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/timelinewidget/trackhandle.h"
namespace olive
{
@@ -47,7 +49,7 @@ void RazorTool::mouse_move(TimelineViewMouseEvent *event)
}
// Split at the current cursor track
Track::Reference split_track = event->get_track();
TrackReference split_track = event->get_track();
if (!split_tracks_.contains(split_track)) {
split_tracks_.append(split_track);
@@ -61,29 +63,42 @@ void RazorTool::mouse_release(TimelineViewMouseEvent *event)
// Always split at the same time
Rational split_time = drag_start_.get_frame();
QVector<Block *> blocks_to_split;
QVector<OakEngineBlock *> blocks_to_split;
foreach (const Track::Reference &track_ref, split_tracks_) {
Track *track = parent()->get_track_from_reference(track_ref);
foreach (const TrackReference &track_ref, split_tracks_) {
OakEngineTrack *track = parent()->get_track_from_reference(track_ref);
if (track == nullptr || track->is_locked()) {
if (track == nullptr || track_is_locked(track)) {
continue;
}
Block *block_at_time = track->nearest_block_before(split_time);
OakEngineBlock *block_at_time = oakengine_track_nearest_block_before(
track,
Timecode::time_to_timestamp(split_time, parent()->timebase(),
Timecode::k_round));
// Ensure there's a valid block here
ClipBlock *clip_at_time;
if (block_at_time && block_at_time->out() != split_time &&
(clip_at_time = dynamic_cast<ClipBlock *>(block_at_time)) &&
!blocks_to_split.contains(block_at_time)) {
blocks_to_split.append(block_at_time);
if (block_at_time &&
oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(block_at_time))) {
int out_num = 0, out_den = 1;
oakengine_block_get_out_rational(
reinterpret_cast<const OakEngineNode *>(block_at_time),
&out_num, &out_den);
if (Rational(out_num, out_den) != split_time &&
!blocks_to_split.contains(block_at_time)) {
blocks_to_split.append(block_at_time);
// Add links if no alt is held
if (!(event->get_modifiers() & Qt::AltModifier)) {
foreach (Block *link, clip_at_time->block_links()) {
if (!blocks_to_split.contains(link)) {
blocks_to_split.append(link);
// Add links if no alt is held
if (!(event->get_modifiers() & Qt::AltModifier)) {
const int link_count =
oakengine_block_link_count(block_at_time);
for (int i = 0; i < link_count; i++) {
OakEngineBlock *link =
oakengine_block_link_at(block_at_time, i);
if (!blocks_to_split.contains(link)) {
blocks_to_split.append(link);
}
}
}
}
@@ -98,13 +113,13 @@ void RazorTool::mouse_release(TimelineViewMouseEvent *event)
// app-side BlockSplitPreservingLinksCommand push.
QVector<OakEngineClip *> clips;
clips.reserve(blocks_to_split.size());
foreach (Block *b, blocks_to_split) {
if (ClipBlock *clip = dynamic_cast<ClipBlock *>(b)) {
clips.append(reinterpret_cast<OakEngineClip *>(clip));
foreach (OakEngineBlock *b, blocks_to_split) {
if (oakengine_node_is_clip(reinterpret_cast<OakEngineNode *>(b))) {
clips.append(reinterpret_cast<OakEngineClip *>(b));
}
}
oakengine_sequence_split_clips(
reinterpret_cast<OakEngineSequence *>(parent()->sequence()),
parent()->sequence(),
clips.data(), clips.size(),
Timecode::time_to_timestamp(split_time, parent()->timebase(),
Timecode::k_round));
+1 -1
View File
@@ -36,7 +36,7 @@ public:
virtual void mouse_release(TimelineViewMouseEvent *event) override;
private:
QVector<Track::Reference> split_tracks_;
QVector<TrackReference> split_tracks_;
};
}

Some files were not shown because too many files have changed in this diff Show More