refactor: invert the last engine-to-UI dependencies

- NodeFactory's menu creation moves to UI-side widget/menu/factorymenu
  (the factory only exposes its node library read-only now)
- DiskManager's cache-settings dialog is created through a registered
  std::function handler (registered by Core at startup)
- OlivePluginInstance creates progress UIs through a
  PluginProgressReporter interface (Null fallback headless) and queries
  the active viewer through a provider callback, both registered by Core
- factory.h, diskmanager and pluginSupport no longer reference any
  widget//dialog//panel//window headers or classes
This commit is contained in:
2026-07-20 02:05:01 +08:00
parent bff06e00e5
commit 026ff94b5e
21 changed files with 630 additions and 179 deletions
+45
View File
@@ -44,11 +44,13 @@
#include "config/config.h"
#include "dialog/about/about.h"
#include "dialog/autorecovery/autorecoverydialog.h"
#include "dialog/diskcache/diskcachedialog.h"
#include "dialog/export/export.h"
#include "dialog/footagerelink/footagerelinkdialog.h"
#ifdef USE_OTIO
#include "dialog/otioproperties/otiopropertiesdialog.h"
#endif
#include "dialog/progress/pluginprogressdialogreporter.h"
#include "dialog/projectproperties/projectproperties.h"
#include "dialog/sequence/sequence.h"
#include "dialog/task/task.h"
@@ -56,7 +58,11 @@
#include "node/nodeundo.h"
#include "panel/panelmanager.h"
#include "panel/project/project.h"
#include "panel/timebased/timebased.h"
#include "panel/timeline/timeline.h"
#include "panel/viewer/viewer.h"
#include "pluginSupport/oliveplugininstance.h"
#include "pluginSupport/pluginprogressreporter.h"
#include "render/diskmanager.h"
#ifdef USE_OTIO
#include "task/project/loadotio/loadotio.h"
@@ -103,6 +109,45 @@ Core::Core(const CoreParams &params)
return DialogImportOTIOShow(sequences);
});
#endif
// Disk cache settings dialog (engine -> UI)
DiskManager::set_show_disk_cache_settings_handler(
[](DiskCacheFolder *folder, QWidget *parent) {
DiskCacheDialog d(folder, parent);
d.exec();
});
// OFX plugin progress dialog (engine -> UI)
plugin::set_plugin_progress_reporter_factory(
[](const QString &message,
const QString &title) -> plugin::PluginProgressReporter * {
return new PluginProgressDialogReporter(message, title);
});
// OFX timeline suite: resolve the active viewer through the panels
plugin::set_active_viewer_provider([]() -> ViewerOutput * {
PanelManager *manager = PanelManager::instance();
if (!manager) {
return nullptr;
}
if (auto *time_panel =
manager->most_recently_focused<TimeBasedPanel>()) {
if (time_panel->get_connected_viewer()) {
return time_panel->get_connected_viewer();
}
}
QList<TimelinePanel *> timelines =
manager->get_panels_of_type<TimelinePanel>();
for (TimelinePanel *panel : timelines) {
if (panel && panel->get_connected_viewer()) {
return panel->get_connected_viewer();
}
}
return nullptr;
});
}
void Core::start()
+2
View File
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
dialog/progress/pluginprogressdialogreporter.h
dialog/progress/pluginprogressdialogreporter.cpp
dialog/progress/progress.h
dialog/progress/progress.cpp
PARENT_SCOPE
@@ -0,0 +1,64 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "pluginprogressdialogreporter.h"
#include "dialog/progress/progress.h"
namespace olive
{
PluginProgressDialogReporter::PluginProgressDialogReporter(
const QString &message, const QString &title)
: dialog_(new ProgressDialog(message, title, nullptr))
{
dialog_->setAttribute(Qt::WA_DeleteOnClose);
connect(dialog_, &ProgressDialog::cancelled, this,
&PluginProgressReporter::cancelled);
}
PluginProgressDialogReporter::~PluginProgressDialogReporter()
{
delete dialog_;
}
void PluginProgressDialogReporter::set_progress(double value)
{
if (dialog_) {
dialog_->set_progress(value);
}
}
void PluginProgressDialogReporter::show()
{
if (dialog_) {
dialog_->show();
}
}
void PluginProgressDialogReporter::close()
{
if (dialog_) {
dialog_->close();
}
}
}
@@ -0,0 +1,61 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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_PLUGINPROGRESSDIALOGREPORTER_H
#define OAK_PLUGINPROGRESSDIALOGREPORTER_H
#include <QPointer>
#include "pluginSupport/pluginprogressreporter.h"
namespace olive
{
class ProgressDialog;
/**
* @brief PluginProgressReporter implementation wrapping a ProgressDialog
*
* UI-side reporter created through the plugin progress reporter factory.
* Forwards progress calls to the dialog and the dialog's cancelled() signal
* back to the engine. The dialog is deleted on close; the reporter itself
* is destroyed by the engine with deleteLater().
*/
class PluginProgressDialogReporter : public plugin::PluginProgressReporter {
Q_OBJECT
public:
PluginProgressDialogReporter(const QString &message, const QString &title);
virtual ~PluginProgressDialogReporter() override;
virtual void set_progress(double value) override;
virtual void show() override;
virtual void close() override;
private:
QPointer<ProgressDialog> dialog_;
};
}
#endif // OAK_PLUGINPROGRESSDIALOGREPORTER_H
-108
View File
@@ -21,7 +21,6 @@
#include "factory.h"
#include <QCoreApplication>
#include <QHash>
#include "audio/pan/pan.h"
@@ -105,113 +104,6 @@ void NodeFactory::destroy()
library.clear();
}
Menu *NodeFactory::create_menu(QWidget *parent, bool create_none_item,
Node::CategoryID restrict_to,
uint64_t restrict_flags)
{
Menu *menu = new Menu(parent);
menu->setToolTipsVisible(true);
for (int i = 0; i < library.size(); i++) {
Node *n = library.at(i);
if (restrict_to != Node::k_category_unknown &&
!n->category().contains(restrict_to)) {
// Skip this node
continue;
}
if (restrict_flags && !(n->get_flags() & restrict_flags)) {
continue;
}
if (n->get_flags() & Node::k_dont_show_in_create_menu) {
continue;
}
// Make sure nodes are up-to-date with the current translation
n->retranslate();
QString category_name = Node::get_category_name(
n->category().isEmpty() ? Node::k_category_unknown :
n->category().first());
// Find or create top-level category menu
Menu *top_menu = nullptr;
QList<QAction *> menu_actions = menu->actions();
foreach (QAction *action, menu_actions) {
if (action->menu() && action->menu()->title() == category_name) {
top_menu = static_cast<Menu *>(action->menu());
break;
}
}
if (!top_menu) {
top_menu = new Menu(category_name, menu);
menu->insert_alphabetically(top_menu);
}
// Determine final destination (support secondary grouping)
Menu *destination = top_menu;
QString sub = n->sub_category();
if (!sub.isEmpty() && n->category().contains(Node::k_category_open_fx)) {
QList<QAction *> sub_actions = top_menu->actions();
foreach (QAction *action, sub_actions) {
if (action->menu() && action->menu()->title() == sub) {
destination = static_cast<Menu *>(action->menu());
break;
}
}
if (destination == top_menu) {
destination = new Menu(sub, top_menu);
top_menu->insert_alphabetically(destination);
}
}
// Add entry to menu
QAction *a = destination->insert_alphabetically(n->name());
a->setData(i);
a->setToolTip(n->description());
}
if (create_none_item) {
QAction *none_item = new QAction(
QCoreApplication::translate("NodeFactory", "None"), menu);
none_item->setData(-1);
if (menu->actions().isEmpty()) {
menu->addAction(none_item);
} else {
QAction *separator = menu->insertSeparator(menu->actions().first());
menu->insertAction(separator, none_item);
}
}
return menu;
}
Node *NodeFactory::CreateFromMenuAction(QAction *action)
{
int index = action->data().toInt();
if (index == -1) {
return nullptr;
}
return library.at(index)->copy();
}
QString NodeFactory::GetIDFromMenuAction(QAction *action)
{
int index = action->data().toInt();
if (index == -1) {
return QString();
}
return library.at(action->data().toInt())->id();
}
QString NodeFactory::get_name_from_id(const QString &id)
{
if (!id.isEmpty()) {
+12 -10
View File
@@ -25,7 +25,6 @@
#include <QList>
#include "node.h"
#include "widget/menu/menu.h"
namespace olive
{
@@ -97,15 +96,6 @@ public:
static void destroy();
static Menu *
create_menu(QWidget *parent, bool create_none_item = false,
Node::CategoryID restrict_to = Node::k_category_unknown,
uint64_t restrict_flags = 0);
static Node *CreateFromMenuAction(QAction *action);
static QString GetIDFromMenuAction(QAction *action);
static QString get_name_from_id(const QString &id);
static Node *create_from_id(const QString &id);
@@ -113,6 +103,18 @@ public:
static Node *create_from_factory_index(const InternalID &id);
/**
* @brief Access the internal node library
*
* Exposed for the UI layer (e.g. widget/menu/factorymenu), which builds
* node creation menus from the library. The library itself stays
* UI-independent.
*/
static const QList<Node *> &get_library()
{
return library;
}
private:
static QList<Node *> library;
};
+2
View File
@@ -3,6 +3,8 @@ target_sources(libolive-editor PRIVATE
olivehost.cpp
oliveplugininstance.h
oliveplugininstance.cpp
pluginprogressreporter.h
pluginprogressreporter.cpp
oliveclip.cpp
oliveclip.h
paraminstance.cpp
+22 -38
View File
@@ -23,11 +23,8 @@
#include "ofxMessage.h"
#include "common/current.h"
#include "coreengine.h"
#include "dialog/progress/progress.h"
#include "pluginprogressreporter.h"
#include "node/output/viewer/viewer.h"
#include "panel/panelmanager.h"
#include "panel/timebased/timebased.h"
#include "panel/timeline/timeline.h"
#include <cstdio>
#include <QApplication>
@@ -125,31 +122,19 @@ private:
bool skip_first_redo_ = true;
};
ActiveViewerProvider active_viewer_provider_;
ViewerOutput *get_active_viewer_output()
{
PanelManager *manager = PanelManager::instance();
if (!manager) {
return nullptr;
}
if (auto *time_panel = manager->most_recently_focused<TimeBasedPanel>()) {
if (time_panel->get_connected_viewer()) {
return time_panel->get_connected_viewer();
}
}
QList<TimelinePanel *> timelines =
manager->get_panels_of_type<TimelinePanel>();
for (TimelinePanel *panel : timelines) {
if (panel && panel->get_connected_viewer()) {
return panel->get_connected_viewer();
}
}
return nullptr;
return active_viewer_provider_ ? active_viewer_provider_() : nullptr;
}
} // namespace
void set_active_viewer_provider(ActiveViewerProvider provider)
{
active_viewer_provider_ = std::move(provider);
}
const std::string &OlivePluginInstance::getDefaultOutputFielding() const
{
return field_order_for_params(params_);
@@ -452,21 +437,20 @@ void OlivePluginInstance::progressStart(const std::string &message,
return;
}
if (progress_dialog_) {
progress_dialog_->close();
progress_dialog_->deleteLater();
if (progress_reporter_) {
progress_reporter_->close();
progress_reporter_->deleteLater();
}
QString dialog_message = message.empty() ? QStringLiteral("Processing...") :
QString::fromStdString(message);
progress_dialog_ = new ::olive::ProgressDialog(
dialog_message, QStringLiteral("OpenFX"), nullptr);
progress_dialog_->setAttribute(Qt::WA_DeleteOnClose);
QObject::connect(progress_dialog_, &::olive::ProgressDialog::cancelled,
progress_dialog_,
progress_reporter_ = create_plugin_progress_reporter(
dialog_message, QStringLiteral("OpenFX"));
QObject::connect(progress_reporter_, &PluginProgressReporter::cancelled,
progress_reporter_,
[this]() { progress_cancelled_ = true; });
progress_dialog_->show();
progress_reporter_->show();
}
void OlivePluginInstance::progressEnd()
@@ -474,9 +458,9 @@ void OlivePluginInstance::progressEnd()
progress_active_ = false;
progress_cancelled_ = false;
if (progress_dialog_) {
progress_dialog_->close();
progress_dialog_->deleteLater();
if (progress_reporter_) {
progress_reporter_->close();
progress_reporter_->deleteLater();
}
}
@@ -486,9 +470,9 @@ bool OlivePluginInstance::progressUpdate(double t)
return true;
}
if (progress_dialog_) {
if (progress_reporter_) {
double clamped = qBound(0.0, t, 1.0);
progress_dialog_->set_progress(clamped);
progress_reporter_->set_progress(clamped);
}
return !progress_cancelled_;
+16 -2
View File
@@ -27,6 +27,7 @@
#include <map>
#include <mutex>
#include <functional>
#include <QCoreApplication>
#include <QPointer>
#include <QThread>
@@ -43,15 +44,28 @@ inline bool is_gui_thread()
}
return true;
}
class ProgressDialog;
class ViewerOutput;
namespace plugin
{
class PluginNode;
class PluginProgressReporter;
enum class ErrorType { error, warning, message };
struct PersistentErrors {
ErrorType type;
QString message;
};
/**
* @brief Provider returning the currently active viewer
*
* Registered by the UI layer, which resolves the viewer through the panel
* manager. Without a provider, the OFX timeline suite falls back to its
* safe defaults (current time 0, empty bounds, seeking does nothing).
*/
using ActiveViewerProvider = std::function<ViewerOutput *()>;
void set_active_viewer_provider(ActiveViewerProvider provider);
class OlivePluginInstance : public OFX::Host::ImageEffect::Instance {
public:
OlivePluginInstance(OFX::Host::ImageEffect::ImageEffectPlugin *plugin,
@@ -214,7 +228,7 @@ private:
QString edit_label_;
QString edit_first_label_;
int edit_param_count_ = 0;
QPointer<olive::ProgressDialog> progress_dialog_;
QPointer<PluginProgressReporter> progress_reporter_;
bool progress_cancelled_ = false;
bool progress_active_ = false;
bool open_gl_enabled_ = false;
@@ -0,0 +1,69 @@
/*
* Oak Video Editor - Non-Linear Video Editor
* Copyright (C) 2025 Olive CE Team
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pluginprogressreporter.h"
namespace olive
{
namespace plugin
{
namespace
{
/**
* @brief No-op reporter used when no UI factory is registered
*
* Never emits cancelled(), so processing always continues.
*/
class NullPluginProgressReporter : public PluginProgressReporter {
public:
void set_progress(double value) override
{
(void)value;
}
void show() override
{
}
void close() override
{
}
};
PluginProgressReporterFactory reporter_factory_;
}
void set_plugin_progress_reporter_factory(
PluginProgressReporterFactory factory)
{
reporter_factory_ = std::move(factory);
}
PluginProgressReporter *
create_plugin_progress_reporter(const QString &message, const QString &title)
{
if (reporter_factory_) {
return reporter_factory_(message, title);
}
return new NullPluginProgressReporter();
}
}
}
@@ -0,0 +1,84 @@
/*
* Oak Video Editor - Non-Linear Video Editor
* Copyright (C) 2025 Olive CE 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_PLUGIN_PROGRESS_REPORTER_H
#define OAK_PLUGIN_PROGRESS_REPORTER_H
#include <QObject>
#include <QString>
#include <functional>
namespace olive
{
namespace plugin
{
/**
* @brief UI-independent interface for reporting plugin progress
*
* The engine cannot show UI itself, so OFX progress reporting goes through
* this interface. The UI layer registers a factory (see
* set_plugin_progress_reporter_factory()) that creates a reporter wrapping a
* ProgressDialog; without a factory, a no-op reporter is used instead.
*/
class PluginProgressReporter : public QObject {
Q_OBJECT
public:
explicit PluginProgressReporter(QObject *parent = nullptr)
: QObject(parent)
{
}
virtual ~PluginProgressReporter() override = default;
virtual void set_progress(double value) = 0;
virtual void show() = 0;
virtual void close() = 0;
signals:
void cancelled();
};
/**
* @brief Factory creating a PluginProgressReporter for a progress session
*
* Registered by the UI layer at startup. The caller takes ownership of the
* returned reporter.
*/
using PluginProgressReporterFactory =
std::function<PluginProgressReporter *(const QString &message,
const QString &title)>;
void set_plugin_progress_reporter_factory(
PluginProgressReporterFactory factory);
/**
* @brief Create a progress reporter through the registered factory
*
* Without a factory, returns a no-op reporter so engine code can run
* headless. The caller takes ownership of the returned reporter.
*/
PluginProgressReporter *
create_plugin_progress_reporter(const QString &message, const QString &title);
}
}
#endif // OAK_PLUGIN_PROGRESS_REPORTER_H
+15 -3
View File
@@ -31,13 +31,15 @@
#include "common/filefunctions.h"
#include "config/config.h"
#include "coreengine.h"
#include "dialog/diskcache/diskcachedialog.h"
namespace olive
{
DiskManager *DiskManager::instance_ = nullptr;
DiskManager::ShowDiskCacheSettingsHandler
DiskManager::show_disk_cache_settings_handler_;
DiskManager::DiskManager()
{
// Add default cache location
@@ -181,11 +183,21 @@ QString DiskManager::get_default_disk_cache_path()
.filePath("mediacache");
}
void DiskManager::set_show_disk_cache_settings_handler(
ShowDiskCacheSettingsHandler handler)
{
show_disk_cache_settings_handler_ = std::move(handler);
}
void DiskManager::show_disk_cache_settings_dialog(DiskCacheFolder *folder,
QWidget *parent)
{
DiskCacheDialog d(folder, parent);
d.exec();
if (show_disk_cache_settings_handler_) {
show_disk_cache_settings_handler_(folder, parent);
return;
}
qWarning() << "No disk cache settings dialog handler registered, skipping";
}
void DiskManager::show_disk_cache_settings_dialog(const QString &path,
+17
View File
@@ -27,6 +27,8 @@
#include <QObject>
#include <QTimer>
#include <functional>
#include "common/define.h"
#include "node/project.h"
@@ -143,6 +145,19 @@ public:
static QString get_default_disk_cache_path();
/**
* @brief Handler showing the disk cache settings dialog for a folder
*
* Registered by the UI layer (e.g. a DiskCacheDialog-based
* implementation), since the engine cannot show dialogs itself. Without
* a handler, the request is logged and skipped.
*/
using ShowDiskCacheSettingsHandler =
std::function<void(DiskCacheFolder *folder, QWidget *parent)>;
static void set_show_disk_cache_settings_handler(
ShowDiskCacheSettingsHandler handler);
void show_disk_cache_settings_dialog(DiskCacheFolder *folder, QWidget *parent);
void show_disk_cache_settings_dialog(const QString &path, QWidget *parent);
@@ -165,6 +180,8 @@ private:
static DiskManager *instance_;
static ShowDiskCacheSettingsHandler show_disk_cache_settings_handler_;
QVector<DiskCacheFolder *> open_folders_;
};
+2
View File
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/menu/factorymenu.cpp
widget/menu/factorymenu.h
widget/menu/menu.cpp
widget/menu/menu.h
widget/menu/menushared.cpp
+137
View File
@@ -0,0 +1,137 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "factorymenu.h"
#include <QCoreApplication>
namespace olive
{
Menu *create_node_menu(QWidget *parent, bool create_none_item,
Node::CategoryID restrict_to, uint64_t restrict_flags)
{
const QList<Node *> &library = NodeFactory::get_library();
Menu *menu = new Menu(parent);
menu->setToolTipsVisible(true);
for (int i = 0; i < library.size(); i++) {
Node *n = library.at(i);
if (restrict_to != Node::k_category_unknown &&
!n->category().contains(restrict_to)) {
// Skip this node
continue;
}
if (restrict_flags && !(n->get_flags() & restrict_flags)) {
continue;
}
if (n->get_flags() & Node::k_dont_show_in_create_menu) {
continue;
}
// Make sure nodes are up-to-date with the current translation
n->retranslate();
QString category_name = Node::get_category_name(
n->category().isEmpty() ? Node::k_category_unknown :
n->category().first());
// Find or create top-level category menu
Menu *top_menu = nullptr;
QList<QAction *> menu_actions = menu->actions();
foreach (QAction *action, menu_actions) {
if (action->menu() && action->menu()->title() == category_name) {
top_menu = static_cast<Menu *>(action->menu());
break;
}
}
if (!top_menu) {
top_menu = new Menu(category_name, menu);
menu->insert_alphabetically(top_menu);
}
// Determine final destination (support secondary grouping)
Menu *destination = top_menu;
QString sub = n->sub_category();
if (!sub.isEmpty() && n->category().contains(Node::k_category_open_fx)) {
QList<QAction *> sub_actions = top_menu->actions();
foreach (QAction *action, sub_actions) {
if (action->menu() && action->menu()->title() == sub) {
destination = static_cast<Menu *>(action->menu());
break;
}
}
if (destination == top_menu) {
destination = new Menu(sub, top_menu);
top_menu->insert_alphabetically(destination);
}
}
// Add entry to menu
QAction *a = destination->insert_alphabetically(n->name());
a->setData(i);
a->setToolTip(n->description());
}
if (create_none_item) {
QAction *none_item = new QAction(
QCoreApplication::translate("NodeFactory", "None"), menu);
none_item->setData(-1);
if (menu->actions().isEmpty()) {
menu->addAction(none_item);
} else {
QAction *separator = menu->insertSeparator(menu->actions().first());
menu->insertAction(separator, none_item);
}
}
return menu;
}
Node *create_node_from_menu_action(QAction *action)
{
int index = action->data().toInt();
if (index == -1) {
return nullptr;
}
return NodeFactory::get_library().at(index)->copy();
}
QString get_node_id_from_menu_action(QAction *action)
{
int index = action->data().toInt();
if (index == -1) {
return QString();
}
return NodeFactory::get_library().at(action->data().toInt())->id();
}
}
+62
View File
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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_FACTORYMENU_H
#define OAK_FACTORYMENU_H
#include <QAction>
#include "node/factory.h"
#include "widget/menu/menu.h"
namespace olive
{
/**
* @brief Create a menu of all available nodes, grouped by category
*
* UI-side counterpart of the node library: NodeFactory stays UI-independent
* while this builds an olive::Menu from NodeFactory::get_library(). Each
* leaf action carries the node's library index in its data, so it can be
* resolved back with create_node_from_menu_action() or
* get_node_id_from_menu_action().
*/
Menu *create_node_menu(QWidget *parent, bool create_none_item = false,
Node::CategoryID restrict_to = Node::k_category_unknown,
uint64_t restrict_flags = 0);
/**
* @brief Create a node from an action of a menu built by create_node_menu()
*
* Returns nullptr for the "None" item.
*/
Node *create_node_from_menu_action(QAction *action);
/**
* @brief Get the node ID from an action of a menu built by create_node_menu()
*
* Returns an empty string for the "None" item.
*/
QString get_node_id_from_menu_action(QAction *action);
}
#endif // OAK_FACTORYMENU_H
+3 -2
View File
@@ -27,6 +27,7 @@
#include "node/factory.h"
#include "ui/icons/icons.h"
#include "widget/menu/factorymenu.h"
#include "widget/menu/menu.h"
namespace olive
@@ -39,12 +40,12 @@ NodeComboBox::NodeComboBox(QWidget *parent)
void NodeComboBox::showPopup()
{
Menu *m = NodeFactory::create_menu(this, true);
Menu *m = create_node_menu(this, true);
QAction *selected = m->exec(parentWidget()->mapToGlobal(pos()));
if (selected) {
QString new_id = NodeFactory::GetIDFromMenuAction(selected);
QString new_id = get_node_id_from_menu_action(selected);
set_node_internal(new_id, true);
}
@@ -24,8 +24,8 @@
#include <QMessageBox>
#include "node/block/clip/clip.h"
#include "node/factory.h"
#include "node/nodeundo.h"
#include "widget/menu/factorymenu.h"
namespace olive
{
@@ -147,7 +147,7 @@ void NodeParamViewContext::add_effect_button_clicked()
}
Menu *m =
NodeFactory::create_menu(this, false, Node::k_category_unknown, flag);
create_node_menu(this, false, Node::k_category_unknown, flag);
connect(m, &Menu::triggered, this,
&NodeParamViewContext::add_effect_menu_item_triggered);
m->exec(QCursor::pos());
@@ -156,7 +156,7 @@ void NodeParamViewContext::add_effect_button_clicked()
void NodeParamViewContext::add_effect_menu_item_triggered(QAction *a)
{
Node *n = NodeFactory::CreateFromMenuAction(a);
Node *n = create_node_from_menu_action(a);
if (n) {
NodeInput new_node_input = n->get_effect_input();
+3 -3
View File
@@ -31,13 +31,13 @@
#include "node/audio/volume/volume.h"
#include "node/distort/transform/transformdistortnode.h"
#include "node/factory.h"
#include "node/group/group.h"
#include "node/nodeundo.h"
#include "node/project/serializer/serializer.h"
#include "panel/panelmanager.h"
#include "node/traverser.h"
#include "ui/icons/icons.h"
#include "widget/menu/factorymenu.h"
#include "widget/menu/menushared.h"
#include "widget/timebased/timebasedview.h"
@@ -936,7 +936,7 @@ void NodeView::show_context_menu(const QPoint &pos)
void NodeView::create_node_slot(QAction *action)
{
Node *new_node = NodeFactory::CreateFromMenuAction(action);
Node *new_node = create_node_from_menu_action(action);
if (new_node) {
NodeViewItem *new_item = new NodeViewItem(new_node, nullptr);
@@ -1381,7 +1381,7 @@ bool NodeView::get_assumed_position_for_selected_node(Node *node,
Menu *NodeView::create_add_menu(Menu *parent)
{
Menu *add_menu = NodeFactory::create_menu(parent);
Menu *add_menu = create_node_menu(parent);
add_menu->setTitle(tr("Add"));
connect(add_menu, &Menu::triggered, this, &NodeView::create_node_slot);
return add_menu;
+3 -3
View File
@@ -27,8 +27,8 @@
#include <QResizeEvent>
#include <QVariant>
#include "node/factory.h"
#include "ui/icons/icons.h"
#include "widget/menu/factorymenu.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
@@ -206,7 +206,7 @@ void Toolbar::add_button_clicked()
void Toolbar::transition_button_clicked()
{
Menu *m = NodeFactory::create_menu(this, false, Node::k_category_transition);
Menu *m = create_node_menu(this, false, Node::k_category_transition);
connect(m, &QMenu::triggered, this, &Toolbar::transition_menu_item_triggered);
@@ -217,7 +217,7 @@ void Toolbar::transition_button_clicked()
void Toolbar::transition_menu_item_triggered(QAction *a)
{
emit selected_transition_changed(NodeFactory::GetIDFromMenuAction(a));
emit selected_transition_changed(get_node_id_from_menu_action(a));
}
}
+8 -7
View File
@@ -31,6 +31,7 @@
#include "node/serializeddata.h"
#include "core.h"
#include "render/diskmanager.h"
#include "widget/menu/factorymenu.h"
#include "widget/menu/menu.h"
namespace
@@ -532,15 +533,15 @@ TEST(NodeFactory, CreateMenuWithNoneItem)
olive::NodeFactory::initialize();
std::unique_ptr<olive::Menu> menu(
olive::NodeFactory::create_menu(nullptr, true));
olive::create_node_menu(nullptr, true));
ASSERT_NE(menu, nullptr);
ASSERT_FALSE(menu->actions().isEmpty());
// The "None" item is inserted at the very top and maps to nothing
QAction *none_item = menu->actions().first();
EXPECT_EQ(none_item->data().toInt(), -1);
EXPECT_EQ(olive::NodeFactory::CreateFromMenuAction(none_item), nullptr);
EXPECT_TRUE(olive::NodeFactory::GetIDFromMenuAction(none_item).isEmpty());
EXPECT_EQ(olive::create_node_from_menu_action(none_item), nullptr);
EXPECT_TRUE(olive::get_node_id_from_menu_action(none_item).isEmpty());
// Leaf actions carry a library index that maps back to node ids
QList<QAction *> leaves;
@@ -552,10 +553,10 @@ TEST(NodeFactory, CreateMenuWithNoneItem)
if (leaf->data().toInt() < 0) {
continue;
}
const QString id = olive::NodeFactory::GetIDFromMenuAction(leaf);
const QString id = olive::get_node_id_from_menu_action(leaf);
EXPECT_FALSE(id.isEmpty());
std::unique_ptr<olive::Node> node(
olive::NodeFactory::CreateFromMenuAction(leaf));
olive::create_node_from_menu_action(leaf));
ASSERT_NE(node, nullptr);
EXPECT_EQ(node->id(), id);
++created;
@@ -569,7 +570,7 @@ TEST(NodeFactory, CreateMenuRestrictedToCategory)
{
olive::NodeFactory::initialize();
std::unique_ptr<olive::Menu> menu(olive::NodeFactory::create_menu(
std::unique_ptr<olive::Menu> menu(olive::create_node_menu(
nullptr, false, olive::Node::k_category_math));
ASSERT_NE(menu, nullptr);
@@ -579,7 +580,7 @@ TEST(NodeFactory, CreateMenuRestrictedToCategory)
for (QAction *leaf : leaves) {
std::unique_ptr<olive::Node> node(
olive::NodeFactory::CreateFromMenuAction(leaf));
olive::create_node_from_menu_action(leaf));
ASSERT_NE(node, nullptr);
EXPECT_TRUE(node->category().contains(olive::Node::k_category_math))
<< node->id().toStdString();