1 // -*- C++ -*-
2
3 /*
4 * GChemPaint library
5 * reactant.cc
6 *
7 * Copyright (C) 2002-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 "application.h"
27 #include "document.h"
28 #include "reactant.h"
29 #include "reaction-step.h"
30 #include "text.h"
31 #include "theme.h"
32 #include "tool.h"
33 #include "view.h"
34 #include "widgetdata.h"
35 #include <gcugtk/ui-manager.h>
36 #include <gcu/objprops.h>
37 #include <glib/gi18n-lib.h>
38 #include <cstring>
39
40 using namespace gcu;
41 using namespace std;
42
43 namespace gcp {
44
45 extern xmlDocPtr pXmlDoc;
46
Reactant()47 Reactant::Reactant (): Object (ReactantType)
48 {
49 SetId ("r1");
50 m_Stoich = 0;
51 Child = NULL;
52 Stoichiometry = NULL;
53 }
54
Reactant(ReactionStep * step,Object * object)55 Reactant::Reactant (ReactionStep* step, Object *object) throw (invalid_argument): Object (ReactantType)
56 {
57 SetId ("r1");
58 step->AddChild (this);
59 GetDocument ()->EmptyTranslationTable();
60 static const set<TypeId>& allowed_types = Object::GetRules ("reactant", RuleMayContain);
61 if (allowed_types.find (object->GetType ()) == allowed_types.end ())
62 throw invalid_argument ("invalid reactant");
63 AddChild (object);
64 Child = object;
65 Stoichiometry = NULL;
66 m_Stoich = 0;
67 }
68
~Reactant()69 Reactant::~Reactant ()
70 {
71 }
72
do_add_stoichiometry(Reactant * reactant)73 static void do_add_stoichiometry (Reactant *reactant)
74 {
75 reactant->AddStoichiometry ();
76 }
77
BuildContextualMenu(gcu::UIManager * UIManager,Object * object,double x,double y)78 bool Reactant::BuildContextualMenu (gcu::UIManager *UIManager, Object *object, double x, double y)
79 {
80 GtkUIManager *uim = static_cast < gcugtk::UIManager * > (UIManager)->GetUIManager ();
81 bool result = false;
82 if (m_Stoich == 0 && !Stoichiometry) {
83 GtkActionGroup *group = gtk_action_group_new ("reactant");
84 GtkAction *action = gtk_action_new ("stoichiometry", _("Add a stoichiometry coefficient"), NULL, NULL);
85 gtk_action_group_add_action (group, action);
86 g_object_unref (action);
87 gtk_ui_manager_insert_action_group (uim, group, 0);
88 g_object_unref (group);
89 char buf[] = "<ui><popup><menuitem action='stoichiometry'/></popup></ui>";
90 gtk_ui_manager_add_ui_from_string (uim, buf, -1, NULL);
91 GtkWidget *w = gtk_ui_manager_get_widget (uim, "/popup/stoichiometry");
92 g_signal_connect_swapped (w, "activate", G_CALLBACK (do_add_stoichiometry), this);
93 result = true;
94 }
95 return result | Object::BuildContextualMenu (UIManager, object, x, y);
96 }
97
Save(xmlDocPtr xml) const98 xmlNodePtr Reactant::Save (xmlDocPtr xml) const
99 {
100 if (!Child)
101 return NULL;
102 xmlNodePtr node = xmlNewDocNode (xml, NULL, (const xmlChar*) "reactant", NULL);
103 SaveId (node);
104 xmlNodePtr child = Child->Save (xml);
105 xmlAddChild (node, child);
106 if (Stoichiometry) {
107 xmlNodePtr stoich = Stoichiometry->Save (xml);
108 xmlNodeSetName (stoich, (const xmlChar*) "stoichiometry");
109 xmlAddChild (node, stoich);
110 }
111 return node;
112 }
113
Load(xmlNodePtr node)114 bool Reactant::Load (xmlNodePtr node)
115 {
116 xmlChar* buf;
117 xmlNodePtr child;
118
119 Lock ();
120 buf = xmlGetProp (node, (xmlChar*) "id");
121 if (buf) {
122 SetId ((char*) buf);
123 xmlFree (buf);
124 }
125 child = node->children;
126 Document *pDoc = (Document*) GetDocument ();
127 while (child) {
128 if (!strcmp ((const char*) child->name, "stoichiometry")) {
129 if (Stoichiometry) {
130 Lock (false);
131 return false;
132 }
133 Stoichiometry = new Text ();
134 AddChild (Stoichiometry);
135 if (!Stoichiometry->Load (child)) {
136 delete Stoichiometry;
137 Lock (false);
138 return false;
139 };
140 pDoc->AddObject (Stoichiometry);
141 } else {
142 if (Child) {
143 if (strcmp ((const char*) child->name, "text")) {
144 Lock (false);
145 return false;
146 } else {
147 child = child->next;
148 continue;
149 }
150 }
151 Child = CreateObject ((const char*) child->name, this);
152 if (Child) {
153 AddChild (Child);
154 if (!Child->Load (child)) {
155 delete Child;
156 Child = NULL;
157 }
158 }
159 }
160 child = child->next;
161 }
162 Lock (false);
163 if (Child != NULL) {
164 pDoc->ObjectLoaded (this);
165 return true;
166 } else
167 return false;
168 }
169
GetYAlign()170 double Reactant::GetYAlign ()
171 {
172 return (Child)? Child->GetYAlign (): 0.;
173 }
174
AddStoichiometry()175 void Reactant::AddStoichiometry ()
176 {
177 Document *pDoc = dynamic_cast <Document*> (GetDocument ());
178 Application * pApp = pDoc->GetApplication ();
179 View *pView = pDoc->GetView ();
180 Theme *pTheme= pDoc->GetTheme ();
181 WidgetData *pData = (WidgetData*) g_object_get_data (G_OBJECT (pDoc->GetWidget ()), "data");
182 gccv::Rect rect;
183 Operation *op = pDoc->GetNewOperation (GCP_MODIFY_OPERATION);
184 op->AddNode (GetGroup ()->Save (pXmlDoc), 0);
185 pData->GetObjectBounds (this, &rect);
186 double x = rect.x0 / pTheme->GetZoomFactor ();
187 Text *text = new Text (x, GetYAlign ());
188 Stoichiometry = text;
189 AddChild (text);
190 pDoc->AddObject (text);
191 pData->GetObjectBounds (text, &rect);
192 Child->Move (rect.x1 / pTheme->GetZoomFactor () + pTheme->GetStoichiometryPadding () - x, 0.);
193 Tool *tool = pApp->GetTool ("Text");
194 GetParent ()->EmitSignal (OnChangedSignal);
195 pApp->ActivateTool ("Text", true);
196 tool->OnClicked (pView, text, rect.x0 * pTheme->GetZoomFactor (), GetYAlign () * pTheme->GetZoomFactor (), 0);
197 }
198
AddStoichiometry(gcp::Text * stoichiometry)199 void Reactant::AddStoichiometry (gcp::Text *stoichiometry)
200 {
201 if (stoichiometry == NULL)
202 return;
203 Stoichiometry = stoichiometry;
204 AddChild (stoichiometry);
205 }
206
OnSignal(SignalId Signal,G_GNUC_UNUSED Object * Obj)207 bool Reactant::OnSignal (SignalId Signal, G_GNUC_UNUSED Object *Obj)
208 {
209 if (Signal == OnChangedSignal) {
210 Document *pDoc = (Document*) GetDocument ();
211 Theme *pTheme= pDoc->GetTheme ();
212 WidgetData *pData = (WidgetData*) g_object_get_data (G_OBJECT (pDoc->GetWidget ()), "data");
213 gccv::Rect rect;
214 unsigned n = GetChildrenNumber ();
215 map<string, Object*>::iterator i;
216 Object *pObj, *parent = GetParent ();
217 if (n == 0)
218 delete this;
219 else if (n == 1) {
220 if (Stoichiometry) {
221 // Child or stoichiometry have been deleted
222 pObj = GetFirstChild (i);
223 if (pObj == Child)
224 Stoichiometry = NULL;
225 else {
226 pDoc->Remove (Stoichiometry);
227 delete this;
228 }
229 } else if (GetFirstChild (i) != Child)
230 Child = (*i).second;
231 parent->EmitSignal (OnChangedSignal);
232 } else if ((n == 2) && Stoichiometry) {
233 // Just need to space the two children
234 pData->GetObjectBounds (Stoichiometry, &rect);
235 double x = rect.x1 / pTheme->GetZoomFactor () + pTheme->GetStoichiometryPadding ();
236 pData->GetObjectBounds (Child, &rect);
237 Child->Move (x - rect.x0 / pTheme->GetZoomFactor (), 0.);
238 char const *txt = static_cast <TextObject *> (Stoichiometry)->GetBuffer ().c_str ();
239 char *endptr;
240 int n = strtol (txt, &endptr, 10);
241 m_Stoich = (!*endptr)? n: 0;
242 } else {
243 // Most probably child has been splitted
244 xmlNodePtr node = NULL;
245 bool ChildFound = false;
246 ReactionStep *step = reinterpret_cast<ReactionStep*> (GetParent ());
247 if (Stoichiometry)
248 node = Stoichiometry->Save (pXmlDoc);
249 pObj = GetFirstChild (i);
250 while (pObj) {
251 if (pObj == Child)
252 ChildFound = true;
253 else if (pObj->GetType () == MesomeryArrowType) {
254 // A mesomery inside the reaction has been destroyed, we need to destoy the whole reaction as well
255 ChildFound = false;
256 break;
257 } else if (pObj != Stoichiometry) {
258 Reactant *reactant = new Reactant (step, pObj);
259 if (Stoichiometry) {
260 reactant->Stoichiometry = new Text ();
261 reactant->AddChild (reactant->Stoichiometry);
262 pDoc->AddObject (reactant->Stoichiometry);
263 reactant->Stoichiometry->Load (node);
264 reactant->EmitSignal (OnChangedSignal);
265 }
266 pObj = GetFirstChild (i);
267 continue;
268 }
269 pObj = GetNextChild (i);
270 }
271 if (!ChildFound) {
272 if (Stoichiometry)
273 pDoc->Remove (Stoichiometry);
274 delete this;
275 }
276 if (node)
277 xmlFreeNode (node);
278 return true;
279 }
280 }
281 return true;
282 }
283
SetMolecule(gcu::Object * molecule)284 void Reactant::SetMolecule (gcu::Object *molecule)
285 {
286 if (molecule == NULL)
287 return;
288 if (Child)
289 delete Child;
290 Child = molecule;
291 AddChild (molecule);
292 }
293
GetProperty(unsigned property) const294 std::string Reactant::GetProperty (unsigned property) const
295 {
296 std::string res;
297 switch (property) {
298 case GCU_PROP_MOLECULE:
299 if (Child)
300 res = Child->GetId ();
301 break;
302 case GCU_PROP_STOICHIOMETRY:
303 if (Stoichiometry)
304 res = Stoichiometry->GetId ();
305 break;
306 default:
307 return Object::GetProperty (property);
308 }
309 return res;
310 }
311
SetProperty(unsigned property,char const * value)312 bool Reactant::SetProperty (unsigned property, char const *value)
313 {
314 gcu::Document *doc = GetDocument ();
315 switch (property) {
316 case GCU_PROP_MOLECULE: {
317 if (doc == NULL)
318 return false;
319 if (Child != NULL && !strcmp (Child->GetId (), value)) {
320 break;
321 }
322 gcu::Object *new_child = doc->GetDescendant (value);
323 Application *app = static_cast <gcp::Application * > (doc->GetApplication ());
324 std::set < TypeId > const &rules = app->GetRules (ReactantType, RuleMayContain);
325 if (new_child != NULL && rules.find (new_child->GetType ()) != rules.end ()) {
326 if (Child != NULL)
327 Child->SetParent (doc);
328 Child = new_child;
329 AddChild (Child);
330 }
331 break;
332 }
333 case GCU_PROP_STOICHIOMETRY:
334 if (doc == NULL)
335 return false;
336 if (Stoichiometry != NULL && !strcmp (Stoichiometry->GetId (), value)) {
337 break;
338 }
339 if (Stoichiometry != NULL)
340 Stoichiometry->SetParent (doc);
341 Stoichiometry = dynamic_cast < gcp::Text * > (doc->GetDescendant (value));
342 if (Stoichiometry != NULL)
343 AddChild (Stoichiometry);
344 break;
345 }
346 return true;
347 }
348
Name()349 std::string Reactant::Name ()
350 {
351 return _("Reactant");
352 }
353
354 } // namespace gcp
355