1242 lines
40 KiB
C++
1242 lines
40 KiB
C++
/***
|
|
|
|
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/>.
|
|
|
|
***/
|
|
|
|
#include "oakengine/events.h"
|
|
|
|
#include <atomic>
|
|
|
|
#include <cstring>
|
|
|
|
#include <QHash>
|
|
#include <QMutex>
|
|
#include <QMutexLocker>
|
|
#include <QVector>
|
|
|
|
#include "audio/audiomanager.h"
|
|
#include "coreengine.h"
|
|
#include "node/keyframe.h"
|
|
#include "oakengine/node.h"
|
|
#include "node/block/block.h"
|
|
#include "node/color/colormanager/colormanager.h"
|
|
#include "node/group/group.h"
|
|
#include "node/output/track/track.h"
|
|
#include "node/output/track/tracklist.h"
|
|
#include "node/output/viewer/viewer.h"
|
|
#include "node/project.h"
|
|
#include "node/project/folder/folder.h"
|
|
#include "node/project/sequence/sequence.h"
|
|
#include "render/framehashcache.h"
|
|
#include "render/playbackcache.h"
|
|
#include "task/task.h"
|
|
#include "task/taskmanager.h"
|
|
#include "timeline/timelinemarker.h"
|
|
#include "timeline/timelineworkarea.h"
|
|
#include "undo/undostack.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
// Subscription registry: id -> connections. Callbacks capture the function
|
|
// pointer and userdata directly, so delivery never touches the registry;
|
|
// the map only tracks lifecycle (unsubscribe, sender teardown).
|
|
struct Subscription {
|
|
QVector<QMetaObject::Connection> connections;
|
|
};
|
|
|
|
QMutex g_registry_mutex;
|
|
QHash<int64_t, Subscription> g_registry;
|
|
std::atomic<int64_t> g_next_id{1};
|
|
|
|
// The observed engine object died: drop the registry entry. Qt has already
|
|
// torn down the connections themselves.
|
|
void drop_subscription(int64_t id)
|
|
{
|
|
QMutexLocker locker(&g_registry_mutex);
|
|
g_registry.remove(id);
|
|
}
|
|
|
|
void invoke(oakengine_event_fn fn, void *userdata, int32_t id, void *source,
|
|
int64_t a, int64_t b, void *related, int64_t c = 0,
|
|
const char *s = nullptr)
|
|
{
|
|
oakengine_event event;
|
|
event.id = id;
|
|
event.reserved = 0;
|
|
event.a = a;
|
|
event.b = b;
|
|
event.c = c;
|
|
event.source = source;
|
|
event.handle = related;
|
|
event.s = s;
|
|
fn(&event, userdata);
|
|
}
|
|
|
|
// Frame-timestamp timebase for node events: the frame rate of the
|
|
// project's first sequence, or the engine default (1001/30000 s per
|
|
// frame). Same convention as node.cpp's project_time_base().
|
|
olive::Rational node_frame_time_base(const olive::Node *node)
|
|
{
|
|
if (const olive::Project *p =
|
|
olive::Project::get_project_from_object(node)) {
|
|
for (olive::Node *n : p->nodes()) {
|
|
if (const olive::Sequence *s =
|
|
dynamic_cast<olive::Sequence *>(n)) {
|
|
const olive::Rational fr = s->get_video_params().frame_rate();
|
|
if (!fr.isNull() && !fr.isNaN()) {
|
|
return fr.flipped();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return olive::Rational(1001, 30000);
|
|
}
|
|
|
|
// NodeValue::Type -> facade value type (same mapping as node.cpp).
|
|
int node_value_type_to_c(olive::NodeValue::Type t)
|
|
{
|
|
switch (t) {
|
|
case olive::NodeValue::k_int:
|
|
return OAK_NODE_VALUE_INT;
|
|
case olive::NodeValue::k_float:
|
|
return OAK_NODE_VALUE_FLOAT;
|
|
case olive::NodeValue::k_boolean:
|
|
return OAK_NODE_VALUE_BOOL;
|
|
case olive::NodeValue::k_rational:
|
|
return OAK_NODE_VALUE_RATIONAL;
|
|
case olive::NodeValue::k_color:
|
|
return OAK_NODE_VALUE_COLOR;
|
|
case olive::NodeValue::k_vec2:
|
|
return OAK_NODE_VALUE_VEC2;
|
|
case olive::NodeValue::k_vec3:
|
|
return OAK_NODE_VALUE_VEC3;
|
|
case olive::NodeValue::k_vec4:
|
|
return OAK_NODE_VALUE_VEC4;
|
|
case olive::NodeValue::k_combo:
|
|
return OAK_NODE_VALUE_COMBO;
|
|
case olive::NodeValue::k_file:
|
|
return OAK_NODE_VALUE_STRING;
|
|
case olive::NodeValue::k_text:
|
|
return OAK_NODE_VALUE_TEXT;
|
|
case olive::NodeValue::k_font:
|
|
return OAK_NODE_VALUE_FONT;
|
|
case olive::NodeValue::k_str_combo:
|
|
return OAK_NODE_VALUE_STR_COMBO;
|
|
case olive::NodeValue::k_binary:
|
|
return OAK_NODE_VALUE_BINARY;
|
|
case olive::NodeValue::k_bezier:
|
|
return OAK_NODE_VALUE_BEZIER;
|
|
default:
|
|
return OAK_NODE_VALUE_NONE;
|
|
}
|
|
}
|
|
|
|
// Wire the node-family events (handle validated as a Node). Appended to
|
|
// `conns`; returns false when nothing matched.
|
|
bool connect_node_event(olive::Node *node, int32_t event_id,
|
|
oakengine_event_fn fn, void *userdata,
|
|
QVector<QMetaObject::Connection> *conns)
|
|
{
|
|
using namespace olive;
|
|
|
|
switch (event_id) {
|
|
case OAKENGINE_EVENT_NODE_LABEL_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::label_changed, node,
|
|
[fn, userdata, node](const QString &label) {
|
|
const QByteArray utf = label.toUtf8();
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_LABEL_CHANGED, node,
|
|
0, 0, nullptr, 0, utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_VALUE_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::value_changed, node,
|
|
[fn, userdata, node](const NodeInput &input,
|
|
const TimeRange &range) {
|
|
const Rational tb = node_frame_time_base(node);
|
|
const QByteArray utf = input.input().toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_INPUT_VALUE_CHANGED, node,
|
|
input.element(),
|
|
core::Timecode::time_to_timestamp(
|
|
range.in(), tb, core::Timecode::k_round),
|
|
nullptr,
|
|
core::Timecode::time_to_timestamp(
|
|
range.out(), tb, core::Timecode::k_round),
|
|
utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_CONNECTED:
|
|
case OAKENGINE_EVENT_NODE_INPUT_DISCONNECTED: {
|
|
const bool connected =
|
|
event_id == OAKENGINE_EVENT_NODE_INPUT_CONNECTED;
|
|
auto deliver = [fn, userdata, node, event_id](
|
|
Node *output, const NodeInput &input) {
|
|
const QByteArray utf = input.input().toUtf8();
|
|
invoke(fn, userdata, event_id, node, input.element(), 0, output, 0,
|
|
utf.constData());
|
|
};
|
|
if (connected) {
|
|
conns->append(QObject::connect(node, &Node::input_connected, node,
|
|
deliver, Qt::DirectConnection));
|
|
} else {
|
|
conns->append(QObject::connect(node, &Node::input_disconnected,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_NODE_INPUT_FLAGS_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_flags_changed, node,
|
|
[fn, userdata, node](const QString &input,
|
|
const InputFlags &flags) {
|
|
const QByteArray utf = input.toUtf8();
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_INPUT_FLAGS_CHANGED,
|
|
node, int64_t(flags.value()), 0, nullptr, 0,
|
|
utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_PROPERTY_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_property_changed, node,
|
|
[fn, userdata, node](const QString &input, const QString &,
|
|
const QVariant &) {
|
|
const QByteArray utf = input.toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_INPUT_PROPERTY_CHANGED, node, 0, 0,
|
|
nullptr, 0, utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_DATA_TYPE_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_data_type_changed, node,
|
|
[fn, userdata, node](const QString &input, NodeValue::Type type) {
|
|
const QByteArray utf = input.toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_INPUT_DATA_TYPE_CHANGED, node,
|
|
node_value_type_to_c(type), 0, nullptr, 0,
|
|
utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_array_size_changed, node,
|
|
[fn, userdata, node](const QString &input, int old_size,
|
|
int new_size) {
|
|
const QByteArray utf = input.toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_INPUT_ARRAY_SIZE_CHANGED, node,
|
|
old_size, new_size, nullptr, 0, utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_ENABLE_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::keyframe_enable_changed, node,
|
|
[fn, userdata, node](const NodeInput &input, bool enabled) {
|
|
const QByteArray utf = input.input().toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_KEYFRAME_ENABLE_CHANGED, node,
|
|
input.element(), enabled ? 1 : 0, nullptr, 0,
|
|
utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_ADDED:
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_REMOVED: {
|
|
auto deliver = [fn, userdata, node, event_id](OakEngineKeyframe *k) {
|
|
auto *key = reinterpret_cast<NodeKeyframe *>(k);
|
|
const QByteArray utf = key->input().toUtf8();
|
|
invoke(fn, userdata, event_id, node, key->element(), key->track(),
|
|
k, 0, utf.constData());
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_NODE_KEYFRAME_ADDED) {
|
|
conns->append(QObject::connect(node, &Node::keyframe_added, node,
|
|
deliver, Qt::DirectConnection));
|
|
} else {
|
|
conns->append(QObject::connect(node, &Node::keyframe_removed,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_TIME_CHANGED:
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_TYPE_CHANGED:
|
|
case OAKENGINE_EVENT_NODE_KEYFRAME_VALUE_CHANGED: {
|
|
auto deliver = [fn, userdata, node, event_id](OakEngineKeyframe *k) {
|
|
invoke(fn, userdata, event_id, node, 0, 0, k);
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_NODE_KEYFRAME_TIME_CHANGED) {
|
|
conns->append(QObject::connect(node, &Node::keyframe_time_changed,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_NODE_KEYFRAME_TYPE_CHANGED) {
|
|
conns->append(QObject::connect(node, &Node::keyframe_type_changed,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns->append(QObject::connect(node,
|
|
&Node::keyframe_value_changed,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT:
|
|
case OAKENGINE_EVENT_NODE_NODE_REMOVED_FROM_CONTEXT: {
|
|
auto deliver = [fn, userdata, node, event_id](Node *child) {
|
|
invoke(fn, userdata, event_id, node, 0, 0, child);
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_NODE_NODE_ADDED_TO_CONTEXT) {
|
|
conns->append(QObject::connect(node, &Node::node_added_to_context,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns->append(QObject::connect(node,
|
|
&Node::node_removed_from_context,
|
|
node, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::message_count_changed, node,
|
|
[fn, userdata, node]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_MESSAGE_COUNT_CHANGED,
|
|
node, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_ADDED:
|
|
case OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_REMOVED: {
|
|
auto *group = dynamic_cast<NodeGroup *>(node);
|
|
if (!group) {
|
|
return false;
|
|
}
|
|
auto deliver = [fn, userdata, node, event_id](NodeGroup *,
|
|
const NodeInput &input) {
|
|
const QByteArray id = input.input().toUtf8();
|
|
invoke(fn, userdata, event_id, node, input.element(), 0,
|
|
input.node(), 0, id.constData());
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_GROUP_INPUT_PASSTHROUGH_ADDED) {
|
|
conns->append(QObject::connect(
|
|
group, &NodeGroup::input_passthrough_added, group, deliver,
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns->append(QObject::connect(
|
|
group, &NodeGroup::input_passthrough_removed, group, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_GROUP_OUTPUT_PASSTHROUGH_CHANGED: {
|
|
auto *group = dynamic_cast<NodeGroup *>(node);
|
|
if (!group) {
|
|
return false;
|
|
}
|
|
conns->append(QObject::connect(
|
|
group, &NodeGroup::output_passthrough_changed, group,
|
|
[fn, userdata, node](NodeGroup *, Node *output) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_GROUP_OUTPUT_PASSTHROUGH_CHANGED, node,
|
|
0, 0, output);
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
}
|
|
case OAKENGINE_EVENT_NODE_CONTEXT_POSITION_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::node_position_in_context_changed, node,
|
|
[fn, userdata, node](Node *child, const QPointF &pos) {
|
|
int64_t xb, yb;
|
|
const double x = pos.x(), y = pos.y();
|
|
memcpy(&xb, &x, sizeof(xb));
|
|
memcpy(&yb, &y, sizeof(yb));
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_NODE_CONTEXT_POSITION_CHANGED, node, xb,
|
|
yb, child);
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_LINKS_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::links_changed, node,
|
|
[fn, userdata, node]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_LINKS_CHANGED, node,
|
|
0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_COLOR_CHANGED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::color_changed, node,
|
|
[fn, userdata, node]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_COLOR_CHANGED, node,
|
|
0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_ADDED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_added, node,
|
|
[fn, userdata, node](const QString &id) {
|
|
QByteArray utf = id.toUtf8();
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_INPUT_ADDED, node,
|
|
0, 0, nullptr, 0, utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_INPUT_REMOVED:
|
|
conns->append(QObject::connect(
|
|
node, &Node::input_removed, node,
|
|
[fn, userdata, node](const QString &id) {
|
|
QByteArray utf = id.toUtf8();
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_INPUT_REMOVED, node,
|
|
0, 0, nullptr, 0, utf.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
case OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH:
|
|
conns->append(QObject::connect(
|
|
node, &Node::removed_from_graph, node,
|
|
[fn, userdata, node](olive::Project *project) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH,
|
|
node,
|
|
0, 0, reinterpret_cast<void *>(project));
|
|
},
|
|
Qt::DirectConnection));
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// The sequence's frame duration as a Rational timebase, like timeline.cpp.
|
|
bool time_base_of(const olive::Sequence *s, olive::Rational *out)
|
|
{
|
|
const olive::Rational frame_rate = s->get_video_params().frame_rate();
|
|
if (frame_rate.isNull() || frame_rate.isNaN()) {
|
|
return false;
|
|
}
|
|
*out = frame_rate.flipped();
|
|
return true;
|
|
}
|
|
|
|
int64_t time_to_ts(const olive::Rational &time, const olive::Rational &tb)
|
|
{
|
|
return olive::core::Timecode::time_to_timestamp(
|
|
time, tb, olive::core::Timecode::k_round);
|
|
}
|
|
|
|
// Block range as frame timestamps in the track's sequence timebase; -1/-1
|
|
// when the block is not on a sequenced track at emission time.
|
|
void block_timestamps(const olive::Block *block, int64_t *in_ts,
|
|
int64_t *out_ts)
|
|
{
|
|
*in_ts = -1;
|
|
*out_ts = -1;
|
|
if (!block || !block->track() || !block->track()->sequence()) {
|
|
return;
|
|
}
|
|
olive::Rational tb;
|
|
if (!time_base_of(block->track()->sequence(), &tb)) {
|
|
return;
|
|
}
|
|
*in_ts = time_to_ts(block->in(), tb);
|
|
*out_ts = time_to_ts(block->out(), tb);
|
|
}
|
|
|
|
int64_t marker_timestamp(const olive::Sequence *seq,
|
|
const olive::TimelineMarker *marker)
|
|
{
|
|
olive::Rational tb;
|
|
if (!marker || !time_base_of(seq, &tb)) {
|
|
return -1;
|
|
}
|
|
return time_to_ts(marker->time().in(), tb);
|
|
}
|
|
|
|
// Wire the connections for one subscription. `obj` is the validated engine
|
|
// object (already cast-checked). Returns the connection list, empty when
|
|
// the event family does not match `obj`.
|
|
QVector<QMetaObject::Connection> connect_event(
|
|
QObject *obj, int32_t event_id, oakengine_event_fn fn, void *userdata)
|
|
{
|
|
using namespace olive;
|
|
|
|
QVector<QMetaObject::Connection> conns;
|
|
|
|
switch (event_id) {
|
|
case OAKENGINE_EVENT_PROJECT_MODIFIED_CHANGED: {
|
|
auto *project = dynamic_cast<Project *>(obj);
|
|
if (!project) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
project, &Project::modified_changed, project,
|
|
[fn, userdata, project](bool modified) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_PROJECT_MODIFIED_CHANGED,
|
|
project, modified ? 1 : 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_PROJECT_NAME_CHANGED: {
|
|
auto *project = dynamic_cast<Project *>(obj);
|
|
if (!project) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
project, &Project::name_changed, project,
|
|
[fn, userdata, project]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_PROJECT_NAME_CHANGED,
|
|
project, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_FOLDER_BEGIN_INSERT_ITEM:
|
|
case OAKENGINE_EVENT_FOLDER_END_INSERT_ITEM:
|
|
case OAKENGINE_EVENT_FOLDER_BEGIN_REMOVE_ITEM:
|
|
case OAKENGINE_EVENT_FOLDER_END_REMOVE_ITEM: {
|
|
auto *folder = dynamic_cast<Folder *>(obj);
|
|
if (!folder) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_FOLDER_BEGIN_INSERT_ITEM) {
|
|
conns.append(QObject::connect(
|
|
folder, &Folder::begin_insert_item, folder,
|
|
[fn, userdata, folder](Node *child, int index) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_FOLDER_BEGIN_INSERT_ITEM,
|
|
folder, index, 0, child);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_FOLDER_END_INSERT_ITEM) {
|
|
conns.append(QObject::connect(
|
|
folder, &Folder::end_insert_item, folder,
|
|
[fn, userdata, folder]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_FOLDER_END_INSERT_ITEM,
|
|
folder, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_FOLDER_BEGIN_REMOVE_ITEM) {
|
|
conns.append(QObject::connect(
|
|
folder, &Folder::begin_remove_item, folder,
|
|
[fn, userdata, folder](Node *child, int index) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_FOLDER_BEGIN_REMOVE_ITEM,
|
|
folder, index, 0, child);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
folder, &Folder::end_remove_item, folder,
|
|
[fn, userdata, folder]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_FOLDER_END_REMOVE_ITEM,
|
|
folder, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_SEQUENCE_TRACK_ADDED:
|
|
case OAKENGINE_EVENT_SEQUENCE_TRACK_REMOVED: {
|
|
auto *seq = dynamic_cast<Sequence *>(obj);
|
|
if (!seq) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_SEQUENCE_TRACK_ADDED) {
|
|
conns.append(QObject::connect(
|
|
seq, &Sequence::track_added, seq,
|
|
[fn, userdata, seq](Track *track) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_TRACK_ADDED,
|
|
seq, track ? int(track->type()) : -1, 0, track);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
seq, &Sequence::track_removed, seq,
|
|
[fn, userdata, seq](Track *track) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_TRACK_REMOVED,
|
|
seq, track ? int(track->type()) : -1, 0, track);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_TRACK_BLOCK_ADDED:
|
|
case OAKENGINE_EVENT_TRACK_BLOCK_REMOVED: {
|
|
auto *track = dynamic_cast<Track *>(obj);
|
|
if (!track) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_TRACK_BLOCK_ADDED) {
|
|
conns.append(QObject::connect(
|
|
track, &Track::block_added, track,
|
|
[fn, userdata, track](Block *block) {
|
|
int64_t in_ts, out_ts;
|
|
block_timestamps(block, &in_ts, &out_ts);
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_BLOCK_ADDED,
|
|
track, in_ts, out_ts, block);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
track, &Track::block_removed, track,
|
|
[fn, userdata, track](Block *block) {
|
|
int64_t in_ts, out_ts;
|
|
block_timestamps(block, &in_ts, &out_ts);
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_BLOCK_REMOVED,
|
|
track, in_ts, out_ts, block);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_TRACK_INDEX_CHANGED:
|
|
case OAKENGINE_EVENT_TRACK_HEIGHT_CHANGED:
|
|
case OAKENGINE_EVENT_TRACK_BLOCKS_REFRESHED:
|
|
case OAKENGINE_EVENT_TRACK_MUTED_CHANGED: {
|
|
auto *track = dynamic_cast<Track *>(obj);
|
|
if (!track) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_TRACK_INDEX_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
track, &Track::index_changed, track,
|
|
[fn, userdata, track](int old_index, int new_index) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_INDEX_CHANGED,
|
|
track, old_index, new_index, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_TRACK_HEIGHT_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
track, &Track::track_height_changed, track,
|
|
[fn, userdata, track](qreal height) {
|
|
int64_t bits;
|
|
const double h = double(height);
|
|
memcpy(&bits, &h, sizeof(bits));
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_HEIGHT_CHANGED,
|
|
track, bits, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_TRACK_BLOCKS_REFRESHED) {
|
|
conns.append(QObject::connect(
|
|
track, &Track::blocks_refreshed, track,
|
|
[fn, userdata, track]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_BLOCKS_REFRESHED,
|
|
track, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
track, &Track::muted_changed, track,
|
|
[fn, userdata, track](bool muted) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TRACK_MUTED_CHANGED,
|
|
track, muted ? 1 : 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_BLOCK_ENABLED_CHANGED:
|
|
case OAKENGINE_EVENT_BLOCK_PREVIEW_CHANGED: {
|
|
auto *block = dynamic_cast<Block *>(obj);
|
|
if (!block) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_BLOCK_ENABLED_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
block, &Block::enabled_changed, block,
|
|
[fn, userdata, block]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_BLOCK_ENABLED_CHANGED,
|
|
block, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
block, &Block::preview_changed, block,
|
|
[fn, userdata, block]() {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_BLOCK_PREVIEW_CHANGED,
|
|
block, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_SEQUENCE_TRACK_LIST_CHANGED:
|
|
case OAKENGINE_EVENT_SEQUENCE_TRACK_HEIGHT_CHANGED: {
|
|
auto *seq = dynamic_cast<Sequence *>(obj);
|
|
if (!seq) {
|
|
break;
|
|
}
|
|
for (int type = 0; type < 3; type++) {
|
|
TrackList *list = seq->track_list(static_cast<Track::Type>(type));
|
|
if (!list) {
|
|
continue;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_SEQUENCE_TRACK_LIST_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
list, &TrackList::track_list_changed, seq,
|
|
[fn, userdata, seq, type]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_SEQUENCE_TRACK_LIST_CHANGED, seq,
|
|
type, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
list, &TrackList::track_height_changed, seq,
|
|
[fn, userdata, seq, type](Track *track, int height) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_SEQUENCE_TRACK_HEIGHT_CHANGED,
|
|
seq, type, height, track);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_SEQUENCE_SUBTITLES_CHANGED: {
|
|
auto *seq = dynamic_cast<Sequence *>(obj);
|
|
if (!seq) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
seq, &Sequence::subtitles_changed, seq,
|
|
[fn, userdata, seq](const TimeRange &range) {
|
|
olive::Rational tb;
|
|
int64_t in_ts = -1, out_ts = -1;
|
|
if (time_base_of(seq, &tb)) {
|
|
in_ts = time_to_ts(range.in(), tb);
|
|
out_ts = time_to_ts(range.out(), tb);
|
|
}
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_SUBTITLES_CHANGED,
|
|
seq, in_ts, out_ts, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED:
|
|
case OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED:
|
|
case OAKENGINE_EVENT_MARKER_LIST_MARKER_MODIFIED: {
|
|
auto *markers = dynamic_cast<TimelineMarkerList *>(obj);
|
|
if (!markers) {
|
|
break;
|
|
}
|
|
auto deliver = [fn, userdata, markers, event_id](
|
|
TimelineMarker *marker) {
|
|
invoke(fn, userdata, event_id, markers, 0, 0, marker);
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_MARKER_LIST_MARKER_ADDED) {
|
|
conns.append(QObject::connect(markers,
|
|
&TimelineMarkerList::marker_added,
|
|
markers, deliver,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_MARKER_LIST_MARKER_REMOVED) {
|
|
conns.append(QObject::connect(markers,
|
|
&TimelineMarkerList::marker_removed,
|
|
markers, deliver,
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(markers,
|
|
&TimelineMarkerList::marker_modified,
|
|
markers, deliver,
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED:
|
|
case OAKENGINE_EVENT_WORKAREA_ENABLED_CHANGED: {
|
|
auto *workarea = dynamic_cast<TimelineWorkArea *>(obj);
|
|
if (!workarea) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
workarea, &TimelineWorkArea::range_changed, workarea,
|
|
[fn, userdata, workarea](const TimeRange &) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_WORKAREA_RANGE_CHANGED,
|
|
workarea, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
workarea, &TimelineWorkArea::enabled_changed, workarea,
|
|
[fn, userdata, workarea](bool enabled) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_WORKAREA_ENABLED_CHANGED, workarea,
|
|
enabled ? 1 : 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_SEQUENCE_MARKER_ADDED:
|
|
case OAKENGINE_EVENT_SEQUENCE_MARKER_REMOVED:
|
|
case OAKENGINE_EVENT_SEQUENCE_MARKER_MODIFIED: {
|
|
auto *seq = dynamic_cast<Sequence *>(obj);
|
|
if (!seq) {
|
|
break;
|
|
}
|
|
TimelineMarkerList *markers = seq->get_markers();
|
|
if (event_id == OAKENGINE_EVENT_SEQUENCE_MARKER_ADDED) {
|
|
conns.append(QObject::connect(
|
|
markers, &TimelineMarkerList::marker_added, seq,
|
|
[fn, userdata, seq](TimelineMarker *marker) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_MARKER_ADDED,
|
|
seq, marker_timestamp(seq, marker), 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_SEQUENCE_MARKER_REMOVED) {
|
|
conns.append(QObject::connect(
|
|
markers, &TimelineMarkerList::marker_removed, seq,
|
|
[fn, userdata, seq](TimelineMarker *marker) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_MARKER_REMOVED,
|
|
seq, marker_timestamp(seq, marker), 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
markers, &TimelineMarkerList::marker_modified, seq,
|
|
[fn, userdata, seq](TimelineMarker *marker) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_SEQUENCE_MARKER_MODIFIED,
|
|
seq, marker_timestamp(seq, marker), 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_SEQUENCE_WORKAREA_RANGE_CHANGED:
|
|
case OAKENGINE_EVENT_SEQUENCE_WORKAREA_ENABLED_CHANGED: {
|
|
auto *seq = dynamic_cast<Sequence *>(obj);
|
|
if (!seq) {
|
|
break;
|
|
}
|
|
TimelineWorkArea *workarea = seq->get_work_area();
|
|
if (event_id == OAKENGINE_EVENT_SEQUENCE_WORKAREA_RANGE_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
workarea, &TimelineWorkArea::range_changed, seq,
|
|
[fn, userdata, seq](const TimeRange &range) {
|
|
olive::Rational tb;
|
|
int64_t in_ts = -1, out_ts = -1;
|
|
if (time_base_of(seq, &tb)) {
|
|
in_ts = time_to_ts(range.in(), tb);
|
|
out_ts = time_to_ts(range.out(), tb);
|
|
}
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_SEQUENCE_WORKAREA_RANGE_CHANGED, seq,
|
|
in_ts, out_ts, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
workarea, &TimelineWorkArea::enabled_changed, seq,
|
|
[fn, userdata, seq](bool enabled) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_SEQUENCE_WORKAREA_ENABLED_CHANGED,
|
|
seq, enabled ? 1 : 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_COLOR_MANAGER_CONFIG_CHANGED:
|
|
case OAKENGINE_EVENT_COLOR_MANAGER_REFERENCE_SPACE_CHANGED: {
|
|
auto *cm = dynamic_cast<ColorManager *>(obj);
|
|
if (!cm) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_COLOR_MANAGER_CONFIG_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
cm, &ColorManager::config_changed, cm,
|
|
[fn, userdata, cm](const QString &) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_COLOR_MANAGER_CONFIG_CHANGED, cm, 0,
|
|
0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
cm, &ColorManager::reference_space_changed, cm,
|
|
[fn, userdata, cm](const QString &) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_COLOR_MANAGER_REFERENCE_SPACE_CHANGED,
|
|
cm, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_SIZE_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_INTERLACING_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_VIDEO_PARAMS_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_AUDIO_PARAMS_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_TEXTURE_INPUT_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED:
|
|
case OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED: {
|
|
auto *viewer = dynamic_cast<ViewerOutput *>(obj);
|
|
if (!viewer) {
|
|
break;
|
|
}
|
|
// Rational payloads are a = numerator, b = denominator (seconds).
|
|
auto deliver_rational = [fn, userdata, viewer, event_id](
|
|
const Rational &r) {
|
|
invoke(fn, userdata, event_id, viewer, r.numerator(),
|
|
r.denominator(), nullptr);
|
|
};
|
|
if (event_id == OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED) {
|
|
conns.append(QObject::connect(viewer, &ViewerOutput::length_changed,
|
|
viewer, deliver_rational,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED) {
|
|
conns.append(QObject::connect(viewer,
|
|
&ViewerOutput::playhead_changed,
|
|
viewer, deliver_rational,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_FRAME_RATE_CHANGED) {
|
|
conns.append(QObject::connect(viewer,
|
|
&ViewerOutput::frame_rate_changed,
|
|
viewer, deliver_rational,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_SIZE_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::size_changed, viewer,
|
|
[fn, userdata, viewer](int width, int height) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_VIEWER_SIZE_CHANGED,
|
|
viewer, width, height, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED) {
|
|
conns.append(QObject::connect(viewer,
|
|
&ViewerOutput::pixel_aspect_changed,
|
|
viewer, deliver_rational,
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_INTERLACING_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::interlacing_changed, viewer,
|
|
[fn, userdata, viewer](VideoParams::Interlacing mode) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_INTERLACING_CHANGED, viewer,
|
|
int64_t(mode), 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_VIDEO_PARAMS_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::video_params_changed, viewer,
|
|
[fn, userdata, viewer]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_VIDEO_PARAMS_CHANGED, viewer,
|
|
0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_AUDIO_PARAMS_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::audio_params_changed, viewer,
|
|
[fn, userdata, viewer]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_AUDIO_PARAMS_CHANGED, viewer,
|
|
0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_TEXTURE_INPUT_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::texture_input_changed, viewer,
|
|
[fn, userdata, viewer]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_TEXTURE_INPUT_CHANGED, viewer,
|
|
0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED) {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::sample_rate_changed, viewer,
|
|
[fn, userdata, viewer](int sr) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_SAMPLE_RATE_CHANGED, viewer,
|
|
sr, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
viewer, &ViewerOutput::connected_waveform_changed, viewer,
|
|
[fn, userdata, viewer]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED,
|
|
viewer, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_TASK_MANAGER_TASK_ADDED:
|
|
case OAKENGINE_EVENT_TASK_MANAGER_TASK_REMOVED:
|
|
case OAKENGINE_EVENT_TASK_MANAGER_TASK_FAILED:
|
|
case OAKENGINE_EVENT_TASK_MANAGER_LIST_CHANGED: {
|
|
auto *manager = dynamic_cast<TaskManager *>(obj);
|
|
if (!manager) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_TASK_MANAGER_TASK_ADDED) {
|
|
conns.append(QObject::connect(
|
|
manager, &TaskManager::task_added, manager,
|
|
[fn, userdata, manager](Task *t) {
|
|
const QByteArray title = t->get_title().toUtf8();
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_TASK_MANAGER_TASK_ADDED, manager, 0, 0,
|
|
t, 0, title.constData());
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_TASK_MANAGER_TASK_REMOVED) {
|
|
conns.append(QObject::connect(
|
|
manager, &TaskManager::task_removed, manager,
|
|
[fn, userdata, manager](Task *t) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_TASK_MANAGER_TASK_REMOVED, manager, 0,
|
|
0, t);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_TASK_MANAGER_TASK_FAILED) {
|
|
conns.append(QObject::connect(
|
|
manager, &TaskManager::task_failed, manager,
|
|
[fn, userdata, manager](Task *t) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_TASK_MANAGER_TASK_FAILED, manager, 0,
|
|
0, t);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
manager, &TaskManager::task_list_changed, manager,
|
|
[fn, userdata, manager]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_TASK_MANAGER_LIST_CHANGED, manager, 0,
|
|
0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_TASK_STARTED:
|
|
case OAKENGINE_EVENT_TASK_PROGRESS:
|
|
case OAKENGINE_EVENT_TASK_FINISHED: {
|
|
auto *task = dynamic_cast<Task *>(obj);
|
|
if (!task) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_TASK_STARTED) {
|
|
conns.append(QObject::connect(
|
|
task, &Task::started, task,
|
|
[fn, userdata, task](qint64 start_time) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TASK_STARTED, task,
|
|
start_time, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else if (event_id == OAKENGINE_EVENT_TASK_PROGRESS) {
|
|
conns.append(QObject::connect(
|
|
task, &Task::progress_changed, task,
|
|
[fn, userdata, task](double d) {
|
|
int64_t bits;
|
|
static_assert(sizeof(bits) == sizeof(d));
|
|
memcpy(&bits, &d, sizeof(bits));
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TASK_PROGRESS, task,
|
|
bits, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
task, &Task::finished, task,
|
|
[fn, userdata](Task *t, bool succeeded) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_TASK_FINISHED, t,
|
|
succeeded ? 1 : 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_UNDO_INDEX_CHANGED: {
|
|
auto *undo_stack = dynamic_cast<UndoStack *>(obj);
|
|
if (!undo_stack) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
undo_stack, &UndoStack::index_changed, undo_stack,
|
|
[fn, userdata, undo_stack](int i) {
|
|
invoke(fn, userdata, OAKENGINE_EVENT_UNDO_INDEX_CHANGED,
|
|
undo_stack, i, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_AUDIO_MANAGER_OUTPUT_PARAMS_CHANGED: {
|
|
auto *audio_manager = dynamic_cast<AudioManager *>(obj);
|
|
if (!audio_manager) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
audio_manager, &AudioManager::output_params_changed, audio_manager,
|
|
[fn, userdata, audio_manager]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_AUDIO_MANAGER_OUTPUT_PARAMS_CHANGED,
|
|
audio_manager, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_AUDIO_MANAGER_OUTPUT_NOTIFY: {
|
|
auto *audio_manager = dynamic_cast<AudioManager *>(obj);
|
|
if (!audio_manager) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
audio_manager, &AudioManager::output_notify, audio_manager,
|
|
[fn, userdata, audio_manager]() {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_AUDIO_MANAGER_OUTPUT_NOTIFY,
|
|
audio_manager, 0, 0, nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED:
|
|
case OAKENGINE_EVENT_PLAYBACK_CACHE_VALIDATED: {
|
|
auto *cache = dynamic_cast<PlaybackCache *>(obj);
|
|
if (!cache) {
|
|
break;
|
|
}
|
|
if (event_id == OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED) {
|
|
conns.append(QObject::connect(
|
|
cache, &PlaybackCache::invalidated, cache,
|
|
[fn, userdata, cache](const TimeRange &r) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_PLAYBACK_CACHE_INVALIDATED,
|
|
cache, r.in().numerator(), r.in().denominator(),
|
|
nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
} else {
|
|
conns.append(QObject::connect(
|
|
cache, &PlaybackCache::validated, cache,
|
|
[fn, userdata, cache](const TimeRange &r) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_PLAYBACK_CACHE_VALIDATED,
|
|
cache, r.in().numerator(), r.in().denominator(),
|
|
nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
}
|
|
break;
|
|
}
|
|
case OAKENGINE_EVENT_FRAME_CACHE_INVALIDATED: {
|
|
auto *cache = dynamic_cast<FrameHashCache *>(obj);
|
|
if (!cache) {
|
|
break;
|
|
}
|
|
conns.append(QObject::connect(
|
|
cache, &PlaybackCache::invalidated, cache,
|
|
[fn, userdata, cache](const TimeRange &r) {
|
|
invoke(fn, userdata,
|
|
OAKENGINE_EVENT_FRAME_CACHE_INVALIDATED,
|
|
cache, r.in().numerator(), r.in().denominator(),
|
|
nullptr);
|
|
},
|
|
Qt::DirectConnection));
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (conns.isEmpty()) {
|
|
if (auto *node = dynamic_cast<Node *>(obj)) {
|
|
connect_node_event(node, event_id, fn, userdata, &conns);
|
|
}
|
|
}
|
|
|
|
return conns;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" int64_t oakengine_event_subscribe(void *handle, int32_t event_id,
|
|
oakengine_event_fn fn,
|
|
void *userdata)
|
|
{
|
|
if (!handle || !fn) {
|
|
return 0;
|
|
}
|
|
|
|
// Every facade handle is the engine QObject pointer itself (see
|
|
// timeline.cpp/project.cpp wrap()); dynamic_cast from QObject* both
|
|
// validates the family match and is safe across the Node/Project split.
|
|
auto *obj = reinterpret_cast<QObject *>(handle);
|
|
|
|
QVector<QMetaObject::Connection> conns =
|
|
connect_event(obj, event_id, fn, userdata);
|
|
if (conns.isEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
// Drop the registry entry automatically when the observed object dies so
|
|
// a stale subscription id is never a dangling engine pointer. Qt removes
|
|
// the signal connections itself; only the map entry needs cleanup.
|
|
const int64_t id = g_next_id.fetch_add(1);
|
|
conns.append(QObject::connect(obj, &QObject::destroyed, obj,
|
|
[id]() { drop_subscription(id); },
|
|
Qt::DirectConnection));
|
|
|
|
QMutexLocker locker(&g_registry_mutex);
|
|
g_registry.insert(id, Subscription{std::move(conns)});
|
|
return id;
|
|
}
|
|
|
|
extern "C" int oakengine_event_unsubscribe(int64_t id)
|
|
{
|
|
if (id <= 0) {
|
|
return OAKENGINE_E_INVALID;
|
|
}
|
|
QMutexLocker locker(&g_registry_mutex);
|
|
const auto it = g_registry.find(id);
|
|
if (it == g_registry.end()) {
|
|
return OAKENGINE_E_NOT_FOUND;
|
|
}
|
|
for (const QMetaObject::Connection &conn : it->connections) {
|
|
QObject::disconnect(conn);
|
|
}
|
|
g_registry.erase(it);
|
|
return OAKENGINE_OK;
|
|
}
|