File indexing completed on 2025-02-16 03:30:07
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"