From 85e9bdd0b43d96a3bbba905dba31b8f319b07882 Mon Sep 17 00:00:00 2001 From: Patrick Avery Date: Sat, 25 Jun 2016 11:38:51 -0400 Subject: [PATCH] Added undo features for all crystal operations Added undo and redo commands for all crystal operations in RWMolecule. They are also now all called so that the undo stack gets updated with the changes. --- avogadro/core/crystaltools.cpp | 1 - avogadro/qtgui/rwmolecule.cpp | 301 ++++++++++++++++++ avogadro/qtgui/rwmolecule.h | 81 +++++ avogadro/qtplugins/crystal/crystal.cpp | 50 +-- .../qtplugins/crystal/supercelldialog.cpp | 8 +- avogadro/qtplugins/crystal/supercelldialog.h | 4 +- avogadro/qtplugins/crystal/unitcelldialog.cpp | 10 +- avogadro/qtplugins/spacegroup/spacegroup.cpp | 13 +- 8 files changed, 410 insertions(+), 58 deletions(-) diff --git a/avogadro/core/crystaltools.cpp b/avogadro/core/crystaltools.cpp index dd2658d8d..a799639db 100644 --- a/avogadro/core/crystaltools.cpp +++ b/avogadro/core/crystaltools.cpp @@ -672,7 +672,6 @@ bool CrystalTools::setCellMatrix(Molecule &molecule, const Matrix3 &newCellColMatrix, Options opt) { - if (opt & TransformAtoms && molecule.unitCell()) { const Matrix3 xform(newCellColMatrix * molecule.unitCell()->cellMatrix().inverse()); diff --git a/avogadro/qtgui/rwmolecule.cpp b/avogadro/qtgui/rwmolecule.cpp index 06887957c..fa63f0a97 100644 --- a/avogadro/qtgui/rwmolecule.cpp +++ b/avogadro/qtgui/rwmolecule.cpp @@ -21,11 +21,15 @@ #include #include +#include + namespace Avogadro { namespace QtGui { using Core::Array; using Core::AtomHybridization; +using Core::CrystalTools; +using Core::UnitCell; // Base class for all undo commands used by this class. // Used to expose molecule internals without needing to add explicit friendships @@ -907,6 +911,303 @@ bool RWMolecule::setBondPair(Index bondId, const std::pair &pair) return true; } +namespace { +class AddUnitCellCommand : public RWMolecule::UndoCommand +{ + UnitCell m_newUnitCell; +public: + AddUnitCellCommand(RWMolecule &m, const UnitCell &newUnitCell) + : UndoCommand(m), m_newUnitCell(newUnitCell) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(new UnitCell(m_newUnitCell)); + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(NULL); + } +}; +} // end anon namespace + +void RWMolecule::addUnitCell() +{ + // If there is already a unit cell, there is nothing to do + if (m_molecule.unitCell()) + return; + + UnitCell *cell = new UnitCell; + cell->setCellParameters(static_cast(3.0), + static_cast(3.0), + static_cast(3.0), + static_cast(90.0) * DEG_TO_RAD, + static_cast(90.0) * DEG_TO_RAD, + static_cast(90.0) * DEG_TO_RAD); + m_molecule.setUnitCell(cell); + + AddUnitCellCommand *comm = new AddUnitCellCommand(*this, + *m_molecule.unitCell()); + comm->setText(tr("Add Unit Cell")); + m_undoStack.push(comm); + emitChanged(Molecule::UnitCell | Molecule::Added); +} + +namespace { +class RemoveUnitCellCommand : public RWMolecule::UndoCommand +{ + UnitCell m_oldUnitCell; +public: + RemoveUnitCellCommand(RWMolecule &m, const UnitCell &oldUnitCell) + : UndoCommand(m), m_oldUnitCell(oldUnitCell) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(NULL); + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule().setUnitCell(new UnitCell(m_oldUnitCell)); + } +}; +} // end anon namespace + +void RWMolecule::removeUnitCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + RemoveUnitCellCommand *comm = new RemoveUnitCellCommand( + *this, + *m_molecule.unitCell()); + comm->setText(tr("Remove Unit Cell")); + m_undoStack.push(comm); + + m_molecule.setUnitCell(NULL); + emitChanged(Molecule::UnitCell | Molecule::Removed); +} + +namespace { +class ModifyMoleculeCommand : public RWMolecule::UndoCommand +{ + Molecule m_oldMolecule; + Molecule m_newMolecule; +public: + ModifyMoleculeCommand(RWMolecule &m, + const Molecule &oldMolecule, + const Molecule &newMolecule) + : UndoCommand(m), m_oldMolecule(oldMolecule), m_newMolecule(newMolecule) + { + } + + void redo() AVO_OVERRIDE + { + m_mol.molecule() = m_newMolecule; + } + + void undo() AVO_OVERRIDE + { + m_mol.molecule() = m_oldMolecule; + } +}; +} // end anon namespace + +void RWMolecule::modifyMolecule(const Molecule &newMolecule, + Molecule::MoleculeChanges changes, + const QString &undoText) +{ + ModifyMoleculeCommand *comm = new ModifyMoleculeCommand(*this, + m_molecule, + newMolecule); + + comm->setText(undoText); + m_undoStack.push(comm); + + m_molecule = newMolecule; + emitChanged(changes); +} + +void RWMolecule::editUnitCell(Matrix3 cellMatrix, + CrystalTools::Options options) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // If the user has "TransformAtoms" set in the options, then + // the atom positions will move as well. + Molecule newMolecule = m_molecule; + CrystalTools::setCellMatrix(newMolecule, cellMatrix, options); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; + // If TransformAtoms is set in the options, then the atoms may be modified + // as well. + if (options & CrystalTools::TransformAtoms) + changes |= Molecule::Atoms | Molecule::Modified; + QString undoText = tr("Edit Unit Cell"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::wrapAtomsToCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + Core::Array oldPos = m_molecule.atomPositions3d(); + CrystalTools::wrapAtomsToUnitCell(m_molecule); + Core::Array newPos = m_molecule.atomPositions3d(); + + SetPositions3dCommand *comm = new SetPositions3dCommand(*this, oldPos, + newPos); + comm->setText(tr("Wrap Atoms to Cell")); + m_undoStack.push(comm); + + Molecule::MoleculeChanges changes = Molecule::Atoms | Molecule::Modified; + emitChanged(changes); +} + +void RWMolecule::setCellVolume(double newVolume, + CrystalTools::Options options) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + CrystalTools::setVolume(newMolecule, newVolume, options); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; + if (options & CrystalTools::TransformAtoms) + changes |= Molecule::Atoms | Molecule::Modified; + QString undoText = tr("Scale Cell Volume"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::buildSupercell(unsigned int a, unsigned int b, unsigned int c) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + CrystalTools::buildSupercell(newMolecule, a, b, c); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified | + Molecule::Atoms | Molecule::Added; + QString undoText = tr("Build Super Cell"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::niggliReduceCell() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell and atom positions may change + Molecule newMolecule = m_molecule; + + // We need to perform all three of these operations... + CrystalTools::niggliReduce(newMolecule, CrystalTools::TransformAtoms); + CrystalTools::rotateToStandardOrientation(newMolecule, + CrystalTools::TransformAtoms); + CrystalTools::wrapAtomsToUnitCell(newMolecule); + + // We will just modify the whole molecule since there may be many changes + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Modified; + QString undoText = tr("Niggli Reduction"); + + modifyMolecule(newMolecule, changes, undoText); +} + +void RWMolecule::rotateCellToStandardOrientation() +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return; + + // Store a copy of the old molecule + // The atom positions may move as well. + Molecule newMolecule = m_molecule; + + CrystalTools::rotateToStandardOrientation(newMolecule, + CrystalTools::TransformAtoms); + + // Since most components of the molecule will be modified (atom positions + // and the unit cell), we will just modify the whole thing... + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Modified; + QString undoText = tr("Rotate to Standard Orientation"); + + modifyMolecule(newMolecule, changes, undoText); +} + +bool RWMolecule::reduceCellToPrimitive(double cartTol) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return false; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell, atom positions, and numbers of atoms may change + Molecule newMolecule = m_molecule; + + if (!Core::AvoSpglib::reduceToPrimitive(newMolecule, cartTol)) + return false; + + // Since most components of the molecule will be modified, + // we will just modify the whole thing... + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Added; + QString undoText = tr("Reduce to Primitive"); + + modifyMolecule(newMolecule, changes, undoText); + return true; +} + +bool RWMolecule::conventionalizeCell(double cartTol) +{ + // If there is no unit cell, there is nothing to do + if (!m_molecule.unitCell()) + return false; + + // Make a copy of the molecule to edit so we can store the old one + // The unit cell, atom positions, and numbers of atoms may all change + Molecule newMolecule = m_molecule; + + if (!Core::AvoSpglib::conventionalizeCell(newMolecule, cartTol)) + return false; + + Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Atoms | + Molecule::Added; + QString undoText = tr("Conventionalize Cell"); + + modifyMolecule(newMolecule, changes, undoText); + return true; +} + void RWMolecule::emitChanged(unsigned int change) { m_molecule.emitChanged(change); diff --git a/avogadro/qtgui/rwmolecule.h b/avogadro/qtgui/rwmolecule.h index 394fb2620..f27de5ff0 100644 --- a/avogadro/qtgui/rwmolecule.h +++ b/avogadro/qtgui/rwmolecule.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -399,6 +400,86 @@ class AVOGADROQTGUI_EXPORT RWMolecule : public QObject */ bool setBondPair(Index bondId, const std::pair &pair); + /** + * Add a default unit cell to the molecule. Does nothing if there already + * is a unit cell. Changes are emitted. + */ + void addUnitCell(); + + /** + * Remove the unit cell from the molecule. Does nothing if there is + * no unit cell. Changes are emitted. + */ + void removeUnitCell(); + + /** + * Generic edit that changes the current molecule to be @a newMolecule. + * Also sets the text for the undo command to be @a undoText. Changes are + * emitted. + * @param newMolecule The new molecule to be set. + * @param changes The changes to be emitted. + * @param undoText The text description for the undo command. + */ + void modifyMolecule(const Molecule &newMolecule, + Molecule::MoleculeChanges changes, + const QString &undoText = "Modify Molecule"); + + /** + * Edit the unit cell by replacing the current cell matrix with a new cell + * matrix. Changes are emitted. + * @param cellMatrix The new cell matrix to be set. + * @param opts If TransformAtoms is specified, the atoms in @a molecule are + * adjusted so that their fractional (lattice) coordinates are preserved. This + * option is ignored if the input molecule has no unit cell. + */ + void editUnitCell(Matrix3 cellMatrix, Core::CrystalTools::Options opts); + + /** + * Wrap atoms to the unit cell. Changes are emitted. + */ + void wrapAtomsToCell(); + + /** + * Rotate cell to standard orientation. Changes are emitted. + */ + void rotateCellToStandardOrientation(); + + /** + * Scale a cell's volume. Changes are emitted. + * @param newVolume The new volume to be set. + * @param options If CrystalTools::TransformAtoms is set, then + * the atoms will be transformed during the scaling. + */ + void setCellVolume(double newVolume, Core::CrystalTools::Options options); + + /** + * Build a supercell. Changes are emitted. + * @param a The final number of units along the A vector (at least 1). + * @param b The final number of units along the B vector (at least 1). + * @param c The final number of units along the C vector (at least 1). + */ + void buildSupercell(unsigned int a, unsigned int b, unsigned int c); + + /** + * Perform a Niggli reduction on the cell. Changes are emitted. + */ + void niggliReduceCell(); + + /** + * Use spglib to reduce the cell to its primitive form. Changes are emitted. + * @param cartTol Cartesian tolerance for primitive reduction. + * @return True if the algorithm succeeded, and false if it failed. + */ + bool reduceCellToPrimitive(double cartTol = 1e-5); + + /** + * Use spglib to convert the cell to its conventional form. Changes are + * emitted. + * @param cartTol Cartesian tolerance for conventionalization. + * @return True if the algorithm succeeded, and false if it failed. + */ + bool conventionalizeCell(double cartTol = 1e-5); + /** * @brief Begin or end an interactive edit. * diff --git a/avogadro/qtplugins/crystal/crystal.cpp b/avogadro/qtplugins/crystal/crystal.cpp index a6f2a3514..a8b76494b 100644 --- a/avogadro/qtplugins/crystal/crystal.cpp +++ b/avogadro/qtplugins/crystal/crystal.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -179,9 +180,10 @@ void Crystal::importCrystalClipboard() Core::Molecule m; if (d.importCrystalClipboard(m)) { // If we succeeded, update m_molecule - *m_molecule = m; - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); + Molecule::MoleculeChanges changes = Molecule::Added | + Molecule::Atoms | Molecule::UnitCell; + QString undoText = tr("Import Crystal from Clipboard"); + m_molecule->undoMolecule()->modifyMolecule(m, changes, undoText); } } @@ -198,10 +200,7 @@ void Crystal::editUnitCell() void Crystal::buildSupercell() { SupercellDialog d; - if (d.buildSupercell(*m_molecule)) { - m_molecule->emitChanged(Molecule::Modified | - Molecule::Atoms | Molecule::UnitCell); - } + d.buildSupercell(*m_molecule); } void Crystal::niggliReduce() @@ -213,12 +212,7 @@ void Crystal::niggliReduce() QMessageBox::Ok); return; } - CrystalTools::niggliReduce(*m_molecule, CrystalTools::TransformAtoms); - CrystalTools::rotateToStandardOrientation(*m_molecule, - CrystalTools::TransformAtoms); - CrystalTools::wrapAtomsToUnitCell(*m_molecule); - m_molecule->emitChanged(Molecule::Modified - | Molecule::Atoms | Molecule::UnitCell); + m_molecule->undoMolecule()->niggliReduceCell(); } void Crystal::scaleVolume() @@ -232,46 +226,30 @@ void Crystal::scaleVolume() if (reply != QDialog::Accepted) return; - CrystalTools::setVolume(*m_molecule, dlg.newVolume(), - dlg.transformAtoms() ? CrystalTools::TransformAtoms - : CrystalTools::None); - m_molecule->emitChanged(Molecule::Modified | Molecule::UnitCell - | (dlg.transformAtoms() ? Molecule::Atoms - : Molecule::NoChange)); + m_molecule->undoMolecule()->setCellVolume( + dlg.newVolume(), dlg.transformAtoms() ? CrystalTools::TransformAtoms + : CrystalTools::None); } void Crystal::standardOrientation() { - CrystalTools::rotateToStandardOrientation(*m_molecule, - CrystalTools::TransformAtoms); - m_molecule->emitChanged(Molecule::Modified - | Molecule::Atoms | Molecule::UnitCell); + m_molecule->undoMolecule()->rotateCellToStandardOrientation(); } void Crystal::toggleUnitCell() { if (m_molecule->unitCell()) { - m_molecule->setUnitCell(NULL); - m_molecule->emitChanged(Molecule::UnitCell | Molecule::Removed); + m_molecule->undoMolecule()->removeUnitCell(); } else { - UnitCell *cell = new UnitCell; - cell->setCellParameters(static_cast(3.0), - static_cast(3.0), - static_cast(3.0), - static_cast(90.0) * DEG_TO_RAD, - static_cast(90.0) * DEG_TO_RAD, - static_cast(90.0) * DEG_TO_RAD); - m_molecule->setUnitCell(cell); - m_molecule->emitChanged(Molecule::UnitCell | Molecule::Added); + m_molecule->undoMolecule()->addUnitCell(); editUnitCell(); } } void Crystal::wrapAtomsToCell() { - CrystalTools::wrapAtomsToUnitCell(*m_molecule); - m_molecule->emitChanged(Molecule::Atoms | Molecule::Modified); + m_molecule->undoMolecule()->wrapAtomsToCell(); } } // namespace QtPlugins diff --git a/avogadro/qtplugins/crystal/supercelldialog.cpp b/avogadro/qtplugins/crystal/supercelldialog.cpp index 3befa7055..c2c952b1c 100644 --- a/avogadro/qtplugins/crystal/supercelldialog.cpp +++ b/avogadro/qtplugins/crystal/supercelldialog.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include + using Avogadro::Core::CrystalTools; namespace Avogadro { @@ -37,7 +40,7 @@ SupercellDialog::~SupercellDialog() delete m_ui; } -bool SupercellDialog::buildSupercell(Avogadro::Core::Molecule &mol) +bool SupercellDialog::buildSupercell(Avogadro::QtGui::Molecule &mol) { // If the user rejected, just return false if (this->exec() == QDialog::Rejected) @@ -53,7 +56,8 @@ bool SupercellDialog::buildSupercell(Avogadro::Core::Molecule &mol) return true; // Run the supercell-building tool - return CrystalTools::buildSupercell(mol, a, b, c); + mol.undoMolecule()->buildSupercell(a, b, c); + return true; } } // namespace QtPlugins diff --git a/avogadro/qtplugins/crystal/supercelldialog.h b/avogadro/qtplugins/crystal/supercelldialog.h index b2fc58d38..8e74c9ee3 100644 --- a/avogadro/qtplugins/crystal/supercelldialog.h +++ b/avogadro/qtplugins/crystal/supercelldialog.h @@ -23,7 +23,7 @@ namespace Avogadro { -namespace Core { +namespace QtGui { class Molecule; } @@ -45,7 +45,7 @@ class SupercellDialog : public QDialog SupercellDialog(QWidget *p = 0); ~SupercellDialog(); - bool buildSupercell(Avogadro::Core::Molecule &mol); + bool buildSupercell(Avogadro::QtGui::Molecule &mol); void displayInvalidFormatMessage(); diff --git a/avogadro/qtplugins/crystal/unitcelldialog.cpp b/avogadro/qtplugins/crystal/unitcelldialog.cpp index a1a3bb615..c6f954801 100644 --- a/avogadro/qtplugins/crystal/unitcelldialog.cpp +++ b/avogadro/qtplugins/crystal/unitcelldialog.cpp @@ -18,6 +18,7 @@ #include "ui_unitcelldialog.h" #include +#include #include #include @@ -143,14 +144,9 @@ void UnitCellDialog::apply() break; default: { Core::CrystalTools::Options options = Core::CrystalTools::None; - Molecule::MoleculeChanges changes = Molecule::UnitCell | Molecule::Modified; - if (m_ui->transformAtoms->isChecked()) { + if (m_ui->transformAtoms->isChecked()) options |= Core::CrystalTools::TransformAtoms; - changes |= Molecule::Atoms | Molecule::Modified; - } - Core::CrystalTools::setCellMatrix(*m_molecule, m_tempCell.cellMatrix(), - options); - m_molecule->emitChanged(changes); + m_molecule->undoMolecule()->editUnitCell(m_tempCell.cellMatrix(), options); break; } } diff --git a/avogadro/qtplugins/spacegroup/spacegroup.cpp b/avogadro/qtplugins/spacegroup/spacegroup.cpp index ad2403e99..7b47407ee 100644 --- a/avogadro/qtplugins/spacegroup/spacegroup.cpp +++ b/avogadro/qtplugins/spacegroup/spacegroup.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -190,7 +191,7 @@ void SpaceGroup::reduceToPrimitive() setTolerance(); // Primitive reduction! - bool success = AvoSpglib::reduceToPrimitive(*m_molecule, m_spgTol); + bool success = m_molecule->undoMolecule()->reduceCellToPrimitive(m_spgTol); if (!success) { // Print an error message. @@ -200,10 +201,6 @@ void SpaceGroup::reduceToPrimitive() "with a different tolerance.")); retMsgBox.exec(); } - else { - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); - } } void SpaceGroup::conventionalizeCell() @@ -219,7 +216,7 @@ void SpaceGroup::conventionalizeCell() setTolerance(); // Conventionalize the cell! - bool success = AvoSpglib::conventionalizeCell(*m_molecule, m_spgTol); + bool success = m_molecule->undoMolecule()->conventionalizeCell(m_spgTol); if (!success) { // Print an error message. @@ -229,10 +226,6 @@ void SpaceGroup::conventionalizeCell() "with a different tolerance.")); retMsgBox.exec(); } - else { - m_molecule->emitChanged(Molecule::Added | - Molecule::Atoms | Molecule::UnitCell); - } } void SpaceGroup::setTolerance()