diff --git a/GUI/Models/DoubleDescriptor.h b/GUI/Models/DoubleDescriptor.h index 80fc254783d314a890b17603690287f944b08bcc..9a6f2cfa381efe40d85d8a1ae9b59be58f6d52dd 100644 --- a/GUI/Models/DoubleDescriptor.h +++ b/GUI/Models/DoubleDescriptor.h @@ -76,7 +76,7 @@ public: RealLimits limits; //!< Limits of the value. function<void(double)> set = nullptr; //!< function to set the value function<double()> get = nullptr; //!< function to get the current value - variant<QString, Unit> unit = Unit::undefined; //!< Unit of the value (internal unit only!) + variant<QString, Unit> unit = Unit::unitless; //!< Unit of the value (internal unit only!) function<QString()> path = nullptr; //<! Path describing this value. Used e.g. for undo/redo }; diff --git a/GUI/Models/LayerItem.cpp b/GUI/Models/LayerItem.cpp index e41af63646274542a5bc8ecc4738ff8ebee034e2..c5d7c075620f9c76983fbfd8a333b906e11e4c10 100644 --- a/GUI/Models/LayerItem.cpp +++ b/GUI/Models/LayerItem.cpp @@ -41,6 +41,8 @@ LayerItem::LayerItem() : SessionGraphicsItem(M_TYPE), ItemWithMaterial(M_TYPE) ->setLimits(RealLimits::lowerLimited(0.0)) .setToolTip(layer_nslices_tooltip); + addProperty(P_COLOR, QColor()); // initially no color + GroupInfo info; info.add(LayerBasicRoughnessItem::M_TYPE, "Basic"); info.add(LayerZeroRoughnessItem::M_TYPE, "No"); @@ -124,6 +126,16 @@ void LayerItem::removeLayout(ParticleLayoutItem* layout) model()->removeItem(layout); } +QColor LayerItem::color() const +{ + return getItemValue(P_COLOR).value<QColor>(); +} + +void LayerItem::setColor(const QColor& color) +{ + setItemValue(P_COLOR, color); +} + void LayerItem::updateAppearance(SessionItem* new_parent) { if (!new_parent) { diff --git a/GUI/Models/LayerItem.h b/GUI/Models/LayerItem.h index 64dcf035bb5728dc00171e3f2702af87f3357d1e..913325bf14924a83fcc34fb36e7638e1fc0fd333 100644 --- a/GUI/Models/LayerItem.h +++ b/GUI/Models/LayerItem.h @@ -31,6 +31,7 @@ private: static constexpr auto P_ROUGHNESS{"Top roughness"}; static constexpr auto P_NSLICES{"Number of slices"}; static constexpr auto T_LAYOUTS{"Layout tag"}; + static constexpr auto P_COLOR{"Color"}; public: static constexpr auto M_TYPE{"Layer"}; @@ -53,6 +54,9 @@ public: QVector<ParticleLayoutItem*> layouts() const; void removeLayout(ParticleLayoutItem* layout); + QColor color() const; + void setColor(const QColor& color); + private: void updateAppearance(SessionItem* new_parent); diff --git a/GUI/Models/UIntDescriptor.h b/GUI/Models/UIntDescriptor.h index b7ea127b8f87b2b807e047cb2c28d0d7d76d7715..e0433fde72d575134bda90829358ba0fc198a610 100644 --- a/GUI/Models/UIntDescriptor.h +++ b/GUI/Models/UIntDescriptor.h @@ -60,12 +60,12 @@ public: //! Return the current value of the handled parameter. operator uint() const; - QString label; //!< A label text (short, no trailing colon) - QString tooltip; //!< Tooltip text - RealLimits limits; //!< Limits of the value. - function<void(uint)> set = nullptr; //!< function to set the value - function<uint()> get = nullptr; //!< function to get the current value - variant<QString, Unit> unit = Unit::undefined; //!< Unit of the value (internal unit only!) + QString label; //!< A label text (short, no trailing colon) + QString tooltip; //!< Tooltip text + RealLimits limits; //!< Limits of the value. + function<void(uint)> set = nullptr; //!< function to set the value + function<uint()> get = nullptr; //!< function to get the current value + variant<QString, Unit> unit = Unit::unitless; //!< Unit of the value (internal unit only!) function<QString()> path = nullptr; //<! Path describing this value. Used e.g. for undo/redo }; diff --git a/GUI/Models/Unit.cpp b/GUI/Models/Unit.cpp index 87bf0902c54c8bf4e2c737a445b15b9cad2a3cf9..e49eab8d544d687fb0cc4ecb907f8cf2559dcb54 100644 --- a/GUI/Models/Unit.cpp +++ b/GUI/Models/Unit.cpp @@ -15,10 +15,11 @@ #include "GUI/Models/Unit.h" #include "Base/Const/Units.h" #include "Base/Utils/Assert.h" +#include <QString> double convert(double d, Unit from, Unit to) { - if (from == Unit::undefined || to == Unit::undefined || from == to) + if (from == to) return d; if (from == Unit::angstrom && to == Unit::nanometer) @@ -45,6 +46,41 @@ double convert(double d, Unit from, Unit to) if (from == Unit::degree && to == Unit::radiant) return Units::deg2rad(d); + if (from == Unit::other || to == Unit::other) { + ASSERT(false); // no conversion possible + return d; + } + ASSERT(false); // no conversion implemented return d; } + +QString unitAsString(const Unit& unit) +{ + switch (unit) { + case Unit::unitless: + return ""; + case Unit::nanometer: + return "nm"; + case Unit::nanometerPower2: + return "nm²"; + case Unit::nanometerPowerMinus2: + return "1/nm²"; + case Unit::angstrom: + return "\303\205"; + case Unit::angstromPower2: + return "\303\205²"; + case Unit::angstromPowerMinus2: + return "1/\303\205²"; + case Unit::degree: + return "°"; + case Unit::radiant: + return "rad"; + case Unit::other: + ASSERT(false); // this function should not be called for Unit::other + return ""; + default: + ASSERT(false); // no string implemented + return ""; + } +} diff --git a/GUI/Models/Unit.h b/GUI/Models/Unit.h index 661f6aa0a898810e67b53ac77b3c7b36f75d1bb7..597935db447756e8f0a5db7f6bbe5038edce4b2b 100644 --- a/GUI/Models/Unit.h +++ b/GUI/Models/Unit.h @@ -15,13 +15,15 @@ #ifndef BORNAGAIN_GUI_MODELS_UNIT_H #define BORNAGAIN_GUI_MODELS_UNIT_H +class QString; + //! Defines units, mainly to be able to convert between units. +//! //! E.g. internal unit is nanometer, displayed unit is angstrom. //! Units which do not support conversion do not have to be //! part of the enum, since the relevant code parts support defining a //! unit via enum or via string enum class Unit { - undefined, unitless, nanometer, nanometerPower2, @@ -30,11 +32,15 @@ enum class Unit { angstromPower2, angstromPowerMinus2, degree, - radiant + radiant, + other //!< The unit has no enum value defined in here (e.g. when defined as an explicit string) }; //! Convert the given value d from unit "from" to unit "to" //! Throws an exception if no conversion possible. double convert(double d, Unit from, Unit to); +//! Returns the string for the given unit +QString unitAsString(const Unit& unit); + #endif // BORNAGAIN_GUI_MODELS_UNIT_H diff --git a/GUI/Views/CommonWidgets/DoubleLineEdit.cpp b/GUI/Views/CommonWidgets/DoubleLineEdit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2bff7cdcfde8991b1b8791fd18d5db85bf3c4181 --- /dev/null +++ b/GUI/Views/CommonWidgets/DoubleLineEdit.cpp @@ -0,0 +1,46 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/CommonWidgets/DoubleLineEdit.cpp +//! @brief Implements class DoubleLineEdit +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2021 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Views/CommonWidgets/DoubleLineEdit.h" +#include "GUI/Views/CommonWidgets/GUIHelpers.h" +#include <QDoubleValidator> + +DoubleLineEdit::DoubleLineEdit(QWidget* parent, const DoubleDescriptor& d) + : QLineEdit(parent), m_valueDescriptor(d) +{ + m_validator = new QDoubleValidator(0.0, 1e+200, 1000, this); + m_validator->setNotation(QDoubleValidator::ScientificNotation); + const double minimum = + d.limits.hasLowerLimit() ? std::max(d.limits.lowerLimit(), -1e+200) : -1e+200; + const double maximum = + d.limits.hasUpperLimit() ? std::min(d.limits.upperLimit(), +1e+200) : +1e+200; + m_validator->setRange(minimum, maximum, 1000); + setValidator(m_validator); + + setBaseValue(m_valueDescriptor.get()); + + connect(this, &QLineEdit::editingFinished, this, &DoubleLineEdit::onEditingFinished); +} + +void DoubleLineEdit::setBaseValue(double baseValue) +{ + setText(QString::number(baseValue, 'g')); +} + +void DoubleLineEdit::onEditingFinished() +{ + const double new_value = text().toDouble(); + if (new_value != m_valueDescriptor.get()) + emit baseValueChanged(new_value); +} diff --git a/GUI/Views/CommonWidgets/DoubleLineEdit.h b/GUI/Views/CommonWidgets/DoubleLineEdit.h new file mode 100644 index 0000000000000000000000000000000000000000..4aff9d7dfcede0b980f55445819303242a3fbb93 --- /dev/null +++ b/GUI/Views/CommonWidgets/DoubleLineEdit.h @@ -0,0 +1,51 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/CommonWidgets/DoubleLineEdit.h +//! @brief Defines class DoubleLineEdit +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2021 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLELINEEDIT_H +#define BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLELINEEDIT_H + +#include "GUI/Models/DoubleDescriptor.h" +#include <QLineEdit> + +class QDoubleValidator; + +//! LineEdit to edit values in a scientific notation, operating on a DoubleDescriptor. +//! +//! In the future it can be enhanced to support units. At the moment, no DoubleDescriptor with units +//! is used with a DoubleLineEdit, therefore the handling of units is not implemented yet. Only the +//! naming is prepared already (also to have a naming alike to DoubleSpinBox). +class DoubleLineEdit : public QLineEdit { + Q_OBJECT +public: + DoubleLineEdit(QWidget* parent, const DoubleDescriptor& d); + + //! Set the base value (unit is the one of the contained descriptor). + void setBaseValue(double baseValue); + +signals: + //! Emitted whenever the value changes. + //! + //! newBaseValue is in the unit of the valueDescriptor. + void baseValueChanged(double newBaseValue); + +private slots: + void onEditingFinished(); + +private: + QDoubleValidator* m_validator; + DoubleDescriptor m_valueDescriptor; +}; + + +#endif // BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLELINEEDIT_H diff --git a/GUI/Views/CommonWidgets/DoubleSpinBox.cpp b/GUI/Views/CommonWidgets/DoubleSpinBox.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f08d4180743283fab736b21ef39ce1dde038e586 --- /dev/null +++ b/GUI/Views/CommonWidgets/DoubleSpinBox.cpp @@ -0,0 +1,89 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/CommonWidgets/DoubleSpinBox.cpp +//! @brief Implements class DoubleSpinBox +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2021 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#include "GUI/Views/CommonWidgets/DoubleSpinBox.h" +#include "GUI/Views/CommonWidgets/GUIHelpers.h" + +DoubleSpinBox::DoubleSpinBox(QWidget* parent, const DoubleDescriptor& d) + : QDoubleSpinBox(parent), m_valueDescriptor(d) +{ + GUI::View::Helpers::configSpinbox(this, d.decimals, d.limits); + setToolTip(d.tooltip); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + if (std::holds_alternative<QString>(m_valueDescriptor.unit)) + setDisplayUnit(Unit::other); + else + setDisplayUnit(std::get<Unit>(m_valueDescriptor.unit)); + + QObject::connect(this, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, + &DoubleSpinBox::onDisplayValueChanged); +} + +void DoubleSpinBox::setDisplayUnit(Unit displayUnit) +{ + m_displayUnit = displayUnit; + + if (m_showUnitAsSuffix) { + const QString suffix = displayUnitAsString(); + if (suffix.isEmpty()) + setSuffix(""); + else + setSuffix(" " + suffix); + } + + QSignalBlocker b(this); + setValue(toDisplayValue(m_valueDescriptor.get())); +} + +double DoubleSpinBox::toDisplayValue(double baseValue) const +{ + return convert(baseValue, baseUnit(), m_displayUnit); +} + +double DoubleSpinBox::toBaseValue(double displayValue) const +{ + return convert(displayValue, m_displayUnit, baseUnit()); +} + +QString DoubleSpinBox::displayUnitAsString() const +{ + if (std::holds_alternative<QString>(m_valueDescriptor.unit)) + return std::get<QString>(m_valueDescriptor.unit); + + return unitAsString(m_displayUnit); +} + +const DoubleDescriptor& DoubleSpinBox::valueDescriptor() const +{ + return m_valueDescriptor; +} + +void DoubleSpinBox::setBaseValue(double baseValue) +{ + setValue(toDisplayValue(baseValue)); +} + +void DoubleSpinBox::onDisplayValueChanged(double newDisplayValue) +{ + emit baseValueChanged(toBaseValue(newDisplayValue)); +} + +Unit DoubleSpinBox::baseUnit() const +{ + if (std::holds_alternative<QString>(m_valueDescriptor.unit)) + return Unit::other; + + return std::get<Unit>(m_valueDescriptor.unit); +} diff --git a/GUI/Views/CommonWidgets/DoubleSpinBox.h b/GUI/Views/CommonWidgets/DoubleSpinBox.h new file mode 100644 index 0000000000000000000000000000000000000000..103ede224bd195f8e0f6a44dc982e73927f49605 --- /dev/null +++ b/GUI/Views/CommonWidgets/DoubleSpinBox.h @@ -0,0 +1,73 @@ +// ************************************************************************************************ +// +// BornAgain: simulate and fit reflection and scattering +// +//! @file GUI/Views/CommonWidgets/DoubleSpinBox.h +//! @brief Defines class DoubleSpinBox +//! +//! @homepage http://www.bornagainproject.org +//! @license GNU General Public License v3 or higher (see COPYING) +//! @copyright Forschungszentrum Jülich GmbH 2021 +//! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS) +// +// ************************************************************************************************ + +#ifndef BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLESPINBOX_H +#define BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLESPINBOX_H + +#include "GUI/Models/DoubleDescriptor.h" +#include <QDoubleSpinBox> + +//! SpinBox for DoubleDescriptors, supporting units. +class DoubleSpinBox : public QDoubleSpinBox { + Q_OBJECT +public: + DoubleSpinBox(QWidget* parent, const DoubleDescriptor& d); + + //! Set a display unit. + //! + //! The caller has to make sure that the new display unit has a conversion to/from the contained + //! base value unit. + void setDisplayUnit(Unit displayUnit); + + //! Set the base value (unit is the one of the contained descriptor). + void setBaseValue(double baseValue); + + //! The display unit as human readable string. + QString displayUnitAsString() const; + + //! The descriptor on which this spinbox operates. + const DoubleDescriptor& valueDescriptor() const; + + //! Returns the unit of the contained DoubleDescriptor. + //! + //! If the unit is defined as a string, this method returns Unit::other. To get the string, use + //! valueDescriptor().unit + Unit baseUnit() const; + +signals: + //! Emitted whenever the value changes. + //! + //! newBaseValue is in the unit of the valueDescriptor. + void baseValueChanged(double newBaseValue); + +private: + void onDisplayValueChanged(double newValue); + + double toDisplayValue(double baseValue) const; + double toBaseValue(double displayValue) const; + + using QDoubleSpinBox::setValue; // To hide from usage + +private: + Unit m_displayUnit = Unit::unitless; + + DoubleDescriptor m_valueDescriptor; + + //! it was decided to not show the unit as a suffix. However, this may be user + //! selectable once, therefore the code is kept and controlled by this flag + bool m_showUnitAsSuffix = false; +}; + + +#endif // BORNAGAIN_GUI_VIEWS_COMMONWIDGETS_DOUBLESPINBOX_H diff --git a/GUI/mainwindow/actionmanager.cpp b/GUI/mainwindow/actionmanager.cpp index 4e0f6dcad20938c5b089aecb9167f51fdda35d46..84e373bc7faf90c65de053719ddc14097c699c90 100644 --- a/GUI/mainwindow/actionmanager.cpp +++ b/GUI/mainwindow/actionmanager.cpp @@ -226,6 +226,8 @@ void ActionManager::onAboutToShowFileMenu() void ActionManager::onAboutToShowSettingsMenu() { m_settingsMenu->clear(); + m_settingsMenu->setToolTipsVisible(true); + QSettings settings; settings.beginGroup(GUI::Constants::S_SESSIONMODELVIEW); @@ -250,8 +252,6 @@ void ActionManager::onAboutToShowSettingsMenu() connect(action, &QAction::toggled, [](bool b) { baApp->settings().setCreateNewProjectOnStartup(b); }); - m_settingsMenu->setToolTipsVisible(true); - m_settingsMenu->addSeparator(); QActionGroup* styleActions = new QActionGroup(this); diff --git a/Tests/Unit/GUI/TestComponentProxyModel.cpp b/Tests/Unit/GUI/TestComponentProxyModel.cpp index b2fef3e50731bff9730c14422521590750b1cfce..3cbd9e2c445e22e6f544298d16218b140b84b5b5 100644 --- a/Tests/Unit/GUI/TestComponentProxyModel.cpp +++ b/Tests/Unit/GUI/TestComponentProxyModel.cpp @@ -373,7 +373,7 @@ TEST_F(TestComponentProxyModel, test_setRootIndexLayer) EXPECT_FALSE(multilayerProxyIndex.isValid()); QModelIndex layerProxyIndex = proxy.mapFromSource(model.indexOfItem(layer1)); - EXPECT_EQ(proxy.rowCount(layerProxyIndex), 4); // thickness, material, slices, roughness + EXPECT_EQ(proxy.rowCount(layerProxyIndex), 5); // thickness, material, slices, roughness, color EXPECT_EQ(proxy.columnCount(layerProxyIndex), 2); EXPECT_TRUE(layerProxyIndex.isValid()); EXPECT_TRUE(layerProxyIndex.parent() == QModelIndex());