diff --git a/avogadro/qtplugins/molecularproperties/molecularmodel.cpp b/avogadro/qtplugins/molecularproperties/molecularmodel.cpp index b854a33f6b..38de1d849f 100644 --- a/avogadro/qtplugins/molecularproperties/molecularmodel.cpp +++ b/avogadro/qtplugins/molecularproperties/molecularmodel.cpp @@ -7,11 +7,20 @@ #include #include +#include #include #include #include -#include +#include + +#include +#include +#include +#include + +#include +#include #include @@ -23,11 +32,110 @@ using QtGui::Molecule; MolecularModel::MolecularModel(QObject* parent) : QAbstractTableModel(parent), m_molecule(nullptr) { + m_network = new QNetworkAccessManager(this); + connect(m_network, SIGNAL(finished(QNetworkReply*)), this, + SLOT(updateNameReady(QNetworkReply*))); } void MolecularModel::setMolecule(QtGui::Molecule* molecule) { m_molecule = molecule; + // check if it has a pre-defined name + if (molecule) { + if (m_molecule->data("name").toString().empty()) + m_autoName = true; + else + m_autoName = false; + m_name = QString::fromStdString(molecule->data("name").toString()); + } +} + +QString MolecularModel::name() const +{ + // if we have a defined name + // or we're not ready to update + // then return the current name + if (!m_autoName || m_nameRequestPending) + return m_name; + + if (!m_molecule || m_molecule->atomCount() == 0) + return m_name; // empty + + // okay, kick off the update + m_name = tr("(pending)", "asking server for molecule name"); + m_nameRequestPending = true; + + std::string smiles; + Io::FileFormatManager::instance().writeString(*m_molecule, smiles, "smi"); + QString smilesString = QString::fromStdString(smiles); + smilesString.remove(QRegularExpression("\\s+.*")); + QString requestURL = + QString("https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/smiles/" + + QUrl::toPercentEncoding(smilesString) + "/json"); + m_network->get(QNetworkRequest(QUrl(requestURL))); + + // don't update again until we're ready - 5 seconds + QTimer::singleShot(5000, this, SLOT(canUpdateName())); + + return m_name; +} + +void MolecularModel::canUpdateName() +{ + m_nameRequestPending = false; +} + +void MolecularModel::updateNameReady(QNetworkReply* reply) +{ + // Read in all the data + if (!reply->isReadable()) { + reply->deleteLater(); + m_name = tr("unknown molecule"); + return; + } + + // check if the data came through + QByteArray data = reply->readAll(); + if (data.contains("Error report") || data.contains("

")) { + reply->deleteLater(); + m_name = tr("unknown molecule"); + return; + } + + // parse the JSON + // https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/smiles/…/json + + // PC_Compounds[0].props + // iterate // get "urn" / "name" == "Markup" and "Preferred" + // .. get "value" / "sval" + + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject obj = doc.object(); + QJsonArray array = obj["PC_Compounds"].toArray(); + if (array.isEmpty()) { + reply->deleteLater(); + m_name = tr("unknown molecule"); + return; + } + obj = array.first().toObject(); + array = obj["props"].toArray(); // props is an array of objects + for (const QJsonValue& value : array) { + obj = value.toObject(); + QJsonObject urn = obj["urn"].toObject(); + + if (urn["name"].toString() == "Markup") { + // HTML version for dialog + QJsonObject nameValue = obj["value"].toObject(); + m_name = nameValue["sval"].toString(); + } else if (urn["name"].toString() == "Preferred") { + // save this text version for files and copy/paste + QJsonObject nameValue = obj["value"].toObject(); + m_molecule->setData("name", nameValue["sval"].toString().toStdString()); + m_name = nameValue["sval"].toString(); + } + } + + reply->deleteLater(); } int MolecularModel::rowCount(const QModelIndex& parent) const @@ -59,7 +167,7 @@ int MolecularModel::columnCount(const QModelIndex& parent) const return 1; // values } -QString formatFormula(Molecule* m) +QString formatFormula(Molecule* molecule) { QString formula = QString::fromStdString(molecule->formula()); QRegularExpression digitParser("(\\d+)"); @@ -76,9 +184,11 @@ QString formatFormula(Molecule* m) } // add total charge as a superscript - int charge = m->totalCharge(); - if (charge != 0) + int charge = molecule->totalCharge(); + if (charge < 0) formula += QString("%1").arg(charge); + else if (charge > 0) + formula += QString("+%1").arg(charge); return formula; } @@ -107,7 +217,7 @@ QVariant MolecularModel::data(const QModelIndex& index, int role) const return QVariant(); if (row == Name) { - return QString::fromStdString(m_molecule->data("name").toString()); + return this->name(); } else if (row == Mass) { return m_molecule->mass(); } else if (row == Formula) { diff --git a/avogadro/qtplugins/molecularproperties/molecularmodel.h b/avogadro/qtplugins/molecularproperties/molecularmodel.h index 98e75d9d6e..eef19f160f 100644 --- a/avogadro/qtplugins/molecularproperties/molecularmodel.h +++ b/avogadro/qtplugins/molecularproperties/molecularmodel.h @@ -11,6 +11,9 @@ #include #include +class QNetworkAccessManager; +class QNetworkReply; + #include namespace Avogadro { @@ -35,6 +38,8 @@ class MolecularModel : public QAbstractTableModel public slots: void updateTable(unsigned int flags); + void updateNameReady(QNetworkReply* reply); // reply from network + void canUpdateName(); // don't do it too often public: explicit MolecularModel(QObject* parent = 0); @@ -50,8 +55,15 @@ public slots: void setMolecule(QtGui::Molecule* molecule); + QString name() const; + private: - QtGui::Molecule* m_molecule; + QtGui::Molecule* m_molecule = nullptr; + mutable QString m_name; + mutable bool m_autoName = true; + mutable bool m_nameRequestPending = false; + + QNetworkAccessManager* m_network = nullptr; }; } // end namespace Avogadro