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