[libgda] Initial support for canvas-based relations view in the Browser



commit 1e30677c5b134aa3f9bc630132bb00dae37e5881
Author: Vivien Malerba <malerba gnome-db org>
Date:   Tue Jun 23 21:17:22 2009 +0200

    Initial support for canvas-based relations view in the Browser
    
    Merged Libgnomedb's canvas into the gda-browser tool to display
    tables' relations using a canvas from the GooCanvas library

 configure.in                                       |    1 +
 tools/browser/.gitignore                           |    1 +
 tools/browser/Makefile.am                          |   24 +
 tools/browser/browser-core.c                       |   31 +-
 tools/browser/browser-core.h                       |    1 +
 tools/browser/browser-window.c                     |   17 +-
 tools/browser/canvas-example.c                     |  176 ++++
 tools/browser/canvas/Makefile.am                   |   37 +
 tools/browser/canvas/browser-canvas-column.c       |  307 +++++++
 tools/browser/canvas/browser-canvas-column.h       |   65 ++
 tools/browser/canvas/browser-canvas-db-relations.c |  476 ++++++++++
 tools/browser/canvas/browser-canvas-db-relations.h |   66 ++
 tools/browser/canvas/browser-canvas-decl.h         |   46 +
 tools/browser/canvas/browser-canvas-fkey.c         |  477 ++++++++++
 tools/browser/canvas/browser-canvas-fkey.h         |   64 ++
 tools/browser/canvas/browser-canvas-item.c         |  405 +++++++++
 tools/browser/canvas/browser-canvas-item.h         |   69 ++
 tools/browser/canvas/browser-canvas-print.c        |  456 ++++++++++
 tools/browser/canvas/browser-canvas-print.h        |   27 +
 tools/browser/canvas/browser-canvas-priv.h         |   36 +
 tools/browser/canvas/browser-canvas-table.c        |  500 +++++++++++
 tools/browser/canvas/browser-canvas-table.h        |   66 ++
 tools/browser/canvas/browser-canvas-text.c         |  527 +++++++++++
 tools/browser/canvas/browser-canvas-text.h         |   71 ++
 tools/browser/canvas/browser-canvas-utility.c      |  836 ++++++++++++++++++
 tools/browser/canvas/browser-canvas-utility.h      |   51 ++
 tools/browser/canvas/browser-canvas.c              |  934 ++++++++++++++++++++
 tools/browser/canvas/browser-canvas.h              |   79 ++
 tools/browser/main.c                               |   23 +
 tools/browser/schema-browser/Makefile.am           |    6 +
 tools/browser/schema-browser/favorite-selector.c   |    3 +-
 tools/browser/schema-browser/table-columns.c       |    2 -
 tools/browser/schema-browser/table-info.c          |   16 +
 tools/browser/schema-browser/table-relations.c     |  200 +++++
 tools/browser/schema-browser/table-relations.h     |   56 ++
 35 files changed, 6138 insertions(+), 14 deletions(-)
---
diff --git a/configure.in b/configure.in
index 2df2bf2..3c5fa35 100644
--- a/configure.in
+++ b/configure.in
@@ -1702,6 +1702,7 @@ tools/gda-sql-4.0.1:tools/gda-sql.1.in
 tools/browser/Makefile
 tools/browser/schema-browser/Makefile
 tools/browser/dummy-perspective/Makefile
+tools/browser/canvas/Makefile
 tools/binreloc/Makefile
 testing/Makefile
 tests/Makefile
diff --git a/tools/browser/.gitignore b/tools/browser/.gitignore
index d6da0c2..b6f4891 100644
--- a/tools/browser/.gitignore
+++ b/tools/browser/.gitignore
@@ -1,4 +1,5 @@
 gda-browser-4.*
 gda-browser-4.*.desktop
+canvas-example
 marshal.c
 marshal.h
diff --git a/tools/browser/Makefile.am b/tools/browser/Makefile.am
index df839ff..92d93db 100644
--- a/tools/browser/Makefile.am
+++ b/tools/browser/Makefile.am
@@ -1,6 +1,11 @@
 bin_PROGRAMS=gda-browser-4.0
+noinst_PROGRAMS = 
 
 SUBDIRS = schema-browser dummy-perspective
+if HAVE_GOOCANVAS
+SUBDIRS+=canvas
+noinst_PROGRAMS+=canvas-example
+endif
 
 AM_CPPFLAGS = \
         -I$(top_srcdir) \
@@ -73,12 +78,31 @@ gda_browser_4_0_LDADD=\
 	$(top_builddir)/libgdaui/libgdaui-4.0.la \
 	$(LIBGDA_LIBS) $(GTK_LIBS)
 
+if HAVE_GOOCANVAS
+gda_browser_4_0_LDADD+= \
+	$(top_builddir)/tools/browser/canvas/libcanvas.la
+endif
+
 @INTLTOOL_DESKTOP_RULE@
 
 desktopdir=$(datadir)/applications
 Desktop_in_files = gda-browser-4.0.desktop.in
 desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop)
 
+# canvas example
+canvas_example_DEPENDENCIES = \
+	canvas/libcanvas.la
+canvas_example_SOURCES = \
+	canvas-example.c \
+	dnd.c \
+	dnd.h
+canvas_example_CFLAGS = -DCANVAS_EXAMPLE
+canvas_example_LDFLAGS = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(top_builddir)/libgdaui/libgdaui-4.0.la \
+	canvas/libcanvas.la
+
+
 # icons
 iconsdir=$(datadir)/pixmaps
 icons_DATA= \
diff --git a/tools/browser/browser-core.c b/tools/browser/browser-core.c
index 5dff518..46dcd22 100644
--- a/tools/browser/browser-core.c
+++ b/tools/browser/browser-core.c
@@ -119,9 +119,13 @@ browser_core_init (BrowserCore *bcore)
 	bcore->priv = g_new0 (BrowserCorePrivate, 1);
 	bcore->priv->factories = NULL;
 
-	bcore->priv->factories = g_slist_append (bcore->priv->factories, schema_browser_perspective_get_factory ());
-	bcore->priv->default_factory = (BrowserPerspectiveFactory*) bcore->priv->factories->data; /* set default perspective */
-	bcore->priv->factories = g_slist_append (bcore->priv->factories, dummy_perspective_get_factory ());
+	bcore->priv->factories = g_slist_append (bcore->priv->factories,
+						 schema_browser_perspective_get_factory ());
+	/* set default perspective */
+	bcore->priv->default_factory = (BrowserPerspectiveFactory*) bcore->priv->factories->data; 
+
+	bcore->priv->factories = g_slist_append (bcore->priv->factories,
+						 dummy_perspective_get_factory ());
 
 	bcore->priv->windows = NULL;
 }
