Skip to content
Snippets Groups Projects
SampleEditorController.cpp 16.3 KiB
Newer Older
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleDesigner/SampleEditorController.cpp
//! @brief     Implements class SampleEditorController
//!
//! @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/View/SampleDesigner/SampleEditorController.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
#include "GUI/Model/CatSample/FormFactorItemCatalog.h"
#include "GUI/Model/Project/ProjectDocument.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/CoreAndShellItem.h"
#include "GUI/Model/Sample/InterferenceItems.h"
#include "GUI/Model/Sample/LayerItem.h"
#include "GUI/Model/Sample/MaterialModel.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
#include "GUI/Model/Sample/MesocrystalItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "GUI/Model/Sample/ParticleLayoutItem.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Common/DoubleSpinBox.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
#include "GUI/View/SampleDesigner/CompoundForm.h"
#include "GUI/View/SampleDesigner/CoreAndShellForm.h"
#include "GUI/View/SampleDesigner/InterferenceForm.h"
#include "GUI/View/SampleDesigner/LatticeTypeSelectionForm.h"
#include "GUI/View/SampleDesigner/LayerForm.h"
#include "GUI/View/SampleDesigner/MaterialInplaceForm.h"
Wuttke, Joachim's avatar
Wuttke, Joachim committed
#include "GUI/View/SampleDesigner/MesocrystalForm.h"
#include "GUI/View/SampleDesigner/MultiLayerForm.h"
#include "GUI/View/SampleDesigner/ParticleLayoutForm.h"
#include "GUI/View/SampleDesigner/SampleEditorCommands.h"
#include "Sample/HardParticle/Cylinder.h"
SampleEditorController::SampleEditorController(ProjectDocument* document, SampleItem* multi)
    : m_sampleItem(multi)
    , m_sampleForm(nullptr)
    , m_document(document)
{
}

void SampleEditorController::setMultiLayerForm(MultiLayerForm* view)
{
    m_sampleForm = view;
MultiLayerForm* SampleEditorController::sampleForm() const
    return m_sampleForm;
SampleItem* SampleEditorController::sampleItem() const
    return m_sampleItem;
void SampleEditorController::addLayerItem(LayerItem* before)
    const int newIndex = (before != nullptr) ? m_sampleItem->layers().indexOf(before)
                                             : m_sampleItem->layers().size();
    m_undoStack.push(new CommandAddLayer(this, newIndex));
}

void SampleEditorController::addLayerItemFromUndo(int atIndex)
    // -- find a color for the new layer
    QColor color;
    auto unusedColors = LayerEditorUtils::predefinedLayerColors();
    for (auto* l : m_sampleItem->layers())
        unusedColors.removeAll(l->color());
    if (!unusedColors.isEmpty())
        color = unusedColors.first();
    else {
        // search for a color which has been used the less, and which is not the same as in the
        // layers above and below
        QMap<QString, int> usage;
        for (auto* l : m_sampleItem->layers())
            usage[l->color().name()] += 1;

        auto sortedByUsage = LayerEditorUtils::predefinedLayerColors();
        std::stable_sort(
            sortedByUsage.begin(), sortedByUsage.end(),
            [&](const QColor& a, const QColor& b) { return usage[a.name()] < usage[b.name()]; });


        const QColor above =
            (atIndex > 0) ? m_sampleItem->layers()[atIndex - 1]->color() : QColor();
        const QColor below = (atIndex < m_sampleItem->layers().size())
                                 ? m_sampleItem->layers()[atIndex]->color()
            if (col != above && col != below) {
                color = col;
                break;
            }
    }

    // - create new layer
    LayerItem* layer = m_sampleItem->createLayerItemAt(atIndex);
Matthias Puchner's avatar
Matthias Puchner committed
    layer->setMaterial(materialItems()->defaultMaterial());
    layer->setColor(color);
    ASSERT(m_sampleForm);
    m_sampleForm->onLayerAdded(layer);
    m_sampleForm->updateUnits();

    // expand the new layer's form for better workflow
    for (auto* c : m_sampleForm->findChildren<LayerForm*>())
        if (c->layerItem() == layer)
            c->expand();
