File indexing completed on 2024-04-28 07:28:58

0001 /*
0002     SPDX-FileCopyrightText: 2006 Carsten Niehaus <cniehaus@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 Marcus D. Hanwell <marcus@cryos.org>
0004     SPDX-FileCopyrightText: 2016 Andreas Cord-Landwehr <cordlandwehr@kde.org>
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "moleculeview.h"
0009 
0010 #include <avogadro/qtgui/elementtranslator.h>
0011 #include <avogadro/qtgui/periodictableview.h>
0012 #include <avogadro/qtgui/scenepluginmodel.h>
0013 #include <avogadro/qtgui/toolplugin.h>
0014 
0015 #include <QDebug>
0016 #include <QFileInfo>
0017 #include <QGLFormat>
0018 #include <QUrl>
0019 
0020 #include <KIO/Job>
0021 #include <KJob>
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <KNS3/QtQuickDialogWrapper>
0025 
0026 #include "iowrapper.h"
0027 
0028 #include <openbabel/mol.h>
0029 #include <openbabel/obiter.h>
0030 // This is needed to ensure that the forcefields are set up right with GCC vis
0031 #ifdef __KDE_HAVE_GCC_VISIBILITY
0032 #define HAVE_GCC_VISIBILITY
0033 #endif
0034 #include <KGuiItem>
0035 #include <QDialogButtonBox>
0036 #include <QFileDialog>
0037 #include <QPushButton>
0038 #include <QStandardPaths>
0039 #include <QVBoxLayout>
0040 #include <openbabel/forcefield.h>
0041 
0042 using namespace OpenBabel;
0043 using namespace Avogadro::QtGui;
0044 
0045 MoleculeDialog::MoleculeDialog(QWidget *parent)
0046     : QDialog(parent)
0047     , m_path(QString())
0048     , m_periodicTable(nullptr)
0049 {
0050     // use multi-sample (anti-aliased) OpenGL if available
0051     QGLFormat defFormat = QGLFormat::defaultFormat();
0052     defFormat.setSampleBuffers(true);
0053     QGLFormat::setDefaultFormat(defFormat);
0054 
0055     setWindowTitle(i18nc("@title:window", "Molecular Editor"));
0056     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0057     QWidget *mainWidget = new QWidget(this);
0058     QVBoxLayout *mainLayout = new QVBoxLayout;
0059     setLayout(mainLayout);
0060     mainLayout->addWidget(mainWidget);
0061     QPushButton *user1Button = new QPushButton;
0062     buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
0063     QPushButton *user2Button = new QPushButton;
0064     buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
0065     QPushButton *user3Button = new QPushButton;
0066     buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole);
0067     connect(buttonBox, &QDialogButtonBox::rejected, this, &MoleculeDialog::reject);
0068 
0069     user1Button->setDefault(true);
0070 
0071     KGuiItem::assign(user1Button, KGuiItem(i18n("Load Molecule")));
0072 
0073     KGuiItem::assign(user2Button, KGuiItem(i18n("Download New Molecules")));
0074 
0075     KGuiItem::assign(user3Button, KGuiItem(i18n("Save Molecule")));
0076 
0077     ui.setupUi(mainWidget);
0078 
0079     // Attempt to set up the UFF forcefield
0080     //     m_forceField = OBForceField::FindForceField("UFF");
0081     //     if (!m_forceField) {
0082     //         ui.optimizeButton->setEnabled(false);
0083     //     }
0084 
0085     ui.styleCombo->addItems({"Ball and Stick", "Licorice", "Van der Waals", "Van der Waals (AO)", "Wireframe"});
0086     connect(ui.styleCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MoleculeDialog::slotUpdateScenePlugin);
0087     slotUpdateScenePlugin();
0088 
0089     connect(ui.tabWidget, &QTabWidget::currentChanged, this, &MoleculeDialog::setViewEdit);
0090 
0091     // Editing parameters
0092     // commented out until we find new API for pumbling to OpenBabel
0093     //     connect(ui.optimizeButton, &QPushButton::clicked,
0094     //             this, &MoleculeDialog::slotGeometryOptimize);
0095     connect(ui.clearDrawingButton, &QPushButton::clicked, this, &MoleculeDialog::clearAllElementsInEditor);
0096 
0097     connect(ui.glWidget->molecule(), &Avogadro::QtGui::Molecule::changed, this, &MoleculeDialog::slotUpdateStatistics);
0098 
0099     connect(user1Button, &QPushButton::clicked, this, &MoleculeDialog::slotLoadMolecule);
0100     connect(user2Button, &QPushButton::clicked, this, &MoleculeDialog::slotDownloadNewStuff);
0101     connect(user3Button, &QPushButton::clicked, this, &MoleculeDialog::slotSaveMolecule);
0102 
0103     mainLayout->addWidget(buttonBox);
0104 
0105     // Check that we have managed to load up some tools and engines
0106     int nTools = ui.glWidget->tools().size();
0107     if (!nTools) {
0108         QString error = i18n("No tools loaded - it is likely that the Avogadro plugins could not be located.");
0109         // use parent as parent for the messagebix, as this dialog is not shown yet
0110         // and thus not known to the window system to position to dialog relative to it
0111         KMessageBox::error(parent, error, i18n("Kalzium"));
0112     }
0113 
0114     // objectName is also used in Avogadro2 for identifying tools
0115     foreach (auto *tool, ui.glWidget->tools()) {
0116         if (tool->objectName() == QLatin1String("Editor")) {
0117             ui.editTabLayout->insertWidget(0, tool->toolWidget());
0118             break;
0119         }
0120     }
0121 }
0122 
0123 void MoleculeDialog::slotLoadMolecule()
0124 {
0125     // Check that we have managed to load up some tools and engines
0126     int nTools = ui.glWidget->tools().size();
0127 
0128     if (!nTools) {
0129         QString error = i18n(
0130             "No tools loaded - it is likely that the Avogadro plugins could not be located. "
0131             "No molecules can be viewed until this issue is resolved.");
0132         KMessageBox::information(this, error);
0133     }
0134 
0135     m_path = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("data/molecules/"), QStandardPaths::LocateDirectory);
0136 
0137     QString commonMoleculeFormats = i18n("Common molecule formats");
0138     QString allFiles = i18n("All files");
0139 
0140     QString filename = QFileDialog::getOpenFileName(
0141                        this,
0142                        i18n("Choose a file to open"),
0143                        m_path,
0144                        commonMoleculeFormats + "(*.cml *.xyz *.ent *.pdb *.alc *.chm *.cdx *.cdxml *.c3d1 *.c3d2"
0145                        " *.gpr *.mdl *.mol *.sdf *.sd *.crk3d *.cht *.dmol *.bgf"
0146                        " *.gam *.inp *.gamin *.gamout *.tmol *.fract"
0147                        " *.mpd *.mol2);;" + allFiles + "(*)");
0148 
0149     loadMolecule(filename);
0150 }
0151 
0152 void MoleculeDialog::slotUpdateScenePlugin()
0153 {
0154     const QString text = ui.styleCombo->currentText();
0155     for (int i = 0; i < ui.glWidget->sceneModel().rowCount(QModelIndex()); ++i) {
0156         QModelIndex index = ui.glWidget->sceneModel().index(i, 0);
0157         if (text == ui.glWidget->sceneModel().data(index, Qt::DisplayRole)) {
0158             ui.glWidget->sceneModel().setData(index, Qt::Checked, Qt::CheckStateRole);
0159         } else {
0160             ui.glWidget->sceneModel().setData(index, Qt::Unchecked, Qt::CheckStateRole);
0161         }
0162     }
0163 }
0164 
0165 void MoleculeDialog::loadMolecule(const QString &filename)
0166 {
0167     if (filename.isEmpty()) {
0168         return;
0169     }
0170 
0171     // 1. workaround for missing copy-constructor: fixed in Avogadro2 > 0.9
0172     // 2. another workaround for broken copy-constructor that does not
0173     //    initialize the m_undoMolecule private member variable;
0174     //    this molecule should be created on the heap instead of the stack
0175     auto molecule_ptr = IoWrapper::readMolecule(filename);
0176     if (!molecule_ptr) {
0177         KMessageBox::error(this, i18n("Could not load molecule"), i18n("Loading the molecule failed."));
0178         return;
0179     }
0180 
0181     m_molecule = *molecule_ptr;
0182 
0183     if (m_molecule.atomCount() != 0) {
0184         disconnect(ui.glWidget->molecule(), nullptr, this, nullptr);
0185         ui.glWidget->setMolecule(&m_molecule);
0186         ui.glWidget->update();
0187         slotUpdateStatistics();
0188         connect(&m_molecule, &Avogadro::QtGui::Molecule::changed, this, &MoleculeDialog::slotUpdateStatistics);
0189     }
0190     ui.glWidget->resetCamera();
0191     ui.glWidget->updateScene();
0192 }
0193 
0194 void MoleculeDialog::clearAllElementsInEditor()
0195 {
0196     ui.glWidget->molecule()->clearBonds();
0197     ui.glWidget->molecule()->clearAtoms();
0198     ui.glWidget->updateScene();
0199 }
0200 
0201 void MoleculeDialog::slotSaveMolecule()
0202 {
0203     QString commonMoleculeFormats = i18n("Common molecule formats");
0204     QString allFiles = i18n("All files");
0205     QString filename = QFileDialog::getSaveFileName(this,
0206                                                     i18n("Choose a file to save to"),
0207                                                     QString(),
0208                                                     commonMoleculeFormats
0209                                                         + QStringLiteral(" (*.cml *.xyz *.ent *.pdb *.alc *.chm *.cdx *.cdxml *.c3d1 *.c3d2"
0210                                                                          " *.gpr *.mdl *.mol *.sdf *.sd *.crk3d *.cht *.dmol *.bgf"
0211                                                                          " *.gam *.inp *.gamin *.gamout *.tmol *.fract"
0212                                                                          " *.mpd *.mol2);;")
0213                                                         + allFiles + QStringLiteral(" (*)"));
0214 
0215     if (!filename.contains(QLatin1String("."))) {
0216         filename.append(QLatin1String(".cml"));
0217     }
0218 
0219     IoWrapper io;
0220     io.writeMolecule(filename, ui.glWidget->molecule());
0221 }
0222 
0223 void MoleculeDialog::setViewEdit(int mode)
0224 {
0225     if (mode == 0) {
0226         ui.glWidget->setActiveTool(QStringLiteral("Navigator"));
0227     } else if (mode == 1) {
0228         ui.glWidget->setActiveTool(QStringLiteral("Editor"));
0229     } else if (mode == 2) {
0230         ui.glWidget->setActiveTool(QStringLiteral("MeasureTool"));
0231     }
0232 }
0233 
0234 MoleculeDialog::~MoleculeDialog()
0235 {
0236 }
0237 
0238 void MoleculeDialog::slotUpdateStatistics()
0239 {
0240     Molecule *mol = ui.glWidget->molecule();
0241     if (!mol) {
0242         return;
0243     }
0244     const std::string name = mol->data(QStringLiteral("name").toStdString()).toString();
0245     ui.nameLabel->setText(QString::fromStdString(name));
0246     ui.weightLabel->setText(
0247         i18nc("This 'u' stands for the chemical unit (u for 'units'). Most likely this does not need to be translated at all!", "%1 u", mol->mass()));
0248     ui.formulaLabel->setText(IoWrapper::getPrettyFormula(mol));
0249     ui.glWidget->update();
0250 }
0251 
0252 void MoleculeDialog::slotDownloadNewStuff()
0253 {
0254     qDebug() << "Kalzium new stuff";
0255 
0256     KNS3::QtQuickDialogWrapper *dialog = new KNS3::QtQuickDialogWrapper(QStringLiteral("kalzium.knsrc"), this);
0257     dialog->open();
0258     connect(dialog, &KNS3::QtQuickDialogWrapper::closed, this, [this, dialog] {
0259         // list of changed entries
0260         QString destinationDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
0261         QDir dir(destinationDir);
0262         if (!dir.exists()) {
0263             destinationDir = QDir::homePath();
0264         }
0265 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0266         const QList<KNSCore::EntryInternal> entries = dialog->changedEntries();
0267 #else
0268         const QList<KNSCore::Entry> entries = dialog->changedEntries();
0269 #endif
0270         bool anyError = false;
0271         bool anySuccess = false;
0272         bool moreOneInstalledFile = false;
0273         QString exactlyOneFile;
0274         foreach (const auto &entry, entries) {
0275             // care only about installed ones
0276             if (entry.status() == KNS3::Entry::Installed) {
0277                 qDebug() << "Changed Entry: " << entry.installedFiles();
0278                 foreach (const QString &origFile, entry.installedFiles()) {
0279                     const QString destFile = destinationDir + '/' + QFileInfo(origFile).fileName();
0280                     KJob *job = KIO::file_move(QUrl::fromLocalFile(origFile), QUrl::fromLocalFile(destFile));
0281                     const bool success = job->exec();
0282                     if (success) {
0283                         if (exactlyOneFile.isEmpty()) {
0284                             exactlyOneFile = destFile;
0285                         } else {
0286                             moreOneInstalledFile = true;
0287                         }
0288                         anySuccess = true;
0289                     } else {
0290                         KMessageBox::error(this, i18n("Failed to download molecule %1 to %2.", entry.name(), destFile));
0291                         anyError = true;
0292                     }
0293                 }
0294             }
0295         }
0296         if (anySuccess) {
0297             if (anyError) {
0298                 KMessageBox::information(this, i18n("The molecules that could be downloaded have been saved to %1.", destinationDir));
0299             } else {
0300                 KMessageBox::information(this, i18n("The molecules have been saved to %1.", destinationDir));
0301             }
0302             if (!moreOneInstalledFile) {
0303                 loadMolecule(exactlyOneFile);
0304             }
0305         }
0306     });
0307 }
0308 
0309 // TODO there is currently no API to perform the necessary OpenBabel-Avogadro
0310 //      conversions, after the migration to Avogadro2; at least with v0.9
0311 void MoleculeDialog::slotGeometryOptimize()
0312 {
0313     //     // Perform a geometry optimization
0314     //     if (!m_forceField) {
0315     //         return;
0316     //     }
0317     //
0318     //     Molecule* molecule = ui.glWidget->molecule();
0319     //     OpenBabel::OBMol obmol;//(molecule->OBMol());
0320     //
0321     //     // Warn the user if the force field cannot be set up for the molecule
0322     //     if (!m_forceField->Setup(obmol)) {
0323     //         KMessageBox::error(this,
0324     //                         i18n("Could not set up force field for this molecule"),
0325     //                         i18n("Kalzium"));
0326     //         return;
0327     //     }
0328     //
0329     //     // Reasonable default values for most users
0330     //     m_forceField->SteepestDescentInitialize(500, 1.0e-5);
0331     //     // Provide some feedback as the optimization runs
0332     //     while (m_forceField->SteepestDescentTakeNSteps(5)) {
0333     //         m_forceField->UpdateCoordinates(obmol);
0334     //         molecule->setOBMol(&obmol);
0335     //         molecule->update();
0336     //     }
0337 }
0338 
0339 #include "moc_moleculeview.cpp"