1 // -*- C++ -*-
2
3 /*
4 * GChemPaint bonds plugin
5 * chaintool.cc
6 *
7 * Copyright (C) 2006-2011 Jean Bréfort <jean.brefort@normalesup.org>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 3 of the
12 * License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 * USA
23 */
24
25 #include "config.h"
26 #include "chaintool.h"
27 #include <gcp/application.h>
28 #include <gcp/atom.h>
29 #include <gcp/bond.h>
30 #include <gcp/document.h>
31 #include <gcp/molecule.h>
32 #include <gcp/settings.h>
33 #include <gcp/theme.h>
34 #include <gcp/view.h>
35 #include <gccv/canvas.h>
36 #include <gccv/group.h>
37 #include <gccv/line.h>
38 #include <gcugtk/ui-builder.h>
39 #include <gdk/gdkkeysyms.h>
40 #include <glib/gi18n-lib.h>
41 #include <cmath>
42 #include <list>
43
44 using namespace gcu;
45 using namespace std;
46
gcpChainTool(gcp::Application * App)47 gcpChainTool::gcpChainTool (gcp::Application *App): gcp::Tool (App, "Chain")
48 {
49 m_Length = 0; // < 2 is auto.
50 m_Points = new gccv::Point[3];
51 m_Atoms.resize (3);
52 m_CurPoints = 3;
53 m_AutoNb = true;
54 m_AutoDir = false;
55 }
56
~gcpChainTool()57 gcpChainTool::~gcpChainTool()
58 {
59 delete [] m_Points;
60 }
61
OnClicked()62 bool gcpChainTool::OnClicked()
63 {
64 if (Element::GetMaxBonds (m_pApp->GetCurZ()) < 2)
65 return false;
66 m_dAngle = 0.;
67 unsigned nb = (m_Length > 2)? m_Length + 1: 3;
68 double a1, x, y;
69 gcp::Document* pDoc = m_pView->GetDoc();
70 m_BondLength = pDoc->GetBondLength ();
71 if (nb != m_CurPoints) {
72 m_CurPoints = nb;
73 delete [] m_Points;
74 m_Points = new gccv::Point[m_CurPoints];
75 if (m_CurPoints > m_Atoms.size ())
76 m_Atoms.resize (m_CurPoints);
77 }
78 m_Positive = (m_nState & GDK_LOCK_MASK && !(m_nState & GDK_MOD5_MASK)) ||
79 (m_nState & GDK_MOD5_MASK && !(m_nState & GDK_LOCK_MASK));
80 if (m_pObject) {
81 if (m_pObject->GetType () != AtomType)
82 return false;
83 m_Atoms[0] = static_cast<gcp::Atom*> (m_pObject);
84 if (!m_Atoms[0]->AcceptNewBonds (1))
85 return false;
86 nb = m_Atoms[0]->GetBondsNumber ();
87 m_Atoms[0]->GetCoords(&m_x0, &m_y0, NULL);
88 m_Points[0].x = x = m_x0 *= m_dZoomFactor;
89 m_Points[0].y = y = m_y0 *= m_dZoomFactor;
90 switch (nb) {
91 case 1: {
92 map<Atom*, Bond*>::iterator i;
93 gcp::Bond* bond = (gcp::Bond*) ((Atom*) m_pObject)->GetFirstBond (i);
94 m_RefAngle = m_dAngle = bond->GetAngle2D ((gcp::Atom*) m_pObject);
95 m_dAngle += (m_Positive)? +150: -150;
96 m_AutoDir = true;
97 break;
98 }
99 case 2: {
100 double a2;
101 map<Atom*, Bond*>::iterator i;
102 gcp::Bond* bond = (gcp::Bond*) ((Atom*) m_pObject)->GetFirstBond (i);
103 a1 = bond->GetAngle2D ((gcp::Atom*) m_pObject);
104 bond = (gcp::Bond*) ((Atom*) m_pObject)->GetNextBond (i);
105 a2 = bond->GetAngle2D ((gcp::Atom*) m_pObject);
106 m_dAngle = (a1 + a2) / 2.;
107 a2 = fabs (a2 - m_dAngle);
108 if (a2 < 90.)
109 m_dAngle += 180.;
110 if (m_dAngle > 360.)
111 m_dAngle -= 360.;
112 m_dAngle += (m_Positive)?
113 90. - pDoc->GetBondAngle () / 2.:
114 pDoc->GetBondAngle () / 2. - 90;
115 break;
116 }
117 default:
118 break;
119 }
120 } else {
121 m_Atoms[0] = NULL;
122 x = m_Points->x = m_x0;
123 y = m_Points->y = m_y0;
124 m_AutoDir = true;
125 }
126 FindAtoms ();
127 m_Allowed = false;
128 if (gcp::MergeAtoms && !(m_Allowed = CheckIfAllowed ()))
129 return true; // true, since dragging the mouse might make things OK.
130 char tmp[32];
131 snprintf(tmp, sizeof(tmp) - 1, _("Bonds: %d, Orientation: %g"), m_CurPoints - 1, m_dAngle);
132 m_pApp->SetStatusText(tmp);
133 Draw ();
134 m_dMeanLength = pDoc->GetBondLength () * sin (pDoc->GetBondAngle () / 360. * M_PI) * m_dZoomFactor;
135 m_Allowed = true;
136 return true;
137 }
138
OnDrag()139 void gcpChainTool::OnDrag ()
140 {
141 double x1 = 0., y1 = 0., x2; // initialize to make gcc happy
142 unsigned nb;
143 gcp::Document* pDoc = m_pView->GetDoc ();
144 m_BondLength = pDoc->GetBondLength ();
145 gccv::Canvas *canvas = m_pView->GetCanvas ();
146 gccv::Item *item = canvas->GetItemAt (m_x, m_y);
147 Object* pObject = NULL;
148 if (item)
149 pObject = dynamic_cast <Object *> (item->GetClient ());
150 double dAngle = m_dAngle;
151 gcp::Atom *pAtom = NULL;
152 if (pObject) {
153 if (pObject->GetType () == BondType)
154 pAtom = (gcp::Atom*) pObject->GetAtomAt (m_x / m_dZoomFactor, m_y / m_dZoomFactor);
155 else if (pObject->GetType () == FragmentType)
156 pAtom = (gcp::Atom*) pObject->GetAtomAt (m_x1 / m_dZoomFactor, m_y1 / m_dZoomFactor);
157 else if (pObject->GetType () == AtomType)
158 pAtom = (gcp::Atom*) pObject;
159 }
160 m_Positive = (m_nState & GDK_LOCK_MASK && !(m_nState & GDK_MOD5_MASK)) ||
161 (m_nState & GDK_MOD5_MASK && !(m_nState & GDK_LOCK_MASK));
162 if (m_pObject && pAtom == m_pObject) {
163 if (m_AutoDir) {
164 m_dAngle = m_RefAngle + ((m_Positive)? +150: -150);
165 pAtom = NULL;
166 } else
167 return;
168 } else if (m_pObject || m_x != m_x0 || m_y != m_y0)
169 m_AutoDir = false;
170 // If m_Length has changed, adjust the points number
171 if (m_Length > 1 && m_CurPoints != m_Length + 1) {
172 m_CurPoints = m_Length + 1;
173 delete [] m_Points;
174 m_Points = new gccv::Point[m_CurPoints];
175 if (m_CurPoints > m_Atoms.size ())
176 m_Atoms.resize (m_CurPoints);
177 }
178 if (pAtom && gcp::MergeAtoms) {
179 // in that case, end the chain there with the current number of bonds
180 pAtom->GetCoords (&m_x, &m_y, NULL);
181 m_x *= m_dZoomFactor;
182 m_y *= m_dZoomFactor;
183 m_x-= m_x0;
184 m_y -= m_y0;
185 x2 = sqrt (m_x * m_x + m_y * m_y);
186 if (m_CurPoints % 2 == 0) {
187 x1 = m_dMeanLength * (m_CurPoints - 1);
188 y1 = pDoc->GetBondLength () * cos (pDoc->GetBondAngle () / 360. * M_PI) * m_dZoomFactor;
189 m_dAngle = (atan2 (-m_y, m_x) - atan2((m_Positive)? -y1: y1, x1)) / M_PI * 180.;
190 m_BondLength = pDoc->GetBondLength () * x2 / x1;
191 } else {
192 m_dAngle = atan2 (-m_y, m_x) / M_PI * 180.;
193 m_BondLength = x2 / (m_CurPoints - 1) / sin (pDoc->GetBondAngle () / 360. * M_PI) / m_dZoomFactor;
194 }
195 } else if (!m_AutoDir) {
196 m_x-= m_x0;
197 m_y -= m_y0;
198 if (m_x == 0) {
199 if (m_y == 0)
200 return;
201 dAngle = (m_y < 0)? 90: 270;
202 } else {
203 // calculate the angle and the real distance
204 dAngle = atan (-m_y/m_x) * 180 / M_PI;
205 if (!(m_nState & GDK_CONTROL_MASK))
206 dAngle = rint(dAngle / 5) * 5;
207 if (isnan (dAngle))
208 dAngle = m_dAngle;
209 else if (m_x < 0.)
210 dAngle += 180.;
211 }
212 m_dAngle = dAngle;
213 // Calculate number of bonds if Shift key is not pressed and we do use
214 // an automatic bonds number (otherwise, change the bonds lengths,
215 // but not their number
216 dAngle = atan2 (-m_y, m_x) - m_dAngle * M_PI / 180.;
217 x2 = sqrt ((m_x * m_x + m_y * m_y) * cos (dAngle));
218 if (m_nState & GDK_SHIFT_MASK)
219 m_BondLength = x2 / (m_CurPoints - 1) / sin (pDoc->GetBondAngle () / 360. * M_PI) / m_dZoomFactor;
220 else if (m_Length < 2) {
221 nb = 1 + (unsigned) rint (x2 / m_dMeanLength);
222 if (nb < 3)
223 nb = 3;
224 if (nb != m_CurPoints) {
225 m_CurPoints = nb;
226 delete [] m_Points;
227 m_Points = new gccv::Point[m_CurPoints];
228 if (m_CurPoints > m_Atoms.size ())
229 m_Atoms.resize (m_CurPoints);
230 }
231 }
232 }
233 m_Points->x = m_x0;
234 m_Points->y = m_y0;
235 FindAtoms ();
236 if (gcp::MergeAtoms && !(m_Allowed = CheckIfAllowed ())) {
237 if (m_Item) {
238 delete m_Item;
239 m_Item = NULL;
240 }
241 return;
242 }
243 char tmp[32];
244 snprintf (tmp, sizeof (tmp) - 1, _("Bonds: %d, Orientation: %g"), m_CurPoints - 1, m_dAngle);
245 m_pApp->SetStatusText(tmp);
246 Draw ();
247
248 }
249
OnRelease()250 void gcpChainTool::OnRelease ()
251 {
252 gcp::Document* pDoc = m_pView->GetDoc ();
253 unsigned nb;
254 gcp::Operation *pOp = NULL;
255 Object *pObject;
256 char const *Id;
257 gcp::Molecule *pMol = NULL;
258 gcp::Bond* pBond = NULL;
259 m_pApp->ClearStatus ();
260 m_AutoDir = false;
261 if (m_Item) {
262 delete m_Item;
263 m_Item = NULL;
264 } else
265 return;
266 if (!m_Allowed)
267 return;
268 // first save groups which need to be saved
269 for (nb = 0; nb < m_CurPoints; nb++) {
270 if (m_Atoms[nb]) {
271 if (pMol == NULL) {
272 pMol = dynamic_cast<gcp::Molecule *> (m_Atoms[nb]->GetMolecule ());
273 pMol->Lock (true);
274 }
275 pObject = m_Atoms[nb]->GetGroup ();
276 Id = pObject->GetId ();
277 if (ModifiedObjects.find (Id) == ModifiedObjects.end ()) {
278 if (!pOp)
279 pOp = pDoc->GetNewOperation (gcp::GCP_MODIFY_OPERATION);
280 pOp->AddObject (pObject);
281 ModifiedObjects.insert (Id);
282 }
283 }
284 }
285 // now add new atoms and bonds
286 for (nb = 0; nb < m_CurPoints; nb++) {
287 if (!m_Atoms[nb]) {
288 m_Atoms[nb] = new gcp::Atom (m_pApp->GetCurZ(),
289 m_Points[nb].x / m_dZoomFactor,
290 m_Points[nb].y / m_dZoomFactor,
291 0);
292 pDoc->AddAtom (m_Atoms[nb]);
293 }
294 // now add the bond. Atoms might be the same if the bonds are too short.
295 if (nb > 0 && m_Atoms[nb] != m_Atoms[nb - 1]) {
296 pBond = reinterpret_cast<gcp::Bond*> (m_Atoms[nb]->GetBond (m_Atoms[nb - 1]));
297 if (!pBond) {
298 pBond = new gcp::Bond (m_Atoms[nb - 1], m_Atoms[nb], 1);
299 pDoc->AddBond (pBond);
300 }
301 }
302 }
303 pObject = pBond->GetGroup ();
304 if (pOp) {
305 ModifiedObjects.insert (pObject->GetId ());
306 set<string>::iterator it, end = ModifiedObjects.end ();
307 for (it = ModifiedObjects.begin (); it != end; it++) {
308 pObject = pDoc->GetDescendant ((*it).c_str ());
309 if (pObject)
310 pOp->AddObject (pObject, 1);
311 }
312 } else {
313 pOp = pDoc->GetNewOperation (gcp::GCP_ADD_OPERATION);
314 pOp->AddObject (pObject);
315 }
316 pDoc->FinishOperation ();
317 if (pMol) {
318 pMol->Lock (false);
319 pMol->EmitSignal (gcp::OnChangedSignal);
320 }
321 ModifiedObjects.clear ();
322 }
323
on_length_changed(GtkSpinButton * btn,gcpChainTool * tool)324 static void on_length_changed (GtkSpinButton *btn, gcpChainTool *tool)
325 {
326 tool->SetLength (gtk_spin_button_get_value (btn));
327 }
328
on_angle_changed(GtkSpinButton * btn,gcpChainTool * tool)329 static void on_angle_changed (GtkSpinButton *btn, gcpChainTool *tool)
330 {
331 tool->SetAngle (gtk_spin_button_get_value (btn));
332 }
333
on_merge_toggled(GtkToggleButton * btn)334 static void on_merge_toggled (GtkToggleButton *btn)
335 {
336 gcp::MergeAtoms = gtk_toggle_button_get_active (btn);
337 }
338
on_number_toggled(GtkToggleButton * btn,gcpChainTool * tool)339 static void on_number_toggled (GtkToggleButton *btn, gcpChainTool *tool)
340 {
341 bool active = gtk_toggle_button_get_active (btn);
342 if (active)
343 tool->SetChainLength (0);
344 tool->SetAutoNumber (!gtk_toggle_button_get_active (btn));
345 }
346
on_number_changed(GtkSpinButton * btn,gcpChainTool * tool)347 static void on_number_changed (GtkSpinButton *btn, gcpChainTool *tool)
348 {
349 tool->SetChainLength (gtk_spin_button_get_value_as_int (btn));
350 }
351
SetAngle(double angle)352 void gcpChainTool::SetAngle (double angle)
353 {
354 m_pApp->GetActiveDocument ()->SetBondAngle (angle);
355 }
356
SetLength(double length)357 void gcpChainTool::SetLength (double length)
358 {
359 m_pApp->GetActiveDocument ()->SetBondLength (length);
360 }
361
GetPropertyPage()362 GtkWidget *gcpChainTool::GetPropertyPage ()
363 {
364 gcugtk::UIBuilder *builder = new gcugtk::UIBuilder (UIDIR"/chain.ui", GETTEXT_PACKAGE);
365 m_LengthBtn = GTK_SPIN_BUTTON (builder->GetWidget ("bond-length"));
366 g_signal_connect (m_LengthBtn, "value-changed", G_CALLBACK (on_length_changed), this);
367 m_AngleBtn = GTK_SPIN_BUTTON (builder->GetWidget ("bond-angle"));
368 g_signal_connect (m_AngleBtn, "value-changed", G_CALLBACK (on_angle_changed), this);
369 m_MergeBtn = GTK_TOGGLE_BUTTON (builder->GetWidget ("merge"));
370 g_signal_connect (m_MergeBtn, "toggled", G_CALLBACK (on_merge_toggled), NULL);
371 m_NumberBtn = GTK_SPIN_BUTTON (builder->GetWidget ("bonds-number"));
372 gtk_widget_set_sensitive (GTK_WIDGET (m_NumberBtn), false);
373 g_signal_connect (m_NumberBtn, "value-changed", G_CALLBACK (on_number_changed), this);
374 m_AutoBtn = GTK_TOGGLE_BUTTON (builder->GetWidget ("auto-number"));
375 gtk_toggle_button_set_active (m_AutoBtn, true);
376 g_signal_connect (m_AutoBtn, "toggled", G_CALLBACK (on_number_toggled), this);
377 GtkWidget *res = builder->GetRefdWidget ("chain");
378 delete builder;
379 return res;
380 }
381
Activate()382 void gcpChainTool::Activate ()
383 {
384 gcp::Document *pDoc = m_pApp->GetActiveDocument ();
385 gtk_spin_button_set_value (m_LengthBtn, pDoc->GetBondLength ());
386 gtk_spin_button_set_value (m_AngleBtn, pDoc->GetBondAngle ());
387 gtk_toggle_button_set_active (m_MergeBtn, gcp::MergeAtoms);
388 }
389
FindAtoms()390 void gcpChainTool::FindAtoms ()
391 {
392 double x1 = m_Points->x, y1 = m_Points->y, a;
393 unsigned nb;
394 for (nb = 1; nb < m_CurPoints; nb++) {
395 a = (m_dAngle +
396 ((m_Positive ^ (nb % 2))?
397 90. - m_pView->GetDoc ()->GetBondAngle () / 2.:
398 m_pView->GetDoc ()->GetBondAngle () / 2. - 90))
399 * M_PI / 180.;
400 x1 += m_BondLength * m_dZoomFactor * cos (a);
401 y1 -= m_BondLength * m_dZoomFactor * sin (a);
402 m_Atoms[nb] = NULL;
403 if (gcp::MergeAtoms) {
404 gccv::Canvas *canvas = m_pView->GetCanvas ();
405 gccv::Item *item = canvas->GetItemAt (x1, y1);
406 Object* pObject = NULL;
407 if (item)
408 pObject = dynamic_cast <Object *> (item->GetClient ());
409 if (pObject && pObject != m_pObject) {
410 if ((pObject->GetType () == BondType) || (pObject->GetType () == FragmentType)) {
411 m_Atoms[nb] = (gcp::Atom*) pObject->GetAtomAt (x1 / m_dZoomFactor, y1 / m_dZoomFactor);
412 } else if (pObject->GetType () == AtomType) {
413 m_Atoms[nb] = (gcp::Atom*) pObject;
414 }
415 }
416 if (m_Atoms[nb]) {
417 m_Atoms[nb]->GetCoords(&x1, &y1, NULL);
418 x1 *= m_dZoomFactor;
419 y1 *= m_dZoomFactor;
420 }
421 }
422 m_Points[nb].x = x1;
423 m_Points[nb].y = y1;
424 }
425 }
426
CheckIfAllowed()427 bool gcpChainTool::CheckIfAllowed ()
428 {
429 unsigned i, n;
430 gcp::Document *pDoc = m_pView->GetDoc ();
431 Object *group, *other;
432 if (m_Atoms[0]) {
433 group = m_Atoms[0]->GetMolecule ()->GetParent ();
434 if (group == pDoc)
435 group = NULL;
436 } else
437 group = NULL;
438 for (i = 1; i < m_CurPoints; i++) {
439 if (m_Atoms[i] == NULL)
440 continue;
441 if (group == NULL) {
442 other = m_Atoms[i]->GetMolecule ()->GetParent ();
443 if (other != pDoc)
444 group = other;
445 } else {
446 other = m_Atoms[i]->GetMolecule ()->GetParent ();
447 if (other && other != pDoc && other != group)
448 return false;
449 }
450 n = (!m_Atoms[i]->GetBond(m_Atoms[i - 1]))? 1: 0;
451 if ((i < m_CurPoints - 1) && !m_Atoms[i]->GetBond(m_Atoms[i + 1]))
452 n++;
453 if (n && !m_Atoms[i]->AcceptNewBonds (n))
454 return false;
455 }
456 return true;
457 }
458
OnKeyPress(GdkEvent * event)459 bool gcpChainTool::OnKeyPress (GdkEvent* event)
460 {
461 if (event->type == GDK_KEY_PRESS) {
462 unsigned n;
463 switch (((GdkEventKey*) event)->keyval) {
464 case GDK_KEY_KP_0:
465 case GDK_KEY_0:
466 gtk_toggle_button_set_active (m_AutoBtn, true);
467 return true;
468 case GDK_KEY_KP_1:
469 case GDK_KEY_1:
470 n = 10;
471 break;
472 case GDK_KEY_KP_2:
473 case GDK_KEY_2:
474 n = 2;
475 break;
476 case GDK_KEY_KP_3:
477 case GDK_KEY_3:
478 n = 3;
479 break;
480 case GDK_KEY_KP_4:
481 case GDK_KEY_4:
482 n = 4;
483 break;
484 case GDK_KEY_KP_5:
485 case GDK_KEY_5:
486 n = 5;
487 break;
488 case GDK_KEY_KP_6:
489 case GDK_KEY_6:
490 n = 6;
491 break;
492 case GDK_KEY_KP_7:
493 case GDK_KEY_7:
494 n = 7;
495 break;
496 case GDK_KEY_KP_8:
497 case GDK_KEY_8:
498 n = 8;
499 break;
500 case GDK_KEY_KP_9:
501 case GDK_KEY_9:
502 n = 9;
503 break;
504 default:
505 return false;
506 }
507 gtk_toggle_button_set_active (m_AutoBtn, false);
508 gtk_spin_button_set_value (m_NumberBtn, n);
509 OnChangeState ();
510 }
511 return false;
512 }
513
Draw()514 void gcpChainTool::Draw ()
515 {
516 gcp::Theme *pTheme = m_pView->GetDoc ()->GetTheme ();
517 if (!m_Item)
518 m_Item = new gccv::Group (m_pView->GetCanvas ());
519 unsigned i;
520 gccv::Item *item, *next;
521 list <gccv::Item *>::iterator it;
522 next = reinterpret_cast <gccv::Group *> (m_Item)->GetFirstChild (it);
523 for (i = 1; i < m_CurPoints; i++) {
524 if (next) {
525 item = next;
526 next = static_cast <gccv::Group *> (m_Item)->GetNextChild (it);
527 static_cast <gccv::Line *> (item)->SetPosition (m_Points[i-1].x, m_Points[i-1].y, m_Points[i].x, m_Points[i].y);
528 } else {
529 item = new gccv::Line (static_cast <gccv::Group *> (m_Item), m_Points[i-1].x, m_Points[i-1].y, m_Points[i].x, m_Points[i].y);
530 static_cast <gccv::LineItem *> (item)->SetLineColor (gcp::AddColor);
531 static_cast <gccv::LineItem *> (item)->SetLineWidth (pTheme->GetBondWidth ());
532 }
533 }
534 // now delete extra lines if any
535 list <gccv::Item *> lines;
536 while (next) {
537 lines.push_front (next);
538 next = static_cast <gccv::Group *> (m_Item)->GetNextChild (it);
539 }
540 while (!lines.empty ()) {
541 delete lines.front ();
542 lines.pop_front ();
543 }
544 }
545