diff --git a/avogadro/qtplugins/customelements/CMakeLists.txt b/avogadro/qtplugins/customelements/CMakeLists.txt index c405001fd7..fd0bc3a8d5 100644 --- a/avogadro/qtplugins/customelements/CMakeLists.txt +++ b/avogadro/qtplugins/customelements/CMakeLists.txt @@ -3,5 +3,5 @@ avogadro_plugin(CustomElements ExtensionPlugin customelements.h CustomElements - "customelements.cpp" + "customelements.cpp;backgroundfileformat.cpp" ) diff --git a/avogadro/qtplugins/customelements/backgroundfileformat.cpp b/avogadro/qtplugins/customelements/backgroundfileformat.cpp new file mode 100644 index 0000000000..7885869676 --- /dev/null +++ b/avogadro/qtplugins/customelements/backgroundfileformat.cpp @@ -0,0 +1,86 @@ +/****************************************************************************** + + This source file is part of the Avogadro project. + + Copyright 2013 Kitware, Inc. + + This source code is released under the New BSD License, (the "License"). + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +******************************************************************************/ + +#include "backgroundfileformat.h" + +#include + +namespace Avogadro { + +BackgroundFileFormat::BackgroundFileFormat(Io::FileFormat* format, + QObject* aparent) + : QObject(aparent) + , m_format(format) + , m_molecule(nullptr) + , m_success(false) +{} + +BackgroundFileFormat::~BackgroundFileFormat() +{ + delete m_format; +} + +void BackgroundFileFormat::read() +{ + m_success = false; + m_error.clear(); + + if (!m_molecule) + m_error = tr("No molecule set in BackgroundFileFormat!"); + + if (!m_format) + m_error = tr("No Io::FileFormat set in BackgroundFileFormat!"); + + if (m_fileName.isEmpty()) + m_error = tr("No file name set in BackgroundFileFormat!"); + + if (m_error.isEmpty()) { + m_success = + m_format->readFile(m_fileName.toLocal8Bit().data(), *m_molecule); + + if (!m_success) + m_error = QString::fromStdString(m_format->error()); + } + + emit finished(); +} + +void BackgroundFileFormat::write() +{ + m_success = false; + m_error.clear(); + + if (!m_molecule) + m_error = tr("No molecule set in BackgroundFileFormat!"); + + if (!m_format) + m_error = tr("No Io::FileFormat set in BackgroundFileFormat!"); + + if (m_fileName.isEmpty()) + m_error = tr("No file name set in BackgroundFileFormat!"); + + if (m_error.isEmpty()) { + m_success = + m_format->writeFile(m_fileName.toLocal8Bit().data(), *m_molecule); + + if (!m_success) + m_error = QString::fromStdString(m_format->error()); + } + + emit finished(); +} + +} // namespace Avogadro diff --git a/avogadro/qtplugins/customelements/backgroundfileformat.h b/avogadro/qtplugins/customelements/backgroundfileformat.h new file mode 100644 index 0000000000..b54cf31515 --- /dev/null +++ b/avogadro/qtplugins/customelements/backgroundfileformat.h @@ -0,0 +1,107 @@ +/****************************************************************************** + + This source file is part of the Avogadro project. + + Copyright 2013 Kitware, Inc. + + This source code is released under the New BSD License, (the "License"). + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +******************************************************************************/ + +#ifndef AVOGADRO_BACKGROUNDFILEFORMAT_H +#define AVOGADRO_BACKGROUNDFILEFORMAT_H + +#include +#include + +namespace Avogadro { + +namespace Core { +class Molecule; +} + +namespace Io { +class FileFormat; +} + +/** + * @brief The BackgroundFileFormat class provides a thin QObject wrapper around + * an instance of Io::FileFormat. + */ +class BackgroundFileFormat : public QObject +{ + Q_OBJECT +public: + /** + * This class takes ownership of @a format and will delete it when destructed. + */ + explicit BackgroundFileFormat(Io::FileFormat* format, QObject* aparent = 0); + ~BackgroundFileFormat(); + + /** + * The molecule instance to read/write. + * @{ + */ + void setMolecule(Core::Molecule* mol) { m_molecule = mol; } + Core::Molecule* molecule() const { return m_molecule; } + /**@}*/ + + /** + * The name of the file to read/write. + * @{ + */ + void setFileName(const QString& filename) { m_fileName = filename; } + QString fileName() const { return m_fileName; } + /**@}*/ + + /** + * The Io::FileFormat to use. + */ + Io::FileFormat* fileFormat() const { return m_format; } + + /** + * @return True if the operation was successful. + */ + bool success() const { return m_success; } + + /** + * @return An error string, set if success() is false. + */ + QString error() const { return m_error; } + +signals: + + /** + * Emitted when a call to read or write is called. + */ + void finished(); + +public slots: + + /** + * Use the fileFormat() to read fileName() into molecule(). + */ + void read(); + + /** + * Use the fileFormat() to write fileName() from molecule(). + */ + void write(); + +private: + Io::FileFormat* m_format; + Core::Molecule* m_molecule; + QString m_fileName; + QString m_error; + bool m_success; +}; + +} // namespace Avogadro + +#endif // AVOGADRO_BACKGROUNDFILEFORMAT_H diff --git a/avogadro/qtplugins/customelements/customelements.cpp b/avogadro/qtplugins/customelements/customelements.cpp index 6a561056fb..4805021d5b 100644 --- a/avogadro/qtplugins/customelements/customelements.cpp +++ b/avogadro/qtplugins/customelements/customelements.cpp @@ -15,11 +15,17 @@ ******************************************************************************/ #include "customelements.h" +#include "backgroundfileformat.h" #include +#include #include +#include +#include #include +#include +#include using Avogadro::QtGui::Molecule; @@ -27,17 +33,24 @@ namespace Avogadro { namespace QtPlugins { CustomElements::CustomElements(QObject* parent_) - : Avogadro::QtGui::ExtensionPlugin(parent_), m_molecule(nullptr), - m_reassignAction(new QAction(tr("Reassign &Custom Elements..."), this)) + : Avogadro::QtGui::ExtensionPlugin(parent_) + , m_molecule(nullptr) + , m_reassignUsingTool(nullptr) + , m_reassignFromFile(nullptr) + , m_fileReadThread(nullptr) + , m_threadedReader(nullptr) + , m_fileReadMolecule(nullptr) + , m_progressDialog(nullptr) { - connect(m_reassignAction, SIGNAL(triggered()), SLOT(reassign())); + m_reassignUsingTool = new QAction(tr("Using &Mapping tool..."), this); + m_reassignFromFile = new QAction(tr("From &File..."), this); + connect(m_reassignUsingTool, SIGNAL(triggered()), SLOT(reassign())); + connect(m_reassignFromFile, SIGNAL(triggered()), SLOT(importMapFile())); updateReassignAction(); } -CustomElements::~CustomElements() -{ -} +CustomElements::~CustomElements() {} QString CustomElements::description() const { @@ -46,12 +59,12 @@ QString CustomElements::description() const QList CustomElements::actions() const { - return QList() << m_reassignAction; + return QList() << m_reassignUsingTool << m_reassignFromFile; } QStringList CustomElements::menuPath(QAction*) const { - return QStringList() << tr("&Build"); + return QStringList() << tr("&Build") << tr("Assign &Custom Elements..."); } void CustomElements::setMolecule(QtGui::Molecule* mol) @@ -86,9 +99,123 @@ void CustomElements::reassign() } } +bool CustomElements::openFile(const QString& fileName, Io::FileFormat* reader) +{ + if (fileName.isEmpty() || reader == nullptr) { + delete reader; + return false; + } + + QString ident = QString::fromStdString(reader->identifier()); + + // Prepare the background thread to read in the selected file. + if (!m_fileReadThread) + m_fileReadThread = new QThread(qobject_cast(parent())); + if (m_threadedReader) + m_threadedReader->deleteLater(); + m_threadedReader = new BackgroundFileFormat(reader); + if (m_fileReadMolecule) + m_fileReadMolecule->deleteLater(); + m_fileReadMolecule = new Molecule(qobject_cast(parent())); + m_fileReadMolecule->setData("fileName", fileName.toLocal8Bit().data()); + m_threadedReader->moveToThread(m_fileReadThread); + m_threadedReader->setMolecule(m_fileReadMolecule); + m_threadedReader->setFileName(fileName); + + // Setup a progress dialog in case file loading is slow + m_progressDialog = new QProgressDialog(qobject_cast(parent())); + m_progressDialog->setRange(0, 0); + m_progressDialog->setValue(0); + m_progressDialog->setMinimumDuration(750); + m_progressDialog->setWindowTitle(tr("Reading File")); + m_progressDialog->setLabelText( + tr("Opening file '%1'\nwith '%2'").arg(fileName).arg(ident)); + /// @todo Add API to abort file ops + m_progressDialog->setCancelButton(nullptr); + connect(m_fileReadThread, SIGNAL(started()), m_threadedReader, SLOT(read())); + connect(m_threadedReader, SIGNAL(finished()), m_fileReadThread, SLOT(quit())); + connect(m_threadedReader, SIGNAL(finished()), + SLOT(backgroundReaderFinished())); + + // Start the file operation + m_fileReadThread->start(); + m_progressDialog->show(); + + return true; +} + +void CustomElements::backgroundReaderFinished() +{ + QString fileName = m_threadedReader->fileName(); + if (m_progressDialog->wasCanceled()) { + delete m_fileReadMolecule; + } else if (m_threadedReader->success()) { + if (!fileName.isEmpty()) { + m_fileReadMolecule->setData("fileName", fileName.toLocal8Bit().data()); + } else { + m_fileReadMolecule->setData("fileName", Core::Variant()); + } + setMapFromMolecule(m_fileReadMolecule); + } else { + QMessageBox::critical(qobject_cast(parent()), tr("File error"), + tr("Error while reading file '%1':\n%2") + .arg(fileName) + .arg(m_threadedReader->error())); + delete m_fileReadMolecule; + } + m_fileReadThread->deleteLater(); + m_fileReadThread = nullptr; + m_threadedReader->deleteLater(); + m_threadedReader = nullptr; + m_fileReadMolecule = nullptr; + m_progressDialog->hide(); + m_progressDialog->deleteLater(); + m_progressDialog = nullptr; +} + +void CustomElements::setMapFromMolecule(QtGui::Molecule* mol) +{ + if (mol->atomCount() != m_molecule->atomCount()) { + QMessageBox::critical( + qobject_cast(parent()), tr("Error"), + tr("Atom count mismatch.\nExpected %1 atoms, found %2.") + .arg(m_molecule->atomCount()) + .arg(mol->atomCount())); + } else { + size_t n = m_molecule->atomCount(); + for (size_t i = 0; i < n; ++i) { + m_molecule->atom(i).setAtomicNumber(mol->atom(i).atomicNumber()); + } + m_molecule->emitChanged(Molecule::Atoms | Molecule::Modified); + } +} + +void CustomElements::importMapFile() +{ + QSettings settings; + QString dir = settings.value("MainWindow/lastOpenDir").toString(); + + QtGui::FileFormatDialog::FormatFilePair reply = + QtGui::FileFormatDialog::fileToRead(qobject_cast(parent()), + tr("Open Molecule"), dir); + + if (reply.first == NULL) // user cancel + return; + + dir = QFileInfo(reply.second).absoluteDir().absolutePath(); + settings.setValue("MainWindow/lastOpenDir", dir); + + if (!openFile(reply.second, reply.first->newInstance())) { + QMessageBox::information( + qobject_cast(parent()), tr("Cannot open file"), + tr("Can't open supplied file %1").arg(reply.second)); + } +} + void CustomElements::updateReassignAction() { - m_reassignAction->setEnabled(m_molecule && m_molecule->hasCustomElements()); + m_reassignUsingTool->setEnabled(m_molecule); + m_reassignFromFile->setEnabled(m_molecule); } } // namespace QtPlugins diff --git a/avogadro/qtplugins/customelements/customelements.h b/avogadro/qtplugins/customelements/customelements.h index b53a9b397b..f6f57ac78b 100644 --- a/avogadro/qtplugins/customelements/customelements.h +++ b/avogadro/qtplugins/customelements/customelements.h @@ -19,7 +19,13 @@ #include +class QProgressDialog; +class QThread; + namespace Avogadro { + +class BackgroundFileFormat; + namespace QtPlugins { /** @@ -43,12 +49,21 @@ public slots: private slots: void moleculeChanged(unsigned int changes); void reassign(); + void importMapFile(); + void backgroundReaderFinished(); private: QtGui::Molecule* m_molecule; - QAction* m_reassignAction; + QAction* m_reassignUsingTool; + QAction* m_reassignFromFile; + QThread* m_fileReadThread; + BackgroundFileFormat* m_threadedReader; + QtGui::Molecule* m_fileReadMolecule; + QProgressDialog* m_progressDialog; void updateReassignAction(); + bool openFile(const QString& fileName, Io::FileFormat* reader); + void setMapFromMolecule(QtGui::Molecule* mol); }; } // namespace QtPlugins