1 /*
2  * Copyright (C) 2009 - 2011 Vivien Malerba <malerba@gnome-db.org>
3  * Copyright (C) 2010 David King <davidk@openismus.com>
4  * Copyright (C) 2010 - 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 <gtk/gtk.h>
23 #include "browser-canvas.h"
24 #include "browser-canvas-priv.h"
25 #include "browser-canvas-item.h"
26 #include "browser-canvas-print.h"
27 #include <libgda/libgda.h>
28 #include <libgda-ui/libgda-ui.h>
29 
30 #define DEFAULT_SCALE .8
31 #ifdef HAVE_GRAPHVIZ
32 #include <stddef.h>
33 #include <gvc.h>
34 #ifndef ND_coord_i
35     #define ND_coord_i ND_coord
36 #endif
37 static GVC_t* gvc = NULL;
38 #endif
39 #include <cairo.h>
40 #include <cairo-svg.h>
41 #include <math.h>
42 
43 static void browser_canvas_class_init (BrowserCanvasClass *klass);
44 static void browser_canvas_init       (BrowserCanvas *canvas);
45 static void browser_canvas_dispose    (GObject *object);
46 static void browser_canvas_finalize   (GObject *object);
47 
48 /* get a pointer to the parents to be able to call their destructor */
49 static GObjectClass *parent_class = NULL;
50 
51 enum
52 {
53 	ITEM_SELECTED,
54 	LAST_SIGNAL
55 };
56 
57 static gint canvas_signals[LAST_SIGNAL] = { 0 };
58 
59 GType
browser_canvas_get_type(void)60 browser_canvas_get_type (void)
61 {
62 	static GType type = 0;
63 
64 	if (G_UNLIKELY (type == 0)) {
65 		static const GTypeInfo info = {
66 			sizeof (BrowserCanvasClass),
67 			(GBaseInitFunc) NULL,
68 			(GBaseFinalizeFunc) NULL,
69 			(GClassInitFunc) browser_canvas_class_init,
70 			NULL,
71 			NULL,
72 			sizeof (BrowserCanvas),
73 			0,
74 			(GInstanceInitFunc) browser_canvas_init,
75 			0
76 		};
77 
78 		type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW, "BrowserCanvas", &info, 0);
79 	}
80 	return type;
81 }
82 
83 static void
browser_canvas_class_init(BrowserCanvasClass * klass)84 browser_canvas_class_init (BrowserCanvasClass *klass)
85 {
86 	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
87 	parent_class = g_type_class_peek_parent (klass);
88 
89 	/* signals */
90 	canvas_signals[ITEM_SELECTED] =
91 		g_signal_new ("item-selected",
92 			      G_TYPE_FROM_CLASS (object_class),
93 			      G_SIGNAL_RUN_FIRST,
94 			      G_STRUCT_OFFSET (BrowserCanvasClass, item_selected),
95 			      NULL, NULL,
96 			      g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
97 			      TYPE_BROWSER_CANVAS_ITEM);
98 
99 	/* virtual functions */
100 	klass->clean_canvas_items = NULL;
101 	klass->build_context_menu = NULL;
102 
103 	object_class->dispose = browser_canvas_dispose;
104 	object_class->finalize = browser_canvas_finalize;
105 }
106 
107 static gboolean canvas_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas);
108 static gboolean motion_notify_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas);
109 static gboolean canvas_scroll_event_cb (GooCanvas *gcanvas, GdkEvent *event, BrowserCanvas *canvas);
110 static void drag_begin_cb (BrowserCanvas *canvas, GdkDragContext *drag_context, GooCanvas *gcanvas);
111 static void drag_data_get_cb (BrowserCanvas *canvas, GdkDragContext   *drag_context,
112 			      GtkSelectionData *data, guint info,
113 			      guint time, GooCanvas *gcanvas);
114 static void drag_data_received_cb (BrowserCanvas *canvas, GdkDragContext *context,
115 				   gint x, gint y, GtkSelectionData *data,
116 				   guint info, guint time, GooCanvas *gcanvas);
117 static gboolean idle_add_canvas_cb (BrowserCanvas *canvas);
118 static void
browser_canvas_init(BrowserCanvas * canvas)119 browser_canvas_init (BrowserCanvas *canvas)
120 {
121 	canvas->priv = g_new0 (BrowserCanvasPrivate, 1);
122 
123 	canvas->priv->goocanvas = GOO_CANVAS (goo_canvas_new ());
124 	gtk_widget_show (GTK_WIDGET (canvas->priv->goocanvas));
125 	g_object_set_data (G_OBJECT (canvas->priv->goocanvas), "browsercanvas", canvas);
126 
127 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (canvas),
128 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
129         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (canvas), GTK_SHADOW_NONE);
130 	g_idle_add ((GSourceFunc) idle_add_canvas_cb, canvas);
131 	canvas->priv->items = NULL;
132 	canvas->priv->current_selected_item = NULL;
133 
134 	canvas->xmouse = 50.;
135 	canvas->ymouse = 50.;
136 
137 	g_signal_connect (canvas, "event",
138 			  G_CALLBACK (canvas_event_cb), canvas->priv->goocanvas);
139 	g_signal_connect (canvas->priv->goocanvas, "scroll-event",
140 			  G_CALLBACK (canvas_scroll_event_cb), canvas);
141 	g_signal_connect (canvas, "motion-notify-event",
142 			  G_CALLBACK (motion_notify_event_cb), canvas->priv->goocanvas);
143 	g_signal_connect (canvas, "drag-begin",
144 			  G_CALLBACK (drag_begin_cb), canvas->priv->goocanvas);
145 	g_signal_connect (canvas, "drag-data-get",
146 			  G_CALLBACK (drag_data_get_cb), canvas->priv->goocanvas);
147 	g_signal_connect (canvas, "drag-data-received",
148 			  G_CALLBACK (drag_data_received_cb), canvas->priv->goocanvas);
149 	g_object_set (G_OBJECT (canvas->priv->goocanvas),
150 		      "automatic-bounds", TRUE,
151 		      "bounds-padding", 5.,
152 		      "bounds-from-origin", FALSE,
153 		      "anchor", GOO_CANVAS_ANCHOR_CENTER, NULL);
154 
155 	/* reseting the zoom */
156 	goo_canvas_set_scale (canvas->priv->goocanvas, DEFAULT_SCALE);
157 }
158 
159 static gboolean
idle_add_canvas_cb(BrowserCanvas * canvas)160 idle_add_canvas_cb (BrowserCanvas *canvas)
161 {
162 	gtk_container_add (GTK_CONTAINER (canvas), GTK_WIDGET (canvas->priv->goocanvas));
163 	return FALSE;
164 }
165 
166 static void
drag_begin_cb(BrowserCanvas * canvas,G_GNUC_UNUSED GdkDragContext * drag_context,G_GNUC_UNUSED GooCanvas * gcanvas)167 drag_begin_cb (BrowserCanvas *canvas, G_GNUC_UNUSED GdkDragContext *drag_context,
168 	       G_GNUC_UNUSED GooCanvas *gcanvas)
169 {
170 	BrowserCanvasItem *citem;
171 
172 	citem = g_object_get_data (G_OBJECT (canvas), "__drag_src_item");
173 	if (citem) {
174 		/*
175 		gtk_drag_source_set_icon_pixbuf (GTK_WIDGET (canvas),
176 						 browser_get_pixbuf_icon (BROWSER_ICON_TABLE));
177 		*/
178 	}
179 }
180 
181 static void
drag_data_get_cb(BrowserCanvas * canvas,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,G_GNUC_UNUSED GooCanvas * gcanvas)182 drag_data_get_cb (BrowserCanvas *canvas, GdkDragContext *drag_context,
183 		  GtkSelectionData *data, guint info,
184 		  guint time, G_GNUC_UNUSED GooCanvas *gcanvas)
185 {
186 	BrowserCanvasItem *citem;
187 
188 	citem = g_object_get_data (G_OBJECT (canvas), "__drag_src_item");
189 	if (citem) {
190 		BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (citem));
191 		if (iclass->drag_data_get)
192 			iclass->drag_data_get (citem, drag_context, data, info, time);
193 	}
194 }
195 
196 static void
drag_data_received_cb(G_GNUC_UNUSED BrowserCanvas * canvas,GdkDragContext * context,gint x,gint y,G_GNUC_UNUSED GtkSelectionData * data,G_GNUC_UNUSED guint info,guint time,GooCanvas * gcanvas)197 drag_data_received_cb (G_GNUC_UNUSED BrowserCanvas *canvas, GdkDragContext *context,
198 		       gint x, gint y, G_GNUC_UNUSED GtkSelectionData *data,
199 		       G_GNUC_UNUSED guint info, guint time, GooCanvas *gcanvas)
200 {
201 	GooCanvasItem *item;
202 	item = goo_canvas_get_item_at (gcanvas, x, y, TRUE);
203 	if (item) {
204 		/*g_print ("Dragged into %s\n", G_OBJECT_TYPE_NAME (item));*/
205 		gtk_drag_finish (context, TRUE, FALSE, time);
206 	}
207 	else {
208 		gtk_drag_finish (context, FALSE, FALSE, time);
209 	}
210 }
211 
212 static gboolean
canvas_scroll_event_cb(G_GNUC_UNUSED GooCanvas * gcanvas,GdkEvent * event,BrowserCanvas * canvas)213 canvas_scroll_event_cb (G_GNUC_UNUSED GooCanvas *gcanvas, GdkEvent *event, BrowserCanvas *canvas)
214 {
215 	gboolean done = TRUE;
216 	GdkEventScroll *ev = (GdkEventScroll *) event;
217 
218 	switch (event->type) {
219 	case GDK_SCROLL:
220 		if (ev->state & GDK_SHIFT_MASK) {
221 			if (ev->direction == GDK_SCROLL_UP)
222 				browser_canvas_scale_layout (canvas, 1.05);
223 			else
224 				browser_canvas_scale_layout (canvas, .95);
225 		}
226 		else {
227 			if (ev->direction == GDK_SCROLL_UP)
228 				browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) + .03);
229 			else if (ev->direction == GDK_SCROLL_DOWN)
230 				browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) - .03);
231 		}
232 		done = TRUE;
233 		break;
234 	default:
235 		done = FALSE;
236 		break;
237 	}
238 	return done;
239 }
240 
241 static GdkCursor *hand_cursor = NULL;
242 
243 static gboolean
motion_notify_event_cb(BrowserCanvas * canvas,GdkEvent * event,G_GNUC_UNUSED GooCanvas * gcanvas)244 motion_notify_event_cb (BrowserCanvas *canvas, GdkEvent *event, G_GNUC_UNUSED GooCanvas *gcanvas)
245 {
246 	gboolean done = TRUE;
247 
248 	switch (event->type) {
249 	case GDK_MOTION_NOTIFY:
250 		if (((GdkEventMotion*) event)->state & GDK_BUTTON1_MASK) {
251 			if (canvas->priv->canvas_moving) {
252 				GtkAdjustment *ha, *va;
253 				gdouble x, y;
254 				gdouble upper, lower, page_size;
255 				ha = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (canvas));
256 				va = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (canvas));
257 
258 				upper = gtk_adjustment_get_upper (ha);
259 				lower = gtk_adjustment_get_lower (ha);
260 				page_size = gtk_adjustment_get_page_size (ha);
261 				x = gtk_adjustment_get_value (ha);
262 				x = CLAMP (x + canvas->xmouse - ((GdkEventMotion*) event)->x,
263 					   lower, upper - page_size);
264 				gtk_adjustment_set_value (ha, x);
265 
266 				upper = gtk_adjustment_get_upper (va);
267 				lower = gtk_adjustment_get_lower (va);
268 				page_size = gtk_adjustment_get_page_size (va);
269 				y = gtk_adjustment_get_value (va);
270 				y = CLAMP (y + canvas->ymouse - ((GdkEventMotion*) event)->y,
271 					   lower, upper - page_size);
272 				gtk_adjustment_set_value (va, y);
273 			}
274 			else {
275 				canvas->xmouse = ((GdkEventMotion*) event)->x;
276 				canvas->ymouse = ((GdkEventMotion*) event)->y;
277 				canvas->priv->canvas_moving = TRUE;
278 				if (! hand_cursor)
279 					hand_cursor = gdk_cursor_new (GDK_HAND2);
280 				gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (canvas)),
281 						       hand_cursor);
282 			}
283 		}
284 		done = TRUE;
285 		break;
286 	default:
287 		done = FALSE;
288 		break;
289 	}
290 	return done;
291 }
292 
293 #ifdef HAVE_GRAPHVIZ
294 static void popup_layout_default_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
295 static void popup_layout_radial_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
296 #endif
297 static void popup_zoom_in_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
298 static void popup_zoom_out_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
299 static void popup_zoom_fit_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
300 static void popup_export_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
301 static void popup_print_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
302 static gboolean
canvas_event_cb(BrowserCanvas * canvas,GdkEvent * event,GooCanvas * gcanvas)303 canvas_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas)
304 {
305 	gboolean done = TRUE;
306 	GooCanvasItem *item;
307 	BrowserCanvasClass *class = BROWSER_CANVAS_CLASS (G_OBJECT_GET_CLASS (canvas));
308 	gdouble x, y;
309 
310 	switch (event->type) {
311 	case GDK_BUTTON_PRESS:
312 		x = ((GdkEventButton *) event)->x;
313 		y = ((GdkEventButton *) event)->y;
314 		goo_canvas_convert_from_pixels (gcanvas, &x, &y);
315 		item = goo_canvas_get_item_at (gcanvas, x, y, TRUE);
316 
317 		if (!item) {
318 			if ((((GdkEventButton *) event)->button == 3) && (class->build_context_menu)) {
319 				GtkWidget *menu, *mitem;
320 
321 				canvas->xmouse = x;
322 				canvas->ymouse = y;
323 
324 				/* extra menu items, if any */
325 				menu = (class->build_context_menu) (canvas);
326 
327 				/* default menu items */
328 				if (!menu)
329 					menu = gtk_menu_new ();
330 				else {
331 					mitem = gtk_separator_menu_item_new ();
332 					gtk_widget_show (mitem);
333 					gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
334 				}
335 				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_IN, NULL);
336 				gtk_widget_show (mitem);
337 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
338 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_in_cb), canvas);
339 				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_OUT, NULL);
340 				gtk_widget_show (mitem);
341 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
342 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_out_cb), canvas);
343 				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_FIT, NULL);
344 				gtk_widget_show (mitem);
345 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
346 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_fit_cb), canvas);
347 
348 				mitem = gtk_separator_menu_item_new ();
349 				gtk_widget_show (mitem);
350 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
351 
352 #ifdef HAVE_GRAPHVIZ
353 				mitem = gtk_menu_item_new_with_label (_("Linear layout"));
354 				gtk_widget_show (mitem);
355 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
356 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_layout_default_cb), canvas);
357 
358 				mitem = gtk_menu_item_new_with_label (_("Radial layout"));
359 				gtk_widget_show (mitem);
360 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
361 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_layout_radial_cb), canvas);
362 
363 				mitem = gtk_separator_menu_item_new ();
364 				gtk_widget_show (mitem);
365 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
366 #endif
367 
368 				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
369 				gtk_widget_show (mitem);
370 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
371 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_export_cb), canvas);
372 
373 				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PRINT, NULL);
374 				gtk_widget_show (mitem);
375 				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_print_cb), canvas);
376 				gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
377 
378 				gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
379 						NULL, NULL, ((GdkEventButton *)event)->button,
380 						((GdkEventButton *)event)->time);
381 			}
382 		}
383 		done = TRUE;
384 		break;
385 	case GDK_BUTTON_RELEASE:
386 		if (canvas->priv->canvas_moving) {
387 			canvas->priv->canvas_moving = FALSE;
388 			gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (canvas)), NULL);
389 		}
390 		break;
391 	case GDK_2BUTTON_PRESS:
392 		x = ((GdkEventButton *) event)->x;
393 		y = ((GdkEventButton *) event)->y;
394 		goo_canvas_convert_from_pixels (gcanvas, &x, &y);
395 		item = goo_canvas_get_item_at (gcanvas, x, y, TRUE);
396 		if (item) {
397 			GooCanvasItem *bitem;
398 			for (bitem = item; bitem; bitem = goo_canvas_item_get_parent (bitem)) {
399 				if (IS_BROWSER_CANVAS_ITEM (bitem)) {
400 					gboolean allow_select;
401 					g_object_get (G_OBJECT (bitem), "allow-select", &allow_select, NULL);
402 					if (allow_select) {
403 						browser_canvas_item_toggle_select (canvas, BROWSER_CANVAS_ITEM (bitem));
404 						break;
405 					}
406 				}
407 			}
408 		}
409 		else
410 			browser_canvas_fit_zoom_factor (canvas);
411 		done = TRUE;
412 		break;
413 	default:
414 		done = FALSE;
415 		break;
416 	}
417 	return done;
418 }
419 
420 #ifdef HAVE_GRAPHVIZ
421 static void
popup_layout_default_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)422 popup_layout_default_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
423 {
424 	browser_canvas_perform_auto_layout (canvas, TRUE, BROWSER_CANVAS_LAYOUT_DEFAULT);
425 }
426 
427 static void
popup_layout_radial_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)428 popup_layout_radial_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
429 {
430 	browser_canvas_perform_auto_layout (canvas, TRUE, BROWSER_CANVAS_LAYOUT_RADIAL);
431 }
432 #endif
433 
434 static void
popup_zoom_in_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)435 popup_zoom_in_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
436 {
437 	browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) + .05);
438 }
439 
440 static void
popup_zoom_out_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)441 popup_zoom_out_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
442 {
443 	browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) - .05);
444 }
445 
446 static void
popup_zoom_fit_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)447 popup_zoom_fit_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
448 {
449 	browser_canvas_fit_zoom_factor (canvas);
450 }
451 
452 static void
popup_export_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)453 popup_export_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
454 {
455 	GtkWidget *dlg;
456 	gint result;
457 	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
458 	GtkFileFilter *filter;
459 
460 #define MARGIN 5.
461 
462 	if (!gtk_widget_is_toplevel (toplevel))
463 		toplevel = NULL;
464 
465 	dlg = gtk_file_chooser_dialog_new (_("Save diagram as"), (GtkWindow*) toplevel,
466 					   GTK_FILE_CHOOSER_ACTION_SAVE,
467 					   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
468 					   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
469 					   NULL);
470 	gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg),
471 					     gdaui_get_default_path ());
472 	filter = gtk_file_filter_new ();
473 	gtk_file_filter_set_name (filter, _("PNG Image"));
474 	gtk_file_filter_add_mime_type (filter, "image/png");
475 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
476 
477 	filter = gtk_file_filter_new ();
478 	gtk_file_filter_set_name (filter, _("SVG file"));
479 	gtk_file_filter_add_mime_type (filter, "image/svg+xml");
480 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
481 
482 	result = gtk_dialog_run (GTK_DIALOG (dlg));
483 	if (result == GTK_RESPONSE_ACCEPT) {
484 		gchar *filename;
485 		gchar *lcfilename;
486 		cairo_surface_t *surface = NULL;
487 
488 		gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg)));
489 		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));
490 		if (filename) {
491 			GooCanvasBounds bounds;
492 			gdouble width, height;
493 			gchar *error = NULL;
494 			enum {
495 				OUT_UNKNOWN,
496 				OUT_PNG,
497 				OUT_SVG
498 			} otype = OUT_UNKNOWN;
499 
500 			goo_canvas_item_get_bounds (goo_canvas_get_root_item (canvas->priv->goocanvas), &bounds);
501 			width = (bounds.x2 - bounds.x1) + 2. * MARGIN;
502 			height = (bounds.y2 - bounds.y1) + 2. * MARGIN;
503 
504 			lcfilename = g_ascii_strdown (filename, -1);
505 			if (g_str_has_suffix (lcfilename, "png")) {
506 				otype = OUT_PNG;
507 				surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
508 			}
509 			if (g_str_has_suffix (lcfilename, "svg")) {
510 				cairo_status_t status;
511 				otype = OUT_SVG;
512 				surface = cairo_svg_surface_create (filename, width, height);
513 				status = cairo_surface_status (surface);
514 				if (status != CAIRO_STATUS_SUCCESS) {
515 					error = g_strdup_printf ("<b>%s</b>:\n%s",
516 								 _("Failed to create SVG file"),
517 								 cairo_status_to_string (status));
518 					cairo_surface_destroy (surface);
519 					surface = NULL;
520 				}
521 			}
522 			if (otype == OUT_UNKNOWN)
523 				error = g_strdup_printf ("<b>%s</b>",
524 							 _("File format to save to is not recognized."));
525 
526 			if (surface) {
527 				cairo_t *cr;
528 				cairo_status_t status;
529 
530 				cr = cairo_create (surface);
531 				cairo_set_antialias (cr, CAIRO_ANTIALIAS_GRAY);
532 				cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas->priv->goocanvas));
533 				cairo_translate (cr, MARGIN - bounds.x1, MARGIN - bounds.y1);
534 
535 				goo_canvas_render (canvas->priv->goocanvas, cr, NULL, 0.8);
536 
537 				cairo_show_page (cr);
538 
539 				switch (otype) {
540 				case OUT_PNG:
541 					status = cairo_surface_write_to_png (surface, filename);
542 					if (status != CAIRO_STATUS_SUCCESS)
543 						error = g_strdup_printf ("<b>%s</b>:\n%s",
544 									 _("Failed to create PNG file"),
545 									 cairo_status_to_string (status));
546 					break;
547 				default:
548 					break;
549 				}
550 
551 				cairo_surface_destroy (surface);
552 				cairo_destroy (cr);
553 			}
554 
555 			if (error) {
556 				GtkWidget *errdlg;
557 
558 				errdlg = gtk_message_dialog_new ((GtkWindow*) toplevel,
559 								 GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
560 								 GTK_BUTTONS_CLOSE, NULL);
561 				gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (errdlg), error);
562 				g_free (error);
563 				gtk_dialog_run (GTK_DIALOG (errdlg));
564 				gtk_widget_destroy (errdlg);
565 			}
566 
567 			g_free (filename);
568 			g_free (lcfilename);
569 		}
570 	}
571 	gtk_widget_destroy (dlg);
572 }
573 
574 static void
popup_print_cb(G_GNUC_UNUSED GtkMenuItem * mitem,BrowserCanvas * canvas)575 popup_print_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
576 {
577 	browser_canvas_print (canvas);
578 }
579 
580 
581 static void
weak_ref_lost(BrowserCanvas * canvas,BrowserCanvasItem * old_item)582 weak_ref_lost (BrowserCanvas *canvas, BrowserCanvasItem *old_item)
583 {
584         canvas->priv->items = g_slist_remove (canvas->priv->items, old_item);
585 	if (canvas->priv->current_selected_item == old_item) {
586 		canvas->priv->current_selected_item = NULL;
587 		g_signal_emit (canvas, canvas_signals [ITEM_SELECTED], 0, NULL);
588 	}
589 }
590 
591 static void
browser_canvas_dispose(GObject * object)592 browser_canvas_dispose (GObject   * object)
593 {
594 	BrowserCanvas *canvas;
595 
596 	g_return_if_fail (object != NULL);
597 	g_return_if_fail (IS_BROWSER_CANVAS (object));
598 
599 	canvas = BROWSER_CANVAS (object);
600 
601 	/* get rid of the GooCanvasItems */
602 	if (canvas->priv->items) {
603 		GSList *list;
604 		for (list = canvas->priv->items; list; list = list->next)
605 			g_object_weak_unref (G_OBJECT (list->data), (GWeakNotify) weak_ref_lost, canvas);
606 		g_slist_free (canvas->priv->items);
607 		canvas->priv->items = NULL;
608 	}
609 
610 	/* for the parent class */
611 	parent_class->dispose (object);
612 }
613 
614 
615 /**
616  * browser_canvas_declare_item
617  * @canvas: a #BrowserCanvas widget
618  * @item: a #BrowserCanvasItem object
619  *
620  * Declares @item to be listed by @canvas as one of its items.
621  */
622 void
browser_canvas_declare_item(BrowserCanvas * canvas,BrowserCanvasItem * item)623 browser_canvas_declare_item (BrowserCanvas *canvas, BrowserCanvasItem *item)
624 {
625         g_return_if_fail (IS_BROWSER_CANVAS (canvas));
626         g_return_if_fail (canvas->priv);
627         g_return_if_fail (IS_BROWSER_CANVAS_ITEM (item));
628 
629 	/*g_print ("%s (canvas=>%p, item=>%p)\n", __FUNCTION__, canvas, item);*/
630         if (g_slist_find (canvas->priv->items, item))
631                 return;
632 
633         canvas->priv->items = g_slist_prepend (canvas->priv->items, item);
634 	g_object_weak_ref (G_OBJECT (item), (GWeakNotify) weak_ref_lost, canvas);
635 }
636 
637 
638 static void
browser_canvas_finalize(GObject * object)639 browser_canvas_finalize (GObject *object)
640 {
641 	BrowserCanvas *canvas;
642 
643 	g_return_if_fail (object != NULL);
644 	g_return_if_fail (IS_BROWSER_CANVAS (object));
645 	canvas = BROWSER_CANVAS (object);
646 
647 	if (canvas->priv) {
648 		g_free (canvas->priv);
649 		canvas->priv = NULL;
650 	}
651 
652 	/* for the parent class */
653 	parent_class->finalize (object);
654 }
655 
656 /**
657  * browser_canvas_set_zoom_factor
658  * @canvas: a #BrowserCanvas widget
659  * @n: the zoom factor
660  *
661  * Sets the zooming factor of a canvas by specifying the number of pixels that correspond
662  * to one canvas unit. A zoom factor of 1.0 is the default value; greater than 1.0 makes a zoom in
663  * and lower than 1.0 makes a zoom out.
664  */
665 void
browser_canvas_set_zoom_factor(BrowserCanvas * canvas,gdouble n)666 browser_canvas_set_zoom_factor (BrowserCanvas *canvas, gdouble n)
667 {
668 	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
669 	g_return_if_fail (canvas->priv);
670 
671 	if (n < 0.01)
672 		n = 0.01;
673 	else if (n > 1.)
674 		n = 1.;
675 	goo_canvas_set_scale (canvas->priv->goocanvas, n);
676 }
677 
678 /**
679  * browser_canvas_get_zoom_factor
680  * @canvas: a #BrowserCanvas widget
681  *
682  * Get the current zooming factor of a canvas.
683  *
684  * Returns: the zooming factor.
685  */
686 gdouble
browser_canvas_get_zoom_factor(BrowserCanvas * canvas)687 browser_canvas_get_zoom_factor (BrowserCanvas *canvas)
688 {
689 	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), 1.);
690 	g_return_val_if_fail (canvas->priv, 1.);
691 
692 	return goo_canvas_get_scale (canvas->priv->goocanvas);
693 }
694 
695 /**
696  * browser_canvas_fit_zoom_factor
697  * @canvas: a #BrowserCanvas widget
698  *
699  * Compute and set the correct zoom factor so that all the items on @canvas can be displayed
700  * at once.
701  *
702  * Returns: the new zooming factor.
703  */
704 gdouble
browser_canvas_fit_zoom_factor(BrowserCanvas * canvas)705 browser_canvas_fit_zoom_factor (BrowserCanvas *canvas)
706 {
707 	gdouble zoom, xall, yall;
708 	GooCanvasBounds bounds;
709 
710 	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), 1.);
711 	g_return_val_if_fail (canvas->priv, 1.);
712 
713 	GtkAllocation alloc;
714 	gtk_widget_get_allocation (GTK_WIDGET (canvas), &alloc);
715 	xall = alloc.width;
716 	yall = alloc.height;
717 
718 	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (goo_canvas_get_root_item (canvas->priv->goocanvas)),
719 				    &bounds);
720 	bounds.y1 -= 6.; bounds.y2 += 6.;
721 	bounds.x1 -= 6.; bounds.x2 += 6.;
722 	zoom = yall / (bounds.y2 - bounds.y1);
723 	if (xall / (bounds.x2 - bounds.x1) < zoom)
724 		zoom = xall / (bounds.x2 - bounds.x1);
725 
726 	/* set a limit to the zoom */
727 	if (zoom > DEFAULT_SCALE)
728 		zoom = DEFAULT_SCALE;
729 
730 	browser_canvas_set_zoom_factor (canvas, zoom);
731 
732 	return zoom;
733 }
734 
735 /**
736  * browser_canvas_center
737  * @canvas: a #BrowserCanvas widget
738  *
739  * Centers the display on the layout
740  */
741 void
browser_canvas_center(BrowserCanvas * canvas)742 browser_canvas_center (BrowserCanvas *canvas)
743 {
744 	/* remove top and left margins if we are running out of space */
745 	if (canvas->priv->goocanvas->hadjustment && canvas->priv->goocanvas->vadjustment) {
746 		gdouble hlow, hup, vlow, vup, hmargin, vmargin;
747 		gdouble left, top, right, bottom;
748 		GooCanvasBounds bounds;
749 
750 		goo_canvas_get_bounds (canvas->priv->goocanvas, &left, &top, &right, &bottom);
751 		goo_canvas_item_get_bounds (goo_canvas_get_root_item (canvas->priv->goocanvas),
752 					    &bounds);
753 
754 		g_object_get (G_OBJECT (GOO_CANVAS (canvas->priv->goocanvas)->hadjustment),
755 			      "lower", &hlow, "upper", &hup, NULL);
756 		g_object_get (G_OBJECT (GOO_CANVAS (canvas->priv->goocanvas)->vadjustment),
757 			      "lower", &vlow, "upper", &vup, NULL);
758 
759 		/*
760 		g_print ("Canvas's bounds: %.2f,%.2f -> %.2f,%.2f\n", left, top, right, bottom);
761 		g_print ("Root's bounds: %.2f,%.2f -> %.2f,%.2f\n", bounds.x1, bounds.y1, bounds.x2, bounds.y2);
762 		g_print ("Xm: %.2f, Ym: %.2f\n", hup - hlow - (right - left), vup - vlow - (bottom - top));
763 		*/
764 		hmargin = hup - hlow - (bounds.x2 - bounds.x1);
765 		if (hmargin > 0)
766 			left -= hmargin / 2. + (left - bounds.x1);
767 		vmargin = vup - vlow - (bounds.y2 - bounds.y1);
768 		if (vmargin > 0)
769 			top -= vmargin / 2. + (top - bounds.y1);
770 		if ((hmargin > 0) || (vmargin > 0)) {
771 			goo_canvas_set_bounds (canvas->priv->goocanvas, left, top, right, bottom);
772 			/*g_print ("Canvas's new bounds: %.2f,%.2f -> %.2f,%.2f\n", left, top, right, bottom);*/
773 			goo_canvas_set_scale (canvas->priv->goocanvas, canvas->priv->goocanvas->scale);
774 		}
775 	}
776 }
777 
778 /**
779  * browser_canvas_auto_layout_enabled
780  * @canvas: a #BrowserCanvas widget
781  *
782  * Tells if @canvas has the possibility to automatically adjust its layout
783  * using the GraphViz library.
784  *
785  * Returns: TRUE if @canvas can automatically adjust its layout
786  */
787 gboolean
browser_canvas_auto_layout_enabled(BrowserCanvas * canvas)788 browser_canvas_auto_layout_enabled (BrowserCanvas *canvas)
789 {
790 	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), FALSE);
791 	g_return_val_if_fail (canvas->priv, FALSE);
792 
793 #ifdef HAVE_GRAPHVIZ
794 	return TRUE;
795 #else
796 	return FALSE;
797 #endif
798 }
799 
800 #ifdef HAVE_GRAPHVIZ
801 typedef struct {
802 	BrowserCanvas    *canvas;
803 	Agraph_t      *graph;
804 	GSList        *nodes_list; /* list of NodeLayout structures */
805 } GraphLayout;
806 
807 typedef struct {
808 	BrowserCanvasItem *item; /* item to be moved */
809 	Agnode_t          *node;
810 	gdouble            start_x;
811 	gdouble            start_y;
812 	gdouble            end_x;
813 	gdouble            end_y;
814 	gdouble            width;
815 	gdouble            height;
816 	gdouble            dx;
817 	gdouble            dy;
818 	gboolean           stop;
819 	gdouble            cur_x;
820 	gdouble            cur_y;
821 } NodeLayout;
822 
823 static gboolean canvas_animate_to (GraphLayout *gl);
824 #endif
825 
826 /**
827  * browser_canvas_auto_layout
828  * @canvas: a #BrowserCanvas widget
829  *
830  * Re-organizes the layout of the @canvas' items using the GraphViz
831  * layout engine.
832  */
833 void
browser_canvas_perform_auto_layout(BrowserCanvas * canvas,gboolean animate,BrowserCanvasLayoutAlgorithm algorithm)834 browser_canvas_perform_auto_layout (BrowserCanvas *canvas, gboolean animate, BrowserCanvasLayoutAlgorithm algorithm)
835 {
836 	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
837 	g_return_if_fail (canvas->priv);
838 
839 #define GV_SCALE 72.
840 
841 #ifndef HAVE_GRAPHVIZ
842 	g_message ("GraphViz library support not compiled, cannot do graph layout...\n");
843 	return;
844 #else
845 	BrowserCanvasClass *class = BROWSER_CANVAS_CLASS (G_OBJECT_GET_CLASS (canvas));
846 	GSList *list, *layout_items;
847 	Agraph_t *graph;
848 	GHashTable *nodes_hash; /* key = BrowserCanvasItem, value = Agnode_t *node */
849 	GSList *nodes_list = NULL; /* list of NodeLayout structures */
850 
851 	if (!gvc)
852 		gvc = gvContext ();
853 
854 #ifdef GRAPHVIZ_NEW_API
855 	graph = agopen ("BrowserCanvasLayout", Agdirected, NULL);
856         agnode (graph, "shape", "box");
857         agset (graph, "height", ".1");
858         agset (graph, "width", ".1");
859         agset (graph, "fixedsize", "true");
860         agset (graph, "pack", "true");
861 	agset (graph, "packmode", "node");
862 #else
863 	graph = agopen ("BrowserCanvasLayout", AGRAPH);
864         agnodeattr (graph, "shape", "box");
865         agnodeattr (graph, "height", ".1");
866         agnodeattr (graph, "width", ".1");
867         agnodeattr (graph, "fixedsize", "true");
868         agnodeattr (graph, "pack", "true");
869 	agnodeattr (graph, "packmode", "node");
870 #endif
871 
872 
873 	if (class->get_layout_items)
874 		layout_items = class->get_layout_items (canvas);
875 	else
876 		layout_items = canvas->priv->items;
877 
878 	/* Graph nodes creation */
879 	nodes_hash = g_hash_table_new (NULL, NULL);
880 	for (list = layout_items; list; list = list->next) {
881 		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
882 		Agnode_t *node;
883 		gchar *tmp;
884 		double val;
885 		GooCanvasBounds bounds;
886 		gboolean moving;
887 		NodeLayout *nl;
888 
889 		g_object_get (G_OBJECT (item), "allow-move", &moving, NULL);
890 		if (!moving)
891 			continue;
892 
893 		nl = g_new0 (NodeLayout, 1);
894 		nl->item = item;
895 		nodes_list = g_slist_prepend (nodes_list, nl);
896 
897 		tmp = g_strdup_printf ("%p", item);
898 #ifdef GRAPHVIZ_NEW_API
899 		node = agnode (graph, tmp, 1);
900 #else
901 		node = agnode (graph, tmp);
902 #endif
903 		g_free (tmp);
904 		nl->node = node;
905 		g_hash_table_insert (nodes_hash, item, node);
906 		goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item), &bounds);
907 		nl->width = bounds.x2 - bounds.x1;
908 		nl->height = bounds.y2 - bounds.y1;
909 		val = (bounds.y2 - bounds.y1) / GV_SCALE;
910 
911 		if (node) {
912 			tmp = g_strdup_printf ("%p", node);
913 			agset (node, "label", tmp);
914 			g_free (tmp);
915 
916 			tmp = g_strdup_printf ("%.3f", val);
917 			agset (node, "height", tmp);
918 			g_free (tmp);
919 
920 			val = (bounds.x2 - bounds.x1) / GV_SCALE;
921 			tmp = g_strdup_printf ("%.3f", val);
922 			agset (node, "width", tmp);
923 			g_free (tmp);
924 		}
925 
926 		nl->start_x = bounds.x1;
927 		nl->start_y = bounds.y1;
928 		nl->cur_x = nl->start_x;
929 		nl->cur_y = nl->start_y;
930 
931 		/*g_print ("Before: Node %p: HxW: %.3f %.3f\n", node, (bounds.y2 - bounds.y1) / GV_SCALE,
932 		  (bounds.x2 - bounds.x1) / GV_SCALE);*/
933 	}
934 	/* Graph edges creation */
935 	for (list = layout_items; list; list = list->next) {
936 		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
937 		BrowserCanvasItem *from, *to;
938 		gboolean moving;
939 
940 		g_object_get (G_OBJECT (item), "allow-move", &moving, NULL);
941 		if (moving)
942 			continue;
943 
944 		browser_canvas_item_get_edge_nodes (item, &from, &to);
945 		if (from && to) {
946 			Agnode_t *from_node, *to_node;
947 			from_node = (Agnode_t*) g_hash_table_lookup (nodes_hash, from);
948 			to_node = (Agnode_t*) g_hash_table_lookup (nodes_hash, to);
949 			if (from_node && to_node) {
950 #ifdef GRAPHVIZ_NEW_API
951 				agedge (graph, from_node, to_node, "", 0);
952 #else
953 				agedge (graph, from_node, to_node);
954 #endif
955 			}
956 		}
957 	}
958 
959 	if (layout_items != canvas->priv->items)
960 		g_slist_free (layout_items);
961 
962 	switch (algorithm) {
963 	default:
964 	case BROWSER_CANVAS_LAYOUT_DEFAULT:
965 		gvLayout (gvc, graph, "dot");
966 		break;
967 	case BROWSER_CANVAS_LAYOUT_RADIAL:
968 		gvLayout (gvc, graph, "circo");
969 		break;
970 	}
971         /*gvRender (gvc, graph, "dot", NULL);*/
972 	/*gvRenderFilename (gvc, graph, "png", "out.png");*/
973         /*gvRender (gvc, graph, "dot", stdout);*/
974 
975 	for (list = nodes_list; list; list = list->next) {
976 		NodeLayout *nl = (NodeLayout*) list->data;
977 		nl->end_x = ND_coord_i (nl->node).x - (nl->width / 2.);
978 		nl->end_y = - ND_coord_i (nl->node).y - (nl->height / 2.);
979 		nl->dx = fabs (nl->end_x - nl->start_x);
980 		nl->dy = fabs (nl->end_y - nl->start_y);
981 		nl->stop = FALSE;
982 		/*g_print ("After: Node %p: HxW: %.3f %.3f XxY = %d, %d\n", nl->node,
983 			 ND_height (nl->node), ND_width (nl->node),
984 			 ND_coord_i (nl->node).x, - ND_coord_i (nl->node).y);*/
985 		if (!animate)
986 			goo_canvas_item_translate (GOO_CANVAS_ITEM (nl->item), nl->end_x - nl->start_x,
987 						   nl->end_y - nl->start_y);
988 	}
989 
990 	g_hash_table_destroy (nodes_hash);
991 	gvFreeLayout (gvc, graph);
992 
993 	if (animate) {
994 		GraphLayout *gl;
995 		gl = g_new0 (GraphLayout, 1);
996 		gl->canvas = canvas;
997 		gl->graph = graph;
998 		gl->nodes_list = nodes_list;
999 		while (canvas_animate_to (gl));
1000 	}
1001 	else {
1002 		agclose (graph);
1003 		g_slist_foreach (nodes_list, (GFunc) g_free, NULL);
1004 		g_slist_free (nodes_list);
1005 	}
1006 
1007 #endif
1008 }
1009 
1010 #ifdef HAVE_GRAPHVIZ
1011 static gdouble
compute_animation_inc(float start,float stop,G_GNUC_UNUSED float current)1012 compute_animation_inc (float start, float stop, G_GNUC_UNUSED float current)
1013 {
1014         gdouble inc;
1015 #ifndef PI
1016 #define PI 3.14159265
1017 #endif
1018 #define STEPS 30.
1019 
1020         if (stop == start)
1021                 return 0.;
1022 
1023 	inc = (stop - start) / STEPS;
1024 
1025         return inc;
1026 }
1027 
1028 static gboolean
canvas_animate_to(GraphLayout * gl)1029 canvas_animate_to (GraphLayout *gl)
1030 {
1031 	gboolean stop = TRUE;
1032 	GSList *list;
1033 
1034 #define EPSILON 1.
1035 	for (list = gl->nodes_list; list; list = list->next) {
1036 		NodeLayout *nl = (NodeLayout*) list->data;
1037 		if (!nl->stop) {
1038 			gdouble dx, dy, ndx, ndy;
1039 
1040 			dx = compute_animation_inc (nl->start_x, nl->end_x, nl->cur_x);
1041 			dy = compute_animation_inc (nl->start_y, nl->end_y, nl->cur_y);
1042 			ndx = fabs (nl->cur_x + dx - nl->end_x);
1043 			ndy = fabs (nl->cur_y + dy - nl->end_y);
1044 			nl->cur_x +=  dx;
1045 			nl->cur_y +=  dy;
1046 			browser_canvas_item_translate (nl->item, dx, dy);
1047 			if (((ndx <= EPSILON) || (ndx >= nl->dx)) &&
1048 			    ((ndy <= EPSILON) || (ndy >= nl->dy)))
1049 				nl->stop = TRUE;
1050 			else {
1051 				stop = FALSE;
1052 				nl->dx = ndx;
1053 				nl->dy = ndy;
1054 			}
1055 		}
1056 	}
1057 
1058 	goo_canvas_request_update (GOO_CANVAS (gl->canvas->priv->goocanvas));
1059 	goo_canvas_update (GOO_CANVAS (gl->canvas->priv->goocanvas));
1060 	while (gtk_events_pending ())
1061 		gtk_main_iteration ();
1062 
1063 	if (stop) {
1064 		agclose (gl->graph);
1065 		g_slist_foreach (gl->nodes_list, (GFunc) g_free, NULL);
1066 		g_slist_free (gl->nodes_list);
1067 		g_free (gl);
1068 	}
1069 	return !stop;
1070 }
1071 #endif
1072 
1073 /**
1074  * browser_canvas_scale_layout
1075  */
1076 void
browser_canvas_scale_layout(BrowserCanvas * canvas,gdouble scale)1077 browser_canvas_scale_layout (BrowserCanvas *canvas, gdouble scale)
1078 {
1079 	GSList *list;
1080 	GooCanvasBounds ref_bounds;
1081 	gdouble refx, refy;
1082 
1083 	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
1084 	if (!canvas->priv->items)
1085 		return;
1086 
1087 	goo_canvas_get_bounds (canvas->priv->goocanvas, &ref_bounds.x1, &ref_bounds.y1,
1088 			       &ref_bounds.x2, &ref_bounds.y2);
1089 	refx = (ref_bounds.x2 - ref_bounds.x1) / 2.;
1090 	refy = (ref_bounds.y2 - ref_bounds.y1) / 2.;
1091 	for (list = canvas->priv->items; list; list = list->next) {
1092 		gboolean can_move;
1093 		g_object_get ((GObject*) list->data, "allow-move", &can_move, NULL);
1094 		if (can_move) {
1095 			BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
1096 			GooCanvasBounds bounds;
1097 			gdouble tx, ty;
1098 
1099 			goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item), &bounds);
1100 			tx = (scale - 1.) * (bounds.x1 - refx);
1101 			ty = (scale - 1.) * (bounds.y1 - refy);
1102 			browser_canvas_item_translate (item, tx, ty);
1103 		}
1104 	}
1105 }
1106 
1107 /**
1108  * browser_canvas_serialize_items
1109  */
1110 gchar *
browser_canvas_serialize_items(BrowserCanvas * canvas)1111 browser_canvas_serialize_items (BrowserCanvas *canvas)
1112 {
1113 	gchar *retval = NULL;
1114 	GSList *list;
1115 	xmlDocPtr doc;
1116 	xmlNodePtr topnode;
1117 
1118 	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), NULL);
1119 
1120 	/* create XML doc and root node */
1121 	doc = xmlNewDoc (BAD_CAST "1.0");
1122 	topnode = xmlNewDocNode (doc, NULL, BAD_CAST "canvas", NULL);
1123         xmlDocSetRootElement (doc, topnode);
1124 
1125 	/* actually serialize all the items which can be serialized */
1126 	for (list = canvas->priv->items; list; list = list->next) {
1127 		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
1128 		BrowserCanvasItemClass *iclass = (BrowserCanvasItemClass*) G_OBJECT_GET_CLASS (item);
1129 		if (iclass->serialize) {
1130 			xmlNodePtr node;
1131 			node = iclass->serialize (item);
1132 			if (node)
1133 				xmlAddChild (topnode, node);
1134 		}
1135 	}
1136 
1137 	/* create buffer from XML tree */
1138 	xmlChar *xstr = NULL;
1139 	xmlDocDumpMemory (doc, &xstr, NULL);
1140 	if (xstr) {
1141 		retval = g_strdup ((gchar *) xstr);
1142 		xmlFree (xstr);
1143 	}
1144 	xmlFreeDoc (doc);
1145 
1146 	return retval;
1147 }
1148 
1149 /**
1150  * browser_canvas_item_toggle_select
1151  */
1152 void
browser_canvas_item_toggle_select(BrowserCanvas * canvas,BrowserCanvasItem * item)1153 browser_canvas_item_toggle_select (BrowserCanvas *canvas, BrowserCanvasItem *item)
1154 {
1155 	gboolean do_select = TRUE;
1156 	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
1157 	g_return_if_fail (!item || IS_BROWSER_CANVAS_ITEM (item));
1158 
1159 	if (canvas->priv->current_selected_item == item) {
1160 		/* deselect item */
1161 		do_select = FALSE;
1162 	}
1163 
1164 	if (canvas->priv->current_selected_item) {
1165 		BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (canvas->priv->current_selected_item));
1166 		if (iclass->set_selected)
1167 			iclass->set_selected (canvas->priv->current_selected_item, FALSE);
1168 		canvas->priv->current_selected_item = NULL;
1169 	}
1170 
1171 
1172 	if (do_select && item) {
1173 		BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (item));
1174 		if (iclass->set_selected)
1175 			iclass->set_selected (item, TRUE);
1176 		canvas->priv->current_selected_item = item;
1177 	}
1178 	g_signal_emit (canvas, canvas_signals [ITEM_SELECTED], 0, item);
1179 }
1180 
1181 void
browser_canvas_translate_item(G_GNUC_UNUSED BrowserCanvas * canvas,BrowserCanvasItem * item,gdouble dx,gdouble dy)1182 browser_canvas_translate_item (G_GNUC_UNUSED BrowserCanvas *canvas, BrowserCanvasItem *item,
1183 			       gdouble dx, gdouble dy)
1184 {
1185 	browser_canvas_item_translate (item, dx, dy);
1186 }
1187