diff --git a/gui/qt/mainwidgets/molwidget.cpp b/gui/qt/mainwidgets/molwidget.cpp index c405639c..6b242879 100644 --- a/gui/qt/mainwidgets/molwidget.cpp +++ b/gui/qt/mainwidgets/molwidget.cpp @@ -1,8 +1,5 @@ #include "molwidget.h" #include "ui_molwidget.h" -#include "../mainwindow.h" -#include "molwidget_aux/bonddelegate.h" -#include "molwidget_aux/doubledelegate.h" #include "molwidget_aux/newelement.h" #include "vipsterapplication.h" #include @@ -11,12 +8,23 @@ using namespace Vipster; + +constexpr const char* inactiveKpoints[] = {"Gamma", "Monkhorst-Pack grid", "Discrete"}; +constexpr const char* activeKpoints[] = {"Gamma (active)", + "Monkhorst-Pack grid (active)", + "Discrete (active)"}; + MolWidget::MolWidget(QWidget *parent) : BaseWidget(parent), ui(new Ui::MolWidget) { ui->setupUi(this); + // Connect ui elements + connect(ui->typeButton, &QPushButton::toggled, ui->typeContainer, &QWidget::setVisible); + connect(ui->kpointButton, &QPushButton::toggled, ui->kpointContainer, &QWidget::setVisible); + + // TODO: hide all by default? ui->typeContainer->setVisible(false); // setup k-points @@ -24,49 +32,8 @@ MolWidget::MolWidget(QWidget *parent) : ui->discretetable->addAction(ui->actionDelete_K_Point); ui->kpointContainer->setVisible(ui->kpointButton->isChecked()); - // setup cell-table - ui->cellVecTable->setModel(&cellModel); - ui->cellVecTable->setItemDelegate(new DoubleDelegate{}); - - // setup atom table - ui->atomTable->setModel(&atomModel); - connect(ui->atomTable->selectionModel(), &QItemSelectionModel::selectionChanged, - this, &MolWidget::atomSelectionChanged); - headerActions.push_back(new QAction{"Show type", ui->atomTable}); - headerActions.push_back(new QAction{"Show coordinates", ui->atomTable}); - headerActions.push_back(new QAction{"Show charges", ui->atomTable}); - headerActions.push_back(new QAction{"Show forces", ui->atomTable}); - headerActions.push_back(new QAction{"Show visibility", ui->atomTable}); - headerActions.push_back(new QAction{"Show constraints", ui->atomTable}); - for(auto& action: headerActions){ - action->setCheckable(true); - } - headerActions[0]->setChecked(true); - headerActions[1]->setChecked(true); - auto changeColumns = [&](){ - // triggered through changed columns - int i=0; - for(int j=0; jisChecked() << j; - } - atomModel.setColumns(i); - }; - for(auto& action: headerActions){ - connect(action, &QAction::toggled, this, changeColumns); - } - ui->atomTable->horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); - ui->atomTable->horizontalHeader()->addActions(headerActions); - ui->atomTable->setItemDelegate(new DoubleDelegate{}); - - // setup bond table - ui->bondTable->setModel(&bondModel); - ui->bondTable->setItemDelegateForColumn(3, new BondDelegate{}); - ui->bondContainer->setVisible(ui->bondButton->isChecked()); - // Connect to app state changes - connect(&vApp, &Application::activeStepChanged, this, &MolWidget::setActiveStep); connect(&vApp, &Application::stepChanged, this, &MolWidget::updateStep); - connect(&vApp, &Application::selChanged, this, &MolWidget::updateSelection); connect(&vApp, &Application::activeMolChanged, this, &MolWidget::setActiveMol); } @@ -76,76 +43,23 @@ MolWidget::~MolWidget() delete ui; } -void MolWidget::setActiveStep(Step &step, Step::selection &sel){ - // reset old fmt-string - if(ownStep){ - auto oldFmt = static_cast(ownStep->getFmt())+2; - ui->atomFmtBox->setItemText(oldFmt, inactiveFmt[oldFmt]); - } - - // assign StepFormatter to curStep, mark fmt as active - auto fmt = step.getFmt(); - curStep = &step; - ownStep = std::make_unique(step.asFmt(fmt)); - updateStep(step); - - - auto ifmt = static_cast(fmt)+2; - QSignalBlocker blockAtFmt(ui->atomFmtBox); - ui->atomFmtBox->setCurrentIndex(ifmt); - ui->atomFmtBox->setItemText(ifmt, activeFmt[ifmt]); - // expose BondMode - QSignalBlocker blockBondMode(ui->bondModeBox); - bool autobonds = vApp.stepdata[curStep].automatic_bonds; - ui->bondModeBox->setCurrentIndex(autobonds ? 1 : 0); - if(autobonds){ - ui->bondSetButton->setDisabled(true); - }else{ - ui->bondButton->setChecked(true); - ui->bondSetButton->setEnabled(true); - } - - curSel = &sel; - updateSelection(sel); -} - void MolWidget::updateStep(Step &step) { - // only update active step - if (&step != curStep) return; - - atomModel.setStep(ownStep.get()); -} - -void MolWidget::updateSelection(Step::selection &sel) -{ - // only update selection of active step - if (&sel != curSel) return; - - auto& table = ui->atomTable; - disconnect(ui->atomTable->selectionModel(), &QItemSelectionModel::selectionChanged, - this, &MolWidget::atomSelectionChanged); - table->clearSelection(); - table->setSelectionMode(QAbstractItemView::MultiSelection); - for(const auto& i: sel.getAtoms().indices){ - table->selectRow(static_cast(i.first)); - } - connect(ui->atomTable->selectionModel(), &QItemSelectionModel::selectionChanged, - this, &MolWidget::atomSelectionChanged); - update(); // TODO: necessary? - table->setSelectionMode(QAbstractItemView::ExtendedSelection); + // TODO: missing cell stuff? } void MolWidget::setActiveMol(Molecule &mol) { - curMol = &mol; + //curMol = &mol; + ui->activeKpoint->setCurrentIndex(static_cast(mol.kpoints.active)); + fillKPoints(); } void MolWidget::updateWidget(GUI::change_t change) { if (!updateTriggered){ if ((change & GUI::molChanged) == GUI::molChanged) { - curMol = vApp.curMol; + // curMol = vApp.curMol; } if ((change & GUI::stepChanged) == GUI::stepChanged) { // setActiveStep(*vApp.curStep); @@ -157,187 +71,17 @@ void MolWidget::updateWidget(GUI::change_t change) } } if (change & GUI::Change::atoms) { - ui->typeWidget->setTable(&curMol->getPTE()); - checkOverlap(); - bondModel.setStep(&*ownStep, vApp.stepdata[curStep].automatic_bonds); + ui->typeWidget->setTable(&vApp.curMol->getPTE()); + //checkOverlap(); + //bondModel.setStep(&*ownStep, vApp.stepdata[vApp.curStep].automatic_bonds); } if (change & GUI::Change::cell) { - fillCell(); + //fillCell(); } if (change & GUI::Change::kpoints) { - ui->activeKpoint->setCurrentIndex(static_cast(curMol->kpoints.active)); - fillKPoints(); - } -} - -void MolWidget::checkOverlap() -{ - const auto& ovlp = curStep->getOverlaps(); - if(ovlp.empty()){ - ui->ovlpTable->hide(); - ui->ovlpLabel->hide(); - ui->bondButton->setText("Bonds"); - }else{ - auto& table = *ui->ovlpTable; - QSignalBlocker tableblock{table}; - table.setCurrentCell(-1, -1); - table.setRowCount(ovlp.size()); - for(size_t i=0; iovlpLabel->show(); - ui->bondButton->setText("Bonds (!)"); - } -} - -void MolWidget::fillCell() -{ - //Fill cell view - QSignalBlocker blockDim(ui->cellDimBox); - QSignalBlocker blockEnabled(ui->cellEnabledBox); - ui->cellEnabledBox->setChecked(ownStep->hasCell()); - ui->cellDimBox->setValue(static_cast( - ownStep->getCellDim( - static_cast(ui->cellFmt->currentIndex())))); - cellModel.setStep(curStep); -} - -void MolWidget::on_cellTrajecButton_clicked() -{ - if(ui->cellEnabledBox->isChecked()){ - auto scale = ui->cellScaleBox->isChecked(); - auto dim = ui->cellDimBox->value(); - auto fmt = static_cast(ui->cellFmt->currentIndex()); - Mat vec = cellModel.getVec(); - for(auto& step: vApp.curMol->getSteps()){ - if (&step == vApp.curStep) continue; - step.setCellDim(dim, fmt, scale); - step.setCellVec(vec, scale); - } - }else{ - for(auto& step: vApp.curMol->getSteps()){ - if (&step == vApp.curStep) continue; - step.enableCell(false); - } + //ui->activeKpoint->setCurrentIndex(static_cast(curMol->kpoints.active)); + //fillKPoints(); } - triggerUpdate(GUI::Change::trajec); -} - -void MolWidget::on_cellEnabledBox_toggled(bool checked) -{ - if(checked){ - GUI::change_t change = GUI::Change::cell; - auto scale = ui->cellScaleBox->isChecked(); - if (scale) change |= GUI::Change::atoms; - auto dim = ui->cellDimBox->value(); - auto fmt = static_cast(ui->cellFmt->currentIndex()); - try{ - auto vec = cellModel.getVec(); - if(vec == Mat{}){ - curStep->enableCell(true); - }else{ - curStep->setCellVec(vec, scale); - } - } catch(const Error& e){ - QMessageBox::critical(this, "Error setting cell vectors", e.what()); - QSignalBlocker block{ui->cellEnabledBox}; - ui->cellEnabledBox->setCheckState(Qt::CheckState::Unchecked); - return; - } - curStep->setCellDim(dim, fmt, scale); - triggerUpdate(change); - }else{ - curStep->enableCell(false); - triggerUpdate(GUI::Change::cell); - } -} - -void MolWidget::on_cellFmt_currentIndexChanged(int idx) -{ - QSignalBlocker blockCDB(ui->cellDimBox); - ui->cellDimBox->setValue(static_cast(ownStep->getCellDim(static_cast(idx)))); -} - -void MolWidget::on_cellDimBox_valueChanged(double cdm) -{ - // if cell is disabled, exit early - if(!ui->cellEnabledBox->isChecked()){ - return; - } - auto fmt = static_cast(ui->cellFmt->currentIndex()); - auto scale = ui->cellScaleBox->isChecked(); - curStep->setCellDim(cdm, fmt, scale); - GUI::change_t change = GUI::Change::cell; - // if needed, trigger atom update - if(scale){ - change |= GUI::Change::atoms; - } - // short-circuit resetting the molModel - if(scale == atomFmtAbsolute(ownStep->getFmt())){ - atomModel.setStep(ownStep.get()); - // TODO -// setSelection(); - } - triggerUpdate(change); -} - -bool MolWidget::scale() -{ - return ui->cellScaleBox->isChecked(); -} - -void MolWidget::on_atomFmtBox_currentIndexChanged(int index) -{ - ownStep = std::make_unique(curStep->asFmt(static_cast(index-2))); - atomModel.setStep(ownStep.get()); - bondModel.setStep(ownStep.get(), vApp.stepdata[curStep].automatic_bonds); - cellModel.setStep(curStep); - // TODO -// updateSelection(); -} - -void MolWidget::on_atomFmtButton_clicked() -{ - auto ifmt = ui->atomFmtBox->currentIndex(); - auto fmt = static_cast(ifmt-2); - auto oldFmt = static_cast(curStep->getFmt())+2; - ui->atomFmtBox->setItemText(oldFmt, inactiveFmt[oldFmt]); - ui->atomFmtBox->setItemText(ifmt, activeFmt[ifmt]); - curStep->setFmt(fmt); // (possibly) invalidates dependent objects - // reset formatter - ownStep = std::make_unique(curStep->asFmt(fmt)); - // reset models - atomModel.setStep(ownStep.get()); - bondModel.setStep(ownStep.get(), vApp.stepdata[curStep].automatic_bonds); - cellModel.setStep(curStep); - // reset selection - SelectionFilter filter{}; - filter.mode = SelectionFilter::Mode::Index; - filter.indices = vApp.curSel->getAtoms().indices; - *vApp.curSel = curStep->select(filter); - if(atomFmtRelative(fmt)){ - ui->cellEnabledBox->setChecked(true); - } - triggerUpdate(GUI::Change::fmt); -} - -void MolWidget::on_atomHelpButton_clicked() -{ - QMessageBox::information(this, QString("About atoms"), Vipster::AtomsAbout); -} - -void MolWidget::atomSelectionChanged(const QItemSelection &, const QItemSelection &) -{ - auto idx = ui->atomTable->selectionModel()->selectedRows(); - SelectionFilter filter{}; - filter.mode = SelectionFilter::Mode::Index; - for(const auto& i: idx){ - filter.indices.emplace_back(static_cast(i.row()), SizeVec{}); - } - *vApp.curSel = curStep->select(filter); - triggerUpdate(GUI::Change::selection); } void MolWidget::fillKPoints() @@ -345,7 +89,7 @@ void MolWidget::fillKPoints() QSignalBlocker blockCrystal(ui->crystal); QSignalBlocker blockBands(ui->bands); QSignalBlocker blockDisc(ui->discretetable); - const auto& kpoints = curMol->kpoints; + const auto& kpoints = vApp.curMol->kpoints; for(int i=0; i<3; ++i){ if(i == static_cast(kpoints.active)){ ui->activeKpoint->setItemText(i, activeKpoints[i]); @@ -383,20 +127,20 @@ void MolWidget::fillKPoints() void MolWidget::on_kFmtButton_clicked() { - auto oldFmt = static_cast(curMol->kpoints.active); + auto oldFmt = static_cast(vApp.curMol->kpoints.active); ui->activeKpoint->setItemText(oldFmt, inactiveKpoints[oldFmt]); auto newFmt = ui->activeKpoint->currentIndex(); ui->activeKpoint->setItemText(newFmt, activeKpoints[newFmt]); - curMol->kpoints.active = static_cast(newFmt); + vApp.curMol->kpoints.active = static_cast(newFmt); triggerUpdate(GUI::Change::kpoints); } void MolWidget::on_bands_stateChanged(int arg) { if(arg){ - curMol->kpoints.discrete.properties |= KPoints::Discrete::band; + vApp.curMol->kpoints.discrete.properties |= KPoints::Discrete::band; }else{ - curMol->kpoints.discrete.properties ^= KPoints::Discrete::band; + vApp.curMol->kpoints.discrete.properties ^= KPoints::Discrete::band; } triggerUpdate(GUI::Change::kpoints); } @@ -404,16 +148,16 @@ void MolWidget::on_bands_stateChanged(int arg) void MolWidget::on_crystal_stateChanged(int arg) { if(arg){ - curMol->kpoints.discrete.properties |= KPoints::Discrete::crystal; + vApp.curMol->kpoints.discrete.properties |= KPoints::Discrete::crystal; }else{ - curMol->kpoints.discrete.properties ^= KPoints::Discrete::crystal; + vApp.curMol->kpoints.discrete.properties ^= KPoints::Discrete::crystal; } triggerUpdate(GUI::Change::kpoints); } void MolWidget::mpg_change() { - auto& kpoints = curMol->kpoints.mpg; + auto& kpoints = vApp.curMol->kpoints.mpg; if(sender() == ui->mpg_x){ kpoints.x = ui->mpg_x->value(); }else if(sender() == ui->mpg_y){ @@ -444,7 +188,7 @@ void MolWidget::on_discretetable_itemSelectionChanged() void MolWidget::on_actionNew_K_Point_triggered() { - auto& kpoints = curMol->kpoints.discrete.kpoints; + auto& kpoints = vApp.curMol->kpoints.discrete.kpoints; kpoints.push_back(KPoints::Discrete::Point{}); fillKPoints(); triggerUpdate(GUI::Change::kpoints); @@ -455,7 +199,7 @@ void MolWidget::on_actionDelete_K_Point_triggered() if(curKPoint < 0){ throw Error{"MolWidget: \"Delete K-Point\" triggered with invalid selection"}; } - auto& kpoints = curMol->kpoints.discrete.kpoints; + auto& kpoints = vApp.curMol->kpoints.discrete.kpoints; kpoints.erase(kpoints.begin()+curKPoint); fillKPoints(); triggerUpdate(GUI::Change::kpoints); @@ -463,7 +207,7 @@ void MolWidget::on_actionDelete_K_Point_triggered() void MolWidget::on_discretetable_cellChanged(int row, int column) { - auto& kp = curMol->kpoints.discrete.kpoints[row]; + auto& kp = vApp.curMol->kpoints.discrete.kpoints[row]; QTableWidgetItem *cell = ui->discretetable->item(row, column); if(column == 3){ kp.weight = cell->text().toDouble(); @@ -473,54 +217,15 @@ void MolWidget::on_discretetable_cellChanged(int row, int column) triggerUpdate(GUI::Change::kpoints); } -void MolWidget::on_bondSetButton_clicked() -{ - ownStep->generateBonds(); - bondModel.setStep(ownStep.get(), vApp.stepdata[curStep].automatic_bonds); - triggerUpdate(GUI::Change::atoms); -} - -void MolWidget::on_bondHelpButton_clicked() -{ - QMessageBox::information(this, QString("About bonds"), Vipster::BondsAbout); -} - -void MolWidget::on_bondModeBox_currentIndexChanged(int index) -{ - auto automatic = vApp.stepdata[curStep].automatic_bonds = static_cast(index); - if(automatic){ - ui->bondSetButton->setDisabled(true); - }else{ - ui->bondSetButton->setEnabled(true); - } - for(auto& vp: master->viewports){ - if(vp->curStep == curStep) - vp->setBondMode(automatic); - } - bondModel.setStep(ownStep.get(), automatic); - triggerUpdate(GUI::Change::atoms); -} - -void MolWidget::on_ovlpTable_itemSelectionChanged() -{ - auto selection = ui->ovlpTable->selectedItems(); - if(!selection.empty()){ - const auto& sel = *selection[0]; - const auto& ovlp = curStep->getOverlaps()[sel.row()]; - auto idx = sel.column() == 0 ? ovlp.at1 : ovlp.at2; - ui->atomTable->selectRow(idx); - } -} - void MolWidget::on_clearTableButton_clicked() { - curMol->cleanPTE(); - ui->typeWidget->setTable(&curMol->getPTE()); + vApp.curMol->cleanPTE(); + ui->typeWidget->setTable(&vApp.curMol->getPTE()); } void MolWidget::on_newElemButton_clicked() { - if(newelement(curMol->getPTE()).exec()){ - ui->typeWidget->setTable(&curMol->getPTE()); + if(newelement(vApp.curMol->getPTE()).exec()){ + ui->typeWidget->setTable(&vApp.curMol->getPTE()); } } diff --git a/gui/qt/mainwidgets/molwidget.h b/gui/qt/mainwidgets/molwidget.h index e73faf89..11cbf646 100644 --- a/gui/qt/mainwidgets/molwidget.h +++ b/gui/qt/mainwidgets/molwidget.h @@ -5,9 +5,6 @@ #include #include "vipster/molecule.h" #include "../basewidget.h" -#include "molwidget_aux/atommodel.h" -#include "molwidget_aux/bondmodel.h" -#include "molwidget_aux/cellmodel.h" namespace Ui { class MolWidget; @@ -23,18 +20,6 @@ class MolWidget : public BaseWidget void updateWidget(Vipster::GUI::change_t change) override; private slots: - // atom slots - void on_atomFmtBox_currentIndexChanged(int index); - void on_atomFmtButton_clicked(); - void atomSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); - void on_atomHelpButton_clicked(); - - // cell slots - void on_cellEnabledBox_toggled(bool checked); - void on_cellFmt_currentIndexChanged(int idx); - void on_cellDimBox_valueChanged(double cdm); - void on_cellTrajecButton_clicked(); - // kpoint slots void on_kFmtButton_clicked(); void on_bands_stateChanged(int); @@ -45,47 +30,18 @@ private slots: void on_discretetable_itemSelectionChanged(); void on_discretetable_cellChanged(int row, int column); - // bond slots - void on_bondSetButton_clicked(); - void on_bondHelpButton_clicked(); - void on_bondModeBox_currentIndexChanged(int index); - void on_ovlpTable_itemSelectionChanged(); - // pte slots void on_clearTableButton_clicked(); void on_newElemButton_clicked(); private: - void setActiveStep(Vipster::Step &step, Vipster::Step::selection &sel); void updateStep(Vipster::Step &step); - void updateSelection(Vipster::Step::selection &sel); void setActiveMol(Vipster::Molecule &mol); - bool scale(); - void checkOverlap(void); - void fillCell(void); void fillKPoints(void); Ui::MolWidget *ui; - Vipster::Step *curStep; - Vipster::Step::selection *curSel; - std::unique_ptr ownStep; - Vipster::Molecule* curMol; - AtomModel atomModel{this}; - BondModel bondModel{this}; - friend class CellModel; - CellModel cellModel{this}; - QList headerActions; int curKPoint{-1}; - static constexpr const char* inactiveKpoints[] = {"Gamma", "Monkhorst-Pack grid", "Discrete"}; - static constexpr const char* activeKpoints[] = {"Gamma (active)", - "Monkhorst-Pack grid (active)", - "Discrete (active)"}; - static constexpr const char* inactiveFmt[] = { "Crystal", "Alat", "Angstrom", "Bohr" }; - static constexpr const char* activeFmt[] = {"Crystal (active)", - "Alat (active)", - "Angstrom (active)", - "Bohr (active)"}; }; #endif // MOLWIDGET_H diff --git a/gui/qt/mainwidgets/molwidget.ui b/gui/qt/mainwidgets/molwidget.ui index 9479ab15..ffbc68c8 100644 --- a/gui/qt/mainwidgets/molwidget.ui +++ b/gui/qt/mainwidgets/molwidget.ui @@ -2,411 +2,24 @@ MolWidget - - - 0 - 0 - 373 - 931 - - Form - - - - - Atoms - - - true - - - true - - - + - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - Coordinate format: - - - atomFmtBox - - - - - - - - Crystal - - - - - Alat - - - - - Ångström - - - - - Bohr - - - - - - - - Set Format - - - - - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ? - - - - - - - - - true - - - QAbstractItemView::SelectRows - - - - - + - - - Bonds - - - true - - - false - - + - - - false - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SingleSelection - - - - Atom 1 - - - - - Atom2 - - - - - - - - (Re-)calculate bonds - - - - - - - Overlapping atoms: - - - - - - - true - - - - - - - Mode: - - - bondModeBox - - - - - - - - Manual - - - - - Automatic - - - - - - - - - 0 - 0 - - - - - 25 - 16777215 - - - - ? - - - - - - - - - - Cell Geometry - - - true - - - true - - - - - + 0 0 - - QFrame::Box - - - QFrame::Sunken - - - - - - - - Enabled - - - cellEnabledBox - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Scale coordinates with cell - - - cellScaleBox - - - - - - - - - - - - - - - - - - Lattice constant: - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 4 - - - 0.010000000000000 - - - 1.000000000000000 - - - - - - - - Angstrom - - - - - Bohr - - - - - - - - - - Cell vectors: - - - - - - - - 0 - 0 - - - - - 16777215 - 110 - - - - - - - - Apply to trajectory - - - - @@ -743,41 +356,27 @@
mainwidgets/periodictablewidget.h
1 + + AtomList + QWidget +
mainwidgets/molwidget_aux/atomlist.h
+ 1 +
+ + BondWidget + QWidget +
mainwidgets/molwidget_aux/bondwidget.h
+ 1 +
+ + CellWidget + QWidget +
mainwidgets/molwidget_aux/cellwidget.h
+ 1 +
- - cellWidgetButton - toggled(bool) - cellContainer - setVisible(bool) - - - 174 - 389 - - - 174 - 461 - - - - - kpointButton - toggled(bool) - kpointContainer - setVisible(bool) - - - 212 - 736 - - - 204 - 914 - - - activeKpoint currentIndexChanged(int) @@ -890,54 +489,6 @@ - - atomTableButton - toggled(bool) - atomContainer - setVisible(bool) - - - 148 - 34 - - - 121 - 63 - - - - - bondButton - toggled(bool) - bondContainer - setVisible(bool) - - - 204 - 176 - - - 186 - 223 - - - - - typeButton - toggled(bool) - typeContainer - setVisible(bool) - - - 353 - 587 - - - 360 - 641 - - - mpg_change() diff --git a/gui/qt/mainwidgets/molwidget_aux/atomlist.cpp b/gui/qt/mainwidgets/molwidget_aux/atomlist.cpp new file mode 100644 index 00000000..e11c8103 --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/atomlist.cpp @@ -0,0 +1,157 @@ +#include +#include "atomlist.h" +#include "ui_atomlist.h" +#include "doubledelegate.h" +#include "vipsterapplication.h" + +using namespace Vipster; + +constexpr const char* inactiveFmt[] = { "Crystal", "Alat", "Angstrom", "Bohr" }; +constexpr const char* activeFmt[] = {"Crystal (active)", + "Alat (active)", + "Angstrom (active)", + "Bohr (active)"}; + +AtomList::AtomList(QWidget *parent) : + QWidget(parent), + ui(new Ui::AtomList) +{ + ui->setupUi(this); + + // configure table + ui->atomTable->setModel(&atomModel); + connect(ui->atomTable->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &AtomList::selectionChanged); + headerActions.push_back(new QAction{"Show type", ui->atomTable}); + headerActions.push_back(new QAction{"Show coordinates", ui->atomTable}); + headerActions.push_back(new QAction{"Show charges", ui->atomTable}); + headerActions.push_back(new QAction{"Show forces", ui->atomTable}); + headerActions.push_back(new QAction{"Show visibility", ui->atomTable}); + headerActions.push_back(new QAction{"Show constraints", ui->atomTable}); + for(auto& action: headerActions){ + action->setCheckable(true); + } + headerActions[0]->setChecked(true); + headerActions[1]->setChecked(true); + auto changeColumns = [&](){ + // triggered through changed columns + int i=0; + for(int j=0; jisChecked() << j; + } + atomModel.setColumns(i); + }; + for(auto& action: headerActions){ + connect(action, &QAction::toggled, this, changeColumns); + } + ui->atomTable->horizontalHeader()->setContextMenuPolicy(Qt::ActionsContextMenu); + ui->atomTable->horizontalHeader()->addActions(headerActions); + ui->atomTable->setItemDelegate(new DoubleDelegate{}); + + // Connect ui elements + connect(ui->displayButton, &QPushButton::toggled, ui->frame, &QWidget::setVisible); + connect(ui->atomFmtButton, &QPushButton::clicked, this, &AtomList::fmtButtonHandler); + connect(ui->atomFmtBox, &QComboBox::currentIndexChanged, this, &AtomList::fmtSelectionHandler); + connect(ui->atomHelpButton, &QPushButton::clicked, [&](){ + QMessageBox::information(this, QString("About atoms"), Vipster::AtomsAbout); + }); + + // Connect to app state changes + connect(&vApp, &Application::activeStepChanged, this, &AtomList::setActiveStep); + connect(&vApp, &Application::stepChanged, this, &AtomList::updateStep); + connect(&vApp, &Application::selChanged, this, &AtomList::updateSelection); +} + +AtomList::~AtomList() +{ + delete ui; +} + +void AtomList::setActiveStep(Step &step, Step::selection &sel) +{ + QSignalBlocker blockAtomFmt(ui->atomFmtBox); + auto &fmtBox = *ui->atomFmtBox; + + // reset old fmt-string + const auto oldFmt = fmtBox.currentIndex(); + fmtBox.setItemText(oldFmt, inactiveFmt[oldFmt]); + + // obtain formatter + const auto fmt = step.getFmt(); + atomModel.setStep(step.asFmt(fmt)); + + // mark fmt as active + const auto ifmt = static_cast(fmt)+2; + fmtBox.setCurrentIndex(ifmt); + fmtBox.setItemText(ifmt, activeFmt[ifmt]); + + // trigger further updates + updateStep(step); + updateSelection(sel); +} + +void AtomList::updateStep(Step &step) +{ + // only update active step + if (&step != &vApp.getCurStep()) return; + + atomModel.update(); +} + +void AtomList::updateSelection(Step::selection &sel) +{ + // only update selection of active step + if (&sel != vApp.curSel) return; + + auto& table = *ui->atomTable; + QSignalBlocker blockTable{table.selectionModel()}; + + table.clearSelection(); + table.setSelectionMode(QAbstractItemView::MultiSelection); + for(const auto& i: sel.getAtoms().indices){ + table.selectRow(static_cast(i.first)); + } + update(); // TODO: necessary? + table.setSelectionMode(QAbstractItemView::ExtendedSelection); +} + +void AtomList::selectionChanged() +{ + auto idx = ui->atomTable->selectionModel()->selectedRows(); + SelectionFilter filter{}; + filter.mode = SelectionFilter::Mode::Index; + for(const auto& i: idx){ + filter.indices.emplace_back(static_cast(i.row()), SizeVec{}); + } + vApp.invokeOnSel([](Step::selection &sel, const SelectionFilter &filter){ + sel = vApp.curStep->select(filter); + }, filter); +} + +void AtomList::fmtSelectionHandler(int index) +{ + atomModel.setStep(vApp.curStep->asFmt(static_cast(index-2))); +} + +void AtomList::fmtButtonHandler() +{ + QSignalBlocker blockFmtBox{ui->atomFmtBox}; + // reset old format string + auto oldFmt = static_cast(vApp.curStep->getFmt())+2; + ui->atomFmtBox->setItemText(oldFmt, inactiveFmt[oldFmt]); + + // set new active format string + auto ifmt = ui->atomFmtBox->currentIndex(); + ui->atomFmtBox->setItemText(ifmt, activeFmt[ifmt]); + + // modify actual Step + auto fmt = static_cast(ifmt-2); + vApp.invokeOnStep(&Step::setFmt, fmt, true); + ownStep = vApp.curStep->asFmt(fmt); + + // reset selection + SelectionFilter filter{}; + filter.mode = SelectionFilter::Mode::Index; + filter.indices = vApp.curSel->getAtoms().indices; + *vApp.curSel = vApp.curStep->select(filter); +} diff --git a/gui/qt/mainwidgets/molwidget_aux/atomlist.h b/gui/qt/mainwidgets/molwidget_aux/atomlist.h new file mode 100644 index 00000000..a999d51b --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/atomlist.h @@ -0,0 +1,40 @@ +#ifndef ATOMLIST_H +#define ATOMLIST_H + +#include +#include +#include +#include +#include "vipster/molecule.h" +#include "atommodel.h" + +namespace Ui { + class AtomList; +} + +class AtomList : public QWidget +{ + Q_OBJECT + +public: + explicit AtomList(QWidget *parent = nullptr); + ~AtomList(); + +private slots: + void setActiveStep(Vipster::Step &step, Vipster::Step::selection &sel); + void updateStep(Vipster::Step &step); + void updateSelection(Vipster::Step::selection &sel); + +private: + void selectionChanged(); + void fmtButtonHandler(); + void fmtSelectionHandler(int index); + + Ui::AtomList *ui; + + std::optional ownStep{}; + AtomModel atomModel{}; + QList headerActions; +}; + +#endif // ATOMLIST_H diff --git a/gui/qt/mainwidgets/molwidget_aux/atomlist.ui b/gui/qt/mainwidgets/molwidget_aux/atomlist.ui new file mode 100644 index 00000000..dd1f963d --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/atomlist.ui @@ -0,0 +1,121 @@ + + + AtomList + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Atoms + + + true + + + true + + + + + + + QFrame::Box + + + QFrame::Sunken + + + + + + + + Coordinate format: + + + atomFmtBox + + + + + + + + Crystal + + + + + Alat + + + + + Ångström + + + + + Bohr + + + + + + + + Set Format + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ? + + + + + + + + + true + + + QAbstractItemView::SelectRows + + + + + + + + + + + diff --git a/gui/qt/mainwidgets/molwidget_aux/atommodel.cpp b/gui/qt/mainwidgets/molwidget_aux/atommodel.cpp index 8faca3ac..d893b4d3 100644 --- a/gui/qt/mainwidgets/molwidget_aux/atommodel.cpp +++ b/gui/qt/mainwidgets/molwidget_aux/atommodel.cpp @@ -1,48 +1,57 @@ #include "atommodel.h" -#include "../molwidget.h" +#include "vipsterapplication.h" using namespace Vipster; -AtomModel::AtomModel(MolWidget *parent) - : QAbstractTableModel(parent), parent{parent} -{ -} +enum colIds{ + c_name, + c_x, c_y, c_z, + c_charge, + c_fx, c_fy, c_fz, + c_hidden, + c_fixx, c_fixy, c_fixz +}; -void AtomModel::setStep(Step::formatter *step) +void AtomModel::setStep(Step::formatter &&step) { beginResetModel(); curStep = step; endResetModel(); } +void AtomModel::update() +{ + endResetModel(); +} + void AtomModel::setColumns(int cols) { beginResetModel(); if(cols){ colMap.clear(); if(cols & 0x10){ - colMap.push_back(8); + colMap.push_back(c_hidden); } if(cols & 0x1){ - colMap.push_back(0); + colMap.push_back(c_name); } if(cols & 0x2){ - colMap.push_back(1); - colMap.push_back(2); - colMap.push_back(3); + colMap.push_back(c_x); + colMap.push_back(c_y); + colMap.push_back(c_z); } if(cols & 0x4){ - colMap.push_back(4); + colMap.push_back(c_charge); } if(cols & 0x8){ - colMap.push_back(5); - colMap.push_back(6); - colMap.push_back(7); + colMap.push_back(c_fx); + colMap.push_back(c_fy); + colMap.push_back(c_fz); } if(cols & 0x20){ - colMap.push_back(9); - colMap.push_back(10); - colMap.push_back(11); + colMap.push_back(c_fixx); + colMap.push_back(c_fixy); + colMap.push_back(c_fixz); } } endResetModel(); @@ -83,17 +92,17 @@ QVariant AtomModel::data(const QModelIndex &index, int role) const int col = colMap[index.column()]; const auto& atom = curStep->at(index.row()); switch(col){ - case 0: + case c_name: return atom.name.c_str(); - case 1: - case 2: - case 3: + case c_x: + case c_y: + case c_z: return atom.coord[col-1]; - case 4: + case c_charge: return atom.properties->charge; - case 5: - case 6: - case 7: + case c_fx: + case c_fy: + case c_fz: return atom.properties->forces[col-5]; } }else if(role == Qt::CheckStateRole){ @@ -101,13 +110,13 @@ QVariant AtomModel::data(const QModelIndex &index, int role) const const auto& atom = curStep->at(index.row()); // return bool*2 so true becomes CheckState::Checked switch(col){ - case 8: + case c_hidden: return 2*atom.properties->flags[AtomProperties::Hidden]; - case 9: + case c_fixx: return 2*atom.properties->flags[AtomProperties::FixX]; - case 10: + case c_fixy: return 2*atom.properties->flags[AtomProperties::FixY]; - case 11: + case c_fixz: return 2*atom.properties->flags[AtomProperties::FixZ]; } } @@ -121,45 +130,60 @@ bool AtomModel::setData(const QModelIndex &index, const QVariant &value, int rol int col = colMap[index.column()]; auto atom = curStep->at(index.row()); switch(col){ - case 0: - atom.name = value.toString().toStdString(); + case c_name: + vApp.invokeOnStep([](Step &s, int i, const std::string &name){ + s.at(i).name = name; + }, index.row(), value.toString().toStdString()); break; - case 1: - case 2: - case 3: + case c_x: + case c_y: + case c_z: + // TODO: offload to vApp atom.coord[col-1] = value.toDouble(); + emit vApp.stepChanged(*vApp.curStep); break; - case 4: - atom.properties->charge = value.toDouble(); + case c_charge: + vApp.invokeOnStep([](Step &s, int i, double charge){ + s.at(i).properties->charge = charge; + }, index.row(), value.toDouble()); break; - case 5: - case 6: - case 7: - atom.properties->forces[col-5] = value.toDouble(); + case c_fx: + case c_fy: + case c_fz: + vApp.invokeOnStep([](Step &s, int i, int dir, double charge){ + s.at(i).properties->forces[dir] = charge; + }, index.row(), col-c_fx, value.toDouble()); break; } }else if(role == Qt::CheckStateRole){ int col = colMap[index.column()]; auto atom = curStep->at(index.row()); switch(col){ - case 8: - atom.properties->flags[AtomProperties::Hidden] = value==Qt::Checked; + case c_hidden: + vApp.invokeOnStep([](Step &s, int i, bool val){ + s.at(i).properties->flags[AtomProperties::Hidden] = val; + }, index.row(), value == Qt::Checked); break; - case 9: - atom.properties->flags[AtomProperties::FixX] = value==Qt::Checked; + case c_fixx: + vApp.invokeOnStep([](Step &s, int i, bool val){ + s.at(i).properties->flags[AtomProperties::FixX] = val; + }, index.row(), value == Qt::Checked); break; - case 10: - atom.properties->flags[AtomProperties::FixY] = value==Qt::Checked; + case c_fixy: + vApp.invokeOnStep([](Step &s, int i, bool val){ + s.at(i).properties->flags[AtomProperties::FixY] = val; + }, index.row(), value == Qt::Checked); break; - case 11: - atom.properties->flags[AtomProperties::FixZ] = value==Qt::Checked; + case c_fixz: + vApp.invokeOnStep([](Step &s, int i, bool val){ + s.at(i).properties->flags[AtomProperties::FixZ] = val; + }, index.row(), value == Qt::Checked); break; } }else{ return false; } emit dataChanged(index, index, QVector() << role); - parent->triggerUpdate(GUI::Change::atoms); return true; } return false; diff --git a/gui/qt/mainwidgets/molwidget_aux/atommodel.h b/gui/qt/mainwidgets/molwidget_aux/atommodel.h index e3f35137..1dfc712c 100644 --- a/gui/qt/mainwidgets/molwidget_aux/atommodel.h +++ b/gui/qt/mainwidgets/molwidget_aux/atommodel.h @@ -3,14 +3,13 @@ #include #include "vipster/molecule.h" -class MolWidget; class AtomModel : public QAbstractTableModel { Q_OBJECT public: - explicit AtomModel(MolWidget *parent = nullptr); - void setStep(Vipster::Step::formatter* curStep); + void setStep(Vipster::Step::formatter &&curStep); + void update(); void setColumns(int cols); // Header: @@ -30,8 +29,7 @@ class AtomModel : public QAbstractTableModel Qt::ItemFlags flags(const QModelIndex& index) const override; private: - MolWidget *parent; - Vipster::Step::formatter* curStep{nullptr}; + std::optional curStep{}; QStringList colNames = {"Type" , "x", "y", "z", "Charge", "fx", "fy", "fz", "Hide", "fix x", "fix y", "fix z"}; std::vector colMap = {0,1,2,3}; diff --git a/gui/qt/mainwidgets/molwidget_aux/bondmodel.cpp b/gui/qt/mainwidgets/molwidget_aux/bondmodel.cpp index 78307c29..bb07fdba 100644 --- a/gui/qt/mainwidgets/molwidget_aux/bondmodel.cpp +++ b/gui/qt/mainwidgets/molwidget_aux/bondmodel.cpp @@ -1,19 +1,14 @@ -#include "bondmodel.h" -#include "../molwidget.h" #include +#include "bondmodel.h" +#include "vipsterapplication.h" using namespace Vipster; -BondModel::BondModel(MolWidget *parent) - :parent{parent} -{} - -void BondModel::setStep(Step::formatter *curStep, bool automatic_bonds) +void BondModel::reset() { beginResetModel(); - this->curStep = curStep; - this->automatic_bonds = automatic_bonds; - curBonds = &curStep->getBonds(); + curBonds = &vApp.getCurStep().getBonds(); + automatic_bonds = vApp.getState(vApp.getCurStep()).automatic_bonds; endResetModel(); } @@ -36,7 +31,7 @@ int BondModel::rowCount(const QModelIndex &parent) const int BondModel::columnCount(const QModelIndex &parent) const { - if (parent.isValid() || !curStep) + if (parent.isValid()) return 0; if(automatic_bonds){ @@ -67,8 +62,9 @@ QVariant BondModel::data(const QModelIndex &index, int role) const if (bond.type) { return bond.type->first.c_str(); } else { - const std::string& n1 = (*curStep)[bond.at1].name; - const std::string& n2 = (*curStep)[bond.at2].name; + const auto& curStep = vApp.getCurStep(); + const std::string& n1 = curStep[bond.at1].name; + const std::string& n2 = curStep[bond.at2].name; return QStringLiteral("%1-%2").arg(std::min(n1, n2).c_str()) .arg(std::max(n1, n2).c_str()); } @@ -111,19 +107,19 @@ bool BondModel::setData(const QModelIndex &index, const QVariant &value, int rol if(role == Qt::EditRole){ if(data(index, role) != value){ if(index.column() == 2){ - curStep->setBondType(index.row(), value.toString().toStdString()); - parent->triggerUpdate(GUI::Change::atoms); + vApp.invokeOnStep(&Step::setBondType, index.row(), value.toString().toStdString()); return true; } } }else if(role == Qt::UserRole){ + // TODO: offload to vApp const auto& color = value.value(); (*curBonds)[index.row()].type->second = { static_cast(color.red()), static_cast(color.green()), static_cast(color.blue()), static_cast(color.alpha())}; - parent->triggerUpdate(GUI::Change::atoms); + emit vApp.stepChanged(*vApp.curStep); return true; } return false; @@ -131,7 +127,7 @@ bool BondModel::setData(const QModelIndex &index, const QVariant &value, int rol Qt::ItemFlags BondModel::flags(const QModelIndex& index) const { - if (!index.isValid() || !curStep) + if (!index.isValid()) return Qt::NoItemFlags; if(index.column() == 2){ diff --git a/gui/qt/mainwidgets/molwidget_aux/bondmodel.h b/gui/qt/mainwidgets/molwidget_aux/bondmodel.h index b4d17bc0..28a773ec 100644 --- a/gui/qt/mainwidgets/molwidget_aux/bondmodel.h +++ b/gui/qt/mainwidgets/molwidget_aux/bondmodel.h @@ -4,13 +4,11 @@ #include #include "vipster/step.h" -class MolWidget; class BondModel: public QAbstractTableModel { Q_OBJECT public: - explicit BondModel(MolWidget* parent=nullptr); - void setStep(Vipster::Step::formatter *curStep, bool automatic_bonds); + void reset(); // Header: QVariant headerData(int section, Qt::Orientation orientation, @@ -28,8 +26,7 @@ class BondModel: public QAbstractTableModel Qt::ItemFlags flags(const QModelIndex& index) const override; private: - MolWidget *parent; - Vipster::Step::formatter *curStep{nullptr}; + // Store some state to reduce requests, optimization only bool automatic_bonds{true}; const std::vector *curBonds{nullptr}; QStringList colNames = {"Atoms", "Length / Å", "Type", "Color"}; diff --git a/gui/qt/mainwidgets/molwidget_aux/bondwidget.cpp b/gui/qt/mainwidgets/molwidget_aux/bondwidget.cpp new file mode 100644 index 00000000..00bb1b19 --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/bondwidget.cpp @@ -0,0 +1,119 @@ +#include +#include "bondwidget.h" +#include "ui_bondwidget.h" +#include "vipsterapplication.h" +#include "bonddelegate.h" + +using namespace Vipster; + +BondWidget::BondWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::BondWidget) +{ + ui->setupUi(this); + + // configure table + ui->bondTable->setModel(&bondModel); + ui->bondTable->setItemDelegateForColumn(3, new BondDelegate{}); + + // Connect ui elements + connect(ui->displayButton, &QPushButton::toggled, ui->frame, &QWidget::setVisible); + connect(ui->bondHelpButton, &QPushButton::clicked, [&](){ + QMessageBox::information(this, QString("About bonds"), Vipster::BondsAbout); + }); + connect(ui->bondModeBox, &QComboBox::currentIndexChanged, this, &BondWidget::bondModeHandler); + connect(ui->bondSetButton, &QPushButton::pressed, this, &BondWidget::recalculateBonds); + connect(ui->ovlpTable, &QTableWidget::itemSelectionChanged, this, &BondWidget::ovlpTableSelectionHandler); + + // Connect to app state changes + connect(&vApp, &Application::activeStepChanged, this, &BondWidget::setActiveStep); + connect(&vApp, &Application::stepChanged, this, &BondWidget::updateStep); + + // Hide UI elements until requested + ui->frame->hide(); + ui->ovlpTable->hide(); + ui->ovlpLabel->hide(); +} + +BondWidget::~BondWidget() +{ + delete ui; +} + +void BondWidget::setActiveStep(Step &step, Step::selection &) +{ + // expose BondMode + QSignalBlocker blockBondMode(ui->bondModeBox); + const bool autobonds = vApp.getState(step).automatic_bonds; + ui->bondModeBox->setCurrentIndex(autobonds ? 1 : 0); + ui->bondSetButton->setDisabled(autobonds); + + // enable bond widget when manual bonds are enabled + if(!autobonds){ + ui->displayButton->setChecked(true); + } + + // reset table model + bondModel.reset(); +} + +void BondWidget::updateStep(Step &step) +{ + // only update active step + if (&step != &vApp.getCurStep()) return; + + // reset table model + bondModel.reset(); + + updateOverlap(); +} + +void BondWidget::recalculateBonds() +{ + vApp.invokeOnStep(&Step::generateBonds, false); +} + +void BondWidget::bondModeHandler(int index) +{ + auto is_automatic = static_cast(index); + vApp.getState(vApp.getCurStep()).automatic_bonds = is_automatic; + ui->bondSetButton->setDisabled(is_automatic); +} + +void BondWidget::ovlpTableSelectionHandler() +{ + auto selection = ui->ovlpTable->selectedItems(); + if(!selection.empty()){ + const auto& sel = *selection[0]; + const auto& ovlp = vApp.curStep->getOverlaps()[sel.row()]; + const auto idx = sel.column() == 0 ? ovlp.at1 : ovlp.at2; + vApp.invokeOnSel([](Step::selection &sel, size_t idx){ + SelectionFilter filter{}; + filter.mode = SelectionFilter::Mode::Index; + filter.indices.emplace_back(idx, SizeVec{}); + sel = vApp.curStep->select(filter); + }, idx); + } +} + +void BondWidget::updateOverlap() +{ + const auto& ovlp = vApp.curStep->getOverlaps(); + if(ovlp.empty()){ + ui->ovlpTable->hide(); + ui->ovlpLabel->hide(); + ui->displayButton->setText("Bonds"); + }else{ + auto& table = *ui->ovlpTable; + QSignalBlocker tableblock{table}; + table.setCurrentCell(-1, -1); + table.setRowCount(ovlp.size()); + for(size_t i=0; iovlpLabel->show(); + ui->displayButton->setText("Bonds (!)"); + } +} diff --git a/gui/qt/mainwidgets/molwidget_aux/bondwidget.h b/gui/qt/mainwidgets/molwidget_aux/bondwidget.h new file mode 100644 index 00000000..e0cf277a --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/bondwidget.h @@ -0,0 +1,32 @@ +#ifndef BONDWIDGET_H +#define BONDWIDGET_H + +#include +#include "vipster/molecule.h" +#include "bondmodel.h" + +namespace Ui { + class BondWidget; +} + +class BondWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BondWidget(QWidget *parent = nullptr); + ~BondWidget(); + +private: + void setActiveStep(Vipster::Step &step, Vipster::Step::selection &sel); + void updateStep(Vipster::Step &step); + void recalculateBonds(); + void bondModeHandler(int index); + void updateOverlap(); + void ovlpTableSelectionHandler(); + + Ui::BondWidget *ui; + BondModel bondModel{}; +}; + +#endif // BONDWIDGET_H diff --git a/gui/qt/mainwidgets/molwidget_aux/bondwidget.ui b/gui/qt/mainwidgets/molwidget_aux/bondwidget.ui new file mode 100644 index 00000000..a410a445 --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/bondwidget.ui @@ -0,0 +1,142 @@ + + + BondWidget + + + + 0 + 0 + 400 + 300 + + + + Frame + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Box + + + QFrame::Sunken + + + + + + Mode: + + + bondModeBox + + + + + + + + Manual + + + + + Automatic + + + + + + + + + 0 + 0 + + + + + 25 + 16777215 + + + + ? + + + + + + + true + + + + + + + (Re-)calculate bonds + + + + + + + Overlapping atoms: + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + + Atom 1 + + + + + Atom2 + + + + + + + + + + + Bonds + + + true + + + + + + + + diff --git a/gui/qt/mainwidgets/molwidget_aux/cellmodel.cpp b/gui/qt/mainwidgets/molwidget_aux/cellmodel.cpp deleted file mode 100644 index d75f6007..00000000 --- a/gui/qt/mainwidgets/molwidget_aux/cellmodel.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "cellmodel.h" -#include "../molwidget.h" -#include - -using namespace Vipster; - -CellModel::CellModel(MolWidget *parent) - :QAbstractTableModel(parent), parent{parent} -{} - -void CellModel::setStep(Step *step) -{ - beginResetModel(); - curStep = step; - vec = curStep->getCellVec(); - endResetModel(); -} - -Mat CellModel::getVec() -{ - return vec; -} - -QVariant CellModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if(role != Qt::DisplayRole) return QVariant{}; - if(orientation == Qt::Horizontal){ - constexpr char colnames[] = {'x','y','z'}; - return QString{colnames[section]}; - }else{ - return section+1; - } -} - -int CellModel::rowCount(const QModelIndex &parent) const -{ - if(parent.isValid()) return 0; - return 3; -} - -int CellModel::columnCount(const QModelIndex &parent) const -{ - if(parent.isValid()) return 0; - return 3; -} - -QVariant CellModel::data(const QModelIndex &index, int role) const -{ - if(!index.isValid()) return {}; - if(role == Qt::DisplayRole || role == Qt::EditRole){ - return vec[index.row()][index.column()]; - } - return {}; -} - -bool CellModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if(data(index, role) != value){ - if(role == Qt::EditRole){ - vec[index.row()][index.column()] = value.toDouble(); - // early exit if not enabled - if(!curStep->hasCell()) return true; - // set vec of step - auto scale = parent->scale(); - try{ - curStep->setCellVec(vec, scale); - }catch(const Error &e){ - QMessageBox::critical(parent, "Error setting cell vectors", e.what()); - setStep(curStep); - return false; - } - emit dataChanged(index, index, QVector() << role); - GUI::change_t change = GUI::Change::cell; - if(scale) change |= GUI::Change::atoms; - // short-circuit resetting the molModel on parent - if(scale != (parent->ownStep->getFmt() == AtomFmt::Crystal)){ - parent->atomModel.setStep(parent->ownStep.get()); - // TODO -// parent->setSelection(); - } - parent->triggerUpdate(change); - return true; - } - } - return false; -} - -Qt::ItemFlags CellModel::flags(const QModelIndex &index) const -{ - if(!index.isValid()) return Qt::NoItemFlags; - return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; -} diff --git a/gui/qt/mainwidgets/molwidget_aux/cellmodel.h b/gui/qt/mainwidgets/molwidget_aux/cellmodel.h deleted file mode 100644 index 2c42c38e..00000000 --- a/gui/qt/mainwidgets/molwidget_aux/cellmodel.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CELLMODEL_H -#define CELLMODEL_H - -#include -#include "vipster/step.h" - -class MolWidget; -class CellModel : public QAbstractTableModel -{ - Q_OBJECT -public: - explicit CellModel(MolWidget *parent = nullptr); - void setStep(Vipster::Step *curStep); - - Vipster::Mat getVec(); - - // Header: - QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const override; - - // Basic functionality: - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - // Editable: - bool setData(const QModelIndex &index, const QVariant &value, - int role = Qt::EditRole) override; - - Qt::ItemFlags flags(const QModelIndex &index) const override; -private: - MolWidget *parent; - Vipster::Step *curStep{nullptr}; - Vipster::Mat vec{}; -}; - -#endif // CELLMODEL_H diff --git a/gui/qt/mainwidgets/molwidget_aux/cellwidget.cpp b/gui/qt/mainwidgets/molwidget_aux/cellwidget.cpp new file mode 100644 index 00000000..de5bfccf --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/cellwidget.cpp @@ -0,0 +1,169 @@ +#include +#include "cellwidget.h" +#include "vipsterapplication.h" +#include "doubledelegate.h" +#include "ui_cellwidget.h" + +using namespace Vipster; + +CellWidget::CellWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::CellWidget) +{ + ui->setupUi(this); + + // setup cell-table + for(int j=0;j!=3;++j){ + for(int k=0;k!=3;++k){ + ui->cellVecTable->setItem(j,k,new QTableWidgetItem()); + } + } + ui->cellVecTable->setItemDelegate(new DoubleDelegate{}); + + // Connect ui elements + connect(ui->displayButton, &QPushButton::toggled, ui->frame, &QWidget::setVisible); + connect(ui->cellEnabledBox, &QCheckBox::toggled, this, &CellWidget::enableCell); + connect(ui->cellFmt, &QComboBox::currentIndexChanged, this, &CellWidget::cellFmtChanged); + connect(ui->cellDimBox, &QDoubleSpinBox::valueChanged, this, &CellWidget::cellDimChanged); + connect(ui->cellVecTable, &QTableWidget::cellChanged, this, &CellWidget::cellVecChanged); + connect(ui->cellTrajecButton, &QPushButton::clicked, this, &CellWidget::applyToTrajectory); + + // Connect to app state changes + connect(&vApp, &Application::activeStepChanged, this, &CellWidget::updateStep); + connect(&vApp, &Application::stepChanged, this, &CellWidget::updateStep); + + // hide ui until requested + ui->frame->hide(); +} + +CellWidget::~CellWidget() +{ + delete ui; +} + +void CellWidget::updateStep(Step &step) +{ + // only update active step + if (&step != &vApp.getCurStep()) return; + + QSignalBlocker blockEnabled(ui->cellEnabledBox); + ui->cellEnabledBox->setChecked(step.hasCell()); + if(step.hasCell()){ + ui->displayButton->setChecked(true); + } + + QSignalBlocker blockDim(ui->cellDimBox); + ui->cellDimBox->setValue(static_cast( + step.getCellDim(static_cast(ui->cellFmt->currentIndex())) + )); + + QSignalBlocker blockTable(ui->cellVecTable); + const Mat& vec = step.getCellVec(); + for(int j=0;j!=3;++j){ + for(int k=0;k!=3;++k){ + ui->cellVecTable->item(j,k)->setText(QString::number(vec[j][k])); + } + } +} + +void CellWidget::enableCell(bool enabled) +{ + if(enabled){ + const auto scale = ui->cellScaleBox->isChecked(); + const auto dim = ui->cellDimBox->value(); + const auto fmt = static_cast(ui->cellFmt->currentIndex()); + const auto& table = *ui->cellVecTable; + Mat cell{ + Vec{ + table.item(0,0)->text().toDouble(), + table.item(0,1)->text().toDouble(), + table.item(0,2)->text().toDouble(), + }, Vec{ + table.item(1,0)->text().toDouble(), + table.item(1,1)->text().toDouble(), + table.item(1,2)->text().toDouble(), + }, Vec{ + table.item(2,0)->text().toDouble(), + table.item(2,1)->text().toDouble(), + table.item(2,2)->text().toDouble(), + } + }; + try{ + if(cell == Mat{}){ + vApp.invokeOnStep( + [](Step &step, const double &dim, AtomFmt fmt, bool scale){ + step.enableCell(true); + step.setCellDim(dim, fmt, scale); + }, dim, fmt, scale); + }else{ + vApp.invokeOnStep( + [](Step &step, const Mat &cell, const double &dim, AtomFmt fmt, bool scale){ + step.setCellVec(cell, scale); + step.setCellDim(dim, fmt, scale); + }, cell, dim, fmt, scale); + } + } catch(const Error& e){ + QMessageBox::critical(this, "Error setting cell vectors", e.what()); + QSignalBlocker block{ui->cellEnabledBox}; + ui->cellEnabledBox->setCheckState(Qt::CheckState::Unchecked); + } + }else{ + vApp.invokeOnStep(&Step::enableCell, false); + } +} + +void CellWidget::cellFmtChanged(int idx) +{ + QSignalBlocker blockCDB(ui->cellDimBox); + ui->cellDimBox->setValue(static_cast(vApp.getCurStep().getCellDim(static_cast(idx)))); +} + +void CellWidget::cellDimChanged(double dim) +{ + // if cell is disabled, exit early + if(!ui->cellEnabledBox->isChecked()){ + return; + } + + const auto fmt = static_cast(ui->cellFmt->currentIndex()); + const auto scale = ui->cellScaleBox->isChecked(); + vApp.invokeOnStep(&Step::setCellDim, dim, fmt, scale); +} + +void CellWidget::cellVecChanged(int row, int col) +{ + // if cell is disabled, exit early + if(!ui->cellEnabledBox->isChecked()){ + return; + } + + const auto scale = ui->cellScaleBox->isChecked(); + const auto val = ui->cellVecTable->item(row, col)->text().toDouble(); + Mat cell = vApp.getCurStep().getCellVec(); + const auto origVal = cell[row][col]; + cell[row][col] = val; + try { + vApp.invokeOnStep(&Step::setCellVec, cell, scale); + } catch (const Error &e) { + QMessageBox::critical(this, "Error setting cell vectors", e.what()); + QSignalBlocker block{ui->cellVecTable}; + ui->cellVecTable->item(row, col)->setText(QString::number(origVal)); + } +} + +void CellWidget::applyToTrajectory() +{ + if(ui->cellEnabledBox->isChecked()){ + const auto scale = ui->cellScaleBox->isChecked(); + const auto dim = ui->cellDimBox->value(); + const auto fmt = static_cast(ui->cellFmt->currentIndex()); + const auto cell = vApp.getCurStep().getCellVec(); + vApp.invokeOnTrajec( + [](Step &step, const Mat &cell, const double &dim, AtomFmt fmt, bool scale){ + step.setCellVec(cell, scale); + step.setCellDim(dim, fmt, scale); + }, cell, dim, fmt, scale); + }else{ + vApp.invokeOnTrajec(&Step::enableCell, false); + } +} diff --git a/gui/qt/mainwidgets/molwidget_aux/cellwidget.h b/gui/qt/mainwidgets/molwidget_aux/cellwidget.h new file mode 100644 index 00000000..542c788f --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/cellwidget.h @@ -0,0 +1,30 @@ +#ifndef CELLWIDGET_H +#define CELLWIDGET_H + +#include +#include "vipster/step.h" + +namespace Ui { + class CellWidget; +} + +class CellWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CellWidget(QWidget *parent = nullptr); + ~CellWidget(); + +private: + void updateStep(Vipster::Step &step); + void enableCell(bool enable); + void cellFmtChanged(int idx); + void cellDimChanged(double dim); + void cellVecChanged(int row, int col); + void applyToTrajectory(); + + Ui::CellWidget *ui; +}; + +#endif // CELLWIDGET_H diff --git a/gui/qt/mainwidgets/molwidget_aux/cellwidget.ui b/gui/qt/mainwidgets/molwidget_aux/cellwidget.ui new file mode 100644 index 00000000..b1a6e2e9 --- /dev/null +++ b/gui/qt/mainwidgets/molwidget_aux/cellwidget.ui @@ -0,0 +1,223 @@ + + + CellWidget + + + + 0 + 0 + 317 + 275 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Cell Geometry + + + true + + + false + + + + + + + + 0 + 0 + + + + QFrame::Box + + + QFrame::Sunken + + + + + + + + Enabled + + + cellEnabledBox + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Scale coordinates with cell + + + cellScaleBox + + + + + + + + + + + + + + + + + + Lattice constant: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 4 + + + 0.010000000000000 + + + 1.000000000000000 + + + + + + + + Angstrom + + + + + Bohr + + + + + + + + + + Cell vectors: + + + + + + + + 0 + 0 + + + + + 16777215 + 110 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + x + + + + + y + + + + + z + + + + + + + + Apply to trajectory + + + + + + + + + + + diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index 2c58dbb3..f67ea19a 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -71,7 +71,6 @@ void MainWindow::setupMainWidgets() QDockWidget* firstDock{nullptr}; for(const auto& pair: makeMainWidgets(this)){ auto *tmp = new QDockWidget(this); -// mainWidgets.push_back(pair.first); tmp->setWidget(pair.first); tmp->setAllowedAreas(Qt::LeftDockWidgetArea); tmp->setFeatures(QDockWidget::DockWidgetMovable | @@ -233,7 +232,7 @@ void MainWindow::setupEditMenu() QKeySequence::Cut); cutAction->setEnabled(false); connect(&vApp, &Application::selChanged, - cutAction, [&](Step::selection &sel){cutAction->setEnabled(sel.getNat() > 0);}); + cutAction, [=](Step::selection &sel){cutAction->setEnabled(sel.getNat() > 0);}); // Copy atoms auto *copyAction = editMenu.addAction("&Copy atom(s)", @@ -243,7 +242,7 @@ void MainWindow::setupEditMenu() QKeySequence::Copy); copyAction->setEnabled(false); connect(&vApp, &Application::selChanged, - copyAction, [&](Step::selection &sel){copyAction->setEnabled(sel.getNat() > 0);}); + copyAction, [=](Step::selection &sel){copyAction->setEnabled(sel.getNat() > 0);}); // Paste atoms auto *pasteAction = editMenu.addAction("&Paste atom(s)", @@ -253,7 +252,7 @@ void MainWindow::setupEditMenu() QKeySequence::Paste); pasteAction->setEnabled(false); connect(&vApp, &Application::copyBufChanged, - pasteAction, [&](Step::selection &buf){pasteAction->setEnabled(buf.getNat() > 0);}); + pasteAction, [=](Step::selection &buf){pasteAction->setEnabled(buf.getNat() > 0);}); // Separator editMenu.addSeparator(); @@ -261,7 +260,7 @@ void MainWindow::setupEditMenu() // Rename auto *renameAction = editMenu.addAction("&Rename atom(s)", [this](){ - auto tmp = QInputDialog::getText(this, "Rename atoms", + auto newName = QInputDialog::getText(this, "Rename atoms", "Enter new Atom-type for selected atoms:") .toStdString(); auto f = [](Step::selection &sel, const std::string &name){ @@ -269,10 +268,10 @@ void MainWindow::setupEditMenu() at.name = name; } }; - vApp.invokeOnSel(f, tmp); + vApp.invokeOnSel(f, newName); }); connect(&vApp, &Application::selChanged, - renameAction, [&](Step::selection &sel){renameAction->setEnabled(sel.getNat() > 0);}); + renameAction, [=](Step::selection &sel){renameAction->setEnabled(sel.getNat() > 0);}); // Hide auto *hideAction = editMenu.addAction("&Hide atom(s)", @@ -285,7 +284,7 @@ void MainWindow::setupEditMenu() vApp.invokeOnSel(f); }); connect(&vApp, &Application::selChanged, - hideAction, [&](Step::selection &sel){hideAction->setEnabled(sel.getNat() > 0);}); + hideAction, [=](Step::selection &sel){hideAction->setEnabled(sel.getNat() > 0);}); // Show auto *showAction = editMenu.addAction("&Show atom(s)", @@ -298,7 +297,7 @@ void MainWindow::setupEditMenu() vApp.invokeOnSel(f); }); connect(&vApp, &Application::selChanged, - showAction, [&](Step::selection &sel){showAction->setEnabled(sel.getNat() > 0);}); + showAction, [=](Step::selection &sel){showAction->setEnabled(sel.getNat() > 0);}); } void MainWindow::setupHelpMenu() @@ -343,9 +342,9 @@ void MainWindow::updateWidgets(GUI::change_t change) } // if necessary, make sure that bonds/overlaps are up to date if((change & GUI::Change::atoms) && - (vApp.stepdata[vApp.curStep].automatic_bonds || + (vApp.getState(*vApp.curStep).automatic_bonds || vApp.config.settings.overlap.val)){ - vApp.curStep->generateBonds(!vApp.stepdata[vApp.curStep].automatic_bonds); + vApp.curStep->generateBonds(!vApp.getState(*vApp.curStep).automatic_bonds); } // notify widgets for(auto& w: viewports){ diff --git a/gui/qt/toolwidgets/definewidget.cpp b/gui/qt/toolwidgets/definewidget.cpp index 5cc49dc3..efaf249d 100644 --- a/gui/qt/toolwidgets/definewidget.cpp +++ b/gui/qt/toolwidgets/definewidget.cpp @@ -37,7 +37,7 @@ void DefineWidget::updateWidget(Vipster::GUI::change_t change) { if((change & GUI::stepChanged) == GUI::stepChanged){ curStep = vApp.curStep; - defMap = &vApp.stepdata[curStep].definitions; + defMap = &vApp.getState(*curStep).definitions; curIt = defMap->end(); fillTable(); }else if(change & (GUI::Change::definitions)){ diff --git a/gui/qt/toolwidgets/scriptwidget.cpp b/gui/qt/toolwidgets/scriptwidget.cpp index 92897054..1c0f4d0b 100644 --- a/gui/qt/toolwidgets/scriptwidget.cpp +++ b/gui/qt/toolwidgets/scriptwidget.cpp @@ -14,7 +14,8 @@ using OpVec = ScriptWidget::OpVec; std::pair ScriptWidget::execute( const std::vector& operations, - Step& step, ViewPort::StepState& data) + Step& step, + ViewPort::StepState& data) { auto change = GUI::change_t{}; auto mkVec = [&](const OpVec& in)->Vec{ @@ -66,7 +67,7 @@ std::pair ScriptWidget::execute( break; case ScriptOp::Mode::Define: { - auto &defMap = vApp.stepdata[&step].definitions; + auto &defMap = vApp.getState(step).definitions; auto [it, _] = defMap.insert_or_assign(op.s1, std::tuple{s.select(op.s2), op.s2, std::make_shared()}); auto& seldata = *std::get<2>(it->second); @@ -88,7 +89,7 @@ std::pair ScriptWidget::execute( }else if(op.target == "sel"){ execOp(*data.sel, op); }else{ - auto &defMap = vApp.stepdata[&step].definitions; + auto &defMap = vApp.getState(step).definitions; auto def = defMap.find(op.target); if(def == defMap.end()){ throw Error("Unknown target: "+op.target); diff --git a/gui/qt/viewport.cpp b/gui/qt/viewport.cpp index a9804434..a310954f 100644 --- a/gui/qt/viewport.cpp +++ b/gui/qt/viewport.cpp @@ -33,6 +33,7 @@ ViewPort::ViewPort(MainWindow *parent, bool active) : // molecule drop-down list connect(&vApp, &Vipster::Application::molListChanged, this, &ViewPort::updateMoleculeList); + // TODO: what else to react on? at least selChanged! // update rendering if active step has changed connect(&vApp, &Vipster::Application::stepChanged, this, [&](Step &s){if (&s == curStep) { @@ -82,10 +83,11 @@ void ViewPort::triggerUpdate(Vipster::GUI::change_t change) }else{ // short-circuit if rest of GUI does not need to be updated // if necessary, make sure that bonds/overlaps are up to date + // TODO: VP should not be responsible for this if((change & GUI::Change::atoms) && - (vApp.stepdata[curStep].automatic_bonds || + (vApp.getState(*curStep).automatic_bonds || vApp.config.settings.overlap.val)){ - curStep->generateBonds(!vApp.stepdata[curStep].automatic_bonds); + curStep->generateBonds(!vApp.getState(*curStep).automatic_bonds); } // trigger update in viewports that display the same step for(auto& vp: master->viewports){ @@ -190,13 +192,8 @@ void ViewPort::setStep(int i, bool setMol) curStep = &curMol->getStep(static_cast(i-1)); // ensure this step will be reused when mol is selected again moldata[curMol].curStep = i; - // if step has not been accessed previously, set bonds to manual if bonds are already present - if((vApp.stepdata.find(curStep) == vApp.stepdata.end()) && - !curStep->getBonds().empty()){ - vApp.stepdata[curStep].automatic_bonds = false; - } // set widget's bond mode accordingly - setBondMode(vApp.stepdata[curStep].automatic_bonds); + setBondMode(vApp.getState(*curStep).automatic_bonds); // if no cell exists, disable mult-selectors setMultEnabled(curStep->hasCell()); // if no previous selection exists in this viewport, create one, afterwards assign it diff --git a/gui/qt/vipsterapplication.cpp b/gui/qt/vipsterapplication.cpp index 0baa2ad0..fc942133 100644 --- a/gui/qt/vipsterapplication.cpp +++ b/gui/qt/vipsterapplication.cpp @@ -4,16 +4,40 @@ using namespace Vipster; Application::Application() { + auto updateBonds = [&](){ + getCurStep().generateBonds(!getState(getCurStep()).automatic_bonds); + }; + connect(this, &Application::stepChanged, updateBonds); +} +const Step& Application::getCurStep() +{ + return *curStep; } void Application::setActiveStep(Step &step, Step::selection &sel) { curStep = &step; curSel = &sel; + + getState(step); emit activeStepChanged(step, sel); } +Application::StepState& Application::getState(const Step &step) +{ + // initialize state if required + if (stepdata.find(&step) == stepdata.end()) { + stepdata.emplace(&step, StepState{ + // when bonds are present (e.g. in file), use manual mode + step.getBonds().empty(), + // default formatter is passthrough + const_cast(step).asFmt(step.getFmt()) + }); + } + return stepdata.at(&step); +} + void Application::newMol(Molecule &&mol){ molecules.emplace_back(std::move(mol)); molecules.back().getPTE().root = &config.periodicTable; diff --git a/gui/qt/vipsterapplication.h b/gui/qt/vipsterapplication.h index 2b980baa..e0f04621 100644 --- a/gui/qt/vipsterapplication.h +++ b/gui/qt/vipsterapplication.h @@ -35,11 +35,26 @@ namespace Vipster{ * - selecting a different step * - providing a new step */ +// TODO: hide state (curMol, curStep etc.) behind invoke interface +// TODO: provide const ref only via signals // Singleton to manage process state class Application: public QObject { Q_OBJECT +private: + // Functional abstraction for managed state + // TODO: provide undo-mechanism via this function + template + std::invoke_result_t invokeImpl(S &s, F &&f, Args &&...args) + { + // TODO: if not required unless other functionality is introduced + if constexpr (!std::is_void_v>) { + return std::invoke(f, s, args...); + } else { + std::invoke(f, s, args...); + } + } public: /* Accessor function @@ -51,8 +66,6 @@ class Application: public QObject return app; } - // TODO: use weak_ptr for dependent state data - // expose config read from file private: public: @@ -68,9 +81,30 @@ class Application: public QObject Molecule *curMol{nullptr}; public: void newMol(Molecule &&mol); + template + std::invoke_result invokeOnMol(F &&f, Args &&...args) + { + if constexpr (!std::is_void_v(f), std::forward(args)...))>) { + auto tmp = invokeImpl(*curMol, std::forward(f), std::forward(args)...); + emit molChanged(*curMol); + return tmp; + } else { + invokeImpl(*curMol, std::forward(f), std::forward(args)...); + emit molChanged(*curMol); + } + } + template + void invokeOnTrajec(F &&f, Args &&...args) + { + static_assert(std::is_void_v(f), std::forward(args)...))>); + for (auto &step: curMol->getSteps()) { + invokeImpl(step, std::forward(f), std::forward(args)...); + emit stepChanged(step); + } + } signals: void molListChanged(const std::list &molecules); - void molChanged(int idx, Vipster::Molecule &mol); + void molChanged(Vipster::Molecule &mol); void activeMolChanged(Vipster::Molecule &mol); // Additional data @@ -91,51 +125,59 @@ class Application: public QObject // Currently active state public: - template - std::invoke_result_t invokeImpl(S &s, F &&f, Args &&...args) + Step *curStep{nullptr}; + Step::selection *curSel{nullptr}; + std::unique_ptr copyBuf{}; // TODO: are lifetime semantics of selection sufficient?? convert to optional instead + const Step& getCurStep(); + void setActiveStep(Step &step, Step::selection &sel); // TODO: prefer an index based interface? + void selectionToCopy(); + + template + auto invokeOnStep(F &&f, Args &&...args) { - if constexpr (!std::is_void_v>) { - auto tmp = std::invoke(f, s, args...); + if constexpr (!std::is_void_v(f), std::forward(args)...))>) { + auto tmp = invokeImpl(*curStep, std::forward(f), std::forward(args)...); emit stepChanged(*curStep); return tmp; } else { - std::invoke(f, s, args...); + invokeImpl(*curStep, std::forward(f), std::forward(args)...); emit stepChanged(*curStep); } } -public: - Step *curStep{nullptr}; - Step::selection *curSel{nullptr}; - std::unique_ptr copyBuf{}; // TODO: are lifetime semantics of selection sufficient?? - void setActiveStep(Step &step, Step::selection &sel); - void selectionToCopy(); - - template - std::invoke_result_t invokeOnStep(F &&f, Args &&...args) - { - return invokeImpl(*curStep, std::forward(f), std::forward(args)...); - } - template - std::invoke_result_t invokeOnSel(F &&f, Args &&...args) + template + auto invokeOnSel(F &&f, Args &&...args) { - return invokeImpl(*curSel, std::forward(f), std::forward(args)...); + if constexpr (!std::is_void_v(f), std::forward(args)...))>) { + auto tmp = invokeImpl(*curSel, std::forward(f), std::forward(args)...); + emit selChanged(*curSel); + return tmp; + } else { + invokeImpl(*curSel, std::forward(f), std::forward(args)...); + emit selChanged(*curSel); + } } signals: void activeStepChanged(Vipster::Step &step, Vipster::Step::selection &sel); - void stepChanged(Step &step); - void selChanged(Step::selection &sel); - void copyBufChanged(Step::selection &buf); + void stepChanged(Vipster::Step &step); + void selChanged(Vipster::Step::selection &sel); + void copyBufChanged(Vipster::Step::selection &buf); public: // Data related to a loaded Step struct StepState{ - bool automatic_bonds{true}; + bool automatic_bonds; + Step::formatter formatter; + std::map>> selections; std::map>> definitions; }; - std::map stepdata{}; + // TODO: should return const ref + StepState& getState(const Step &s); +private: + // TODO: use weak_ptr + std::map stepdata{}; private: // don't allow user-side construction