From 948abe121e7020756765c5e9463dce99de22ee53 Mon Sep 17 00:00:00 2001
From: Tobias Knopff <t.knopff@fz-juelich.de>
Date: Wed, 6 Oct 2021 15:16:58 +0200
Subject: [PATCH] Make JobPropertiesWidget a regular widget and not derived
 from SessionItemWidget

---
 GUI/Models/JobItem.cpp                        |  44 +++--
 GUI/Models/JobItem.h                          |   9 +-
 .../JobWidgets/JobPropertiesTableModel.cpp    | 183 ++++++++++++++++++
 .../JobWidgets/JobPropertiesTableModel.h      |  48 +++++
 GUI/Views/JobWidgets/JobPropertiesWidget.cpp  | 125 ++++++------
 GUI/Views/JobWidgets/JobPropertiesWidget.h    |  30 ++-
 6 files changed, 350 insertions(+), 89 deletions(-)
 create mode 100644 GUI/Views/JobWidgets/JobPropertiesTableModel.cpp
 create mode 100644 GUI/Views/JobWidgets/JobPropertiesTableModel.h

diff --git a/GUI/Models/JobItem.cpp b/GUI/Models/JobItem.cpp
index 524c2d8083a..985203c3c2c 100644
--- a/GUI/Models/JobItem.cpp
+++ b/GUI/Models/JobItem.cpp
@@ -41,10 +41,6 @@ JobItem::JobItem() : SessionItem(M_TYPE)
     addProperty(P_BEGIN_TIME, QString())->setEditable(false);
     addProperty(P_END_TIME, QString())->setEditable(false);
 
-    auto durationItem = addProperty(P_DURATION, QString());
-    durationItem->setEditable(false);
-    durationItem->setToolTip("Duration of DWBA simulation in sec.msec format");
-
     addProperty(P_COMMENTS, QString())->setVisible(false);
     addProperty(P_PROGRESS, 0)->setVisible(false);
     addProperty(P_PRESENTATION_TYPE, QVariant::Type::Invalid)->setVisible(false);
@@ -162,6 +158,11 @@ void JobItem::setBeginTime(const QDateTime& begin_time)
     setItemValue(P_BEGIN_TIME, begin_time.toString(Qt::ISODateWithMs));
 }
 
+bool JobItem::isBeginTimePropertyName(const QString& name)
+{
+    return name == P_BEGIN_TIME;
+}
+
 QDateTime JobItem::endTime() const
 {
     return QDateTime::fromString(getItemValue(P_END_TIME).toString(), Qt::ISODateWithMs);
@@ -170,16 +171,21 @@ QDateTime JobItem::endTime() const
 void JobItem::setEndTime(const QDateTime& end_time)
 {
     setItemValue(P_END_TIME, end_time.toString(Qt::ISODateWithMs));
-    QString duration;
-    if (end_time.isValid()) {
-        QDateTime begin_time = beginTime();
-        if (begin_time.isValid()) {
-            qint64 delta = begin_time.msecsTo(end_time);
-            if (delta >0)
-                duration = QString::number(delta / 1000., 'f', 3);
-        }
-    }
-    setItemValue(P_DURATION, duration);
+}
+
+bool JobItem::isEndTimePropertyName(const QString& name)
+{
+    return name == P_END_TIME;
+}
+
+std::optional<size_t> JobItem::duration() const
+{
+    QDateTime begin_time = beginTime();
+    QDateTime end_time = endTime();
+    if (begin_time.isValid() && end_time.isValid() && begin_time < end_time)
+        return begin_time.msecsTo(end_time);
+    else
+        return std::nullopt;
 }
 
 QString JobItem::getComments() const
@@ -333,11 +339,21 @@ Data1DViewItem* JobItem::createDataViewItem()
     return model()->insertItem<Data1DViewItem>(this, -1, T_DATAVIEW);
 }
 
+QString JobItem::sampleName() const
+{
+    return getItemValue(P_SAMPLE_NAME).toString();
+}
+
 void JobItem::setSampleName(const QString& name)
 {
     getItem(P_SAMPLE_NAME)->setValue(name);
 }
 
