refactor(node,timeline): route oaknode->oaktimeline calls through the C ABI

- ViewerOutput owns timeline markers/work area via refcounted
  OakTimeline* value handles (oaktimeline_*_create) instead of C++
  objects
- oaktimeline C API: add marker_list_create/marker_add/
  workarea_create/workarea_set_enabled; handle boxes gain an optional
  deleter for owning handles
- oaknode_node_get_markers/get_work_area return addref'd OakTimeline*
  handles via out params (named-struct forward declarations avoid the
  public header cycle); drop OakNodeMarkerList/OakNodeWorkArea
- serializers 210528-230220 load/save markers and work areas through
  the oaktimeline C API; SerializedMarker POD replaces
  std::vector<TimelineMarker*> in the serializer interface
- Sequence::add_default_nodes uses oaktimeline_add_track_command
- clip.cpp display-mode enums come from the new neutral
  include/timeline/displaymode.h; delete src/node/transition/timeline
- oakcommon xml: add oakcommon_xml_reader/writer_wrap_native borrowed
  wrappers (C++ adapter use)
- liboaknode now has zero C++ symbol references into liboaktimeline

Tests: new owning-handle and wrap_native cases; all standalone trees
green (common 196, codec 22, audio 40, task 110, render 49,
timeline 123, node 96, plugin 100).
This commit is contained in:
2026-08-07 22:53:44 +08:00
parent fd2111d560
commit 81431d180d
33 changed files with 669 additions and 207 deletions
+35
View File
@@ -317,3 +317,38 @@ ColorManager)去Qt化过程中的删除与语义变化,迁移调用方时需
src/codec/src/ffmpeg/CMakeLists.txt 的回归:FFmpegDecoder 构造
函数在 flat namespace 悬空,decoder probe/decode/encoder
roundtrip 三个测试 jump-to-0。
## oaknode→oaktimeline 切到 C ABI(2026-08-07)
- ViewerOutput 不再持有 TimelineWorkArea/TimelineMarkerList 的 C++
对象,改持 OakTimelineWorkArea/OakTimelineMarkerList 拥有型值句柄
(oaktimeline_workarea_create/marker_list_create,析构时 free);
workarea_handle()/markers_handle() 返回借用副本(调用方不得
free)。
- oaktimeline C API 新增:marker_list_create/marker_add/
workarea_create/workarea_set_enabled;句柄 box 增加可选 deleter
区分借用/拥有(timelinehandle.h)。
- oaknode_node_get_markers/get_work_area 改为经 out 参数返回
addref 后的 OakTimeline* 句柄(include/node/node.h 只做具名
struct 前置声明,公开 C 头互相 include 构成环,前置声明规避);
timeline/marker.h、timeline/workarea.h 的 typedef 改为具名
struct。OakNodeMarkerList/OakNodeWorkArea 不透明指针类型删除。
- 4 个旧项目序列化器(210528/210907/211228/220403)的
load_work_area/load_marker_list 改走 oaktimeline C API;
ProjectSerializer::SerializedMarker POD 取代
std::vector<TimelineMarker*>(LoadData::markers 与
only_serialize_markers_),serializer230220 的 marker 读写直接
展开为 XML 属性读写。**行为变化**:app 层 paste/markers 接口
签名改变(app 尚未拆分,编译点留待 facade 阶段)。
- Sequence::add_default_nodes 的 TimelineAddTrackCommand 改为
oaktimeline_add_track_command + oakundo_capi::to_command/
mark_container_owned 转入 MultiUndoCommand。
- clip.cpp 的 Timeline::ThumbnailMode/WaveformMode 枚举改自新中立
头 include/timeline/displaymode.h(OAK_TIMELINE_THUMBNAIL_*/
OAK_TIMELINE_WAVEFORMS_*,值与 olive::Timeline 枚举保持兼容)。
- tracklist.h/block.h 里 timelinecommon.h 的死引用删除;
src/node/transition/timeline/ 四个 stub 头删除。
- oakcommon xml C API 新增 oakcommon_xml_reader_wrap_native/
oakcommon_xml_writer_wrap_native(C++ only 借用包装),
XmlReaderState/XmlWriterState 支持 owning/borrowed 双模式。
- liboaknode 对 liboaktimeline 的 C++ 符号引用降为 0(nm 验证)。
+10
View File
@@ -79,6 +79,16 @@ extern "C" {
olive::XmlStreamReader *oakcommon_xml_reader_get_native(OakXmlReader reader);
olive::XmlStreamWriter *oakcommon_xml_writer_get_native(OakXmlWriter writer);
/**
* @brief Wrap an existing C++ reader/writer in a borrowed handle (C++
* only, for adapter layers). The box never owns the object; the caller
* must keep it alive and release the box with
* oakcommon_xml_reader_free()/oakcommon_xml_writer_free(). Empty handle
* for a NULL object or on allocation failure.
*/
OakXmlReader oakcommon_xml_reader_wrap_native(olive::XmlStreamReader *reader);
OakXmlWriter oakcommon_xml_writer_wrap_native(olive::XmlStreamWriter *writer);
/**
* @brief Release one reference to a reader.
*
+15 -9
View File
@@ -121,12 +121,13 @@ typedef struct OakNodeProject OakNodeProject;
typedef struct OakNodeFootage OakNodeFootage;
/**
* @brief Opaque borrowed handles to the timeline data owned by viewer
* nodes (TimelineMarkerList / TimelineWorkArea in oaktimeline).
* oaktimeline reinterprets these into its own handle types.
* @brief Timeline data owned by viewer nodes (TimelineMarkerList /
* TimelineWorkArea in oaktimeline) cross the boundary as oaktimeline
* value handles. Forward-declared here so node.h stays self-contained;
* include timeline/marker.h / timeline/workarea.h for the definitions.
*/
typedef struct OakNodeMarkerList OakNodeMarkerList;
typedef struct OakNodeWorkArea OakNodeWorkArea;
struct OakTimelineMarkerList;
struct OakTimelineWorkArea;
/**
* @brief Opaque borrowed handle to a node's video frame cache
@@ -562,11 +563,16 @@ OakUndoCommand oaknode_command_create_set_position_recursive(
OakNodeNode node, OakNodeNode context, double x, double y);
/**
* @brief Borrowed marker list / work area of a viewer node. *out is NULL
* when the node is not a viewer (or for an empty handle).
* @brief Marker list / work area of a viewer node, as addref'd
* oaktimeline value handles (release with
* oaktimeline_marker_list_free()/oaktimeline_workarea_free()). *out is
* an empty handle (ctx == NULL) when the node is not a viewer or for
* an empty node handle.
*/
int oaknode_node_get_markers(OakNodeNode node, OakNodeMarkerList **out);
int oaknode_node_get_work_area(OakNodeNode node, OakNodeWorkArea **out);
int oaknode_node_get_markers(OakNodeNode node,
struct OakTimelineMarkerList *out);
int oaknode_node_get_work_area(OakNodeNode node,
struct OakTimelineWorkArea *out);
/**
* @brief Borrowed video frame cache of a node (NULL when the node has
+51
View File
@@ -0,0 +1,51 @@
/***
Oak Video Editor - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAK_EDITOR_TIMELINE_DISPLAYMODE_H
#define OAK_EDITOR_TIMELINE_DISPLAYMODE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Shared timeline display-mode constants.
*
* Neutral home for the enum values behind the TimelineThumbnailMode /
* TimelineWaveformMode config keys; mirrors olive::Timeline::ThumbnailMode
* / WaveformMode (src/timeline/src/timelinecommon.h) and must stay
* value-compatible with them.
*/
enum OakTimelineThumbnailMode {
OAK_TIMELINE_THUMBNAIL_OFF = 0,
OAK_TIMELINE_THUMBNAIL_IN_OUT = 1,
OAK_TIMELINE_THUMBNAIL_ON = 2
};
enum OakTimelineWaveformMode {
OAK_TIMELINE_WAVEFORMS_DISABLED = 0,
OAK_TIMELINE_WAVEFORMS_ENABLED = 1
};
#ifdef __cplusplus
}
#endif
#endif // OAK_EDITOR_TIMELINE_DISPLAYMODE_H
+25 -9
View File
@@ -31,21 +31,28 @@ extern "C" {
#endif
/**
* @brief Borrowed by-value handle to a timeline marker list
* (olive::TimelineMarkerList), owned by a viewer node.
* @brief By-value handle to a timeline marker list
* (olive::TimelineMarkerList).
*
* Obtained via oaktimeline_marker_list_of(). The handle boxes a
* reference into the owning node: addref/release manage the box only,
* never the list. Release the box with oaktimeline_marker_list_free()
* (or handle.release(handle.ctx)) when done.
* Borrowed handles are obtained via oaktimeline_marker_list_of() and box
* a reference into the owning node; owning handles are created by
* oaktimeline_marker_list_create(). Either way, release with
* oaktimeline_marker_list_free() (or handle.release(handle.ctx)) when
* done — release destroys the list only for owning handles.
*/
typedef struct OakTimelineMarkerList {
void *ctx; /**< Opaque pointer to the borrowed object's box. */
void *ctx; /**< Opaque pointer to the object's box. */
void (*addref)(void *ctx); /**< Atomically increments the box count. */
void (*release)(void *ctx); /**< Decrements the count, frees the box. */
uint32_t abi_version; /**< OAKTIMELINE_ABI_VERSION. */
} OakTimelineMarkerList;
/**
* @brief Create an owning handle to a new, empty marker list. Empty
* handle (ctx == NULL) on allocation failure.
*/
OakTimelineMarkerList oaktimeline_marker_list_create(void);
/**
* @brief Borrowed marker list of a viewer node (sequence). Empty handle
* (ctx == NULL) for an empty node handle or when the node is not a
@@ -54,11 +61,20 @@ typedef struct OakTimelineMarkerList {
OakTimelineMarkerList oaktimeline_marker_list_of(OakNodeNode owner);
/**
* @brief Release a borrowed marker list box (never the list itself).
* NULL / empty-handle no-op; clears list->ctx after releasing.
* @brief Release a marker list handle (destroys the list itself only
* for owning handles). NULL / empty-handle no-op; clears
* list->ctx after releasing.
*/
void oaktimeline_marker_list_free(OakTimelineMarkerList *list);
/**
* @brief Append a marker directly (no undo command). name may be NULL
* for an empty name.
*/
int oaktimeline_marker_add(OakTimelineMarkerList list, int in_num,
int in_den, int out_num, int out_den,
const char *name, int color);
/**
* @brief Number of markers. Out-param convention; OAKTIMELINE_E_INVALID
* for empty/NULL arguments.
+22 -9
View File
@@ -31,21 +31,28 @@ extern "C" {
#endif
/**
* @brief Borrowed by-value handle to a timeline work area
* (olive::TimelineWorkArea), owned by a viewer node.
* @brief By-value handle to a timeline work area
* (olive::TimelineWorkArea).
*
* Obtained via oaktimeline_workarea_of(). The handle boxes a reference
* into the owning node: addref/release manage the box only, never the
* work area. Release the box with oaktimeline_workarea_free() (or
* handle.release(handle.ctx)) when done.
* Borrowed handles are obtained via oaktimeline_workarea_of() and box a
* reference into the owning node; owning handles are created by
* oaktimeline_workarea_create(). Either way, release with
* oaktimeline_workarea_free() (or handle.release(handle.ctx)) when
* done — release destroys the work area only for owning handles.
*/
typedef struct OakTimelineWorkArea {
void *ctx; /**< Opaque pointer to the borrowed object's box. */
void *ctx; /**< Opaque pointer to the object's box. */
void (*addref)(void *ctx); /**< Atomically increments the box count. */
void (*release)(void *ctx); /**< Decrements the count, frees the box. */
uint32_t abi_version; /**< OAKTIMELINE_ABI_VERSION. */
} OakTimelineWorkArea;
/**
* @brief Create an owning handle to a new, default-constructed work
* area. Empty handle (ctx == NULL) on allocation failure.
*/
OakTimelineWorkArea oaktimeline_workarea_create(void);
/**
* @brief Borrowed work area of a viewer node (sequence). Empty handle
* (ctx == NULL) for an empty node handle or when the node is not a
@@ -54,11 +61,17 @@ typedef struct OakTimelineWorkArea {
OakTimelineWorkArea oaktimeline_workarea_of(OakNodeNode owner);
/**
* @brief Release a borrowed work area box (never the work area itself).
* NULL / empty-handle no-op; clears w->ctx after releasing.
* @brief Release a work area handle (destroys the work area itself only
* for owning handles). NULL / empty-handle no-op; clears w->ctx
* after releasing.
*/
void oaktimeline_workarea_free(OakTimelineWorkArea *w);
/**
* @brief Set enabled directly (live).
*/
int oaktimeline_workarea_set_enabled(OakTimelineWorkArea w, int enabled);
/**
* @brief Read the work area state. Out params may individually be NULL.
*/
+75 -17
View File
@@ -21,6 +21,7 @@
#include "common/xmlutils.h"
#include <cstring>
#include <memory>
#include <new>
#include "../src/xmlutils.h"
@@ -31,14 +32,45 @@ namespace
/**
* @brief Reader state boxed behind the handle's ctx pointer.
*
* Owning states (oakcommon_xml_reader_init) hold the reader via
* `owned`; borrowed states (oakcommon_xml_reader_wrap_native) leave it
* null and never delete the reader.
*/
struct XmlReaderState {
olive::XmlStreamReader reader;
olive::XmlStreamReader *reader;
std::unique_ptr<olive::XmlStreamReader> owned;
std::string cached_text;
bool has_cached_text = false;
explicit XmlReaderState(const char *data)
: reader(data)
: reader(new olive::XmlStreamReader(data))
, owned(reader)
{
}
explicit XmlReaderState(olive::XmlStreamReader *borrowed)
: reader(borrowed)
{
}
};
/**
* @brief Writer state boxed behind the handle's ctx pointer (same
* owning/borrowed split as XmlReaderState).
*/
struct XmlWriterState {
olive::XmlStreamWriter *writer;
std::unique_ptr<olive::XmlStreamWriter> owned;
XmlWriterState()
: writer(new olive::XmlStreamWriter())
, owned(writer)
{
}
explicit XmlWriterState(olive::XmlStreamWriter *borrowed)
: writer(borrowed)
{
}
};
@@ -56,7 +88,8 @@ XmlReaderState *xr(OakXmlReader reader)
*/
olive::XmlStreamWriter *xw(OakXmlWriter writer)
{
return oakcommon::handle_impl<olive::XmlStreamWriter>(writer.ctx);
XmlWriterState *state = oakcommon::handle_impl<XmlWriterState>(writer.ctx);
return state ? state->writer : nullptr;
}
/**
@@ -84,8 +117,8 @@ OakXmlReader oakcommon_xml_reader_init(const char *data)
if (!data)
return h;
try {
return oakcommon::make_handle<OakXmlReader>(
XmlReaderState(data));
return oakcommon::make_handle_in_place<OakXmlReader, XmlReaderState>(
data);
} catch (...) {
OakXmlReader empty = {};
return empty;
@@ -104,7 +137,7 @@ int oakcommon_xml_reader_read_next_start_element(OakXmlReader reader,
return OAKCOMMON_E_INVALID;
try {
xr(reader)->has_cached_text = false;
*found = olive::xml_read_next_start_element(&xr(reader)->reader) ? 1 : 0;
*found = olive::xml_read_next_start_element(xr(reader)->reader) ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -117,7 +150,7 @@ int oakcommon_xml_reader_name(OakXmlReader reader, char *buf,
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
return copy_string(xr(reader)->reader.name(), buf, buf_size);
return copy_string(xr(reader)->reader->name(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
@@ -132,7 +165,7 @@ int oakcommon_xml_reader_read_element_text(OakXmlReader reader,
// read_element_text() consumes the stream, so cache the result to
// keep the two-stage (size query then copy) buffer convention working.
if (!xr(reader)->has_cached_text) {
xr(reader)->cached_text = xr(reader)->reader.read_element_text();
xr(reader)->cached_text = xr(reader)->reader->read_element_text();
xr(reader)->has_cached_text = true;
}
return copy_string(xr(reader)->cached_text, buf, buf_size);
@@ -147,7 +180,7 @@ int oakcommon_xml_reader_skip_current_element(OakXmlReader reader)
return OAKCOMMON_E_INVALID;
try {
xr(reader)->has_cached_text = false;
xr(reader)->reader.skip_current_element();
xr(reader)->reader->skip_current_element();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -160,7 +193,7 @@ int oakcommon_xml_reader_attribute_count(OakXmlReader reader,
if (!xr(reader) || !count)
return OAKCOMMON_E_INVALID;
try {
*count = static_cast<int>(xr(reader)->reader.attributes().size());
*count = static_cast<int>(xr(reader)->reader->attributes().size());
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -173,7 +206,7 @@ int oakcommon_xml_reader_attribute_name(OakXmlReader reader, int index,
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = xr(reader)->reader.attributes();
const auto &attrs = xr(reader)->reader->attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].name, buf, buf_size);
@@ -188,7 +221,7 @@ int oakcommon_xml_reader_attribute_value(OakXmlReader reader,
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = xr(reader)->reader.attributes();
const auto &attrs = xr(reader)->reader->attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].value, buf, buf_size);
@@ -203,7 +236,7 @@ int oakcommon_xml_reader_has_error(OakXmlReader reader,
if (!xr(reader) || !has_error)
return OAKCOMMON_E_INVALID;
try {
*has_error = xr(reader)->reader.has_error() ? 1 : 0;
*has_error = xr(reader)->reader->has_error() ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -213,8 +246,7 @@ int oakcommon_xml_reader_has_error(OakXmlReader reader,
OakXmlWriter oakcommon_xml_writer_init(void)
{
try {
return oakcommon::make_handle<OakXmlWriter>(
olive::XmlStreamWriter());
return oakcommon::make_handle_in_place<OakXmlWriter, XmlWriterState>();
} catch (...) {
OakXmlWriter h = {};
return h;
@@ -322,7 +354,7 @@ olive::XmlStreamReader *oakcommon_xml_reader_get_native(OakXmlReader reader)
if (!reader.ctx) {
return nullptr;
}
return &oakcommon::handle_impl<XmlReaderState>(reader.ctx)->reader;
return oakcommon::handle_impl<XmlReaderState>(reader.ctx)->reader;
}
olive::XmlStreamWriter *oakcommon_xml_writer_get_native(OakXmlWriter writer)
@@ -330,5 +362,31 @@ olive::XmlStreamWriter *oakcommon_xml_writer_get_native(OakXmlWriter writer)
if (!writer.ctx) {
return nullptr;
}
return oakcommon::handle_impl<olive::XmlStreamWriter>(writer.ctx);
return xw(writer);
}
OakXmlReader oakcommon_xml_reader_wrap_native(olive::XmlStreamReader *reader)
{
if (!reader) {
return OakXmlReader{};
}
try {
return oakcommon::make_handle_in_place<OakXmlReader, XmlReaderState>(
reader);
} catch (...) {
return OakXmlReader{};
}
}
OakXmlWriter oakcommon_xml_writer_wrap_native(olive::XmlStreamWriter *writer)
{
if (!writer) {
return OakXmlWriter{};
}
try {
return oakcommon::make_handle_in_place<OakXmlWriter, XmlWriterState>(
writer);
} catch (...) {
return OakXmlWriter{};
}
}
+51
View File
@@ -26,6 +26,8 @@
#include "common/xmlutils.h"
#include "../src/xmlutils.h"
namespace
{
@@ -326,3 +328,52 @@ TEST(CommonXmlUtilsCApi, WriterRoundTrip)
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, WrapNativeNull)
{
EXPECT_EQ(oakcommon_xml_reader_wrap_native(nullptr).ctx, nullptr);
EXPECT_EQ(oakcommon_xml_writer_wrap_native(nullptr).ctx, nullptr);
}
TEST(CommonXmlUtilsCApi, WrapNativeReaderRoundTrip)
{
olive::XmlStreamReader native("<root><child>text</child></root>");
OakXmlReader r = oakcommon_xml_reader_wrap_native(&native);
ASSERT_NE(r.ctx, nullptr);
// Borrowed box and native object must observe the same stream
EXPECT_EQ(oakcommon_xml_reader_get_native(r), &native);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
EXPECT_EQ(found, 1);
char name[32];
EXPECT_GT(oakcommon_xml_reader_name(r, name, sizeof(name)), 0);
EXPECT_STREQ(name, "root");
oakcommon_xml_reader_free(&r);
EXPECT_EQ(r.ctx, nullptr);
}
TEST(CommonXmlUtilsCApi, WrapNativeWriterRoundTrip)
{
olive::XmlStreamWriter native;
OakXmlWriter w = oakcommon_xml_writer_wrap_native(&native);
ASSERT_NE(w.ctx, nullptr);
EXPECT_EQ(oakcommon_xml_writer_get_native(w), &native);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, "root"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_xml_writer_write_end_element(w), OAKCOMMON_OK);
char out[64];
int needed = oakcommon_xml_writer_output(w, out, sizeof(out));
EXPECT_GT(needed, 0);
EXPECT_LT(needed, int(sizeof(out)));
EXPECT_NE(strstr(out, "root"), nullptr);
oakcommon_xml_writer_free(&w);
EXPECT_EQ(w.ctx, nullptr);
}
+16 -6
View File
@@ -24,6 +24,8 @@
#include "node/footage.h"
#include "node/project.h"
#include "olive/core/oakcore/audioparams.h"
#include "timeline/marker.h"
#include "timeline/workarea.h"
#include <atomic>
#include <string>
@@ -1279,7 +1281,8 @@ OakUndoCommand oaknode_command_create_set_position_recursive(
}
}
int oaknode_node_get_markers(OakNodeNode node, OakNodeMarkerList **out)
int oaknode_node_get_markers(OakNodeNode node,
struct OakTimelineMarkerList *out)
{
if (!node.ctx || !out) {
return OAKNODE_E_INVALID;
@@ -1287,14 +1290,18 @@ int oaknode_node_get_markers(OakNodeNode node, OakNodeMarkerList **out)
const olive::Node *n = oaknode_c_api::to_native<olive::Node>(node);
if (auto *v = dynamic_cast<const olive::ViewerOutput *>(n)) {
*out = reinterpret_cast<OakNodeMarkerList *>(v->get_markers());
*out = v->markers_handle();
if (out->ctx) {
out->addref(out->ctx);
}
} else {
*out = NULL;
*out = OakTimelineMarkerList{};
}
return OAKNODE_OK;
}
int oaknode_node_get_work_area(OakNodeNode node, OakNodeWorkArea **out)
int oaknode_node_get_work_area(OakNodeNode node,
struct OakTimelineWorkArea *out)
{
if (!node.ctx || !out) {
return OAKNODE_E_INVALID;
@@ -1302,9 +1309,12 @@ int oaknode_node_get_work_area(OakNodeNode node, OakNodeWorkArea **out)
const olive::Node *n = oaknode_c_api::to_native<olive::Node>(node);
if (auto *v = dynamic_cast<const olive::ViewerOutput *>(n)) {
*out = reinterpret_cast<OakNodeWorkArea *>(v->get_work_area());
*out = v->workarea_handle();
if (out->ctx) {
out->addref(out->ctx);
}
} else {
*out = NULL;
*out = OakTimelineWorkArea{};
}
return OAKNODE_OK;
}
+1 -1
View File
@@ -23,12 +23,12 @@
#define OAK_BLOCK_H
#include "node.h"
#include "timeline/timelinecommon.h"
namespace olive
{
class TransitionBlock;
class Track;
/**
* @brief A Node that represents a block of time, also displayable on a Timeline
+7 -8
View File
@@ -30,7 +30,7 @@
#include "output/viewer/viewer.h"
#include "project/sequence/sequence.h"
#include "sliderdisplaytype.h"
#include "sliderdisplaytype.h"
#include "timeline/displaymode.h"
namespace olive
{
@@ -297,7 +297,7 @@ void ClipBlock::request_range_from_connected(const TimeRange &range)
request_range_for_cache(
connected->waveform_cache(), max_range, range, true,
(OAK_CONFIG("TimelineWaveformMode").to_int() ==
Timeline::k_waveforms_enabled));
OAK_TIMELINE_WAVEFORMS_ENABLED));
// Handle audio cache
request_range_for_cache(connected->audio_playback_cache(),
@@ -336,7 +336,7 @@ void ClipBlock::request_invalidated_from_connected(bool force_all,
} else if (type == Track::k_audio) {
// Handle waveforms
if (OAK_CONFIG("TimelineWaveformMode").to_int() ==
Timeline::k_waveforms_enabled) {
OAK_TIMELINE_WAVEFORMS_ENABLED) {
request_invalidated_for_cache(connected->waveform_cache(),
max_range);
}
@@ -383,12 +383,11 @@ void ClipBlock::request_invalidated_for_cache(PlaybackCache *cache,
bool ClipBlock::get_adjusted_thumbnail_range(TimeRange *r) const
{
switch (static_cast<Timeline::ThumbnailMode>(
OAK_CONFIG("TimelineThumbnailMode").to_int())) {
case Timeline::k_thumbnail_off:
switch (OAK_CONFIG("TimelineThumbnailMode").to_int()) {
case OAK_TIMELINE_THUMBNAIL_OFF:
// Don't cache any range
return false;
case Timeline::k_thumbnail_in_out: {
case OAK_TIMELINE_THUMBNAIL_IN_OUT: {
// Only cache in point
Rational in = this->media_range().in();
if (r->contains(in)) {
@@ -400,7 +399,7 @@ bool ClipBlock::get_adjusted_thumbnail_range(TimeRange *r) const
return false;
}
}
case Timeline::k_thumbnail_on:
case OAK_TIMELINE_THUMBNAIL_ON:
// Cache entire range
return true;
}
-1
View File
@@ -27,7 +27,6 @@
#include <vector>
#include "output/track/track.h"
#include "timeline/timelinecommon.h"
namespace olive
{
+32 -12
View File
@@ -21,6 +21,7 @@
#include "viewer.h"
#include "common/xmlutils.h"
#include "configaccessor.h"
#include "coreengine.h"
#include "traverser.h"
@@ -77,8 +78,15 @@ ViewerOutput::ViewerOutput(bool create_buffer_inputs,
set_flag(k_dont_show_in_param_view);
workarea_ = std::make_unique<TimelineWorkArea>();
markers_ = std::make_unique<TimelineMarkerList>();
workarea_ = oaktimeline_workarea_create();
markers_ = oaktimeline_marker_list_create();
}
ViewerOutput::~ViewerOutput()
{
disconnect_all();
oaktimeline_workarea_free(&workarea_);
oaktimeline_marker_list_free(&markers_);
}
std::string ViewerOutput::name() const
@@ -452,32 +460,44 @@ void ViewerOutput::value(const NodeValueRow &value, const NodeGlobals &globals,
bool ViewerOutput::load_custom(XmlStreamReader *reader, SerializedData *data)
{
while (xml_read_next_start_element(reader)) {
OakXmlReader xml = oakcommon_xml_reader_wrap_native(reader);
if (!xml.ctx) {
return false;
}
bool ok = true;
while (ok && xml_read_next_start_element(reader)) {
if (reader->name() == "markers") {
if (!this->get_markers()->load(reader)) {
return false;
}
ok = oaktimeline_marker_list_load(markers_, xml) ==
OAKTIMELINE_OK;
} else if (reader->name() == "workarea") {
if (!this->get_work_area()->load(reader)) {
return false;
}
ok = oaktimeline_workarea_load(workarea_, xml) ==
OAKTIMELINE_OK;
} else {
reader->skip_current_element();
}
}
return true;
oakcommon_xml_reader_free(&xml);
return ok;
}
void ViewerOutput::save_custom(XmlStreamWriter *writer) const
{
OakXmlWriter xml = oakcommon_xml_writer_wrap_native(writer);
if (!xml.ctx) {
return;
}
writer->write_start_element("workarea");
this->get_work_area()->save(writer);
oaktimeline_workarea_save(workarea_, xml);
writer->write_end_element(); // workarea
writer->write_start_element("markers");
this->get_markers()->save(writer);
oaktimeline_marker_list_save(markers_, xml);
writer->write_end_element(); // markers
oakcommon_xml_writer_free(&xml);
}
void ViewerOutput::InputValueChangedEvent(const std::string &input, int element)
+15 -9
View File
@@ -31,8 +31,8 @@
#include "render/framehashcache.h"
#include "subtitleparams.h"
#include "videoparams.h"
#include "timeline/timelinemarker.h"
#include "timeline/timelineworkarea.h"
#include "timeline/marker.h"
#include "timeline/workarea.h"
namespace olive
{
@@ -48,8 +48,9 @@ class ViewerOutput : public Node {
public:
ViewerOutput(bool create_buffer_inputs = true,
bool create_default_streams = true);
virtual ~ViewerOutput() override;
NODE_DEFAULT_FUNCTIONS(ViewerOutput)
NODE_COPY_FUNCTION(ViewerOutput)
virtual std::string name() const override;
virtual std::string id() const override;
@@ -174,13 +175,18 @@ public:
return audio_length_;
}
TimelineWorkArea *get_work_area() const
/**
* @brief Borrowed copies of the timeline handles owned by this
* viewer. Callers must NOT free them; addref first to keep
* one beyond the viewer's lifetime.
*/
const OakTimelineWorkArea &workarea_handle() const
{
return workarea_.get();
return workarea_;
}
TimelineMarkerList *get_markers() const
const OakTimelineMarkerList &markers_handle() const
{
return markers_.get();
return markers_;
}
virtual TimeRange get_video_cache_range() const override
@@ -275,8 +281,8 @@ private:
AudioParams cached_audio_params_;
std::unique_ptr<TimelineWorkArea> workarea_;
std::unique_ptr<TimelineMarkerList> markers_;
OakTimelineWorkArea workarea_ = {};
OakTimelineMarkerList markers_ = {};
bool autocache_input_video_;
bool autocache_input_audio_;
+16 -9
View File
@@ -21,7 +21,10 @@
#include "sequence.h"
#include "timeline/timelineundogeneral.h"
#include "timeline/edit.h"
#include "undo/undocommand.h"
#include "../../../c_api/nodehandle.h"
#include "../../../../undo/c_api/commandhandle.h"
namespace olive
{
@@ -85,21 +88,25 @@ void Sequence::add_default_nodes(MultiUndoCommand *command)
{
// Create tracks and connect them to the viewer
// (borrowed handles: the track lists are owned by this sequence)
UndoCommand *video_track_command = new TimelineAddTrackCommand(
OakUndoCommand video_track_command = oaktimeline_add_track_command(
oaknode_c_api::make_handle<OakNodeTrackList>(
track_list(Track::k_video), false, nullptr));
UndoCommand *audio_track_command = new TimelineAddTrackCommand(
OakUndoCommand audio_track_command = oaktimeline_add_track_command(
oaknode_c_api::make_handle<OakNodeTrackList>(
track_list(Track::k_audio), false, nullptr));
if (command) {
command->add_child(video_track_command);
command->add_child(audio_track_command);
command->add_child(oakundo_capi::to_command(video_track_command));
oakundo_capi::mark_container_owned(video_track_command);
command->add_child(oakundo_capi::to_command(audio_track_command));
oakundo_capi::mark_container_owned(audio_track_command);
oakundo_command_free(&video_track_command);
oakundo_command_free(&audio_track_command);
} else {
video_track_command->redo_now();
audio_track_command->redo_now();
delete video_track_command;
delete audio_track_command;
oakundo_command_redo_now(video_track_command);
oakundo_command_redo_now(audio_track_command);
oakundo_command_free(&video_track_command);
oakundo_command_free(&audio_track_command);
}
}
+20 -4
View File
@@ -30,6 +30,8 @@
#include "define.h"
#include "project.h"
#include "project/serializer/serializedlayoutinfo.h"
#include "timeline/marker.h"
#include "timeline/workarea.h"
#include "typeserializer.h"
namespace olive
@@ -75,13 +77,27 @@ public:
using SerializedKeyframes =
std::map<std::string, std::vector<NodeKeyframe *>>;
/**
* @brief Plain-data snapshot of a timeline marker.
*
* Markers live in oaktimeline; serializers ferry them across the
* module boundary as plain data instead of C++ TimelineMarker
* pointers (01 §0 铁律 6).
*/
struct SerializedMarker {
core::Rational in;
core::Rational out;
std::string name;
int color = 0;
};
class LoadData {
public:
LoadData() = default;
SerializedProperties properties;
std::vector<TimelineMarker *> markers;
std::vector<SerializedMarker> markers;
SerializedKeyframes keyframes;
@@ -198,12 +214,12 @@ public:
void set_only_serialize_nodes_and_resolve_groups(
std::vector<Node *> only);
const std::vector<TimelineMarker *> &get_only_serialize_markers() const
const std::vector<SerializedMarker> &get_only_serialize_markers() const
{
return only_serialize_markers_;
}
void set_only_serialize_markers(
const std::vector<TimelineMarker *> &only)
const std::vector<SerializedMarker> &only)
{
only_serialize_markers_ = only;
}
@@ -240,7 +256,7 @@ public:
SerializedProperties properties_;
std::vector<TimelineMarker *> only_serialize_markers_;
std::vector<SerializedMarker> only_serialize_markers_;
std::vector<NodeKeyframe *> only_serialize_keyframes_;
};
@@ -784,9 +784,9 @@ void ProjectSerializer210528::load_timeline_points(XmlStreamReader *reader,
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "markers") {
load_marker_list(reader, points->get_markers());
load_marker_list(reader, points->markers_handle());
} else if (reader->name() == "workarea") {
load_work_area(reader, points->get_work_area());
load_work_area(reader, points->workarea_handle());
} else {
reader->skip_current_element();
}
@@ -794,14 +794,23 @@ void ProjectSerializer210528::load_timeline_points(XmlStreamReader *reader,
}
void ProjectSerializer210528::load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const
const OakTimelineWorkArea &workarea) const
{
Rational range_in = workarea->in();
Rational range_out = workarea->out();
int cur_in_num, cur_in_den, cur_out_num, cur_out_den;
if (oaktimeline_workarea_get(workarea, &cur_in_num, &cur_in_den,
&cur_out_num, &cur_out_den,
NULL) != OAKTIMELINE_OK) {
reader->skip_current_element();
return;
}
Rational range_in(cur_in_num, cur_in_den);
Rational range_out(cur_out_num, cur_out_den);
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "enabled") {
workarea->set_enabled(attr.value != "0");
oaktimeline_workarea_set_enabled(workarea,
attr.value != "0");
} else if (attr.name == "in") {
range_in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
@@ -809,17 +818,18 @@ void ProjectSerializer210528::load_work_area(XmlStreamReader *reader,
}
}
TimeRange loaded_workarea(range_in, range_out);
if (loaded_workarea != workarea->range()) {
workarea->set_range(loaded_workarea);
if (range_in != Rational(cur_in_num, cur_in_den) ||
range_out != Rational(cur_out_num, cur_out_den)) {
oaktimeline_workarea_set_range(workarea, range_in.numerator(),
range_in.denominator(),
range_out.numerator(),
range_out.denominator());
}
reader->skip_current_element();
}
void ProjectSerializer210528::load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const
const OakTimelineMarkerList &markers) const
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
@@ -838,14 +848,15 @@ void ProjectSerializer210528::load_marker_list(XmlStreamReader *reader,
// MarkerColor resolves through configaccessor.h
// (oakcommon_config_* C ABI)
markers->add_marker(std::make_unique<TimelineMarker>(
OAK_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name));
oaktimeline_marker_add(markers, in.numerator(), in.denominator(),
out.numerator(), out.denominator(),
name.c_str(),
OAK_CONFIG("MarkerColor").toInt());
}
reader->skip_current_element();
}
}
void ProjectSerializer210528::load_value_hint(Node::ValueHint *hint,
XmlStreamReader *reader) const
{
@@ -93,10 +93,10 @@ private:
ViewerOutput *points) const;
void load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const;
const OakTimelineWorkArea &workarea) const;
void load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const;
const OakTimelineMarkerList &markers) const;
void load_value_hint(Node::ValueHint *hint, XmlStreamReader *reader) const;
};
@@ -773,9 +773,9 @@ void ProjectSerializer210907::load_timeline_points(XmlStreamReader *reader,
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "markers") {
load_marker_list(reader, points->get_markers());
load_marker_list(reader, points->markers_handle());
} else if (reader->name() == "workarea") {
load_work_area(reader, points->get_work_area());
load_work_area(reader, points->workarea_handle());
} else {
reader->skip_current_element();
}
@@ -783,14 +783,23 @@ void ProjectSerializer210907::load_timeline_points(XmlStreamReader *reader,
}
void ProjectSerializer210907::load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const
const OakTimelineWorkArea &workarea) const
{
Rational range_in = workarea->in();
Rational range_out = workarea->out();
int cur_in_num, cur_in_den, cur_out_num, cur_out_den;
if (oaktimeline_workarea_get(workarea, &cur_in_num, &cur_in_den,
&cur_out_num, &cur_out_den,
NULL) != OAKTIMELINE_OK) {
reader->skip_current_element();
return;
}
Rational range_in(cur_in_num, cur_in_den);
Rational range_out(cur_out_num, cur_out_den);
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "enabled") {
workarea->set_enabled(attr.value != "0");
oaktimeline_workarea_set_enabled(workarea,
attr.value != "0");
} else if (attr.name == "in") {
range_in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
@@ -798,17 +807,18 @@ void ProjectSerializer210907::load_work_area(XmlStreamReader *reader,
}
}
TimeRange loaded_workarea(range_in, range_out);
if (loaded_workarea != workarea->range()) {
workarea->set_range(loaded_workarea);
if (range_in != Rational(cur_in_num, cur_in_den) ||
range_out != Rational(cur_out_num, cur_out_den)) {
oaktimeline_workarea_set_range(workarea, range_in.numerator(),
range_in.denominator(),
range_out.numerator(),
range_out.denominator());
}
reader->skip_current_element();
}
void ProjectSerializer210907::load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const
const OakTimelineMarkerList &markers) const
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
@@ -827,14 +837,15 @@ void ProjectSerializer210907::load_marker_list(XmlStreamReader *reader,
// MarkerColor resolves through configaccessor.h
// (oakcommon_config_* C ABI)
markers->add_marker(std::make_unique<TimelineMarker>(
OAK_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name));
oaktimeline_marker_add(markers, in.numerator(), in.denominator(),
out.numerator(), out.denominator(),
name.c_str(),
OAK_CONFIG("MarkerColor").toInt());
}
reader->skip_current_element();
}
}
void ProjectSerializer210907::load_value_hint(Node::ValueHint *hint,
XmlStreamReader *reader) const
{
@@ -92,10 +92,10 @@ private:
ViewerOutput *points) const;
void load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const;
const OakTimelineWorkArea &workarea) const;
void load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const;
const OakTimelineMarkerList &markers) const;
void load_value_hint(Node::ValueHint *hint, XmlStreamReader *reader) const;
};
@@ -827,9 +827,9 @@ void ProjectSerializer211228::load_timeline_points(XmlStreamReader *reader,
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "markers") {
load_marker_list(reader, points->get_markers());
load_marker_list(reader, points->markers_handle());
} else if (reader->name() == "workarea") {
load_work_area(reader, points->get_work_area());
load_work_area(reader, points->workarea_handle());
} else {
reader->skip_current_element();
}
@@ -837,14 +837,23 @@ void ProjectSerializer211228::load_timeline_points(XmlStreamReader *reader,
}
void ProjectSerializer211228::load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const
const OakTimelineWorkArea &workarea) const
{
Rational range_in = workarea->in();
Rational range_out = workarea->out();
int cur_in_num, cur_in_den, cur_out_num, cur_out_den;
if (oaktimeline_workarea_get(workarea, &cur_in_num, &cur_in_den,
&cur_out_num, &cur_out_den,
NULL) != OAKTIMELINE_OK) {
reader->skip_current_element();
return;
}
Rational range_in(cur_in_num, cur_in_den);
Rational range_out(cur_out_num, cur_out_den);
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "enabled") {
workarea->set_enabled(attr.value != "0");
oaktimeline_workarea_set_enabled(workarea,
attr.value != "0");
} else if (attr.name == "in") {
range_in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
@@ -852,17 +861,18 @@ void ProjectSerializer211228::load_work_area(XmlStreamReader *reader,
}
}
TimeRange loaded_workarea(range_in, range_out);
if (loaded_workarea != workarea->range()) {
workarea->set_range(loaded_workarea);
if (range_in != Rational(cur_in_num, cur_in_den) ||
range_out != Rational(cur_out_num, cur_out_den)) {
oaktimeline_workarea_set_range(workarea, range_in.numerator(),
range_in.denominator(),
range_out.numerator(),
range_out.denominator());
}
reader->skip_current_element();
}
void ProjectSerializer211228::load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const
const OakTimelineMarkerList &markers) const
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
@@ -881,14 +891,15 @@ void ProjectSerializer211228::load_marker_list(XmlStreamReader *reader,
// MarkerColor resolves through configaccessor.h
// (oakcommon_config_* C ABI)
markers->add_marker(std::make_unique<TimelineMarker>(
OAK_CONFIG("MarkerColor").toInt(), TimeRange(in, out), name));
oaktimeline_marker_add(markers, in.numerator(), in.denominator(),
out.numerator(), out.denominator(),
name.c_str(),
OAK_CONFIG("MarkerColor").toInt());
}
reader->skip_current_element();
}
}
void ProjectSerializer211228::load_value_hint(Node::ValueHint *hint,
XmlStreamReader *reader) const
{
@@ -93,10 +93,10 @@ private:
ViewerOutput *points) const;
void load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const;
const OakTimelineWorkArea &workarea) const;
void load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const;
const OakTimelineMarkerList &markers) const;
void load_value_hint(Node::ValueHint *hint, XmlStreamReader *reader) const;
};
@@ -267,8 +267,9 @@ ProjectSerializer220403::load(Project *project, XmlStreamReader *reader,
} else if (reader->name() == "markers") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
TimelineMarker *marker = new TimelineMarker();
load_marker(reader, marker);
SerializedMarker marker;
marker.color = OAK_CONFIG("MarkerColor").toInt();
load_marker(reader, &marker);
load_data.markers.push_back(marker);
} else {
reader->skip_current_element();
@@ -1103,9 +1104,9 @@ void ProjectSerializer220403::load_timeline_points(XmlStreamReader *reader,
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "markers") {
load_marker_list(reader, viewer->get_markers());
load_marker_list(reader, viewer->markers_handle());
} else if (reader->name() == "workarea") {
load_work_area(reader, viewer->get_work_area());
load_work_area(reader, viewer->workarea_handle());
} else {
reader->skip_current_element();
}
@@ -1113,37 +1114,42 @@ void ProjectSerializer220403::load_timeline_points(XmlStreamReader *reader,
}
void ProjectSerializer220403::load_marker(XmlStreamReader *reader,
TimelineMarker *marker) const
SerializedMarker *marker) const
{
Rational in, out;
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "name") {
marker->set_name(attr.value);
marker->name = attr.value;
} else if (attr.name == "in") {
in = Rational::from_string(attr.value);
marker->in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
out = Rational::from_string(attr.value);
marker->out = Rational::from_string(attr.value);
} else if (attr.name == "color") {
marker->set_color(atoi(attr.value.c_str()));
marker->color = atoi(attr.value.c_str());
}
}
marker->set_time(TimeRange(in, out));
// This element has no inner text, so just skip it
reader->skip_current_element();
}
void ProjectSerializer220403::load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const
const OakTimelineWorkArea &workarea) const
{
Rational range_in = workarea->in();
Rational range_out = workarea->out();
int cur_in_num, cur_in_den, cur_out_num, cur_out_den;
if (oaktimeline_workarea_get(workarea, &cur_in_num, &cur_in_den,
&cur_out_num, &cur_out_den,
NULL) != OAKTIMELINE_OK) {
reader->skip_current_element();
return;
}
Rational range_in(cur_in_num, cur_in_den);
Rational range_out(cur_out_num, cur_out_den);
for (const XmlStreamAttribute &attr : reader->attributes()) {
if (attr.name == "enabled") {
workarea->set_enabled(attr.value != "0");
oaktimeline_workarea_set_enabled(workarea,
attr.value != "0");
} else if (attr.name == "in") {
range_in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
@@ -1151,23 +1157,30 @@ void ProjectSerializer220403::load_work_area(XmlStreamReader *reader,
}
}
TimeRange loaded_workarea(range_in, range_out);
if (loaded_workarea != workarea->range()) {
workarea->set_range(loaded_workarea);
if (range_in != Rational(cur_in_num, cur_in_den) ||
range_out != Rational(cur_out_num, cur_out_den)) {
oaktimeline_workarea_set_range(workarea, range_in.numerator(),
range_in.denominator(),
range_out.numerator(),
range_out.denominator());
}
reader->skip_current_element();
}
void ProjectSerializer220403::load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const
const OakTimelineMarkerList &markers) const
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
auto marker = std::make_unique<TimelineMarker>();
load_marker(reader, marker.get());
markers->add_marker(std::move(marker));
SerializedMarker marker;
marker.color = OAK_CONFIG("MarkerColor").toInt();
load_marker(reader, &marker);
oaktimeline_marker_add(markers, marker.in.numerator(),
marker.in.denominator(),
marker.out.numerator(),
marker.out.denominator(),
marker.name.c_str(), marker.color);
} else {
reader->skip_current_element();
}
@@ -101,13 +101,13 @@ private:
void load_timeline_points(XmlStreamReader *reader,
ViewerOutput *viewer) const;
void load_marker(XmlStreamReader *reader, TimelineMarker *marker) const;
void load_marker(XmlStreamReader *reader, SerializedMarker *marker) const;
void load_work_area(XmlStreamReader *reader,
TimelineWorkArea *workarea) const;
const OakTimelineWorkArea &workarea) const;
void load_marker_list(XmlStreamReader *reader,
TimelineMarkerList *markers) const;
const OakTimelineMarkerList &markers) const;
void load_value_hint(Node::ValueHint *hint, XmlStreamReader *reader) const;
};
@@ -68,8 +68,21 @@ ProjectSerializer230220::load(Project *project, XmlStreamReader *reader,
if (reader->name() == "markers") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "marker") {
TimelineMarker *marker = new TimelineMarker();
marker->load(reader);
SerializedMarker marker;
marker.color = OAK_CONFIG("MarkerColor").toInt();
for (const XmlStreamAttribute &attr :
reader->attributes()) {
if (attr.name == "name") {
marker.name = attr.value;
} else if (attr.name == "in") {
marker.in = Rational::from_string(attr.value);
} else if (attr.name == "out") {
marker.out = Rational::from_string(attr.value);
} else if (attr.name == "color") {
marker.color = atoi(attr.value.c_str());
}
}
reader->skip_current_element();
load_data.markers.push_back(marker);
} else {
reader->skip_current_element();
@@ -404,9 +417,12 @@ void ProjectSerializer230220::save(XmlStreamWriter *writer,
for (auto it = data.get_only_serialize_markers().cbegin();
it != data.get_only_serialize_markers().cend(); it++) {
TimelineMarker *marker = *it;
const SerializedMarker &marker = *it;
writer->write_start_element("marker");
marker->save(writer);
writer->write_attribute("name", marker.name);
writer->write_attribute("in", marker.in.to_string());
writer->write_attribute("out", marker.out.to_string());
writer->write_attribute("color", std::to_string(marker.color));
writer->write_end_element(); // marker
}
@@ -1,3 +0,0 @@
#pragma once
// M4 收尾:桥接到 oaktimeline 真身
#include "../../../timeline/src/timelinecommon.h"
@@ -1,3 +0,0 @@
#pragma once
// M4 收尾:桥接到 oaktimeline 真身
#include "../../../timeline/src/timelinemarker.h"
@@ -1,3 +0,0 @@
#pragma once
// M4 收尾:桥接到 oaktimeline 真身
#include "../../../timeline/src/timelineundogeneral.h"
@@ -1,3 +0,0 @@
#pragma once
// M4 收尾:桥接到 oaktimeline 真身
#include "../../../timeline/src/timelineworkarea.h"
+33 -4
View File
@@ -20,6 +20,7 @@
#include "timeline/marker.h"
#include <memory>
#include <new>
#include "../src/timelinemarker.h"
@@ -57,19 +58,31 @@ olive::TimelineMarker *marker_at(OakTimelineMarkerList list, int index)
} // namespace
OakTimelineMarkerList oaktimeline_marker_list_create(void)
{
olive::TimelineMarkerList *list =
new (std::nothrow) olive::TimelineMarkerList();
if (!list) {
return OakTimelineMarkerList{};
}
return oaktimeline_capi::make_handle<OakTimelineMarkerList>(
list, [](void *p) { delete static_cast<olive::TimelineMarkerList *>(p); });
}
OakTimelineMarkerList oaktimeline_marker_list_of(OakNodeNode owner)
{
if (!owner.ctx) {
return OakTimelineMarkerList{};
}
OakNodeMarkerList *markers = NULL;
// The viewer node keeps the owning handle; oaknode returns an
// addref'd copy (still the same box; the list itself is owned by the
// node's own handle).
OakTimelineMarkerList markers = {};
if (oaknode_node_get_markers(owner, &markers) != OAKNODE_OK) {
return OakTimelineMarkerList{};
}
// Borrowed box: the list stays owned by the viewer node.
return oaktimeline_capi::make_handle<OakTimelineMarkerList>(
reinterpret_cast<olive::TimelineMarkerList *>(markers));
return markers;
}
void oaktimeline_marker_list_free(OakTimelineMarkerList *list)
@@ -77,6 +90,22 @@ void oaktimeline_marker_list_free(OakTimelineMarkerList *list)
oaktimeline_capi::free_handle(list);
}
int oaktimeline_marker_add(OakTimelineMarkerList list, int in_num,
int in_den, int out_num, int out_den,
const char *name, int color)
{
if (!list.ctx) {
return OAKTIMELINE_E_INVALID;
}
auto marker = std::make_unique<olive::TimelineMarker>(
color,
olive::core::TimeRange(rat(in_num, in_den), rat(out_num, out_den)),
name ? name : "");
impl(list)->add_marker(std::move(marker));
return OAKTIMELINE_OK;
}
int oaktimeline_marker_count(OakTimelineMarkerList list, int *out_count)
{
if (!list.ctx || !out_count) {
+20 -10
View File
@@ -30,18 +30,21 @@
* @brief Internal control block behind the oaktimeline value handles,
* shared between the c_api translation units.
*
* Both public handle types (OakTimelineMarkerList, OakTimelineWorkArea)
* are borrowed references into viewer nodes: the box never owns the
* underlying object, so release() only destroys the box. A single
* generic box and addref/release pair serves both families (identical
* Handles are either borrowed references into viewer nodes (destroy ==
* nullptr: release() only frees the box) or owning handles created by
* oaktimeline_marker_list_create()/oaktimeline_workarea_create()
* (destroy deletes the native object). A single generic box and
* addref/release pair serves both families (identical
* ctx/addref/release/abi_version layout).
*/
struct OakTimelineBox {
void *object;
void (*destroy)(void *object); /**< NULL for borrowed boxes. */
std::atomic<uint32_t> refs;
explicit OakTimelineBox(void *o)
explicit OakTimelineBox(void *o, void (*d)(void *) = nullptr)
: object(o)
, destroy(d)
, refs(1)
{
}
@@ -64,25 +67,32 @@ inline void handle_release(void *ctx)
}
OakTimelineBox *box = static_cast<OakTimelineBox *>(ctx);
if (box->refs.fetch_sub(1) == 1) {
if (box->destroy) {
box->destroy(box->object);
}
delete box;
}
}
/**
* @brief Wrap a borrowed native object in a value handle with reference
* count 1. Returns an empty handle (ctx == nullptr) for a null
* object or on allocation failure.
* @brief Wrap a native object in a value handle with reference count 1.
* Returns an empty handle (ctx == nullptr) for a null object or on
* allocation failure. Pass a non-null deleter for an owning handle
* (release() destroys the object with the box).
*/
template <typename Handle>
inline Handle make_handle(void *object)
inline Handle make_handle(void *object, void (*destroy)(void *) = nullptr)
{
Handle handle = {};
if (!object) {
return handle;
}
OakTimelineBox *box = new (std::nothrow) OakTimelineBox(object);
OakTimelineBox *box = new (std::nothrow) OakTimelineBox(object, destroy);
if (!box) {
if (destroy) {
destroy(object);
}
return handle;
}
+26 -4
View File
@@ -50,19 +50,32 @@ olive::core::Rational rat(int64_t n, int64_t d)
} // namespace
OakTimelineWorkArea oaktimeline_workarea_create(void)
{
olive::TimelineWorkArea *workarea =
new (std::nothrow) olive::TimelineWorkArea();
if (!workarea) {
return OakTimelineWorkArea{};
}
return oaktimeline_capi::make_handle<OakTimelineWorkArea>(
workarea,
[](void *p) { delete static_cast<olive::TimelineWorkArea *>(p); });
}
OakTimelineWorkArea oaktimeline_workarea_of(OakNodeNode owner)
{
if (!owner.ctx) {
return OakTimelineWorkArea{};
}
OakNodeWorkArea *workarea = NULL;
// The viewer node keeps the owning handle; oaknode returns an
// addref'd copy (still the same box; the work area itself is owned
// by the node's own handle).
OakTimelineWorkArea workarea = {};
if (oaknode_node_get_work_area(owner, &workarea) != OAKNODE_OK) {
return OakTimelineWorkArea{};
}
// Borrowed box: the work area stays owned by the viewer node.
return oaktimeline_capi::make_handle<OakTimelineWorkArea>(
reinterpret_cast<olive::TimelineWorkArea *>(workarea));
return workarea;
}
void oaktimeline_workarea_free(OakTimelineWorkArea *w)
@@ -70,6 +83,15 @@ void oaktimeline_workarea_free(OakTimelineWorkArea *w)
oaktimeline_capi::free_handle(w);
}
int oaktimeline_workarea_set_enabled(OakTimelineWorkArea w, int enabled)
{
if (!w.ctx) {
return OAKTIMELINE_E_INVALID;
}
impl(w)->set_enabled(enabled != 0);
return OAKTIMELINE_OK;
}
int oaktimeline_workarea_get(OakTimelineWorkArea w, int *in_num,
int *in_den, int *out_num, int *out_den,
int *enabled)
+54
View File
@@ -617,3 +617,57 @@ TEST_F(TimelineSequenceFixture, SlideAndInsertGapsAndRippleDeleteGapsFactories)
}
} // namespace
TEST(OakTimelineOwningHandles, MarkerListCreateAddFree)
{
OakTimelineMarkerList list = oaktimeline_marker_list_create();
ASSERT_NE(list.ctx, nullptr);
EXPECT_EQ(oaktimeline_marker_add(list, 1, 2, 3, 4, "owned", 5),
OAKTIMELINE_OK);
EXPECT_EQ(oaktimeline_marker_add(OakTimelineMarkerList{}, 0, 1, 1, 1,
"x", 0),
OAKTIMELINE_E_INVALID);
int count = -1;
EXPECT_EQ(oaktimeline_marker_count(list, &count), OAKTIMELINE_OK);
EXPECT_EQ(count, 1);
int in_n = 0, in_d = 0, out_n = 0, out_d = 0, color = -1;
char name[32];
EXPECT_GT(oaktimeline_marker_at(list, 0, &in_n, &in_d, &out_n, &out_d,
&color, name, sizeof(name)),
0);
EXPECT_EQ(in_n, 1);
EXPECT_EQ(in_d, 2);
EXPECT_EQ(out_n, 3);
EXPECT_EQ(out_d, 4);
EXPECT_EQ(color, 5);
EXPECT_STREQ(name, "owned");
oaktimeline_marker_list_free(&list);
EXPECT_EQ(list.ctx, nullptr);
oaktimeline_marker_list_free(nullptr);
}
TEST(OakTimelineOwningHandles, WorkareaCreateSetEnabledFree)
{
OakTimelineWorkArea w = oaktimeline_workarea_create();
ASSERT_NE(w.ctx, nullptr);
int enabled = -1;
EXPECT_EQ(oaktimeline_workarea_get(w, NULL, NULL, NULL, NULL, &enabled),
OAKTIMELINE_OK);
EXPECT_EQ(enabled, 0);
EXPECT_EQ(oaktimeline_workarea_set_enabled(w, 1), OAKTIMELINE_OK);
EXPECT_EQ(oaktimeline_workarea_get(w, NULL, NULL, NULL, NULL, &enabled),
OAKTIMELINE_OK);
EXPECT_EQ(enabled, 1);
EXPECT_EQ(oaktimeline_workarea_set_enabled(OakTimelineWorkArea{}, 1),
OAKTIMELINE_E_INVALID);
oaktimeline_workarea_free(&w);
EXPECT_EQ(w.ctx, nullptr);
}