[libgda] Tools: re-added canvas part



commit 4a3c92c5e7a91687a4aef79786544c769fe51335
Author: Vivien Malerba <malerba gnome-db org>
Date:   Sun Nov 30 22:34:34 2014 +0100

    Tools: re-added canvas part

 configure.ac                                       |    1 +
 tools/Makefile.am                                  |    5 +
 tools/browser/Makefile.am                          |    4 +
 tools/browser/canvas/Makefile.am                   |   47 +
 tools/browser/canvas/browser-canvas-column.c       |  330 ++++++
 tools/browser/canvas/browser-canvas-column.h       |   64 ++
 tools/browser/canvas/browser-canvas-db-relations.c | 1042 +++++++++++++++++
 tools/browser/canvas/browser-canvas-db-relations.h |   68 ++
 tools/browser/canvas/browser-canvas-decl.h         |   44 +
 tools/browser/canvas/browser-canvas-fkey.c         |  550 +++++++++
 tools/browser/canvas/browser-canvas-fkey.h         |   63 +
 tools/browser/canvas/browser-canvas-item.c         |  411 +++++++
 tools/browser/canvas/browser-canvas-item.h         |   70 ++
 tools/browser/canvas/browser-canvas-print.c        |  457 ++++++++
 tools/browser/canvas/browser-canvas-print.h        |   29 +
 tools/browser/canvas/browser-canvas-priv.h         |   39 +
 tools/browser/canvas/browser-canvas-table.c        |  603 ++++++++++
 tools/browser/canvas/browser-canvas-table.h        |   66 ++
 tools/browser/canvas/browser-canvas-text.c         |  528 +++++++++
 tools/browser/canvas/browser-canvas-text.h         |   69 ++
 tools/browser/canvas/browser-canvas-utility.c      |  747 ++++++++++++
 tools/browser/canvas/browser-canvas-utility.h      |   50 +
 tools/browser/canvas/browser-canvas.c              | 1183 ++++++++++++++++++++
 tools/browser/canvas/browser-canvas.h              |   87 ++
 tools/browser/schema-browser/Makefile.am           |    1 +
 tools/browser/schema-browser/relations-diagram.c   |   15 +-
 tools/browser/schema-browser/table-relations.c     |    3 +-
 27 files changed, 6567 insertions(+), 9 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e7c10b5..5b34cb9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1019,6 +1019,7 @@ tools/Makefile
 tools/gda-sql-6.0.1:tools/gda-sql.1.in
 tools/base/Makefile
 tools/browser/Makefile
+tools/browser/canvas/Makefile
 tools/browser/dummy-perspective/Makefile
 tools/browser/schema-browser/Makefile
 tools/browser/query-exec/Makefile
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0fa1c96..d873d26 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -58,6 +58,10 @@ else
 EXTRALDFLAGS=
 endif
 
+if HAVE_GOOCANVAS
+CANVAS_LDADD=browser/canvas/libcanvas.la -lm
+endif
+
 libbrowser_la_SOURCES=
 
 libbrowser_la_LIBADD = \
@@ -66,6 +70,7 @@ libbrowser_la_LIBADD = \
        browser/libbrowsercore.la \
        $(ldap_lib) \
        browser/schema-browser/libperspective.la \
+       $(CANVAS_LDADD) \
        browser/dummy-perspective/libperspective.la \
        browser/query-exec/libperspective.la \
        browser/data-manager/libperspective.la
diff --git a/tools/browser/Makefile.am b/tools/browser/Makefile.am
index 7853b46..6b672e3 100644
--- a/tools/browser/Makefile.am
+++ b/tools/browser/Makefile.am
@@ -4,6 +4,9 @@ SUBDIRS = dummy-perspective schema-browser query-exec data-manager
 if LDAP
 SUBDIRS += ldap-browser
 endif
+if HAVE_GOOCANVAS
+SUBDIRS += canvas
+endif
 SUBDIRS += help
 
 AM_CPPFLAGS = \
@@ -93,3 +96,4 @@ EXTRA_DIST = \
 
 CLEANFILES = \
        $(appdata_DATA)
