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.
1314 lines
40 KiB
C++
1314 lines
40 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2022 Olive Team
|
|
Modifications Copyright (C) 2025 mikesolar
|
|
|
|
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/>.
|
|
|
|
***/
|
|
|
|
#include "nodeparamviewwidgetbridge.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QFontComboBox>
|
|
#include <QUrl>
|
|
#include <QVector2D>
|
|
#include <QVector3D>
|
|
#include <QVector4D>
|
|
|
|
#include "common/qtutils.h"
|
|
#include "core.h"
|
|
#include "nodeparambutton.h"
|
|
#include "node/node.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 "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"
|
|
|
|
//#include <OpenImageIO/detail/fmt/base.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));
|
|
}
|
|
|
|
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);
|
|
|
|
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 oakengine_node_value_keyframe_track_count(node_value_type_to_c(type));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
// Map a panel widget's per-track scalar QVariant into the facade POD.
|
|
// Returns false for types that have no facade mapping (the caller keeps
|
|
// the legacy path for those).
|
|
bool variant_to_c_value(NodeValue::Type type, const QVariant &value,
|
|
oak_node_value *out)
|
|
{
|
|
memset(out, 0, sizeof(*out));
|
|
switch (type) {
|
|
case NodeValue::k_int:
|
|
out->type = OAK_NODE_VALUE_INT;
|
|
out->num = value.toLongLong();
|
|
return true;
|
|
case NodeValue::k_combo:
|
|
out->type = OAK_NODE_VALUE_COMBO;
|
|
out->num = value.toLongLong();
|
|
return true;
|
|
case NodeValue::k_float:
|
|
case NodeValue::k_bezier:
|
|
out->type = OAK_NODE_VALUE_FLOAT;
|
|
out->f[0] = value.toDouble();
|
|
return true;
|
|
case NodeValue::k_boolean:
|
|
out->type = OAK_NODE_VALUE_BOOL;
|
|
out->num = value.toBool() ? 1 : 0;
|
|
return true;
|
|
case NodeValue::k_rational: {
|
|
const Rational r = value.value<Rational>();
|
|
out->type = OAK_NODE_VALUE_RATIONAL;
|
|
out->num = r.numerator();
|
|
out->den = r.denominator();
|
|
return true;
|
|
}
|
|
case NodeValue::k_color:
|
|
out->type = OAK_NODE_VALUE_COLOR;
|
|
out->f[0] = value.toDouble();
|
|
return true;
|
|
case NodeValue::k_vec2:
|
|
out->type = OAK_NODE_VALUE_VEC2;
|
|
out->f[0] = value.toDouble();
|
|
return true;
|
|
case NodeValue::k_vec3:
|
|
out->type = OAK_NODE_VALUE_VEC3;
|
|
out->f[0] = value.toDouble();
|
|
return true;
|
|
case NodeValue::k_vec4:
|
|
out->type = OAK_NODE_VALUE_VEC4;
|
|
out->f[0] = value.toDouble();
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Convert rational node time to the facade's frame timestamps using the
|
|
// facade's own timebase (so the round trip is exact).
|
|
int64_t node_time_to_ts(OakEngineNode *node, const Rational &time)
|
|
{
|
|
int tbn = 0, tbd = 0;
|
|
oakengine_node_frame_time_base(node, &tbn, &tbd);
|
|
return Timecode::time_to_timestamp(time, Rational(tbn, tbd),
|
|
Timecode::k_round);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void NodeParamViewWidgetBridge::create_widgets()
|
|
{
|
|
QWidget *parent = dynamic_cast<QWidget *>(this->parent());
|
|
|
|
if (get_inner_input().is_array() && get_inner_input().element() == -1) {
|
|
NodeParamViewArrayWidget *w = new NodeParamViewArrayWidget(
|
|
get_inner_input().node(), get_inner_input().input(), parent);
|
|
connect(w, &NodeParamViewArrayWidget::double_clicked, this,
|
|
&NodeParamViewWidgetBridge::array_widget_double_clicked);
|
|
widgets_.append(w);
|
|
|
|
} else {
|
|
// We assume the first data type is the "primary" type
|
|
NodeValue::Type t = get_data_type();
|
|
switch (t) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeValue::k_none:
|
|
case NodeValue::k_texture:
|
|
case NodeValue::k_matrix:
|
|
case NodeValue::k_samples:
|
|
case NodeValue::k_video_params:
|
|
case NodeValue::k_audio_params:
|
|
case NodeValue::k_subtitle_params:
|
|
case NodeValue::k_binary:
|
|
case NodeValue::k_data_type_count:
|
|
break;
|
|
case NodeValue::k_int: {
|
|
create_sliders<IntegerSlider>(1, parent);
|
|
break;
|
|
}
|
|
case NodeValue::k_rational: {
|
|
create_sliders<RationalSlider>(1, parent);
|
|
break;
|
|
}
|
|
case NodeValue::k_float:
|
|
case NodeValue::k_vec2:
|
|
case NodeValue::k_vec3:
|
|
case NodeValue::k_vec4: {
|
|
create_sliders<FloatSlider>(get_slider_count(t), parent);
|
|
break;
|
|
}
|
|
case NodeValue::k_combo:
|
|
case NodeValue::k_str_combo: {
|
|
QComboBox *combobox = new QComboBox(parent);
|
|
|
|
QStringList items = get_inner_input().get_combo_box_strings();
|
|
QStringList values =
|
|
get_inner_input().get_property("combo_value_str").toStringList();
|
|
const bool use_value_data = (t == NodeValue::k_str_combo) &&
|
|
!values.isEmpty();
|
|
for (int i = 0; i < items.size(); ++i) {
|
|
const QString &label = items.at(i);
|
|
if (use_value_data && i < values.size()) {
|
|
combobox->addItem(label, values.at(i));
|
|
} else {
|
|
combobox->addItem(label);
|
|
}
|
|
}
|
|
|
|
widgets_.append(combobox);
|
|
connect(combobox,
|
|
static_cast<void (QComboBox::*)(int)>(
|
|
&QComboBox::currentIndexChanged),
|
|
this, &NodeParamViewWidgetBridge::widget_callback);
|
|
break;
|
|
}
|
|
case NodeValue::k_file: {
|
|
FileField *file_field;
|
|
if (get_inner_input().get_property(QStringLiteral("lut_library"))
|
|
.toBool()) {
|
|
// File inputs that accept LUTs get a combo box for picking
|
|
// from the global LUT library
|
|
file_field = new LutFileField(parent);
|
|
} else {
|
|
file_field = new FileField(parent);
|
|
}
|
|
widgets_.append(file_field);
|
|
connect(file_field, &FileField::filename_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
break;
|
|
}
|
|
case NodeValue::k_color: {
|
|
if (get_inner_input().get_property("color_semantic").toString() ==
|
|
QStringLiteral("scalar")) {
|
|
create_sliders<FloatSlider>(4, parent);
|
|
} else {
|
|
ColorButton *color_button = new ColorButton(
|
|
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);
|
|
}
|
|
break;
|
|
}
|
|
case NodeValue::k_text: {
|
|
NodeParamViewTextEdit *line_edit =
|
|
new NodeParamViewTextEdit(parent);
|
|
widgets_.append(line_edit);
|
|
connect(line_edit, &NodeParamViewTextEdit::text_edited, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(line_edit, &NodeParamViewTextEdit::request_edit_in_viewer,
|
|
this, &NodeParamViewWidgetBridge::request_edit_text_in_viewer);
|
|
break;
|
|
}
|
|
case NodeValue::k_boolean: {
|
|
QCheckBox *check_box = new QCheckBox(parent);
|
|
widgets_.append(check_box);
|
|
connect(check_box, &QCheckBox::clicked, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
break;
|
|
}
|
|
case NodeValue::k_font: {
|
|
QFontComboBox *font_combobox = new QFontComboBox(parent);
|
|
widgets_.append(font_combobox);
|
|
connect(font_combobox, &QFontComboBox::currentFontChanged, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
break;
|
|
}
|
|
case NodeValue::k_bezier: {
|
|
BezierWidget *bezier = new BezierWidget(parent);
|
|
widgets_.append(bezier);
|
|
|
|
connect(bezier->x_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(bezier->y_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(bezier->cp1_x_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(bezier->cp1_y_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(bezier->cp2_x_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
connect(bezier->cp2_y_slider(), &FloatSlider::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
break;
|
|
}
|
|
case NodeValue::k_push_button: {
|
|
NodeInput input = get_inner_input();
|
|
NodeParamButton *button = new NodeParamButton(input.name(), parent);
|
|
widgets_.append(button);
|
|
plugin::PluginNode *plugin_node =
|
|
dynamic_cast<plugin::PluginNode *>(input.node());
|
|
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());
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check all properties
|
|
update_properties();
|
|
|
|
update_widget_values();
|
|
|
|
// Install event filter to disable widgets picking up scroll events
|
|
foreach (QWidget *w, widgets_) {
|
|
w->installEventFilter(&scroll_filter_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::set_input_value(const QVariant &value, int track)
|
|
{
|
|
// POD values go through the liboakengine C ABI facade (one undoable
|
|
// command with the same set-value-at-time semantics as the old
|
|
// app-side assembly); types without a facade mapping keep the legacy
|
|
// undo assembly below.
|
|
const NodeInput &input = get_inner_input();
|
|
oak_node_value c_value;
|
|
if (!variant_to_c_value(get_data_type(), value, &c_value)) {
|
|
void *command = oakengine_undo_command_create_multi();
|
|
set_input_value_internal(value, track, command, true);
|
|
oakengine_undo_push(command, get_command_name().toUtf8().constData());
|
|
return;
|
|
}
|
|
|
|
oakengine_node_set_input_at_time(
|
|
reinterpret_cast<OakEngineNode *>(input.node()),
|
|
input.input().toUtf8().constData(), input.element(),
|
|
node_time_to_ts(reinterpret_cast<OakEngineNode *>(input.node()),
|
|
get_current_time_as_node_time()),
|
|
track, &c_value, 1);
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::set_string_value(const QString &value)
|
|
{
|
|
// String-family inputs (file/text/font/str_combo) through the facade.
|
|
const NodeInput &input = get_inner_input();
|
|
oakengine_node_set_input_string_at_time(
|
|
reinterpret_cast<OakEngineNode *>(input.node()),
|
|
input.input().toUtf8().constData(), input.element(),
|
|
node_time_to_ts(reinterpret_cast<OakEngineNode *>(input.node()),
|
|
get_current_time_as_node_time()),
|
|
value.toUtf8().constData());
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::set_input_value_internal(
|
|
const QVariant &value, int track, void *command,
|
|
bool 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,
|
|
int slider_track,
|
|
const QVariant &value)
|
|
{
|
|
if (slider->is_dragging()) {
|
|
// While we're dragging, we block the input's normal signalling and create our own
|
|
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);
|
|
|
|
oakengine_dragger_start(dragger_, ts, slider_track, 0);
|
|
}
|
|
|
|
oak_node_value c_value;
|
|
if (variant_to_c_value(get_data_type(), value, &c_value)) {
|
|
oakengine_dragger_drag(dragger_, &c_value);
|
|
}
|
|
|
|
} else if (oakengine_dragger_is_started(dragger_)) {
|
|
// We were dragging and just stopped
|
|
oak_node_value c_value;
|
|
if (variant_to_c_value(get_data_type(), value, &c_value)) {
|
|
oakengine_dragger_drag(dragger_, &c_value);
|
|
}
|
|
|
|
oakengine_dragger_end(dragger_, get_command_name().toUtf8().constData());
|
|
|
|
} else {
|
|
// No drag was involved, we can just push the value
|
|
set_input_value(value, slider_track);
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::widget_callback()
|
|
{
|
|
switch (get_data_type()) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeValue::k_none:
|
|
case NodeValue::k_texture:
|
|
case NodeValue::k_matrix:
|
|
case NodeValue::k_samples:
|
|
case NodeValue::k_video_params:
|
|
case NodeValue::k_audio_params:
|
|
case NodeValue::k_subtitle_params:
|
|
case NodeValue::k_data_type_count:
|
|
break;
|
|
case NodeValue::k_int: {
|
|
// Widget is a IntegerSlider
|
|
IntegerSlider *slider = static_cast<IntegerSlider *>(sender());
|
|
|
|
process_slider(slider, QVariant::fromValue(slider->get_value()));
|
|
break;
|
|
}
|
|
case NodeValue::k_float: {
|
|
// Widget is a FloatSlider
|
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
|
|
|
process_slider(slider, slider->get_value());
|
|
break;
|
|
}
|
|
case NodeValue::k_rational: {
|
|
// Widget is a RationalSlider
|
|
RationalSlider *slider = static_cast<RationalSlider *>(sender());
|
|
process_slider(slider, QVariant::fromValue(slider->get_value()));
|
|
break;
|
|
}
|
|
case NodeValue::k_vec2: {
|
|
// Widget is a FloatSlider
|
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
|
|
|
process_slider(slider, slider->get_value());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec3: {
|
|
// Widget is a FloatSlider
|
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
|
|
|
process_slider(slider, slider->get_value());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec4: {
|
|
// Widget is a FloatSlider
|
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
|
|
|
process_slider(slider, slider->get_value());
|
|
break;
|
|
}
|
|
case NodeValue::k_file: {
|
|
set_string_value(static_cast<FileField *>(sender())->get_filename());
|
|
break;
|
|
}
|
|
case NodeValue::k_color: {
|
|
if (get_inner_input().get_property("color_semantic").toString() ==
|
|
QStringLiteral("scalar")) {
|
|
FloatSlider *slider = static_cast<FloatSlider *>(sender());
|
|
process_slider(slider, slider->get_value());
|
|
} else {
|
|
// Sender is a ColorButton: all four components go through the
|
|
// facade in one undoable command (track -1). The
|
|
// color-management input properties are not undoable in the
|
|
// engine and stay direct (same as the old code).
|
|
ManagedColor c = static_cast<ColorButton *>(sender())->get_color();
|
|
|
|
const NodeInput &input = get_inner_input();
|
|
oak_node_value c_value;
|
|
memset(&c_value, 0, sizeof(c_value));
|
|
c_value.type = OAK_NODE_VALUE_COLOR;
|
|
c_value.f[0] = c.red();
|
|
c_value.f[1] = c.green();
|
|
c_value.f[2] = c.blue();
|
|
c_value.f[3] = c.alpha();
|
|
oakengine_node_set_input_at_time(
|
|
reinterpret_cast<OakEngineNode *>(input.node()),
|
|
input.input().toUtf8().constData(), input.element(),
|
|
node_time_to_ts(
|
|
reinterpret_cast<OakEngineNode *>(input.node()),
|
|
get_current_time_as_node_time()),
|
|
-1, &c_value, 0);
|
|
|
|
Node *n = get_inner_input().node();
|
|
n->blockSignals(true);
|
|
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;
|
|
}
|
|
case NodeValue::k_text: {
|
|
// Sender is a NodeParamViewRichText
|
|
set_string_value(static_cast<NodeParamViewTextEdit *>(sender())->text());
|
|
break;
|
|
}
|
|
case NodeValue::k_binary: {
|
|
QString text = static_cast<NodeParamViewTextEdit *>(sender())->text();
|
|
QByteArray raw = text.toUtf8();
|
|
QByteArray decoded = QByteArray::fromBase64(raw);
|
|
if (decoded.isEmpty() && !raw.isEmpty()) {
|
|
decoded = raw;
|
|
}
|
|
set_input_value(decoded, 0);
|
|
break;
|
|
}
|
|
case NodeValue::k_boolean: {
|
|
// Widget is a QCheckBox
|
|
set_input_value(static_cast<QCheckBox *>(sender())->isChecked(), 0);
|
|
break;
|
|
}
|
|
case NodeValue::k_font: {
|
|
// Widget is a QFontComboBox
|
|
set_string_value(
|
|
static_cast<QFontComboBox *>(sender())->currentFont().family());
|
|
break;
|
|
}
|
|
case NodeValue::k_combo: {
|
|
// Widget is a QComboBox
|
|
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
|
int index = cb->currentIndex();
|
|
|
|
// Subtract any splitters up until this point
|
|
for (int i = index - 1; i >= 0; i--) {
|
|
if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() ==
|
|
QStringLiteral("separator")) {
|
|
index--;
|
|
}
|
|
}
|
|
|
|
set_input_value(index, 0);
|
|
break;
|
|
}
|
|
case NodeValue::k_str_combo: {
|
|
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
|
const QVariant data = cb->currentData();
|
|
if (data.isValid()) {
|
|
set_string_value(data.toString());
|
|
} else {
|
|
set_string_value(cb->currentText());
|
|
}
|
|
break;
|
|
}
|
|
case NodeValue::k_bezier: {
|
|
// Widget is a FloatSlider (child of BezierWidget)
|
|
BezierWidget *bw = static_cast<BezierWidget *>(widgets_.first());
|
|
FloatSlider *fs = static_cast<FloatSlider *>(sender());
|
|
|
|
int index = -1;
|
|
if (fs == bw->x_slider()) {
|
|
index = 0;
|
|
} else if (fs == bw->y_slider()) {
|
|
index = 1;
|
|
} else if (fs == bw->cp1_x_slider()) {
|
|
index = 2;
|
|
} else if (fs == bw->cp1_y_slider()) {
|
|
index = 3;
|
|
} else if (fs == bw->cp2_x_slider()) {
|
|
index = 4;
|
|
} else if (fs == bw->cp2_y_slider()) {
|
|
index = 5;
|
|
}
|
|
|
|
if (index != -1) {
|
|
process_slider(fs, index, fs->get_value());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void NodeParamViewWidgetBridge::create_sliders(int count, QWidget *parent)
|
|
{
|
|
for (int i = 0; i < count; i++) {
|
|
T *fs = new T(parent);
|
|
fs->SliderBase::set_default_value(
|
|
get_inner_input().get_split_default_value_for_track(i));
|
|
fs->set_ladder_element_count(2);
|
|
|
|
// HACK: Force some spacing between sliders
|
|
fs->setContentsMargins(
|
|
0, 0,
|
|
QtUtils::q_font_metrics_width(fs->fontMetrics(),
|
|
QStringLiteral(" ")),
|
|
0);
|
|
|
|
widgets_.append(fs);
|
|
connect(fs, &T::value_changed, this,
|
|
&NodeParamViewWidgetBridge::widget_callback);
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::update_widget_values()
|
|
{
|
|
if (get_inner_input().is_array() && get_inner_input().element() == -1) {
|
|
return;
|
|
}
|
|
|
|
Rational node_time;
|
|
if (get_inner_input().is_keyframing()) {
|
|
node_time = get_current_time_as_node_time();
|
|
}
|
|
|
|
// We assume the first data type is the "primary" type
|
|
switch (get_data_type()) {
|
|
// None of these inputs have applicable UI widgets
|
|
case NodeValue::k_none:
|
|
case NodeValue::k_texture:
|
|
case NodeValue::k_matrix:
|
|
case NodeValue::k_samples:
|
|
case NodeValue::k_video_params:
|
|
case NodeValue::k_audio_params:
|
|
case NodeValue::k_subtitle_params:
|
|
case NodeValue::k_data_type_count:
|
|
break;
|
|
case NodeValue::k_binary: {
|
|
NodeParamViewTextEdit *e =
|
|
static_cast<NodeParamViewTextEdit *>(widgets_.first());
|
|
QByteArray bytes =
|
|
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(GetInputValueAtTime(get_inner_input(), node_time).toLongLong());
|
|
break;
|
|
}
|
|
case NodeValue::k_float: {
|
|
static_cast<FloatSlider *>(widgets_.first())
|
|
->set_value(GetInputValueAtTime(get_inner_input(), node_time).toDouble());
|
|
break;
|
|
}
|
|
case NodeValue::k_rational: {
|
|
static_cast<RationalSlider *>(widgets_.first())
|
|
->set_value(
|
|
GetInputValueAtTime(get_inner_input(), node_time).value<Rational>());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec2: {
|
|
QVector2D vec2 =
|
|
GetInputValueAtTime(get_inner_input(), node_time).value<QVector2D>();
|
|
|
|
static_cast<FloatSlider *>(widgets_.at(0))
|
|
->set_value(static_cast<double>(vec2.x()));
|
|
static_cast<FloatSlider *>(widgets_.at(1))
|
|
->set_value(static_cast<double>(vec2.y()));
|
|
break;
|
|
}
|
|
case NodeValue::k_vec3: {
|
|
QVector3D vec3 =
|
|
GetInputValueAtTime(get_inner_input(), node_time).value<QVector3D>();
|
|
|
|
static_cast<FloatSlider *>(widgets_.at(0))
|
|
->set_value(static_cast<double>(vec3.x()));
|
|
static_cast<FloatSlider *>(widgets_.at(1))
|
|
->set_value(static_cast<double>(vec3.y()));
|
|
static_cast<FloatSlider *>(widgets_.at(2))
|
|
->set_value(static_cast<double>(vec3.z()));
|
|
break;
|
|
}
|
|
case NodeValue::k_vec4: {
|
|
QVector4D vec4 =
|
|
GetInputValueAtTime(get_inner_input(), node_time).value<QVector4D>();
|
|
|
|
static_cast<FloatSlider *>(widgets_.at(0))
|
|
->set_value(static_cast<double>(vec4.x()));
|
|
static_cast<FloatSlider *>(widgets_.at(1))
|
|
->set_value(static_cast<double>(vec4.y()));
|
|
static_cast<FloatSlider *>(widgets_.at(2))
|
|
->set_value(static_cast<double>(vec4.z()));
|
|
static_cast<FloatSlider *>(widgets_.at(3))
|
|
->set_value(static_cast<double>(vec4.w()));
|
|
break;
|
|
}
|
|
case NodeValue::k_file: {
|
|
FileField *ff = static_cast<FileField *>(widgets_.first());
|
|
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 = 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))
|
|
->set_value(static_cast<double>(c.green()));
|
|
static_cast<FloatSlider *>(widgets_.at(2))
|
|
->set_value(static_cast<double>(c.blue()));
|
|
static_cast<FloatSlider *>(widgets_.at(3))
|
|
->set_value(static_cast<double>(c.alpha()));
|
|
} else {
|
|
ManagedColor mc =
|
|
GetInputValueAtTime(get_inner_input(), node_time).value<Color>();
|
|
|
|
mc.set_color_input(
|
|
get_inner_input().get_property("col_input").toString());
|
|
|
|
QString d = get_inner_input().get_property("col_display").toString();
|
|
QString v = get_inner_input().get_property("col_view").toString();
|
|
QString l = get_inner_input().get_property("col_look").toString();
|
|
|
|
mc.set_color_output(ColorTransform(d, v, l));
|
|
|
|
static_cast<ColorButton *>(widgets_.first())->set_color(mc);
|
|
}
|
|
break;
|
|
}
|
|
case NodeValue::k_text: {
|
|
NodeParamViewTextEdit *e =
|
|
static_cast<NodeParamViewTextEdit *>(widgets_.first());
|
|
e->setTextPreservingCursor(
|
|
GetInputValueAtTime(get_inner_input(), node_time).toString());
|
|
break;
|
|
}
|
|
case NodeValue::k_boolean:
|
|
static_cast<QCheckBox *>(widgets_.first())
|
|
->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(
|
|
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 = GetInputValueAtTime(get_inner_input(), node_time).toInt();
|
|
for (int i = 0; i < cb->count(); i++) {
|
|
if (cb->itemData(i).toInt() == index) {
|
|
cb->setCurrentIndex(i);
|
|
}
|
|
}
|
|
cb->blockSignals(false);
|
|
break;
|
|
}
|
|
case NodeValue::k_str_combo: {
|
|
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
|
cb->blockSignals(true);
|
|
const QString current =
|
|
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) ||
|
|
(!data.isValid() && cb->itemText(i) == current)) {
|
|
cb->setCurrentIndex(i);
|
|
break;
|
|
}
|
|
}
|
|
cb->blockSignals(false);
|
|
break;
|
|
}
|
|
case NodeValue::k_bezier: {
|
|
BezierWidget *bw = static_cast<BezierWidget *>(widgets_.first());
|
|
bw->set_value(GetInputValueAtTime(get_inner_input(), node_time).value<Bezier>());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Rational NodeParamViewWidgetBridge::get_current_time_as_node_time() const
|
|
{
|
|
if (get_time_target()) {
|
|
return get_adjusted_time(get_time_target(), get_inner_input().node(),
|
|
get_time_target()->get_playhead(),
|
|
Node::k_transform_towards_input);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
QString NodeParamViewWidgetBridge::get_command_name() const
|
|
{
|
|
NodeInput i = get_inner_input();
|
|
return tr("Edited Value Of %1 - %2")
|
|
.arg(i.node()->get_label_and_name(), i.node()->get_input_name(i.input()));
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::set_timebase(const Rational &timebase)
|
|
{
|
|
if (get_data_type() == NodeValue::k_rational) {
|
|
static_cast<RationalSlider *>(widgets_.first())->set_timebase(timebase);
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::TimeTargetDisconnectEvent(ViewerOutput *v)
|
|
{
|
|
if (viewer_sub_ > 0) {
|
|
oakengine_event_unsubscribe(viewer_sub_);
|
|
viewer_sub_ = 0;
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::TimeTargetConnectEvent(ViewerOutput *v)
|
|
{
|
|
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(OakEngineNode *source,
|
|
const QString &input,
|
|
int element, qint64 in_ts,
|
|
qint64 out_ts)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::set_property(const QString &key,
|
|
const QVariant &value)
|
|
{
|
|
NodeValue::Type data_type = get_data_type();
|
|
|
|
// Parameters for all types
|
|
bool key_is_disable = key.startsWith(QStringLiteral("disable"));
|
|
if (key_is_disable || key.startsWith(QStringLiteral("enabled"))) {
|
|
bool e = value.toBool();
|
|
if (key_is_disable) {
|
|
e = !e;
|
|
}
|
|
|
|
if (key.size() == 7) { // just the word "disable" or "enabled"
|
|
for (int i = 0; i < widgets_.size(); i++) {
|
|
widgets_.at(i)->setEnabled(e);
|
|
}
|
|
} else { // set specific track/widget
|
|
bool ok;
|
|
int element = key.mid(7).toInt(&ok);
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (key == QStringLiteral("tooltip")) {
|
|
for (int i = 0; i < widgets_.size(); i++) {
|
|
widgets_.at(i)->setToolTip(value.toString());
|
|
}
|
|
}
|
|
|
|
// Parameters for integers, floats, and vectors
|
|
if (NodeValue::type_is_numeric(data_type) ||
|
|
NodeValue::type_is_vector(data_type)) {
|
|
if (key == QStringLiteral("min")) {
|
|
switch (data_type) {
|
|
case NodeValue::k_int:
|
|
static_cast<IntegerSlider *>(widgets_.first())
|
|
->set_minimum(value.value<int64_t>());
|
|
break;
|
|
case NodeValue::k_float:
|
|
static_cast<FloatSlider *>(widgets_.first())
|
|
->set_minimum(value.toDouble());
|
|
break;
|
|
case NodeValue::k_rational:
|
|
static_cast<RationalSlider *>(widgets_.first())
|
|
->set_minimum(value.value<Rational>());
|
|
break;
|
|
case NodeValue::k_vec2: {
|
|
QVector2D min = value.value<QVector2D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_minimum(min.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_minimum(min.y());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec3: {
|
|
QVector3D min = value.value<QVector3D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_minimum(min.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_minimum(min.y());
|
|
static_cast<FloatSlider *>(widgets_.at(2))->set_minimum(min.z());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec4: {
|
|
QVector4D min = value.value<QVector4D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_minimum(min.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_minimum(min.y());
|
|
static_cast<FloatSlider *>(widgets_.at(2))->set_minimum(min.z());
|
|
static_cast<FloatSlider *>(widgets_.at(3))->set_minimum(min.w());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
} else if (key == QStringLiteral("max")) {
|
|
switch (data_type) {
|
|
case NodeValue::k_int:
|
|
static_cast<IntegerSlider *>(widgets_.first())
|
|
->set_maximum(value.value<int64_t>());
|
|
break;
|
|
case NodeValue::k_float:
|
|
static_cast<FloatSlider *>(widgets_.first())
|
|
->set_maximum(value.toDouble());
|
|
break;
|
|
case NodeValue::k_rational:
|
|
static_cast<RationalSlider *>(widgets_.first())
|
|
->set_maximum(value.value<Rational>());
|
|
break;
|
|
case NodeValue::k_vec2: {
|
|
QVector2D max = value.value<QVector2D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_maximum(max.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_maximum(max.y());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec3: {
|
|
QVector3D max = value.value<QVector3D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_maximum(max.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_maximum(max.y());
|
|
static_cast<FloatSlider *>(widgets_.at(2))->set_maximum(max.z());
|
|
break;
|
|
}
|
|
case NodeValue::k_vec4: {
|
|
QVector4D max = value.value<QVector4D>();
|
|
static_cast<FloatSlider *>(widgets_.at(0))->set_maximum(max.x());
|
|
static_cast<FloatSlider *>(widgets_.at(1))->set_maximum(max.y());
|
|
static_cast<FloatSlider *>(widgets_.at(2))->set_maximum(max.z());
|
|
static_cast<FloatSlider *>(widgets_.at(3))->set_maximum(max.w());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
} else if (key == QStringLiteral("offset")) {
|
|
const int c_type = node_value_type_to_c(data_type);
|
|
int tracks = oakengine_node_value_keyframe_track_count(c_type);
|
|
|
|
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();
|
|
|
|
} else if (key.startsWith(QStringLiteral("color"))) {
|
|
QColor c(value.toString());
|
|
|
|
int tracks = oakengine_node_value_keyframe_track_count(node_value_type_to_c(data_type));
|
|
|
|
if (key.size() == 5) {
|
|
// Set for all tracks
|
|
for (int i = 0; i < tracks; i++) {
|
|
static_cast<SliderBase *>(widgets_.at(i))->set_color(c);
|
|
}
|
|
} else {
|
|
bool ok;
|
|
int element = key.mid(5).toInt(&ok);
|
|
if (ok && element >= 0 && element < tracks) {
|
|
static_cast<SliderBase *>(widgets_.at(element))->set_color(c);
|
|
}
|
|
}
|
|
|
|
} else if (key == QStringLiteral("base")) {
|
|
double d = value.toDouble();
|
|
for (int i = 0; i < widgets_.size(); i++) {
|
|
static_cast<NumericSliderBase *>(widgets_.at(i))
|
|
->set_drag_multiplier(d);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ComboBox strings changing
|
|
if (data_type == NodeValue::k_combo || data_type == NodeValue::k_str_combo) {
|
|
if (key == QStringLiteral("combo_str")) {
|
|
QComboBox *cb = static_cast<QComboBox *>(widgets_.first());
|
|
|
|
int old_index = cb->currentIndex();
|
|
|
|
// Block the combobox changed signals since we anticipate the index will be the same and not require a re-render
|
|
cb->blockSignals(true);
|
|
|
|
cb->clear();
|
|
|
|
QStringList items = value.toStringList();
|
|
QStringList values =
|
|
get_inner_input().get_property("combo_value_str").toStringList();
|
|
const bool use_value_data = (data_type == NodeValue::k_str_combo) &&
|
|
!values.isEmpty();
|
|
int index = 0;
|
|
for (int i = 0; i < items.size(); ++i) {
|
|
const QString &s = items.at(i);
|
|
if (s.isEmpty()) {
|
|
cb->insertSeparator(cb->count());
|
|
cb->setItemData(cb->count() - 1, -1);
|
|
} else {
|
|
if (use_value_data && i < values.size()) {
|
|
cb->addItem(s, values.at(i));
|
|
} else {
|
|
cb->addItem(s, index);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
cb->setCurrentIndex(old_index);
|
|
|
|
cb->blockSignals(false);
|
|
|
|
// In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the
|
|
// value has changed
|
|
if (cb->currentIndex() != old_index) {
|
|
widget_callback();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parameters for floats and vectors only
|
|
if (data_type == NodeValue::k_float ||
|
|
NodeValue::type_is_vector(data_type)) {
|
|
if (key == QStringLiteral("view")) {
|
|
FloatSlider::DisplayType display_type =
|
|
static_cast<FloatSlider::DisplayType>(value.toInt());
|
|
|
|
foreach (QWidget *w, widgets_) {
|
|
static_cast<FloatSlider *>(w)->set_display_type(display_type);
|
|
}
|
|
} else if (key == QStringLiteral("decimalplaces")) {
|
|
int dec_places = value.toInt();
|
|
|
|
foreach (QWidget *w, widgets_) {
|
|
static_cast<FloatSlider *>(w)->set_decimal_places(dec_places);
|
|
}
|
|
} else if (key == QStringLiteral("autotrim")) {
|
|
bool autotrim = value.toBool();
|
|
|
|
foreach (QWidget *w, widgets_) {
|
|
static_cast<FloatSlider *>(w)->set_auto_trim_decimal_places(
|
|
autotrim);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data_type == NodeValue::k_rational) {
|
|
if (key == QStringLiteral("view")) {
|
|
RationalSlider::DisplayType display_type =
|
|
static_cast<RationalSlider::DisplayType>(value.toInt());
|
|
|
|
foreach (QWidget *w, widgets_) {
|
|
static_cast<RationalSlider *>(w)->set_display_type(display_type);
|
|
}
|
|
} else if (key == QStringLiteral("viewlock")) {
|
|
bool locked = value.toBool();
|
|
|
|
foreach (QWidget *w, widgets_) {
|
|
static_cast<RationalSlider *>(w)->set_lock_display_type(locked);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parameters for files
|
|
if (data_type == NodeValue::k_file) {
|
|
FileField *ff = static_cast<FileField *>(widgets_.first());
|
|
|
|
if (key == QStringLiteral("placeholder")) {
|
|
ff->set_placeholder(value.toString());
|
|
} else if (key == QStringLiteral("directory")) {
|
|
ff->set_directory_mode(value.toBool());
|
|
} else if (key == QStringLiteral("filter")) {
|
|
ff->set_name_filter(value.toString());
|
|
} else if (key == QStringLiteral("lut_library") && value.toBool()) {
|
|
// Offer the global LUT library directories as sidebar shortcuts in
|
|
// the browse dialog
|
|
QList<QUrl> sidebar_urls;
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parameters for text
|
|
if (data_type == NodeValue::k_text) {
|
|
NodeParamViewTextEdit *tex =
|
|
static_cast<NodeParamViewTextEdit *>(widgets_.first());
|
|
|
|
if (key == QStringLiteral("vieweronly")) {
|
|
tex->set_edit_in_viewer_only_mode(value.toBool());
|
|
}
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::input_data_type_changed(OakEngineNode *source,
|
|
const QString &input)
|
|
{
|
|
if (reinterpret_cast<Node *>(source) == get_outer_input().node() &&
|
|
input == get_outer_input().input()) {
|
|
qDeleteAll(widgets_);
|
|
widgets_.clear();
|
|
|
|
create_widgets();
|
|
|
|
emit widgets_recreated(get_outer_input());
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::property_changed(const QString &input)
|
|
{
|
|
bool found = false;
|
|
|
|
for (auto it = input_hierarchy_.cbegin(); it != input_hierarchy_.cend();
|
|
it++) {
|
|
if (it->input() == input) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
update_properties();
|
|
}
|
|
}
|
|
|
|
void NodeParamViewWidgetBridge::update_properties()
|
|
{
|
|
// Set properties from the last entry (the innermost input) to the first (the outermost)
|
|
for (auto it = input_hierarchy_.crbegin(); it != input_hierarchy_.crend();
|
|
it++) {
|
|
auto input_properties = it->node()->get_input_properties(it->input());
|
|
for (auto jt = input_properties.cbegin(); jt != input_properties.cend();
|
|
jt++) {
|
|
set_property(jt.key(), jt.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NodeParamViewScrollBlocker::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
Q_UNUSED(watched)
|
|
|
|
if (event->type() == QEvent::Wheel) {
|
|
// Block this event
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|