Some checks failed
Build and Test / build (pull_request) Has been cancelled
When double-clicking a PartDesign Body inside an Assembly, the editing
context resolver failed to show PartDesign toolbars or a proper
breadcrumb. Three root causes:
1. Priority preemption: assembly.edit (priority 90) always matched
because the Assembly VP remained in edit mode, blocking all
PartDesign contexts (priorities 30-40).
2. Wrong active-object key: PartDesign matchers only queried the
"part" active object, but ViewProviderBody::toggleActiveBody()
sets "part" to the containing App::Part (which is the Assembly
itself). The Body is set under "pdbody", which was never queried.
3. Missing refresh trigger: ActiveObjectList::setObject() fires
Document::signalActivatedViewProvider, but EditingContextResolver
had no connection to it, so setActiveObject("pdbody", body) never
triggered a context re-resolve.
Changes:
- Forward signalActivatedViewProvider from Gui::Document to
Gui::Application (same pattern as signalInEdit/signalResetEdit)
- Connect EditingContextResolver to the new application-level signal
- Add getActivePdBodyObject() helper querying the "pdbody" key
- Add partdesign.in_assembly context (priority 95) that matches when
a Body is active pdbody while an Assembly is in edit
- Update partdesign.body and partdesign.feature matchers to check
pdbody before falling back to the part key
- Add Assembly > Body breadcrumb with Blue > Mauve coloring
- Update label resolution to prefer pdbody name
765 lines
25 KiB
C++
765 lines
25 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* Copyright (c) 2025 Kindred Systems *
|
|
* *
|
|
* This file is part of Kindred Create. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "EditingContext.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <App/Document.h>
|
|
#include <App/DocumentObject.h>
|
|
#include <Base/Type.h>
|
|
|
|
#include "Application.h"
|
|
#include "Document.h"
|
|
#include "MDIView.h"
|
|
#include "ToolBarManager.h"
|
|
#include "ViewProvider.h"
|
|
#include "ViewProviderDocumentObject.h"
|
|
#include "WorkbenchManager.h"
|
|
|
|
using namespace Gui;
|
|
|
|
// Catppuccin Mocha palette
|
|
namespace CatppuccinMocha
|
|
{
|
|
constexpr const char* Surface0 = "#313244";
|
|
constexpr const char* Surface1 = "#45475a";
|
|
constexpr const char* Mauve = "#cba6f7";
|
|
constexpr const char* Green = "#a6e3a1";
|
|
constexpr const char* Blue = "#89b4fa";
|
|
constexpr const char* Yellow = "#f9e2af";
|
|
constexpr const char* Teal = "#94e2d5";
|
|
constexpr const char* Red = "#f38ba8";
|
|
constexpr const char* Peach = "#fab387";
|
|
constexpr const char* Text = "#cdd6f4";
|
|
} // namespace CatppuccinMocha
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Private data
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct EditingContextResolver::Private
|
|
{
|
|
QList<ContextDefinition> contexts; // sorted by descending priority
|
|
QList<OverlayDefinition> overlays;
|
|
EditingContext current;
|
|
|
|
// Additional commands injected into context toolbars
|
|
// Key: contextId -> toolbarName -> list of command names
|
|
QMap<QString, QMap<QString, QStringList>> injections;
|
|
|
|
void sortContexts()
|
|
{
|
|
std::sort(
|
|
contexts.begin(),
|
|
contexts.end(),
|
|
[](const ContextDefinition& a, const ContextDefinition& b) {
|
|
return a.priority > b.priority;
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Singleton
|
|
// ---------------------------------------------------------------------------
|
|
|
|
EditingContextResolver* EditingContextResolver::_instance = nullptr;
|
|
|
|
EditingContextResolver* EditingContextResolver::instance()
|
|
{
|
|
if (!_instance) {
|
|
_instance = new EditingContextResolver();
|
|
}
|
|
return _instance;
|
|
}
|
|
|
|
void EditingContextResolver::destruct()
|
|
{
|
|
delete _instance;
|
|
_instance = nullptr;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Construction
|
|
// ---------------------------------------------------------------------------
|
|
|
|
EditingContextResolver::EditingContextResolver()
|
|
: QObject(nullptr)
|
|
, d(new Private)
|
|
{
|
|
registerBuiltinContexts();
|
|
|
|
// Connect to application signals
|
|
auto& app = *Application::Instance;
|
|
app.signalInEdit.connect([this](const ViewProviderDocumentObject& vp) { onInEdit(vp); });
|
|
app.signalResetEdit.connect([this](const ViewProviderDocumentObject& vp) { onResetEdit(vp); });
|
|
app.signalActiveDocument.connect([this](const Document& doc) { onActiveDocument(doc); });
|
|
app.signalActivateView.connect([this](const MDIView* view) { onActivateView(view); });
|
|
app.signalActivateWorkbench.connect([this](const char*) { refresh(); });
|
|
app.signalActivatedViewProvider.connect(
|
|
[this](const ViewProviderDocumentObject*, const char*) { refresh(); }
|
|
);
|
|
}
|
|
|
|
EditingContextResolver::~EditingContextResolver()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: check if an App::DocumentObject's type name matches (by string)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static bool objectIsDerivedFrom(App::DocumentObject* obj, const char* typeName)
|
|
{
|
|
if (!obj) {
|
|
return false;
|
|
}
|
|
Base::Type target = Base::Type::fromName(typeName);
|
|
if (target.isBad()) {
|
|
return false;
|
|
}
|
|
return obj->getTypeId().isDerivedFrom(target);
|
|
}
|
|
|
|
static bool vpObjectIsDerivedFrom(ViewProvider* vp, const char* typeName)
|
|
{
|
|
auto* vpd = dynamic_cast<ViewProviderDocumentObject*>(vp);
|
|
if (!vpd) {
|
|
return false;
|
|
}
|
|
return objectIsDerivedFrom(vpd->getObject(), typeName);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: get the active "part" object from the active view
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static App::DocumentObject* getActivePartObject()
|
|
{
|
|
auto* guiDoc = Application::Instance->activeDocument();
|
|
if (!guiDoc) {
|
|
return nullptr;
|
|
}
|
|
auto* view = guiDoc->getActiveView();
|
|
if (!view) {
|
|
return nullptr;
|
|
}
|
|
return view->getActiveObject<App::DocumentObject*>("part");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: get the active "pdbody" object from the active view
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static App::DocumentObject* getActivePdBodyObject()
|
|
{
|
|
auto* guiDoc = Application::Instance->activeDocument();
|
|
if (!guiDoc) {
|
|
return nullptr;
|
|
}
|
|
auto* view = guiDoc->getActiveView();
|
|
if (!view) {
|
|
return nullptr;
|
|
}
|
|
return view->getActiveObject<App::DocumentObject*>("pdbody");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: get the label of the active "part" object
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static QString getActivePartLabel()
|
|
{
|
|
auto* obj = getActivePartObject();
|
|
if (!obj) {
|
|
return {};
|
|
}
|
|
return QString::fromUtf8(obj->Label.getValue());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper: get label of the object currently in edit
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static QString getInEditLabel()
|
|
{
|
|
auto* guiDoc = Application::Instance->activeDocument();
|
|
if (!guiDoc) {
|
|
return {};
|
|
}
|
|
auto* vp = guiDoc->getInEdit();
|
|
if (!vp) {
|
|
return {};
|
|
}
|
|
auto* vpd = dynamic_cast<ViewProviderDocumentObject*>(vp);
|
|
if (!vpd || !vpd->getObject()) {
|
|
return {};
|
|
}
|
|
return QString::fromUtf8(vpd->getObject()->Label.getValue());
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Built-in context registrations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void EditingContextResolver::registerBuiltinContexts()
|
|
{
|
|
// --- PartDesign body active inside an assembly (supersedes assembly.edit) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("partdesign.in_assembly"),
|
|
/*.labelTemplate =*/QStringLiteral("Body: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Mauve),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Part Design Helper Features"),
|
|
QStringLiteral("Part Design Modeling Features"),
|
|
QStringLiteral("Part Design Dress-Up Features"),
|
|
QStringLiteral("Part Design Transformation Features"),
|
|
QStringLiteral("Sketcher")},
|
|
/*.priority =*/95,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* body = getActivePdBodyObject();
|
|
if (!body || !objectIsDerivedFrom(body, "PartDesign::Body")) {
|
|
return false;
|
|
}
|
|
// Only match when we're inside an assembly edit session
|
|
auto* doc = Application::Instance->activeDocument();
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
auto* vp = doc->getInEdit();
|
|
return vp && vpObjectIsDerivedFrom(vp, "Assembly::AssemblyObject");
|
|
},
|
|
});
|
|
|
|
// --- Sketcher edit (highest priority — VP in edit) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("sketcher.edit"),
|
|
/*.labelTemplate =*/QStringLiteral("Sketcher: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Green),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Edit Mode"),
|
|
QStringLiteral("Geometries"),
|
|
QStringLiteral("Constraints"),
|
|
QStringLiteral("Sketcher Tools"),
|
|
QStringLiteral("B-Spline Tools"),
|
|
QStringLiteral("Visual Helpers")},
|
|
/*.priority =*/90,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* doc = Application::Instance->activeDocument();
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
auto* vp = doc->getInEdit();
|
|
return vp && vpObjectIsDerivedFrom(vp, "Sketcher::SketchObject");
|
|
},
|
|
});
|
|
|
|
// --- Assembly edit (VP in edit) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("assembly.edit"),
|
|
/*.labelTemplate =*/QStringLiteral("Assembly: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Blue),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Assembly"),
|
|
QStringLiteral("Assembly Joints"),
|
|
QStringLiteral("Assembly Management")},
|
|
/*.priority =*/90,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* doc = Application::Instance->activeDocument();
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
auto* vp = doc->getInEdit();
|
|
return vp && vpObjectIsDerivedFrom(vp, "Assembly::AssemblyObject");
|
|
},
|
|
});
|
|
|
|
// --- PartDesign with features (active body has children) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("partdesign.feature"),
|
|
/*.labelTemplate =*/QStringLiteral("Body: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Mauve),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Part Design Helper Features"),
|
|
QStringLiteral("Part Design Modeling Features"),
|
|
QStringLiteral("Part Design Dress-Up Features"),
|
|
QStringLiteral("Part Design Transformation Features"),
|
|
QStringLiteral("Sketcher")},
|
|
/*.priority =*/40,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* obj = getActivePdBodyObject();
|
|
if (!obj) {
|
|
obj = getActivePartObject();
|
|
}
|
|
if (!obj || !objectIsDerivedFrom(obj, "PartDesign::Body")) {
|
|
return false;
|
|
}
|
|
// Body has at least one child beyond the origin
|
|
auto children = obj->getOutList();
|
|
for (auto* child : children) {
|
|
if (child && !objectIsDerivedFrom(child, "App::Origin")) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
});
|
|
|
|
// --- PartDesign body (active body, empty or origin-only) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("partdesign.body"),
|
|
/*.labelTemplate =*/QStringLiteral("Body: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Mauve),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Part Design Helper Features"),
|
|
QStringLiteral("Part Design Modeling Features"),
|
|
QStringLiteral("Part Design Dress-Up Features"),
|
|
QStringLiteral("Part Design Transformation Features"),
|
|
QStringLiteral("Sketcher")},
|
|
/*.priority =*/30,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* obj = getActivePdBodyObject();
|
|
if (!obj) {
|
|
obj = getActivePartObject();
|
|
}
|
|
return obj && objectIsDerivedFrom(obj, "PartDesign::Body");
|
|
},
|
|
});
|
|
|
|
// --- Assembly idle (assembly exists, active, but not in edit) ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("assembly.idle"),
|
|
/*.labelTemplate =*/QStringLiteral("Assembly: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Blue),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Assembly"),
|
|
QStringLiteral("Assembly Joints"),
|
|
QStringLiteral("Assembly Management")},
|
|
/*.priority =*/30,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* obj = getActivePartObject();
|
|
return obj && objectIsDerivedFrom(obj, "Assembly::AssemblyObject");
|
|
},
|
|
});
|
|
|
|
// --- Spreadsheet ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("spreadsheet"),
|
|
/*.labelTemplate =*/QStringLiteral("Spreadsheet: {name}"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Yellow),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Spreadsheet")},
|
|
/*.priority =*/30,
|
|
/*.match =*/
|
|
[]() {
|
|
auto* obj = getActivePartObject();
|
|
return obj && objectIsDerivedFrom(obj, "Spreadsheet::Sheet");
|
|
},
|
|
});
|
|
|
|
// --- Workbench-level fallbacks (priority 20) ---
|
|
// Show basic workbench toolbars when the workbench is active but no
|
|
// specific editing context matches (e.g. no Body selected in PartDesign).
|
|
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("partdesign.workbench"),
|
|
/*.labelTemplate =*/QStringLiteral("Part Design"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Mauve),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Part Design Helper Features"),
|
|
QStringLiteral("Part Design Modeling Features"),
|
|
QStringLiteral("Part Design Dress-Up Features"),
|
|
QStringLiteral("Part Design Transformation Features"),
|
|
QStringLiteral("Sketcher")},
|
|
/*.priority =*/20,
|
|
/*.match =*/
|
|
[]() {
|
|
return WorkbenchManager::instance()->activeName() == "PartDesignWorkbench";
|
|
},
|
|
});
|
|
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("sketcher.workbench"),
|
|
/*.labelTemplate =*/QStringLiteral("Sketcher"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Green),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Sketcher"),
|
|
QStringLiteral("Sketcher Tools"),
|
|
QStringLiteral("Geometries"),
|
|
QStringLiteral("Constraints"),
|
|
QStringLiteral("B-Spline Tools"),
|
|
QStringLiteral("Visual Helpers")},
|
|
/*.priority =*/20,
|
|
/*.match =*/
|
|
[]() {
|
|
return WorkbenchManager::instance()->activeName() == "SketcherWorkbench";
|
|
},
|
|
});
|
|
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("assembly.workbench"),
|
|
/*.labelTemplate =*/QStringLiteral("Assembly"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Blue),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Assembly"),
|
|
QStringLiteral("Assembly Joints"),
|
|
QStringLiteral("Assembly Management")},
|
|
/*.priority =*/20,
|
|
/*.match =*/
|
|
[]() {
|
|
return WorkbenchManager::instance()->activeName() == "AssemblyWorkbench";
|
|
},
|
|
});
|
|
|
|
// --- Empty document ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("empty_document"),
|
|
/*.labelTemplate =*/QStringLiteral("New Document"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Surface1),
|
|
/*.toolbars =*/
|
|
{QStringLiteral("Structure")},
|
|
/*.priority =*/10,
|
|
/*.match =*/
|
|
[]() { return Application::Instance->activeDocument() != nullptr; },
|
|
});
|
|
|
|
// --- No document ---
|
|
registerContext({
|
|
/*.id =*/QStringLiteral("no_document"),
|
|
/*.labelTemplate =*/QStringLiteral("Kindred Create"),
|
|
/*.color =*/QLatin1String(CatppuccinMocha::Surface0),
|
|
/*.toolbars =*/ {},
|
|
/*.priority =*/0,
|
|
/*.match =*/[]() { return true; },
|
|
});
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Registration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void EditingContextResolver::registerContext(const ContextDefinition& def)
|
|
{
|
|
// Remove any existing context with the same id
|
|
unregisterContext(def.id);
|
|
d->contexts.append(def);
|
|
d->sortContexts();
|
|
}
|
|
|
|
void EditingContextResolver::unregisterContext(const QString& id)
|
|
{
|
|
d->contexts.erase(
|
|
std::remove_if(
|
|
d->contexts.begin(),
|
|
d->contexts.end(),
|
|
[&](const ContextDefinition& c) { return c.id == id; }
|
|
),
|
|
d->contexts.end()
|
|
);
|
|
}
|
|
|
|
void EditingContextResolver::registerOverlay(const OverlayDefinition& def)
|
|
{
|
|
unregisterOverlay(def.id);
|
|
d->overlays.append(def);
|
|
}
|
|
|
|
void EditingContextResolver::unregisterOverlay(const QString& id)
|
|
{
|
|
d->overlays.erase(
|
|
std::remove_if(
|
|
d->overlays.begin(),
|
|
d->overlays.end(),
|
|
[&](const OverlayDefinition& o) { return o.id == id; }
|
|
),
|
|
d->overlays.end()
|
|
);
|
|
}
|
|
|
|
void EditingContextResolver::injectCommands(
|
|
const QString& contextId,
|
|
const QString& toolbarName,
|
|
const QStringList& commands
|
|
)
|
|
{
|
|
d->injections[contextId][toolbarName].append(commands);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Resolution
|
|
// ---------------------------------------------------------------------------
|
|
|
|
EditingContext EditingContextResolver::resolve() const
|
|
{
|
|
EditingContext ctx;
|
|
|
|
// Find the first matching primary context
|
|
for (const auto& def : d->contexts) {
|
|
if (def.match && def.match()) {
|
|
ctx.id = def.id;
|
|
ctx.color = def.color;
|
|
ctx.toolbars = def.toolbars;
|
|
|
|
// Expand label template
|
|
QString label = def.labelTemplate;
|
|
if (label.contains(QStringLiteral("{name}"))) {
|
|
// For edit-mode contexts, use the in-edit object name
|
|
QString name = getInEditLabel();
|
|
if (name.isEmpty()) {
|
|
// Try pdbody first for PartDesign contexts
|
|
auto* bodyObj = getActivePdBodyObject();
|
|
if (bodyObj) {
|
|
name = QString::fromUtf8(bodyObj->Label.getValue());
|
|
}
|
|
}
|
|
if (name.isEmpty()) {
|
|
name = getActivePartLabel();
|
|
}
|
|
if (name.isEmpty()) {
|
|
name = QStringLiteral("?");
|
|
}
|
|
label.replace(QStringLiteral("{name}"), name);
|
|
}
|
|
ctx.label = label;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Append overlay toolbars
|
|
for (const auto& overlay : d->overlays) {
|
|
if (overlay.match && overlay.match()) {
|
|
for (const auto& tb : overlay.toolbars) {
|
|
if (!ctx.toolbars.contains(tb)) {
|
|
ctx.toolbars.append(tb);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Append any injected toolbar names that aren't already in the context
|
|
auto injIt = d->injections.find(ctx.id);
|
|
if (injIt != d->injections.end()) {
|
|
for (auto it = injIt->begin(); it != injIt->end(); ++it) {
|
|
if (!ctx.toolbars.contains(it.key())) {
|
|
ctx.toolbars.append(it.key());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build breadcrumb
|
|
ctx.breadcrumb = buildBreadcrumb(ctx);
|
|
ctx.breadcrumbColors = buildBreadcrumbColors(ctx);
|
|
|
|
return ctx;
|
|
}
|
|
|
|
EditingContext EditingContextResolver::currentContext() const
|
|
{
|
|
return d->current;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Breadcrumb building
|
|
// ---------------------------------------------------------------------------
|
|
|
|
QStringList EditingContextResolver::buildBreadcrumb(const EditingContext& ctx) const
|
|
{
|
|
QStringList crumbs;
|
|
|
|
if (ctx.id == QStringLiteral("no_document") || ctx.id == QStringLiteral("empty_document")) {
|
|
crumbs << ctx.label;
|
|
return crumbs;
|
|
}
|
|
|
|
// Assembly > Body breadcrumb for in-assembly part editing
|
|
if (ctx.id == QStringLiteral("partdesign.in_assembly")) {
|
|
auto* guiDoc = Application::Instance->activeDocument();
|
|
if (guiDoc) {
|
|
auto* vp = guiDoc->getInEdit();
|
|
if (vp) {
|
|
auto* vpd = dynamic_cast<ViewProviderDocumentObject*>(vp);
|
|
if (vpd && vpd->getObject()) {
|
|
crumbs << QString::fromUtf8(vpd->getObject()->Label.getValue());
|
|
}
|
|
}
|
|
}
|
|
auto* body = getActivePdBodyObject();
|
|
if (body) {
|
|
crumbs << QString::fromUtf8(body->Label.getValue());
|
|
}
|
|
return crumbs;
|
|
}
|
|
|
|
// Always start with the active part/body/assembly label
|
|
QString partLabel = getActivePartLabel();
|
|
if (!partLabel.isEmpty()) {
|
|
crumbs << partLabel;
|
|
}
|
|
|
|
// If in edit mode, add the edited object
|
|
auto* guiDoc = Application::Instance->activeDocument();
|
|
if (guiDoc) {
|
|
auto* vp = guiDoc->getInEdit();
|
|
if (vp) {
|
|
auto* vpd = dynamic_cast<ViewProviderDocumentObject*>(vp);
|
|
if (vpd && vpd->getObject()) {
|
|
QString editLabel = QString::fromUtf8(vpd->getObject()->Label.getValue());
|
|
// Don't duplicate if the part label IS the edited object
|
|
if (editLabel != partLabel) {
|
|
crumbs << editLabel;
|
|
}
|
|
crumbs << QStringLiteral("[editing]");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (crumbs.isEmpty()) {
|
|
crumbs << ctx.label;
|
|
}
|
|
|
|
return crumbs;
|
|
}
|
|
|
|
QStringList EditingContextResolver::buildBreadcrumbColors(const EditingContext& ctx) const
|
|
{
|
|
QStringList colors;
|
|
|
|
if (ctx.id == QStringLiteral("partdesign.in_assembly")) {
|
|
for (int i = 0; i < ctx.breadcrumb.size(); ++i) {
|
|
colors << (i == 0 ? QLatin1String(CatppuccinMocha::Blue)
|
|
: QLatin1String(CatppuccinMocha::Mauve));
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
if (ctx.breadcrumb.size() <= 1) {
|
|
colors << ctx.color;
|
|
return colors;
|
|
}
|
|
|
|
// For multi-segment breadcrumbs:
|
|
// - First segments (parent) use the parent context color
|
|
// - Last segments (active edit) use the current context color
|
|
// e.g., Body (mauve) > Sketch001 (green) > [editing] (green)
|
|
|
|
// Determine parent color
|
|
QString parentColor = QLatin1String(CatppuccinMocha::Mauve); // default for Body
|
|
auto* partObj = getActivePartObject();
|
|
if (partObj) {
|
|
if (objectIsDerivedFrom(partObj, "Assembly::AssemblyObject")) {
|
|
parentColor = QLatin1String(CatppuccinMocha::Blue);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < ctx.breadcrumb.size(); ++i) {
|
|
if (i == 0 && ctx.breadcrumb.size() > 1) {
|
|
colors << parentColor;
|
|
}
|
|
else {
|
|
colors << ctx.color;
|
|
}
|
|
}
|
|
|
|
return colors;
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Apply context → toolbar state changes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void EditingContextResolver::applyContext(const EditingContext& ctx)
|
|
{
|
|
if (ctx.id == d->current.id && ctx.toolbars == d->current.toolbars) {
|
|
return; // No change
|
|
}
|
|
|
|
auto* tbm = ToolBarManager::getInstance();
|
|
if (!tbm) {
|
|
return;
|
|
}
|
|
|
|
// Hide previously active context toolbars
|
|
if (!d->current.toolbars.isEmpty()) {
|
|
tbm->setState(d->current.toolbars, ToolBarManager::State::RestoreDefault);
|
|
}
|
|
|
|
// Show new context toolbars
|
|
if (!ctx.toolbars.isEmpty()) {
|
|
tbm->setState(ctx.toolbars, ToolBarManager::State::ForceAvailable);
|
|
}
|
|
|
|
d->current = ctx;
|
|
Q_EMIT contextChanged(ctx);
|
|
}
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Signal handlers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void EditingContextResolver::onInEdit(const ViewProviderDocumentObject& /*vp*/)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void EditingContextResolver::onResetEdit(const ViewProviderDocumentObject& /*vp*/)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void EditingContextResolver::onActiveDocument(const Document& /*doc*/)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void EditingContextResolver::onActivateView(const MDIView* /*view*/)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void EditingContextResolver::refresh()
|
|
{
|
|
applyContext(resolve());
|
|
}
|
|
|
|
#include "moc_EditingContext.cpp"
|