+
diff --git a/tools/browser/canvas/Makefile.am b/tools/browser/canvas/Makefile.am
new file mode 100644
index 0000000..3dfc69b
--- /dev/null
+++ b/tools/browser/canvas/Makefile.am
@@ -0,0 +1,47 @@
+noinst_LTLIBRARIES = libcanvas.la
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir)/tools \
+        -I$(top_srcdir)/tools/browser \
+        -I$(top_srcdir)/tools/base \
+        -I$(top_builddir) \
+        -I$(top_builddir)/libgda \
+        -I$(top_srcdir) \
+        -I$(top_srcdir)/libgda \
+        -I$(top_srcdir)/libgda/sqlite \
+       $(COREDEPS_CFLAGS) \
+       $(COREDEPS_WFLAGS) \
+       $(GTK_CFLAGS) \
+       $(GOOCANVAS_CFLAGS) \
+       $(GRAPHVIZ_CFLAGS) \
+       $(MAC_INTEGRATION_CFLAGS)
+
+libcanvas_la_SOURCES = \
+       browser-canvas.c \
+       browser-canvas.h \
+       browser-canvas-priv.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_LIBADD = \
+       $(top_builddir)/libgda/libgda-6.0.la \
+       $(COREDEPS_LIBS) \
+       $(GTK_LIBS) \
+       $(GOOCANVAS_LIBS) \
+       $(GRAPHVIZ_LIBS) \
+       $(MAC_INTEGRATION_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..f6ff9f3
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-column.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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_META_STRUCT,
+       PROP_COLUMN,
+};
+
+struct _BrowserCanvasColumnPrivate
+{
+       GdaMetaStruct      *mstruct;
+       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,
+                       0
+               };              
+
+               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_META_STRUCT,
+                 g_param_spec_object ("meta-struct", NULL, NULL, 
+                                     GDA_TYPE_META_STRUCT,
+                                     (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
+       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->mstruct = NULL;
+       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->mstruct)
+                       g_object_unref (cf->priv->mstruct);
+               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_META_STRUCT:
+               cf->priv->mstruct = g_value_dup_object (value);
+               break;
+       case PROP_COLUMN:
+               g_return_if_fail (cf->priv->mstruct);
+               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;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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_META_STRUCT:
+               g_value_set_object (value, cf->priv->mstruct);
+               break;
+       case PROP_COLUMN:
+               g_value_set_pointer (value, cf->priv->column);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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: (skip)
+ * @parent: (allow-none): the parent item, or %NULL
+ * @mstruct: the #GdaMetaStruct @column is from
+ * @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, GdaMetaStruct *mstruct, GdaMetaTableColumn *column,
+                          gdouble x, gdouble y, ...)
+{
+       GooCanvasItem *item;
+       const char *first_property;
+       va_list var_args;
+
+       g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL);
+
+       item = g_object_new (TYPE_BROWSER_CANVAS_COLUMN, "meta-struct", mstruct, 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 (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, G_GNUC_UNUSED GdkDragContext *drag_context,
+                                    GtkSelectionData *data, G_GNUC_UNUSED guint info,
+                                    G_GNUC_UNUSED 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, gtk_selection_data_get_target (data), 8, (guchar*) 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..c01c769
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-column.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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,
+                                                           GdaMetaStruct *mstruct, 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..249bcaa
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-db-relations.c
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (C) 2009 - 2014 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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"
+#include "../objects-cloud.h"
+#include "../fk-declare.h"
+#include <libgda-ui/internal/popup-container.h>
+#include "../browser-window.h"
+#include "../ui-support.h"
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+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);
+static GSList    *get_layout_items    (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 */
+
+       GdaMetaStruct    *mstruct;
+       GooCanvasItem    *level_separator; /* all tables items will be above this item and FK lines below */
+
+       GtkWidget        *add_dialog;
+       ObjectsCloud     *cloud;
+};
+
+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,
+                       0
+               };              
+
+               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;
+       BROWSER_CANVAS_CLASS (class)->get_layout_items = get_layout_items;
+       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);
+
+               g_hash_table_destroy (canvas->priv->hash_tables);
+               g_hash_table_destroy (canvas->priv->hash_fkeys);
+
+               if (canvas->priv->add_dialog)
+                       gtk_widget_destroy (canvas->priv->add_dialog);
+
+               g_free (canvas->priv);
+               canvas->priv = NULL;
+       }
+
+       /* for the parent class */
+       parent_class->dispose (object);
+}
+
+typedef struct {
+       GdaMetaTable   *table;
+       GooCanvasBounds bounds;
+} PresentTable;
+
+static void
+browser_canvas_db_relations_set_property (GObject *object,
+                                         guint param_id,
+                                         const GValue *value,
+                                         GParamSpec *pspec)
+{
+       BrowserCanvasDbRelations *canvas;
+       BrowserCanvas *b_canvas;
+
+        canvas = BROWSER_CANVAS_DB_RELATIONS (object);
+       b_canvas = BROWSER_CANVAS (object);
+        if (canvas->priv) {
+                switch (param_id) {
+               case PROP_META_STRUCT: {
+                       GSList *present_tables = NULL;
+                       GdaMetaStruct *mstruct = g_value_get_object (value);
+                       GdaMetaStruct *old_mstruct;
+                       if (canvas->priv->mstruct == mstruct)
+                               break;
+                       if (mstruct)
+                               g_object_ref (mstruct);
+                       
+                       if (canvas->priv->mstruct) {
+                               GSList *list;
+                               for (list = b_canvas->priv->items; list; list = list->next) {
+                                       BrowserCanvasItem *item;
+                                       GdaMetaTable *mtable;
+                                       item = BROWSER_CANVAS_ITEM (list->data);
+                                       mtable = g_hash_table_lookup (canvas->priv->hash_tables,
+                                                                     item);
+                                       if (! mtable)
+                                               continue;
+                                       
+                                       PresentTable *pt;
+                                       pt = g_new (PresentTable, 1);
+                                       present_tables = g_slist_prepend (present_tables, pt);
+                                       pt->table = mtable;
+                                       goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item),
+                                                                   &(pt->bounds));
+                               }
+                       }
+                       old_mstruct = canvas->priv->mstruct;
+                       clean_canvas_items (BROWSER_CANVAS (canvas));
+                       canvas->priv->mstruct = mstruct;
+                       if (present_tables) {
+                               GSList *list;
+                               for (list = present_tables; list; list = list->next) {
+                                       PresentTable *pt = (PresentTable*) list->data;
+                                       GdaMetaDbObject *dbo = (GdaMetaDbObject*) pt->table;
+                                       GValue *v1 = NULL, *v2 = NULL, *v3 = NULL;
+                                       BrowserCanvasTable *ctable;
+                                       GooCanvasBounds bounds;
+                                                                               
+                                       if (dbo->obj_catalog)
+                                               g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)),
+                                                                   dbo->obj_catalog);
+                                       if (dbo->obj_schema)
+                                               g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)),
+                                                                   dbo->obj_schema);
+                                       if (dbo->obj_name)
+                                               g_value_set_string ((v3 = gda_value_new (G_TYPE_STRING)),
+                                                                   dbo->obj_name);
+                                       
+                                       ctable = browser_canvas_db_relations_add_table (canvas, v1, v2,
+                                                                                       v3);
+                                       if (v1) gda_value_free (v1);
+                                       if (v3) gda_value_free (v2);
+                                       if (v2) gda_value_free (v3);
+                                       
+                                       if (ctable) {
+                                               goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ctable),
+                                                                           &bounds);
+                                               browser_canvas_translate_item (BROWSER_CANVAS (canvas),
+                                                                              (BrowserCanvasItem*) ctable,
+                                                                              pt->bounds.x1 - bounds.x1,
+                                                                              pt->bounds.y1 - bounds.y1);
+                                       }
+                                       g_free (pt);
+                               }
+                               g_slist_free (present_tables);
+                               g_object_set (G_OBJECT (b_canvas->priv->goocanvas),
+                                             "automatic-bounds", TRUE, NULL);
+                       }
+                       if (old_mstruct)
+                               g_object_unref (old_mstruct);
+
+                       if (canvas->priv->cloud)
+                               objects_cloud_set_meta_struct (canvas->priv->cloud,
+                                                              canvas->priv->mstruct);
+                       break;
+               }
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                       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;
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                       break;
+               }
+       }
+}
+
+static void
+cloud_object_selected_cb (G_GNUC_UNUSED ObjectsCloud *ocloud, G_GNUC_UNUSED ObjectsCloudObjType sel_type,
+                         const gchar *sel_contents, BrowserCanvasDbRelations *dbrel)
+{
+       GdaMetaTable *mtable;
+       GValue *table_schema;
+       GValue *table_name;
+       GdaQuarkList *ql;
+
+       ql = gda_quark_list_new_from_string (sel_contents);
+       g_value_set_string ((table_schema = gda_value_new (G_TYPE_STRING)),
+                           gda_quark_list_find (ql, "OBJ_SCHEMA"));
+       g_value_set_string ((table_name = gda_value_new (G_TYPE_STRING)),
+                           gda_quark_list_find (ql, "OBJ_NAME"));
+       gda_quark_list_free (ql);
+
+#ifdef GDA_DEBUG_NO
+       g_print ("Add %s.%s\n",
+                g_value_get_string (table_schema), g_value_get_string (table_name));
+#endif
+       mtable = (GdaMetaTable*) gda_meta_struct_complement (dbrel->priv->mstruct, GDA_META_DB_TABLE,
+                                                            NULL, table_schema, table_name, NULL);
+       if (mtable) {
+               BrowserCanvasTable *ctable;
+               GooCanvasBounds bounds;
+               gdouble x, y;
+               
+               x = BROWSER_CANVAS (dbrel)->xmouse;
+               y = BROWSER_CANVAS (dbrel)->ymouse;
+
+               ctable = browser_canvas_db_relations_add_table (dbrel, NULL, table_schema, table_name);
+               goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ctable), &bounds);
+               browser_canvas_item_translate (BROWSER_CANVAS_ITEM (ctable),
+                                              x - bounds.x1, y - bounds.y1);
+       }
+       gda_value_free (table_schema);
+       gda_value_free (table_name);
+}
+
+/**
+ * browser_canvas_db_relations_new
+ * @mstruct: (allow-none): 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)
+{
+       BrowserCanvas *canvas;
+       BrowserCanvasDbRelations *dbrels;
+       GooCanvasItem *item;
+       g_return_val_if_fail (!mstruct || GDA_IS_META_STRUCT (mstruct), NULL);
+
+       canvas = BROWSER_CANVAS (g_object_new (TYPE_BROWSER_CANVAS_DB_RELATIONS,
+                                              "meta-struct", mstruct, NULL));
+       dbrels = BROWSER_CANVAS_DB_RELATIONS (canvas);
+       item = goo_canvas_group_new (goo_canvas_get_root_item (canvas->priv->goocanvas), NULL);
+       dbrels->priv->level_separator = item;
+
+        return GTK_WIDGET (canvas);
+}
+
+static void
+clean_canvas_items (BrowserCanvas *canvas)
+{
+       BrowserCanvasDbRelations *dbrel = BROWSER_CANVAS_DB_RELATIONS (canvas);
+       GSList *clist, *list;
+
+       /* remove canvas item */
+       clist = g_slist_copy (canvas->priv->items);
+       for (list = clist; list; list = list->next)
+               goo_canvas_item_remove (GOO_CANVAS_ITEM (list->data));
+       g_slist_free (clist);
+
+       /* 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);
+}
+
+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 void popup_func_add_ref_cb (GtkMenuItem *mitem, BrowserCanvasTable *ce);
+static void popup_func_declare_fk_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 from graph"));
+       g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_delete_cb), ce);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+       gtk_widget_show (entry);
+       entry = gtk_menu_item_new_with_label (_("Add referenced tables to graph"));
+       g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_add_depend_cb), ce);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+       gtk_widget_show (entry);
+       entry = gtk_menu_item_new_with_label (_("Add tables referencing this table to graph"));
+       g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_add_ref_cb), ce);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+       gtk_widget_show (entry);
+
+       entry = gtk_separator_menu_item_new ();
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+       gtk_widget_show (entry);
+
+       entry = gtk_menu_item_new_with_label (_("Declare foreign key for this table"));
+       g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (popup_func_declare_fk_cb), ce);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+       gtk_widget_show (entry);
+
+       return menu;
+}
+
+static void
+popup_func_declare_fk_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvasTable *ce)
+{
+       GtkWidget *dlg, *parent;
+       GdaMetaStruct *mstruct;
+       GdaMetaTable *mtable;
+       gint response;
+
+       parent = (GtkWidget*) gtk_widget_get_toplevel ((GtkWidget*) goo_canvas_item_get_canvas 
(GOO_CANVAS_ITEM (ce)));
+       g_object_get (G_OBJECT (ce), "meta-struct", &mstruct, "table", &mtable, NULL);
+       dlg = fk_declare_new ((GtkWindow *) parent, mstruct, mtable);
+       response = gtk_dialog_run (GTK_DIALOG (dlg));
+       if (response == GTK_RESPONSE_ACCEPT) {
+               GError *error = NULL;
+               if (! fk_declare_write (FK_DECLARE (dlg),
+                                       BROWSER_IS_WINDOW (parent) ? BROWSER_WINDOW (parent) : NULL,
+                                       &error)) {
+                       ui_show_error ((GtkWindow *) parent, _("Failed to declare foreign key: %s"),
+                                      error && error->message ? error->message : _("No detail"));
+                       g_clear_error (&error);
+               }
+               else if (BROWSER_IS_WINDOW (parent))
+                       browser_window_show_notice (BROWSER_WINDOW (parent),
+                                                   GTK_MESSAGE_INFO, "fkdeclare",
+                                                   _("Successfully declared foreign key"));
+               else
+                       ui_show_message ((GtkWindow *) parent, "%s",
+                                        _("Successfully declared foreign key"));
+       }
+       
+       gtk_widget_destroy (dlg);
+       g_object_unref (mstruct);
+}
+
+static void
+popup_func_delete_cb (G_GNUC_UNUSED 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));
+
+       /* remove FK items */
+       GSList *list;
+       for (list = mtable->fk_list; list; list = list->next) {
+               GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+               GooCanvasItem *fk_item;
+               
+               fk_item = g_hash_table_lookup (dbrel->priv->hash_fkeys, fk);
+               if (fk_item) {
+                       goo_canvas_item_remove (fk_item);
+                       g_hash_table_remove (dbrel->priv->hash_fkeys, fk);
+               }
+       }
+       
+       for (list = mtable->reverse_fk_list; list; list = list->next) {
+               GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+               GooCanvasItem *fk_item;
+               
+               fk_item = g_hash_table_lookup (dbrel->priv->hash_fkeys, fk);
+               if (fk_item) {
+                       goo_canvas_item_remove (fk_item);
+                       g_hash_table_remove (dbrel->priv->hash_fkeys, fk);
+               }
+       }
+}
+
+static void
+popup_func_add_depend_cb (G_GNUC_UNUSED 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;
+
+       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;
+
+               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);
+               browser_canvas_db_relations_add_table (dbrel, v1, v2, v3);
+               gda_value_free (v1);
+               gda_value_free (v2);
+               gda_value_free (v3);
+       }
+}
+
+static void
+popup_func_add_ref_cb (G_GNUC_UNUSED 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;
+       
+       GSList *alldbo, *list;
+
+       alldbo = gda_meta_struct_get_all_db_objects (dbrel->priv->mstruct);
+       for (list = alldbo; list; list = list->next) {
+               GdaMetaDbObject *fkdbo = GDA_META_DB_OBJECT (list->data);
+               if (fkdbo->obj_type != GDA_META_DB_TABLE)
+                       continue;
+
+               GSList *fklist;
+               for (fklist = GDA_META_TABLE (fkdbo)->fk_list; fklist; fklist = fklist->next) {
+                       GdaMetaTableForeignKey *fk = GDA_META_TABLE_FOREIGN_KEY (fklist->data);
+                       if (fk->depend_on != dbo)
+                               continue;
+                       if (g_hash_table_lookup (dbrel->priv->hash_tables, fkdbo))
+                               continue;
+
+                       GValue *v1, *v2, *v3;
+                       g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), fkdbo->obj_catalog);
+                       g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), fkdbo->obj_schema);
+                       g_value_set_string ((v3 = gda_value_new (G_TYPE_STRING)), fkdbo->obj_name);
+                       browser_canvas_db_relations_add_table (dbrel, v1, v2, v3);
+                       gda_value_free (v1);
+                       gda_value_free (v2);
+                       gda_value_free (v3);
+               }
+       }
+       g_slist_free (alldbo);
+}
+
+static GSList *
+complement_layout_items (BrowserCanvasDbRelations *dbrel, BrowserCanvasItem *current, GSList *elist)
+{
+       GSList *items = elist;
+       GdaMetaTable *mtable;
+       mtable = g_hash_table_lookup (dbrel->priv->hash_tables, current);
+       if (!mtable)
+               return items;
+
+       GSList *list;
+       for (list = mtable->fk_list; list; list = list->next) {
+               GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+               BrowserCanvasItem *item;
+
+               item = g_hash_table_lookup (dbrel->priv->hash_fkeys, fk);
+               if (item && !g_slist_find (items, item)) {
+                       items = g_slist_prepend (items, item);
+                       items = complement_layout_items (dbrel, item, items);
+               }
+       }
+
+       for (list = mtable->reverse_fk_list; list; list = list->next) {
+               GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) list->data;
+               BrowserCanvasItem *item;
+
+               item = g_hash_table_lookup (dbrel->priv->hash_fkeys, fk);
+               if (item && !g_slist_find (items, item)) {
+                       items = g_slist_prepend (items, item);
+                       items = complement_layout_items (dbrel, item, items);
+               }
+       }
+
+       return items;
+}
+
+static GSList *
+get_layout_items (BrowserCanvas *canvas)
+{
+       GSList *items = NULL;
+       BrowserCanvasDbRelations *dbrel = BROWSER_CANVAS_DB_RELATIONS (canvas);
+
+       if (!canvas->priv->current_selected_item)
+               return g_slist_copy (canvas->priv->items);
+       
+       GdaMetaTable *mtable;
+       mtable = g_hash_table_lookup (dbrel->priv->hash_tables, canvas->priv->current_selected_item);
+       if (!mtable)
+               return g_slist_copy (canvas->priv->items);
+
+       items = g_slist_prepend (NULL, canvas->priv->current_selected_item);
+       items = complement_layout_items (dbrel, canvas->priv->current_selected_item, items);
+       
+       /* add non related items */
+       GSList *list;
+       for (list = canvas->priv->items; list; list = list->next) {
+               if (!g_slist_find (items, list->data))
+                       items = g_slist_prepend (items, list->data);
+       }
+
+       return g_slist_reverse (items);
+}
+static gint dbo_sort_func (GdaMetaDbObject *dbo1, GdaMetaDbObject *dbo2);
+static void popup_add_table_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *canvas);
+static void table_menu_item_activated_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrel);
+static void popup_add_all_tables_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrel);
+static GtkWidget *
+build_context_menu (BrowserCanvas *canvas)
+{
+       GtkWidget *menu, *submitem, *submenu, *mitem;
+       BrowserCanvasDbRelations *dbrel = BROWSER_CANVAS_DB_RELATIONS (canvas);
+       GSList *list, *all_dbo;
+
+       if (!dbrel->priv->mstruct)
+               return NULL;
+
+       menu = gtk_menu_new ();
+       /* entry to display a window with tables in it */
+       submitem = gtk_menu_item_new_with_label (_("Add tables"));
+       gtk_widget_show (submitem);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), submitem);
+       g_signal_connect (G_OBJECT (submitem), "activate", G_CALLBACK (popup_add_table_cb), canvas);
+
+       /* entry to display sub menus */
+       submitem = gtk_menu_item_new_with_label (_("Add one table"));
+       gtk_widget_show (submitem);
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), submitem);
+       submenu = gtk_menu_new ();
+       gtk_menu_item_set_submenu (GTK_MENU_ITEM (submitem), submenu);
+
+       /* build sub menu */
+       GHashTable *schemas = NULL; /* key = schema name, value = a #GtkMenu as parent */
+       GSList *added_schemas = NULL;
+       schemas = g_hash_table_new (g_str_hash, g_str_equal);
+       all_dbo = gda_meta_struct_get_all_db_objects (dbrel->priv->mstruct);
+       all_dbo = g_slist_sort (all_dbo, (GCompareFunc) dbo_sort_func);
+
+       for (list = all_dbo; list; list = list->next) {
+               GdaMetaDbObject *dbo = GDA_META_DB_OBJECT (list->data);
+               GtkWidget *img;
+               if (dbo->obj_type != GDA_META_DB_TABLE)
+                       continue;
+               if (g_hash_table_lookup (dbrel->priv->hash_tables, dbo))
+                       /* table already present on canvas */
+                       continue;
+               
+               if (strcmp (dbo->obj_short_name, dbo->obj_full_name)) {
+                       mitem = gtk_image_menu_item_new_with_label (dbo->obj_short_name);
+                       img = gtk_image_new_from_pixbuf (ui_get_pixbuf_icon (UI_ICON_TABLE));
+                       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mitem), img);
+                       g_object_set_data (G_OBJECT (mitem), "dbtable", GDA_META_TABLE (dbo));
+                       gtk_menu_shell_prepend (GTK_MENU_SHELL (submenu), mitem);
+                       g_signal_connect (mitem, "activate",
+                                         G_CALLBACK (table_menu_item_activated_cb), dbrel);
+               }
+
+               GtkWidget *schema_menu;
+               schema_menu = g_hash_table_lookup (schemas, dbo->obj_schema);
+               if (!schema_menu) {
+                       mitem = gtk_image_menu_item_new_with_label (dbo->obj_schema);
+                       img = gtk_image_new_from_pixbuf (ui_get_pixbuf_icon (UI_ICON_SCHEMA));
+                       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mitem), img);
+                       gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mitem);
+
+                       schema_menu = gtk_menu_new ();
+                       g_object_set_data (G_OBJECT (schema_menu), "dbo", dbo);
+                       gtk_menu_item_set_submenu (GTK_MENU_ITEM (mitem), schema_menu);
+                       g_hash_table_insert (schemas, dbo->obj_schema, schema_menu);
+                       added_schemas = g_slist_prepend (added_schemas, schema_menu);
+               }
+               
+               mitem = gtk_image_menu_item_new_with_label (dbo->obj_short_name);
+               img = gtk_image_new_from_pixbuf (ui_get_pixbuf_icon (UI_ICON_TABLE));
+               gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mitem), img);
+               g_object_set_data (G_OBJECT (mitem), "dbtable", GDA_META_TABLE (dbo));
+               gtk_menu_shell_prepend (GTK_MENU_SHELL (schema_menu), mitem);
+               g_signal_connect (mitem, "activate",
+                                 G_CALLBACK (table_menu_item_activated_cb), dbrel);
+       }
+       g_slist_free (all_dbo);
+       g_hash_table_destroy (schemas);
+
+       /* entry to add ALL tables */
+       mitem = gtk_separator_menu_item_new ();
+       gtk_widget_show (mitem);
+       gtk_menu_shell_prepend (GTK_MENU_SHELL (submenu), mitem);
+
+       mitem = gtk_menu_item_new_with_label (_("Add all tables"));
+       gtk_widget_show (mitem);
+       gtk_menu_shell_prepend (GTK_MENU_SHELL (submenu), mitem);
+       g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK (popup_add_all_tables_cb), dbrel);
+
+       /* entry below each schema sub menu to add all tables in schema */
+       for (list = added_schemas; list; list = list->next) {
+               GdaMetaDbObject *dbo;
+               dbo = g_object_get_data (G_OBJECT (list->data), "dbo");
+               g_assert (dbo);
+
+               mitem = gtk_separator_menu_item_new ();
+               gtk_widget_show (mitem);
+               gtk_menu_shell_prepend (GTK_MENU_SHELL (list->data), mitem);
+
+               mitem = gtk_menu_item_new_with_label (_("Add all tables in schema"));
+               gtk_widget_show (mitem);
+               gtk_menu_shell_prepend (GTK_MENU_SHELL (list->data), mitem);
+               g_object_set_data_full (G_OBJECT (mitem), "schema", g_strdup (dbo->obj_schema),
+                                       g_free);
+               g_signal_connect (G_OBJECT (mitem), "activate",
+                                 G_CALLBACK (popup_add_all_tables_cb), dbrel);
+       }
+       g_slist_free (added_schemas);
+
+       gtk_widget_show_all (submenu);
+
+       return menu;
+}
+
+static gint
+dbo_sort_func (GdaMetaDbObject *dbo1, GdaMetaDbObject *dbo2)
+{
+       const gchar *n1, *n2;
+       g_assert (dbo1);
+       g_assert (dbo2);
+       if (dbo1->obj_name[0] ==  '"')
+               n1 = dbo1->obj_name + 1;
+       else
+               n1 = dbo1->obj_name;
+       if (dbo2->obj_name[0] ==  '"')
+               n2 = dbo2->obj_name + 1;
+       else
+               n2 = dbo2->obj_name;
+       return strcmp (n2, n1);
+}
+
+static void
+popup_add_all_tables_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrel)
+{
+       GSList *all_dbo, *list;
+       const gchar *schema;
+       schema = g_object_get_data (G_OBJECT (mitem), "schema");
+       all_dbo = gda_meta_struct_get_all_db_objects (dbrel->priv->mstruct);
+       for (list = all_dbo; list; list = list->next) {
+               GdaMetaDbObject *dbo = GDA_META_DB_OBJECT (list->data);
+               if (dbo->obj_type != GDA_META_DB_TABLE)
+                       continue;
+               if (g_hash_table_lookup (dbrel->priv->hash_tables, dbo))
+                       /* table already present on canvas */
+                       continue;
+               if (schema && strcmp (schema, dbo->obj_schema))
+                       continue;
+               
+               GValue *table_schema;
+               GValue *table_name;
+               BrowserCanvasTable *ctable;
+               GooCanvasBounds bounds;
+               gdouble x, y;
+               g_value_set_string ((table_schema = gda_value_new (G_TYPE_STRING)), dbo->obj_schema);
+               g_value_set_string ((table_name = gda_value_new (G_TYPE_STRING)), dbo->obj_name);
+               
+               x = BROWSER_CANVAS (dbrel)->xmouse;
+               y = BROWSER_CANVAS (dbrel)->ymouse;
+               
+               ctable = browser_canvas_db_relations_add_table (dbrel, NULL, table_schema, table_name);
+               goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ctable), &bounds);
+               browser_canvas_item_translate (BROWSER_CANVAS_ITEM (ctable),
+                                              x - bounds.x1, y - bounds.y1);
+               gda_value_free (table_schema);
+               gda_value_free (table_name);
+       }
+       g_slist_free (all_dbo);
+}
+
+static void
+table_menu_item_activated_cb (GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrel)
+{
+       GValue *table_schema;
+       GValue *table_name;
+       GdaMetaTable *mtable;
+       GdaMetaDbObject *dbo;
+       BrowserCanvasTable *ctable;
+       GooCanvasBounds bounds;
+       gdouble x, y;
+
+       mtable = g_object_get_data (G_OBJECT (mitem), "dbtable");
+       if (! mtable)
+               return;
+       
+       dbo = GDA_META_DB_OBJECT (mtable);
+       g_value_set_string ((table_schema = gda_value_new (G_TYPE_STRING)), dbo->obj_schema);
+       g_value_set_string ((table_name = gda_value_new (G_TYPE_STRING)), dbo->obj_name);
+
+       x = BROWSER_CANVAS (dbrel)->xmouse;
+       y = BROWSER_CANVAS (dbrel)->ymouse;
+
+       ctable = browser_canvas_db_relations_add_table (dbrel, NULL, table_schema, table_name);
+       goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ctable), &bounds);
+       browser_canvas_item_translate (BROWSER_CANVAS_ITEM (ctable),
+                                      x - bounds.x1, y - bounds.y1);
+       gda_value_free (table_schema);
+       gda_value_free (table_name);
+}
+
+static gboolean
+add_dialog_delete_event (GtkWidget *dialog, G_GNUC_UNUSED GdkEvent *event, G_GNUC_UNUSED gpointer data)
+{
+       gtk_widget_hide (dialog);
+       return TRUE;
+}
+
+static void
+popup_add_table_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvasDbRelations *dbrels)
+{
+       if (! dbrels->priv->add_dialog) {
+               GtkWidget *vbox, *cloud, *find, *dcontents;
+               dbrels->priv->add_dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+               gtk_window_set_title (GTK_WINDOW (dbrels->priv->add_dialog),
+                                     _("Select tables to add to diagram"));
+               gtk_window_set_transient_for (GTK_WINDOW (dbrels->priv->add_dialog),
+                                             (GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) dbrels));
+               g_signal_connect (dbrels->priv->add_dialog, "delete-event",
+                                 G_CALLBACK (add_dialog_delete_event), NULL);
+               gtk_window_set_default_size (GTK_WINDOW (dbrels->priv->add_dialog), 430, 400);
+               
+               g_object_set_data (G_OBJECT (dbrels->priv->add_dialog), "__canvas", dbrels);
+
+               vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+               gtk_container_add (GTK_CONTAINER (dbrels->priv->add_dialog), vbox);
+               
+               cloud = objects_cloud_new (dbrels->priv->mstruct, OBJECTS_CLOUD_TYPE_TABLE);
+               dbrels->priv->cloud = OBJECTS_CLOUD (cloud);
+               gtk_widget_set_size_request (GTK_WIDGET (cloud), 200, 300);
+               g_signal_connect (cloud, "selected",
+                                 G_CALLBACK (cloud_object_selected_cb), dbrels);
+               gtk_box_pack_start (GTK_BOX (vbox), cloud, TRUE, TRUE, 0);
+               
+               find = objects_cloud_create_filter (OBJECTS_CLOUD (cloud));
+               gtk_box_pack_start (GTK_BOX (vbox), find, FALSE, FALSE, 0);
+               
+               gtk_widget_show_all (vbox);
+       }
+
+       gtk_widget_show (dbrels->priv->add_dialog);
+}
+
+/**
+ * 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: (allow-none): the catalog in which the table is, or %NULL
+ * @table_schema: (allow-none): the schema in which the table is, or %NULL
+ * @table_name: the table's name
+ *
+ * Add a table to @canvas.
+ *
+ * Returns: (transfer none): 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);
+
+       GdaMetaTable *mtable;
+       GooCanvas *goocanvas;
+       GError *lerror = NULL;
+
+       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, 
&lerror);
+       if (mtable) {
+               gdouble x = 0, y = 0;
+               GooCanvasItem *table_item;
+
+               table_item = g_hash_table_lookup (canvas->priv->hash_tables, mtable);
+               if (table_item)
+                       return BROWSER_CANVAS_TABLE (table_item);
+
+               table_item = browser_canvas_table_new (goo_canvas_get_root_item (goocanvas), 
+                                                      canvas->priv->mstruct, 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);
+               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));
+               goo_canvas_item_raise (GOO_CANVAS_ITEM (table_item), canvas->priv->level_separator);
+
+               /* 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 = g_hash_table_lookup (canvas->priv->hash_fkeys, fk);
+                               if (!fk_item) {
+                                       fk_item = browser_canvas_fkey_new (goo_canvas_get_root_item 
(goocanvas),
+                                                                          canvas->priv->mstruct, 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);
+                                       goo_canvas_item_lower (GOO_CANVAS_ITEM (fk_item),
+                                                              canvas->priv->level_separator);
+                               }
+                       }
+               }
+               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 = g_hash_table_lookup (canvas->priv->hash_fkeys, fk);
+                               if (!fk_item) {
+                                       fk_item = browser_canvas_fkey_new (goo_canvas_get_root_item 
(goocanvas),
+                                                                          canvas->priv->mstruct, 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);
+                                       goo_canvas_item_lower (GOO_CANVAS_ITEM (fk_item),
+                                                              canvas->priv->level_separator);
+                               }
+                       }
+               }
+
+               return BROWSER_CANVAS_TABLE (table_item);
+       }
+       else {
+               g_print ("WARNING: %s\n", lerror && lerror->message ? lerror->message : "No detail");
+               g_clear_error (&lerror);
+               return NULL;
+       }
+}
+
+/**
+ * browser_canvas_db_relations_select_table
+ */
+void
+browser_canvas_db_relations_select_table (BrowserCanvasDbRelations *canvas,
+                                         BrowserCanvasTable *table)
+{
+       g_return_if_fail (IS_BROWSER_CANVAS_DB_RELATIONS (canvas));
+       g_return_if_fail (!table || IS_BROWSER_CANVAS_ITEM (table));
+
+       browser_canvas_item_toggle_select (BROWSER_CANVAS (canvas), (BrowserCanvasItem*) table);
+}
+
+/**
+ * browser_canvas_db_relations_items_to_data_manager
+ */
+gchar *
+browser_canvas_db_relations_items_to_data_manager (BrowserCanvasDbRelations *canvas)
+{
+       gchar *retval = NULL;
+       GSList *list;
+       xmlDocPtr doc;
+       xmlNodePtr topnode;
+
+       g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), NULL);
+       
+       /* create XML doc and root node */
+       doc = xmlNewDoc (BAD_CAST "1.0");
+       topnode = xmlNewDocNode (doc, NULL, BAD_CAST "data", NULL);
+        xmlDocSetRootElement (doc, topnode);
+       
+       /* actually serialize all the items which can be serialized */
+       for (list = BROWSER_CANVAS (canvas)->priv->items; list; list = list->next) {
+                BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+               GdaMetaTable *mtable;
+
+               mtable = g_hash_table_lookup (canvas->priv->hash_tables, item);
+               if (mtable) {
+                       xmlNodePtr node;
+                       node = xmlNewChild (topnode, NULL, BAD_CAST "table", NULL);
+                       xmlSetProp (node, BAD_CAST "name",
+                                   BAD_CAST GDA_META_DB_OBJECT (mtable)->obj_short_name);
+
+                       GSList *fklist;
+                       for (fklist = mtable->fk_list; fklist; fklist = fklist->next) {
+                               GdaMetaTableForeignKey *fk = (GdaMetaTableForeignKey*) fklist->data;
+                               GooCanvasItem *fk_item;
+                               
+                               fk_item = g_hash_table_lookup (canvas->priv->hash_fkeys, fk);
+                               if (fk_item) {
+                                       node = xmlNewChild (node, NULL, BAD_CAST "depend", NULL);
+                                       xmlSetProp (node, BAD_CAST "foreign_key_table",
+                                                   BAD_CAST fk->depend_on->obj_short_name);
+                               }
+                       }
+
+               }
+       }
+
+       /* create buffer from XML tree */
+       xmlChar *xstr = NULL;
+       xmlDocDumpFormatMemory (doc, &xstr, NULL, 1);
+       if (xstr) {
+               retval = g_strdup ((gchar *) xstr);
+               xmlFree (xstr);
+       }
+       xmlFreeDoc (doc);
+       
+       return retval;  
+}
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..59c4267
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-db-relations.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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);
+void                browser_canvas_db_relations_select_table          (BrowserCanvasDbRelations *canvas,
+                                                                      BrowserCanvasTable *table);
+gchar              *browser_canvas_db_relations_items_to_data_manager (BrowserCanvasDbRelations *canvas);
+
+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..3aaa395
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-decl.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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..48a4b66
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-fkey.c
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2009 - 2014 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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"
+#include "../ui-support.h"
+#include "../browser-window.h"
+#include "../fk-declare.h"
+#include "../common/t-utils.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_META_STRUCT,
+       PROP_FK_CONSTRAINT
+};
+
+struct _BrowserCanvasFkeyPrivate
+{
+       GdaMetaStruct          *mstruct;
+       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;
+static GooCanvasLineDash *dash = NULL, *no_dash = 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,
+                       0
+               };              
+
+               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_META_STRUCT,
+                 g_param_spec_object ("meta-struct", NULL, NULL, 
+                                     GDA_TYPE_META_STRUCT,
+                                     (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
+       g_object_class_install_property (object_class, PROP_FK_CONSTRAINT,
+                                        g_param_spec_pointer ("fk_constraint", "FK constraint", 
+                                                              NULL, 
+                                                              G_PARAM_WRITABLE));
+
+       dash = goo_canvas_line_dash_new (2, 5., 1.5);
+       no_dash = goo_canvas_line_dash_new (0);
+}
+
+static void
+browser_canvas_fkey_init (BrowserCanvasFkey *cc)
+{
+       cc->priv = g_new0 (BrowserCanvasFkeyPrivate, 1);
+       cc->priv->mstruct = NULL;
+       cc->priv->fk = NULL;
+       cc->priv->fk_table_item = NULL;
+       cc->priv->ref_pk_table_item = NULL;
+       cc->priv->shapes = NULL;
+}
+
+static void
+fk_table_item_weak_ref_lost (BrowserCanvasFkey *cc, G_GNUC_UNUSED BrowserCanvasTable *old_table_item)
+{
+       cc->priv->fk_table_item = NULL;
+}
+
+static void
+ref_pk_table_item_weak_ref_lost (BrowserCanvasFkey *cc, G_GNUC_UNUSED BrowserCanvasTable *old_table_item)
+{
+       cc->priv->ref_pk_table_item = 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);
+       if (cc->priv->mstruct) {
+               g_object_unref (cc->priv->mstruct);
+               cc->priv->mstruct = NULL;
+       }
+       cc->priv->fk = NULL;
+       if (cc->priv->fk_table_item) {
+               g_object_weak_unref (G_OBJECT (cc->priv->fk_table_item),
+                                    (GWeakNotify) fk_table_item_weak_ref_lost, cc);
+               cc->priv->fk_table_item = NULL;
+       }
+       if (cc->priv->ref_pk_table_item) {
+               g_object_weak_unref (G_OBJECT (cc->priv->ref_pk_table_item),
+                                    (GWeakNotify) ref_pk_table_item_weak_ref_lost, cc);
+               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_META_STRUCT:
+               cc->priv->mstruct = g_value_dup_object (value);
+               break;
+       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;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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) {
+       case PROP_META_STRUCT:
+               g_value_set_object (value, cc->priv->mstruct);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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);
+
+/* 
+ * 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_object_weak_unref (G_OBJECT (cc->priv->fk_table_item),
+                                    (GWeakNotify) fk_table_item_weak_ref_lost, 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_object_weak_unref (G_OBJECT (cc->priv->ref_pk_table_item),
+                                    (GWeakNotify) ref_pk_table_item_weak_ref_lost, 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_object_weak_ref (G_OBJECT (table_item), (GWeakNotify) fk_table_item_weak_ref_lost, cc);
+
+       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);
+
+       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_object_weak_ref (G_OBJECT (table_item), (GWeakNotify) ref_pk_table_item_weak_ref_lost, cc);
+       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);
+
+       /* 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,
+                             "line-dash", GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (cc->priv->fk) ? dash : 
no_dash,
+                             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);
+}
+
+/*
+ * item is for a single FK constraint
+ */
+static gboolean
+single_item_enter_notify_event_cb (GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item,
+                                  G_GNUC_UNUSED 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);
+
+               gchar *str;
+               str = g_strdup_printf ("%s '%s'\n%s: %s\n%s: %s",
+                                      GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (cc->priv->fk) ? 
+                                      _("Declared foreign key") : _("Foreign key"),
+                                      cc->priv->fk->fk_name,
+                                      _("Policy on UPDATE"),
+                                      t_utils_fk_policy_to_string 
(GDA_META_TABLE_FOREIGN_KEY_ON_UPDATE_POLICY (cc->priv->fk)),
+                                      _("Policy on DELETE"),
+                                      t_utils_fk_policy_to_string 
(GDA_META_TABLE_FOREIGN_KEY_ON_DELETE_POLICY (cc->priv->fk)));
+               gtk_widget_set_tooltip_text (GTK_WIDGET (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (ci))),
+                                            str);
+               g_free (str);
+       }
+
+       return FALSE;
+}
+
+static gboolean
+single_item_leave_notify_event_cb (G_GNUC_UNUSED GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item,
+                                  G_GNUC_UNUSED 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 void
+delete_declared_fk_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvasFkey *cc)
+{
+       GError *error = NULL;
+       GtkWidget *parent;
+       parent = (GtkWidget*) gtk_widget_get_toplevel ((GtkWidget*) goo_canvas_item_get_canvas 
(GOO_CANVAS_ITEM (cc)));
+       if (! fk_declare_undeclare (cc->priv->mstruct,
+                                   BROWSER_IS_WINDOW (parent) ? BROWSER_WINDOW (parent) : NULL,
+                                   cc->priv->fk, &error)) {
+               ui_show_error ((GtkWindow *) parent, _("Failed to undeclare foreign key: %s"),
+                              error && error->message ? error->message : _("No detail"));
+               g_clear_error (&error);
+       }
+       else if (BROWSER_IS_WINDOW (parent))
+               browser_window_show_notice (BROWSER_WINDOW (parent),
+                                           GTK_MESSAGE_INFO, "fkdeclare",
+                                           _("Successfully undeclared foreign key"));
+       else
+               ui_show_message ((GtkWindow *) parent, "%s",
+                                _("Successfully undeclared foreign key"));
+}
+
+static gboolean
+single_item_button_press_event_cb (G_GNUC_UNUSED GooCanvasItem *ci, G_GNUC_UNUSED GooCanvasItem *target_item,
+                                  G_GNUC_UNUSED GdkEventButton *event, BrowserCanvasFkey *cc)
+{
+       GdaMetaTableForeignKey *fk = g_object_get_data (G_OBJECT (ci), "fkcons");
+       if (GDA_META_TABLE_FOREIGN_KEY_IS_DECLARED (fk)) {
+               GtkWidget *menu, *entry;
+               
+               menu = gtk_menu_new ();
+               entry = gtk_menu_item_new_with_label (_("Remove this declared foreign key"));
+               g_object_set_data (G_OBJECT (entry), "fkcons", fk);
+               g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (delete_declared_fk_cb), cc);
+               gtk_menu_shell_append (GTK_MENU_SHELL (menu), entry);
+               gtk_widget_show (entry);
+               gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                               NULL, NULL, ((GdkEventButton *)event)->button,
+                               ((GdkEventButton *)event)->time);
+               return TRUE;
+       }
+       else
+               return FALSE;
+}
+
+static void
+table_item_moved_cb (G_GNUC_UNUSED GooCanvasItem *table, BrowserCanvasFkey *cc)
+{
+       update_items (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, GdaMetaStruct *mstruct, GdaMetaTableForeignKey *fkcons, ...)
+{
+       GooCanvasItem *item;
+       const char *first_property;
+       va_list var_args;
+
+       g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL);
+
+       item = g_object_new (TYPE_BROWSER_CANVAS_FKEY, "meta-struct", mstruct, 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..c4318f0
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-fkey.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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,
+                                                  GdaMetaStruct *mstruct, 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..e50182e
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-item.c
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "browser-canvas-item.h"
+#include "browser-canvas.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;
+       gboolean            allow_select;
+
+       gchar              *tooltip_text;
+};
+
+enum
+{
+       MOVED,
+       MOVING,
+       LAST_SIGNAL
+};
+
+enum
+{
+       PROP_0,
+       PROP_ALLOW_MOVE,
+       PROP_ALLOW_SELECT,
+       PROP_TOOLTIP_TEXT
+};
+
+static gint browser_canvas_item_signals[LAST_SIGNAL] = { 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,
+                       0
+               };
+               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);
+
+       class->moved = NULL;
+       class->moving = 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, FALSE, (G_PARAM_READABLE | 
G_PARAM_WRITABLE)));
+       g_object_class_install_property
+                (object_class, PROP_ALLOW_SELECT,
+                 g_param_spec_boolean ("allow-select", NULL, NULL, FALSE, (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) {
+               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_ALLOW_SELECT:
+               citem->priv->allow_select = 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;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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_ALLOW_SELECT:
+                g_value_set_boolean (value, citem->priv->allow_select);
+                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: (allow-none): a place to store the FROM part of the edge, or %NULL
+ * @to: (allow-none): 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, G_GNUC_UNUSED GooCanvasItem *target_item,
+                   G_GNUC_UNUSED GdkEventCrossing *event, G_GNUC_UNUSED 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, G_GNUC_UNUSED GooCanvasItem *target_item,
+                   GdkEventButton *event, G_GNUC_UNUSED 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_with_coordinates (GTK_WIDGET (canvas),
+                                                                          target_list,
+                                                                          
GDK_ACTION_MOVE|GDK_ACTION_COPY|GDK_ACTION_DEFAULT,
+                                                                          event->button, (GdkEvent*) event,
+                                                                          -1, -1);
+                               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_select && (event->state & GDK_CONTROL_MASK)) {
+                               browser_canvas_item_toggle_select (browser_canvas_item_get_canvas (citem), 
citem);
+                               done = TRUE;
+                       }
+                       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;
+                               goo_canvas_pointer_grab (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (citem)),
+                                                        GOO_CANVAS_ITEM (citem),
+                                                        GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
+                                                        NULL, event->time);
+                       }
+               }
+               break;
+       default:
+               break;
+       }
+
+       return done;
+}
+
+static gboolean
+button_release_event (BrowserCanvasItem *citem, G_GNUC_UNUSED GooCanvasItem *target_item,
+                     GdkEventButton *event, G_GNUC_UNUSED gpointer data)
+{
+       if (citem->priv->allow_move) {
+               citem->priv->moving = FALSE;
+               goo_canvas_pointer_ungrab (goo_canvas_item_get_canvas (GOO_CANVAS_ITEM (citem)),
+                                          GOO_CANVAS_ITEM (citem), event->time);
+#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, G_GNUC_UNUSED GooCanvasItem *target_item,
+                    GdkEventMotion *event, G_GNUC_UNUSED 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..891dfcc
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-item.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __BROWSER_CANVAS_ITEM__
+#define __BROWSER_CANVAS_ITEM__
+
+#include <goocanvas.h>
+#include "browser-canvas-decl.h"
+#include <libxml/tree.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);
+
+       /* 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);
+       void (*set_selected)  (BrowserCanvasItem *citem, gboolean selected);
+
+       /* serialization and de-serialization virtual methods (don't need to be implemented) */
+       xmlNodePtr (*serialize) (BrowserCanvasItem *citem);
+};
+
+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..610feae
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-print.c
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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, G_GNUC_UNUSED 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 (G_GNUC_UNUSED GtkPrintOperation *operation, G_GNUC_UNUSED GtkPrintContext *context,
+          G_GNUC_UNUSED PrintPageData *pdata)
+{
+       
+}
+
+static void
+print_draw_page (G_GNUC_UNUSED 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 (G_GNUC_UNUSED GtkPrintOperation *operation, PrintPageData *pdata)
+{
+       GtkWidget *vbox, *bbox, *button, *label, *hbox, *grid, *entry;
+       PrintCustomData *cdata;
+
+       cdata = g_new0 (PrintCustomData, 1);
+       cdata->pdata = pdata;
+
+       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+       gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
+
+       /* page size's adjustments */
+       bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+       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_box_new (GTK_ORIENTATION_HORIZONTAL, 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);
+
+       grid = gtk_grid_new ();
+       gtk_box_pack_start (GTK_BOX (hbox), grid, TRUE, TRUE, 0);
+
+       label = gtk_label_new (_("Number of pages used:"));
+       gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+       gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
+
+       entry = gtk_spin_button_new_with_range (1., 100., 1.);
+       gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 0);
+       gtk_grid_attach (GTK_GRID (grid), entry, 1, 0, 1, 1);
+       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_grid_attach (GTK_GRID (grid), label, 2, 0, 1, 1);
+
+       entry = gtk_spin_button_new_with_range (1., 100., 1.);
+       gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 0);
+       gtk_grid_attach (GTK_GRID (grid), entry, 1, 1, 1, 1);
+       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_grid_attach (GTK_GRID (grid), label, 2, 1, 1, 1);
+
+       label = gtk_label_new (_("Zoom factor:"));
+       gtk_misc_set_alignment (GTK_MISC (label), 0., 0.5);
+       gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1);
+
+       entry = gtk_spin_button_new_with_range (.1, 10., .05);
+       gtk_spin_button_set_digits (GTK_SPIN_BUTTON (entry), 2);
+       gtk_grid_attach (GTK_GRID (grid), entry, 1, 2, 1, 1);
+       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_box_new (GTK_ORIENTATION_HORIZONTAL, 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_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+       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..444ab1f
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-print.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2001 - 2002 Gonzalo Paniagua Javier <gonzalo gnome-db org>
+ * Copyright (C) 2001 - 2002 Rodrigo Moya <rodrigo gnome-db org>
+ * Copyright (C) 2003 Danilo Schoeneberg <dj starfire-programming net>
+ * Copyright (C) 2003 Laurent Sansonetti <lrz gnome org>
+ * Copyright (C) 2005 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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..41681e5
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-priv.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __BROWSER_CANVAS_PRIV__
+#define __BROWSER_CANVAS_PRIV__
+
+#include <goocanvas.h>
+#include "browser-canvas-decl.h"
+
+G_BEGIN_DECLS
+
+struct _BrowserCanvasPrivate
+{
+       GooCanvas          *goocanvas;
+       GSList             *items; /* BrowserCanvasItem objects, non ordered */
+
+       gboolean            canvas_moving;
+
+       BrowserCanvasItem  *current_selected_item;
+};
+
+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..77b8c7a
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-table.c
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "browser-canvas.h"
+#include "browser-canvas-priv.h"
+#include "browser-canvas-table.h"
+#include "browser-canvas-column.h"
+#include <glib/gi18n-lib.h>
+#include <string.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);
+static void browser_canvas_table_set_selected (BrowserCanvasItem *citem, gboolean selected);
+
+static xmlNodePtr browser_canvas_table_serialize (BrowserCanvasItem *citem);
+
+enum
+{
+       PROP_0,
+       PROP_META_STRUCT,
+       PROP_TABLE,
+       PROP_MENU_FUNC
+};
+
+struct _BrowserCanvasTablePrivate
+{
+       GdaMetaStruct      *mstruct;
+       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);
+
+       GooCanvasItem      *selection_mark;
+};
+
+/* 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,
+                       0
+               };              
+
+               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;
+       iclass->set_selected = browser_canvas_table_set_selected;
+       iclass->serialize = browser_canvas_table_serialize;
+
+       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_META_STRUCT,
+                 g_param_spec_object ("meta-struct", NULL, NULL, 
+                                     GDA_TYPE_META_STRUCT,
+                                     (G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)));
+       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->mstruct = NULL;
+       table->priv->table = NULL;
+       table->priv->column_ypos = NULL;
+       table->priv->popup_menu_func = NULL;
+
+       table->priv->selection_mark = NULL;
+
+       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;
+       if (ce->priv->mstruct) {
+               g_object_unref (ce->priv->mstruct);
+               ce->priv->mstruct = 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_META_STRUCT:
+               ce->priv->mstruct = g_value_dup_object (value);
+               break;
+       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;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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_META_STRUCT:
+               g_value_set_object (value, ce->priv->mstruct);
+               break;
+       case PROP_TABLE:
+               g_value_set_pointer (value, ce->priv->table);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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.
+#define SELECTION_SIZE 4.
+        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, GOO_CANVAS_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),
+                                                 ce->priv->mstruct,
+                                                 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);
+
+       ce->priv->selection_mark = goo_canvas_rect_new (GOO_CANVAS_ITEM (ce), border_bounds.x1 - 
SELECTION_SIZE,
+                                                       border_bounds.y1 - SELECTION_SIZE, 
+                                                       border_bounds.x2 + 2 * SELECTION_SIZE,
+                                                       border_bounds.y2 + 2 * SELECTION_SIZE,
+                                                       "radius-x", RADIUS_X,
+                                                       "radius-y", RADIUS_Y,
+                                                       "fill-color", "#11d155",//"#ffea08",
+                                                       "stroke-color", "#11d155",//"#ffea08",
+                                                       NULL);
+       g_object_set (G_OBJECT (ce->priv->selection_mark), "visibility", GOO_CANVAS_ITEM_HIDDEN, NULL);
+
+       /* 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);
+       goo_canvas_item_lower (ce->priv->selection_mark, 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, G_GNUC_UNUSED GooCanvasItem  *target_item,
+                      GdkEventButton *event,
+                      G_GNUC_UNUSED gpointer data)
+{
+       if ((event->button == 3) && 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, GdaMetaStruct *mstruct, GdaMetaTable *table, 
+                        gdouble x, gdouble y, ...)
+{
+       GooCanvasItem *item;
+       const char *first_property;
+       va_list var_args;
+
+       g_return_val_if_fail (GDA_IS_META_STRUCT (mstruct), NULL);
+
+       item = g_object_new (TYPE_BROWSER_CANVAS_TABLE, "meta-struct", mstruct, 
+                            "allow-move", TRUE,
+                            "allow-select", 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, G_GNUC_UNUSED GdkDragContext *drag_context,
+                                   GtkSelectionData *data, G_GNUC_UNUSED guint info,
+                                   G_GNUC_UNUSED 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, gtk_selection_data_get_target (data), 8, (guchar*) str, strlen (str));
+       g_free (str);
+}
+
+static void
+browser_canvas_table_set_selected (BrowserCanvasItem *citem, gboolean selected)
+{
+       g_object_set (G_OBJECT (BROWSER_CANVAS_TABLE (citem)->priv->selection_mark),
+                     "visibility", selected ? GOO_CANVAS_ITEM_VISIBLE : GOO_CANVAS_ITEM_HIDDEN, NULL);
+}
+
+static xmlNodePtr
+browser_canvas_table_serialize (BrowserCanvasItem *citem)
+{
+       BrowserCanvasTable *ctable;
+
+       ctable = BROWSER_CANVAS_TABLE (citem);
+       if (!ctable->priv->table)
+               return NULL;
+
+       GdaMetaDbObject *dbo;
+       xmlNodePtr node;
+       GooCanvasBounds bounds;
+       gchar *str;
+
+       dbo = GDA_META_DB_OBJECT (ctable->priv->table);
+       node = xmlNewNode (NULL, BAD_CAST "table");
+       xmlSetProp (node, BAD_CAST "schema", BAD_CAST (dbo->obj_schema));
+       xmlSetProp (node, BAD_CAST "name", BAD_CAST (dbo->obj_name));
+       goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (citem), &bounds);
+       str = g_strdup_printf ("%.1f", bounds.x1);
+       xmlSetProp (node, BAD_CAST "x", BAD_CAST str);
+       g_free (str);
+       str = g_strdup_printf ("%.1f", bounds.y1);
+       xmlSetProp (node, BAD_CAST "y", BAD_CAST str);
+       g_free (str);
+       
+       return node;
+}
+
+/**
+ * browser_canvas_table_get_anchor_bounds
+ *
+ * Get the bounds to be used to compute anchors, ie. without the selection mark or any other
+ * artefact not part of the table's rectangle.
+ */
+void
+browser_canvas_table_get_anchor_bounds (BrowserCanvasTable *ce, GooCanvasBounds *bounds)
+{
+       g_return_if_fail (IS_BROWSER_CANVAS_TABLE (ce));
+       g_return_if_fail (bounds);
+
+       goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (ce), bounds);
+       bounds->x1 += SELECTION_SIZE;
+       bounds->y1 += SELECTION_SIZE;
+       bounds->x2 -= SELECTION_SIZE;
+       bounds->y2 -= SELECTION_SIZE;
+}
diff --git a/tools/browser/canvas/browser-canvas-table.h b/tools/browser/canvas/browser-canvas-table.h
new file mode 100644
index 0000000..5d59daf
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-table.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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, 
+                                                          GdaMetaStruct *mstruct, 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);
+void                 browser_canvas_table_get_anchor_bounds (BrowserCanvasTable *ce, GooCanvasBounds 
*bounds);
+
+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..644bca9
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-text.c
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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,
+                       0
+               };              
+
+               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;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+static void 
+browser_canvas_text_get_property    (GObject *object,
+                                   guint param_id,
+                                   G_GNUC_UNUSED GValue *value,
+                                   GParamSpec *pspec)
+{
+       switch (param_id) {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               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, GOO_CANVAS_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 (G_GNUC_UNUSED GooCanvasItem *item, G_GNUC_UNUSED GooCanvasItem *target_item,
+                G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasText *ct)
+{
+       browser_canvas_text_set_highlight (ct, TRUE);
+       return FALSE;
+}
+
+static gboolean 
+leave_notify_cb (G_GNUC_UNUSED GooCanvasItem *item, G_GNUC_UNUSED GooCanvasItem *target_item,
+                G_GNUC_UNUSED GdkEventCrossing *event, BrowserCanvasText *ct)
+{
+       browser_canvas_text_set_highlight (ct, FALSE); 
+       return FALSE;
+}
+
+static guint
+compute_step_value (guint current, guint end)
+{
+       const guint 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..e38bd13
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-text.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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..0e7edb7
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-utility.c
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "browser-canvas-utility.h"
+#include <math.h>
+#include <string.h>
+
+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, GooCanvasAnchorType *anchor_type);
+
+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);
+
+       browser_canvas_table_get_anchor_bounds (fk_ent, &bounds);
+       fx1 = bounds.x1;
+       fy1 = bounds.y1;
+       fx2 = bounds.x2;
+       fy2 = bounds.y2;
+       browser_canvas_table_get_anchor_bounds (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,
+                                                                   GOO_CANVAS_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,
+                                                                   GOO_CANVAS_ANCHOR_NORTH, NULL);
+                                       retval = browser_canvas_canvas_shape_add_to_list (retval, id, item);
+                               }
+                       }
+                       
+                       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.;
+                               GooCanvasAnchorType 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;
+                               GooCanvasAnchorType 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);
+                               }
+                       }
+
+                       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, GooCanvasAnchorType *anchor_type)
+{
+       gdouble mxoff, myoff;
+       GooCanvasAnchorType atype = GOO_CANVAS_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);
+       browser_canvas_table_get_anchor_bounds (ent1, &bounds);
+       xl1 = bounds.x1;
+       yt1 = bounds.y1;
+       xr1 = bounds.x2;
+       browser_canvas_table_get_anchor_bounds (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.;
+               GooCanvasAnchorType 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;
+               GooCanvasAnchorType 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);
+               }
+       }
+
+       goo_canvas_points_unref (points);
+
+       return retval;
+}
+
+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..1eee72a
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas-utility.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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..7ac57bf
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas.c
@@ -0,0 +1,1183 @@
+/*
+ * Copyright (C) 2009 - 2014 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2010 David King <davidk openismus com>
+ * Copyright (C) 2010 - 2011 Murray Cumming <murrayc murrayc com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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>
+#include <libgda-ui/libgda-ui.h>
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
+#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);
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass *parent_class = NULL;
+
+enum
+{
+       ITEM_SELECTED,
+       LAST_SIGNAL
+};
+
+static gint canvas_signals[LAST_SIGNAL] = { 0 };
+
+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,
+                       0
+               };              
+
+               type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW, "BrowserCanvas", &info, 0);
+       }
+       return type;
+}
+
+static void
+browser_canvas_class_init (BrowserCanvasClass *klass)
+{
+       GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+       parent_class = g_type_class_peek_parent (klass);
+
+       /* signals */
+       canvas_signals[ITEM_SELECTED] =
+               g_signal_new ("item-selected",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_FIRST,
+                             G_STRUCT_OFFSET (BrowserCanvasClass, item_selected),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
+                             TYPE_BROWSER_CANVAS_ITEM);
+
+       /* 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 gboolean canvas_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas);
+static gboolean motion_notify_event_cb (BrowserCanvas *canvas, GdkEvent *event, GooCanvas *gcanvas);
+static gboolean canvas_scroll_event_cb (GooCanvas *gcanvas, GdkEvent *event, BrowserCanvas *canvas);
+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->priv->current_selected_item = NULL;
+
+       canvas->xmouse = 50.;
+       canvas->ymouse = 50.;
+       
+       g_signal_connect (canvas, "event",
+                         G_CALLBACK (canvas_event_cb), canvas->priv->goocanvas);
+       g_signal_connect (canvas->priv->goocanvas, "scroll-event",
+                         G_CALLBACK (canvas_scroll_event_cb), canvas);
+       g_signal_connect (canvas, "motion-notify-event",
+                         G_CALLBACK (motion_notify_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", GOO_CANVAS_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, G_GNUC_UNUSED GdkDragContext *drag_context,
+              G_GNUC_UNUSED 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, G_GNUC_UNUSED 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 (G_GNUC_UNUSED BrowserCanvas *canvas, GdkDragContext *context,
+                      gint x, gint y, G_GNUC_UNUSED GtkSelectionData *data,
+                      G_GNUC_UNUSED guint info, guint time, GooCanvas *gcanvas)
+{
+       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 gboolean
+canvas_scroll_event_cb (G_GNUC_UNUSED GooCanvas *gcanvas, GdkEvent *event, BrowserCanvas *canvas)
+{
+       gboolean done = TRUE;
+       GdkEventScroll *ev = (GdkEventScroll *) event;
+
+       switch (event->type) {
+       case GDK_SCROLL:
+               if (ev->state & GDK_SHIFT_MASK) {
+                       if (ev->direction == GDK_SCROLL_UP)
+                               browser_canvas_scale_layout (canvas, 1.05);
+                       else
+                               browser_canvas_scale_layout (canvas, .95);
+               }
+               else {
+                       if (ev->direction == GDK_SCROLL_UP)
+                               browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor 
(canvas) + .03);
+                       else if (ev->direction == GDK_SCROLL_DOWN)
+                               browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor 
(canvas) - .03);
+               }
+               done = TRUE;
+               break;
+       default:
+               done = FALSE;
+               break;
+       }
+       return done;
+}
+
+static GdkCursor *hand_cursor = NULL;
+
+static gboolean
+motion_notify_event_cb (BrowserCanvas *canvas, GdkEvent *event, G_GNUC_UNUSED GooCanvas *gcanvas)
+{
+       gboolean done = TRUE;
+
+       switch (event->type) {
+       case GDK_MOTION_NOTIFY:
+               if (((GdkEventMotion*) event)->state & GDK_BUTTON1_MASK) {
+                       if (canvas->priv->canvas_moving) {
+                               GtkAdjustment *ha, *va;
+                               gdouble x, y;
+                               gdouble upper, lower, page_size;
+                               ha = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (canvas));
+                               va = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (canvas));
+
+                               upper = gtk_adjustment_get_upper (ha);
+                               lower = gtk_adjustment_get_lower (ha);
+                               page_size = gtk_adjustment_get_page_size (ha);
+                               x = gtk_adjustment_get_value (ha);
+                               x = CLAMP (x + canvas->xmouse - ((GdkEventMotion*) event)->x,
+                                          lower, upper - page_size);
+                               gtk_adjustment_set_value (ha, x);
+
+                               upper = gtk_adjustment_get_upper (va);
+                               lower = gtk_adjustment_get_lower (va);
+                               page_size = gtk_adjustment_get_page_size (va);
+                               y = gtk_adjustment_get_value (va);
+                               y = CLAMP (y + canvas->ymouse - ((GdkEventMotion*) event)->y,
+                                          lower, upper - page_size);
+                               gtk_adjustment_set_value (va, y);
+                       }
+                       else {
+                               canvas->xmouse = ((GdkEventMotion*) event)->x;
+                               canvas->ymouse = ((GdkEventMotion*) event)->y;
+                               canvas->priv->canvas_moving = TRUE;
+                               if (! hand_cursor)
+                                       hand_cursor = gdk_cursor_new (GDK_HAND2);
+                               gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (canvas)),
+                                                      hand_cursor);
+                       }
+               }
+               done = TRUE;
+               break;
+       default:
+               done = FALSE;
+               break;
+       }
+       return done;
+}
+
+#ifdef HAVE_GRAPHVIZ
+static void popup_layout_default_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+static void popup_layout_radial_cb (GtkMenuItem *mitem, BrowserCanvas *canvas);
+#endif
+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 gboolean
+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_shell_append (GTK_MENU_SHELL (menu), mitem);
+                               }
+                               mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ZOOM_IN, NULL);
+                               gtk_widget_show (mitem);
+                               gtk_menu_shell_append (GTK_MENU_SHELL (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_shell_append (GTK_MENU_SHELL (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_shell_append (GTK_MENU_SHELL (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_shell_append (GTK_MENU_SHELL (menu), mitem);
+
+#ifdef HAVE_GRAPHVIZ
+                               mitem = gtk_menu_item_new_with_label (_("Linear layout"));
+                               gtk_widget_show (mitem);
+                               gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
+                               g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK 
(popup_layout_default_cb), canvas);
+
+                               mitem = gtk_menu_item_new_with_label (_("Radial layout"));
+                               gtk_widget_show (mitem);
+                               gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
+                               g_signal_connect (G_OBJECT (mitem), "activate", G_CALLBACK 
(popup_layout_radial_cb), canvas);
+
+                               mitem = gtk_separator_menu_item_new ();
+                               gtk_widget_show (mitem);
+                               gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
+#endif
+                               
+                               mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
+                               gtk_widget_show (mitem);
+                               gtk_menu_shell_append (GTK_MENU_SHELL (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_shell_append (GTK_MENU_SHELL (menu), mitem);
+
+                               gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                                               NULL, NULL, ((GdkEventButton *)event)->button,
+                                               ((GdkEventButton *)event)->time);
+                       }
+               }
+               done = TRUE;
+               break;
+       case GDK_BUTTON_RELEASE:
+               if (canvas->priv->canvas_moving) {
+                       canvas->priv->canvas_moving = FALSE;
+                       gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (canvas)), NULL);
+               }
+               break;
+       case GDK_2BUTTON_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) {
+                       GooCanvasItem *bitem;
+                       for (bitem = item; bitem; bitem = goo_canvas_item_get_parent (bitem)) {
+                               if (IS_BROWSER_CANVAS_ITEM (bitem)) {
+                                       gboolean allow_select;
+                                       g_object_get (G_OBJECT (bitem), "allow-select", &allow_select, NULL);
+                                       if (allow_select) {
+                                               browser_canvas_item_toggle_select (canvas, 
BROWSER_CANVAS_ITEM (bitem));
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               else
+                       browser_canvas_fit_zoom_factor (canvas);
+               done = TRUE;
+               break;
+       default:
+               done = FALSE;
+               break;
+       }
+       return done;    
+}
+
+#ifdef HAVE_GRAPHVIZ
+static void
+popup_layout_default_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_perform_auto_layout (canvas, TRUE, BROWSER_CANVAS_LAYOUT_DEFAULT);
+}
+
+static void
+popup_layout_radial_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_perform_auto_layout (canvas, TRUE, BROWSER_CANVAS_LAYOUT_RADIAL);
+}
+#endif
+
+static void
+popup_zoom_in_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) + .05);
+}
+
+static void
+popup_zoom_out_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_set_zoom_factor (canvas, browser_canvas_get_zoom_factor (canvas) - .05);
+}
+
+static void
+popup_zoom_fit_cb (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_fit_zoom_factor (canvas);
+}
+
+static void
+popup_export_cb (G_GNUC_UNUSED 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_is_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);
+       gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dlg),
+                                            gdaui_get_default_path ());
+       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;
+
+               gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg)));
+               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 (canvas->priv->goocanvas, 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 ((GtkWindow*) toplevel,
+                                                                GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, 
+                                                                GTK_BUTTONS_CLOSE, NULL);
+                               gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (errdlg), 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 (G_GNUC_UNUSED GtkMenuItem *mitem, BrowserCanvas *canvas)
+{
+       browser_canvas_print (canvas);
+}
+
+
+static void
+weak_ref_lost (BrowserCanvas *canvas, BrowserCanvasItem *old_item)
+{
+        canvas->priv->items = g_slist_remove (canvas->priv->items, old_item);
+       if (canvas->priv->current_selected_item == old_item) {
+               canvas->priv->current_selected_item = NULL;
+               g_signal_emit (canvas, canvas_signals [ITEM_SELECTED], 0, NULL);
+       }
+}
+
+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_object_weak_unref (G_OBJECT (list->data), (GWeakNotify) weak_ref_lost, 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_object_weak_ref (G_OBJECT (item), (GWeakNotify) weak_ref_lost, canvas);
+}
+
+
+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);
+}
+
+/**
+ * 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);
+
+       if (n < 0.01)
+               n = 0.01;
+       else if (n > 1.)
+               n = 1.;
+       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.);
+
+       GtkAllocation alloc;
+       gtk_widget_get_allocation (GTK_WIDGET (canvas), &alloc);
+       xall = alloc.width;
+       yall = alloc.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;
+#else
+       BrowserCanvasClass *class = BROWSER_CANVAS_CLASS (G_OBJECT_GET_CLASS (canvas));
+       GSList *list, *layout_items;
+       Agraph_t *graph;
+       GHashTable *nodes_hash; /* key = BrowserCanvasItem, value = Agnode_t *node */
+       GSList *nodes_list = NULL; /* list of NodeLayout structures */
+
+       if (!gvc)
+               gvc = gvContext ();
+
+#ifdef GRAPHVIZ_NEW_API
+       graph = agopen ("BrowserCanvasLayout", Agdirected, NULL);
+        agset (graph, "shape", "box");
+        agset (graph, "height", ".1");
+        agset (graph, "width", ".1");
+        agset (graph, "fixedsize", "true");
+        agset (graph, "pack", "true");
+       agset (graph, "packmode", "node");
+#else
+       graph = agopen ("BrowserCanvasLayout", AGRAPH);
+        agnodeattr (graph, "shape", "box");
+        agnodeattr (graph, "height", ".1");
+        agnodeattr (graph, "width", ".1");
+        agnodeattr (graph, "fixedsize", "true");
+        agnodeattr (graph, "pack", "true");
+       agnodeattr (graph, "packmode", "node");
+#endif
+
+
+       if (class->get_layout_items)
+               layout_items = class->get_layout_items (canvas);
+       else
+               layout_items = canvas->priv->items;
+
+       /* Graph nodes creation */
+       nodes_hash = g_hash_table_new (NULL, NULL);
+       for (list = layout_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);
+#ifdef GRAPHVIZ_NEW_API
+               node = agnode (graph, tmp, 0);
+#else
+               node = agnode (graph, tmp);
+#endif
+               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 = layout_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) {
+#ifdef GRAPHVIZ_NEW_API
+                               agedge (graph, from_node, to_node, "", 0);
+#else
+                               agedge (graph, from_node, to_node);
+#endif
+                       }
+               }
+       }
+
+       if (layout_items != canvas->priv->items)
+               g_slist_free (layout_items);
+
+       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, G_GNUC_UNUSED 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_scale_layout
+ */
+void
+browser_canvas_scale_layout (BrowserCanvas *canvas, gdouble scale)
+{
+       GSList *list;
+       GooCanvasBounds ref_bounds;
+       gdouble refx, refy;
+
+       g_return_if_fail (IS_BROWSER_CANVAS (canvas));
+       if (!canvas->priv->items)
+               return;
+
+       goo_canvas_get_bounds (canvas->priv->goocanvas, &ref_bounds.x1, &ref_bounds.y1,
+                              &ref_bounds.x2, &ref_bounds.y2);
+       refx = (ref_bounds.x2 - ref_bounds.x1) / 2.;
+       refy = (ref_bounds.y2 - ref_bounds.y1) / 2.;
+       for (list = canvas->priv->items; list; list = list->next) {
+               gboolean can_move;
+               g_object_get ((GObject*) list->data, "allow-move", &can_move, NULL);
+               if (can_move) {
+                       BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+                       GooCanvasBounds bounds;
+                       gdouble tx, ty;
+                       
+                       goo_canvas_item_get_bounds (GOO_CANVAS_ITEM (item), &bounds);
+                       tx = (scale - 1.) * (bounds.x1 - refx);
+                       ty = (scale - 1.) * (bounds.y1 - refy);
+                       browser_canvas_item_translate (item, tx, ty);
+               }
+       }
+}
+
+/**
+ * browser_canvas_serialize_items
+ */
+gchar *
+browser_canvas_serialize_items (BrowserCanvas *canvas)
+{
+       gchar *retval = NULL;
+       GSList *list;
+       xmlDocPtr doc;
+       xmlNodePtr topnode;
+
+       g_return_val_if_fail (IS_BROWSER_CANVAS (canvas), NULL);
+       
+       /* create XML doc and root node */
+       doc = xmlNewDoc (BAD_CAST "1.0");
+       topnode = xmlNewDocNode (doc, NULL, BAD_CAST "canvas", NULL);
+        xmlDocSetRootElement (doc, topnode);
+       
+       /* actually serialize all the items which can be serialized */
+       for (list = canvas->priv->items; list; list = list->next) {
+               BrowserCanvasItem *item = BROWSER_CANVAS_ITEM (list->data);
+               BrowserCanvasItemClass *iclass = (BrowserCanvasItemClass*) G_OBJECT_GET_CLASS (item);
+               if (iclass->serialize) {
+                       xmlNodePtr node;
+                       node = iclass->serialize (item);
+                       if (node)
+                               xmlAddChild (topnode, node);
+               }
+       }
+
+       /* create buffer from XML tree */
+       xmlChar *xstr = NULL;
+       xmlDocDumpMemory (doc, &xstr, NULL);
+       if (xstr) {
+               retval = g_strdup ((gchar *) xstr);
+               xmlFree (xstr);
+       }
+       xmlFreeDoc (doc);
+       
+       return retval;
+}
+
+/**
+ * browser_canvas_item_toggle_select
+ */
+void
+browser_canvas_item_toggle_select (BrowserCanvas *canvas, BrowserCanvasItem *item)
+{
+       gboolean do_select = TRUE;
+       g_return_if_fail (IS_BROWSER_CANVAS (canvas));
+       g_return_if_fail (!item || IS_BROWSER_CANVAS_ITEM (item));
+
+       if (canvas->priv->current_selected_item == item) {
+               /* deselect item */
+               do_select = FALSE;
+       }
+
+       if (canvas->priv->current_selected_item) {
+               BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS 
(canvas->priv->current_selected_item));
+               if (iclass->set_selected)
+                       iclass->set_selected (canvas->priv->current_selected_item, FALSE);
+               canvas->priv->current_selected_item = NULL;
+       }
+
+
+       if (do_select && item) {
+               BrowserCanvasItemClass *iclass = BROWSER_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (item));
+               if (iclass->set_selected)
+                       iclass->set_selected (item, TRUE);
+               canvas->priv->current_selected_item = item;
+       }
+       g_signal_emit (canvas, canvas_signals [ITEM_SELECTED], 0, item);
+}
+
+void
+browser_canvas_translate_item (G_GNUC_UNUSED BrowserCanvas *canvas, BrowserCanvasItem *item,
+                              gdouble dx, gdouble dy)
+{
+       browser_canvas_item_translate (item, dx, dy);
+}
diff --git a/tools/browser/canvas/browser-canvas.h b/tools/browser/canvas/browser-canvas.h
new file mode 100644
index 0000000..2b0049a
--- /dev/null
+++ b/tools/browser/canvas/browser-canvas.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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, or while moving around the canvas */
+       gdouble             xmouse;
+       gdouble             ymouse;
+
+       /* private */
+       BrowserCanvasPrivate  *priv;
+};
+
+/* struct for the object's class */
+struct _BrowserCanvasClass
+{
+       GtkScrolledWindowClass parent_class;
+
+       /* signals */
+       void           (*item_selected) (BrowserCanvas *canvas, BrowserCanvasItem *item);
+
+       /* virtual functions */
+       void           (*clean_canvas_items)  (BrowserCanvas *canvas); /* clean any extra structure, not the 
individual items */
+       GSList        *(*get_layout_items) (BrowserCanvas *canvas);
+
+       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);
+void               browser_canvas_scale_layout            (BrowserCanvas *canvas, gdouble scale);
+
+gchar             *browser_canvas_serialize_items         (BrowserCanvas *canvas);
+
+void               browser_canvas_item_toggle_select      (BrowserCanvas *canvas, BrowserCanvasItem *item);
+void               browser_canvas_translate_item          (BrowserCanvas *canvas, BrowserCanvasItem *item,
+                                                          gdouble dx, gdouble dy);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/schema-browser/Makefile.am b/tools/browser/schema-browser/Makefile.am
index 326f24e..ebad86e 100644
--- a/tools/browser/schema-browser/Makefile.am
+++ b/tools/browser/schema-browser/Makefile.am
@@ -10,6 +10,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/tools/common \
        -I$(top_srcdir)/tools/browser \
        -I$(top_builddir) \