+QString JobItem::instrumentName() const
+{
+    return getItemValue(P_INSTRUMENT_NAME).toString();
+}
+
 void JobItem::setInstrumentName(const QString& name)
 {
     getItem(P_INSTRUMENT_NAME)->setValue(name);
diff --git a/GUI/Models/JobItem.h b/GUI/Models/JobItem.h
index 22506f34906..9536c3b6f5e 100644
--- a/GUI/Models/JobItem.h
+++ b/GUI/Models/JobItem.h
@@ -33,6 +33,7 @@ class ISimulation;
 class SimulationOptionsItem;
 
 #include <QDateTime>
+#include <optional>
 
 class BA_CORE_API_ JobItem : public SessionItem {
 
@@ -44,7 +45,6 @@ private:
     static constexpr auto P_STATUS{"Status"};
     static constexpr auto P_BEGIN_TIME{"Begin time"};
     static constexpr auto P_END_TIME{"End time"};
-    static constexpr auto P_DURATION{"Duration"};
     static constexpr auto P_COMMENTS{"Comments"};
     static constexpr auto P_PROGRESS{"Progress"};
     static constexpr auto P_PRESENTATION_TYPE{"Presentation type"};
@@ -84,9 +84,14 @@ public:
 
     QDateTime beginTime() const;
     void setBeginTime(const QDateTime& begin_time);
+    static bool isBeginTimePropertyName(const QString& name);
 
     QDateTime endTime() const;
     void setEndTime(const QDateTime& end_time);
+    static bool isEndTimePropertyName(const QString& name);
+
+    /// if begin and end time are both available the duration in ms, otherwise empty
+    std::optional<size_t> duration() const;
 
     QString getComments() const;
     void setComments(const QString& comments);
@@ -130,8 +135,10 @@ public:
     void addDataViewItem(Data1DViewItem* data_view);
     Data1DViewItem* createDataViewItem();
 
+    QString sampleName() const;
     void setSampleName(const QString& name);
 
+    QString instrumentName() const;
     void setInstrumentName(const QString& name);
 
     const QString presentationType() const;
diff --git a/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp b/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp
new file mode 100644
index 00000000000..ac4ab1b73c5
--- /dev/null
+++ b/GUI/Views/JobWidgets/JobPropertiesTableModel.cpp
@@ -0,0 +1,183 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Views/JobWidgets/JobPropertiesTableModel.cpp
+//! @brief     Implements class JobPropertiesTableModel
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+//  ************************************************************************************************
+
+#include "GUI/Views/JobWidgets/JobPropertiesTableModel.h"
+#include "GUI/Models/JobItem.h"
+
+namespace {
+namespace Column {
+enum Columns { Name, Value };
+}
+const QString ColumnNames[] = {"Name", "Value"};
+const int NumColumns = std::size(ColumnNames);
+
+namespace Row {
+enum Rows { Name, Sample, Instrument, Status, Begin, End, Duration };
+}
+const QString RowNames[] = {"Name", "Sample", "Instrument", "Status", "Begin", "End", "Duration"};
+const int NumRows = std::size(RowNames);
+
+const QString ModelDateShortFormat = "yyyy.MM.dd hh:mm:ss";
+} // namespace
+
+//==================================================================================================
+// JobPropertiesTableModel
+//==================================================================================================
+
+//--------------------------------------------------------------------------------------------------
+// public member functions
+//--------------------------------------------------------------------------------------------------
+
+JobPropertiesTableModel::JobPropertiesTableModel(QObject* parent)
+    : QAbstractTableModel(parent), m_item(nullptr)
+{
+}
+
+JobPropertiesTableModel::~JobPropertiesTableModel()
+{
+    if (m_item)
+        m_item->mapper()->unsubscribe(this);
+}
+
+int JobPropertiesTableModel::rowCount(const QModelIndex& parent) const
+{
+    if (!parent.isValid() && m_item)
+        return NumRows;
+    else
+        return 0;
+}
+
+int JobPropertiesTableModel::columnCount(const QModelIndex& parent) const
+{
+    if (!parent.isValid() && m_item)
+        return NumColumns;
+    else
+        return 0;
+}
+
+QVariant JobPropertiesTableModel::data(const QModelIndex& index, int role) const
+{
+    if ((role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole)
+        || index.column() < 0 || index.column() >= NumColumns || index.row() < 0
+        || index.row() >= NumRows || !m_item)
+        return QVariant();
+
+    switch (index.column()) {
+    case Column::Name:
+        return RowNames[index.row()];
+        break;
+    case Column::Value: {
+        switch (index.row()) {
+        case Row::Name:
+            return m_item->itemName();
+            break;
+        case Row::Sample:
+            return m_item->sampleName();
+            break;
+        case Row::Instrument:
+            return m_item->instrumentName();
+            break;
+        case Row::Status:
+            return m_item->getStatus();
+            break;
+        case Row::Begin:
+            if (role == Qt::ToolTipRole)
+                return m_item->beginTime().toString(Qt::DefaultLocaleLongDate);
+            else
+                return m_item->beginTime().toString(ModelDateShortFormat);
+            break;
+        case Row::End:
+            if (role == Qt::ToolTipRole)
+                return m_item->endTime().toString(Qt::DefaultLocaleLongDate);
+            else
+                return m_item->endTime().toString(ModelDateShortFormat);
+            break;
+        case Row::Duration: {
+            std::optional<size_t> duration = m_item->duration();
+            if (duration)
+                return QString("%1 s").arg(duration.value() / 1000., 0, 'f', 3);
+            else
+                return QVariant();
+        } break;
+        default:
+            return QVariant();
+        }
+    }
+    default:
+        return QVariant();
+    }
+}
+
+QVariant JobPropertiesTableModel::headerData(int section, Qt::Orientation orientation,
+                                             int role) const
+{
+    if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section >= 0
+        && section < NumColumns)
+        return ColumnNames[section];
+    else
+        return QVariant();
+}
+
+Qt::ItemFlags JobPropertiesTableModel::flags(const QModelIndex& index) const
+{
+    Qt::ItemFlags f = QAbstractTableModel::flags(index);
+    if (index.column() == Column::Value && index.row() == Row::Name && m_item)
+        f.setFlag(Qt::ItemIsEditable);
+    return f;
+}
+
+bool JobPropertiesTableModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+    if (role != Qt::EditRole || index.column() != Column::Value || index.row() != Row::Name
+        || !m_item)
+        return false;
+    m_item->setItemName(value.toString());
+    emit dataChanged(index, index, {role});
+    return true;
+}
+
+void JobPropertiesTableModel::setItem(JobItem* item)
+{
+    beginResetModel();
+    if (m_item)
+        m_item->mapper()->unsubscribe(this);
+    m_item = item;
+    if (m_item)
+        m_item->mapper()->setOnPropertyChange(
+            std::bind(&JobPropertiesTableModel::notifyJobPropertyChange, this,
+                      std::placeholders::_1),
+            this);
+    endResetModel();
+}
+
+//--------------------------------------------------------------------------------------------------
+// private member functions
+//--------------------------------------------------------------------------------------------------
+
+void JobPropertiesTableModel::notifyJobPropertyChange(const QString& property)
+{
+    if (SessionItem::isItemNamePropertyName(property))
+        emit dataChanged(index(Row::Name, Column::Value), index(Row::Name, Column::Value),
+                         {Qt::DisplayRole, Qt::EditRole});
+    else if (JobItem::isStatusPropertyName(property))
+        emit dataChanged(index(Row::Status, Column::Value), index(Row::Status, Column::Value),
+                         {Qt::DisplayRole, Qt::EditRole});
+    else if (JobItem::isBeginTimePropertyName(property))
+        emit dataChanged(index(Row::Begin, Column::Value), index(Row::Begin, Column::Value),
+                         {Qt::DisplayRole, Qt::EditRole});
+    else if (JobItem::isEndTimePropertyName(property))
+        // will change duration very probably too
+        emit dataChanged(index(Row::End, Column::Value), index(Row::Duration, Column::Value),
+                         {Qt::DisplayRole, Qt::EditRole});
+}
diff --git a/GUI/Views/JobWidgets/JobPropertiesTableModel.h b/GUI/Views/JobWidgets/JobPropertiesTableModel.h
new file mode 100644
index 00000000000..92481115eec
--- /dev/null
+++ b/GUI/Views/JobWidgets/JobPropertiesTableModel.h
@@ -0,0 +1,48 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Views/JobWidgets/JobPropertiesWidget.h
+//! @brief     Defines class JobPropertiesWidget
+//!
+//! @homepage  http://www.bornagainproject.org
+//! @license   GNU General Public License v3 or higher (see COPYING)
+//! @copyright Forschungszentrum Jülich GmbH 2018
+//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
+//
+//  ************************************************************************************************
+
+#ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODEL_H
+#define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODE_H
+
+#include <QAbstractTableModel>
+
+class JobItem;
+
+
+//! The JobPropertiesTableModel is a table model for the properties of a job except for the comment.
+//! The name of the job is editable, all other fields are read only.
+
+class JobPropertiesTableModel : public QAbstractTableModel {
+    Q_OBJECT
+public:
+    explicit JobPropertiesTableModel(QObject* parent = nullptr);
+    ~JobPropertiesTableModel();
+    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+    virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    virtual QVariant headerData(int section, Qt::Orientation orientation,
+                                int role = Qt::DisplayRole) const override;
+    virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
+    virtual bool setData(const QModelIndex& index, const QVariant& value,
+                         int role = Qt::EditRole) override;
+    void setItem(JobItem* item);
+
+private:
+    void notifyJobPropertyChange(const QString& property);
+
+private:
+    JobItem* m_item;
+};
+
+#endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESTABLEMODEL_H
diff --git a/GUI/Views/JobWidgets/JobPropertiesWidget.cpp b/GUI/Views/JobWidgets/JobPropertiesWidget.cpp
index a8ae841465f..00552914d67 100644
--- a/GUI/Views/JobWidgets/JobPropertiesWidget.cpp
+++ b/GUI/Views/JobWidgets/JobPropertiesWidget.cpp
@@ -15,97 +15,106 @@
 #include "GUI/Views/JobWidgets/JobPropertiesWidget.h"
 #include "GUI/Models/JobItem.h"
 #include "GUI/Views/CommonWidgets/StyleUtils.h"
