1 /*
2  ----------------------------------------------------------------------------
3  "THE BEER-WARE LICENSE" (Revision 42):
4  <dkratzert@gmx.de> wrote this file. As long as you retain
5  this notice you can do whatever you want with this stuff. If we meet some day,
6  and you think this stuff is worth it, you can buy me a beer in return.
7  Daniel Kratzert
8  ----------------------------------------------------------------------------
9 */
10 
11 //#include "bits/stdc++.h"
12 #include <QtGui>
13 #include <QDir>
14 #include <QNetworkAccessManager>
15 #include "dsrgui.h"
16 #include "window.h"
17 #include "dsrglwindow.h"
18 #include "dsreditwindow.h"
19 #include "deprecation.h"
20 #include "itsme.h"
21 
22 #if defined(Q_OS_WIN) || defined(Q_WS_WIN32)
23    static const QString mysystem = "win";
24 #else
25    static const QString mysystem = "unix";
26 #endif
27 
28 /*
29 Explanations
30 
31 - This GUI is for DSR https://dkratzert.de/dsr.html
32 - DSR stores the information for pre-defined molecular fragments and their
33   restraints in a text database in $DSR_DB_DIR.
34 - This GUI uses that information to provide a convenient interface for DSR.
35 - DSR is controlled by a special command in the SHELXL res file starting with REM DSR ...
36   and by commandline options.
37 - A special command line option -ah tells DSR to print the information about a
38   fragment in a special format for this GUI.
39   It is a list of html-like tags <tag> data </tag> with ;; separated values containing for
40   example the unit cell information and coordinates of the fragment. The first line
41   contains the version number of DSR.
42 - The GUI first runs dsr with -lc to get a list of all fragment names in the database.
43   A mouse click on a fragment in the list invokes "dsr -ah name" to get the details of
44   the selected fragment (and stores it in struct DSRMol). This details are also used to
45   edit the fragment ("edit fragment" button).
46 - The text widget below the search field displays some status output from DSR,
47   especially the text output from DSR during the fragment transfer/fit to the target structure.
48 
49 - merging: go into shelxle-kratzert and svn merge -r 165:HEAD /Users/daniel/Downloads/shelxle-trunk
50        "-r (latest own commit):HEAD"    svn merge -r 165:HEAD d:\downloads\shelxle-svn\trunk
51 */
52 
53 
54 /*
55  * TODO:
56 */
57 
58 
DSRGui(Molecule * mole,QString shelxlPath,Window * parent)59 DSRGui::DSRGui(Molecule *mole, QString shelxlPath, Window *parent): QWidget(parent) {
60   m_shelxle = parent;
61   m_molecule = mole;
62   m_shelxlPath = shelxlPath;
63   this->setWindowFlags(Qt::Window);
64   setWindowTitle(QString(tr("DSR GUI")));
65   if (mysystem == QString("win")) {
66     dsrpath = getDSRDir()+"/dsr.bat";
67     dsr_db_path = getDSRdbDir();
68   } else if (mysystem == QString("unix")){
69     dsrpath = QDir::cleanPath(getDSRDir()+"/dsr");
70     dsr_db_path = QDir::cleanPath(getDSRdbDir());
71   }
72   this->hide();
73   dsrVersion = "";
74   replace = false;
75   norefine = false; // this has to stay here
76   runext = false;
77   invert = false;
78   rigid = false;
79   cf3 = false;
80   splitmode = false;
81   fragmentsList = new QVector<QStringList>;
82   fragmentNameTag.clear();
83   header = new DSRMol;
84   mainVLayout = new QVBoxLayout(this);
85   editLayout = new QHBoxLayout;
86   chooserLayout = new QGridLayout;
87   searchLayout = new QHBoxLayout;
88   partLayout = new QHBoxLayout;
89   fvarLayout = new QHBoxLayout;
90   occLayout = new QHBoxLayout;
91   resclassLayout = new QHBoxLayout;
92   optionsLayout1 = new QVBoxLayout;
93   optionsLayout2 = new QVBoxLayout;
94   optionsLayout3 = new QVBoxLayout;
95   groupBox1 = new QGroupBox();
96   groupBox2 = new QGroupBox();
97   residueOptionsBox = new QGroupBox(tr("Use a Residue"));
98   buttonLayout = new QVBoxLayout;
99   optionboxes = new QHBoxLayout;
100   fitDSRButton = new QPushButton(tr("Fit Fragment!"));
101   splitButton = new QPushButton(tr("Split Atoms"));
102   fitDSRButton->setStyleSheet("QPushButton {"
103                               "font-weight: bold; "
104                               "color: #209920}");
105   exportFragButton = new QPushButton(tr("Export Fragment"));
106   editButton = new QPushButton(tr("Edit Fragment"));
107   editButton->setStyleSheet("QPushButton {"
108                             "font-weight: bold; "
109                             "color: rgb(8, 82, 182)}");
110   openLastButton = new QPushButton(tr("Restore last .res"));
111   runExtBox = new QCheckBox(tr("External Restraints"));
112   invertFragBox = new QCheckBox(tr("Invert Coordinates"));
113   dfixBox = new QCheckBox(tr("Calculate DFIX"));
114   rigidBox = new QCheckBox(tr("Rigid Group (no restraints)"));
115   replaceModeBox = new QCheckBox(tr("Replace Target"));
116   splitModeBox = new QCheckBox(tr("Split Target"));
117   searchLabel = new QLabel(tr("Search:"));
118   SearchInp = new QLineEdit;//(tr("Search a fragment here"));
119   partLabel = new QLabel(tr("PART"));
120   fvarLabel = new QPushButton(tr("Free Variable"));  // Is a QPushbutton to have a clicked() signal
121   fvarLabel->setStyleSheet("QPushButton {border-style: outset; border-width: 0px;}");  // remove border to appear like a label
122   occLabel = new QLabel(tr("Occupancy"));
123   classLabel = new QLabel(tr("Residue Class"));
124   usermanualLabel = new QLabel(tr("<a href=\"https://github.com/dkratzert/DSR/raw/master/manuals/DSR-manual.pdf\"> DSR User Manual </a>"));
125   usermanualLabel->setOpenExternalLinks(true);
126   resnumbertext = new QString(tr("A residue number will be\nchosen automatically."));
127   resiTextLabel = new QLabel();
128   resiTextLabel->setText(*resnumbertext);
129   outtext = new QTextBrowser;
130   outtext->setOpenExternalLinks(true);
131   info = new QTextBrowser;
132   //this->move(-this->width(), -this->height());
133   info->hide();
134   info->setMinimumWidth(235);
135   //info->setMaximumWidth(235);
136   fragmentTableView = new QTableView;
137   occEdit = new QLineEdit;
138   partspinner = new QSpinBox;
139   fvarspinner = new QSpinBox;
140   resiclassEdit = new QLineEdit;
141   // layout for the interactions
142   optionboxes->addWidget(groupBox1);
143   optionboxes->addStretch();
144   optionboxes->addWidget(groupBox2);
145   optionboxes->addStretch();
146   optionboxes->addWidget(residueOptionsBox);
147   optionboxes->addStretch();
148   optionboxes->addLayout(buttonLayout);
149   buttonLayout->setSizeConstraint(QLayout::SetMaximumSize);
150   // The search field:
151   searchLayout->addWidget(searchLabel);
152   searchLayout->addWidget(SearchInp);
153   SearchInp->setFocus();  // searching should be the default
154   SearchInp->setMinimumWidth(getCharWidth(9));
155   fragmentNameTag.clear();
156   // The OpenGL widget with the molecule:
157   mygl = new DSRGlWindow(this, m_molecule, *header, fragmentNameTag);
158   m_molecule->loadSettings();
159   mygl->showFit = new QCheckBox(tr("show fitted target overlay"));
160   mygl->showFit->setChecked(true);
161   mygl->showFitLabel = new QCheckBox(tr("labels on target overlay"));
162   mygl->showFitLabel->setChecked(true);
163   // Table of fragments and 3D window:
164   chooserLayout->addWidget(fragmentTableView,  0, 0, 1, 1);
165   chooserLayout->addWidget(mygl,               0, 1, 1, 2);
166   chooserLayout->addWidget(mygl->showFit,      2, 1, 1, 1);
167   chooserLayout->addWidget(mygl->showFitLabel, 2, 2, 1, 1);
168   chooserLayout->addLayout(searchLayout,       2, 0, 1, 1);
169   chooserLayout->setColumnStretch(0, 4);
170   chooserLayout->setColumnStretch(1, 3);
171   chooserLayout->setRowStretch(2, 0);
172   // Three main layours:
173   mainVLayout->addLayout(chooserLayout, 3);
174   mainVLayout->addLayout(editLayout, 2);
175   mainVLayout->addLayout(optionboxes, 0);
176   editLayout->addWidget(outtext);
177   editLayout->addWidget(info);
178   editLayout->setStretchFactor(outtext, 2);
179   editLayout->setStretchFactor(info, 1);
180   outtext->setReadOnly(true);
181   QFont font("Courier");
182   font.setStyleHint(QFont::TypeWriter);
183   outtext->setFont(font);
184   outtext->setMinimumHeight(170);
185   // Layouts for optionbox1
186   partLayout->addWidget(partspinner);
187   partLayout->addWidget(partLabel);
188   partLayout->addStretch();
189   partspinner->setRange(-99, 99);
190   partspinner->setValue(0);
191   fvarLayout->addWidget(fvarspinner);
192   fvarLayout->addWidget(fvarLabel);
193   fvarLayout->addStretch();
194   fvarspinner->setRange(-99, 99);
195   fvarspinner->setValue(1);
196   occLayout->addWidget(occEdit);
197   occLayout->addWidget(occLabel);
198   occEdit->setValidator(new QDoubleValidator(0, 99, 5, occEdit));
199   occEdit->setMaximumWidth(getCharWidth(5));
200   occEdit->setMinimumWidth(getCharWidth(5));
201   // box1
202   optionsLayout1->addLayout(partLayout);
203   optionsLayout1->addLayout(fvarLayout);
204   optionsLayout1->addLayout(occLayout);
205   optionsLayout1->addWidget(replaceModeBox);
206   optionsLayout1->addWidget(splitModeBox);
207   splitModeBox->hide();
208   optionsLayout1->addStretch();
209   groupBox1->setLayout(optionsLayout1);
210   // box2
211   optionsLayout2->addWidget(invertFragBox);
212   optionsLayout2->addWidget(runExtBox);
213   optionsLayout2->addWidget(dfixBox);
214   optionsLayout2->addWidget(rigidBox);
215   optionsLayout2->addStretch();
216   groupBox2->setLayout(optionsLayout2);
217   // box3
218   resclassLayout->addWidget(resiclassEdit);
219   resclassLayout->addWidget(classLabel);
220   resiclassEdit->setMaximumWidth(getCharWidth(8));
221   resiclassEdit->setMinimumWidth(getCharWidth(6));
222   optionsLayout3->addLayout(resclassLayout);
223   optionsLayout3->addWidget(resiTextLabel);
224   optionsLayout3->addWidget(usermanualLabel);
225   optionsLayout3->addStretch();
226   residueOptionsBox->setLayout(optionsLayout3);
227   residueOptionsBox->setCheckable(true);
228   // buttons:
229   buttonLayout->addWidget(fitDSRButton);
230   buttonLayout->addWidget(exportFragButton);
231   exportFragButton->setEnabled(false);
232   buttonLayout->addWidget(splitButton);
233   splitButton->setDisabled(true);
234   splitButton->hide();  // there is no DSR version that can do this until now.
235   buttonLayout->addWidget(editButton);
236   buttonLayout->addWidget(openLastButton);
237   // tooltips:
238   setToolTips();
239   QPixmap pix = QPixmap(250, 50);
240   pix.fill(); // need to fill in order to see the text.
241   QPainter painter(&pix);
242   QRectF rectangle(0, 0, 250-1, 49);
243   painter.setFont(QFont("Sans-Serif", 12));
244   painter.drawRect(rectangle);
245   painter.drawText(QPoint(12, 30), "Loading fragment list...");
246   QSplashScreen *splash = new QSplashScreen(pix);
247   splash->show();
248   splash->showMessage("");
249   target_atoms = getSelectedAtomsList(); // must be before signals
250   // request version.txt to warn for updates:
251   getVersionFromServer();
252   // Signal slot connections:
253   connect_signals_and_slots();
254   splitDecide();
255   occEdit->setText("1");
256   checkForDSRexecutable(splash);
257   splash->finish(this);
258   // call last, because keyboard focus is reated to tab order,
259   // which is based on the order the widgets are created:
260   SearchInp->setFocus();
261   // Has to be here, otherwise fragmentTableView is not initialized:
262   QItemSelectionModel *sm = fragmentTableView->selectionModel();
263   this->show();
264   if (!(sm == nullptr)){  // prevents error about missing connection if DSR is not present
265     connect(sm, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
266         this, SLOT(setFragName(QModelIndex)), Qt::UniqueConnection);
267   }
268   this->move(QApplication::desktop()->width()-this->width()-30, 30);
269   this->resize(this->width()-2, this->height()-2);  // a trick to redraw the gl windows
270   if (!(dsrVersion.toLatin1() == "0")) {
271     outtext->append(QString("\nFound DSR version %1.").arg(dsrVersion));
272     if (dsrVersion.toInt() > 9999) {  // To be changed later
273         splitButton->show();  // logic turned, because a non-replying web server turned this button on
274     }
275   }
276 }
277 
278 
~DSRGui()279 DSRGui::~DSRGui() {
280   delete mygl;
281 }
282 
aboutDSR()283 void DSRGui::aboutDSR() {
284   QMessageBox::about(this, QString(tr("About DSR Plugin")),//only one about dlg in macOS
285       QString(
286         tr("<p><b>DSR plugin for ShelXle</b></p>"
287           "This plugin is a graphical interface for DSR.<br>"
288           "The GUI part and the interaction with DSR was developed by <b>Daniel Kratzert</b>, "
289           "while the 3D OpenGL part was developed by <b>Christian H&uuml;bschle</b>.<br>"
290           "DSR is a refinement tool with a database of molecular fragments and corresponding restraints.<br>"
291           "Place these fragments in a molecular structure to model disorder or just quickly rename "
292           "groups of atoms.<br><br>"
293           "Please cite DSR as:<br>"
294           "<a href=https://dkratzert.de/files/dsr/documents/dsr_2_reprint.pdf> D. Kratzert, I. Krossing, <i>J. Appl. Cryst.</i><b> 2018</b>, <i>51</i>, 928-934.<br></a>"
295           "<a href=https://doi.org/10.1107/S1600576718004508> doi:10.1107/S1600576718004508 </a><br><br>"
296           "If you have additional fragments for the DSR database or a bug to report, please send them to "
297           "<a href=\"dkratzert@gmx.de\"> dkratzert@gmx.de </a> <br>"
298           "Please find the most recent version of DSR at <br>"
299           "<a href=\"https://dkratzert.de/dsr.html\"> https://dkratzert.de/dsr.html </a><br><br>"
300           "All DSR related software is developed at GitHub:<br>"
301           "<a href=\"https://github.com/dkratzert/DSR\"> https://github.com/dkratzert/DSR </a><br><br>"
302           "Please refer to the <a href=\"http://www.xs3-data.uni-freiburg.de/data/DSR-manual.pdf\"> manual </a> if you have any questions."
303           )));
304 }
305 
connect_signals_and_slots()306 void DSRGui::connect_signals_and_slots() {
307   //! Handles most of the signal slot connections
308     connect(this, SIGNAL(fragmentSelected(void)),
309             this, SLOT(activateFitButton(void)));
310     connect(this, SIGNAL(fragmentSelected()),
311             this, SLOT(resetSelection()));
312     connect(fitDSRButton, SIGNAL (clicked(bool)),
313             this, SLOT (fitDSR()));
314     connect(fitDSRButton, SIGNAL (clicked(bool)),
315             this->info, SLOT(hide()));
316     connect(runExtBox, SIGNAL (clicked(bool)),
317             this, SLOT (fitDSRExtern(bool)));
318     connect(dfixBox, SIGNAL (clicked(bool)),
319             this, SLOT (DFIX(bool)));
320     connect(invertFragBox, SIGNAL (clicked(bool)),
321             this, SLOT (invertFrag(bool)));
322     connect(rigidBox, SIGNAL (clicked(bool)),
323             this, SLOT (rigid_group(bool)));
324     connect(replaceModeBox, SIGNAL(clicked(bool)),
325             this, SLOT(replaceOrNot(bool)));
326     connect(SearchInp, SIGNAL(textChanged(QString)),
327             this, SLOT(searchFragment(QString)));
328     connect(occEdit, SIGNAL(textChanged(QString)),
329             this, SLOT(setFvarOcc(void)));
330     connect(fvarspinner, SIGNAL(valueChanged(QString)),
331             this, SLOT(setFvarOcc(void)));
332     connect(resiclassEdit, SIGNAL(textChanged(QString)),
333             this, SLOT(setResiClass(QString)));
334     connect(residueOptionsBox, SIGNAL(toggled(bool)),
335             this, SLOT(combineOptionstext()));
336     connect(splitModeBox, SIGNAL(toggled(bool)),
337             this, SLOT(combineOptionstext()));
338     connect(partspinner, SIGNAL(valueChanged(int)),
339             this, SLOT(setPART(int)));
340     connect(exportFragButton, SIGNAL(clicked(bool)),
341             this, SLOT(setExportDirDialog(void)));
342     connect(fragmentTableView, SIGNAL(clicked(QModelIndex)),
343             this, SLOT(setFragName(QModelIndex)));
344     connect(this, SIGNAL(optionTextChanged(void)),
345             this, SLOT(combineOptionstext(void)));
346     connect(editButton, SIGNAL(clicked(bool)),
347             this, SLOT(runEditWindow()));
348     connect(openLastButton, SIGNAL(clicked(bool)),
349             this, SLOT(open_last_fileversion()));
350     connect(this, SIGNAL(exportDirChanged(QString)),
351             this, SLOT(exportFrag(QString)));
352     // sigslot for uppdating the 3D view
353     connect(this, SIGNAL(currentFragmentChanged(DSRMol)),
354             mygl, SLOT(display_fragment(DSRMol)));
355     connect(m_shelxle->chgl, SIGNAL(selectionChanged()),
356             mygl, SLOT(updateGL()));
357     connect(m_shelxle->chgl, SIGNAL(selectionChanged()),
358             this, SLOT(updateTarget()));
359     connect(m_shelxle->chgl, SIGNAL(selectionChanged()),
360             this, SLOT(update()));
361     connect(mygl->showFitLabel, SIGNAL(stateChanged(int)),
362             mygl, SLOT(updateGL()));
363     connect(mygl->showFit, SIGNAL(stateChanged(int)),
364             mygl, SLOT(updateGL()));
365     connect(m_shelxle->chgl, SIGNAL(selectionChanged()),
366             mygl, SLOT(makeInfo()));
367     connect(mygl, SIGNAL(sourceStringChanged()),
368             this, SLOT(combineOptionstext()));
369     connect(mygl, SIGNAL(updateInfo(QString)),
370             this, SLOT(setInfo(QString)));
371     connect(net_manager, SIGNAL(finished(QNetworkReply*)),
372             this, SLOT(isDSRUpToDate(QNetworkReply*)));
373     connect(fvarLabel, SIGNAL(clicked(bool)),
374             this, SLOT(invertFvar()));
375     connect(m_shelxle->chgl, SIGNAL(selectionChanged()),
376             this, SLOT(splitDecide(void)));
377     connect(splitButton, SIGNAL(clicked(bool)),
378             this, SLOT(splitSelectedAtoms(void)));
379 }
380 
minimumSizeHint() const381 QSize DSRGui::minimumSizeHint() const {
382   return QSize(700, 700);
383 }
384 
sizeHint() const385 QSize DSRGui::sizeHint() const {
386   return QSize(700, 780);
387 }
388 
getVersionFromServer()389 void DSRGui::getVersionFromServer() {
390   //! Writes the proposed version number of DSR into reply
391     net_manager = new QNetworkAccessManager(this);
392     QNetworkRequest request;
393     request.setUrl(QUrl("http://www.xs3-data.uni-freiburg.de/data/version.txt"));
394     request.setRawHeader("User-Agent", "DSRGui");
395     reply = net_manager->get(request);
396   }
397 
runTCPServer()398 bool DSRGui::runTCPServer() {
399   //! Socket server to get messages from DSR. Might be used in future.
400   tcpServer = new QTcpServer(this);
401   //connect(tcpServer, SIGNAL(newConnection()), this, SLOT(ReadClientData()));
402   if (!tcpServer->listen(QHostAddress::LocalHost, 51234)) {
403       tcpServer->close();
404       return false;
405   } else {
406     if (tcpServer->waitForNewConnection()) {
407       if (tcpServer->hasPendingConnections()) {
408         ReadClientData();
409       }
410     }
411   }
412   return true;
413 }
414 
ReadClientData()415 void DSRGui::ReadClientData() {
416   //! Reads DSR client data from a tcp soccet connection.
417   QByteArray data_buffer;
418   QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
419   connect(clientConnection, SIGNAL(readyRead()), clientConnection, SLOT(deleteLater()));
420   // this wait is essential to retrieve data:
421   clientConnection->waitForReadyRead();
422   while (clientConnection->bytesAvailable() > 0) {
423       data_buffer = clientConnection->readAll();
424   }
425   //close tcpServer after reading data:
426   tcpServer->deleteLater();
427   outtext->append(data_buffer);
428   //dsrResulttext.append(data_buffer);
429 }
430 
isDSRUpToDate(QNetworkReply * reply)431 void DSRGui::isDSRUpToDate(QNetworkReply* reply) {
432   /*
433    *! Displays a warning if the current DSR version is too old.
434   */
435   QString latestRev_str = reply->readAll();
436   bool ok;
437   bool ok2;
438   QMessageBox info;
439   info.addButton(QMessageBox::Close);
440   int dsrv = dsrVersion.toInt(&ok2, 10);
441   QPushButton *updateButton = info.addButton(tr("Update now"), QMessageBox::ActionRole);
442   if (dsrv < 193) {  // This is the first version with self-update mechanism
443     updateButton->hide();
444   }
445   int latestrev_int = latestRev_str.toInt(&ok,10);
446   if ((ok&&ok2)&&(latestrev_int)>(dsrv)){
447     info.setText(QString(
448                    "<h3>You should probably update DSR!</h3>"
449                    "This is revision: <b>%1</b><br> "
450                    "The latest version is: <b>%2</b> <br><br>"
451                    "New versions can be downloaded here: <br>"
452                    "<a href=\"https://dkratzert.de/dsr.html\">"
453                    "https://dkratzert.de/dsr.html</a><br><br>"
454                    "Please contact the author Daniel Kratzert"
455                    " <a href=\"mailto:dkratzert@gmx.de\">dkratzert@gmx.de</a> "
456                    "if you find any bugs.<br> Thank you!")
457                     .arg(dsrVersion.toInt())
458                     .arg(latestrev_int));
459     info.exec();
460     if (info.clickedButton() == updateButton) {
461       updateDSR();
462     }
463   }
464   reply->close();
465   reply->deleteLater();
466   disconnect(net_manager, SIGNAL(finished(QNetworkReply*)), nullptr, nullptr);
467 }
468 
updateTarget()469 void DSRGui::updateTarget() {
470   //! updates the target atoms list
471   target_atoms = getSelectedAtomsList();
472   combineOptionstext();
473 }
474 
writeFavorites(QString name)475 void DSRGui::writeFavorites(QString name) {
476   //! Stores favorites in dsrgui.ini
477   QSettings dsr_settings( QSettings::IniFormat, QSettings::UserScope, PROGRAM_NAME, "dsrgui" );
478   dsr_settings.beginGroup("LastFragment");
479   dsr_settings.setValue("last", name);
480   dsr_settings.endGroup();
481 }
482 
loadFavoriteFragment()483 QString DSRGui::loadFavoriteFragment() {
484   //! Loads Favorites from dsrgui.ini
485   QSettings dsr_settings( QSettings::IniFormat, QSettings::UserScope, PROGRAM_NAME, "dsrgui" );
486   dsr_settings.beginGroup("LastFragment");
487   QString last = dsr_settings.value("last").toString();
488   dsr_settings.endGroup();
489   if (last == QString("cf6")) {
490     splitModeBox->show();
491   }
492   if (last == QString("")) {
493     last = QString("benzene");
494   }
495   return last;
496 }
497 
which(QString programName)498 QStringList DSRGui::which(QString programName) {
499   //! Implements a which like method
500   //! It returns all paths where programName is found in
501   //! the system path variable
502   QStringList foundInPath;
503   QStringList execlist;
504   QStringList pathList;
505   pathList.clear();
506   foundInPath.clear();
507   execlist.clear();
508   if (mysystem == "win") {
509 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
510     pathList = QString(qEnvironmentVariable("PATH")).split(";");
511 #else
512     pathList = QString(qgetenv("PATH")).split(";");
513 #endif
514   } else {
515 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
516     pathList = QString(qEnvironmentVariable("PATH")).split(":");
517 #else
518     pathList = QString(qgetenv("PATH")).split(":");
519 #endif
520   }
521   execlist << ".bat" << ".exe" << "";
522   foreach (QString exec, execlist) {
523     foreach (QString path, pathList) {
524       QString fullpath = path+"/"+programName+exec;
525       QFileInfo fi;
526       fi = fullpath;
527       if (QFile::exists(fullpath) && (fi.isExecutable())){
528         foundInPath.append(QDir::cleanPath(fullpath));
529       }
530     }
531   }
532   return foundInPath;
533 }
534 
checkForDSRexecutable(QSplashScreen * splash)535 void DSRGui::checkForDSRexecutable(QSplashScreen *splash) {
536   //! list fragments only if the path of dsr installation is found:
537   if (QFile::exists(dsrpath)) {
538     if (listDSRdbFragments("None")) {
539       outtext->append(tr("\n* Please pick a fragment first."));
540       outtext->append(tr("* Then select at least three atoms from "
541                         "the main structure and the fragment respectively. (STRG+click)"));
542       outtext->append(tr("* Do not forget to apply a part number and free "
543                          "variable in case of disorder."));
544       outtext->append(tr("* The fragment will be placed after FVAR in the SHELX file."));
545     }
546   } else {
547     outtext->clear();
548     outtext->append(QString(tr("Unable to find DSR executable."
549                 "\nIs DSR_DIR environment variable set correctly?")));
550     outtext->append(QString(tr("Please find the most recent version of DSR at "
551                                "<a href=\"https://dkratzert.de/dsr.html\">"
552                                "https://dkratzert.de/dsr.html</a>")));
553     splash->finish(this);
554   }
555 }
556 
setToolTips()557 void DSRGui::setToolTips() {
558   partspinner->setToolTip(tr("The PART of a fragment controls the binding of atoms.\n"
559                              "For example two fragments in PART 1 and PART 2 do not\n"
560                              "bind each other but to PART 0. Negative PARTs only bind themselves."));
561   replaceModeBox->setToolTip(tr("Toggle replacement of target atoms and all atoms of "
562                              "PART 0 \nin 1.3A distance around the fitted fragment atoms."));
563   occEdit->setToolTip(tr("The occupancy of the fragment\n"));
564   fvarspinner->setToolTip(tr("The Free Variable for the fragment.\n"
565                              "Free Variable and Occupancy will be combined occording to the SHELXL syntax.\n"
566                              "For example, 21.0 means second free variable with occupancy of 1."));
567   invertFragBox->setToolTip(tr("Invert the fragment coordinates during fit."));
568   runExtBox->setToolTip(tr("Write restraints to external file."));
569   dfixBox->setToolTip(tr("Calculate DFIX/DANG/FLAT restraints "
570                          "\naccording to fragment geometry. \n"
571                          "Database restraints will be ignored."));
572   rigidBox->setToolTip(tr("Keep the fragment as rigid (AFIX 9) group. \n"
573                           "Apply no restraints."));
574   residueOptionsBox->setToolTip(tr("Enables residues. Usually, you can leave the default.\n"
575                            "It will always take the next free residue number."));
576   fitDSRButton->setToolTip("Run DSR to fit the fragment into the structure.");
577   exportFragButton->setToolTip("Export the current fragment to a .res file.");
578   editButton->setToolTip("Edit the current fragment or create a new one.");
579   openLastButton->setToolTip("Opens the .res file state before the last DSR fragment fit.");
580 }
581 
textWrap(QString inText,QString indent)582 QString DSRGui::textWrap(QString inText, QString indent) {
583   //! returns a SHELXL compatible wrapped string (string =\\n string) in case of over 77 characters
584   //! line length.
585   //! The default of subsequent_indent is "=\n  "
586   int length = 70;  // 70 should be save in any case
587   QStringList line_list;
588   QStringList wrapped;
589   QStringList inText_list = inText.split(" ");
590   for (int i=0; i<inText_list.length(); i++) {
591     QString word = inText_list.at(i);
592     QString testline = line_list.join(" ") + " ";
593       if (QString(word + testline + indent).length() >= length) {
594         if (i < inText_list.length()-2) {
595           line_list.append(word + " " + indent);
596         } else {
597           line_list.append(word);
598         }
599         wrapped.append(line_list.join(" "));
600         line_list.clear();
601       } else{
602         line_list.append(word);
603       }
604   }
605   if (!line_list.isEmpty()) {
606     wrapped.append(line_list.join(" "));
607   }
608   //qDebug() << wrapped.join(" ");
609   return wrapped.join(" ");
610 }
611 
open_last_fileversion()612 bool DSRGui::open_last_fileversion() {
613     //! Opens the last res file version before the last DSR action from the save history (./Shelxsaves/SAVEHIST)
614     QFileInfo fi(m_shelxle->dirName);
615     if (!fi.isReadable()) {
616         outtext->append("No last .res file found to load.");
617         return false;
618     }
619     QFile currentpath(fi.absolutePath());
620     QString saveHistFileName=QString("%1/%2saves/SAVEHIST").arg(currentpath.fileName()).arg(PROGRAM_NAME);
621     QFile savehist(saveHistFileName);
622     savehist.open(QIODevice::ReadOnly|QIODevice::Text);
623     if (!savehist.isReadable()) {
624         outtext->append("No last .res file found to load.");
625         return false;
626     }
627     QString savehist_content = savehist.readAll();  //Entry|@|2011-03-08T13:32:19|@|b11.res|@|
628     savehist.close();
629     QRegExp re = QRegExp("Entry\\|@\\|\\w+-\\w+-\\w+:\\w+:\\w+\\|@\\|[^@]+@\\|");
630     QStringList entries = savehist_content.split(re, skipEmptyParts);
631     QFile f(m_shelxle->dirName);
632     bool success = f.open(QIODevice::WriteOnly|QIODevice::Text);
633     if (success){
634       f.write(entries.last().toLatin1().replace(0, 1, ""));
635       f.close();
636     } else {
637       return false;
638     }
639     outtext->append("Successfully restored last file version.");
640     m_shelxle->loadFile(m_shelxle->dirName);
641     return true;
642 }
643 
invertFvar()644 void DSRGui::invertFvar(){
645     //! Inverts the value of FVAR in the OptionsLayout1
646     int fvar;
647     fvar = fvarspinner->text().toInt();
648     fvarspinner->setValue(-fvar);
649 }
650 
resetSelection()651 void DSRGui::resetSelection() {
652   //! reset the selection of source atoms
653   mygl->source_atoms.clear();
654   combineOptionstext();
655   info->hide();
656 }
657 
setInfo(QString s)658 void DSRGui::setInfo(QString s) {
659   //! shows an info table with bond distances
660   if (mygl->selFragAt.size()<1) {
661     info->hide();
662   }
663   else {
664     info->show();
665     info->setText(s);
666   }
667 }
668 
splitDecide(void)669 void DSRGui::splitDecide(void) {
670     //! Enables the "split atoms" button if at least one atom is selected.
671     if (target_atoms.count() >= 1) {
672         splitButton->setEnabled(true);
673     }
674 }
675 
splitSelectedAtoms(void)676 void DSRGui::splitSelectedAtoms(void) {
677     //! Runs DSR to split the currebtly selected atoms along the principal axis of the ellipsoid.
678     QFileInfo resfip(m_shelxle->dirName);
679     QString option = " -splt ";
680     option = option + getSelectedAtomsList();
681     option = option + " -r " + resfip.completeBaseName();
682     this->runDSR(option);
683 }
684 
runEditWindow(void)685 void DSRGui::runEditWindow(void) {
686   //! Starts the edit window in order to edit a fragment
687   editwindow = new DSREditWindow(m_molecule, header, dsr_db_path,
688                                  fragmentNameTag, *fragmentsList, this);
689   editwindow->move(60, 50);
690   editwindow->show();
691   editwindow->setFocus();
692   editwindow->setMinimumWidth(800);
693   outtext->clear();
694   connect(editwindow, SIGNAL(updated(QString)),
695           this, SLOT(listDSRdbFragments(QString)));
696 }
697 
closeEvent(QCloseEvent * event)698 void DSRGui::closeEvent(QCloseEvent *event) {
699 //! close event is emmited during DSRGui closing to reset its pointer
700   emit closed();
701   (void)event; // prevent compiler warning
702 }
703 
findFVARlines(QStringList * reslist)704 int DSRGui::findFVARlines(QStringList *reslist) {
705   //! finds the line number of last FVAR or the first atom
706   //! I restrict the use of this plugin to stuctures with a valid
707   //! FVAR, because a missing FVAR makes it all too error prone.
708   int fvarline;
709   // I need lastIndexOf() here, because in case of
710   // several FVAR lines indexOf() would fail:
711   fvarline = reslist->lastIndexOf(QRegExp("^FVAR.*", Qt::CaseInsensitive));
712   if (fvarline > 0) {
713     return fvarline;
714   } else {
715     return 0;
716   }
717 }
718 
findDSRLines(QStringList * reslist)719 QVector<int> DSRGui::findDSRLines(QStringList *reslist) {
720   //! Find line with "rem DSR ..." in res file and return line number
721   //! if it exists.
722   QVector<int> lineNums;
723   int Num = 0;
724   foreach (QString line, *reslist) {
725     if (line.contains(QRegExp("^rem\\s{1,6}DSR\\s{1,6}.*", Qt::CaseInsensitive))) {
726       lineNums.append(Num);
727     }
728     Num++;
729   }
730   return lineNums;
731 }
732 
decideDSRInsertLine(QStringList * reslist)733 int DSRGui::decideDSRInsertLine(QStringList *reslist) {
734   //! decides where to instert the DSR command
735   int fvarline = findFVARlines(reslist);
736   if (fvarline > 0) {
737     return fvarline;
738   } else if (m_shelxle->firstAtomLine > 0) {
739     // in this case, no FVAR is present and we put the DSR command line
740     // before the first atom
741     combiDSRline = QString("FVAR 1\n") + combiDSRline; // dummy FVAR is added for DSR
742     return m_shelxle->firstAtomLine - 1;
743   } else {
744     return 0;
745   }
746 }
747 
readResfile()748 QStringList DSRGui::readResfile() {
749   //! read the entire res file into a stringlist
750   QFileInfo fi(m_shelxle->dirName);
751   QFile file(fi.absoluteFilePath());
752   QStringList stringList;
753   if (file.open(QFile::ReadOnly | QFile::Text)) {
754     QTextStream textStream(&file);
755     while (true) {
756       QString line = textStream.readLine();
757       if (line.isNull()) {
758           break;
759       } else {
760         stringList.append(line);
761       }
762     }
763   }
764   file.close();
765   return stringList;
766 }
767 
findFreeResiNumber()768 QString DSRGui::findFreeResiNumber() {
769   //! returns the next free residue number in the structure
770   QSet<int> resiset;
771   QList<int> resilist;
772   int resnum = 0;
773   for (int i=0; i < m_molecule->asymm.size(); i++) {
774     MyAtom atom;
775     atom = m_molecule->asymm.at(i);
776     if (atom.resiNr >= 0) {
777       resiset.insert(atom.resiNr);
778     }
779   }
780   resilist = resiset.toList();
781 #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
782   std::sort(resilist.begin(), resilist.end());
783 #else
784   qSort(resilist);
785 #endif
786   int count = 0;
787   foreach (int num, resilist) {
788     if (num != count) {
789       // a gap in residue numbers is found, use this number:
790       resnum = count;
791       break;
792     }
793     count++;
794   }
795   // no gap found, use next number:
796   resnum = count;
797   // In this case we have no residue in the file:
798   if (resilist.isEmpty()) {
799     resnum = 1;
800   }
801   return QString("%1").arg(resnum);
802 }
803 
804 
getFragmentHeader(QString frag)805 bool DSRGui::getFragmentHeader(QString frag) {
806   //! defines Name, residue, comment and unit cell of the fragment
807   //! in DSRMol
808   /*!
809     <tag>
810      benze
811     </tag>
812     <comment>
813      Benzene2, Benzol, C6H6
814     </comment>
815     <source>
816      CCDC UGEDEQ
817     </source>
818     <cell>
819      1;;1;;1;;90;;90;;90
820     </cell>
821     <residue>
822      ERT
823     </residue>
824     <dbtype>
825      dsr_user_db
826     </dbtype>
827     <restr>
828      RIGU C1 > C6;;SADI C1 C2 C3 C4
829      </restr>
830     <atoms>
831       C1 6 1.78099 7.14907 12.00423;;C2 6 2.20089 8.30676 11.13758;;C3 6 1.26895 9.02168 10.39032;;C4 6 1.64225 10.07768 9.58845;;C5 6 2.98081 10.44432 9.51725;;C6 6 3.92045 9.74974 10.25408
832     </atoms>
833    */
834   header->atoms.clear();
835   header->cell.clear();
836   header->comment.clear();
837   header->dbtype.clear();
838   header->residue.clear();
839   header->restr.clear();
840   header->tag.clear();
841   outtext->clear();
842   QString *rawheader = new QString;
843   QStringList *headerlist = new QStringList;
844   QString options = " -ah " + frag;
845   dsrResulttext.clear();
846   runDSR(options);
847   if (dsrResulttext.isEmpty()) {
848     outtext->clear();
849     outtext->append(tr("Unable to run DSR."));
850     editButton->setDisabled(true); // editing the empty header would crash ShelXle
851     return false;
852   } else {
853     rawheader->append(dsrResulttext);
854   }
855   // in this case no fragment list returned. Hence, we have an error.
856   if (!rawheader->contains(";;")) {
857     outtext->clear();
858     outtext->append(*rawheader);
859     //editButton->setDisabled(true); // editing the empty header would crash ShelXle
860     return false;
861   }
862   headerlist->append(rawheader->split(QRegExp("\n|\r\n|\r")));
863   QStringList line;
864   for (int i=0; i<headerlist->size(); i++) {
865     if (headerlist->at(i).size() == 0){
866       continue;
867     }
868     line.clear();
869     if (headerlist->at(i).trimmed().startsWith("***")) {
870       QString error;
871       error = headerlist->at(i).trimmed();
872       outtext->append("<b>"+error+"</b>");
873     }
874     if (headerlist->at(i).trimmed().startsWith("<tag>")) {
875       line = headerlist->at(i+1).trimmed().split(" ");
876       header->tag = line.join("");
877     }
878     if (headerlist->at(i).trimmed().startsWith("<comment>")) {
879       line = headerlist->at(i+1).trimmed().split(" ");
880       header->comment = line.join(" ");
881     }
882     if (headerlist->at(i).trimmed().startsWith("<source>")) {
883       line = headerlist->at(i+1).trimmed().split(" ");
884       header->source = line.join(" ");
885     }
886     if (headerlist->at(i).trimmed().startsWith("<cell>")) {
887       line = headerlist->at(i+1).simplified().split(";;");
888       foreach (QString item, line) {
889         header->cell.append(item.toDouble());
890       }
891     }
892     if (headerlist->at(i).trimmed().startsWith("<residue>")) {
893       line = headerlist->at(i+1).trimmed().split(" ");
894       header->residue = line.join("");
895       resiclass = header->residue;
896       resiclassEdit->setText(resiclass);
897     }
898     if (headerlist->at(i).trimmed().startsWith("<dbtype>")) {
899       line = headerlist->at(i+1).trimmed().split(" ");
900       header->dbtype = line.join("");
901     }
902     if (headerlist->at(i).trimmed().startsWith("<restr>")) {
903       line = headerlist->at(i+1).simplified().split(";;");
904       foreach (QString item, line) {
905         header->restr.append(item);
906       }
907     }
908     if (headerlist->at(i).trimmed().contains("<atoms>")) {
909       line = headerlist->at(i+1).simplified().split(";;");
910       foreach (QString item, line) {
911         header->atoms.append(item.split(" "));
912       }
913     }
914   }
915   editButton->setEnabled(true);
916   return true;
917 }
918 
919 
getCharWidth(int numchars)920 int DSRGui::getCharWidth(int numchars) {
921   //! returns the width in pixel of numchars times the # character
922   QString buchstaben;
923   buchstaben.clear();
924   for (int i=1; i<=numchars; i++) {
925     buchstaben += "#";
926   }
927 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
928   return QFontMetrics(this->font()).horizontalAdvance(buchstaben);
929 #else
930   return QFontMetrics(this->font()).width(buchstaben);
931 #endif
932 }
933 
934 
activateFitButton(void)935 void DSRGui::activateFitButton(void) {
936     fitDSRButton->setEnabled(true);
937     exportFragButton->setEnabled(true);
938 }
939 
940 
combineOptionstext(void)941 void DSRGui::combineOptionstext(void) {
942   //! combines all options to a single DSR command line
943   //! This method gets invoked if optionTextChanged signal is emmitted
944   combiDSRline.clear();
945   // Displays error messages from DSR:
946   if (!outtext->toPlainText().contains("***")) {
947     outtext->clear();
948   } //else {
949     //return;
950   //}
951   QString split = "";
952   // CF3 groups are dummy entries in the DSR database. They get calculated
953   // on ideal positions at the respective carbon atom:
954   if (fragmentNameTag == QString("cf6")) {
955     splitModeBox->show();
956   } else {
957     splitModeBox->hide();
958   }
959   if ( (fragmentNameTag == QString("cf3")) ||
960        (fragmentNameTag == QString("cf6")) ||
961        (fragmentNameTag == QString("cf9")) ) {
962     this->cf3 = true;
963     rigidBox->setDisabled(true);
964     invertFragBox->setDisabled(true);
965     runExtBox->setDisabled(true);
966     replaceModeBox->setDisabled(true);
967     residueOptionsBox->setDisabled(true);
968     if (splitModeBox->isChecked() ) {
969       split = "SPLIT";
970     }
971   } else {
972     splitModeBox->hide();
973     this->cf3 = false;
974     rigidBox->setDisabled(false);
975     invertFragBox->setDisabled(false);
976     runExtBox->setDisabled(false);
977     replaceModeBox->setDisabled(false);
978     residueOptionsBox->setDisabled(false);
979   }
980   target_atoms = getSelectedAtomsList();
981   QString putreplace = QString("PUT ");
982   if (replace) {
983       putreplace = QString("REPLACE ");
984   }
985   if (!cf3) {
986     if (target_atoms.split(" ").length() != 3) {
987       target_atoms = "<font color=red> Please select 3 target atoms/q-peaks! </font>";
988     }
989     if (mygl->source_atoms.split(" ").length() != 3){
990       mygl->source_atoms = "<font color=red> Please select 3 fragment atoms! </font>";
991     }
992   } else {
993     if (target_atoms.split(" ").length() != 1) {
994       target_atoms = "<font color=red> Please select only <b>a single</b> carbon atom! </font>";
995     }
996     mygl->source_atoms = "";
997   }
998   QString resistr = "RESI ";
999   if (!residueOptionsBox->isChecked() || !residueOptionsBox->isEnabled()) {
1000     resistr = "";
1001     resiTextLabel->setText("");
1002   } else {
1003     resiTextLabel->setText(*resnumbertext);
1004     if (resiclassEdit->text() == resiclass) {
1005       resistr = "RESI "+resiclass;
1006     } else {
1007       resistr = "RESI "+resiclassEdit->text();
1008     }
1009   }
1010   QString with;
1011   if (cf3) {
1012     with = QString(" ");
1013   } else {
1014     with = QString(" WITH ");
1015   }
1016   QString outstring = QString(QString("REM DSR ")+putreplace+fragmentNameTag+" "+
1017                           with+mygl->source_atoms+" "+QString("ON ")+
1018                           target_atoms+" "+part+" "+fvarocc+" "+dfix+" "+resistr+split);
1019   combiDSRline = outstring.simplified().toUpper();
1020   outtext->append(combiDSRline);
1021 }
1022 
1023 
setResiClass(QString rclass)1024 void DSRGui::setResiClass(QString rclass) {
1025 //! defines the residue class
1026   if (rclass[0].isLetter()){
1027     if (rclass.length() > 4) {
1028       resiclassEdit->setText(QString(rclass.mid(0, 4)));
1029     }
1030     emit optionTextChanged();
1031   } else {
1032     outtext->append(QString(tr("Please start residue "
1033                             "class with a letter.")));
1034     resiclassEdit->clear();
1035   }
1036 }
1037 
setFragName(QModelIndex name)1038 void DSRGui::setFragName(QModelIndex name) {
1039 //! set the fragment name variable
1040   //outtext->clear();
1041   fragmentNameTag = name.sibling(name.row(), 0).data().toString();
1042   writeFavorites(fragmentNameTag);
1043   //outtext->clear();
1044   if (!getFragmentHeader(fragmentNameTag)) {
1045     // In this case, the most important thing is missing
1046     return;
1047   }
1048   if (header->comment.isEmpty()){
1049     outtext->append("You should give this fragment a name.");
1050   }
1051   if (header->atoms.isEmpty()){
1052     return;
1053   }
1054   if (header->tag.isEmpty()){
1055     return;
1056   }
1057   if (header->restr.isEmpty()){
1058     return;
1059   }
1060   if (header->cell.isEmpty()){
1061     return;
1062   }
1063   //emit optionTextChanged();
1064   emit fragmentSelected();
1065   emit currentFragmentChanged(*header);
1066 }
1067 
1068 
DFIX(bool checked)1069 void DSRGui::DFIX(bool checked) {
1070 //! toggles the dfix option
1071   outtext->clear();
1072   if (checked)
1073   {
1074     this->dfix = QString("DFIX");
1075   } else
1076   {
1077     this->dfix.clear();
1078   }
1079   emit optionTextChanged();
1080 }
1081 
setFvarOcc(void)1082 void DSRGui::setFvarOcc(void) {
1083 //! defines the FVAR and occupancy option
1084   outtext->clear();
1085   int fvar = 0;
1086   double occ = 0;
1087   double focc = 0;
1088   fvar = fvarspinner->value();
1089   occ = (occEdit->text().replace(',', '.').toDouble());
1090   if (fvar < 0) {
1091     focc = -(abs(fvar) * 10 + occ);
1092   } else {
1093     focc = fvar * 10 + occ;
1094   }
1095   fvarocc = QString("OCC ")+QString::number(focc);
1096   emit optionTextChanged();
1097   outtext->append("\nFVAR num  ->  times used");
1098   foreach (int fvar, m_shelxle->fvarCntr.keys()) {
1099     if (fvar < 2) continue;
1100     outtext->append(QString("FVAR %1  -> %2  ").arg(fvar, 3).arg(m_shelxle->fvarCntr.value(fvar), 3));
1101   }
1102 }
1103 
setPART(int partnum)1104 void DSRGui::setPART(int partnum) {
1105 //! defines the PART option
1106   outtext->clear();
1107   if (partnum == 0)
1108   {
1109     part.clear();
1110   }
1111   else
1112   {
1113     QString s = QString::number(partnum);
1114     part = QString("PART ")+s ;
1115   }
1116   emit optionTextChanged();
1117 }
1118 
fitDSRExtern(bool checked)1119 void DSRGui::fitDSRExtern(bool checked) {
1120 //! enable write restraints to external file
1121   if (checked) {
1122     this->runext = true;
1123   } else {
1124     this->runext = false;
1125   }
1126 }
1127 
invertFrag(bool checked)1128 void DSRGui::invertFrag(bool checked) {
1129 //! Inverts the fragment coordinates.
1130 //! They are also inverted in the 3D view.
1131   // Invert coordinates in DSR:
1132   if (checked){
1133     this->invert = true;
1134   } else {
1135     this->invert = false;
1136   }
1137   V3 v1 = V3(-1,  0,  0);
1138   V3 v2 = V3( 0, -1,  0);
1139   V3 v3 = V3( 0,  0,  1);
1140   //rotation matrix to rotate the selection 180deg.
1141   // for less disturbance of the inverted view:
1142   Matrix rmat = Matrix(v1, v2, v3);
1143   // Invert the OpenGL coordinates:
1144   double coord;
1145   int index;
1146   index = 0;
1147   foreach(QStringList line, header->atoms) {
1148     for (int n = 2; n<5; n++) {
1149       coord = line[n].toDouble();
1150       // rotate fragment 180 deg.
1151       if ((n == 2) | (n == 3)) {
1152         coord = coord * -1;
1153       }
1154       header->atoms[index][n] = QString("%1").arg(-coord);
1155     }
1156     index = index+1;
1157   }
1158   //Also invert the selection of the atoms:
1159   for (int n=0; n<mygl->selFragAt.size(); n++) {
1160     //this line is essential:
1161     mygl->selFragAt[n].pos = mygl->selFragAt[n].pos * rmat;
1162     mygl->selFragAt[n].pos.x *= -1;
1163     mygl->selFragAt[n].pos.y *= -1;
1164     mygl->selFragAt[n].pos.z *= -1;
1165   }
1166   mygl->display_fragment(*header, false);
1167   // This mouse press hack reactivates the fit overlay after inversion:
1168   QMouseEvent ev ((QEvent::MouseButtonPress), QPoint(0, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
1169   mygl->mousePressEvent(&ev);
1170 }
1171 
rigid_group(bool checked)1172 void DSRGui::rigid_group(bool checked)
1173 //! toggle rigid group refinenement
1174 {
1175   if (checked) {
1176     this->rigid = true;
1177   } else {
1178     this->rigid = false;
1179   }
1180 }
1181 
exportFrag(QString dirname)1182 bool DSRGui::exportFrag(QString dirname) {
1183   //! export the current fragment to a res file+png
1184   //! change to the current dir here, because dsr exports in current dir:
1185   QDir::setCurrent(dirname);
1186   outtext->clear();
1187   if (fragmentNameTag.isEmpty()) {
1188       outtext->append("No fragment chosen. Doing nothing!");
1189       return false;
1190   }
1191   //outtext->clear();
1192   QString options = QString(" -e ") + fragmentNameTag;
1193   dsrResulttext.clear();
1194   runDSR(options, true, dirname);
1195   if (dsrResulttext.isEmpty()) {
1196     outtext->append("Unable to start DSR.");
1197     return false;
1198   }
1199   return true;
1200 }
1201 
refineOrNot(bool checked)1202 void DSRGui::refineOrNot(bool checked) {
1203 //! enable or disable refinement after transfer
1204   if (checked) {
1205     this->norefine = true;
1206   } else {
1207     this->norefine = false;
1208   }
1209 }
1210 
replaceOrNot(bool checked)1211 void DSRGui::replaceOrNot(bool checked) {
1212 //! enable or disable replace mode
1213   outtext->clear();
1214   if (checked) {
1215     this->replace = true;
1216   } else {
1217     this->replace = false;
1218   }
1219   emit optionTextChanged();
1220 }
1221 
getSelectedAtomsList()1222 QString DSRGui::getSelectedAtomsList() {
1223   //! returns the Names of the currently selected atoms of the structure
1224   //! loaded in ShelXle as StringList
1225   QStringList atoms;
1226   atoms.clear();
1227   for (int i=0; i<m_molecule->selectedatoms.size(); i++) {
1228     atoms.append(m_molecule->selectedatoms.at(i).Label.toLatin1().replace(QChar(187), ">>"));
1229   }
1230   return atoms.join(" ");
1231 }
1232 
getSelectedAtomsCoords()1233 QString DSRGui::getSelectedAtomsCoords() {
1234   //! returns the atoms coordinates that are selected inside shelxle as a string
1235   QString atoms = "";
1236   QString line = "";
1237   for (int i = 0; i < m_molecule->selectedatoms.size(); ++i) {
1238     line = QString(" %1 %2 %3").arg(m_molecule->selectedatoms.at(i).frac.x, 10, 'f', 5)
1239                                .arg(m_molecule->selectedatoms.at(i).frac.y, 10, 'f', 5)
1240                                .arg(m_molecule->selectedatoms.at(i).frac.z, 10, 'f', 5);
1241     atoms.append(line);
1242   }
1243   return atoms;
1244 }
1245 
fitDSR()1246 bool DSRGui::fitDSR() {
1247   /*!
1248    runs DSR from command line
1249 
1250    bool QDir::setCurrent(const QString & path)
1251    QString QFileInfo::completeBaseName() const : "c:/programme/dsr/p21c.res -> p21c.res
1252    QString QFileInfo::absolutePath() const : c:/programme/dsr/p21c.res -> c:/programme/dsr/
1253    QString QFileInfo::absoluteFilePath() const : c:/programme/dsr/p21c.res -> c:/programme/dsr/p21c.res
1254    QFileInfo fi("c:/temp/foo"); => fi.absoluteFilePath()
1255   */
1256   QString atoms = getSelectedAtomsCoords();
1257   if (!cf3) {
1258     if (m_molecule->selectedatoms.size() != 3) {
1259       emit optionTextChanged();
1260       //return false;
1261     }
1262     if (mygl->source_atoms.split(" ").length() != 3) {
1263       emit optionTextChanged();
1264       outtext->append("<font color=red> \nPlease select three "
1265                       "atoms of the fragment! </font>");
1266       return false;
1267     }
1268   }
1269   info->hide(); // Even if the fit does not work, I want to hide it to better see the output
1270   QString option;
1271   QString source_atoms_tmp = mygl->source_atoms;
1272   if ((m_molecule->selectedatoms.size() != 3) && !cf3) {
1273     //outtext->clear();
1274     outtext->append("<font color=red> Please select at least three "
1275                     "atoms or Q-peaks as target in your structure! </font>");
1276     return false;
1277   }
1278   if ((m_molecule->selectedatoms.size() != 1) && cf3) {
1279     outtext->clear();
1280     outtext->append("<font color=red> Please select one carbon"
1281                     "atom as target in your structure! </font>");
1282     return false;
1283   }
1284   target_atoms = getSelectedAtomsList();
1285   bool ok;
1286   int dsrv = dsrVersion.toInt(&ok, 10);
1287   if (ok && dsrv < 200) {
1288     // This is obsolete with DSR >= 200 since we fit the fragment to
1289     // the actual coordinates and not to "names":
1290     if ( target_atoms.contains(QString(">>"))) {
1291       outtext->clear();
1292       outtext->append(QString("<font color=red><b>Symmetrie generated atoms not allowed as target! <br><br>"
1293                               "Please update DSR.</b></font>"));
1294       return false;
1295     }
1296   }
1297   emit optionTextChanged();
1298   bool save_sucess = m_shelxle->fileSave(true, false);
1299   if (!save_sucess) {
1300     outtext->append("Could not save the instruction file!");
1301     return false;
1302   }
1303   QStringList *reslist = new QStringList;
1304   reslist->append(readResfile());
1305   mygl->source_atoms = source_atoms_tmp;
1306   int DSRposition = decideDSRInsertLine(reslist);
1307   if (DSRposition == 0){
1308     outtext->append("Could not fit fragment. No FVAR command found. \nPlease refine at least one cycle.");
1309     return false;
1310   }
1311   QVector<int> previousLines = findDSRLines(reslist);
1312   QString wrappedDSRLine = textWrap(combiDSRline);
1313   m_shelxle->insertDSRLine(wrappedDSRLine, DSRposition, previousLines);
1314   delete reslist;
1315   if (rigid) {
1316     option = "-g ";
1317   } else {
1318     option = "";
1319   }
1320   if (norefine){
1321     option += " -n ";
1322   }
1323   if (!runext && !invert) // the standard run without extra options
1324   {
1325     option += " -r ";
1326   }
1327   else if (runext && !invert) {
1328     option += " -re ";
1329   }
1330   else if (!runext && invert)
1331   {
1332     option += " -t -r ";
1333   }
1334   else if (runext && invert) {
1335     option += " -t -re ";
1336   } else {
1337     qDebug() << "Unhandeled option case in DSRGui occoured!!";
1338     return false;
1339   }
1340   if (!m_shelxlPath.isEmpty() && (dsrVersion.toInt() > 182)) {
1341     option = " -shx " + m_shelxlPath + " " + option;
1342   }
1343   if ((dsrVersion.toInt() >= 200)) {
1344     option = " -target " + atoms + " " + option;
1345   }
1346   QFileInfo resfip(m_shelxle->dirName);
1347   option = option + resfip.completeBaseName();
1348   QFileInfo check_res(resfip.completeBaseName()+".res");
1349   if ( resfip.completeSuffix() == "ins" && check_res.size() == 0) {
1350     outtext->clear();
1351     outtext->append("Warning! Something went wrong with your .res file.\n"
1352                     "You are currently working on the .ins file. Please restore "
1353                     "the .res file before you continue.");
1354     return false;
1355   }
1356   bool dsr = this->runDSR(option);
1357   if (!dsr) {
1358       return false;
1359   }
1360   m_shelxle->loadAFile();
1361   this->show();
1362   this->raise();
1363   this->activateWindow();
1364   return true;
1365 }
1366 
updateDSR(void)1367 void DSRGui::updateDSR(void) {
1368   //!
1369   //! Updates DSR to the current version on the web server
1370   //!
1371   QString option = " -u";
1372   outtext->append("Update running...");
1373   this->runDSR(option);
1374 }
1375 
runDSR(QString option,bool showresults,QString workdir)1376 bool DSRGui::runDSR(QString option, bool showresults, QString workdir) {
1377   //! runs DSR with options and shows the results in outtext if desired.
1378   dsrResulttext.clear();
1379   outtext->clear();
1380   QProcess dsr;
1381   QFileInfo resfip;
1382   if (workdir.isEmpty()) {
1383     // workdir can be the export (fragment) directory for example.
1384     resfip.setFile(m_shelxle->dirName);
1385     if (resfip.exists()) {
1386       // Need to check if path is not empty, because en empty path would cause dsr.start() to fail.
1387       // absolutePath() truncates the last path part even though its C:\foo\bar --> C:\foo
1388       dsr.setWorkingDirectory(resfip.absolutePath());
1389     }
1390   } else {
1391     resfip.setFile(workdir);
1392     dsr.setWorkingDirectory(resfip.absoluteFilePath());
1393   }
1394   dsr.setProcessChannelMode(QProcess::MergedChannels);
1395   dsr.closeWriteChannel();
1396   // Do not use this with fragment export:
1397   if (!resfip.absolutePath().isEmpty() && workdir.isEmpty()) {
1398     // Need to do this, because en empty path would cause dsr.start() to fail.
1399     dsr.setWorkingDirectory(resfip.absolutePath());
1400   }
1401   dsr.start(dsrpath, option.split(" ", skipEmptyParts));
1402   if (!dsr.waitForFinished()) {
1403     outtext->append("Unable to start DSR.");
1404     outtext->append(dsrpath + " " + option);
1405     outtext->append(dsr.readAll());
1406     return false;
1407   } else {
1408     dsrResulttext.append(dsr.readAll());
1409     //qDebug() << dsrResulttext;
1410   }
1411   /*
1412   if (dsrResulttext.isEmpty()) {
1413     bool socket = runTCPServer();
1414     if (!socket) {
1415       outtext->append("Unable read DSR output.");
1416     }
1417   } */
1418   if (showresults) {
1419     // Display the results:
1420     //outtext->clear();
1421     outtext->append(combiDSRline);
1422     outtext->append(dsrResulttext);
1423   }
1424   this->show();
1425   this->raise();
1426   this->activateWindow();
1427   //qDebug() << dsrResulttext;
1428   return true;
1429 }
1430 
searchFragment(QString searchName)1431 void DSRGui::searchFragment(QString searchName) {
1432   //! Searches for fragments in the database
1433   if ((searchName.length() < 2) && (searchName.length() > 0)){
1434     return;
1435   }
1436   // If search length is zero, restore full list:
1437   if (searchName.length() == 0){
1438     if (!listDSRdbFragments("None")) {
1439       outtext->append("Unable to find DSR fragment database.");
1440       return;
1441     }
1442     return;
1443   }
1444   QString option = QString(" -x ") + searchName;
1445   dsrResulttext.clear();
1446   this->runDSR(option, false);
1447   QVector<QStringList> fraglist;
1448   fraglist.clear();
1449   foreach (QString line, dsrResulttext.split(QRegExp("\n|\r\n|\r"))) {
1450     if (line.isEmpty()){
1451       continue;
1452     }
1453     if (line.contains(";;")) {
1454       fraglist.append(line.split(";;"));
1455     }
1456   }
1457   if (fraglist.size() > 0) {
1458     displayFragmentsList(fraglist, fraglist.at(0).at(0).toLatin1().toUpper());
1459   } else {
1460     displayFragmentsList(fraglist, "None");
1461   }
1462 }
1463 
isDSRVersionCorrect(QString version)1464 bool DSRGui::isDSRVersionCorrect(QString version) {
1465   //! checks if the version of DSR found in DSR_DIR
1466   //! is compatible with this GUI
1467   bool ok;
1468   ok = false;
1469   if (version.trimmed().toInt() >= 182) {
1470     ok = true;
1471   }
1472   return ok;
1473 }
1474 
listDSRdbFragments(QString fav="None")1475 bool DSRGui::listDSRdbFragments(QString fav="None") {
1476   //! list fragments in DSR database
1477   //! The -lc parameter of DSR returns a semicolon (;;) separated list
1478   //! tag;;fullname;;line number;;db
1479   if (QString("None") == fav) {
1480     fav = loadFavoriteFragment().toLatin1().toUpper();
1481   }
1482   fragmentsList->clear();
1483   fragmentNameTag.clear();
1484   QString options = " -lc";
1485   runDSR(options, true);
1486   QStringList frag_str_list = dsrResulttext.split(QRegExp("\n|\r\n|\r"));
1487   bool versionOk = false;
1488   foreach (QString line, frag_str_list) {
1489     if (line.contains("Duplicate database entry")) {
1490       outtext->clear();
1491       outtext->append(dsrResulttext);
1492       return false;
1493     }
1494   }
1495   // in this case no fragment list returned. Hence, we have an error.
1496   if (!dsrResulttext.contains(";;")) {
1497     //outtext->clear();
1498     outtext->append("Something went wrong with DSR. please tell Daniel Kratzert (dkratzert@gmx.de) about this problem.");
1499     outtext->append(dsrResulttext);
1500     return false;
1501   }
1502   foreach (QString line, frag_str_list) {
1503     if (line.isEmpty()){
1504       continue;
1505     }
1506     if ( (line.split(":").size() >= 1) && line.contains("DSR version:")) {
1507       dsrVersion = line.split(":").at(1).trimmed();
1508     }
1509     if (line.trimmed().contains("DSR version:")) {
1510       versionOk = isDSRVersionCorrect(dsrVersion);
1511       continue;
1512     }
1513     if (line.trimmed().startsWith("***")) {
1514       outtext->append(line);
1515       continue;
1516     }
1517     if (line.contains(";;")) {
1518       // collect frags to global list here:
1519       fragmentsList->append(line.split(";;")); // the global list of all fragments
1520     }
1521   }
1522   if (!versionOk) {
1523     outtext->clear();
1524     outtext->append(QString(tr("Detected an old version of DSR.")));
1525     outtext->append(QString(tr("Please get the most recent version from https://dkratzert.de/dsr.html")));
1526     return false;
1527   }
1528   displayFragmentsList(*fragmentsList, fav);
1529   return true;
1530 }
1531 
1532 
displayFragmentsList(QVector<QStringList> list,QString fav="None")1533 void DSRGui::displayFragmentsList(QVector<QStringList> list, QString fav="None") {
1534   //! displays the list of fragments in a table view
1535   //! The first column (the tag) is hidden, only the name is displayed.
1536   QStandardItem *fraglabel = new QStandardItem(QString("Fragment Name"));
1537   fraglabel->setTextAlignment(Qt::AlignLeft);
1538   QStandardItemModel *FragListmodel = new QStandardItemModel(list.size(), 2, this); //x Rows and 2 Columns
1539   FragListmodel->setHorizontalHeaderItem(0, new QStandardItem(QString("tag")));
1540   FragListmodel->setHorizontalHeaderItem(1, fraglabel);
1541   QStringList line;
1542   for (int i = 0; i < list.size(); ++i) {
1543     line.clear();
1544     line = list[i];
1545     if ( line.size() < 3 ) {
1546       continue;
1547     }
1548     QString column1 = line[0];
1549     QString column2 = line[1];
1550     QStandardItem *nameItem;
1551     // Add a bold *user* to all fragments from the user db
1552     if (line[3].contains("dsr_user_db")) {
1553       column2.append(" *user*");
1554       nameItem = new QStandardItem(QString(column2));
1555       QFont f = nameItem->font();
1556       f.setBold(true);
1557       nameItem->setFont(f);
1558     } else {
1559       nameItem = new QStandardItem(QString(column2));
1560     }
1561     FragListmodel->setItem(i, 0, new QStandardItem(QString(column1)));
1562     FragListmodel->setItem(i, 1, nameItem);
1563     // load the last fragment:
1564     if (fav == line.at(0).toLatin1().toUpper()) {
1565       if (FragListmodel->hasIndex(i, 0)) {
1566         setFragName(FragListmodel->index(i, 0));
1567       }
1568     }
1569   }
1570   fragmentTableView->setModel(FragListmodel);
1571   fragmentTableView->verticalHeader()->hide();
1572   fragmentTableView->hideColumn(0);
1573   fragmentTableView->setColumnWidth(1, 600);
1574   fragmentTableView->setGridStyle(Qt::PenStyle(Qt::NoPen));
1575   fragmentTableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
1576 }
1577 
getDSRDir()1578 QString DSRGui::getDSRDir() {
1579   //! returns the value of the DSR_DIR variable
1580   //! in case of error, it returns an empty string
1581 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1582   QString dsrdir = qEnvironmentVariable("DSR_DIR");
1583 #else
1584   QString dsrdir = qgetenv("DSR_DIR");
1585 #endif
1586   if (!QFile::exists(dsrdir)){
1587     if (!which("dsr").isEmpty()) {
1588       QFileInfo fi(which("dsr").at(0));
1589       if (fi.exists()) {
1590         dsrdir = fi.absolutePath();
1591       }
1592     }
1593     if (QFile::exists("/Applications/DSR")) {
1594         dsrdir = "/Applications/DSR";
1595     }
1596 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1597     QString dsrdir2 = qEnvironmentVariable("DSRDIR");
1598 #else
1599     QString dsrdir2 = qgetenv("DSRDIR");
1600 #endif
1601     if (QFile::exists(dsrdir2)) {
1602       qDebug() << "You are probably using an older version of DSR."
1603                   "\n Please use version 1.7.7 or above.";
1604       // return empty string, because dsr.bat will
1605       // hinder dsr from starting properly:
1606       return QString("");
1607     }
1608   }
1609   return QDir::fromNativeSeparators(dsrdir);
1610 }
1611 
getDSRdbDir()1612 QString DSRGui::getDSRdbDir() {
1613   //! returns the directory with the userdb -> the home directory
1614   QString dbdir = QDir::homePath();
1615   return QDir::fromNativeSeparators(dbdir);;
1616 }
1617 
1618 
setExportDirDialog()1619 void DSRGui::setExportDirDialog() {
1620   //! File dialog to define the directory for the res file
1621   //! exported by DSR
1622   export_dir = "";
1623   export_dir = QFileDialog::getExistingDirectory(this, tr("Export Fragment to ..."), tr("Directory"));
1624   if (QFile::exists(export_dir)){
1625     emit exportDirChanged(export_dir);
1626   }
1627 }
1628 
1629 
1630