feat: implement type-safe ReferenceProperty for SmartPointer fields and add UI support for object selection via context-aware dropdowns
This commit is contained in:
@@ -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()) {
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
~ContextPanel();
|
||||
|
||||
void setContext(uLib::ObjectsContext* context);
|
||||
void setPropertyContext(uLib::ObjectsContext* context);
|
||||
void selectObject(uLib::Object* obj);
|
||||
void clearSelection();
|
||||
|
||||
|
||||
@@ -128,6 +128,9 @@ 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<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
|
||||
viewport->RemoveProp3D(*m_mainVtkContext);
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
namespace uLib {
|
||||
class Object;
|
||||
class ObjectsContext;
|
||||
namespace Qt { class PropertyEditor; }
|
||||
}
|
||||
|
||||
@@ -24,6 +25,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();
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QSlider>
|
||||
#include <QFontDialog>
|
||||
#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<QWidget*>(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<quintptr>();
|
||||
Object* obj = reinterpret_cast<Object*>(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<float>(pflt, m_Container);
|
||||
}
|
||||
} else {
|
||||
// Priority 2: Standard factory lookup
|
||||
// Priority 2: Check for reference properties (SmartPointer<T>)
|
||||
if (auto* refProp = dynamic_cast<::uLib::ReferencePropertyBase*>(prop)) {
|
||||
widget = static_cast<QWidget*>(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) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QWidget>
|
||||
class QPushButton;
|
||||
class QSlider;
|
||||
class QComboBox;
|
||||
#include <QLabel>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
@@ -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<typename T>
|
||||
void registerFactory(std::function<QWidget*(PropertyBase*, QWidget*)> 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
namespace uLib {
|
||||
class Object;
|
||||
class ObjectsContext;
|
||||
namespace Qt { class PropertyEditor; }
|
||||
namespace Vtk { class Viewport; }
|
||||
}
|
||||
@@ -30,6 +31,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();
|
||||
void showContextMenu(const QPoint& pos);
|
||||
|
||||
@@ -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<T>
|
||||
namespace uLib {
|
||||
template<typename T> struct is_smart_pointer : std::false_type {};
|
||||
template<typename T> struct is_smart_pointer<SmartPointer<T>> : std::true_type {};
|
||||
template<typename T> struct smart_pointer_element { using type = void; };
|
||||
template<typename T> struct smart_pointer_element<SmartPointer<T>> { using type = T; };
|
||||
} // namespace uLib
|
||||
|
||||
namespace uLib {
|
||||
|
||||
@@ -216,6 +225,109 @@ private:
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
namespace uLib {
|
||||
|
||||
/**
|
||||
* @brief Base class for reference properties (SmartPointer<T> 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<T> fields.
|
||||
* Filters context objects by dynamic_cast compatibility with T.
|
||||
*/
|
||||
template <typename T>
|
||||
class ReferenceProperty : public ReferencePropertyBase {
|
||||
public:
|
||||
ReferenceProperty(Object* owner, const std::string& name, SmartPointer<T>& 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<T>).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<Object*>(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<Object*>(m_ref.Get());
|
||||
}
|
||||
|
||||
virtual void SetReferencedObject(Object* obj) override {
|
||||
if (!obj) {
|
||||
m_ref = SmartPointer<T>(nullptr);
|
||||
this->Updated();
|
||||
if (m_owner) m_owner->Updated();
|
||||
return;
|
||||
}
|
||||
T* casted = dynamic_cast<T*>(obj);
|
||||
if (casted) {
|
||||
m_ref = SmartPointer<T>(casted);
|
||||
this->Updated();
|
||||
if (m_owner) m_owner->Updated();
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool IsCompatible(Object* obj) const override {
|
||||
return dynamic_cast<T*>(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<T>& m_ref;
|
||||
bool m_ReadOnly;
|
||||
};
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
namespace uLib {
|
||||
namespace Archive {
|
||||
|
||||
@@ -267,7 +379,20 @@ public:
|
||||
}
|
||||
|
||||
template<class T> 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<T>::value) {
|
||||
// SmartPointer<U> field: create a ReferenceProperty<U> for type-safe selection
|
||||
using ElementT = typename smart_pointer_element<T>::type;
|
||||
auto* p = new ReferenceProperty<ElementT>(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<T>* p = new Property<T>(m_Object, name, &val, units ? units : "", GetCurrentGroup());
|
||||
set_range_helper(p, hasRange, minVal, maxVal, typename std::is_arithmetic<T>::type());
|
||||
p->SetReadOnly(isReadOnly);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<TriangleMesh> 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<ContainerBox> m_ContainerBox;
|
||||
|
||||
G4Box *m_Solid;
|
||||
|
||||
ULIB_DECLARE_PROPERTIES(BoxSolid)
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user