diff --git a/avogadro/mainwindow.cpp b/avogadro/mainwindow.cpp index db36be55..8de2c819 100644 --- a/avogadro/mainwindow.cpp +++ b/avogadro/mainwindow.cpp @@ -287,6 +287,10 @@ MainWindow::MainWindow(const QStringList& fileNames, bool disableSettings) &MainWindow::setActiveTool); connect(extension, &QtGui::ExtensionPlugin::requestActiveDisplayTypes, this, &MainWindow::setActiveDisplayTypes); + connect(extension, &QtGui::ExtensionPlugin::registerCommand, this, + &MainWindow::registerExtensionCommand); + extension->registerCommands(); + buildMenu(extension); m_extensions.append(extension); } @@ -1486,6 +1490,38 @@ bool MainWindow::exportFile(bool async) return saveFileAs(reply.second, reply.first->newInstance(), async); } +bool MainWindow::exportFile(const QString& fileName, bool async) +{ + if (fileName.isEmpty()) { + return false; + } + + // Create one of our writers to save the file: + FileFormat* writer = nullptr; + + std::vector writers = + Io::FileFormatManager::instance().fileFormatsFromFileExtension( + QFileInfo(fileName).suffix().toStdString(), + FileFormat::File | FileFormat::Write); + + if (!writers.empty()) { + writer = writers[0]->newInstance(); + return saveFileAs(fileName, writer, async); + } + + return false; +} + +std::string MainWindow::exportString(const std::string& format) +{ + std::string output; + auto* mol = qobject_cast(m_moleculeModel->activeMolecule()); + + Io::FileFormatManager::instance().writeString(*mol, output, format); + + return output; +} + bool MainWindow::saveFileAs(const QString& fileName, Io::FileFormat* writer, bool async) { @@ -1849,7 +1885,7 @@ void MainWindow::buildMenu() #ifndef Q_OS_MAC action->setIcon(QIcon::fromTheme("document-export")); #endif - connect(action, &QAction::triggered, this, &MainWindow::exportFile); + connect(action, SIGNAL(triggered()), this, SLOT(exportFile())); // Export graphics action = new QAction(tr("&Graphics…"), this); m_menuBuilder->addAction(exportPath, action, 100); @@ -2062,6 +2098,10 @@ void MainWindow::buildTools() m_toolToolBar->addAction(action); connect(action, &QAction::triggered, this, &MainWindow::toolActivated); + connect(toolPlugin, &ToolPlugin::registerCommand, this, + &MainWindow::registerToolCommand); + toolPlugin->registerCommands(); + ++index; } @@ -2380,4 +2420,47 @@ void MainWindow::clearQueuedFiles() } } +void MainWindow::registerToolCommand(QString command, QString description) +{ + if (m_toolCommandMap.contains(command)) + return; + + m_commandDescriptionsMap.insert(command, description); + + // get the calling plugin + auto* tool(qobject_cast(sender())); + if (!tool) + return; + + m_toolCommandMap.insert(command, tool); +} + +void MainWindow::registerExtensionCommand(QString command, QString description) +{ + if (m_extensionCommandMap.contains(command)) + return; + + m_commandDescriptionsMap.insert(command, description); + + // get the calling plugin + auto* extension(qobject_cast(sender())); + if (!extension) + return; + + m_extensionCommandMap.insert(command, extension); +} + +bool MainWindow::handleCommand(const QString& command, + const QVariantMap& options) +{ + if (m_toolCommandMap.contains(command)) { + auto* tool = m_toolCommandMap.value(command); + return tool->handleCommand(command, options); + } else if (m_extensionCommandMap.contains(command)) { + auto* extension = m_extensionCommandMap.value(command); + return extension->handleCommand(command, options); + } + return false; +} + } // End of Avogadro namespace diff --git a/avogadro/mainwindow.h b/avogadro/mainwindow.h index 906a8d28..27bcc140 100644 --- a/avogadro/mainwindow.h +++ b/avogadro/mainwindow.h @@ -7,6 +7,7 @@ #define AVOGADRO_MAINWINDOW_H #include +#include #include #ifdef QTTESTING @@ -92,6 +93,25 @@ public slots: void exportGraphics(QString fileName); + /** + * Export a file, using the full selection of formats capable of writing. + * The format will be guessed based on the filename extension. + * If @a async is true (default), the file is saved asynchronously. + * @return If @a async is true, this function returns true if a suitable + * writer was found (not if the write was successful). If @a async is + * false, the return value indicates whether or not the file was written + * successfully. + */ + bool exportFile(const QString& fileName, bool async = true); + + /** + * Export a file, using the full selection of formats capable of writing. + * Will use @a format to determine the file format to use. + * @return String-representation of the exported file, or an empty string if + * the export failed. + */ + std::string exportString(const std::string& format); + /** * Move @a fileName as a plugin script (i.e. put it in the correct dir) */ @@ -125,6 +145,15 @@ public slots: m_localeCodes = codes; } + /** + * Handle script commands + * @param command The command to execute + * @param options The options to the command + * + * @return True if the command was handled, false otherwise + */ + bool handleCommand(const QString& command, const QVariantMap& options); + signals: /** * Emitted when the active molecule in the application has changed. @@ -181,7 +210,7 @@ protected slots: /** * Save the current molecule to its current fileName. If it is not a standard * format, offer to export and warn about possible data loss. - * If @a async is true (default), the file is loaded asynchronously. + * If @a async is true (default), the file is saved asynchronously. * @return If @a async is true, this function returns true if a suitable * writer was found (not if the write was successful). If @a async is * false, the return value indicates whether or not the file was written @@ -192,7 +221,7 @@ protected slots: /** * Prompt for a file location, and attempt to save the active molecule to the * specified location. - * If @a async is true (default), the file is loaded asynchronously. + * If @a async is true (default), the file is saved asynchronously. * @return If @a async is true, this function returns true if a suitable * writer was found (not if the write was successful). If @a async is * false, the return value indicates whether or not the file was written @@ -202,7 +231,7 @@ protected slots: /** * Export a file, using the full selection of formats capable of writing. - * If @a async is true (default), the file is loaded asynchronously. + * If @a async is true (default), the file is saved asynchronously. * @return If @a async is true, this function returns true if a suitable * writer was found (not if the write was successful). If @a async is * false, the return value indicates whether or not the file was written @@ -213,7 +242,7 @@ protected slots: /** * If specified, use the FileFormat @a writer to save the file. This method * takes ownership of @a writer and will delete it before returning. - * If @a async is true (default), the file is loaded asynchronously. + * If @a async is true (default), the file is saved asynchronously. * @return If @a async is true, this function returns true if the write begins * successfully (not if the writer completes). If @a async is * false, the return value indicates whether or not the file was written @@ -266,6 +295,10 @@ private slots: void finishUpdateRequest(QNetworkReply*); + void registerToolCommand(QString command, QString description); + + void registerExtensionCommand(QString command, QString description); + /** * @brief Register file formats from extensions when ready. */ @@ -397,6 +430,11 @@ private slots: QDockWidget* m_moleculeDock; QList m_tools; QList m_extensions; + // map from script commands to tools and extensions + QMap m_toolCommandMap; + QMap m_extensionCommandMap; + // used for help - provide description for a command + QMap m_commandDescriptionsMap; QAction* m_undo; QAction* m_redo; diff --git a/avogadro/rpclistener.cpp b/avogadro/rpclistener.cpp index 0eb24507..78a42773 100644 --- a/avogadro/rpclistener.cpp +++ b/avogadro/rpclistener.cpp @@ -184,6 +184,16 @@ void RpcListener::messageReceived(const MoleQueue::Message& message) MoleQueue::Message response = message.generateResponse(); response.setResult(true); response.send(); + } else if (method == "exportFile") { + // Save to the supplied file name + QString filename = params["fileName"].toString(); + + bool result = m_window->exportFile(filename); + + // set response + MoleQueue::Message response = message.generateResponse(); + response.setResult(result); + response.send(); } else if (method == "loadMolecule") { // get molecule data and format string content = params["content"].toString().toStdString(); @@ -211,14 +221,25 @@ void RpcListener::messageReceived(const MoleQueue::Message& message) .arg(QString::fromStdString(FileFormatManager::instance().error()))); errorMessage.send(); } - } else { // invalid method - MoleQueue::Message errorMessage = message.generateErrorResponse(); - errorMessage.setErrorCode(-32601); - errorMessage.setErrorMessage("Method not found"); - QJsonObject errorDataObject; - errorDataObject.insert("request", message.toJsonObject()); - errorMessage.setErrorData(errorDataObject); - errorMessage.send(); + } else { // ask the main window to handle the message + QVariantMap options = params.toVariantMap(); + bool success = m_window->handleCommand(method, options); + + if (success) { + // send response + MoleQueue::Message response = message.generateResponse(); + response.setResult(true); + response.send(); + } else { + // send error response + MoleQueue::Message errorMessage = message.generateErrorResponse(); + errorMessage.setErrorCode(-32601); + errorMessage.setErrorMessage("Method not found"); + QJsonObject errorDataObject; + errorDataObject.insert("request", message.toJsonObject()); + errorMessage.setErrorData(errorDataObject); + errorMessage.send(); + } } }