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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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 ¤t,
|
||||
const QModelIndex &previous)
|
||||
{
|
||||
size_t jump_to = (current.row() + 1);
|
||||
stack_->jump(jump_to);
|
||||
oakengine_undo_jump(static_cast<int64_t>(jump_to));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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(©_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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "numericsliderbase.h"
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "config/config.h"
|
||||
#include "common/configwrapper.h"
|
||||
#include "core.h"
|
||||
|
||||
namespace olive
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
#include "common/lerp.h"
|
||||
#include "common/qtutils.h"
|
||||
#include "config/config.h"
|
||||
#include "common/configwrapper.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user