// ************************************************************************************************ // // 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" #include "Base/Util/Assert.h" #include "GUI/Model/Project/ProjectDocument.h" #include "GUI/Model/Sample/CompoundItem.h" #include "GUI/Model/Sample/CoreAndShellItem.h" #include "GUI/Model/Sample/FormFactorItemCatalog.h" #include "GUI/Model/Sample/InterferenceItems.h" #include "GUI/Model/Sample/LayerItem.h" #include "GUI/Model/Sample/MaterialModel.h" #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/Support/XML/Backup.h" #include "GUI/View/Numeric/DoubleSpinBox.h" #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" #include "GUI/View/SampleDesigner/MesocrystalForm.h" #include "GUI/View/SampleDesigner/ParticleLayoutForm.h" #include "GUI/View/SampleDesigner/SampleForm.h" SampleEditorController::SampleEditorController(ProjectDocument* document, SampleItem* multi) : m_sampleItem(multi) , m_sampleForm(nullptr) , m_document(document) { } void SampleEditorController::setSampleForm(SampleForm* view) { m_sampleForm = view; } SampleForm* SampleEditorController::sampleForm() const { return m_sampleForm; } SampleItem* SampleEditorController::sampleItem() const { return m_sampleItem; } void SampleEditorController::addLayerItem(LayerItem* before) { const int i = (before != nullptr) ? m_sampleItem->layerItems().indexOf(before) : m_sampleItem->layerItems().size(); QColor color = findColor(i); // before adding layer LayerItem* layer = m_sampleItem->createLayerItemAt(i); layer->setMaterial(materialModel()->defaultMaterialItem()); layer->setColor(color); layer->expandLayer = true; // manually added layer => expanded onLayerAdded(layer); } QColor SampleEditorController::findColor(int atIndex) { QColor result; auto unusedColors = LayerEditorUtil::predefinedLayerColors(); for (auto* l : m_sampleItem->layerItems()) unusedColors.removeAll(l->color()); if (!unusedColors.isEmpty()) result = 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->layerItems()) usage[l->color().name()] += 1; auto sortedByUsage = LayerEditorUtil::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->layerItems()[atIndex - 1]->color() : QColor(); const QColor below = (atIndex < m_sampleItem->layerItems().size()) ? m_sampleItem->layerItems()[atIndex]->color() : QColor(); for (const auto& col : sortedByUsage) if (col != above && col != below) { result = col; break; } } return result; } void SampleEditorController::onLayerAdded(LayerItem* layer) { ASSERT(m_sampleForm); m_sampleForm->onLayerAdded(layer); emit modified(); // expand the new layer's form for better workflow for (auto* c : m_sampleForm->findChildren<LayerForm*>()) if (c->layerItem() == layer) c->expand(); } void SampleEditorController::duplicateLayerItem(const LayerItem* layer) { int atIndex = m_sampleItem->layerItems().indexOf(layer) + 1; QColor color = findColor(atIndex); // before adding layer LayerItem* newLayer = m_sampleItem->createLayerItemAt(atIndex); GUI::Util::copyContents(layer, newLayer); newLayer->setColor(color); onLayerAdded(newLayer); } void SampleEditorController::removeLayerItem(LayerItem* layer) { emit aboutToRemoveItem(layer); m_sampleForm->onAboutToRemoveLayer(layer); m_sampleItem->removeLayer(layer); emit modified(); } void SampleEditorController::onLayoutAdded(LayerForm* layerForm, ParticleLayoutItem* layout) { layerForm->onLayoutAdded(layout); for (auto* layoutForms : layerForm->findChildren<ParticleLayoutForm*>()) layoutForms->updateTitle(layerForm->layerItem()); emit modified(); } void SampleEditorController::addLayoutItem(LayerForm* layerForm) { auto* newLayout = layerForm->layerItem()->addLayoutItem(); onLayoutAdded(layerForm, newLayout); } void SampleEditorController::duplicateLayoutItem(LayerForm* layerForm, ParticleLayoutItem* layout) { auto* newLayout = layerForm->layerItem()->addLayoutItem(); GUI::Util::copyContents(layout, newLayout); onLayoutAdded(layerForm, newLayout); } void SampleEditorController::removeLayoutItem(LayerForm* layerForm, ParticleLayoutItem* layout) { emit aboutToRemoveItem(layout); layerForm->onAboutToRemoveLayout(layout); layerForm->layerItem()->removeLayoutItem(layout); for (auto* layoutForm : layerForm->findChildren<ParticleLayoutForm*>()) layoutForm->updateTitle(layerForm->layerItem()); emit modified(); } void SampleEditorController::onParticleLayoutAdded(ParticleLayoutItem* layout, ItemWithParticles* newItem) { emit modified(); // search for particle layout widget for notification ASSERT(m_sampleForm); for (auto* w : m_sampleForm->findChildren<ParticleLayoutForm*>()) if (w->layoutItem() == layout) w->onParticleAdded(newItem); } void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layoutItem, FormFactorItemCatalog::Type formFactorType) { auto* newParticle = createAndInitItem(formFactorType); layoutItem->addItemWithParticleSelection(newParticle); onParticleLayoutAdded(layoutItem, newParticle); } void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layoutItem, ItemWithParticlesCatalog::Type type) { auto* newItem = createAndInitItem(type); layoutItem->addItemWithParticleSelection(newItem); onParticleLayoutAdded(layoutItem, newItem); } void SampleEditorController::duplicateItemWithParticles(ItemWithParticles* item) { auto type = ItemWithParticlesCatalog::type(item); auto* newItem = createAndInitItem(type); GUI::Util::copyContents(item, newItem); if (ParticleLayoutItem* parent_layout = parentLayoutItem(item)) { parent_layout->addItemWithParticleSelection(newItem); onParticleLayoutAdded(parent_layout, newItem); } else if (CompoundItem* parent_compound = parentCompoundItem(item)) { parent_compound->addItemWithParticleSelection(newItem); onParticleCompoundAdded(parent_compound, newItem); } else ASSERT_NEVER; } void SampleEditorController::onParticleCompoundAdded(CompoundItem* composition, ItemWithParticles* newItem) { emit modified(); // search for composition widget for notification ASSERT(m_sampleForm); for (auto* c : m_sampleForm->findChildren<CompoundForm*>()) if (c->compositionItem() == composition) c->onParticleAdded(newItem); } void SampleEditorController::addCompoundItem(CompoundItem* compositionItem, ItemWithParticlesCatalog::Type type) { auto* newItem = createAndInitItem(type); compositionItem->addItemWithParticleSelection(newItem); onParticleCompoundAdded(compositionItem, newItem); } void SampleEditorController::addCompoundItem(CompoundItem* compositionItem, FormFactorItemCatalog::Type formFactorType) { auto* newParticle = createAndInitItem(formFactorType); compositionItem->addItemWithParticleSelection(newParticle); onParticleCompoundAdded(compositionItem, newParticle); } ItemWithParticles* SampleEditorController::createAndInitItem(FormFactorItemCatalog::Type formFactorType) const { auto* newParticle = new ParticleItem(materialModel()); newParticle->setFormFactor(FormFactorItemCatalog::create(formFactorType)); newParticle->setMaterial(materialModel()->defaultParticleMaterialItem()); return newParticle; } ItemWithParticles* SampleEditorController::createAndInitItem(ItemWithParticlesCatalog::Type itemType) const { auto* newItem = ItemWithParticlesCatalog::create(itemType, materialModel()); if (auto* p = dynamic_cast<ItemWithMaterial*>(newItem)) p->setMaterial(materialModel()->defaultMaterialItem()); if (auto* cs = dynamic_cast<CoreAndShellItem*>(newItem)) { cs->createCoreItem(materialModel()); cs->createShellItem(materialModel()); cs->coreItem()->setFormFactor(new CylinderItem()); cs->shellItem()->setFormFactor(new CylinderItem()); } if (auto* meso = dynamic_cast<MesocrystalItem*>(newItem); meso && meso->basisItem()) if (auto* p = dynamic_cast<ItemWithMaterial*>(meso->basisItem())) p->setMaterial(materialModel()->defaultMaterialItem()); return newItem; } void SampleEditorController::setCoreFormFactor(CoreAndShellForm* widget, FormFactorItemCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->coreItem() == nullptr) particleCoreShell->createCoreItem(materialModel()); particleCoreShell->coreItem()->setFormFactor(FormFactorItemCatalog::create(type)); widget->createCoreWidgets(); emit modified(); } void SampleEditorController::setShellFormFactor(CoreAndShellForm* widget, FormFactorItemCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->shellItem() == nullptr) particleCoreShell->createShellItem(materialModel()); particleCoreShell->shellItem()->setFormFactor(FormFactorItemCatalog::create(type)); widget->createShellWidgets(); emit modified(); } ParticleLayoutItem* SampleEditorController::parentLayoutItem(ItemWithParticles* item) { for (auto* layoutForm : m_sampleForm->findChildren<ParticleLayoutForm*>()) if (layoutForm->layoutItem()->itemsWithParticles().contains(item)) return layoutForm->layoutItem(); return nullptr; } CompoundItem* SampleEditorController::parentCompoundItem(ItemWithParticles* item) { for (auto* compoundForm : m_sampleForm->findChildren<CompoundForm*>()) if (compoundForm->compositionItem()->itemsWithParticles().contains(item)) return compoundForm->compositionItem(); return nullptr; } void SampleEditorController::removeParticle(ItemWithParticles* itemToRemove) { ASSERT(m_sampleForm); for (auto* layoutForm : m_sampleForm->findChildren<ParticleLayoutForm*>()) if (layoutForm->layoutItem()->itemsWithParticles().contains(itemToRemove)) { layoutForm->onAboutToRemoveParticle(itemToRemove); emit aboutToRemoveItem(itemToRemove); layoutForm->layoutItem()->removeItemWithParticle(itemToRemove); emit modified(); return; } for (auto* c : m_sampleForm->findChildren<CompoundForm*>()) if (c->compositionItem()->itemsWithParticles().contains(itemToRemove)) { c->onAboutToRemoveParticle(itemToRemove); emit aboutToRemoveItem(itemToRemove); c->compositionItem()->removeItemWithParticle(itemToRemove); emit modified(); return; } } void SampleEditorController::setDouble(double value, DoubleProperty& d) { d.setValue(value); emit modified(); } void SampleEditorController::setCurrentIndex(ISelectionContainerForm* widget, int index, ISelectionProperty& d) { d.setCurrentIndex(index); widget->createContent(); emit modified(); } MaterialModel* SampleEditorController::materialModel() const { return &m_sampleItem->materialModel(); } 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 value, DoubleProperty& d) { setDouble(value, d); // -- 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 value, DoubleProperty& d) { setDouble(value, d); // -- notify the containing particle layout UI about changed value ASSERT(m_sampleForm); for (auto* c : m_sampleForm->findChildren<ParticleLayoutForm*>()) if (c->layoutItem()->interferenceSelection().currentItem() == interferenceItem) { c->updateDensityValue(); break; } } void SampleEditorController::onStoppedToMoveLayer(QWidget* widgetToMove, QWidget* moveAboveThisWidget) { 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); // #baLayerEditor: tab order! emit modified(); } void SampleEditorController::setSampleName(const QString& name) { m_sampleItem->setSampleName(name); emit modified(); } void SampleEditorController::setSampleDescription(const QString& description) { m_sampleItem->setDescription(description); emit modified(); } void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget, ItemWithParticlesCatalog::Type type) { auto* meso = widget->mesocrystalItem(); meso->setBasisItem(createAndInitItem(type)); widget->createBasisWidgets(); emit modified(); } void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget, FormFactorItemCatalog::Type type) { auto* meso = widget->mesocrystalItem(); meso->setBasisItem(createAndInitItem(type)); widget->createBasisWidgets(); emit modified(); } void SampleEditorController::selectInterference(InterferenceForm* widget, int newIndex) { widget->layoutItem()->interferenceSelection().setCurrentIndex(newIndex); widget->onInterferenceTypeChanged(); // 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(); } emit modified(); } void SampleEditorController::setIntegrateOverXi(LatticeTypeSelectionForm* widget, bool value) { widget->interferenceItem()->setXiIntegration(value); widget->onIntegrateOverXiChanged(); emit modified(); }