// ************************************************************************************************ // // 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 "GUI/Model/CatSample/FormFactorItemCatalog.h" #include "GUI/Model/Descriptor/UIntDescriptor.h" #include "GUI/Model/Project/ProjectDocument.h" #include "GUI/Model/Sample/InterferenceItems.h" #include "GUI/Model/Sample/LayerItem.h" #include "GUI/Model/Sample/MaterialItems.h" #include "GUI/Model/Sample/MesocrystalItem.h" #include "GUI/Model/Sample/MultiLayerItem.h" #include "GUI/Model/Sample/ParticleCompositionItem.h" #include "GUI/Model/Sample/ParticleCoreShellItem.h" #include "GUI/Model/Sample/ParticleItem.h" #include "GUI/Model/Sample/ParticleLayoutItem.h" #include "GUI/View/Common/DoubleSpinBox.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/MultiLayerForm.h" #include "GUI/View/SampleDesigner/ParticleCompositionForm.h" #include "GUI/View/SampleDesigner/ParticleCoreShellForm.h" #include "GUI/View/SampleDesigner/ParticleLayoutForm.h" #include "GUI/View/SampleDesigner/SampleEditorCommands.h" #include "Sample/HardParticle/Cylinder.h" SampleEditorController::SampleEditorController(ProjectDocument* document, MultiLayerItem* 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; } MultiLayerItem* SampleEditorController::sampleItem() const { return m_sampleItem; } void SampleEditorController::addLayer(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::addLayerFromUndo(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() : QColor(); for (const auto& col : sortedByUsage) if (col != above && col != below) { color = col; break; } } // - create new layer LayerItem* layer = m_sampleItem->addLayer(atIndex); layer->setMaterial(materialItems()->defaultMaterial()); layer->setColor(color); ASSERT(m_sampleForm); m_sampleForm->onLayerAdded(layer); m_sampleForm->updateUnits(); 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::addLayout(LayerForm* layerItemWidget) { auto* newLayoutItem = layerItemWidget->layerItem()->addLayout(); layerItemWidget->onLayoutAdded(newLayoutItem); m_sampleForm->updateUnits(); for (auto* layoutForms : layerItemWidget->findChildren<ParticleLayoutForm*>()) layoutForms->updateTitle(layerItemWidget->layerItem()); emit modified(); } void SampleEditorController::removeLayer(LayerItem* layerItem) { m_undoStack.push(new CommandRemoveLayer(this, layerItem)); } void SampleEditorController::removeLayerFromUndo(int atIndex) { auto* layer = m_sampleItem->layers()[atIndex]; emit aboutToRemoveItem(layer); m_sampleForm->onAboutToRemoveLayer(layer); m_sampleItem->removeLayer(layer); m_sampleForm->updateRowVisibilities(); emit modified(); } void SampleEditorController::removeLayout(LayerForm* layerItemWidget, ParticleLayoutItem* layout) { emit aboutToRemoveItem(layout); layerItemWidget->onAboutToRemoveLayout(layout); layerItemWidget->layerItem()->removeLayout(layout); for (auto* layoutForms : layerItemWidget->findChildren<ParticleLayoutForm*>()) layoutForms->updateTitle(layerItemWidget->layerItem()); emit modified(); } void SampleEditorController::addParticle(ParticleLayoutItem* layoutItem, FormFactorItemCatalog::Type formFactorType) { auto* newParticle = createAndInitParticle(formFactorType); layoutItem->addParticle(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::addParticle(ParticleLayoutItem* layoutItem, ItemWithParticlesCatalog::Type type) { auto* newItem = createAndInitParticle(type); layoutItem->addParticle(newItem); 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(newItem); m_sampleForm->updateUnits(); } void SampleEditorController::addParticle(ParticleCompositionItem* compositionItem, ItemWithParticlesCatalog::Type type) { auto* newItem = createAndInitParticle(type); compositionItem->addParticle(newItem); emit modified(); // search for composition widget for notification ASSERT(m_sampleForm); for (auto* c : m_sampleForm->findChildren<ParticleCompositionForm*>()) if (c->compositionItem() == compositionItem) c->onParticleAdded(newItem); m_sampleForm->updateUnits(); } void SampleEditorController::addParticle(ParticleCompositionItem* compositionItem, FormFactorItemCatalog::Type formFactorType) { auto* newParticle = createAndInitParticle(formFactorType); compositionItem->addParticle(newParticle); emit modified(); // search for composition widget for notification ASSERT(m_sampleForm); for (auto* c : m_sampleForm->findChildren<ParticleCompositionForm*>()) if (c->compositionItem() == compositionItem) c->onParticleAdded(newParticle); m_sampleForm->updateUnits(); } ItemWithParticles* SampleEditorController::createAndInitParticle(FormFactorItemCatalog::Type formFactorType) const { auto* newParticle = new ParticleItem(materialItems()); newParticle->setFormFactor(FormFactorItemCatalog::create(formFactorType)); newParticle->setMaterial(materialItems()->defaultMaterial()); return newParticle; } ItemWithParticles* SampleEditorController::createAndInitParticle(ItemWithParticlesCatalog::Type itemType) const { auto* newItem = ItemWithParticlesCatalog::create(itemType, materialItems()); if (auto* p = dynamic_cast<ItemWithMaterial*>(newItem)) p->setMaterial(materialItems()->defaultMaterial()); if (auto* cs = dynamic_cast<ParticleCoreShellItem*>(newItem)) { cs->createCore(materialItems()); cs->createShell(materialItems()); cs->core()->setFormFactor(new CylinderItem()); cs->shell()->setFormFactor(new CylinderItem()); } if (auto* meso = dynamic_cast<MesocrystalItem*>(newItem); meso && meso->basisParticle()) if (auto* p = dynamic_cast<ItemWithMaterial*>(meso->basisParticle())) p->setMaterial(materialItems()->defaultMaterial()); return newItem; } void SampleEditorController::setCoreFormFactor(ParticleCoreShellForm* widget, FormFactorItemCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->core() == nullptr) particleCoreShell->createCore(materialItems()); particleCoreShell->core()->setFormFactor(FormFactorItemCatalog::create(type)); widget->createCoreWidgets(); m_sampleForm->updateUnits(); emit modified(); } void SampleEditorController::setShellFormFactor(ParticleCoreShellForm* widget, FormFactorItemCatalog::Type type) { auto* particleCoreShell = widget->coreShellItem(); if (particleCoreShell->shell() == nullptr) 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()->removeParticle(itemToRemove); emit modified(); return; } for (auto* c : m_sampleForm->findChildren<ParticleCompositionForm*>()) if (c->compositionItem()->particles().contains(itemToRemove)) { c->onAboutToRemoveParticle(itemToRemove); emit aboutToRemoveItem(itemToRemove); c->compositionItem()->removeParticle(itemToRemove); emit modified(); return; } } void SampleEditorController::setDouble(double newValue, DoubleDescriptor d) { m_undoStack.push(new CommandChangeValue(d.label, this, d.get(), newValue, d.path())); d.set(newValue); emit modified(); } void SampleEditorController::setDoubleFromUndo(double newValue, const QString& path) { ASSERT(m_sampleForm); DoubleSpinBox* spinBox = nullptr; for (auto* s : m_sampleForm->findChildren<DoubleSpinBox*>()) { if (s->valueDescriptor().path() == path) { spinBox = s; break; } } if (!spinBox) return; spinBox->valueDescriptor().set(newValue); m_sampleForm->ensureVisible(spinBox); QSignalBlocker b(spinBox); spinBox->setBaseValue(newValue); spinBox->setFocus(); spinBox->selectAll(); emit modified(); } void SampleEditorController::setInt(int newValue, UIntDescriptor d) { d.set(newValue); emit modified(); } void SampleEditorController::setCurrentIndex(AbstractSelectionContainerForm* widget, int index, const AbstractSelectionDescriptor& d) { d.setCurrentIndex(index); widget->createContent(); m_sampleForm->updateUnits(); emit modified(); } QUndoStack* SampleEditorController::undoStack() { return &m_undoStack; } MaterialItems* 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, DoubleDescriptor d) { setDouble(newValue, 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 newValue, DoubleDescriptor d) { setDouble(newValue, d); // -- notify the containing particle layout UI about changed value ASSERT(m_sampleForm); for (auto* c : m_sampleForm->findChildren<ParticleLayoutForm*>()) if (c->layoutItem()->interference() == interferenceItem) { c->updateDensityValue(); break; } } void SampleEditorController::onStartingToMoveLayer() { ASSERT(m_sampleForm); m_sampleForm->showAddLayerButtons(false); } void SampleEditorController::onStoppedToMoveLayer(QWidget* widgetToMove, QWidget* moveAboveThisWidget) { ASSERT(m_sampleForm); m_sampleForm->showAddLayerButtons(true); const auto* moveAboveThisLayerForm = m_sampleForm->findNextLayerForm(moveAboveThisWidget); auto* itemToMove = dynamic_cast<LayerForm*>(widgetToMove)->layerItem(); auto* moveBeforeThisItem = moveAboveThisLayerForm != nullptr ? moveAboveThisLayerForm->layerItem() : nullptr; m_sampleItem->moveLayer(itemToMove, moveBeforeThisItem); 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->setBasis(createAndInitParticle(type)); widget->createBasisWidgets(); m_sampleForm->updateUnits(); emit modified(); } 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(); } emit modified(); } void SampleEditorController::setIntegrateOverXi(LatticeTypeSelectionForm* widget, bool newValue) { widget->interferenceItem()->setXiIntegration(newValue); widget->onIntegrateOverXiChanged(); emit modified(); }