+       -I$(top_builddir)/libgda \
        -I$(top_srcdir) \
        -I$(top_srcdir)/libgda \
        -I$(top_srcdir)/libgda/sqlite \
diff --git a/tools/browser/schema-browser/relations-diagram.c 
b/tools/browser/schema-browser/relations-diagram.c
index e28d3b6..3adfe9c 100644
--- a/tools/browser/schema-browser/relations-diagram.c
+++ b/tools/browser/schema-browser/relations-diagram.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 - 2012 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2009 - 2014 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2010 David King <davidk openismus com>
  * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
  *
@@ -20,8 +20,8 @@
 
 #include <glib/gi18n-lib.h>
 #include <string.h>
+#include "t-app.h"
 #include "relations-diagram.h"
-#include "../support.h"
 #include "../gdaui-bar.h"
 #include "../canvas/browser-canvas-db-relations.h"
 #include <gdk/gdkkeysyms.h>
@@ -30,7 +30,8 @@
 #include "../browser-perspective.h"
 #include "../browser-window.h"
 #include "../data-manager/data-manager-perspective.h"
-#include "../../base-tool-utils.h"
+#include "../ui-support.h"
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 
 struct _RelationsDiagramPrivate {
        TConnection *tcnc;
@@ -332,7 +333,7 @@ relations_diagram_new (TConnection *tcnc)
         gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
        diagram->priv->header = GDAUI_BAR (label);
 
-       wid = gdaui_bar_add_button_from_stock (GDAUI_BAR (label), GTK_STOCK_SAVE);
+       wid = gdaui_bar_add_button_from_icon_name (GDAUI_BAR (label), "document-save");
        diagram->priv->save_button = wid;
 
        g_signal_connect (wid, "clicked",
@@ -566,9 +567,9 @@ relations_diagram_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_b
                tab_name = g_strdup (_("Diagram"));
 
        table_pixbuf = ui_get_pixbuf_icon (UI_ICON_DIAGRAM);
-       wid = browser_make_tab_label_with_pixbuf (tab_name,
-                                                 table_pixbuf,
-                                                 out_close_button ? TRUE : FALSE, out_close_button);
+       wid = ui_make_tab_label_with_pixbuf (tab_name,
+                                            table_pixbuf,
+                                            out_close_button ? TRUE : FALSE, out_close_button);
        g_free (tab_name);
        return wid;
 }
diff --git a/tools/browser/schema-browser/table-relations.c b/tools/browser/schema-browser/table-relations.c
index 3dc1b1b..f0147fe 100644
--- a/tools/browser/schema-browser/table-relations.c
+++ b/tools/browser/schema-browser/table-relations.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2009 - 2014 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2010 David King <davidk openismus com>
  * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
  *
@@ -26,7 +26,6 @@
 #include "table-info.h"
 #include "table-relations.h"
 #include <libgda-ui/gdaui-tree-store.h>
-#include "../support.h"
 #include "../gdaui-bar.h"
 #include "schema-browser-perspective.h"
 #include "../canvas/browser-canvas-db-relations.h"


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