void SampleEditorController::addLayoutItem(LayerForm* layerItemWidget)
    auto* newLayoutItem = layerItemWidget->layerItem()->addLayoutItem();
    layerItemWidget->onLayoutAdded(newLayoutItem);
    m_sampleForm->updateUnits();

    for (auto* layoutForms : layerItemWidget->findChildren<ParticleLayoutForm*>())
        layoutForms->updateTitle(layerItemWidget->layerItem());
void SampleEditorController::removeLayerItem(LayerItem* layerItem)
{
    m_undoStack.push(new CommandRemoveLayer(this, layerItem));
}

void SampleEditorController::removeLayerItemFromUndo(int atIndex)
    auto* layer = m_sampleItem->layers()[atIndex];
    emit aboutToRemoveItem(layer);
    m_sampleForm->onAboutToRemoveLayer(layer);
    m_sampleItem->removeLayer(layer);
    m_sampleForm->updateRowVisibilities();
void SampleEditorController::removeLayoutItem(LayerForm* layerItemWidget,
                                              ParticleLayoutItem* layout)
    emit aboutToRemoveItem(layout);
    layerItemWidget->onAboutToRemoveLayout(layout);
    layerItemWidget->layerItem()->removeLayoutItem(layout);

    for (auto* layoutForms : layerItemWidget->findChildren<ParticleLayoutForm*>())
        layoutForms->updateTitle(layerItemWidget->layerItem());
void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layoutItem,
                                                   FormFactorItemCatalog::Type formFactorType)
    auto* newParticle = createAndInitParticle(formFactorType);
    layoutItem->addParticleItem(newParticle);

    emit modified();

    //  search for particle layout widget for notification
    ASSERT(m_sampleForm);
    for (auto* w : m_sampleForm->findChildren<ParticleLayoutForm*>())
        if (w->layoutItem() == layoutItem)
            w->onParticleAdded(newParticle);
    m_sampleForm->updateUnits();
void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layoutItem,
                                                   ItemWithParticlesCatalog::Type type)
    auto* newItem = createAndInitParticle(type);
    layoutItem->addParticleItem(newItem);
    //  search for particle layout widget for notification
    ASSERT(m_sampleForm);
    for (auto* w : m_sampleForm->findChildren<ParticleLayoutForm*>())
        if (w->layoutItem() == layoutItem)
            w->onParticleAdded(newItem);
    m_sampleForm->updateUnits();
void SampleEditorController::addCompoundItem(CompoundItem* compositionItem,
                                             ItemWithParticlesCatalog::Type type)
    auto* newItem = createAndInitParticle(type);
    compositionItem->addParticleItem(newItem);
    //  search for composition widget for notification
    ASSERT(m_sampleForm);
    for (auto* c : m_sampleForm->findChildren<CompoundForm*>())
        if (c->compositionItem() == compositionItem)
            c->onParticleAdded(newItem);
    m_sampleForm->updateUnits();
void SampleEditorController::addCompoundItem(CompoundItem* compositionItem,
                                             FormFactorItemCatalog::Type formFactorType)
    auto* newParticle = createAndInitParticle(formFactorType);
    compositionItem->addParticleItem(newParticle);

    emit modified();

    //  search for composition widget for notification
    ASSERT(m_sampleForm);
    for (auto* c : m_sampleForm->findChildren<CompoundForm*>())
        if (c->compositionItem() == compositionItem)
            c->onParticleAdded(newParticle);
    m_sampleForm->updateUnits();
ItemWithParticles*
SampleEditorController::createAndInitParticle(FormFactorItemCatalog::Type formFactorType) const
{
Matthias Puchner's avatar
Matthias Puchner committed
    auto* newParticle = new ParticleItem(materialItems());
    newParticle->setFormFactor(FormFactorItemCatalog::create(formFactorType));
Matthias Puchner's avatar
Matthias Puchner committed
    newParticle->setMaterial(materialItems()->defaultMaterial());
    return newParticle;
}

