Simplify UI
- Greatly simplify distribution editors; get rid of a lot of complicated UI code
- Fix missing units (-> now they are like on other UI spots)
This will as well ease fixing #175 (closed) a lot, since the spinbox creation is centralized to one spot instead of dozens.
For further migration:
This MR can also be used as a documentation how to simplify UI by using descriptors.
As done on many spots already, it works like this:
A certain value is described by a descriptor, e.g. the standard deviation in a Gauss distribution by DoubleDescriptor
. In this descriptor, all relevant UI information is contained (label, tooltip, unit, current value, setter, getter, limits). So for creation of a spinbox to edit this value a commonly used method can be used, instead of repeatingly creating the UI elements from scratch. When using these simplifying methods, also the usage of sub-classes can be avoided since the code becomes so simple that using separate classes would be a useless and complicating overhead.
Example standard deviation in Gauss distribution:
The following old code shows only the elements necessary for the one value "standard deviation"; for all other values a similar amount of code is necessary in addition.
// extra form for gauss distribution
class GaussDistributionForm : public DistributionForm {
Q_OBJECT
public:
GaussDistributionForm(const std::optional<MeanConfig>& mean_config, QWidget* parent = nullptr,
Qt::WindowFlags f = Qt::WindowFlags());
void setupDistribution(BeamDistributionItem* item) override;
private slots:
void onDeviationChanged(double value);
private:
QDoubleSpinBox* m_stdDevSpinBox;
DistributionGaussianItem* m_item;
};
// ---------- implementation of GaussDistributionForm
GaussDistributionForm::GaussDistributionForm(const std::optional<MeanConfig>& mean_config,
QWidget* parent, Qt::WindowFlags f)
: DistributionForm(parent, f)
, m_item(nullptr)
{
auto* layout = new QFormLayout(this);
layout->setMargin(0);
// ...
m_stdDevSpinBox = new QDoubleSpinBox(this);
configSpinBox(m_stdDevSpinBox, Range::nonnegative);
connect(m_stdDevSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this,
&GaussDistributionForm::onDeviationChanged);
layout->addRow(u8"Standard deviation (\u03c3):", m_stdDevSpinBox);
}
void GaussDistributionForm::setupDistribution(BeamDistributionItem* item)
{
// ...
m_stdDevSpinBox->setValue(m_item->standardDeviation());
}
void GaussDistributionForm::onDeviationChanged(double value)
{
m_item->setStandardDeviation(value);
emit distributionChanged();
}
// ------- usage of this form:
void DistributionSelector::createDistributionWidgets()
{
DistributionForm* form = nullptr;
if (m_item->distribution()->is<DistributionGaussianItem>())
form = new GaussDistributionForm(m_meanConfig, this);
// .... more if's for other distributions
ASSERT(form);
form->setupDistribution(m_item);
m_formLayout->addRow(form);
connect(form, &CosineDistributionForm::distributionChanged, this,
&DistributionSelector::distributionChanged);
}
In the new code, the spinboxes are created directly in DistributionSelector::createDistributionWidgets()
instead of in a separate class. The spinboxes are created with the dedicated methods from GUI::Util::createSpinBox()
, and they are directly initialized and connected to changes by using the descriptor's getter and setter. To achive this, one creation method for all double descriptors was added. No extra code is necessary besides this creation.
DoubleSpinBox* DistributionSelector::createSpinBox(const DoubleDescriptor& d)
{
auto* sb = GUI::Util::createSpinBox(m_formLayout, d);
connect(sb, &DoubleSpinBox::baseValueChanged, [=](double v) {
d.set(v);
emit distributionChanged();
});
return sb;
}
void DistributionSelector::createDistributionWidgets()
{
if (auto* gauss = dynamic_cast<DistributionGaussianItem*>(m_item->distribution())) {
createMeanSpinBox(gauss->mean());
createSpinBox(gauss->standardDeviation());
createSpinBox(gauss->numberOfSamples());
createSpinBox(gauss->sigmaFactor());
}
// more if clauses for other distribution types
}