-#include "GUI/Views/PropertyEditor/ComponentEditor.h"
+#include "GUI/Views/JobWidgets/JobPropertiesTableModel.h"
 #include "GUI/Views/Tools/mainwindow_constants.h"
 #include <QTabBar>
 #include <QTabWidget>
 #include <QTextEdit>
+#include <QTreeView>
 #include <QVBoxLayout>
 
-JobPropertiesWidget::JobPropertiesWidget(QWidget* parent)
-    : SessionItemWidget(parent)
-    , m_tabWidget(new QTabWidget)
-    , m_componentEditor(new ComponentEditor)
-    , m_commentsEditor(new QTextEdit)
-    , m_block_update(false)
+namespace {
+enum ETabId { JOB_PROPERTIES, JOB_COMMENTS };
+}
+
+//==================================================================================================
+// JobPropertiesWidget
+//==================================================================================================
+
+//--------------------------------------------------------------------------------------------------
+// public member functions
+//--------------------------------------------------------------------------------------------------
+
+JobPropertiesWidget::JobPropertiesWidget(QWidget* parent, Qt::WindowFlags f)
+    : QWidget(parent, f), m_item(nullptr)
 {
     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
     setWindowTitle(GUI::Constants::JobPropertiesWidgetName);
 
-    auto mainLayout = new QVBoxLayout;
-    mainLayout->setMargin(0);
-    mainLayout->setSpacing(0);
-    mainLayout->setContentsMargins(0, 0, 0, 0);
+    QVBoxLayout* layout = new QVBoxLayout(this);
+    layout->setMargin(0);
+    layout->setSpacing(0);
+    layout->setContentsMargins(0, 0, 0, 0);
 
+    m_tabWidget = new QTabWidget(this);
     m_tabWidget->setTabPosition(QTabWidget::South);
-    m_tabWidget->insertTab(JOB_PROPERTIES, m_componentEditor, "Job Properties");
-    m_tabWidget->insertTab(JOB_COMMENTS, m_commentsEditor, "Details");
+    layout->addWidget(m_tabWidget);
 
-    mainLayout->addWidget(m_tabWidget);
-    setLayout(mainLayout);
+    m_propertiesView = new QTreeView(this);
+    m_propertiesView->setRootIsDecorated(false);
+    m_propertiesView->setAlternatingRowColors(true);
+    m_tabWidget->insertTab(JOB_PROPERTIES, m_propertiesView, "Job Properties");
+    m_propertiesModel = new JobPropertiesTableModel(this);
+    m_propertiesView->setModel(m_propertiesModel);
 
-    connect(m_commentsEditor, &QTextEdit::textChanged, this, &JobPropertiesWidget::onTextChanged);
+    m_commentsEditor = new QTextEdit(this);
+    m_tabWidget->insertTab(JOB_COMMENTS, m_commentsEditor, "Comments");
+
+    connect(m_commentsEditor, &QTextEdit::textChanged, this,
+            &JobPropertiesWidget::onCommentsEdited);
 }
 
