From 506b8f037f7ffc4918f91afba0a67d90f999d99b Mon Sep 17 00:00:00 2001 From: AndreaRigoni Date: Fri, 17 Apr 2026 13:20:21 +0000 Subject: [PATCH] feat: implement type-safe ReferenceProperty for SmartPointer fields and add UI support for object selection via context-aware dropdowns --- app/gcompose/src/ContextPanel.cpp | 4 + app/gcompose/src/ContextPanel.h | 1 + app/gcompose/src/MainPanel.cpp | 5 +- app/gcompose/src/PropertiesPanel.cpp | 4 + app/gcompose/src/PropertiesPanel.h | 4 + app/gcompose/src/PropertyWidgets.cpp | 85 ++++++++++++++++-- app/gcompose/src/PropertyWidgets.h | 20 +++++ app/gcompose/src/ViewportPane.cpp | 4 + app/gcompose/src/ViewportPane.h | 4 + src/Core/Property.h | 127 ++++++++++++++++++++++++++- src/HEP/Geant/GeantRegistration.cpp | 2 + src/HEP/Geant/Solid.h | 17 +++- 12 files changed, 265 insertions(+), 12 deletions(-) diff --git a/app/gcompose/src/ContextPanel.cpp b/app/gcompose/src/ContextPanel.cpp index 1ead4b1..e5dc1c6 100644 --- a/app/gcompose/src/ContextPanel.cpp +++ b/app/gcompose/src/ContextPanel.cpp @@ -90,6 +90,10 @@ void ContextPanel::setContext(uLib::ObjectsContext* context) { m_treeView->expandAll(); } +void ContextPanel::setPropertyContext(uLib::ObjectsContext* context) { + m_propertiesPanel->setContext(context); +} + void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { uLib::Object* target = nullptr; if (!selected.indexes().isEmpty()) { diff --git a/app/gcompose/src/ContextPanel.h b/app/gcompose/src/ContextPanel.h index 0366495..52fc1ef 100644 --- a/app/gcompose/src/ContextPanel.h +++ b/app/gcompose/src/ContextPanel.h @@ -20,6 +20,7 @@ public: ~ContextPanel(); void setContext(uLib::ObjectsContext* context); + void setPropertyContext(uLib::ObjectsContext* context); void selectObject(uLib::Object* obj); void clearSelection(); diff --git a/app/gcompose/src/MainPanel.cpp b/app/gcompose/src/MainPanel.cpp index 8889bc8..4951d4d 100644 --- a/app/gcompose/src/MainPanel.cpp +++ b/app/gcompose/src/MainPanel.cpp @@ -127,7 +127,10 @@ MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_m void MainPanel::setContext(uLib::ObjectsContext* context) { m_context = context; m_contextPanel->setContext(context); - + + // Propagate context to all panels for reference property dropdowns + m_contextPanel->setPropertyContext(context); + m_firstPane->setContext(context); if (m_mainVtkContext) { if (auto* viewport = qobject_cast(m_firstPane->currentViewport())) { viewport->RemoveProp3D(*m_mainVtkContext); diff --git a/app/gcompose/src/PropertiesPanel.cpp b/app/gcompose/src/PropertiesPanel.cpp index ab6cddb..75413f1 100644 --- a/app/gcompose/src/PropertiesPanel.cpp +++ b/app/gcompose/src/PropertiesPanel.cpp @@ -47,4 +47,8 @@ void PropertiesPanel::setObject(uLib::Object* obj) { m_editor->setObject(obj); } +void PropertiesPanel::setContext(uLib::ObjectsContext* context) { + m_editor->setContext(context); +} + PropertiesPanel::~PropertiesPanel() {} diff --git a/app/gcompose/src/PropertiesPanel.h b/app/gcompose/src/PropertiesPanel.h index f7e6fc0..f8419ee 100644 --- a/app/gcompose/src/PropertiesPanel.h +++ b/app/gcompose/src/PropertiesPanel.h @@ -5,6 +5,7 @@ namespace uLib { class Object; + class ObjectsContext; namespace Qt { class PropertyEditor; } } @@ -23,6 +24,9 @@ public: /** @brief Sets the object to be inspected. */ void setObject(uLib::Object* obj); + + /** @brief Sets the context for reference property dropdowns. */ + void setContext(uLib::ObjectsContext* context); signals: void propertyUpdated(); diff --git a/app/gcompose/src/PropertyWidgets.cpp b/app/gcompose/src/PropertyWidgets.cpp index eb162e7..2671b0c 100644 --- a/app/gcompose/src/PropertyWidgets.cpp +++ b/app/gcompose/src/PropertyWidgets.cpp @@ -13,6 +13,7 @@ #include #include #include "Settings.h" +#include "Core/ObjectsContext.h" namespace uLib { namespace Qt { @@ -386,7 +387,74 @@ public: } }; -PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) { +//////////////////////////////////////////////////////////////////////////////// +// ReferencePropertyWidget + +ReferencePropertyWidget::ReferencePropertyWidget(ReferencePropertyBase* prop, ::uLib::ObjectsContext* context, QWidget* parent) + : PropertyWidgetBase(prop, parent), m_RefProp(prop), m_Context(context) { + m_Combo = new QComboBox(static_cast(this)); + m_Layout->addWidget(m_Combo, 1); + refreshCombo(); + connect(m_Combo, &QComboBox::currentIndexChanged, this, &ReferencePropertyWidget::onComboChanged); + + // Listen for property updates to refresh selected item + m_Connection = uLib::Object::connect(prop, &uLib::Object::Updated, [this](){ + QSignalBlocker blocker(m_Combo); + refreshCombo(); + }); + + // Listen for context changes to refresh the dropdown list + if (m_Context) { + m_ContextConnection = uLib::Object::connect(m_Context, &uLib::Object::Updated, [this](){ + QSignalBlocker blocker(m_Combo); + refreshCombo(); + }); + } +} + +ReferencePropertyWidget::~ReferencePropertyWidget() { + m_Connection.disconnect(); + m_ContextConnection.disconnect(); +} + +void ReferencePropertyWidget::refreshCombo() { + m_Combo->clear(); + m_Combo->addItem("(none)", QVariant::fromValue((quintptr)0)); + + int selectedIdx = 0; + Object* currentRef = m_RefProp->GetReferencedObject(); + + if (m_Context) { + const auto& objects = m_Context->GetObjects(); + for (auto* obj : objects) { + if (m_RefProp->IsCompatible(obj)) { + QString label = QString::fromStdString(obj->GetInstanceName()); + if (label.isEmpty()) { + label = QString::fromStdString(std::string(obj->GetClassName())); + } + // Add index suffix if name is empty to disambiguate + m_Combo->addItem(label, QVariant::fromValue((quintptr)obj)); + if (obj == currentRef) { + selectedIdx = m_Combo->count() - 1; + } + } + } + } + m_Combo->setCurrentIndex(selectedIdx); +} + +void ReferencePropertyWidget::onComboChanged(int index) { + if (index < 0) return; + quintptr ptr = m_Combo->itemData(index).value(); + Object* obj = reinterpret_cast(ptr); + m_RefProp->SetReferencedObject(obj); + Q_EMIT updated(); +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyEditor + +PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr), m_Context(nullptr) { m_MainLayout = new QVBoxLayout(this); m_MainLayout->setContentsMargins(0, 0, 0, 0); m_ScrollArea = new QScrollArea(this); @@ -488,18 +556,23 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { // widget = new RangePropertyWidget(pflt, m_Container); } } else { - // Priority 2: Standard factory lookup + // Priority 2: Check for reference properties (SmartPointer) + if (auto* refProp = dynamic_cast<::uLib::ReferencePropertyBase*>(prop)) { + widget = static_cast(new ReferencePropertyWidget(refProp, m_Context, m_Container)); + } else { + // Priority 3: Standard factory lookup auto it = m_Factories.find(prop->GetTypeIndex()); if (it != m_Factories.end()) { widget = it->second(prop, m_Container); } else { - // Debug info for unknown types - std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() - << " (Type: " << prop->GetTypeName() << ")" << std::endl; + // Debug info for unknown types + std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName() + << " (Type: " << prop->GetTypeName() << ")" << std::endl; widget = new PropertyWidgetBase(prop, m_Container); - widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); + widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")")); } + } } if (widget) { diff --git a/app/gcompose/src/PropertyWidgets.h b/app/gcompose/src/PropertyWidgets.h index cfb2c62..0fb52f8 100644 --- a/app/gcompose/src/PropertyWidgets.h +++ b/app/gcompose/src/PropertyWidgets.h @@ -4,6 +4,7 @@ #include class QPushButton; class QSlider; +class QComboBox; #include #include #include @@ -21,6 +22,8 @@ class QSlider; #include "Math/Dense.h" #include "Settings.h" +namespace uLib { class ObjectsContext; } + namespace uLib { namespace Qt { @@ -211,12 +214,28 @@ private: QPushButton* m_Button; }; +class ReferencePropertyWidget : public PropertyWidgetBase { + Q_OBJECT +public: + ReferencePropertyWidget(ReferencePropertyBase* prop, ::uLib::ObjectsContext* context, QWidget* parent = nullptr); + virtual ~ReferencePropertyWidget(); +private slots: + void onComboChanged(int index); +private: + void refreshCombo(); + ReferencePropertyBase* m_RefProp; + ::uLib::ObjectsContext* m_Context; + QComboBox* m_Combo; + Connection m_ContextConnection; +}; + class PropertyEditor : public QWidget { Q_OBJECT public: PropertyEditor(QWidget* parent = nullptr); virtual ~PropertyEditor(); void setObject(uLib::Object* obj, bool displayOnly = false); + void setContext(uLib::ObjectsContext* context) { m_Context = context; } template void registerFactory(std::function factory) { m_Factories[std::type_index(typeid(T))] = factory; @@ -228,6 +247,7 @@ signals: private: void clear(); uLib::Object* m_Object; + uLib::ObjectsContext* m_Context; QVBoxLayout* m_MainLayout; QScrollArea* m_ScrollArea; QWidget* m_Container; diff --git a/app/gcompose/src/ViewportPane.cpp b/app/gcompose/src/ViewportPane.cpp index 495053d..6400422 100644 --- a/app/gcompose/src/ViewportPane.cpp +++ b/app/gcompose/src/ViewportPane.cpp @@ -113,6 +113,10 @@ void ViewportPane::setObject(uLib::Object* obj) { } } +void ViewportPane::setContext(uLib::ObjectsContext* context) { + m_displayEditor->setContext(context); +} + void ViewportPane::setViewport(QWidget* viewport, const QString& title) { if (m_viewport) { delete m_viewport; diff --git a/app/gcompose/src/ViewportPane.h b/app/gcompose/src/ViewportPane.h index e88a9d4..b4b252b 100644 --- a/app/gcompose/src/ViewportPane.h +++ b/app/gcompose/src/ViewportPane.h @@ -7,6 +7,7 @@ namespace uLib { class Object; + class ObjectsContext; namespace Qt { class PropertyEditor; } namespace Vtk { class Viewport; } } @@ -29,6 +30,9 @@ public: /** @brief Update the display properties for the given object. */ void setObject(uLib::Object* obj); + + /** @brief Sets the context for reference property dropdowns. */ + void setContext(uLib::ObjectsContext* context); private slots: void onCloseRequested(); diff --git a/src/Core/Property.h b/src/Core/Property.h index e9386da..69f47c5 100644 --- a/src/Core/Property.h +++ b/src/Core/Property.h @@ -15,6 +15,15 @@ #include "Core/Archives.h" #include "Core/Signal.h" #include "Core/Object.h" +#include "Core/SmartPointer.h" + +// Type traits for detecting SmartPointer +namespace uLib { +template struct is_smart_pointer : std::false_type {}; +template struct is_smart_pointer> : std::true_type {}; +template struct smart_pointer_element { using type = void; }; +template struct smart_pointer_element> { using type = T; }; +} // namespace uLib namespace uLib { @@ -216,6 +225,109 @@ private: } // namespace uLib +namespace uLib { + +/** + * @brief Base class for reference properties (SmartPointer fields). + * Provides a type-erased interface for getting/setting object references + * and checking type compatibility. + */ +class ReferencePropertyBase : public PropertyBase { +public: + virtual ~ReferencePropertyBase() {} + virtual Object* GetReferencedObject() const = 0; + virtual void SetReferencedObject(Object* obj) = 0; + virtual bool IsCompatible(Object* obj) const = 0; + virtual const char* GetReferenceTypeName() const = 0; +}; + +/** + * @brief Typed reference property for SmartPointer fields. + * Filters context objects by dynamic_cast compatibility with T. + */ +template +class ReferenceProperty : public ReferencePropertyBase { +public: + ReferenceProperty(Object* owner, const std::string& name, SmartPointer& ref, + const std::string& units = "", const std::string& group = "") + : m_owner(owner), m_name(name), m_units(units), m_group(group), m_ref(ref), m_ReadOnly(false) { + if (m_owner) m_owner->RegisterProperty(this); + } + + virtual ~ReferenceProperty() {} + + // PropertyBase interface + virtual const std::string& GetName() const override { return m_name; } + virtual const char* GetTypeName() const override { return typeid(SmartPointer).name(); } + virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(ReferencePropertyBase)); } + virtual const std::string& GetUnits() const override { return m_units; } + virtual void SetUnits(const std::string& units) override { m_units = units; } + virtual const std::string& GetGroup() const override { return m_group; } + virtual void SetGroup(const std::string& group) override { m_group = group; } + virtual bool IsReadOnly() const override { return m_ReadOnly; } + void SetReadOnly(bool ro) { m_ReadOnly = ro; } + + virtual std::string GetValueAsString() const override { + T* ptr = m_ref.Get(); + if (!ptr) return "(none)"; + Object* obj = dynamic_cast(ptr); + if (obj) { + std::string iname = obj->GetInstanceName(); + if (!iname.empty()) return iname; + return obj->GetClassName(); + } + return "(set)"; + } + + // ReferencePropertyBase interface + virtual Object* GetReferencedObject() const override { + return dynamic_cast(m_ref.Get()); + } + + virtual void SetReferencedObject(Object* obj) override { + if (!obj) { + m_ref = SmartPointer(nullptr); + this->Updated(); + if (m_owner) m_owner->Updated(); + return; + } + T* casted = dynamic_cast(obj); + if (casted) { + m_ref = SmartPointer(casted); + this->Updated(); + if (m_owner) m_owner->Updated(); + } + } + + virtual bool IsCompatible(Object* obj) const override { + return dynamic_cast(obj) != nullptr; + } + + virtual const char* GetReferenceTypeName() const override { + return typeid(T).name(); + } + + // Serialization stubs + virtual void serialize(Archive::xml_oarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::xml_iarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::text_oarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::text_iarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::hrt_oarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::hrt_iarchive & ar, const unsigned int v) override {} + virtual void serialize(Archive::log_archive & ar, const unsigned int v) override {} + virtual void serialize(Archive::property_register_archive & ar, const unsigned int v) override {} + +private: + Object* m_owner; + std::string m_name; + std::string m_units; + std::string m_group; + SmartPointer& m_ref; + bool m_ReadOnly; +}; + +} // namespace uLib + namespace uLib { namespace Archive { @@ -267,7 +379,20 @@ public: } template void save_property_impl(const char* name, T& val, const char* units, bool hasRange, const T& minVal, const T& maxVal, bool isReadOnly) { - if (m_Object) { + if (!m_Object) return; + if constexpr (is_smart_pointer::value) { + // SmartPointer field: create a ReferenceProperty for type-safe selection + using ElementT = typename smart_pointer_element::type; + auto* p = new ReferenceProperty(m_Object, name, val, units ? units : "", GetCurrentGroup()); + p->SetReadOnly(isReadOnly); + if (m_DisplayOnly) { + m_Object->RegisterDisplayProperty(p); + Object* obj = m_Object; + Object::connect(p, &Object::Updated, [obj]() { obj->Updated(); }); + } else { + m_Object->RegisterDynamicProperty(p); + } + } else { Property* p = new Property(m_Object, name, &val, units ? units : "", GetCurrentGroup()); set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic::type()); p->SetReadOnly(isReadOnly); diff --git a/src/HEP/Geant/GeantRegistration.cpp b/src/HEP/Geant/GeantRegistration.cpp index b148d3d..3c442b3 100644 --- a/src/HEP/Geant/GeantRegistration.cpp +++ b/src/HEP/Geant/GeantRegistration.cpp @@ -12,6 +12,8 @@ ULIB_REGISTER_OBJECT(Material) ULIB_REGISTER_OBJECT(Solid) ULIB_REGISTER_OBJECT(TessellatedSolid) ULIB_REGISTER_OBJECT(BoxSolid) +ULIB_REGISTER_OBJECT(LogicalVolume) +ULIB_REGISTER_OBJECT(PhysicalVolume) ULIB_REGISTER_OBJECT(Scene) ULIB_REGISTER_OBJECT(SkyPlaneEmitterPrimary) ULIB_REGISTER_OBJECT(CylinderEmitterPrimary) diff --git a/src/HEP/Geant/Solid.h b/src/HEP/Geant/Solid.h index 0c02535..cc74e24 100644 --- a/src/HEP/Geant/Solid.h +++ b/src/HEP/Geant/Solid.h @@ -163,7 +163,7 @@ protected: G4VPhysicalVolume *m_Physical; - // ULIB_DECLARE_PROPERTIES(PhysicalVolume) + ULIB_DECLARE_PROPERTIES(PhysicalVolume) }; @@ -173,9 +173,11 @@ protected: class TessellatedSolid : public Solid { -public: - uLibTypeMacro(TessellatedSolid, Solid) + uLibTypeMacro(TessellatedSolid, Solid) + ULIB_SERIALIZE_ACCESS + +public: TessellatedSolid(); TessellatedSolid(const char *name); @@ -191,6 +193,8 @@ public: protected: SmartPointer m_Mesh; G4TessellatedSolid *m_Solid; + + //ULIB_DECLARE_PROPERTIES(TessellatedSolid) }; @@ -198,8 +202,11 @@ protected: //// BOX SOLID ///////////////////////////////////////////////////////////////// class BoxSolid : public Solid { -public: + uLibTypeMacro(BoxSolid, Solid) + ULIB_SERIALIZE_ACCESS + +public: BoxSolid(); BoxSolid(const char *name); @@ -222,6 +229,8 @@ private: SmartPointer m_ContainerBox; G4Box *m_Solid; + + ULIB_DECLARE_PROPERTIES(BoxSolid) };