fix some on properties and signal connection

This commit is contained in:
AndreaRigoni
2026-03-26 09:50:52 +00:00
parent 2c5d6842c3
commit e0ffeff5b7
22 changed files with 578 additions and 62 deletions

View File

@@ -14,10 +14,25 @@ void ContextModel::setContext(uLib::ObjectsContext* context) {
beginResetModel();
m_rootContext = context;
if (m_rootContext) {
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, [this]() {
auto refresh = [this]() {
this->beginResetModel();
this->endResetModel();
};
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
refresh();
});
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
// Disconnect would be good here but not strictly required if refresh handles it
refresh();
});
// Connect existing objects
for (auto* obj : m_rootContext->GetObjects()) {
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
}
}
endResetModel();
}

View File

@@ -92,3 +92,27 @@ void ContextPanel::onSelectionChanged(const QItemSelection& selected, const QIte
emit objectSelected(target);
m_propertiesPanel->setObject(target);
}
void ContextPanel::selectObject(uLib::Object* obj) {
if (!obj) {
clearSelection();
return;
}
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex idx = m_model->index(i, 0);
if (idx.internalPointer() == obj) {
QSignalBlocker blocker(m_treeView->selectionModel());
m_treeView->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
m_treeView->scrollTo(idx);
m_propertiesPanel->setObject(obj); // Explicitly update properties too
return;
}
}
}
void ContextPanel::clearSelection() {
QSignalBlocker blocker(m_treeView->selectionModel());
m_treeView->selectionModel()->clearSelection();
m_propertiesPanel->setObject(nullptr);
}

View File

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

View File

