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:
@@ -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 验证)。
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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{};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "output/track/track.h"
|
||||
#include "timeline/timelinecommon.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user