feat: implement type-safe ReferenceProperty for SmartPointer fields and add UI support for object selection via context-aware dropdowns

This commit is contained in:
AndreaRigoni
2026-04-17 13:20:21 +00:00
parent ec2d437819
commit 506b8f037f
12 changed files with 265 additions and 12 deletions

View File

@@ -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()) {

View File

@@ -20,6 +20,7 @@ public:
~ContextPanel();
void setContext(uLib::ObjectsContext* context);
void setPropertyContext(uLib::ObjectsContext* context);
void selectObject(uLib::Object* obj);
void clearSelection();

View File

@@ -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<uLib::Vtk::QViewport*>(m_firstPane->currentViewport())) {
viewport->RemoveProp3D(*m_mainVtkContext);

View File

@@ -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() {}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();