-QSize JobPropertiesWidget::sizeHint() const
+JobPropertiesWidget::~JobPropertiesWidget()
 {
-    return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth());
+    if (m_item)
+        m_item->mapper()->unsubscribe(this);
 }
 
-QSize JobPropertiesWidget::minimumSizeHint() const
+QSize JobPropertiesWidget::sizeHint() const
 {
     return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth());
 }
 
-void JobPropertiesWidget::subscribeToItem()
+QSize JobPropertiesWidget::minimumSizeHint() const
 {
-    currentItem()->mapper()->setOnPropertyChange(
-        [this](const QString& name) {
-            if (JobItem::isCommentsPropertyName(name))
-                updateItem();
-        },
-        this);
-
-    m_componentEditor->setItem(currentItem());
-
-    updateItem();
+    return QSize(GUI::Utils::Style::PropertyPanelWidth(), GUI::Utils::Style::PropertyPanelWidth());
 }
 
-void JobPropertiesWidget::unsubscribeFromItem()
+void JobPropertiesWidget::setItem(JobItem* item)
 {
-    m_componentEditor->setItem(nullptr);
+    if (m_item)
+        m_item->mapper()->unsubscribe(this);
+    m_item = item;
+    m_propertiesModel->setItem(m_item);
+    if (m_item) {
+        if (m_item->isFailed())
+            m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::red);
+        else
+            m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::black);
+        m_commentsEditor->setText(m_item->getComments());
+        m_item->mapper()->setOnPropertyChange(
+            std::bind(&JobPropertiesWidget::notifyJobPropertyChange, this, std::placeholders::_1));
+    } else
+        m_commentsEditor->clear();
 }
 
