Files
uLib/app/gcompose/src/PropertyWidgets.cpp
2026-03-26 09:50:52 +00:00

343 lines
14 KiB
C++

#include "PropertyWidgets.h"
#include <QSignalBlocker>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QComboBox>
#include <QCheckBox>
#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<double>* 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<double>::PropertyChanged, [this](){
m_Edit->setValue(m_Prop->Get());
});
}
FloatPropertyWidget::FloatPropertyWidget(Property<float>* 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<float>::PropertyChanged, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
}
IntPropertyWidget::IntPropertyWidget(Property<int>* 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<int>::PropertyChanged, [this](){
m_Edit->setValue((double)m_Prop->Get());
});
}
BoolPropertyWidget::BoolPropertyWidget(Property<bool>* 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<bool>::PropertyChanged, [this](){
if (m_CheckBox->isChecked() != m_Prop->Get()) {
QSignalBlocker blocker(m_CheckBox);
m_CheckBox->setChecked(m_Prop->Get());
}
});
}
BoolPropertyWidget::~BoolPropertyWidget() {}
StringPropertyWidget::StringPropertyWidget(Property<std::string>* 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<std::string>::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<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);
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<double>([](PropertyBase* p, QWidget* parent){
return new DoublePropertyWidget(static_cast<Property<double>*>(p), parent);
});
registerFactory<float>([](PropertyBase* p, QWidget* parent){
return new FloatPropertyWidget(static_cast<Property<float>*>(p), parent);
});
registerFactory<int>([](PropertyBase* p, QWidget* parent){
return new IntPropertyWidget(static_cast<Property<int>*>(p), parent);
});
registerFactory<bool>([](PropertyBase* p, QWidget* parent){
return new BoolPropertyWidget(static_cast<Property<bool>*>(p), parent);
});
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); });
registerFactory<Vector2f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2f, 2>(static_cast<Property<Vector2f>*>(p), parent); });
registerFactory<Vector2d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector2d, 2>(static_cast<Property<Vector2d>*>(p), parent); });
registerFactory<Vector3i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3i, 3>(static_cast<Property<Vector3i>*>(p), parent); });
registerFactory<Vector3f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3f, 3>(static_cast<Property<Vector3f>*>(p), parent); });
registerFactory<Vector3d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector3d, 3>(static_cast<Property<Vector3d>*>(p), parent); });
registerFactory<Vector4i>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4i, 4>(static_cast<Property<Vector4i>*>(p), parent); });
registerFactory<Vector4f>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4f, 4>(static_cast<Property<Vector4f>*>(p), parent); });
registerFactory<Vector4d>([](PropertyBase* p, QWidget* parent){ return new VectorPropertyWidget<Vector4d, 4>(static_cast<Property<Vector4d>*>(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