606 lines
24 KiB
C++
606 lines
24 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"
|
|
#include <QPushButton>
|
|
#include <QColorDialog>
|
|
#include <QFrame>
|
|
#include <QSlider>
|
|
#include <QFontDialog>
|
|
#include "Settings.h"
|
|
#include "Core/ObjectsContext.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);
|
|
|
|
std::string unit = prop->GetUnits();
|
|
QString labelText = QString::fromStdString(prop->GetName());
|
|
if (!unit.empty() && unit != "color") {
|
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
|
if (!pref.empty()) {
|
|
labelText += " [" + QString::fromStdString(pref) + "]";
|
|
} else {
|
|
labelText += " [" + QString::fromStdString(unit) + "]";
|
|
}
|
|
}
|
|
|
|
m_Label = new QLabel(labelText, this);
|
|
m_Label->setMinimumWidth(120);
|
|
m_Layout->addWidget(m_Label);
|
|
|
|
this->setEnabled(!prop->IsReadOnly());
|
|
}
|
|
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 = factorOut ? *factorOut : 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 (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";
|
|
}
|
|
}
|
|
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);
|
|
std::string unit = prop->GetUnits();
|
|
if (!unit.empty()) {
|
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
|
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
|
}
|
|
m_Edit->setValue(prop->Get());
|
|
m_Layout->addWidget(m_Edit, 1);
|
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); emit updated(); });
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::Updated, [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);
|
|
std::string unit = prop->GetUnits();
|
|
if (!unit.empty()) {
|
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
|
m_Edit->setUnits(QString::fromStdString(pref), 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); emit updated(); });
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<float>::Updated, [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);
|
|
std::string unit = prop->GetUnits();
|
|
if (!unit.empty()) {
|
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
|
m_Edit->setUnits(QString::fromStdString(pref), 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); emit updated(); });
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<int>::Updated, [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); emit updated(); } });
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<bool>::Updated, [this](){
|
|
if (m_CheckBox->isChecked() != m_Prop->Get()) {
|
|
QSignalBlocker blocker(m_CheckBox);
|
|
m_CheckBox->setChecked(m_Prop->Get());
|
|
}
|
|
});
|
|
}
|
|
BoolPropertyWidget::~BoolPropertyWidget() {}
|
|
|
|
RangePropertyWidget::RangePropertyWidget(Property<double>* prop, QWidget* parent)
|
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
|
m_Slider = new QSlider(::Qt::Horizontal, this);
|
|
m_Slider->setRange(0, 100);
|
|
m_Slider->setMinimumWidth(80);
|
|
|
|
m_Edit = new UnitLineEdit(this);
|
|
m_Edit->setFixedWidth(50);
|
|
|
|
m_Layout->addWidget(m_Slider, 1);
|
|
m_Layout->addWidget(m_Edit, 0);
|
|
|
|
connect(m_Slider, &QSlider::valueChanged, this, &RangePropertyWidget::onSliderChanged);
|
|
connect(m_Edit, &UnitLineEdit::valueManualChanged, [this](double val){ m_Prop->Set(val); emit updated(); });
|
|
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<double>::Updated, [this](){
|
|
this->updateUi();
|
|
});
|
|
updateUi();
|
|
}
|
|
RangePropertyWidget::~RangePropertyWidget() { m_Connection.disconnect(); }
|
|
|
|
void RangePropertyWidget::updateUi() {
|
|
double val = m_Prop->Get();
|
|
m_Edit->setValue(val);
|
|
if (m_Prop->GetMax() != m_Prop->GetMin()) {
|
|
int sliderVal = (int)((val - m_Prop->GetMin()) / (m_Prop->GetMax() - m_Prop->GetMin()) * 100.0);
|
|
QSignalBlocker blocker(m_Slider);
|
|
m_Slider->setValue(sliderVal);
|
|
}
|
|
}
|
|
|
|
void RangePropertyWidget::onSliderChanged(int val) {
|
|
double realVal = m_Prop->GetMin() + (val / 100.0) * (m_Prop->GetMax() - m_Prop->GetMin());
|
|
m_Prop->Set(realVal);
|
|
emit updated();
|
|
}
|
|
|
|
ColorPropertyWidget::ColorPropertyWidget(Property<Vector3d>* prop, QWidget* parent)
|
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
|
m_Button = new QPushButton(this);
|
|
m_Button->setFixedWidth(60);
|
|
this->updateButtonColor();
|
|
m_Layout->addWidget(m_Button, 0, ::Qt::AlignRight);
|
|
|
|
connect(m_Button, &QPushButton::clicked, this, &ColorPropertyWidget::onClicked);
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<Vector3d>::Updated, [this](){
|
|
this->updateButtonColor();
|
|
});
|
|
}
|
|
ColorPropertyWidget::~ColorPropertyWidget() {}
|
|
|
|
void ColorPropertyWidget::updateButtonColor() {
|
|
Vector3d c = m_Prop->Get();
|
|
QColor color = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())),
|
|
std::max(0.0, std::min(1.0, c.y())),
|
|
std::max(0.0, std::min(1.0, c.z())));
|
|
m_Button->setStyleSheet(QString("background-color: %1; border: 1px solid #555; height: 18px;").arg(color.name()));
|
|
}
|
|
|
|
void ColorPropertyWidget::onClicked() {
|
|
Vector3d c = m_Prop->Get();
|
|
QColor current = QColor::fromRgbF(std::max(0.0, std::min(1.0, c.x())),
|
|
std::max(0.0, std::min(1.0, c.y())),
|
|
std::max(0.0, std::min(1.0, c.z())));
|
|
QColor selected = QColorDialog::getColor(current, this, "Select Color");
|
|
if (selected.isValid()) {
|
|
m_Prop->Set(Vector3d(selected.redF(), selected.greenF(), selected.blueF()));
|
|
emit updated();
|
|
}
|
|
}
|
|
|
|
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); emit updated(); }
|
|
});
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<std::string>::Updated, [this](){
|
|
if (m_LineEdit->text().toStdString() != m_Prop->Get()) {
|
|
QSignalBlocker blocker(m_LineEdit);
|
|
m_LineEdit->setText(QString::fromStdString(m_Prop->Get()));
|
|
}
|
|
});
|
|
}
|
|
StringPropertyWidget::~StringPropertyWidget() {}
|
|
|
|
FontPropertyWidget::FontPropertyWidget(Property<FontConfig>* prop, QWidget* parent)
|
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
|
m_Button = new QPushButton(this);
|
|
m_Button->setMinimumWidth(100);
|
|
this->updateButtonText();
|
|
m_Layout->addWidget(m_Button, 1);
|
|
|
|
connect(m_Button, &QPushButton::clicked, this, &FontPropertyWidget::onClicked);
|
|
m_Connection = uLib::Object::connect(m_Prop, &Property<FontConfig>::Updated, [this](){
|
|
this->updateButtonText();
|
|
});
|
|
}
|
|
FontPropertyWidget::~FontPropertyWidget() {}
|
|
|
|
void FontPropertyWidget::updateButtonText() {
|
|
FontConfig f = m_Prop->Get();
|
|
m_Button->setText(QString::fromStdString(f.family) + " " + QString::number(f.size));
|
|
}
|
|
|
|
void FontPropertyWidget::onClicked() {
|
|
FontConfig current = m_Prop->Get();
|
|
QFont font(QString::fromStdString(current.family), current.size);
|
|
font.setBold(current.bold);
|
|
font.setItalic(current.italic);
|
|
|
|
bool ok;
|
|
QFont selected = QFontDialog::getFont(&ok, font, this, "Select Font");
|
|
if (ok) {
|
|
FontConfig newF(selected.family().toStdString(), selected.pointSize(), selected.bold(), selected.italic());
|
|
m_Prop->Set(newF);
|
|
emit updated();
|
|
}
|
|
}
|
|
|
|
class GroupHeaderWidget : public QWidget {
|
|
public:
|
|
GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) {
|
|
auto* layout = new QVBoxLayout(this);
|
|
layout->setContentsMargins(0, 8, 0, 4);
|
|
auto* line = new QFrame(this);
|
|
line->setFrameShape(QFrame::HLine);
|
|
line->setFrameShadow(QFrame::Sunken);
|
|
line->setStyleSheet("color: #555;");
|
|
layout->addWidget(line);
|
|
auto* label = new QLabel(name, this);
|
|
QFont font = label->font();
|
|
font.setBold(true);
|
|
font.setPointSize(font.pointSize() + 1);
|
|
label->setFont(font);
|
|
label->setStyleSheet("color: #aaa; text-transform: uppercase;");
|
|
layout->addWidget(label);
|
|
}
|
|
};
|
|
|
|
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, [this, p](int index){
|
|
p->Set(index);
|
|
emit updated();
|
|
});
|
|
// Store connection in base m_Connection so it's auto-disconnected on destruction.
|
|
m_Connection = uLib::Object::connect(p, &Property<int>::Updated, [this, p](){
|
|
if (m_Combo->currentIndex() != p->Get()) {
|
|
QSignalBlocker blocker(m_Combo);
|
|
m_Combo->setCurrentIndex(p->Get());
|
|
}
|
|
});
|
|
}
|
|
|
|
m_Layout->addWidget(m_Combo, 1);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// 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 (const auto& obj : objects) {
|
|
if (m_RefProp->IsCompatible(obj.get())) {
|
|
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.get()));
|
|
if (obj.get() == 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);
|
|
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);
|
|
});
|
|
registerFactory<FontConfig>([](PropertyBase* p, QWidget* parent){
|
|
return new FontPropertyWidget(static_cast<Property<FontConfig>*>(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* prop3d = dynamic_cast<::uLib::Vtk::Prop3D*>(obj)) {
|
|
props = &prop3d->GetDisplayProperties();
|
|
} else {
|
|
// If it's not a prop3d but displayOnly is requested, showing nothing or fallback?
|
|
// Fallback: core properties.
|
|
}
|
|
}
|
|
|
|
// Group properties by their group string
|
|
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
|
|
std::vector<std::string> groupOrder;
|
|
|
|
for (auto* prop : *props) {
|
|
std::string group = prop->GetGroup();
|
|
if (groupedProps.find(group) == groupedProps.end()) {
|
|
groupOrder.push_back(group);
|
|
}
|
|
groupedProps[group].push_back(prop);
|
|
}
|
|
|
|
for (const auto& groupName : groupOrder) {
|
|
if (!groupName.empty()) {
|
|
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
|
|
}
|
|
|
|
for (auto* prop : groupedProps[groupName]) {
|
|
QWidget* widget = nullptr;
|
|
|
|
// Priority 1: Check if it provides enum labels
|
|
if (!prop->GetEnumLabels().empty()) {
|
|
widget = new EnumPropertyWidget(prop, m_Container);
|
|
} else if (prop->GetUnits() == "color") {
|
|
// Color Picker for Vector3d
|
|
if (auto* pvec = dynamic_cast<Property<Vector3d>*>(prop)) {
|
|
widget = new ColorPropertyWidget(pvec, m_Container);
|
|
}
|
|
} else if (prop->HasRange()) {
|
|
// Slider for ranged doubles
|
|
if (auto* pdbl = dynamic_cast<Property<double>*>(prop)) {
|
|
widget = new RangePropertyWidget(pdbl, m_Container);
|
|
} else if (auto* pflt = dynamic_cast<Property<float>*>(prop)) {
|
|
// widget = new RangePropertyWidget<float>(pflt, m_Container);
|
|
}
|
|
} else {
|
|
// 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;
|
|
|
|
widget = new PropertyWidgetBase(prop, m_Container);
|
|
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (widget) {
|
|
if (auto* propWidget = qobject_cast<PropertyWidgetBase*>(widget)) {
|
|
connect(propWidget, &PropertyWidgetBase::updated, [this, prop](){
|
|
emit propertyUpdated(prop);
|
|
});
|
|
}
|
|
|
|
if (!groupName.empty()) {
|
|
// Indent grouped properties
|
|
widget->setContentsMargins(16, 0, 0, 0);
|
|
}
|
|
m_ContainerLayout->addWidget(widget);
|
|
}
|
|
}
|
|
}
|
|
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
|