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