Skip to content
Snippets Groups Projects
InstrumentView.cpp 11.78 KiB
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Instrument/InstrumentView.cpp
//! @brief     Implements class InstrumentView
//!
//! @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/InstrumentView.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/View/Instrument/DepthprobeInstrumentEditor.h"
#include "GUI/View/Instrument/GISASInstrumentEditor.h"
#include "GUI/View/Instrument/InstrumentListView.h"
#include "GUI/View/Instrument/OffspecInstrumentEditor.h"
#include "GUI/View/Instrument/SpecularInstrumentEditor.h"
#include "GUI/View/Tool/GroupBoxCollapser.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include <QAction>
#include <QCheckBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QScrollArea>
#include <QTextEdit>
#include <QWidgetAction>

InstrumentView::InstrumentView(QWidget* parent, ProjectDocument* document)
    : QWidget(parent)
    , m_document(document)
{
    auto* horizontalLayout = new QHBoxLayout;
    m_instrumentListView = new InstrumentListView(document, this);
    horizontalLayout->addWidget(m_instrumentListView);

    m_scrollArea = new QScrollArea(this);
    m_scrollArea->setWidgetResizable(true);
    m_scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    horizontalLayout->addWidget(m_scrollArea, 1);

    auto* toolbar = new StyledToolbar(this);
    toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolbar->addActions(m_instrumentListView->toolbarActions());

    auto* mainLayout = new QVBoxLayout(this);
    mainLayout->setContentsMargins(0, 0, 0, 0);
    mainLayout->setSpacing(0);
    mainLayout->addWidget(toolbar);
    mainLayout->addLayout(horizontalLayout);

    createWidgetsForCurrentInstrument();
    updateSingleInstrumentMode();

    connect(m_instrumentListView, &InstrumentListView::instrumentSelected, this,
            &InstrumentView::createWidgetsForCurrentInstrument);

    connect(m_document, &ProjectDocument::singleInstrumentModeChanged, this,
            &InstrumentView::updateSingleInstrumentMode);
}

void InstrumentView::showEvent(QShowEvent*)
{
    // disconnect because when this view is visible, no other instance is modifying instruments. By
    // disconnecting, no additional logic is necessary to avoid recursive calls (recursive since
    // this view also causes instrumentChanged to be emitted).
    disconnect(m_document->multiNotifier(), &MultiInstrumentNotifier::instrumentChanged, this,
               &InstrumentView::onInstrumentChangedFromExternal);
}

void InstrumentView::hideEvent(QHideEvent*)
{
    // when the instrument view gets hidden (meaning another view is shown), it's necessary to
    // listen to changes (e.g. performed by the LinkInstrumentManager).
    connect(m_document->multiNotifier(), &MultiInstrumentNotifier::instrumentChanged, this,
            &InstrumentView::onInstrumentChangedFromExternal);
}

