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:
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "oakutil/qtutils.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <QGraphicsView>
|
||||
#include <QMenu>
|
||||
|
||||
#include "tool/tool.h"
|
||||
#include "common/tooltypes.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+276
-300
File diff suppressed because it is too large
Load Diff
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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_);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user