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