Files
create/src/Gui/SDK/SDKRegistry.cpp
forbes 18532e3bd7 feat(sdk): add panel provider and theme engine to kcsdk (#352, #353)
- IPanelProvider: abstract interface for dock panels with PySide widget bridging
- PyIPanelProvider/PyProviderHolder: pybind11 trampoline + GIL-safe holder
- WidgetBridge: PySide QWidget → C++ QWidget* conversion via Shiboken
- SDKRegistry: panel registration, creation, and lifecycle management
- ThemeEngine: C++ singleton with minimal YAML parser, palette cache,
  getColor/allTokens/formatQss matching Python Palette API
- kcsdk bindings: DockArea, PanelPersistence enums, panel functions,
  theme_color, theme_tokens, format_qss, load_palette
- dock.py: kcsdk delegation with FreeCADGui fallback
- theme.py: kcsdk delegation with Python YAML fallback
2026-02-28 14:53:38 -06:00

256 lines
7.6 KiB
C++

// 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/>. *
* *
***************************************************************************/
#include "SDKRegistry.h"
#include "IPanelProvider.h"
#include <QString>
#include <QStringList>
#include <Base/Console.h>
#include <Gui/DockWindowManager.h>
#include <Gui/EditingContext.h>
namespace KCSDK
{
// -- Helpers: std ↔ Qt conversion (internal) --------------------------------
namespace
{
QString toQString(const std::string& s)
{
return QString::fromUtf8(s.data(), static_cast<int>(s.size()));
}
std::string fromQString(const QString& s)
{
QByteArray utf8 = s.toUtf8();
return std::string(utf8.constData(), utf8.size());
}
QStringList toQStringList(const std::vector<std::string>& v)
{
QStringList out;
out.reserve(static_cast<int>(v.size()));
for (const auto& s : v) {
out.append(toQString(s));
}
return out;
}
std::vector<std::string> fromQStringList(const QStringList& list)
{
std::vector<std::string> out;
out.reserve(list.size());
for (const auto& s : list) {
out.push_back(fromQString(s));
}
return out;
}
} // anonymous namespace
// -- Singleton --------------------------------------------------------------
SDKRegistry& SDKRegistry::instance()
{
static SDKRegistry reg;
return reg;
}
SDKRegistry::SDKRegistry()
{
Base::Console().log("KCSDK: registry initialized (API v%d)\n", API_VERSION_MAJOR);
}
SDKRegistry::~SDKRegistry() = default;
std::vector<std::string> SDKRegistry::available() const
{
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> result;
result.reserve(panels_.size());
for (const auto& [id, _] : panels_) {
result.push_back(id);
}
return result;
}
// -- Editing context API ----------------------------------------------------
void SDKRegistry::registerContext(const ContextDef& def)
{
Gui::ContextDefinition guiDef;
guiDef.id = toQString(def.id);
guiDef.labelTemplate = toQString(def.labelTemplate);
guiDef.color = toQString(def.color);
guiDef.toolbars = toQStringList(def.toolbars);
guiDef.priority = def.priority;
guiDef.match = def.match;
Gui::EditingContextResolver::instance()->registerContext(guiDef);
}
void SDKRegistry::unregisterContext(const std::string& id)
{
Gui::EditingContextResolver::instance()->unregisterContext(toQString(id));
}
void SDKRegistry::registerOverlay(const OverlayDef& def)
{
Gui::OverlayDefinition guiDef;
guiDef.id = toQString(def.id);
guiDef.toolbars = toQStringList(def.toolbars);
guiDef.match = def.match;
Gui::EditingContextResolver::instance()->registerOverlay(guiDef);
}
void SDKRegistry::unregisterOverlay(const std::string& id)
{
Gui::EditingContextResolver::instance()->unregisterOverlay(toQString(id));
}
void SDKRegistry::injectCommands(const std::string& contextId,
const std::string& toolbarName,
const std::vector<std::string>& commands)
{
Gui::EditingContextResolver::instance()->injectCommands(
toQString(contextId), toQString(toolbarName), toQStringList(commands));
}
ContextSnapshot SDKRegistry::currentContext() const
{
Gui::EditingContext ctx =
Gui::EditingContextResolver::instance()->currentContext();
ContextSnapshot snap;
snap.id = fromQString(ctx.id);
snap.label = fromQString(ctx.label);
snap.color = fromQString(ctx.color);
snap.toolbars = fromQStringList(ctx.toolbars);
snap.breadcrumb = fromQStringList(ctx.breadcrumb);
snap.breadcrumbColors = fromQStringList(ctx.breadcrumbColors);
return snap;
}
void SDKRegistry::refresh()
{
Gui::EditingContextResolver::instance()->refresh();
}
// -- Panel provider API -----------------------------------------------------
void SDKRegistry::registerPanel(std::unique_ptr<IPanelProvider> provider)
{
if (!provider) {
return;
}
std::string id = provider->id();
std::lock_guard<std::mutex> lock(mutex_);
panels_[id] = std::move(provider);
Base::Console().log("KCSDK: registered panel provider '%s'\n", id.c_str());
}
void SDKRegistry::unregisterPanel(const std::string& id)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = panels_.find(id);
if (it == panels_.end()) {
return;
}
// Remove the dock widget if it was created.
auto* dwm = Gui::DockWindowManager::instance();
if (dwm) {
dwm->removeDockWindow(id.c_str());
}
panels_.erase(it);
Base::Console().log("KCSDK: unregistered panel provider '%s'\n", id.c_str());
}
void SDKRegistry::createPanel(const std::string& id)
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = panels_.find(id);
if (it == panels_.end()) {
Base::Console().warning("KCSDK: no panel provider '%s' registered\n", id.c_str());
return;
}
auto* dwm = Gui::DockWindowManager::instance();
if (!dwm) {
return;
}
// Skip if already created.
if (dwm->getDockWindow(id.c_str())) {
return;
}
IPanelProvider* provider = it->second.get();
QWidget* widget = provider->create_widget();
if (!widget) {
Base::Console().warning("KCSDK: panel '%s' create_widget() returned null\n",
id.c_str());
return;
}
auto qtArea = static_cast<Qt::DockWidgetArea>(provider->preferred_area());
dwm->addDockWindow(id.c_str(), widget, qtArea);
}
void SDKRegistry::createAllPanels()
{
// Collect IDs under lock, then create outside to avoid recursive locking.
std::vector<std::string> ids;
{
std::lock_guard<std::mutex> lock(mutex_);
ids.reserve(panels_.size());
for (const auto& [id, _] : panels_) {
ids.push_back(id);
}
}
for (const auto& id : ids) {
createPanel(id);
}
}
std::vector<std::string> SDKRegistry::registeredPanels() const
{
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::string> result;
result.reserve(panels_.size());
for (const auto& [id, _] : panels_) {
result.push_back(id);
}
return result;
}
} // namespace KCSDK