-void JobPropertiesWidget::contextMenuEvent(QContextMenuEvent*)
-{
-    // Reimplemented to suppress menu from main window
-}
+//--------------------------------------------------------------------------------------------------
+// private member functions
+//--------------------------------------------------------------------------------------------------
 
-void JobPropertiesWidget::onTextChanged()
+void JobPropertiesWidget::notifyJobPropertyChange(const QString& property)
 {
-    m_block_update = true;
-    JobItem* item = jobItem();
-    if (item)
-        item->setComments(m_commentsEditor->toPlainText());
-    m_block_update = false;
+    if (JobItem::isCommentsPropertyName(property) && m_item
+        && m_item->getComments() != m_commentsEditor->toPlainText())
+        m_commentsEditor->setPlainText(m_item->getComments());
 }
 
-void JobPropertiesWidget::updateItem()
-{
-    if (m_block_update)
-        return;
-
-    if (JobItem* item = jobItem()) {
-        if (item->getStatus() == "Failed")
-            m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::red);
-        else
-            m_tabWidget->tabBar()->setTabTextColor(JOB_COMMENTS, Qt::black);
-
-        m_commentsEditor->setText(item->getComments());
-    }
-}
+//--------------------------------------------------------------------------------------------------
+// private slots
+//--------------------------------------------------------------------------------------------------
 
-JobItem* JobPropertiesWidget::jobItem()
+void JobPropertiesWidget::onCommentsEdited()
 {
-    return dynamic_cast<JobItem*>(currentItem());
+    if (m_item)
+        m_item->setComments(m_commentsEditor->toPlainText());
 }
diff --git a/GUI/Views/JobWidgets/JobPropertiesWidget.h b/GUI/Views/JobWidgets/JobPropertiesWidget.h
index 47ddd1a3e1e..cad504f2edc 100644
--- a/GUI/Views/JobWidgets/JobPropertiesWidget.h
+++ b/GUI/Views/JobWidgets/JobPropertiesWidget.h
@@ -15,41 +15,39 @@
 #ifndef BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H
 #define BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H
 
-#include "GUI/Views/CommonWidgets/SessionItemWidget.h"
+#include <QWidget>
 
 class JobItem;
-class QTextEdit;
+class JobPropertiesTableModel;
 class QTabWidget;
-class ComponentEditor;
+class QTextEdit;
+class QTreeView;
 
 //! The JobPropertiesWidget class holds component editor for JobItem. Part of JobSelectorWidget,
 //! resides at lower left corner of JobView.
 
-class JobPropertiesWidget : public SessionItemWidget {
+class JobPropertiesWidget : public QWidget {
     Q_OBJECT
 public:
-    enum ETabId { JOB_PROPERTIES, JOB_COMMENTS };
-    explicit JobPropertiesWidget(QWidget* parent = nullptr);
+    explicit JobPropertiesWidget(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
+    ~JobPropertiesWidget();
+    void setItem(JobItem* item);
 
     QSize sizeHint() const;
     QSize minimumSizeHint() const;
 
-protected:
-    void subscribeToItem();
-    void unsubscribeFromItem();
-    void contextMenuEvent(QContextMenuEvent*);
+private:
+    void notifyJobPropertyChange(const QString& property);
 
 private slots:
-    void onTextChanged();
+    void onCommentsEdited();
 
 private:
-    void updateItem();
-    JobItem* jobItem();
-
     QTabWidget* m_tabWidget;
-    ComponentEditor* m_componentEditor;
+    QTreeView* m_propertiesView;
+    JobPropertiesTableModel* m_propertiesModel;
     QTextEdit* m_commentsEditor;
-    bool m_block_update;
+    JobItem* m_item;
 };
 
 #endif // BORNAGAIN_GUI_VIEWS_JOBWIDGETS_JOBPROPERTIESWIDGET_H
-- 
GitLab