feat(sdk): add IMenuProvider interface and register_command wrapper (#355)
All checks were successful
Build and Test / build (pull_request) Successful in 28m41s
All checks were successful
Build and Test / build (pull_request) Successful in 28m41s
IMenuProvider: declarative menu placement with optional context awareness. C++ interface with pybind11 bindings + GIL-safe holder. SDKMenuManipulator (shared WorkbenchManipulator) injects menu items on workbench switch, filtered by editing context when context_ids() is non-empty. register_command(): thin Python wrapper around FreeCADGui.addCommand() that standardizes the calling convention within the SDK contract. Python wrappers (kindred_sdk.register_menu, kindred_sdk.register_command) use kcsdk-first routing with FreeCADGui fallback.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# kindred-addon-sdk — stable API for Kindred Create addon integration
|
||||
|
||||
from kindred_sdk.command import register_command
|
||||
from kindred_sdk.compat import create_version, freecad_version
|
||||
from kindred_sdk.context import (
|
||||
current_context,
|
||||
@@ -11,6 +12,7 @@ from kindred_sdk.context import (
|
||||
unregister_overlay,
|
||||
)
|
||||
from kindred_sdk.dock import register_dock_panel
|
||||
from kindred_sdk.menu import register_menu
|
||||
from kindred_sdk.origin import register_origin, unregister_origin
|
||||
from kindred_sdk.theme import get_theme_tokens, load_palette
|
||||
from kindred_sdk.toolbar import register_toolbar
|
||||
@@ -18,6 +20,7 @@ from kindred_sdk.version import SDK_VERSION
|
||||
|
||||
__all__ = [
|
||||
"SDK_VERSION",
|
||||
"register_command",
|
||||
"register_context",
|
||||
"unregister_context",
|
||||
"register_overlay",
|
||||
@@ -27,6 +30,7 @@ __all__ = [
|
||||
"refresh_context",
|
||||
"get_theme_tokens",
|
||||
"load_palette",
|
||||
"register_menu",
|
||||
"register_toolbar",
|
||||
"register_origin",
|
||||
"unregister_origin",
|
||||
|
||||
58
mods/sdk/kindred_sdk/command.py
Normal file
58
mods/sdk/kindred_sdk/command.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Command registration wrapper.
|
||||
|
||||
Provides a standardized SDK entry point for registering FreeCAD commands.
|
||||
This is a thin wrapper around ``FreeCADGui.addCommand()`` — no C++ interface
|
||||
is needed since FreeCAD's command system is already stable and well-known.
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
|
||||
|
||||
def register_command(name, activated, resources, is_active=None):
|
||||
"""Register a FreeCAD command through the SDK.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Command name (e.g. ``"MyAddon_DoThing"``).
|
||||
activated : callable
|
||||
Called when command is triggered. Receives an optional ``int``
|
||||
index argument (for group commands).
|
||||
resources : dict
|
||||
Command resources passed to ``GetResources()``. Common keys:
|
||||
``MenuText``, ``ToolTip``, ``Pixmap``, ``Accel``.
|
||||
is_active : callable, optional
|
||||
Zero-arg callable returning ``True`` when the command should be
|
||||
enabled. Default: always active.
|
||||
"""
|
||||
if not callable(activated):
|
||||
raise TypeError("activated must be callable")
|
||||
if not isinstance(resources, dict):
|
||||
raise TypeError("resources must be a dict")
|
||||
if is_active is not None and not callable(is_active):
|
||||
raise TypeError("is_active must be callable or None")
|
||||
|
||||
_resources = dict(resources)
|
||||
_activated = activated
|
||||
_is_active = is_active
|
||||
|
||||
class _SDKCommand:
|
||||
def GetResources(self):
|
||||
return _resources
|
||||
|
||||
def Activated(self, index=0):
|
||||
_activated()
|
||||
|
||||
def IsActive(self):
|
||||
if _is_active is not None:
|
||||
return bool(_is_active())
|
||||
return True
|
||||
|
||||
try:
|
||||
import FreeCADGui
|
||||
|
||||
FreeCADGui.addCommand(name, _SDKCommand())
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"kindred_sdk: Failed to register command '{name}': {e}\n"
|
||||
)
|
||||
53
mods/sdk/kindred_sdk/menu.py
Normal file
53
mods/sdk/kindred_sdk/menu.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""Menu provider registration.
|
||||
|
||||
Wraps the C++ ``kcsdk.register_menu()`` API with a Python fallback
|
||||
that installs a WorkbenchManipulator to inject menu items.
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
|
||||
|
||||
def _kcsdk_available():
|
||||
"""Return the kcsdk module if available, else None."""
|
||||
try:
|
||||
import kcsdk
|
||||
|
||||
return kcsdk
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
|
||||
def register_menu(provider):
|
||||
"""Register a menu provider for declarative menu placement.
|
||||
|
||||
When the C++ ``kcsdk`` module is available, delegates to its
|
||||
``register_menu()`` which installs a shared WorkbenchManipulator
|
||||
that injects items at the specified menu path.
|
||||
|
||||
Falls back to installing a Python WorkbenchManipulator directly.
|
||||
"""
|
||||
kcsdk = _kcsdk_available()
|
||||
if kcsdk is not None:
|
||||
kcsdk.register_menu(provider)
|
||||
return
|
||||
|
||||
# Fallback: extract data and install a Python manipulator.
|
||||
menu_path = provider.menu_path()
|
||||
items = provider.items()
|
||||
|
||||
try:
|
||||
import FreeCADGui
|
||||
|
||||
class _SDKMenuManipulator:
|
||||
def modifyMenuBar(self, menuBar):
|
||||
menu_items = [
|
||||
"Separator" if item == "separator" else item for item in items
|
||||
]
|
||||
FreeCADGui.addCommand # Just to verify we're in GUI mode
|
||||
menuBar.appendMenu(menu_path, menu_items)
|
||||
|
||||
FreeCADGui.addWorkbenchManipulator(_SDKMenuManipulator())
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
f"kindred_sdk: Failed to register menu '{provider.id()}': {e}\n"
|
||||
)
|
||||
@@ -3,6 +3,7 @@
|
||||
set(KCSDK_SRCS
|
||||
KCSDKGlobal.h
|
||||
Types.h
|
||||
IMenuProvider.h
|
||||
IPanelProvider.h
|
||||
IToolbarProvider.h
|
||||
WidgetBridge.h
|
||||
|
||||
67
src/Gui/SDK/IMenuProvider.h
Normal file
67
src/Gui/SDK/IMenuProvider.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef KCSDK_IMENUPROVIDER_H
|
||||
#define KCSDK_IMENUPROVIDER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "KCSDKGlobal.h"
|
||||
|
||||
namespace KCSDK
|
||||
{
|
||||
|
||||
/// Abstract interface for addon-provided menu declarations.
|
||||
///
|
||||
/// Addons implement this interface to declaratively register menu items.
|
||||
/// On workbench switch the SDK's shared WorkbenchManipulator injects the
|
||||
/// declared items at the specified menu path. If context_ids() is
|
||||
/// non-empty, items are only injected when the current editing context
|
||||
/// matches.
|
||||
class KCSDKExport IMenuProvider
|
||||
{
|
||||
public:
|
||||
virtual ~IMenuProvider() = default;
|
||||
|
||||
/// Unique provider identifier (e.g. "silo.menu", "gears.tools").
|
||||
virtual std::string id() const = 0;
|
||||
|
||||
/// Menu path where items are inserted.
|
||||
/// Use "/" to separate levels (e.g. "&Tools" or "&Edit/Custom").
|
||||
virtual std::string menu_path() const = 0;
|
||||
|
||||
/// Command names to add as menu items. Use "separator" for dividers.
|
||||
virtual std::vector<std::string> items() const = 0;
|
||||
|
||||
/// Editing context IDs this menu applies to.
|
||||
/// If empty (default), items are shown in all contexts.
|
||||
virtual std::vector<std::string> context_ids() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace KCSDK
|
||||
|
||||
#endif // KCSDK_IMENUPROVIDER_H
|
||||
@@ -22,6 +22,7 @@
|
||||
***************************************************************************/
|
||||
|
||||
#include "SDKRegistry.h"
|
||||
#include "IMenuProvider.h"
|
||||
#include "IPanelProvider.h"
|
||||
#include "IToolbarProvider.h"
|
||||
|
||||
@@ -31,6 +32,8 @@
|
||||
#include <Base/Console.h>
|
||||
#include <Gui/DockWindowManager.h>
|
||||
#include <Gui/EditingContext.h>
|
||||
#include <Gui/MenuManager.h>
|
||||
#include <Gui/WorkbenchManipulator.h>
|
||||
|
||||
namespace KCSDK
|
||||
{
|
||||
@@ -92,13 +95,16 @@ std::vector<std::string> SDKRegistry::available() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> result;
|
||||
result.reserve(panels_.size() + toolbars_.size());
|
||||
result.reserve(panels_.size() + toolbars_.size() + menus_.size());
|
||||
for (const auto& [id, _] : panels_) {
|
||||
result.push_back(id);
|
||||
}
|
||||
for (const auto& [id, _] : toolbars_) {
|
||||
result.push_back(id);
|
||||
}
|
||||
for (const auto& [id, _] : menus_) {
|
||||
result.push_back(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -305,4 +311,158 @@ std::vector<std::string> SDKRegistry::registeredToolbars() const
|
||||
return result;
|
||||
}
|
||||
|
||||
// -- Menu provider API ------------------------------------------------------
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Resolve a menu path like "&Tools" or "&Edit/Custom" into a MenuItem,
|
||||
/// creating intermediate nodes as needed.
|
||||
Gui::MenuItem* findOrCreateMenuPath(Gui::MenuItem* root, const std::string& path)
|
||||
{
|
||||
Gui::MenuItem* current = root;
|
||||
|
||||
// Split on '/'
|
||||
std::string remaining = path;
|
||||
while (!remaining.empty()) {
|
||||
std::string segment;
|
||||
auto pos = remaining.find('/');
|
||||
if (pos != std::string::npos) {
|
||||
segment = remaining.substr(0, pos);
|
||||
remaining = remaining.substr(pos + 1);
|
||||
}
|
||||
else {
|
||||
segment = remaining;
|
||||
remaining.clear();
|
||||
}
|
||||
|
||||
if (segment.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Gui::MenuItem* child = current->findItem(segment);
|
||||
if (!child) {
|
||||
child = new Gui::MenuItem(current);
|
||||
child->setCommand(segment);
|
||||
}
|
||||
current = child;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/// Shared WorkbenchManipulator that injects menu items from all
|
||||
/// registered IMenuProvider instances.
|
||||
class SDKMenuManipulator : public Gui::WorkbenchManipulator
|
||||
{
|
||||
public:
|
||||
explicit SDKMenuManipulator(SDKRegistry& registry)
|
||||
: registry_(registry)
|
||||
{}
|
||||
|
||||
protected:
|
||||
void modifyMenuBar(Gui::MenuItem* menuBar) override
|
||||
{
|
||||
// Snapshot providers under lock.
|
||||
std::vector<IMenuProvider*> providers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(registry_.mutex_);
|
||||
providers.reserve(registry_.menus_.size());
|
||||
for (const auto& [_, p] : registry_.menus_) {
|
||||
providers.push_back(p.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Get current editing context for filtering.
|
||||
std::string currentCtxId;
|
||||
auto* resolver = Gui::EditingContextResolver::instance();
|
||||
if (resolver) {
|
||||
currentCtxId = fromQString(resolver->currentContext().id);
|
||||
}
|
||||
|
||||
for (auto* provider : providers) {
|
||||
// Check context filter.
|
||||
auto ctxIds = provider->context_ids();
|
||||
if (!ctxIds.empty()) {
|
||||
bool matches = false;
|
||||
for (const auto& cid : ctxIds) {
|
||||
if (cid == currentCtxId) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve menu path and append items.
|
||||
std::string path = provider->menu_path();
|
||||
Gui::MenuItem* target = findOrCreateMenuPath(menuBar, path);
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& item : provider->items()) {
|
||||
auto* mi = new Gui::MenuItem(target);
|
||||
mi->setCommand(item == "separator" ? "Separator" : item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SDKRegistry& registry_;
|
||||
};
|
||||
|
||||
void SDKRegistry::ensureMenuManipulator()
|
||||
{
|
||||
if (!menuManipulator_) {
|
||||
menuManipulator_ = std::make_shared<SDKMenuManipulator>(*this);
|
||||
Gui::WorkbenchManipulator::installManipulator(menuManipulator_);
|
||||
}
|
||||
}
|
||||
|
||||
void SDKRegistry::registerMenu(std::unique_ptr<IMenuProvider> provider)
|
||||
{
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
std::string id = provider->id();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
menus_[id] = std::move(provider);
|
||||
}
|
||||
|
||||
ensureMenuManipulator();
|
||||
|
||||
Base::Console().log("KCSDK: registered menu provider '%s'\n", id.c_str());
|
||||
}
|
||||
|
||||
void SDKRegistry::unregisterMenu(const std::string& id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = menus_.find(id);
|
||||
if (it == menus_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
menus_.erase(it);
|
||||
Base::Console().log("KCSDK: unregistered menu provider '%s'\n", id.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> SDKRegistry::registeredMenus() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> result;
|
||||
result.reserve(menus_.size());
|
||||
for (const auto& [id, _] : menus_) {
|
||||
result.push_back(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace KCSDK
|
||||
|
||||
@@ -33,11 +33,17 @@
|
||||
#include "KCSDKGlobal.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
class WorkbenchManipulator;
|
||||
}
|
||||
|
||||
namespace KCSDK
|
||||
{
|
||||
|
||||
class IPanelProvider;
|
||||
class IToolbarProvider;
|
||||
class IMenuProvider;
|
||||
|
||||
/// Current KCSDK API major version. Addons should check this at load time.
|
||||
constexpr int API_VERSION_MAJOR = 1;
|
||||
@@ -115,6 +121,18 @@ public:
|
||||
/// Return IDs of all registered toolbar providers.
|
||||
std::vector<std::string> registeredToolbars() const;
|
||||
|
||||
// -- Menu provider API -------------------------------------------------
|
||||
|
||||
/// Register a menu provider. Ownership transfers to the registry.
|
||||
/// Items are injected into the menu bar on each workbench switch.
|
||||
void registerMenu(std::unique_ptr<IMenuProvider> provider);
|
||||
|
||||
/// Remove a registered menu provider.
|
||||
void unregisterMenu(const std::string& id);
|
||||
|
||||
/// Return IDs of all registered menu providers.
|
||||
std::vector<std::string> registeredMenus() const;
|
||||
|
||||
private:
|
||||
SDKRegistry();
|
||||
|
||||
@@ -123,9 +141,15 @@ private:
|
||||
SDKRegistry(SDKRegistry&&) = delete;
|
||||
SDKRegistry& operator=(SDKRegistry&&) = delete;
|
||||
|
||||
friend class SDKMenuManipulator;
|
||||
|
||||
void ensureMenuManipulator();
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, std::unique_ptr<IPanelProvider>> panels_;
|
||||
std::unordered_map<std::string, std::unique_ptr<IToolbarProvider>> toolbars_;
|
||||
std::unordered_map<std::string, std::unique_ptr<IMenuProvider>> menus_;
|
||||
std::shared_ptr<Gui::WorkbenchManipulator> menuManipulator_;
|
||||
};
|
||||
|
||||
} // namespace KCSDK
|
||||
|
||||
@@ -4,6 +4,8 @@ set(KCSDKPy_SRCS
|
||||
kcsdk_py.cpp
|
||||
PyIPanelProvider.h
|
||||
PyProviderHolder.h
|
||||
PyIMenuProvider.h
|
||||
PyMenuHolder.h
|
||||
PyIToolbarProvider.h
|
||||
PyToolbarHolder.h
|
||||
)
|
||||
|
||||
69
src/Gui/SDK/bindings/PyIMenuProvider.h
Normal file
69
src/Gui/SDK/bindings/PyIMenuProvider.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef KCSDK_PYIMENUPROVIDER_H
|
||||
#define KCSDK_PYIMENUPROVIDER_H
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <Gui/SDK/IMenuProvider.h>
|
||||
|
||||
namespace KCSDK
|
||||
{
|
||||
|
||||
/// pybind11 trampoline class for IMenuProvider.
|
||||
/// Enables Python subclasses that override virtual methods.
|
||||
class PyIMenuProvider : public IMenuProvider
|
||||
{
|
||||
public:
|
||||
using IMenuProvider::IMenuProvider;
|
||||
|
||||
// ── Pure virtuals ──────────────────────────────────────────────
|
||||
|
||||
std::string id() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::string, IMenuProvider, id);
|
||||
}
|
||||
|
||||
std::string menu_path() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::string, IMenuProvider, menu_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> items() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE_PURE(std::vector<std::string>, IMenuProvider, items);
|
||||
}
|
||||
|
||||
// ── Virtuals with defaults ─────────────────────────────────────
|
||||
|
||||
std::vector<std::string> context_ids() const override
|
||||
{
|
||||
PYBIND11_OVERRIDE(std::vector<std::string>, IMenuProvider, context_ids);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace KCSDK
|
||||
|
||||
#endif // KCSDK_PYIMENUPROVIDER_H
|
||||
81
src/Gui/SDK/bindings/PyMenuHolder.h
Normal file
81
src/Gui/SDK/bindings/PyMenuHolder.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD 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 *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef KCSDK_PYMENUHOLDER_H
|
||||
#define KCSDK_PYMENUHOLDER_H
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <Gui/SDK/IMenuProvider.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace KCSDK
|
||||
{
|
||||
|
||||
/// GIL-safe forwarding wrapper that holds a Python IMenuProvider instance.
|
||||
///
|
||||
/// Stores the py::object to prevent garbage collection. Acquires the GIL
|
||||
/// before every call into Python. All methods return std types so no
|
||||
/// Qt/PySide bridging is needed.
|
||||
class PyMenuHolder : public IMenuProvider
|
||||
{
|
||||
public:
|
||||
explicit PyMenuHolder(py::object obj)
|
||||
: obj_(std::move(obj))
|
||||
, provider_(obj_.cast<IMenuProvider*>())
|
||||
{}
|
||||
|
||||
std::string id() const override
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
return provider_->id();
|
||||
}
|
||||
|
||||
std::string menu_path() const override
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
return provider_->menu_path();
|
||||
}
|
||||
|
||||
std::vector<std::string> items() const override
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
return provider_->items();
|
||||
}
|
||||
|
||||
std::vector<std::string> context_ids() const override
|
||||
{
|
||||
py::gil_scoped_acquire gil;
|
||||
return provider_->context_ids();
|
||||
}
|
||||
|
||||
private:
|
||||
py::object obj_; ///< Prevents Python GC — keeps reference alive.
|
||||
IMenuProvider* provider_; ///< Raw pointer into trampoline inside obj_.
|
||||
};
|
||||
|
||||
} // namespace KCSDK
|
||||
|
||||
#endif // KCSDK_PYMENUHOLDER_H
|
||||
@@ -25,14 +25,17 @@
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/functional.h>
|
||||
|
||||
#include <Gui/SDK/IMenuProvider.h>
|
||||
#include <Gui/SDK/IPanelProvider.h>
|
||||
#include <Gui/SDK/IToolbarProvider.h>
|
||||
#include <Gui/SDK/SDKRegistry.h>
|
||||
#include <Gui/SDK/ThemeEngine.h>
|
||||
#include <Gui/SDK/Types.h>
|
||||
|
||||
#include "PyIMenuProvider.h"
|
||||
#include "PyIPanelProvider.h"
|
||||
#include "PyIToolbarProvider.h"
|
||||
#include "PyMenuHolder.h"
|
||||
#include "PyProviderHolder.h"
|
||||
#include "PyToolbarHolder.h"
|
||||
|
||||
@@ -284,6 +287,41 @@ PYBIND11_MODULE(kcsdk, m)
|
||||
},
|
||||
"Return IDs of all registered toolbar providers.");
|
||||
|
||||
// -- Menu provider API --------------------------------------------------
|
||||
|
||||
py::class_<IMenuProvider, PyIMenuProvider>(m, "IMenuProvider")
|
||||
.def(py::init<>())
|
||||
.def("id", &IMenuProvider::id)
|
||||
.def("menu_path", &IMenuProvider::menu_path)
|
||||
.def("items", &IMenuProvider::items)
|
||||
.def("context_ids", &IMenuProvider::context_ids);
|
||||
|
||||
m.def("register_menu",
|
||||
[](py::object provider) {
|
||||
auto holder = std::make_unique<PyMenuHolder>(std::move(provider));
|
||||
SDKRegistry::instance().registerMenu(std::move(holder));
|
||||
},
|
||||
py::arg("provider"),
|
||||
"Register a menu provider for declarative menu placement.\n\n"
|
||||
"Parameters\n"
|
||||
"----------\n"
|
||||
"provider : IMenuProvider\n"
|
||||
" Menu provider implementing id(), menu_path(), items().\n"
|
||||
" Optionally override context_ids() to limit to specific contexts.");
|
||||
|
||||
m.def("unregister_menu",
|
||||
[](const std::string& id) {
|
||||
SDKRegistry::instance().unregisterMenu(id);
|
||||
},
|
||||
py::arg("id"),
|
||||
"Remove a registered menu provider.");
|
||||
|
||||
m.def("registered_menus",
|
||||
[]() {
|
||||
return SDKRegistry::instance().registeredMenus();
|
||||
},
|
||||
"Return IDs of all registered menu providers.");
|
||||
|
||||
// -- Theme engine API ---------------------------------------------------
|
||||
|
||||
m.def("theme_color",
|
||||
|
||||
Reference in New Issue
Block a user