@@ -296,6 +300,27 @@ browser_core_get_default_factory (void)
 }
 
 /**
+ * browser_core_set_default_factory
+ */
+void
+browser_core_set_default_factory (const gchar *factory)
+{
+	GSList *list;
+	_bcore = browser_core_get ();
+
+	if (!factory)
+		return;
+
+	for (list = _bcore->priv->factories; list; list = list->next) {
+		BrowserPerspectiveFactory *fact = (BrowserPerspectiveFactory*) list->data;
+		if (strstr (fact->perspective_name, factory)) {
+			_bcore->priv->default_factory = fact;
+			break;
+		}
+	}
+}
+
+/**
  * browser_core_get_factories
  *
  * Get a list of all the known Perspective factories
diff --git a/tools/browser/browser-core.h b/tools/browser/browser-core.h
index dcdac1d..0378c66 100644
--- a/tools/browser/browser-core.h
+++ b/tools/browser/browser-core.h
@@ -66,6 +66,7 @@ void            browser_core_close_connection       (BrowserConnection *bcnc);
 void            browser_core_quit                   (void);
 
 BrowserPerspectiveFactory *browser_core_get_default_factory    (void);
+void           browser_core_set_default_factory (const gchar *factory);
 const GSList       *browser_core_get_factories          (void);
 
 G_END_DECLS
diff --git a/tools/browser/browser-window.c b/tools/browser/browser-window.c
index 79b7f9b..d740067 100644
--- a/tools/browser/browser-window.c
+++ b/tools/browser/browser-window.c
@@ -374,19 +374,18 @@ browser_window_new (BrowserConnection *bcnc, BrowserPerspectiveFactory *factory)
 	gtk_ui_manager_insert_action_group (bwin->priv->ui_manager, agroup, 0);
 	g_object_unref (agroup);
 
+	GtkAction *active_action = NULL;
 	for (plist = browser_core_get_factories (); plist; plist = plist->next) {
 		GtkAction *action;
 		const gchar *name;
-		gboolean active;
 		
-		if ((factory && (BROWSER_PERSPECTIVE_FACTORY (plist->data) == factory)) ||
-		    (!factory && (BROWSER_PERSPECTIVE_FACTORY (plist->data) == browser_core_get_default_factory ())))
-			active = TRUE;
-		else
-			active = FALSE;
 		name = BROWSER_PERSPECTIVE_FACTORY (plist->data)->perspective_name;
-		action = GTK_ACTION (gtk_radio_action_new (name, name, NULL, NULL, active));
-		gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), active);
+		action = GTK_ACTION (gtk_radio_action_new (name, name, NULL, NULL, FALSE));
+
+		if (!active_action && 
+		    (factory && (BROWSER_PERSPECTIVE_FACTORY (plist->data) == factory)) ||
+		    (!factory && (BROWSER_PERSPECTIVE_FACTORY (plist->data) == browser_core_get_default_factory ())))
+			active_action = action;
 		gtk_action_group_add_action (agroup, action);
 		
 		gtk_radio_action_set_group (GTK_RADIO_ACTION (action), radio_group);
@@ -402,6 +401,8 @@ browser_window_new (BrowserConnection *bcnc, BrowserPerspectiveFactory *factory)
 				       name, name,
 				       GTK_UI_MANAGER_AUTO, FALSE);
 	}
+	
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (active_action), TRUE);
 
         gtk_widget_show (GTK_WIDGET (bwin));
 
diff --git a/tools/browser/canvas-example.c b/tools/browser/canvas-example.c
new file mode 100644
index 0000000..a2660ce
--- /dev/null
+++ b/tools/browser/canvas-example.c
@@ -0,0 +1,176 @@
+#include <libgda/libgda.h>
+#include <libgda/binreloc/gda-binreloc.h>
+#include <libgdaui/libgdaui.h>
+#include "canvas/browser-canvas-db-relations.h"
+#include "dnd.h"
+
+static gboolean on_delete_event (GtkWidget *window, GdkEvent *event, gpointer unused_data);
+static void auto_layout_cb (GtkWidget *button, BrowserCanvas *canvas);
+static void label_drag_data_received (GtkWidget *label, GdkDragContext *context,
+				      gint x, gint y, GtkSelectionData *data,
+				      guint info, guint time);
+
+int
+main (int argc, char *argv[])
+{
+	GdaConnection* connection;
+	GError* error = NULL;
+
+	gdaui_init ();
+        gtk_init (&argc, &argv);
+
+	/* open connection to the SalesTest data source */
+	connection = gda_connection_open_from_dsn ("SalesTest", NULL, GDA_CONNECTION_OPTIONS_NONE, &error);
+	if (!connection) {
+		fprintf (stderr, "%s\n", error->message);
+		return -1;
+	}
+
+	/* mate store update */
+	g_print ("Metastore update...\n");
+	if (!gda_connection_update_meta_store (connection, NULL, &error))
+		return -1;
+
+	/* GdaMetaStruct */
+	GdaMetaStruct *mstruct;
+	mstruct = gda_meta_struct_new (gda_connection_get_meta_store (connection),
+				       GDA_META_STRUCT_FEATURE_ALL);
+
+	/* UI Part */
+	GtkWidget *window, *table, *canvas;
+	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_default_size (GTK_WINDOW (window), 640, 600);
+	g_signal_connect (window, "delete_event", G_CALLBACK (on_delete_event),
+			  NULL);
+
+	table = gtk_table_new (3, 1, FALSE);
+	gtk_container_set_border_width (GTK_CONTAINER (table), 15);
+	gtk_container_add (GTK_CONTAINER (window), table);
+
+	canvas = browser_canvas_db_relations_new (mstruct);
+	g_object_unref (mstruct);
+
+	gtk_table_attach_defaults (GTK_TABLE (table), canvas,
+				   0, 1, 0, 1);
+
+	GtkWidget *bbox, *button;
+	bbox = gtk_hbutton_box_new ();
+	gtk_table_attach (GTK_TABLE (table), bbox, 0, 1, 1, 2, 0, 0, 0, 0);
+	button = gtk_button_new_with_label ("Auto layout");
+	g_signal_connect (button, "clicked",
+			  G_CALLBACK (auto_layout_cb), canvas);
+	gtk_box_pack_start (GTK_BOX (bbox), button, TRUE, TRUE, 0);
+
+	GtkWidget *wid;
+	GtkTargetEntry dbo_table[] = {
+		{ "text/plain", 0, 0 },
+		{ "key-value", 0, 1 },
+		{ "application/x-rootwindow-drop", 0, 2 }
+	};
+	wid = gtk_label_new ("\nDROP ZONE\n(hold SHIFT to drag and drop)\n");
+	gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 2, 3, 0, 0, 0, 0);
+	gtk_drag_dest_set (wid,
+			   GTK_DEST_DEFAULT_ALL,
+			   dbo_table, G_N_ELEMENTS (dbo_table),
+			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
+	g_signal_connect (wid, "drag_data_received",
+			  G_CALLBACK (label_drag_data_received), NULL);
+
+	gtk_window_set_default_size (GTK_WINDOW (window), 600, 450);
+	gtk_widget_show_all (window);
+
+	/* add some tables */
+	GValue *tname;
+	g_value_set_string ((tname = gda_value_new (G_TYPE_STRING)), "customers");
+	browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (canvas), NULL, NULL, tname);
+	g_value_set_string (tname, "orders");
+	browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (canvas), NULL, NULL, tname);
+	g_value_set_string (tname, "order_contents");
+	browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (canvas), NULL, NULL, tname);
+	g_value_set_string (tname, "products");
+	browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (canvas), NULL, NULL, tname);
+	g_value_set_string (tname, "locations");
+	browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (canvas), NULL, NULL, tname);
+	gda_value_free (tname);
+
+	/* Pass control to the GTK+ main event loop. */
+	gtk_main ();
+
+	return 0;
+}
+
+static void
+auto_layout_cb (GtkWidget *button, BrowserCanvas *canvas)
+{
+	browser_canvas_perform_auto_layout (BROWSER_CANVAS (canvas), TRUE, BROWSER_CANVAS_LAYOUT_RADIAL);
+}
+
+static void
+label_drag_data_received (GtkWidget *label, GdkDragContext *context,
+			  gint x, gint y, GtkSelectionData *data,
+			  guint info, guint time)
+{
+	if ((data->length >= 0) && (data->format == 8)) {
+		g_print ("Received \"%s\" in drop zone\n", (gchar *)data->data);
+		gtk_drag_finish (context, TRUE, FALSE, time);
+		return;
+	}
+
+	gtk_drag_finish (context, FALSE, FALSE, time);
+}
+
+static gboolean
+on_delete_event (GtkWidget *window, GdkEvent *event, gpointer unused_data)
+{
+	exit (0);
+}
+
+/*
+ * icons
+ */
+typedef enum {
+        BROWSER_ICON_BOOKMARK,
+        BROWSER_ICON_SCHEMA,
+        BROWSER_ICON_TABLE,
+        BROWSER_ICON_COLUMN,
+        BROWSER_ICON_COLUMN_PK,
+        BROWSER_ICON_COLUMN_FK,
+        BROWSER_ICON_COLUMN_FK_NN,
+        BROWSER_ICON_COLUMN_NN,
+        BROWSER_ICON_REFERENCE,
+
+        BROWSER_ICON_LAST
+} BrowserIconType;
+
+GdkPixbuf *
+browser_get_pixbuf_icon (BrowserIconType type)
+{
+        static GdkPixbuf **array = NULL;
+        static const gchar* names[] = {
+                "gda-browser-bookmark.png",
+                "gda-browser-schema.png",
+                "gda-browser-table.png",
+                "gda-browser-column.png",
+                "gda-browser-column-pk.png",
+                "gda-browser-column-fk.png",
+                "gda-browser-column-fknn.png",
+                "gda-browser-column-nn.png",
+                "gda-browser-reference.png"
+        };
+
+        if (!array)
+                array = g_new0 (GdkPixbuf *, BROWSER_ICON_LAST);
+        if (!array [type]) {
+                gchar *path;
+                path = gda_gbr_get_file_path (GDA_DATA_DIR, "pixmaps", names[type], NULL);
+                array [type] = gdk_pixbuf_new_from_file (path, NULL);
+                g_free (path);
+
+                if (!array [type])
+                        array [type] = (GdkPixbuf*) 0x01;
+        }
+        if (array [type] == (GdkPixbuf*) 0x01)
+                return NULL;
+        else
+                return array [type];
+}
diff --git a/tools/browser/canvas/Makefile.am b/tools/browser/canvas/Makefile.am
new file mode 100644
index 0000000..971ace4
--- /dev/null
+++ b/tools/browser/canvas/Makefile.am
@@ -0,0 +1,37 @@
+noinst_LTLIBRARIES = libcanvas.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/tools/browser \
+	-I$(top_builddir) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/libgda \
+	$(LIBGDA_CFLAGS) \
+	$(GTK_CFLAGS) \
+	$(GOOCANVAS_CFLAGS) \
+	$(GRAPHVIZ_CFLAGS)
+
+libcanvas_la_SOURCES = \
+	browser-canvas.c \
+	browser-canvas.h \
+	browser-canvas-column.c \
+	browser-canvas-column.h \
+	browser-canvas-db-relations.c \
+	browser-canvas-db-relations.h \
+	browser-canvas-decl.h \
+	browser-canvas-fkey.c \
+	browser-canvas-fkey.h \
+	browser-canvas-item.c \
+	browser-canvas-item.h \
+	browser-canvas-print.c \
+	browser-canvas-print.h \
+	browser-canvas-table.c \
+	browser-canvas-table.h \
+	browser-canvas-text.c \
+	browser-canvas-text.h \
+	browser-canvas-utility.c \
+	browser-canvas-utility.h
+
+
+libcanvas_la_LDFLAGS = \
+	$(GOOCANVAS_LIBS) \
+	$(GRAPHVIZ_LIBS)
diff --git a/tools/browser/canvas/browser-canvas-column.c b/tools/browser/canvas/browser-canvas-column.c
new file mode 100644
index 0000000..47dc979
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-column.c
@@ -0,0 +1,307 @@
+/* browser-canvas-column.c
+ *
+ * Copyright (C) 2002 - 2008 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <libgda/libgda.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "browser-canvas.h"
+#include "browser-canvas-column.h"
+#include "browser-canvas-table.h"
+
+static void browser_canvas_column_class_init (BrowserCanvasColumnClass * class);
+static void browser_canvas_column_init       (BrowserCanvasColumn * drag);
+static void browser_canvas_column_dispose   (GObject *object);
+
+static void browser_canvas_column_set_property (GObject *object,
+					     guint param_id,
+					     const GValue *value,
+					     GParamSpec *pspec);
+static void browser_canvas_column_get_property (GObject *object,
+					     guint param_id,
+					     GValue *value,
+					     GParamSpec *pspec);
+
+static void browser_canvas_column_drag_data_get (BrowserCanvasItem *citem, GdkDragContext *drag_context,
+						 GtkSelectionData *data, guint info, guint time);
+
+static void browser_canvas_column_extra_event  (BrowserCanvasItem * citem, GdkEventType event_type);
+enum
+{
+	PROP_0,
+	PROP_COLUMN,
+};
+
+struct _BrowserCanvasColumnPrivate
+{
+	GdaMetaTableColumn *column;
+};
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *column_parent_class = NULL;
+
+GType
+browser_canvas_column_get_type (void)
+{
+	static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasColumnClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_column_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasColumn),
+			0,
+			(GInstanceInitFunc) browser_canvas_column_init
+		};		
+
+		type = g_type_register_static (TYPE_BROWSER_CANVAS_TEXT, "BrowserCanvasColumn", &info, 0);
+	}
+
+	return type;
+}
+
+	
+
+static void
+browser_canvas_column_class_init (BrowserCanvasColumnClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (klass);
+
+	column_parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = browser_canvas_column_dispose;
+
+	iclass->drag_data_get = browser_canvas_column_drag_data_get;
+	iclass->extra_event = browser_canvas_column_extra_event;
+
+	/* Properties */
+	object_class->set_property = browser_canvas_column_set_property;
+	object_class->get_property = browser_canvas_column_get_property;
+
+	g_object_class_install_property
+                (object_class, PROP_COLUMN,
+                 g_param_spec_pointer ("column", NULL, NULL, 
+				       (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+}
+
+static void
+browser_canvas_column_init (BrowserCanvasColumn * column)
+{
+	column->priv = g_new0 (BrowserCanvasColumnPrivate, 1);
+	column->priv->column = NULL;
+}
+
+static void
+browser_canvas_column_dispose (GObject   * object)
+{
+	BrowserCanvasColumn *cf;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_COLUMN (object));
+
+	cf = BROWSER_CANVAS_COLUMN (object);
+	if (cf->priv) {
+		if (cf->priv->column)
+			cf->priv->column = NULL;
+		g_free (cf->priv);
+		cf->priv = NULL;
+	}
+
+	/* for the parent class */
+	column_parent_class->dispose (object);
+}
+
+static void 
+browser_canvas_column_set_property (GObject *object,
+				    guint param_id,
+				    const GValue *value,
+				    GParamSpec *pspec)
+{
+	BrowserCanvasColumn *cf = NULL;
+	GdaMetaTableColumn* column = NULL;
+	GString *string = NULL;
+
+	cf = BROWSER_CANVAS_COLUMN (object);
+
+	switch (param_id) {
+	case PROP_COLUMN:
+		column = g_value_get_pointer (value);
+
+		cf->priv->column = column;
+		/* column name */
+		g_object_set (object, "text", column->column_name, NULL);
+		
+		/* attributes setting */
+		string = g_string_new ("");
+		if (column->column_type)
+			g_string_append_printf (string, _("Type: %s"), column->column_type);
+		
+		g_object_set (object, 
+			      "highlight_color", BROWSER_CANVAS_DB_TABLE_COLOR, 
+			      "text_underline", !column->nullok,
+			      "text_bold", column->pkey,
+			      NULL);
+		
+		if (*string->str)
+			g_object_set (object, "tip-text", string->str, NULL);
+		else
+			g_object_set (object, "tip-text", NULL, NULL);
+		g_string_free (string, TRUE);
+		break;
+	}
+}
+
+static void 
+browser_canvas_column_get_property (GObject *object,
+				    guint param_id,
+				    GValue *value,
+				    GParamSpec *pspec)
+{
+	BrowserCanvasColumn *cf;
+
+	cf = BROWSER_CANVAS_COLUMN (object);
+
+	switch (param_id) {
+	case PROP_COLUMN:
+		g_value_set_pointer (value, cf->priv->column);
+		break;
+	}
+}
+
+static void
+browser_canvas_column_extra_event  (BrowserCanvasItem *citem, GdkEventType event_type)
+{
+	if (event_type == GDK_LEAVE_NOTIFY)
+		browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (citem), FALSE);
+}
+
+/**
+ * browser_canvas_column_new
+ * @parent: the parent item, or %NULL
+ * @column: the represented entity's column
+ * @x: the x coordinate of the text
+ * @y: the y coordinate of the text
+ * @...: optional pairs of property names and values, and a terminating %NULL.
+ *
+ * Creates a new #BrowserCanvasColumn object
+ */
+GooCanvasItem*
+browser_canvas_column_new (GooCanvasItem *parent, GdaMetaTableColumn *column,
+			gdouble x, gdouble y, ...)
+{
+	GooCanvasItem *item;
+	BrowserCanvasColumn *goocolumn;
+	const char *first_property;
+	va_list var_args;
+
+	item = g_object_new (TYPE_BROWSER_CANVAS_COLUMN, NULL);
+	goocolumn = (BrowserCanvasColumn*) item;
+
+	if (parent) {
+		goo_canvas_item_add_child (parent, item, -1);
+		g_object_unref (item);
+	}
+
+	va_start (var_args, y);
+	first_property = va_arg (var_args, char*);
+	if (first_property)
+		g_object_set_valist ((GObject*) item, first_property, var_args);
+	va_end (var_args);
+
+	g_object_set (G_OBJECT (item), "column", column, NULL);
+	goo_canvas_item_translate (item, x, y);
+	
+	return item;
+}
+
+
+/**
+ * browser_canvas_column_get_column
+ * @column: a #BrowserCanvasColumn object
+ *
+ * Get the #GdaMetaTableColumn which @column represents
+ *
+ * Returns: the object implementing the #GdaMetaTableColumn interface
+ */
+GdaMetaTableColumn *
+browser_canvas_column_get_column (BrowserCanvasColumn *column)
+{
+	g_return_val_if_fail (IS_BROWSER_CANVAS_COLUMN (column), NULL);
+	g_return_val_if_fail (column->priv, NULL);
+
+	return column->priv->column;
+}
+
+
+/**
+ * browser_canvas_column_get_parent_item
+ * @column: a #BrowserCanvasColumn object
+ *
+ * Get the #BrowserCanvasTable in which @column is
+ *
+ * Returns: the #BrowserCanvasTable in which @column is, or %NULL
+ */
+BrowserCanvasTable *
+browser_canvas_column_get_parent_item (BrowserCanvasColumn *column)
+{
+	GooCanvasItem *ci;
+
+	g_return_val_if_fail (IS_BROWSER_CANVAS_COLUMN (column), NULL);
+	for (ci = goo_canvas_item_get_parent (GOO_CANVAS_ITEM (column));
+	     ci && !IS_BROWSER_CANVAS_TABLE (ci);
+	     ci = goo_canvas_item_get_parent (ci));
+
+	return (BrowserCanvasTable *) ci;
+}
+
+static void
+browser_canvas_column_drag_data_get (BrowserCanvasItem *citem, GdkDragContext *drag_context,
+				     GtkSelectionData *data, guint info, guint time)
+{
+	BrowserCanvasColumn *column;
+	BrowserCanvasTable *ctable;
+	GdaMetaTable *mtable;
+
+	column = BROWSER_CANVAS_COLUMN (citem);
+	ctable = browser_canvas_column_get_parent_item (column);
+	g_object_get (G_OBJECT (ctable), "table", &mtable, NULL);
+	if (!column->priv->column || !mtable)
+		return;
+
+	GdaMetaDbObject *dbo;
+	gchar *str, *tmp1, *tmp2, *tmp3, *tmp4;
+
+	dbo = GDA_META_DB_OBJECT (mtable);
+	tmp1 = gda_rfc1738_encode (dbo->obj_schema);
+	tmp2 = gda_rfc1738_encode (dbo->obj_name);
+	tmp3 = gda_rfc1738_encode (dbo->obj_short_name);
+	tmp4 = gda_rfc1738_encode (column->priv->column->column_name);
+	str = g_strdup_printf ("OBJ_TYPE=tablecolumn;OBJ_SCHEMA=%s;OBJ_NAME=%s;OBJ_SHORT_NAME=%s;COL_NAME=%s",
+			       tmp1, tmp2, tmp3, tmp4);
+	g_free (tmp1);
+	g_free (tmp2);
+	g_free (tmp3);
+	g_free (tmp4);
+	gtk_selection_data_set (data, data->target, 8, str, strlen (str));
+	g_free (str);
+}
diff --git a/tools/browser/canvas/browser-canvas-column.h b/tools/browser/canvas/browser-canvas-column.h
new file mode 100644
index 0000000..b0bc600
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-column.h
@@ -0,0 +1,65 @@
+/* browser-canvas-column.h
+ *
+ * Copyright (C) 2004 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_COLUMN__
+#define __BROWSER_CANVAS_COLUMN__
+
+#include "browser-canvas-text.h"
+#include "browser-canvas-decl.h"
+
+G_BEGIN_DECLS
+
+/*
+ * 
+ * "Drag item" GooCanvas item: a BrowserCanvasItem item which is used to represent
+ * an element being dragged, and destroys itself when the mouse button is released
+ *
+ */
+
+#define TYPE_BROWSER_CANVAS_COLUMN          (browser_canvas_column_get_type())
+#define BROWSER_CANVAS_COLUMN(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_column_get_type(), BrowserCanvasColumn)
+#define BROWSER_CANVAS_COLUMN_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_column_get_type (), BrowserCanvasColumnClass)
+#define IS_BROWSER_CANVAS_COLUMN(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_column_get_type ())
+
+
+/* struct for the object's data */
+struct _BrowserCanvasColumn
+{
+	BrowserCanvasText          object;
+
+	BrowserCanvasColumnPrivate *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasColumnClass
+{
+	BrowserCanvasTextClass     parent_class;
+};
+
+/* generic widget's functions */
+GType                browser_canvas_column_get_type        (void) G_GNUC_CONST;
+GooCanvasItem*       browser_canvas_column_new             (GooCanvasItem *parent, GdaMetaTableColumn *column,
+							  gdouble x, gdouble y, ...);
+GdaMetaTableColumn  *browser_canvas_column_get_column      (BrowserCanvasColumn *column);
+BrowserCanvasTable  *browser_canvas_column_get_parent_item (BrowserCanvasColumn *column);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-db-relations.c b/tools/browser/canvas/browser-canvas-db-relations.c
new file mode 100644
index 0000000..ea50309
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-db-relations.c
@@ -0,0 +1,476 @@
+/* browser-canvas-db-relations.c
+ *
+ * Copyright (C) 2002 - 2007 Vivien Malerba
+ * Copyright (C) 2002 Fernando Martins
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include <glib/gi18n-lib.h>
+#include "browser-canvas-priv.h"
+#include "browser-canvas-db-relations.h"
+#include "browser-canvas-table.h"
+#include "browser-canvas-column.h"
+#include "browser-canvas-fkey.h"
+
+static void browser_canvas_db_relations_class_init (BrowserCanvasDbRelationsClass *class);
+static void browser_canvas_db_relations_init       (BrowserCanvasDbRelations *canvas);
+static void browser_canvas_db_relations_dispose   (GObject *object);
+
+static void browser_canvas_db_relations_set_property (GObject *object,
+						      guint param_id,
+						      const GValue *value,
+						      GParamSpec *pspec);
+static void browser_canvas_db_relations_get_property (GObject *object,
+						      guint param_id,
+						      GValue *value,
+						      GParamSpec *pspec);
+
+/* virtual functions */
+static void       clean_canvas_items  (BrowserCanvas *canvas);
+static GtkWidget *build_context_menu  (BrowserCanvas *canvas);
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *parent_class = NULL;
+
+enum
+{
+        PROP_0,
+	PROP_META_STRUCT
+};
+
+struct _BrowserCanvasDbRelationsPrivate
+{
+	GHashTable       *hash_tables; /* key = GdaMetaTable, value = BrowserCanvasMetaTable (and the reverse) */
+	GHashTable       *hash_fkeys; /* key = GdaMetaTableForeignKey, value = BrowserCanvasFkey */
+	GSList           *all_items; /* list of all the BrowserCanvasItem objects */
+
+	GdaMetaStruct    *mstruct;
+};
+
+GType
+browser_canvas_db_relations_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasDbRelationsClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_db_relations_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasDbRelations),
+			0,
+			(GInstanceInitFunc) browser_canvas_db_relations_init
+		};		
+
+		type = g_type_register_static (TYPE_BROWSER_CANVAS, "BrowserCanvasDbRelations", &info, 0);
+	}
+	return type;
+}
+
+static void
+browser_canvas_db_relations_init (BrowserCanvasDbRelations * canvas)
+{
+	canvas->priv = g_new0 (BrowserCanvasDbRelationsPrivate, 1);
+	canvas->priv->hash_tables = g_hash_table_new (NULL, NULL);
+	canvas->priv->hash_fkeys = g_hash_table_new (NULL, NULL);
+	canvas->priv->mstruct = NULL;
+}
+
+static void
+browser_canvas_db_relations_class_init (BrowserCanvasDbRelationsClass * class)
+{
+	GObjectClass   *object_class = G_OBJECT_CLASS (class);
+	parent_class = g_type_class_peek_parent (class);
+
+	/* BrowserCanvas virtual functions */
+	BROWSER_CANVAS_CLASS (class)->clean_canvas_items = clean_canvas_items;
+	BROWSER_CANVAS_CLASS (class)->build_context_menu = build_context_menu;
+	object_class->dispose = browser_canvas_db_relations_dispose;
+
+	/* properties */
+	object_class->set_property = browser_canvas_db_relations_set_property;
+        object_class->get_property = browser_canvas_db_relations_get_property;
+	g_object_class_install_property (object_class, PROP_META_STRUCT,
+                                         g_param_spec_object ("meta-struct", "GdaMetaStruct", NULL,
+							      GDA_TYPE_META_STRUCT,
+							      G_PARAM_READABLE | G_PARAM_WRITABLE));
+}
+
+static void
+browser_canvas_db_relations_dispose (GObject *object)
+{
+	BrowserCanvasDbRelations *canvas;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_DB_RELATIONS (object));
+
+	canvas = BROWSER_CANVAS_DB_RELATIONS (object);
+
+	if (canvas->priv) {
+		clean_canvas_items (BROWSER_CANVAS (canvas));
+		if (canvas->priv->mstruct) {
+			g_object_unref (canvas->priv->mstruct);
+			canvas->priv->mstruct = NULL;
+		}
+
+		g_hash_table_destroy (canvas->priv->hash_tables);
+		g_hash_table_destroy (canvas->priv->hash_fkeys);
+
+		g_free (canvas->priv);
+		canvas->priv = NULL;
+	}
+
+	/* for the parent class */
+	parent_class->dispose (object);
+}
+
+static void
+browser_canvas_db_relations_set_property (GObject *object,
+					guint param_id,
+					const GValue *value,
+					GParamSpec *pspec)
+{
+	BrowserCanvasDbRelations *canvas;
+
+        canvas = BROWSER_CANVAS_DB_RELATIONS (object);
+        if (canvas->priv) {
+                switch (param_id) {
+		case PROP_META_STRUCT:
+			if (canvas->priv->mstruct)
+				clean_canvas_items (BROWSER_CANVAS (canvas));
+
+			canvas->priv->mstruct = g_value_get_object (value);
+                        if (canvas->priv->mstruct) 
+                                g_object_ref (canvas->priv->mstruct);
+			break;
+		}
+	}
+}
+
+static void
+browser_canvas_db_relations_get_property (GObject *object,
+					guint param_id,
+					GValue *value,
+					GParamSpec *pspec)
+{
+	BrowserCanvasDbRelations *canvas;
+
+        canvas = BROWSER_CANVAS_DB_RELATIONS (object);
+        if (canvas->priv) {
+                switch (param_id) {
+		case PROP_META_STRUCT:
+			g_value_set_object (value, canvas->priv->mstruct);
+			break;
+		}
+	}
+}
+
+/**
+ * browser_canvas_db_relations_new
+ * @mstruct: a #GdaMetaStruct object, or %NULL
+ *
+ * Creates a new canvas widget to display the relations between the database's tables.
+ *
+ * After the #BrowserCanvasDbRelations has been created, it is possible to display tables
+ * using browser_canvas_db_relations_add_table().
+ *
+ * Returns: a new #GtkWidget widget
+ */
+GtkWidget *
+browser_canvas_db_relations_new (GdaMetaStruct *mstruct)
+{
+	g_return_val_if_fail (!mstruct || GDA_IS_META_STRUCT (mstruct), NULL);
+        return GTK_WIDGET (g_object_new (TYPE_BROWSER_CANVAS_DB_RELATIONS, "meta-struct", mstruct, NULL));
+}
+
+
+
+static void
+clean_canvas_items (BrowserCanvas *canvas)
+{
+	BrowserCanvasDbRelations *dbrel = BROWSER_CANVAS_DB_RELATIONS (canvas);
+
+	/* clean memory */
+	g_hash_table_destroy (dbrel->priv->hash_tables);
+	g_hash_table_destroy (dbrel->priv->hash_fkeys);
+	dbrel->priv->hash_tables = g_hash_table_new (NULL, NULL);
+	dbrel->priv->hash_fkeys = g_hash_table_new (NULL, NULL);
+	if (dbrel->priv->all_items) {
+		g_slist_free (dbrel->priv->all_items);
+		dbrel->priv->all_items = NULL;
+	}
+}
+
+static GtkWidget *canvas_entity_popup_func (BrowserCanvasTable *ce);
+
+static void popup_func_delete_cb (GtkMenuItem *mitem, BrowserCanvasTable *ce);
+static void popup_func_add_depend_cb (GtkMenuItem *mitem, BrowserCanvasTable *ce);
+static GtkWidget *
+canvas_entity_popup_func (BrowserCanvasTable *ce)
+{
+	GtkWidget *menu, *entry;
+
+	menu = gtk_menu_new ();
+	entry = gtk_menu_item_new_with_label (_("Remove"));
+	g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_delete_cb), ce);
+	gtk_menu_append (GTK_MENU (menu), entry);
+	gtk_widget_show (entry);
+	entry = gtk_menu_item_new_with_label (_("Add referenced tables"));
+	g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_add_depend_cb), ce);
+	gtk_menu_append (GTK_MENU (menu), entry);
+	gtk_widget_show (entry);
+
+	return menu;
+}
+
+static void
+popup_func_delete_cb (GtkMenuItem *mitem, BrowserCanvasTable *ce)
+{
+	GdaMetaTable *mtable;
+	BrowserCanvasDbRelations *dbrel;
+
+	dbrel = BROWSER_CANVAS_DB_RELATIONS (browser_canvas_item_get_canvas (BROWSER_CANVAS_ITEM (ce)));
+
+	mtable = g_hash_table_lookup (dbrel->priv->hash_tables, ce);
+	g_hash_table_remove (dbrel->priv->hash_tables, ce);
+	g_hash_table_remove (dbrel->priv->hash_tables, mtable);
+	goo_canvas_item_remove (GOO_CANVAS_ITEM (ce));
+	dbrel->priv->all_items = g_slist_remove (dbrel->priv->all_items, ce);
+}
+
+static void
+popup_func_add_depend_cb (GtkMenuItem *mitem, BrowserCanvasTable *ce)
+{
+	BrowserCanvasDbRelations *dbrel;
+	GdaMetaDbObject *dbo;
+
+	dbrel = BROWSER_CANVAS_DB_RELATIONS (browser_canvas_item_get_canvas (BROWSER_CANVAS_ITEM (ce)));
+	dbo = g_hash_table_lookup (dbrel->priv->hash_tables, ce);
+	if (!dbo || (dbo->obj_type != GDA_META_DB_TABLE))
+		return;
+
+	if (!dbrel->priv->mstruct)
+		return;
+	
+	GdaMetaTable *mtable = GDA_META_TABLE (dbo);
+	GSList *list;
+	GooCanvasBounds bounds;
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ce), &bounds);
+	bounds.y1 = bounds.y2 + 35.;
+	bounds.x2 = bounds.x1 - 20.;
+
+	for (list = mtable->fk_list; list; list = list->next) {
+		GdaMetaTableForeignKey *fk = GDA_META_TABLE_FOREIGN_KEY (list->data);
+		if (fk->depend_on->obj_type != GDA_META_DB_TABLE)
+			continue;
+		if (g_hash_table_lookup (dbrel->priv->hash_tables, fk->depend_on))
+			continue;
+
+		BrowserCanvasTable *new_item;
+		GValue *v1, *v2, *v3;
+		g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), fk->depend_on->obj_catalog);
+		g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), fk->depend_on->obj_schema);
+		g_value_set_string ((v3 = gda_value_new (G_TYPE_STRING)), fk->depend_on->obj_name);
+		new_item = browser_canvas_db_relations_add_table (dbrel, v1, v2, v3);
+		gda_value_free (v1);
+		gda_value_free (v2);
+		gda_value_free (v3);
+								       
+		goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (new_item), &bounds);
+	}
+}
+
+static void popup_add_table_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *canvas);
+static GtkWidget *
+build_context_menu (BrowserCanvas *canvas)
+{
+	GtkWidget *menu, *mitem, *submenu, *submitem;
+	GSList *dbolist, *list;
+	BrowserCanvasDbRelations *dbrel = BROWSER_CANVAS_DB_RELATIONS (canvas);
+
+	if (!dbrel->priv->mstruct)
+		return NULL;
+
+	menu = gtk_menu_new ();
+	submitem = gtk_menu_item_new_with_label (_("Add table"));
+	gtk_widget_show (submitem);
+	gtk_menu_append (menu, submitem);
+	submenu = NULL;
+
+	/* build list of tables */
+	dbolist = gda_meta_struct_get_all_db_objects (dbrel->priv->mstruct);
+	for (list = dbolist; list; list = list->next) {
+		GdaMetaDbObject *dbo = GDA_META_DB_OBJECT (list->data);
+		GdaMetaTable *mtable;
+
+		if (dbo->obj_type != GDA_META_DB_TABLE)
+			continue;
+		
+		mtable = GDA_META_TABLE (dbo);
+		if (mtable && g_hash_table_lookup (dbrel->priv->hash_tables, mtable))
+			continue; /* skip that table as it is already present in the canvas */
+
+		if (!submenu) {
+			submenu = gtk_menu_new ();
+			gtk_menu_item_set_submenu (GTK_MENU_ITEM (submitem), submenu);
+			gtk_widget_show (submenu);
+		}
+		
+		mitem = gtk_menu_item_new_with_label (dbo->obj_name);
+		gtk_widget_show (mitem);
+		gtk_menu_append (submenu, mitem);
+
+		GValue *tcatalog, *tschema, *tname;
+		g_value_set_string ((tcatalog = gda_value_new (G_TYPE_STRING)), dbo->obj_catalog);
+		g_value_set_string ((tschema = gda_value_new (G_TYPE_STRING)), dbo->obj_schema);
+		g_value_set_string ((tname = gda_value_new (G_TYPE_STRING)), dbo->obj_name);
+		g_object_set_data_full (G_OBJECT (mitem), "tcat", tcatalog, (GDestroyNotify) gda_value_free);
+		g_object_set_data_full (G_OBJECT (mitem), "tschema",  tschema, (GDestroyNotify) gda_value_free);
+		g_object_set_data_full (G_OBJECT (mitem), "tname", tname, (GDestroyNotify) gda_value_free);
+		g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_add_table_cb), canvas);
+	}
+	g_slist_free (dbolist);
+
+	/* sub menu is incensitive if there are no more tables left to add */
+	gtk_widget_set_sensitive (submitem, submenu ? TRUE : FALSE);
+
+	return menu;
+}
+
+static void
+popup_add_table_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrel)
+{
+	GdaMetaTable *mtable;
+	GValue *table_catalog;
+	GValue *table_schema;
+	GValue *table_name;
+
+	table_catalog = g_object_get_data (G_OBJECT (mitem), "tcat");
+	table_schema = g_object_get_data (G_OBJECT (mitem), "tschema");
+	table_name = g_object_get_data (G_OBJECT (mitem), "tname");
+
+	/*g_print ("Add %s.%s.%s\n", g_value_get_string (table_catalog), 
+	  g_value_get_string (table_schema), g_value_get_string (table_name));*/
+	mtable = (GdaMetaTable*) gda_meta_struct_complement (dbrel->priv->mstruct, GDA_META_DB_TABLE,
+							     table_catalog, table_schema, table_name, NULL);
+	if (mtable) {
+		browser_canvas_db_relations_add_table (dbrel, table_catalog, table_schema, table_name);
+	}
+	else
+		g_print ("Unknown...\n");
+}
+
+/**
+ * browser_canvas_db_relations_get_table_item
+ * @canvas:
+ * @table:
+ *
+ * Returns:
+ */
+BrowserCanvasTable *
+browser_canvas_db_relations_get_table_item  (BrowserCanvasDbRelations *canvas, GdaMetaTable *table)
+{
+	BrowserCanvasTable *table_item;
+	g_return_val_if_fail (IS_BROWSER_CANVAS_DB_RELATIONS (canvas), NULL);
+	g_return_val_if_fail (canvas->priv, NULL);
+
+	table_item = g_hash_table_lookup (canvas->priv->hash_tables, table);
+	return BROWSER_CANVAS_TABLE (table_item);
+}
+
+/**
+ * browser_canvas_db_relations_add_table
+ * @canvas: a #BrowserCanvasDbRelations canvas
+ * @table_catalog: the catalog in which the table is, or %NULL
+ * @table_schema: the schema in which the table is, or %NULL
+ * @table_name: the table's name
+ *
+ * Add a table to @canvas.
+ *
+ * Returns: the corresponding canvas item, or %NULL if the table was not found.
+ */
+BrowserCanvasTable *
+browser_canvas_db_relations_add_table  (BrowserCanvasDbRelations *canvas, 
+					const GValue *table_catalog, const GValue *table_schema,
+					const GValue *table_name)
+{
+	g_return_val_if_fail (IS_BROWSER_CANVAS_DB_RELATIONS (canvas), NULL);
+	g_return_val_if_fail (canvas->priv, NULL);
+
+	GdaMetaTable *mtable;
+	GooCanvas *goocanvas;
+
+	if (!canvas->priv->mstruct)
+		return NULL;
+
+	goocanvas = BROWSER_CANVAS (canvas)->priv->goocanvas;
+	mtable = (GdaMetaTable *) gda_meta_struct_complement (canvas->priv->mstruct, GDA_META_DB_TABLE,
+							      table_catalog, table_schema, table_name, NULL);
+	if (mtable) {
+		gdouble x = 0, y = 0;
+		GooCanvasItem *table_item;
+
+		table_item = browser_canvas_table_new (goo_canvas_get_root_item (goocanvas), 
+						       mtable, x, y, NULL);
+		g_hash_table_insert (canvas->priv->hash_tables, mtable, table_item);
+		g_hash_table_insert (canvas->priv->hash_tables,table_item, mtable);
+		canvas->priv->all_items = g_slist_prepend (canvas->priv->all_items, table_item);
+		g_object_set (G_OBJECT (table_item), 
+			      "popup_menu_func", canvas_entity_popup_func, NULL);
+		browser_canvas_declare_item (BROWSER_CANVAS (canvas),
+					     BROWSER_CANVAS_ITEM (table_item));
+
+		/* if there are some FK links, then also add them */
+		GSList *list;
+		for (list = mtable->fk_list; list; list = list->next) {
+			GooCanvasItem *ref_table_item;
+			GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+			ref_table_item = g_hash_table_lookup (canvas->priv->hash_tables, fk->depend_on);
+			if (ref_table_item) {
+				GooCanvasItem *fk_item;
+				fk_item = browser_canvas_fkey_new (goo_canvas_get_root_item (goocanvas), fk, NULL);
+				browser_canvas_declare_item (BROWSER_CANVAS (canvas),
+							     BROWSER_CANVAS_ITEM (fk_item));
+
+				g_hash_table_insert (canvas->priv->hash_fkeys, fk, fk_item);
+			}
+		}
+		for (list = mtable->reverse_fk_list; list; list = list->next) {
+			GooCanvasItem *ref_table_item;
+			GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+			ref_table_item = g_hash_table_lookup (canvas->priv->hash_tables, fk->meta_table);
+			if (ref_table_item) {
+				GooCanvasItem *fk_item;
+				fk_item = browser_canvas_fkey_new (goo_canvas_get_root_item (goocanvas), fk, NULL);
+				browser_canvas_declare_item (BROWSER_CANVAS (canvas),
+							     BROWSER_CANVAS_ITEM (fk_item));
+
+				g_hash_table_insert (canvas->priv->hash_fkeys, fk, fk_item);
+			}
+		}
+
+		return BROWSER_CANVAS_TABLE (table_item);
+	}
+	else
+		return NULL;
+}
+ 
diff --git a/tools/browser/canvas/browser-canvas-db-relations.h b/tools/browser/canvas/browser-canvas-db-relations.h
new file mode 100644
index 0000000..bcbefc8
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-db-relations.h
@@ -0,0 +1,66 @@
+/* browser-canvas-db-relations.h
+ *
+ * Copyright (C) 2004 - 2009 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_DB_RELATIONS__
+#define __BROWSER_CANVAS_DB_RELATIONS__
+
+#include "browser-canvas.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_BROWSER_CANVAS_DB_RELATIONS          (browser_canvas_db_relations_get_type())
+#define BROWSER_CANVAS_DB_RELATIONS(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_db_relations_get_type(), BrowserCanvasDbRelations)
+#define BROWSER_CANVAS_DB_RELATIONS_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_db_relations_get_type (), BrowserCanvasDbRelationsClass)
+#define IS_BROWSER_CANVAS_DB_RELATIONS(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_db_relations_get_type ())
+
+
+typedef struct _BrowserCanvasDbRelations        BrowserCanvasDbRelations;
+typedef struct _BrowserCanvasDbRelationsClass   BrowserCanvasDbRelationsClass;
+typedef struct _BrowserCanvasDbRelationsPrivate BrowserCanvasDbRelationsPrivate;
+
+
+/* struct for the object's data */
+struct _BrowserCanvasDbRelations
+{
+	BrowserCanvas                       widget;
+
+	BrowserCanvasDbRelationsPrivate    *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasDbRelationsClass
+{
+	BrowserCanvasClass                  parent_class;
+};
+
+/* generic widget's functions */
+GType            browser_canvas_db_relations_get_type        (void) G_GNUC_CONST;
+
+GtkWidget       *browser_canvas_db_relations_new             (GdaMetaStruct *mstruct);
+
+BrowserCanvasTable *browser_canvas_db_relations_get_table_item  (BrowserCanvasDbRelations *canvas, GdaMetaTable *table);
+BrowserCanvasTable *browser_canvas_db_relations_add_table  (BrowserCanvasDbRelations *canvas, 
+							    const GValue *table_catalog,
+							    const GValue *table_schema,
+							    const GValue *table_name);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-decl.h b/tools/browser/canvas/browser-canvas-decl.h
new file mode 100644
index 0000000..9b506d8
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-decl.h
@@ -0,0 +1,46 @@
+/* browser-canvas-decl.h
+ *
+ * Copyright (C) 2009 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_DECL_H_
+#define __BROWSER_CANVAS_DECL_H_
+
+typedef struct _BrowserCanvas        BrowserCanvas;
+typedef struct _BrowserCanvasClass   BrowserCanvasClass;
+typedef struct _BrowserCanvasPrivate BrowserCanvasPrivate;
+
+typedef struct _BrowserCanvasItem        BrowserCanvasItem;
+typedef struct _BrowserCanvasItemClass   BrowserCanvasItemClass;
+typedef struct _BrowserCanvasItemPrivate BrowserCanvasItemPrivate;
+
+typedef struct _BrowserCanvasTable        BrowserCanvasTable;
+typedef struct _BrowserCanvasTableClass   BrowserCanvasTableClass;
+typedef struct _BrowserCanvasTablePrivate BrowserCanvasTablePrivate;
+
+typedef struct _BrowserCanvasColumn        BrowserCanvasColumn;
+typedef struct _BrowserCanvasColumnClass   BrowserCanvasColumnClass;
+typedef struct _BrowserCanvasColumnPrivate BrowserCanvasColumnPrivate;
+
+#define BROWSER_CANVAS_ENTITY_COLOR      "yellow"
+#define BROWSER_CANVAS_DB_TABLE_COLOR    "#b9b9b9"
+
+#define BROWSER_CANVAS_OBJ_BG_COLOR      "#f8f8f8"
+#define BROWSER_CANVAS_FONT              "Sans 10"
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-fkey.c b/tools/browser/canvas/browser-canvas-fkey.c
new file mode 100644
index 0000000..7e06257
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-fkey.c
@@ -0,0 +1,477 @@
+/* browser-canvas-fkey.c
+ *
+ * Copyright (C) 2004 - 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <gtk/gtk.h>
+#include <math.h>
+#include <libgda/libgda.h>
+#include <glib/gi18n-lib.h>
+#include "browser-canvas.h"
+#include "browser-canvas-fkey.h"
+#include "browser-canvas-table.h"
+#include "browser-canvas-text.h"
+#include "browser-canvas-utility.h"
+#include "browser-canvas-db-relations.h"
+
+static void browser_canvas_fkey_class_init (BrowserCanvasFkeyClass * class);
+static void browser_canvas_fkey_init       (BrowserCanvasFkey * cc);
+static void browser_canvas_fkey_dispose    (GObject *object);
+static void browser_canvas_fkey_finalize   (GObject *object);
+
+static void browser_canvas_fkey_set_property (GObject *object,
+					    guint param_id,
+					    const GValue *value,
+					    GParamSpec *pspec);
+static void browser_canvas_fkey_get_property (GObject *object,
+					    guint param_id,
+					    GValue *value,
+					    GParamSpec *pspec);
+
+static void browser_canvas_fkey_get_edge_nodes (BrowserCanvasItem *citem, 
+					      BrowserCanvasItem **from, BrowserCanvasItem **to);
+
+static void clean_items (BrowserCanvasFkey *cc);
+static void create_items (BrowserCanvasFkey *cc);
+static void update_items (BrowserCanvasFkey *cc);
+
+enum
+{
+	PROP_0,
+	PROP_FK_CONSTRAINT
+};
+
+struct _BrowserCanvasFkeyPrivate
+{
+	GdaMetaTableForeignKey *fk;
+	BrowserCanvasTable     *fk_table_item;
+	BrowserCanvasTable     *ref_pk_table_item;
+	GSList                 *shapes; /* list of BrowserCanvasCanvasShape structures */
+};
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *parent_class = NULL;
+
+GType
+browser_canvas_fkey_get_type (void)
+{
+	static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasFkeyClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_fkey_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasFkey),
+			0,
+			(GInstanceInitFunc) browser_canvas_fkey_init
+		};		
+
+		type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasFkey", &info, 0);
+	}
+
+	return type;
+}	
+
+static void
+browser_canvas_fkey_class_init (BrowserCanvasFkeyClass * class)
+{
+	GObjectClass   *object_class = G_OBJECT_CLASS (class);
+
+	parent_class = g_type_class_peek_parent (class);
+
+	BROWSER_CANVAS_ITEM_CLASS (class)->get_edge_nodes = browser_canvas_fkey_get_edge_nodes;
+
+	object_class->dispose = browser_canvas_fkey_dispose;
+	object_class->finalize = browser_canvas_fkey_finalize;
+
+	/* Properties */
+	object_class->set_property = browser_canvas_fkey_set_property;
+	object_class->get_property = browser_canvas_fkey_get_property;
+	g_object_class_install_property (object_class, PROP_FK_CONSTRAINT,
+					 g_param_spec_pointer ("fk_constraint", "FK constraint", 
+							       NULL, 
+							       G_PARAM_WRITABLE));
+       
+}
+
+static void
+browser_canvas_fkey_init (BrowserCanvasFkey *cc)
+{
+	cc->priv = g_new0 (BrowserCanvasFkeyPrivate, 1);
+	cc->priv->fk = NULL;
+	cc->priv->fk_table_item = NULL;
+	cc->priv->ref_pk_table_item = NULL;
+	cc->priv->shapes = NULL;
+}
+
+static void
+browser_canvas_fkey_dispose (GObject *object)
+{
+	BrowserCanvasFkey *cc;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_FKEY (object));
+
+	cc = BROWSER_CANVAS_FKEY (object);
+
+	clean_items (cc);
+	cc->priv->fk = NULL;
+	cc->priv->fk_table_item = NULL;
+	cc->priv->ref_pk_table_item = NULL;
+
+	/* for the parent class */
+	parent_class->dispose (object);
+}
+
+
+static void
+browser_canvas_fkey_finalize (GObject *object)
+{
+	BrowserCanvasFkey *cc;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_FKEY (object));
+
+	cc = BROWSER_CANVAS_FKEY (object);
+	if (cc->priv) {
+		g_slist_free (cc->priv->shapes);
+		g_free (cc->priv);
+		cc->priv = NULL;
+	}
+
+	/* for the parent class */
+	parent_class->finalize (object);
+}
+
+static void 
+browser_canvas_fkey_set_property (GObject *object,
+				  guint param_id,
+				  const GValue *value,
+				  GParamSpec *pspec)
+{
+	BrowserCanvasFkey *cc;
+
+	cc = BROWSER_CANVAS_FKEY (object);
+
+	switch (param_id) {
+	case PROP_FK_CONSTRAINT:
+		if (cc->priv->fk != g_value_get_pointer (value)) {
+			cc->priv->fk = g_value_get_pointer (value);
+			clean_items (cc);
+			create_items (cc);
+		}
+		break;
+	}
+}
+
+static void 
+browser_canvas_fkey_get_property (GObject *object,
+				  guint param_id,
+				  GValue *value,
+				  GParamSpec *pspec)
+{
+	BrowserCanvasFkey *cc;
+
+	cc = BROWSER_CANVAS_FKEY (object);
+
+	switch (param_id) {
+	default:
+		g_warning ("No such property!");
+		break;
+	}
+}
+
+static void
+browser_canvas_fkey_get_edge_nodes (BrowserCanvasItem *citem, 
+					  BrowserCanvasItem **from, BrowserCanvasItem **to)
+{
+	BrowserCanvasFkey *cc;
+
+	cc = BROWSER_CANVAS_FKEY (citem);
+
+	if (from)
+		*from = (BrowserCanvasItem*) cc->priv->fk_table_item;
+	if (to)
+		*to = (BrowserCanvasItem*) cc->priv->ref_pk_table_item;
+}
+
+static gboolean single_item_enter_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+						   GdkEventCrossing *event, BrowserCanvasFkey *cc);
+static gboolean single_item_leave_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+						   GdkEventCrossing *event, BrowserCanvasFkey *cc);
+static gboolean single_item_button_press_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+						   GdkEventButton *event, BrowserCanvasFkey *cc);
+static void table_item_moved_cb (GooCanvasItem *table, BrowserCanvasFkey *cc);
+static void table_destroy_cb (BrowserCanvasTable *table, BrowserCanvasFkey *cc);
+
+/* 
+ * destroy any existing GooCanvasItem objects 
+ */
+static void 
+clean_items (BrowserCanvasFkey *cc)
+{
+	if (cc->priv->fk_table_item) {
+		g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->fk_table_item),
+						      G_CALLBACK (table_item_moved_cb), cc);
+		g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->fk_table_item),
+                                                      G_CALLBACK (table_destroy_cb), cc);
+		cc->priv->fk_table_item = NULL;
+	}
+
+	if (cc->priv->ref_pk_table_item) {
+		g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->ref_pk_table_item),
+						      G_CALLBACK (table_item_moved_cb), cc);
+		g_signal_handlers_disconnect_by_func (G_OBJECT (cc->priv->ref_pk_table_item),
+						      G_CALLBACK (table_destroy_cb), cc);
+		cc->priv->ref_pk_table_item = NULL;
+	}
+	
+	/* remove all the GooCanvasItem objects */
+	browser_canvas_canvas_shapes_remove_all (cc->priv->shapes);
+	cc->priv->shapes = NULL;
+}
+
+/*
+ * create new GooCanvasItem objects
+ */
+static void 
+create_items (BrowserCanvasFkey *cc)
+{
+	GSList *list, *canvas_shapes;
+	BrowserCanvasTable *table_item;
+	BrowserCanvas *canvas =  g_object_get_data (G_OBJECT (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (cc))),
+						    "browsercanvas");
+
+	g_assert (cc->priv->fk);
+
+	/* Analyse FK constraint */
+	table_item = browser_canvas_db_relations_get_table_item (BROWSER_CANVAS_DB_RELATIONS (canvas),
+								 GDA_META_TABLE (cc->priv->fk->meta_table));
+	cc->priv->fk_table_item = table_item;
+	g_return_if_fail (table_item);
+	g_signal_connect (G_OBJECT (table_item), "moving",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "moved",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "shifted",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "destroy",
+			  G_CALLBACK (table_destroy_cb), cc);
+
+	table_item = browser_canvas_db_relations_get_table_item (BROWSER_CANVAS_DB_RELATIONS (canvas),
+								 GDA_META_TABLE (cc->priv->fk->depend_on));
+	cc->priv->ref_pk_table_item = table_item;
+	g_return_if_fail (table_item);
+	g_signal_connect (G_OBJECT (table_item), "moving",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "moved",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "shifted",
+			  G_CALLBACK (table_item_moved_cb), cc);
+	g_signal_connect (G_OBJECT (table_item), "destroy",
+			  G_CALLBACK (table_destroy_cb), cc);
+
+	/* actual line(s) */
+	g_assert (!cc->priv->shapes);
+	canvas_shapes = browser_canvas_util_compute_anchor_shapes (GOO_CANVAS_ITEM (cc), NULL,
+								   cc->priv->fk_table_item, 
+								   cc->priv->ref_pk_table_item, 
+								   /*MAX (cc->priv->fk->cols_nb, 1)*/ 1,
+								   0, TRUE);
+
+	cc->priv->shapes = browser_canvas_canvas_shapes_remove_obsolete_shapes (canvas_shapes);
+	for (list = canvas_shapes; list; list = list->next) {
+		GooCanvasItem *item = BROWSER_CANVAS_CANVAS_SHAPE (list->data)->item;
+		gchar *color = "black";
+		g_object_set (G_OBJECT (item), 
+			      "stroke-color", color,
+			      NULL);
+		
+		if (G_OBJECT_TYPE (item) == GOO_TYPE_CANVAS_POLYLINE) {
+			g_object_set (G_OBJECT (item), 
+				      "start-arrow", TRUE,
+				      "arrow-tip-length", 4.,
+				      "arrow-length", 5.,
+				      "arrow-width", 4.,
+				      NULL);
+		}
+		else if (G_OBJECT_TYPE (item) == GOO_TYPE_CANVAS_ELLIPSE)
+			g_object_set (G_OBJECT (item), 
+				      "fill-color", color,
+				      NULL);
+
+		g_object_set_data (G_OBJECT (item), "fkcons", cc->priv->fk);
+		g_signal_connect (G_OBJECT (item), "enter-notify-event", 
+				  G_CALLBACK (single_item_enter_notify_event_cb), cc);
+		g_signal_connect (G_OBJECT (item), "leave-notify-event", 
+				  G_CALLBACK (single_item_leave_notify_event_cb), cc);
+		g_signal_connect (G_OBJECT (item), "button-press-event",
+				  G_CALLBACK (single_item_button_press_event_cb), cc);
+		
+	}
+}
+
+/*
+ * update GooCanvasItem objects
+ */
+static void 
+update_items (BrowserCanvasFkey *cc)
+{
+	cc->priv->shapes = browser_canvas_util_compute_anchor_shapes (GOO_CANVAS_ITEM (cc), cc->priv->shapes,
+								      cc->priv->fk_table_item, 
+								      cc->priv->ref_pk_table_item, 
+								      /*MAX (cc->priv->fk->cols_nb, 1)*/ 1,
+								      0, TRUE);
+	cc->priv->shapes = browser_canvas_canvas_shapes_remove_obsolete_shapes (cc->priv->shapes);
+}
+
+static void popup_delete_cb (GtkMenuItem *mitem, BrowserCanvasFkey *cc);
+
+/*
+ * item is for a single FK constraint
+ */
+static gboolean
+single_item_enter_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+				   GdkEventCrossing *event, BrowserCanvasFkey *cc)
+{
+	gint i;
+
+	for (i = 0; i < cc->priv->fk->cols_nb; i++) {
+		GdaMetaTableColumn *tcol;
+		BrowserCanvasColumn *column;
+
+		/* fk column */
+		tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->meta_table)->columns, 
+					 cc->priv->fk->fk_cols_array[i] - 1);
+
+		column = browser_canvas_table_get_column_item (cc->priv->fk_table_item, tcol);
+		browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), TRUE);
+		
+		/* ref pk column */
+		tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->depend_on)->columns, 
+					 cc->priv->fk->ref_pk_cols_array[i] - 1);
+
+		column = browser_canvas_table_get_column_item (cc->priv->ref_pk_table_item, tcol);
+		browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), TRUE);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+single_item_leave_notify_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+				   GdkEventCrossing *event, BrowserCanvasFkey *cc)
+{
+	gint i;
+
+	for (i = 0; i < cc->priv->fk->cols_nb; i++) {
+		GdaMetaTableColumn *tcol;
+		BrowserCanvasColumn *column;
+
+		/* fk column */
+		tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->meta_table)->columns, 
+					 cc->priv->fk->fk_cols_array[i] - 1);
+
+		column = browser_canvas_table_get_column_item (cc->priv->fk_table_item, tcol);
+		browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), FALSE);
+		
+		/* ref pk column */
+		tcol = g_slist_nth_data (GDA_META_TABLE (cc->priv->fk->depend_on)->columns, 
+					 cc->priv->fk->ref_pk_cols_array[i] - 1);
+
+		column = browser_canvas_table_get_column_item (cc->priv->ref_pk_table_item, tcol);
+		browser_canvas_text_set_highlight (BROWSER_CANVAS_TEXT (column), FALSE);
+	}
+
+	return FALSE;
+}
+
+static gboolean
+single_item_button_press_event_cb (GooCanvasItem *ci, GooCanvasItem *target_item,
+				   GdkEventButton *event, BrowserCanvasFkey *cc)
+{
+	GdaMetaTableForeignKey *fkcons = g_object_get_data (G_OBJECT (ci), "fkcons");
+	GtkWidget *menu, *entry;
+
+	menu = gtk_menu_new ();
+	entry = gtk_menu_item_new_with_label (_("Remove"));
+	g_object_set_data (G_OBJECT (entry), "fkcons", fkcons);
+	g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_delete_cb), cc);
+	gtk_menu_append (GTK_MENU (menu), entry);
+	gtk_widget_show (entry);
+	gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+			NULL, NULL, ((GdkEventButton *)event)->button,
+			((GdkEventButton *)event)->time);
+	return TRUE;
+}
+
+
+static void
+popup_delete_cb (GtkMenuItem *mitem, BrowserCanvasFkey *cc)
+{
+	TO_IMPLEMENT;
+}
+
+static void
+table_item_moved_cb (GooCanvasItem *table, BrowserCanvasFkey *cc)
+{
+	update_items (cc);
+}
+
+static void
+table_destroy_cb (BrowserCanvasTable *table, BrowserCanvasFkey *cc)
+{
+        goo_canvas_item_remove (GOO_CANVAS_ITEM (cc));
+}
+
+/**
+ * browser_canvas_fkey_new
+ * @parent: the parent item, or NULL. 
+ * @fkcons: the #GdaMetaTableForeignKey to represent
+ * @...: optional pairs of property names and values, and a terminating NULL.
+ *
+ * Creates a new canvas item to represent the @fkcons FK constraint
+ *
+ * Returns: a new #GooCanvasItem object
+ */
+GooCanvasItem *
+browser_canvas_fkey_new (GooCanvasItem *parent, GdaMetaTableForeignKey *fkcons, ...)
+{
+	GooCanvasItem *item;
+	const char *first_property;
+	va_list var_args;
+		
+	item = g_object_new (TYPE_BROWSER_CANVAS_FKEY, NULL);
+
+	if (parent) {
+		goo_canvas_item_add_child (parent, item, -1);
+		g_object_unref (item);
+	}
+
+	g_object_set (item, "fk_constraint", fkcons, NULL);
+
+	va_start (var_args, fkcons);
+	first_property = va_arg (var_args, char*);
+	if (first_property)
+		g_object_set_valist ((GObject*) item, first_property, var_args);
+	va_end (var_args);
+
+	return item;
+}
diff --git a/tools/browser/canvas/browser-canvas-fkey.h b/tools/browser/canvas/browser-canvas-fkey.h
new file mode 100644
index 0000000..e63a901
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-fkey.h
@@ -0,0 +1,64 @@
+/* browser-canvas-fkey.h
+ *
+ * Copyright (C) 2005 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_FKEY__
+#define __BROWSER_CANVAS_FKEY__
+
+#include "browser-canvas-item.h"
+
+G_BEGIN_DECLS
+
+/*
+ * 
+ * "Drag item" GooCanvas item: a BrowserCanvasItem item which is used to represent
+ * an element being dragged, and destroys itself when the mouse button is released
+ *
+ */
+
+#define TYPE_BROWSER_CANVAS_FKEY          (browser_canvas_fkey_get_type())
+#define BROWSER_CANVAS_FKEY(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_fkey_get_type(), BrowserCanvasFkey)
+#define BROWSER_CANVAS_FKEY_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_fkey_get_type (), BrowserCanvasFkeyClass)
+#define IS_BROWSER_CANVAS_FKEY(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_fkey_get_type ())
+
+
+typedef struct _BrowserCanvasFkey        BrowserCanvasFkey;
+typedef struct _BrowserCanvasFkeyClass   BrowserCanvasFkeyClass;
+typedef struct _BrowserCanvasFkeyPrivate BrowserCanvasFkeyPrivate;
+
+
+/* struct for the object's data */
+struct _BrowserCanvasFkey
+{
+	BrowserCanvasItem         object;
+	BrowserCanvasFkeyPrivate *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasFkeyClass
+{
+	BrowserCanvasItemClass    parent_class;
+};
+
+GType          browser_canvas_fkey_get_type       (void) G_GNUC_CONST;
+GooCanvasItem *browser_canvas_fkey_new            (GooCanvasItem *parent, GdaMetaTableForeignKey *fk, ...);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-item.c b/tools/browser/canvas/browser-canvas-item.c
new file mode 100644
index 0000000..f57412c
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-item.c
@@ -0,0 +1,405 @@
+/* browser-canvas-item.c
+ *
+ * Copyright (C) 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "browser-canvas-item.h"
+#include "../dnd.h"
+
+static void browser_canvas_item_class_init (BrowserCanvasItemClass * class);
+static void browser_canvas_item_init       (BrowserCanvasItem * item);
+static void browser_canvas_item_dispose    (GObject *object);
+
+
+static void browser_canvas_item_set_property (GObject *object,
+					      guint param_id,
+					      const GValue *value,
+					      GParamSpec *pspec);
+static void browser_canvas_item_get_property (GObject *object,
+					      guint param_id,
+					      GValue *value,
+					      GParamSpec *pspec);
+struct _BrowserCanvasItemPrivate
+{
+	gboolean            moving;
+	double              xstart;
+	double              ystart;
+	gboolean            allow_move;
+	gchar              *tooltip_text;
+};
+
+enum
+{
+	MOVED,
+	MOVING,
+	SHIFTED,
+	DESTROY,
+	LAST_SIGNAL
+};
+
+enum
+{
+	PROP_0,
+	PROP_ALLOW_MOVE,
+	PROP_TOOLTIP_TEXT
+};
+
+static gint browser_canvas_item_signals[LAST_SIGNAL] = { 0, 0, 0, 0 };
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *base_parent_class = NULL;
+
+GType
+browser_canvas_item_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasItemClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_item_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasItem),
+			0,
+			(GInstanceInitFunc) browser_canvas_item_init
+		};
+		type = g_type_register_static (GOO_TYPE_CANVAS_GROUP, "BrowserCanvasItem", &info, 0);
+	}
+
+	return type;
+}
+
+
+static void
+browser_canvas_item_class_init (BrowserCanvasItemClass * class)
+{
+	GObjectClass   *object_class = G_OBJECT_CLASS (class);
+
+	base_parent_class = g_type_class_peek_parent (class);
+
+	browser_canvas_item_signals[MOVED] =
+		g_signal_new ("moved",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (BrowserCanvasItemClass, moved),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
+			      0);
+	browser_canvas_item_signals[MOVING] =
+		g_signal_new ("moving",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (BrowserCanvasItemClass, moving),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
+			      0);
+	browser_canvas_item_signals[SHIFTED] =
+		g_signal_new ("shifted",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (BrowserCanvasItemClass, shifted),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
+			      0);
+	browser_canvas_item_signals[DESTROY] =
+		g_signal_new ("destroy",
+			      G_TYPE_FROM_CLASS (object_class),
+			      G_SIGNAL_RUN_FIRST,
+			      G_STRUCT_OFFSET (BrowserCanvasItemClass, destroy),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
+			      0);
+
+
+	class->moved = NULL;
+	class->moving = NULL;
+	class->shifted = NULL;
+	class->destroy = NULL;
+	object_class->dispose = browser_canvas_item_dispose;
+
+	/* virtual funstionc */
+	class->extra_event = NULL;
+
+	/* Properties */
+	object_class->set_property = browser_canvas_item_set_property;
+	object_class->get_property = browser_canvas_item_get_property;
+
+	g_object_class_install_property
+                (object_class, PROP_ALLOW_MOVE,
+                 g_param_spec_boolean ("allow-move", NULL, NULL, TRUE, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+	g_object_class_install_property
+		(object_class, PROP_TOOLTIP_TEXT,
+		 g_param_spec_string ("tip-text", NULL, NULL, NULL, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+}
+
+static gboolean leave_notify_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+				    GdkEventCrossing *event, gpointer data);
+static gboolean button_press_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+				    GdkEventButton *event, gpointer data);
+static gboolean button_release_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+				      GdkEventButton *event, gpointer data);
+static gboolean motion_notify_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+				     GdkEventMotion *event, gpointer data);
+
+
+static void
+browser_canvas_item_init (BrowserCanvasItem * item)
+{
+	item->priv = g_new0 (BrowserCanvasItemPrivate, 1);
+	item->priv->moving = FALSE;
+	item->priv->xstart = 0;
+	item->priv->ystart = 0;
+	item->priv->allow_move = FALSE;
+	item->priv->tooltip_text = NULL;
+	
+	g_signal_connect (G_OBJECT (item), "leave-notify-event",
+			  G_CALLBACK (leave_notify_event), NULL);
+	g_signal_connect (G_OBJECT (item), "motion-notify-event",
+			  G_CALLBACK (motion_notify_event), NULL);
+	g_signal_connect (G_OBJECT (item), "button-press-event",
+			  G_CALLBACK (button_press_event), NULL);
+	g_signal_connect (G_OBJECT (item), "button-release-event",
+			  G_CALLBACK (button_release_event), NULL);
+	
+}
+
+static void
+browser_canvas_item_dispose (GObject *object)
+{
+	BrowserCanvasItem *citem;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_ITEM (object));
+	
+	citem = BROWSER_CANVAS_ITEM (object);
+	if (citem->priv) {
+		g_signal_emit (object, browser_canvas_item_signals[DESTROY], 0);
+
+		if (citem->priv->tooltip_text) 
+			g_free (citem->priv->tooltip_text);
+
+		g_free (citem->priv);
+		citem->priv = NULL;
+	}
+
+	/* for the parent class */
+	base_parent_class->dispose (object);
+}
+
+static void 
+browser_canvas_item_set_property (GObject *object,
+				  guint param_id,
+				  const GValue *value,
+				  GParamSpec *pspec)
+{
+	BrowserCanvasItem *citem = NULL;
+	const gchar *str = NULL;
+
+	citem = BROWSER_CANVAS_ITEM (object);
+
+	switch (param_id) {
+	case PROP_ALLOW_MOVE:
+		citem->priv->allow_move = g_value_get_boolean (value);
+		break;
+	case PROP_TOOLTIP_TEXT:
+		str = g_value_get_string (value);
+		if (citem->priv->tooltip_text) {
+			g_free (citem->priv->tooltip_text);
+			citem->priv->tooltip_text = NULL;
+		}
+		if (str)
+			citem->priv->tooltip_text = g_strdup (str);
+		break;
+	}
+}
+
+static void
+browser_canvas_item_get_property (GObject *object,
+				  guint param_id,
+				  GValue *value,
+				  GParamSpec *pspec)
+{
+        BrowserCanvasItem *citem = NULL;
+        
+        citem = BROWSER_CANVAS_ITEM (object);
+        switch (param_id) {
+        case PROP_ALLOW_MOVE:
+                g_value_set_boolean (value, citem->priv->allow_move);
+                break;
+        case PROP_TOOLTIP_TEXT:
+                g_value_set_string (value, citem->priv->tooltip_text);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+                break;
+        }
+        
+}
+
+/**
+ * browser_canvas_item_get_canvas
+ * @item: a #BrowserCanvasItem object
+ *
+ * Get the #BrowserCanvas on which @item is drawn
+ *
+ * Returns: the #BrowserCanvas widget
+ */
+BrowserCanvas *
+browser_canvas_item_get_canvas (BrowserCanvasItem *item)
+{
+	g_return_val_if_fail (IS_BROWSER_CANVAS_ITEM (item), NULL);
+	return (BrowserCanvas *) g_object_get_data (G_OBJECT (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (item))),
+						     "browsercanvas");
+}
+
+/**
+ * browser_canvas_item_get_edge_nodes
+ * @item: a #BrowserCanvasItem object
+ * @from: a place to store the FROM part of the edge, or %NULL
+ * @to: a place to store the TO part of the edge, or %NULL
+ *
+ * If the @item canvas item represents a "link" between two other canvas items (an edge), then
+ * set @from and @to to those items.
+ */
+void 
+browser_canvas_item_get_edge_nodes (BrowserCanvasItem *item, 
+				  BrowserCanvasItem **from, BrowserCanvasItem **to)
+{
+	BrowserCanvasItemClass *class;
+
+	g_return_if_fail (IS_BROWSER_CANVAS_ITEM (item));
+
+	class = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (item));
+	if (class->get_edge_nodes)
+		(class->get_edge_nodes) (item, from, to);
+	else {
+		if (from)
+			*from = NULL;
+		if (to)
+			*to = NULL;
+	}
+}
+
+static gboolean
+leave_notify_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+		    GdkEventCrossing *event, gpointer data)
+{
+	gtk_widget_set_has_tooltip (GTK_WIDGET (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (citem))),
+				    FALSE);
+	return FALSE;
+}
+
+static gboolean
+button_press_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+		    GdkEventButton *event, gpointer data)
+{
+	gboolean done = FALSE;
+
+	switch (event->button) {
+	case 1:
+		if (event->state & GDK_SHIFT_MASK) {
+			GdkDragContext *context;
+			GtkTargetList *target_list;
+			BrowserCanvas *canvas;
+			BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (citem));
+			
+			if (iclass->drag_data_get) {
+				canvas =  browser_canvas_item_get_canvas (citem);
+				target_list = gtk_target_list_new (NULL, 0);
+				gtk_target_list_add_table (target_list, dbo_table, G_N_ELEMENTS (dbo_table));
+				context = gtk_drag_begin (GTK_WIDGET (canvas),
+							  target_list,
+							  GDK_ACTION_MOVE|GDK_ACTION_COPY|GDK_ACTION_DEFAULT,
+							  event->button, (GdkEvent*) event);
+				gtk_drag_set_icon_default (context);
+				gtk_target_list_unref (target_list);
+				g_object_set_data (G_OBJECT (canvas), "__drag_src_item", citem);
+			}
+			done = TRUE;
+		}
+		else if (citem->priv->allow_move) {
+			/* movement management */
+			goo_canvas_item_raise (GOO_CANVAS_ITEM (citem), NULL);
+			citem->priv->xstart = event->x;
+			citem->priv->ystart = event->y;
+			citem->priv->moving = TRUE;
+			done = TRUE;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return done;
+}
+
+static gboolean
+button_release_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+		      GdkEventButton *event, gpointer data)
+{
+	if (citem->priv->allow_move) {
+		citem->priv->moving = FALSE;
+#ifdef debug_signal
+		g_print (">> 'MOVED' from %s::item_event()\n", __FILE__);
+#endif
+		g_signal_emit (G_OBJECT (citem), browser_canvas_item_signals[MOVED], 0);
+#ifdef debug_signal
+		g_print ("<< 'MOVED' from %s::item_event()\n", __FILE__);
+#endif
+	}
+	
+	return FALSE;
+}
+
+static gboolean
+motion_notify_event (BrowserCanvasItem *citem, GooCanvasItem *target_item,
+		     GdkEventMotion *event, gpointer data)
+{
+	gboolean retval = FALSE;
+
+	if (citem->priv->moving && (event->state & GDK_BUTTON1_MASK)) {
+		g_assert (IS_BROWSER_CANVAS_ITEM (citem));
+		goo_canvas_item_translate (GOO_CANVAS_ITEM (citem), 
+					   (gdouble) event->x - citem->priv->xstart, 
+					   (gdouble) event->y - citem->priv->ystart);
+		g_signal_emit (G_OBJECT (citem), browser_canvas_item_signals[MOVING], 0);
+		retval = TRUE;
+	}
+	else {
+		if (citem->priv->tooltip_text)
+			gtk_widget_set_tooltip_text (GTK_WIDGET (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (citem))),
+						     citem->priv->tooltip_text);
+	}
+	
+	return retval;
+}
+
+/**
+ * browser_canvas_item_translate
+ */
+void
+browser_canvas_item_translate (BrowserCanvasItem *item, gdouble dx, gdouble dy)
+{
+	g_return_if_fail (IS_BROWSER_CANVAS_ITEM (item));
+	goo_canvas_item_translate (GOO_CANVAS_ITEM (item), dx, dy);
+	g_signal_emit (G_OBJECT (item), browser_canvas_item_signals [MOVED], 0);
+}
diff --git a/tools/browser/canvas/browser-canvas-item.h b/tools/browser/canvas/browser-canvas-item.h
new file mode 100644
index 0000000..c43f2b2
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-item.h
@@ -0,0 +1,69 @@
+/* browser-canvas-item.h
+ *
+ * Copyright (C) 2007 - 2008 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_ITEM__
+#define __BROWSER_CANVAS_ITEM__
+
+#include <goocanvas.h>
+#include "browser-canvas-decl.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_BROWSER_CANVAS_ITEM          (browser_canvas_item_get_type())
+#define BROWSER_CANVAS_ITEM(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_item_get_type(), BrowserCanvasItem)
+#define BROWSER_CANVAS_ITEM_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_item_get_type (), BrowserCanvasItemClass)
+#define IS_BROWSER_CANVAS_ITEM(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_item_get_type ())
+
+/* struct for the object's data */
+struct _BrowserCanvasItem
+{
+	GooCanvasGroup         object;
+
+	BrowserCanvasItemPrivate *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasItemClass
+{
+	GooCanvasGroupClass    parent_class;
+
+	/* signals */
+	void (*moved)        (BrowserCanvasItem *citem);
+	void (*moving)       (BrowserCanvasItem *citem);
+	void (*shifted)      (BrowserCanvasItem *citem);
+	void (*destroy)      (BrowserCanvasItem *citem);
+
+	/* virtual functions */
+	void (*extra_event)  (BrowserCanvasItem *citem, GdkEventType event_type);
+	void (*get_edge_nodes)(BrowserCanvasItem *citem, BrowserCanvasItem **from, BrowserCanvasItem **to);
+	void (*drag_data_get) (BrowserCanvasItem *citem, GdkDragContext *drag_context,
+			       GtkSelectionData *data, guint info, guint time);
+};
+
+GType              browser_canvas_item_get_type       (void) G_GNUC_CONST;
+
+void               browser_canvas_item_get_edge_nodes (BrowserCanvasItem *item, 
+						       BrowserCanvasItem **from, BrowserCanvasItem **to);
+void               browser_canvas_item_translate      (BrowserCanvasItem *item, gdouble dx, gdouble dy);
+BrowserCanvas     *browser_canvas_item_get_canvas     (BrowserCanvasItem *item);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-print.c b/tools/browser/canvas/browser-canvas-print.c
new file mode 100644
index 0000000..17ad7fa
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-print.c
@@ -0,0 +1,456 @@
+/* browser-canvas-print.c
+ *
+ * Copyright (C) 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "browser-canvas.h"
+#include "browser-canvas-priv.h"
+#include <cairo.h>
+#include <goocanvas.h>
+
+typedef struct {
+	BrowserCanvas     *canvas;
+	GtkPrintSettings *settings;
+	GtkPageSetup     *page_setup;
+	gboolean          show_page_numbers;
+
+	gdouble           page_width, page_height;
+	gint              h_npages, v_npages;
+	gdouble           scale;
+} PrintPageData;
+
+static GObject *print_create_custom_widget_cb (GtkPrintOperation *operation, PrintPageData *pdata);
+static void print_begin (GtkPrintOperation *operation, GtkPrintContext *context, PrintPageData *pdata);
+static void print_end (GtkPrintOperation *operation, GtkPrintContext *context, PrintPageData *pdata);
+static void print_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, PrintPageData *pdata);
+
+static GtkPrintSettings *print_settings = NULL;
+static GtkPageSetup *page_setup = NULL;
+static gboolean show_page_numbers = TRUE;
+
+/**
+ * browser_canvas_print
+ * @canvas: the #BrowserCanvas to print
+ *
+ * Prints @canvas using the GTK+ printing framework (displays printing options)
+ */
+void
+browser_canvas_print (BrowserCanvas *canvas)
+{
+	GtkPrintOperation *print;
+	GtkPrintOperationResult res;
+	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
+	PrintPageData *pdata;
+
+	if (!print_settings)
+		print_settings = gtk_print_settings_new  ();
+	if (!page_setup)
+		page_setup = gtk_page_setup_new ();
+
+	pdata = g_new0 (PrintPageData, 1);
+	pdata->canvas = canvas;
+	pdata->settings = print_settings;
+	pdata->page_setup = page_setup;
+	pdata->show_page_numbers = show_page_numbers;
+
+	print = gtk_print_operation_new ();
+	
+	gtk_print_operation_set_print_settings (print, print_settings);
+	gtk_print_operation_set_default_page_setup (print, pdata->page_setup);
+	
+	g_signal_connect (print, "create-custom-widget", G_CALLBACK (print_create_custom_widget_cb), pdata);
+	g_signal_connect (print, "begin_print", G_CALLBACK (print_begin), pdata);
+	g_signal_connect (print, "end_print", G_CALLBACK (print_end), pdata);
+	g_signal_connect (print, "draw_page", G_CALLBACK (print_draw_page), pdata);
+	gtk_print_operation_set_custom_tab_label (print, _("Page size and zoom"));
+	res = gtk_print_operation_run (print, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+				       (GtkWindow*) toplevel, NULL);
+	
+	if (res == GTK_PRINT_OPERATION_RESULT_APPLY) {
+		g_object_unref (print_settings);
+		print_settings = g_object_ref (gtk_print_operation_get_print_settings (print));
+		if (page_setup != pdata->page_setup) {
+			g_object_unref (page_setup);
+			page_setup = pdata->page_setup;
+		}
+		show_page_numbers = pdata->show_page_numbers;
+	}
+	else if (page_setup != pdata->page_setup)
+		g_object_unref (pdata->page_setup);
+
+	g_object_unref (print);
+	g_free (pdata);
+}
+
+static void
+print_begin (GtkPrintOperation *operation, GtkPrintContext *context, PrintPageData *pdata)
+{
+	gtk_print_operation_set_n_pages (operation, pdata->h_npages * pdata->v_npages);
+	gtk_print_operation_set_default_page_setup (operation, pdata->page_setup);
+}
+
+static void
+print_end (GtkPrintOperation *operation, GtkPrintContext *context, PrintPageData *pdata)
+{
+	
+}
+
+static void
+print_draw_page (GtkPrintOperation *operation, GtkPrintContext *context, gint page_nr, PrintPageData *pdata)
+{
+	cairo_t *cr;
+	GooCanvasBounds bounds, canvas_bounds;
+	gint col_page, line_page;
+#define DRAW_DEBUG
+#undef DRAW_DEBUG
+
+	cr = gtk_print_context_get_cairo_context (context);
+
+	line_page = page_nr / pdata->h_npages;
+	col_page = page_nr % pdata->h_npages;
+
+	goo_canvas_item_get_bounds (goo_canvas_get_root_item (pdata->canvas->priv->goocanvas), &canvas_bounds);
+
+	/*g_print ("Printing page col%d line%d\n", col_page, line_page);*/
+
+#ifdef DRAW_DEBUG
+	cairo_save (cr);
+	/* X axis */
+	cairo_set_source_rgba (cr, 0., 1., 0., 0.8);
+	cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+	cairo_set_font_size (cr, 10.);
+	cairo_move_to (cr, 50., -2.);
+	cairo_show_text (cr, "X");
+	cairo_stroke (cr);
+
+	cairo_move_to (cr, 0., 0.);
+	cairo_line_to (cr, 50., 0.);
+	cairo_rel_line_to (cr, -10., 10.);
+	cairo_stroke (cr);
+	cairo_move_to (cr, 50., 0.);
+	cairo_rel_line_to (cr, -10., -10.);
+	cairo_stroke (cr);
+
+	/* Y axis */
+	cairo_set_source_rgba (cr, 0., 0., 1., 0.8);
+
+	cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
+	cairo_set_font_size (cr, 10.);
+	cairo_move_to (cr, -12., 55.);
+	cairo_show_text (cr, "Y");
+	cairo_stroke (cr);
+
+	cairo_move_to (cr, 0., 0.);
+	cairo_line_to (cr, 0., 50.);
+	cairo_rel_line_to (cr, 10., -10.);
+	cairo_stroke (cr);
+	cairo_move_to (cr, 0., 50.);
+	cairo_rel_line_to (cr, -10., -10.);
+	cairo_stroke (cr);
+
+	cairo_rectangle (cr, 0, 0, gtk_print_context_get_width (context), gtk_print_context_get_height (context));
+	cairo_set_source_rgba (cr, 0., 0., 0., 1.);
+	cairo_set_line_width (cr, 0.5);
+	gdouble dash_length = 2.;
+	cairo_set_dash (cr, &dash_length, 1, 0.);
+	cairo_stroke (cr);
+	cairo_restore (cr);
+#endif
+
+	if (pdata->show_page_numbers) {
+		cairo_text_extents_t extents;
+		gchar *str = g_strdup_printf (_("Page %d of %d horizontally and %d of %d vertically"), 
+					      col_page + 1, pdata->h_npages, 
+					      line_page + 1, pdata->v_npages);
+		cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+		cairo_set_font_size (cr, 10.);
+		cairo_text_extents (cr, str, &extents);
+		cairo_move_to (cr, gtk_print_context_get_width (context) - extents.width - extents.x_bearing, 
+			       gtk_print_context_get_height (context) - extents.height - extents.y_bearing);
+		cairo_show_text (cr, str);
+		g_free (str);
+		cairo_stroke (cr);
+	}
+
+	bounds.x1 = col_page * pdata->page_width + canvas_bounds.x1;
+	bounds.x2 = bounds.x1 + pdata->page_width;
+	bounds.y1 = line_page * pdata->page_height + canvas_bounds.y1;
+	bounds.y2 = bounds.y1 + pdata->page_height;
+
+	cairo_scale (cr, pdata->scale, pdata->scale);
+	cairo_translate (cr, - bounds.x1, - bounds.y1);
+	goo_canvas_render (pdata->canvas->priv->goocanvas, cr, &bounds, .8);
+	/*
+	g_print ("Scale %.2f, cairo's bounds: (%.2fx%.2f) => (%.2fx%.2f), canvas's bounds: (%.2fx%.2f) => (%.2fx%.2f)\n", 
+		 pdata->scale, bounds.x1, bounds.y1, bounds.x2, bounds.y2,
+		 canvas_bounds.x1, canvas_bounds.y1, canvas_bounds.x2, canvas_bounds.y2);
+	*/
+}
+
+typedef struct {
+	PrintPageData *pdata;
+	GtkSpinButton *zoom;
+	GtkSpinButton *h_npages;
+	GtkSpinButton *v_npages;
+} PrintCustomData;
+
+static void print_page_setup_cb (GtkWidget *button, PrintCustomData *cdata);
+static void print_h_npages_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata);
+static void print_v_npages_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata);
+static void print_zoom_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata);
+static void print_page_numbers_toggled_cb (GtkToggleButton *toggle, PrintCustomData *cdata);
+
+static GObject *
+print_create_custom_widget_cb (GtkPrintOperation *operation, PrintPageData *pdata)
+{
+	GtkWidget *vbox, *bbox, *button, *label, *hbox, *table, *entry;
+	PrintCustomData *cdata;
+
+	cdata = g_new0 (PrintCustomData, 1);
+	cdata->pdata = pdata;
+
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
+
+	/* page size's adjustments */
+	bbox = gtk_hbutton_box_new ();
+	gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START);
+
+	button = gtk_button_new_with_label (_("Adjust page's size and orientation"));
+	g_signal_connect (button, "clicked", G_CALLBACK (print_page_setup_cb), cdata);
+	gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, FALSE, 0);
+
+	/* zoom control */
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), _("<b>Zoom</b>"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 10);
+
+	hbox = gtk_hbox_new (FALSE, 0); /* HIG */
+        gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+        label = gtk_label_new ("    ");
+        gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+	table = gtk_table_new (3, 3, FALSE);
+	gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0);
+
+	label = gtk_label_new (_("Number of pages used:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 2, GTK_FILL, GTK_FILL, 0, 0);
+
+	entry = gtk_spin_button_new_with_range (1., 100., 1.);
+	gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 0);
+	gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_FILL, 0, 5, 0);
+	cdata->h_npages = (GtkSpinButton*) entry;
+	g_signal_connect (entry, "value-changed",
+			  G_CALLBACK (print_h_npages_value_changed_cb), cdata);
+
+	label = gtk_label_new (_("horizontally"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = gtk_spin_button_new_with_range (1., 100., 1.);
+	gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 0);
+	gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, GTK_FILL, 0, 5, 0);
+	cdata->v_npages = (GtkSpinButton*) entry;
+	g_signal_connect (entry, "value-changed",
+			  G_CALLBACK (print_v_npages_value_changed_cb), cdata);
+
+	label = gtk_label_new (_("vertically"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
+
+	label = gtk_label_new (_("Zoom factor:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
+
+	entry = gtk_spin_button_new_with_range (.1, 10., .05);
+	gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 2);
+	gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 2, 3, GTK_FILL, 0, 5, 0);
+	cdata->zoom = (GtkSpinButton*) entry;
+	g_signal_connect (entry, "value-changed",
+			  G_CALLBACK (print_zoom_value_changed_cb), cdata);
+
+	/* misc options */
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), _("<b>Page numbers</b>"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 10);
+
+	hbox = gtk_hbox_new (FALSE, 0); /* HIG */
+        gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+        label = gtk_label_new ("    ");
+        gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+	bbox = gtk_hbutton_box_new ();
+	gtk_box_pack_start (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_START);
+
+	button = gtk_check_button_new_with_label (_("Print page numbers"));
+	g_signal_connect (button, "toggled", G_CALLBACK (print_page_numbers_toggled_cb), cdata);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), cdata->pdata->show_page_numbers);
+	gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, FALSE, 0);
+
+	/* correct start state */
+	gtk_widget_show_all (vbox);
+	g_object_set_data_full (G_OBJECT (vbox), "cdata", cdata, g_free);
+
+	/* default zoom to 1 */
+	gtk_spin_button_set_value (cdata->zoom, 1.);
+
+	return G_OBJECT (vbox);
+}
+
+static void
+print_page_numbers_toggled_cb (GtkToggleButton *toggle, PrintCustomData *cdata)
+{
+	cdata->pdata->show_page_numbers = gtk_toggle_button_get_active (toggle);
+}
+
+static void
+print_page_setup_cb (GtkWidget *button, PrintCustomData *cdata)
+{
+	GtkPageSetup *setup;
+	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
+
+	setup = cdata->pdata->page_setup;
+	cdata->pdata->page_setup = gtk_print_run_page_setup_dialog ((GtkWindow *) toplevel, 
+								    cdata->pdata->page_setup, cdata->pdata->settings);
+	if ((cdata->pdata->page_setup != setup) && (setup != page_setup))
+		g_object_unref (setup);
+
+	print_zoom_value_changed_cb (cdata->zoom, cdata);
+}
+
+static void
+print_h_npages_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata)
+{
+	gdouble page_width, page_height;
+	GooCanvasBounds bounds;
+	gdouble canvas_height;
+	gdouble zoom;
+	gint h_npages, v_npages;
+
+	h_npages = (gint) gtk_spin_button_get_value (entry);
+	page_width = gtk_page_setup_get_page_width (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+	page_height = gtk_page_setup_get_page_height (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+
+	goo_canvas_item_get_bounds (goo_canvas_get_root_item (cdata->pdata->canvas->priv->goocanvas), &bounds);
+	zoom = (gdouble) h_npages * page_width / (bounds.x2 - bounds.x1);
+
+	canvas_height = (bounds.y2 - bounds.y1) * zoom;
+	v_npages = (gint) (canvas_height / page_height + 1.);
+	
+	g_signal_handlers_block_by_func (cdata->zoom, G_CALLBACK (print_zoom_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->zoom, zoom);
+	g_signal_handlers_unblock_by_func (cdata->zoom, G_CALLBACK (print_zoom_value_changed_cb), cdata);
+	g_signal_handlers_block_by_func (cdata->v_npages, G_CALLBACK (print_v_npages_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->v_npages, v_npages);
+	g_signal_handlers_unblock_by_func (cdata->v_npages, G_CALLBACK (print_v_npages_value_changed_cb), cdata);
+
+	cdata->pdata->scale = zoom;
+	cdata->pdata->page_width = page_width / zoom;
+	cdata->pdata->page_height = page_height / zoom;
+	cdata->pdata->h_npages = h_npages;
+	cdata->pdata->v_npages = v_npages;
+
+	/*
+	g_print ("Pages: %d/%d Page:%.2f/%.2f\n", cdata->pdata->h_npages, cdata->pdata->v_npages,
+		 cdata->pdata->page_width, cdata->pdata->page_height);
+	*/
+}
+
+static void
+print_v_npages_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata)
+{
+	gdouble page_width, page_height;
+	GooCanvasBounds bounds;
+	gdouble canvas_width;
+	gdouble zoom;
+	gint h_npages, v_npages;
+
+	v_npages = (gint) gtk_spin_button_get_value (entry);
+	page_width = gtk_page_setup_get_page_width (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+	page_height = gtk_page_setup_get_page_height (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+
+	goo_canvas_item_get_bounds (goo_canvas_get_root_item (cdata->pdata->canvas->priv->goocanvas), &bounds);
+	zoom = (gdouble) v_npages * page_height / (bounds.y2 - bounds.y1);
+
+	canvas_width = (bounds.x2 - bounds.x1) * zoom;
+	h_npages = (gint) (canvas_width / page_width + 1.);
+	
+	g_signal_handlers_block_by_func (cdata->zoom, G_CALLBACK (print_zoom_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->zoom, zoom);
+	g_signal_handlers_unblock_by_func (cdata->zoom, G_CALLBACK (print_zoom_value_changed_cb), cdata);
+	g_signal_handlers_block_by_func (cdata->h_npages, G_CALLBACK (print_h_npages_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->h_npages, h_npages);
+	g_signal_handlers_unblock_by_func (cdata->h_npages, G_CALLBACK (print_h_npages_value_changed_cb), cdata);
+
+	cdata->pdata->scale = zoom;
+	cdata->pdata->page_width = page_width / zoom;
+	cdata->pdata->page_height = page_height / zoom;
+	cdata->pdata->h_npages = h_npages;
+	cdata->pdata->v_npages = v_npages;
+
+	/*
+	g_print ("Pages: %d/%d Page:%.2f/%.2f\n", cdata->pdata->h_npages, cdata->pdata->v_npages,
+		 cdata->pdata->page_width, cdata->pdata->page_height);
+	*/
+}
+
+static void
+print_zoom_value_changed_cb (GtkSpinButton *entry, PrintCustomData *cdata)
+{
+	gdouble page_width, page_height;
+	GooCanvasBounds bounds;
+	gdouble canvas_width, canvas_height;
+	gdouble zoom;
+	gint h_npages, v_npages;
+
+	zoom = gtk_spin_button_get_value (entry);
+	page_width = gtk_page_setup_get_page_width (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+	page_height = gtk_page_setup_get_page_height (cdata->pdata->page_setup, GTK_UNIT_POINTS);
+
+	goo_canvas_item_get_bounds (goo_canvas_get_root_item (cdata->pdata->canvas->priv->goocanvas), &bounds);
+	canvas_width = (bounds.x2 - bounds.x1) * zoom;
+	canvas_height = (bounds.y2 - bounds.y1) * zoom;
+	h_npages = (gint) (canvas_width / page_width + 1.);
+	v_npages = (gint) (canvas_height / page_height + 1.);
+
+	g_signal_handlers_block_by_func (cdata->h_npages, G_CALLBACK (print_h_npages_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->h_npages, h_npages);
+	g_signal_handlers_unblock_by_func (cdata->h_npages, G_CALLBACK (print_h_npages_value_changed_cb), cdata);
+	g_signal_handlers_block_by_func (cdata->v_npages, G_CALLBACK (print_v_npages_value_changed_cb), cdata);
+	gtk_spin_button_set_value (cdata->v_npages, v_npages);
+	g_signal_handlers_unblock_by_func (cdata->v_npages, G_CALLBACK (print_v_npages_value_changed_cb), cdata);
+
+	cdata->pdata->scale = zoom;
+	cdata->pdata->page_width = page_width / zoom;
+	cdata->pdata->page_height = page_height / zoom;
+	cdata->pdata->h_npages = h_npages;
+	cdata->pdata->v_npages = v_npages;
+
+	/*
+	g_print ("Pages: %d/%d Page:%.2f/%.2f\n", cdata->pdata->h_npages, cdata->pdata->v_npages,
+		 cdata->pdata->page_width, cdata->pdata->page_height);
+	*/
+}
+
diff --git a/tools/browser/canvas/browser-canvas-print.h b/tools/browser/canvas/browser-canvas-print.h
new file mode 100644
index 0000000..1fd39f8
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-print.h
@@ -0,0 +1,27 @@
+/* browser-canvas-print.h
+ *
+ * Copyright (C) 2007 Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <goocanvas.h>
+
+G_BEGIN_DECLS
+
+void browser_canvas_print (BrowserCanvas *canvas);
+
+G_END_DECLS
diff --git a/tools/browser/canvas/browser-canvas-priv.h b/tools/browser/canvas/browser-canvas-priv.h
new file mode 100644
index 0000000..989cb31
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-priv.h
@@ -0,0 +1,36 @@
+/* browser-canvas.h
+ *
+ * Copyright (C) 2009 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_PRIV__
+#define __BROWSER_CANVAS_PRIV__
+
+#include <goocanvas.h>
+
+G_BEGIN_DECLS
+
+struct _BrowserCanvasPrivate
+{
+	GooCanvas          *goocanvas;
+	GSList             *items; /* BrowserCanvasItem objects, non ordered */
+};
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-table.c b/tools/browser/canvas/browser-canvas-table.c
new file mode 100644
index 0000000..7c2d05a
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-table.c
@@ -0,0 +1,500 @@
+/* browser-canvas-table.c
+ *
+ * Copyright (C) 2002 - 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "browser-canvas.h"
+#include "browser-canvas-table.h"
+#include "browser-canvas-column.h"
+#include <glib/gi18n-lib.h>
+
+static void browser_canvas_table_class_init (BrowserCanvasTableClass *class);
+static void browser_canvas_table_init       (BrowserCanvasTable *drag);
+static void browser_canvas_table_dispose    (GObject *object);
+static void browser_canvas_table_finalize   (GObject *object);
+
+static void browser_canvas_table_set_property (GObject *object,
+					       guint param_id,
+					       const GValue *value,
+					       GParamSpec *pspec);
+static void browser_canvas_table_get_property (GObject *object,
+					       guint param_id,
+					       GValue *value,
+					       GParamSpec *pspec);
+
+static void browser_canvas_table_drag_data_get (BrowserCanvasItem *citem, GdkDragContext *drag_context,
+						GtkSelectionData *data, guint info, guint time);
+
+enum
+{
+	PROP_0,
+	PROP_TABLE,
+	PROP_MENU_FUNC
+};
+
+struct _BrowserCanvasTablePrivate
+{
+	GdaMetaTable       *table;
+
+	/* UI building information */
+        GSList             *column_items; /* list of GooCanvasItem for the columns */
+	GSList             *other_items; /* list of GooCanvasItem for other purposes */
+	gdouble            *column_ypos; /* array for each column's Y position in this canvas group */
+	GtkWidget          *(*popup_menu_func) (BrowserCanvasTable *ce);
+
+	/* presentation parameters */
+        gdouble             x_text_space;
+        gdouble             y_text_space;
+};
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *table_parent_class = NULL;
+
+GType
+browser_canvas_table_get_type (void)
+{
+	static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasTableClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_table_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasTable),
+			0,
+			(GInstanceInitFunc) browser_canvas_table_init
+		};		
+
+		type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasTable", &info, 0);
+	}
+
+	return type;
+}
+
+	
+static void
+browser_canvas_table_class_init (BrowserCanvasTableClass *class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (class);
+	BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (class);
+
+	table_parent_class = g_type_class_peek_parent (class);
+	iclass->drag_data_get = browser_canvas_table_drag_data_get;
+
+	object_class->dispose = browser_canvas_table_dispose;
+	object_class->finalize = browser_canvas_table_finalize;
+
+	/* Properties */
+	object_class->set_property = browser_canvas_table_set_property;
+	object_class->get_property = browser_canvas_table_get_property;
+
+	g_object_class_install_property
+                (object_class, PROP_TABLE,
+                 g_param_spec_pointer ("table", NULL, NULL,
+				       (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+	g_object_class_install_property 
+		(object_class, PROP_MENU_FUNC,
+                 g_param_spec_pointer ("popup_menu_func", "Popup menu function", 
+				       "Function to create a popup menu on each BrowserCanvasTable", 
+				       G_PARAM_WRITABLE));
+}
+
+static gboolean button_press_event_cb (BrowserCanvasTable *ce, GooCanvasItem *target_item, GdkEventButton *event,
+				       gpointer unused_data);
+
+static void
+browser_canvas_table_init (BrowserCanvasTable *table)
+{
+	table->priv = g_new0 (BrowserCanvasTablePrivate, 1);
+	table->priv->table = NULL;
+	table->priv->column_ypos = NULL;
+	table->priv->popup_menu_func = NULL;
+
+	table->priv->x_text_space = 3.;
+	table->priv->y_text_space = 3.;
+
+	g_signal_connect (G_OBJECT (table), "button-press-event",
+			  G_CALLBACK (button_press_event_cb), NULL);
+}
+
+static void clean_items (BrowserCanvasTable *ce);
+static void create_items (BrowserCanvasTable *ce);
+
+static void
+browser_canvas_table_dispose (GObject *object)
+{
+	BrowserCanvasTable *ce;
+
+	g_return_if_fail (IS_BROWSER_CANVAS_TABLE (object));
+
+	ce = BROWSER_CANVAS_TABLE (object);
+
+	/* REM: let the GooCanvas library destroy the items itself */
+	ce->priv->table = NULL;
+
+	/* for the parent class */
+	table_parent_class->dispose (object);
+}
+
+
+static void
+browser_canvas_table_finalize (GObject   * object)
+{
+	BrowserCanvasTable *ce;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_TABLE (object));
+
+	ce = BROWSER_CANVAS_TABLE (object);
+	if (ce->priv) {
+		g_slist_free (ce->priv->column_items);
+		g_slist_free (ce->priv->other_items);
+		if (ce->priv->column_ypos)
+			g_free (ce->priv->column_ypos);
+
+		g_free (ce->priv);
+		ce->priv = NULL;
+	}
+
+	/* for the parent class */
+	table_parent_class->finalize (object);
+}
+
+static void 
+browser_canvas_table_set_property (GObject *object,
+				   guint param_id,
+				   const GValue *value,
+				   GParamSpec *pspec)
+{
+	BrowserCanvasTable *ce = NULL;
+
+	ce = BROWSER_CANVAS_TABLE (object);
+
+	switch (param_id) {
+	case PROP_TABLE: {
+		GdaMetaTable *table;
+		table = g_value_get_pointer (value);
+		if (table && (table == ce->priv->table))
+			return;
+
+		if (ce->priv->table) {
+			ce->priv->table = NULL;
+			clean_items (ce);
+		}
+
+		if (table) {
+			ce->priv->table = (GdaMetaTable*) table;
+			create_items (ce);
+		}
+		break;
+	}
+	case PROP_MENU_FUNC:
+		ce->priv->popup_menu_func = (GtkWidget *(*) (BrowserCanvasTable *ce)) g_value_get_pointer (value);
+		break;
+	}
+}
+
+static void 
+browser_canvas_table_get_property (GObject *object,
+				   guint param_id,
+				   GValue *value,
+				   GParamSpec *pspec)
+{
+	BrowserCanvasTable *ce = NULL;
+
+	ce = BROWSER_CANVAS_TABLE (object);
+
+	switch (param_id) {
+	case PROP_TABLE:
+		g_value_set_pointer (value, ce->priv->table);
+		break;
+	}
+}
+
+/* 
+ * destroy any existing GooCanvasItem obejcts 
+ */
+static void 
+clean_items (BrowserCanvasTable *ce)
+{
+	GSList *list;
+	/* destroy all the items in the group */
+	while (ce->priv->column_items)
+		g_object_unref (G_OBJECT (ce->priv->column_items->data));
+
+	for (list = ce->priv->other_items; list; list = list->next)
+		g_object_unref (G_OBJECT (list->data));
+	g_slist_free (ce->priv->other_items);
+	ce->priv->other_items = NULL;
+
+	/* free the columns positions */
+	if (ce->priv->column_ypos) {
+		g_free (ce->priv->column_ypos);
+		ce->priv->column_ypos = NULL;
+	}
+}
+
+/*
+ * create new GooCanvasItem objects
+ */
+static void 
+create_items (BrowserCanvasTable *ce)
+{
+	GooCanvasItem *item, *frame, *title;
+        gdouble y, ysep;
+#define HEADER_Y_PAD 3.
+#define Y_PAD 0.
+#define X_PAD 3.
+#define RADIUS_X 5.
+#define RADIUS_Y 5.
+#define MIN_HEIGHT 70.
+        GooCanvasBounds border_bounds;
+        GooCanvasBounds bounds;
+	const gchar *cstr;
+	gchar *tmpstr = NULL;
+	GSList *columns, *list;
+	gint column_nb;
+	gdouble column_width;
+
+	clean_items (ce);
+	g_assert (ce->priv->table);
+
+        /* title */
+	cstr = GDA_META_DB_OBJECT (ce->priv->table)->obj_short_name;
+	if (cstr)
+		tmpstr = g_markup_printf_escaped ("<b>%s</b>", cstr);
+	else
+		tmpstr = g_strdup_printf ("<b>%s</b>", _("No name"));	
+
+	y = RADIUS_Y;
+        title = goo_canvas_text_new  (GOO_CANVAS_ITEM (ce), tmpstr,
+				      RADIUS_X + X_PAD, y, 
+				      -1, GTK_ANCHOR_NORTH_WEST,
+				      "font", "Sans 11",
+				      "use-markup", TRUE, NULL);
+
+	g_free (tmpstr);
+        goo_canvas_item_get_bounds (title, &bounds);
+        border_bounds = bounds;
+        border_bounds.x1 = 0.;
+        border_bounds.y1 = 0.;
+        y += bounds.y2 - bounds.y1 + HEADER_Y_PAD;
+
+	/* separator's placeholder */
+        ysep = y;
+        y += HEADER_Y_PAD;
+
+	/* columns' vertical position */
+	columns = ce->priv->table->columns;
+	ce->priv->column_ypos = g_new0 (gdouble, g_slist_length (columns) + 1);
+
+	/* columns */
+	for (column_nb = 0, list = columns; list; list = list->next, column_nb++) {
+		ce->priv->column_ypos [column_nb] = y;
+		item = browser_canvas_column_new (GOO_CANVAS_ITEM (ce),
+						  GDA_META_TABLE_COLUMN (list->data),
+						  X_PAD, ce->priv->column_ypos [column_nb], NULL);
+		ce->priv->column_items = g_slist_append (ce->priv->column_items, item);
+		goo_canvas_item_get_bounds (item, &bounds);
+		border_bounds.x1 = MIN (border_bounds.x1, bounds.x1);
+                border_bounds.x2 = MAX (border_bounds.x2, bounds.x2);
+                border_bounds.y1 = MIN (border_bounds.y1, bounds.y1);
+                border_bounds.y2 = MAX (border_bounds.y2, bounds.y2);
+
+                y += bounds.y2 - bounds.y1 + Y_PAD;
+	}
+	if (!columns && (border_bounds.y2 - border_bounds.y1 < MIN_HEIGHT))
+		border_bounds.y2 += MIN_HEIGHT - (border_bounds.y2 - border_bounds.y1);
+
+	/* border */
+	column_width = border_bounds.x2 - border_bounds.x1;
+        border_bounds.y2 += RADIUS_Y;
+        border_bounds.x2 += RADIUS_X;
+        frame = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1, border_bounds.y1, 
+				     border_bounds.x2, border_bounds.y2,
+				     "radius-x", RADIUS_X,
+				     "radius-y", RADIUS_Y,
+				     "fill-color", "#f8f8f8",
+				     NULL);		
+	ce->priv->other_items = g_slist_prepend (ce->priv->other_items, frame);
+
+	/* title's background */
+	gchar *cpath;
+	cpath = g_strdup_printf ("M %d %d H %d V %d H %d Z",
+				 (gint) border_bounds.x1, (gint) border_bounds.y1,
+				 (gint) border_bounds.x2, (gint) ysep,
+				 (gint) border_bounds.x1);
+	item = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1, border_bounds.y1, 
+				    border_bounds.x2, ysep + RADIUS_X,
+				    "clip_path", cpath,
+				    "radius-x", RADIUS_X,
+				    "radius-y", RADIUS_Y,
+				    "fill-color", "#aaaaff",
+				    NULL);
+	g_free (cpath);
+	goo_canvas_item_lower (item, NULL);
+
+	/* separator */
+        item = goo_canvas_polyline_new_line (GOO_CANVAS_ITEM (ce), border_bounds.x1, ysep, border_bounds.x2, ysep,
+					     "close-path", FALSE,
+					     "line-width", .7, NULL);
+	ce->priv->other_items = g_slist_prepend (ce->priv->other_items, item);
+
+	goo_canvas_item_lower (frame, NULL);
+
+	/* setting the columns' background width to be the same for all */
+	for (list = ce->priv->column_items; list; list = list->next) 
+		g_object_set (G_OBJECT (list->data), "width", column_width, NULL);
+}
+
+static gboolean
+button_press_event_cb (BrowserCanvasTable *ce, GooCanvasItem  *target_item, GdkEventButton *event,
+		       gpointer unused_data)
+{
+	if (ce->priv->popup_menu_func) {
+		GtkWidget *menu;
+		menu = ce->priv->popup_menu_func (ce);
+		gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+				NULL, NULL, ((GdkEventButton *)event)->button,
+				((GdkEventButton *)event)->time);
+		return TRUE;
+	}
+
+	return FALSE;	
+}
+
+/**
+ * browser_canvas_table_get_column_item
+ * @ce: a #BrowserCanvasTable object
+ * @column: a #GdaMetaTableColumn object
+ *
+ * Get the #BrowserCanvasColumn object representing @column
+ * in @ce.
+ *
+ * Returns: the corresponding #BrowserCanvasColumn
+ */
+BrowserCanvasColumn *
+browser_canvas_table_get_column_item (BrowserCanvasTable *ce, GdaMetaTableColumn *column)
+{
+	gint pos;
+
+	g_return_val_if_fail (ce && IS_BROWSER_CANVAS_TABLE (ce), NULL);
+	g_return_val_if_fail (ce->priv, NULL);
+	g_return_val_if_fail (ce->priv->table, NULL);
+
+	pos = g_slist_index (ce->priv->table->columns, column);
+	g_return_val_if_fail (pos >= 0, NULL);
+
+	return g_slist_nth_data (ce->priv->column_items, pos);
+}
+
+
+/**
+ * browser_canvas_table_get_column_ypos
+ * @ce: a #BrowserCanvasTable object
+ * @column: a #GdaMetaTableColumn object
+ *
+ * Get the Y position of the middle of the #BrowserCanvasColumn object representing @column
+ * in @ce, in @ce's coordinates.
+ *
+ * Returns: the Y coordinate.
+ */
+gdouble
+browser_canvas_table_get_column_ypos (BrowserCanvasTable *ce, GdaMetaTableColumn *column)
+{
+	gint pos;
+
+	g_return_val_if_fail (ce && IS_BROWSER_CANVAS_TABLE (ce), 0.);
+	g_return_val_if_fail (ce->priv, 0.);
+	g_return_val_if_fail (ce->priv->table, 0.);
+	g_return_val_if_fail (ce->priv->column_ypos, 0.);
+
+	pos = g_slist_index (ce->priv->table->columns, column);
+	g_return_val_if_fail (pos >= 0, 0.);
+	return (0.75 * ce->priv->column_ypos[pos+1] + 0.25 * ce->priv->column_ypos[pos]);
+}
+
+
+/**
+ * browser_canvas_table_new
+ * @parent: the parent item, or NULL. 
+ * @table: a #GdaMetaTable to display
+ * @x: the x coordinate
+ * @y: the y coordinate
+ * @...: optional pairs of property names and values, and a terminating NULL.
+ *
+ * Creates a new canvas item to display the @table table
+ *
+ * Returns: a new #GooCanvasItem object
+ */
+GooCanvasItem *
+browser_canvas_table_new (GooCanvasItem *parent, GdaMetaTable *table, 
+			 gdouble x, gdouble y, ...)
+{
+	GooCanvasItem *item;
+	const char *first_property;
+	va_list var_args;
+		
+	item = g_object_new (TYPE_BROWSER_CANVAS_TABLE, "allow-move", TRUE, NULL);
+
+	if (parent) {
+		goo_canvas_item_add_child (parent, item, -1);
+		g_object_unref (item);
+	}
+
+	g_object_set (item, "table", table, NULL);
+
+	va_start (var_args, y);
+	first_property = va_arg (var_args, char*);
+	if (first_property)
+		g_object_set_valist ((GObject*) item, first_property, var_args);
+	va_end (var_args);
+
+	goo_canvas_item_translate (item, x, y);
+
+	return item;
+}
+
+static void
+browser_canvas_table_drag_data_get (BrowserCanvasItem *citem, GdkDragContext *drag_context,
+				    GtkSelectionData *data, guint info, guint time)
+{
+	BrowserCanvasTable *ctable;
+
+	ctable = BROWSER_CANVAS_TABLE (citem);
+	if (!ctable->priv->table)
+		return;
+
+	GdaMetaDbObject *dbo;
+	gchar *str, *tmp1, *tmp2, *tmp3;
+
+	dbo = GDA_META_DB_OBJECT (ctable->priv->table);
+	tmp1 = gda_rfc1738_encode (dbo->obj_schema);
+	tmp2 = gda_rfc1738_encode (dbo->obj_name);
+	tmp3 = gda_rfc1738_encode (dbo->obj_short_name);
+	str = g_strdup_printf ("OBJ_TYPE=table;OBJ_SCHEMA=%s;OBJ_NAME=%s;OBJ_SHORT_NAME=%s", tmp1, tmp2, tmp3);
+	g_free (tmp1);
+	g_free (tmp2);
+	g_free (tmp3);
+	gtk_selection_data_set (data, data->target, 8, str, strlen (str));
+	g_free (str);
+}
diff --git a/tools/browser/canvas/browser-canvas-table.h b/tools/browser/canvas/browser-canvas-table.h
new file mode 100644
index 0000000..7e90145
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-table.h
@@ -0,0 +1,66 @@
+/* browser-canvas-table.h
+ *
+ * Copyright (C) 2007 - 2008 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_TABLE__
+#define __BROWSER_CANVAS_TABLE__
+
+#include "browser-canvas-item.h"
+#include "browser-canvas-decl.h"
+#include <libgda/gda-meta-struct.h>
+
+G_BEGIN_DECLS
+
+/*
+ * 
+ * "Drag item" GooCanvas item: a BrowserCanvasItem item which is used to represent
+ * an element being dragged, and destroys itself when the mouse button is released
+ *
+ */
+
+#define TYPE_BROWSER_CANVAS_TABLE          (browser_canvas_table_get_type())
+#define BROWSER_CANVAS_TABLE(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_table_get_type(), BrowserCanvasTable)
+#define BROWSER_CANVAS_TABLE_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_table_get_type (), BrowserCanvasTableClass)
+#define IS_BROWSER_CANVAS_TABLE(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_table_get_type ())
+
+
+/* struct for the object's data */
+struct _BrowserCanvasTable
+{
+	BrowserCanvasItem           object;
+
+	BrowserCanvasTablePrivate *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasTableClass
+{
+	BrowserCanvasItemClass   parent_class;
+};
+
+/* generic widget's functions */
+GType                browser_canvas_table_get_type        (void) G_GNUC_CONST;
+GooCanvasItem       *browser_canvas_table_new             (GooCanvasItem *parent, GdaMetaTable *table, 
+							    gdouble x, gdouble y, ...);
+BrowserCanvasColumn *browser_canvas_table_get_column_item (BrowserCanvasTable *ce, GdaMetaTableColumn *column);
+gdouble              browser_canvas_table_get_column_ypos (BrowserCanvasTable *ce, GdaMetaTableColumn *column);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-text.c b/tools/browser/canvas/browser-canvas-text.c
new file mode 100644
index 0000000..9e5dbba
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-text.c
@@ -0,0 +1,527 @@
+/* browser-canvas-text.c
+ *
+ * Copyright (C) 2002 - 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <libgda/libgda.h>
+#include "browser-canvas.h"
+#include "browser-canvas-text.h"
+
+static void browser_canvas_text_class_init (BrowserCanvasTextClass * class);
+static void browser_canvas_text_init       (BrowserCanvasText *text);
+static void browser_canvas_text_dispose    (GObject   * object);
+static void browser_canvas_text_finalize   (GObject   * object);
+
+static void browser_canvas_text_set_property    (GObject *object,
+					     guint param_id,
+					     const GValue *value,
+					     GParamSpec *pspec);
+static void browser_canvas_text_get_property    (GObject *object,
+					     guint param_id,
+					     GValue *value,
+					     GParamSpec *pspec);
+
+static gboolean enter_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct);
+static gboolean leave_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct);
+
+enum
+{
+	PROP_0,
+	PROP_TEXT,
+	PROP_WIDTH,
+	PROP_HEIGHT,
+	PROP_HIGHLIGHT_COLOR,
+	PROP_UNDERLINE,
+	PROP_BOLD
+};
+
+struct _BrowserCanvasTextPrivate
+{
+	gchar                *text;
+	
+	/* properties */
+	gboolean              underline;
+	gboolean              bold;
+	gchar                *highlight_color;
+
+	/* UI building information */
+        GooCanvasItem        *bg_item;
+        GooCanvasItem        *text_item;
+
+	/* animation */
+	guint                 anim_id;
+	guint                 current_anim_rgba;
+	guint                 end_anim_rgba;
+	
+};
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *text_parent_class = NULL;
+
+GType
+browser_canvas_text_get_type (void)
+{
+	static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasTextClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_text_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvasText),
+			0,
+			(GInstanceInitFunc) browser_canvas_text_init
+		};		
+
+		type = g_type_register_static (TYPE_BROWSER_CANVAS_ITEM, "BrowserCanvasText", &info, 0);
+	}
+
+	return type;
+}	
+
+static void
+browser_canvas_text_class_init (BrowserCanvasTextClass * class)
+{
+	GObjectClass   *object_class = G_OBJECT_CLASS (class);
+
+	text_parent_class = g_type_class_peek_parent (class);
+
+	object_class->dispose = browser_canvas_text_dispose;
+	object_class->finalize = browser_canvas_text_finalize;
+
+	/* Properties */
+	object_class->set_property = browser_canvas_text_set_property;
+	object_class->get_property = browser_canvas_text_get_property;
+
+	g_object_class_install_property 
+		(object_class, PROP_WIDTH,
+		 g_param_spec_double ("width", NULL, NULL, 0., G_MAXDOUBLE, 0., G_PARAM_WRITABLE));
+
+	g_object_class_install_property 
+		(object_class, PROP_HEIGHT,
+		 g_param_spec_double ("height", NULL, NULL, 0., G_MAXDOUBLE, 0., G_PARAM_WRITABLE));
+
+	g_object_class_install_property 
+		(object_class, PROP_TEXT,
+		 g_param_spec_string ("text", NULL, NULL, NULL, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+	g_object_class_install_property 
+		(object_class, PROP_HIGHLIGHT_COLOR,
+		 g_param_spec_string ("highlight_color", NULL, NULL, NULL, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+	g_object_class_install_property 
+		(object_class, PROP_UNDERLINE,
+		 g_param_spec_boolean ("text_underline", NULL, NULL, FALSE, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+	g_object_class_install_property 
+		(object_class, PROP_BOLD,
+		 g_param_spec_boolean ("text_bold", NULL, NULL, FALSE, (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+}
+
+static void
+browser_canvas_text_init (BrowserCanvasText *text)
+{
+	text->priv = g_new0 (BrowserCanvasTextPrivate, 1);
+	text->priv->text = NULL;
+	text->priv->highlight_color = g_strdup (BROWSER_CANVAS_ENTITY_COLOR);
+
+	g_signal_connect (G_OBJECT (text), "enter-notify-event", 
+			  G_CALLBACK (enter_notify_cb), text);
+	g_signal_connect (G_OBJECT (text), "leave-notify-event", 
+			  G_CALLBACK (leave_notify_cb), text);
+}
+
+static void
+browser_canvas_text_dispose (GObject *object)
+{
+	BrowserCanvasText *ct;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_TEXT (object));
+
+	ct = BROWSER_CANVAS_TEXT (object);
+
+	/* animation */
+	if (ct->priv->anim_id) {
+		g_source_remove (ct->priv->anim_id);
+		ct->priv->anim_id = 0;
+	}
+
+	/* for the parent class */
+	text_parent_class->dispose (object);
+}
+
+
+static void
+browser_canvas_text_finalize (GObject *object)
+{
+	BrowserCanvasText *ct;
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS_TEXT (object));
+
+	ct = BROWSER_CANVAS_TEXT (object);
+	if (ct->priv) {
+		g_free (ct->priv->text);
+
+		if (ct->priv->highlight_color)
+			g_free (ct->priv->highlight_color);
+
+		g_free (ct->priv);
+		ct->priv = NULL;
+	}
+
+	/* for the parent class */
+	text_parent_class->finalize (object);
+}
+
+static void clean_items (BrowserCanvasText *ct);
+static void create_items (BrowserCanvasText *ct);
+
+static void
+adjust_text_pango_attributes (BrowserCanvasText *ct)
+{
+	if (! ct->priv->text_item)
+		return;
+
+	if (ct->priv->bold || ct->priv->underline) {
+		gchar *str;
+		if (ct->priv->bold) {
+			if (ct->priv->underline)
+				str = g_strdup_printf ("<b><u>%s</u></b>", ct->priv->text);
+			else
+				str = g_strdup_printf ("<b>%s</b>", ct->priv->text);
+		}
+		else
+			str = g_strdup_printf ("<u>%s</u>", ct->priv->text);
+		g_object_set (G_OBJECT (ct->priv->text_item),
+			      "text", str,
+			      "use-markup", TRUE, NULL);
+		g_free (str);
+	}
+	else
+		g_object_set (G_OBJECT (ct->priv->text_item),
+			      "text", ct->priv->text,
+			      "use-markup", FALSE, NULL);
+}
+
+static void 
+browser_canvas_text_set_property (GObject *object,
+				  guint param_id,
+				  const GValue *value,
+				  GParamSpec *pspec)
+{
+	BrowserCanvasText *ct = NULL;
+	const gchar *cstr = NULL;
+	gchar *str;
+	gdouble size = 0;
+	gboolean bool = FALSE;
+
+	ct = BROWSER_CANVAS_TEXT (object);
+
+	switch (param_id) {
+	case PROP_TEXT:
+		g_free (ct->priv->text);
+		ct->priv->text = NULL;
+		clean_items (ct);
+		ct->priv->text = g_strdup (g_value_get_string (value));
+		create_items (ct);
+		break;
+	case PROP_WIDTH:
+		size = g_value_get_double (value);
+		if (ct->priv->bg_item)
+			g_object_set (G_OBJECT (ct->priv->bg_item),
+				      "width", size,
+				      NULL);
+		break;
+	case PROP_HEIGHT:
+		size = g_value_get_double (value);
+		if (ct->priv->bg_item)
+			g_object_set (G_OBJECT (ct->priv->bg_item),
+				      "height", size,
+				      NULL);
+		break;
+	case PROP_HIGHLIGHT_COLOR:
+		cstr = g_value_get_string (value);
+		if (ct->priv->highlight_color) {
+			g_free (ct->priv->highlight_color);
+			ct->priv->highlight_color = NULL;
+		}
+		if (cstr) 
+			ct->priv->highlight_color = g_strdup (cstr);
+		else 
+			ct->priv->highlight_color = g_strdup (BROWSER_CANVAS_ENTITY_COLOR);
+		break;
+	case PROP_UNDERLINE:
+		bool = g_value_get_boolean (value);
+		ct->priv->underline = bool;
+		adjust_text_pango_attributes (ct);
+		if (ct->priv->text_item) {
+			if (bool) {
+				str = g_strdup_printf ("<u>%s</u>", ct->priv->text);
+				g_object_set (G_OBJECT (ct->priv->text_item), 
+					      "text", str,
+					      "use-markup", TRUE, NULL);
+				g_free (str);
+			}
+			else 
+				g_object_set (G_OBJECT (ct->priv->text_item), 
+					      "text", ct->priv->text,
+					      "use-markup", FALSE, NULL);
+		}
+	case PROP_BOLD:
+		bool = g_value_get_boolean (value);
+		ct->priv->bold = bool;
+		adjust_text_pango_attributes (ct);
+		break;
+	}
+}
+
+static void 
+browser_canvas_text_get_property    (GObject *object,
+				    guint param_id,
+				    GValue *value,
+				    GParamSpec *pspec)
+{
+	BrowserCanvasText *ct;
+
+	ct = BROWSER_CANVAS_TEXT (object);
+
+	switch (param_id) {
+	default:
+		g_warning ("No such property!");
+		break;
+	}
+}
+
+/* 
+ * destroy any existing GooCanvasItem obejcts 
+ */
+static void 
+clean_items (BrowserCanvasText *ct)
+{
+	if (ct->priv->bg_item) {
+		goo_canvas_item_remove (GOO_CANVAS_ITEM (ct->priv->bg_item));
+		ct->priv->bg_item = NULL;
+	}
+	if (ct->priv->text_item) {
+		goo_canvas_item_remove (GOO_CANVAS_ITEM (ct->priv->text_item));
+		ct->priv->text_item = NULL;
+	}
+}
+
+/*
+ * create new GooCanvasItem objects
+ */
+static void 
+create_items (BrowserCanvasText *ct)
+{
+	GooCanvasItem *item, *text;
+	GooCanvasBounds bounds;
+
+	g_object_set (G_OBJECT (ct), 
+		      "allow_move", FALSE,
+		      NULL);
+
+	/* text: text's name */
+	text = goo_canvas_text_new (GOO_CANVAS_ITEM (ct), ct->priv->text,
+				    0., 0.,
+				    -1, GTK_ANCHOR_NORTH_WEST, 
+				    "fill_color", "black",
+				    "font", BROWSER_CANVAS_FONT,
+				    "alignment", PANGO_ALIGN_RIGHT, 
+				    NULL);
+	ct->priv->text_item = text;
+
+	/* UI metrics */
+	goo_canvas_item_get_bounds (text, &bounds);
+	
+	/* background */
+	item = goo_canvas_rect_new (GOO_CANVAS_ITEM (ct),
+				    0., 0., 
+				    bounds.x2 - bounds.x1,
+				    bounds.y2 - bounds.y1,
+				    "fill_color", BROWSER_CANVAS_OBJ_BG_COLOR,
+				    "radius-x", 2.,
+				    "radius-y", 2.,
+				    "stroke-pattern", NULL,
+				    NULL);
+
+	ct->priv->bg_item = item;
+	goo_canvas_item_lower (item, NULL);
+
+	adjust_text_pango_attributes (ct);	
+}
+
+static gboolean
+enter_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct)
+{
+	browser_canvas_text_set_highlight (ct, TRUE);
+	return FALSE;
+}
+
+static gboolean 
+leave_notify_cb (GooCanvasItem *item, GooCanvasItem *target_item, GdkEventCrossing *event, BrowserCanvasText *ct)
+{
+	browser_canvas_text_set_highlight (ct, FALSE); 
+	return FALSE;
+}
+
+static guint
+compute_step_value (current, end)
+{
+#define STEP 15
+	if (current < end)
+		return current + MIN (STEP, (end - current));
+	else if (current > end)
+		return current - MIN (STEP, (current - end));
+	else
+		return current;
+}
+
+static gboolean
+anim_cb (BrowserCanvasText *ct) 
+{
+	guint current, end, value;
+	guint rgba = 0;
+
+	/* red */
+	current = (ct->priv->current_anim_rgba >> 24) & 0xFF;
+	end = (ct->priv->end_anim_rgba >> 24) & 0xFF;
+	value = compute_step_value (current, end) << 24;
+	rgba += value;
+
+	/* green */
+	current = (ct->priv->current_anim_rgba >> 16) & 0xFF;
+	end = (ct->priv->end_anim_rgba >> 16) & 0xFF;
+	value = compute_step_value (current, end) << 16;
+	rgba += value;
+
+	/* blue */
+	current = (ct->priv->current_anim_rgba >> 8) & 0xFF;
+	end = (ct->priv->end_anim_rgba >> 8) & 0xFF;
+	value = compute_step_value (current, end) << 8;
+	rgba += value;
+
+	/* alpha */
+	current = ct->priv->current_anim_rgba & 0xFF;
+	end = ct->priv->end_anim_rgba & 0xFF;
+	value = compute_step_value (current, end);
+	rgba += value;
+
+	if (rgba == ct->priv->end_anim_rgba) {
+		ct->priv->anim_id = 0;
+		return FALSE;
+	}
+	else {
+		g_object_set (G_OBJECT (ct->priv->bg_item),  "fill_color_rgba", rgba, NULL);
+		ct->priv->current_anim_rgba = rgba;
+		return TRUE;
+	}
+}
+
+/**
+ * browser_canvas_text_set_highlight
+ * @ct: a #BrowserCanvasText object
+ * @highlight:
+ *
+ * Turns ON or OFF the highlighting of @ct
+ */
+void 
+browser_canvas_text_set_highlight (BrowserCanvasText *ct, gboolean highlight)
+{
+	gchar *str_color;
+	GdkColor gdk_color;
+
+	g_return_if_fail (ct && IS_BROWSER_CANVAS_TEXT (ct));
+	g_return_if_fail (ct->priv);
+
+	if (! ct->priv->bg_item)
+		return;
+
+	if (ct->priv->anim_id) {
+		g_source_remove (ct->priv->anim_id);
+		ct->priv->anim_id = 0;
+	}
+
+	str_color = highlight ? ct->priv->highlight_color : BROWSER_CANVAS_OBJ_BG_COLOR;
+	if (gdk_color_parse (str_color, &gdk_color)) {
+		guint col;
+
+		col = ((guint) (gdk_color.red * 255. / 65535.0));
+		ct->priv->end_anim_rgba = col << 24;
+		col = ((guint) (gdk_color.green * 255. / 65535.0));
+		ct->priv->end_anim_rgba += col << 16;
+		col = ((guint) (gdk_color.blue * 255. / 65535.0));
+		ct->priv->end_anim_rgba += col << 8;
+		if (!ct->priv->current_anim_rgba)
+			ct->priv->current_anim_rgba = ct->priv->end_anim_rgba;
+
+		if (highlight)
+			ct->priv->end_anim_rgba += 255;
+		else
+			ct->priv->end_anim_rgba += 50;
+		
+		ct->priv->anim_id = g_timeout_add (10, (GSourceFunc) anim_cb, ct);
+	}
+	else 
+		g_object_set (G_OBJECT (ct->priv->bg_item),  "fill_color", str_color, NULL);
+}
+
+/**
+ * browser_canvas_text_new
+ * @parent: the parent item, or NULL. 
+ * @txt: text to display
+ * @x: the x coordinate of the text.
+ * @y: the y coordinate of the text.
+ * @...: optional pairs of property names and values, and a terminating NULL.
+ *
+ * Creates a new canvas item to display the @txt message
+ *
+ * Returns: a new #GooCanvasItem object
+ */
+GooCanvasItem *
+browser_canvas_text_new (GooCanvasItem *parent,
+		       const gchar *txt,     
+		       gdouble x,
+		       gdouble y,
+		       ...)
+{
+	GooCanvasItem *item;
+	const char *first_property;
+	va_list var_args;
+		
+	item = g_object_new (TYPE_BROWSER_CANVAS_TEXT, NULL);
+
+	if (parent) {
+		goo_canvas_item_add_child (parent, item, -1);
+		g_object_unref (item);
+	}
+
+	va_start (var_args, y);
+	first_property = va_arg (var_args, char*);
+	if (first_property)
+		g_object_set_valist ((GObject*) item, first_property, var_args);
+	va_end (var_args);
+
+	g_object_set (item, "text", txt, NULL);
+	goo_canvas_item_translate (item, x, y);
+
+	return item;
+}
diff --git a/tools/browser/canvas/browser-canvas-text.h b/tools/browser/canvas/browser-canvas-text.h
new file mode 100644
index 0000000..4beba91
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-text.h
@@ -0,0 +1,71 @@
+/* browser-canvas-text.h
+ *
+ * Copyright (C) 2007 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS_TEXT__
+#define __BROWSER_CANVAS_TEXT__
+
+#include "browser-canvas-item.h"
+
+G_BEGIN_DECLS
+
+/*
+ * 
+ * "Drag item" GooCanvas item: a BrowserCanvasItem item which is used to represent
+ * an element being dragged, and destroys itself when the mouse button is released
+ *
+ */
+
+#define TYPE_BROWSER_CANVAS_TEXT          (browser_canvas_text_get_type())
+#define BROWSER_CANVAS_TEXT(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_text_get_type(), BrowserCanvasText)
+#define BROWSER_CANVAS_TEXT_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_text_get_type (), BrowserCanvasTextClass)
+#define IS_BROWSER_CANVAS_TEXT(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_text_get_type ())
+
+
+typedef struct _BrowserCanvasText        BrowserCanvasText;
+typedef struct _BrowserCanvasTextClass   BrowserCanvasTextClass;
+typedef struct _BrowserCanvasTextPrivate BrowserCanvasTextPrivate;
+
+
+/* struct for the object's data */
+struct _BrowserCanvasText
+{
+	BrowserCanvasItem           object;
+
+	BrowserCanvasTextPrivate *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasTextClass
+{
+	BrowserCanvasItemClass   parent_class;
+};
+
+/* generic widget's functions */
+GType          browser_canvas_text_get_type      (void) G_GNUC_CONST;
+GooCanvasItem* browser_canvas_text_new           (GooCanvasItem *parent,
+						   const gchar *txt,     
+						   gdouble x,
+						   gdouble y,
+						   ...);
+void           browser_canvas_text_set_highlight (BrowserCanvasText *ct, gboolean highlight);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/canvas/browser-canvas-utility.c b/tools/browser/canvas/browser-canvas-utility.c
new file mode 100644
index 0000000..4485b22
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-utility.c
@@ -0,0 +1,836 @@
+/* graph-utility.c
+ * Copyright (C) 2004 Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "browser-canvas-utility.h"
+#include <math.h>
+#include <string.h>
+
+static gchar *points_to_path (GooCanvasPoints *points);
+
+static gboolean compute_intersect_rect_line (gdouble rectx1, gdouble recty1, gdouble rectx2, gdouble recty2,
+					     gdouble P1x, gdouble P1y, gdouble P2x, gdouble P2y,
+					     gdouble *R1x, gdouble *R1y, gdouble *R2x, gdouble *R2y);
+
+static void     compute_text_marks_offsets (gdouble x1, gdouble y1, gdouble x2, gdouble y2,
+					    gdouble *xoff, gdouble *yoff, GtkAnchorType *anchor_type);
+
+static GSList *browser_canvas_util_compute_handle_shapes  (GooCanvasItem *parent, GSList *shapes, gint index,
+							 gdouble x1, gdouble y1, gdouble x2, gdouble y2);
+
+static GSList *browser_canvas_canvas_shape_add_to_list (GSList *list, gchar *swallow_id, GooCanvasItem *item);
+static BrowserCanvasCanvasShape *browser_canvas_canvas_shape_find (GSList *list, const gchar *id);
+
+
+/*
+ * Computes the points' coordinates of the line going from
+ * @ref_pk_ent to @fk_ent (which are themselves rectangles)
+ *
+ * if @shapes is not NULL, then the shapes in the list are reused, and the ones which don't need
+ * to exist anymore are removed
+ *
+ * Returns a list of BrowserCanvasCanvasShapes structures
+ */
+GSList *
+browser_canvas_util_compute_anchor_shapes (GooCanvasItem *parent, GSList *shapes,
+					   BrowserCanvasTable *fk_ent, BrowserCanvasTable *ref_pk_ent, 
+					   guint nb_anchors, guint ext, gboolean with_handle)
+{
+	GSList *retval = shapes;
+	guint i;
+	gdouble fx1, fy1, fx2, fy2; /* FK entity item (bounds) */
+	gdouble rx1, ry1, rx2, ry2; /* REF PK entity item  (bounds) */
+
+	BrowserCanvasCanvasShape *shape;
+	gchar *id;
+
+	gdouble rcx, rcy; /* center of ref_pk entity item */
+	gdouble cx, cy;
+
+	gdouble rux, ruy; /* current ref_pk point for the arrow line */
+	gdouble dx, dy; /* increments to compute the new ref_pk point for the arrow line */
+	GooCanvasBounds bounds;
+
+	g_return_val_if_fail (nb_anchors > 0, NULL);
+
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (fk_ent), &bounds);
+	fx1 = bounds.x1;
+	fy1 = bounds.y1;
+	fx2 = bounds.x2;
+	fy2 = bounds.y2;
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ref_pk_ent), &bounds);
+	rx1 = bounds.x1;
+	ry1 = bounds.y1;
+	rx2 = bounds.x2;
+	ry2 = bounds.y2;
+
+	/* compute the cx, cy, dx and dy values */
+	rcx = (rx1 + rx2) / 2.;
+	rcy = (ry1 + ry2) / 2.;
+	cx = (fx1 + fx2) / 2.;
+	cy = (fy1 + fy2) / 2.;
+	rux = rcx;
+	ruy = rcy;
+	dx = 0;
+	dy = 0;
+
+	for (i = 0; i < nb_anchors; i++) {
+		/* TODO:
+		   - detect tables overlapping
+		*/		
+		if ((rcx == cx) && (rcy == cy)) {
+			/* tables have the same center (includes case when they are equal) */
+			gdouble Dy, Dx;
+			GooCanvasPoints *ap, *points;
+			GooCanvasItem *item;
+			
+			points = goo_canvas_points_new (4);
+			ap = goo_canvas_points_new (4);
+
+			Dy = (ry2 - ry1) / 2. / (gdouble ) (nb_anchors + 1) * (gdouble) (i + 1);
+			Dx = (rx2 - rx1) * (0.8 + 0.1 * i);
+			if (! compute_intersect_rect_line (rx1, ry1, rx2, ry2,
+							   cx, cy, cx + Dx, cy - Dy,
+							   &(ap->coords[0]), &(ap->coords[1]),
+							   &(ap->coords[2]), &(ap->coords[3])))
+				return retval;
+			
+			if (ap->coords[0] > ap->coords[2]) {
+				points->coords[0] = ap->coords[0];
+				points->coords[1] = ap->coords[1];
+			}
+			else {
+				points->coords[0] = ap->coords[2];
+				points->coords[1] = ap->coords[3];
+			}
+
+			points->coords[2] = cx + Dx;
+			points->coords[3] = cy - Dy;
+
+			Dy = (fy2 - fy1) / 2. / (gdouble ) (nb_anchors + 1) * (gdouble) (i + 1);
+			Dx = (fx2 - fx1) * (0.8 + 0.1 * i);
+			points->coords[4] = cx + Dx;
+			points->coords[5] = cy + Dy;
+
+			if (! compute_intersect_rect_line (fx1, fy1, fx2, fy2,
+							   cx, cy, cx + Dx, cy + Dy,
+							   &(ap->coords[0]), &(ap->coords[1]),
+							   &(ap->coords[2]), &(ap->coords[3])))
+				return retval;
+			
+			if (ap->coords[0] > ap->coords[2]) {
+				points->coords[6] = ap->coords[0];
+				points->coords[7] = ap->coords[1];
+			}
+			else {
+				points->coords[6] = ap->coords[2];
+				points->coords[7] = ap->coords[3];
+			}
+			
+			id = g_strdup_printf ("a%d", i);
+			shape = browser_canvas_canvas_shape_find (retval, id);
+			if (shape) {
+				g_object_set (shape->item, "points", points, NULL);
+				shape->_used = TRUE;
+				g_free (id);
+			}
+			else {
+				item = goo_canvas_polyline_new_line (parent, 
+								     points->coords[0], points->coords [1],
+								     points->coords[2], points->coords [3],
+								     "close-path", FALSE,
+								     "points", points, NULL);
+				retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+			}
+			goo_canvas_points_unref (ap);
+			
+			/* extension marks as text */
+			if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_1) {
+				id = g_strdup_printf ("a%de1", i);
+				shape = browser_canvas_canvas_shape_find (retval, id);
+				if (shape) {
+					g_object_set (shape->item, 
+						      "x", points->coords[2] + 5.,
+						      "y", points->coords[3] - 5., NULL);
+					shape->_used = TRUE;
+					g_free (id);
+				}
+				else {
+					item = goo_canvas_text_new (parent, "*", 
+								    points->coords[2] + 5.,
+								    points->coords[3] - 5., -1,
+								    GTK_ANCHOR_SOUTH, NULL);
+					retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+				}
+			}
+
+			if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_2) {
+				id = g_strdup_printf ("a%de2", i);
+				if (shape) {
+					g_object_set (shape->item, 
+						      "x", points->coords[4] + 5.,
+						      "y", points->coords[5] + 5., NULL);
+					shape->_used = TRUE;
+					g_free (id);
+				}
+				else {
+					item = goo_canvas_text_new (parent, "*", 
+								    points->coords[4] + 5.,
+								    points->coords[5] + 5., -1,
+								    GTK_ANCHOR_NORTH, NULL);
+					retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+				}
+			}
+			
+			/* handle in the middle */
+			if (with_handle)
+				retval = browser_canvas_util_compute_handle_shapes (parent, retval, i, 
+										  points->coords[2], 
+										  points->coords[3], 
+										  points->coords[4], 
+										  points->coords[5]);
+			goo_canvas_points_unref (points);
+		}
+		else {
+			GooCanvasPoints *ap, *points;
+			GooCanvasItem *item;
+
+			points = goo_canvas_points_new (2);
+			ap = goo_canvas_points_new (4);
+
+			if (nb_anchors > 1) {
+				if ((dx == 0) && (dy == 0)) {
+					/* compute perpendicular to D={(rcx, rcy), (cx, cy)} */
+					gdouble vx = (rcx - cx), vy = (rcy - cy);
+					gdouble tmp;
+					
+					tmp = vx;
+					vx = vy;
+					vy = - tmp;
+					
+					/* compute intersect of ref_pkey rectangle and D=[vx, vy] passing at (rcx, rcy) */
+					if (! compute_intersect_rect_line (rx1, ry1, rx2, ry2,
+									   rcx, rcy, rcx + vx, rcy + vy,
+									   &(ap->coords[0]), &(ap->coords[1]),
+									   &(ap->coords[2]), &(ap->coords[3])))
+						return retval;
+					dx = (ap->coords[2] - ap->coords[0]) / (gdouble) (nb_anchors  + 1);
+					dy = (ap->coords[3] - ap->coords[1]) / (gdouble) (nb_anchors  + 1);
+					rux = ap->coords[0];
+					ruy = ap->coords[1];
+				}
+
+				rux += dx;
+				ruy += dy;
+			}
+			
+			/* compute the 4 intersection points */
+			if (! compute_intersect_rect_line (rx1, ry1, rx2, ry2,
+							   rux, ruy, cx, cy,
+							   &(ap->coords[0]), &(ap->coords[1]),
+							   &(ap->coords[2]), &(ap->coords[3])))
+				return retval;
+			if (! compute_intersect_rect_line (fx1, fy1, fx2, fy2,
+							   rux, ruy, cx, cy,
+							   &(ap->coords[4]), &(ap->coords[5]),
+							   &(ap->coords[6]), &(ap->coords[7])))
+				return retval;
+			
+			/* choosing between point coords(0,1) and coords(2,3) */
+			if (((ap->coords[0] - ap->coords[4]) * (ap->coords[0] - ap->coords[4]) + 
+			     (ap->coords[1] - ap->coords[5]) * (ap->coords[1] - ap->coords[5])) <
+			    ((ap->coords[2] - ap->coords[4]) * (ap->coords[2] - ap->coords[4]) + 
+			     (ap->coords[3] - ap->coords[5]) * (ap->coords[3] - ap->coords[5]))) {
+				points->coords[0] = ap->coords[0];
+				points->coords[1] = ap->coords[1];
+			}
+			else {
+				points->coords[0] = ap->coords[2];
+				points->coords[1] = ap->coords[3];
+			}
+			
+			/* choosing between point coords(4,5) and coords(6,7) */
+			if (((points->coords[0] - ap->coords[4]) * (points->coords[0] - ap->coords[4]) +
+			     (points->coords[1] - ap->coords[5]) * (points->coords[1] - ap->coords[5])) <
+			    ((points->coords[0] - ap->coords[6]) * (points->coords[0] - ap->coords[6]) +
+			     (points->coords[1] - ap->coords[7]) * (points->coords[1] - ap->coords[7]))) {
+				points->coords[2] = ap->coords[4];
+				points->coords[3] = ap->coords[5];
+			}
+			else {
+				points->coords[2] = ap->coords[6];
+				points->coords[3] = ap->coords[7];
+			}
+			
+			id = g_strdup_printf ("a%d", i);
+			shape = browser_canvas_canvas_shape_find (retval, id);
+			if (shape) {
+				g_object_set (shape->item, "points", points, NULL);
+				shape->_used = TRUE;
+				g_free (id);
+			}
+			else {
+				item = goo_canvas_polyline_new_line (parent, 
+								     points->coords[0], points->coords [1],
+								     points->coords[2], points->coords [3],
+								     "close-path", FALSE,
+								     "points", points, NULL);
+				retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+			}
+			goo_canvas_points_unref (ap);
+
+			/* extension marks as text */
+			if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_1) {
+				gdouble mxoff = 0., myoff = 0.;
+				GtkAnchorType atype;
+
+				compute_text_marks_offsets (points->coords[0], points->coords[1], 
+							    points->coords[2], points->coords[3],
+							    &mxoff, &myoff, &atype);
+				id = g_strdup_printf ("a%de1", i);
+				shape = browser_canvas_canvas_shape_find (retval, id);
+				if (shape) {
+					g_object_set (shape->item, 
+						      "x", points->coords[2] + mxoff,
+						      "y", points->coords[3] + myoff, 
+						      "anchor", atype, NULL);
+					shape->_used = TRUE;
+					g_free (id);
+				}
+				else {
+					item = goo_canvas_text_new (parent, "*", 
+								    points->coords[2] + mxoff,
+								    points->coords[3] + myoff, -1,
+								    atype, NULL);
+					retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+				}
+			}
+
+			if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_2) {
+				gdouble mxoff, myoff;
+				GtkAnchorType atype;
+				
+				compute_text_marks_offsets (points->coords[2], points->coords[3], 
+							    points->coords[0], points->coords[1],
+							    &mxoff, &myoff, &atype);
+
+				id = g_strdup_printf ("a%de2", i);
+				if (shape) {
+					g_object_set (shape->item, 
+						      "x", points->coords[0] + mxoff,
+						      "y", points->coords[1] + myoff, 
+						      "anchor", atype, NULL);
+					shape->_used = TRUE;
+					g_free (id);
+				}
+				else {
+					item = goo_canvas_text_new (parent, "*", 
+								    points->coords[0] + mxoff,
+								    points->coords[1] + myoff, -1,
+								    atype, NULL);
+					retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+				}
+			}
+
+			/* handle in the middle */
+			if (with_handle)
+				retval = browser_canvas_util_compute_handle_shapes (parent, retval, i,
+										  points->coords[0], 
+										  points->coords[1], 
+										  points->coords[2], 
+										  points->coords[3]);
+			goo_canvas_points_unref (points);
+		}
+	}
+
+	return retval;
+}
+
+/*
+ * Computes the position offsets, relative to X2=(x2, y2) of a text to be written
+ * close to the X2 point, also computes the anchor type
+ */
+static void
+compute_text_marks_offsets (gdouble x1, gdouble y1, gdouble x2, gdouble y2,
+			    gdouble *xoff, gdouble *yoff, GtkAnchorType *anchor_type)
+{
+	gdouble mxoff, myoff;
+	GtkAnchorType atype = GTK_ANCHOR_CENTER; /* FIXME */
+	gdouble sint, cost;
+	gdouble sina = 0.5;
+	gdouble cosa = 0.866025; /* sqrt(3)/2 */
+	gdouble hyp;
+	gdouble d = 15.;
+
+	hyp = sqrt ((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
+	sint = - (y2 - y1) / hyp;
+	cost = (x2 - x1) / hyp;
+	
+	mxoff = -d * (sina * sint + cosa * cost);
+	myoff = -d * (sina * cost - cosa * sint);
+
+	if (xoff)
+		*xoff = mxoff;
+	if (yoff)
+		*yoff = myoff;
+	if (anchor_type)
+		*anchor_type = atype;
+}
+
+/*
+ * Computes the points of intersection between a rectangle (defined by the first 2 points)
+ * and a line (defined by the next 2 points).
+ *
+ * The result is returned in place of the line's point definition
+ *
+ *             --------- -----   D1
+ *             |       |
+ *             |       |
+ *             |       |
+ *             |       |
+ *             |       |
+ *             |       |
+ *             |       |
+ *             --------- ----   D2
+ *             
+ *             |       |
+ *             |       |
+ *             D3      D4
+ *
+ * Returns: TRUE if the line crosses the rectangle, and FALSE if it misses it.
+ */
+static gboolean
+compute_intersect_rect_line (gdouble rectx1, gdouble recty1, gdouble rectx2, gdouble recty2,
+			     gdouble P1x, gdouble P1y, gdouble P2x, gdouble P2y,
+			     gdouble *R1x, gdouble *R1y, gdouble *R2x, gdouble *R2y)
+{
+	gboolean retval = FALSE;
+	gboolean rotated = FALSE;
+	gdouble a=0.; /* line slope   y = a x + b */
+	gdouble b;    /* line offset  y = a x + b */
+	gdouble offset = 2.;
+		
+	gdouble ptsx[4]; /* points' X coordinate: 0 for intersect with D1, 1 for D2,... */
+	gdouble ptsy[4]; /* points' Y coordinate */
+
+	if ((rectx1 == rectx2) && (recty1 == recty2))
+		return FALSE;
+	if ((rectx1 >= rectx2) || (recty1 >= recty2))
+		return FALSE;
+	if ((P1x == P2x) && (P1y == P2y))
+		return FALSE;
+
+	/* rotate the coordinates to invert X and Y to avoid rounding problems ? */
+	if (P1x != P2x)
+		a = (P1y - P2y) / (P1x - P2x);
+	if ((P1x == P2x) || (fabs (a) > 1)) {
+		gdouble tmp;
+		rotated = TRUE;
+		tmp = rectx1; rectx1 = recty1; recty1 = tmp;
+		tmp = rectx2; rectx2 = recty2; recty2 = tmp;
+		tmp = P1x; P1x = P1y; P1y = tmp;
+		tmp = P2x; P2x = P2y; P2y = tmp;
+		a = (P1y - P2y) / (P1x - P2x);
+	}
+
+	/* here we have (P1x != P2x), non vertical line */
+	b = P1y - a * P1x;
+
+	if (a == 0) {
+		/* horizontal line */
+
+		if ((b <= recty2) && (b >= recty1)) {
+			retval = TRUE;
+			*R1x = rectx1 - offset; *R1y = b;
+			*R2x = rectx2 + offset; *R2y = b;
+		}
+	}
+	else {
+		gdouble retx[2] = {0., 0.};
+		gdouble rety[2] = {0., 0.};
+		gint i = 0;
+
+		/* non horizontal and non vertical line */
+		/* D1 */
+		ptsy[0] = recty1 - offset;
+		ptsx[0] = (recty1 - b) / a;
+
+		/* D2 */
+		ptsy[1] = recty2 + offset;
+		ptsx[1] = (recty2 - b) / a;
+
+		/* D3 */
+		ptsx[2] = rectx1 - offset;
+		ptsy[2] = a * rectx1 + b;
+
+		/* D4 */
+		ptsx[3] = rectx2 + offset;
+		ptsy[3] = a * rectx2 + b;
+		
+		if ((ptsx[0] >= rectx1) && (ptsx[0] <= rectx2)) {
+			retval = TRUE;
+			retx[i] = ptsx[0]; rety[i] = ptsy[0];
+			i ++;
+		}
+		if ((ptsx[1] >= rectx1) && (ptsx[1] <= rectx2)) {
+			retval = TRUE;
+			retx[i] = ptsx[1]; rety[i] = ptsy[1];
+			i ++;
+		}
+		if ((i<2) && (ptsy[2] >= recty1) && (ptsy[2] <= recty2)) {
+			retval = TRUE;
+			retx[i] = ptsx[2]; rety[i] = ptsy[2];
+			i ++;
+		}
+		if ((i<2) && (ptsy[3] >= recty1) && (ptsy[3] <= recty2)) {
+			retval = TRUE;
+			retx[i] = ptsx[3]; rety[i] = ptsy[3];
+			i++;
+		}
+
+		if (retval) {
+			g_assert (i == 2); /* wee need 2 points! */
+			*R1x = retx[0]; *R1y = rety[0];
+			*R2x = retx[1]; *R2y = rety[1];
+		}
+	}
+
+	if (retval && rotated) {
+		/* rotate it back */
+		gdouble tmp;
+
+		tmp = *R1x; *R1x = *R1y; *R1y = tmp;
+		tmp = *R2x; *R2x = *R2y; *R2y = tmp;
+	}
+
+	return retval;
+}
+
+/*
+ * Compute the anchor shapes to link field1 to field2 from ent1 to ent2
+ *
+ * if @shapes is not NULL, then the shapes in the list are reused, and the ones which don't need
+ * to exist anymore are removed
+ *
+ * Returns a list of BrowserCanvasCanvasShapes structures
+ */
+GSList *
+browser_canvas_util_compute_connect_shapes (GooCanvasItem *parent, GSList *shapes,
+					  BrowserCanvasTable *ent1, GdaMetaTableColumn *field1, 
+					  BrowserCanvasTable *ent2, GdaMetaTableColumn *field2,
+					  guint nb_connect, guint ext)
+{
+	GSList *retval = shapes;
+	GooCanvasItem *item;
+	GooCanvasPoints *points;
+	gdouble xl1, xr1, xl2, xr2, yt1, yt2; /* X boundings and Y top of ent1 and ent2 */
+	gdouble x1, x2; /* X positions of the lines extremities close to ent1 and ent2 */
+	gdouble x1offset, x2offset; /* offsets for the horizontal part of the lines */
+	double sq = 5.;
+	double eps = 0.5;
+	GooCanvasBounds bounds;
+
+	BrowserCanvasCanvasShape *shape;
+	gchar *id;
+
+	if (!field1 || !field2)
+		return browser_canvas_util_compute_anchor_shapes (parent, shapes, ent1, ent2, 1, ext, FALSE);
+
+	/* line made of 4 points */
+	points = goo_canvas_points_new (4);
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ent1), &bounds);
+	xl1 = bounds.x1;
+	yt1 = bounds.y1;
+	xr1 = bounds.x2;
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ent2), &bounds);
+	xl2 = bounds.x1;
+	yt2 = bounds.y1;
+	xr2 = bounds.x2;
+
+	if (xl2 > xr1) {
+		x1 = xr1 + eps;
+		x2 = xl2 - eps;
+		x1offset = 2 * sq;
+		x2offset = -2 * sq;
+	}
+	else {
+		if (xl1 >= xr2) {
+			x1 = xl1 - eps;
+			x2 = xr2 + eps;
+			x1offset = - 2 * sq;
+			x2offset = 2 * sq;
+		}
+		else {
+			if ((xl1 + xr1) < (xl2 + xr2)) {
+				x1 = xl1 - eps;
+				x2 = xl2 - eps;
+				x1offset = -2 * sq;
+				x2offset = -2 * sq;
+			}
+			else {
+				x1 = xr1 + eps;
+				x2 = xr2 + eps;
+				x1offset = 2 * sq;
+				x2offset = 2 * sq;
+			}
+		}
+	}
+
+	points->coords[0] = x1;
+	points->coords[1] = browser_canvas_table_get_column_ypos (ent1, field1) + yt1;
+
+	points->coords[2] = x1 + x1offset;
+	points->coords[3] = points->coords[1];
+
+	points->coords[4] = x2 + x2offset;
+	points->coords[5] = browser_canvas_table_get_column_ypos (ent2, field2) + yt2;
+
+	points->coords[6] = x2;
+	points->coords[7] = points->coords[5];
+
+	id = g_strdup_printf ("c%d", nb_connect);
+	shape = browser_canvas_canvas_shape_find (retval, id);
+	if (shape) {
+		g_object_set (shape->item, "points", points, NULL);
+		shape->_used = TRUE;
+		g_free (id);
+	}
+	else {
+		item = goo_canvas_polyline_new_line (parent, 
+						     points->coords[0], points->coords [1],
+						     points->coords[2], points->coords [3],
+						     "close-path", FALSE,
+						     "points", points, NULL);
+		retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+	}
+
+	/* extension marks as text */
+	if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_1) {
+		gdouble mxoff = 0., myoff = 0.;
+		GtkAnchorType atype;
+		
+		compute_text_marks_offsets (points->coords[4], points->coords[5], 
+					    points->coords[2], points->coords[3],
+					    &mxoff, &myoff, &atype);
+		
+		id = g_strdup_printf ("ce%d1", nb_connect);
+		shape = browser_canvas_canvas_shape_find (retval, id);
+		if (shape) {
+			g_object_set (shape->item, 
+				      "x", points->coords[2] + mxoff,
+				      "y", points->coords[3] + myoff, 
+				      "anchor", atype, NULL);
+			shape->_used = TRUE;
+			g_free (id);
+		}
+		else {
+			item = goo_canvas_text_new (parent, "*", 
+						    points->coords[2] + mxoff,
+						    points->coords[3] + myoff, -1,
+						    atype, NULL);
+			retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+		}
+	}
+	
+	if (ext & CANVAS_SHAPE_EXT_JOIN_OUTER_2) {
+		gdouble mxoff, myoff;
+		GtkAnchorType atype;
+		
+		compute_text_marks_offsets (points->coords[2], points->coords[3], 
+					    points->coords[4], points->coords[5],
+					    &mxoff, &myoff, &atype);
+
+		id = g_strdup_printf ("ce%d2", nb_connect);
+		shape = browser_canvas_canvas_shape_find (retval, id);
+		if (shape) {
+			g_object_set (shape->item, 
+				      "x", points->coords[2] + mxoff,
+				      "y", points->coords[3] + myoff, 
+				      "anchor", atype, NULL);
+			shape->_used = TRUE;
+			g_free (id);
+		}
+		else {
+			item = goo_canvas_text_new (parent, "*", 
+						    points->coords[2] + mxoff,
+						    points->coords[3] + myoff, -1,
+						    atype, NULL);
+			retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+		}
+	}
+	
+	
+	/* handle in the middle */
+	/*retval = browser_canvas_util_compute_handle_shapes (parent, retval, nb_connect,
+							  points->coords[2], points->coords[3], 
+							  points->coords[4], points->coords[5]);*/
+	goo_canvas_points_unref (points);
+
+	return retval;
+}
+
+/*
+ * computes a "handle" in the middle of the 2 points passed as argument*
+ * 
+ * Warning: the obsolete shapes in @shapes are _not_ removed.
+ */
+GSList *
+browser_canvas_util_compute_handle_shapes (GooCanvasItem *parent, GSList *shapes, gint index,
+					 gdouble x1, gdouble y1, gdouble x2, gdouble y2)
+{
+	/* don't add anything... */
+	/*return shapes;*/
+
+	GSList *retval = shapes;
+	gdouble x, y, sq = 5.;
+	GooCanvasItem *item;
+	BrowserCanvasCanvasShape *shape;
+	gchar *id;
+
+	/* circle in the middle */
+	x = (x1 + x2) / 2.;
+	y = (y1 + y2) / 2.;
+
+	id = g_strdup_printf ("h%d", index);
+	shape = browser_canvas_canvas_shape_find (retval, id);
+	if (shape) {
+		g_object_set (shape->item, 
+			      "center-x", x, "center-y", y, 
+			      NULL);
+		shape->_used = TRUE;
+		g_free (id);
+	}
+	else {
+		item = goo_canvas_ellipse_new (parent, x, y, sq, sq, 
+					       "fill-color", "black",
+					       "visibility", GOO_CANVAS_ITEM_VISIBLE_ABOVE_THRESHOLD,
+					       "visibility-threshold", .9, NULL);
+		retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+	}
+	
+	return retval;
+}
+
+static gchar *
+points_to_path (GooCanvasPoints *points)
+{
+	GString *string;
+	gchar *path;
+	gint i;
+
+	g_return_val_if_fail (points, NULL);
+	g_return_val_if_fail (points->num_points >= 2, NULL);
+
+	string = g_string_new ("");
+	g_string_append_printf (string, "M%d %d", (int) points->coords[0], (int) points->coords[1]);
+
+	for (i = 1; i < points->num_points; i++)
+		g_string_append_printf (string, " L%d %d", 
+					(int) points->coords[2*i], (int) points->coords[2*i+1]);
+
+	path = string->str;
+	g_string_free (string, FALSE);
+	return path;
+}
+
+static GSList *
+browser_canvas_canvas_shape_add_to_list (GSList *list, gchar *swallow_id, GooCanvasItem *item)
+{
+	BrowserCanvasCanvasShape *shape = g_new (BrowserCanvasCanvasShape, 1);
+
+	g_assert (swallow_id);
+	g_assert (item);
+	shape->id = swallow_id;
+	shape->item = item;
+	shape->_used = TRUE;
+	shape->is_new = TRUE;
+
+	/*g_print ("Shape %p (%s: %s) added\n", item, swallow_id, G_OBJECT_TYPE_NAME (item));*/
+
+	return g_slist_append (list, shape);
+}
+
+static BrowserCanvasCanvasShape *
+browser_canvas_canvas_shape_find (GSList *list, const gchar *id)
+{
+	BrowserCanvasCanvasShape *shape = NULL;
+	GSList *l;
+
+	for (l = list; l && !shape; l = l->next) 
+		if (!strcmp (((BrowserCanvasCanvasShape*) l->data)->id, id))
+			shape = (BrowserCanvasCanvasShape*) l->data;
+
+	/*g_print ("Looking for shape %s: %s\n", id, shape ? "Found" : "Not found");*/
+	return shape;
+}
+
+GSList *
+browser_canvas_canvas_shapes_remove_obsolete_shapes (GSList *list)
+{
+	GSList *l, *ret = list;
+
+	for (l = list; l; ) {
+		if (((BrowserCanvasCanvasShape*)(l->data))->_used) {
+			((BrowserCanvasCanvasShape*)(l->data))->_used = FALSE;
+			l=l->next;
+		}
+		else {
+			GSList *tmp;
+			BrowserCanvasCanvasShape *shape = (BrowserCanvasCanvasShape*) l->data;
+
+			g_free (shape->id);
+			goo_canvas_item_remove (shape->item);
+			g_free (shape);
+
+			tmp = l->next;
+			ret = g_slist_delete_link (ret, l);
+			l = tmp;
+		}
+	}
+
+	return ret;
+}
+
+void
+browser_canvas_canvas_shapes_remove_all (GSList *list)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		BrowserCanvasCanvasShape *shape = (BrowserCanvasCanvasShape*) l->data;
+		
+		g_free (shape->id);
+		goo_canvas_item_remove (shape->item);
+		g_free (shape);
+	}
+
+	g_slist_free (list);
+}
+
+void
+browser_canvas_canvas_shapes_dump (GSList *list)
+{
+	GSList *l;
+	g_print ("Canvas shapes...\n");
+	for (l = list; l; l = l->next) 
+		g_print ("\tShape %s @%p (%s: %p) %s\n", BROWSER_CANVAS_CANVAS_SHAPE (l->data)->id, 
+			 BROWSER_CANVAS_CANVAS_SHAPE (l->data),
+			 G_OBJECT_TYPE_NAME (BROWSER_CANVAS_CANVAS_SHAPE (l->data)->item),
+			 BROWSER_CANVAS_CANVAS_SHAPE (l->data)->item,
+			 BROWSER_CANVAS_CANVAS_SHAPE (l->data)->_used ? "Used": "Not used");
+}
diff --git a/tools/browser/canvas/browser-canvas-utility.h b/tools/browser/canvas/browser-canvas-utility.h
new file mode 100644
index 0000000..bc5d1d4
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-utility.h
@@ -0,0 +1,51 @@
+/* browser-canvas-utility.h
+ * Copyright (C) 2007 Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <goocanvas.h>
+#include "browser-canvas-table.h"
+
+G_BEGIN_DECLS
+
+enum {
+        CANVAS_SHAPE_EXT_JOIN_OUTER_1  = 1 << 0,
+        CANVAS_SHAPE_EXT_JOIN_OUTER_2  = 1 << 1
+};
+
+typedef struct {
+	gchar         *id;
+	GooCanvasItem *item;
+	gboolean       _used;
+	gboolean       is_new;
+} BrowserCanvasCanvasShape;
+
+GSList *browser_canvas_util_compute_anchor_shapes  (GooCanvasItem *parent, GSList *shapes,
+						  BrowserCanvasTable *fk_ent, BrowserCanvasTable *ref_pk_ent,
+						  guint nb_anchors, guint ext, gboolean with_handle);
+GSList *browser_canvas_util_compute_connect_shapes (GooCanvasItem *parent, GSList *shapes,
+						  BrowserCanvasTable *ent1, GdaMetaTableColumn *field1,
+						  BrowserCanvasTable *ent2, GdaMetaTableColumn *field2, 
+						  guint nb_connect, guint ext);
+
+void    browser_canvas_canvas_shapes_dump (GSList *list);
+void    browser_canvas_canvas_shapes_remove_all (GSList *list);
+GSList *browser_canvas_canvas_shapes_remove_obsolete_shapes (GSList *list);
+
+#define BROWSER_CANVAS_CANVAS_SHAPE(x) ((BrowserCanvasCanvasShape *)(x))
+
+G_END_DECLS
diff --git a/tools/browser/canvas/browser-canvas.c b/tools/browser/canvas/browser-canvas.c
new file mode 100644
index 0000000..dfcae3d
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas.c
@@ -0,0 +1,934 @@
+/* browser-canvas.c
+ *
+ * Copyright (C) 2007 - 2008 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include "browser-canvas.h"
+#include "browser-canvas-priv.h"
+#include "browser-canvas-item.h"
+#include "browser-canvas-print.h"
+#include <libgda/libgda.h>
+#ifndef CANVAS_EXAMPLE
+#include "../support.h"
+#endif
+
+#define DEFAULT_SCALE .8
+#ifdef HAVE_GRAPHVIZ
+#include <stddef.h>
+#include <gvc.h>
+#ifndef ND_coord_i
+    #define ND_coord_i ND_coord
+#endif
+static GVC_t* gvc = NULL;
+#endif
+#include <cairo.h>
+#include <cairo-svg.h>
+#include <math.h>
+
+static void browser_canvas_class_init (BrowserCanvasClass *klass);
+static void browser_canvas_init       (BrowserCanvas *canvas);
+static void browser_canvas_dispose    (GObject *object);
+static void browser_canvas_finalize   (GObject *object);
+
+static void browser_canvas_set_property (GObject *object,
+					 guint param_id,
+					 const GValue *value,
+					 GParamSpec *pspec);
+static void browser_canvas_get_property (GObject *object,
+					 guint param_id,
+					 GValue *value,
+					 GParamSpec *pspec);
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *parent_class = NULL;
+
+enum
+{
+	LAST_SIGNAL
+};
+
+enum
+{
+	PROP_0,
+};
+
+static gint canvas_signals[LAST_SIGNAL] = { };
+
+GType
+browser_canvas_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (BrowserCanvasClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) browser_canvas_class_init,
+			NULL,
+			NULL,
+			sizeof (BrowserCanvas),
+			0,
+			(GInstanceInitFunc) browser_canvas_init
+		};		
+
+		type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW, "BrowserCanvas", &info, 0);
+	}
+	return type;
+}
+
+static void
+browser_canvas_class_init (BrowserCanvasClass *klass)
+{
+	GtkWidgetClass *widget_class;
+	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	widget_class = (GtkWidgetClass *) klass;
+
+	/* properties */
+	object_class->set_property = browser_canvas_set_property;
+	object_class->get_property = browser_canvas_get_property;
+
+	/* virtual functions */
+	klass->clean_canvas_items = NULL;
+	klass->build_context_menu = NULL;
+
+	object_class->dispose = browser_canvas_dispose;
+	object_class->finalize = browser_canvas_finalize;
+}
+
+static int canvas_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas);
+static void drag_begin_cb (BrowserCanvas *canvas, GdkDragContext *drag_context, GooCanvas *gcanvas);
+static void drag_data_get_cb (BrowserCanvas *canvas, GdkDragContext   *drag_context,
+			      GtkSelectionData *data, guint info,
+			      guint time, GooCanvas *gcanvas);
+static void drag_data_received_cb (BrowserCanvas *canvas, GdkDragContext *context,
+				   gint x, gint y, GtkSelectionData *data,
+				   guint info, guint time, GooCanvas *gcanvas);
+static gboolean idle_add_canvas_cb (BrowserCanvas *canvas);
+static void
+browser_canvas_init (BrowserCanvas *canvas)
+{
+	canvas->priv = g_new0 (BrowserCanvasPrivate, 1);
+
+	canvas->priv->goocanvas = GOO_CANVAS (goo_canvas_new ());
+	gtk_widget_show (GTK_WIDGET (canvas->priv->goocanvas));
+	g_object_set_data (G_OBJECT (canvas->priv->goocanvas), "browsercanvas", canvas);
+
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (canvas), 
+					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (canvas), GTK_SHADOW_NONE);
+	g_idle_add ((GSourceFunc) idle_add_canvas_cb, canvas);
+	canvas->priv->items = NULL;
+
+	canvas->xmouse = 50.;
+	canvas->ymouse = 50.;
+	
+	g_signal_connect (canvas, "event",
+			  G_CALLBACK (canvas_event_cb), canvas->priv->goocanvas);
+	g_signal_connect (canvas, "drag-begin",
+			  G_CALLBACK (drag_begin_cb), canvas->priv->goocanvas);
+	g_signal_connect (canvas, "drag-data-get",
+			  G_CALLBACK (drag_data_get_cb), canvas->priv->goocanvas);
+	g_signal_connect (canvas, "drag-data-received",
+			  G_CALLBACK (drag_data_received_cb), canvas->priv->goocanvas);
+	g_object_set (G_OBJECT (canvas->priv->goocanvas),
+		      "automatic-bounds", TRUE,
+		      "bounds-padding", 5., 
+		      "bounds-from-origin", FALSE, 
+		      "anchor", GTK_ANCHOR_CENTER, NULL);
+
+	/* reseting the zoom */
+	goo_canvas_set_scale (canvas->priv->goocanvas, DEFAULT_SCALE);
+}
+
+static gboolean
+idle_add_canvas_cb (BrowserCanvas *canvas)
+{
+	gtk_container_add (GTK_CONTAINER (canvas), GTK_WIDGET (canvas->priv->goocanvas));
+	return FALSE;
+}
+
+static void
+drag_begin_cb (BrowserCanvas *canvas, GdkDragContext *drag_context, GooCanvas *gcanvas)
+{
+	BrowserCanvasItem *citem;
+
+	citem = g_object_get_data (G_OBJECT (canvas), "__drag_src_item");
+	if (citem) {
+		/*
+		gtk_drag_source_set_icon_pixbuf (GTK_WIDGET (canvas),
+						 browser_get_pixbuf_icon (BROWSER_ICON_TABLE));
+		*/
+	}
+}
+
+static void
+drag_data_get_cb (BrowserCanvas *canvas, GdkDragContext *drag_context,
+		  GtkSelectionData *data, guint info,
+		  guint time, GooCanvas *gcanvas)
+{
+	BrowserCanvasItem *citem;
+
+	citem = g_object_get_data (G_OBJECT (canvas), "__drag_src_item");
+	if (citem) {
+		BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (citem));
+		if (iclass->drag_data_get)
+			iclass->drag_data_get (citem, drag_context, data, info, time);
+	}
+}
+
+static void
+drag_data_received_cb (BrowserCanvas *canvas, GdkDragContext *context,
+		       gint x, gint y, GtkSelectionData *data,
+		       guint info, guint time, GooCanvas *gcanvas)
+{
+	GooCanvasItem *item;
+	item = goo_canvas_get_item_at (gcanvas, x, y, TRUE);
+	if (item) {
+		g_print ("Dragged into %s\n", G_OBJECT_TYPE_NAME (item));
+		gtk_drag_finish (context, TRUE, FALSE, time);
+	}
+	else {
+		gtk_drag_finish (context, FALSE, FALSE, time);
+	}
+}
+
+
+static void popup_zoom_in_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static void popup_zoom_out_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static void popup_zoom_fit_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static void popup_export_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static void popup_print_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static int
+canvas_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas)
+{
+	gboolean done = TRUE;
+	GooCanvasItem *item;
+	BrowserCanvasClass *class = BROWSER_CANVAS_CLASS (G_OBJECT_GET_CLASS (canvas));
+	gdouble x, y;
+
+	switch (event->type) {
+	case GDK_BUTTON_PRESS:
+		x = ((GdkEventButton *) event)->x;
+		y = ((GdkEventButton *) event)->y;
+		goo_canvas_convert_from_pixels (gcanvas, &x, &y);
+		item = goo_canvas_get_item_at (gcanvas, x, y, TRUE);
+
+		if (!item) {
+			if ((((GdkEventButton *) event)->button == 3) && (class->build_context_menu)) {
+				GtkWidget *menu, *mitem;
+				
+				canvas->xmouse = x;
+				canvas->ymouse = y;
+
+				/* extra menu items, if any */
+				menu = (class->build_context_menu) (canvas);
+				
+				/* default menu items */
+				if (!menu)
+					menu = gtk_menu_new ();
+				else {
+					mitem = gtk_separator_menu_item_new ();
+					gtk_widget_show (mitem);
+					gtk_menu_append (menu, mitem);
+				}
+				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_IN, NULL);
+				gtk_widget_show (mitem);
+				gtk_menu_append (menu, mitem);
+				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_in_cb), canvas);
+				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_OUT, NULL);
+				gtk_widget_show (mitem);
+				gtk_menu_append (menu, mitem);
+				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_out_cb), canvas);
+				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_FIT, NULL);
+				gtk_widget_show (mitem);
+				gtk_menu_append (menu, mitem);
+				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_zoom_fit_cb), canvas);
+
+				mitem = gtk_separator_menu_item_new ();
+				gtk_widget_show (mitem);
+				gtk_menu_append (menu, mitem);
+				
+				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
+				gtk_widget_show (mitem);
+				gtk_menu_append (menu, mitem);
+				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_export_cb), canvas);
+
+				mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PRINT, NULL);
+				gtk_widget_show (mitem);
+				g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_print_cb), canvas);
+				gtk_menu_append (menu, mitem);
+
+				gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+						NULL, NULL, ((GdkEventButton *)event)->button,
+						((GdkEventButton *)event)->time);
+			}
+		}
+		done = TRUE;
+		break;
+	default:
+		done = FALSE;
+		break;
+	}
+	return done;	
+}
+
+static void
+popup_zoom_in_cb (GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+	browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) + .05);
+}
+
+static void
+popup_zoom_out_cb (GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+	browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) - .05);
+}
+
+static void
+popup_zoom_fit_cb (GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+	browser_canvas_fit_zoom_factor (canvas);
+}
+
+static void
+popup_export_cb (GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+	GtkWidget *dlg;
+	gint result;
+	GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (canvas));
+	GtkFileFilter *filter;
+
+#define MARGIN 5.
+
+	if (!GTK_WIDGET_TOPLEVEL (toplevel))
+		toplevel = NULL;
+
+	dlg = gtk_file_chooser_dialog_new (_("Save diagram as"), (GtkWindow*) toplevel,
+					   GTK_FILE_CHOOSER_ACTION_SAVE, 
+					   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+					   NULL);
+	filter = gtk_file_filter_new ();
+	gtk_file_filter_set_name (filter, _("PNG Image"));
+	gtk_file_filter_add_mime_type (filter, "image/png");
+	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
+
+	filter = gtk_file_filter_new ();
+	gtk_file_filter_set_name (filter, _("SVG file"));
+	gtk_file_filter_add_mime_type (filter, "image/svg+xml");
+	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
+
+	result = gtk_dialog_run (GTK_DIALOG (dlg));
+	if (result == GTK_RESPONSE_ACCEPT) {
+		gchar *filename;
+		gchar *lcfilename;
+		cairo_surface_t *surface = NULL;
+
+		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));
+		if (filename) {
+			GooCanvasBounds bounds;
+			gdouble width, height;
+			gchar *error = NULL;
+			enum {
+				OUT_UNKNOWN,
+				OUT_PNG,
+				OUT_SVG
+			} otype = OUT_UNKNOWN;
+
+			goo_canvas_item_get_bounds (goo_canvas_get_root_item (canvas->priv->goocanvas), &bounds);
+			width = (bounds.x2 - bounds.x1) + 2. * MARGIN;
+			height = (bounds.y2 - bounds.y1) + 2. * MARGIN;
+			
+			lcfilename = g_ascii_strdown (filename, -1);
+			if (g_str_has_suffix (lcfilename, "png")) {
+				otype = OUT_PNG;
+				surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+			}
+			if (g_str_has_suffix (lcfilename, "svg")) {
+				cairo_status_t status;
+				otype = OUT_SVG;
+				surface = cairo_svg_surface_create (filename, width, height);
+				status = cairo_surface_status (surface);
+				if (status != CAIRO_STATUS_SUCCESS) {
+					error = g_strdup_printf ("<b>%s</b>:\n%s",
+								 _("Failed to create SVG file"), 
+								 cairo_status_to_string (status));
+					cairo_surface_destroy (surface);
+					surface = NULL;
+				}
+			}
+			if (otype == OUT_UNKNOWN)
+				error = g_strdup_printf ("<b>%s</b>",
+							 _("File format to save to is not recognized."));
+			
+			if (surface) {
+				cairo_t *cr;
+				cairo_status_t status;
+
+				cr = cairo_create (surface);
+				cairo_set_antialias (cr, CAIRO_ANTIALIAS_GRAY);
+				cairo_set_line_width (cr, goo_canvas_get_default_line_width (canvas->priv->goocanvas));
+				cairo_translate (cr, MARGIN - bounds.x1, MARGIN - bounds.y1);
+
+				goo_canvas_render (GOO_CANVAS (canvas), cr, NULL, 0.8);
+
+				cairo_show_page (cr);
+
+				switch (otype) {
+				case OUT_PNG:
+					status = cairo_surface_write_to_png (surface, filename);
+					if (status != CAIRO_STATUS_SUCCESS)
+						error = g_strdup_printf ("<b>%s</b>:\n%s",
+									 _("Failed to create PNG file"), 
+									 cairo_status_to_string (status));
+					break;
+				default:
+					break;
+				}
+
+				cairo_surface_destroy (surface);
+				cairo_destroy (cr);
+			}
+
+			if (error) {
+				GtkWidget *errdlg;
+
+				errdlg = gtk_message_dialog_new_with_markup ((GtkWindow*) toplevel,
+									     GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, 
+									     GTK_BUTTONS_CLOSE, "%s", error);
+				g_free (error);
+				gtk_dialog_run (GTK_DIALOG (errdlg));
+				gtk_widget_destroy (errdlg);
+			}
+				
+			g_free (filename);
+			g_free (lcfilename);
+		}
+	}
+	gtk_widget_destroy (dlg);
+}
+
+static void
+popup_print_cb (GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+	browser_canvas_print (canvas);
+}
+
+static void item_destroyed_cb (BrowserCanvasItem *item, BrowserCanvas *canvas);
+static void
+browser_canvas_dispose (GObject   * object)
+{
+	BrowserCanvas *canvas;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS (object));
+
+	canvas = BROWSER_CANVAS (object);
+
+	/* get rid of the GooCanvasItems */
+	if (canvas->priv->items) {
+		GSList *list;
+		for (list = canvas->priv->items; list; list = list->next) {
+			g_signal_handlers_disconnect_by_func (G_OBJECT (list->data), G_CALLBACK (item_destroyed_cb), canvas);
+		}
+		g_slist_free (canvas->priv->items);
+		canvas->priv->items = NULL;
+	}
+
+	/* for the parent class */
+	parent_class->dispose (object);
+}
+
+/**
+ * browser_canvas_declare_item
+ * @canvas: a #BrowserCanvas widget
+ * @item: a #BrowserCanvasItem object
+ *
+ * Declares @item to be listed by @canvas as one of its items.
+ */
+void
+browser_canvas_declare_item (BrowserCanvas *canvas, BrowserCanvasItem *item)
+{
+        g_return_if_fail (IS_BROWSER_CANVAS (canvas));
+        g_return_if_fail (canvas->priv);
+        g_return_if_fail (IS_BROWSER_CANVAS_ITEM (item));
+
+	g_print ("%s (canvas=>%p, item=>%p)\n", __FUNCTION__, canvas, item);
+        if (g_slist_find (canvas->priv->items, item))
+                return;
+
+        canvas->priv->items = g_slist_prepend (canvas->priv->items, item);
+        g_signal_connect (G_OBJECT (item), "destroy",
+                          G_CALLBACK (item_destroyed_cb), canvas);
+}
+
+static void
+item_destroyed_cb (BrowserCanvasItem *item, BrowserCanvas *canvas)
+{
+	g_print ("%s (canvas=>%p, item=>%p)\n", __FUNCTION__, canvas, item);
+        g_return_if_fail (g_slist_find (canvas->priv->items, item));
+        g_signal_handlers_disconnect_by_func (G_OBJECT (item), G_CALLBACK (item_destroyed_cb), canvas);
+        canvas->priv->items = g_slist_remove (canvas->priv->items, item);
+}
+
+
+static void
+browser_canvas_finalize (GObject *object)
+{
+	BrowserCanvas *canvas;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_BROWSER_CANVAS (object));
+	canvas = BROWSER_CANVAS (object);
+
+	if (canvas->priv) {
+		g_free (canvas->priv);
+		canvas->priv = NULL;
+	}
+
+	/* for the parent class */
+	parent_class->finalize (object);
+}
+
+
+static void 
+browser_canvas_set_property (GObject *object,
+			   guint param_id,
+			   const GValue *value,
+			   GParamSpec *pspec)
+{
+	BrowserCanvas *canvas;
+	
+	canvas = BROWSER_CANVAS (object);
+
+	switch (param_id) {
+	}
+}
+
+static void
+browser_canvas_get_property (GObject *object,
+			   guint param_id,
+			   GValue *value,
+			   GParamSpec *pspec)
+{
+	BrowserCanvas *canvas;
+	
+	canvas = BROWSER_CANVAS (object);
+
+	switch (param_id) {
+	}
+}
+
+/**
+ * browser_canvas_set_zoom_factor
+ * @canvas: a #BrowserCanvas widget
+ * @n: the zoom factor
+ *
+ * Sets the zooming factor of a canvas by specifying the number of pixels that correspond 
+ * to one canvas unit. A zoom factor of 1.0 is the default value; greater than 1.0 makes a zoom in
+ * and lower than 1.0 makes a zoom out.
+ */
+void
+browser_canvas_set_zoom_factor (BrowserCanvas *canvas, gdouble n)
+{
+	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
+	g_return_if_fail (canvas->priv);
+
+	goo_canvas_set_scale (canvas->priv->goocanvas, n);
+}
+
+/**
+ * browser_canvas_get_zoom_factor
+ * @canvas: a #BrowserCanvas widget
+ *
+ * Get the current zooming factor of a canvas.
+ *
+ * Returns: the zooming factor.
+ */
+gdouble
+browser_canvas_get_zoom_factor (BrowserCanvas *canvas)
+{
+	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), 1.);
+	g_return_val_if_fail (canvas->priv, 1.);
+
+	return goo_canvas_get_scale (canvas->priv->goocanvas);
+}
+
+/**
+ * browser_canvas_fit_zoom_factor
+ * @canvas: a #BrowserCanvas widget
+ *
+ * Compute and set the correct zoom factor so that all the items on @canvas can be displayed
+ * at once.
+ *
+ * Returns: the new zooming factor.
+ */
+gdouble
+browser_canvas_fit_zoom_factor (BrowserCanvas *canvas)
+{
+	gdouble zoom, xall, yall;
+	GooCanvasBounds bounds;
+
+	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), 1.);
+	g_return_val_if_fail (canvas->priv, 1.);
+
+	xall = GTK_WIDGET (canvas)->allocation.width;
+	yall = GTK_WIDGET (canvas)->allocation.height;
+
+	goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (goo_canvas_get_root_item (canvas->priv->goocanvas)),
+				    &bounds);
+	bounds.y1 -= 6.; bounds.y2 += 6.;
+	bounds.x1 -= 6.; bounds.x2 += 6.;
+	zoom = yall / (bounds.y2 - bounds.y1);
+	if (xall / (bounds.x2 - bounds.x1) < zoom)
+		zoom = xall / (bounds.x2 - bounds.x1);
+
+	/* set a limit to the zoom */
+	if (zoom > DEFAULT_SCALE)
+		zoom = DEFAULT_SCALE;
+	
+	browser_canvas_set_zoom_factor (canvas, zoom);
+
+	return zoom;
+}
+
+/**
+ * browser_canvas_center
+ * @canvas: a #BrowserCanvas widget
+ *
+ * Centers the display on the layout
+ */ 
+void
+browser_canvas_center (BrowserCanvas *canvas)
+{
+	/* remove top and left margins if we are running out of space */
+	if (canvas->priv->goocanvas->hadjustment && canvas->priv->goocanvas->vadjustment) {
+		gdouble hlow, hup, vlow, vup, hmargin, vmargin;
+		gdouble left, top, right, bottom;
+		GooCanvasBounds bounds;
+
+		goo_canvas_get_bounds (canvas->priv->goocanvas, &left, &top, &right, &bottom);
+		goo_canvas_item_get_bounds (goo_canvas_get_root_item (canvas->priv->goocanvas),
+					    &bounds);
+
+		g_object_get (G_OBJECT (GOO_CANVAS (canvas->priv->goocanvas)->hadjustment),
+			      "lower", &hlow, "upper", &hup, NULL);
+		g_object_get (G_OBJECT (GOO_CANVAS (canvas->priv->goocanvas)->vadjustment),
+			      "lower", &vlow, "upper", &vup, NULL);
+
+		/*
+		g_print ("Canvas's bounds: %.2f,%.2f -> %.2f,%.2f\n", left, top, right, bottom);
+		g_print ("Root's bounds: %.2f,%.2f -> %.2f,%.2f\n", bounds.x1, bounds.y1, bounds.x2, bounds.y2);
+		g_print ("Xm: %.2f, Ym: %.2f\n", hup - hlow - (right - left), vup - vlow - (bottom - top));
+		*/
+		hmargin = hup - hlow - (bounds.x2 - bounds.x1);
+		if (hmargin > 0) 
+			left -= hmargin / 2. + (left - bounds.x1);
+		vmargin = vup - vlow - (bounds.y2 - bounds.y1);
+		if (vmargin > 0) 
+			top -= vmargin / 2. + (top - bounds.y1);
+		if ((hmargin > 0) || (vmargin > 0)) {
+			goo_canvas_set_bounds (canvas->priv->goocanvas, left, top, right, bottom);
+			/*g_print ("Canvas's new bounds: %.2f,%.2f -> %.2f,%.2f\n", left, top, right, bottom);*/
+			goo_canvas_set_scale (canvas->priv->goocanvas, canvas->priv->goocanvas->scale);	
+		}
+	}
+}
+
+/**
+ * browser_canvas_auto_layout_enabled
+ * @canvas: a #BrowserCanvas widget
+ *
+ * Tells if @canvas has the possibility to automatically adjust its layout
+ * using the GraphViz library.
+ *
+ * Returns: TRUE if @canvas can automatically adjust its layout
+ */
+gboolean
+browser_canvas_auto_layout_enabled (BrowserCanvas *canvas)
+{
+	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), FALSE);
+	g_return_val_if_fail (canvas->priv, FALSE);
+
+#ifdef HAVE_GRAPHVIZ
+	return TRUE;
+#else
+	return FALSE;
+#endif
+}
+
+#ifdef HAVE_GRAPHVIZ
+typedef struct {
+	BrowserCanvas    *canvas;
+	Agraph_t      *graph;
+	GSList        *nodes_list; /* list of NodeLayout structures */
+} GraphLayout;
+
+typedef struct {
+	BrowserCanvasItem *item; /* item to be moved */
+	Agnode_t          *node;
+	gdouble            start_x;
+	gdouble            start_y;
+	gdouble            end_x;
+	gdouble            end_y;
+	gdouble            width;
+	gdouble            height;
+	gdouble            dx;
+	gdouble            dy;
+	gboolean           stop;
+	gdouble            cur_x;
+	gdouble            cur_y;
+} NodeLayout;
+
+static gboolean canvas_animate_to (GraphLayout *gl);
+#endif
+
+/**
+ * browser_canvas_auto_layout
+ * @canvas: a #BrowserCanvas widget
+ *
+ * Re-organizes the layout of the @canvas' items using the GraphViz
+ * layout engine.
+ */
+void
+browser_canvas_perform_auto_layout (BrowserCanvas *canvas, gboolean animate, BrowserCanvasLayoutAlgorithm algorithm)
+{
+	g_return_if_fail (IS_BROWSER_CANVAS (canvas));
+	g_return_if_fail (canvas->priv);
+
+#define GV_SCALE 72.
+
+#ifndef HAVE_GRAPHVIZ
+	g_message ("GraphViz library support not compiled, cannot do graph layout...\n");
+	return FALSE;
+#else
+	GSList *list;
+	Agraph_t *graph;
+	GHashTable *nodes_hash; /* key = BrowserCanvasItem, value = Agnode_t *node */
+	GSList *nodes_list = NULL; /* list of NodeLayout structures */
+
+	if (!gvc)
+		gvc = gvContext ();
+
+	graph = agopen ("BrowserCanvasLayout", AGDIGRAPHSTRICT);
+        agnodeattr (graph, "shape", "box");
+        agnodeattr (graph, "height", ".1");
+        agnodeattr (graph, "width", ".1");
+        agnodeattr (graph, "fixedsize", "true");
+        agnodeattr (graph, "pack", "true");
+	agnodeattr (graph, "packmode", "node");
+
+	/* Graph nodes creation */
+	nodes_hash = g_hash_table_new (NULL, NULL);
+	for (list = canvas->priv->items; list; list = list->next) {
+		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+		Agnode_t *node;
+		gchar *tmp;
+		double val;
+		GooCanvasBounds bounds;
+		gboolean moving;
+		NodeLayout *nl;
+
+		g_object_get (G_OBJECT (item), "allow-move", &moving, NULL);
+		if (!moving)
+			continue;
+
+		nl = g_new0 (NodeLayout, 1);
+		nl->item = item;
+		nodes_list = g_slist_prepend (nodes_list, nl);
+		
+		tmp = g_strdup_printf ("%p", item);
+		node = agnode (graph, tmp);
+		nl->node = node;
+		g_hash_table_insert (nodes_hash, item, node);
+		
+		tmp = g_strdup_printf ("%p", node);
+		agset (node, "label", tmp);
+		g_free (tmp);
+		
+		goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item), &bounds);
+		nl->width = bounds.x2 - bounds.x1;
+		nl->height = bounds.y2 - bounds.y1;
+		val = (bounds.y2 - bounds.y1) / GV_SCALE;
+		tmp = g_strdup_printf ("%.3f", val);
+		agset (node, "height", tmp);
+		g_free (tmp);
+		val = (bounds.x2 - bounds.x1) / GV_SCALE;
+		tmp = g_strdup_printf ("%.3f", val);
+		agset (node, "width", tmp);
+		g_free (tmp);
+		
+		nl->start_x = bounds.x1;
+		nl->start_y = bounds.y1;
+		nl->cur_x = nl->start_x;
+		nl->cur_y = nl->start_y;
+		
+		/*g_print ("Before: Node %p: HxW: %.3f %.3f\n", node, (bounds.y2 - bounds.y1) / GV_SCALE, 
+		  (bounds.x2 - bounds.x1) / GV_SCALE);*/
+	}
+	/* Graph edges creation */
+	for (list = canvas->priv->items; list; list = list->next) {
+		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+		BrowserCanvasItem *from, *to;
+		gboolean moving;
+
+		g_object_get (G_OBJECT (item), "allow-move", &moving, NULL);
+		if (moving)
+			continue;
+
+		browser_canvas_item_get_edge_nodes (item, &from, &to);
+		if (from && to) {
+			Agnode_t *from_node, *to_node;
+			from_node = (Agnode_t*) g_hash_table_lookup (nodes_hash, from);
+			to_node = (Agnode_t*) g_hash_table_lookup (nodes_hash, to);
+			if (from_node && to_node)
+				agedge (graph, from_node, to_node);
+		}
+	}
+
+	switch (algorithm) {
+	default:
+	case BROWSER_CANVAS_LAYOUT_DEFAULT:
+		gvLayout (gvc, graph, "dot");
+		break;
+	case BROWSER_CANVAS_LAYOUT_RADIAL:
+		gvLayout (gvc, graph, "circo");
+		break;
+	}
+        gvRender (gvc, graph, "dot", NULL);
+	/*gvRenderFilename (gvc, graph, "png", "out.png");*/
+        /*gvRender (gvc, graph, "dot", stdout);*/
+
+
+	for (list = nodes_list; list; list = list->next) {
+		NodeLayout *nl = (NodeLayout*) list->data;
+		nl->end_x = ND_coord_i (nl->node).x - (nl->width / 2.);
+		nl->end_y = - ND_coord_i (nl->node).y - (nl->height / 2.);
+		nl->dx = fabs (nl->end_x - nl->start_x);
+		nl->dy = fabs (nl->end_y - nl->start_y);
+		nl->stop = FALSE;
+		/*g_print ("After: Node %p: HxW: %.3f %.3f XxY = %d, %d\n", nl->node, 
+			 ND_height (nl->node), ND_width (nl->node),
+			 ND_coord_i (nl->node).x, - ND_coord_i (nl->node).y);*/
+		if (!animate)
+			goo_canvas_item_translate (GOO_CANVAS_ITEM (nl->item), nl->end_x - nl->start_x,
+						   nl->end_y - nl->start_y);
+	}
+
+	g_hash_table_destroy (nodes_hash);
+	gvFreeLayout (gvc, graph);
+
+	if (animate) {
+		GraphLayout *gl;
+		gl = g_new0 (GraphLayout, 1);
+		gl->canvas = canvas;
+		gl->graph = graph;
+		gl->nodes_list = nodes_list;
+		while (canvas_animate_to (gl));
+	}
+	else {
+		agclose (graph);
+		g_slist_foreach (nodes_list, (GFunc) g_free, NULL);
+		g_slist_free (nodes_list);
+	}
+
+#endif
+}
+
+#ifdef HAVE_GRAPHVIZ
+static gdouble
+compute_animation_inc (float start, float stop, float current)
+{
+        gdouble inc;
+#ifndef PI
+#define PI 3.14159265
+#endif
+#define STEPS 30.
+
+        if (stop == start)
+                return 0.;
+
+	inc = (stop - start) / STEPS;
+
+        return inc;
+}
+
+static gboolean
+canvas_animate_to (GraphLayout *gl) 
+{
+	gboolean stop = TRUE;
+	GSList *list;
+
+#define EPSILON 1.
+	for (list = gl->nodes_list; list; list = list->next) {
+		NodeLayout *nl = (NodeLayout*) list->data;
+		if (!nl->stop) {
+			gdouble dx, dy, ndx, ndy;
+
+			dx = compute_animation_inc (nl->start_x, nl->end_x, nl->cur_x);
+			dy = compute_animation_inc (nl->start_y, nl->end_y, nl->cur_y);
+			ndx = fabs (nl->cur_x + dx - nl->end_x);
+			ndy = fabs (nl->cur_y + dy - nl->end_y);
+			nl->cur_x +=  dx;
+			nl->cur_y +=  dy;
+			browser_canvas_item_translate (nl->item, dx, dy);
+			if (((ndx <= EPSILON) || (ndx >= nl->dx)) &&
+			    ((ndy <= EPSILON) || (ndy >= nl->dy)))
+				nl->stop = TRUE;
+			else {
+				stop = FALSE;
+				nl->dx = ndx;
+				nl->dy = ndy;
+			}
+		}
+	}
+
+	goo_canvas_request_update (GOO_CANVAS (gl->canvas->priv->goocanvas));
+	goo_canvas_update (GOO_CANVAS (gl->canvas->priv->goocanvas));
+	while (gtk_events_pending ())
+		gtk_main_iteration ();
+
+	if (stop) {
+		agclose (gl->graph);
+		g_slist_foreach (gl->nodes_list, (GFunc) g_free, NULL);
+		g_slist_free (gl->nodes_list);
+		g_free (gl);
+	}
+	return !stop;
+}
+#endif
+
+/**
+ * browser_canvas_serialize_items
+ */
+gchar *
+browser_canvas_serialize_items (BrowserCanvas *canvas)
+{
+	GSList *list;
+	g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), NULL);
+	
+	for (list = canvas->priv->items; list; list = list->next) {
+		BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+		TO_IMPLEMENT;
+	}
+}
diff --git a/tools/browser/canvas/browser-canvas.h b/tools/browser/canvas/browser-canvas.h
new file mode 100644
index 0000000..97116e1
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas.h
@@ -0,0 +1,79 @@
+/* browser-canvas.h
+ *
+ * Copyright (C) 2007 - 2008 Vivien Malerba
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef __BROWSER_CANVAS__
+#define __BROWSER_CANVAS__
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "browser-canvas-decl.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_BROWSER_CANVAS          (browser_canvas_get_type())
+#define BROWSER_CANVAS(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, browser_canvas_get_type(), BrowserCanvas)
+#define BROWSER_CANVAS_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, browser_canvas_get_type (), BrowserCanvasClass)
+#define IS_BROWSER_CANVAS(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, browser_canvas_get_type ())
+
+typedef enum {
+	BROWSER_CANVAS_LAYOUT_DEFAULT,
+	BROWSER_CANVAS_LAYOUT_RADIAL
+} BrowserCanvasLayoutAlgorithm;
+
+/* struct for the object's data */
+struct _BrowserCanvas
+{
+	GtkScrolledWindow   widget;
+
+	/* pointer position when a context menu was last opened */
+	gdouble             xmouse;
+	gdouble             ymouse;
+
+	/* private */
+	BrowserCanvasPrivate  *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasClass
+{
+	GtkScrolledWindowClass parent_class;
+
+	/* virtual functions */
+	void           (*clean_canvas_items)  (BrowserCanvas *canvas); /* clean any extra structure, not the individual items */
+
+	GtkWidget     *(*build_context_menu)  (BrowserCanvas *canvas);
+};
+
+/* generic widget's functions */
+GType              browser_canvas_get_type                (void) G_GNUC_CONST;
+void               browser_canvas_declare_item            (BrowserCanvas *canvas, BrowserCanvasItem *item);
+
+void               browser_canvas_set_zoom_factor         (BrowserCanvas *canvas, gdouble n);
+gdouble            browser_canvas_get_zoom_factor         (BrowserCanvas *canvas);
+gdouble            browser_canvas_fit_zoom_factor         (BrowserCanvas *canvas);
+gboolean           browser_canvas_auto_layout_enabled     (BrowserCanvas *canvas);
+void               browser_canvas_perform_auto_layout     (BrowserCanvas *canvas, gboolean animate,
+							   BrowserCanvasLayoutAlgorithm algorithm);
+void               browser_canvas_center                  (BrowserCanvas *canvas);
+gchar             *browser_canvas_serialize_items         (BrowserCanvas *canvas);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/main.c b/tools/browser/main.c
index 0423b2b..c04992b 100644
--- a/tools/browser/main.c
+++ b/tools/browser/main.c
@@ -20,6 +20,7 @@
  */
 
 #include <glib/gi18n-lib.h>
+#include <glib/gprintf.h>
 #include <string.h>
 #include <libgdaui/libgdaui.h>
 #include "support.h"
@@ -29,14 +30,36 @@
 #include "login-dialog.h"
 #include "auth-dialog.h"
 
+/* options */
+gchar *perspective = NULL;
+
+static GOptionEntry entries[] = {
+        { "perspective", 'p', 0, G_OPTION_ARG_STRING, &perspective, "Perspective", "default perspective "
+	  "to use when opening windows"},
+	{ NULL }
+};
+
 int
 main (int argc, char *argv[])
 {
+	GOptionContext *context;
+        GError *error = NULL;
 	gboolean have_loop = FALSE;
 
+	context = g_option_context_new (_("[DSN|connection string]..."));
+        g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+        g_option_context_set_ignore_unknown_options (context, TRUE);
+        if (!g_option_context_parse (context, &argc, &argv, &error)) {
+                g_fprintf  (stderr, "Can't parse arguments: %s\n", error->message);
+                return EXIT_FAILURE;
+        }
+        g_option_context_free (context);
+
 	gdaui_init ();
 	gtk_init (&argc, &argv);
 
+	browser_core_set_default_factory (perspective);
+
 	if (argc == 1) {
 		GError *error = NULL;
 		if (browser_connection_open (&error))
diff --git a/tools/browser/schema-browser/Makefile.am b/tools/browser/schema-browser/Makefile.am
index bcdc793..aea39bb 100644
--- a/tools/browser/schema-browser/Makefile.am
+++ b/tools/browser/schema-browser/Makefile.am
@@ -23,3 +23,9 @@ libperspective_la_SOURCES = \
 	table-columns.h \
 	mgr-columns.c \
 	mgr-columns.h
+
+if HAVE_GOOCANVAS
+libperspective_la_SOURCES += \
+	table-relations.c \
+	table-relations.h
+endif
diff --git a/tools/browser/schema-browser/favorite-selector.c b/tools/browser/schema-browser/favorite-selector.c
index 98bef55..0972b81 100644
--- a/tools/browser/schema-browser/favorite-selector.c
+++ b/tools/browser/schema-browser/favorite-selector.c
@@ -350,7 +350,8 @@ gboolean tree_store_drag_get_cb (GdauiTreeStore *store, const gchar *path, GtkSe
 		if (cvalue) {
 			const gchar *str;
 			str = g_value_get_string (cvalue);
-			gtk_selection_data_set (selection_data, selection_data->target, 8, str, strlen (str));
+			gtk_selection_data_set (selection_data, selection_data->target, 8,
+						(guchar*) str, strlen (str));
 			return TRUE;
 		}
 	}
diff --git a/tools/browser/schema-browser/table-columns.c b/tools/browser/schema-browser/table-columns.c
index 87e597c..6dd920d 100644
--- a/tools/browser/schema-browser/table-columns.c
+++ b/tools/browser/schema-browser/table-columns.c
@@ -604,8 +604,6 @@ follow_if_link (GtkWidget *text_view, GtkTextIter *iter, TableColumns *tcolumns)
 								       table_name,
 								       table_short_name);
 		}
-		else
-			g_warning ("Missing information...\n");
         }
 
 	if (tags) 