@@ -14,7 +14,10 @@
#include <QMenu>
#include <QAction>
#include <QApplication>
#include <QFileDialog>
#include <QFileInfo>
#include "StyleManager.h"
#include "Math/VoxImage.h"
MainPanel::MainPanel(QWidget* parent) : QWidget(parent), m_context(nullptr), m_mainVtkContext(nullptr) {
this->setObjectName("MainPanel");
@@ -124,6 +127,17 @@ void MainPanel::setContext(uLib::ObjectsContext* context) {
m_mainVtkContext = new uLib::Vtk::vtkObjectsContext(context);
// viewport->AddPuppet(*m_mainVtkContext); // redundant
auto syncSelection = [this](uLib::Vtk::Puppet* p) {
if (!p) {
m_contextPanel->clearSelection();
m_firstPane->setObject(nullptr);
} else {
m_contextPanel->selectObject(p->GetContent());
m_firstPane->setObject(p);
}
};
connect(viewport, &uLib::Vtk::QViewport::puppetSelected, syncSelection);
uLib::Object::connect(m_mainVtkContext, &uLib::Vtk::vtkObjectsContext::PuppetAdded, [this](uLib::Vtk::Puppet* p) {
if (p) {
auto panes = this->findChildren<ViewportPane*>();
@@ -179,7 +193,35 @@ void MainPanel::onCreateObject(const std::string& className) {
}
void MainPanel::onOpen() {
// Placeholder for open logic
QString fileName = QFileDialog::getOpenFileName(this, "Open File", "",
"VTK/VTI Images (*.vtk *.vti);;All Files (*.*)");
if (fileName.isEmpty()) return;
QFileInfo info(fileName);
QString ext = info.suffix().toLower();
if (ext == "vti" || ext == "vtk") {
auto* obj = uLib::ObjectFactory::Instance().Create("VoxImage");
auto* vox = dynamic_cast<uLib::Abstract::VoxImage*>(obj);
if (vox) {
bool success = false;
if (ext == "vti") {
success = vox->ImportFromVti(fileName.toStdString().c_str());
} else {
success = vox->ImportFromVtk(fileName.toStdString().c_str());
}
if (success) {
obj->SetInstanceName(info.fileName().toStdString());
m_context->AddObject(obj);
} else {
delete obj;
}
} else {
delete obj;
}
}
}
void MainPanel::onSave() {

View File

@@ -2,6 +2,8 @@
#include <QSignalBlocker>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QComboBox>
#include <QCheckBox>
#include "Vtk/uLibVtkInterface.h"
#include "Math/Units.h"
#include "Math/Dense.h"
@@ -17,10 +19,12 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
m_Label->setMinimumWidth(100);
m_Layout->addWidget(m_Label);
}
PropertyWidgetBase::~PropertyWidgetBase() {}
PropertyWidgetBase::~PropertyWidgetBase() {
m_Connection.disconnect();
}
// Helper for unit parsing
static double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr) {
double parseWithUnits(const QString& text, double* factorOut, QString* suffixOut) {
static QRegularExpression re("^\\s*([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)\\s*(_?[a-zA-Z]+)?\\s*$");
QRegularExpressionMatch match = re.match(text);
if (!match.hasMatch()) return 0.0;
@@ -57,14 +61,23 @@ static double parseWithUnits(const QString& text, double* factorOut = nullptr, Q
}
// UnitLineEdit implementation
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix("mm"), m_IsInteger(false) {
UnitLineEdit::UnitLineEdit(QWidget* parent) : QLineEdit(parent), m_Value(0), m_Factor(1.0), m_Suffix(""), m_IsInteger(false) {
connect(this, &QLineEdit::editingFinished, this, &UnitLineEdit::onEditingFinished);
}
void UnitLineEdit::setUnits(const QString& suffix, double factor) {
m_Suffix = suffix;
m_Factor = factor;
updateText();
}
void UnitLineEdit::setValue(double val) {
if (m_Value != val) {
m_Value = val;
// Initial heuristic for unit if it was mm and value becomes large
// Suffix heuristic ONLY if it was mm and no explicit unit was given?
// Actually, if m_Suffix is empty or we have a specific one, we should respect it.
// The original code had a heuristic, but it's better to let property decide.
// Let's keep it ONLY if m_Suffix was mm (legacy behavior)
if (!m_IsInteger && m_Suffix == "mm" && std::abs(val) >= 1000.0) { m_Suffix = "m"; m_Factor = CLHEP::meter; }
updateText();
}
@@ -91,12 +104,21 @@ void UnitLineEdit::onEditingFinished() {
void UnitLineEdit::updateText() {
QSignalBlocker blocker(this);
QString s;
if (m_IsInteger) {
setText(QString::number((int)m_Value));
s = QString::number((int)m_Value);
if (s.isEmpty()) s = "0";
} else {
double displayVal = m_Value / m_Factor;
setText(QString::number(displayVal, 'g', 6) + " " + m_Suffix);
s = QString::number(displayVal, 'g', 6);
if (!s.contains('.') && !s.contains('e')) {
s += ".0";
}
}
if (!m_Suffix.isEmpty()) {
s += " " + m_Suffix;
}
setText(s);
}
void UnitLineEdit::setIntegerOnly(bool integerOnly) {
@@ -107,10 +129,16 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
m_Layout->addWidget(m_Edit, 1);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::PropertyChanged, [this](){
m_Edit->setValue(m_Prop->Get());
});
}
@@ -118,10 +146,16 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
m_Layout->addWidget(m_Edit, 1);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((float)val); });
uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::PropertyChanged, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
}
@@ -130,10 +164,16 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Edit = new UnitLineEdit(this);
m_Edit->setIntegerOnly(true);
m_Layout->addWidget(m_Edit, 1);
QString units = QString::fromStdString(prop->GetUnits());
if (!units.isEmpty()) {
double factor = 1.0;
parseWithUnits("1 " + units, &factor);
m_Edit->setUnits(units, factor);
}
m_Edit->setValue(prop->Get());
m_Layout->addWidget(m_Edit, 1);
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set((int)val); });
uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::PropertyChanged, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
}
@@ -144,7 +184,7 @@ BoolPropertyWidget::BoolPropertyWidget(Property<bool>* prop, QWidget* parent)
m_CheckBox->setChecked(prop->Get());
m_Layout->addWidget(m_CheckBox, 1);
connect(m_CheckBox, &QCheckBox::toggled, [this](bool val){ if (m_Prop->Get() != val) m_Prop->Set(val); });
uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::PropertyChanged, [this](){
if (m_CheckBox->isChecked() != m_Prop->Get()) {
QSignalBlocker blocker(m_CheckBox);
m_CheckBox->setChecked(m_Prop->Get());
@@ -158,11 +198,11 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
m_LineEdit = new QLineEdit(this);
m_LineEdit->setText(QString::fromStdString(prop->Get()));
m_Layout->addWidget(m_LineEdit, 1);
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
connect(m_LineEdit, &QLineEdit::editingFinished, [this](){
std::string val = m_LineEdit->text().toStdString();
if (m_Prop->Get() != val) m_Prop->Set(val);
if (m_Prop->Get() != val) m_Prop->Set(val);
});
uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::PropertyChanged, [this](){
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
QSignalBlocker blocker(m_LineEdit);
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
@@ -171,6 +211,37 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
}
StringPropertyWidget::~StringPropertyWidget() {}
class EnumPropertyWidget : public PropertyWidgetBase {
PropertyBase* m_Prop;
QComboBox* m_Combo;
public:
EnumPropertyWidget(PropertyBase* prop, QWidget* parent)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
m_Combo = new QComboBox(this);
const auto& labels = prop->GetEnumLabels();
for (const auto& label : labels) {
m_Combo->addItem(QString::fromStdString(label));
}
// Get initial value
if (auto* p = dynamic_cast<Property<int>*>(prop)) {
m_Combo->setCurrentIndex(p->Get());
connect(m_Combo, &QComboBox::currentIndexChanged, [p](int index){
p->Set(index);
});
// Store connection in base m_Connection so it's auto-disconnected on destruction.
m_Connection = uLib::Object::connect(p, &Property<int>::PropertyChanged, [this, p](){
if (m_Combo->currentIndex() != p->Get()) {
QSignalBlocker blocker(m_Combo);
m_Combo->setCurrentIndex(p->Get());
}
});
}
m_Layout->addWidget(m_Combo, 1);
}
};
PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(nullptr) {
m_MainLayout = new QVBoxLayout(this);
m_MainLayout->setContentsMargins(0, 0, 0, 0);
@@ -197,6 +268,11 @@ PropertyEditor::PropertyEditor(QWidget* parent) : QWidget(parent), m_Object(null
registerFactory<std::string>([](PropertyBase* p, QWidget* parent){
return new StringPropertyWidget(static_cast<Property<std::string>*>(p), parent);
});
// Register EnumProperty specifically (needs to check type since it holds Property<int> but is EnumProperty)
m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) {
return new EnumPropertyWidget(p, parent);
};
// Vector Registration
registerFactory<Vector2i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2i, 2>(static_cast<Property<Vector2i>*>(p), parent); });
@@ -230,11 +306,22 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
}
for (auto* prop : *props) {
// Priority 1: Check if it provides enum labels
if (!prop->GetEnumLabels().empty()) {
m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container));
continue;
}
// Priority 2: Standard factory lookup
auto it = m_Factories.find(prop->GetTypeIndex());
if (it != m_Factories.end()) {
QWidget* widget = it->second(prop, m_Container);
m_ContainerLayout->addWidget(widget);
} else {
// Debug info for unknown types
std::cout << "PropertyEditor: No factory for " << prop->GetName()
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
m_ContainerLayout->addWidget(fallback);

View File

@@ -14,11 +14,14 @@
#include "Core/Property.h"
#include "Core/Object.h"
#include "Core/Signal.h"
#include "Math/Dense.h"
namespace uLib {
namespace Qt {
double parseWithUnits(const QString& text, double* factorOut = nullptr, QString* suffixOut = nullptr);
class PropertyWidgetBase : public QWidget {
Q_OBJECT
public:
@@ -30,6 +33,9 @@ protected:
PropertyBase* m_BaseProperty;
QHBoxLayout* m_Layout;
QLabel* m_Label;
// Stores the uLib signal connection so it can be disconnected on destruction,
// preventing use-after-free when PropertyEditor::clear() deletes widgets.
Connection m_Connection;
};
class UnitLineEdit : public QLineEdit {
@@ -37,6 +43,7 @@ class UnitLineEdit : public QLineEdit {
public:
UnitLineEdit(QWidget* parent = nullptr);
void setValue(double val);
void setUnits(const QString& suffix, double factor = 1.0);
double getValue() const { return m_Value; }
void setIntegerOnly(bool b);
@@ -86,11 +93,19 @@ class VectorPropertyWidget : public PropertyWidgetBase {
public:
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
QString units = QString::fromStdString(prop->GetUnits());
double factor = 1.0;
if (!units.isEmpty()) {
parseWithUnits("1 " + units, &factor);
}
for (int i = 0; i < Size; ++i) {
m_Edits[i] = new UnitLineEdit(this);
if (std::is_integral<typename VecT::Scalar>::value) {
m_Edits[i]->setIntegerOnly(true);
}
if (!units.isEmpty()) {
m_Edits[i]->setUnits(units, factor);
}
m_Layout->addWidget(m_Edits[i], 1);
connect(m_Edits[i], &UnitLineEdit::valueManualChanged, [this, i](double val){
@@ -100,10 +115,11 @@ public:
});
}
updateEdits();
uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
m_Connection = uLib::Object::connect(m_Prop, &Property<VecT>::PropertyChanged, [this](){
updateEdits();
});
}
~VectorPropertyWidget() { m_Connection.disconnect(); }
private:
void updateEdits() {