From 77e246726771d2f5025487ad6fdb16b8bca4dfe9 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Thu, 30 Sep 2021 07:56:21 +0200
Subject: [PATCH] introduce descriptor class to describe properties of a double
 value

---
 GUI/Models/DoubleDescriptor.cpp | 81 ++++++++++++++++++++++++++++
 GUI/Models/DoubleDescriptor.h   | 94 +++++++++++++++++++++++++++++++++
 GUI/Models/ModelPath.cpp        |  6 +++
 GUI/Models/ModelPath.h          |  2 +
 GUI/Models/Unit.cpp             | 50 ++++++++++++++++++
 GUI/Models/Unit.h               | 40 ++++++++++++++
 6 files changed, 273 insertions(+)
 create mode 100644 GUI/Models/DoubleDescriptor.cpp
 create mode 100644 GUI/Models/DoubleDescriptor.h
 create mode 100644 GUI/Models/Unit.cpp
 create mode 100644 GUI/Models/Unit.h

diff --git a/GUI/Models/DoubleDescriptor.cpp b/GUI/Models/DoubleDescriptor.cpp
new file mode 100644
index 00000000000..2c2d879fd17
--- /dev/null
+++ b/GUI/Models/DoubleDescriptor.cpp
@@ -0,0 +1,81 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Models/DoubleDescriptor.cpp
+//! @brief     Implements class DoubleDescriptor
+//!
+//! @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/Models/DoubleDescriptor.h"
+#include "GUI/Models/ModelPath.h"
+#include "GUI/Models/SessionItem.h"
+
+DoubleDescriptor::DoubleDescriptor(const QString& label, SessionItem* item,
+                                   const variant<QString, Unit>& unit)
+    : DoubleDescriptor(
+        label, item->toolTip(), item->decimals(), item->limits(),
+        [=](double d) { item->setValue(d); }, [=]() { return item->value().toDouble(); }, unit)
+{
+    path = [=] { return GUI::Model::Path::getPathFromItem(item); };
+}
+
+DoubleDescriptor::DoubleDescriptor(SessionItem* item, const variant<QString, Unit>& unit)
+    : DoubleDescriptor(item->displayName(), item, unit)
+{
+}
+
+DoubleDescriptor::DoubleDescriptor(const QString& label, const QString& tooltip,
+                                   function<void(double)> setter, function<double()> getter,
+                                   const variant<QString, Unit>& unit)
+    : DoubleDescriptor(label, tooltip, 3, RealLimits::nonnegative(), setter, getter, unit)
+{
+}
+
+DoubleDescriptor::DoubleDescriptor(const QString& label, const QString& tooltip, int decimals,
+                                   const RealLimits& limits, function<void(double)> setter,
+                                   function<double()> getter, const variant<QString, Unit>& unit)
+    : label(label)
+    , tooltip(tooltip)
+    , decimals(decimals)
+    , limits(limits)
+    , set(setter)
+    , get(getter)
+    , unit(unit)
+{
+    // #baLayerEditor the following path creation has to be implemented when SessionItem migration
+    // has finished
+    path = [] { return QString(); };
+}
+
+DoubleDescriptor::DoubleDescriptor() {}
+
+DoubleDescriptor::DoubleDescriptor(const QString& label, const QString& tooltip, double* var,
+                                   const variant<QString, Unit>& unit)
+    : DoubleDescriptor(label, tooltip, 3, RealLimits::nonnegative(), var, unit)
+
+{
+}
+
+DoubleDescriptor::DoubleDescriptor(const QString& label, const QString& tooltip, int decimals,
+                                   const RealLimits& limits, double* var,
+                                   const variant<QString, Unit>& unit)
+    : DoubleDescriptor(
+        label, tooltip, decimals, limits, [=](double v) { *var = v; }, [=] { return *var; }, unit)
+{
+}
+
+void DoubleDescriptor::init(SessionItem* item, const variant<QString, Unit>& unit)
+{
+    operator=(DoubleDescriptor(item, unit));
+}
+
+DoubleDescriptor::operator double() const
+{
+    return get();
+}
diff --git a/GUI/Models/DoubleDescriptor.h b/GUI/Models/DoubleDescriptor.h
new file mode 100644
index 00000000000..34d2a87d9e4
--- /dev/null
+++ b/GUI/Models/DoubleDescriptor.h
@@ -0,0 +1,94 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Models/DoubleDescriptor.h
+//! @brief     Defines class DoubleDescriptor
+//!
+//! @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_MODELS_DOUBLEDESCRIPTOR_H
+#define BORNAGAIN_GUI_MODELS_DOUBLEDESCRIPTOR_H
+
+#include "Fit/Param/RealLimits.h"
+#include "GUI/Models/Unit.h"
+#include <QString>
+#include <functional>
+#include <variant>
+
+class SessionItem;
+
+using std::function;
+using std::variant;
+
+//! Describes properties of a double value which are necessary to allow GUI representation, editing
+//! the value, undo/redo, unit conversion.
+//!
+//! By using this class, the underlying data scheme is hidden from the user of the data. This e.g.
+//! eases SessionItem migration. The underlying implementation can be a SessionItem, a simple double
+//! member, or any other construction to hold a double value.
+class DoubleDescriptor {
+
+public:
+    //! Operates on a session item. The settings (like decimals, limits) are taken from the session
+    //! item.
+    //! Only for easier migration. Should be removed after SessionItem refactoring.
+    DoubleDescriptor(SessionItem* item, const variant<QString, Unit>& unit);
+
+    //! Operates on a session item. The settings (like decimals, limits) are taken from the session
+    //! item.
+    //! Only for easier migration. Should be removed after SessionItem refactoring.
+    DoubleDescriptor(const QString& label, SessionItem* item, const variant<QString, Unit>& unit);
+
+private: // private as long as path initialization is not included in params (to be done after
+         // SessionItem migration)
+    //! Operates on a double value (e.g a member variable).
+    DoubleDescriptor(const QString& label, const QString& tooltip, int decimals,
+                     const RealLimits& limits, double* var, const variant<QString, Unit>& unit);
+
+    //! Operates on a double value (e.g a member variable).
+    //! Decimals is set to 3, limits is set to nonnegative
+    DoubleDescriptor(const QString& label, const QString& tooltip, double* var,
+                     const variant<QString, Unit>& unit);
+
+    //! Operates on any kind of storage (e.g. session items), by using setter/getter methods
+    DoubleDescriptor(const QString& label, const QString& tooltip, int decimals,
+                     const RealLimits& limits, function<void(double)> setter,
+                     function<double()> getter, const variant<QString, Unit>& unit);
+
+    //! Operates on any kind of storage (e.g. session items), by using setter/getter methods
+    //! decimals is set to 3, limits is set to nonnegative
+    DoubleDescriptor(const QString& label, const QString& tooltip, function<void(double)> setter,
+                     function<double()> getter, const variant<QString, Unit>& unit);
+
+public:
+    //! For delayed initialization.
+    //! Call init before usage!
+    DoubleDescriptor();
+
+    //! Initialize with a session item. To be used if an initialization already in the constructor
+    //! is not possible. Only for easier migration. Should be removed after SessionItem refactoring.
+    void init(SessionItem* item, const variant<QString, Unit>& unit);
+
+    //! Return the current value of the handled parameter.
+    operator double() const;
+
+    QString label;                        //!< A label text (short, no trailing colon)
+    QString tooltip;                      //!< Tooltip text
+    int decimals = 0;                     //!< numbers of decimals to be shown in an edit control
+    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!)
+    function<QString()> path = nullptr; //<! Path describing  this value. Used e.g. for undo/redo
+};
+
+typedef QList<DoubleDescriptor> DoubleDescriptors;
+
+
+#endif // BORNAGAIN_GUI_MODELS_DOUBLEDESCRIPTOR_H
diff --git a/GUI/Models/ModelPath.cpp b/GUI/Models/ModelPath.cpp
index 24cc43a894f..a2f1195b79a 100644
--- a/GUI/Models/ModelPath.cpp
+++ b/GUI/Models/ModelPath.cpp
@@ -95,3 +95,9 @@ const SessionItem* GUI::Model::Path::ancestor(const SessionItem* item,
 
     return cur;
 }
