refactor(timeline): de-Qt oaktimeline and wrap it in a pure C ABI

- de-Qt all 11 sources: markers/workarea lose QObject/signals (list
  re-sort via direct resort() call, drawing moves to the app layer),
  explicit unique_ptr ownership for marker lists and viewer's
  workarea/markers
- timeline undo command families (split/pointer/ripple/general/track)
  now hold oaknode C handles and do all graph work through the oaknode
  C ABI (no adapter layer); gaps are attached to the project graph
  before insertion, orphan handles tracked and freed
- fix the de-Qt severed Block->Track length-changed chain with a
  direct block_length_changed() call (positions were stale after
  split/trim)
- expand oaknode C API by ~20 functions needed by timeline
  (copy_in_graph, block kind/casts, connect_element, input arrays,
  tracklist family, marker/workarea borrowed outlets)
- pure C ABI in include/timeline (marker/workarea/edit, command
  factories returning OakUndoCommand), oakcommon XML get_native
  accessors
- oaknode<->oaktimeline resolve each other at runtime; standalone
  drivers link both into test binaries
- tests: oaktimeline 117, regressions oakcommon 193 / oaknode 96 /
  oakrender 42 / oakcodec 18 / oakaudio 36 all green
This commit is contained in:
2026-08-07 04:37:24 +08:00
parent 354df2e194
commit 66831a870e
59 changed files with 7459 additions and 98 deletions
+54
View File
@@ -475,3 +475,57 @@ int oaknode_transition_get_connected_in_block(OakNodeBlock *transition,
*out = wrap(t->connected_in_block());
return OAKNODE_OK;
}
int oaknode_clip_add_cache_passthrough_from(OakNodeBlock *clip,
OakNodeBlock *other)
{
if (!clip || !other) {
return OAKNODE_E_INVALID;
}
olive::ClipBlock *c = clip_impl(clip);
olive::ClipBlock *o = clip_impl(other);
if (!c || !o) {
return OAKNODE_E_INVALID;
}
try {
c->add_cache_passthrough_from(o);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakNodeNode *oaknode_block_as_node(OakNodeBlock *block)
{
return reinterpret_cast<OakNodeNode *>(block);
}
OakNodeBlock *oaknode_block_from_node(OakNodeNode *node)
{
if (!node) {
return NULL;
}
olive::Node *n = reinterpret_cast<olive::Node *>(node);
return wrap(dynamic_cast<olive::Block *>(n));
}
int oaknode_block_get_kind(OakNodeBlock *block, int *out_kind)
{
if (!block || !out_kind) {
return OAKNODE_E_INVALID;
}
olive::Block *b = impl(block);
if (dynamic_cast<olive::TransitionBlock *>(b)) {
*out_kind = OAKNODE_BLOCK_TRANSITION;
} else if (dynamic_cast<olive::ClipBlock *>(b)) {
*out_kind = OAKNODE_BLOCK_CLIP;
} else if (dynamic_cast<olive::GapBlock *>(b)) {
*out_kind = OAKNODE_BLOCK_GAP;
} else {
*out_kind = OAKNODE_BLOCK_OTHER;
}
return OAKNODE_OK;
}
+202
View File
@@ -25,6 +25,10 @@
#include "nodeundo.h"
#include "project.h"
#include "output/viewer/viewer.h"
#include "valueconvert.h"
namespace
@@ -1028,3 +1032,201 @@ void oaknode_node_free(OakNodeNode *node)
} catch (...) {
}
}
OakNodeNode *oaknode_node_copy_in_graph(OakNodeNode *node,
OakUndoCommand **out_command)
{
if (!node || !out_command) {
return NULL;
}
try {
auto *command = new olive::MultiUndoCommand();
olive::Node *copy =
olive::Node::copy_node_in_graph(to_node(node), command);
if (!copy) {
delete command;
return NULL;
}
*out_command = oaknode_c_api::wrap_command(command);
if (!*out_command) {
delete command;
return NULL;
}
oaknode_c_api::alive_inc();
return from_node(copy);
} catch (...) {
return NULL;
}
}
OakUndoCommand *oaknode_command_create_remove_node(OakNodeNode *node)
{
if (!node) {
return NULL;
}
try {
return oaknode_c_api::wrap_command(
new olive::NodeRemoveWithExclusiveDependenciesAndDisconnect(
to_node(node)));
} catch (...) {
return NULL;
}
}
int oaknode_node_get_project(const OakNodeNode *node, OakNodeProject **out)
{
if (!node || !out) {
return OAKNODE_E_INVALID;
}
*out = reinterpret_cast<OakNodeProject *>(to_node(node)->project());
return OAKNODE_OK;
}
int oaknode_node_input_array_insert(OakNodeNode *node, const char *input_id,
int index)
{
if (!node || !input_id) {
return OAKNODE_E_INVALID;
}
if (!has_input(to_node(node), input_id)) {
return OAKNODE_E_NOT_FOUND;
}
try {
to_node(node)->input_array_insert(input_id, index);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_node_input_array_remove(OakNodeNode *node, const char *input_id,
int index)
{
if (!node || !input_id) {
return OAKNODE_E_INVALID;
}
if (!has_input(to_node(node), input_id)) {
return OAKNODE_E_NOT_FOUND;
}
try {
to_node(node)->input_array_remove(input_id, index);
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_node_connect_element(OakNodeNode *output_node,
OakNodeNode *input_node,
const char *input_id, int element)
{
if (!output_node || !input_node) {
return OAKNODE_E_INVALID;
}
if (!has_input(to_node(input_node), input_id)) {
return OAKNODE_E_NOT_FOUND;
}
try {
olive::Node::connect_edge(
to_node(output_node),
olive::NodeInput(to_node(input_node), input_id, element));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_node_disconnect_element(OakNodeNode *input_node,
const char *input_id, int element)
{
if (!input_node) {
return OAKNODE_E_INVALID;
}
if (!has_input(to_node(input_node), input_id)) {
return OAKNODE_E_NOT_FOUND;
}
try {
olive::Node *output = to_node(input_node)->get_connected_output(
input_id, element);
if (!output) {
return OAKNODE_E_NOT_FOUND;
}
olive::Node::disconnect_edge(
output, olive::NodeInput(to_node(input_node), input_id, element));
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
OakUndoCommand *oaknode_command_create_add_node(OakNodeProject *graph,
OakNodeNode *node)
{
if (!graph || !node) {
return NULL;
}
try {
return oaknode_c_api::wrap_command(
new olive::NodeAddCommand(
reinterpret_cast<olive::Project *>(graph), to_node(node)));
} catch (...) {
return NULL;
}
}
OakUndoCommand *oaknode_command_create_set_position_recursive(
OakNodeNode *node, OakNodeNode *context, double x, double y)
{
if (!node || !context) {
return NULL;
}
try {
return oaknode_c_api::wrap_command(
new olive::NodeSetPositionAndDependenciesRecursivelyCommand(
to_node(node), to_node(context),
olive::Node::Position(olive::PointF(x, y))));
} catch (...) {
return NULL;
}
}
int oaknode_node_get_markers(const OakNodeNode *node,
OakNodeMarkerList **out)
{
if (!node || !out) {
return OAKNODE_E_INVALID;
}
const olive::Node *n = to_node(node);
if (auto *v = dynamic_cast<const olive::ViewerOutput *>(n)) {
*out = reinterpret_cast<OakNodeMarkerList *>(v->get_markers());
} else {
*out = NULL;
}
return OAKNODE_OK;
}
int oaknode_node_get_work_area(const OakNodeNode *node,
OakNodeWorkArea **out)
{
if (!node || !out) {
return OAKNODE_E_INVALID;
}
const olive::Node *n = to_node(node);
if (auto *v = dynamic_cast<const olive::ViewerOutput *>(n)) {
*out = reinterpret_cast<OakNodeWorkArea *>(v->get_work_area());
} else {
*out = NULL;
}
return OAKNODE_OK;
}
+5
View File
@@ -318,3 +318,8 @@ int oaknode_sequence_set_audio_params(OakNodeSequence *sequence, int index,
}
return OAKNODE_OK;
}
OakNodeNode *oaknode_sequence_as_node(OakNodeSequence *sequence)
{
return reinterpret_cast<OakNodeNode *>(sequence);
}
+88
View File
@@ -24,6 +24,8 @@
#include "alivecount.h"
#include "valueconvert.h"
#include "block/block.h"
#include "output/track/track.h"
#include "output/track/tracklist.h"
@@ -572,3 +574,89 @@ int oaknode_tracklist_remove_track(OakNodeTrackList *list, OakNodeTrack *track)
}
return OAKNODE_OK;
}
int oaknode_track_get_nearest_block_before_or_at(OakNodeTrack *track,
int numerator, int denominator, OakNodeBlock **out)
{
olive::Track *t = impl(track);
if (!t || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap_block(
t->nearest_block_before_or_at(olive::core::Rational(numerator, denominator)));
return OAKNODE_OK;
}
int oaknode_track_get_nearest_block_after_or_at(OakNodeTrack *track,
int numerator, int denominator, OakNodeBlock **out)
{
olive::Track *t = impl(track);
if (!t || !out) {
return OAKNODE_E_INVALID;
}
*out = wrap_block(
t->nearest_block_after_or_at(olive::core::Rational(numerator, denominator)));
return OAKNODE_OK;
}
int oaknode_tracklist_get_sequence(OakNodeTrackList *list,
OakNodeSequence **out)
{
if (!list || !out) {
return OAKNODE_E_INVALID;
}
*out = reinterpret_cast<OakNodeSequence *>(list_impl(list)->parent());
return OAKNODE_OK;
}
int oaknode_tracklist_get_track_input_id(OakNodeTrackList *list, char *buf,
int buf_size)
{
if (!list) {
return OAKNODE_E_INVALID;
}
return oaknode_c_api::copy_string(list_impl(list)->track_input(), buf,
buf_size);
}
int oaknode_tracklist_array_append(OakNodeTrackList *list)
{
if (!list) {
return OAKNODE_E_INVALID;
}
try {
list_impl(list)->array_append();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_tracklist_array_remove_last(OakNodeTrackList *list)
{
if (!list) {
return OAKNODE_E_INVALID;
}
try {
list_impl(list)->array_remove_last();
return OAKNODE_OK;
} catch (...) {
return OAKNODE_E_FAILED;
}
}
int oaknode_tracklist_get_array_index_from_cache_index(
OakNodeTrackList *list, int cache_index, int *out_index)
{
if (!list || !out_index) {
return OAKNODE_E_INVALID;
}
*out_index =
list_impl(list)->get_array_index_from_cache_index(cache_index);
return OAKNODE_OK;
}
OakNodeNode *oaknode_track_as_node(OakNodeTrack *track)
{
return reinterpret_cast<OakNodeNode *>(track);
}