From e012f16083c37053227189d4aba6532b1b3c537a Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 14 May 2026 21:05:29 +0800 Subject: [PATCH] feat: group OpenFX plugins under dedicated "OpenFX" category with sub-groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All OpenFX plugins were previously hardcoded to return kCategoryUnknown, causing them to pile up under "Uncategorized" in the node creation menu. This commit introduces a two-level grouping system for OFX plugins: 1. Add new kCategoryOpenFX top-level category - Node::CategoryID enum extended with kCategoryOpenFX - PluginNode::Category() now returns {kCategoryOpenFX} - Node::GetCategoryName() returns "OpenFX" 2. Add secondary sub-grouping support - Node base class gains virtual SubCategory() method - PluginNode implements SubCategory() backed by sub_category_ member - sub_category_ is set in the constructor from the plugin's OFX context: Filter → "Filter" Generator → "Generator" Transition → "Transition" others → "General" 3. Update NodeFactory::CreateMenu() - When a node belongs to kCategoryOpenFX and provides a non-empty SubCategory(), creates a second-level submenu under "OpenFX" - Nodes without a sub-category are placed directly in the top menu Expected menu layout: OpenFX ├── Filter │ ├── ColorCorrect │ └── ... ├── Generator ├── Transition └── General All 4 test suites pass. --- app/node/factory.cpp | 31 +++++++++++++++++++++++-------- app/node/node.cpp | 2 ++ app/node/node.h | 7 +++++++ app/node/plugins/Plugin.cpp | 19 ++++++++++++++++++- app/node/plugins/Plugin.h | 4 ++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 3ecc06226..aea671386 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -128,25 +128,40 @@ Menu *NodeFactory::CreateMenu(QWidget *parent, bool create_none_item, // Make sure nodes are up-to-date with the current translation n->Retranslate(); - Menu *destination = nullptr; - QString category_name = Node::GetCategoryName( n->Category().isEmpty() ? Node::kCategoryUnknown : n->Category().first()); - // See if a menu with this category name already exists + // Find or create top-level category menu + Menu *top_menu = nullptr; QList menu_actions = menu->actions(); foreach (QAction *action, menu_actions) { if (action->menu() && action->menu()->title() == category_name) { - destination = static_cast(action->menu()); + top_menu = static_cast(action->menu()); break; } } + if (!top_menu) { + top_menu = new Menu(category_name, menu); + menu->InsertAlphabetically(top_menu); + } - // Create menu here if it doesn't exist - if (!destination) { - destination = new Menu(category_name, menu); - menu->InsertAlphabetically(destination); + // Determine final destination (support secondary grouping) + Menu *destination = top_menu; + QString sub = n->SubCategory(); + if (!sub.isEmpty() && + n->Category().contains(Node::kCategoryOpenFX)) { + QList sub_actions = top_menu->actions(); + foreach (QAction *action, sub_actions) { + if (action->menu() && action->menu()->title() == sub) { + destination = static_cast(action->menu()); + break; + } + } + if (destination == top_menu) { + destination = new Menu(sub, top_menu); + top_menu->InsertAlphabetically(destination); + } } // Add entry to menu diff --git a/app/node/node.cpp b/app/node/node.cpp index ab3eb7aa8..5f5865ba5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -2295,6 +2295,8 @@ QString Node::GetCategoryName(const CategoryID &c) return tr("Transition"); case kCategoryProject: return tr("Project"); + case kCategoryOpenFX: + return tr("OpenFX"); case kCategoryTime: return tr("Time"); case kCategoryUnknown: diff --git a/app/node/node.h b/app/node/node.h index 4431fd2df..c8a2cdd48 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -101,6 +101,7 @@ public: kCategoryTransition, kCategoryDistort, kCategoryProject, + kCategoryOpenFX, kCategoryCount }; @@ -177,6 +178,12 @@ public: */ virtual QVector Category() const = 0; + /** + * @brief Return a sub-category string for secondary grouping + * within the primary category (e.g. "Filter" under "OpenFX"). + */ + virtual QString SubCategory() const { return QString(); } + /** * @brief Return a description of this node's purpose (optional for subclassing, but recommended) * diff --git a/app/node/plugins/Plugin.cpp b/app/node/plugins/Plugin.cpp index 83737c423..041d0dab7 100644 --- a/app/node/plugins/Plugin.cpp +++ b/app/node/plugins/Plugin.cpp @@ -250,6 +250,18 @@ olive::plugin::PluginNode::PluginNode( OFX::Host::ImageEffect::Instance *plugin) { plugin_instance_=plugin; + + const std::string &ctx = plugin_instance_->getContext(); + if (ctx == kOfxImageEffectContextFilter) { + sub_category_ = tr("Filter"); + } else if (ctx == kOfxImageEffectContextGenerator) { + sub_category_ = tr("Generator"); + } else if (ctx == kOfxImageEffectContextTransition) { + sub_category_ = tr("Transition"); + } else { + sub_category_ = tr("General"); + } + bool has_texture_input = false; QHash group_labels; QHash page_labels; @@ -505,7 +517,12 @@ QString olive::plugin::PluginNode::Name() const QVector olive::plugin::PluginNode::Category() const { - return { olive::Node::kCategoryUnknown }; + return { olive::Node::kCategoryOpenFX }; +} + +QString olive::plugin::PluginNode::SubCategory() const +{ + return sub_category_; } QString olive::plugin::PluginNode::Description() const diff --git a/app/node/plugins/Plugin.h b/app/node/plugins/Plugin.h index e1e33aaa5..063c64c03 100644 --- a/app/node/plugins/Plugin.h +++ b/app/node/plugins/Plugin.h @@ -38,6 +38,7 @@ public: QString Name() const override; QString id() const override; QVector Category() const override; + QString SubCategory() const override; QString Description() const override; void AddPushButton(); @@ -76,6 +77,9 @@ public: */ virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const; +private: + QString sub_category_; + public slots: void pushButtonClicked(QString name);