Skip to content
Snippets Groups Projects
InstrumentLibraryEditor.cpp 13.02 KiB
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Instrument/InstrumentLibraryEditor.cpp
//! @brief     Implements class InstrumentLibraryEditor.
//!
//! @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/View/Instrument/InstrumentLibraryEditor.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/InstrumentLibrary.h"
#include "GUI/Support/Style/Style.h"
#include "GUI/View/Instrument/DepthprobeInstrumentEditor.h"
#include "GUI/View/Instrument/GISASInstrumentEditor.h"
#include "GUI/View/Instrument/OffspecInstrumentEditor.h"
#include "GUI/View/Instrument/SpecularInstrumentEditor.h"
#include "GUI/View/Item/ItemViewOverlayButtons.h"
#include "GUI/View/Tool/ItemDelegateForHTML.h"
#include "GUI/View/Tool/mainwindow_constants.h"
#include "GUI/View/Widget/ApplicationSettings.h"
#include <QAction>
#include <QFormLayout>
#include <QInputDialog>
#include <QPushButton>
#include <QSettings>
#include <QSplitter>
#include <QTextEdit>
#include <QVBoxLayout>

InstrumentLibraryEditor::InstrumentLibraryEditor(QWidget* parent,
                                                 InstrumentLibrary* instrumentLibrary)
    : QDialog(parent)
    , m_instrumentLibrary(instrumentLibrary)
    , m_treeModel(new TreeModel(this, instrumentLibrary->instrumentModel()))
    , m_chosenItem(nullptr)
{
    // object name is needed to reload application settings
    if (this->objectName().isEmpty())
        this->setObjectName("InstrumentLibraryEditor");

    setGeometry(0, 0, 780, 429);

    auto* verticalLayout = new QVBoxLayout(this);
    setLayout(verticalLayout);

    auto* splitter = new QSplitter;
    verticalLayout->addWidget(splitter);
    splitter->setOrientation(Qt::Horizontal);

    m_treeView = new QTreeView;
    splitter->addWidget(m_treeView);

    m_scrollArea = new QScrollArea;
    splitter->addWidget(m_scrollArea);
    m_scrollArea->setWidgetResizable(true);

    auto* scrollAreaWidgetContents = new QWidget;
    scrollAreaWidgetContents->setGeometry(0, 0, 69, 380);
    m_scrollArea->setWidget(scrollAreaWidgetContents);

    m_buttonBox = new QDialogButtonBox;
    verticalLayout->addWidget(m_buttonBox);
    m_buttonBox->setOrientation(Qt::Horizontal);
    m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);

    setWindowIcon(QIcon(":/images/library.svg"));
    setWindowFlag(Qt::WindowContextHelpButtonHint, false);

    m_treeModel->enableEmptyHeadlines(false);

    m_treeView->setItemsExpandable(false);
    m_treeView->setRootIsDecorated(false);
    m_treeView->setHeaderHidden(true);
    m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
    m_treeView->setModel(m_treeModel);
    m_treeView->expandAll();
    m_treeView->setVerticalScrollMode(QTreeView::ScrollPerPixel);
    m_treeView->setIndentation(0);
    m_treeView->setItemDelegate(new ItemDelegateForHTML(this));
    m_treeView->setIconSize(QSize(128, 128));

    connect(m_treeModel, &QAbstractItemModel::modelReset, [this] { m_treeView->expandAll(); });

    connect(m_buttonBox, &QDialogButtonBox::accepted, this, &InstrumentLibraryEditor::accept);
    connect(m_buttonBox, &QDialogButtonBox::rejected, this, &InstrumentLibraryEditor::reject);

    // ensure a current item when widget is shown
    GUI::Style::setResizable(this);
    appSettings->loadWindowSizeAndPos(this);

    applySplitterPos();
    // unfix m_treeView width only on manual resize
    connect(splitter, &QSplitter::splitterMoved, [this] {
        m_treeView->setMinimumWidth(0);
        m_treeView->setMaximumWidth(QWIDGETSIZE_MAX);
    });
}

InstrumentLibraryEditor::~InstrumentLibraryEditor()
{
    appSettings->saveWindowSizeAndPos(this);
    saveSplitterPos();
}

void InstrumentLibraryEditor::applySplitterPos()
{
    QSettings settings;
    if (settings.childGroups().contains(GUI::Style::S_INSTRUMENT_LIBRARY_EDITOR)) {
        settings.beginGroup(GUI::Style::S_INSTRUMENT_LIBRARY_EDITOR);
        m_treeView->setFixedWidth(
            settings.value(GUI::Style::S_INSTRUMENT_LIBRARY_EDITOR_TREE_WIDTH).toInt());
        settings.endGroup();
    } else
        m_treeView->setFixedWidth(GUI::Style::INSTRUMENT_LIBRARY_EDITOR_TREE_WIDTH);
}

