From a2307475272e09c31bd4218dd50773de959f6645 Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Tue, 23 Nov 2021 09:52:16 +0100
Subject: [PATCH] harmonized & less generic UI for mask properties widget

---
 GUI/View/Mask/MaskEditorCanvas.cpp        |   1 +
 GUI/View/Mask/MaskEditorPropertyPanel.cpp | 132 +++++++++++++++++++---
 GUI/View/Mask/MaskEditorPropertyPanel.h   |  33 +++++-
 3 files changed, 146 insertions(+), 20 deletions(-)

diff --git a/GUI/View/Mask/MaskEditorCanvas.cpp b/GUI/View/Mask/MaskEditorCanvas.cpp
index 221c71f2553..c7e28113a3c 100644
--- a/GUI/View/Mask/MaskEditorCanvas.cpp
+++ b/GUI/View/Mask/MaskEditorCanvas.cpp
@@ -79,6 +79,7 @@ MaskGraphicsScene* MaskEditorCanvas::getScene()
 
 void MaskEditorCanvas::onPresentationTypeRequest(MaskEditorFlags::PresentationType presentationType)
 {
+    m_scene->clearSelection(); // important to avoid crash (unsubscribe while calling subscribers)
     m_resultsPresenter->updatePresenter(presentationType);
 
     if (auto* container = m_intensityDataItem->maskContainerItem()) {
diff --git a/GUI/View/Mask/MaskEditorPropertyPanel.cpp b/GUI/View/Mask/MaskEditorPropertyPanel.cpp
index b306d4702de..a7cb3812fbd 100644
--- a/GUI/View/Mask/MaskEditorPropertyPanel.cpp
+++ b/GUI/View/Mask/MaskEditorPropertyPanel.cpp
@@ -14,25 +14,32 @@
 
 #include "GUI/View/Mask/MaskEditorPropertyPanel.h"
 #include "GUI/Model/Data/IntensityDataItem.h"
+#include "GUI/Model/Data/MaskItems.h"
 #include "GUI/Model/Session/SessionModel.h"
-#include "GUI/View/PropertyEditor/ComponentTreeView.h"
+#include "GUI/View/Edit/DoubleSpinBox.h"
 #include "GUI/View/PropertyEditor/IntensityDataPropertyWidget.h"
 #include "GUI/View/Tool/GroupBoxCollapser.h"
+#include "GUI/View/Tool/LayoutUtils.h"
 
+#include <QCheckBox>
+#include <QFormLayout>
 #include <QGroupBox>
+#include <QLineEdit>
 #include <QListView>
-#include <QVBoxLayout>
 
 MaskEditorPropertyPanel::MaskEditorPropertyPanel(QWidget* parent)
     : QWidget(parent)
     , m_listView(new QListView)
-    , m_maskPropertyEditor(new ComponentTreeView)
     , m_plotPropertyEditor(new IntensityDataPropertyWidget)
     , m_maskModel(nullptr)
     , m_intensityDataItem(nullptr)
+    , m_currentMaskItem(nullptr)
+    , m_inhibitSelectionChange(false)
 {
     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
     setObjectName(QLatin1String("MaskEditorToolPanel"));
+    setAttribute(Qt::WA_StyledBackground, true);
+    setProperty("stylable", true); // for stylesheet addressing
 
     m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
     connect(m_listView, &QListView::customContextMenuRequested, this,
@@ -60,10 +67,9 @@ MaskEditorPropertyPanel::MaskEditorPropertyPanel(QWidget* parent)
 
     // -- mask properties
     auto* maskPropertiesGroup = new QGroupBox("Mask properties", this);
-    auto* maskPropertiesLayout = new QVBoxLayout(maskPropertiesGroup);
-    maskPropertiesLayout->setContentsMargins(0, 0, 0, 0);
-    m_maskPropertyEditor->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
-    maskPropertiesLayout->addWidget(m_maskPropertyEditor);
+    maskPropertiesGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
+    m_maskPropertiesLayout = new QFormLayout(maskPropertiesGroup);
+    m_maskPropertiesLayout->setContentsMargins(8, 8, 8, 8);
     GroupBoxCollapser::installIntoGroupBox(maskPropertiesGroup);
 
     mainLayout->addWidget(plotPropertiesGroup);
@@ -110,7 +116,7 @@ void MaskEditorPropertyPanel::resetContext()
     m_rootIndex = {};
     m_intensityDataItem = nullptr;
     m_listView->setModel(nullptr);
-    m_maskPropertyEditor->setItem(nullptr);
+    setCurrentMaskItem(nullptr);
     m_plotPropertyEditor->setItem(nullptr);
 }
 
@@ -121,20 +127,20 @@ QItemSelectionModel* MaskEditorPropertyPanel::selectionModel()
 }
 
 //! Show/Hide panel. When panel is hidden, all property editors are disabled.
-void MaskEditorPropertyPanel::setPanelHidden(bool value)
+void MaskEditorPropertyPanel::setPanelHidden(bool hidden)
 {
-    this->setHidden(value);
+    setHidden(hidden);
 
     if (!m_rootIndex.isValid())
         return;
 
-    if (value) {
-        m_maskPropertyEditor->setItem(nullptr);
+    if (hidden) {
+        setCurrentMaskItem(nullptr);
         m_plotPropertyEditor->setItem(nullptr);
     } else {
         QModelIndexList indexes = selectionModel()->selectedIndexes();
         if (!indexes.empty())
-            m_maskPropertyEditor->setItem(m_maskModel->itemForIndex(indexes.front()));
+            setCurrentMaskItem(maskItemForIndex(indexes.front()));
 
         m_plotPropertyEditor->setItem(m_intensityDataItem);
     }
@@ -143,13 +149,109 @@ void MaskEditorPropertyPanel::setPanelHidden(bool value)
 void MaskEditorPropertyPanel::onSelectionChanged(const QItemSelection& selected,
                                                  const QItemSelection&)
 {
+    if (m_inhibitSelectionChange)
+        return;
+
     if (!selected.empty())
-        m_maskPropertyEditor->setItem(m_maskModel->itemForIndex(selected.indexes().front()));
+        setCurrentMaskItem(maskItemForIndex(selected.indexes().front()));
     else
-        m_maskPropertyEditor->setItem(nullptr);
+        setCurrentMaskItem(nullptr);
 }
 
 void MaskEditorPropertyPanel::onCustomContextMenuRequested(const QPoint& point)
 {
     emit itemContextMenuRequest(m_listView->mapToGlobal(point));
 }
+
+void MaskEditorPropertyPanel::setCurrentMaskItem(MaskItem* maskItem)
+{
+    if (m_currentMaskItem)
+        m_currentMaskItem->mapper()->unsubscribe(this);
+
+    GUI::Util::Layout::clearLayout(m_maskPropertiesLayout);
+
+    m_currentMaskItem = maskItem;
+
+    createMaskEditorUI();
+}
+
+void MaskEditorPropertyPanel::createMaskEditorUI()
+{
+    if (!m_currentMaskItem)
+        return;
+
+    auto* maskItem = m_currentMaskItem; // shorthand
+    // -- mask value (only if not RoI)
+    if (!dynamic_cast<RegionOfInterestItem*>(maskItem)) {
+        const auto maskValueGetter = [=] { return maskItem->maskValue(); };
+        const auto maskValueSetter = [=](bool b) { maskItem->setMaskValue(b); };
+        addMaskCheckBox("Mask value", maskValueGetter, maskValueSetter);
+    }
+    // -- mask visibility
+    const auto visibilityValueGetter = [=] { return maskItem->isVisibleValue(); };
+    const auto visibilityValueSetter = [=](bool b) {
+        m_inhibitSelectionChange = true;
+        maskItem->setIsVisibleValue(b);
+        m_inhibitSelectionChange = false;
+    };
+    addMaskCheckBox("Show", visibilityValueGetter, visibilityValueSetter);
+
+    // -- name (only if not RoI)
+    if (!dynamic_cast<RegionOfInterestItem*>(maskItem)) {
+        auto* edit = new QLineEdit(maskItem->itemName(), m_maskPropertiesLayout->parentWidget());
+        connect(edit, &QLineEdit::textEdited, [=](const QString& t) { maskItem->setItemName(t); });
+        m_maskPropertiesLayout->addRow("Name:", edit);
+    }
+
+    if (auto c = dynamic_cast<RectangleItem*>(maskItem)) {
+        addMaskSpinBox(c->xLow());
+        addMaskSpinBox(c->yLow());
+        addMaskSpinBox(c->xUp());
+        addMaskSpinBox(c->yUp());
+    } else if (auto c = dynamic_cast<EllipseItem*>(maskItem)) {
+        addMaskSpinBox(c->xCenter());
+        addMaskSpinBox(c->yCenter());
+        addMaskSpinBox(c->xRadius());
+        addMaskSpinBox(c->yRadius());
+        addMaskSpinBox(c->angle());
+    } else if (auto c = dynamic_cast<VerticalLineItem*>(maskItem))
+        addMaskSpinBox(c->posX());
+    else if (auto c = dynamic_cast<HorizontalLineItem*>(maskItem))
+        addMaskSpinBox(c->posY());
+}
+
+void MaskEditorPropertyPanel::addMaskSpinBox(DoubleDescriptor d)
+{
+    auto* spinBox = new DoubleSpinBox(m_maskPropertiesLayout->parentWidget(), d);
+    spinBox->setBaseValue(d.get());
+    QObject::connect(spinBox, &DoubleSpinBox::baseValueChanged,
+                     [=](double newValue) { d.set(newValue); });
+
+    m_currentMaskItem->mapper()->setOnPropertyChange(
+        [=](const QString&) { spinBox->updateValue(); }, this);
+
+    m_maskPropertiesLayout->addRow(d.label + ":", spinBox);
+}
+
+void MaskEditorPropertyPanel::addMaskCheckBox(const QString& title, function<bool()> getter,
+                                              function<void(bool)> setter)
+{
+    auto* checkBox = new QCheckBox(title, m_maskPropertiesLayout->parentWidget());
+    checkBox->setChecked(getter());
+    connect(checkBox, &QCheckBox::stateChanged, [=]() { setter(checkBox->isChecked()); });
+
+    // listen on property change
+    m_currentMaskItem->mapper()->setOnPropertyChange(
+        [=](const QString&) {
+            QSignalBlocker b(checkBox);
+            checkBox->setChecked(getter());
+        },
+        this);
+
+    m_maskPropertiesLayout->addRow(checkBox);
+}
+
+MaskItem* MaskEditorPropertyPanel::maskItemForIndex(const QModelIndex& index)
+{
+    return dynamic_cast<MaskItem*>(m_maskModel->itemForIndex(index));
+}
diff --git a/GUI/View/Mask/MaskEditorPropertyPanel.h b/GUI/View/Mask/MaskEditorPropertyPanel.h
index 8aeae2e3d63..aa15f48d23f 100644
--- a/GUI/View/Mask/MaskEditorPropertyPanel.h
+++ b/GUI/View/Mask/MaskEditorPropertyPanel.h
@@ -18,14 +18,19 @@
 #include "GUI/View/Mask/MaskEditorFlags.h"
 #include <QModelIndex>
 #include <QWidget>
+#include <functional>
+
+using std::function;
 
 class QListView;
 class SessionModel;
-class ComponentTreeView;
 class QItemSelection;
 class QItemSelectionModel;
 class IntensityDataItem;
 class IntensityDataPropertyWidget;
+class QFormLayout;
+class DoubleDescriptor;
+class MaskItem;
 
 //! Tool widget for MaskEditor
 
@@ -41,10 +46,8 @@ public:
                         IntensityDataItem* intensityItem);
 
     void resetContext();
-
-    QItemSelectionModel* selectionModel();
-
     void setPanelHidden(bool value);
+    QItemSelectionModel* selectionModel();
 
 signals:
     void itemContextMenuRequest(const QPoint& point);
@@ -53,13 +56,33 @@ private slots:
     void onSelectionChanged(const QItemSelection& selected, const QItemSelection&);
     void onCustomContextMenuRequested(const QPoint& point);
 
+private:
+    //! Set the current mask and creates the UI to edit the mask's properties
+    void setCurrentMaskItem(MaskItem* maskItem);
+
+    //! Add a spinbox to edit a mask's double value
+    void addMaskSpinBox(DoubleDescriptor d);
+
+    //! Add a checkbox to edit a mask's boolean value
+    void addMaskCheckBox(const QString& title, function<bool()> getter,
+                         function<void(bool)> setter);
+
+    //! Return the mask item for the given index. nullptr if index invalid or not pointing to a mask
+    //! item.
+    MaskItem* maskItemForIndex(const QModelIndex& index);
+
+    //! Creates the UI to edit the current mask's properties
+    void createMaskEditorUI();
+
 private:
     QListView* m_listView;
-    ComponentTreeView* m_maskPropertyEditor;
     IntensityDataPropertyWidget* m_plotPropertyEditor;
     SessionModel* m_maskModel;
     QModelIndex m_rootIndex;
     IntensityDataItem* m_intensityDataItem;
+    QFormLayout* m_maskPropertiesLayout;
+    MaskItem* m_currentMaskItem; //!< the mask item whose properties shall be edited
+    bool m_inhibitSelectionChange;
 };
 
 #endif // BORNAGAIN_GUI_VIEW_MASK_MASKEDITORPROPERTYPANEL_H
-- 
GitLab