Skip to content
Snippets Groups Projects
Commit 4e760edd authored by Matthias Puchner's avatar Matthias Puchner
Browse files

introduce sample selection list (model & view) - the left pane of the new...

introduce sample selection list (model & view) - the left pane of the new layer oriented sample editor
parent 8e04c690
No related branches found
No related tags found
1 merge request!405Introduce sample selection list
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/Views/ImportDataWidgets/SampleListModel.cpp
//! @brief Implements class SampleListModel
//!
//! @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/SampleListModel.h"
#include "GUI/Application/Application.h"
#include "GUI/Models/GUIExamplesFactory.h"
#include "GUI/Models/ModelUtils.h"
#include "GUI/Models/MultiLayerItem.h"
#include "GUI/Models/SampleModel.h"
#include "GUI/mainwindow/projectmanager.h"
#include <QApplication>
#include <QFontMetrics>
#include <QIcon>
SampleListModel::SampleListModel(QObject* parent, SampleModel* model)
: QAbstractListModel(parent), m_sampleModel(model)
{
connect(m_sampleModel, &SampleModel::modelAboutToBeReset, this, &SampleListModel::clear,
Qt::UniqueConnection);
}
void SampleListModel::clear()
{
beginResetModel();
endResetModel();
}
MultiLayerItem* SampleListModel::topMostItem() const
{
return m_sampleModel->topItem<MultiLayerItem>();
}
int SampleListModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_sampleModel->multiLayerItems().size();
}
QVariant SampleListModel::data(const QModelIndex& index, int role) const
{
const auto item = itemForIndex(index);
if (role == Qt::ToolTipRole)
return item->description();
if (role == Qt::DisplayRole) {
auto descr = item->description();
if (!descr.isEmpty()) {
descr.prepend("<br><br>");
const int maxDescriptionLines = 8;
while (descr.count("\n") >= maxDescriptionLines) {
descr.truncate(descr.lastIndexOf("\n"));
descr += " [...]";
}
descr.replace("\n", "<br>");
}
return "<b>" + item->itemName() + "</b>" + descr;
}
if (role == Qt::EditRole)
return item->itemName();
return QVariant();
}
Qt::ItemFlags SampleListModel::flags(const QModelIndex& index) const
{
auto f = QAbstractItemModel::flags(index);
f |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
return f;
}
bool SampleListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid())
return false;
if (role == Qt::EditRole && index.column() == 0) {
itemForIndex(index)->setItemName(value.toString());
emit dataChanged(index, index);
return true;
}
if (role == Qt::ToolTipRole && index.column() == 0) {
itemForIndex(index)->setDescription(value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}
MultiLayerItem* SampleListModel::itemForIndex(const QModelIndex& index) const
{
if (!index.isValid())
return nullptr;
return m_sampleModel->multiLayerItems()[index.row()];
}
QModelIndex SampleListModel::indexForItem(MultiLayerItem* item) const
{
if (auto row = m_sampleModel->multiLayerItems().indexOf(item); row >= 0)
return index(row, 0);
return QModelIndex();
}
void SampleListModel::removeSample(MultiLayerItem* item)
{
QModelIndex index = indexForItem(item);
if (!index.isValid())
return;
beginRemoveRows(index.parent(), index.row(), index.row());
m_sampleModel->removeMultiLayer(item);
endRemoveRows();
}
QModelIndex SampleListModel::createSample()
{
using namespace GUI::Model::ItemUtils;
const int row = m_sampleModel->multiLayerItems().size();
beginInsertRows(QModelIndex(), row, row);
auto multilayer_item = m_sampleModel->insertItem<MultiLayerItem>();
multilayer_item->setItemName(suggestName(topItemNames(m_sampleModel), "Sample"));
endInsertRows();
return indexForItem(multilayer_item);
}
QModelIndex SampleListModel::createSampleFromExamples(const QString& className,
const QString& title,
const QString& description)
{
const int row = m_sampleModel->multiLayerItems().size();
beginInsertRows(QModelIndex(), row, row);
auto* sample = dynamic_cast<MultiLayerItem*>(GUIExamplesFactory::createSampleItems(
className, m_sampleModel, ProjectManager::instance()->document()->materialModel()));
sample->setItemName(title);
sample->setDescription(description);
endInsertRows();
return indexForItem(sample);
}
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/Views/ImportDataWidgets/SampleListModel.h
//! @brief Defines class SampleListModel
//!
//! @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_SAMPLELISTMODEL_H
#define BORNAGAIN_GUI_VIEWS_IMPORTDATAWIDGETS_SAMPLELISTMODEL_H
#include <QAbstractItemModel>
#include <QSet>
class SampleModel;
class MultiLayerItem;
//! List model for sample selection (used in the left pane of the layer oriented sample editor)
class SampleListModel : public QAbstractListModel {
public:
SampleListModel(QObject* parent, SampleModel* model);
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;
MultiLayerItem* itemForIndex(const QModelIndex& index) const;
QModelIndex indexForItem(MultiLayerItem* item) const;
//! Remove the given multilayer. nullptr is allowed.
void removeSample(MultiLayerItem* item);
//! The topmost visible item. Can be null of course.
MultiLayerItem* topMostItem() const;
//! Create a new sample (multilayer) and return the index of it.
QModelIndex createSample();
QModelIndex createSampleFromExamples(const QString& className, const QString& title,
const QString& description);
private:
void clear();
private:
SampleModel* m_sampleModel = nullptr;
};
#endif // BORNAGAIN_GUI_VIEWS_IMPORTDATAWIDGETS_SAMPLELISTMODEL_H
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/Views/SampleDesigner/SampleListView.cpp
//! @brief Implements class SampleListView
//!
//! @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/Views/SampleDesigner/SampleListView.h"
#include "GUI/Application/Application.h"
#include "GUI/Models/GUIExamplesFactory.h"
#include "GUI/Models/MultiLayerItem.h"
#include "GUI/Models/SampleListModel.h"
#include "GUI/Views/CommonWidgets/ItemViewOverlayButtons.h"
#include "GUI/utils/ItemDelegateForHTML.h"
#include <QAction>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLineEdit>
#include <QMenu>
#include <QPainter>
#include <QPushButton>
#include <QTextEdit>
namespace {
class ItemDelegateForSampleTree : public ItemDelegateForHTML {
public:
ItemDelegateForSampleTree(QObject* parent) : ItemDelegateForHTML(parent) {}
protected:
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
ItemDelegateForHTML::paint(painter, option, index);
QStyleOptionViewItem options = option;
initStyleOption(&options, index);
painter->save();
painter->setPen(QPen(Qt::lightGray, 1));
painter->drawLine(options.rect.left(), options.rect.bottom(), options.rect.right(),
options.rect.bottom());
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto s = ItemDelegateForHTML::sizeHint(option, index);
s.setHeight(std::max(s.height(), 32));
return s;
}
};
} // namespace
SampleListView::SampleListView(QWidget* parent, SampleModel* sampleModel) : QListView(parent)
{
m_model = new SampleListModel(this, sampleModel);
setContextMenuPolicy(Qt::CustomContextMenu);
setModel(m_model);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
ItemViewOverlayButtons::install(
this, [=](const QModelIndex& i, bool h) { return getOverlayActions(i, h); });
setItemDelegate(new ItemDelegateForSampleTree(this));
connect(selectionModel(), &QItemSelectionModel::currentChanged, this,
&SampleListView::onCurrentChanged);
connect(this, &QWidget::customContextMenuRequested, this, &SampleListView::showContextMenu);
m_newSampleAction = new QAction(this);
m_newSampleAction->setText("Create new sample");
m_newSampleAction->setIcon(QIcon(":/images/shape-square-plus.svg"));
m_newSampleAction->setIconText("New");
m_newSampleAction->setToolTip("Create new sample");
connect(m_newSampleAction, &QAction::triggered, this, &SampleListView::createNewSample);
m_chooseFromLibraryAction = new QAction(this);
m_chooseFromLibraryAction->setText("Choose from sample library");
m_chooseFromLibraryAction->setIcon(QIcon(":/images/library.svg"));
m_chooseFromLibraryAction->setIconText("Library");
m_chooseFromLibraryAction->setToolTip("Choose from sample library");
QMenu* menu = new QMenu(this);
m_chooseFromLibraryAction->setMenu(menu);
for (auto exampleName : GUIExamplesFactory::exampleNames()) {
auto [title, description] = GUIExamplesFactory::exampleInfo(exampleName);
auto icon = QIcon(":/SampleDesignerToolbox/images/sample_layers2.png");
auto* action = menu->addAction(icon, title);
action->setToolTip(description);
connect(action, &QAction::triggered,
[=]() { createSampleFromLibrary(exampleName, title, description); });
}
}
void SampleListView::setCurrentSample(MultiLayerItem* multiLayer)
{
setCurrentIndex(m_model->indexForItem(m_model->topMostItem()));
}
MultiLayerItem* SampleListView::currentSample()
{
return m_model->itemForIndex(currentIndex());
}
QAction* SampleListView::newSampleAction()
{
return m_newSampleAction;
}
QAction* SampleListView::chooseFromLibraryAction()
{
return m_chooseFromLibraryAction;
}
QSize SampleListView::sizeHint() const
{
QSize s = QListView::sizeHint();
s.setWidth(std::max(300, s.width()));
return s;
}
void SampleListView::createNewSample()
{
const QModelIndex newIndex = m_model->createSample();
setCurrentIndex(newIndex);
}
void SampleListView::createSampleFromLibrary(const QString& classname, const QString& title,
const QString& description)
{
const QModelIndex newIndex = m_model->createSampleFromExamples(classname, title, description);
setCurrentIndex(newIndex);
}
QList<QAction*> SampleListView::getOverlayActions(const QModelIndex& index, bool asHover)
{
if (!asHover)
return {};
auto item = m_model->itemForIndex(index);
if (item == nullptr)
return {};
return {createEditAction(this, item), createRemoveAction(this, item)};
}
void SampleListView::editNameAndDescription(MultiLayerItem* item)
{
QDialog dlg(this);
dlg.setObjectName("NameAndDescriptionDialog");
dlg.setWindowTitle("Edit name and description of sample");
dlg.setWindowFlag(Qt::WindowContextHelpButtonHint, false);
dlg.setProperty("stylable", true); // for stylesheet addressing
QFormLayout* layout = new QFormLayout(&dlg);
QLineEdit* nameEdit = new QLineEdit(&dlg);
nameEdit->setText(item->itemName());
nameEdit->selectAll();
QTextEdit* descriptionEdit = new QTextEdit(&dlg);
descriptionEdit->setMinimumWidth(200);
descriptionEdit->setAcceptRichText(false);
descriptionEdit->setTabChangesFocus(true);
descriptionEdit->setPlainText(item->description());
QDialogButtonBox* buttonBox = new QDialogButtonBox(&dlg);
buttonBox->addButton(QDialogButtonBox::Ok);
buttonBox->addButton(QDialogButtonBox::Cancel);
buttonBox->button(QDialogButtonBox::Ok)->setAutoDefault(true);
buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
layout->addRow("Name:", nameEdit);
layout->addRow("Description:", descriptionEdit);
layout->addWidget(buttonBox);
dlg.setLayout(layout);
connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
baApp->settings().loadWindowSizeAndPos(&dlg);
if (dlg.exec() == QDialog::Accepted) {
QModelIndex index = m_model->indexForItem(item);
m_model->setData(index, nameEdit->text(), Qt::EditRole);
m_model->setData(index, descriptionEdit->toPlainText(), Qt::ToolTipRole);
// if length of description changes height of TreeItem, the position of the overlay buttons
// has to be updated -> schedule a re-layout
scheduleDelayedItemsLayout();
}
baApp->settings().saveWindowSizeAndPos(&dlg);
}
void SampleListView::onCurrentChanged(const QModelIndex& index)
{
emit currentSampleChanged(m_model->itemForIndex(index));
}
QAction* SampleListView::createEditAction(QObject* parent, MultiLayerItem* item)
{
QAction* editAction = new QAction(parent);
editAction->setText("Edit name and description");
editAction->setIcon(QIcon(":/images/edit.svg"));
editAction->setIconText("Edit");
editAction->setToolTip("Edit name and description");
connect(editAction, &QAction::triggered, [=]() { editNameAndDescription(item); });
return editAction;
}
QAction* SampleListView::createRemoveAction(QObject* parent, MultiLayerItem* item)
{
QAction* removeAction = new QAction(parent);
removeAction->setText("Remove");
removeAction->setIcon(QIcon(":/images/delete.svg"));
removeAction->setIconText("Remove");
removeAction->setToolTip("Remove this sample");
connect(removeAction, &QAction::triggered, [=]() { m_model->removeSample(item); });
return removeAction;
}
void SampleListView::showContextMenu(const QPoint& pos)
{
auto sampleAtPoint = m_model->itemForIndex(indexAt(pos));
QMenu menu;
menu.setToolTipsVisible(true);
if (sampleAtPoint != nullptr) {
menu.addAction(createEditAction(&menu, sampleAtPoint));
menu.addAction(createRemoveAction(&menu, sampleAtPoint));
menu.addSeparator();
}
menu.addAction(m_newSampleAction);
menu.addAction(m_chooseFromLibraryAction);
menu.exec(mapToGlobal(pos));
}
// ************************************************************************************************
//
// BornAgain: simulate and fit reflection and scattering
//
//! @file GUI/Views/SampleDesigner/SampleListView.h
//! @brief Defines class SampleListView
//!
//! @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_SAMPLEDESIGNER_SAMPLELISTVIEW_H
#define BORNAGAIN_GUI_VIEWS_SAMPLEDESIGNER_SAMPLELISTVIEW_H
#include <QListView>
class SampleModel;
class SampleListModel;
class MultiLayerItem;
//! List view to select one sample (left side of layer-oriented sample editor)
class SampleListView : public QListView {
Q_OBJECT
public:
SampleListView(QWidget* parent, SampleModel* sampleModel);
void setCurrentSample(MultiLayerItem* multiLayer);
MultiLayerItem* currentSample();
QAction* newSampleAction();
QAction* chooseFromLibraryAction();
virtual QSize sizeHint() const override;
signals:
void currentSampleChanged(MultiLayerItem* current);
private:
void createNewSample();
void createSampleFromLibrary(const QString& classname, const QString& title,
const QString& description);
QList<QAction*> getOverlayActions(const QModelIndex& index, bool asHover);
void editNameAndDescription(MultiLayerItem* item);
void onCurrentChanged(const QModelIndex& index);
QAction* createEditAction(QObject* parent, MultiLayerItem* item);
QAction* createRemoveAction(QObject* parent, MultiLayerItem* item);
void showContextMenu(const QPoint& pos);
private:
SampleListModel* m_model;
QAction* m_newSampleAction;
QAction* m_chooseFromLibraryAction;
};
#endif // BORNAGAIN_GUI_VIEWS_SAMPLEDESIGNER_SAMPLELISTVIEW_H
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment