// ************************************************************************************************ // // 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/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(LayerStackItem& parentStack, ItemWithLayers* before) { auto* layerBefore = dynamic_cast<LayerItem*>(before); const int i = (layerBefore != nullptr) ? Vec::indexOfPtr(layerBefore, m_sample_item->uniqueLayerItems()) : m_sample_item->uniqueLayerItems().size(); QColor color = findColor(i); // before adding layer LayerItem* layer = m_sample_item->createLayerItemAt(parentStack, i); layer->setMaterial(materialModel()->defaultMaterialItem()); layer->setColor(color); layer->expandGroupbox = true; // manually added layer => expanded onLayerAdded(layer); } void SampleEditorController::addLayerStackItem(LayerStackItem& parentStack, ItemWithLayers* before) { // TODO qInfo() << "SampleEditorController::addLayerStackItem"; } QColor SampleEditorController::findColor(size_t atIndex) { QColor result; auto unusedColors = GUI::Util::Layer::predefinedLayerColors(); for (auto* l : m_sample_item->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 : m_sample_item->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) ? m_sample_item->uniqueLayerItems()[atIndex - 1]->color() : QColor(); const QColor below = (atIndex < m_sample_item->uniqueLayerItems().size()) ? m_sample_item->uniqueLayerItems()[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_sample_form); m_sample_form->onLayerAdded(layer); emit gDoc->sampleChanged(); } void SampleEditorController::duplicateLayerItem(const LayerItem* layer) { int atIndex = Vec::indexOfPtr(layer, m_sample_item->uniqueLayerItems()) + 1; QColor color = findColor(atIndex); // before adding layer LayerItem* newLayer = m_sample_item->createLayerItemAt(m_sample_item->outerStackItem(), atIndex); GUI::Util::copyContents(layer, newLayer); newLayer->setColor(color); onLayerAdded(newLayer); } void SampleEditorController::removeItemWithLayers(ItemWithLayers* layer) { emit aboutToRemoveItem(layer); m_sample_form->onAboutToRemoveLayerContainer(layer); m_sample_item->removeComponent(layer); 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::onStoppedToMoveLayer(QWidget* widgetToMove, QWidget* moveAboveThisWidget) { auto* itemToMove = dynamic_cast<LayerForm*>(widgetToMove)->layerItem(); const auto* moveAboveThisLayerForm = m_sample_form->findNextLayerForm(moveAboveThisWidget); auto* moveAboveThisItem = moveAboveThisLayerForm != nullptr ? moveAboveThisLayerForm->layerItem() : nullptr; m_sample_item->moveComponent(itemToMove, moveAboveThisItem); m_sample_form->onLayerMoved(itemToMove); // #baLayerEditor: tab order! 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(); }