void InstrumentView::createWidgetsForCurrentInstrument()
{
    auto* currentInstrument = m_instrumentListView->currentInstrumentItem();
    if (!currentInstrument) {
        m_scrollArea->setWidget(new QWidget(m_scrollArea)); // blank widget
        return;
    }

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

    auto* g = new QGroupBox(m_scrollArea);
    g->setTitle(QString("Information (%1 instrument)").arg(currentInstrument->instrumentType()));
    g->setMaximumHeight(200);

    auto* formLayout = new QFormLayout(g);
    formLayout->setContentsMargins(17, 17, 17, 17);
    formLayout->setSpacing(8);
    layout->addWidget(g);

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

    auto* descriptionEdit = new QTextEdit(g);
    descriptionEdit->setMinimumWidth(300);
    descriptionEdit->setMaximumHeight(100);
    descriptionEdit->setAcceptRichText(false);
    descriptionEdit->setTabChangesFocus(true);
    descriptionEdit->setPlainText(currentInstrument->description());
    formLayout->addRow("Description:", descriptionEdit);
    connect(descriptionEdit, &QTextEdit::textChanged,
            [&]() { onInstrumentdescriptionEdited(descriptionEdit->toPlainText()); });

    auto* collapser = GroupBoxCollapser::installIntoGroupBox(g);
    collapser->setExpanded(currentInstrument->isExpandInfo());
    connect(collapser, &GroupBoxCollapser::toggled, this,
            [currentInstrument](bool b) { currentInstrument->setExpandInfo(b); });

    auto* ec = m_document->multiNotifier();
    if (auto* sp = dynamic_cast<SpecularInstrumentItem*>(currentInstrument)) {
        auto* editor = new SpecularInstrumentEditor(m_scrollArea, sp, ec);
        connect(editor, &SpecularInstrumentEditor::dataChanged, this,
                &InstrumentView::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,
                &InstrumentView::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else if (auto* gisas = dynamic_cast<GISASInstrumentItem*>(currentInstrument)) {
        auto* editor = new GISASInstrumentEditor(m_scrollArea, gisas);
        connect(editor, &GISASInstrumentEditor::dataChanged, this,
                &InstrumentView::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,
                &InstrumentView::onInstrumentChangedByEditor);
        layout->addWidget(editor);
    } else
        ASSERT(false);

    m_scrollArea->setWidget(w);
}

void InstrumentView::onInstrumentNameEdited(const QString& newName)
{
    auto* currentInstrument = m_instrumentListView->currentInstrumentItem();
    if (currentInstrument && currentInstrument->instrumentName() != newName)
        m_document->multiNotifier()->setInstrumentName(currentInstrument, newName);
}

void InstrumentView::onInstrumentdescriptionEdited(const QString& t)
{
    auto* currentInstrument = m_instrumentListView->currentInstrumentItem();
    if (currentInstrument && currentInstrument->description() != t) {
        currentInstrument->setDescription(t);
        onInstrumentChangedByEditor();
    }
}

void InstrumentView::onInstrumentChangedByEditor()
{
    // uses 'MultiInstrumentNotifier::instrumentChanged' signal for two purposes:
    // 1) notify 'ProjectDocument' that user has changed data ==> mark project with '*'
    // 2) notify 'LinkInstrumentManager' ==> unlink instrument from data if they are incompatible
    m_document->multiNotifier()->notifyInstrumentChanged(
        m_instrumentListView->currentInstrumentItem());
}

void InstrumentView::onInstrumentChangedFromExternal(const InstrumentItem* instrument)
{
    if (instrument == m_instrumentListView->currentInstrumentItem())
        createWidgetsForCurrentInstrument();
}

void InstrumentView::updateSingleInstrumentMode()
{
    m_instrumentListView->setVisible(!m_document->singleInstrumentMode());
}
void InstrumentView::fillViewMenu(QMenu* menu)
{

    auto* action = new QWidgetAction(menu);
    auto* checkBox = new QCheckBox("Use single instrument", menu);
    action->setDefaultWidget(checkBox);
    action->setText("Use single instrument");
    action->setCheckable(true);
    checkBox->setChecked(m_document->singleInstrumentMode());
    connect(checkBox, &QCheckBox::stateChanged, this,
            &InstrumentView::onSingleInstrumentModeChanged);
    menu->addAction(action);
    menu->addSeparator();


    const auto f = m_document->functionalities();
    gisasCheck = new QCheckBox("GISAS instrument", menu);
    gisasCheck->setChecked(f.testFlag(ProjectDocument::Gisas));
    connect(gisasCheck, &QCheckBox::stateChanged, this, &InstrumentView::onFunctionalityChanged);
    action = new QWidgetAction(menu);
    action->setToolTip("GISAS instrument");
    action->setDefaultWidget(gisasCheck);
    menu->addAction(action);
    offspecCheck = new QCheckBox("Off-specular instrument", menu);
    offspecCheck->setChecked(f.testFlag(ProjectDocument::Offspec));
    connect(offspecCheck, &QCheckBox::stateChanged, this, &InstrumentView::onFunctionalityChanged);
    action = new QWidgetAction(menu);
    action->setToolTip("Off-specular instrument");
    action->setDefaultWidget(offspecCheck);
    menu->addAction(action);
    specularCheck = new QCheckBox("Specular instrument", menu);
    specularCheck->setChecked(f.testFlag(ProjectDocument::Specular));
    connect(specularCheck, &QCheckBox::stateChanged, this, &InstrumentView::onFunctionalityChanged);
    action = new QWidgetAction(menu);
    action->setToolTip("Specular instrument");
    action->setDefaultWidget(specularCheck);
    menu->addAction(action);
    depthProbeCheck = new QCheckBox("Depth probe instrument", menu);
    depthProbeCheck->setChecked(f.testFlag(ProjectDocument::Depthprobe));
    connect(depthProbeCheck, &QCheckBox::stateChanged, this,
            &InstrumentView::onFunctionalityChanged);
    action = new QWidgetAction(menu);
    action->setToolTip("Depth probe instrument");
    action->setDefaultWidget(depthProbeCheck);
    menu->addAction(action);
}

void InstrumentView::onSingleInstrumentModeChanged(bool newState)
{
    if (newState) {
        if (m_document->instrumentModel()->instrumentItems().size() > 1) {
            QMessageBox::warning(this, "Select single instrument mode",
                                 "This project already contains more than one instrument. Changing "
                                 "this setting is not possible.");
            return;
        }

        m_document->setSingleInstrumentMode(true);
    } else
        m_document->setSingleInstrumentMode(false);
    if (gProjectDocument.has_value()) {
        appSettings->setDefaultIsSingleInstrumentMode(
            gProjectDocument.value()->singleInstrumentMode());
    }
}

ProjectDocument::Functionalities InstrumentView::functionalities() const
{
    ProjectDocument::Functionalities f;
    f.setFlag(ProjectDocument::Gisas, gisasCheck->isChecked());
    f.setFlag(ProjectDocument::Offspec, offspecCheck->isChecked());
    f.setFlag(ProjectDocument::Specular, specularCheck->isChecked());
    f.setFlag(ProjectDocument::Depthprobe, depthProbeCheck->isChecked());
    return f;
}

void InstrumentView::onFunctionalityChanged()
{

    const auto f = functionalities();

    if (f == ProjectDocument::None) {
        QMessageBox::warning(this, "Select functionality",
                             "You have to select at least one functionality. Changing "
                             "this setting is not possible.");
        const auto f = m_document->functionalities();
        gisasCheck->setChecked(f.testFlag(ProjectDocument::Gisas));
        offspecCheck->setChecked(f.testFlag(ProjectDocument::Offspec));
        specularCheck->setChecked(f.testFlag(ProjectDocument::Specular));
        depthProbeCheck->setChecked(f.testFlag(ProjectDocument::Depthprobe));
        return;
    }
    m_document->setFunctionalities(f);
    if (gProjectDocument.has_value()) {
        appSettings->setDefaultFunctionalities(toVariant(f));
    }
}