- 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
256 lines
7.6 KiB
C++
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
|