void InstrumentLibraryEditor::saveSplitterPos()
{
    QSettings settings;
    settings.beginGroup(GUI::Style::S_INSTRUMENT_LIBRARY_EDITOR);
    settings.setValue(GUI::Style::S_INSTRUMENT_LIBRARY_EDITOR_TREE_WIDTH, m_treeView->width());
    settings.endGroup();
    settings.sync();
}

InstrumentItem* InstrumentLibraryEditor::execChoose()
{
    setWindowTitle("Instrument Library - Choose instrument");

    ItemViewOverlayButtons::install(
        m_treeView, [this](const QModelIndex& i, bool h) { return getOverlayActions(i, h); });
    m_treeView->setItemDelegate(new ItemDelegateForHTML(this));
    connect(m_treeView, &QTreeView::doubleClicked, this,
            &InstrumentLibraryEditor::onItemDoubleClickedForChoose);
    connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this,
            &InstrumentLibraryEditor::onCurrentChangedForChoose);
    onCurrentChangedForChoose();

    // select the first instrument
    QModelIndex index = m_treeModel->indexForItem(m_instrumentLibrary->instrumentItems().first());
    m_treeView->setCurrentIndex(index);

    if (exec() == QDialog::Accepted)
        return m_chosenItem;

    return nullptr;
}

void InstrumentLibraryEditor::execAdd(const InstrumentItem& instrumentToAdd)
{
    const QString& newName = m_instrumentLibrary->suggestName(instrumentToAdd.instrumentName());
    auto* addedInstrument = m_instrumentLibrary->addItemCopy(newName, instrumentToAdd);

    setWindowTitle("Instrument Library - Add instrument");

    m_treeModel->setNewInstrument(addedInstrument);

    ItemViewOverlayButtons::install(
        m_treeView, [this](const QModelIndex& i, bool h) { return getOverlayActions(i, h); });
    m_treeView->setItemDelegate(new ItemDelegateForHTML(this));
    connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this,
            &InstrumentLibraryEditor::createWidgetsForCurrentInstrument);

    QModelIndex index = m_treeModel->indexForItem(addedInstrument);
    m_treeView->expandAll();
    m_treeView->setCurrentIndex(index);
    m_treeView->scrollTo(index, QAbstractItemView::PositionAtTop);
    createWidgetsForCurrentInstrument();
    exec();
}

void InstrumentLibraryEditor::onItemDoubleClickedForChoose(const QModelIndex& index)
{
    m_chosenItem = m_treeModel->itemForIndex(index);
    if (m_chosenItem != nullptr)
        accept();
}

void InstrumentLibraryEditor::onCurrentChangedForChoose()
{
    m_chosenItem = m_treeModel->itemForIndex(m_treeView->currentIndex());
    m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_chosenItem != nullptr);
    createWidgetsForCurrentInstrument();
}

QList<QAction*> InstrumentLibraryEditor::getOverlayActions(const QModelIndex& index, bool asHover)
{
    if (m_treeModel->isHeadline(index))
        return {};

    // -- index belongs to item
    if (!asHover)
        return {};

    auto* item = m_treeModel->itemForIndex(index);
    if (item == nullptr)
        return {};

    auto* removeAction = new QAction(this);
    removeAction->setText("Remove");
    removeAction->setIcon(QIcon(":/images/delete.svg"));
    removeAction->setIconText("Remove");
    removeAction->setToolTip("Remove this instrument");
    connect(removeAction, &QAction::triggered, [this, item] { m_treeModel->removeItem(item); });

    return {removeAction};
}

