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