diff --git a/tools/browser/schema-browser/table-info.c b/tools/browser/schema-browser/table-info.c
index ada59a6..6fc0610 100644
--- a/tools/browser/schema-browser/table-info.c
+++ b/tools/browser/schema-browser/table-info.c
@@ -28,6 +28,9 @@
 #include "../support.h"
 #include "../cc-gray-bar.h"
 #include "table-columns.h"
+#ifdef HAVE_GOOCANVAS
+#include "table-relations.h"
+#endif
 #include "schema-browser-perspective.h"
 
 struct _TableInfoPrivate {
@@ -334,8 +337,21 @@ table_info_new (BrowserConnection *bcnc,
 		str = g_strdup_printf ("<small>%s</small>", _("Columns"));
 		gtk_label_set_markup (GTK_LABEL (label), str);
 		g_free (str);
+		gtk_widget_show (page);
 		gtk_notebook_append_page (GTK_NOTEBOOK (sub_nb), page, label);
 	}
+#ifdef HAVE_GOOCANVAS
+	page = table_relations_new (tinfo);
+	if (page) {
+		label = gtk_label_new ("");
+		str = g_strdup_printf ("<small>%s</small>", _("Relations"));
+		gtk_label_set_markup (GTK_LABEL (label), str);
+		g_free (str);
+		gtk_widget_show (page);
+		gtk_notebook_append_page (GTK_NOTEBOOK (sub_nb), page, label);
+	}
+#endif
+	gtk_notebook_set_current_page (GTK_NOTEBOOK (sub_nb), 0);
 
 	/* show everything */
         gtk_widget_show_all (top_nb);
diff --git a/tools/browser/schema-browser/table-relations.c b/tools/browser/schema-browser/table-relations.c
new file mode 100644
index 0000000..c04dd2f
--- /dev/null
+++ b/tools/browser/schema-browser/table-relations.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2009 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <libgda/gda-tree.h>
+#include "table-info.h"
+#include "table-relations.h"
+#include <libgdaui/gdaui-tree-store.h>
+#include "../support.h"
+#include "../cc-gray-bar.h"
+#include "schema-browser-perspective.h"
+#include "../canvas/browser-canvas-db-relations.h"
+
+struct _TableRelationsPrivate {
+	BrowserConnection *bcnc;
+	TableInfo *tinfo;
+	GtkWidget *canvas;
+};
+
+static void table_relations_class_init (TableRelationsClass *klass);
+static void table_relations_init       (TableRelations *trels, TableRelationsClass *klass);
+static void table_relations_dispose   (GObject *object);
+
+static void meta_changed_cb (BrowserConnection *bcnc, GdaMetaStruct *mstruct, TableRelations *trels);
+
+enum {
+	SELECTION_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint table_relations_signals[LAST_SIGNAL] = { 0 };
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * TableRelations class implementation
+ */
+
+static void
+table_relations_class_init (TableRelationsClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = table_relations_dispose;
+}
+
+
+static void
+table_relations_init (TableRelations *trels, TableRelationsClass *klass)
+{
+	trels->priv = g_new0 (TableRelationsPrivate, 1);
+}
+
+static void
+table_relations_dispose (GObject *object)
+{
+	TableRelations *trels = (TableRelations *) object;
+
+	/* free memory */
+	if (trels->priv) {
+		if (trels->priv->bcnc)
+			g_object_unref (trels->priv->bcnc);
+		g_free (trels->priv);
+		trels->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+table_relations_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo relations = {
+			sizeof (TableRelationsClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) table_relations_class_init,
+			NULL,
+			NULL,
+			sizeof (TableRelations),
+			0,
+			(GInstanceInitFunc) table_relations_init
+		};
+		type = g_type_register_static (GTK_TYPE_VBOX, "TableRelations", &relations, 0);
+	}
+	return type;
+}
+
+
+static void
+meta_changed_cb (BrowserConnection *bcnc, GdaMetaStruct *mstruct, TableRelations *trels)
+{
+	GdaMetaDbObject *dbo;
+	GValue *tname, *tschema;
+
+	g_object_set (G_OBJECT (trels->priv->canvas), "meta-struct", mstruct, NULL);
+
+	g_value_set_string ((tschema = gda_value_new (G_TYPE_STRING)),
+			    table_info_get_table_schema (trels->priv->tinfo));
+	g_value_set_string ((tname = gda_value_new (G_TYPE_STRING)),
+			    table_info_get_table_name (trels->priv->tinfo));
+        browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (trels->priv->canvas), NULL,
+					       tschema, tname);
+
+	dbo = gda_meta_struct_get_db_object (mstruct, NULL, tschema, tname);
+
+	if (dbo && (dbo->obj_type == GDA_META_DB_TABLE)) {
+		GdaMetaTable *table;
+		table = GDA_META_TABLE (dbo);
+		
+		GSList *list;
+		for (list = table->reverse_fk_list; list; list = list->next) {
+			GdaMetaTableForeignKey *fkey = (GdaMetaTableForeignKey*) list->data;
+			g_value_set_string (tname, fkey->meta_table->obj_name);
+			g_value_set_string (tschema, fkey->meta_table->obj_schema);
+			browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (trels->priv->canvas),
+							       NULL,
+							       tschema, tname);
+		}
+
+		for (list = table->fk_list; list; list = list->next) {
+			GdaMetaTableForeignKey *fkey = (GdaMetaTableForeignKey*) list->data;
+			g_value_set_string (tname, fkey->depend_on->obj_name);
+			g_value_set_string (tschema, fkey->depend_on->obj_schema);
+			browser_canvas_db_relations_add_table (BROWSER_CANVAS_DB_RELATIONS (trels->priv->canvas),
+							       NULL,
+							       tschema, tname);
+		}
+	}
+	gda_value_free (tschema);
+	gda_value_free (tname);
+
+	browser_canvas_perform_auto_layout (BROWSER_CANVAS (trels->priv->canvas), TRUE,
+					    BROWSER_CANVAS_LAYOUT_DEFAULT);
+}
+
+/**
+ * table_relations_new
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+table_relations_new (TableInfo *tinfo)
+{
+	TableRelations *trels;
+
+	g_return_val_if_fail (IS_TABLE_INFO (tinfo), NULL);
+
+	trels = TABLE_RELATIONS (g_object_new (TABLE_RELATIONS_TYPE, NULL));
+
+	trels->priv->tinfo = tinfo;
+	trels->priv->bcnc = g_object_ref (table_info_get_connection (tinfo));
+	g_signal_connect (trels->priv->bcnc, "meta-changed",
+			  G_CALLBACK (meta_changed_cb), trels);
+	
+	/*
+	 * Relations
+	 */
+	trels->priv->canvas = browser_canvas_db_relations_new (NULL);
+	gtk_box_pack_start (GTK_BOX (trels), trels->priv->canvas, TRUE, TRUE, 0);
+	gtk_widget_show (trels);
+
+	/*
+	 * initial update
+	 */
+	GdaMetaStruct *mstruct;
+	mstruct = browser_connection_get_meta_struct (trels->priv->bcnc);
+	if (mstruct)
+		meta_changed_cb (trels->priv->bcnc, mstruct, trels);
+
+	return (GtkWidget*) trels;
+}
+
diff --git a/tools/browser/schema-browser/table-relations.h b/tools/browser/schema-browser/table-relations.h
new file mode 100644
index 0000000..7d66df4
--- /dev/null
+++ b/tools/browser/schema-browser/table-relations.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2009 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __TABLE_RELATIONS_H__
+#define __TABLE_RELATIONS_H__
+
+
+#include "table-info.h"
+
+G_BEGIN_DECLS
+
+#define TABLE_RELATIONS_TYPE            (table_relations_get_type())
+#define TABLE_RELATIONS(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, TABLE_RELATIONS_TYPE, TableRelations))
+#define TABLE_RELATIONS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, TABLE_RELATIONS_TYPE, TableRelationsClass))
+#define IS_TABLE_RELATIONS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, TABLE_RELATIONS_TYPE))
+#define IS_TABLE_RELATIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TABLE_RELATIONS_TYPE))
+
+typedef struct _TableRelations        TableRelations;
+typedef struct _TableRelationsClass   TableRelationsClass;
+typedef struct _TableRelationsPrivate TableRelationsPrivate;
+
+struct _TableRelations {
+	GtkVBox               parent;
+	TableRelationsPrivate *priv;
+};
+
+struct _TableRelationsClass {
+	GtkVBoxClass          parent_class;
+};
+
+GType                    table_relations_get_type (void) G_GNUC_CONST;
+
+GtkWidget               *table_relations_new      (TableInfo *tinfo);
+
+G_END_DECLS
+
+#endif



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]