app: migrate all engine access to the C ABI facade (nm U _ZN5olive = 0)

Every app module now reaches liboakengine exclusively through
oakengine_* C calls, EngineEventBridge subscriptions and app-side
handle headers (cliphandle/keyframehandle/nodevaluehandle/oakvaluehelper).
Direct C++ command construction, engine signal connect()s, and engine
type usage in MOC-visible signatures are gone: 557 -> 0 undefined
olive:: symbols in oak-editor.
This commit is contained in:
2026-07-26 22:43:21 +08:00
parent f95590e924
commit 0aa5879f35
256 changed files with 12357 additions and 4044 deletions
+22 -7
View File
@@ -28,6 +28,7 @@
#include "audio/audiolevelmeter.h"
#include "audio/audiomanager.h"
#include "common/decibel.h"
#include "oakengine/preview.h"
#include "common/qtutils.h"
namespace olive
@@ -90,10 +91,20 @@ void AudioMonitor::push_sample_buffer(const SampleBuffer &d)
QVector<double> v(params_.channel_count(), 0);
const AudioLevelMeter::Stats stats =
AudioLevelMeter::analyze_sample_buffer(d);
for (int i = 0; i < v.size() && i < stats.channels.size(); i++) {
v[i] = stats.channels.at(i).peak_linear;
if (d.is_allocated() && d.channel_count() > 0) {
int channels = d.channel_count();
QVector<const float *> ptrs(channels);
for (int ch = 0; ch < channels; ch++) {
ptrs[ch] = d.data(ch);
}
QVector<double> levels(channels, 0.0);
if (oakengine_audio_analyze_levels(ptrs.data(), channels,
d.sample_count(),
levels.data()) == OAKENGINE_OK) {
for (int i = 0; i < v.size() && i < levels.size(); i++) {
v[i] = levels[i];
}
}
}
// Fill values because they get averaged out for smoothing
@@ -102,12 +113,14 @@ void AudioMonitor::push_sample_buffer(const SampleBuffer &d)
set_update_loop(true);
}
void AudioMonitor::start_waveform(const AudioWaveformCache *waveform,
void AudioMonitor::start_waveform(const void *waveform,
const Rational &start, int playback_speed)
{
stop();
waveform_length_ = waveform->length();
const AudioWaveformCache *cache =
static_cast<const AudioWaveformCache *>(waveform);
waveform_length_ = cache->length();
if (start >= waveform_length_) {
return;
}
@@ -442,8 +455,10 @@ 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 =
waveform_->get_summary_from_time(waveform_time_, length);
cache->get_summary_from_time(waveform_time_, length);
audio_visual_waveform_sample_to_internal_values(sum, v);
+3 -3
View File
@@ -45,7 +45,7 @@ public:
return waveform_;
}
static void start_waveform_on_all(const AudioWaveformCache *waveform,
static void start_waveform_on_all(const void *waveform,
const Rational &start, int playback_speed)
{
foreach (AudioMonitor *m, instances) {
@@ -74,7 +74,7 @@ public slots:
void push_sample_buffer(const SampleBuffer &samples);
void start_waveform(const AudioWaveformCache *waveform,
void start_waveform(const void *waveform,
const Rational &start, int playback_speed);
protected:
@@ -100,7 +100,7 @@ private:
qint64 last_time_;
const AudioWaveformCache *waveform_;
const void *waveform_;
Rational waveform_time_;
Rational waveform_length_;
+35 -8
View File
@@ -21,12 +21,13 @@
#include "colorbutton.h"
#include "common/qtutils.h"
#include "dialog/color/colordialog.h"
namespace olive
{
ColorButton::ColorButton(ColorManager *color_manager, bool show_dialog_on_click,
ColorButton::ColorButton(OakEngineColorManager *color_manager, bool show_dialog_on_click,
QWidget *parent)
: QPushButton(parent)
, color_manager_(color_manager)
@@ -52,10 +53,29 @@ void ColorButton::set_color(const ManagedColor &c)
{
color_ = c;
color_.set_color_input(
color_manager_->get_compliant_color_space(color_.color_input()));
color_.set_color_output(
color_manager_->get_compliant_color_space(color_.color_output()));
QByteArray in_name = color_.color_input().toUtf8();
QString compliant_in = oak_query_string([this, &in_name](char *buf, int size) {
return oakengine_color_manager_compliant_color_space(
color_manager_, in_name.constData(), buf, size);
});
color_.set_color_input(compliant_in);
QByteArray out_name = color_.color_output().output().toUtf8();
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;
char out_buf[256], view_buf[256], look_buf[256];
oakengine_color_manager_compliant_transform(
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),
QString::fromUtf8(view_buf),
QString::fromUtf8(look_buf)));
} else {
color_.set_color_output(ColorTransform(QString::fromUtf8(out_buf)));
}
update_color();
}
@@ -92,10 +112,17 @@ void ColorButton::color_dialog_finished(int e)
void ColorButton::update_color()
{
color_processor_ = ColorProcessor::create(
color_manager_, color_.color_input(), color_.color_output());
QByteArray in_cs = color_.color_input().toUtf8();
ColorTransform out = color_.color_output();
QByteArray o, v, l;
oak_color_transform out_pod = oak_to_transform(out, &o, &v, &l);
color_processor_ = ColorProcessorHandlePtr(
oakengine_color_processor_create(color_manager_, in_cs.constData(),
&out_pod,
OAKENGINE_COLOR_PROCESSOR_NORMAL),
ColorProcessorHandleDeleter());
QColor managed = QtUtils::to_q_color(color_processor_->convert_color(color_));
QColor managed = QtUtils::to_q_color(oak_convert_color(color_processor_, color_));
setStyleSheet(QStringLiteral("%1--ColorButton {background: %2;}")
.arg(MACRO_VAL_AS_STR(olive), managed.name()));
+6 -6
View File
@@ -24,8 +24,8 @@
#include <QPushButton>
#include "node/color/colormanager/colormanager.h"
#include "render/managedcolor.h"
#include "oakengine/color.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
namespace olive
{
@@ -33,9 +33,9 @@ namespace olive
class ColorButton : public QPushButton {
Q_OBJECT
public:
ColorButton(ColorManager *color_manager, bool show_dialog_on_click,
ColorButton(OakEngineColorManager *color_manager, bool show_dialog_on_click,
QWidget *parent = nullptr);
ColorButton(ColorManager *color_manager, QWidget *parent = nullptr)
ColorButton(OakEngineColorManager *color_manager, QWidget *parent = nullptr)
: ColorButton(color_manager, true, parent)
{
}
@@ -56,11 +56,11 @@ private slots:
private:
void update_color();
ColorManager *color_manager_;
OakEngineColorManager *color_manager_;
ManagedColor color_;
ColorProcessorPtr color_processor_;
ColorProcessorHandlePtr color_processor_;
bool dialog_open_;
};
+4 -4
View File
@@ -35,8 +35,8 @@ ColorPreviewBox::ColorPreviewBox(QWidget *parent)
{
}
void ColorPreviewBox::set_color_processor(ColorProcessorPtr to_ref,
ColorProcessorPtr to_display)
void ColorPreviewBox::set_color_processor(ColorProcessorHandlePtr to_ref,
ColorProcessorHandlePtr to_display)
{
to_ref_processor_ = to_ref;
to_display_processor_ = to_display;
@@ -58,8 +58,8 @@ void ColorPreviewBox::paintEvent(QPaintEvent *e)
// Color management
if (to_ref_processor_ && to_display_processor_) {
c = QtUtils::to_q_color(to_display_processor_->convert_color(
to_ref_processor_->convert_color(color_)));
c = QtUtils::to_q_color(oak_convert_color(to_display_processor_,
oak_convert_color(to_ref_processor_, color_)));
} else {
c = QtUtils::to_q_color(color_);
}
+5 -5
View File
@@ -24,7 +24,7 @@
#include <QWidget>
#include "render/colorprocessor.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
namespace olive
{
@@ -34,8 +34,8 @@ class ColorPreviewBox : public QWidget {
public:
ColorPreviewBox(QWidget *parent = nullptr);
void set_color_processor(ColorProcessorPtr to_ref,
ColorProcessorPtr to_display);
void set_color_processor(ColorProcessorHandlePtr to_ref,
ColorProcessorHandlePtr to_display);
public slots:
void set_color(const Color &c);
@@ -46,9 +46,9 @@ protected:
private:
Color color_;
ColorProcessorPtr to_ref_processor_;
ColorProcessorHandlePtr to_ref_processor_;
ColorProcessorPtr to_display_processor_;
ColorProcessorHandlePtr to_display_processor_;
};
}
+71 -16
View File
@@ -24,10 +24,12 @@
#include <QGridLayout>
#include <QLabel>
#include "widget/manageddisplay/colorprocessorhandle.h"
namespace olive
{
ColorSpaceChooser::ColorSpaceChooser(ColorManager *color_manager,
ColorSpaceChooser::ColorSpaceChooser(OakEngineColorManager *color_manager,
bool enable_input_field,
bool enable_display_fields,
QWidget *parent)
@@ -56,15 +58,25 @@ ColorSpaceChooser::ColorSpaceChooser(ColorManager *color_manager,
input_combobox_ = new QComboBox();
layout->addWidget(input_combobox_, row, 1);
QStringList input_spaces = color_manager->list_available_colorspaces();
QStringList input_spaces = oak_query_string_list(
[this]() {
return oakengine_color_manager_colorspace_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_colorspace_at(color_manager_, i,
buf, size);
});
foreach (const QString &s, input_spaces) {
input_combobox_->addItem(s);
}
if (!color_manager_->get_default_input_color_space().isEmpty()) {
input_combobox_->setCurrentText(
color_manager_->get_default_input_color_space());
QString def_input = oak_query_string([this](char *buf, int size) {
return oakengine_color_manager_default_input_color_space(
color_manager_, buf, size);
});
if (!def_input.isEmpty()) {
input_combobox_->setCurrentText(def_input);
}
connect(input_combobox_, &QComboBox::currentTextChanged, this,
@@ -82,14 +94,24 @@ ColorSpaceChooser::ColorSpaceChooser(ColorManager *color_manager,
display_combobox_ = new QComboBox();
layout->addWidget(display_combobox_, row, 1);
QStringList display_spaces = color_manager->list_available_displays();
QStringList display_spaces = oak_query_string_list(
[this]() {
return oakengine_color_manager_display_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_display_at(color_manager_, i,
buf, size);
});
foreach (const QString &s, display_spaces) {
display_combobox_->addItem(s);
}
display_combobox_->setCurrentText(
color_manager_->get_default_display());
oak_query_string([this](char *buf, int size) {
return oakengine_color_manager_default_display(
color_manager_, buf, size);
}));
connect(display_combobox_, &QComboBox::currentTextChanged, this,
&ColorSpaceChooser::combo_box_changed);
@@ -117,7 +139,14 @@ ColorSpaceChooser::ColorSpaceChooser(ColorManager *color_manager,
look_combobox_ = new QComboBox();
layout->addWidget(look_combobox_, row, 1);
QStringList looks = color_manager->list_available_looks();
QStringList looks = oak_query_string_list(
[this]() {
return oakengine_color_manager_look_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_look_at(color_manager_, i,
buf, size);
});
look_combobox_->addItem(tr("(None)"), QString());
@@ -155,20 +184,32 @@ ColorTransform ColorSpaceChooser::output() const
void ColorSpaceChooser::set_input(const QString &s)
{
input_combobox_->setCurrentText(color_manager_->get_compliant_color_space(s));
QByteArray name = s.toUtf8();
QString compliant = oak_query_string([this, &name](char *buf, int size) {
return oakengine_color_manager_compliant_color_space(
color_manager_, name.constData(), buf, size);
});
input_combobox_->setCurrentText(compliant);
}
void ColorSpaceChooser::set_output(const ColorTransform &out)
{
ColorTransform compliant = color_manager_->get_compliant_color_space(out);
QByteArray o, v, l;
oak_color_transform pod = oak_to_transform(out, &o, &v, &l);
int out_is_display = 0;
char out_buf[256], view_buf[256], look_buf[256];
oakengine_color_manager_compliant_transform(
color_manager_, &pod, 0, &out_is_display, out_buf, sizeof(out_buf),
view_buf, sizeof(view_buf), look_buf, sizeof(look_buf));
display_combobox_->setCurrentText(compliant.display());
view_combobox_->setCurrentText(compliant.view());
display_combobox_->setCurrentText(QString::fromUtf8(out_buf));
view_combobox_->setCurrentText(QString::fromUtf8(view_buf));
if (compliant.look().isEmpty()) {
QString look_str = QString::fromUtf8(look_buf);
if (look_str.isEmpty()) {
look_combobox_->setCurrentIndex(0);
} else {
look_combobox_->setCurrentText(compliant.look());
look_combobox_->setCurrentText(look_str);
}
}
@@ -178,7 +219,17 @@ void ColorSpaceChooser::update_views(const QString &display)
view_combobox_->clear();
QStringList views = color_manager_->list_available_views(display);
QByteArray disp = display.toUtf8();
QStringList views = oak_query_string_list(
[this, &disp]() {
return oakengine_color_manager_view_count(color_manager_,
disp.constData());
},
[this, &disp](int i, char *buf, int size) {
return oakengine_color_manager_view_at(color_manager_,
disp.constData(), i, buf,
size);
});
foreach (const QString &s, views) {
view_combobox_->addItem(s);
@@ -189,7 +240,11 @@ void ColorSpaceChooser::update_views(const QString &display)
view_combobox_->setCurrentText(v);
} else {
// Otherwise reset to default view for this display
view_combobox_->setCurrentText(color_manager_->get_default_view(display));
view_combobox_->setCurrentText(
oak_query_string([this, &disp](char *buf, int size) {
return oakengine_color_manager_default_view(
color_manager_, disp.constData(), buf, size);
}));
}
}
+4 -3
View File
@@ -25,7 +25,8 @@
#include <QComboBox>
#include <QGroupBox>
#include "node/color/colormanager/colormanager.h"
#include "oakengine/color.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
namespace olive
{
@@ -33,7 +34,7 @@ namespace olive
class ColorSpaceChooser : public QGroupBox {
Q_OBJECT
public:
ColorSpaceChooser(ColorManager *color_manager,
ColorSpaceChooser(OakEngineColorManager *color_manager,
bool enable_input_field = true,
bool enable_display_fields = true,
QWidget *parent = nullptr);
@@ -55,7 +56,7 @@ private slots:
void update_views(const QString &display);
private:
ColorManager *color_manager_;
OakEngineColorManager *color_manager_;
QComboBox *input_combobox_;
+1 -1
View File
@@ -39,7 +39,7 @@ const Color k_default_colors[k_default_color_count] = {
Color(0.0, 0.0, 0.0)
};
ColorSwatchChooser::ColorSwatchChooser(ColorManager *manager, QWidget *parent)
ColorSwatchChooser::ColorSwatchChooser(OakEngineColorManager *manager, QWidget *parent)
: QWidget(parent)
{
auto layout = new QGridLayout(this);
+2 -2
View File
@@ -22,7 +22,7 @@
#ifndef OAK_COLORSWATCHCHOOSER_H
#define OAK_COLORSWATCHCHOOSER_H
#include "node/color/colormanager/colormanager.h"
#include "oakengine/color.h"
#include "widget/colorbutton/colorbutton.h"
namespace olive
@@ -31,7 +31,7 @@ namespace olive
class ColorSwatchChooser : public QWidget {
Q_OBJECT
public:
ColorSwatchChooser(ColorManager *manager, QWidget *parent = nullptr);
ColorSwatchChooser(OakEngineColorManager *manager, QWidget *parent = nullptr);
public slots:
void set_current_color(const ManagedColor &c)
+4 -4
View File
@@ -40,8 +40,8 @@ const Color &ColorSwatchWidget::get_selected_color() const
return selected_color_;
}
void ColorSwatchWidget::set_color_processor(ColorProcessorPtr to_linear,
ColorProcessorPtr to_display)
void ColorSwatchWidget::set_color_processor(ColorProcessorHandlePtr to_linear,
ColorProcessorHandlePtr to_display)
{
to_linear_processor_ = to_linear;
to_display_processor_ = to_display;
@@ -86,8 +86,8 @@ Qt::GlobalColor ColorSwatchWidget::get_ui_selector_color() const
Color ColorSwatchWidget::get_managed_color(const Color &input) const
{
if (to_linear_processor_ && to_display_processor_) {
return to_display_processor_->convert_color(
to_linear_processor_->convert_color(input));
return oak_convert_color(to_display_processor_,
oak_convert_color(to_linear_processor_, input));
}
return input;
+5 -5
View File
@@ -24,7 +24,7 @@
#include <QOpenGLWidget>
#include "render/colorprocessor.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
namespace olive
{
@@ -36,8 +36,8 @@ public:
const Color &get_selected_color() const;
void set_color_processor(ColorProcessorPtr to_linear,
ColorProcessorPtr to_display);
void set_color_processor(ColorProcessorHandlePtr to_linear,
ColorProcessorHandlePtr to_display);
public slots:
void set_selected_color(const Color &c);
@@ -63,9 +63,9 @@ private:
Color selected_color_;
ColorProcessorPtr to_linear_processor_;
ColorProcessorHandlePtr to_linear_processor_;
ColorProcessorPtr to_display_processor_;
ColorProcessorHandlePtr to_display_processor_;
};
}
+10 -10
View File
@@ -25,14 +25,14 @@
#include <QMouseEvent>
#include <QTabWidget>
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
#include "ui/icons/icons.h"
namespace olive
{
ColorValuesWidget::ColorValuesWidget(ColorManager *manager, QWidget *parent)
ColorValuesWidget::ColorValuesWidget(OakEngineColorManager *manager, QWidget *parent)
: QWidget(parent)
, manager_(manager)
, input_to_ref_(nullptr)
@@ -100,10 +100,10 @@ Color ColorValuesWidget::get_color() const
return reference_tab_->get_color();
}
void ColorValuesWidget::set_color_processor(ColorProcessorPtr input_to_ref,
ColorProcessorPtr ref_to_display,
ColorProcessorPtr display_to_ref,
ColorProcessorPtr ref_to_input)
void ColorValuesWidget::set_color_processor(ColorProcessorHandlePtr input_to_ref,
ColorProcessorHandlePtr ref_to_display,
ColorProcessorHandlePtr display_to_ref,
ColorProcessorHandlePtr ref_to_input)
{
input_to_ref_ = input_to_ref;
ref_to_display_ = ref_to_display;
@@ -200,7 +200,7 @@ void ColorValuesWidget::update_input_from_ref()
{
if (ref_to_input_) {
input_tab_->set_color(
ref_to_input_->convert_color(reference_tab_->get_color()));
oak_convert_color(ref_to_input_, reference_tab_->get_color()));
} else {
input_tab_->set_color(reference_tab_->get_color());
}
@@ -213,7 +213,7 @@ void ColorValuesWidget::update_display_from_ref()
{
if (ref_to_display_) {
display_tab_->set_color(
ref_to_display_->convert_color(reference_tab_->get_color()));
oak_convert_color(ref_to_display_, reference_tab_->get_color()));
} else {
display_tab_->set_color(reference_tab_->get_color());
}
@@ -223,7 +223,7 @@ void ColorValuesWidget::update_ref_from_input()
{
if (input_to_ref_) {
reference_tab_->set_color(
input_to_ref_->convert_color(input_tab_->get_color()));
oak_convert_color(input_to_ref_, input_tab_->get_color()));
} else {
reference_tab_->set_color(input_tab_->get_color());
}
@@ -233,7 +233,7 @@ void ColorValuesWidget::update_ref_from_display()
{
if (display_to_ref_) {
reference_tab_->set_color(
display_to_ref_->convert_color(display_tab_->get_color()));
oak_convert_color(display_to_ref_, display_tab_->get_color()));
} else {
reference_tab_->set_color(display_tab_->get_color());
}
+12 -11
View File
@@ -27,7 +27,8 @@
#include <QWidget>
#include "colorpreviewbox.h"
#include "node/color/colormanager/colormanager.h"
#include "oakengine/color.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/slider/floatslider.h"
#include "widget/slider/stringslider.h"
@@ -87,14 +88,14 @@ private slots:
class ColorValuesWidget : public QWidget {
Q_OBJECT
public:
ColorValuesWidget(ColorManager *manager, QWidget *parent = nullptr);
ColorValuesWidget(OakEngineColorManager *manager, QWidget *parent = nullptr);
Color get_color() const;
void set_color_processor(ColorProcessorPtr input_to_ref,
ColorProcessorPtr ref_to_display,
ColorProcessorPtr display_to_ref,
ColorProcessorPtr ref_to_input);
void set_color_processor(ColorProcessorHandlePtr input_to_ref,
ColorProcessorHandlePtr ref_to_display,
ColorProcessorHandlePtr display_to_ref,
ColorProcessorHandlePtr ref_to_input);
virtual bool eventFilter(QObject *watcher, QEvent *event) override;
@@ -120,7 +121,7 @@ private:
void update_ref_from_display();
ColorManager *manager_;
OakEngineColorManager *manager_;
ColorPreviewBox *preview_;
@@ -130,13 +131,13 @@ private:
ColorValuesTab *display_tab_;
ColorProcessorPtr input_to_ref_;
ColorProcessorHandlePtr input_to_ref_;
ColorProcessorPtr ref_to_display_;
ColorProcessorHandlePtr ref_to_display_;
ColorProcessorPtr display_to_ref_;
ColorProcessorHandlePtr display_to_ref_;
ColorProcessorPtr ref_to_input_;
ColorProcessorHandlePtr ref_to_input_;
QPushButton *color_picker_btn_;
+46 -24
View File
@@ -29,8 +29,11 @@
#include <QtMath>
#include "common/decibel.h"
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "common/qtutils.h"
#include "oakengine/node.h"
#include "widget/keyframeview/keyframehandle.h"
namespace olive
{
@@ -441,22 +444,27 @@ void CurveView::first_chance_mouse_move(QMouseEvent *event)
// If the user is NOT holding control, we set the other handle to the exact negative of this handle
QPointF new_opposing_pos;
NodeKeyframe::BezierType opposing_type =
NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
int opposing_type =
oakengine_keyframe_opposing_bezier_type(dragging_bezier_pt_->type);
if (!(event->modifiers() & Qt::ControlModifier)) {
new_opposing_pos = generate_bezier_control_position(
opposing_type, dragging_bezier_point_opposing_start_,
static_cast<NodeKeyframe::BezierType>(opposing_type),
dragging_bezier_point_opposing_start_,
-mouse_diff_scaled);
} else {
new_opposing_pos = dragging_bezier_point_opposing_start_;
}
dragging_bezier_pt_->keyframe->set_bezier_control(dragging_bezier_pt_->type,
new_bezier_pos);
oakengine_keyframe_set_bezier_point_live(
reinterpret_cast<OakEngineKeyframe *>(dragging_bezier_pt_->keyframe),
dragging_bezier_pt_->type,
new_bezier_pos.x(), new_bezier_pos.y());
dragging_bezier_pt_->keyframe->set_bezier_control(opposing_type,
new_opposing_pos);
oakengine_keyframe_set_bezier_point_live(
reinterpret_cast<OakEngineKeyframe *>(dragging_bezier_pt_->keyframe),
opposing_type,
new_opposing_pos.x(), new_opposing_pos.y());
redraw();
}
@@ -484,13 +492,13 @@ void CurveView::first_chance_mouse_release(QMouseEvent *event)
dragging_bezier_point_start_.y());
if (!(event->modifiers() & Qt::ControlModifier)) {
auto opposing_type =
NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
const QPointF opposing_current = key->bezier_control(opposing_type);
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));
oakengine_node_keyframe_set_bezier_point(
handle, key->input().toUtf8().constData(), key->element(), ts,
key->track(),
(opposing_type == NodeKeyframe::k_in_handle) ? 0 : 1,
opposing_type,
opposing_current.x(), opposing_current.y(),
dragging_bezier_point_opposing_start_.x(),
dragging_bezier_point_opposing_start_.y());
@@ -516,7 +524,10 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
// 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);
key->set_value(drag_keyframe_values_.at(i));
oak_node_value v;
track_value_to_c(key->parent()->get_input_data_type(key->input()),
drag_keyframe_values_.at(i), &v);
key_set_value_live(key, v);
}
return;
}
@@ -559,11 +570,16 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
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);
key->set_value(FloatSlider::transform_display_to_value(
FloatSlider::transform_value_to_display(
drag_keyframe_values_.at(i).toDouble(), display) -
scaled_diff,
display));
oak_node_value v;
track_value_to_c(
key->parent()->get_input_data_type(key->input()),
FloatSlider::transform_display_to_value(
FloatSlider::transform_value_to_display(
drag_keyframe_values_.at(i).toDouble(), display) -
scaled_diff,
display),
&v);
key_set_value_live(key, v);
}
NodeKeyframe *tip_item = get_selected_keyframes().front();
@@ -580,7 +596,7 @@ void CurveView::keyframe_drag_move(QMouseEvent *event, QString &tip)
}
void CurveView::keyframe_drag_release(QMouseEvent *event,
MultiUndoCommand *command)
void *command)
{
Q_UNUSED(command) // the facade pushes its own single command below
@@ -791,12 +807,18 @@ double CurveView::get_offset_from_keyframe(NodeKeyframe *key)
if (node->has_input_property(input, QStringLiteral("offset"))) {
QVariant v = node->get_input_property(input, QStringLiteral("offset"));
// NOTE: Implement getting correct offset for the track based on the data type
QVector<QVariant> track_vals =
NodeValue::split_normal_value_into_track_values(
node->get_input_data_type(input), v);
return track_vals.at(key->track()).toDouble();
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;
}
return 0;
+1 -1
View File
@@ -78,7 +78,7 @@ protected:
virtual void keyframe_drag_start(QMouseEvent *event) override;
virtual void keyframe_drag_move(QMouseEvent *event, QString &tip) override;
virtual void keyframe_drag_release(QMouseEvent *event,
MultiUndoCommand *command) override;
void *command) override;
private:
void zoom_to_fit_internal(bool selected_only);
+2 -1
View File
@@ -30,6 +30,7 @@
#include "core.h"
#include "common/qtutils.h"
#include "node/node.h"
#include "common/nodevaluehandle.h"
#include "oakengine/node.h"
#include "widget/timeruler/timeruler.h"
@@ -275,7 +276,7 @@ void CurveWidget::connect_input_internal(Node *node, const QString &input,
{
NodeInput input_ref(node, input, element);
int track_count =
NodeValue::get_number_of_keyframe_tracks(input_ref.get_data_type());
oakengine_node_value_keyframe_track_count(node_value_type_to_c(input_ref.get_data_type()));
for (int i = 0; i < track_count; i++) {
NodeKeyframeTrackReference track_ref(input_ref, i);
view_->connect_input(track_ref);
+4 -1
View File
@@ -62,7 +62,10 @@ public:
virtual bool paste() override;
public slots:
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);
protected:
+23 -3
View File
@@ -23,7 +23,7 @@
#include <QDir>
#include <QHBoxLayout>
#include "render/lutlibrary.h"
#include "oakengine/lut.h"
namespace olive
{
@@ -68,8 +68,28 @@ void LutFileField::refresh_library_entries()
library_combo_->clear();
library_combo_->addItem(tr("Other (Custom File)..."), QString());
const QStringList library_dirs = LUTLibrary::get_directories();
const QStringList luts = LUTLibrary::get_lut_files();
QStringList library_dirs;
{
int dir_count = oakengine_lut_directory_count();
for (int i = 0; i < dir_count; i++) {
char buf[4096];
int len = oakengine_lut_directory_at(i, buf, sizeof(buf));
if (len > 0) {
library_dirs.append(QString::fromUtf8(buf, len));
}
}
}
QStringList luts;
{
int file_count = oakengine_lut_file_count();
for (int i = 0; i < file_count; i++) {
char buf[4096];
int len = oakengine_lut_file_at(i, buf, sizeof(buf));
if (len > 0) {
luts.append(QString::fromUtf8(buf, len));
}
}
}
for (const QString &lut : luts) {
// Show the path relative to the library directory that contains it
QString display = lut;
@@ -23,7 +23,7 @@
#include <QMouseEvent>
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
namespace olive
+19 -3
View File
@@ -22,6 +22,8 @@
#include "historywidget.h"
#include "core.h"
#include "oakengine/events.h"
#include "oakengine/undo.h"
namespace olive
{
@@ -33,12 +35,26 @@ HistoryWidget::HistoryWidget(QWidget *parent)
this->setModel(stack_);
this->setRootIsDecorated(false);
connect(stack_, &UndoStack::index_changed, this,
&HistoryWidget::index_changed);
undo_sub_ = oakengine_event_subscribe(
oakengine_undo_handle(), OAKENGINE_EVENT_UNDO_INDEX_CHANGED,
[](const oakengine_event *event, void *userdata) {
auto *self = static_cast<HistoryWidget *>(userdata);
self->index_changed(static_cast<int>(event->a));
},
this);
connect(this->selectionModel(), &QItemSelectionModel::currentRowChanged,
this, &HistoryWidget::current_row_changed);
}
HistoryWidget::~HistoryWidget()
{
// Raw subscription carries `this` as userdata; cancel it or the engine
// calls back into a dead widget (the undo stack outlives us).
if (undo_sub_ > 0) {
oakengine_event_unsubscribe(undo_sub_);
}
}
void HistoryWidget::index_changed(int i)
{
this->selectionModel()->select(this->model()->index(i - 1, 0),
@@ -50,7 +66,7 @@ void HistoryWidget::current_row_changed(const QModelIndex &current,
const QModelIndex &previous)
{
size_t jump_to = (current.row() + 1);
stack_->jump(jump_to);
oakengine_undo_jump(static_cast<int64_t>(jump_to));
}
}
+5
View File
@@ -24,6 +24,8 @@
#include <QTreeView>
#include <cstdint>
#include "undo/undostack.h"
namespace olive
@@ -33,10 +35,13 @@ class HistoryWidget : public QTreeView {
Q_OBJECT
public:
HistoryWidget(QWidget *parent = nullptr);
~HistoryWidget() override;
private:
UndoStack *stack_;
int64_t undo_sub_ = 0;
size_t current_row_;
private slots:
+209
View File
@@ -0,0 +1,209 @@
/***
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_KEYFRAMEHANDLE_H
#define OAK_KEYFRAMEHANDLE_H
#include <olive/core/core.h>
#include <QPointF>
#include <QString>
#include "oakengine/node.h"
namespace olive
{
class Node;
class NodeKeyframe;
using olive::core::Rational;
/**
* @brief Facade accessors for keyframe pointers held by the keyframe
* views.
*
* The keyframe/curve views keep olive::NodeKeyframe* as opaque identity
* pointers (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:
* 0 = linear, 1 = bezier, 2 = hold.
*/
inline OakEngineKeyframe *keyhandle(NodeKeyframe *key)
{
return reinterpret_cast<OakEngineKeyframe *>(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)
{
int64_t num = 0, den = 1;
oakengine_keyframe_get_time(keyhandle(key), &num, &den);
return Rational(int(num), int(den));
}
inline int key_easing(const NodeKeyframe *key)
{
return oakengine_keyframe_get_type(keyhandle(key));
}
inline oak_node_value key_value(const NodeKeyframe *key)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
oakengine_keyframe_get_value(keyhandle(key), &v);
return v;
}
inline double key_value_as_double(const NodeKeyframe *key)
{
const oak_node_value v = key_value(key);
switch (v.type) {
case OAK_NODE_VALUE_INT:
case OAK_NODE_VALUE_COMBO:
case OAK_NODE_VALUE_BOOL:
return double(v.num);
case OAK_NODE_VALUE_RATIONAL:
return v.den ? double(v.num) / double(v.den) : 0.0;
default:
return v.f[0];
}
}
inline void key_set_value_live(NodeKeyframe *key, const oak_node_value &v)
{
oakengine_keyframe_set_value_live(keyhandle(key), &v);
}
inline QPointF key_bezier_point(const NodeKeyframe *key, int point_index)
{
double x = 0, y = 0;
oakengine_keyframe_get_bezier_point(keyhandle(key), point_index, &x, &y);
return QPointF(x, y);
}
inline QPointF key_valid_bezier_point(const NodeKeyframe *key,
int point_index)
{
double x = 0, y = 0;
oakengine_keyframe_get_valid_bezier_point(keyhandle(key), point_index,
&x, &y);
return QPointF(x, y);
}
inline void key_set_bezier_point_live(NodeKeyframe *key, int point_index,
const QPointF &point)
{
oakengine_keyframe_set_bezier_point_live(keyhandle(key), point_index,
point.x(), point.y());
}
inline void key_set_time_live(NodeKeyframe *key, const Rational &time)
{
oakengine_keyframe_set_time_live(keyhandle(key), time.numerator(),
time.denominator());
}
inline bool key_has_sibling_at_time(const NodeKeyframe *key,
const Rational &time)
{
return oakengine_keyframe_has_sibling_at_time(
keyhandle(key), time.numerator(), time.denominator()) != 0;
}
inline QString key_input_id(const NodeKeyframe *key)
{
const int size =
oakengine_keyframe_get_input_id(keyhandle(key), nullptr, 0);
QByteArray buf(size + 1, '\0');
oakengine_keyframe_get_input_id(keyhandle(key), buf.data(),
int(buf.size()));
return QString::fromUtf8(buf.constData());
}
inline int key_track(const NodeKeyframe *key)
{
return oakengine_keyframe_get_track(keyhandle(key));
}
inline int key_element(const NodeKeyframe *key)
{
return oakengine_keyframe_get_element(keyhandle(key));
}
/**
* @brief ADL customization points for
* TimeBasedViewSelectionManager<NodeKeyframe>.
*
* 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.
*/
inline Rational selection_time(NodeKeyframe *key)
{
return key_time(key);
}
inline void selection_set_time(NodeKeyframe *key, const Rational &time)
{
key_set_time_live(key, time);
}
inline bool selection_has_sibling_at_time(NodeKeyframe *key,
const Rational &time)
{
return key_has_sibling_at_time(key, time);
}
inline Node *selection_time_target_parent(NodeKeyframe *key)
{
return key_node(key);
}
} // namespace olive
#endif // OAK_KEYFRAMEHANDLE_H
+142 -53
View File
@@ -25,13 +25,16 @@
#include <QToolTip>
#include <QVBoxLayout>
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "common/qtutils.h"
#include "dialog/keyframeproperties/keyframeproperties.h"
#include "node/group/group.h"
#include "keyframehandle.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "node/project/serializer/serializer.h"
#include "node/value.h"
#include "oakengine/node.h"
#include "oakengine/serializer.h"
#include "oakengine/undo.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
@@ -40,6 +43,26 @@ namespace olive
#define super TimeBasedView
static bool KeyframeToOakNodeValue(Node *node, NodeKeyframe *key,
oak_node_value *out)
{
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;
}
KeyframeView::KeyframeView(QWidget *parent)
: super(parent)
, selection_manager_(this)
@@ -58,15 +81,16 @@ KeyframeView::KeyframeView(QWidget *parent)
void KeyframeView::delete_selected()
{
if (!selection_manager_.is_dragging()) {
MultiUndoCommand *command = new MultiUndoCommand();
QVector<OakEngineKeyframe *> keys;
foreach (NodeKeyframe *key, get_selected_keyframes()) {
command->add_child(new NodeParamRemoveKeyframeCommand(key));
keys.append(reinterpret_cast<OakEngineKeyframe *>(key));
}
Core::instance()->undo_stack()->push(
command,
tr("Deleted %1 Keyframe(s)").arg(get_selected_keyframes().size()));
oakengine_keyframes_remove_many(
keys.data(), keys.size(),
tr("Deleted %1 Keyframe(s)")
.arg(keys.size())
.toUtf8()
.constData());
}
}
@@ -86,7 +110,15 @@ KeyframeView::add_keyframes_of_input(Node *on, const QString &oinput)
{
InputConnections vec;
NodeInput resolved = NodeGroup::resolve_input(NodeInput(on, oinput));
OakEngineNode *resolved_node = nullptr;
char resolved_input[256];
int resolved_element = 0;
oakengine_group_resolve_input(
reinterpret_cast<OakEngineNode *>(on), 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();
@@ -202,11 +234,17 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj)
bool KeyframeView::copy_selected(bool cut)
{
if (!selection_manager_.get_selected_objects().empty()) {
ProjectSerializer::SaveData sdata(ProjectSerializer::k_only_keyframes);
sdata.set_only_serialize_keyframes(
selection_manager_.get_selected_objects());
ProjectSerializer::copy(sdata);
const auto &keys = selection_manager_.get_selected_objects();
OakEngineClipboard *cb =
oakengine_clipboard_create(OAKENGINE_CLIPBOARD_KEYFRAMES,
nullptr, nullptr);
oakengine_clipboard_set_keyframes(
cb,
reinterpret_cast<const OakEngineKeyframe *const *>(
keys.data()),
static_cast<int>(keys.size()));
oakengine_clipboard_copy(cb);
oakengine_clipboard_free(cb);
if (cut) {
delete_selected();
@@ -225,57 +263,108 @@ bool KeyframeView::paste(
return false;
}
ProjectSerializer::Result res =
ProjectSerializer::paste(ProjectSerializer::k_only_keyframes);
if (res == ProjectSerializer::k_success) {
const ProjectSerializer::SerializedKeyframes &keys =
res.get_load_data().keyframes;
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_KEYFRAMES, nullptr, nullptr);
int result_code = OAKENGINE_SERIALIZER_NO_DATA;
oakengine_clipboard_paste(cb, OAKENGINE_CLIPBOARD_KEYFRAMES, nullptr,
&result_code, nullptr, 0);
MultiUndoCommand *command = new MultiUndoCommand();
if (result_code == OAKENGINE_SERIALIZER_OK) {
struct PasteCtx {
KeyframeView *self;
void *find_fn;
void *command;
Rational min;
int total;
};
Rational min = RATIONAL_MAX;
for (auto it = keys.cbegin(); it != keys.cend(); it++) {
for (NodeKeyframe *key : it.value()) {
min = std::min(min, key->time());
}
}
min -= get_viewer_node()->get_playhead();
PasteCtx ctx;
ctx.self = this;
ctx.find_fn = &find_node_function;
ctx.command = oakengine_undo_command_create_multi();
ctx.min = RATIONAL_MAX;
ctx.total = 0;
for (auto it = keys.cbegin(); it != keys.cend(); it++) {
const QString &paste_id = it.key();
// First pass: find minimum time
oakengine_clipboard_foreach_keyframe(
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->total++;
return 0;
},
&ctx);
// Find a node with this ID
Node *node_with_id = find_node_function(paste_id);
ctx.min -= ctx.self->get_viewer_node()->get_playhead();
if (node_with_id) {
for (NodeKeyframe *key : it.value()) {
// Adjust sequence time to node's time
Rational t = key->time() - min;
t = get_adjusted_time(get_time_target(), node_with_id, t,
Node::k_transform_towards_input);
key->set_time(t);
// Second pass: process keyframes
oakengine_clipboard_foreach_keyframe(
cb,
[](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 &)> *>(
ctx->find_fn);
Node *node_with_id =
find_fn(QString::fromUtf8(node_id));
if (node_with_id) {
Rational t = key->time() - 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);
if (NodeKeyframe *existing =
node_with_id->get_keyframe_at_time_on_track(
key->input(), key->time(), key->track(),
key->element())) {
command->add_child(
new NodeParamRemoveKeyframeCommand(existing));
void *rm = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(existing));
oakengine_undo_command_multi_add_child(
ctx->command, rm);
}
command->add_child(
new NodeParamInsertKeyframeCommand(node_with_id, key));
oak_node_value v;
KeyframeToOakNodeValue(node_with_id, key, &v);
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(node_with_id),
&tbn, &tbd);
const int64_t time_ts = Timecode::time_to_timestamp(
key->time(), Rational(tbn, tbd), Timecode::k_round);
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()));
oakengine_undo_command_multi_add_child(ctx->command, cmd);
} else {
delete key;
}
} else {
qDeleteAll(it.value());
}
}
Core::instance()->undo_stack()->push(
command, tr("Pasted %1 Keyframe(s)").arg(keys.size()));
return 0;
},
&ctx);
oakengine_undo_push(ctx.command,
tr("Pasted %1 Keyframe(s)")
.arg(ctx.total)
.toUtf8()
.constData());
oakengine_clipboard_free(cb);
return true;
}
oakengine_clipboard_free(cb);
return false;
}
@@ -343,12 +432,12 @@ void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
first_chance_mouse_release(event);
first_chance_mouse_event_ = false;
} else if (selection_manager_.is_dragging()) {
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
selection_manager_.drag_stop(command);
keyframe_drag_release(event, command);
Core::instance()->undo_stack()->push(
oakengine_undo_push(
command, tr("Moved %1 Keyframe(s)")
.arg(selection_manager_.get_selected_objects().size()));
.arg(selection_manager_.get_selected_objects().size()).toUtf8().constData());
} else if (selection_manager_.is_rubber_banding()) {
selection_manager_.rubber_band_stop();
redraw();
+1 -1
View File
@@ -131,7 +131,7 @@ protected:
{
}
virtual void keyframe_drag_release(QMouseEvent *event,
MultiUndoCommand *command)
void *command)
{
}
@@ -34,21 +34,47 @@ KeyframeViewInputConnection::KeyframeViewInputConnection(
, y_(0)
, y_behavior_(k_single_row)
, brush_(Qt::white)
, bridge_(new EngineEventBridge(this))
{
Node *n = input.input().node();
connect(n, &Node::keyframe_added, this,
&KeyframeViewInputConnection::add_keyframe);
connect(n, &Node::keyframe_removed, this,
&KeyframeViewInputConnection::remove_keyframe);
connect(n, &Node::keyframe_time_changed, this,
&KeyframeViewInputConnection::keyframe_changed);
connect(n, &Node::keyframe_type_changed, this,
&KeyframeViewInputConnection::keyframe_changed);
connect(n, &Node::keyframe_type_changed, this,
&KeyframeViewInputConnection::keyframe_type_changed);
connect(n, &Node::keyframe_value_changed, this,
&KeyframeViewInputConnection::keyframe_changed);
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_ADDED);
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_REMOVED);
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_TIME_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_TYPE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(n),
OAKENGINE_EVENT_NODE_KEYFRAME_VALUE_CHANGED);
connect(bridge_, &EngineEventBridge::node_keyframe_added, this,
[this](OakEngineNode *, OakEngineKeyframe *key,
const QString &, int, int) {
add_keyframe(key);
});
connect(bridge_, &EngineEventBridge::node_keyframe_removed, this,
[this](OakEngineNode *, OakEngineKeyframe *key,
const QString &, int, int) {
remove_keyframe(key);
});
connect(bridge_, &EngineEventBridge::node_keyframe_time_changed, this,
[this](OakEngineNode *, OakEngineKeyframe *key) {
keyframe_changed(key);
});
connect(bridge_, &EngineEventBridge::node_keyframe_type_changed, this,
[this](OakEngineNode *, OakEngineKeyframe *key) {
keyframe_changed(key);
});
connect(bridge_, &EngineEventBridge::node_keyframe_type_changed, this,
[this](OakEngineNode *, OakEngineKeyframe *key) {
keyframe_type_changed(key);
});
connect(bridge_, &EngineEventBridge::node_keyframe_value_changed, this,
[this](OakEngineNode *, OakEngineKeyframe *key) {
keyframe_changed(key);
});
}
void KeyframeViewInputConnection::set_keyframe_y(int y)
@@ -78,30 +104,34 @@ void KeyframeViewInputConnection::set_brush(const QBrush &brush)
}
}
void KeyframeViewInputConnection::add_keyframe(NodeKeyframe *key)
void KeyframeViewInputConnection::add_keyframe(OakEngineKeyframe *key)
{
if (key->key_track_ref() == input_) {
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
emit require_update();
}
}
void KeyframeViewInputConnection::remove_keyframe(NodeKeyframe *key)
void KeyframeViewInputConnection::remove_keyframe(OakEngineKeyframe *key)
{
if (key->key_track_ref() == input_) {
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
emit require_update();
}
}
void KeyframeViewInputConnection::keyframe_changed(NodeKeyframe *key)
void KeyframeViewInputConnection::keyframe_changed(OakEngineKeyframe *key)
{
if (key->key_track_ref() == input_) {
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
emit require_update();
}
}
void KeyframeViewInputConnection::keyframe_type_changed(NodeKeyframe *key)
void KeyframeViewInputConnection::keyframe_type_changed(OakEngineKeyframe *key)
{
if (key->key_track_ref() == input_) {
NodeKeyframe *nk = reinterpret_cast<NodeKeyframe *>(key);
if (nk->key_track_ref() == input_) {
emit type_changed();
}
}
@@ -24,9 +24,12 @@
#include <QObject>
#include "engineeventbridge.h"
#include "node/node.h"
#include "node/param.h"
struct OakEngineKeyframe;
namespace olive
{
@@ -85,14 +88,16 @@ private:
QBrush brush_;
EngineEventBridge *bridge_ = nullptr;
private slots:
void add_keyframe(NodeKeyframe *key);
void add_keyframe(OakEngineKeyframe *key);
void remove_keyframe(NodeKeyframe *key);
void remove_keyframe(OakEngineKeyframe *key);
void keyframe_changed(NodeKeyframe *key);
void keyframe_changed(OakEngineKeyframe *key);
void keyframe_type_changed(NodeKeyframe *key);
void keyframe_type_changed(OakEngineKeyframe *key);
};
}
@@ -0,0 +1,246 @@
/***
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_COLORPROCESSORHANDLE_H
#define OAK_COLORPROCESSORHANDLE_H
#include <memory>
#include <QString>
#include <QStringList>
#include <oakengine/color.h>
#include <olive/core/core.h>
#include "render/colortransform.h"
namespace olive
{
class ColorManager;
/**
* @brief App-side replacement for the engine's former ManagedColor class.
*
* The engine-side class was removed during the C ABI migration; this
* header-only equivalent lives entirely in application code (every method
* is inline), so no olive::ManagedColor symbol is imported from the engine
* shared library. It pairs an RGBA color with the color-space input id and
* the output transform, exactly like the original.
*/
class ManagedColor : public Color {
public:
ManagedColor() = default;
ManagedColor(const double &r, const double &g, const double &b,
const double &a = 1.0)
: Color(r, g, b, a)
{
}
ManagedColor(const char *data, const core::PixelFormat &format,
int channel_layout)
: Color(data, format, channel_layout)
{
}
ManagedColor(const Color &c)
: Color(c)
{
}
const QString &color_input() const
{
return color_input_;
}
void set_color_input(const QString &color_input)
{
color_input_ = color_input;
}
const ColorTransform &color_output() const
{
return color_transform_;
}
void set_color_output(const ColorTransform &color_output)
{
color_transform_ = color_output;
}
private:
QString color_input_;
ColorTransform color_transform_;
};
/**
* @brief Opaque-handle replacement for ColorProcessorPtr
*
* The color processor object lives behind the engine's C ABI
* (oakengine/color.h); application code only ever holds this shared
* handle, so no olive::ColorProcessor symbol is imported from the engine
* library.
*/
struct ColorProcessorHandleDeleter {
void operator()(OakEngineColorProcessor *p) const
{
oakengine_color_processor_free(p);
}
};
using ColorProcessorHandlePtr = std::shared_ptr<OakEngineColorProcessor>;
/**
* @brief Reinterpret an engine ColorManager pointer as its borrowed facade
* handle (the same reinterpret pattern the other facade families use).
*/
inline OakEngineColorManager *oak_color_manager(ColorManager *mgr)
{
return reinterpret_cast<OakEngineColorManager *>(mgr);
}
inline const OakEngineColorManager *oak_color_manager(const ColorManager *mgr)
{
return reinterpret_cast<const OakEngineColorManager *>(mgr);
}
/**
* @brief Run a buf/size facade string getter and return the QString.
*/
template <typename Fn> QString oak_query_string(Fn &&fn)
{
const int len = fn(nullptr, 0);
if (len <= 0) {
return QString();
}
QByteArray buf(len + 1, 0);
fn(buf.data(), buf.size());
return QString::fromUtf8(buf.constData(), len);
}
/**
* @brief Run a count/at facade list getter pair and return the QStringList.
*/
template <typename CountFn, typename AtFn>
QStringList oak_query_string_list(CountFn &&count_fn, AtFn &&at_fn)
{
QStringList list;
const int count = count_fn();
list.reserve(count);
for (int i = 0; i < count; i++) {
list.append(oak_query_string(
[&](char *buf, int size) { return at_fn(i, buf, size); }));
}
return list;
}
/**
* @brief Convert an olive::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,
QByteArray *output,
QByteArray *view,
QByteArray *look)
{
*output = t.output().toUtf8();
*view = t.view().toUtf8();
*look = t.look().toUtf8();
oak_color_transform pod;
pod.is_display = t.is_display() ? 1 : 0;
pod.output = output->constData();
pod.view = view->constData();
pod.look = look->constData();
return pod;
}
/**
* @brief ColorManager::get_compliant_color_space(ColorTransform) through
* the facade.
*/
inline ColorTransform oak_compliant_transform(ColorManager *mgr,
const ColorTransform &in,
bool force_display = false)
{
QByteArray o, v, l;
const oak_color_transform pod = oak_to_transform(in, &o, &v, &l);
int is_display = 0;
char out[256], view[256], look[256];
if (oakengine_color_manager_compliant_transform(
oak_color_manager(mgr), &pod, force_display ? 1 : 0, &is_display,
out, sizeof(out), view, sizeof(view), look, sizeof(look)) !=
OAKENGINE_OK) {
return in;
}
if (is_display) {
return ColorTransform(QString::fromUtf8(out), QString::fromUtf8(view),
QString::fromUtf8(look));
}
return ColorTransform(QString::fromUtf8(out));
}
/**
* @brief ColorManager::get_compliant_color_space(QString) through the
* facade.
*/
inline QString oak_compliant_color_space(ColorManager *mgr, const QString &s)
{
const QByteArray name = s.toUtf8();
return oak_query_string([&](char *buf, int size) {
return oakengine_color_manager_compliant_color_space(
oak_color_manager(mgr), name.constData(), buf, size);
});
}
/**
* @brief ColorProcessor::create() through the facade (never throws; an
* OCIO failure yields a handle for which IsValid() is false).
*/
inline ColorProcessorHandlePtr
oak_make_color_processor(ColorManager *mgr, const QString &input,
const ColorTransform &dest,
int direction = OAKENGINE_COLOR_PROCESSOR_NORMAL)
{
QByteArray o, v, l;
const oak_color_transform pod = oak_to_transform(dest, &o, &v, &l);
const QByteArray in = input.toUtf8();
return ColorProcessorHandlePtr(
oakengine_color_processor_create(oak_color_manager(mgr),
in.constData(), &pod, direction),
ColorProcessorHandleDeleter());
}
/**
* @brief ColorProcessor::convert_color() through the facade. A null or
* invalid processor passes the color through unchanged (matching the
* engine's behavior).
*/
inline Color oak_convert_color(const ColorProcessorHandlePtr &proc,
const Color &in)
{
double rgba[4] = { in.red(), in.green(), in.blue(), in.alpha() };
double out[4];
if (oakengine_color_processor_convert_color(proc.get(), rgba, out) ==
OAKENGINE_OK) {
return Color(out[0], out[1], out[2], out[3]);
}
return in;
}
} // namespace olive
#endif // OAK_COLORPROCESSORHANDLE_H
+116 -69
View File
@@ -25,11 +25,11 @@
#include <QMessageBox>
#include "panel/panelmanager.h"
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
#include "render/backend/dynamicrenderer.h"
#endif
#include "render/opengl/openglrenderer.h"
#include "render/rendermanager.h"
#include "oakengine/videoparams.h"
#include "oakengine/renderer.h"
#include "oakengine/display.h"
#include "widget/viewer/vieweroutpututils.h"
#include "common/configwrapper.h"
namespace olive
{
@@ -49,21 +49,24 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
// Create renderer
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
{
auto *dynamic_renderer = new DynamicRenderer(
RenderManager::backend_to_string(
RenderManager::instance()->requested_backend()),
this);
if (!dynamic_renderer->load()) {
const QString backend = oak_query_string([](char *buf, int sz) {
int backend = oakengine_render_manager_requested_backend();
return oakengine_render_manager_backend_to_string(backend, buf,
sz);
});
attached_renderer_ = static_cast<Renderer *>(
oakengine_display_renderer_create_dynamic(
backend.toUtf8().constData(), this));
if (!attached_renderer_) {
qWarning()
<< "Failed to load dynamic render backend for viewer, falling back to OpenGL";
delete dynamic_renderer;
attached_renderer_ = new OpenGLRenderer(this);
} else {
attached_renderer_ = dynamic_renderer;
attached_renderer_ = static_cast<Renderer *>(
oakengine_display_renderer_create_opengl(this));
}
}
#else
attached_renderer_ = new OpenGLRenderer(this);
attached_renderer_ = static_cast<Renderer *>(
oakengine_display_renderer_create_opengl(this));
#endif
if (attached_renderer_->is_open_gl()) {
@@ -120,33 +123,43 @@ ManagedDisplayWidget::~ManagedDisplayWidget()
}
}
void ManagedDisplayWidget::connect_color_manager(ColorManager *color_manager)
void ManagedDisplayWidget::connect_color_manager(OakEngineColorManager *color_manager)
{
if (color_manager_ == color_manager) {
return;
}
if (color_manager_ != nullptr) {
disconnect(color_manager_, &ColorManager::config_changed, this,
&ManagedDisplayWidget::color_config_changed);
disconnect(color_manager_, &ColorManager::reference_space_changed, this,
&ManagedDisplayWidget::color_config_changed);
for (int64_t id : color_subs_) {
oakengine_event_unsubscribe(id);
}
color_subs_.clear();
}
color_manager_ = color_manager;
if (color_manager_ != nullptr) {
connect(color_manager_, &ColorManager::config_changed, this,
&ManagedDisplayWidget::color_config_changed);
connect(color_manager_, &ColorManager::reference_space_changed, this,
&ManagedDisplayWidget::color_config_changed);
auto sub = [this](int ev) {
int64_t id = oakengine_event_subscribe(
color_manager_, ev,
[](const oakengine_event *, void *userdata) {
static_cast<ManagedDisplayWidget *>(userdata)
->color_config_changed();
},
this);
if (id > 0) {
color_subs_.append(id);
}
};
sub(OAKENGINE_EVENT_COLOR_MANAGER_CONFIG_CHANGED);
sub(OAKENGINE_EVENT_COLOR_MANAGER_REFERENCE_SPACE_CHANGED);
}
color_config_changed();
emit color_manager_changed(color_manager_);
}
ColorManager *ManagedDisplayWidget::color_manager() const
OakEngineColorManager *ManagedDisplayWidget::color_manager() const
{
return color_manager_;
}
@@ -163,7 +176,14 @@ const ColorTransform &ManagedDisplayWidget::get_color_transform() const
Menu *ManagedDisplayWidget::get_color_space_menu(QMenu *parent, bool auto_connect)
{
QStringList colorspaces = color_manager()->list_available_colorspaces();
QStringList colorspaces = oak_query_string_list(
[this]() {
return oakengine_color_manager_colorspace_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_colorspace_at(color_manager_, i, buf,
size);
});
Menu *ocio_colorspace_menu = new Menu(tr("Color Space"), parent);
@@ -195,17 +215,27 @@ void ManagedDisplayWidget::color_config_changed()
// which is usually a scene-referred space (e.g. ACEScg / Linear) and makes
// the picture look raw/wrong on a monitor.
if (color_transform_.output().isEmpty()) {
QString display = color_manager_->get_default_display();
QString view = color_manager_->get_default_view(display);
set_color_transform(color_manager_->get_compliant_color_space(
QString display = oak_query_string([this](char *buf, int size) {
return oakengine_color_manager_default_display(color_manager_, buf,
size);
});
QString view = oak_query_string([this, &display](char *buf, int size) {
QByteArray d = display.toUtf8();
return oakengine_color_manager_default_view(color_manager_,
d.constData(), buf,
size);
});
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(display, view, QString()), true));
} else {
set_color_transform(
color_manager_->get_compliant_color_space(color_transform_, false));
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
color_transform_, false));
}
}
ColorProcessorPtr ManagedDisplayWidget::color_service()
ColorProcessorHandlePtr ManagedDisplayWidget::color_service()
{
return color_service_;
}
@@ -232,7 +262,8 @@ void ManagedDisplayWidget::menu_display_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = color_manager()->get_compliant_color_space(
ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(action->data().toString(), old_transform.view(),
old_transform.look()));
@@ -243,7 +274,8 @@ void ManagedDisplayWidget::menu_view_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = color_manager()->get_compliant_color_space(
ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(old_transform.display(), action->data().toString(),
old_transform.look()));
@@ -254,7 +286,8 @@ void ManagedDisplayWidget::menu_look_select(QAction *action)
{
const ColorTransform &old_transform = get_color_transform();
ColorTransform new_transform = color_manager()->get_compliant_color_space(
ColorTransform new_transform = oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(old_transform.display(), old_transform.view(),
action->data().toString()));
@@ -263,14 +296,14 @@ void ManagedDisplayWidget::menu_look_select(QAction *action)
void ManagedDisplayWidget::menu_colorspace_select(QAction *action)
{
set_color_transform(color_manager()->get_compliant_color_space(
set_color_transform(oak_compliant_transform(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ColorTransform(action->data().toString())));
}
void ManagedDisplayWidget::on_destroy()
{
attached_renderer_->destroy();
attached_renderer_->post_destroy();
oakengine_display_renderer_destroy(attached_renderer_);
}
void ManagedDisplayWidget::set_color_transform(const ColorTransform &transform)
@@ -279,7 +312,7 @@ void ManagedDisplayWidget::set_color_transform(const ColorTransform &transform)
setup_color_processor();
ColorProcessorChangedEvent();
color_processor_changed_event();
}
void ManagedDisplayWidget::on_init()
@@ -287,19 +320,9 @@ void ManagedDisplayWidget::on_init()
if (!is_backend_neutral_) {
QOpenGLContext *context =
static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_)->context();
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
if (auto *dynamic_renderer =
dynamic_cast<DynamicRenderer *>(attached_renderer_)) {
dynamic_renderer->init_with_open_gl_context(context);
dynamic_renderer->post_init();
return;
}
#endif
static_cast<OpenGLRenderer *>(attached_renderer_)->init(context);
static_cast<OpenGLRenderer *>(attached_renderer_)->post_init();
oakengine_display_renderer_init(attached_renderer_, context);
} else {
attached_renderer_->init();
attached_renderer_->post_init();
oakengine_display_renderer_init(attached_renderer_, nullptr);
}
}
@@ -309,7 +332,7 @@ void ManagedDisplayWidget::enable_default_context_menu()
&ManagedDisplayWidget::show_default_context_menu);
}
void ManagedDisplayWidget::ColorProcessorChangedEvent()
void ManagedDisplayWidget::color_processor_changed_event()
{
update();
}
@@ -346,8 +369,11 @@ VideoParams ManagedDisplayWidget::get_viewport_params() const
int device_height = height() * devicePixelRatioF();
PixelFormat device_format = static_cast<PixelFormat::Format>(
OAK_CONFIG("OfflinePixelFormat").toInt());
return VideoParams(device_width, device_height, device_format,
VideoParams::k_internal_channel_count);
oak_video_params pod = {};
pod.width = device_width;
pod.height = device_height;
pod.format = device_format;
return video_params_from_pod(pod);
}
void ManagedDisplayWidget::update()
@@ -393,7 +419,14 @@ bool ManagedDisplayWidget::eventFilter(QObject *o, QEvent *e)
Menu *ManagedDisplayWidget::get_display_menu(QMenu *parent, bool auto_connect)
{
QStringList displays = color_manager()->list_available_displays();
QStringList displays = oak_query_string_list(
[this]() {
return oakengine_color_manager_display_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_display_at(color_manager_, i, buf,
size);
});
Menu *ocio_display_menu = new Menu(tr("Display"), parent);
@@ -414,8 +447,17 @@ Menu *ManagedDisplayWidget::get_display_menu(QMenu *parent, bool auto_connect)
Menu *ManagedDisplayWidget::get_view_menu(QMenu *parent, bool auto_connect)
{
QStringList views =
color_manager()->list_available_views(color_transform_.display());
QByteArray disp = color_transform_.display().toUtf8();
QStringList views = oak_query_string_list(
[this, &disp]() {
return oakengine_color_manager_view_count(color_manager_,
disp.constData());
},
[this, &disp](int i, char *buf, int size) {
return oakengine_color_manager_view_at(color_manager_,
disp.constData(), i, buf,
size);
});
Menu *ocio_view_menu = new Menu(tr("View"), parent);
@@ -436,7 +478,14 @@ Menu *ManagedDisplayWidget::get_view_menu(QMenu *parent, bool auto_connect)
Menu *ManagedDisplayWidget::get_look_menu(QMenu *parent, bool auto_connect)
{
QStringList looks = color_manager()->list_available_looks();
QStringList looks = oak_query_string_list(
[this]() {
return oakengine_color_manager_look_count(color_manager_);
},
[this](int i, char *buf, int size) {
return oakengine_color_manager_look_at(color_manager_, i, buf,
size);
});
Menu *ocio_look_menu = new Menu(tr("Look"), parent);
@@ -467,17 +516,15 @@ void ManagedDisplayWidget::setup_color_processor()
color_service_ = nullptr;
if (color_manager_) {
// (Re)create color processor
try {
color_service_ = ColorProcessor::create(
color_manager_, color_manager_->get_reference_color_space(),
color_transform_);
} catch (ocio::Exception &e) {
QMessageBox::critical(
this, tr("OpenColorIO Error"),
tr("Failed to set color configuration: %1").arg(e.what()),
QMessageBox::Ok);
}
// (Re)create color processor. The facade never throws: OCIO failures
// are caught inside the engine and surface as an invalid processor.
QString ref_cs = oak_query_string([this](char *buf, int size) {
return oakengine_color_manager_reference_color_space(
color_manager_, buf, size);
});
color_service_ = oak_make_color_processor(
reinterpret_cast<olive::ColorManager *>(color_manager_),
ref_cs, color_transform_);
} else {
color_service_ = nullptr;
}
+22 -16
View File
@@ -19,8 +19,8 @@
***/
#ifndef OAK_MANAGEDDISPLAYOBJECT_H
#define OAK_MANAGEDDISPLAYOBJECT_H
#ifndef MANAGEDDISPLAYOBJECT_H
#define MANAGEDDISPLAYOBJECT_H
//#define USE_QOPENGLWINDOW
@@ -32,8 +32,12 @@
#include <QOpenGLWidget>
#endif
#include "node/color/colormanager/colormanager.h"
#include "oakengine/color.h"
#include "oakengine/events.h"
#include "render/colorprocessor.h"
#include "render/colortransform.h"
#include "render/renderer.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/menu/menu.h"
namespace olive
@@ -53,9 +57,9 @@ public:
virtual ~ManagedDisplayWidgetOpenGL() override
{
if (context()) {
destroy_listener();
DestroyListener();
disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this,
&ManagedDisplayWidgetOpenGL::destroy_listener);
&ManagedDisplayWidgetOpenGL::DestroyListener);
}
}
@@ -69,7 +73,7 @@ protected:
virtual void initializeGL() override
{
connect(context(), &QOpenGLContext::aboutToBeDestroyed, this,
&ManagedDisplayWidgetOpenGL::destroy_listener,
&ManagedDisplayWidgetOpenGL::DestroyListener,
Qt::DirectConnection);
emit on_init();
@@ -81,7 +85,7 @@ protected:
}
private slots:
void destroy_listener()
void DestroyListener()
{
makeCurrent();
@@ -142,7 +146,7 @@ public:
/**
* @brief Access currently connected ColorManager (nullptr if none)
*/
ColorManager *color_manager() const;
OakEngineColorManager *color_manager() const;
/**
* @brief Get current color transform
@@ -185,18 +189,18 @@ public slots:
/**
* @brief Connect a ColorManager (ColorManagers usually belong to the Project)
*/
void connect_color_manager(ColorManager *color_manager);
void connect_color_manager(OakEngineColorManager *color_manager);
signals:
/**
* @brief Emitted when the color processor changes
*/
void color_processor_changed(ColorProcessorPtr processor);
void color_processor_changed(ColorProcessorHandlePtr processor);
/**
* @brief Emitted when a new color manager is connected
*/
void color_manager_changed(ColorManager *color_manager);
void color_manager_changed(OakEngineColorManager *color_manager);
void frame_swapped();
@@ -204,7 +208,7 @@ protected:
/**
* @brief Provides access to the color processor (nullptr if none is set)
*/
ColorProcessorPtr color_service();
ColorProcessorHandlePtr color_service();
/**
* @brief Enables a context menu that allows simple access to the DVL pipeline
@@ -216,7 +220,7 @@ protected:
*
* Default functionality is just to call update()
*/
virtual void ColorProcessorChangedEvent();
virtual void color_processor_changed_event();
Renderer *renderer() const
{
@@ -304,12 +308,12 @@ private:
/**
* @brief Connected color manager
*/
ColorManager *color_manager_;
OakEngineColorManager *color_manager_;
/**
* @brief Color management service
*/
ColorProcessorPtr color_service_;
ColorProcessorHandlePtr color_service_;
/**
* @brief Internal color transform storage
@@ -318,6 +322,8 @@ private:
bool is_backend_neutral_ = false;
QVector<int64_t> color_subs_;
private slots:
/**
* @brief Sets all color settings to the defaults pertaining to this configuration
@@ -352,4 +358,4 @@ private slots:
}
#endif // OAK_MANAGEDDISPLAYOBJECT_H
#endif // MANAGEDDISPLAYOBJECT_H
+17 -8
View File
@@ -23,19 +23,22 @@
#include <QCoreApplication>
#include "oakengine/node.h"
namespace olive
{
Menu *create_node_menu(QWidget *parent, bool create_none_item,
Node::CategoryID restrict_to, uint64_t restrict_flags)
{
const QList<Node *> &library = NodeFactory::get_library();
const int library_size = oakengine_node_factory_id_count();
Menu *menu = new Menu(parent);
menu->setToolTipsVisible(true);
for (int i = 0; i < library.size(); i++) {
Node *n = library.at(i);
for (int i = 0; i < library_size; i++) {
olive::Node *n = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(i));
if (restrict_to != Node::k_category_unknown &&
!n->category().contains(restrict_to)) {
@@ -54,9 +57,11 @@ Menu *create_node_menu(QWidget *parent, bool create_none_item,
// Make sure nodes are up-to-date with the current translation
n->retranslate();
QString category_name = Node::get_category_name(
n->category().isEmpty() ? Node::k_category_unknown :
n->category().first());
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);
// Find or create top-level category menu
Menu *top_menu = nullptr;
@@ -120,7 +125,9 @@ Node *create_node_from_menu_action(QAction *action)
return nullptr;
}
return NodeFactory::get_library().at(index)->copy();
olive::Node *proto = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(index));
return proto ? proto->copy() : nullptr;
}
QString get_node_id_from_menu_action(QAction *action)
@@ -131,7 +138,9 @@ QString get_node_id_from_menu_action(QAction *action)
return QString();
}
return NodeFactory::get_library().at(action->data().toInt())->id();
olive::Node *proto = reinterpret_cast<olive::Node *>(
oakengine_node_factory_node_at(index));
return proto ? proto->id() : QString();
}
}
+2 -1
View File
@@ -24,7 +24,8 @@
#include <QAction>
#include "node/factory.h"
#include "oakengine/node.h"
#include "node/node.h"
#include "widget/menu/menu.h"
namespace olive
+3 -2
View File
@@ -28,6 +28,7 @@
#include "panel/timeline/timeline.h"
#include "window/mainwindow/mainwindow.h"
#include "oakengine/undo.h"
namespace olive
{
@@ -172,8 +173,8 @@ void MenuShared::add_items_for_new_menu(Menu *m)
void MenuShared::add_items_for_edit_menu(Menu *m, bool for_clips)
{
m->addAction(Core::instance()->undo_stack()->GetUndoAction());
m->addAction(Core::instance()->undo_stack()->GetRedoAction());
m->addAction(reinterpret_cast<QAction*>(oakengine_undo_undo_action()));
m->addAction(reinterpret_cast<QAction*>(oakengine_undo_redo_action()));
m->addSeparator();
+17 -6
View File
@@ -21,6 +21,9 @@
#include "multicamdisplay.h"
#include "oakengine/display.h"
#include "oakengine/node.h"
namespace olive
{
@@ -45,15 +48,20 @@ void MulticamDisplay::on_paint()
p.setBrush(Qt::NoBrush);
int rows, cols;
node_->get_rows_and_columns(&rows, &cols);
oakengine_multicam_get_rows_and_columns(
oakengine_multicam_get_source_count(
reinterpret_cast<OakEngineNode *>(node_)),
&rows, &cols);
int multi = std::max(rows, cols);
int cell_width = width() / multi;
int cell_height = height() / multi;
int col, row;
node_->index_to_row_cols(node_->get_current_source(), rows, cols, &row,
&col);
int current_source = oakengine_multicam_get_current_source(
reinterpret_cast<OakEngineNode *>(node_));
oakengine_multicam_index_to_row_cols(
current_source, rows, cols, &row, &col);
QRect r(cell_width * col, cell_height * row, cell_width, cell_height);
p.drawRect(generate_world_transform().mapRect(r));
@@ -70,10 +78,13 @@ TexturePtr MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
if (v.canConvert<QVector<TexturePtr>>()) {
QVector<TexturePtr> tex = v.value<QVector<TexturePtr>>();
TexturePtr main = renderer()->create_texture(this->get_viewport_params());
TexturePtr main;
const VideoParams main_params = this->get_viewport_params();
oakengine_display_renderer_create_texture(renderer(), &main_params,
nullptr, 0, &main);
int rows, cols;
MultiCamNode::get_rows_and_columns(tex.size(), &rows, &cols);
oakengine_multicam_get_rows_and_columns(tex.size(), &rows, &cols);
if (shader_.isNull() || rows_ != rows || cols_ != cols) {
if (!shader_.isNull()) {
@@ -91,7 +102,7 @@ TexturePtr MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
for (int i = 0; i < tex.size(); i++) {
int c, r;
MultiCamNode::index_to_row_cols(i, rows, cols, &r, &c);
oakengine_multicam_index_to_row_cols(i, rows, cols, &r, &c);
job.insert(QStringLiteral("tex_%1_%2")
.arg(QString::number(r), QString::number(c)),
NodeValue(NodeValue::k_texture, tex.at(i)));
+79 -35
View File
@@ -21,9 +21,14 @@
#include "multicamwidget.h"
#include "oakengine/node.h"
#include <QShortcut>
#include "node/nodeundo.h"
#include "oakengine/events.h"
#include "oakengine/viewer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelineundosplit.h"
#include "widget/timeruler/timeruler.h"
@@ -98,21 +103,41 @@ void MulticamWidget::set_multicam_node(ViewerOutput *viewer, MultiCamNode *n,
void MulticamWidget::ConnectNodeEvent(ViewerOutput *n)
{
connect(n, &ViewerOutput::size_changed, sizer_, &ViewerSizer::set_child_size);
connect(n, &ViewerOutput::pixel_aspect_changed, sizer_,
&ViewerSizer::set_pixel_aspect_ratio);
OakEngineNode *handle = reinterpret_cast<OakEngineNode *>(n);
VideoParams vp = n->get_video_params();
sizer_->set_child_size(vp.width(), vp.height());
sizer_->set_pixel_aspect_ratio(vp.pixel_aspect_ratio());
viewer_sub_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_VIEWER_SIZE_CHANGED,
[](const oakengine_event *event, void *userdata) {
auto *w = static_cast<MulticamWidget *>(userdata);
w->sizer_->set_child_size(int(event->a), int(event->b));
},
this);
viewer_sub2_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED,
[](const oakengine_event *event, void *userdata) {
auto *w = static_cast<MulticamWidget *>(userdata);
w->sizer_->set_pixel_aspect_ratio(
Rational(event->a, event->b));
},
this);
oak_video_params vp;
oakengine_viewer_get_video_params(handle, 0, &vp);
sizer_->set_child_size(vp.width, vp.height);
sizer_->set_pixel_aspect_ratio(
Rational(vp.pixel_aspect_num, vp.pixel_aspect_den));
}
void MulticamWidget::DisconnectNodeEvent(ViewerOutput *n)
{
disconnect(n, &ViewerOutput::size_changed, sizer_,
&ViewerSizer::set_child_size);
disconnect(n, &ViewerOutput::pixel_aspect_changed, sizer_,
&ViewerSizer::set_pixel_aspect_ratio);
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
viewer_sub_ = 0;
}
if (viewer_sub2_ > 0) {
oakengine_event_unsubscribe(viewer_sub2_);
viewer_sub2_ = 0;
}
}
void MulticamWidget::TimeChangedEvent(const Rational &t)
@@ -134,12 +159,11 @@ void MulticamWidget::Switch(int source, bool split_clip)
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
MultiCamNode *cam = node_;
ClipBlock *clip = clip_;
BlockSplitPreservingLinksCommand *split = nullptr;
const QByteArray undo_name = tr("Switched Multi-Camera Source").toUtf8();
oakengine_undo_group_begin(undo_name.constData());
if (clip_ && split_clip &&
clip_->in() < get_connected_node()->get_playhead() &&
@@ -149,33 +173,50 @@ void MulticamWidget::Switch(int source, bool split_clip)
blocks.append(clip_);
blocks.append(clip_->block_links());
split = new BlockSplitPreservingLinksCommand(
blocks, { get_connected_node()->get_playhead() });
split->redo_now();
command->add_child(split);
int split_tbn = 0, split_tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(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(),
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 = static_cast<ClipBlock *>(split->get_split(clip_, 0));
cam = clip->find_multicam();
cam = reinterpret_cast<MultiCamNode *>(
oakengine_clip_find_multicam(reinterpret_cast<OakEngineNode *>(clip)));
}
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(NodeInput(cam, cam->k_current_input)),
source));
oak_node_value val;
memset(&val, 0, sizeof(val));
val.type = OAK_NODE_VALUE_INT;
val.num = source;
for (Block *link : clip->block_links()) {
if (ClipBlock *clink = dynamic_cast<ClipBlock *>(link)) {
if (MultiCamNode *mlink = clink->find_multicam()) {
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(
NodeInput(mlink, mlink->k_current_input)),
source));
if (cam) {
oakengine_node_set_input(
reinterpret_cast<OakEngineNode *>(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)))) {
oakengine_node_set_input(
reinterpret_cast<OakEngineNode *>(mlink),
oakengine_multicam_input_current(), &val);
}
}
}
}
Core::instance()->undo_stack()->push(command,
tr("Switched Multi-Camera Source"));
oakengine_undo_group_end();
display_->update();
@@ -198,14 +239,17 @@ void MulticamWidget::display_clicked(const QPoint &p)
}
int rows, cols;
node_->get_rows_and_columns(&rows, &cols);
oakengine_multicam_get_rows_and_columns(
oakengine_multicam_get_source_count(
reinterpret_cast<OakEngineNode *>(node_)),
&rows, &cols);
int multi = std::max(cols, rows);
int c = click.x() / (width / multi);
int r = click.y() / (height / multi);
int source = node_->rows_cols_to_index(r, c, rows, cols);
int source = oakengine_multicam_rows_cols_to_index(r, c, rows, cols);
Switch(source, true);
}
+5
View File
@@ -23,6 +23,8 @@
#define OAK_MULTICAMWIDGET_H
#include "multicamdisplay.h"
#include <cstdint>
#include "node/input/multicam/multicamnode.h"
#include "widget/viewer/viewer.h"
@@ -58,6 +60,9 @@ private:
ViewerSizer *sizer_;
int64_t viewer_sub_ = 0;
int64_t viewer_sub2_ = 0;
MulticamDisplay *display_;
MultiCamNode *node_;
+5 -2
View File
@@ -25,7 +25,7 @@
#include <QEvent>
#include <QDebug>
#include "node/factory.h"
#include "oakengine/node.h"
#include "ui/icons/icons.h"
#include "widget/menu/factorymenu.h"
#include "widget/menu/menu.h"
@@ -77,7 +77,10 @@ void NodeComboBox::update_text()
clear();
if (!selected_id_.isEmpty()) {
addItem(NodeFactory::get_name_from_id(selected_id_));
char buf[256];
oakengine_node_factory_name_from_id(selected_id_.toUtf8().constData(),
buf, sizeof(buf));
addItem(QString::fromUtf8(buf));
}
}
+241 -108
View File
@@ -25,10 +25,13 @@
#include <QMessageBox>
#include <QScrollArea>
#include <QScrollBar>
#include <QSet>
#include <QSplitter>
#include "node/nodeundo.h"
#include "node/output/viewer/viewer.h"
#include "oakengine/footage.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "widget/timeruler/timeruler.h"
namespace olive
@@ -89,8 +92,15 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent)
c->set_effect_type(static_cast<Track::Type>(i));
title_bar->set_add_effect_button_visible(true);
title_bar->set_text(tr("%1 Nodes")
.arg(Footage::get_stream_type_name(
static_cast<Track::Type>(i))));
.arg(QString::fromUtf8(
[i]() -> QByteArray {
char buf[64];
buf[0] = '\0';
oakengine_footage_stream_type_name(
static_cast<int>(i), buf,
sizeof(buf));
return QByteArray(buf);
}())));
} else {
title_bar->set_text(tr("Other"));
}
@@ -173,6 +183,30 @@ NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent)
&QApplication::focusChanged,
this,
&NodeParamView::FocusChanged);*/
// Connect bridge signals for group input passthrough events
connect(bridge_, &EngineEventBridge::group_input_passthrough_added, this,
[this](OakEngineNode *source, OakEngineNode *node,
const QString &input, int element) {
group_input_passthrough_added(source,
NodeInput(reinterpret_cast<Node *>(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));
});
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));
}, 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));
}, Qt::QueuedConnection);
}
NodeParamView::~NodeParamView()
@@ -276,18 +310,46 @@ void NodeParamView::update_contexts()
if (changes_made) {
current_contexts_ = contexts_;
// Tear down any previous group input passthrough subscriptions before
// (re-)subscribing to the current group, avoiding duplicates across
// repeated calls and stale callbacks after leaving group mode.
if (group_passthrough_added_sub_ > 0) {
bridge_->unsubscribe(group_passthrough_added_sub_);
group_passthrough_added_sub_ = 0;
}
if (group_passthrough_removed_sub_ > 0) {
bridge_->unsubscribe(group_passthrough_removed_sub_);
group_passthrough_removed_sub_ = 0;
}
if (is_group_mode()) {
// Check inputs that have been passed through
NodeGroup *group = static_cast<NodeGroup *>(contexts_.first());
for (auto it = group->get_input_passthroughs().cbegin();
it != group->get_input_passthroughs().cend(); it++) {
group_input_passthrough_added(group, it->second);
Node *group = contexts_.first();
const int pt_count = oakengine_group_input_passthrough_count(
reinterpret_cast<OakEngineNode *>(group));
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,
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),
QString::fromUtf8(inner_input),
inner_element));
}
}
connect(group, &NodeGroup::input_passthrough_added, this,
&NodeParamView::group_input_passthrough_added);
connect(group, &NodeGroup::input_passthrough_removed, this,
&NodeParamView::group_input_passthrough_removed);
OakEngineNode *group_handle = reinterpret_cast<OakEngineNode *>(group);
group_passthrough_added_sub_ = bridge_->subscribe(
group_handle, OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_ADDED);
group_passthrough_removed_sub_ = bridge_->subscribe(
group_handle, OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_REMOVED);
}
foreach (NodeParamViewContext *ctx, context_items_) {
@@ -324,11 +386,12 @@ void NodeParamView::item_clicked()
toggle_select(static_cast<NodeParamViewItem *>(sender()));
}
void NodeParamView::select_node_from_connected_link(Node *node)
void NodeParamView::select_node_from_connected_link(OakEngineNode *node)
{
NodeParamViewItem *item = static_cast<NodeParamViewItem *>(sender());
Node::ContextPair p = { node, item->get_context() };
QPair<OakEngineNode *, OakEngineNode *> p = qMakePair(
node, reinterpret_cast<OakEngineNode *>(item->get_context()));
set_selected_nodes({ p });
}
@@ -388,20 +451,46 @@ void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n)
}
}
void reconnect_outputs_if_not_deleting_node(MultiUndoCommand *c,
NodeViewDeleteCommand *dc, Node *output,
Node *deleting, Node *context)
// Collects the bypass rewiring edges for a node about to be deleted: every
// consumer of `deleting` gets rewired to `output` (the first surviving node
// upstream). The edges are handed to oakengine_nodes_delete_many_ex(), which
// applies them AFTER the deletion inside the same undoable command — the
// target inputs are still occupied until then, so connecting here directly
// would fail with OAKENGINE_E_STATE.
struct ReconnectEdgeList {
QVector<OakEngineNode *> outputs;
QVector<OakEngineNode *> input_nodes;
QVector<QByteArray> ids_storage;
QVector<const char *> ids;
QVector<int> elements;
void finalize_ids()
{
ids.clear();
ids.reserve(ids_storage.size());
for (const QByteArray &b : ids_storage) {
ids.append(b.constData());
}
}
};
void collect_reconnect_edges(QSet<Node *> &deleted_nodes, Node *output,
Node *deleting, ReconnectEdgeList &edges)
{
for (auto it = deleting->output_connections().cbegin();
it != deleting->output_connections().cend(); it++) {
const NodeInput &proposed_reconnect = it->second;
if (dc->contains_node(proposed_reconnect.node(), context)) {
if (deleted_nodes.contains(proposed_reconnect.node())) {
// Uh-oh we're deleting this node too, instead connect to its outputs
reconnect_outputs_if_not_deleting_node(
c, dc, output, proposed_reconnect.node(), context);
collect_reconnect_edges(deleted_nodes, output,
proposed_reconnect.node(), edges);
} else {
c->add_child(new NodeEdgeAddCommand(output, it->second));
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());
}
}
}
@@ -411,19 +500,22 @@ void NodeParamView::DeleteSelected()
if (keyframe_view_ && keyframe_view_->hasFocus()) {
keyframe_view_->delete_selected();
} else if (!selected_nodes_.isEmpty()) {
MultiUndoCommand *c = new MultiUndoCommand();
QVector<OakEngineNode *> nodes;
QVector<OakEngineNode *> contexts;
// Create command to delete node from context and/or graph
NodeViewDeleteCommand *dc = new NodeViewDeleteCommand();
c->add_child(dc);
QSet<Node *> deleted_nodes_set;
// Add all nodes
// Collect all nodes to delete
foreach (NodeParamViewItem *item, selected_nodes_) {
Node *n = item->get_node();
dc->add_node(n, item->get_context());
nodes.append(reinterpret_cast<OakEngineNode *>(n));
contexts.append(reinterpret_cast<OakEngineNode *>(item->get_context()));
deleted_nodes_set.insert(n);
}
// Make reconnections where possible
// Collect bypass rewiring edges (applied after the deletion by the
// facade, inside the same undoable command)
ReconnectEdgeList edges;
foreach (NodeParamViewItem *item, selected_nodes_) {
Node *n = item->get_node();
@@ -435,8 +527,7 @@ void NodeParamView::DeleteSelected()
if ((connected_to_effect_input =
node_being_deleted->get_effect_input()
.get_connected_output())) {
if (dc->contains_node(connected_to_effect_input,
item->get_context())) {
if (deleted_nodes_set.contains(connected_to_effect_input)) {
// Node's getting deleted, recurse
node_being_deleted = connected_to_effect_input;
continue;
@@ -448,13 +539,21 @@ void NodeParamView::DeleteSelected()
}
if (connected_to_effect_input) {
reconnect_outputs_if_not_deleting_node(
c, dc, connected_to_effect_input, n, item->get_context());
collect_reconnect_edges(deleted_nodes_set,
connected_to_effect_input, n, edges);
}
}
edges.finalize_ids();
Core::instance()->undo_stack()->push(
c, tr("Deleted %1 Node(s)").arg(selected_nodes_.size()));
// Delete the nodes and rewire around them in ONE undoable command
oakengine_nodes_delete_many_ex(
nodes.constData(), contexts.constData(), nodes.size(),
nullptr, nullptr, nullptr, nullptr, 0,
edges.outputs.isEmpty() ? nullptr : edges.outputs.constData(),
edges.input_nodes.isEmpty() ? nullptr : edges.input_nodes.constData(),
edges.ids.isEmpty() ? nullptr : edges.ids.constData(),
edges.elements.isEmpty() ? nullptr : edges.elements.constData(),
edges.outputs.size());
}
}
@@ -472,7 +571,7 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
selected_nodes_ = nodes;
QVector<Node::ContextPair> p;
QVector<QPair<OakEngineNode *, OakEngineNode *>> p;
if (emit_signal) {
p.resize(selected_nodes_.size());
}
@@ -482,7 +581,8 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
n->set_highlighted(true);
if (emit_signal) {
p[i] = { n->get_node(), n->get_context() };
p[i] = qMakePair(reinterpret_cast<OakEngineNode *>(n->get_node()),
reinterpret_cast<OakEngineNode *>(n->get_context()));
}
}
@@ -497,7 +597,7 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
}
Node *n = focused_node_ ? focused_node_->get_node() : nullptr;
emit focused_node_changed(n);
emit focused_node_changed(reinterpret_cast<OakEngineNode *>(n));
}
if (emit_signal) {
@@ -505,18 +605,21 @@ void NodeParamView::set_selected_nodes(const QVector<NodeParamViewItem *> &nodes
}
}
void NodeParamView::set_selected_nodes(const QVector<Node::ContextPair> &nodes,
bool emit_signal)
void NodeParamView::set_selected_nodes(
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes,
bool emit_signal)
{
QVector<NodeParamViewItem *> items;
NodeParamViewContext *scrolled_ctx = nullptr;
foreach (const Node::ContextPair &n, nodes) {
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(n.node, n.context);
NodeParamViewItem *item = ctx->get_item(node, context);
if (item) {
items.append(item);
@@ -588,8 +691,8 @@ bool NodeParamView::copy_selected(bool cut)
return false;
}
ProjectSerializer::SaveData sdata(ProjectSerializer::k_only_nodes);
ProjectSerializer::SerializedProperties properties;
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_NODES, nullptr, nullptr);
QVector<Node *> nodes;
for (NodeParamViewItem *item : selected_nodes_) {
@@ -601,19 +704,26 @@ bool NodeParamView::copy_selected(bool cut)
Node::Position pos =
item->get_context()->get_node_position_data_in_context(n);
properties[n][QStringLiteral("x")] =
QString::number(pos.position.x());
properties[n][QStringLiteral("y")] =
QString::number(pos.position.y());
properties[n][QStringLiteral("expanded")] =
QString::number(pos.expanded);
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "x",
QByteArray::number(pos.position.x()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "y",
QByteArray::number(pos.position.y()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "expanded",
QByteArray::number(pos.expanded).constData());
}
}
sdata.set_only_serialize_nodes_and_resolve_groups(nodes);
sdata.set_properties(properties);
oakengine_clipboard_set_nodes(
cb,
reinterpret_cast<const OakEngineNode *const *>(
nodes.constData()),
nodes.size());
ProjectSerializer::copy(sdata);
oakengine_clipboard_copy(cb);
oakengine_clipboard_free(cb);
if (cut) {
DeleteSelected();
@@ -637,20 +747,39 @@ bool NodeParamView::paste()
bool NodeParamView::paste(
QWidget *parent,
std::function<QHash<Node *, Node *>(const ProjectSerializer::Result &)>
std::function<QHash<Node *, Node *>(void *)>
get_existing_map_function)
{
ProjectSerializer::Result res =
ProjectSerializer::paste(ProjectSerializer::k_only_nodes);
if (res.get_load_data().nodes.isEmpty()) {
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_NODES, nullptr, nullptr);
int result_code = OAKENGINE_SERIALIZER_NO_DATA;
oakengine_clipboard_paste(cb, OAKENGINE_CLIPBOARD_NODES, nullptr,
&result_code, nullptr, 0);
if (result_code != OAKENGINE_SERIALIZER_OK) {
oakengine_clipboard_free(cb);
return false;
}
// Collect pasted nodes
QVector<Node *> 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)));
}
if (pasted_nodes.isEmpty()) {
oakengine_clipboard_free(cb);
return false;
}
// Determine if any nodes of this type are already in the editor
QHash<Node *, Node *> existing_nodes = get_existing_map_function(res);
QHash<Node *, Node *> existing_nodes = get_existing_map_function(cb);
QVector<Node *> nodes_to_paste_as_new = res.get_load_data().nodes;
MultiUndoCommand *command = new MultiUndoCommand();
QVector<Node *> nodes_to_paste_as_new = pasted_nodes;
void *command = oakengine_undo_command_create_multi();
if (!existing_nodes.empty()) {
QMessageBox b(parent);
@@ -677,44 +806,31 @@ bool NodeParamView::paste(
b.exec();
if (b.clickedButton() == cancel_btn) {
// Delete pasted nodes and clear array so no later code runs
qDeleteAll(nodes_to_paste_as_new);
nodes_to_paste_as_new.clear();
} else if (b.clickedButton() == as_vals) {
// Filter out existing nodes
for (auto it = existing_nodes.cbegin(); it != existing_nodes.cend();
it++) {
Node::copy_inputs(it.value(), it.key(), false, command);
// 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()));
nodes_to_paste_as_new.removeOne(it.value());
}
}
}
if (!nodes_to_paste_as_new.isEmpty()) {
Node::PositionMap map;
// NOTE: upstream Olive built a Node::PositionMap from the clipboard
// properties here but never applied it (dead code). Dropped during the
// facade migration instead of carrying a placeholder; positions fall
// back to the context default, same as before.
for (auto it = res.get_load_data().properties.cbegin();
it != res.get_load_data().properties.cend(); it++) {
if (nodes_to_paste_as_new.contains(it.key())) {
Node::Position pos;
const QMap<QString, QString> &node_props = it.value();
pos.position.setX(
node_props.value(QStringLiteral("x")).toDouble());
pos.position.setY(
node_props.value(QStringLiteral("y")).toDouble());
pos.expanded =
node_props.value(QStringLiteral("expanded")).toDouble();
map.insert(it.key(), pos);
}
}
}
Core::instance()->undo_stack()->push(
command, tr("Pasted %1 Node(s)").arg(nodes_to_paste_as_new.size()));
oakengine_undo_push(
command, tr("Pasted %1 Node(s)").arg(nodes_to_paste_as_new.size()).toUtf8().constData());
oakengine_clipboard_free(cb);
return true;
}
@@ -737,10 +853,12 @@ 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
connect(ctx, &Node::node_added_to_context, this,
&NodeParamView::node_added_to_context, Qt::QueuedConnection);
connect(ctx, &Node::node_removed_from_context, this,
&NodeParamView::node_removed_from_context, Qt::QueuedConnection);
context_subs_[ctx].first = bridge_->subscribe(
reinterpret_cast<void *>(ctx),
OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT);
context_subs_[ctx].second = bridge_->subscribe(
reinterpret_cast<void *>(ctx),
OAKENGINE_EVENT_NODE_NODE_REMOVED_FROM_CONTEXT);
item->add_context(ctx);
item->setVisible(true);
@@ -753,10 +871,9 @@ void NodeParamView::add_context(Node *ctx)
void NodeParamView::remove_context(Node *ctx)
{
disconnect(ctx, &Node::node_added_to_context, this,
&NodeParamView::node_added_to_context);
disconnect(ctx, &Node::node_removed_from_context, this,
&NodeParamView::node_removed_from_context);
auto subs = context_subs_.take(ctx);
bridge_->unsubscribe(subs.first);
bridge_->unsubscribe(subs.second);
foreach (NodeParamViewContext *item, context_items_) {
item->remove_context(ctx);
@@ -915,18 +1032,22 @@ void NodeParamView::toggle_select(NodeParamViewItem *item)
// no gizmos
focused_node_ = item;
emit focused_node_changed(focused_node_ ? focused_node_->get_node() :
emit focused_node_changed(focused_node_ ? reinterpret_cast<OakEngineNode *>(focused_node_->get_node()) :
nullptr);
}
}
}
QHash<Node *, Node *>
NodeParamView::generate_existing_paste_map(const ProjectSerializer::Result &r)
NodeParamView::generate_existing_paste_map(void *clipboard)
{
QVector<Node *> ignore_nodes;
QHash<Node *, Node *> existing_nodes;
for (Node *n : r.get_load_data().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)) {
existing_nodes.insert(existing, n);
@@ -1004,10 +1125,19 @@ void NodeParamView::update_element_y()
if (!connections.isEmpty()) {
for (const QString &input : node->inputs()) {
if (!(node->get_input_flags(input) & k_input_flag_hidden)) {
int arr_sz =
NodeGroup::resolve_input(NodeInput(node, input))
.get_array_size();
OakEngineNode *out_node = nullptr;
char out_input[256];
int out_element = 0;
int arr_sz = 0;
if (oakengine_group_resolve_input(
reinterpret_cast<OakEngineNode *>(
contexts_.first()),
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);
}
for (int i = -1; i < arr_sz; i++) {
NodeInput ic = { node, input, i };
@@ -1035,9 +1165,8 @@ void NodeParamView::update_element_y()
}
}
void NodeParamView::node_added_to_context(Node *n)
void NodeParamView::node_added_to_context(Node *n, Node *ctx)
{
Node *ctx = static_cast<Node *>(sender());
NodeParamViewContext *item = get_context_item_from_context(ctx);
add_node(n, ctx, item);
@@ -1049,10 +1178,8 @@ void NodeParamView::node_added_to_context(Node *n)
}
}
void NodeParamView::node_removed_from_context(Node *n)
void NodeParamView::node_removed_from_context(Node *n, Node *ctx)
{
Node *ctx = static_cast<Node *>(sender());
foreach (NodeParamViewContext *ctx_item, context_items_) {
ctx_item->remove_node(n, ctx);
}
@@ -1064,16 +1191,22 @@ void NodeParamView::node_removed_from_context(Node *n)
void NodeParamView::input_check_box_changed(const NodeInput &input, bool e)
{
NodeGroup *group = static_cast<NodeGroup *>(contexts_.first());
Node *group = contexts_.first();
if (e) {
group->add_input_passthrough(input);
char out_id[256];
oakengine_group_add_input_passthrough(
reinterpret_cast<OakEngineNode *>(group),
nullptr, input.input().toUtf8().constData(), input.element(),
nullptr, out_id, sizeof(out_id));
} else {
group->remove_input_passthrough(input);
oakengine_group_remove_input_passthrough(
reinterpret_cast<OakEngineNode *>(group),
nullptr, input.input().toUtf8().constData(), input.element());
}
}
void NodeParamView::group_input_passthrough_added(NodeGroup *group,
void NodeParamView::group_input_passthrough_added(OakEngineNode *group,
const NodeInput &input)
{
foreach (NodeParamViewContext *pvctx, context_items_) {
@@ -1081,7 +1214,7 @@ void NodeParamView::group_input_passthrough_added(NodeGroup *group,
}
}
void NodeParamView::group_input_passthrough_removed(NodeGroup *group,
void NodeParamView::group_input_passthrough_removed(OakEngineNode *group,
const NodeInput &input)
{
foreach (NodeParamViewContext *pvctx, context_items_) {
+34 -17
View File
@@ -22,12 +22,12 @@
#ifndef OAK_NODEPARAMVIEW_H
#define OAK_NODEPARAMVIEW_H
#include <QHash>
#include <QVBoxLayout>
#include <QWidget>
#include "node/group/group.h"
#include "node/node.h"
#include "node/project/serializer/serializer.h"
#include "oakengine/serializer.h"
#include "nodeparamviewcontext.h"
#include "nodeparamviewdockarea.h"
#include "nodeparamviewitem.h"
@@ -65,8 +65,9 @@ public:
void set_selected_nodes(const QVector<NodeParamViewItem *> &nodes,
bool handle_focused_node = true,
bool emit_signal = true);
void set_selected_nodes(const QVector<Node::ContextPair> &nodes,
bool emit_signal = true);
void set_selected_nodes(
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,
@@ -82,18 +83,23 @@ public:
virtual bool paste() override;
static bool paste(
QWidget *parent,
std::function<QHash<Node *, Node *>(const ProjectSerializer::Result &)>
std::function<QHash<Node *, Node *>(void *)>
get_existing_map_function);
public slots:
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);
public slots:
void update_element_y();
signals:
void focused_node_changed(Node *n);
void focused_node_changed(OakEngineNode *n);
void selected_nodes_changed(const QVector<Node::ContextPair> &nodes);
void selected_nodes_changed(
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes);
void request_viewer_to_start_editing_text();
@@ -130,6 +136,13 @@ private:
void remove_context(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);
void node_removed_from_context(Node *n, Node *ctx);
void add_node(Node *n, Node *ctx, NodeParamViewContext *context);
void sort_items_in_context(NodeParamViewContext *context);
@@ -139,13 +152,14 @@ private:
bool is_group_mode() const
{
return contexts_.size() == 1 &&
dynamic_cast<NodeGroup *>(contexts_.first());
oakengine_node_is_group(
reinterpret_cast<OakEngineNode *>(contexts_.first()));
}
void toggle_select(NodeParamViewItem *item);
QHash<Node *, Node *>
generate_existing_paste_map(const ProjectSerializer::Result &r);
generate_existing_paste_map(void *clipboard);
KeyframeView *keyframe_view_;
@@ -173,6 +187,13 @@ private:
bool show_all_nodes_;
// Group input passthrough subscription IDs (guarded against duplicate
// subscriptions when update_contexts() is called repeatedly).
int64_t group_passthrough_added_sub_ = 0;
int64_t group_passthrough_removed_sub_ = 0;
QHash<Node *, QPair<int64_t, int64_t>> context_subs_;
private slots:
void update_global_scroll_bar();
@@ -180,16 +201,12 @@ private slots:
//void FocusChanged(QWidget *old, QWidget *now);
void node_added_to_context(Node *n);
void node_removed_from_context(Node *n);
void input_check_box_changed(const NodeInput &input, bool e);
void group_input_passthrough_added(olive::NodeGroup *group,
void group_input_passthrough_added(OakEngineNode *group,
const olive::NodeInput &input);
void group_input_passthrough_removed(olive::NodeGroup *group,
void group_input_passthrough_removed(OakEngineNode *group,
const olive::NodeInput &input);
void update_contexts();
@@ -198,7 +215,7 @@ private slots:
void item_clicked();
void select_node_from_connected_link(Node *node);
void select_node_from_connected_link(OakEngineNode *node);
void request_edit_text_in_viewer();
@@ -35,14 +35,20 @@ NodeParamViewArrayWidget::NodeParamViewArrayWidget(Node *node,
: QWidget(parent)
, node_(node)
, input_(input)
, bridge_(new EngineEventBridge(this))
{
QHBoxLayout *layout = new QHBoxLayout(this);
count_lbl_ = new QLabel();
layout->addWidget(count_lbl_);
connect(node_, &Node::input_array_size_changed, this,
&NodeParamViewArrayWidget::update_counter);
bridge_->subscribe(reinterpret_cast<void *>(node_),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
[this](OakEngineNode *, const QString &input, int old_size,
int new_size) {
update_counter(input, old_size, new_size);
});
update_counter(input_, 0, node_->input_array_size(input_));
}
@@ -26,6 +26,7 @@
#include <QPushButton>
#include <QWidget>
#include "engineeventbridge.h"
#include "node/param.h"
namespace olive
@@ -66,6 +67,8 @@ private:
QLabel *count_lbl_;
EngineEventBridge *bridge_ = nullptr;
private slots:
void update_counter(const QString &input, int old_size, int new_size);
};
@@ -30,6 +30,9 @@
#include "widget/collapsebutton/collapsebutton.h"
#include "widget/menu/menu.h"
#include "oakengine/viewer.h"
#include "oakengine/events.h"
namespace olive
{
@@ -39,6 +42,7 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
, input_(input)
, connected_node_(nullptr)
, viewer_(nullptr)
, bridge_(new EngineEventBridge(this))
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
@@ -79,15 +83,29 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
connected_to_lbl_->setFont(link_font);
if (input_.is_connected()) {
input_connected(input_.get_connected_output(), input_);
input_connected(reinterpret_cast<OakEngineNode *>(input_.get_connected_output()), input_);
} else {
input_disconnected(nullptr, input_);
}
connect(input_.node(), &Node::input_connected, this,
&NodeParamViewConnectedLabel::input_connected);
connect(input_.node(), &Node::input_disconnected, this,
&NodeParamViewConnectedLabel::input_disconnected);
bridge_->subscribe(reinterpret_cast<void *>(input_.node()),
OAKENGINE_EVENT_NODE_INPUT_CONNECTED);
bridge_->subscribe(reinterpret_cast<void *>(input_.node()),
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));
});
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));
});
// Creating the tree is expensive, hold off until the user specifically requests it
value_tree_ = nullptr;
@@ -98,15 +116,21 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(const NodeInput &input,
void NodeParamViewConnectedLabel::set_viewer_node(ViewerOutput *viewer)
{
if (viewer_) {
disconnect(viewer_, &ViewerOutput::playhead_changed, this,
&NodeParamViewConnectedLabel::update_value_tree);
oakengine_event_unsubscribe(viewer_sub_);
viewer_sub_ = 0;
}
viewer_ = viewer;
if (viewer_) {
connect(viewer_, &ViewerOutput::playhead_changed, this,
&NodeParamViewConnectedLabel::update_value_tree);
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(viewer_),
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<NodeParamViewConnectedLabel *>(userdata)
->update_value_tree();
},
this);
update_value_tree();
}
}
@@ -118,19 +142,19 @@ void NodeParamViewConnectedLabel::create_tree()
layout()->addWidget(value_tree_);
}
void NodeParamViewConnectedLabel::input_connected(Node *output,
void NodeParamViewConnectedLabel::input_connected(OakEngineNode *output,
const NodeInput &input)
{
if (input_ != input) {
return;
}
connected_node_ = output;
connected_node_ = reinterpret_cast<Node *>(output);
update_label();
}
void NodeParamViewConnectedLabel::input_disconnected(Node *output,
void NodeParamViewConnectedLabel::input_disconnected(OakEngineNode *output,
const NodeInput &input)
{
if (input_ != input) {
@@ -163,7 +187,7 @@ void NodeParamViewConnectedLabel::show_label_context_menu()
void NodeParamViewConnectedLabel::connection_clicked()
{
if (connected_node_) {
emit request_select_node(connected_node_);
emit request_select_node(reinterpret_cast<OakEngineNode *>(connected_node_));
}
}
@@ -22,10 +22,13 @@
#ifndef OAK_NODEPARAMVIEWCONNECTEDLABEL_H
#define OAK_NODEPARAMVIEWCONNECTEDLABEL_H
#include "engineeventbridge.h"
#include "node/param.h"
#include "widget/clickablelabel/clickablelabel.h"
#include "widget/nodevaluetree/nodevaluetree.h"
#include <cstdint>
namespace olive
{
@@ -38,12 +41,12 @@ public:
void set_viewer_node(ViewerOutput *viewer);
signals:
void request_select_node(Node *n);
void request_select_node(OakEngineNode *n);
private slots:
void input_connected(Node *output, const NodeInput &input);
void input_connected(OakEngineNode *output, const NodeInput &input);
void input_disconnected(Node *output, const NodeInput &input);
void input_disconnected(OakEngineNode *output, const NodeInput &input);
void show_label_context_menu();
@@ -66,6 +69,10 @@ private:
ViewerOutput *viewer_;
EngineEventBridge *bridge_ = nullptr;
int64_t viewer_sub_ = 0;
private slots:
void set_value_tree_visible(bool e);
};
@@ -24,7 +24,8 @@
#include <QMessageBox>
#include "node/block/clip/clip.h"
#include "node/nodeundo.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "widget/menu/factorymenu.h"
namespace olive
@@ -160,7 +161,7 @@ void NodeParamViewContext::add_effect_menu_item_triggered(QAction *a)
if (n) {
NodeInput new_node_input = n->get_effect_input();
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
QVector<Project *> graphs_added_to;
@@ -168,29 +169,51 @@ void NodeParamViewContext::add_effect_menu_item_triggered(QAction *a)
NodeInput ctx_input = ctx->get_effect_input();
if (!graphs_added_to.contains(ctx->parent())) {
command->add_child(new NodeAddCommand(ctx->parent(), n));
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());
}
command->add_child(new NodeSetPositionCommand(
n, ctx, ctx->get_node_position_in_context(ctx)));
command->add_child(new NodeSetPositionCommand(
ctx, ctx, ctx->get_node_position_in_context(ctx) + QPointF(1, 0)));
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));
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));
if (ctx_input.is_connected()) {
Node *prev_output = ctx_input.get_connected_output();
command->add_child(
new NodeEdgeRemoveCommand(prev_output, ctx_input));
command->add_child(
new NodeEdgeAddCommand(prev_output, new_node_input));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_disconnect_command(
reinterpret_cast<OakEngineNode *>(ctx_input.node()),
ctx_input.input().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(),
new_node_input.element()));
}
command->add_child(new NodeEdgeAddCommand(n, ctx_input));
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(),
ctx_input.element()));
}
Core::instance()->undo_stack()->push(
command, tr("Added %1 to Node Chain").arg(n->name()));
oakengine_undo_push(
command, tr("Added %1 to Node Chain").arg(n->name()).toUtf8().constData());
}
}
@@ -68,7 +68,10 @@ public:
signals:
void about_to_delete_item(NodeParamViewItem *item);
public slots:
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)
{
contexts_.append(node);
+71 -25
View File
@@ -26,7 +26,6 @@
#include "common/qtutils.h"
#include "dialog/speedduration/speeddurationdialog.h"
#include "node/group/group.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/node.h"
#include "pluginSupport/oliveplugininstance.h"
@@ -34,6 +33,23 @@
namespace olive
{
static NodeInput ResolveGroupInput(const NodeInput &input)
{
OakEngineNode *node = reinterpret_cast<OakEngineNode *>(input.node());
char input_id[256];
int element = input.element();
const QByteArray utf = input.input().toUtf8();
memcpy(input_id, utf.constData(), qMin<int>(sizeof(input_id) - 1, utf.size()));
input_id[sizeof(input_id) - 1] = '\0';
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);
}
const int NodeParamViewItemBody::k_key_control_column = 10;
const int NodeParamViewItemBody::k_array_insert_column = k_key_control_column - 1;
const int NodeParamViewItemBody::k_array_remove_column = k_array_insert_column - 1;
@@ -59,21 +75,32 @@ NodeParamViewItem::NodeParamViewItem(
, create_checkboxes_(create_checkboxes)
, ctx_(nullptr)
, time_target_(nullptr)
, bridge_(new EngineEventBridge(this))
{
node_->retranslate();
// Create and add contents widget
recreate_body();
connect(node_, &Node::label_changed, this, &NodeParamViewItem::retranslate);
connect(node_, &Node::input_array_size_changed, this,
&NodeParamViewItem::input_array_size_changed);
connect(node_, &Node::message_count_changed, this,
&NodeParamViewItem::update_message_panel);
bridge_->subscribe(reinterpret_cast<void *>(node_),
OAKENGINE_EVENT_NODE_LABEL_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(node_),
OAKENGINE_EVENT_NODE_INPUT_FLAGS_CHANGED);
// FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast
// way of doing this, but "fine" for now.
connect(node_, &Node::input_flags_changed, this,
connect(bridge_, &EngineEventBridge::node_label_changed, this,
&NodeParamViewItem::retranslate);
connect(bridge_, &EngineEventBridge::node_input_array_size_changed, this,
[this](OakEngineNode *, const QString &input, int old_sz,
int new_size) {
emit input_array_size_changed(input, old_sz, new_size);
});
connect(bridge_, &EngineEventBridge::node_message_count_changed, this,
&NodeParamViewItem::update_message_panel);
connect(bridge_, &EngineEventBridge::node_input_flags_changed, this,
&NodeParamViewItem::recreate_body);
setBackgroundRole(QPalette::Window);
@@ -224,6 +251,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(
, node_(node)
, time_target_(nullptr)
, create_checkboxes_(create_checkboxes)
, bridge_(new EngineEventBridge(this))
{
QGridLayout *root_layout = new QGridLayout(this);
@@ -233,18 +261,35 @@ NodeParamViewItemBody::NodeParamViewItemBody(
QVector<Node *> connected_signals;
connect(bridge_, &EngineEventBridge::node_input_array_size_changed,
this, &NodeParamViewItemBody::input_array_size_changed);
connect(bridge_, &EngineEventBridge::node_input_connected, this,
[this](OakEngineNode *source, OakEngineNode *output,
const QString &input, int element) {
edge_changed(output,
NodeInput(reinterpret_cast<Node *>(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));
});
// Create widgets all root level components
foreach (QString input, node->inputs()) {
Node *n = node;
NodeInput resolved = NodeGroup::resolve_input(NodeInput(n, input));
NodeInput resolved = ResolveGroupInput(NodeInput(n, input));
if (!connected_signals.contains(resolved.node())) {
connect(resolved.node(), &Node::input_array_size_changed, this,
&NodeParamViewItemBody::input_array_size_changed);
connect(resolved.node(), &Node::input_connected, this,
&NodeParamViewItemBody::edge_changed);
connect(resolved.node(), &Node::input_disconnected, this,
&NodeParamViewItemBody::edge_changed);
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
OAKENGINE_EVENT_NODE_INPUT_CONNECTED);
bridge_->subscribe(reinterpret_cast<void *>(resolved.node()),
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED);
connected_signals.append(resolved.node());
}
@@ -402,7 +447,7 @@ 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 = NodeGroup::resolve_input(input_ref);
NodeInput resolved = ResolveGroupInput(input_ref);
if (node->is_input_connectable(input)) {
// Create clickable label used when an input is connected
@@ -492,7 +537,7 @@ int NodeParamViewItemBody::get_element_y(NodeInput c) const
return lbl_center.y();
}
void NodeParamViewItemBody::edge_changed(Node *output, const NodeInput &input)
void NodeParamViewItemBody::edge_changed(OakEngineNode *output, const NodeInput &input)
{
Q_UNUSED(output)
@@ -509,7 +554,7 @@ void NodeParamViewItemBody::update_ui_for_edge_connection(const NodeInput &input
if (input_ui_map_.contains(input)) {
const InputUI &ui_objects = input_ui_map_[input];
bool is_connected = NodeGroup::resolve_input(input).is_connected();
bool is_connected = ResolveGroupInput(input).is_connected();
foreach (QWidget *w, ui_objects.widget_bridge->widgets()) {
w->setVisible(!is_connected);
@@ -604,7 +649,7 @@ void NodeParamViewItemBody::array_collapse_btn_pressed(bool checked)
if (checked) {
// Ensure widgets are created (the signal will be ignored if they are)
NodeInput resolved =
NodeGroup::resolve_input(NodeInput(input.node, input.input));
ResolveGroupInput(NodeInput(input.node, input.input));
input_array_size_changed_internal(input.node, input.input,
resolved.get_array_size());
}
@@ -612,13 +657,14 @@ void NodeParamViewItemBody::array_collapse_btn_pressed(bool checked)
emit array_expanded_changed(checked);
}
void NodeParamViewItemBody::input_array_size_changed(const QString &input,
void NodeParamViewItemBody::input_array_size_changed(OakEngineNode *source,
const QString &input,
int old_sz, int size)
{
Q_UNUSED(old_sz)
NodeInputPair nip =
input_group_lookup_.value({ static_cast<Node *>(sender()), input });
input_group_lookup_.value({ reinterpret_cast<Node *>(source), input });
input_array_size_changed_internal(nip.node, nip.input, size);
}
@@ -627,7 +673,7 @@ 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 = NodeGroup::resolve_input(
NodeInput real_input = ResolveGroupInput(
NodeInput(it.key().node, it.key().input));
// Through the liboakengine C ABI facade (one undoable command,
// same as the old NodeArrayInsertCommand push).
@@ -645,7 +691,7 @@ 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 = NodeGroup::resolve_input(it.key());
NodeInput ic = ResolveGroupInput(it.key());
// Through the liboakengine C ABI facade (one undoable command).
oakengine_node_array_insert_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
@@ -660,7 +706,7 @@ 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 = NodeGroup::resolve_input(it.key());
NodeInput ic = ResolveGroupInput(it.key());
// Through the liboakengine C ABI facade (one undoable command).
oakengine_node_array_remove_at(
reinterpret_cast<OakEngineNode *>(ic.node()),
+10 -4
View File
@@ -30,6 +30,7 @@
#include <QWidget>
#include "node/node.h"
#include "engineeventbridge.h"
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewconnectedlabel.h"
#include "nodeparamviewkeyframecontrol.h"
@@ -67,7 +68,7 @@ public:
void set_input_checked(const NodeInput &input, bool e);
signals:
void request_select_node(Node *node);
void request_select_node(OakEngineNode *node);
void array_expanded_changed(bool e);
@@ -128,6 +129,8 @@ private:
QHash<NodeInputPair, NodeInputPair> input_group_lookup_;
EngineEventBridge *bridge_ = nullptr;
/**
* @brief The column to place the keyframe controls in
*
@@ -147,11 +150,12 @@ private:
static const int k_max_widget_column;
private slots:
void edge_changed(Node *output, const NodeInput &input);
void edge_changed(OakEngineNode *output, const NodeInput &input);
void array_collapse_btn_pressed(bool checked);
void input_array_size_changed(const QString &input, int old_sz, int size);
void input_array_size_changed(OakEngineNode *source,
const QString &input, int old_sz, int size);
void array_append_clicked();
@@ -219,7 +223,7 @@ public:
}
signals:
void request_select_node(Node *node);
void request_select_node(OakEngineNode *node);
void array_expanded_changed(bool e);
@@ -251,6 +255,8 @@ private:
KeyframeView::NodeConnections keyframe_connections_;
EngineEventBridge *bridge_ = nullptr;
private slots:
void recreate_body();
void update_message_panel();
@@ -24,16 +24,31 @@
#include <QHBoxLayout>
#include <QMessageBox>
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "core.h"
#include "node/nodeundo.h"
#include "node/value.h"
#include "oakengine/events.h"
#include "oakengine/undo.h"
#include "oakengine/viewer.h"
#include "oakengine/node.h"
#include "ui/icons/icons.h"
namespace olive
{
static int64_t rational_to_node_ts(Node *node, const Rational &time)
{
int num = 0, den = 1;
oakengine_node_frame_time_base(reinterpret_cast<OakEngineNode *>(node),
&num, &den);
return core::Timecode::time_to_timestamp(time, Rational(num, den));
}
NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
QWidget *parent)
: QWidget(parent)
, bridge_(new EngineEventBridge(this))
{
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
@@ -73,6 +88,21 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
connect(enable_key_btn_, &QPushButton::clicked, this,
&NodeParamViewKeyframeControl::keyframe_enable_btn_clicked);
connect(bridge_, &EngineEventBridge::node_keyframe_enable_changed, this,
[this](OakEngineNode *source, const QString &input, int element,
bool enabled) {
keyframe_enable_changed(
NodeInput(reinterpret_cast<Node *>(source), input,
element),
enabled);
});
connect(bridge_, &EngineEventBridge::node_keyframe_added, this,
&NodeParamViewKeyframeControl::update_state);
connect(bridge_, &EngineEventBridge::node_keyframe_removed, this,
&NodeParamViewKeyframeControl::update_state);
connect(bridge_, &EngineEventBridge::node_keyframe_time_changed, this,
&NodeParamViewKeyframeControl::update_state);
// Set defaults
set_input(NodeInput());
show_buttons_from_keyframe_enable(false);
@@ -81,14 +111,14 @@ NodeParamViewKeyframeControl::NodeParamViewKeyframeControl(bool right_align,
void NodeParamViewKeyframeControl::set_input(const NodeInput &input)
{
if (input_.is_valid()) {
disconnect(input_.node(), &Node::keyframe_enable_changed, this,
&NodeParamViewKeyframeControl::keyframe_enable_changed);
disconnect(input_.node(), &Node::keyframe_added, this,
&NodeParamViewKeyframeControl::update_state);
disconnect(input_.node(), &Node::keyframe_removed, this,
&NodeParamViewKeyframeControl::update_state);
disconnect(input_.node(), &Node::keyframe_time_changed, this,
&NodeParamViewKeyframeControl::update_state);
bridge_->unsubscribe(keyframe_enable_sub_);
bridge_->unsubscribe(keyframe_added_sub_);
bridge_->unsubscribe(keyframe_removed_sub_);
bridge_->unsubscribe(keyframe_time_sub_);
keyframe_enable_sub_ = 0;
keyframe_added_sub_ = 0;
keyframe_removed_sub_ = 0;
keyframe_time_sub_ = 0;
}
input_ = input;
@@ -101,27 +131,39 @@ void NodeParamViewKeyframeControl::set_input(const NodeInput &input)
update_state();
if (input_.is_valid()) {
connect(input_.node(), &Node::keyframe_enable_changed, this,
&NodeParamViewKeyframeControl::keyframe_enable_changed);
connect(input_.node(), &Node::keyframe_added, this,
&NodeParamViewKeyframeControl::update_state);
connect(input_.node(), &Node::keyframe_removed, this,
&NodeParamViewKeyframeControl::update_state);
connect(input_.node(), &Node::keyframe_time_changed, this,
&NodeParamViewKeyframeControl::update_state);
keyframe_enable_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
OAKENGINE_EVENT_NODE_KEYFRAME_ENABLE_CHANGED);
keyframe_added_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
OAKENGINE_EVENT_NODE_KEYFRAME_ADDED);
keyframe_removed_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
OAKENGINE_EVENT_NODE_KEYFRAME_REMOVED);
keyframe_time_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(input_.node()),
OAKENGINE_EVENT_NODE_KEYFRAME_TIME_CHANGED);
}
}
void NodeParamViewKeyframeControl::TimeTargetDisconnectEvent(ViewerOutput *v)
{
disconnect(v, &ViewerOutput::playhead_changed, this,
&NodeParamViewKeyframeControl::update_state);
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
viewer_sub_ = 0;
}
}
void NodeParamViewKeyframeControl::TimeTargetConnectEvent(ViewerOutput *v)
{
connect(v, &ViewerOutput::playhead_changed, this,
&NodeParamViewKeyframeControl::update_state);
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(v),
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<NodeParamViewKeyframeControl *>(userdata)
->update_state();
},
this);
update_state();
}
@@ -171,39 +213,64 @@ void NodeParamViewKeyframeControl::toggle_keyframe(bool e)
QVector<NodeKeyframe *> keys =
input_.node()->get_keyframes_at_time(input_, node_time);
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
int nb_tracks = input_.node()->get_number_of_keyframe_tracks(input_);
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 char *input_id = input_utf8.constData();
const int element = input_.element();
const int64_t time_ts = rational_to_node_ts(node, node_time);
if (e && keys.isEmpty()) {
// Add a keyframe here (one for each track)
for (int i = 0; i < nb_tracks; i++) {
NodeKeyframe *key = new NodeKeyframe(
node_time,
input_.node()->get_split_value_at_time_on_track(input_, node_time, i),
input_.node()->get_best_keyframe_type_for_time_on_track(input_,
node_time, i),
i, input_.element(), input_.input());
oak_node_value v;
if (oakengine_node_get_input_at_time(
reinterpret_cast<OakEngineNode *>(node), input_id, element, -1,
time_ts, 1, &v) != OAKENGINE_OK) {
oakengine_undo_command_free(command);
return;
}
command->add_child(
new NodeParamInsertKeyframeCommand(input_.node(), key));
for (int i = 0; i < nb_tracks; i++) {
void *cmd = oakengine_node_insert_keyframe_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, i,
time_ts, &v,
NodeKeyframeTypeToFacade(
node->get_best_keyframe_type_for_time_on_track(input_,
node_time, i)),
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) {
command->add_child(new NodeParamRemoveKeyframeCommand(key));
void *cmd = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(key));
oakengine_undo_command_multi_add_child(command, cmd);
if (input_.node()->get_keyframe_tracks(input_).size() == 1) {
// If this was the last keyframe on this track, set the standard value to the value at this time too
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(input_, key->track()),
input_.node()->get_split_value_at_time_on_track(input_, node_time,
key->track())));
if (node->get_keyframe_tracks(input_).size() == 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);
}
}
}
Core::instance()->undo_stack()->push(command, tr("Toggled Keyframe"));
oakengine_undo_push(command, tr("Toggled Keyframe").toUtf8().constData());
}
void NodeParamViewKeyframeControl::update_state()
@@ -232,7 +299,9 @@ void NodeParamViewKeyframeControl::go_to_previous_key()
if (previous_key && get_time_target()) {
Rational key_time = convert_to_viewer_time(previous_key->time());
get_time_target()->set_playhead(key_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_time_target()),
key_time.numerator(), key_time.denominator());
}
}
@@ -245,7 +314,9 @@ void NodeParamViewKeyframeControl::go_to_next_key()
if (next_key && get_time_target()) {
Rational key_time = convert_to_viewer_time(next_key->time());
get_time_target()->set_playhead(key_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_time_target()),
key_time.numerator(), key_time.denominator());
}
}
@@ -256,71 +327,106 @@ void NodeParamViewKeyframeControl::keyframe_enable_btn_clicked(bool e)
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
Node *node = input_.node();
const NodeValue::Type declared = node->get_input_data_type(input_.input());
const QByteArray input_utf8 = input_.input().toUtf8();
const char *input_id = input_utf8.constData();
const int element = input_.element();
QString command_name;
if (e) {
// Enable keyframing
command->add_child(new NodeParamSetKeyframingCommand(input_, true));
void *command = oakengine_undo_command_create_multi();
void *kf = oakengine_node_set_input_keyframing_command(
reinterpret_cast<OakEngineNode *>(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 =
input_.node()->get_split_standard_value(input_);
const QVector<QVariant> &key_vals = node->get_split_standard_value(input_);
for (int i = 0; i < key_vals.size(); i++) {
NodeKeyframe *key =
new NodeKeyframe(get_current_time_as_node_time(), key_vals.at(i),
NodeKeyframe::k_default_type, i,
input_.element(), input_.input());
command->add_child(
new NodeParamInsertKeyframeCommand(input_.node(), key));
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++) {
void *cmd = oakengine_node_insert_keyframe_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, i,
time_ts, &v, type, 0, 0, 0, 0);
oakengine_undo_command_multi_add_child(command, cmd);
}
}
command_name =
tr("Enabled Keyframing On %1 - %2")
.arg(input_.node()->get_label_and_name(), input_.get_input_name());
.arg(node->get_label_and_name(), input_.get_input_name());
oakengine_undo_push(command, command_name.toUtf8().constData());
} else {
// Confirm the user wants to clear all keyframes
if (QMessageBox::warning(
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) {
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 =
input_.node()->get_split_value_at_time(input_,
get_current_time_as_node_time());
node->get_split_value_at_time(input_,
get_current_time_as_node_time());
// Delete all keyframes
foreach (const NodeKeyframeTrack &track,
input_.node()->get_keyframe_tracks(input_)) {
node->get_keyframe_tracks(input_)) {
for (int i = track.size() - 1; i >= 0; i--) {
command->add_child(
new NodeParamRemoveKeyframeCommand(track.at(i)));
void *cmd = oakengine_node_remove_keyframe_command(
reinterpret_cast<OakEngineKeyframe *>(track.at(i)));
oakengine_undo_command_multi_add_child(command, cmd);
}
}
// Update standard value
for (int i = 0; i < stored_vals.size(); i++) {
command->add_child(new NodeParamSetStandardValueCommand(
NodeKeyframeTrackReference(input_, i), stored_vals.at(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);
}
// Disable keyframing
command->add_child(
new NodeParamSetKeyframingCommand(input_, false));
void *kf = oakengine_node_set_input_keyframing_command(
reinterpret_cast<OakEngineNode *>(node), input_id, element, 0);
oakengine_undo_command_multi_add_child(command, kf);
command_name = tr("Disabled Keyframing On %1 - %2")
.arg(input_.node()->get_label_and_name(),
input_.get_input_name());
.arg(node->get_label_and_name(),
input_.get_input_name());
oakengine_undo_push(command, command_name.toUtf8().constData());
} else {
// Disable action has effectively been ignored
enable_key_btn_->setChecked(true);
}
}
Core::instance()->undo_stack()->push(command, command_name);
}
void NodeParamViewKeyframeControl::keyframe_enable_changed(const NodeInput &input,
@@ -24,7 +24,9 @@
#include <QPushButton>
#include <QWidget>
#include <cstdint>
#include "engineeventbridge.h"
#include "node/param.h"
#include "widget/timetarget/timetarget.h"
@@ -67,6 +69,15 @@ private:
NodeInput input_;
EngineEventBridge *bridge_ = nullptr;
int64_t keyframe_enable_sub_ = 0;
int64_t keyframe_added_sub_ = 0;
int64_t keyframe_removed_sub_ = 0;
int64_t keyframe_time_sub_ = 0;
int64_t viewer_sub_ = 0;
private slots:
void show_buttons_from_keyframe_enable(bool e);
@@ -31,19 +31,24 @@
#include "common/qtutils.h"
#include "core.h"
#include "nodeparambutton.h"
#include "node/group/group.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "node/project/sequence/sequence.h"
#include "nodeparamviewarraywidget.h"
#include "nodeparamviewtextedit.h"
#include "oakengine/events.h"
#include "common/nodevaluehandle.h"
#include "common/oakvaluehelper.h"
#include "oakengine/node.h"
#include "render/lutlibrary.h"
#include "oakengine/plugin.h"
#include "oakengine/viewer.h"
#include "oakengine/lut.h"
#include "oakengine/undo.h"
#include "undo/undostack.h"
#include "widget/bezier/bezierwidget.h"
#include "widget/colorbutton/colorbutton.h"
#include "widget/filefield/filefield.h"
#include "widget/filefield/lutfilefield.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/slider/floatslider.h"
#include "widget/slider/integerslider.h"
#include "widget/slider/rationalslider.h"
@@ -53,27 +58,150 @@
namespace olive
{
static int64_t rational_to_node_ts(Node *node, const Rational &time)
{
int num = 0, den = 1;
oakengine_node_frame_time_base(reinterpret_cast<OakEngineNode *>(node),
&num, &den);
return core::Timecode::time_to_timestamp(time, Rational(num, den));
}
static QVariant GetInputValueAtTime(const NodeInput &input,
const Rational &node_time)
{
Node *node = input.node();
if (!node) {
return QVariant();
}
const NodeValue::Type type = node->get_input_data_type(input.input());
const QByteArray input_utf8 = input.input().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);
const OakEngineNode *enode = reinterpret_cast<OakEngineNode *>(node);
switch (type) {
case NodeValue::k_int:
case NodeValue::k_float:
case NodeValue::k_boolean:
case NodeValue::k_rational:
case NodeValue::k_color:
case NodeValue::k_vec2:
case NodeValue::k_vec3:
case NodeValue::k_vec4:
case NodeValue::k_combo: {
oak_node_value v;
if (oakengine_node_get_input_at_time(enode, input_id, element, -1,
time_ts, 1, &v) ==
OAKENGINE_OK) {
return OakNodeValueToQVariant(v);
}
break;
}
case NodeValue::k_file:
case NodeValue::k_text:
case NodeValue::k_font:
case NodeValue::k_str_combo: {
char buf[4096];
const int len = oakengine_node_get_input_string_at_time(
enode, input_id, element, time_ts, 0, buf, sizeof(buf));
if (len >= 0) {
return QString::fromUtf8(buf, len);
}
break;
}
case NodeValue::k_binary: {
const int len = oakengine_node_get_input_binary_at_time(
enode, input_id, element, time_ts, 0, nullptr, 0);
if (len > 0) {
QByteArray bytes(len, '\0');
oakengine_node_get_input_binary_at_time(
enode, input_id, element, time_ts, 0, bytes.data(), len);
return bytes;
} else if (len == 0) {
return QByteArray();
}
break;
}
case NodeValue::k_bezier: {
double out[6];
if (oakengine_node_get_input_bezier_at_time(
enode, input_id, element, time_ts, 0, out) == OAKENGINE_OK) {
return QVariant::fromValue(
Bezier(out[0], out[1], out[2], out[3], out[4], out[5]));
}
break;
}
default:
break;
}
return QVariant();
}
static bool ResolveGroupInput(NodeInput *input)
{
OakEngineNode *node = reinterpret_cast<OakEngineNode *>(input->node());
char input_id[256];
int element = input->element();
memcpy(input_id, input->input().toUtf8().constData(),
qMin<int>(sizeof(input_id) - 1, input->input().toUtf8().size()));
input_id[sizeof(input_id) - 1] = '\0';
if (!oakengine_node_group_get_inner(&node, input_id, sizeof(input_id),
&element)) {
return false;
}
*input = NodeInput(reinterpret_cast<Node *>(node),
QString::fromUtf8(input_id), element);
return true;
}
NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input,
QObject *parent)
: QObject(parent)
, bridge_(new EngineEventBridge(this))
{
connect(bridge_, &EngineEventBridge::node_input_value_changed, this,
&NodeParamViewWidgetBridge::input_value_changed);
connect(bridge_, &EngineEventBridge::node_input_property_changed, this,
[this](OakEngineNode *, const QString &input) {
property_changed(input);
});
connect(bridge_, &EngineEventBridge::node_input_data_type_changed, this,
&NodeParamViewWidgetBridge::input_data_type_changed);
do {
input_hierarchy_.append(input);
connect(input.node(), &Node::value_changed, this,
&NodeParamViewWidgetBridge::input_value_changed);
connect(input.node(), &Node::input_property_changed, this,
&NodeParamViewWidgetBridge::property_changed);
connect(input.node(), &Node::input_data_type_changed, this,
&NodeParamViewWidgetBridge::input_data_type_changed);
} while (NodeGroup::get_inner(&input));
bridge_->subscribe(reinterpret_cast<void *>(input.node()),
OAKENGINE_EVENT_NODE_INPUT_VALUE_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(input.node()),
OAKENGINE_EVENT_NODE_INPUT_PROPERTY_CHANGED);
bridge_->subscribe(reinterpret_cast<void *>(input.node()),
OAKENGINE_EVENT_NODE_INPUT_DATA_TYPE_CHANGED);
} while (ResolveGroupInput(&input));
dragger_ = oakengine_dragger_create(
reinterpret_cast<OakEngineNode *>(get_inner_input().node()),
get_inner_input().input().toUtf8().constData(),
get_inner_input().element(),
-1); // track set in process_slider at start time
create_widgets();
}
NodeParamViewWidgetBridge::~NodeParamViewWidgetBridge()
{
// oakengine_dragger_create() ownership is ours (no-op on NULL)
oakengine_dragger_free(dragger_);
// Raw viewer subscription carries `this` as userdata
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
}
}
int get_slider_count(NodeValue::Type type)
{
return NodeValue::get_number_of_keyframe_tracks(type);
return oakengine_node_value_keyframe_track_count(node_value_type_to_c(type));
}
namespace
@@ -231,7 +359,7 @@ void NodeParamViewWidgetBridge::create_widgets()
create_sliders<FloatSlider>(4, parent);
} else {
ColorButton *color_button = new ColorButton(
get_inner_input().node()->project()->color_manager(), parent);
oak_color_manager(get_inner_input().node()->project()->color_manager()), parent);
widgets_.append(color_button);
connect(color_button, &ColorButton::color_changed, this,
&NodeParamViewWidgetBridge::widget_callback);
@@ -286,8 +414,12 @@ void NodeParamViewWidgetBridge::create_widgets()
widgets_.append(button);
plugin::PluginNode *plugin_node =
dynamic_cast<plugin::PluginNode *>(input.node());
connect(button, &NodeParamButton::on_pressed, plugin_node,
&plugin::PluginNode::push_button_clicked);
connect(button, &NodeParamButton::on_pressed, this,
[plugin_node](const QString &name) {
oakengine_plugin_node_push_button_clicked(
reinterpret_cast<OakEngineNode*>(plugin_node),
name.toUtf8().constData());
});
}
}
@@ -312,9 +444,9 @@ void NodeParamViewWidgetBridge::set_input_value(const QVariant &value, int track
const NodeInput &input = get_inner_input();
oak_node_value c_value;
if (!variant_to_c_value(get_data_type(), value, &c_value)) {
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
set_input_value_internal(value, track, command, true);
Core::instance()->undo_stack()->push(command, get_command_name());
oakengine_undo_push(command, get_command_name().toUtf8().constData());
return;
}
@@ -339,11 +471,21 @@ void NodeParamViewWidgetBridge::set_string_value(const QString &value)
}
void NodeParamViewWidgetBridge::set_input_value_internal(
const QVariant &value, int track, MultiUndoCommand *command,
const QVariant &value, int track, void *command,
bool insert_on_all_tracks_if_no_key)
{
Node::set_value_at_time(get_inner_input(), get_current_time_as_node_time(), value,
track, command, insert_on_all_tracks_if_no_key);
const NodeInput &input = get_inner_input();
olive::Rational t = get_current_time_as_node_time();
oak_node_value c_value;
if (variant_to_c_value(get_data_type(), value, &c_value)) {
oakengine_undo_command_multi_add_child(
command,
oakengine_node_set_value_at_time_command(
reinterpret_cast<void *>(input.node()),
input.input().toUtf8().constData(), input.element(),
t.numerator(), t.denominator(), &c_value, track,
insert_on_all_tracks_if_no_key ? 1 : 0));
}
}
void NodeParamViewWidgetBridge::process_slider(NumericSliderBase *slider,
@@ -352,23 +494,27 @@ void NodeParamViewWidgetBridge::process_slider(NumericSliderBase *slider,
{
if (slider->is_dragging()) {
// While we're dragging, we block the input's normal signalling and create our own
if (!dragger_.is_started()) {
if (!oakengine_dragger_is_started(dragger_)) {
OakEngineNode *node = reinterpret_cast<OakEngineNode *>(get_inner_input().node());
Rational node_time = get_current_time_as_node_time();
int64_t ts = node_time_to_ts(node, node_time);
dragger_.start(NodeKeyframeTrackReference(get_inner_input(),
slider_track),
node_time);
oakengine_dragger_start(dragger_, ts, slider_track, 0);
}
dragger_.drag(value);
oak_node_value c_value;
if (variant_to_c_value(get_data_type(), value, &c_value)) {
oakengine_dragger_drag(dragger_, &c_value);
}
} else if (dragger_.is_started()) {
} else if (oakengine_dragger_is_started(dragger_)) {
// We were dragging and just stopped
dragger_.drag(value);
oak_node_value c_value;
if (variant_to_c_value(get_data_type(), value, &c_value)) {
oakengine_dragger_drag(dragger_, &c_value);
}
MultiUndoCommand *command = new MultiUndoCommand();
dragger_.end(command);
Core::instance()->undo_stack()->push(command, get_command_name());
oakengine_dragger_end(dragger_, get_command_name().toUtf8().constData());
} else {
// No drag was involved, we can just push the value
@@ -464,17 +610,22 @@ void NodeParamViewWidgetBridge::widget_callback()
Node *n = get_inner_input().node();
n->blockSignals(true);
n->set_input_property(get_inner_input().input(),
QStringLiteral("col_input"), c.color_input());
n->set_input_property(get_inner_input().input(),
QStringLiteral("col_display"),
c.color_output().display());
n->set_input_property(get_inner_input().input(),
QStringLiteral("col_view"),
c.color_output().view());
n->set_input_property(get_inner_input().input(),
QStringLiteral("col_look"),
c.color_output().look());
oakengine_node_set_input_property_string(
reinterpret_cast<OakEngineNode*>(n),
get_inner_input().input().toUtf8().constData(),
"col_input", c.color_input().toUtf8().constData(), 0);
oakengine_node_set_input_property_string(
reinterpret_cast<OakEngineNode*>(n),
get_inner_input().input().toUtf8().constData(),
"col_display", c.color_output().display().toUtf8().constData(), 0);
oakengine_node_set_input_property_string(
reinterpret_cast<OakEngineNode*>(n),
get_inner_input().input().toUtf8().constData(),
"col_view", c.color_output().view().toUtf8().constData(), 0);
oakengine_node_set_input_property_string(
reinterpret_cast<OakEngineNode*>(n),
get_inner_input().input().toUtf8().constData(),
"col_look", c.color_output().look().toUtf8().constData(), 0);
n->blockSignals(false);
}
break;
@@ -608,29 +759,29 @@ void NodeParamViewWidgetBridge::update_widget_values()
NodeParamViewTextEdit *e =
static_cast<NodeParamViewTextEdit *>(widgets_.first());
QByteArray bytes =
get_inner_input().get_value_at_time(node_time).toByteArray();
GetInputValueAtTime(get_inner_input(), node_time).toByteArray();
e->setTextPreservingCursor(QString::fromUtf8(bytes.toBase64()));
break;
}
case NodeValue::k_int: {
static_cast<IntegerSlider *>(widgets_.first())
->set_value(get_inner_input().get_value_at_time(node_time).toLongLong());
->set_value(GetInputValueAtTime(get_inner_input(), node_time).toLongLong());
break;
}
case NodeValue::k_float: {
static_cast<FloatSlider *>(widgets_.first())
->set_value(get_inner_input().get_value_at_time(node_time).toDouble());
->set_value(GetInputValueAtTime(get_inner_input(), node_time).toDouble());
break;
}
case NodeValue::k_rational: {
static_cast<RationalSlider *>(widgets_.first())
->set_value(
get_inner_input().get_value_at_time(node_time).value<Rational>());
GetInputValueAtTime(get_inner_input(), node_time).value<Rational>());
break;
}
case NodeValue::k_vec2: {
QVector2D vec2 =
get_inner_input().get_value_at_time(node_time).value<QVector2D>();
GetInputValueAtTime(get_inner_input(), node_time).value<QVector2D>();
static_cast<FloatSlider *>(widgets_.at(0))
->set_value(static_cast<double>(vec2.x()));
@@ -640,7 +791,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
}
case NodeValue::k_vec3: {
QVector3D vec3 =
get_inner_input().get_value_at_time(node_time).value<QVector3D>();
GetInputValueAtTime(get_inner_input(), node_time).value<QVector3D>();
static_cast<FloatSlider *>(widgets_.at(0))
->set_value(static_cast<double>(vec3.x()));
@@ -652,7 +803,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
}
case NodeValue::k_vec4: {
QVector4D vec4 =
get_inner_input().get_value_at_time(node_time).value<QVector4D>();
GetInputValueAtTime(get_inner_input(), node_time).value<QVector4D>();
static_cast<FloatSlider *>(widgets_.at(0))
->set_value(static_cast<double>(vec4.x()));
@@ -666,13 +817,13 @@ void NodeParamViewWidgetBridge::update_widget_values()
}
case NodeValue::k_file: {
FileField *ff = static_cast<FileField *>(widgets_.first());
ff->set_filename(get_inner_input().get_value_at_time(node_time).toString());
ff->set_filename(GetInputValueAtTime(get_inner_input(), node_time).toString());
break;
}
case NodeValue::k_color: {
if (get_inner_input().get_property("color_semantic").toString() ==
QStringLiteral("scalar")) {
Color c = get_inner_input().get_value_at_time(node_time).value<Color>();
Color c = GetInputValueAtTime(get_inner_input(), node_time).value<Color>();
static_cast<FloatSlider *>(widgets_.at(0))
->set_value(static_cast<double>(c.red()));
static_cast<FloatSlider *>(widgets_.at(1))
@@ -683,7 +834,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
->set_value(static_cast<double>(c.alpha()));
} else {
ManagedColor mc =
get_inner_input().get_value_at_time(node_time).value<Color>();
GetInputValueAtTime(get_inner_input(), node_time).value<Color>();
mc.set_color_input(
get_inner_input().get_property("col_input").toString());
@@ -702,25 +853,25 @@ void NodeParamViewWidgetBridge::update_widget_values()
NodeParamViewTextEdit *e =
static_cast<NodeParamViewTextEdit *>(widgets_.first());
e->setTextPreservingCursor(
get_inner_input().get_value_at_time(node_time).toString());
GetInputValueAtTime(get_inner_input(), node_time).toString());
break;
}
case NodeValue::k_boolean:
static_cast<QCheckBox *>(widgets_.first())
->setChecked(get_inner_input().get_value_at_time(node_time).toBool());
->setChecked(GetInputValueAtTime(get_inner_input(), node_time).toBool());
break;
case NodeValue::k_font: {
QFontComboBox *fc = static_cast<QFontComboBox *>(widgets_.first());
fc->blockSignals(true);
fc->setCurrentFont(
get_inner_input().get_value_at_time(node_time).toString());
GetInputValueAtTime(get_inner_input(), node_time).toString());
fc->blockSignals(false);
break;
}
case NodeValue::k_combo: {
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
cb->blockSignals(true);
int index = get_inner_input().get_value_at_time(node_time).toInt();
int index = GetInputValueAtTime(get_inner_input(), node_time).toInt();
for (int i = 0; i < cb->count(); i++) {
if (cb->itemData(i).toInt() == index) {
cb->setCurrentIndex(i);
@@ -733,7 +884,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
cb->blockSignals(true);
const QString current =
get_inner_input().get_value_at_time(node_time).toString();
GetInputValueAtTime(get_inner_input(), node_time).toString();
for (int i = 0; i < cb->count(); ++i) {
const QVariant data = cb->itemData(i);
if ((data.isValid() && data.toString() == current) ||
@@ -747,7 +898,7 @@ void NodeParamViewWidgetBridge::update_widget_values()
}
case NodeValue::k_bezier: {
BezierWidget *bw = static_cast<BezierWidget *>(widgets_.first());
bw->set_value(get_inner_input().get_value_at_time(node_time).value<Bezier>());
bw->set_value(GetInputValueAtTime(get_inner_input(), node_time).value<Bezier>());
break;
}
}
@@ -780,24 +931,37 @@ void NodeParamViewWidgetBridge::set_timebase(const Rational &timebase)
void NodeParamViewWidgetBridge::TimeTargetDisconnectEvent(ViewerOutput *v)
{
disconnect(v, &ViewerOutput::playhead_changed, this,
&NodeParamViewWidgetBridge::update_widget_values);
if (viewer_sub_ > 0) {
oakengine_event_unsubscribe(viewer_sub_);
viewer_sub_ = 0;
}
}
void NodeParamViewWidgetBridge::TimeTargetConnectEvent(ViewerOutput *v)
{
connect(v, &ViewerOutput::playhead_changed, this,
&NodeParamViewWidgetBridge::update_widget_values);
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(v),
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<NodeParamViewWidgetBridge *>(userdata)
->update_widget_values();
},
this);
}
void NodeParamViewWidgetBridge::input_value_changed(const NodeInput &input,
const TimeRange &range)
void NodeParamViewWidgetBridge::input_value_changed(OakEngineNode *source,
const QString &input,
int element, qint64 in_ts,
qint64 out_ts)
{
if (get_time_target() && get_inner_input() == input && !dragger_.is_started() &&
range.in() <= get_time_target()->get_playhead() &&
range.out() >= get_time_target()->get_playhead()) {
// We'll need to update the widgets because the values have changed on our current time
update_widget_values();
NodeInput ni(reinterpret_cast<Node *>(source), input, element);
if (get_time_target() && get_inner_input() == ni &&
!oakengine_dragger_is_started(dragger_)) {
int64_t playhead_ts =
node_time_to_ts(source, get_time_target()->get_playhead());
if (in_ts <= playhead_ts && out_ts >= playhead_ts) {
update_widget_values();
}
}
}
@@ -821,7 +985,7 @@ void NodeParamViewWidgetBridge::set_property(const QString &key,
} else { // set specific track/widget
bool ok;
int element = key.mid(7).toInt(&ok);
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
int tracks = oakengine_node_value_keyframe_track_count(node_value_type_to_c(data_type));
if (ok && element >= 0 && element < tracks) {
widgets_.at(element)->setEnabled(e);
@@ -915,15 +1079,19 @@ void NodeParamViewWidgetBridge::set_property(const QString &key,
break;
}
} else if (key == QStringLiteral("offset")) {
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
const int c_type = node_value_type_to_c(data_type);
int tracks = oakengine_node_value_keyframe_track_count(c_type);
QVector<QVariant> offsets =
NodeValue::split_normal_value_into_track_values(data_type,
value);
for (int i = 0; i < tracks; i++) {
static_cast<NumericSliderBase *>(widgets_.at(i))
->set_offset(offsets.at(i));
oak_node_value normal;
QVector<oak_node_value> track_vals(tracks);
if (QVariantToOakNodeValue(data_type, value, &normal) &&
oakengine_node_value_split_to_tracks(
c_type, &normal, track_vals.data(), tracks) ==
OAKENGINE_OK) {
for (int i = 0; i < tracks; i++) {
static_cast<NumericSliderBase *>(widgets_.at(i))
->set_offset(track_vals.at(i).f[0]);
}
}
update_widget_values();
@@ -931,7 +1099,7 @@ void NodeParamViewWidgetBridge::set_property(const QString &key,
} else if (key.startsWith(QStringLiteral("color"))) {
QColor c(value.toString());
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
int tracks = oakengine_node_value_keyframe_track_count(node_value_type_to_c(data_type));
if (key.size() == 5) {
// Set for all tracks
@@ -1057,8 +1225,17 @@ void NodeParamViewWidgetBridge::set_property(const QString &key,
// Offer the global LUT library directories as sidebar shortcuts in
// the browse dialog
QList<QUrl> sidebar_urls;
for (const QString &dir : LUTLibrary::get_directories()) {
sidebar_urls.append(QUrl::fromLocalFile(dir));
{
int dir_count = oakengine_lut_directory_count();
for (int i = 0; i < dir_count; i++) {
char buf[4096];
int len = oakengine_lut_directory_at(i, buf, sizeof(buf));
if (len > 0) {
sidebar_urls.append(
QUrl::fromLocalFile(
QString::fromUtf8(buf, len)));
}
}
}
if (!sidebar_urls.isEmpty()) {
ff->set_sidebar_urls(sidebar_urls);
@@ -1077,26 +1254,21 @@ void NodeParamViewWidgetBridge::set_property(const QString &key,
}
}
void NodeParamViewWidgetBridge::input_data_type_changed(const QString &input,
NodeValue::Type type)
void NodeParamViewWidgetBridge::input_data_type_changed(OakEngineNode *source,
const QString &input)
{
if (sender() == get_outer_input().node() &&
if (reinterpret_cast<Node *>(source) == get_outer_input().node() &&
input == get_outer_input().input()) {
// Delete all widgets
qDeleteAll(widgets_);
widgets_.clear();
// Create new widgets
create_widgets();
// Signal that widgets are new
emit widgets_recreated(get_outer_input());
}
}
void NodeParamViewWidgetBridge::property_changed(const QString &input,
const QString &key,
const QVariant &value)
void NodeParamViewWidgetBridge::property_changed(const QString &input)
{
bool found = false;
@@ -23,8 +23,10 @@
#define OAK_NODEPARAMVIEWWIDGETBRIDGE_H
#include <QObject>
#include <cstdint>
#include "node/inputdragger.h"
#include "engineeventbridge.h"
#include "oakengine/node.h"
#include "widget/slider/base/numericsliderbase.h"
#include "widget/timetarget/timetarget.h"
@@ -41,6 +43,7 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject {
Q_OBJECT
public:
NodeParamViewWidgetBridge(NodeInput input, QObject *parent);
~NodeParamViewWidgetBridge() override;
const QVector<QWidget *> &widgets() const
{
@@ -69,7 +72,7 @@ private:
void set_string_value(const QString &value);
void set_input_value_internal(const QVariant &value, int track,
MultiUndoCommand *command,
void *command,
bool insert_on_all_tracks_if_no_key);
void process_slider(NumericSliderBase *slider, int slider_track,
@@ -110,19 +113,24 @@ private:
QVector<QWidget *> widgets_;
NodeInputDragger dragger_;
OakEngineNodeDragger *dragger_ = nullptr;
NodeParamViewScrollBlocker scroll_filter_;
EngineEventBridge *bridge_ = nullptr;
int64_t viewer_sub_ = 0;
private slots:
void widget_callback();
void input_value_changed(const NodeInput &input, const TimeRange &range);
void input_value_changed(OakEngineNode *source, const QString &input,
int element, qint64 in_ts, qint64 out_ts);
void input_data_type_changed(const QString &input, NodeValue::Type type);
void input_data_type_changed(OakEngineNode *source,
const QString &input);
void property_changed(const QString &input, const QString &key,
const QVariant &value);
void property_changed(const QString &input);
};
}
+68 -35
View File
@@ -24,7 +24,9 @@
#include <QCheckBox>
#include <QHeaderView>
#include "node/traverser.h"
#include "oakengine/traverse.h"
#include "oakengine/node.h"
#include "node/value.h"
namespace olive
{
@@ -61,41 +63,53 @@ void NodeTableView::set_time(const Rational &time)
{
last_time_ = time;
NodeTraverser traverser;
for (auto i = top_level_item_map_.constBegin();
i != top_level_item_map_.constEnd(); i++) {
Node *node = i.key();
QTreeWidgetItem *item = i.value();
// Generate a value database for this node at this time
NodeValueDatabase db =
traverser.generate_database(node, TimeRange(time, time));
OakEngineTraverseDb *db = oakengine_traverse_generate_database(
reinterpret_cast<OakEngineNode *>(node), time.numerator(),
time.denominator(), time.numerator(), time.denominator());
int input_count = oakengine_traverse_db_input_count(db);
// Delete any children of this item that aren't in this database
for (int j = 0; j < item->childCount(); j++) {
if (!db.contains(
item->child(j)->data(0, Qt::UserRole).toString())) {
QString child_id =
item->child(j)->data(0, Qt::UserRole).toString();
bool found = false;
for (int k = 0; k < input_count; k++) {
if (child_id ==
QString::fromUtf8(
oakengine_traverse_db_input_id(db, k))) {
found = true;
break;
}
}
if (!found) {
delete item->takeChild(j);
j--;
}
}
// Update all inputs
for (auto l = db.begin(); l != db.end(); l++) {
const NodeValueTable &table = l.value();
for (int l = 0; l < input_count; l++) {
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(l.key())) {
// Filters out table entries that aren't inputs (like "global")
if (!node->has_input_with_id(input_id)) {
continue;
}
int row_count = oakengine_traverse_db_row_count(db, l);
QTreeWidgetItem *input_item = nullptr;
for (int j = 0; j < item->childCount(); j++) {
QTreeWidgetItem *compare = item->child(j);
if (compare->data(0, Qt::UserRole).toString() == l.key()) {
if (compare->data(0, Qt::UserRole).toString() == input_id) {
input_item = compare;
break;
}
@@ -103,64 +117,83 @@ void NodeTableView::set_time(const Rational &time)
if (!input_item) {
input_item = new QTreeWidgetItem();
input_item->setText(0, node->get_input_name(l.key()));
input_item->setData(0, Qt::UserRole, l.key());
input_item->setText(0, node->get_input_name(input_id));
input_item->setData(0, Qt::UserRole, input_id);
input_item->setFirstColumnSpanned(true);
item->addChild(input_item);
}
// Create children if necessary
while (input_item->childCount() < table.count()) {
while (input_item->childCount() < row_count) {
input_item->addChild(new QTreeWidgetItem());
}
// Remove children if necessary
while (input_item->childCount() > table.count()) {
delete input_item->takeChild(input_item->childCount() - 1);
while (input_item->childCount() > row_count) {
delete input_item->takeChild(
input_item->childCount() - 1);
}
for (int j = 0; j < table.count(); j++) {
const NodeValue &value = table.at(table.count() - 1 - j);
// Create item
for (int j = 0; j < row_count; j++) {
int actual_row = row_count - 1 - j;
QTreeWidgetItem *sub_item = input_item->child(j);
int type =
oakengine_traverse_row_type(db, l, actual_row);
// Set data type name
sub_item->setText(
0, NodeValue::get_pretty_data_type_name(value.type()));
char name_buf[64];
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
if (len > 0 && len < sizeof(name_buf)) {
name_buf[len] = '\0';
} else {
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
}
sub_item->setText(0, QString::fromUtf8(name_buf));
// Determine source
OakEngineNode *source =
oakengine_traverse_row_source(db, l, actual_row);
QString source_name;
if (value.source()) {
source_name = value.source()->get_label_and_name();
if (source) {
char label_buf[256];
oakengine_node_get_label_and_name(
source, label_buf, sizeof(label_buf));
source_name = QString(label_buf);
} else {
source_name = tr("(unknown)");
}
sub_item->setText(1, source_name);
switch (value.type()) {
switch (type) {
case NodeValue::k_video_params:
case NodeValue::k_audio_params:
// These types have no string representation
break;
case NodeValue::k_texture: {
// NodeTraverser puts video params in here
for (int k = 0; k < VideoParams::k_rgba_channel_count; k++) {
this->setItemWidget(sub_item, 2 + k, new QCheckBox());
for (int k = 0; k < 4; k++) {
this->setItemWidget(sub_item, 2 + k,
new QCheckBox());
}
break;
}
default: {
QVector<QVariant> split_values = value.to_split_value();
for (int k = 0; k < split_values.size(); k++) {
sub_item->setText(2 + k, NodeValue::value_to_string(
value.type(),
split_values.at(k), true));
int split_count =
oakengine_traverse_row_split_count(db, l,
actual_row);
for (int k = 0; k < split_count; k++) {
const char *split_str =
oakengine_traverse_row_split_string(
db, l, actual_row, k);
sub_item->setText(2 + k,
QString(split_str));
}
}
}
}
}
oakengine_traverse_db_free(db);
}
}
+6 -3
View File
@@ -23,6 +23,9 @@
#include <QEvent>
#include "common/nodevaluehandle.h"
#include "oakengine/node.h"
namespace olive
{
@@ -190,7 +193,7 @@ QTreeWidgetItem *NodeTreeView::create_item(QTreeWidgetItem *parent,
QString item_name;
if (ref.track() == -1 ||
NodeValue::get_number_of_keyframe_tracks(ref.input().get_data_type()) ==
oakengine_node_value_keyframe_track_count(node_value_type_to_c(ref.input().get_data_type())) ==
1 ||
(ref.input().is_array() && ref.input().element() == -1)) {
if (ref.input().element() == -1) {
@@ -258,11 +261,11 @@ void NodeTreeView::item_check_state_changed(QTreeWidgetItem *item, int column)
if (item->checkState(0) == Qt::Checked) {
if (disabled_nodes_.contains(n)) {
disabled_nodes_.removeOne(n);
emit node_enable_changed(n, true);
emit node_enable_changed(reinterpret_cast<OakEngineNode *>(n), true);
}
} else if (!disabled_nodes_.contains(n)) {
disabled_nodes_.append(n);
emit node_enable_changed(n, false);
emit node_enable_changed(reinterpret_cast<OakEngineNode *>(n), false);
}
break;
}
+6 -2
View File
@@ -25,6 +25,7 @@
#include <QTreeWidget>
#include "node/node.h"
#include "oakengine/node.h"
namespace olive
{
@@ -56,11 +57,14 @@ public:
show_keyframe_tracks_as_rows_ = e;
}
public slots:
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);
signals:
void node_enable_changed(Node *n, bool e);
void node_enable_changed(OakEngineNode *n, bool e);
void input_enable_changed(const NodeKeyframeTrackReference &ref, bool e);
+55 -16
View File
@@ -20,7 +20,10 @@
#include <QEvent>
#include "node/traverser.h"
#include "common/nodevaluehandle.h"
#include "oakengine/traverse.h"
#include "oakengine/node.h"
#include "node/value.h"
namespace olive
{
@@ -47,22 +50,30 @@ void NodeValueTree::set_node(const NodeInput &input, const Rational &time)
{
clear();
NodeTraverser traverser;
Node *connected_node = input.get_connected_output();
NodeValueTable table =
traverser.generate_table(connected_node, TimeRange(time, time));
OakEngineTraverseDb *table_db = oakengine_traverse_generate_table(
reinterpret_cast<OakEngineNode *>(connected_node),
time.numerator(), time.denominator(), time.numerator(),
time.denominator());
int index = traverser.generate_row_value_element_index(
input.node(), input.input(), input.element(), &table);
int db_index = 0;
int row_count = oakengine_traverse_db_row_count(table_db, db_index);
for (int i = 0; i < table.count(); i++) {
const NodeValue &value = table.at(i);
int index = oakengine_traverse_table_element_index_for_hint(
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(), table_db);
for (int i = 0; i < row_count; i++) {
QTreeWidgetItem *item = new QTreeWidgetItem(this);
Node::ValueHint hint({ value.type() }, table.count() - 1 - i,
value.tag());
int type = oakengine_traverse_row_type(table_db, db_index, i);
OakEngineNode *source = oakengine_traverse_row_source(table_db, db_index,
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));
QRadioButton *radio = new QRadioButton(this);
radio->setProperty("input", QVariant::fromValue(input));
@@ -74,10 +85,29 @@ void NodeValueTree::set_node(const NodeInput &input, const Rational &time)
&NodeValueTree::radio_button_checked);
setItemWidget(item, 0, radio);
item->setText(1, NodeValue::get_pretty_data_type_name(value.type()));
item->setText(2, NodeValue::value_to_string(value, false));
item->setText(3, value.source()->get_label_and_name());
char name_buf[64];
int len = oakengine_node_value_pretty_type_name(type, name_buf, sizeof(name_buf));
if (len > 0 && len < sizeof(name_buf)) {
name_buf[len] = '\0';
} else {
snprintf(name_buf, sizeof(name_buf), "Type %d", type);
}
item->setText(1, QString::fromUtf8(name_buf));
const char *vs = oakengine_traverse_row_value_string(table_db, db_index, i);
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));
} else {
item->setText(3, QString());
}
}
oakengine_traverse_db_free(table_db);
}
void NodeValueTree::changeEvent(QEvent *event)
@@ -101,8 +131,17 @@ void NodeValueTree::radio_button_checked(bool e)
Node::ValueHint hint = btn->property("hint").value<Node::ValueHint>();
NodeInput input = btn->property("input").value<NodeInput>();
input.node()->set_value_hint_for_input(input.input(), hint,
input.element());
// 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());
}
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());
}
}
+403 -191
View File
@@ -31,9 +31,10 @@
#include "node/audio/volume/volume.h"
#include "node/distort/transform/transformdistortnode.h"
#include "node/group/group.h"
#include "node/nodeundo.h"
#include "node/project/serializer/serializer.h"
#include "oakengine/node.h"
#include "oakengine/project.h"
#include "oakengine/serializer.h"
#include "oakengine/undo.h"
#include "panel/panelmanager.h"
#include "node/traverser.h"
#include "ui/icons/icons.h"
@@ -46,6 +47,33 @@
namespace olive
{
namespace
{
QVector<OakEngineNode *> node_vector_to_engine(const QVector<Node *> &nodes)
{
QVector<OakEngineNode *> result;
result.reserve(nodes.size());
foreach (Node *n, nodes) {
result.append(reinterpret_cast<OakEngineNode *>(n));
}
return result;
}
QVector<QPair<OakEngineNode *, OakEngineNode *>>
context_pair_vector_to_engine(const QVector<Node::ContextPair> &pairs)
{
QVector<QPair<OakEngineNode *, OakEngineNode *>> result;
result.reserve(pairs.size());
foreach (const Node::ContextPair &p, pairs) {
result.append(qMakePair(reinterpret_cast<OakEngineNode *>(p.node),
reinterpret_cast<OakEngineNode *>(p.context)));
}
return result;
}
} // namespace
const double NodeView::k_minimum_scale = 0.1;
const int NodeView::k_maximum_contexts = 10;
@@ -59,6 +87,7 @@ NodeView::NodeView(QWidget *parent)
, scale_(1.0)
, dont_emit_selection_signals_(false)
, show_in_param_editor_action_(nullptr)
, bridge_(new EngineEventBridge(this))
{
setScene(&scene_);
set_default_drag_mode(RubberBandDrag);
@@ -70,6 +99,9 @@ NodeView::NodeView(QWidget *parent)
connect(this, &NodeView::customContextMenuRequested, this,
&NodeView::show_context_menu);
connect(bridge_, &EngineEventBridge::node_removed_from_graph, this,
&NodeView::node_removed_from_graph);
connect_selection_changed_signal();
set_flow_direction(NodeViewCommon::k_left_to_right);
@@ -159,16 +191,49 @@ void NodeView::clear_graph()
void NodeView::delete_selected()
{
NodeViewDeleteCommand *command = new NodeViewDeleteCommand();
int count = 0;
QVector<Node *> nodes;
QVector<Node *> contexts;
QVector<NodeViewEdge *> edges;
foreach (NodeViewContext *ctx, scene_.context_map()) {
count += ctx->delete_selected(command);
ctx->get_selected_for_deletion(nodes, contexts, edges);
}
Core::instance()->undo_stack()->push(command,
tr("Deleted %1 Node(s)").arg(count));
if (nodes.isEmpty() && edges.isEmpty()) {
return;
}
// Build C arrays for the facade
QVector<OakEngineNode *> oak_nodes(nodes.size());
QVector<OakEngineNode *> oak_contexts(nodes.size());
for (int i = 0; i < nodes.size(); i++) {
oak_nodes[i] = reinterpret_cast<OakEngineNode *>(nodes[i]);
oak_contexts[i] = reinterpret_cast<OakEngineNode *>(contexts[i]);
}
QVector<OakEngineNode *> edge_outputs(edges.size());
QVector<OakEngineNode *> edge_input_nodes(edges.size());
QVector<QByteArray> edge_input_ids_storage(edges.size());
QVector<const char *> edge_input_ids(edges.size());
QVector<int> edge_input_elements(edges.size());
for (int i = 0; i < edges.size(); i++) {
edge_outputs[i] = reinterpret_cast<OakEngineNode *>(edges[i]->output());
edge_input_nodes[i] = reinterpret_cast<OakEngineNode *>(edges[i]->input().node());
edge_input_ids_storage[i] = edges[i]->input().input().toUtf8();
edge_input_ids[i] = edge_input_ids_storage[i].constData();
edge_input_elements[i] = edges[i]->input().element();
}
// ONE undoable command whether this deletes nodes, edges, or both
oakengine_nodes_delete_many(
oak_nodes.isEmpty() ? nullptr : oak_nodes.constData(),
oak_contexts.isEmpty() ? nullptr : oak_contexts.constData(),
nodes.size(),
edge_outputs.isEmpty() ? nullptr : edge_outputs.constData(),
edge_input_nodes.isEmpty() ? nullptr : edge_input_nodes.constData(),
edge_input_ids.isEmpty() ? nullptr : edge_input_ids.constData(),
edge_input_elements.isEmpty() ? nullptr : edge_input_elements.constData(),
edges.size());
}
void NodeView::select_all()
@@ -199,14 +264,16 @@ void NodeView::deselect_all()
connect_selection_changed_signal();
// Just emit all the nodes that are currently selected as no longer selected
emit nodes_deselected(selected_nodes_);
emit nodes_deselected(node_vector_to_engine(selected_nodes_));
selected_nodes_.clear();
emit node_selection_changed(selected_nodes_);
emit node_selection_changed_with_contexts(QVector<Node::ContextPair>());
emit node_selection_changed(node_vector_to_engine(selected_nodes_));
emit node_selection_changed_with_contexts(
QVector<QPair<OakEngineNode *, OakEngineNode *>>());
}
void NodeView::select(const QVector<Node::ContextPair> &nodes,
bool center_view_on_item)
void NodeView::select(
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes,
bool center_view_on_item)
{
// Optimization: rather than respond to every single item being selected, ignore the signal and
// then handle them all at the end.
@@ -217,10 +284,12 @@ void NodeView::select(const QVector<Node::ContextPair> &nodes,
scene_.deselect_all();
foreach (const Node::ContextPair &p, nodes) {
NodeViewContext *ctx = scene_.context_map().value(p.context);
foreach (const auto &p, nodes) {
Node *node = reinterpret_cast<Node *>(p.first);
Node *context = reinterpret_cast<Node *>(p.second);
NodeViewContext *ctx = scene_.context_map().value(context);
if (ctx) {
NodeViewItem *item = ctx->get_item_from_map(p.node);
NodeViewItem *item = ctx->get_item_from_map(node);
if (item) {
item->setSelected(true);
}
@@ -230,7 +299,7 @@ void NodeView::select(const QVector<Node::ContextPair> &nodes,
// Center on something
if (center_view_on_item && !nodes.isEmpty()) {
QMetaObject::invokeMethod(this, "center_on_node", Qt::QueuedConnection,
OLIVE_NS_ARG(Node *, nodes.first().node));
Q_ARG(OakEngineNode *, nodes.first().first));
}
connect_selection_changed_signal();
@@ -247,34 +316,42 @@ void NodeView::copy_selected(bool cut)
return;
}
QString copy_str;
QXmlStreamWriter writer(&copy_str);
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_NODES, nullptr, nullptr);
ProjectSerializer::SaveData sdata(ProjectSerializer::k_only_nodes);
sdata.set_only_serialize_nodes_and_resolve_groups(selected_nodes_);
ProjectSerializer::SerializedProperties properties;
oakengine_clipboard_set_nodes(
cb,
reinterpret_cast<const OakEngineNode *const *>(
selected_nodes_.constData()),
selected_nodes_.size());
for (Node *n : selected_nodes_) {
NodeViewItem *item = get_assumed_item_for_selected_node(n);
if (item) {
Node::Position pos = item->get_node_position_data();
properties[n][QStringLiteral("x")] =
QString::number(pos.position.x());
properties[n][QStringLiteral("y")] =
QString::number(pos.position.y());
properties[n][QStringLiteral("expanded")] =
QString::number(pos.expanded);
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "x",
QByteArray::number(pos.position.x()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "y",
QByteArray::number(pos.position.y()).constData());
oakengine_clipboard_set_property(
cb, reinterpret_cast<OakEngineNode *>(n), "expanded",
QByteArray::number(pos.expanded).constData());
}
}
sdata.set_properties(properties);
// Two-phase buf/size: query the needed length first — serialized node
// graphs have no size bound, a fixed buffer would truncate large copies.
const int needed = oakengine_clipboard_save_to_xml(cb, nullptr, 0);
if (needed >= 0) {
QByteArray buf(needed + 1, '\0');
oakengine_clipboard_save_to_xml(cb, buf.data(), buf.size());
Core::copy_string_to_clipboard(QString::fromUtf8(buf.constData()));
}
ProjectSerializer::save(&writer, sdata);
Core::copy_string_to_clipboard(copy_str);
oakengine_clipboard_free(cb);
if (cut) {
delete_selected();
@@ -287,27 +364,48 @@ void NodeView::paste()
return;
}
ProjectSerializer::Result res =
ProjectSerializer::paste(ProjectSerializer::k_only_nodes);
if (res.get_load_data().nodes.isEmpty()) {
OakEngineClipboard *cb = oakengine_clipboard_create(
OAKENGINE_CLIPBOARD_NODES, nullptr, nullptr);
int result_code = OAKENGINE_SERIALIZER_NO_DATA;
oakengine_clipboard_paste(cb, OAKENGINE_CLIPBOARD_NODES, nullptr,
&result_code, nullptr, 0);
if (result_code != OAKENGINE_SERIALIZER_OK) {
oakengine_clipboard_free(cb);
return;
}
QVector<Node *> nodes;
const int node_count = oakengine_clipboard_get_loaded_node_count(cb);
nodes.reserve(node_count);
for (int i = 0; i < node_count; i++) {
nodes.append(reinterpret_cast<Node *>(
oakengine_clipboard_get_loaded_node_at(cb, i)));
}
Node::PositionMap map;
for (auto it = res.get_load_data().properties.cbegin();
it != res.get_load_data().properties.cend(); it++) {
Node::Position pos;
oakengine_clipboard_foreach_property(
cb,
[](OakEngineNode *node, const char *key, const char *value,
void *userdata) -> int {
auto *m = static_cast<Node::PositionMap *>(userdata);
Node *n = reinterpret_cast<Node *>(node);
Node::Position &pos = (*m)[n];
if (std::strcmp(key, "x") == 0) {
pos.position.setX(QString::fromUtf8(value).toDouble());
} else if (std::strcmp(key, "y") == 0) {
pos.position.setY(QString::fromUtf8(value).toDouble());
} else if (std::strcmp(key, "expanded") == 0) {
pos.expanded = QString::fromUtf8(value).toDouble();
}
return 0;
},
&map);
const QMap<QString, QString> &node_props = it.value();
pos.position.setX(node_props.value(QStringLiteral("x")).toDouble());
pos.position.setY(node_props.value(QStringLiteral("y")).toDouble());
pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble();
oakengine_clipboard_free(cb);
map.insert(it.key(), pos);
}
post_paste(res.get_load_data().nodes, map);
post_paste(nodes, map);
}
void NodeView::duplicate()
@@ -323,9 +421,10 @@ void NodeView::duplicate()
for (int i = 0; i < selected.size(); i++) {
new_nodes[i] = selected.at(i)->copy();
if (NodeGroup *g = dynamic_cast<NodeGroup *>(selected.at(i))) {
for (auto it = g->get_context_positions().cbegin();
it != g->get_context_positions().cend(); it++) {
if (oakengine_node_is_group(
reinterpret_cast<OakEngineNode *>(selected.at(i)))) {
for (auto it = selected.at(i)->get_context_positions().cbegin();
it != selected.at(i)->get_context_positions().cend(); it++) {
if (!selected.contains(it.key())) {
// This should automatically recurse if this is a group inside a group
selected.append(it.key());
@@ -353,30 +452,67 @@ void NodeView::duplicate()
if (child_index != -1) {
Node *child_copy = new_nodes.at(child_index);
copy->set_node_position_in_context(child_copy, it.value());
oakengine_node_set_context_position(
reinterpret_cast<OakEngineNode*>(copy),
reinterpret_cast<OakEngineNode*>(child_copy),
it.value().position.x(), it.value().position.y());
}
}
if (NodeGroup *src_group = dynamic_cast<NodeGroup *>(og)) {
NodeGroup *dst_group = static_cast<NodeGroup *>(copy);
for (auto it = src_group->get_input_passthroughs().cbegin();
it != src_group->get_input_passthroughs().cend(); it++) {
NodeInput input = it->second;
input.set_node(
new_nodes.at(selected.indexOf(input.node())));
dst_group->add_input_passthrough(input, it->first);
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(og))) {
const int pt_count = oakengine_group_input_passthrough_count(
reinterpret_cast<OakEngineNode *>(og));
for (int pt = 0; pt < pt_count; pt++) {
OakEngineNode *inner_node = nullptr;
char inner_input[256];
int inner_element = 0;
char id[256];
if (oakengine_group_input_passthrough_at(
reinterpret_cast<OakEngineNode *>(og), pt,
id, sizeof(id), &inner_node, inner_input,
sizeof(inner_input), &inner_element) !=
OAKENGINE_OK) {
continue;
}
Node *inner = reinterpret_cast<Node *>(inner_node);
int src_index = selected.indexOf(inner);
if (src_index == -1) {
continue;
}
Node *copy_inner = new_nodes.at(src_index);
char out_id[256];
oakengine_group_add_input_passthrough(
reinterpret_cast<OakEngineNode *>(copy),
reinterpret_cast<OakEngineNode *>(copy_inner),
inner_input, inner_element, id,
out_id, sizeof(out_id));
}
dst_group->set_output_passthrough(new_nodes.at(
selected.indexOf(src_group->get_output_passthrough())));
if (OakEngineNode *output = oakengine_group_get_output_passthrough(
reinterpret_cast<OakEngineNode *>(og))) {
int idx = selected.indexOf(reinterpret_cast<Node *>(output));
if (idx >= 0 && idx < new_nodes.size()) {
oakengine_group_set_output_passthrough(
reinterpret_cast<OakEngineNode *>(copy),
reinterpret_cast<OakEngineNode *>(
new_nodes.at(idx)));
}
}
}
Node::copy_inputs(selected.at(i), new_nodes.at(i), false);
oakengine_node_copy_inputs(
reinterpret_cast<OakEngineNode*>(new_nodes.at(i)),
reinterpret_cast<const OakEngineNode*>(selected.at(i)));
}
// Copy connections
Node::copy_dependency_graph(selected, new_nodes, nullptr);
{
QVector<OakEngineNode*> sel_arr, new_arr;
for (Node *n : selected) sel_arr.append(reinterpret_cast<OakEngineNode*>(n));
for (Node *n : new_nodes) new_arr.append(reinterpret_cast<OakEngineNode*>(n));
oakengine_node_copy_dependency_graph(
sel_arr.data(), new_arr.data(), sel_arr.size(), nullptr);
}
// Set root level context positions and attach to
post_paste(new_nodes, map);
@@ -385,14 +521,17 @@ void NodeView::duplicate()
void NodeView::set_color_label(int index)
{
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
for (Node *node : qAsConst(selected_nodes_)) {
command->add_child(new NodeOverrideColorCommand(node, index));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_set_color_label_command(
reinterpret_cast<OakEngineNode *>(node), index));
}
Core::instance()->undo_stack()->push(
command, tr("Set Color of %1 Node(s)").arg(selected_nodes_.size()));
oakengine_undo_push(
command, tr("Set Color of %1 Node(s)").arg(selected_nodes_.size()).toUtf8().constData());
}
void NodeView::zoom_in()
@@ -412,7 +551,7 @@ void NodeView::keyPressEvent(QKeyEvent *event)
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down: {
MultiUndoCommand *pos_command = new MultiUndoCommand();
void *pos_command = oakengine_undo_command_create_multi();
for (Node *n : qAsConst(selected_nodes_)) {
for (Node *context : qAsConst(contexts_)) {
if (context->context_contains_node(n)) {
@@ -444,13 +583,20 @@ void NodeView::keyPressEvent(QKeyEvent *event)
node_movement, scene_.get_flow_direction());
// Move command
pos_command->add_child(new NodeSetPositionCommand(
n, context, old_pos + node_movement));
Node::Position new_pos = old_pos;
new_pos.position += node_movement;
oakengine_undo_command_multi_add_child(
pos_command,
oakengine_node_set_position_command(
reinterpret_cast<void *>(n),
reinterpret_cast<void *>(context),
new_pos.position.x(), new_pos.position.y(),
new_pos.expanded ? 1 : 0));
}
}
}
Core::instance()->undo_stack()->push(
pos_command, tr("Moved %1 Node(s)").arg(selected_nodes_.size()));
oakengine_undo_push(
pos_command, tr("Moved %1 Node(s)").arg(selected_nodes_.size()).toUtf8().constData());
break;
}
case Qt::Key_Escape:
@@ -601,7 +747,7 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
end_edge_drag();
}
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
Node *select_context = nullptr;
QVector<Node *> select_nodes;
@@ -632,13 +778,15 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
}
QPointF current_pos = i->get_node_position();
if (dragging_items_.value(i) != current_pos) {
command->add_child(new NodeSetPositionCommand(
i->get_node(), i->get_context(), current_pos));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(
reinterpret_cast<void *>(i->get_node()),
reinterpret_cast<void *>(i->get_context()), current_pos.x(),
current_pos.y(), 0));
}
}
Core::instance()->undo_stack()->push(
command, tr("Moved %1 Node(s)").arg(dragging_items_.size()));
oakengine_undo_push(
command, tr("Moved %1 Node(s)").arg(dragging_items_.size()).toUtf8().constData());
dragging_items_.clear();
@@ -674,8 +822,8 @@ void NodeView::dragEnterEvent(QDragEnterEvent *event)
QStringList mime_fmts = event->mimeData()->formats();
if (mime_fmts.contains(Project::k_item_mime_type)) {
QByteArray model_data = event->mimeData()->data(Project::k_item_mime_type);
if (mime_fmts.contains(QString::fromUtf8(oakengine_project_item_mime_type()))) {
QByteArray model_data = event->mimeData()->data(QString::fromUtf8(oakengine_project_item_mime_type()));
QDataStream stream(&model_data, QIODevice::ReadOnly);
// Variables to deserialize into
@@ -732,11 +880,11 @@ void NodeView::dragMoveEvent(QDragMoveEvent *event)
void NodeView::dropEvent(QDropEvent *event)
{
if (Node *drop_ctx = get_context_at_mouse_pos(event->pos())) {
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
QVector<Node *> select_nodes =
process_dropping_attached_nodes(command, drop_ctx, event->pos());
Core::instance()->undo_stack()->push(
command, tr("Dropped %1 Node(s)").arg(select_nodes.size()));
oakengine_undo_push(
command, tr("Dropped %1 Node(s)").arg(select_nodes.size()).toUtf8().constData());
deselect_all();
scene_.context_map().value(drop_ctx)->select(select_nodes);
@@ -815,16 +963,17 @@ void NodeView::update_selection_cache()
}
if (!deselected.isEmpty()) {
emit nodes_deselected(deselected);
emit nodes_deselected(node_vector_to_engine(deselected));
}
if (!selected.isEmpty()) {
emit nodes_selected(selected);
emit nodes_selected(node_vector_to_engine(selected));
}
if (!dont_emit_selection_signals_) {
emit node_selection_changed(selected_nodes_);
emit node_selection_changed_with_contexts(sel_with_ctx);
emit node_selection_changed(node_vector_to_engine(selected_nodes_));
emit node_selection_changed_with_contexts(
context_pair_vector_to_engine(sel_with_ctx));
}
}
@@ -856,7 +1005,8 @@ void NodeView::show_context_menu(const QPoint &pos)
if (item_under_cursor && !selected.isEmpty()) {
// Grouping
if (selected.size() == 1 &&
dynamic_cast<NodeGroup *>(selected.first()->get_node())) {
oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(
selected.first()->get_node()))) {
QAction *ungroup_action = m.addAction(tr("Ungroup"));
connect(ungroup_action, &QAction::triggered, this,
&NodeView::ungroup_nodes);
@@ -947,9 +1097,10 @@ void NodeView::create_node_slot(QAction *action)
new_attached.append({ new_item, new_node, QPointF(0, 0) });
if (NodeGroup *new_group = dynamic_cast<NodeGroup *>(new_node)) {
for (auto it = new_group->get_context_positions().cbegin();
it != new_group->get_context_positions().cend(); it++) {
if (oakengine_node_is_group(
reinterpret_cast<OakEngineNode *>(new_node))) {
for (auto it = new_node->get_context_positions().cbegin();
it != new_node->get_context_positions().cend(); it++) {
new_attached.append({ nullptr, it.key(), QPointF(0, 0) });
}
}
@@ -992,10 +1143,11 @@ void NodeView::center_on_items_bounding_rect()
centerOn(scene_.itemsBoundingRect().center());
}
void NodeView::center_on_node(Node *n)
void NodeView::center_on_node(OakEngineNode *n)
{
foreach (NodeViewContext *ctx, scene_.context_map()) {
if (NodeViewItem *item = ctx->get_item_from_map(n)) {
if (NodeViewItem *item = ctx->get_item_from_map(
reinterpret_cast<Node *>(n))) {
centerOn(item);
break;
}
@@ -1036,9 +1188,9 @@ void NodeView::move_to_scene_point(const QPointF &pos)
centerOn(pos);
}
void NodeView::node_removed_from_graph()
void NodeView::node_removed_from_graph(OakEngineNode *source)
{
Node *context = static_cast<Node *>(sender());
Node *context = reinterpret_cast<Node *>(source);
remove_context(context);
@@ -1111,7 +1263,7 @@ void NodeView::process_moving_attached_nodes(const QPoint &pos)
} else {
// Otherwise, we may have to iterate to find a valid one
for (const QString &input : attached_node->inputs()) {
if (input == Node::k_enabled_input) {
if (input == QLatin1String(oakengine_node_enabled_input_id())) {
// Ignore enabled input
continue;
}
@@ -1160,7 +1312,7 @@ void NodeView::process_moving_attached_nodes(const QPoint &pos)
}
QVector<Node *>
NodeView::process_dropping_attached_nodes(MultiUndoCommand *command,
NodeView::process_dropping_attached_nodes(void *command,
Node *select_context, const QPoint &pos)
{
QVector<Node *> select_nodes;
@@ -1180,41 +1332,44 @@ NodeView::process_dropping_attached_nodes(MultiUndoCommand *command,
}
{
MultiUndoCommand *add_command = new MultiUndoCommand();
void *add_command = oakengine_undo_command_create_multi();
foreach (const AttachedItem &ai, attached) {
// Add node to the same graph that the context is in
if (ai.node->parent() != select_context->parent()) {
add_command->add_child(
new NodeAddCommand(select_context->parent(), ai.node));
oakengine_undo_command_multi_add_child(add_command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(select_context->parent()),
reinterpret_cast<OakEngineNode *>(ai.node)));
if (ai.node->is_item() && !ai.node->folder()) {
add_command->add_child(new FolderAddChild(
select_context->parent()->root(), ai.node));
oakengine_folder_add_child(
reinterpret_cast<OakEngineNode *>(select_context->parent()->root()),
reinterpret_cast<OakEngineNode *>(ai.node));
}
}
// Add node to the context
if (ai.item) {
select_nodes.append(ai.node);
add_command->add_child(new NodeSetPositionCommand(
ai.node, select_context,
scene_.context_map()
oakengine_undo_command_multi_add_child(add_command, oakengine_node_set_position_command(reinterpret_cast<void *>(ai.node), reinterpret_cast<void *>(select_context), scene_.context_map()
.value(select_context)
->map_scene_pos_to_node_pos_in_context(ai.item->pos())));
->map_scene_pos_to_node_pos_in_context(ai.item->pos()).x(), scene_.context_map()
.value(select_context)
->map_scene_pos_to_node_pos_in_context(ai.item->pos()).y(), 0));
}
}
if (add_command->child_count()) {
add_command->redo_now();
command->add_child(add_command);
if (oakengine_undo_command_multi_child_count(add_command) > 0) {
oakengine_undo_command_redo_now(add_command);
oakengine_undo_command_multi_add_child(command, add_command);
} else {
delete add_command;
oakengine_undo_command_free(add_command);
}
}
{
// Dropped attached item onto an edge, connect it between them
MultiUndoCommand *drop_edge_command = new MultiUndoCommand();
// Dropped attached item onto an edge, connect it between them as one
// undoable child of the parent command.
if (attached.size() == 1) {
Node *dropping_node = nullptr;
@@ -1226,25 +1381,35 @@ NodeView::process_dropping_attached_nodes(MultiUndoCommand *command,
}
if (dropping_node && drop_edge_) {
// Remove old edge
drop_edge_command->add_child(new NodeEdgeRemoveCommand(
drop_edge_->output(), drop_edge_->input()));
void *edge_command = oakengine_undo_command_create_multi();
// Place new edges
drop_edge_command->add_child(
new NodeEdgeAddCommand(drop_edge_->output(), drop_input_));
drop_edge_command->add_child(
new NodeEdgeAddCommand(dropping_node, drop_edge_->input()));
oakengine_undo_command_multi_add_child(
edge_command,
oakengine_node_disconnect_command(
reinterpret_cast<OakEngineNode *>(drop_edge_->input().node()),
drop_edge_->input().input().toUtf8().constData(),
drop_edge_->input().element()));
oakengine_undo_command_multi_add_child(
edge_command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(drop_edge_->output()),
reinterpret_cast<OakEngineNode *>(drop_input_.node()),
drop_input_.input().toUtf8().constData(),
drop_input_.element()));
oakengine_undo_command_multi_add_child(
edge_command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(dropping_node),
reinterpret_cast<OakEngineNode *>(drop_edge_->input().node()),
drop_edge_->input().input().toUtf8().constData(),
drop_edge_->input().element()));
oakengine_undo_command_redo_now(edge_command);
oakengine_undo_command_multi_add_child(command, edge_command);
}
drop_edge_ = nullptr;
}
if (drop_edge_command->child_count()) {
drop_edge_command->redo_now();
command->add_child(drop_edge_command);
} else {
delete drop_edge_command;
}
}
detach_items_from_cursor(false);
@@ -1518,28 +1683,29 @@ void NodeView::group_nodes()
avg_pos /= items.size();
// Create group
NodeGroup *group = new NodeGroup();
Node *group = reinterpret_cast<Node *>(oakengine_node_group_create());
// Add group to graph and context
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
// Add nodes to group
Node *output_passthrough = nullptr;
QVector<Node *> nodes_to_group = selected_nodes_;
deselect_all();
foreach (Node *n, nodes_to_group) {
command->add_child(
new NodeRemovePositionFromContextCommand(n, context));
command->add_child(new NodeSetPositionCommand(
n, group, context->get_node_position_data_in_context(n)));
oakengine_undo_command_multi_add_child(command, oakengine_node_remove_position_command(reinterpret_cast<void *>(n), reinterpret_cast<void *>(context)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(n), reinterpret_cast<void *>(group), context->get_node_position_data_in_context(n).position.x(), context->get_node_position_data_in_context(n).position.y(), context->get_node_position_data_in_context(n).expanded ? 1 : 0));
for (auto it = n->inputs().cbegin(); it != n->inputs().cend(); it++) {
NodeInput input(n, *it, -1);
if (!input.is_connected() ||
!nodes_to_group.contains(input.get_connected_output())) {
command->add_child(
new NodeGroupAddInputPassthrough(group, input));
oakengine_undo_command_multi_add_child(command, (void *)(oakengine_group_add_input_passthrough_command(
reinterpret_cast<OakEngineNode *>(group),
reinterpret_cast<OakEngineNode *>(input.node()),
input.input().toUtf8().constData(), input.element(),
nullptr)));
}
}
@@ -1556,17 +1722,21 @@ void NodeView::group_nodes()
}
// Set output passthrough
command->add_child(
new NodeGroupSetOutputPassthrough(group, output_passthrough));
oakengine_undo_command_multi_add_child(command, (void *)(oakengine_group_set_output_passthrough_command(
reinterpret_cast<OakEngineNode *>(group),
reinterpret_cast<OakEngineNode *>(output_passthrough))));
// Add group to graph
command->add_child(new NodeAddCommand(context->parent(), group));
command->add_child(new NodeSetPositionCommand(group, context, avg_pos));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(context->parent()),
reinterpret_cast<OakEngineNode *>(group)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(group), reinterpret_cast<void *>(context), avg_pos.x(), avg_pos.y(), 0));
// Do command
Core::instance()->label_nodes({ group }, command);
Core::instance()->undo_stack()->push(command, tr("Grouped Nodes"));
oakengine_undo_push(command, tr("Grouped Nodes").toUtf8().constData());
}
void NodeView::ungroup_nodes()
@@ -1577,9 +1747,11 @@ void NodeView::ungroup_nodes()
return;
}
NodeGroup *group = nullptr;
Node *group = nullptr;
foreach (NodeViewItem *i, items) {
if ((group = dynamic_cast<NodeGroup *>(i->get_node()))) {
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(
i->get_node()))) {
group = i->get_node();
group_item = i;
break;
}
@@ -1589,30 +1761,27 @@ void NodeView::ungroup_nodes()
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
Node *context = group_item->get_context();
command->add_child(
new NodeRemovePositionFromContextCommand(group, context));
command->add_child(new NodeRemoveAndDisconnectCommand(group));
oakengine_undo_command_multi_add_child(command, oakengine_node_remove_position_command(reinterpret_cast<void *>(group), reinterpret_cast<void *>(context)));
oakengine_undo_command_multi_add_child(command, oakengine_node_remove_and_disconnect_command(reinterpret_cast<void *>(group)));
for (auto it = group->get_context_positions().cbegin();
it != group->get_context_positions().cend(); it++) {
command->add_child(
new NodeRemovePositionFromContextCommand(it.key(), group));
command->add_child(new NodeSetPositionCommand(
it.key(), context, group->get_node_position_data_in_context(it.key())));
oakengine_undo_command_multi_add_child(command, oakengine_node_remove_position_command(reinterpret_cast<void *>(it.key()), reinterpret_cast<void *>(group)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(it.key()), reinterpret_cast<void *>(context), group->get_node_position_data_in_context(it.key()).position.x(), group->get_node_position_data_in_context(it.key()).position.y(), group->get_node_position_data_in_context(it.key()).expanded ? 1 : 0));
}
Core::instance()->undo_stack()->push(command, tr("Ungrouped Nodes"));
oakengine_undo_push(command, tr("Ungrouped Nodes").toUtf8().constData());
}
void NodeView::show_node_properties()
{
Node *first_node = selected_nodes_.first();
if (NodeGroup *group = dynamic_cast<NodeGroup *>(first_node)) {
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(first_node))) {
if (!overlay_view_) {
overlay_view_ = new NodeView(this);
overlay_view_->show();
@@ -1647,19 +1816,20 @@ void NodeView::show_node_properties()
overlay_close_btn->setStyleSheet(
QStringLiteral("background: transparent; border: none;"));
}
overlay_view_->set_contexts({ group });
overlay_view_->set_contexts({ first_node });
resize_overlay();
QMetaObject::invokeMethod(overlay_view_,
&NodeView::center_on_items_bounding_rect,
Qt::QueuedConnection);
overlay_view_->setFocus();
emit nodes_deselected(selected_nodes_);
emit node_selection_changed(QVector<Node *>());
emit node_selection_changed_with_contexts(QVector<Node::ContextPair>());
emit nodes_deselected(node_vector_to_engine(selected_nodes_));
emit node_selection_changed(QVector<OakEngineNode *>());
emit node_selection_changed_with_contexts(
QVector<QPair<OakEngineNode *, OakEngineNode *>>());
overlay_view_->select_all();
emit node_group_opened(group);
emit node_group_opened(reinterpret_cast<OakEngineNode *>(first_node));
} else {
label_selected_nodes();
}
@@ -1701,7 +1871,8 @@ void NodeView::show_selected_node_in_param_editor()
}
}
emit node_selection_changed_with_contexts(selection_with_contexts);
emit node_selection_changed_with_contexts(
context_pair_vector_to_engine(selection_with_contexts));
}
void NodeView::label_selected_nodes()
@@ -1752,14 +1923,14 @@ void NodeView::add_context(Node *n)
connect(ctx, &NodeViewContext::item_about_to_be_deleted, this,
&NodeView::item_about_to_be_deleted);
connect(n, &Node::removed_from_graph, this, &NodeView::node_removed_from_graph);
removed_from_graph_subs_[n] = bridge_->subscribe(
reinterpret_cast<void *>(n), OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH);
}
void NodeView::remove_context(Node *n)
{
scene_.remove_context(n);
disconnect(n, &Node::removed_from_graph, this,
&NodeView::node_removed_from_graph);
bridge_->unsubscribe(removed_from_graph_subs_.take(n));
}
bool NodeView::is_item_attached_to_cursor(NodeViewItem *item) const
@@ -1788,7 +1959,7 @@ void NodeView::collapse_item(NodeViewItem *item)
void NodeView::end_edge_drag(bool cancel)
{
// Check if the edge was reconnected to the same place as before
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
bool reconnected_to_itself = false;
@@ -1799,8 +1970,12 @@ void NodeView::end_edge_drag(bool cancel)
reconnected_to_itself = true;
} else {
// We are moving (or removing) an existing edge
command->add_child(new NodeEdgeRemoveCommand(
create_edge_->output(), create_edge_->input()));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_disconnect_command(
reinterpret_cast<OakEngineNode *>(create_edge_->input().node()),
create_edge_->input().input().toUtf8().constData(),
create_edge_->input().element()));
}
}
} else {
@@ -1827,15 +2002,32 @@ void NodeView::end_edge_drag(bool cancel)
if (!reconnected_to_itself) {
Node *creating_output = create_edge_output_item_->get_node();
while (NodeGroup *output_group =
dynamic_cast<NodeGroup *>(creating_output)) {
creating_output = output_group->get_output_passthrough();
while (oakengine_node_is_group(
reinterpret_cast<OakEngineNode *>(creating_output))) {
OakEngineNode *out = oakengine_group_get_output_passthrough(
reinterpret_cast<OakEngineNode *>(creating_output));
if (!out) {
break;
}
creating_output = reinterpret_cast<Node *>(out);
}
while (NodeGroup *input_group =
dynamic_cast<NodeGroup *>(creating_input.node())) {
creating_input =
input_group->get_input_from_id(creating_input.input());
while (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(
creating_input.node()))) {
OakEngineNode *inner_node = nullptr;
char inner_input[256];
int inner_element = 0;
if (oakengine_group_get_passthrough_from_id(
reinterpret_cast<OakEngineNode *>(
creating_input.node()),
creating_input.input().toUtf8().constData(),
&inner_node, inner_input, sizeof(inner_input),
&inner_element) != OAKENGINE_OK) {
break;
}
creating_input = NodeInput(
reinterpret_cast<Node *>(inner_node),
QString::fromUtf8(inner_input), inner_element);
}
if (creating_input.is_connected()) {
@@ -1862,33 +2054,52 @@ void NodeView::end_edge_drag(bool cancel)
}
if (!cancel) {
command->add_child(new NodeEdgeRemoveCommand(
existing_edge_to_remove.first,
existing_edge_to_remove.second));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_disconnect_command(
reinterpret_cast<OakEngineNode *>(existing_edge_to_remove.second.node()),
existing_edge_to_remove.second.input().toUtf8().constData(),
existing_edge_to_remove.second.element()));
}
}
if (!cancel) {
command->add_child(new NodeEdgeAddCommand(creating_output,
creating_input));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(creating_output),
reinterpret_cast<OakEngineNode *>(creating_input.node()),
creating_input.input().toUtf8().constData(),
creating_input.element()));
command_name = Node::get_connect_command_string(
creating_output, creating_input);
{
char cmd_buf[256];
oakengine_node_connect_command_string(
reinterpret_cast<OakEngineNode*>(creating_output),
reinterpret_cast<OakEngineNode*>(creating_input.node()),
creating_input.input().toUtf8().constData(),
creating_input.element(), cmd_buf, sizeof(cmd_buf));
command_name = QString::fromUtf8(cmd_buf);
}
// If the output is not in the input's context, add it now. We check the item rather than
// the node itself, because sometimes a node may not be in the context but another node
// representing it will be (e.g. groups)
if (!scene_.context_map()
.value(create_edge_input_item_->get_context())
->get_item_from_map(creating_output)) {
command->add_child(new NodeSetPositionCommand(
creating_output,
create_edge_input_item_->get_context(),
scene_.context_map()
.value(create_edge_input_item_->get_context())
->map_scene_pos_to_node_pos_in_context(
create_edge_output_item_->scenePos())));
}
if (!scene_.context_map()
.value(create_edge_input_item_->get_context())
->get_item_from_map(creating_output)) {
QPointF new_pos = scene_.context_map()
.value(create_edge_input_item_->get_context())
->map_scene_pos_to_node_pos_in_context(
create_edge_output_item_->scenePos());
oakengine_undo_command_multi_add_child(
command,
oakengine_node_set_position_command(
reinterpret_cast<void *>(creating_output),
reinterpret_cast<void *>(
create_edge_input_item_->get_context()),
new_pos.x(), new_pos.y(), 0));
}
}
}
}
@@ -1905,9 +2116,10 @@ void NodeView::end_edge_drag(bool cancel)
}
create_edge_expanded_items_.clear();
Core::instance()->undo_stack()->push(command, command_name);
oakengine_undo_push(command, command_name.toUtf8().constData());
}
void NodeView::post_paste(const QVector<Node *> &new_nodes,
const Node::PositionMap &map)
{
+18 -12
View File
@@ -23,10 +23,11 @@
#define OAK_NODEVIEW_H
#include <QGraphicsView>
#include <QHash>
#include <QTimer>
#include "core.h"
#include "node/group/group.h"
#include "engineeventbridge.h"
#include "nodeviewedge.h"
#include "nodeviewcontext.h"
#include "nodeviewminimap.h"
@@ -78,8 +79,9 @@ public:
void select_all();
void deselect_all();
void select(const QVector<Node::ContextPair> &nodes,
bool center_view_on_item);
void select(
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes,
bool center_view_on_item);
void copy_selected(bool cut);
void paste();
@@ -112,20 +114,20 @@ public slots:
void center_on_items_bounding_rect();
void center_on_node(olive::Node *n);
void center_on_node(OakEngineNode *n);
void label_selected_nodes();
signals:
void nodes_selected(const QVector<Node *> &nodes);
void nodes_selected(const QVector<OakEngineNode *> &nodes);
void nodes_deselected(const QVector<Node *> &nodes);
void nodes_deselected(const QVector<OakEngineNode *> &nodes);
void node_selection_changed(const QVector<Node *> &nodes);
void
node_selection_changed_with_contexts(const QVector<Node::ContextPair> &nodes);
void node_selection_changed(const QVector<OakEngineNode *> &nodes);
void node_selection_changed_with_contexts(
const QVector<QPair<OakEngineNode *, OakEngineNode *>> &nodes);
void node_group_opened(NodeGroup *group);
void node_group_opened(OakEngineNode *group);
void node_group_closed();
void esc_pressed();
@@ -161,7 +163,7 @@ private:
void move_attached_nodes_to_cursor(const QPoint &p);
void process_moving_attached_nodes(const QPoint &pos);
QVector<Node *> process_dropping_attached_nodes(MultiUndoCommand *command,
QVector<Node *> process_dropping_attached_nodes(void *command,
Node *select_context,
const QPoint &pos);
Node *get_context_at_mouse_pos(const QPoint &p);
@@ -241,6 +243,10 @@ private:
bool dont_emit_selection_signals_;
EngineEventBridge *bridge_ = nullptr;
QHash<Node *, int64_t> removed_from_graph_subs_;
QAction *show_in_param_editor_action_;
static const double k_minimum_scale;
@@ -281,7 +287,7 @@ private slots:
void move_to_scene_point(const QPointF &pos);
void node_removed_from_graph();
void node_removed_from_graph(OakEngineNode *source);
void group_nodes();
+98 -51
View File
@@ -19,6 +19,8 @@
#include "nodeviewcontext.h"
#include <QBrush>
#include "oakengine/node.h"
#include <QCoreApplication>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
@@ -27,13 +29,13 @@
#include "core.h"
#include "node/block/block.h"
#include "node/group/group.h"
#include "node/output/track/track.h"
#include "node/project.h"
#include "node/project/sequence/sequence.h"
#include "nodeviewitem.h"
#include "ui/colorcoding.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -42,18 +44,31 @@ namespace olive
NodeViewContext::NodeViewContext(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 = block->track()
->sequence()
->get_video_params()
Rational timebase = viewer_output_video_params(block->track()->sequence())
.frame_rate_as_time_base();
QString type_label;
switch (block->track()->type()) {
case Track::k_video:
type_label = QCoreApplication::translate("NodeViewContext", "V");
break;
case Track::k_audio:
type_label = QCoreApplication::translate("NodeViewContext", "A");
break;
case Track::k_subtitle:
type_label = QCoreApplication::translate("NodeViewContext", "S");
break;
default:
break;
}
lbl_ =
QCoreApplication::translate("NodeViewContext", "%1 [%2] :: %3 - %4")
.arg(block->get_label_and_name(),
Track::Reference::type_to_translated_string(
block->track()->type()),
type_label,
QString::fromStdString(Timecode::time_to_timecode(
block->in(), timebase,
Core::instance()->get_timecode_display())),
@@ -64,17 +79,58 @@ NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item)
lbl_ = context_->get_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));
} else {
group_added_node(reinterpret_cast<Node *>(node), src);
}
});
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));
} else {
group_removed_node(reinterpret_cast<Node *>(node), src);
}
});
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));
});
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));
});
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));
});
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT));
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
OAKENGINE_EVENT_NODE_CONTEXT_POSITION_CHANGED));
node_subs_[context_].append(bridge_->subscribe(
reinterpret_cast<void *>(context_),
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());
}
connect(context_, &Node::node_added_to_context, this,
&NodeViewContext::add_child, Qt::DirectConnection);
connect(context_, &Node::node_position_in_context_changed, this,
&NodeViewContext::set_child_position, Qt::DirectConnection);
connect(context_, &Node::node_removed_from_context, this,
&NodeViewContext::remove_child, Qt::DirectConnection);
}
NodeViewContext::~NodeViewContext()
@@ -95,17 +151,19 @@ void NodeViewContext::add_child(Node *node)
add_node_internal(node, item);
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
for (auto it = group->get_context_positions().cbegin();
it != group->get_context_positions().cend(); it++) {
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);
}
connect(group, &NodeGroup::node_added_to_context, this,
&NodeViewContext::group_added_node);
connect(group, &NodeGroup::node_removed_from_context, this,
&NodeViewContext::group_removed_node);
node_subs_[node].append(bridge_->subscribe(
reinterpret_cast<void *>(node),
OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT));
node_subs_[node].append(bridge_->subscribe(
reinterpret_cast<void *>(node),
OAKENGINE_EVENT_NODE_NODE_REMOVED_FROM_CONTEXT));
}
update_rect();
@@ -118,16 +176,8 @@ void NodeViewContext::set_child_position(Node *node, const QPointF &pos)
void NodeViewContext::remove_child(Node *node)
{
disconnect(node, &Node::input_connected, this,
&NodeViewContext::child_input_connected);
disconnect(node, &Node::input_disconnected, this,
&NodeViewContext::child_input_disconnected);
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node)) {
disconnect(group, &NodeGroup::node_added_to_context, this,
&NodeViewContext::group_added_node);
disconnect(group, &NodeGroup::node_removed_from_context, this,
&NodeViewContext::group_removed_node);
foreach (int64_t id, node_subs_.take(node)) {
bridge_->unsubscribe(id);
}
NodeViewItem *item = item_map_.take(node);
@@ -151,7 +201,8 @@ void NodeViewContext::remove_child(Node *node)
// 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 (dynamic_cast<NodeGroup *>(item->get_node())) {
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(
item->get_node()))) {
for (auto it = item_map_.begin(); it != item_map_.end();) {
if (it.value() == item) {
it = item_map_.erase(it);
@@ -233,26 +284,24 @@ void NodeViewContext::set_curved_edges(bool e)
}
}
int NodeViewContext::delete_selected(NodeViewDeleteCommand *command)
void NodeViewContext::get_selected_for_deletion(QVector<Node *> &nodes,
QVector<Node *> &contexts,
QVector<NodeViewEdge *> &edges) const
{
int count = 0;
// Delete any selected edges
// Collect any selected edges
foreach (NodeViewEdge *edge, edges_) {
if (edge->isSelected()) {
command->add_edge(edge->output(), edge->input());
edges.append(edge);
}
}
// Delete any selected nodes
// Collect any selected nodes
foreach (NodeViewItem *node, item_map_) {
if (node->isSelected()) {
command->add_node(node->get_node(), context_);
count++;
nodes.append(node->get_node());
contexts.append(context_);
}
}
return count;
}
void NodeViewContext::select(const QVector<Node *> &nodes)
@@ -346,10 +395,12 @@ void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event)
void NodeViewContext::add_node_internal(Node *node, NodeViewItem *item)
{
connect(node, &Node::input_connected, this,
&NodeViewContext::child_input_connected);
connect(node, &Node::input_disconnected, this,
&NodeViewContext::child_input_disconnected);
node_subs_[node].append(bridge_->subscribe(
reinterpret_cast<void *>(node),
OAKENGINE_EVENT_NODE_INPUT_CONNECTED));
node_subs_[node].append(bridge_->subscribe(
reinterpret_cast<void *>(node),
OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED));
item_map_.insert(node, item);
@@ -393,17 +444,13 @@ void NodeViewContext::add_edge_internal(Node *output, const NodeInput &input,
edges_.append(edge_ui);
}
void NodeViewContext::group_added_node(Node *node)
void NodeViewContext::group_added_node(Node *node, Node *group)
{
NodeGroup *group = static_cast<NodeGroup *>(sender());
add_node_internal(node, item_map_.value(group));
}
void NodeViewContext::group_removed_node(Node *node)
void NodeViewContext::group_removed_node(Node *node, Node *group)
{
NodeGroup *group = static_cast<NodeGroup *>(sender());
if (item_map_.value(node) == item_map_.value(group)) {
item_map_.remove(node);
}
+19 -6
View File
@@ -21,9 +21,10 @@
#include <QGraphicsRectItem>
#include <QGraphicsTextItem>
#include <QHash>
#include "engineeventbridge.h"
#include "node/node.h"
#include "node/nodeundo.h"
#include "nodeviewcommon.h"
#include "nodeviewedge.h"
@@ -48,7 +49,9 @@ public:
void set_curved_edges(bool e);
int delete_selected(NodeViewDeleteCommand *command);
void get_selected_for_deletion(QVector<Node *> &nodes,
QVector<Node *> &contexts,
QVector<NodeViewEdge *> &edges) const;
void select(const QVector<Node *> &nodes);
@@ -65,7 +68,11 @@ public:
const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override;
public slots:
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);
void set_child_position(Node *node, const QPointF &pos);
@@ -105,10 +112,16 @@ private:
QVector<NodeViewEdge *> edges_;
private slots:
void group_added_node(Node *node);
EngineEventBridge *bridge_ = nullptr;
void group_removed_node(Node *node);
QHash<Node *, 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);
void group_removed_node(Node *node, Node *group);
};
}
+30 -16
View File
@@ -22,15 +22,16 @@
#include "nodeviewitem.h"
#include <QDebug>
#include "oakengine/node.h"
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
#include "common/qtutils.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/nodeundo.h"
#include "node/value.h"
#include "pluginSupport/oliveplugininstance.h"
#include "nodeview.h"
@@ -69,17 +70,23 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
input_connector_ = new NodeViewItemConnector(false, this);
output_connector_ = new NodeViewItemConnector(true, this);
connect(node_, &Node::label_changed, 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);
connect(bridge_, &EngineEventBridge::node_label_changed, this,
&NodeViewItem::node_appearance_changed);
connect(node_, &Node::color_changed, this,
connect(bridge_, &EngineEventBridge::node_color_changed, this,
&NodeViewItem::node_appearance_changed);
connect(node_, &Node::message_count_changed, this,
connect(bridge_, &EngineEventBridge::node_message_count_changed, this,
&NodeViewItem::node_appearance_changed);
if (is_output_item()) {
connect(node_, &Node::input_added, this,
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_INPUT_ADDED);
bridge_->subscribe(reinterpret_cast<void *>(node_), OAKENGINE_EVENT_NODE_INPUT_REMOVED);
connect(bridge_, &EngineEventBridge::node_input_added, this,
&NodeViewItem::repopulate_inputs);
connect(node_, &Node::input_removed, this,
connect(bridge_, &EngineEventBridge::node_input_removed, this,
&NodeViewItem::repopulate_inputs);
repopulate_inputs();
@@ -94,10 +101,11 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element,
} else {
output_connector_->setVisible(false);
connect(node_, &Node::input_array_size_changed, this,
&NodeViewItem::input_array_size_changed);
connect(node_, &Node::input_array_size_changed, this,
&NodeViewItem::input_array_size_changed);
bridge_->subscribe(reinterpret_cast<void *>(node_), 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);
});
}
// This should be set during runtime, but just in case here's a default fallback
@@ -875,12 +883,18 @@ void NodeViewItem::set_highlighted(bool e)
NodeViewItem *NodeViewItem::get_item_for_input(NodeInput input)
{
if (NodeGroup *group = dynamic_cast<NodeGroup *>(node_)) {
if (input.node() != group) {
if (oakengine_node_is_group(reinterpret_cast<OakEngineNode *>(node_))) {
if (input.node() != node_) {
// Translate input to group input
QString id = group->get_id_of_passthrough(input);
input.set_node(group);
input.set_input(id);
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(),
id, sizeof(id)) > 0) {
input.set_node(node_);
input.set_input(QString::fromUtf8(id));
}
}
}
+3
View File
@@ -30,6 +30,7 @@
#include "node/node.h"
#include "nodeviewcommon.h"
#include "nodeviewitemconnector.h"
#include "engineeventbridge.h"
namespace olive
{
@@ -233,6 +234,8 @@ private:
bool label_as_output_;
EngineEventBridge *bridge_ = nullptr;
private slots:
void node_appearance_changed();
+5 -1
View File
@@ -63,10 +63,14 @@ public:
return curved_edges_;
}
public slots:
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);
public slots:
/**
* @brief Set whether edges in this scene should be curved or not
*/
@@ -26,7 +26,7 @@
#include <QHBoxLayout>
#include "core.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "ui/icons/icons.h"
namespace olive
+52 -65
View File
@@ -38,10 +38,12 @@
#include "projectexplorerundo.h"
#include "oakengine/footage.h"
#include "oakengine/node.h"
#include "task/taskmanager.h"
#include "oakengine/task.h"
#include "oakengine/videoparams.h"
#include "oakengine/project.h"
#include "oakengine/undo.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "node/nodeundo.h"
#include "window/mainwindow/mainwindow.h"
#include "window/mainwindow/mainwindowundo.h"
#include "widget/timelinewidget/timelinewidget.h"
@@ -56,7 +58,11 @@ QVector<Footage *> get_selected_proxy_footage(const QVector<Node *> &items)
QVector<Footage *> footage;
for (Node *node : items) {
Footage *candidate = dynamic_cast<Footage *>(node);
if (!candidate || !candidate->get_first_enabled_video_stream().is_valid() ||
oak_video_params _vp;
if (!candidate ||
oakengine_viewer_get_first_enabled_video_stream(
reinterpret_cast<OakEngineNode *>(candidate), &_vp) < 0 ||
!oakengine_video_params_is_valid(&_vp) ||
footage.contains(candidate)) {
continue;
}
@@ -65,45 +71,6 @@ QVector<Footage *> get_selected_proxy_footage(const QVector<Node *> &items)
return footage;
}
/**
* @brief Proxy generation driven by the liboakengine C ABI facade
*
* Replaces the direct ProxyManager::get_or_start_proxy() drive: the actual
* transcode and its synchronous wait live behind
* oakengine_footage_proxy_generate() (which also records the proxy state
* on the footage and invalidates it), while the task stays on the
* TaskManager queue like before.
*/
class FacadeProxyTask : public Task {
public:
FacadeProxyTask(Footage *footage)
: footage_(footage)
{
set_title(tr("Generating proxy for \"%1\"")
.arg(footage->get_label_or_name()));
}
protected:
virtual bool run() override
{
OakEngineFootage *handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(footage_));
const int rc = oakengine_footage_proxy_generate(handle);
oakengine_footage_free(handle);
if (rc != OAKENGINE_OK) {
char err[512];
err[0] = '\0';
oakengine_footage_last_error(err, sizeof(err));
set_error(err[0] ? QString::fromUtf8(err) :
tr("Proxy generation failed"));
return false;
}
return true;
}
private:
Footage *footage_;
};
}
ProjectExplorer::ProjectExplorer(QWidget *parent)
@@ -191,10 +158,10 @@ void ProjectExplorer::set_view_type(ProjectToolbar::ViewType type)
}
}
void ProjectExplorer::edit(Node *item)
void ProjectExplorer::edit(OakEngineNode *item)
{
current_view()->edit(
sort_model_.mapFromSource(model_.create_index_from_item(item)));
sort_model_.mapFromSource(model_.create_index_from_item(reinterpret_cast<Node *>(item))));
}
void ProjectExplorer::add_view(QAbstractItemView *view)
@@ -257,7 +224,7 @@ int ProjectExplorer::confirm_item_deletion(Node *item)
bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
bool &check_if_item_is_in_use,
MultiUndoCommand *command)
void *command)
{
for (int i = 0; i < selected.size(); i++) {
// Delete sequences first
@@ -291,16 +258,29 @@ bool ProjectExplorer::delete_items_internal(const QVector<Node *> &selected,
Sequence *sequence = dynamic_cast<Sequence *>(node);
if (sequence &&
Core::instance()->main_window()->is_sequence_open(sequence)) {
command->add_child(new CloseSequenceCommand(sequence));
oakengine_undo_command_multi_add_child(command, make_close_sequence_command(sequence));
}
if (node->folder()) {
command->add_child(
new Folder::RemoveElementCommand(node->folder(), node));
oakengine_undo_command_multi_add_child(
command,
oakengine_folder_remove_element_command(
reinterpret_cast<OakEngineNode *>(node->folder()),
reinterpret_cast<OakEngineNode *>(node)));
}
command->add_child(
new NodeRemoveWithExclusiveDependenciesAndDisconnect(node));
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()) {
oakengine_undo_command_multi_add_child(
remove_cmd,
oakengine_node_remove_and_disconnect_command(
reinterpret_cast<void *>(dep)));
}
oakengine_undo_command_multi_add_child(command, remove_cmd);
}
}
@@ -356,7 +336,7 @@ void ProjectExplorer::item_double_clicked_slot(const QModelIndex &index)
}
// Emit a signal
emit double_clicked_item(i);
emit double_clicked_item(reinterpret_cast<OakEngineNode *>(i));
}
void ProjectExplorer::size_changed_slot(int s)
@@ -457,7 +437,9 @@ void ProjectExplorer::show_context_menu()
Sequence *sequence_cast_test = dynamic_cast<Sequence *>(i);
if (footage_cast_test &&
!footage_cast_test->has_enabled_video_streams()) {
!oakengine_viewer_has_enabled_streams(
reinterpret_cast<OakEngineNode *>(footage_cast_test),
OAKENGINE_TRACK_TYPE_VIDEO)) {
all_items_have_video_streams = false;
}
@@ -659,8 +641,10 @@ void ProjectExplorer::generate_proxies_for_selected_footage()
<< "GenerateProxiesForSelectedFootage: starting proxy generation for"
<< footage.size() << "footage item(s)";
for (Footage *item : footage) {
const VideoParams video = item->get_first_enabled_video_stream();
if (!video.is_valid()) {
oak_video_params _vp;
if (oakengine_viewer_get_first_enabled_video_stream(
reinterpret_cast<OakEngineNode *>(item), &_vp) < 0 ||
!oakengine_video_params_is_valid(&_vp)) {
qWarning()
<< "GenerateProxiesForSelectedFootage: skipping item with no valid video stream"
<< item->filename();
@@ -669,7 +653,9 @@ void ProjectExplorer::generate_proxies_for_selected_footage()
// Queue one facade-backed task per footage item (same queueing
// semantics as the old per-footage proxy tasks).
TaskManager::instance()->add_task(new FacadeProxyTask(item));
OakEngineTask *proxy_task = oakengine_task_create_proxy(
reinterpret_cast<OakEngineNode *>(item));
oakengine_task_manager_add(proxy_task);
}
}
@@ -689,10 +675,10 @@ void ProjectExplorer::set_selected_footage_proxy_enabled(bool enabled)
OakEngineFootage *handle = oakengine_footage_borrow(
reinterpret_cast<OakEngineNode *>(item));
oakengine_footage_proxy_set_enabled(handle, enabled ? 1 : 0);
oakengine_footage_free(handle);
// The facade call toggles the flag; cache invalidation for the UI
// stays here.
item->invalidate_all(Footage::k_filename_input);
oakengine_footage_invalidate(handle);
oakengine_footage_free(handle);
}
}
@@ -765,18 +751,19 @@ void ProjectExplorer::view_selection_changed()
QModelIndexList selection = model->selectedIndexes();
QVector<Node *> nodes;
QVector<OakEngineNode *> nodes;
foreach (const QModelIndex &index, selection) {
Node *sel = static_cast<Node *>(
sort_model_.mapToSource(index).internalPointer());
if (!nodes.contains(sel)) {
nodes.append(sel);
auto handle = reinterpret_cast<OakEngineNode *>(sel);
if (!nodes.contains(handle)) {
nodes.append(handle);
}
}
if (nodes.isEmpty()) {
nodes.append(get_root());
nodes.append(reinterpret_cast<OakEngineNode *>(get_root()));
}
emit selection_changed(nodes);
@@ -900,15 +887,15 @@ void ProjectExplorer::delete_selected()
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
bool check_if_item_is_in_use = true;
if (delete_items_internal(selected, check_if_item_is_in_use, command)) {
Core::instance()->undo_stack()->push(
command, tr("Deleted %1 Item(s)").arg(selected.size()));
oakengine_undo_push(
command, tr("Deleted %1 Item(s)").arg(selected.size()).toUtf8().constData());
} else {
delete command;
oakengine_undo_command_free(command);
}
}
+6 -4
View File
@@ -35,6 +35,8 @@
#include "widget/projectexplorer/projectexplorernavigation.h"
#include "widget/projecttoolbar/projecttoolbar.h"
struct OakEngineNode;
namespace olive
{
@@ -91,7 +93,7 @@ public:
public slots:
void set_view_type(ProjectToolbar::ViewType type);
void edit(Node *item);
void edit(OakEngineNode *item);
void rename_selected_item();
@@ -105,9 +107,9 @@ signals:
*
* The Item that was double clicked, or nullptr if empty area was double clicked
*/
void double_clicked_item(Node *item);
void double_clicked_item(OakEngineNode *item);
void selection_changed(const QVector<Node *> &selected);
void selection_changed(const QVector<OakEngineNode *> &selected);
private:
/**
@@ -143,7 +145,7 @@ private:
bool delete_items_internal(const QVector<Node *> &selected,
bool &check_if_item_is_in_use,
MultiUndoCommand *command);
void *command);
static QString get_human_readable_node_name(Node *node);
@@ -22,7 +22,6 @@
#ifndef OAK_PROJECTEXPLORERUNDO_H
#define OAK_PROJECTEXPLORERUNDO_H
#include "undo/undocommand.h"
namespace olive
{
+76 -52
View File
@@ -20,6 +20,7 @@
***/
#include "projectviewmodel.h"
#include "ui/icons/icons.h"
#include <QDebug>
#include <QMimeData>
@@ -27,7 +28,9 @@
#include "common/qtutils.h"
#include "core.h"
#include "node/nodeundo.h"
#include "oakengine/project.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
namespace olive
{
@@ -35,7 +38,35 @@ namespace olive
ProjectViewModel::ProjectViewModel(QObject *parent)
: QAbstractItemModel(parent)
, project_(nullptr)
, bridge_(new EngineEventBridge(this))
{
connect_bridge_signals();
}
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);
});
connect(bridge_, &EngineEventBridge::folder_end_insert_item, this,
[this](OakEngineNode *) {
this->folder_end_insert_item();
});
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);
});
connect(bridge_, &EngineEventBridge::folder_end_remove_item, this,
[this](OakEngineNode *) {
this->folder_end_remove_item();
});
connect(bridge_, &EngineEventBridge::node_label_changed, this,
&ProjectViewModel::item_renamed);
}
Project *ProjectViewModel::project() const
@@ -49,6 +80,10 @@ void ProjectViewModel::set_project(Project *p)
if (project_) {
disconnect_item(project_->root());
// Recreate bridge to clear all folder subscriptions
delete bridge_;
bridge_ = new EngineEventBridge(this);
connect_bridge_signals();
}
project_ = p;
@@ -182,7 +217,7 @@ QVariant ProjectViewModel::data(const QModelIndex &index, int role) const
case Qt::DecorationRole:
// If this is the first column, return the Item's icon
if (column_type == k_name) {
return internal_item->data(Node::icon);
return icon::from_name(internal_item->data(Node::icon).toString());
}
break;
case Qt::ToolTipRole:
@@ -239,13 +274,13 @@ bool ProjectViewModel::setData(const QModelIndex &index, const QVariant &value,
QString new_name = value.toString();
if (!new_name.isEmpty()) {
NodeRenameCommand *nrc = new NodeRenameCommand();
void *nrc = oakengine_node_rename_command(
reinterpret_cast<OakEngineNode *>(item),
new_name.toUtf8().constData());
nrc->add_node(item, value.toString());
Core::instance()->undo_stack()->push(
nrc, tr("Renamed Item \"%1\" to \"%2\"")
.arg(item->get_label(), new_name));
oakengine_undo_push(
nrc,
tr("Renamed Item \"%1\" to \"%2\"").arg(item->get_label(), new_name).toUtf8().constData());
return true;
}
@@ -284,7 +319,7 @@ Qt::ItemFlags ProjectViewModel::flags(const QModelIndex &index) const
QStringList ProjectViewModel::mimeTypes() const
{
// Allow data from this model and a file list from external sources
return { Project::k_item_mime_type, QStringLiteral("text/uri-list") };
return { QString::fromUtf8(oakengine_project_item_mime_type()), QStringLiteral("text/uri-list") };
}
QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
@@ -326,7 +361,7 @@ QMimeData *ProjectViewModel::mimeData(const QModelIndexList &indexes) const
}
// Set byte array as the mime data and return the mime data
data->setData(Project::k_item_mime_type, encoded_data);
data->setData(QString::fromUtf8(oakengine_project_item_mime_type()), encoded_data);
return data;
}
@@ -347,9 +382,9 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
// Probe mime data for its format
QStringList mime_formats = data->formats();
if (mime_formats.contains(Project::k_item_mime_type)) {
if (mime_formats.contains(QString::fromUtf8(oakengine_project_item_mime_type()))) {
// Data is drag/drop data from this model
QByteArray model_data = data->data(Project::k_item_mime_type);
QByteArray model_data = data->data(QString::fromUtf8(oakengine_project_item_mime_type()));
// Use QDataStream to deserialize the data
QDataStream stream(&model_data, QIODevice::ReadOnly);
@@ -367,10 +402,8 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
quintptr item_ptr;
QList<Track::Reference> streams;
// Loop through all data
MultiUndoCommand *move_command = new MultiUndoCommand();
int count = 0;
// Loop through all data, collecting the items to move
QVector<OakEngineNode *> items_to_move;
while (!stream.atEnd()) {
stream >> streams >> item_ptr;
@@ -384,18 +417,18 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data,
(!dynamic_cast<Folder *>(item) ||
!item_is_parent_of_child(static_cast<Folder *>(item),
drop_location))) {
move_command->add_child(new NodeEdgeRemoveCommand(
item,
NodeInput(item->folder(), Folder::k_child_input,
item->folder()->index_of_child_in_array(item))));
move_command->add_child(
new FolderAddChild(drop_location, item));
count++;
items_to_move.append(reinterpret_cast<OakEngineNode *>(item));
}
}
Core::instance()->undo_stack()->push(move_command,
tr("Move %1 Item(s)").arg(count));
if (!items_to_move.isEmpty()) {
// ONE undoable command for the whole move (facade removes each
// 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),
tr("Move %1 Item(s)").arg(items_to_move.size()).toUtf8().constData());
}
return true;
@@ -475,18 +508,16 @@ bool ProjectViewModel::item_is_parent_of_child(Folder *parent, Node *child) cons
void ProjectViewModel::connect_item(Node *n)
{
connect(n, &Node::label_changed, this, &ProjectViewModel::item_renamed);
label_changed_subs_[n] = bridge_->subscribe(
reinterpret_cast<void *>(n), OAKENGINE_EVENT_NODE_LABEL_CHANGED);
Folder *f = dynamic_cast<Folder *>(n);
if (f) {
connect(f, &Folder::begin_insert_item, this,
&ProjectViewModel::folder_begin_insert_item);
connect(f, &Folder::end_insert_item, this,
&ProjectViewModel::folder_end_insert_item);
connect(f, &Folder::begin_remove_item, this,
&ProjectViewModel::folder_begin_remove_item);
connect(f, &Folder::end_remove_item, this,
&ProjectViewModel::folder_end_remove_item);
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);
foreach (Node *c, f->children()) {
connect_item(c);
@@ -496,29 +527,23 @@ void ProjectViewModel::connect_item(Node *n)
void ProjectViewModel::disconnect_item(Node *n)
{
disconnect(n, &Node::label_changed, this, &ProjectViewModel::item_renamed);
int64_t sub = label_changed_subs_.take(n);
if (sub > 0) {
bridge_->unsubscribe(sub);
}
Folder *f = dynamic_cast<Folder *>(n);
if (f) {
disconnect(f, &Folder::begin_insert_item, this,
&ProjectViewModel::folder_begin_insert_item);
disconnect(f, &Folder::end_insert_item, this,
&ProjectViewModel::folder_end_insert_item);
disconnect(f, &Folder::begin_remove_item, this,
&ProjectViewModel::folder_begin_remove_item);
disconnect(f, &Folder::end_remove_item, this,
&ProjectViewModel::folder_end_remove_item);
// Bridge subscriptions are cleaned up by recreating the bridge in set_project
foreach (Node *c, f->children()) {
disconnect_item(c);
}
}
}
void ProjectViewModel::folder_begin_insert_item(Node *n, int insert_index)
void ProjectViewModel::folder_begin_insert_item(Folder *folder, Node *n,
int insert_index)
{
Folder *folder = static_cast<Folder *>(sender());
connect_item(n);
QModelIndex index;
@@ -535,10 +560,9 @@ void ProjectViewModel::folder_end_insert_item()
endInsertRows();
}
void ProjectViewModel::folder_begin_remove_item(Node *n, int child_index)
void ProjectViewModel::folder_begin_remove_item(Folder *folder, Node *n,
int child_index)
{
Folder *folder = static_cast<Folder *>(sender());
disconnect_item(n);
QModelIndex index;
@@ -555,9 +579,9 @@ void ProjectViewModel::folder_end_remove_item()
endRemoveRows();
}
void ProjectViewModel::item_renamed()
void ProjectViewModel::item_renamed(OakEngineNode *source)
{
Node *item = static_cast<Node *>(sender());
Node *item = reinterpret_cast<Node *>(source);
QModelIndex index = create_index_from_item(item);
+19 -6
View File
@@ -23,10 +23,11 @@
#define OAK_VIEWMODEL_H
#include <QAbstractItemModel>
#include <QHash>
#include "engineeventbridge.h"
#include "node/block/block.h"
#include "node/project.h"
#include "undo/undocommand.h"
namespace olive
{
@@ -157,18 +158,30 @@ private:
void disconnect_item(Node *n);
Project *project_;
/**
* @brief Wire the bridge's folder signals to our handlers.
*
* Must be re-run every time bridge_ is recreated (set_project), since
* Qt connections belong to the old bridge instance.
*/
void connect_bridge_signals();
private slots:
void folder_begin_insert_item(Node *n, int insert_index);
void folder_begin_insert_item(Folder *folder, Node *n, int insert_index);
void folder_end_insert_item();
void folder_begin_remove_item(Node *n, int child_index);
void folder_begin_remove_item(Folder *folder, Node *n, int child_index);
void folder_end_remove_item();
void item_renamed();
Project *project_;
EngineEventBridge *bridge_;
QHash<Node *, int64_t> label_changed_subs_;
private slots:
void item_renamed(OakEngineNode *source);
};
}
@@ -36,6 +36,7 @@ ResizableTimelineScrollBar::ResizableTimelineScrollBar(QWidget *parent)
, markers_(nullptr)
, workarea_(nullptr)
, scale_(1.0)
, bridge_(new EngineEventBridge(this))
{
}
@@ -45,35 +46,59 @@ ResizableTimelineScrollBar::ResizableTimelineScrollBar(
, markers_(nullptr)
, workarea_(nullptr)
, scale_(1.0)
, bridge_(new EngineEventBridge(this))
{
}
ResizableTimelineScrollBar::~ResizableTimelineScrollBar()
{
// Raw workarea subscriptions carry `this` as userdata; unlike the
// bridge (which dies with us), they must be cancelled explicitly or the
// engine would call back into a dead widget.
if (workarea_range_sub_ > 0) {
oakengine_event_unsubscribe(workarea_range_sub_);
}
if (workarea_enabled_sub_ > 0) {
oakengine_event_unsubscribe(workarea_enabled_sub_);
}
}
void ResizableTimelineScrollBar::connect_markers(TimelineMarkerList *markers)
{
if (markers_) {
disconnect(markers_, &TimelineMarkerList::marker_added, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
disconnect(markers_, &TimelineMarkerList::marker_removed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
disconnect(markers_, &TimelineMarkerList::marker_modified, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
if (marker_sub_add_)
bridge_->unsubscribe(marker_sub_add_);
if (marker_sub_rem_)
bridge_->unsubscribe(marker_sub_rem_);
if (marker_sub_mod_)
bridge_->unsubscribe(marker_sub_mod_);
}
markers_ = markers;
if (markers_) {
connect(markers_, &TimelineMarkerList::marker_added, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
connect(markers_, &TimelineMarkerList::marker_removed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
connect(markers_, &TimelineMarkerList::marker_modified, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
marker_sub_add_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED);
marker_sub_rem_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED);
marker_sub_mod_ = bridge_->subscribe(
reinterpret_cast<OakEngineMarkerList *>(markers_),
OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED);
connect(bridge_, &EngineEventBridge::marker_list_marker_added, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
update();
});
connect(bridge_, &EngineEventBridge::marker_list_marker_removed, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
update();
});
connect(bridge_, &EngineEventBridge::marker_list_marker_modified, this,
[this](OakEngineMarkerList *, OakEngineMarker *) {
update();
});
}
update();
@@ -82,23 +107,30 @@ void ResizableTimelineScrollBar::connect_markers(TimelineMarkerList *markers)
void ResizableTimelineScrollBar::connect_work_area(TimelineWorkArea *workarea)
{
if (workarea_) {
disconnect(workarea_, &TimelineWorkArea::range_changed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
disconnect(workarea_, &TimelineWorkArea::enabled_changed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
if (workarea_range_sub_ > 0) {
oakengine_event_unsubscribe(workarea_range_sub_);
workarea_range_sub_ = 0;
}
if (workarea_enabled_sub_ > 0) {
oakengine_event_unsubscribe(workarea_enabled_sub_);
workarea_enabled_sub_ = 0;
}
}
workarea_ = workarea;
if (workarea_) {
connect(workarea_, &TimelineWorkArea::range_changed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
connect(workarea_, &TimelineWorkArea::enabled_changed, this,
static_cast<void (ResizableTimelineScrollBar::*)()>(
&ResizableTimelineScrollBar::update));
void *handle = reinterpret_cast<void *>(workarea_);
workarea_range_sub_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<ResizableTimelineScrollBar *>(userdata)->update();
}, this);
workarea_enabled_sub_ = oakengine_event_subscribe(
handle, OAKENGINE_EVENT_WORKAREA_ENABLED_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<ResizableTimelineScrollBar *>(userdata)->update();
}, this);
}
update();
@@ -26,6 +26,8 @@
#include "timeline/timelinemarker.h"
#include "timeline/timelineworkarea.h"
#include "widget/timebased/timescaledobject.h"
#include "engineeventbridge.h"
#include "oakengine/events.h"
namespace olive
{
@@ -37,6 +39,7 @@ public:
ResizableTimelineScrollBar(QWidget *parent = nullptr);
ResizableTimelineScrollBar(Qt::Orientation orientation,
QWidget *parent = nullptr);
~ResizableTimelineScrollBar() override;
void connect_markers(TimelineMarkerList *markers);
void connect_work_area(TimelineWorkArea *workarea);
@@ -51,7 +54,20 @@ private:
TimelineWorkArea *workarea_;
// Workarea signal subscriptions (event 141/142-style, but workarea is a
// timeline-level concept tracked via OakEngineEvents).
int64_t workarea_range_sub_ = 0;
int64_t workarea_enabled_sub_ = 0;
double scale_;
EngineEventBridge *bridge_ = nullptr;
int64_t marker_sub_add_ = 0;
int64_t marker_sub_rem_ = 0;
int64_t marker_sub_mod_ = 0;
};
}
+10 -3
View File
@@ -28,6 +28,8 @@
#include "common/qtutils.h"
#include "node/node.h"
#include "oakengine/display.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -86,9 +88,14 @@ void HistogramScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
if (!texture_row_sums_ || texture_row_sums_->width() != this->width() ||
texture_row_sums_->height() != this->height()) {
texture_row_sums_ = renderer()->create_texture(
VideoParams(width(), height(), managed_tex->format(),
managed_tex->channel_count()));
oak_video_params pod = {};
pod.width = width();
pod.height = height();
pod.format = managed_tex->format();
const VideoParams row_sums_params = video_params_from_pod(pod);
oakengine_display_renderer_create_texture(renderer(),
&row_sums_params, nullptr, 0,
&texture_row_sums_);
}
// Draw managed texture to a sums texture
+37 -21
View File
@@ -21,8 +21,14 @@
#include "scopebase.h"
#include "config/config.h"
#include <QPainter>
#include "common/configwrapper.h"
#include "oakengine/display.h"
#include "oakengine/videoparams.h"
#include "render/job/colortransformjob.h"
#include "widget/viewer/vieweroutpututils.h"
#include "render/job/shaderjob.h"
namespace olive
{
@@ -76,14 +82,17 @@ void ScopeBase::update_software_image()
// it into this scope's renderer before we can sample it.
TexturePtr source_tex = texture_;
if (texture_->renderer() && texture_->renderer() != renderer()) {
FramePtr temp_frame = Frame::create();
temp_frame->set_video_params(texture_->params());
temp_frame->allocate();
texture_->download(temp_frame->data(), temp_frame->linesize_pixels());
FramePtr temp_frame;
oakengine_codec_frame_create(&temp_frame);
oakengine_codec_frame_set_video_params(temp_frame.get(),
&texture_->params());
oakengine_codec_frame_allocate(temp_frame.get());
oakengine_display_texture_download(texture_.get(), temp_frame->data(),
temp_frame->linesize_pixels());
local_texture_ = renderer()->create_texture(
temp_frame->video_params(), temp_frame->data(),
temp_frame->linesize_pixels());
oakengine_display_renderer_create_texture(
renderer(), &temp_frame->video_params(), temp_frame->data(),
temp_frame->linesize_pixels(), &local_texture_);
source_tex = local_texture_;
}
@@ -96,16 +105,20 @@ void ScopeBase::update_software_image()
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
const VideoParams offscreen_params(texture_width, texture_height,
PixelFormat::u8,
VideoParams::k_rgba_channel_count);
oak_video_params pod = {};
pod.width = texture_width;
pod.height = texture_height;
pod.format = PixelFormat::u8;
const VideoParams offscreen_params(video_params_from_pod(pod));
if (!software_tex_ || software_tex_->params() != offscreen_params) {
software_tex_ = renderer()->create_texture(offscreen_params);
oakengine_display_renderer_create_texture(renderer(),
&offscreen_params, nullptr, 0,
&software_tex_);
software_buffer_.resize(
texture_width * texture_height *
VideoParams::get_bytes_per_pixel(PixelFormat::u8,
VideoParams::k_rgba_channel_count));
oakengine_video_params_bytes_per_pixel(PixelFormat::u8,
4));
}
if (!software_tex_ || software_tex_->is_dummy()) {
@@ -115,13 +128,14 @@ void ScopeBase::update_software_image()
}
ColorTransformJob job;
job.set_color_processor(color_service());
oakengine_color_transform_job_set_processor(&job, color_service().get());
job.set_input_texture(source_tex);
job.set_input_alpha_association(k_alpha_none);
job.set_clear_destination_enabled(true);
job.set_force_opaque(true);
renderer()->blit_color_managed(job, software_tex_.get());
oakengine_display_renderer_blit_color_managed(renderer(), &job,
software_tex_.get(), nullptr);
renderer()->download_from_texture(software_tex_->id(),
software_tex_->params(),
software_buffer_.data(), 0);
@@ -129,8 +143,8 @@ void ScopeBase::update_software_image()
software_image_ = QImage(
reinterpret_cast<const uchar *>(software_buffer_.constData()),
texture_width, texture_height,
texture_width * VideoParams::get_bytes_per_pixel(
PixelFormat::u8, VideoParams::k_rgba_channel_count),
texture_width * oakengine_video_params_bytes_per_pixel(
PixelFormat::u8, 4),
QImage::Format_RGBA8888_Premultiplied);
software_image_.setDevicePixelRatio(devicePixelRatioF());
@@ -169,14 +183,16 @@ void ScopeBase::on_paint()
// Convert reference frame to display space
if (!managed_tex_ || !managed_tex_up_to_date_ ||
managed_tex_->params() != texture_->params()) {
managed_tex_ = renderer()->create_texture(texture_->params());
oakengine_display_renderer_create_texture(
renderer(), &texture_->params(), nullptr, 0, &managed_tex_);
ColorTransformJob job;
job.set_color_processor(color_service());
oakengine_color_transform_job_set_processor(&job, color_service().get());
job.set_input_texture(texture_);
job.set_input_alpha_association(k_alpha_none);
renderer()->blit_color_managed(job, managed_tex_.get());
oakengine_display_renderer_blit_color_managed(
renderer(), &job, managed_tex_.get(), nullptr);
managed_tex_up_to_date_ = true;
}
+2 -2
View File
@@ -60,7 +60,7 @@ void VectorscopeScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
NodeValue(NodeValue::k_vec2, QVector2D(width(), height())));
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
color_manager()->get_default_luma_coefs(luma_coeffs);
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
job.insert(
QStringLiteral("luma_coeffs"),
NodeValue(NodeValue::k_vec3,
@@ -139,7 +139,7 @@ void VectorscopeScope::draw_scope_software(QPainter &p, const QImage &image)
buf.fill(Qt::transparent);
double luma_coeffs[3] = { 0.0, 0.0, 0.0 };
color_manager()->get_default_luma_coefs(luma_coeffs);
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
const int src_w = image.width();
const int src_h = image.height();
+2 -2
View File
@@ -31,7 +31,7 @@
#include <QVector3D>
#include "common/qtutils.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "node/node.h"
namespace olive
@@ -82,7 +82,7 @@ void WaveformScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
// Set luma coefficients
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
color_manager()->get_default_luma_coefs(luma_coeffs);
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
job.insert(
QStringLiteral("luma_coeffs"),
NodeValue(NodeValue::k_vec3,
+1 -1
View File
@@ -22,7 +22,7 @@
#include "numericsliderbase.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
namespace olive
+1 -1
View File
@@ -34,7 +34,7 @@
#include "common/lerp.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "common/configwrapper.h"
namespace olive
{
+22 -7
View File
@@ -28,7 +28,7 @@
#include <QInputDialog>
#include <QMessageBox>
#include "render/videoparams.h"
#include "oakengine/videoparams.h"
namespace olive
{
@@ -164,17 +164,32 @@ private:
inner_->clear();
foreach (const Rational &fr, VideoParams::k_supported_frame_rates) {
inner_->addItem(VideoParams::frame_rate_to_string(fr),
QVariant::fromValue(fr));
{
const int n = oakengine_video_params_supported_frame_rate_count();
for (int i = 0; i < n; i++) {
int num, den;
oakengine_video_params_supported_frame_rate_at(i, &num, &den);
char buf[64];
oakengine_video_params_frame_rate_to_string(num, den, buf,
sizeof(buf));
Rational fr(num, den);
inner_->addItem(QString::fromUtf8(buf),
QVariant::fromValue(fr));
}
}
if (custom_rate_.isNull()) {
inner_->addItem(tr("Custom..."));
} else {
inner_->addItem(
tr("Custom (%1)")
.arg(VideoParams::frame_rate_to_string(custom_rate_)));
{
char buf[64];
oakengine_video_params_frame_rate_to_string(
custom_rate_.numerator(), custom_rate_.denominator(), buf,
sizeof(buf));
inner_->addItem(
tr("Custom (%1)")
.arg(QString::fromUtf8(buf)));
}
}
// On the first populate there is no current index (-1); select the
@@ -24,7 +24,7 @@
#include <QComboBox>
#include "render/videoparams.h"
#include "oakengine/videoparams.h"
namespace olive
{
@@ -41,12 +41,12 @@ public:
this->addItem(tr("Bottom-Field First"));
}
VideoParams::Interlacing get_interlace_mode() const
int get_interlace_mode() const
{
return static_cast<VideoParams::Interlacing>(this->currentIndex());
return this->currentIndex();
}
void set_interlace_mode(VideoParams::Interlacing mode)
void set_interlace_mode(int mode)
{
this->setCurrentIndex(mode);
}
@@ -25,7 +25,7 @@
#include <QComboBox>
#include "dialog/ratiodialog.h"
#include "render/videoparams.h"
#include "oakengine/videoparams.h"
namespace olive
{
@@ -37,11 +37,16 @@ public:
: QComboBox(parent)
, dont_prompt_custom_par_(false)
{
QStringList par_names = VideoParams::get_standard_pixel_aspect_ratio_names();
for (int i = 0; i < VideoParams::k_standard_pixel_aspects.size(); i++) {
const Rational &ratio = VideoParams::k_standard_pixel_aspects.at(i);
this->addItem(par_names.at(i), QVariant::fromValue(ratio));
const int n = oakengine_video_params_standard_pixel_aspect_count();
for (int i = 0; i < n; i++) {
int num, den;
oakengine_video_params_standard_pixel_aspect_at(i, &num, &den);
char name_buf[256];
oakengine_video_params_standard_pixel_aspect_name(i, name_buf,
sizeof(name_buf));
Rational ratio(num, den);
this->addItem(QString::fromUtf8(name_buf),
QVariant::fromValue(ratio));
}
// Always add custom item last, much of the logic relies on this. Set this to the current AR so
@@ -110,9 +115,13 @@ private:
// Use 1:1 to prevent any real chance of the PAR being set to 0
this->setItemData(custom_index, QVariant::fromValue(Rational(1)));
} else {
this->setItemText(custom_index,
VideoParams::format_pixel_aspect_ratio_string(
tr("Custom (%1)"), ratio));
char buf[256];
oakengine_video_params_format_pixel_aspect_ratio_string(
"%1", ratio.numerator(), ratio.denominator(), buf,
sizeof(buf));
this->setItemText(
custom_index,
tr("Custom (%1)").arg(QString::fromUtf8(buf)));
this->setItemData(custom_index, QVariant::fromValue(ratio));
}
}
@@ -24,7 +24,7 @@
#include <QComboBox>
#include "render/videoparams.h"
#include "oakengine/videoparams.h"
namespace olive
{
@@ -35,13 +35,13 @@ public:
PixelFormatComboBox(bool float_only, QWidget *parent = nullptr)
: QComboBox(parent)
{
// Set up preview formats
for (int i = 0; i < PixelFormat::count; i++) {
PixelFormat pix_fmt = static_cast<PixelFormat::Format>(i);
if (!float_only || pix_fmt.is_float()) {
this->addItem(VideoParams::get_format_name(pix_fmt),
static_cast<PixelFormat::Format>(pix_fmt));
// Set up preview formats (PixelFormat::u8=0 .. f32=4)
for (int i = 0; i < 5; i++) {
if (!float_only || oakengine_video_params_format_is_float(i)) {
char name_buf[64];
oakengine_video_params_pixel_format_name(i, name_buf,
sizeof(name_buf));
this->addItem(QString::fromUtf8(name_buf), i);
}
}
}
@@ -24,7 +24,7 @@
#include <QComboBox>
#include "render/videoparams.h"
#include "oakengine/videoparams.h"
namespace olive
{
@@ -35,8 +35,12 @@ public:
VideoDividerComboBox(QWidget *parent = nullptr)
: QComboBox(parent)
{
foreach (int d, VideoParams::k_supported_dividers) {
this->addItem(VideoParams::get_name_for_divider(d), d);
const int n = oakengine_video_params_supported_divider_count();
for (int i = 0; i < n; i++) {
int d = oakengine_video_params_supported_divider_at(i);
char name_buf[64];
oakengine_video_params_divider_name(d, name_buf, sizeof(name_buf));
this->addItem(QString::fromUtf8(name_buf), d);
}
}
+3 -3
View File
@@ -45,7 +45,7 @@ TaskView::TaskView(QWidget *parent)
layout_->addStretch();
}
void TaskView::add_task(Task *t)
void TaskView::add_task(OakEngineTask *t)
{
// Create TaskViewItem (UI representation of a Task) and connect it
TaskViewItem *item = new TaskViewItem(t);
@@ -54,12 +54,12 @@ void TaskView::add_task(Task *t)
layout_->insertWidget(layout_->count() - 1, item);
}
void TaskView::task_failed(Task *t)
void TaskView::task_failed(OakEngineTask *t)
{
items_.value(t)->failed();
}
void TaskView::remove_task(Task *t)
void TaskView::remove_task(OakEngineTask *t)
{
items_.value(t)->deleteLater();
items_.remove(t);
+9 -9
View File
@@ -44,26 +44,26 @@ public:
TaskView(QWidget *parent);
signals:
void task_cancelled(Task *t);
void task_cancelled(OakEngineTask *t);
public slots:
/**
* @brief Creates a TaskViewItem, connects it to a Task, and adds it to this widget
*
* Connect this to TaskManager::TaskAdded().
*/
void add_task(Task *t);
* @brief Creates a TaskViewItem, connects it to a Task, and adds it to this widget
*
* Connect this to TaskManager::TaskAdded().
*/
void add_task(OakEngineTask *t);
void task_failed(Task *t);
void task_failed(OakEngineTask *t);
void remove_task(Task *t);
void remove_task(OakEngineTask *t);
private:
QWidget *central_widget_;
QVBoxLayout *layout_;
QHash<Task *, TaskViewItem *> items_;
QHash<OakEngineTask *, TaskViewItem *> items_;
};
}
+21 -7
View File
@@ -29,7 +29,7 @@
namespace olive
{
TaskViewItem::TaskViewItem(Task *task, QWidget *parent)
TaskViewItem::TaskViewItem(OakEngineTask *task, QWidget *parent)
: QFrame(parent)
, task_(task)
{
@@ -41,7 +41,10 @@ TaskViewItem::TaskViewItem(Task *task, QWidget *parent)
// Create header label
task_name_lbl_ = new QLabel(this);
task_name_lbl_->setText(task_->get_title());
char title_buf[512];
title_buf[0] = '\0';
oakengine_task_title(task_, title_buf, sizeof(title_buf));
task_name_lbl_->setText(QString::fromUtf8(title_buf));
layout->addWidget(task_name_lbl_);
// Create center layout (combines progress bar and a cancel button)
@@ -74,10 +77,18 @@ TaskViewItem::TaskViewItem(Task *task, QWidget *parent)
// Set up elapsed timer
status_stack_->setCurrentWidget(elapsed_timer_lbl_);
// Connect to the task
connect(task_, &Task::started, elapsed_timer_lbl_,
qOverload<qint64>(&ElapsedCounterWidget::start));
connect(task_, &Task::progress_changed, this, &TaskViewItem::update_progress);
// Connect to the task via EngineEventBridge
bridge_ = new EngineEventBridge(this);
bridge_->subscribe(task_, OAKENGINE_EVENT_TASK_STARTED);
bridge_->subscribe(task_, OAKENGINE_EVENT_TASK_PROGRESS);
connect(bridge_, &EngineEventBridge::task_started, this,
[this](OakEngineTask *, qint64 start_time) {
elapsed_timer_lbl_->start(start_time);
});
connect(bridge_, &EngineEventBridge::task_progress, this,
[this](OakEngineTask *, double progress) {
update_progress(progress);
});
connect(cancel_btn_, &QPushButton::clicked, this,
[this] { emit task_cancelled(task_); });
}
@@ -86,7 +97,10 @@ void TaskViewItem::failed()
{
status_stack_->setCurrentWidget(task_error_lbl_);
task_error_lbl_->setStyleSheet("color: red");
task_error_lbl_->setText(tr("Error: %1").arg(task_->get_error()));
char err[512];
err[0] = '\0';
oakengine_task_error(task_, err, sizeof(err));
task_error_lbl_->setText(tr("Error: %1").arg(QString::fromUtf8(err)));
}
void TaskViewItem::update_progress(double d)
+7 -13
View File
@@ -29,29 +29,21 @@
#include <QWidget>
#include "elapsedcounterwidget.h"
#include "task/task.h"
#include "engineeventbridge.h"
#include "oakengine/task.h"
namespace olive
{
/**
* @brief A widget that visually represents the status of a Task
*
* The TaskViewItem widget shows a description of the Task (Task::text(), a progress bar (updated by
* Task::ProgressChanged), the Task's status (text generated from Task::status() or Task::error()), and provides
* a cancel button (triggering Task::Cancel()) for cancelling a Task before it finishes.
*
* The main entry point is SetTask() after a Task and TaskViewItem objects are created.
*/
class TaskViewItem : public QFrame {
Q_OBJECT
public:
TaskViewItem(Task *task, QWidget *parent = nullptr);
TaskViewItem(OakEngineTask *task, QWidget *parent = nullptr);
void failed();
signals:
void task_cancelled(Task *t);
void task_cancelled(OakEngineTask *t);
private:
QLabel *task_name_lbl_;
@@ -62,7 +54,9 @@ private:
ElapsedCounterWidget *elapsed_timer_lbl_;
QLabel *task_error_lbl_;
Task *task_;
OakEngineTask *task_;
EngineEventBridge *bridge_;
private slots:
void update_progress(double d);
+14 -5
View File
@@ -27,6 +27,8 @@
#include <QTimer>
#include "widget/timebased/timebasedwidget.h"
#include "oakengine/events.h"
#include "oakengine/viewer.h"
namespace olive
{
@@ -156,15 +158,20 @@ void TimeBasedView::set_y_scale(const double &y_scale)
void TimeBasedView::set_viewer_node(ViewerOutput *v)
{
if (viewer_) {
disconnect(viewer_, &ViewerOutput::playhead_changed, viewport(),
static_cast<void (QWidget::*)()>(&TimeBasedView::update));
oakengine_event_unsubscribe(viewer_sub_);
viewer_sub_ = 0;
}
viewer_ = v;
if (viewer_) {
connect(viewer_, &ViewerOutput::playhead_changed, viewport(),
static_cast<void (QWidget::*)()>(&TimeBasedView::update));
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(viewer_),
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *, void *userdata) {
static_cast<TimeBasedView *>(userdata)->viewport()->update();
},
this);
}
}
@@ -247,7 +254,9 @@ bool TimeBasedView::playhead_move(QMouseEvent *event)
mouse_time += movement;
}
viewer_->set_playhead(mouse_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_),
mouse_time.numerator(), mouse_time.denominator());
}
return true;
+3
View File
@@ -23,6 +23,7 @@
#define OAK_TIMELINEVIEWBASE_H
#include <QGraphicsView>
#include <cstdint>
#include <vector>
#include "core.h"
@@ -146,6 +147,8 @@ private:
double y_scale_;
ViewerOutput *viewer_;
int64_t viewer_sub_ = 0;
};
}
@@ -28,8 +28,13 @@
#include <QToolTip>
#include "common/qtutils.h"
#include "olive/core/util/timecodefunctions.h"
#include "oakengine/node.h"
#include "oakengine/undo.h"
#include "timebasedview.h"
#include "timebasedwidget.h"
#include "widget/keyframeview/keyframehandle.h"
#include "widget/timeruler/markerhandle.h"
#include "widget/timetarget/timetarget.h"
namespace olive
@@ -311,7 +316,13 @@ public:
// Apply movement
for (size_t i = 0; i < selected_.size(); i++) {
selected_.at(i)->set_time(dragging_.at(i) + time_diff);
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);
}
}
// Show information about this keyframe
@@ -336,19 +347,32 @@ public:
QToolTip::showText(QCursor::pos(), tip);
}
void drag_stop(MultiUndoCommand *command)
void drag_stop(void *command)
{
QToolTip::hideText();
for (size_t i = 0; i < selected_.size(); i++) {
Rational current;
if constexpr (std::is_same_v<T, TimelineMarker>) {
current = selected_.at(i)->time().in();
} else {
current = selected_.at(i)->time();
if constexpr (std::is_same_v<T, NodeKeyframe>) {
int tbn = 0, tbd = 0;
oakengine_node_frame_time_base(
reinterpret_cast<OakEngineNode *>(selected_.at(i)->parent()),
&tbn, &tbd);
const int64_t new_ts = olive::core::Timecode::time_to_timestamp(
dragging_.at(i), olive::Rational(tbn, tbd),
olive::core::Timecode::k_round);
oakengine_undo_command_multi_add_child(
command,
oakengine_keyframe_set_time_command(
reinterpret_cast<OakEngineKeyframe *>(selected_.at(i)),
new_ts));
} else if constexpr (std::is_same_v<T, TimelineMarker>) {
oakengine_undo_command_multi_add_child(
command,
oakengine_marker_set_time_command(
reinterpret_cast<OakEngineMarker *>(selected_.at(i)),
dragging_.at(i).numerator(),
dragging_.at(i).denominator()));
}
command->add_child(
new SetTimeCommand(selected_.at(i), current, dragging_.at(i)));
}
dragging_.clear();
@@ -419,47 +443,7 @@ public:
}
private:
class SetTimeCommand : public UndoCommand {
public:
SetTimeCommand(T *key, const Rational &time)
{
key_ = key;
new_time_ = time;
old_time_ = key_->time();
}
SetTimeCommand(T *key, const Rational &new_time,
const Rational &old_time)
{
key_ = key;
new_time_ = new_time;
old_time_ = old_time;
}
virtual Project *get_relevant_project() const override
{
return Project::get_project_from_object(key_);
}
protected:
virtual void redo() override
{
key_->set_time(new_time_);
}
virtual void undo() override
{
key_->set_time(old_time_);
}
private:
T *key_;
Rational old_time_;
Rational new_time_;
};
TimeBasedView *view_;
TimeBasedView *view_;
using DrawnObject = QPair<T *, QRectF>;
std::vector<DrawnObject> drawn_objects_;
+146 -70
View File
@@ -25,15 +25,20 @@
#include "common/autoscroll.h"
#include "common/range.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
#include "engineeventbridge.h"
#include "common/current.h"
#include "dialog/markerproperties/markerpropertiesdialog.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/timeline.h"
#include "timeline/timelineundoworkarea.h"
#include "oakengine/viewer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "widget/timeruler/timeruler.h"
#include "widget/timelinewidget/cliphandle.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -42,6 +47,7 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible,
QWidget *parent)
: TimelineScaledWidget(parent)
, viewer_node_(nullptr)
, bridge_(new EngineEventBridge(this))
, auto_max_scrollbar_(false)
, toggle_show_all_(false)
, auto_set_timebase_(true)
@@ -95,33 +101,28 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
// Set viewer node
ViewerOutput *old = viewer_node_.data();
viewer_node_ = node;
// Disconnect old bridge subscriptions and connections
disconnect(bridge_, nullptr, this, nullptr);
bridge_->unsubscribe_all();
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
Current::getInstance().setCurrentVideoParams(
viewer_node_->get_video_params());
viewer_output_video_params(viewer_node_));
Current::getInstance().setCurrentAudioParams(
viewer_node_->get_audio_params());
viewer_output_audio_params(viewer_node_));
} else {
Current::getInstance().setCurrentVideoParams(VideoParams());
Current::getInstance().setCurrentVideoParams(empty_video_params());
Current::getInstance().setCurrentAudioParams(AudioParams());
}
if (old) {
// Call potential derivative functions for disconnecting the viewer node
DisconnectNodeEvent(old);
// Disconnect length changed signal
disconnect(old, &ViewerOutput::length_changed, this,
&TimeBasedWidget::update_maximum_scroll);
disconnect(old, &ViewerOutput::removed_from_graph, this,
&TimeBasedWidget::connected_node_removed_from_graph);
disconnect(old, &ViewerOutput::playhead_changed, this,
&TimeBasedWidget::playhead_time_changed);
// Disconnect rate change signals if they were connected
disconnect(old, &ViewerOutput::frame_rate_changed, this,
&TimeBasedWidget::auto_update_timebase);
disconnect(old, &ViewerOutput::sample_rate_changed, this,
&TimeBasedWidget::auto_update_timebase);
// Reset timebase to null
SetTimebase(Rational());
@@ -137,13 +138,28 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
ConnectedNodeChangeEvent(viewer_node_.data());
if (viewer_node_) {
// Connect length changed signal
connect(viewer_node_.data(), &ViewerOutput::length_changed, this,
&TimeBasedWidget::update_maximum_scroll);
connect(viewer_node_.data(), &ViewerOutput::removed_from_graph, this,
&TimeBasedWidget::connected_node_removed_from_graph);
connect(viewer_node_.data(), &ViewerOutput::playhead_changed, this,
&TimeBasedWidget::playhead_time_changed);
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);
connect(bridge_, &EngineEventBridge::viewer_length_changed, this,
[this](OakEngineNode *, qint64, qint64) {
update_maximum_scroll();
});
connect(bridge_, &EngineEventBridge::viewer_playhead_changed, this,
[this](OakEngineNode *, qint64 num, qint64 den) {
playhead_time_changed(Rational(num, den));
});
// Node removed from graph - use the bridge signal
connect(bridge_, &EngineEventBridge::node_removed_from_graph, this,
[this](OakEngineNode *, OakEngineNode *) {
connected_node_removed_from_graph();
});
// Connect ruler and scrollbar to timeline points
connect_work_area(viewer_node_->get_work_area());
@@ -152,10 +168,16 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
// If we're setting the timebase, set it automatically based on the video and audio parameters
if (auto_set_timebase_) {
auto_update_timebase();
connect(viewer_node_.data(), &ViewerOutput::frame_rate_changed, this,
&TimeBasedWidget::auto_update_timebase);
connect(viewer_node_.data(), &ViewerOutput::sample_rate_changed, this,
&TimeBasedWidget::auto_update_timebase);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED);
connect(bridge_, &EngineEventBridge::viewer_frame_rate_changed, this,
[this](OakEngineNode *, qint64, qint64) {
auto_update_timebase();
});
connect(bridge_, &EngineEventBridge::viewer_sample_rate_changed, this,
[this](OakEngineNode *, int) {
auto_update_timebase();
});
}
// Call derivatives
@@ -164,7 +186,8 @@ void TimeBasedWidget::connect_viewer_node(ViewerOutput *node)
update_maximum_scroll();
emit connected_node_changed(old, node);
emit connected_node_changed(reinterpret_cast<OakEngineNode *>(old),
reinterpret_cast<OakEngineNode *>(node));
}
void TimeBasedWidget::connect_work_area(TimelineWorkArea *workarea)
@@ -287,13 +310,13 @@ void TimeBasedWidget::auto_update_timebase()
return;
}
Rational video_tb =
viewer_node_->get_video_params().frame_rate_as_time_base();
viewer_output_video_params(viewer_node_).frame_rate_as_time_base();
if (!video_tb.isNull()) {
SetTimebase(video_tb);
} else {
Rational audio_tb =
viewer_node_->get_audio_params().sample_rate_as_time_base();
viewer_output_audio_params(viewer_node_).sample_rate_as_time_base();
if (!audio_tb.isNull()) {
SetTimebase(audio_tb);
@@ -489,7 +512,9 @@ void TimeBasedWidget::go_to_prev_cut()
closest_cut = qMax(closest_cut, this_track_closest_cut);
}
get_connected_node()->set_playhead(closest_cut);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
closest_cut.numerator(), closest_cut.denominator());
}
void TimeBasedWidget::go_to_next_cut()
@@ -521,14 +546,17 @@ void TimeBasedWidget::go_to_next_cut()
}
if (closest_cut < RATIONAL_MAX) {
get_connected_node()->set_playhead(closest_cut);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
closest_cut.numerator(), closest_cut.denominator());
}
}
void TimeBasedWidget::go_to_start()
{
if (viewer_node_) {
viewer_node_->set_playhead(0);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()), 0, 1);
}
}
@@ -542,7 +570,12 @@ void TimeBasedWidget::prev_frame()
// Catch rounding error, assume this time is snapped and just subtract a timebase
proposed_time -= timebase();
}
viewer_node_->set_playhead(qMax(Rational(0), proposed_time));
{
Rational _pt = qMax(Rational(0), proposed_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
_pt.numerator(), _pt.denominator());
}
}
}
@@ -556,14 +589,19 @@ void TimeBasedWidget::next_frame()
// Catch rounding error, assume this time is snapped and just add a timebase
proposed_time += timebase();
}
viewer_node_->set_playhead(proposed_time);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
proposed_time.numerator(), proposed_time.denominator());
}
}
void TimeBasedWidget::go_to_end()
{
if (viewer_node_) {
viewer_node_->set_playhead(viewer_node_->get_length());
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node_.data()),
viewer_node_->get_length().numerator(),
viewer_node_->get_length().denominator());
}
}
@@ -587,13 +625,13 @@ void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
TimelineWorkArea *points = viewer_node_->get_work_area();
// Enable workarea if it isn't already enabled
if (!points->enabled()) {
command->add_child(new WorkareaSetEnabledCommand(
viewer_node_->project(), points, true));
oakengine_workarea_set_enabled_undoable(
reinterpret_cast<OakEngineWorkarea *>(points), 1, command);
}
// Determine our new range
@@ -603,7 +641,7 @@ void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
in_point = time;
if (!points->enabled() || points->out() < in_point) {
out_point = TimelineWorkArea::k_reset_out;
out_point = RATIONAL_MAX;
} else {
out_point = points->out();
}
@@ -611,17 +649,28 @@ void TimeBasedWidget::set_point(Timeline::MovementMode m, const Rational &time)
out_point = time;
if (!points->enabled() || points->in() > out_point) {
in_point = TimelineWorkArea::k_reset_in;
in_point = Rational(0, 1);
} else {
in_point = points->in();
}
}
// Set workarea
command->add_child(
new WorkareaSetRangeCommand(points, TimeRange(in_point, out_point)));
{
int64_t old_in_num, old_in_den, old_out_num, old_out_den;
int old_enabled;
oakengine_workarea_get(
reinterpret_cast<OakEngineWorkarea *>(points),
&old_in_num, &old_in_den, &old_out_num, &old_out_den,
&old_enabled);
oakengine_workarea_set_range_undoable(
reinterpret_cast<OakEngineWorkarea *>(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);
}
Core::instance()->undo_stack()->push(command, tr("Set In/Out Point"));
oakengine_undo_push(command, tr("Set In/Out Point").toUtf8().constData());
}
void TimeBasedWidget::reset_point(Timeline::MovementMode m)
@@ -639,13 +688,27 @@ void TimeBasedWidget::reset_point(Timeline::MovementMode m)
TimeRange r = points->range();
if (m == Timeline::k_trim_in) {
r.set_in(TimelineWorkArea::k_reset_in);
r.set_in(Rational(0, 1));
} else {
r.set_out(TimelineWorkArea::k_reset_out);
r.set_out(RATIONAL_MAX);
}
Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points, r),
tr("Reset In/Out Points"));
{
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),
&old_in_num, &old_in_den, &old_out_num, &old_out_den,
&old_enabled);
oakengine_workarea_set_range_undoable(
reinterpret_cast<OakEngineWorkarea *>(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);
oakengine_undo_push(reset_cmd,
tr("Reset In/Out Points").toUtf8().constData());
}
}
void TimeBasedWidget::page_scroll_internal(QScrollBar *bar, int maximum,
@@ -717,10 +780,15 @@ void TimeBasedWidget::clear_in_out_points()
return;
}
Core::instance()->undo_stack()->push(
new WorkareaSetEnabledCommand(get_connected_node()->project(),
get_connected_node()->get_work_area(), false),
tr("Cleared 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()),
0, clear_cmd);
oakengine_undo_push(clear_cmd,
tr("Cleared In/Out Points").toUtf8().constData());
}
}
void TimeBasedWidget::set_marker()
@@ -748,16 +816,20 @@ void TimeBasedWidget::set_marker()
color = OAK_CONFIG("MarkerColor").toInt();
}
TimelineMarker *marker = new TimelineMarker(
color, TimeRange(get_connected_node()->get_playhead(),
get_connected_node()->get_playhead()));
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({ marker }, timebase(), this);
MarkerPropertiesDialog mpd({ cpp_marker }, timebase(), this);
if (mpd.exec() != QDialog::Accepted) {
delete marker;
oakengine_marker_free(marker);
marker = nullptr;
cpp_marker = nullptr;
} else {
edited_in_dialog = true;
}
@@ -767,19 +839,19 @@ 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.
Core::instance()->undo_stack()->push(
new MarkerAddCommand(markers, marker), tr("Added Marker"));
oakengine_marker_list_add_existing(
reinterpret_cast<OakEngineMarkerList *>(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(marker->time().in(),
Timecode::time_to_timestamp(cpp_marker->time().in(),
timebase(),
Timecode::k_round),
"", marker->color());
delete marker;
"", cpp_marker->color());
oakengine_marker_free(marker);
}
}
}
@@ -820,8 +892,10 @@ void TimeBasedWidget::go_to_in()
{
if (get_connected_node()) {
if (get_connected_node()->get_work_area()->enabled()) {
get_connected_node()->set_playhead(
get_connected_node()->get_work_area()->in());
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());
} else {
go_to_start();
}
@@ -832,8 +906,10 @@ void TimeBasedWidget::go_to_out()
{
if (get_connected_node()) {
if (get_connected_node()->get_work_area()->enabled()) {
get_connected_node()->set_playhead(
get_connected_node()->get_work_area()->out());
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());
} else {
go_to_end();
}
@@ -913,7 +989,7 @@ bool TimeBasedWidget::snap_point(const std::vector<Rational> &start_times,
TimelineMarker *marker = *jt;
TimeRange marker_range =
marker->time() + clip->in() - clip->media_in();
marker->time() + clip->in() - clip_media_in(clip);
qreal marker_in_screen =
time_to_scene(marker_range.in());
+4 -1
View File
@@ -36,6 +36,7 @@
namespace olive
{
class EngineEventBridge;
class TimeRuler;
class TimeBasedWidget : public TimelineScaledWidget {
@@ -149,6 +150,8 @@ protected:
{
}
EngineEventBridge *bridge_ = nullptr;
virtual void ConnectNodeEvent(ViewerOutput *)
{
}
@@ -213,7 +216,7 @@ protected slots:
signals:
void timebase_changed(const Rational &);
void connected_node_changed(ViewerOutput *old, ViewerOutput *now);
void connected_node_changed(OakEngineNode *old, OakEngineNode *now);
protected slots:
virtual void SendCatchUpScrollEvent();
+2 -1
View File
@@ -26,6 +26,7 @@
#include "audio/audiovisualwaveform.h"
#include "oakengine/node.h"
namespace olive
{
@@ -34,7 +35,7 @@ const int TimeScaledObject::k_calculate_dimensions_padding = 10;
TimeScaledObject::TimeScaledObject()
: scale_(1.0)
, min_scale_(0)
, max_scale_(AudioVisualWaveform::k_maximum_sample_rate.to_double())
, max_scale_(oakengine_audio_waveform_max_sample_rate())
{
}
+210
View File
@@ -0,0 +1,210 @@
/***
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_CLIPHANDLE_H
#define OAK_CLIPHANDLE_H
#include <cstring>
#include "node/block/clip/clip.h"
#include "oakengine/node.h"
#include "oakengine/timeline.h"
namespace olive
{
/**
* @brief Facade accessors for ClipBlock pointers held by the timeline UI.
*
* ClipBlock's header-inline 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.
*/
inline OakEngineClip *cliphandle(ClipBlock *clip)
{
return reinterpret_cast<OakEngineClip *>(clip);
}
/** @brief The node feeding the clip's buffer input (ClipBlock's inline
* get_connected_output(k_buffer_in) uses; borrowed, may be null). */
inline Node *clip_connected_node(ClipBlock *clip)
{
return reinterpret_cast<Node *>(
oakengine_node_input_get_connected_node(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(), -1));
}
inline FrameHashCache *clip_thumbnails(ClipBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->thumbnail_cache() : nullptr;
}
inline AudioWaveformCache *clip_waveform(ClipBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->waveform_cache() : nullptr;
}
inline FrameHashCache *clip_connected_video_cache(ClipBlock *clip)
{
Node *n = clip_connected_node(clip);
return n ? n->video_frame_cache() : nullptr;
}
/** @brief ClipBlock::speed() through the facade input getter. */
inline double clip_speed(ClipBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
if (oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_speed_input_id(),
&v) != OAKENGINE_OK) {
return 1.0;
}
return v.f[0];
}
/** @brief ClipBlock::loop_mode() value (an olive::LoopMode int). */
inline int clip_loop_mode(ClipBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
if (oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_loop_mode_input_id(),
&v) != OAKENGINE_OK) {
return 0;
}
return int(v.num);
}
/** @brief ClipBlock::is_reversed() through the facade input getter. */
inline bool clip_is_reversed(ClipBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
return oakengine_node_get_input(reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_reverse_input_id(),
&v) == OAKENGINE_OK &&
v.num != 0;
}
/** @brief ClipBlock::maintain_audio_pitch() through the facade. */
inline bool clip_maintain_audio_pitch(ClipBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
return oakengine_node_get_input(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_maintain_audio_pitch_input_id(), &v) ==
OAKENGINE_OK &&
v.num != 0;
}
/** @brief Create a new empty ClipBlock through the C ABI. */
inline ClipBlock *clip_create_empty(const char *label = nullptr)
{
return reinterpret_cast<ClipBlock *>(oakengine_clip_create_empty(label));
}
/** @brief ClipBlock::media_in() through the facade. */
inline Rational clip_media_in(ClipBlock *clip)
{
int64_t num = 0, den = 1;
if (oakengine_clip_get_media_in_rational(cliphandle(clip), &num, &den) ==
OAKENGINE_OK) {
return Rational(static_cast<int>(num), static_cast<int>(den));
}
return Rational(0, 1);
}
/** @brief ClipBlock::media_range() through the facade. */
inline TimeRange clip_media_range(ClipBlock *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)),
Rational(static_cast<int>(out_num),
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,
bool undoable = false)
{
if (!clip) {
return;
}
oakengine_clip_set_media_in_rational(cliphandle(clip),
media_in.numerator(),
media_in.denominator(),
undoable ? 1 : 0);
}
/** @brief ClipBlock::is_autocaching() through the facade. */
inline bool clip_is_autocaching(ClipBlock *clip)
{
oak_node_value v;
memset(&v, 0, sizeof(v));
return oakengine_node_get_input(
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_auto_cache_input_id(), &v) ==
OAKENGINE_OK &&
v.num != 0;
}
/** @brief ClipBlock::request_invalidated_from_connected() through the facade. */
inline void clip_request_invalidate_connected(
ClipBlock *clip, bool force_all = false,
const TimeRange &intersect = TimeRange())
{
if (!clip) {
return;
}
int64_t in_num = 0, in_den = 0, out_num = 0, out_den = 0;
if (!intersect.length().isNull()) {
const Rational in = intersect.in();
const Rational out = intersect.out();
in_num = in.numerator();
in_den = in.denominator();
out_num = out.numerator();
out_den = out.denominator();
}
oakengine_clip_request_invalidate_connected(cliphandle(clip), force_all ? 1 : 0,
in_num, in_den, out_num,
out_den);
}
} // namespace olive
#endif // OAK_CLIPHANDLE_H
File diff suppressed because it is too large Load Diff
+66 -79
View File
@@ -22,6 +22,7 @@
#ifndef OAK_TIMELINEWIDGET_H
#define OAK_TIMELINEWIDGET_H
#include <QHash>
#include <QScrollBar>
#include <QRubberBand>
#include <QWidget>
@@ -29,7 +30,10 @@
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/output/viewer/viewer.h"
#include "node/project/serializer/serializer.h"
#include "oakengine/events.h"
#include "oakengine/serializer.h"
#include "oakengine/timeline.h"
#include "oakengine/undo.h"
#include "timeline/timelinecommon.h"
#include "timelineandtrackview.h"
#include "widget/slider/rationalslider.h"
@@ -75,9 +79,9 @@ public:
void decrease_track_height();
void insert_footage_at_playhead(const QVector<ViewerOutput *> &footage);
void insert_footage_at_playhead(const QVector<OakEngineNode *> &footage);
void overwrite_footage_at_playhead(const QVector<ViewerOutput *> &footage);
void overwrite_footage_at_playhead(const QVector<OakEngineNode *> &footage);
void toggle_links_on_selected();
@@ -151,7 +155,7 @@ public:
static void replace_blocks_with_gaps(const QVector<Block *> &blocks,
bool remove_from_graph,
MultiUndoCommand *command,
void *command,
bool handle_transitions = true);
/**
@@ -187,7 +191,7 @@ public:
}
void insert_gaps_at(const Rational &time, const Rational &length,
MultiUndoCommand *command);
void *command);
void start_rubber_band_select(const QPoint &global_cursor_start);
void move_rubber_band_select(bool enable_selecting, bool select_links);
@@ -254,10 +258,10 @@ public:
update_viewports();
}
MultiUndoCommand *take_subtitle_section_command()
void *take_subtitle_section_command()
{
// Copy pointer
MultiUndoCommand *c = subtitle_show_command_;
void *c = subtitle_show_command_;
// Set to null
subtitle_show_command_ = nullptr;
@@ -267,41 +271,9 @@ public:
return c;
}
class SetSelectionsCommand : public UndoCommand {
public:
SetSelectionsCommand(TimelineWidget *timeline,
const TimelineWidgetSelections &now,
const TimelineWidgetSelections &old,
bool process_block_changes = true)
: timeline_(timeline)
, old_(old)
, now_(now)
, process_block_changes_(process_block_changes)
{
}
virtual Project *get_relevant_project() const override
{
return nullptr;
}
protected:
virtual void redo() override
{
timeline_->set_selections(now_, process_block_changes_);
}
virtual void undo() override
{
timeline_->set_selections(old_, process_block_changes_);
}
private:
TimelineWidget *timeline_;
TimelineWidgetSelections old_;
TimelineWidgetSelections now_;
bool process_block_changes_;
};
void *create_set_selections_command(const TimelineWidgetSelections &now,
const TimelineWidgetSelections &old,
bool process_block_changes = true);
public slots:
void clear_tentative_subtitle_track();
@@ -309,13 +281,13 @@ public slots:
void rename_selected_blocks();
signals:
void block_selection_changed(const QVector<Block *> &selected_blocks);
void block_selection_changed(const QVector<OakEngineBlock *> &selected_blocks);
void request_capture_start(const TimeRange &time,
const Track::Reference &track);
void reveal_viewer_in_footage_viewer(ViewerOutput *r, const TimeRange &range);
void reveal_viewer_in_project(ViewerOutput *r);
void reveal_viewer_in_footage_viewer(OakEngineNode *r, const TimeRange &range);
void reveal_viewer_in_project(OakEngineNode *r);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -352,7 +324,7 @@ private:
TimelineAndTrackView *add_timeline_and_track_view(Qt::Alignment alignment);
QHash<Node *, Node *>
generate_existing_paste_map(const ProjectSerializer::Result &r);
generate_existing_paste_map(void *clipboard);
QRubberBand rubberband_;
QVector<QPointF> rubberband_scene_pos_;
@@ -381,40 +353,54 @@ private:
QVector<Block *> added_blocks_;
QHash<Block *, QVector<int64_t>> block_subscriptions_;
int deferred_scroll_value_;
bool use_audio_time_units_;
QSplitter *view_splitter_;
MultiUndoCommand *subtitle_show_command_;
Track *subtitle_tentative_track_;
void *subtitle_show_command_;
OakEngineTrack *subtitle_tentative_track_;
QTimer *signal_block_change_timer_;
class SetSplitterSizesCommand : public UndoCommand {
public:
SetSplitterSizesCommand(QSplitter *splitter, const QList<int> &sizes)
: splitter_(splitter)
, new_sizes_(sizes)
{
}
virtual Project *get_relevant_project() const override
{
return nullptr;
}
protected:
virtual void redo() override;
virtual void undo() override;
private:
QSplitter *splitter_;
QList<int> new_sizes_;
QList<int> old_sizes_;
// Command userdata for splitter size changes
struct SplitterSizesCmdData {
QSplitter *splitter;
QList<int> new_sizes;
QList<int> old_sizes;
};
static void splitter_sizes_redo(void *userdata)
{
auto *d = static_cast<SplitterSizesCmdData *>(userdata);
d->old_sizes = d->splitter->sizes();
d->splitter->setSizes(d->new_sizes);
}
static void splitter_sizes_undo(void *userdata)
{
auto *d = static_cast<SplitterSizesCmdData *>(userdata);
d->splitter->setSizes(d->old_sizes);
}
static void splitter_sizes_free(void *userdata)
{
delete static_cast<SplitterSizesCmdData *>(userdata);
}
static void *make_splitter_sizes_command(QSplitter *splitter, const QList<int> &sizes)
{
auto *d = new SplitterSizesCmdData;
d->splitter = splitter;
d->new_sizes = sizes;
return oakengine_undo_command_create(nullptr, splitter_sizes_redo,
splitter_sizes_undo,
splitter_sizes_free, d);
}
void center_on(qreal scene_pos);
void update_view_timebases();
@@ -434,12 +420,7 @@ private slots:
void view_drag_left(QDragLeaveEvent *event);
void view_drag_dropped(TimelineViewMouseEvent *event);
void add_block(Block *block);
void remove_block(Block *blocks);
void add_track(Track *track);
void remove_track(Track *track);
void track_updated();
void track_updated(Track::Type type);
void block_updated();
@@ -467,15 +448,11 @@ private slots:
void sample_rate_changed();
void track_index_changed(int old, int now);
void signal_block_selection_change();
void reveal_in_footage_viewer();
void reveal_in_project();
void track_about_to_be_deleted(Track *track);
void set_selected_clips_autocaching(bool e);
void cache_clips();
@@ -485,6 +462,16 @@ private slots:
void multicam_enabled_triggered(bool e);
void force_update_rubber_band();
private:
void add_block(Block *block);
void remove_block(Block *blocks);
void add_track(Track *track);
void remove_track(Track *track);
void track_index_changed(Track *track, int old, int now);
void track_about_to_be_deleted(OakEngineTrack *track);
};
}
@@ -25,6 +25,7 @@
#include "node/block/clip/clip.h"
#include "render/audiowaveformcache.h"
#include "widget/timelinewidget/cliphandle.h"
namespace olive
{
@@ -35,16 +36,16 @@ namespace timeline_waveform_sync
bool get_waveform_sync_clip(Block *block, WaveformSyncClip *out)
{
ClipBlock *clip = dynamic_cast<ClipBlock *>(block);
if (!clip || !clip->waveform()) {
if (!clip || !clip_waveform(clip)) {
return false;
}
const TimeRange media_range = clip->media_range();
const TimeRange media_range = clip_media_range(clip);
if (media_range.length().isNull()) {
return false;
}
const AudioWaveformCache *waveform = clip->waveform();
const AudioWaveformCache *waveform = clip_waveform(clip);
if (waveform->get_parameters().sample_rate() <= 0) {
return false;
}
+50 -28
View File
@@ -26,10 +26,14 @@
#include "node/generator/shape/shapenode.h"
#include "node/generator/solid/solid.h"
#include "node/generator/text/textv3.h"
#include "node/nodeundo.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/viewer/vieweroutpututils.h"
namespace olive
{
@@ -100,11 +104,11 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
{
if (ghost_) {
if (!ghost_->get_adjusted_length().isNull()) {
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
if (MultiUndoCommand *subtitle_section_command =
if (void *subtitle_section_command =
parent()->take_subtitle_section_command()) {
command->add_child(subtitle_section_command);
oakengine_undo_command_multi_add_child(command, subtitle_section_command);
}
Sequence *s = parent()->sequence();
@@ -112,7 +116,7 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
QRectF r;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_title) {
VideoParams svp = s->get_video_params();
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);
@@ -122,8 +126,8 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
ghost_->get_adjusted_in(),
ghost_->get_adjusted_length(), r);
Core::instance()->undo_stack()->push(
command, qApp->translate("AddTool", "Added Clip"));
oakengine_undo_push(
command, qApp->translate("AddTool", "Added Clip").toUtf8().constData());
}
parent()->clear_ghosts();
@@ -132,7 +136,7 @@ void AddTool::mouse_release(TimelineViewMouseEvent *event)
}
}
Node *AddTool::create_addable_clip(MultiUndoCommand *command, Sequence *sequence,
Node *AddTool::create_addable_clip(void *command, Sequence *sequence,
const Track::Reference &track,
const Rational &in, const Rational &length,
const QRectF &rect)
@@ -140,20 +144,21 @@ Node *AddTool::create_addable_clip(MultiUndoCommand *command, Sequence *sequence
ClipBlock *clip;
if (Core::instance()->get_selected_addable_object() ==
Tool::k_addable_subtitle) {
clip = new SubtitleBlock();
clip = reinterpret_cast<SubtitleBlock*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.subtitle"));
} else {
clip = new ClipBlock();
clip->set_label(olive::Tool::get_addable_object_name(
Core::instance()->get_selected_addable_object()));
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();
command->add_child(new NodeAddCommand(graph, clip));
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
command->add_child(new TrackPlaceBlockCommand(
sequence->track_list(track.type()), track.index(), clip, in));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(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))));
Node *node_to_add = nullptr;
@@ -162,13 +167,13 @@ Node *AddTool::create_addable_clip(MultiUndoCommand *command, Sequence *sequence
// Empty, nothing to be done
break;
case Tool::k_addable_solid:
node_to_add = new SolidGenerator();
node_to_add = reinterpret_cast<SolidGenerator*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.solidgenerator"));
break;
case Tool::k_addable_shape:
node_to_add = new ShapeNode();
node_to_add = reinterpret_cast<ShapeNode*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.shape"));
break;
case Tool::k_addable_title:
node_to_add = new TextGeneratorV3();
node_to_add = reinterpret_cast<TextGeneratorV3*>(oakengine_node_factory_create_from_id("org.olivevideoeditor.Olive.text3"));
break;
case Tool::k_addable_bars:
case Tool::k_addable_tone:
@@ -186,17 +191,34 @@ Node *AddTool::create_addable_clip(MultiUndoCommand *command, Sequence *sequence
if (node_to_add) {
QPointF extra_node_offset(k_default_distance_from_output, 0);
command->add_child(new NodeAddCommand(graph, node_to_add));
command->add_child(new NodeEdgeAddCommand(
node_to_add, NodeInput(clip, ClipBlock::k_buffer_in)));
command->add_child(
new NodeSetPositionCommand(node_to_add, clip, extra_node_offset));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(graph),
reinterpret_cast<OakEngineNode *>(node_to_add)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(node_to_add),
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(),
-1));
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()) {
if (ShapeNodeBase *shape =
dynamic_cast<ShapeNodeBase *>(node_to_add)) {
shape->set_rect(rect, sequence->get_video_params(), command);
}
const VideoParams vp = viewer_output_video_params(sequence);
oak_video_params pod = {};
pod.width = vp.width();
pod.height = vp.height();
pod.time_base_num = vp.time_base().numerator();
pod.time_base_den = vp.time_base().denominator();
pod.format = vp.format();
pod.pixel_aspect_num = vp.pixel_aspect_ratio().numerator();
pod.pixel_aspect_den = vp.pixel_aspect_ratio().denominator();
pod.interlacing = vp.interlacing();
pod.divider = vp.divider();
oakengine_shape_set_rect_undoable(
reinterpret_cast<OakEngineNode *>(node_to_add), rect.x(),
rect.y(), rect.width(), rect.height(), &pod, command);
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ public:
virtual void mouse_move(TimelineViewMouseEvent *event) override;
virtual void mouse_release(TimelineViewMouseEvent *event) override;
static Node *create_addable_clip(MultiUndoCommand *command,
static Node *create_addable_clip(void *command,
Sequence *sequence,
const Track::Reference &track,
const Rational &in, const Rational &length,
+138 -82
View File
@@ -26,7 +26,7 @@
#include <QMimeData>
#include <QToolTip>
#include "config/config.h"
#include "common/configwrapper.h"
#include "common/qtutils.h"
#include "core.h"
#include "dialog/sequence/sequence.h"
@@ -35,12 +35,18 @@
#include "node/distort/transform/transformdistortnode.h"
#include "node/generator/matrix/matrix.h"
#include "node/math/math/math.h"
#include "node/nodeundo.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"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -57,10 +63,10 @@ void ImportTool::drag_enter(TimelineViewMouseEvent *event)
QStringList mime_formats = event->get_mime_data()->formats();
// Listen for MIME data from a ProjectViewModel
if (mime_formats.contains(Project::k_item_mime_type)) {
if (mime_formats.contains(QString::fromUtf8(oakengine_project_item_mime_type()))) {
// Data is drag/drop data from a ProjectViewModel
QByteArray model_data =
event->get_mime_data()->data(Project::k_item_mime_type);
event->get_mime_data()->data(QString::fromUtf8(oakengine_project_item_mime_type()));
// Use QDataStream to deserialize the data
QDataStream stream(&model_data, QIODevice::ReadOnly);
@@ -189,11 +195,11 @@ void ImportTool::drag_leave(QDragLeaveEvent *event)
void ImportTool::drag_drop(TimelineViewMouseEvent *event)
{
if (!dragged_footage_.isEmpty()) {
auto command = new MultiUndoCommand();
auto command = oakengine_undo_command_create_multi();
drop_ghosts(event->get_modifiers() & Qt::ControlModifier, command);
Core::instance()->undo_stack()->push(
oakengine_undo_push(
command,
qApp->translate("ImportTool", "Dropped Footage Into Sequence"));
qApp->translate("ImportTool", "Dropped Footage Into Sequence").toUtf8().constData());
event->accept();
} else {
@@ -203,7 +209,7 @@ void ImportTool::drag_drop(TimelineViewMouseEvent *event)
void ImportTool::place_at(const QVector<ViewerOutput *> &footage,
const Rational &start, bool insert,
MultiUndoCommand *command, int track_offset,
void *command, int track_offset,
bool jump_to_end)
{
DraggedFootageData refs;
@@ -217,7 +223,7 @@ void ImportTool::place_at(const QVector<ViewerOutput *> &footage,
void ImportTool::place_at(const DraggedFootageData &footage,
const Rational &start, bool insert,
MultiUndoCommand *command, int track_offset,
void *command, int track_offset,
bool jump_to_end)
{
dragged_footage_ = footage;
@@ -238,7 +244,9 @@ void ImportTool::place_at(const DraggedFootageData &footage,
drop_ghosts(insert, command);
if (jump_to_end) {
this->sequence()->set_playhead(max);
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(this->sequence()),
max.numerator(), max.denominator());
}
}
@@ -303,14 +311,20 @@ void ImportTool::footage_to_ghosts(Rational ghost_start,
ghost->set_data(TimelineViewGhostItem::k_attached_footage,
QVariant::fromValue(af));
} else if (track_type == Track::k_subtitle) {
SubtitleParams sp = footage->get_subtitle_params(ref.index());
int sub_count = oakengine_viewer_get_subtitle_count(
reinterpret_cast<const OakEngineNode *>(footage),
ref.index());
for (const Subtitle &sub : sp) {
for (int si = 0; si < sub_count; si++) {
const Subtitle *sub = static_cast<const Subtitle *>(
oakengine_viewer_get_subtitle_at(
reinterpret_cast<const OakEngineNode *>(footage),
ref.index(), si));
auto ghost =
create_ghost(sub.time() + ghost_start, 0, dest_track);
create_ghost(sub->time() + ghost_start, 0, dest_track);
ghost->set_data(TimelineViewGhostItem::k_attached_footage,
QVariant::fromValue(sub));
QVariant::fromValue(*sub));
}
parent()->add_tentative_subtitle_track();
@@ -327,17 +341,17 @@ void ImportTool::prep_ghosts(const Rational &frame, const int &track_index)
if (parent()->get_connected_node()) {
footage_to_ghosts(
frame, dragged_footage_,
parent()->get_connected_node()->get_video_params().time_base(),
viewer_output_video_params(parent()->get_connected_node()).time_base(),
track_index);
}
}
void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
void ImportTool::drop_ghosts(bool insert, void *parent_command)
{
auto command = new MultiUndoCommand();
auto command = oakengine_undo_command_create_multi();
if (MultiUndoCommand *c = parent()->take_subtitle_section_command()) {
command->add_child(c);
if (void *c = parent()->take_subtitle_section_command()) {
oakengine_undo_command_multi_add_child(command, c);
}
Project *dst_graph = nullptr;
@@ -402,7 +416,8 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
Core::instance()->create_new_sequence_for_project(
active_project);
new_sequence->set_default_parameters();
oakengine_viewer_set_default_parameters(
reinterpret_cast<OakEngineNode *>(new_sequence));
bool sequence_is_valid = true;
@@ -417,7 +432,14 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
}
}
new_sequence->set_parameters_from_footage(footage_only);
QVector<OakEngineNode *> _footage_nodes;
for (auto *f : footage_only) {
_footage_nodes.append(
reinterpret_cast<OakEngineNode *>(f));
}
oakengine_viewer_set_parameters_from_footage(
reinterpret_cast<OakEngineNode *>(new_sequence),
_footage_nodes.data(), _footage_nodes.size());
// If the user selected manual, show them a dialog with parameters
if (behavior == k_dws_manual) {
@@ -433,22 +455,25 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
if (sequence_is_valid) {
dst_graph = Core::instance()->get_active_project();
command->add_child(
new NodeAddCommand(dst_graph, new_sequence));
command->add_child(new FolderAddChild(
Core::instance()->get_selected_folder_in_active_project(),
new_sequence));
command->add_child(new NodeSetPositionCommand(
new_sequence, new_sequence, QPointF(0, 0)));
new_sequence->add_default_nodes(command);
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
reinterpret_cast<OakEngineNode *>(new_sequence)));
oakengine_folder_add_child(
reinterpret_cast<OakEngineNode *>(
Core::instance()->get_selected_folder_in_active_project()),
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));
footage_to_ghosts(0, dragged_footage_,
new_sequence->get_video_params().time_base(),
viewer_output_video_params(new_sequence).time_base(),
0);
if (MultiUndoCommand *c =
if (void *c =
parent()->take_subtitle_section_command()) {
command->add_child(c);
oakengine_undo_command_multi_add_child(command, c);
}
sequence = new_sequence;
@@ -484,20 +509,21 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<TimelineViewGhostItem::AttachedFootage>();
ClipBlock *clip = new ClipBlock();
ClipBlock *clip = clip_create_empty();
block = clip;
clip->set_media_in(ghost->get_media_in());
command->add_child(new NodeAddCommand(dst_graph, clip));
clip_set_media_in(clip, ghost->get_media_in());
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
reinterpret_cast<OakEngineNode *>(clip)));
// Position clip in its own context
command->add_child(
new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(clip), reinterpret_cast<void *>(clip), 0, 0, 0));
int dep_pos = k_default_distance_from_output;
// Position footage in its context
command->add_child(new NodeSetPositionCommand(
footage_stream.footage, clip, QPointF(dep_pos, 0)));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(footage_stream.footage), reinterpret_cast<void *>(clip), dep_pos, 0, 0));
dep_pos++;
@@ -505,42 +531,67 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
Track::Reference::type_from_string(footage_stream.output)) {
case Track::k_video: {
TransformDistortNode *transform =
new TransformDistortNode();
command->add_child(
new NodeAddCommand(dst_graph, transform));
reinterpret_cast<TransformDistortNode*>(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),
reinterpret_cast<OakEngineNode *>(transform)));
command->add_child(new NodeSetValueHintCommand(
transform, TransformDistortNode::k_texture_input, -1,
Node::ValueHint({ NodeValue::k_texture },
footage_stream.output)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_set_value_hint_command(
reinterpret_cast<void *>(transform),
oakengine_transform_texture_input_id(), -1,
OAK_NODE_VALUE_TEXTURE, -1,
footage_stream.output.toUtf8().constData()));
command->add_child(new NodeEdgeAddCommand(
footage_stream.footage,
NodeInput(transform,
TransformDistortNode::k_texture_input)));
command->add_child(new NodeEdgeAddCommand(
transform, NodeInput(clip, ClipBlock::k_buffer_in)));
command->add_child(new NodeSetPositionCommand(
transform, clip, QPointF(dep_pos, 0)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
reinterpret_cast<OakEngineNode *>(transform),
QLatin1String(oakengine_transform_texture_input_id()).toUtf8().constData(),
-1));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(transform),
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(),
-1));
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 = new VolumeNode();
command->add_child(
new NodeAddCommand(dst_graph, volume_node));
VolumeNode *volume_node = reinterpret_cast<VolumeNode*>(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),
reinterpret_cast<OakEngineNode *>(volume_node)));
command->add_child(new NodeSetValueHintCommand(
volume_node, VolumeNode::k_samples_input, -1,
Node::ValueHint({ NodeValue::k_samples },
footage_stream.output)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_set_value_hint_command(
reinterpret_cast<void *>(volume_node),
oakengine_volume_samples_input_id(), -1,
OAK_NODE_VALUE_SAMPLES, -1,
footage_stream.output.toUtf8().constData()));
command->add_child(new NodeEdgeAddCommand(
footage_stream.footage,
NodeInput(volume_node, VolumeNode::k_samples_input)));
command->add_child(new NodeEdgeAddCommand(
volume_node, NodeInput(clip, ClipBlock::k_buffer_in)));
command->add_child(new NodeSetPositionCommand(
volume_node, clip, QPointF(dep_pos, 0)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(footage_stream.footage),
reinterpret_cast<OakEngineNode *>(volume_node),
QLatin1String(oakengine_volume_samples_input_id()).toUtf8().constData(),
-1));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(volume_node),
reinterpret_cast<OakEngineNode *>(clip),
oakengine_clip_buffer_input_id(),
-1));
oakengine_undo_command_multi_add_child(command, oakengine_node_set_position_command(reinterpret_cast<void *>(volume_node), reinterpret_cast<void *>(clip), dep_pos, 0, 0));
break;
}
default:
@@ -557,7 +608,9 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
.value<TimelineViewGhostItem::AttachedFootage>();
if (footage_compare.footage == footage_stream.footage) {
Block::link(block_items.at(j), clip);
oakengine_block_link(
reinterpret_cast<void *>(block_items.at(j)),
reinterpret_cast<void *>(clip), 1);
}
}
@@ -566,37 +619,40 @@ void ImportTool::drop_ghosts(bool insert, MultiUndoCommand *parent_command)
Subtitle src =
ghost->get_data(TimelineViewGhostItem::k_attached_footage)
.value<Subtitle>();
SubtitleBlock *sub = new SubtitleBlock();
sub->set_text(src.text());
SubtitleBlock *sub = reinterpret_cast<SubtitleBlock *>(
oakengine_node_factory_create_from_id(
"org.olivevideoeditor.Olive.subtitle"));
oakengine_subtitle_set_text(
reinterpret_cast<OakEngineNode *>(sub),
src.text().toUtf8().constData());
block = sub;
command->add_child(new NodeAddCommand(dst_graph, sub));
command->add_child(
new NodeSetPositionCommand(sub, sub, QPointF(0, 0)));
oakengine_undo_command_multi_add_child(command,
oakengine_node_add_to_project_command(
reinterpret_cast<OakEngineProject *>(dst_graph),
reinterpret_cast<OakEngineNode *>(sub)));
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());
command->add_child(new TrackPlaceBlockCommand(
sequence->track_list(ghost->get_adjusted_track().type()),
ghost->get_adjusted_track().index(), block,
ghost->get_adjusted_in()));
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())));
block_items.replace(i, block);
}
}
if (open_sequence) {
command->add_child(new OpenSequenceCommand(sequence));
oakengine_undo_command_multi_add_child(command, make_open_sequence_command(sequence));
}
// Do command now because RequestInvalidatedFromConnected relies on track type, which will be
// "none" before this command is done because it won't be connected to any track
command->redo_now();
parent_command->add_child(command);
oakengine_undo_command_redo_now(command);
oakengine_undo_command_multi_add_child(parent_command, command);
while (!imported_clips.empty()) {
imported_clips.front()->request_invalidated_from_connected();
clip_request_invalidate_connected(imported_clips.front());
imported_clips.pop_front();
}
+3 -3
View File
@@ -41,10 +41,10 @@ public:
QVector<QPair<ViewerOutput *, QVector<Track::Reference>>>;
void place_at(const QVector<ViewerOutput *> &footage, const Rational &start,
bool insert, MultiUndoCommand *command, int track_offset = 0,
bool insert, void *command, int track_offset = 0,
bool jump_to_end = false);
void place_at(const DraggedFootageData &footage, const Rational &start,
bool insert, MultiUndoCommand *command, int track_offset = 0,
bool insert, void *command, int track_offset = 0,
bool jump_to_end = false);
// The canonical definition lives in the engine layer
@@ -61,7 +61,7 @@ private:
void prep_ghosts(const Rational &frame, const int &track_index);
void drop_ghosts(bool insert, MultiUndoCommand *parent_command);
void drop_ghosts(bool insert, void *parent_command);
TimelineViewGhostItem *create_ghost(const TimeRange &range,
const Rational &media_in,
+60 -43
View File
@@ -26,11 +26,13 @@
#include "common/qtutils.h"
#include "common/range.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "node/nodeundo.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"
@@ -685,7 +687,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
return;
}
MultiUndoCommand *command = new MultiUndoCommand();
void *command = oakengine_undo_command_create_multi();
if (!blocks_trimming.isEmpty()) {
foreach (const GhostBlockPair &p, blocks_trimming) {
@@ -694,18 +696,19 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (!ghost->get_data(TimelineViewGhostItem::k_trim_should_be_ignored)
.toBool()) {
// Must be an ordinary trim/roll
BlockTrimCommand *c = new BlockTrimCommand(
parent()->get_track_from_reference(ghost->get_adjusted_track()),
p.block, ghost->get_adjusted_length(), ghost->get_mode());
if (event->get_modifiers() & Qt::ControlModifier) {
}
c->set_trim_is_a_roll_edit(
ghost->get_data(TimelineViewGhostItem::k_trim_is_a_roll_edit)
.toBool());
command->add_child(c);
oakengine_undo_command_multi_add_child(
command,
oakengine_block_trim_command(
reinterpret_cast<void *>(
parent()->get_track_from_reference(ghost->get_adjusted_track())),
reinterpret_cast<void *>(p.block),
ghost->get_adjusted_length().numerator(),
ghost->get_adjusted_length().denominator(),
ghost->get_mode(),
ghost->get_data(TimelineViewGhostItem::k_trim_is_a_roll_edit)
.toBool()
? 1
: 0));
}
}
@@ -719,8 +722,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
} else {
new_sel.trim_out(reference_ghost->get_out_adjustment());
}
command->add_child(new TimelineWidget::SetSelectionsCommand(
parent(), new_sel, parent()->get_selections()));
oakengine_undo_command_multi_add_child(command, parent()->create_set_selections_command(new_sel, parent()->get_selections()));
}
}
@@ -756,20 +758,20 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
// Duplicate rather than move
// Place the copy instead of the original block
Block *new_block =
static_cast<Block *>(Node::copy_node_in_graph(block, command));
reinterpret_cast<Block *>(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)) {
new_clip->add_cache_passthrough_from(
static_cast<ClipBlock *>(p.block));
oakengine_clip_add_cache_passthrough(
reinterpret_cast<OakEngineClip *>(new_clip),
reinterpret_cast<OakEngineClip *>(p.block));
}
}
const Track::Reference &track_ref = p.ghost->get_adjusted_track();
command->add_child(new TrackPlaceBlockCommand(
sequence()->track_list(track_ref.type()), track_ref.index(),
block, p.ghost->get_adjusted_in()));
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())));
}
if (!relinks.empty()) {
@@ -780,8 +782,9 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
Node *link = *jt;
Node *copy_link = relinks.value(link);
if (copy_link) {
command->add_child(
new NodeLinkCommand(it.value(), copy_link, true));
oakengine_undo_command_multi_add_child(command, (void *)(oakengine_node_link_command(
reinterpret_cast<OakEngineNode*>(it.value()),
reinterpret_cast<OakEngineNode*>(copy_link), 1)));
}
}
@@ -799,10 +802,13 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
TransitionBlock *cp_in_transition =
static_cast<TransitionBlock *>(
relinks.value(og_in_transition));
command->add_child(new NodeEdgeAddCommand(
cp_clip,
NodeInput(cp_in_transition,
TransitionBlock::k_in_block_input)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(cp_in_transition),
QLatin1String(oakengine_transition_in_block_input_id()).toUtf8().constData(),
-1));
}
if (og_out_transition &&
@@ -810,10 +816,13 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
TransitionBlock *cp_out_transition =
static_cast<TransitionBlock *>(
relinks.value(og_out_transition));
command->add_child(new NodeEdgeAddCommand(
cp_clip,
NodeInput(cp_out_transition,
TransitionBlock::k_out_block_input)));
oakengine_undo_command_multi_add_child(
command,
oakengine_node_connect_command(
reinterpret_cast<OakEngineNode *>(cp_clip),
reinterpret_cast<OakEngineNode *>(cp_out_transition),
QLatin1String(oakengine_transition_out_block_input_id()).toUtf8().constData(),
-1));
}
}
}
@@ -824,8 +833,7 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
new_sel.shift_time(blocks_moving.first().ghost->get_in_adjustment());
new_sel.shift_tracks(drag_track_type_,
blocks_moving.first().ghost->get_track_adjustment());
command->add_child(new TimelineWidget::SetSelectionsCommand(
parent(), new_sel, parent()->get_selections()));
oakengine_undo_command_multi_add_child(command, parent()->create_set_selections_command(new_sel, parent()->get_selections()));
}
if (!blocks_sliding.isEmpty()) {
@@ -875,22 +883,31 @@ void PointerTool::finish_drag(TimelineViewMouseEvent *event)
if (!movement.isNull()) {
for (auto i = slide_info.constBegin(); i != slide_info.constEnd();
i++) {
command->add_child(new TrackSlideCommand(
parent()->get_track_from_reference(i.key()), i.value(),
in_adjacents.value(i.key()), out_adjacents.value(i.key()),
movement));
const QList<Block *> &moving_blocks = i.value();
QVector<void *> slide_blocks;
slide_blocks.reserve(moving_blocks.size());
for (Block *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())),
slide_blocks.constData(), slide_blocks.size(),
reinterpret_cast<void *>(in_adjacents.value(i.key())),
reinterpret_cast<void *>(out_adjacents.value(i.key())),
movement.numerator(), movement.denominator()));
}
// Adjust selections
TimelineWidgetSelections new_sel = parent()->get_selections();
new_sel.shift_time(movement);
command->add_child(new TimelineWidget::SetSelectionsCommand(
parent(), new_sel, parent()->get_selections()));
oakengine_undo_command_multi_add_child(command, parent()->create_set_selections_command(new_sel, parent()->get_selections()));
}
}
Core::instance()->undo_stack()->push(
command, qApp->translate("PointerTool", "Moved Clips"));
oakengine_undo_push(
command, qApp->translate("PointerTool", "Moved Clips").toUtf8().constData());
}
Timeline::MovementMode PointerTool::is_cursor_in_trim_handle(Block *block,

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