// ************************************************************************************************ // // BornAgain: simulate and fit reflection and scattering // //! @file GUI/View/Sample/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/Sample/SampleEditorController.h" #include "Base/Util/Assert.h" #include "Base/Util/Vec.h" #include "GUI/Model/Material/MaterialsSet.h" #include "GUI/Model/Project/ProjectDocument.h" #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/LayerStackItem.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/Model/Util/Backup.h" #include "GUI/View/Sample/CompoundForm.h" #include "GUI/View/Sample/CoreAndShellForm.h" #include "GUI/View/Sample/InterferenceForm.h" #include "GUI/View/Sample/LatticeTypeSelectionForm.h" #include "GUI/View/Sample/LayerForm.h" #include "GUI/View/Sample/LayerStackForm.h" #include "GUI/View/Sample/MaterialInplaceForm.h" #include "GUI/View/Sample/MesocrystalForm.h" #include "GUI/View/Sample/ParticleLayoutForm.h" #include "GUI/View/Sample/SampleForm.h" #include <QLabel> SampleEditorController::SampleEditorController(SampleItem* multi) : m_sample_item(multi) , m_sample_form(nullptr) { } void SampleEditorController::setSampleForm(SampleForm* view) { m_sample_form = view; } void SampleEditorController::addLayerItem(LayerStackForm& parentStackForm, const ItemWithLayers* before) { LayerStackItem& parentStack = parentStackForm.stackItem(); const int at_index = parentStack.indexOfComponent(before) + 1; QColor color = findColor(at_index); // before adding layer LayerItem* new_layer = m_sample_item->createLayerItemAt(parentStack, at_index); new_layer->setMaterial(materialModel()->defaultMaterialItem()); new_layer->setColor(color); onComponentAdded(new_layer); } void SampleEditorController::addLayerStackItem(LayerStackForm& parentStackForm, const ItemWithLayers* before) { LayerStackItem& parentStack = parentStackForm.stackItem(); const int at_index = parentStack.indexOfComponent(before) + 1; LayerStackItem* new_stack = m_sample_item->createLayerStackItemAt(parentStack, at_index); onComponentAdded(new_stack); } void SampleEditorController::duplicateItemWithLayers(const ItemWithLayers* component) { LayerStackItem* parentStack = m_sample_item->parentOfComponent(component); // case of the outer stack if (!parentStack) return; LayerStackForm* parentStackForm = m_sample_form->formOfStackItem(parentStack); ASSERT(parentStackForm); const int at_index = parentStack->indexOfComponent(component) + 1; if (const auto* layer = dynamic_cast<const LayerItem*>(component)) { QColor color = findColor(at_index); // before adding layer LayerItem* new_layer = m_sample_item->createLayerItemAt(*parentStack, at_index); new_layer->setMaterial(materialModel()->defaultMaterialItem()); GUI::Util::copyContents(layer, new_layer); new_layer->setColor(color); onComponentAdded(new_layer); } else if (const auto* stack = dynamic_cast<const LayerStackItem*>(component)) { LayerStackItem* new_stack = m_sample_item->createLayerStackItemAt(*parentStack, at_index); GUI::Util::copyContents(stack, new_stack); onComponentAdded(new_stack); } else ASSERT_NEVER; } void SampleEditorController::onComponentAdded(ItemWithLayers* component) { LayerStackItem* parentStack = m_sample_item->parentOfComponent(component); ASSERT(parentStack); LayerStackForm* parentStackForm = m_sample_form->formOfStackItem(parentStack); ASSERT(parentStackForm); parentStackForm->addComponentForm(component); m_sample_form->updateRowVisibilities(); emit gDoc->sampleChanged(); } QColor SampleEditorController::findColor(size_t atIndex) { std::vector<LayerItem*> uniqueLayerItems = m_sample_item->uniqueLayerItems(); QColor result; auto unusedColors = GUI::Util::Layer::predefinedLayerColors(); for (auto* l : uniqueLayerItems) 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 : uniqueLayerItems) usage[l->color().name()] += 1; auto sortedByUsage = GUI::Util::Layer::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) ? uniqueLayerItems[atIndex - 1]->color() : QColor(); const QColor below = (atIndex < uniqueLayerItems.size()) ? uniqueLayerItems[atIndex]->color() : QColor(); for (const auto& col : sortedByUsage) if (col != above && col != below) { result = col; break; } } return result; } void SampleEditorController::removeItemWithLayers(ItemWithLayers* component) { LayerStackItem* parentStack = m_sample_item->parentOfComponent(component); // case of the outer stack if (!parentStack) return; LayerStackForm* parentStackForm = m_sample_form->formOfStackItem(parentStack); ASSERT(parentStackForm); emit aboutToRemoveItem(component); parentStackForm->removeComponentForm(component); m_sample_item->removeComponent(component); m_sample_form->updateRowVisibilities(); emit gDoc->sampleChanged(); } void SampleEditorController::onLayoutAdded(LayerForm* layerForm, ParticleLayoutItem* layout) { layerForm->onLayoutAdded(layout); for (auto* layoutForms : layerForm->findChildren<ParticleLayoutForm*>()) layoutForms->updateTitle(layerForm->layerItem()); emit gDoc->sampleChanged(); } 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 gDoc->sampleChanged(); } void SampleEditorController::onParticleLayoutAdded(ParticleLayoutItem* layout, ItemWithParticles* newItem) { emit gDoc->sampleChanged(); // search for particle layout widget for notification ASSERT(m_sample_form); for (auto* w : m_sample_form->findChildren<ParticleLayoutForm*>()) if (w->layoutItem() == layout) w->onParticleAdded(newItem); } void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layout, FormfactorCatalog::Type formFactorType) { auto* newParticle = createAndInitItem(formFactorType); layout->addItemWithParticleSelection(newParticle); onParticleLayoutAdded(layout, newParticle); } void SampleEditorController::addParticleLayoutItem(ParticleLayoutItem* layout, ParticleCatalog::Type type) { auto* newItem = createAndInitItem(type); layout->addItemWithParticleSelection(newItem); onParticleLayoutAdded(layout, newItem); } void SampleEditorController::duplicateItemWithParticles(ItemWithParticles* item) { auto type = ParticleCatalog::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 gDoc->sampleChanged(); // search for composition widget for notification ASSERT(m_sample_form); for (auto* c : m_sample_form->findChildren<CompoundForm*>()) if (c->compositionItem() == composition) c->onParticleAdded(newItem); } void SampleEditorController::addCompoundItem(CompoundItem* compositionItem, ParticleCatalog::Type type) { auto* newItem = createAndInitItem(type); compositionItem->addItemWithParticleSelection(newItem); onParticleCompoundAdded(compositionItem, newItem); } void SampleEditorController::addCompoundItem(CompoundItem* compositionItem, FormfactorCatalog::Type formFactorType) { auto* newParticle = createAndInitItem(formFactorType); compositionItem->addItemWithParticleSelection(newParticle); onParticleCompoundAdded(compositionItem, newParticle); } ItemWithParticles* SampleEditorController::createAndInitItem(FormfactorCatalog::Type formFactorType) const { auto* newParticle = new ParticleItem(materialModel()); newParticle->setFormfactor(FormfactorCatalog::create(formFactorType)); newParticle->setMaterial(materialModel()->defaultParticleMaterialItem()); return newParticle; } ItemWithParticles* SampleEditorController::createAndInitItem(ParticleCatalog::Type type) const { auto* newItem = ParticleCatalog::create(type, 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, FormfactorCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->coreItem() == nullptr) particleCoreShell->createCoreItem(materialModel()); particleCoreShell->coreItem()->setFormfactor(FormfactorCatalog::create(type)); widget->createCoreWidgets(); emit gDoc->sampleChanged(); } void SampleEditorController::setShellFormfactor(CoreAndShellForm* widget, FormfactorCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->shellItem() == nullptr) particleCoreShell->createShellItem(materialModel()); particleCoreShell->shellItem()->setFormfactor(FormfactorCatalog::create(type)); widget->createShellWidgets(); emit gDoc->sampleChanged(); } ParticleLayoutItem* SampleEditorController::parentLayoutItem(ItemWithParticles* item) { for (auto* layoutForm : m_sample_form->findChildren<ParticleLayoutForm*>()) if (Vec::containsPtr(item, layoutForm->layoutItem()->itemsWithParticles())) return layoutForm->layoutItem(); return nullptr; } CompoundItem* SampleEditorController::parentCompoundItem(ItemWithParticles* item) { for (auto* compoundForm : m_sample_form->findChildren<CompoundForm*>()) if (Vec::containsPtr(item, compoundForm->compositionItem()->itemsWithParticles())) return compoundForm->compositionItem(); return nullptr; } void SampleEditorController::removeParticle(ItemWithParticles* itemToRemove) { ASSERT(m_sample_form); for (auto* layoutForm : m_sample_form->findChildren<ParticleLayoutForm*>()) if (Vec::containsPtr(itemToRemove, layoutForm->layoutItem()->itemsWithParticles())) { layoutForm->onAboutToRemoveParticle(itemToRemove); emit aboutToRemoveItem(itemToRemove); layoutForm->layoutItem()->removeItemWithParticle(itemToRemove); emit gDoc->sampleChanged(); return; } for (auto* c : m_sample_form->findChildren<CompoundForm*>()) if (Vec::containsPtr(itemToRemove, c->compositionItem()->itemsWithParticles())) { c->onAboutToRemoveParticle(itemToRemove); emit aboutToRemoveItem(itemToRemove); c->compositionItem()->removeItemWithParticle(itemToRemove); emit gDoc->sampleChanged(); return; } } MaterialsSet* SampleEditorController::materialModel() const { return &m_sample_item->materialModel(); } void SampleEditorController::selectMaterial(ItemWithMaterial* item, const QString& newMaterialIdentifier) { item->setMaterial(newMaterialIdentifier); // update Layer title ASSERT(m_sample_form); for (auto* c : m_sample_form->findChildren<LayerForm*>()) if (c->layerItem() == item) c->updateTitle(); // #baLayerEditor notify all material users (update link info) emit gDoc->sampleChanged(); } void SampleEditorController::setMaterialValue(ItemWithMaterial* item, double value, DoubleProperty& d) { d.setDVal(value); emit gDoc->sampleChanged(); // -- notify all other users of this material (update values in the UI) ASSERT(m_sample_form); for (auto* c : m_sample_form->findChildren<MaterialInplaceForm*>()) if (c->itemWithMaterial() != item && c->itemWithMaterial()->materialIdentifier() == item->materialIdentifier()) c->updateValues(); emit gDoc->sampleChanged(); } //! notify the containing particle layout UI about changed value void SampleEditorController::setDensityRelatedValue(InterferenceItem* interferenceItem) { ASSERT(m_sample_form); for (auto* c : m_sample_form->findChildren<ParticleLayoutForm*>()) if (c->layoutItem()->interferenceSelection().certainItem() == interferenceItem) { c->updateDensityValue(); break; } } void SampleEditorController::onStoppedToMoveComponent(QWidget* widgetToMove, QWidget* moveAboveThisWidget) { auto* formToMove = dynamic_cast<LayerContainerForm*>(widgetToMove); ASSERT(formToMove); auto* itemToMove = formToMove->item(); LayerStackItem* parentStack = m_sample_item->parentOfComponent(itemToMove); ASSERT(parentStack); LayerStackForm* parentStackForm = m_sample_form->formOfStackItem(parentStack); ASSERT(parentStackForm); const LayerContainerForm* moveAboveThisLayerForm = parentStackForm->findNextComponentForm(moveAboveThisWidget); auto* moveAboveThisItem = moveAboveThisLayerForm ? moveAboveThisLayerForm->item() : nullptr; m_sample_item->moveComponent(itemToMove, moveAboveThisItem); parentStackForm->onComponentMoved(itemToMove); m_sample_form->updateRowVisibilities(); emit gDoc->sampleChanged(); } void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget, ParticleCatalog::Type type) { auto* meso = widget->mesocrystalItem(); meso->setBasisItem(createAndInitItem(type)); widget->createBasisWidgets(); emit gDoc->sampleChanged(); } void SampleEditorController::setMesocrystalBasis(MesocrystalForm* widget, FormfactorCatalog::Type type) { auto* meso = widget->mesocrystalItem(); meso->setBasisItem(createAndInitItem(type)); widget->createBasisWidgets(); emit gDoc->sampleChanged(); } void SampleEditorController::selectInterference(InterferenceForm* widget, int newIndex) { widget->layoutItem()->interferenceSelection().setCertainIndex(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 gDoc->sampleChanged(); }