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ü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