R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+8 -2
View File
@@ -49,9 +49,15 @@ void CurvePanel::deselect_all()
static_cast<CurveWidget *>(get_time_based_widget())->deselect_all();
}
void CurvePanel::set_nodes(const QVector<Node *> &nodes)
void CurvePanel::set_nodes(const QVector<OakEngineNode *> &nodes)
{
static_cast<CurveWidget *>(get_time_based_widget())->set_nodes(nodes);
// Convert to oak::Node for the curve widget (C ABI wrapper layer)
QVector<oak::Node> oak_nodes;
oak_nodes.reserve(nodes.size());
foreach (OakEngineNode *n, nodes) {
oak_nodes.append(oak::Node(n));
}
static_cast<CurveWidget *>(get_time_based_widget())->set_nodes(oak_nodes);
}
void CurvePanel::increase_track_height()
+3 -6
View File
@@ -43,20 +43,17 @@ public slots:
void set_node(OakEngineNode *node)
{
// Convert single pointer to either an empty vector or a vector of one
QVector<Node *> nodes;
QVector<OakEngineNode *> nodes;
if (node) {
nodes.append(reinterpret_cast<Node *>(node));
nodes.append(node);
}
set_nodes(nodes);
}
public:
// Not a slot: signature uses the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void set_nodes(const QVector<Node *> &nodes);
void set_nodes(const QVector<OakEngineNode *> &nodes);
public slots:
virtual void increase_track_height() override;
+3 -3
View File
@@ -43,7 +43,7 @@ public:
return node_widget_;
}
const QVector<Node *> &get_contexts() const
const QVector<oak::Node> &get_contexts() const
{
return node_widget_->view()->get_contexts();
}
@@ -53,12 +53,12 @@ public:
return node_widget_->view()->is_group_overlay();
}
void set_contexts(const QVector<Node *> &nodes)
void set_contexts(const QVector<oak::Node> &nodes)
{
node_widget_->set_contexts(nodes);
}
void close_contexts_belonging_to_project(Project *project)
void close_contexts_belonging_to_project(oak::Project project)
{
node_widget_->view()->close_contexts_belonging_to_project(project);
}
+1 -1
View File
@@ -57,7 +57,7 @@ void ParamPanel::deselect_all()
static_cast<NodeParamView *>(get_time_based_widget())->deselect_all();
}
void ParamPanel::set_contexts(const QVector<Node *> &contexts)
void ParamPanel::set_contexts(const QVector<oak::Node> &contexts)
{
static_cast<NodeParamView *>(get_time_based_widget())->set_contexts(contexts);
}
+5 -6
View File
@@ -39,12 +39,12 @@ public:
return static_cast<NodeParamView *>(get_time_based_widget());
}
const QVector<Node *> &get_contexts() const
const QVector<oak::Node> &get_contexts() const
{
return get_param_view()->get_contexts();
}
void close_contexts_belonging_to_project(Project *p)
void close_contexts_belonging_to_project(oak::Project p)
{
get_param_view()->close_contexts_belonging_to_project(p);
}
@@ -63,10 +63,9 @@ public slots:
virtual void deselect_all() override;
public:
// Not a slot: signature uses the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void set_contexts(const QVector<Node *> &contexts);
// Not a slot: mirrors NodePanel::set_contexts(), which is also a plain
// member function. All connections use new-style member-function syntax.
void set_contexts(const QVector<oak::Node> &contexts);
signals:
void focused_node_changed(OakEngineNode *n);
@@ -24,8 +24,6 @@
#include <QList>
#include "node/project/footage/footage.h"
struct OakEngineNode;
namespace olive
+23 -30
View File
@@ -27,7 +27,6 @@
#include "core.h"
#include "oakengine/events.h"
#include "oakengine/project.h"
#include "node/project/sequence/sequence.h"
#include "panel/footageviewer/footageviewer.h"
#include "panel/timeline/timeline.h"
#include "panel/panelmanager.h"
@@ -89,12 +88,12 @@ ProjectPanel::ProjectPanel(const QString &unique_name)
retranslate();
}
Project *ProjectPanel::project() const
oak::Project ProjectPanel::project() const
{
return explorer_->project();
}
void ProjectPanel::set_project(Project *p)
void ProjectPanel::set_project(oak::Project p)
{
if (project()) {
if (project_name_sub_ > 0) {
@@ -106,9 +105,8 @@ void ProjectPanel::set_project(Project *p)
explorer_->set_project(p);
if (project()) {
auto *ph = reinterpret_cast<OakEngineProject *>(project());
project_name_sub_ = oakengine_event_subscribe(
ph, OAKENGINE_EVENT_PROJECT_NAME_CHANGED,
project().handle(), OAKENGINE_EVENT_PROJECT_NAME_CHANGED,
[](const oakengine_event *, void *userdata) {
auto *s = static_cast<ProjectPanel *>(userdata);
s->update_subtitle();
@@ -122,24 +120,24 @@ void ProjectPanel::set_project(Project *p)
emit project_name_changed();
}
Folder *ProjectPanel::get_root() const
oak::Node ProjectPanel::get_root() const
{
return explorer_->get_root();
}
void ProjectPanel::set_root(Folder *item)
void ProjectPanel::set_root(oak::Node item)
{
explorer_->set_root(item);
retranslate();
}
QVector<Node *> ProjectPanel::selected_items() const
QVector<oak::Node> ProjectPanel::selected_items() const
{
return explorer_->selected_items();
}
Folder *ProjectPanel::get_selected_folder() const
oak::Node ProjectPanel::get_selected_folder() const
{
return explorer_->get_selected_folder();
}
@@ -176,7 +174,7 @@ void ProjectPanel::edit(OakEngineNode *item)
void ProjectPanel::retranslate()
{
if (project() && explorer_->get_root() != project()->root()) {
if (project() && explorer_->get_root() != project().root()) {
set_title(tr("Folder"));
} else {
set_title(tr("Project"));
@@ -187,21 +185,20 @@ void ProjectPanel::retranslate()
void ProjectPanel::item_double_click_slot(OakEngineNode *item_handle)
{
Node *item = reinterpret_cast<Node *>(item_handle);
oak::Node item(item_handle);
if (item == nullptr) {
// If the user double clicks on empty space, show the import dialog
Core::instance()->dialog_import_show();
} else if (dynamic_cast<Footage *>(item)) {
} else if (item.is_footage()) {
// Open this footage in a FootageViewer
auto panel =
PanelManager::instance()->most_recently_focused<FootageViewerPanel>();
panel->connect_viewer_node(static_cast<Footage *>(item));
panel->connect_viewer_node(item.handle());
panel->raise();
panel->setFocus(Qt::FocusReason::MouseFocusReason);
} else if (dynamic_cast<Sequence *>(item)) {
} else if (item.is_sequence()) {
// Open this sequence in the Timeline
Core::instance()->main_window()->open_sequence(
static_cast<Sequence *>(item));
Core::instance()->main_window()->open_sequence(item.handle());
}
}
@@ -217,23 +214,19 @@ void ProjectPanel::show_new_menu()
void ProjectPanel::update_subtitle()
{
if (project()) {
char name_buf[256];
oakengine_project_name(
reinterpret_cast<OakEngineProject *>(project()),
name_buf, sizeof(name_buf));
QString project_title = QString::fromUtf8(name_buf);
QString project_title = project().name();
if (explorer_->get_root() != project()->root()) {
if (explorer_->get_root() != project().root()) {
QString folder_path;
Folder *item = explorer_->get_root();
oak::Node item = explorer_->get_root();
do {
folder_path.prepend(
QStringLiteral("/%1").arg(item->get_label()));
QStringLiteral("/%1").arg(item.get_label()));
item = item->folder();
} while (item != project()->root());
item = item.folder();
} while (item != project().root());
project_title.append(folder_path);
}
@@ -251,12 +244,12 @@ void ProjectPanel::save_connected_project()
QVector<OakEngineNode *> ProjectPanel::get_selected_footage() const
{
QVector<Node *> items = selected_items();
QVector<oak::Node> items = selected_items();
QVector<OakEngineNode *> footage;
foreach (Node *i, items) {
if (dynamic_cast<ViewerOutput *>(i)) {
footage.append(reinterpret_cast<OakEngineNode *>(i));
foreach (const oak::Node &i, items) {
if (i.is_viewer_output()) {
footage.append(i.handle());
}
}
+7 -8
View File
@@ -23,7 +23,6 @@
#define OAK_PROJECT_PANEL_H
#include "footagemanagementpanel.h"
#include "node/project.h"
#include "panel/panel.h"
#include "widget/projectexplorer/projectexplorer.h"
@@ -41,22 +40,22 @@ public:
ProjectPanel(const QString &unique_name);
~ProjectPanel() override;
Project *project() const;
void set_project(Project *p);
oak::Project project() const;
void set_project(oak::Project p);
Folder *get_root() const;
oak::Node get_root() const;
void set_root(Folder *item);
void set_root(oak::Node item);
QVector<Node *> selected_items() const;
QVector<oak::Node> selected_items() const;
Folder *get_selected_folder() const;
oak::Node get_selected_folder() const;
virtual QVector<OakEngineNode *> get_selected_footage() const override;
ProjectViewModel *model() const;
bool select_item(Node *n, bool deselect_all_first = true)
bool select_item(oak::Node n, bool deselect_all_first = true)
{
return explorer_->select_item(n, deselect_all_first);
}
+1 -1
View File
@@ -33,7 +33,7 @@ SequenceViewerPanel::SequenceViewerPanel()
}
void SequenceViewerPanel::start_capture(const TimeRange &time,
const Track::Reference &track)
const TrackReference &track)
{
TimelinePanel *tp = static_cast<TimelinePanel *>(sender());
static_cast<ViewerWidget *>(get_time_based_widget())
+2 -1
View File
@@ -22,6 +22,7 @@
#ifndef OAK_SEQUENCEVIEWERPANEL_H
#define OAK_SEQUENCEVIEWERPANEL_H
#include "common/trackreferencehandle.h"
#include "panel/viewer/viewer.h"
namespace olive
@@ -33,7 +34,7 @@ public:
SequenceViewerPanel();
public slots:
void start_capture(const TimeRange &time, const Track::Reference &track);
void start_capture(const TimeRange &time, const TrackReference &track);
protected:
virtual void retranslate() override;
+2 -5
View File
@@ -34,15 +34,12 @@ public:
NodeTablePanel();
public:
// Not slots: signatures use the engine C++ type Node*, which must not be
// exposed to MOC (it would pull Node::staticMetaObject across the ABI
// boundary). All connections use new-style member-function syntax.
void select_nodes(const QVector<Node *> &nodes)
void select_nodes(const QVector<OakEngineNode *> &nodes)
{
static_cast<NodeTableWidget *>(get_time_based_widget())->select_nodes(nodes);
}
void deselect_nodes(const QVector<Node *> &nodes)
void deselect_nodes(const QVector<OakEngineNode *> &nodes)
{
static_cast<NodeTableWidget *>(get_time_based_widget())
->deselect_nodes(nodes);
+5 -3
View File
@@ -21,6 +21,8 @@
#include "timebased.h"
#include "oakutil/oaknode.h"
namespace olive
{
@@ -116,7 +118,7 @@ void TimeBasedPanel::shuttle_right()
emit shuttle_right_requested();
}
void TimeBasedPanel::connect_viewer_node(ViewerOutput *node)
void TimeBasedPanel::connect_viewer_node(OakEngineNode *node)
{
widget_->connect_viewer_node(node);
}
@@ -140,8 +142,8 @@ void TimeBasedPanel::set_time_based_widget(TimeBasedWidget *widget)
void TimeBasedPanel::retranslate()
{
if (get_time_based_widget()->get_connected_node()) {
set_subtitle(get_time_based_widget()->get_connected_node()->get_label());
if (get_connected_viewer()) {
set_subtitle(oak::Node(get_connected_viewer()).get_label());
} else {
set_subtitle(tr("(none)"));
}
+2 -2
View File
@@ -36,7 +36,7 @@ public:
virtual ~TimeBasedPanel() override;
void connect_viewer_node(ViewerOutput *node);
void connect_viewer_node(OakEngineNode *node);
void disconnect_viewer_node()
{
@@ -46,7 +46,7 @@ public:
// Get the timebase of this panels widget
const Rational &timebase();
ViewerOutput *get_connected_viewer() const
OakEngineNode *get_connected_viewer() const
{
return widget_->get_connected_node();
}
+21 -5
View File
@@ -22,6 +22,7 @@
#ifndef OAK_TIMELINE_PANEL_H
#define OAK_TIMELINE_PANEL_H
#include "common/trackreferencehandle.h"
#include "panel/timebased/timebased.h"
#include "widget/timelinewidget/timelinewidget.h"
@@ -107,14 +108,29 @@ public:
void overwrite_footage_at_playhead(const QVector<OakEngineNode *> &footage);
const QVector<Block *> &get_selected_blocks() const
QVector<OakEngineBlock *> get_selected_blocks() const
{
return timeline_widget()->get_selected_blocks();
// TimelineWidget still exposes engine Block* (its own wave converts
// it); re-wrap into the handle family here so callers never see the
// engine type.
const auto &selected = timeline_widget()->get_selected_blocks();
QVector<OakEngineBlock *> handles;
handles.reserve(selected.size());
for (auto *b : selected) {
handles.append(reinterpret_cast<OakEngineBlock *>(b));
}
return handles;
}
Sequence *get_sequence() const
OakEngineSequence *get_sequence() const
{
return dynamic_cast<Sequence *>(get_connected_viewer());
// R8: type check via the C ABI facade predicate (replaces
// dynamic_cast<Sequence*>, which needed the complete engine type);
// the handle is shared, so the cast is a reinterpret.
OakEngineNode *connected = get_connected_viewer();
return (connected && oakengine_node_is_sequence(connected))
? reinterpret_cast<OakEngineSequence *>(connected)
: nullptr;
}
protected:
@@ -124,7 +140,7 @@ signals:
void block_selection_changed(const QVector<OakEngineBlock *> &selected_blocks);
void request_capture_start(const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void reveal_viewer_in_project(OakEngineNode *r);
void reveal_viewer_in_footage_viewer(OakEngineNode *r, const TimeRange &range);
+1 -1
View File
@@ -95,7 +95,7 @@ void ViewerPanelBase::set_full_screen(QScreen *screen)
void ViewerPanelBase::set_gizmos(OakEngineNode *node)
{
get_viewer_widget()->set_gizmos(reinterpret_cast<Node *>(node));
get_viewer_widget()->set_gizmos(node);
}
void ViewerPanelBase::cache_entire_sequence()
+1 -1
View File
@@ -76,7 +76,7 @@ public:
get_viewer_widget()->add_playback_device(vw);
}
void set_timeline_selected_blocks(const QVector<Block *> &b)
void set_timeline_selected_blocks(const QVector<OakEngineBlock *> &b)
{
get_viewer_widget()->set_timeline_selected_blocks(b);
}