From f20201e1d5a3e015e5cd728b4a536009c4bf22aa Mon Sep 17 00:00:00 2001
From: Matthias Puchner <github@mpuchner.de>
Date: Tue, 31 Aug 2021 15:58:28 +0200
Subject: [PATCH] add instrument tree model (for instrument library)

---
 GUI/Models/InstrumentsTreeModel.cpp | 319 ++++++++++++++++++++++++++++
 GUI/Models/InstrumentsTreeModel.h   |  74 +++++++
 2 files changed, 393 insertions(+)
 create mode 100644 GUI/Models/InstrumentsTreeModel.cpp
 create mode 100644 GUI/Models/InstrumentsTreeModel.h

diff --git a/GUI/Models/InstrumentsTreeModel.cpp b/GUI/Models/InstrumentsTreeModel.cpp
new file mode 100644
index 00000000000..ac3e51b5b2f
--- /dev/null
+++ b/GUI/Models/InstrumentsTreeModel.cpp
@@ -0,0 +1,319 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Views/ImportDataWidgets/InstrumentsTreeModel.cpp
+//! @brief     Implements class InstrumentsTreeModel
+//!
+//! @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/InstrumentsTreeModel.h"
+#include "GUI/Application/Application.h"
+#include "GUI/Application/ApplicationSettings.h"
+#include "GUI/Models/InstrumentItems.h"
+#include "GUI/Models/InstrumentModel.h"
+#include <QApplication>
+#include <QtCore>
+#include <QtGui>
+
+InstrumentsTreeModel::InstrumentsTreeModel(QObject* parent, InstrumentModel* model)
+    : QAbstractItemModel(parent)
+    , m_model(model)
+    , m_visibleTypes(All)
+    , m_namesAreEditable(false)
+    , m_enableEmptyHeadlines(true)
+{
+    connect(m_model, &InstrumentModel::modelAboutToBeReset, this, &InstrumentsTreeModel::clear,
+            Qt::UniqueConnection);
+}
+
+void InstrumentsTreeModel::enableEmptyHeadlines(bool b)
+{
+    if (b != m_enableEmptyHeadlines) {
+        beginResetModel();
+        m_enableEmptyHeadlines = b;
+        endResetModel();
+    }
+}
+
+void InstrumentsTreeModel::setTypeEnabled(InstrumentType type, bool b)
+{
+    if (m_visibleTypes.testFlag(type) != b) {
+        beginResetModel();
+        m_visibleTypes.setFlag(type, b);
+        endResetModel();
+    }
+}
+
+void InstrumentsTreeModel::refreshAfterModelChange()
+{
+    //     for (auto rank : m_visibleRanks) {
+    //         if (!m_items[rank - 1].isEmpty()) {
+    //             beginRemoveRows(indexOfHeadline(rank), 0, m_items[rank - 1].size() - 1);
+    //             m_items[rank - 1] = m_model->InstrumentItems(rank);
+    //             endRemoveRows();
+    //         }
+    //     }
+    //
+    //     updateSubsriptions();
+}
+
+void InstrumentsTreeModel::clear()
+{
+    beginResetModel();
+    endResetModel();
+}
+
+QList<InstrumentsTreeModel::InstrumentType> InstrumentsTreeModel::visibleTypes() const
+{
+    QList<InstrumentsTreeModel::InstrumentType> result;
+
+    const auto forType = [&](InstrumentType type) {
+        if (m_visibleTypes.testFlag(type)
+            && (m_enableEmptyHeadlines || !instruments(type).isEmpty()))
+            result << type;
+    };
+
+    forType(Gisas);
+    forType(OffSpecular);
+    forType(Specular);
+    forType(DepthProbe);
+
+    return result;
+}
+
+QVector<InstrumentItem*> InstrumentsTreeModel::instruments(InstrumentType type) const
+{
+    switch (type) {
+    case Gisas:
+        return m_model->instrumentItems([](const InstrumentItem* p) {
+            return dynamic_cast<const GISASInstrumentItem*>(p) != nullptr;
+        });
+    case OffSpecular:
+        return m_model->instrumentItems([](const InstrumentItem* p) {
+            return dynamic_cast<const OffSpecularInstrumentItem*>(p) != nullptr;
+        });
+    case Specular:
+        return m_model->instrumentItems([](const InstrumentItem* p) {
+            return dynamic_cast<const SpecularInstrumentItem*>(p) != nullptr;
+        });
+    case DepthProbe:
+        return m_model->instrumentItems([](const InstrumentItem* p) {
+            return dynamic_cast<const DepthProbeInstrumentItem*>(p) != nullptr;
+        });
+    default:
+        return {};
+    }
+}
+
+InstrumentItem* InstrumentsTreeModel::topMostItem() const
+{
+    for (auto t : visibleTypes())
+        if (auto instr = instruments(t); !instr.isEmpty())
+            return instr.first();
+
+    return nullptr;
+}
+
+QModelIndex InstrumentsTreeModel::indexOfHeadline(InstrumentType type) const
+{
+    int row = 0;
+    for (auto t : visibleTypes()) {
+        if (t == type)
+            return createIndex(row, 0, nullptr);
+        row++;
+    }
+
+    return QModelIndex();
+}
+
+QModelIndex InstrumentsTreeModel::index(int row, int column, const QModelIndex& parent) const
+{
+    if (!hasIndex(row, column, parent))
+        return QModelIndex();
+
+    if (!parent.isValid())
+        return createIndex(row, column, nullptr);
+
+    for (auto type : visibleTypes())
+        if (parent == indexOfHeadline(type))
+            return createIndex(row, column, instruments(type)[row]);
+
+    return QModelIndex();
+}
+
+QModelIndex InstrumentsTreeModel::parent(const QModelIndex& index) const
+{
+    if (!index.isValid())
+        return QModelIndex();
+
+    if (index.internalPointer() == nullptr) // index is headline => no parent
+        return QModelIndex();
+
+    auto item = itemForIndex(index);
+    for (auto type : visibleTypes())
+        if (instruments(type).contains(item))
+            return indexOfHeadline(type);
+
+    return QModelIndex();
+}
+
+int InstrumentsTreeModel::columnCount(const QModelIndex& /*parent*/) const
+{
+    return 1;
+}
+
+int InstrumentsTreeModel::rowCount(const QModelIndex& parent) const
+{
+    if (!parent.isValid())
+        return visibleTypes().size();
+
+    // parent is a headline
+    for (auto type : visibleTypes())
+        if (parent == indexOfHeadline(type))
+            return instruments(type).size();
+
+    return 0;
+}
+
+QVariant InstrumentsTreeModel::data(const QModelIndex& index, int role) const
+{
+    if (isHeadline(index)) {
+        QString title;
+        if (index == indexOfHeadline(Gisas))
+            title = "GISAS";
+        else if (index == indexOfHeadline(OffSpecular))
+            title = "OffSpecular";
+        else if (index == indexOfHeadline(Specular))
+            title = "Specular";
+        else if (index == indexOfHeadline(DepthProbe))
+            title = "DepthProbe";
+
+        switch (role) {
+        case Qt::DisplayRole:
+            return title;
+
+        case Qt::FontRole: {
+            QFont f(QApplication::font());
+            f.setPointSize(f.pointSize() * 1.5);
+            f.setBold(true);
+            return f;
+        }
+
+        case Qt::SizeHintRole: {
+            QFont f(QApplication::font());
+            f.setPointSize(f.pointSize() * 1.5);
+            f.setBold(true);
+            QSize s = QFontMetrics(f).boundingRect(title).size();
+            return QSize(s.width() * 2, s.height() * 2);
+        }
+
+        case Qt::TextAlignmentRole:
+            return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
+
+        case Qt::BackgroundRole:
+            return baApp->styleSheetPalette().headlineBackground;
+
+        case Qt::ForegroundRole:
+            return baApp->styleSheetPalette().headlineText;
+
+        default:
+            return QVariant();
+        }
+    }
+
+    if (role == Qt::ToolTipRole)
+        return QString();
+
+    const auto item = itemForIndex(index);
+
+    if (role == Qt::DisplayRole)
+        if (item->is<GISASInstrumentItem>())
+            return "<b>" + item->instrumentName() + "</b><br><br>This is a description!";
+        else
+            return item->instrumentName();
+
+    if (role == Qt::EditRole)
+        return item->instrumentName();
+
+    if (role == Qt::TextAlignmentRole)
+        return QVariant(Qt::AlignLeft | Qt::AlignTop);
+
+    if (role == Qt::DecorationRole) {
+        if (item->is<GISASInstrumentItem>())
+            return QIcon(":/images/gisas_instrument.svg");
+
+        if (item->is<OffSpecularInstrumentItem>())
+            return QIcon(":/images/offspec_instrument.svg");
+
+        if (item->is<SpecularInstrumentItem>())
+            return QIcon(":/images/specular_instrument.svg");
+
+        if (item->is<DepthProbeInstrumentItem>())
+            return QIcon(":/images/depth_instrument.svg");
+    }
+
+
+    return QVariant();
+}
+
+Qt::ItemFlags InstrumentsTreeModel::flags(const QModelIndex& index) const
+{
+    if (isHeadline(index) || !index.isValid())
+        return Qt::NoItemFlags;
+
+    auto f = QAbstractItemModel::flags(index);
+    f |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
+
+    if (index.column() == 0 && m_namesAreEditable) // col 0 contains name of the data entry
+        f |= Qt::ItemIsEditable;
+
+    return f;
+}
+
+bool InstrumentsTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+    if (!index.isValid())
+        return false;
+
+    if (role == Qt::EditRole && index.column() == 0) {
+        itemForIndex(index)->setInstrumentName(value.toString());
+        emit dataChanged(index, index);
+        return true;
+    }
+
+    return false;
+}
+
+InstrumentItem* InstrumentsTreeModel::itemForIndex(const QModelIndex& index) const
+{
+    if (!index.isValid())
+        return nullptr;
+
+    return reinterpret_cast<InstrumentItem*>(index.internalPointer());
+}
+
+QModelIndex InstrumentsTreeModel::indexForItem(InstrumentItem* item) const
+{
+    if (item == nullptr)
+        return QModelIndex();
+
+    for (auto type : visibleTypes())
+        if (auto row = instruments(type).indexOf(item); row >= 0)
+            return createIndex(row, 0, item);
+
+    return QModelIndex();
+}
+
+bool InstrumentsTreeModel::isHeadline(const QModelIndex& index) const
+{
+    if (!index.isValid())
+        return false;
+
+    return index.internalPointer() == nullptr;
+}
diff --git a/GUI/Models/InstrumentsTreeModel.h b/GUI/Models/InstrumentsTreeModel.h
new file mode 100644
index 00000000000..c25212a0f69
--- /dev/null
+++ b/GUI/Models/InstrumentsTreeModel.h
@@ -0,0 +1,74 @@
+//  ************************************************************************************************
+//
+//  BornAgain: simulate and fit reflection and scattering
+//
+//! @file      GUI/Views/ImportDataWidgets/InstrumentsTreeModel.h
+//! @brief     Defines class InstrumentsTreeModel
+//!
+//! @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_IMPORTDATAWIDGETS_INSTRUMENTSTREEMODEL_H
+#define BORNAGAIN_GUI_VIEWS_IMPORTDATAWIDGETS_INSTRUMENTSTREEMODEL_H
+
+#include <QAbstractItemModel>
+#include <QSet>
+
+class InstrumentModel;
+class InstrumentItem;
+
+//! Tree model for instrument item selection. Used e.g. for the instrument library.
+class InstrumentsTreeModel : public QAbstractItemModel {
+public:
+    InstrumentsTreeModel(QObject* parent, InstrumentModel* model);
+
+    enum InstrumentType {
+        None = 0x0,
+        Gisas = 0x1,
+        OffSpecular = 0x2,
+        Specular = 0x4,
+        DepthProbe = 0x8,
+        All = Gisas | OffSpecular | Specular | DepthProbe
+    };
+    Q_DECLARE_FLAGS(VisibleInstrumentTypes, InstrumentType)
+
+    void enableEmptyHeadlines(bool b);
+    void setTypeEnabled(InstrumentType type, bool b);
+
+    virtual QModelIndex index(int row, int column,
+                              const QModelIndex& parent = QModelIndex()) const override;
+
+    virtual QModelIndex parent(const QModelIndex& index) const override;
+    virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+    virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    virtual QVariant data(const QModelIndex& index, 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) override;
+    InstrumentItem* itemForIndex(const QModelIndex& index) const;
+    QModelIndex indexForItem(InstrumentItem* item) const;
+
+    void refreshAfterModelChange();
+
+    //! The topmost visible item. Can be null of course.
+    InstrumentItem* topMostItem() const;
+
+    QModelIndex indexOfHeadline(InstrumentType type) const;
+    bool isHeadline(const QModelIndex& index) const;
+
+private:
+    void clear();
+    QList<InstrumentType> visibleTypes() const;
+    QVector<InstrumentItem*> instruments(InstrumentType type) const;
+
+private:
+    InstrumentModel* m_model = nullptr;
+    VisibleInstrumentTypes m_visibleTypes;
+    bool m_namesAreEditable;
+    bool m_enableEmptyHeadlines;
+};
+
+#endif // BORNAGAIN_GUI_VIEWS_IMPORTDATAWIDGETS_INSTRUMENTSTREEMODEL_H
-- 
GitLab