ItemWithParticles*
SampleEditorController::createAndInitParticle(ItemWithParticlesCatalog::Type itemType) const
{
Matthias Puchner's avatar
Matthias Puchner committed
    auto* newItem = ItemWithParticlesCatalog::create(itemType, materialItems());
    if (auto* p = dynamic_cast<ItemWithMaterial*>(newItem))
Matthias Puchner's avatar
Matthias Puchner committed
        p->setMaterial(materialItems()->defaultMaterial());
    if (auto* cs = dynamic_cast<CoreAndShellItem*>(newItem)) {
Matthias Puchner's avatar
Matthias Puchner committed
        cs->createCore(materialItems());
        cs->createShell(materialItems());
        cs->core()->setFormFactor(new CylinderItem());
        cs->shell()->setFormFactor(new CylinderItem());
    }

Wuttke, Joachim's avatar
Wuttke, Joachim committed
    if (auto* meso = dynamic_cast<MesocrystalItem*>(newItem); meso && meso->basisParticle())
        if (auto* p = dynamic_cast<ItemWithMaterial*>(meso->basisParticle()))
Matthias Puchner's avatar
Matthias Puchner committed
            p->setMaterial(materialItems()->defaultMaterial());
void SampleEditorController::setCoreFormFactor(CoreAndShellForm* widget,
                                               FormFactorItemCatalog::Type type)
{
    auto* particleCoreShell = widget->coreShellItem();

    if (particleCoreShell->core() == nullptr)
Matthias Puchner's avatar
Matthias Puchner committed
        particleCoreShell->createCore(materialItems());
    particleCoreShell->core()->setFormFactor(FormFactorItemCatalog::create(type));
    widget->createCoreWidgets();
    m_sampleForm->updateUnits();
    emit modified();
void SampleEditorController::setShellFormFactor(CoreAndShellForm* widget,
                                                FormFactorItemCatalog::Type type)
{
    auto* particleCoreShell = widget->coreShellItem();

    if (particleCoreShell->shell() == nullptr)
Matthias Puchner's avatar
Matthias Puchner committed
        particleCoreShell->createShell(materialItems());
    particleCoreShell->shell()->setFormFactor(FormFactorItemCatalog::create(type));
    widget->createShellWidgets();
    m_sampleForm->updateUnits();
    emit modified();
void SampleEditorController::removeParticle(ItemWithParticles* itemToRemove)
    ASSERT(m_sampleForm);
    for (auto* layoutForm : m_sampleForm->findChildren<ParticleLayoutForm*>())
        if (layoutForm->layoutItem()->particles().contains(itemToRemove)) {
            layoutForm->onAboutToRemoveParticle(itemToRemove);

            emit aboutToRemoveItem(itemToRemove);
            layoutForm->layoutItem()->removeParticleItem(itemToRemove);
    for (auto* c : m_sampleForm->findChildren<CompoundForm*>())
        if (c->compositionItem()->particles().contains(itemToRemove)) {
            c->onAboutToRemoveParticle(itemToRemove);

            emit aboutToRemoveItem(itemToRemove);
            c->compositionItem()->removeParticleItem(itemToRemove);
void SampleEditorController::setDouble(double newValue, DoubleProperty& d)
    m_undoStack.push(new CommandChangeValue(d.label(), this, d.value(), newValue, d.uid()));
    d.setValue(newValue);
    emit modified();
}

void SampleEditorController::setDoubleFromUndo(double newValue, const QString& path)
{
    ASSERT(m_sampleForm);
    for (auto* s : m_sampleForm->findChildren<DoubleSpinBox*>()) {
    m_sampleForm->ensureVisible(spinBox);
    QSignalBlocker b(spinBox);
    spinBox->setBaseValue(newValue);
    spinBox->setFocus();
    spinBox->selectAll();
void SampleEditorController::setCurrentIndex(AbstractSelectionContainerForm* widget, int index,
{
    d.setCurrentIndex(index);
    widget->createContent();
    m_sampleForm->updateUnits();
    emit modified();
QUndoStack* SampleEditorController::undoStack()
{
    return &m_undoStack;
}

MaterialModel* SampleEditorController::materialItems() const
    return &m_sampleItem->materialItems();
}

ProjectDocument* SampleEditorController::projectDocument() const
{
    return m_document;
void SampleEditorController::selectMaterial(ItemWithMaterial* item,
                                            const QString& newMaterialIdentifier)
{
    item->setMaterial(newMaterialIdentifier);

    //  update Layer title
    ASSERT(m_sampleForm);
    for (auto* c : m_sampleForm->findChildren<LayerForm*>())
        if (c->layerItem() == item)
            c->updateTitle();

    // #baLayerEditor notify all material users (update link info)
    emit modified();
void SampleEditorController::setMaterialValue(ItemWithMaterial* item, double newValue,

    // -- notify all other users of this material (update values in the UI)
    ASSERT(m_sampleForm);
    for (auto* c : m_sampleForm->findChildren<MaterialInplaceForm*>())
        if (c->itemWithMaterial() != item
            && c->itemWithMaterial()->materialIdentifier() == item->materialIdentifier())
            c->updateValues();
    emit modified();
void SampleEditorController::setDensityRelatedValue(InterferenceItem* interferenceItem,
                                                    double newValue, DoubleProperty& d)
    // -- notify the containing particle layout UI about changed value
    ASSERT(m_sampleForm);
    for (auto* c : m_sampleForm->findChildren<ParticleLayoutForm*>())
        if (c->layoutItem()->interference().currentItem() == interferenceItem) {
void SampleEditorController::onStartingToMoveLayer()
{
    ASSERT(m_sampleForm);
    m_sampleForm->showAddLayerButtons(false);
}

void SampleEditorController::onStoppedToMoveLayer(QWidget* widgetToMove,
                                                  QWidget* moveAboveThisWidget)
{
    ASSERT(m_sampleForm);
    m_sampleForm->showAddLayerButtons(true);
    auto* itemToMove = dynamic_cast<LayerForm*>(widgetToMove)->layerItem();

    const auto* moveAboveThisLayerForm = m_sampleForm->findNextLayerForm(moveAboveThisWidget);
    auto* moveAboveThisItem =
        moveAboveThisLayerForm != nullptr ? moveAboveThisLayerForm->layerItem() : nullptr;

    m_sampleItem->moveLayer(itemToMove, moveAboveThisItem);
    m_sampleForm->onLayerMoved(itemToMove);
void SampleEditorController::setSampleName(const QString& name)
{
    m_sampleItem->setSampleName(name);
    emit modified();
}

void SampleEditorController::setSampleDescription(const QString& description)
{
    m_sampleItem->setDescription(description);
Wuttke, Joachim's avatar
Wuttke, Joachim committed
void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget,
                                                 ItemWithParticlesCatalog::Type type)
    auto* meso = widget->mesocrystalItem();
    meso->setBasis(createAndInitParticle(type));
    widget->createBasisWidgets();
    m_sampleForm->updateUnits();
Wuttke, Joachim's avatar
Wuttke, Joachim committed
void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget,
                                                 FormFactorItemCatalog::Type type)
{
    auto* meso = widget->mesocrystalItem();
    meso->setBasis(createAndInitParticle(type));
    widget->createBasisWidgets();
    m_sampleForm->updateUnits();
    emit modified();
void SampleEditorController::selectInterference(InterferenceForm* widget, int newIndex)
    widget->layoutItem()->interference().setCurrentIndex(newIndex);
    widget->onInterferenceTypeChanged();
    m_sampleForm->updateUnits();

    // Disable/enable total density property in the particle layout, depending on type of
    // interference function.
    QWidget* parent = widget->parentWidget();
    while (parent != nullptr && dynamic_cast<ParticleLayoutForm*>(parent) == nullptr)
        parent = parent->parentWidget();

    if (auto* particleLayoutForm = dynamic_cast<ParticleLayoutForm*>(parent)) {
        particleLayoutForm->updateDensityEnabling();
        particleLayoutForm->updateDensityValue();

void SampleEditorController::setIntegrateOverXi(LatticeTypeSelectionForm* widget, bool newValue)
{
    widget->interferenceItem()->setXiIntegration(newValue);
    widget->onIntegrateOverXiChanged();
    emit modified();