+
+QString GUI::Model::Path::getPathFromItem(SessionItem* item)
+{
+    ASSERT(item->model()); // if assert, item is not completely initialized
+    return getPathFromIndex(item->model()->indexOfItem(item));
+}
diff --git a/GUI/Models/ModelPath.h b/GUI/Models/ModelPath.h
index f70740cbd70..3cf7792d67b 100644
--- a/GUI/Models/ModelPath.h
+++ b/GUI/Models/ModelPath.h
@@ -27,6 +27,8 @@ namespace GUI::Model::Path {
 
 QString getPathFromIndex(const QModelIndex& index);
 
+QString getPathFromItem(SessionItem* item);
+
 QModelIndex getIndexFromPath(const SessionModel* model, const QString& path);
 
 SessionItem* getItemFromPath(const QString& relPath, const SessionItem* parent);
diff --git a/GUI/Models/Unit.cpp b/GUI/Models/Unit.cpp
new file mode 100644
index 00000000000..87bf0902c54
--- /dev/null
+++ b/GUI/Models/Unit.cpp
@@ -0,0 +1,50 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Models/Unit.cpp
+//! @brief     Implements class Unit
+//!
+//! @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/Models/Unit.h"
+#include "Base/Const/Units.h"
+#include "Base/Utils/Assert.h"
+
+double convert(double d, Unit from, Unit to)
+{
+    if (from == Unit::undefined || to == Unit::undefined || from == to)
+        return d;
+
+    if (from == Unit::angstrom && to == Unit::nanometer)
+        return d / 10.0;
+
+    if (from == Unit::nanometer && to == Unit::angstrom)
+        return d * 10.0;
+
+    if (from == Unit::angstromPower2 && to == Unit::nanometerPower2)
+        return d / 100.0;
+
+    if (from == Unit::nanometerPower2 && to == Unit::angstromPower2)
+        return d * 100.0;
+
+    if (from == Unit::angstromPowerMinus2 && to == Unit::nanometerPowerMinus2)
+        return d * 100.0;
+
+    if (from == Unit::nanometerPowerMinus2 && to == Unit::angstromPowerMinus2)
+        return d / 100.0;
+
+    if (from == Unit::radiant && to == Unit::degree)
+        return Units::rad2deg(d);
+
+    if (from == Unit::degree && to == Unit::radiant)
+        return Units::deg2rad(d);
+
+    ASSERT(false); // no conversion implemented
+    return d;
+}
diff --git a/GUI/Models/Unit.h b/GUI/Models/Unit.h
new file mode 100644
index 00000000000..661f6aa0a89
--- /dev/null
+++ b/GUI/Models/Unit.h
@@ -0,0 +1,40 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Models/Unit.h
+//! @brief     Defines class Unit
+//!
+//! @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_MODELS_UNIT_H
+#define BORNAGAIN_GUI_MODELS_UNIT_H
+
+//! 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,
+    nanometerPowerMinus2,
+    angstrom,
+    angstromPower2,
+    angstromPowerMinus2,
+    degree,
+    radiant
+};
+
+//! 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);
+
+#endif // BORNAGAIN_GUI_MODELS_UNIT_H
-- 
GitLab