// ************************************************************************************************ // // 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/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/Widget/ApplicationSettings.h" #include "GUI/View/Widget/GroupBoxes.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, [this, descriptionEdit]() { onInstrumentdescriptionEdited(descriptionEdit->toPlainText()); }); GroupBoxCollapser::installIntoGroupBox2(g, currentInstrument->expandInfo); 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_NEVER; 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)); } }