#include "PropertyWidgets.h" #include #include #include #include #include #include "Vtk/uLibVtkInterface.h" #include "Math/Units.h" #include "Math/Dense.h" namespace uLib { namespace Qt { PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent) : QWidget(parent), m_BaseProperty(prop) { m_Layout = new QHBoxLayout(this); m_Layout->setContentsMargins(4, 2, 4, 2); m_Label = new QLabel(QString::fromStdString(prop->GetName()), this); m_Label->setMinimumWidth(100); m_Layout->addWidget(m_Label); } PropertyWidgetBase::~PropertyWidgetBase() { m_Connection.disconnect(); } // Helper for unit parsing 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; double num = match.captured(1).toDouble(); QString unit = match.captured(3); double factor = 1.0; if (!unit.isEmpty()) { QString u = unit.startsWith('_') ? unit.mid(1) : unit; if (u == "m") factor = CLHEP::meter; else if (u == "cm") factor = CLHEP::centimeter; else if (u == "mm") factor = CLHEP::millimeter; else if (u == "um") factor = CLHEP::micrometer; else if (u == "nm") factor = CLHEP::nanometer; else if (u == "km") factor = CLHEP::kilometer; else if (u == "deg") factor = CLHEP::degree; else if (u == "rad") factor = CLHEP::radian; else if (u == "ns") factor = CLHEP::nanosecond; else if (u == "s") factor = CLHEP::second; else if (u == "ms") factor = CLHEP::millisecond; else if (u == "MeV") factor = CLHEP::megaelectronvolt; else if (u == "eV") factor = CLHEP::electronvolt; else if (u == "keV") factor = CLHEP::kiloelectronvolt; else if (u == "GeV") factor = CLHEP::gigaelectronvolt; else if (u == "TeV") factor = CLHEP::teraelectronvolt; if (suffixOut) *suffixOut = u; } else if (suffixOut) { // Reuse previous suffix if none provided, or empty } if (factorOut) *factorOut = factor; return num * factor; } // UnitLineEdit implementation 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; // 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(); } } void UnitLineEdit::onEditingFinished() { double factor = m_Factor; QString suffix = m_Suffix; double parsedVal = parseWithUnits(text(), &factor, &suffix); if (!suffix.isEmpty()) { m_Suffix = suffix; m_Factor = factor; } if (m_IsInteger) { parsedVal = std::round(parsedVal); } if (m_Value != parsedVal) { m_Value = parsedVal; emit valueManualChanged(m_Value); } updateText(); } void UnitLineEdit::updateText() { QSignalBlocker blocker(this); QString s; if (m_IsInteger) { s = QString::number((int)m_Value); if (s.isEmpty()) s = "0"; } else { double displayVal = m_Value / m_Factor; 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) { m_IsInteger = integerOnly; updateText(); } DoublePropertyWidget::DoublePropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); 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); }); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ m_Edit->setValue(m_Prop->Get()); }); } FloatPropertyWidget::FloatPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); 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); }); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); } IntPropertyWidget::IntPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_Edit = new UnitLineEdit(this); m_Edit->setIntegerOnly(true); 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); }); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ m_Edit->setValue((double)m_Prop->Get()); }); } BoolPropertyWidget::BoolPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_CheckBox = new QCheckBox(this); 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); }); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ if (m_CheckBox->isChecked() != m_Prop->Get()) { QSignalBlocker blocker(m_CheckBox); m_CheckBox->setChecked(m_Prop->Get()); } }); } BoolPropertyWidget::~BoolPropertyWidget() {} StringPropertyWidget::StringPropertyWidget(Property* prop, QWidget* parent) : PropertyWidgetBase(prop, parent), m_Prop(prop) { m_LineEdit = new QLineEdit(this); m_LineEdit->setText(QString::fromStdString(prop->Get())); m_Layout->addWidget(m_LineEdit, 1); connect(m_LineEdit, &QLineEdit::editingFinished, [this](){ std::string val = m_LineEdit->text().toStdString(); if (m_Prop->Get() != val) m_Prop->Set(val); }); m_Connection = uLib::Object::connect(m_Prop, &Property::PropertyChanged, [this](){ if (m_LineEdit->text().toStdString() != m_Prop->Get()) { QSignalBlocker blocker(m_LineEdit); m_LineEdit->setText(QString::fromStdString(m_Prop->Get())); } }); } 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*>(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::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); m_ScrollArea = new QScrollArea(this); m_ScrollArea->setWidgetResizable(true); m_MainLayout->addWidget(m_ScrollArea); m_Container = new QWidget(); m_ContainerLayout = new QVBoxLayout(m_Container); m_ContainerLayout->setAlignment(::Qt::AlignTop); m_ScrollArea->setWidget(m_Container); registerFactory([](PropertyBase* p, QWidget* parent){ return new DoublePropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new FloatPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new IntPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new BoolPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new StringPropertyWidget(static_cast*>(p), parent); }); // Register EnumProperty specifically (needs to check type since it holds Property but is EnumProperty) m_Factories[std::type_index(typeid(EnumProperty))] = [](PropertyBase* p, QWidget* parent) { return new EnumPropertyWidget(p, parent); }; // Vector Registration registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); registerFactory([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget(static_cast*>(p), parent); }); } PropertyEditor::~PropertyEditor() {} void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) { m_Object = obj; clear(); if (!obj) return; // Choose which properties to show const std::vector<::uLib::PropertyBase*>* props = &obj->GetProperties(); if (displayOnly) { if (auto* puppet = dynamic_cast<::uLib::Vtk::Puppet*>(obj)) { props = &puppet->GetDisplayProperties(); } else { // If it's not a puppet but displayOnly is requested, showing nothing or fallback? // Fallback: core properties. } } 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); } } m_ContainerLayout->addStretch(1); } void PropertyEditor::clear() { QLayoutItem* item; while ((item = m_ContainerLayout->takeAt(0)) != nullptr) { delete item->widget(); delete item; } } } // namespace Qt } // namespace uLib