1 // XDrawChem
2 // Copyright (C) 2004-2005 Bryan Herger <bherger@users.sourceforge.net>
3 // Copyright (C) 2020 Yaman Qalieh <ybq987@gmail.com>
4
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18 #include <iostream>
19
20 #include <QApplication>
21 #include <QClipboard>
22 #include <QMessageBox>
23
24 #include "arrow.h"
25 #include "bond.h"
26 #include "bracket.h"
27 #include "chemdata.h"
28 #include "crings_dialog.h"
29 #include "defs.h"
30 #include "drawable.h"
31 #include "gobject.h"
32 #include "molecule.h"
33 #include "netaccess.h"
34 #include "text.h"
35 #include "tool_13c_nmr.h"
36 #include "tool_1h_nmr.h"
37 #include "tool_2d3d.h"
38 #include "tool_ir.h"
39 #include "tooldialog.h"
40
41 // For AutoLayout
42 class LayoutGroup {
43 public:
44 QList<Drawable *> items;
45 LayoutGroup *left;
46 LayoutGroup *right;
47 LayoutGroup *above;
48 LayoutGroup *below;
49 bool placed;
Move(double x,double y)50 void Move(double x, double y) {
51 qDebug() << "MOVE:" << x << " " << y;
52 for (Drawable *tmp_draw : items) {
53 tmp_draw->SelectAll();
54 tmp_draw->Move(x, y);
55 tmp_draw->DeselectAll();
56 }
57 }
BoundingBox()58 QRect BoundingBox() {
59 int top = 99999, bottom = 0, left = 99999, right = 0;
60 QRect tmprect;
61
62 for (Drawable *tmp_draw : items) {
63 tmp_draw->SelectAll();
64 tmprect = tmp_draw->BoundingBox();
65 tmp_draw->DeselectAll();
66 if (tmprect.isValid()) {
67 if (tmprect.left() < left)
68 left = tmprect.left();
69 if (tmprect.right() > right)
70 right = tmprect.right();
71 if (tmprect.top() < top)
72 top = tmprect.top();
73 if (tmprect.bottom() > bottom)
74 bottom = tmprect.bottom();
75 }
76 }
77 return QRect(QPoint(left, top), QPoint(right, bottom));
78 }
Center()79 QPoint Center() { return BoundingBox().center(); }
80 };
81
82 // Determine Molecule clicked, do Tool action
Tool(DPoint * target,int mode)83 void ChemData::Tool(DPoint *target, int mode) {
84 // QClipboard *cb = QApplication::clipboard();
85
86 qDebug() << "ChemData::Tool: " << mode;
87 Molecule *m = 0;
88 NetAccess *na = new NetAccess();
89 CustomRingDialog cr1;
90 ToolDialog tool1;
91 Tool_1HNMR_Dialog *tool1hnmr;
92 Tool_13CNMR_Dialog *tool13cnmr;
93 Tool_IR_Dialog *toolir;
94 Tool_2D3D tool2d3d;
95 QString tmpname, serverName;
96 QStringList choices;
97 // int dret;
98
99 for (Drawable *tmp_draw : drawlist) {
100 if (tmp_draw->Type() == TYPE_MOLECULE) {
101 m = (Molecule *)tmp_draw;
102 if (m->BoundingBoxAll().contains(target->toQPoint(), false))
103 break;
104 m = 0;
105 }
106 }
107 if (m == 0)
108 return;
109 double kow = 0.0;
110 QString thisInChI;
111
112 switch (mode) {
113 case MODE_TOOL_MOLECULE_INFO:
114 mi = new MolInfoDialog(r);
115 thisInChI = m->ToInChI();
116 tt_mw = m->CalcMW();
117 mi->setMW(tt_mw->getText());
118 tt_ef = m->CalcEmpiricalFormula();
119 mi->setEF(tt_ef->getText());
120 tt_ea = m->CalcElementalAnalysis();
121 mi->setEA(tt_ea->getText());
122
123 serverName = getenv("XDC_SERVER");
124 if (serverName.length() < 2)
125 serverName = XDC_SERVER;
126 if (na->getNameCAS(serverName, thisInChI)) {
127 mi->setCAS(na->scas);
128 mi->setName(na->siupacname);
129 mi->setSynonyms(na->sname);
130 mi->setPCC(na->spccompound);
131 }
132
133 connect(mi, SIGNAL(MIDClose()), this, SLOT(returnFromMID()));
134
135 mi->show();
136 // if ( !mi->exec() ) return;
137 // if (mi->isMWChecked()) drawlist.append(tt_mw);
138 // if (mi->isEFChecked()) drawlist.append(tt_ef);
139 // delete mi;
140 break;
141 case MODE_TOOL_CALCMW:
142 tt = m->CalcMW();
143 if (tt != 0)
144 drawlist.append(tt);
145 break;
146 case MODE_TOOL_CALCEF:
147 tt = m->CalcEmpiricalFormula();
148 if (tt != 0)
149 drawlist.append(tt);
150 break;
151 case MODE_TOOL_CALCEA:
152 tt = m->CalcElementalAnalysis();
153 if (tt != 0)
154 drawlist.append(tt);
155 break;
156 case MODE_TOOL_13CNMR:
157 tool13cnmr = new Tool_13CNMR_Dialog;
158 tool13cnmr->setMolecule(m);
159 tool13cnmr->show();
160 // m->Calc13CNMR();
161 break;
162 case MODE_TOOL_1HNMR:
163 tool1hnmr = new Tool_1HNMR_Dialog;
164 tool1hnmr->setMolecule(m);
165 tool1hnmr->show();
166 // m->Calc1HNMR();
167 break;
168 case MODE_TOOL_IR:
169 toolir = new Tool_IR_Dialog;
170 toolir->setMolecule(m);
171 toolir->show();
172 // m->CalcIR();
173 break;
174 case MODE_TOOL_PKA:
175 m->CalcpKa();
176 break;
177 case MODE_TOOL_RETRO:
178 Retro(m);
179 break;
180 case MODE_TOOL_REACTIVITY_FORWARD:
181 m->Reactivity(mode);
182 break;
183 case MODE_TOOL_REACTIVITY_RETRO:
184 m->Reactivity(mode);
185 for (Bond *tmp_bond = m->bondsFirst(); tmp_bond != 0; tmp_bond = m->bondsNext()) {
186 if (tmp_bond->getReactions().length() > 2) {
187 tmp_bond->SetColor(QColor(124, 252, 0));
188 }
189 }
190 r->update();
191 break;
192 case MODE_TOOL_CHARGES:
193 m->Reactivity(mode);
194 break;
195 case MODE_TOOL_KOW:
196 kow = m->CalcKOW();
197 QMessageBox::information(
198 r, tr("Octanol-water partition"),
199 tr("Estimated octanol-water partition constant (log Kow) = %1").arg(kow));
200 break;
201 case MODE_TOOL_2D3D:
202 m->Make3DVersion();
203 break;
204 case MODE_TOOL_NAME:
205 m->CalcName();
206 break;
207 case MODE_TOOL_CUSTOMRING:
208 cr1.setMolecule(m);
209 if (cr1.exec() == QDialog::Accepted)
210 emit SignalUpdateCustomRingMenu();
211
212 break;
213 case MODE_TOOL_TOSMILES:
214 tmpname = m->ToSMILES();
215 if (tmpname.length() == 0) {
216 qDebug() << "Could not get SMILES string!";
217 }
218 QMessageBox::information(r, tr("SMILES string"),
219 tr("SMILES string for selected molecule:") + "\n\n" + tmpname);
220 break;
221 case MODE_TOOL_TOINCHI:
222 // m->AddNMRprotons();
223 tmpname = m->ToInChI();
224 // m->RemoveNMRprotons();
225 if (tmpname.length() == 0) {
226 qDebug() << "Could not get InChI string!";
227 }
228 QMessageBox::information(r, tr("InChI string"),
229 tr("InChI string for selected molecule:") + "\n\n" + tmpname);
230 break;
231 case MODE_TOOL_CLEANUPMOL:
232 m->CleanUp();
233 break;
234 case MODE_TOOL_GROUP_REACTANT:
235 m->setGroupType(GROUP_REACTANT);
236 break;
237 case MODE_TOOL_GROUP_PRODUCT:
238 m->setGroupType(GROUP_PRODUCT);
239 break;
240 case MODE_TOOL_GROUP_CLEAR:
241 m->setGroupType(GROUP_NONE);
242 break;
243 case MODE_TOOL_TEST:
244 tool1.setMolecule(m);
245 tool1.exec();
246 break;
247 }
248 // Need to pick next tool manually
249 }
250
Save3D(QString fn3d)251 void ChemData::Save3D(QString fn3d) {
252 // save 3D image of first molecule
253 Molecule *m = 0;
254
255 for (Drawable *tmp_draw : drawlist) {
256 if (tmp_draw->Type() == TYPE_MOLECULE) {
257 m = (Molecule *)tmp_draw;
258 break;
259 }
260 }
261
262 if (m != 0) {
263 m->Make3DVersion(fn3d);
264 } else {
265 qDebug() << "No molecule in input file!";
266 }
267 }
268
returnFromMID()269 void ChemData::returnFromMID() {
270 Q_CHECK_PTR(mi);
271 Q_CHECK_PTR(tt_mw);
272 Q_CHECK_PTR(tt_ef);
273 Q_CHECK_PTR(tt_ea);
274 if (mi->isMWChecked())
275 drawlist.append(tt_mw);
276 if (mi->isEFChecked())
277 drawlist.append(tt_ef);
278 if (mi->isEAChecked())
279 drawlist.append(tt_ea);
280 mi->hide();
281 delete mi;
282 }
283
clearAllGroups()284 void ChemData::clearAllGroups() {
285 Molecule *m = 0;
286 QString tmpname;
287
288 for (Drawable *tmp_draw : drawlist) {
289 if (tmp_draw->Type() == TYPE_MOLECULE) {
290 m = (Molecule *)tmp_draw;
291 m->setGroupType(GROUP_NONE);
292 }
293 }
294 }
295
296 // calculate molecular weights of Molecules
297 // AutoLayout
AutoLayout()298 void ChemData::AutoLayout() {
299 QList<LayoutGroup *> layout;
300 LayoutGroup *tmp_lo, *tl, *tr, *ta, *tb, *tl1;
301 int dista, distb, distl, distr, d1, d2, d3, d4, ds;
302 Text *tmp_text;
303 Arrow *tmp_arrow;
304 Drawable *td2;
305 Drawable *tmp_draw;
306
307 // first, put Arrows and Molecules into LayoutGroups
308 for (Drawable *tmp_draw : drawlist) {
309 if (tmp_draw->Type() == TYPE_ARROW) {
310 tmp_lo = new LayoutGroup;
311 tmp_lo->items.append(tmp_draw);
312 tmp_lo->placed = false;
313 tmp_lo->left = 0;
314 tmp_lo->right = 0;
315 tmp_lo->above = 0;
316 tmp_lo->below = 0;
317 layout.append(tmp_lo);
318 }
319 if (tmp_draw->Type() == TYPE_MOLECULE) {
320 tmp_lo = new LayoutGroup;
321 tmp_lo->items.append(tmp_draw);
322 tmp_lo->placed = false;
323 tmp_lo->left = 0;
324 tmp_lo->right = 0;
325 tmp_lo->above = 0;
326 tmp_lo->below = 0;
327 layout.append(tmp_lo);
328 }
329 }
330 // now, attach Text to Arrows as needed
331 for (LayoutGroup *tmp_lo : layout) {
332 td2 = tmp_lo->items.first();
333 if (td2->Type() == TYPE_ARROW) {
334 tmp_arrow = (Arrow *)td2;
335 foreach (tmp_draw, drawlist) {
336 if (tmp_draw->Type() == TYPE_TEXT) {
337 tmp_text = (Text *)tmp_draw;
338 int ns;
339 QPoint amid = tmp_arrow->Midpoint();
340 QPoint tcenter = tmp_text->NearestCenter(amid, tmp_arrow->Orientation(), ns);
341 int dx = tcenter.x() - amid.x();
342 int dy = tcenter.y() - amid.y();
343 double dist = sqrt((double)(dx * dx + dy * dy));
344
345 qDebug() << dist;
346 if (dist < 25) {
347 if (tmp_arrow->Orientation() == ARROW_HORIZONTAL) {
348 if (dy < 0) { // above arrow
349 dy = dy + 12;
350 tmp_text->ForceMove(-dx, -dy);
351 } else { // below arrow
352 dy = dy - 12;
353 tmp_text->ForceMove(-dx, -dy);
354 }
355 } else { // ARROW_VERTICAL
356 if (dx < 0) { // above arrow
357 dx = dx + 12;
358 tmp_text->ForceMove(-dx, -dy);
359 } else { // below arrow
360 dx = dx - 12;
361 tmp_text->ForceMove(-dx, -dy);
362 }
363 }
364 tmp_lo->items.append(tmp_text); // add Text to LayoutGroup
365 } // if (dist...)
366 } // if (...TYPE_TEXT)
367 } // for (...)
368 } // if (...TYPE_ARROW)
369 } // for(...)
370 // Now determine position of LayoutGroups
371 for (LayoutGroup *tmp_lo : layout) {
372 QRect box = tmp_lo->BoundingBox();
373 QPoint l1(box.left(), box.center().y());
374 QPoint r1(box.right(), box.center().y());
375 QPoint a1(box.top(), box.center().x());
376 QPoint b1(box.bottom(), box.center().x());
377
378 // check sides
379 dista = 9999;
380 distb = 9999;
381 distl = 9999;
382 distr = 9999;
383 ds = 9999;
384 tl = 0;
385 tr = 0;
386 ta = 0;
387 tb = 0;
388 foreach (tl1, layout) {
389 QRect box1 = tl1->BoundingBox();
390 QPoint l2(box1.left(), box1.center().y());
391 QPoint r2(box1.right(), box1.center().y());
392 QPoint a2(box1.top(), box1.center().x());
393 QPoint b2(box1.bottom(), box1.center().x());
394
395 d1 = (int)(r->DistanceBetween(l1, r2));
396 d2 = (int)(r->DistanceBetween(r1, l2));
397 d3 = (int)(r->DistanceBetween(a1, b2));
398 d4 = (int)(r->DistanceBetween(b1, a2));
399 if (d1 < ds)
400 ds = d1;
401 if (d2 < ds)
402 ds = d2;
403 if (d3 < ds)
404 ds = d3;
405 if (d4 < ds)
406 ds = d4;
407 if (d1 == ds)
408 tl = tl1;
409 if (d2 == ds)
410 tr = tl1;
411 if (d3 == ds)
412 ta = tl1;
413 if (d4 == ds)
414 tb = tl1;
415 }
416 if (tl != 0) {
417 qDebug() << "left";
418 tl->right = tmp_lo;
419 tmp_lo->left = tl;
420 }
421 if (tr != 0) {
422 qDebug() << "right";
423 tr->left = tmp_lo;
424 tmp_lo->right = tr;
425 }
426 if (ta != 0) {
427 qDebug() << "above";
428 ta->below = tmp_lo;
429 tmp_lo->above = ta;
430 }
431 if (tb != 0) {
432 qDebug() << "below";
433 tb->above = tmp_lo;
434 tmp_lo->below = tb;
435 }
436 qDebug();
437 }
438 // Place everything
439 // Start with things near arrows
440 for (LayoutGroup *tmp_lo : layout) {
441 if (tmp_lo->items.first()->Type() == TYPE_ARROW) {
442 tmp_lo->placed = true;
443 tmp_arrow = (Arrow *)(tmp_lo->items.first());
444 if (tmp_arrow->Orientation() == ARROW_HORIZONTAL) {
445 // adjust position according to already placed Molecule or Arrow
446 if (tmp_lo->left != 0) {
447 if (tmp_lo->left->placed == true) {
448 int dy = tmp_lo->Center().y() - tmp_lo->left->Center().y();
449
450 tmp_lo->Move(0, -dy);
451 }
452 }
453 if (tmp_lo->right != 0) {
454 if (tmp_lo->right->placed == true) {
455 int dy = tmp_lo->Center().y() - tmp_lo->right->Center().y();
456
457 tmp_lo->Move(0, -dy);
458 }
459 }
460 if (tmp_lo->left != 0) {
461 if (tmp_lo->left->placed == false) {
462 int dy = tmp_lo->left->Center().y() - tmp_lo->Center().y();
463
464 tmp_lo->left->Move(0, -dy);
465 tmp_lo->left->placed = true;
466 }
467 }
468 if (tmp_lo->right != 0) {
469 if (tmp_lo->right->placed == false) {
470 int dy = tmp_lo->right->Center().y() - tmp_lo->Center().y();
471
472 tmp_lo->right->Move(0, -dy);
473 tmp_lo->right->placed = true;
474 }
475 }
476 } else { // ARROW_VERTICAL
477 // adjust position according to already placed Molecule or Arrow
478 if (tmp_lo->above != 0) {
479 if (tmp_lo->above->placed == true) {
480 int dx = tmp_lo->Center().y() - tmp_lo->above->Center().y();
481
482 tmp_lo->Move(-dx, 0);
483 }
484 }
485 if (tmp_lo->below != 0) {
486 if (tmp_lo->below->placed == true) {
487 int dx = tmp_lo->Center().x() - tmp_lo->below->Center().x();
488
489 tmp_lo->Move(-dx, 0);
490 }
491 }
492 if (tmp_lo->above != 0) {
493 if (tmp_lo->above->placed == false) {
494 int dx = tmp_lo->above->Center().x() - tmp_lo->Center().x();
495
496 tmp_lo->above->Move(-dx, 0);
497 tmp_lo->above->placed = true;
498 }
499 }
500 if (tmp_lo->below != 0) {
501 if (tmp_lo->below->placed == false) {
502 int dx = tmp_lo->below->Center().x() - tmp_lo->Center().x();
503
504 tmp_lo->below->Move(-dx, 0);
505 tmp_lo->below->placed = true;
506 }
507 }
508 } // if (tmp_arrow...)
509 } // if (...TYPE_ARROW)
510 } // for (...)
511 }
512
fromSMILES(QString sm)513 void ChemData::fromSMILES(QString sm) {
514 Molecule *m1 = new Molecule(r);
515
516 m1->FromSMILES(sm);
517 m1->SelectAll();
518 drawlist.append(m1);
519 }
520
SmartPlace(QString sf,DPoint * t1)521 void ChemData::SmartPlace(QString sf, DPoint *t1) {
522
523 if (sf.contains("cyclopentadiene") > 0)
524 sf.replace(QRegExp("diene"), "diene-sp");
525 double ang1 = -CalculateRingAttachAngle(t1) + 1.5708;
526
527 load(sf);
528 Drawable *tmp_draw = drawlist.last();
529 Molecule *m1 = (Molecule *)tmp_draw;
530
531 if (fabs(ang1) < 0.1) {
532 ang1 = 3.14159;
533 } else {
534 if (fabs(ang1) > 3.13) {
535 ang1 = 0.0;
536 }
537 }
538 qDebug() << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!";
539 DPoint *tmp_pt = m1->GetRingAttachPoint();
540 m1->Rotate(ang1);
541 double dx, dy;
542
543 dx = t1->x - tmp_pt->x;
544 dy = t1->y - tmp_pt->y;
545 m1->Move(dx, dy);
546 QList<DPoint *> nb = m1->BreakRingBonds(tmp_pt);
547 foreach (tmp_pt, nb) {
548 addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0, 0, 0), true);
549 qDebug() << "added a bond";
550 }
551 }
552
SmartPlaceToo(QString sf,DPoint * t1)553 void ChemData::SmartPlaceToo(QString sf, DPoint *t1) {
554 double ang1 = -CalculateRingAttachAngle(t1) + 3.14159;
555
556 load(sf);
557 Drawable *tmp_draw = drawlist.last();
558 Molecule *m1 = (Molecule *)tmp_draw;
559
560 qDebug() << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!";
561 // tmp_pt = m1->GetRingAttachPoint();
562 DPoint *tmp_pt = m1->GetAttachPoint(sf);
563 m1->Rotate(ang1);
564 double dx, dy;
565
566 dx = t1->x - tmp_pt->x;
567 dy = t1->y - tmp_pt->y;
568 m1->Move(dx, dy);
569 QList<DPoint *> nb = m1->BreakRingBonds(tmp_pt);
570 for (DPoint *tmp_pt : nb) {
571 addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0, 0, 0), true);
572 qDebug() << "added a bond";
573 }
574 // need to put back N if EDANS
575 if (sf.contains("edans") > 0) {
576 t1->element = "NH";
577 Text *nt = new Text(r);
578
579 nt->setPoint(t1);
580 nt->setJustify(JUSTIFY_CENTER);
581 nt->setText("NH");
582 // nt->setTextMask( " " );
583 addText(nt);
584 }
585 }
586
SmartPlaceThree(QString sf,DPoint * t1)587 void ChemData::SmartPlaceThree(QString sf, DPoint *t1) {
588 double ang1 = -CalculateRingAttachAngle(t1);
589
590 load(sf);
591 Drawable *tmp_draw = drawlist.last();
592 Molecule *m1 = (Molecule *)tmp_draw;
593
594 qDebug() << "angle = " << (ang1 * 180.0 / 3.14159) << " degrees!";
595 // tmp_pt = m1->GetRingAttachPoint();
596 DPoint *tmp_pt = m1->GetAttachPoint(sf);
597 m1->Rotate(ang1);
598 double dx, dy;
599
600 dx = t1->x - tmp_pt->x;
601 dy = t1->y - tmp_pt->y;
602 m1->Move(dx, dy);
603 QList<DPoint *> nb = m1->BreakRingBonds(tmp_pt);
604 for (DPoint *tmp_pt : nb) {
605 addBond(tmp_pt, t1, 1, tmp_pt->new_order, QColor(0, 0, 0), true);
606 qDebug() << "added a bond";
607 }
608 // need to put back N
609 t1->element = "NH";
610 Text *nt = new Text(r);
611
612 nt->setPoint(t1);
613 nt->setJustify(JUSTIFY_CENTER);
614 nt->setText("NH");
615 // nt->setTextMask( " " );
616 addText(nt);
617 }
618
CalculateRingAttachAngle(DPoint * t1)619 double ChemData::CalculateRingAttachAngle(DPoint *t1) {
620 double a1;
621 Molecule *m = 0;
622
623 for (Drawable *tmp_draw : drawlist) {
624 if ((tmp_draw->Type() == TYPE_MOLECULE) && (tmp_draw->Find(t1) == true)) {
625 m = (Molecule *)tmp_draw;
626 break;
627 }
628 }
629 if (m == 0)
630 return 0.0;
631
632 a1 = m->CalculateRingAttachAngle(t1);
633
634 return a1;
635 }
636
637 // Implemented using Crossing Number Algorithm
SelectWithinLoop(QVector<QPoint> curr_lasso)638 bool ChemData::SelectWithinLoop(QVector<QPoint> curr_lasso) {
639
640 int count;
641 bool retval = false; // Were any selectable Drawables found?
642
643 QList<Drawable *> obj_list = UniqueObjects();
644
645 for (Drawable *tmp_draw : obj_list) {
646 count = 0;
647
648 QPointF center;
649
650 // Get center of object
651 if (tmp_draw->End()) {
652 center = r->Midpoint(tmp_draw->Start()->toQPoint(), tmp_draw->End()->toQPoint());
653 } else {
654 center = tmp_draw->Start()->toQPoint();
655 }
656
657 // Count number of times lasso passes through left of center
658 for (int i = 0; i < curr_lasso.size() - 1; ++i) {
659 count +=
660 ((curr_lasso.at(i).y() < center.y()) ^ (curr_lasso.at(i + 1).y() < center.y())) &
661 (curr_lasso.at(i).x() < center.x());
662 }
663
664 // Odd count -> Drawable is inside
665 if (count & 1) {
666 retval = true;
667 tmp_draw->Highlight(true);
668 }
669 }
670
671 return retval;
672 }
673