1 // -*- C++ -*-
2 
3 /*
4  * GChemPaint templates plugin
5  * templatetool.cc
6  *
7  * Copyright (C) 2004-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 "templatetool.h"
27 #include "templatetree.h"
28 #include <gcp/application.h>
29 #include <gcp/document.h>
30 #include <gcp/theme.h>
31 #include <gcp/view.h>
32 #include <gcp/widgetdata.h>
33 #include <gccv/canvas.h>
34 #include <gcugtk/dialog.h>
35 #include <gcugtk/message.h>
36 #include <gtk/gtk.h>
37 #include <glib/gi18n-lib.h>
38 #include <cmath>
39 
40 using namespace gcu;
41 
42 xmlDocPtr xml;
43 
44 class gcpNewTemplateToolDlg: public gcugtk::Dialog
45 {
46 public:
47 	gcpNewTemplateToolDlg (gcp::Application* App);
48 	virtual ~gcpNewTemplateToolDlg ();
49 
50 	virtual bool Apply ();
51 	void SetTemplate (xmlNodePtr node);
52 
53 private:
54 	gcpTemplate *temp;
55 	gcp::Document *pDoc;
56 	gcp::WidgetData* pData;
57 	xmlNodePtr m_node;
58 	GtkEntry *category_entry;
59 };
60 
gcpTemplateTool(gcp::Application * App)61 gcpTemplateTool::gcpTemplateTool (gcp::Application* App): gcp::Tool (App, "Templates")
62 {
63 	m_Template = NULL;
64 	xml = xmlNewDoc ((const xmlChar*) "1.0");
65 }
66 
~gcpTemplateTool()67 gcpTemplateTool::~gcpTemplateTool ()
68 {
69 	xmlFreeDoc (xml);
70 }
71 
OnClicked()72 bool gcpTemplateTool::OnClicked ()
73 {
74 	gcp::Document* pDoc = m_pView->GetDoc ();
75 	gcpNewTemplateToolDlg *dlg = (gcpNewTemplateToolDlg*) m_pApp->GetDialog ("new-template");
76 	if (dlg) {
77 		m_pObject = m_pObject->GetMolecule ();
78 		if (m_pObject) {
79 			xmlNodePtr node = m_pObject->Save (xml);
80 			if (node) {
81 				char *buf = g_strdup_printf ("%g", pDoc->GetTheme ()->GetBondLength ());
82 				xmlNewProp (node, (const xmlChar*) "bond-length", (const xmlChar*) buf);
83 				g_free (buf);
84 				dlg->SetTemplate (node);
85 				gdk_window_raise (gtk_widget_get_window (GTK_WIDGET (dlg->GetWindow ())));
86 			}
87 		}
88 		return false;
89 	}
90 	if (!m_Template)
91 		return false;
92 	pDoc->PasteData (m_Template->node);
93 	m_pObject = *m_pData->SelectedObjects.begin ();
94 	if (m_Template->bond_length != 0.) { // if not, there is no bond...
95 		double r = pDoc->GetBondLength () / m_Template->bond_length;
96 		if (fabs (r - 1.) > .0001) {
97 			Matrix2D m (r, 0., 0., r);
98 			// FIXME: this would not work for reactions
99 			m_pObject->Transform2D (m, 0., 0.);
100 			m_pView->Update (m_pObject);
101 		}
102 	}
103 	gccv::Rect rect;
104 	double dx, dy;
105 	pDoc->AbortOperation ();
106 	m_pData->GetSelectionBounds (rect);
107 	dx = m_x0 - (rect.x0 + rect.x1) / 2.;
108 	dy = m_y0 - (rect.y0 + rect.y1) / 2.;
109 	m_x0 -= dx;
110 	m_y0 -= dy;
111 	m_pData->MoveSelectedItems (dx, dy);
112 	return true;
113 }
114 
OnDrag()115 void gcpTemplateTool::OnDrag ()
116 {
117 	double dx = m_x - m_x1, dy = m_y - m_y1;
118 	m_x1 = m_x;
119 	m_y1 = m_y;
120 	m_pData->MoveSelectedItems (dx, dy);
121 }
122 
OnRelease()123 void gcpTemplateTool::OnRelease ()
124 {
125 	gcp::Document* pDoc = m_pView->GetDoc ();
126 	double dx = m_x - m_x0, dy = m_y - m_y0;
127 	m_pData->MoveSelectedItems (-dx, -dy);
128 	m_pData->MoveSelection(dx, dy);
129 	pDoc->PopOperation ();
130 	m_pData->UnselectAll ();
131 	gcp::Operation* pOp = pDoc->GetNewOperation (gcp::GCP_ADD_OPERATION);
132 	pOp->AddObject (m_pObject);
133 	pDoc->FinishOperation ();
134 }
135 
136 enum {
137 	NAME_COLUMN,
138 	NUM_COLUMNS
139 };
140 
on_template_changed(GtkComboBox * combo,gcpTemplateTool * tool)141 static void on_template_changed (GtkComboBox *combo, gcpTemplateTool *tool)
142 {
143 	tool->OnChanged (combo);
144 }
145 
on_size(G_GNUC_UNUSED GtkWidget * widget,GtkAllocation * allocation,gcpTemplateTool * tool)146 static void on_size (G_GNUC_UNUSED GtkWidget *widget, GtkAllocation *allocation, gcpTemplateTool *tool)
147 {
148 	tool->OnPreviewSize (allocation);
149 }
150 
on_add_template(G_GNUC_UNUSED GtkWidget * w,gcpTemplateTool * tool)151 static void on_add_template (G_GNUC_UNUSED GtkWidget *w, gcpTemplateTool *tool)
152 {
153 	tool->OnAddTemplate ();
154 }
155 
on_delete_template(G_GNUC_UNUSED GtkWidget * w,gcpTemplateTool * tool)156 static void on_delete_template (G_GNUC_UNUSED GtkWidget *w, gcpTemplateTool *tool)
157 {
158 	tool->OnDeleteTemplate ();
159 }
160 
GetPropertyPage()161 GtkWidget *gcpTemplateTool::GetPropertyPage ()
162 {
163 	gcugtk::UIBuilder *builder = new gcugtk::UIBuilder (UIDIR"/templates.ui", GETTEXT_PACKAGE);
164 	gcpTemplateTree *tree = (gcpTemplateTree*) m_pApp->GetTool ("TemplateTree");
165 	if (!tree)
166 		return NULL;
167 	GtkComboBox *combo = GTK_COMBO_BOX (builder->GetWidget ("templates-combo"));
168 	gtk_combo_box_set_model (combo, tree->GetModel ());
169 	GtkCellRenderer* renderer = (GtkCellRenderer*) gtk_cell_renderer_text_new ();
170 	g_object_set (renderer, "xalign", 0.0, NULL);
171 	gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo));
172 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, true);
173 	gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo),
174 								renderer, "text",
175 								NAME_COLUMN);
176 	gtk_combo_box_set_active (combo, 0);
177 	g_signal_connect (G_OBJECT (combo), "changed", G_CALLBACK (on_template_changed), this);
178 	m_DeleteBtn = builder->GetWidget ("delete");
179 	g_signal_connect (m_DeleteBtn, "clicked", G_CALLBACK (on_delete_template), this);
180 	gtk_widget_set_sensitive (m_DeleteBtn, false);
181 	GtkWidget *w = builder->GetWidget ("add");
182 	g_signal_connect (w, "clicked", G_CALLBACK (on_add_template), this);
183 	m_Book = GTK_NOTEBOOK (builder->GetWidget ("book"));
184 	g_signal_connect (m_Book, "size-allocate", G_CALLBACK (on_size), this);
185 
186 	GtkWidget *res = builder->GetRefdWidget ("templates");
187 	delete builder;
188 	return res;
189 }
190 
OnChanged(GtkComboBox * combo)191 void gcpTemplateTool::OnChanged (GtkComboBox *combo)
192 {
193 	gcpTemplateTree *tree = (gcpTemplateTree*) m_pApp->GetTool ("TemplateTree");
194 	if (!tree)
195 		return;
196 	GtkTreeIter iter;
197 	if (gtk_combo_box_get_active_iter (combo, &iter)) {
198 		GtkTreePath *path = gtk_tree_model_get_path ((GtkTreeModel*) tree->GetModel (), &iter);
199 		char* path_string = gtk_tree_path_to_string (path);
200 		m_Template = tree->GetTemplate (path_string);
201 		int page;
202 		if (m_Template) {
203 			if (!m_Template->doc) {
204 				m_Template->doc = new gcp::Document (NULL, false);
205 				gcp::Theme *pTheme = m_Template->doc->GetTheme ();
206 				m_Template->doc->SetEditable (false);
207 				m_Template->data =  (gcp::WidgetData*) g_object_get_data (G_OBJECT (m_Template->doc->GetView ()->CreateNewWidget ()), "data");
208 				m_Template->doc->PasteData (m_Template->node);
209 				m_Template->data->UnselectAll ();
210 				m_Template->data->GetObjectBounds (m_Template->doc, &m_Template->rect);
211 				m_Template->doc->Move (-m_Template->rect.x0 / pTheme->GetZoomFactor (), -m_Template->rect.y0 / pTheme->GetZoomFactor ());
212 				m_Template->doc->GetView ()->Update (m_Template->doc);
213 				m_Template->bond_length = 140.;
214 				page = -1;
215 			} else
216 				page = gtk_notebook_page_num (m_Book, m_Template->w);
217 			if (page < 0) {
218 				m_Template->data =  (gcp::WidgetData*) g_object_get_data (G_OBJECT (m_Template->doc->GetView ()->CreateNewWidget ()), "data");
219 				m_Template->data->GetObjectBounds (m_Template->doc, &m_Template->rect);
220 				m_Template->w = gtk_scrolled_window_new (NULL, NULL);
221 				gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (m_Template->w), GTK_SHADOW_NONE);
222 				gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (m_Template->w), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
223 				gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW (m_Template->w), m_Template->doc->GetWidget ());
224 				gtk_widget_show_all (m_Template->w);
225 				gtk_notebook_append_page (m_Book, m_Template->w, NULL);
226 				page = gtk_notebook_page_num (m_Book, m_Template->w);
227 			}
228 			// Why 4 and not 2 in following lines?
229 //			GtkStyle *style = gtk_widget_get_style (m_Template->w);
230 			double w = (m_Width /*- 4 * style->xthickness*/) / (m_Template->rect.x1 - m_Template->rect.x0);
231 			double h = (m_Height/* - 4 * style->ythickness*/) / (m_Template->rect.y1 - m_Template->rect.y0);
232 			if (w < 1. || h < 1.) {
233 				m_Template->data->Zoom = MIN (w, h);
234 			}
235 			gtk_notebook_set_current_page (m_Book, page);
236 			gtk_widget_set_sensitive (m_DeleteBtn, m_Template->writeable);
237 		} else {
238 			// a category has been selected
239 			m_Template = NULL;
240 			GtkTreeModel *model = tree->GetModel ();
241 			char const *name;
242 			gtk_tree_model_get (model, &iter, 0, &name, -1);
243 			gtk_widget_set_sensitive (m_DeleteBtn, false);
244 			gtk_notebook_set_current_page (m_Book, 0);
245 			gtk_widget_set_sensitive (m_DeleteBtn, false);
246 		}
247 		g_free (path_string);
248 		gtk_tree_path_free (path);
249 	}
250 }
251 
OnPreviewSize(GtkAllocation * allocation)252 void gcpTemplateTool::OnPreviewSize (GtkAllocation *allocation)
253 {
254 	m_Width = allocation->width;
255 	m_Height = allocation->height;
256 	if (m_Template) {
257 //		GtkStyle *style = gtk_widget_get_style (m_Template->w);
258 		double w = (m_Width /*- 4 * style->xthickness*/) / (m_Template->rect.x1 - m_Template->rect.x0);
259 		double h = (m_Height /*- 4 * style->ythickness*/) / (m_Template->rect.y1 - m_Template->rect.y0);
260 		m_Template->data->Zoom = (w < 1. || h < 1.)? MIN (w, h): 1.;
261 		m_Template->doc->GetView ()->GetCanvas ()->SetZoom (m_Template->data->Zoom);
262 	}
263 }
264 
OnAddTemplate()265 void gcpTemplateTool::OnAddTemplate ()
266 {
267 	new gcpNewTemplateToolDlg (m_pApp);
268 }
269 
OnDeleteTemplate()270 void gcpTemplateTool::OnDeleteTemplate ()
271 {
272 	gcpTemplateTree *tree = (gcpTemplateTree*) m_pApp->GetTool ("TemplateTree");
273 	if (!tree)
274 		return;
275 	string key = m_Template->category + "/" + m_Template->name;
276 	if (Templates[key] != m_Template) {
277 		int i = 0;
278 		char* str = g_strdup_printf ("%d", i);
279 		while (Templates[key + str] != m_Template) {
280 			g_free (str);
281 			str = g_strdup_printf ("%d", ++i);
282 		}
283 		key += str;
284 		g_free (str);
285 	}
286 	tree->DeleteTemplate (key);
287 	m_Template = NULL;
288 	gtk_notebook_set_current_page (m_Book, 0);
289 }
290 
OnConfigChanged()291 void gcpTemplateTool::OnConfigChanged ()
292 {
293 	map<string, gcpTemplate*>::iterator t, tend = Templates.end ();
294 	for (t = Templates.begin (); t != tend; t++)
295 		if ((*t).second->doc)
296 			(*t).second->doc->GetView ()->Update ((*t).second->doc);
297 }
298 
gcpNewTemplateToolDlg(gcp::Application * App)299 gcpNewTemplateToolDlg::gcpNewTemplateToolDlg (gcp::Application* App):
300 	gcugtk::Dialog(App, UIDIR"/new-template.ui", "new-template", GETTEXT_PACKAGE, App)
301 {
302 	m_node = NULL;
303 	if (!xml) {
304 		delete this;
305 		return;
306 	}
307 	pDoc = new gcp::Document (reinterpret_cast<gcp::Application*> (m_App), true);
308 	pDoc->SetEditable (false);
309 	GtkWidget* w;
310 	GtkScrolledWindow* scroll = GTK_SCROLLED_WINDOW (GetWidget ("scrolledcanvas"));
311 	gtk_scrolled_window_add_with_viewport (scroll, w = pDoc->GetView ()->CreateNewWidget ());
312 	pData = (gcp::WidgetData*) g_object_get_data (G_OBJECT (w), "data");
313 	/* build the categories list */
314 	GtkListStore *model = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING);
315 	GtkTreeIter iter;
316 	set<string>::iterator it = categories.begin (), end = categories.end ();
317 	for (; it != end; it++) {
318 		gtk_list_store_append (model, &iter);
319 		gtk_list_store_set (model, &iter,
320 			  NAME_COLUMN, (*it).c_str(),
321 			  -1);
322 	}
323 	w = gtk_combo_box_new_with_entry ();
324 	gtk_combo_box_set_model (GTK_COMBO_BOX (w), GTK_TREE_MODEL (model));
325 	gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (w), NAME_COLUMN);
326 	g_object_unref (model);
327 	gtk_grid_attach (GTK_GRID (GetWidget ("new-template-grid")), w, 1, 2, 1, 1);
328 	gtk_widget_show (w);
329 	category_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (w)));
330 	gtk_widget_show_all (GTK_WIDGET (dialog));
331 }
332 
~gcpNewTemplateToolDlg()333 gcpNewTemplateToolDlg::~gcpNewTemplateToolDlg ()
334 {
335 	if (m_node){
336 		xmlUnlinkNode (m_node);
337 		xmlFreeNode (m_node);
338 	}
339 }
340 
Apply()341 bool gcpNewTemplateToolDlg::Apply ()
342 {
343 	const char* name = gtk_entry_get_text (GTK_ENTRY (GetWidget ("name")));
344 	const char* category = gtk_entry_get_text (category_entry);
345 	if (!m_node || (*name == 0) || (*category == 0)) {
346 		char* msg;
347 		if (!m_node)
348 			msg = _("Please provide an object.");
349 		else if (*name == 0)
350 			msg = _("Please give a name.");
351 		else
352 			msg = _("Please choose a category.");
353 		gcugtk::Message *box = new gcugtk::Message (static_cast < gcugtk::Application * > (GetApp ()),
354 		                                            msg, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
355 		                                            GetWindow ());
356 		box->Show ();
357 		return false;
358 	}
359 	gcpTemplate *temp = new gcpTemplate ();
360 	temp->node = m_node;
361 	temp->writeable = true;
362 	temp->name = name;
363 	temp->category = category;
364 	if (!user_templates) {
365 		user_templates = xmlNewDoc ((xmlChar*) "1.0");
366 		xmlDocSetRootElement (user_templates,  xmlNewDocNode (user_templates, NULL, (xmlChar*) "templates", NULL));
367 		char* filename = g_strconcat (getenv ("HOME"), "/.gchempaint/templates/templates.xml", NULL);
368 		user_templates->URL = xmlStrdup ((xmlChar*) filename);
369 		g_free (filename);
370 	}
371 	xmlNodePtr node = xmlNewDocNode (user_templates, NULL, (xmlChar*) "template", NULL);
372 	xmlNodePtr child = xmlNewDocNode (user_templates, NULL, (xmlChar*)"category", (xmlChar*) category);
373 	xmlAddChild (node, child);
374 	child = xmlNewDocNode (user_templates, NULL, (xmlChar*)"name", (xmlChar*) name);
375 	xmlAddChild (node, child);
376 	xmlUnlinkNode (m_node);
377 	xmlAddChild (node, m_node);
378 	set<string>::iterator it = categories.find (category);
379 	if (it == categories.end()) categories.insert (category);
380 	string key = temp->name;
381 	if (TempbyName[key]) {
382 		int i = 0;
383 		char* str = g_strdup_printf ("%d", i);
384 		while (TempbyName[key + str]) {
385 			g_free (str);
386 			str = g_strdup_printf ("%d", ++i);
387 		}
388 		key += str;
389 		g_free (str);
390 	}
391 	TempbyName[key] = temp;
392 	key = string ((char*) category) + "/" + (char*) name;
393 	if (Templates[key]) {
394 		int i = 0;
395 		char* str = g_strdup_printf ("%d", i);
396 		while (Templates[key + str]) {
397 			g_free (str);
398 			str = g_strdup_printf ("%d", ++i);
399 		}
400 		key += str;
401 		g_free (str);
402 	}
403 	Templates[key] = temp;
404 	m_node = NULL;
405 	xmlAddChild (user_templates->children, node);
406 	xmlIndentTreeOutput = true;
407 	xmlKeepBlanksDefault (0);
408 	xmlSaveFormatFile ((char*) user_templates->URL, user_templates, true);
409 	gcpTemplateTree *tree = (gcpTemplateTree*)
410 			reinterpret_cast<gcp::Application*> (m_App)->GetTool ("TemplateTree");
411 	if (tree)
412 		tree->AddTemplate (key);
413 	return true;
414 }
415 
SetTemplate(xmlNodePtr node)416 void gcpNewTemplateToolDlg::SetTemplate (xmlNodePtr node)
417 {
418 	map<string, Object*>::iterator it;
419 	Object* obj = pDoc->GetFirstChild(it);
420 	if (obj) pDoc->Remove (obj);
421 	pDoc->PopOperation ();
422 	if (m_node) {
423 		xmlUnlinkNode (m_node);
424 		xmlFreeNode (m_node);
425 	}
426 	pDoc->PasteData (node);
427 	gccv::Rect rect;
428 	char *buf = (char*) xmlGetProp (node, (const xmlChar*) "bond-length");
429 	double r = 140. / strtod (buf, NULL);
430 	xmlFree (buf);
431 	if (fabs (r - 1.) > .0001) {
432 		Matrix2D m (r, 0., 0., r);
433 		// FIXME: this would not work for reactions
434 		pDoc->Transform2D (m, 0., 0.);
435 		pDoc->GetView ()->Update (pDoc);
436 	}
437 	while (gtk_events_pending ())
438 		gtk_main_iteration ();
439 	pDoc->AbortOperation ();
440 	pData->GetSelectionBounds (rect);
441 	pData->MoveSelection (- rect.x0, - rect.y0);
442 	pDoc->PopOperation ();
443 	pData->UnselectAll ();
444 	xmlUnlinkNode (node);
445 	xmlFreeNode (node);
446 	obj = pDoc->GetFirstChild (it);
447 	m_node = obj->Save (::xml);
448 }
449