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