1 /*
2  * Copyright (C) 2009 - 2012 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 
21 #include <glib/gi18n-lib.h>
22 #include <string.h>
23 #include "relations-diagram.h"
24 #include "../support.h"
25 #include "../gdaui-bar.h"
26 #include "../canvas/browser-canvas-db-relations.h"
27 #include <gdk/gdkkeysyms.h>
28 #include <libgda-ui/internal/popup-container.h>
29 #include "../browser-page.h"
30 #include "../browser-perspective.h"
31 #include "../browser-window.h"
32 #include "../data-manager/data-manager-perspective.h"
33 #include "../../tool-utils.h"
34 
35 struct _RelationsDiagramPrivate {
36 	BrowserConnection *bcnc;
37 	gint fav_id; /* diagram's ID as a favorite, -1=>not a favorite */
38 
39 	GdauiBar *header;
40 	GtkWidget *canvas;
41 	GtkWidget *save_button;
42 
43 	GtkWidget *popup_container; /* to enter canvas's name */
44 	GtkWidget *name_entry;
45 	GtkWidget *real_save_button;
46 };
47 
48 static void relations_diagram_class_init (RelationsDiagramClass *klass);
49 static void relations_diagram_init       (RelationsDiagram *diagram, RelationsDiagramClass *klass);
50 static void relations_diagram_dispose   (GObject *object);
51 static void relations_diagram_set_property (GObject *object,
52 					    guint param_id,
53 					    const GValue *value,
54 					    GParamSpec *pspec);
55 static void relations_diagram_get_property (GObject *object,
56 					    guint param_id,
57 					    GValue *value,
58 					    GParamSpec *pspec);
59 
60 /* BrowserPage interface */
61 static void                 relations_diagram_page_init (BrowserPageIface *iface);
62 static GtkActionGroup      *relations_diagram_page_get_actions_group (BrowserPage *page);
63 static const gchar         *relations_diagram_page_get_actions_ui (BrowserPage *page);
64 static GtkWidget           *relations_diagram_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
65 
66 static void meta_changed_cb (BrowserConnection *bcnc, GdaMetaStruct *mstruct, RelationsDiagram *diagram);
67 static void favorites_changed_cb (BrowserConnection *bcnc, RelationsDiagram *diagram);
68 static void relations_diagram_set_fav_id (RelationsDiagram *diagram, gint fav_id, GError **error);
69 
70 /* properties */
71 enum {
72         PROP_0,
73 };
74 
75 static GObjectClass *parent_class = NULL;
76 
77 
78 /*
79  * RelationsDiagram class implementation
80  */
81 
82 static void
83 relations_diagram_class_init (RelationsDiagramClass *klass)
84 {
85 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
86 
87 	parent_class = g_type_class_peek_parent (klass);
88 
89 	/* Properties */
90         object_class->set_property = relations_diagram_set_property;
91         object_class->get_property = relations_diagram_get_property;
92 
93 	object_class->dispose = relations_diagram_dispose;
94 }
95 
96 static void
97 relations_diagram_page_init (BrowserPageIface *iface)
98 {
99 	iface->i_get_actions_group = relations_diagram_page_get_actions_group;
100 	iface->i_get_actions_ui = relations_diagram_page_get_actions_ui;
101 	iface->i_get_tab_label = relations_diagram_page_get_tab_label;
102 }
103 
104 static void
105 relations_diagram_init (RelationsDiagram *diagram, G_GNUC_UNUSED RelationsDiagramClass *klass)
106 {
107 	diagram->priv = g_new0 (RelationsDiagramPrivate, 1);
108 	diagram->priv->fav_id = -1;
109 	diagram->priv->popup_container = NULL;
110 
111 	gtk_orientable_set_orientation (GTK_ORIENTABLE (diagram), GTK_ORIENTATION_VERTICAL);
112 }
113 
114 static void
115 relations_diagram_dispose (GObject *object)
116 {
117 	RelationsDiagram *diagram = (RelationsDiagram *) object;
118 
119 	/* free memory */
120 	if (diagram->priv) {
121 		if (diagram->priv->bcnc) {
122 			g_signal_handlers_disconnect_by_func (diagram->priv->bcnc,
123 							      G_CALLBACK (meta_changed_cb), diagram);
124 			g_signal_handlers_disconnect_by_func (diagram->priv->bcnc,
125 							      G_CALLBACK (favorites_changed_cb), diagram);
126 			g_object_unref (diagram->priv->bcnc);
127 		}
128 
129 		if (diagram->priv->popup_container)
130 			gtk_widget_destroy (diagram->priv->popup_container);
131 
132 		g_free (diagram->priv);
133 		diagram->priv = NULL;
134 	}
135 
136 	parent_class->dispose (object);
137 }
138 
139 GType
140 relations_diagram_get_type (void)
141 {
142 	static GType type = 0;
143 
144 	if (G_UNLIKELY (type == 0)) {
145 		static const GTypeInfo info = {
146 			sizeof (RelationsDiagramClass),
147 			(GBaseInitFunc) NULL,
148 			(GBaseFinalizeFunc) NULL,
149 			(GClassInitFunc) relations_diagram_class_init,
150 			NULL,
151 			NULL,
152 			sizeof (RelationsDiagram),
153 			0,
154 			(GInstanceInitFunc) relations_diagram_init,
155 			0
156 		};
157 		static GInterfaceInfo page_info = {
158                         (GInterfaceInitFunc) relations_diagram_page_init,
159 			NULL,
160                         NULL
161                 };
162 
163 		type = g_type_register_static (GTK_TYPE_BOX, "RelationsDiagram", &info, 0);
164 		g_type_add_interface_static (type, BROWSER_PAGE_TYPE, &page_info);
165 	}
166 	return type;
167 }
168 
169 static void
170 relations_diagram_set_property (GObject *object,
171 				guint param_id,
172 				G_GNUC_UNUSED const GValue *value,
173 				GParamSpec *pspec)
174 {
175 	/*RelationsDiagram *diagram;
176 	  diagram = RELATIONS_DIAGRAM (object);*/
177 	switch (param_id) {
178 	default:
179 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
180 		break;
181 	}
182 }
183 
184 static void
185 relations_diagram_get_property (GObject *object,
186 				guint param_id,
187 				G_GNUC_UNUSED GValue *value,
188 				GParamSpec *pspec)
189 {
190 	/*RelationsDiagram *diagram;
191 	  diagram = RELATIONS_DIAGRAM (object);*/
192 	switch (param_id) {
193 	default:
194 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
195 		break;
196 	}
197 }
198 
199 static void
200 meta_changed_cb (G_GNUC_UNUSED BrowserConnection *bcnc, GdaMetaStruct *mstruct, RelationsDiagram *diagram)
201 {
202 	g_object_set (G_OBJECT (diagram->priv->canvas), "meta-struct", mstruct, NULL);
203 }
204 
205 static void
206 favorites_changed_cb (G_GNUC_UNUSED BrowserConnection *bcnc, RelationsDiagram *diagram)
207 {
208 	if (diagram->priv->fav_id >= 0)
209 		relations_diagram_set_fav_id (diagram, diagram->priv->fav_id, NULL);
210 }
211 
212 /*
213  * POPUP
214  */
215 static void
216 real_save_clicked_cb (GtkWidget *button, RelationsDiagram *diagram)
217 {
218 	gchar *str;
219 
220 	str = browser_canvas_serialize_items (BROWSER_CANVAS (diagram->priv->canvas));
221 
222 	GError *lerror = NULL;
223 	ToolsFavorites *bfav;
224 	ToolsFavoritesAttributes fav;
225 
226 	memset (&fav, 0, sizeof (ToolsFavoritesAttributes));
227 	fav.id = diagram->priv->fav_id;
228 	fav.type = GDA_TOOLS_FAVORITES_DIAGRAMS;
229 	fav.name = gtk_editable_get_chars (GTK_EDITABLE (diagram->priv->name_entry), 0, -1);
230 	if (!*fav.name) {
231 		g_free (fav.name);
232 		fav.name = g_strdup (_("Diagram"));
233 	}
234 	fav.contents = str;
235 
236 	gtk_widget_hide (diagram->priv->popup_container);
237 
238 	bfav = browser_connection_get_favorites (diagram->priv->bcnc);
239 	if (! gda_tools_favorites_add (bfav, 0, &fav, ORDER_KEY_SCHEMA, G_MAXINT, &lerror)) {
240 		browser_show_error ((GtkWindow*) gtk_widget_get_toplevel (button),
241 				    "<b>%s:</b>\n%s",
242 				    _("Could not save diagram"),
243 				    lerror && lerror->message ? lerror->message : _("No detail"));
244 		if (lerror)
245 			g_error_free (lerror);
246 	}
247 
248 	relations_diagram_set_fav_id (diagram, fav.id, NULL);
249 
250 	g_free (fav.name);
251 	g_free (str);
252 }
253 
254 static void
255 save_clicked_cb (GtkWidget *button, RelationsDiagram *diagram)
256 {
257 	gchar *str;
258 
259 	if (!diagram->priv->popup_container) {
260 		GtkWidget *window, *wid, *hbox;
261 
262 		window = popup_container_new (button);
263 		diagram->priv->popup_container = window;
264 
265 		hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
266 		gtk_container_add (GTK_CONTAINER (window), hbox);
267 		wid = gtk_label_new ("");
268 		str = g_strdup_printf ("%s:", _("Canvas's name"));
269 		gtk_label_set_markup (GTK_LABEL (wid), str);
270 		g_free (str);
271 		gtk_box_pack_start (GTK_BOX (hbox), wid, FALSE, FALSE, 0);
272 
273 		wid = gtk_entry_new ();
274 		gtk_box_pack_start (GTK_BOX (hbox), wid, FALSE, FALSE, 5);
275 		diagram->priv->name_entry = wid;
276 		if (diagram->priv->fav_id > 0) {
277 			ToolsFavoritesAttributes fav;
278 			if (gda_tools_favorites_get (browser_connection_get_favorites (diagram->priv->bcnc),
279 						   diagram->priv->fav_id, &fav, NULL)) {
280 				gtk_entry_set_text (GTK_ENTRY (wid), fav.name);
281 				gda_tools_favorites_reset_attributes (&fav);
282 			}
283 		}
284 
285 		g_signal_connect (wid, "activate",
286 				  G_CALLBACK (real_save_clicked_cb), diagram);
287 
288 		wid = gtk_button_new_with_label (_("Save"));
289 		gtk_box_pack_start (GTK_BOX (hbox), wid, FALSE, FALSE, 0);
290 		g_signal_connect (wid, "clicked",
291 				  G_CALLBACK (real_save_clicked_cb), diagram);
292 		diagram->priv->real_save_button = wid;
293 
294 		gtk_widget_show_all (hbox);
295 	}
296 
297         gtk_widget_show (diagram->priv->popup_container);
298 }
299 
300 
301 /**
302  * relations_diagram_new
303  *
304  * Returns: a new #GtkWidget
305  */
306 GtkWidget *
307 relations_diagram_new (BrowserConnection *bcnc)
308 {
309 	RelationsDiagram *diagram;
310 
311 	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
312 
313 	diagram = RELATIONS_DIAGRAM (g_object_new (RELATIONS_DIAGRAM_TYPE, NULL));
314 
315 	diagram->priv->bcnc = g_object_ref (bcnc);
316 	g_signal_connect (diagram->priv->bcnc, "meta-changed",
317 			  G_CALLBACK (meta_changed_cb), diagram);
318 	g_signal_connect (bcnc, "favorites-changed",
319 			  G_CALLBACK (favorites_changed_cb), diagram);
320 
321 
322 	/* header */
323 	GtkWidget *hbox, *wid;
324 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
325 	gtk_box_pack_start (GTK_BOX (diagram), hbox, FALSE, FALSE, 0);
326 
327         GtkWidget *label;
328 	gchar *str;
329 	str = g_strdup_printf ("<b>%s</b>\n%s", _("Relations diagram"), _("Unsaved"));
330 	label = gdaui_bar_new (str);
331 	g_free (str);
332         gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
333 	diagram->priv->header = GDAUI_BAR (label);
334 
335 	wid = gdaui_bar_add_button_from_stock (GDAUI_BAR (label), GTK_STOCK_SAVE);
336 	diagram->priv->save_button = wid;
337 
338 	g_signal_connect (wid, "clicked",
339 			  G_CALLBACK (save_clicked_cb), diagram);
340 
341         gtk_widget_show_all (hbox);
342 
343 	/* main contents */
344 	wid = browser_canvas_db_relations_new (NULL);
345 	diagram->priv->canvas = wid;
346 	gtk_box_pack_start (GTK_BOX (diagram), wid, TRUE, TRUE, 0);
347         gtk_widget_show_all (wid);
348 
349 	GdaMetaStruct *mstruct;
350 	mstruct = browser_connection_get_meta_struct (diagram->priv->bcnc);
351 	if (mstruct)
352 		meta_changed_cb (diagram->priv->bcnc, mstruct, diagram);
353 
354 	return (GtkWidget*) diagram;
355 }
356 
357 GtkWidget *
358 relations_diagram_new_with_fav_id (BrowserConnection *bcnc, gint fav_id, GError **error)
359 {
360 	RelationsDiagram *diagram = NULL;
361 	ToolsFavoritesAttributes fav;
362 	xmlDocPtr doc = NULL;
363 
364 	if (! gda_tools_favorites_get (browser_connection_get_favorites (bcnc),
365 				     fav_id, &fav, error))
366 		return FALSE;
367 
368 
369 	doc = xmlParseDoc (BAD_CAST fav.contents);
370 	if (!doc) {
371 		g_set_error (error, GDA_TOOLS_ERROR, GDA_TOOLS_INTERNAL_COMMAND_ERROR,
372 			     "%s", _("Error parsing favorite's contents"));
373 		goto out;
374 	}
375 
376 	/* create diagram */
377 	diagram = RELATIONS_DIAGRAM (relations_diagram_new (bcnc));
378 	if (!diagram)
379 		goto out;
380 	gchar *str, *tmp;
381 	tmp = g_markup_printf_escaped (_("'%s' diagram"), fav.name);
382 	str = g_strdup_printf ("<b>%s</b>\n%s", _("Relations diagram"), tmp);
383 	g_free (tmp);
384 	gdaui_bar_set_text (diagram->priv->header, str);
385 	g_free (str);
386 	diagram->priv->fav_id = fav_id;
387 	relations_diagram_set_fav_id (diagram, fav_id, NULL);
388 
389 	/* fill the diagram */
390 	xmlNodePtr root, node;
391 	root = xmlDocGetRootElement (doc);
392 	if (!root)
393 		goto out;
394 	for (node = root->children; node; node = node->next) {
395 		if (!strcmp ((gchar*) node->name, "table")) {
396 			xmlChar *schema;
397 			xmlChar *name;
398 			schema = xmlGetProp (node, BAD_CAST "schema");
399 			name = xmlGetProp (node, BAD_CAST "name");
400 			if (schema && name) {
401 				BrowserCanvasTable *table;
402 				GValue *v1, *v2;
403 				g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), (gchar*) schema);
404 				g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), (gchar*) name);
405 				xmlFree (schema);
406 				xmlFree (name);
407 				table = browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (diagram->priv->canvas),
408 									       NULL, v1, v2);
409 				gda_value_free (v1);
410 				gda_value_free (v2);
411 				if (table) {
412 					xmlChar *x, *y;
413 					x = xmlGetProp (node, BAD_CAST "x");
414 					y = xmlGetProp (node, BAD_CAST "y");
415 					browser_canvas_translate_item (BROWSER_CANVAS (diagram->priv->canvas),
416 								       (BrowserCanvasItem*) table,
417 								       x ? g_ascii_strtod ((gchar*) x, NULL) : 0.,
418 								       y ? g_ascii_strtod ((gchar*) y, NULL) : 0.);
419 					if (x)
420 						xmlFree (x);
421 					if (y)
422 						xmlFree (y);
423 				}
424 			}
425 			else {
426 				if (schema)
427 					xmlFree (schema);
428 				if (name)
429 					xmlFree (name);
430 				g_set_error (error, GDA_TOOLS_ERROR, GDA_TOOLS_STORED_DATA_ERROR,
431 					     "%s", _("Missing table attribute in favorite's contents"));
432 				gtk_widget_destroy ((GtkWidget*) diagram);
433 				diagram = NULL;
434 				goto out;
435 			}
436 		}
437 	}
438 
439  out:
440 	gda_tools_favorites_reset_attributes (&fav);
441 	if (doc)
442 		xmlFreeDoc (doc);
443 	return (GtkWidget*) diagram;
444 }
445 
446 /*
447  * relations_diagram_set_fav_id
448  *
449  * Sets the favorite ID of @diagram: ensure every displayed information is up to date
450  */
451 static void
452 relations_diagram_set_fav_id (RelationsDiagram *diagram, gint fav_id, GError **error)
453 {
454 	g_return_if_fail (IS_RELATIONS_DIAGRAM (diagram));
455 	ToolsFavoritesAttributes fav;
456 
457 	if ((fav_id >=0) &&
458 	    gda_tools_favorites_get (browser_connection_get_favorites (diagram->priv->bcnc),
459 				   fav_id, &fav, error)) {
460 		gchar *str, *tmp;
461 		tmp = g_markup_printf_escaped (_("'%s' diagram"), fav.name);
462 		str = g_strdup_printf ("<b>%s</b>\n%s", _("Relations diagram"), tmp);
463 		g_free (tmp);
464 		gdaui_bar_set_text (diagram->priv->header, str);
465 		g_free (str);
466 
467 		diagram->priv->fav_id = fav.id;
468 
469 		gda_tools_favorites_reset_attributes (&fav);
470 	}
471 	else {
472 		gchar *str;
473 		str = g_strdup_printf ("<b>%s</b>\n%s", _("Relations diagram"), _("Unsaved"));
474 		gdaui_bar_set_text (diagram->priv->header, str);
475 		g_free (str);
476 		diagram->priv->fav_id = -1;
477 	}
478 
479 	/* update notebook's tab label */
480 	BrowserPerspective *pers;
481 	pers = browser_page_get_perspective (BROWSER_PAGE (diagram));
482 	if (pers)
483 		browser_perspective_page_tab_label_change (pers, BROWSER_PAGE (diagram));
484 }
485 
486 /**
487  * relations_diagram_get_fav_id
488  *
489  */
490 gint
491 relations_diagram_get_fav_id (RelationsDiagram *diagram)
492 {
493 	g_return_val_if_fail (IS_RELATIONS_DIAGRAM (diagram), -1);
494 	return diagram->priv->fav_id;
495 }
496 
497 static void
498 action_view_contents_cb  (G_GNUC_UNUSED GtkAction *action, RelationsDiagram *diagram)
499 {
500 	gchar *str;
501 	str = browser_canvas_db_relations_items_to_data_manager (BROWSER_CANVAS_DB_RELATIONS (diagram->priv->canvas));
502 	g_print ("%s\n", str);
503 
504 	if (str) {
505 		BrowserWindow *bwin;
506 		BrowserPerspective *pers;
507 		bwin = (BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) diagram);
508 		pers = browser_window_change_perspective (bwin, _("Data manager"));
509 
510 		data_manager_perspective_new_tab (DATA_MANAGER_PERSPECTIVE (pers), str);
511 		g_free (str);
512 	}
513 }
514 
515 
516 static GtkActionEntry ui_actions[] = {
517 	{ "ViewContents", GTK_STOCK_EDIT, N_("_Contents"), NULL, N_("View contents"),
518 	  G_CALLBACK (action_view_contents_cb)},
519 };
520 static const gchar *ui_actions_info =
521 	"<ui>"
522 	"  <menubar name='MenuBar'>"
523 	"  </menubar>"
524 	"  <toolbar name='ToolBar'>"
525 	"    <separator/>"
526 	"    <toolitem action='ViewContents'/>"
527 	"  </toolbar>"
528 	"</ui>";
529 
530 static GtkActionGroup *
531 relations_diagram_page_get_actions_group (BrowserPage *page)
532 {
533 	GtkActionGroup *agroup;
534 	agroup = gtk_action_group_new ("SchemaBrowserRelationsDiagramActions");
535 	gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
536 	gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), page);
537 
538 	return agroup;
539 }
540 
541 static const gchar *
542 relations_diagram_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
543 {
544 	return ui_actions_info;
545 }
546 
547 
548 static GtkWidget *
549 relations_diagram_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
550 {
551 	GtkWidget *wid;
552 	RelationsDiagram *diagram;
553 	gchar *tab_name = NULL;
554 	GdkPixbuf *table_pixbuf;
555 
556 	diagram = RELATIONS_DIAGRAM (page);
557 	if (diagram->priv->fav_id > 0) {
558 		ToolsFavoritesAttributes fav;
559 		if (gda_tools_favorites_get (browser_connection_get_favorites (diagram->priv->bcnc),
560 					   diagram->priv->fav_id, &fav, NULL)) {
561 			tab_name = g_strdup (fav.name);
562 			gda_tools_favorites_reset_attributes (&fav);
563 		}
564 	}
565 	if (!tab_name)
566 		tab_name = g_strdup (_("Diagram"));
567 
568 	table_pixbuf = browser_get_pixbuf_icon (BROWSER_ICON_DIAGRAM);
569 	wid = browser_make_tab_label_with_pixbuf (tab_name,
570 						  table_pixbuf,
571 						  out_close_button ? TRUE : FALSE, out_close_button);
572 	g_free (tab_name);
573 	return wid;
574 }
575