1 // -*- C++ -*-
2 
3 /*
4  * GChemPaint selection plugin
5  * bracketstool.cc
6  *
7  * Copyright (C) 2007-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 "bracketstool.h"
27 #include "group.h"
28 #include <gcugtk/message.h>
29 #include <gcugtk/ui-builder.h>
30 #include <gcp/application.h>
31 #include <gcp/atom.h>
32 #include <gcp/brackets.h>
33 #include <gcp/bond.h>
34 #include <gcp/document.h>
35 #include <gcp/fontsel.h>
36 #include <gcp/fragment.h>
37 #include <gcp/mechanism-step.h>
38 #include <gcp/reaction-step.h>
39 #include <gcp/settings.h>
40 #include <gcp/theme.h>
41 #include <gcp/view.h>
42 #include <gcp/widgetdata.h>
43 #include <gccv/canvas.h>
44 #include <gccv/group.h>
45 #include <gccv/item-client.h>
46 #include <gccv/rectangle.h>
47 #include <glib/gi18n-lib.h>
48 #include <typeinfo>
49 
gcpBracketsTool(gcp::Application * App)50 gcpBracketsTool::gcpBracketsTool (gcp::Application* App): gcp::Tool (App, "Brackets")
51 {
52 	m_Type = gccv::BracketsTypeNormal;
53 	m_Used = gccv::BracketsBoth;
54 	m_FontDesc = pango_font_description_new ();
55 	m_ActualBounds.x0 = 0.;
56 	m_ActualBounds.y0 = 0.;
57 	m_ActualBounds.x1 = 0.;
58 	m_ActualBounds.y1 = 0.;
59 }
60 
~gcpBracketsTool()61 gcpBracketsTool::~gcpBracketsTool ()
62 {
63 	pango_font_description_free (m_FontDesc);
64 }
65 
OnClicked()66 bool gcpBracketsTool::OnClicked ()
67 {
68 	return true;
69 }
70 
OnDrag()71 void gcpBracketsTool::OnDrag ()
72 {
73 	gcp::Theme *theme = m_pView->GetDoc ()->GetTheme ();
74 	if (m_Item) {
75 		static_cast < gccv::Rectangle * > (m_Rect)->SetPosition (m_x0, m_y0, m_x - m_x0, m_y - m_y0);
76 	} else {
77 		m_Item = new gccv::Group (m_pView->GetCanvas ());
78 		m_Rect = new gccv::Rectangle (static_cast < gccv::Group * > (m_Item), m_x0, m_y0, m_x - m_x0, m_y - m_y0, NULL);
79 		static_cast <gccv::LineItem *> (m_Rect)->SetLineWidth (theme->GetBondWidth ());
80 		static_cast <gccv::FillItem *> (m_Rect)->SetFillColor (0);
81 		static_cast <gccv::LineItem *> (m_Rect)->SetLineColor (gcp::AddColor);
82 		m_Bracket = new gccv::Brackets (static_cast < gccv::Group * > (m_Item), m_Type, m_Used, m_FontName.c_str (), 0., 0., 0., 0., NULL);
83 		static_cast <gccv::Brackets *> (m_Bracket)->SetLineColor (gcp::AddColor);
84 	}
85 	// find everything inside the selected rectangle and select
86 	gccv::Group *group = m_pView->GetCanvas ()->GetRoot ();
87 	// all client top items are implemented as a child of the root
88 	std::list <gccv::Item *>::iterator it;
89 	gccv::Item *item = group->GetFirstChild (it);
90 	double x0, x1, y0, y1, xmin, xmax, ymin, ymax;
91 	m_Rect->GetBounds (xmin, ymin, xmax, ymax);
92 	gcu::Object *object;
93 	m_pData->UnselectAll ();
94 	std::set <gcu::Object *> linked_objects;
95 	std::set <gcu::Object *>::iterator i, iend;
96 	while (item) {
97 		if (item != m_Item) {
98 			item->GetBounds (x0, y0, x1, y1);
99 			if ((x0 < xmax) && (y0 < ymax) && (x1 > xmin) && (y1 > ymin)) {
100 				object = dynamic_cast <gcu::Object *> (item->GetClient ());
101 				if (object && object->GetCoords (&x0, &y0) && !m_pData->IsSelected (object)) {
102 					x0 *= m_dZoomFactor;
103 					y0 *= m_dZoomFactor;
104 					if (x0 >= xmin && x0 <= xmax && y0 >= ymin && y0 <= ymax) {
105 						m_pData->SetSelected (object);
106 						gcp::Atom *atom = NULL;
107 						switch (object->GetType ()) {
108 						case gcu::FragmentType:
109 							atom = static_cast <gcp::Fragment *> (object)->GetAtom ();
110 						case gcu::AtomType: {
111 							if (atom == NULL)
112 								atom = static_cast <gcp::Atom *> (object);
113 							// go through the bonds and select them if both ends are selected
114 							std::map<gcu::Atom*, gcu::Bond*>::iterator i;
115 							gcu::Bond *bond = atom->GetFirstBond (i);
116 							while (bond) {
117 								if (m_pData->IsSelected (bond->GetAtom (atom)))
118 									m_pData->SetSelected (bond);
119 								bond = atom->GetNextBond (i);
120 							}
121 						}
122 						default: {
123 							// go through the links and store them for later treatment
124 							gcu::Object *linked_obj;
125 							linked_obj = object->GetFirstLink (i);
126 							while (linked_obj) {
127 								linked_objects.insert (linked_obj);
128 								linked_obj = object->GetNextLink (i);
129 							}
130 							break;
131 						}
132 						}
133 					}
134 				}
135 			}
136 		}
137 		item = group->GetNextChild (it);
138 	}
139 	// now check if linked objects have all their links selected, and, if yes, select them
140 	for (i = linked_objects.begin (), iend = linked_objects.end (); i != iend; i++)
141 		if ((*i)->CanSelect ())
142 			m_pData->SetSelected (*i);
143 	m_pData->SimplifySelection ();
144 	gccv::Rect r = m_ActualBounds;
145 	if (Evaluate ()) {
146 		// add padding
147 		double pad = theme->GetPadding (); // FIXME: BracketsPadding?
148 		m_ActualBounds.x0 -= pad;
149 		m_ActualBounds.y0 -= pad;
150 		m_ActualBounds.x1 += pad;
151 		m_ActualBounds.y1 += pad;
152 		static_cast < gccv::LineItem * > (m_Rect)->SetLineColor (gcp::AddColor);
153 		if (r.x0 != m_ActualBounds.x0 || r.y0 != m_ActualBounds.y0 || r.x1 != m_ActualBounds.x1 || r.y1 != m_ActualBounds.y1)
154 			static_cast < gccv::Brackets * > (m_Bracket)->SetPosition (m_ActualBounds.x0, m_ActualBounds.y0, m_ActualBounds.x1, m_ActualBounds.y1);
155 		m_Bracket->SetVisible (true);
156 	} else {
157 		static_cast < gccv::LineItem * > (m_Rect)->SetLineColor (gcp::DeleteColor);
158 		m_Bracket->SetVisible (false);
159 	}
160 }
161 
OnRelease()162 void gcpBracketsTool::OnRelease ()
163 {
164 	if (Evaluate ()) {
165 		gcp::Document * doc = m_pView->GetDoc ();
166 		gcp::Operation *op = doc->GetNewOperation (gcp::GCP_MODIFY_OPERATION);
167 		op->AddObject (m_Target, 0);
168 		gcp::Brackets *brackets = new gcp::Brackets (m_Type);
169 		if (m_Used != gccv::BracketsBoth)
170 			brackets->SetUsed (m_Used);
171 		brackets->SetEmbeddedObjects (m_pData->SelectedObjects);
172 		op->AddObject (m_Target, 1);
173 		m_pView->AddObject (brackets);
174 		brackets->EmitSignal (gcp::OnChangedSignal);
175 	}
176 	m_pData->UnselectAll ();
177 }
178 
GetPropertyPage()179 GtkWidget *gcpBracketsTool::GetPropertyPage ()
180 {
181 	gcugtk::UIBuilder *builder= NULL;
182 	try {
183 		builder = new gcugtk::UIBuilder (UIDIR"/brackets.ui", GETTEXT_PACKAGE);
184 		GtkComboBox *box = GTK_COMBO_BOX (builder->GetWidget ("type-box"));
185 		gtk_combo_box_set_active (box, m_Type);
186 		g_signal_connect (box, "changed", G_CALLBACK (gcpBracketsTool::OnTypeChanged), this);
187 		box = GTK_COMBO_BOX (builder->GetWidget ("used-box"));
188 		gtk_combo_box_set_active (box, m_Used - 1);
189 		g_signal_connect (box, "changed", G_CALLBACK (gcpBracketsTool::OnUsedChanged), this);
190 		GtkWidget *fgrid = builder->GetWidget ("font-grid");
191 		GtkWidget *widget = GTK_WIDGET (g_object_new (GCP_TYPE_FONT_SEL, "allow-slanted", false, "label", "{[()]}", "expand", true, NULL));
192 		gtk_container_add (GTK_CONTAINER (fgrid), widget);
193 		gtk_widget_show_all (widget);
194 		m_FontSel = reinterpret_cast <GcpFontSel *> (widget);
195 		g_signal_connect (m_FontSel, "changed", G_CALLBACK (gcpBracketsTool::OnFontChanged), this);
196 
197 		GtkWidget *res = builder->GetRefdWidget ("brackets");
198 		delete builder;
199 		return res;
200 	}
201 	catch (std::runtime_error &e) {
202 		// TODO: add a one time message box
203 		static bool done = false;
204 		if (!done) {
205 			done = true;
206 			std::string mess = _("Error loading the properties widget description:\n");
207 			mess += e.what ();
208 			new gcugtk::Message (GetApplication (), mess, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE);
209 		}
210 		if (builder)
211 			delete builder;
212 		return NULL;
213 	}
214 }
215 
OnTypeChanged(GtkComboBox * box,gcpBracketsTool * tool)216 void gcpBracketsTool::OnTypeChanged (GtkComboBox *box, gcpBracketsTool *tool)
217 {
218 	tool->m_Type = static_cast < gccv::BracketsTypes > (gtk_combo_box_get_active (box));
219 }
220 
OnUsedChanged(GtkComboBox * box,gcpBracketsTool * tool)221 void gcpBracketsTool::OnUsedChanged (GtkComboBox *box, gcpBracketsTool *tool)
222 {
223 	tool->m_Used = static_cast < gccv::BracketsUses > (gtk_combo_box_get_active (box) % 3 + 1);
224 }
225 
OnFontChanged(GcpFontSel * fontsel,gcpBracketsTool * tool)226 void gcpBracketsTool::OnFontChanged (GcpFontSel *fontsel, gcpBracketsTool *tool)
227 {
228 	char *family;
229 	gcp::Document *doc = tool->m_pApp->GetActiveDocument ();
230 	g_object_get (fontsel, "family", &family, "size", &tool->m_FontSize, NULL);
231 	tool->m_FontFamily = family;
232 	doc->SetBracketsFontFamily (family);
233 	doc->SetBracketsFontSize (tool->m_FontSize);
234 	pango_font_description_set_family (tool->m_FontDesc, family);
235 	pango_font_description_set_size (tool->m_FontDesc, tool->m_FontSize);
236 	g_free (family);
237 	family = pango_font_description_to_string (tool->m_FontDesc);
238 	tool->m_FontName = family;
239 	g_free (family);
240 }
241 
Activate()242 void gcpBracketsTool::Activate ()
243 {
244 	gcp::Document *doc = m_pApp->GetActiveDocument ();
245 	m_FontFamily = doc->GetBracketsFontFamily ();
246 	m_FontSize = doc->GetBracketsFontSize ();
247 	pango_font_description_set_family (m_FontDesc, m_FontFamily.c_str ());
248 	pango_font_description_set_size (m_FontDesc, m_FontSize);
249 	g_object_set (G_OBJECT (m_FontSel),
250 					"family", m_FontFamily.c_str (),
251 					"size", m_FontSize,
252 					NULL);
253 	char *buf = pango_font_description_to_string (m_FontDesc);
254 	m_FontName = buf;
255 	g_free (buf);
256 }
257 
Evaluate()258 bool gcpBracketsTool::Evaluate ()
259 {
260 	gcu::Object *obj;
261 	if (m_pData->SelectedObjects.size () == 0)
262 		return false;
263 	std::set <gcu::TypeId> const &rules = m_pApp->GetRules (gcp::BracketsType, gcu::RuleMayContain);
264 	std::set < gcu::Object * >::iterator i = m_pData->SelectedObjects.begin (),
265 									   end = m_pData->SelectedObjects.end ();
266 	if (m_pData->SelectedObjects.size () == 1) {
267 		obj = *m_pData->SelectedObjects.begin ();
268 		gcu::TypeId type = obj->GetType ();
269 		if (type == gcu::MoleculeType || type == gcp::ReactionStepType ||
270 		    type == gcp::MechanismStepType || type == gcu::MesomeryType ||
271 		    rules.find (type) != rules.end ()) {
272 			// Do not accept a new bracket if one already exist
273 			std::map < std::string, gcu::Object * >::iterator j;
274 			gcu::Object *child;
275 			for (child = obj->GetFirstChild (j); child; child = obj->GetNextChild (j)) {
276 				gcp::Brackets *br = dynamic_cast < gcp::Brackets * > (child);
277 				if (br && br->GetEmbeddedObjects ().size () == 1 && *br->GetEmbeddedObjects ().begin () == obj)
278 					return false;
279 			}
280 			// Evaluate bounds
281 			m_pData->GetObjectBounds (obj, &m_ActualBounds);
282 			m_Target = obj;
283 			return true;
284 		}
285 	}
286 	if (m_Used != gccv::BracketsBoth)
287 		return false;
288 	i = m_pData->SelectedObjects.begin ();
289 	gcu::Object *molecule = (*i)->GetMolecule ();
290 	if (molecule != NULL) {
291 		for (i++; i != end; i++)
292 			if ((*i)->GetMolecule () != molecule)
293 				goto not_a_molecule;
294 		// now we need to test whether all selected atoms are connected (is this true?)
295 		if (!gcp::Brackets::ConnectedAtoms (m_pData->SelectedObjects))
296 			return false;
297 		// Do not accept a new bracket if one already exist with same embedded objects.
298 		std::map < std::string, gcu::Object * >::iterator o;
299 		gcu::Object *child;
300 		for (child = molecule->GetFirstChild (o); child; child = molecule->GetNextChild (o)) {
301 			if (child->GetType () != gcp::BracketsType)
302 				continue;
303 			std::set < gcu::Object * > const &objects = static_cast < gcp::Brackets * > (child)->GetEmbeddedObjects ();
304 			std::set < gcu::Object * >::const_iterator oend = objects.end ();
305 			bool result = false;
306 			for (i = m_pData->SelectedObjects.begin (); i != end; i++) {
307 				if ((*i)->GetType () == gcp::BracketsType)
308 					continue;
309 				if (objects.find (*i) == oend) {
310 					result = true;
311 					break;
312 				}
313 			}
314 			if (!result)
315 				return false;
316 		}
317 		m_pData->GetSelectionBounds (m_ActualBounds);
318 		m_Target = molecule;
319 		return true;
320 	}
321 not_a_molecule:
322 	return false;
323 }
324