void InstrumentLibraryEditor::createWidgetsForCurrentInstrument()
{
    auto* currentInstrument = m_treeModel->itemForIndex(m_treeView->currentIndex());
    if (!currentInstrument) {
        m_scrollArea->setWidget(new QWidget(m_scrollArea)); // blank widget
        return;
    }

    auto* w = new QWidget(m_scrollArea);
    auto* layout = new QVBoxLayout(w);

    auto title = QString("Summary (%1 instrument)").arg(currentInstrument->instrumentType());
    auto* g = new CollapsibleGroupBox(title, m_scrollArea, currentInstrument->expandInfo);
    g->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    auto* formLayout = new QFormLayout(g->body());
    formLayout->setContentsMargins(17, 17, 17, 17);
    layout->addWidget(g);

    auto* nameEdit = new QLineEdit(g);
    formLayout->addRow("Name:", nameEdit);
    nameEdit->setText(currentInstrument->instrumentName());
    connect(nameEdit, &QLineEdit::textEdited, this,
            &InstrumentLibraryEditor::onInstrumentNameEdited);

    auto* descriptionEdit = new QTextEdit(g);
    descriptionEdit->setMinimumWidth(300);
    descriptionEdit->setFixedHeight(60); // TODO replace by 2*line_height
    descriptionEdit->setAcceptRichText(false);
    descriptionEdit->setTabChangesFocus(true);
    descriptionEdit->setPlainText(currentInstrument->description());
    formLayout->addRow("Description:", descriptionEdit);
    connect(descriptionEdit, &QTextEdit::textChanged, [this, descriptionEdit] {
        onInstrumentDescriptionEdited(descriptionEdit->toPlainText());
    });

    auto* ec = m_instrumentLibrary->editController();
    if (auto* sp = dynamic_cast<SpecularInstrumentItem*>(currentInstrument)) {
        auto* editor = new SpecularInstrumentEditor(m_scrollArea, sp, ec);
        connect(editor, &SpecularInstrumentEditor::dataChanged, this,
                &InstrumentLibraryEditor::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else if (auto* os = dynamic_cast<OffspecInstrumentItem*>(currentInstrument)) {
        auto* editor = new OffspecInstrumentEditor(m_scrollArea, os, ec);
        connect(editor, &OffspecInstrumentEditor::dataChanged, this,
                &InstrumentLibraryEditor::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else if (auto* gisas = dynamic_cast<GISASInstrumentItem*>(currentInstrument)) {
        auto* editor = new GISASInstrumentEditor(m_scrollArea, gisas);
        connect(editor, &GISASInstrumentEditor::dataChanged, this,
                &InstrumentLibraryEditor::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else if (auto* dp = dynamic_cast<DepthprobeInstrumentItem*>(currentInstrument)) {
        auto* editor = new DepthprobeInstrumentEditor(m_scrollArea, dp, ec);
        connect(editor, &DepthprobeInstrumentEditor::dataChanged, this,
                &InstrumentLibraryEditor::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else
        ASSERT_NEVER;

    m_scrollArea->setWidget(w);
}

void InstrumentLibraryEditor::onInstrumentNameEdited(const QString& newName)
{
    QModelIndex index = m_treeView->currentIndex();
    m_treeModel->setData(index, newName, Qt::EditRole);
}

void InstrumentLibraryEditor::onInstrumentDescriptionEdited(const QString& t)
{
    QModelIndex index = m_treeView->currentIndex();
    m_treeModel->setData(index, t, Qt::ToolTipRole);
}

void InstrumentLibraryEditor::onInstrumentChangedByEditor()
{
    // uses 'MultiInstrumentNotifier::instrumentChanged' signal to set
    // 'InstrumentLibrary:m_modified' flag to true ==> save library on close.
    auto* currentInstrument = m_treeModel->itemForIndex(m_treeView->currentIndex());
    m_instrumentLibrary->editController()->notifyInstrumentChanged(currentInstrument);
}

/*********************************************************************************************/

TreeModel::TreeModel(QObject* parent, InstrumentModel* model)
    : InstrumentsTreeModel(parent, model)
{
}

void TreeModel::setNewInstrument(InstrumentItem* addedInstrument)
{
    m_newInstrument = addedInstrument;
}

QVariant TreeModel::data(const QModelIndex& index, int role) const
{
    if (isHeadline(index))
        return InstrumentsTreeModel::data(index, role);

    auto* const item = itemForIndex(index);

    if (role == Qt::DisplayRole) {
        auto descr = item->description();
        if (!descr.isEmpty()) {
            descr.prepend("<br><br>");
            // max 4 lines
            while (descr.count("\n") > 3) {
                descr.truncate(descr.lastIndexOf("\n"));
                descr += " [...]";
            }
            descr.replace("\n", "<br>");
        }
        return "<b>" + item->instrumentName() + "</b>" + descr;
    }

    if (role == Qt::DecorationRole && (item == m_newInstrument)) {
        if (role == Qt::DecorationRole)
            switch (instrumentType(item)) {
            case Gisas:
                return QIcon(":/images/gisas_instrument_new.svg");
            case Offspec:
                return QIcon(":/images/offspec_instrument_new.svg");
            case Specular:
                return QIcon(":/images/specular_instrument_new.svg");
            case Depthprobe:
                return QIcon(":/images/depth_instrument_new.svg");
            default:
                break;
            }
    }
    return InstrumentsTreeModel::data(index, role);
}