[libgda/LIBGDA_4.2] Initial LDAP support (read only)



commit e13a86685ac880ba1be1eebebc47bee9a23173fa
Author: Vivien Malerba <malerba gnome-db org>
Date:   Wed Jun 1 18:32:26 2011 +0200

    Initial LDAP support (read only)

 Makefile.am                                        |    5 +-
 configure.ac                                       |   11 +-
 doc/C/libgda-4.0-docs.sgml                         |   12 +
 doc/C/libgda-sections.txt                          |   66 +
 doc/C/libgda-ui-sections.txt                       |    2 +
 doc/C/libgda.types.in                              |    3 +
 doc/C/limitations.xml                              |    6 +
 doc/C/prov-notes.xml                               |   24 +
 doc/C/tmpl/gda-attributes-manager.sgml             |    7 +
 doc/C/tmpl/gda-batch.sgml                          |    3 -
 doc/C/tmpl/gda-column.sgml                         |    7 -
 doc/C/tmpl/gda-config.sgml                         |   12 -
 doc/C/tmpl/gda-connection.sgml                     |   13 -
 doc/C/tmpl/gda-data-access-wrapper.sgml            |    4 -
 doc/C/tmpl/gda-data-model-array.sgml               |   10 +
 doc/C/tmpl/gda-data-model-iter.sgml                |    2 -
 doc/C/tmpl/gda-data-model-ldap.sgml                |   98 ++
 doc/C/tmpl/gda-data-model.sgml                     |   11 +
 doc/C/tmpl/gda-data-proxy.sgml                     |   21 -
 doc/C/tmpl/gda-data-select.sgml                    |    9 +
 doc/C/tmpl/gda-ldap-connection.sgml                |  207 +++
 doc/C/tmpl/gda-meta-store.sgml                     |   12 +
 doc/C/tmpl/gda-repetitive-statement.sgml           |    1 -
 doc/C/tmpl/gda-server-operation.sgml               |   34 +
 doc/C/tmpl/gda-tree-mgr-ldap.sgml                  |   48 +
 doc/C/tmpl/gda-tree.sgml                           |   11 +
 doc/C/tmpl/gda-value.sgml                          |   21 -
 doc/C/tmpl/gda-vconnection-data-model.sgml         |   26 +
 doc/C/tmpl/gdaui-rt-editor.sgml                    |    5 +
 doc/C/tmpl/gdaui-tree-store.sgml                   |   21 +
 doc/C/tmpl/provider-support.sgml                   |   16 +
 installers/Windows/gda-browser.nsi                 |    4 +
 installers/Windows/make-zip-setup.sh               |   22 +-
 libgda-report/engine/Makefile.am                   |    1 +
 libgda-ui/gdaui-tree-store.c                       |  357 ++++--
 libgda-ui/gdaui-tree-store.h                       |   10 +-
 libgda-ui/libgda-ui.symbols                        |    2 +
 libgda/Makefile.am                                 |   12 +-
 libgda/gda-attributes-manager.h                    |    6 +
 libgda/gda-data-model-ldap.c                       |  408 ++++++
 libgda/gda-data-model-ldap.h                       |   92 ++
 libgda/gda-data-model.h                            |    3 +-
 libgda/gda-tree-mgr-ldap.c                         |  313 +++++
 libgda/gda-tree-mgr-ldap.h                         |   70 +
 libgda/gda-tree-node.c                             |   24 +-
 libgda/gda-tree.c                                  |   94 +-
 libgda/gda-tree.h                                  |    1 +
 libgda/libgda.h.in                                 |    2 +
 libgda/libgda.symbols                              |   24 +
 libgda/sqlite/gda-sqlite-provider.c                |   27 +-
 libgda/sqlite/gda-sqlite-recordset.c               |   10 +-
 libgda/sqlite/virtual/.gitignore                   |    1 +
 libgda/sqlite/virtual/Makefile.am                  |   11 +-
 libgda/sqlite/virtual/gda-ldap-connection.c        | 1025 ++++++++++++++
 libgda/sqlite/virtual/gda-ldap-connection.h        |  232 ++++
 libgda/sqlite/virtual/gda-vprovider-data-model.c   |   58 +-
 .../{libgda-virtual.h => libgda-virtual.h.in}      |    6 +-
 m4/ldap.m4                                         |  181 +++
 po/POTFILES.in                                     |   23 +-
 providers/Makefile.am                              |    7 +-
 providers/ldap/Makefile.am                         |   40 +
 providers/ldap/gda-ldap-provider.c                 |  927 +++++++++++++
 providers/ldap/gda-ldap-provider.h                 |   50 +
 providers/ldap/gda-ldap-util.c                     | 1410 +++++++++++++++++++
 providers/ldap/gda-ldap-util.h                     |   57 +
 providers/ldap/gda-ldap.h                          |   54 +
 providers/ldap/gdaprov-data-model-ldap.c           | 1447 ++++++++++++++++++++
 .../ldap/gdaprov-data-model-ldap.h                 |   24 +-
 providers/ldap/ldap_specs_auth.xml.in              |    7 +
 providers/ldap/ldap_specs_dsn.xml.in               |   12 +
 providers/ldap/libgda-ldap-4.0.pc.in               |    9 +
 providers/ldap/libmain.c                           |  104 ++
 samples/LdapBrowser/README                         |   22 +
 samples/LdapBrowser/ldap-browser.c                 |  276 ++++
 samples/Makefile                                   |    2 +-
 testing/Makefile.am                                |    1 +
 tests/data-models/check_pmodel.c                   |    2 +-
 tools/Makefile.am                                  |    1 +
 tools/browser/Makefile.am                          |   22 +-
 tools/browser/auth-dialog.c                        |    7 +
 tools/browser/browser-connection.c                 |  509 +++++++-
 tools/browser/browser-connection.h                 |   65 +
 tools/browser/browser-favorites.c                  |   28 +-
 tools/browser/browser-favorites.h                  |   14 +-
 tools/browser/browser-perspective.c                |  138 ++
 tools/browser/browser-perspective.h                |    4 +
 tools/browser/browser-stock-icons.c                |    4 +-
 tools/browser/browser-stock-icons.h                |    5 +-
 tools/browser/browser-window.c                     |    9 +-
 tools/browser/common/Makefile.am                   |    8 +-
 tools/browser/common/ui-formgrid.c                 |  113 ++-
 .../data-manager/data-manager-perspective.c        |  135 +--
 tools/browser/data/Makefile.am                     |    3 +
 .../data/hicolor_actions_24x24_table-add.png       |  Bin 0 -> 914 bytes
 .../data/hicolor_actions_32x32_ldap-entries.png    |  Bin 0 -> 1322 bytes
 .../data/hicolor_actions_32x32_table-add.png       |  Bin 0 -> 1916 bytes
 tools/browser/decl.h                               |    6 +-
 tools/browser/doc/Makefile.am                      |   12 +-
 tools/browser/doc/gda-browser-sections.txt         |    4 +
 tools/browser/doc/tmpl/browser-connection.sgml     |    9 +
 tools/browser/doc/tmpl/browser-favorites.sgml      |   11 +
 tools/browser/doc/tmpl/browser-perspective.sgml    |   18 +
 tools/browser/doc/tmpl/support.sgml                |    8 +
 tools/browser/doc/tmpl/ui-formgrid.sgml            |    6 +
 .../browser/dummy-perspective/dummy-perspective.c  |   17 +-
 tools/browser/gda-browser-ldap-class-a.png         |  Bin 0 -> 513 bytes
 tools/browser/gda-browser-ldap-class-s.png         |  Bin 0 -> 394 bytes
 tools/browser/gda-browser-ldap-class-u.png         |  Bin 0 -> 514 bytes
 tools/browser/gda-browser-ldap-class-x.png         |  Bin 0 -> 506 bytes
 tools/browser/gda-browser-ldap-entry.png           |  Bin 0 -> 560 bytes
 tools/browser/gda-browser-ldap-group.png           |  Bin 0 -> 808 bytes
 tools/browser/gda-browser-ldap-organization.png    |  Bin 0 -> 590 bytes
 tools/browser/gda-browser-ldap-person.png          |  Bin 0 -> 796 bytes
 tools/browser/help/C/features.page                 |    2 +
 .../help/C/figures/data-man-persp-multi.png        |  Bin 0 -> 81462 bytes
 .../browser/help/C/figures/ldap-browser-persp.png  |  Bin 0 -> 107744 bytes
 tools/browser/help/C/figures/ldap-classes.png      |  Bin 0 -> 162544 bytes
 tools/browser/help/C/figures/ldap-search.png       |  Bin 0 -> 104938 bytes
 .../browser/help/C/figures/ldap-table-mapping.png  |  Bin 0 -> 83895 bytes
 tools/browser/help/C/index.page                    |    3 +-
 tools/browser/help/C/ldap-browser-perspective.page |   61 +
 tools/browser/help/C/ldap-connections.page         |  132 ++
 tools/browser/help/C/sql-sqlite.page               |  100 ++
 tools/browser/help/C/virtual-connections.page      |   12 +
 tools/browser/help/Makefile.am                     |   10 +-
 tools/browser/ldap-browser/Makefile.am             |   58 +
 tools/browser/ldap-browser/class-properties.c      |  577 ++++++++
 tools/browser/ldap-browser/class-properties.h      |   59 +
 tools/browser/ldap-browser/classes-view.c          |  408 ++++++
 tools/browser/ldap-browser/classes-view.h          |   58 +
 tools/browser/ldap-browser/entry-properties.c      |  927 +++++++++++++
 tools/browser/ldap-browser/entry-properties.h      |   60 +
 tools/browser/ldap-browser/filter-editor.c         |  262 ++++
 tools/browser/ldap-browser/filter-editor.h         |   64 +
 tools/browser/ldap-browser/hierarchy-view.c        |  626 +++++++++
 tools/browser/ldap-browser/hierarchy-view.h        |   58 +
 .../ldap-browser/ldap-browser-perspective.c        |  533 +++++++
 .../ldap-browser/ldap-browser-perspective.h        |   69 +
 tools/browser/ldap-browser/ldap-classes-page.c     |  627 +++++++++
 tools/browser/ldap-browser/ldap-classes-page.h     |   58 +
 tools/browser/ldap-browser/ldap-entries-page.c     |  632 +++++++++
 tools/browser/ldap-browser/ldap-entries-page.h     |   58 +
 .../browser/ldap-browser/ldap-favorite-selector.c  |  657 +++++++++
 .../browser/ldap-browser/ldap-favorite-selector.h  |   59 +
 tools/browser/ldap-browser/ldap-search-page.c      |  538 ++++++++
 tools/browser/ldap-browser/ldap-search-page.h      |   56 +
 tools/browser/ldap-browser/marshal.list            |   27 +
 tools/browser/ldap-browser/mgr-ldap-classes.c      |  347 +++++
 tools/browser/ldap-browser/mgr-ldap-classes.h      |   56 +
 tools/browser/ldap-browser/mgr-ldap-entries.c      |  309 +++++
 tools/browser/ldap-browser/mgr-ldap-entries.h      |   56 +
 tools/browser/ldap-browser/perspective-main.c      |   37 +
 tools/browser/ldap-browser/perspective-main.h      |   29 +
 tools/browser/ldap-browser/vtable-dialog.c         |  186 +++
 tools/browser/ldap-browser/vtable-dialog.h         |   57 +
 tools/browser/main.c                               |    8 +-
 tools/browser/mgr-favorites.c                      |  231 +++-
 tools/browser/query-exec/Makefile.am               |    4 +-
 .../{query-console.c => query-console-page.c}      |  170 ++--
 tools/browser/query-exec/query-console-page.h      |   57 +
 tools/browser/query-exec/query-console.h           |   57 -
 tools/browser/query-exec/query-exec-perspective.c  |  167 +--
 tools/browser/schema-browser/Makefile.am           |    6 +
 .../schema-browser/schema-browser-perspective.c    |   76 +-
 tools/browser/schema-browser/table-columns.c       |  187 +++-
 tools/browser/support.c                            |   49 +-
 tools/browser/support.h                            |   18 +-
 167 files changed, 17521 insertions(+), 731 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 45e69ea..cf26163 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -96,7 +96,10 @@ example_files = \
 	samples/Blobs/blobtest.c \
 	samples/Blobs/Makefile \
 	samples/Blobs/README \
-	samples/Blobs/testblob.db
+	samples/Blobs/testblob.db \
+	samples/LdapBrowser/Makefile \
+	samples/LdapBrowser/README \
+	samples/LdapBrowser/ldap-browser.c
 
 EXTRA_DIST = \
 	COPYING \
diff --git a/configure.ac b/configure.ac
index 3b290c9..33ca101 100644
--- a/configure.ac
+++ b/configure.ac
@@ -12,6 +12,7 @@ m4_include(m4/mysql.m4)
 m4_include(m4/postgresql.m4)
 m4_include(m4/oracle.m4)
 m4_include(m4/java.m4)
+m4_include(m4/ldap.m4)
 
 AC_INIT(GNU Data Access, major.minor.micro, gnome-db-list gnome org, libgda)
 AC_PREREQ(2.59)
@@ -310,7 +311,7 @@ dnl ******************************
 dnl linklibext is the shared link library extension, which varies by platform
 
 EXPORT_SYM_REGEX='-export-symbols-regex "^(gda_|fnYM49765777344607__gda).*"'
-EXPORT_PROV_SYM_REGEX='-export-symbols-regex "^(plugin_|Java_|g_module).*"'
+EXPORT_PROV_SYM_REGEX='-export-symbols-regex "^(plugin_|Java_|g_module|gdaprov_).*"'
 EXPORT_UI_SYM_REGEX='-export-symbols-regex "^(gdaui_).*"'
 AC_MSG_CHECKING([for platform])
 platform_win32=no
@@ -574,6 +575,9 @@ ORACLE_CHECK($lib)
 dnl Test for JAVA and JDBC
 JAVA_CHECK()
 
+dnl Test for LDAP
+LDAP_CHECK($lib)
+
 dnl test for FireBird
 try_firebird=true
 AC_ARG_WITH(firebird,
@@ -840,6 +844,7 @@ libgda/providers-support/Makefile
 libgda/sql-parser/Makefile
 libgda/sqlite/Makefile
 libgda/sqlite/sqlite-src/Makefile
+libgda/sqlite/virtual/libgda-virtual.h
 libgda/sqlite/virtual/Makefile
 libgda/thread-wrapper/Makefile
 providers/Makefile
@@ -863,6 +868,8 @@ providers/sqlite/Makefile
 providers/sqlite/libgda-sqlite-4.0.pc
 providers/jdbc/Makefile
 providers/jdbc/libgda-jdbc-4.0.pc
+providers/ldap/Makefile
+providers/ldap/libgda-ldap-4.0.pc
 providers/web/Makefile
 providers/web/libgda-web-4.0.pc
 providers/skel-implementation/Makefile
@@ -897,6 +904,7 @@ tools/browser/common/Makefile
 tools/browser/schema-browser/Makefile
 tools/browser/query-exec/Makefile
 tools/browser/data-manager/Makefile
+tools/browser/ldap-browser/Makefile
 tools/browser/dummy-perspective/Makefile
 tools/browser/canvas/Makefile
 tools/browser/doc/Makefile
@@ -961,6 +969,7 @@ echo "      SQLite = yes `if test x$have_sqlite = xyes; then echo '(from system
 echo "      SQLCipher = `if test x$enable_crypto != xyes; then echo no; else echo yes; fi`"
 echo "      JDBC = $java_found"
 echo "      WEB = `if test x$have_libsoup = xyes; then echo yes; else echo no; fi`"
+echo "      LDAP = `if test x$ldap_found = xyes; then echo yes; else echo no; fi`"
 if test x"$br_cv_binreloc" != "xyes" -a x"$platform_win32" != "xyes"
 then
        echo "   Binreloc support is disabled: Libgda will not be relocatable. To enable binreloc support re-run with --enable-binreloc (see http://autopackage.org/docs/binreloc for more information)"
diff --git a/doc/C/libgda-4.0-docs.sgml b/doc/C/libgda-4.0-docs.sgml
index d28dca0..4bc0ca4 100644
--- a/doc/C/libgda-4.0-docs.sgml
+++ b/doc/C/libgda-4.0-docs.sgml
@@ -1843,6 +1843,18 @@ g_object_unref (eng);
     <index id="index-4-2-4" role="4.2.4">
       <title>Index of new symbols in 4.2.4</title>
     </index>
+    <index id="index-4-2-5" role="4.2.5">
+      <title>Index of new symbols in 4.2.5</title>
+    </index>
+    <index id="index-4-2-6" role="4.2.6">
+      <title>Index of new symbols in 4.2.6</title>
+    </index>
+    <index id="index-4-2-7" role="4.2.7">
+      <title>Index of new symbols in 4.2.7</title>
+    </index>
+    <index id="index-4-2-8" role="4.2.8">
+      <title>Index of new symbols in 4.2.8</title>
+    </index>
     <index id="index-deprecated" role="deprecated">
       <title>Index of deprecated symbols</title>
     </index>
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index b920d57..130dd9f 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -33,6 +33,7 @@ GDA_ATTRIBUTE_AUTO_INCREMENT
 GDA_ATTRIBUTE_NUMERIC_PRECISION
 GDA_ATTRIBUTE_NUMERIC_SCALE
 GDA_ATTRIBUTE_IS_DEFAULT
+GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN
 <SUBSECTION>
 GdaAttributesManager
 GdaAttributesManagerSignal
@@ -218,6 +219,70 @@ gda_data_model_bdb_get_type
 </SECTION>
 
 <SECTION>
+<FILE>gda-data-model-ldap</FILE>
+<TITLE>GdaDataModelLdap</TITLE>
+GdaDataModelLdap
+GdaDataModelLdapClass
+GdaDataModelLdapPrivate
+GdaLdapSearchScope
+gda_data_model_ldap_new
+gda_data_model_ldap_compute_columns
+<SUBSECTION Standard>
+GDA_DATA_MODEL_LDAP
+GDA_DATA_MODEL_LDAP_CLASS
+GDA_IS_DATA_MODEL_LDAP
+GDA_IS_DATA_MODEL_LDAP_CLASS
+GDA_TYPE_DATA_MODEL_LDAP
+gda_data_model_ldap_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gda-ldap-connection</FILE>
+<TITLE>GdaLdapConnection</TITLE>
+<INCLUDE>virtual/gda-ldap-connection.h</INCLUDE>
+GdaLdapConnection
+GdaLdapConnectionClass
+GdaLdapConnectionPrivate
+gda_ldap_connection_get_base_dn
+gda_ldap_connection_declare_table
+gda_ldap_connection_undeclare_table
+<SUBSECTION>
+gda_ldap_dn_split
+GdaLdapAttribute
+GdaLdapEntry
+gda_ldap_is_dn
+gda_ldap_dn_split
+gda_ldap_describe_entry
+gda_ldap_entry_free
+gda_ldap_get_entry_children
+<SUBSECTION>
+GdaLdapClassKind
+GdaLdapClass
+gda_ldap_get_class_info
+gda_ldap_get_top_classes
+<SUBSECTION Standard>
+GDA_LDAP_CONNECTION
+GDA_LDAP_CONNECTION_CLASS
+GDA_IS_LDAP_CONNECTION
+GDA_IS_LDAP_CONNECTION_CLASS
+GDA_TYPE_LDAP_CONNECTION
+gda_ldap_connection_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gda-tree-mgr-ldap</FILE>
+<TITLE>GdaTreeMgrLdap</TITLE>
+GdaTreeMgrLdap
+gda_tree_mgr_ldap_new
+<SUBSECTION Standard>
+GDA_TREE_MGR_LDAP
+GDA_TREE_MGR_LDAP_GET_CLASS
+GDA_IS_TREE_MGR_LDAP
+GDA_TYPE_TREE_MGR_LDAP
+gda_tree_mgr_ldap_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gda-data-model-dir</FILE>
 <TITLE>GdaDataModelDir</TITLE>
 GdaDataModelDir
@@ -1594,6 +1659,7 @@ gda_tree_add_manager
 gda_tree_clean
 gda_tree_update_all
 gda_tree_update_part
+gda_tree_update_children
 gda_tree_get_nodes_in_path
 gda_tree_get_node
 gda_tree_get_node_path
diff --git a/doc/C/libgda-ui-sections.txt b/doc/C/libgda-ui-sections.txt
index 2fa2538..21cae09 100644
--- a/doc/C/libgda-ui-sections.txt
+++ b/doc/C/libgda-ui-sections.txt
@@ -281,6 +281,8 @@ gdaui_server_operation_get_type
 GdauiTreeStore
 gdaui_tree_store_new
 gdaui_tree_store_newv
+gdaui_tree_store_get_node
+gdaui_tree_store_get_iter
 <SUBSECTION Standard>
 GDAUI_TREE_STORE
 GDAUI_IS_TREE_STORE
diff --git a/doc/C/libgda.types.in b/doc/C/libgda.types.in
index 448e17c..ae4879c 100644
--- a/doc/C/libgda.types.in
+++ b/doc/C/libgda.types.in
@@ -21,6 +21,9 @@ gda_handler_bin_get_type
 gda_handler_type_get_type
 gda_data_model_array_get_type
 @LIBGDA_BDB_TYPE@
+ LIBGDA_LDAP_TYPE@
+ LIBGDA_LDAP_TYPE2@
+ LIBGDA_LDAP_TYPE3@
 gda_data_model_dir_get_type
 gda_row_get_type
 gda_data_model_get_type
diff --git a/doc/C/limitations.xml b/doc/C/limitations.xml
index e510951..01b8dc2 100644
--- a/doc/C/limitations.xml
+++ b/doc/C/limitations.xml
@@ -158,6 +158,12 @@
     </para>
   </sect1>
 
+  <sect1 id="limitations_ldap"><title>For LDAP</title>
+    <para>
+      TODO.
+    </para>
+  </sect1>
+
   <sect1 id="limitations_jdbc"><title>For JDBC based providers</title>
     <para>
       The following limitations apply to databases accessed via Libgda through a JDBC driver. When loading
diff --git a/doc/C/prov-notes.xml b/doc/C/prov-notes.xml
index 1b233f2..8877280 100644
--- a/doc/C/prov-notes.xml
+++ b/doc/C/prov-notes.xml
@@ -222,5 +222,29 @@ Opening connection 'c0' for: SQLCipher://DB_NAME=testcrypt
     and <link linkend="limitations_sqlcipher">SQLCipher provider's limitations</link>.
   </para>
   </sect1>
+
+  <sect1 id="provider_notes_ldap"><title>For LDAP</title>
+    <para>
+      The LDAP provider maps LDAP searches to &LIBGDA;'s data models, with the following design choices:
+      <itemizedlist>
+	<listitem><para>A data model column is created for each attibute the LDAP search returns, plus one
+	column for the DN (Distinguished name), as the 1st column of each search;
+	so if no attribute is requested, the resulting data model
+	will only contain one column for the DN</para></listitem>
+	<listitem><para>If not otherwise specified, the data type of each data model column is determined
+	by the data type of the corresponding column attribute</para></listitem>
+	<listitem><para>Multi valued attributes are by default handled as an invalid data, but it is possible to
+	specify instead to report a NULL value, or an array in a CSV notation.</para></listitem>
+	<listitem><para>For performances reasons, some data is cached (unless the "USE_CACHE" connection
+	variable is set to FALSE). Cache files are in the users's home directory, as per the
+	<ulink url="http://www.freedesktop.org/wiki/Specifications/basedir-spec";>XDG Base Directory Specification</ulink></para></listitem>
+      </itemizedlist>
+    </para>
+    <para>
+      For more information, see the <link linkend="GdaDataModelLdap">GdaDataModelLdap</link> and
+      the <link linkend="GdaLdapConnection">GdaLdapConnection</link> objects.
+    </para>
+  </sect1>
+
   
 </chapter>
diff --git a/doc/C/tmpl/gda-attributes-manager.sgml b/doc/C/tmpl/gda-attributes-manager.sgml
index 2b172eb..84b723e 100644
--- a/doc/C/tmpl/gda-attributes-manager.sgml
+++ b/doc/C/tmpl/gda-attributes-manager.sgml
@@ -81,6 +81,13 @@ Manager for lists of attributes
 
 
 
+<!-- ##### MACRO GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN ##### -->
+<para>
+
+</para>
+
+
+
 <!-- ##### STRUCT GdaAttributesManager ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-batch.sgml b/doc/C/tmpl/gda-batch.sgml
index 526add7..7c12079 100644
--- a/doc/C/tmpl/gda-batch.sgml
+++ b/doc/C/tmpl/gda-batch.sgml
@@ -35,9 +35,6 @@ a #GdaSqlParser object.
 
 </para>
 
-@: 
-@:
-
 @gdabatch: the object which received the signal.
 @arg1: 
 
diff --git a/doc/C/tmpl/gda-column.sgml b/doc/C/tmpl/gda-column.sgml
index 2673d9f..13be76a 100644
--- a/doc/C/tmpl/gda-column.sgml
+++ b/doc/C/tmpl/gda-column.sgml
@@ -32,10 +32,6 @@ Management of #GdaDataModel column attributes
 
 </para>
 
-@: 
-@: 
-@:
-
 @gdacolumn: the object which received the signal.
 @arg1: 
 @arg2: 
@@ -45,9 +41,6 @@ Management of #GdaDataModel column attributes
 
 </para>
 
-@: 
-@:
-
 @gdacolumn: the object which received the signal.
 @arg1: 
 
diff --git a/doc/C/tmpl/gda-config.sgml b/doc/C/tmpl/gda-config.sgml
index 244c0a1..b005201 100644
--- a/doc/C/tmpl/gda-config.sgml
+++ b/doc/C/tmpl/gda-config.sgml
@@ -68,9 +68,6 @@ g_object_new (GDA_TYPE_CONFIG, "user-filename", "my_file", NULL);
 
 </para>
 
-@: 
-@:
-
 @gdaconfig: the object which received the signal.
 @arg1: 
 
@@ -79,9 +76,6 @@ g_object_new (GDA_TYPE_CONFIG, "user-filename", "my_file", NULL);
 
 </para>
 
-@: 
-@:
-
 @gdaconfig: the object which received the signal.
 @arg1: 
 
@@ -90,9 +84,6 @@ g_object_new (GDA_TYPE_CONFIG, "user-filename", "my_file", NULL);
 
 </para>
 
-@: 
-@:
-
 @gdaconfig: the object which received the signal.
 @arg1: 
 
@@ -101,9 +92,6 @@ g_object_new (GDA_TYPE_CONFIG, "user-filename", "my_file", NULL);
 
 </para>
 
-@: 
-@:
-
 @gdaconfig: the object which received the signal.
 @arg1: 
 
diff --git a/doc/C/tmpl/gda-connection.sgml b/doc/C/tmpl/gda-connection.sgml
index e3f0e86..3d01cd3 100644
--- a/doc/C/tmpl/gda-connection.sgml
+++ b/doc/C/tmpl/gda-connection.sgml
@@ -67,8 +67,6 @@ A connection to a database
 
 </para>
 
-@:
-
 @gdaconnection: the object which received the signal.
 
 <!-- ##### SIGNAL GdaConnection::conn-opened ##### -->
@@ -76,8 +74,6 @@ A connection to a database
 
 </para>
 
-@:
-
 @gdaconnection: the object which received the signal.
 
 <!-- ##### SIGNAL GdaConnection::conn-to-close ##### -->
@@ -85,8 +81,6 @@ A connection to a database
 
 </para>
 
-@:
-
 @gdaconnection: the object which received the signal.
 
 <!-- ##### SIGNAL GdaConnection::dsn-changed ##### -->
@@ -94,8 +88,6 @@ A connection to a database
 
 </para>
 
-@:
-
 @gdaconnection: the object which received the signal.
 
 <!-- ##### SIGNAL GdaConnection::error ##### -->
@@ -103,9 +95,6 @@ A connection to a database
 
 </para>
 
-@: 
-@:
-
 @gdaconnection: the object which received the signal.
 @arg1: 
 
@@ -114,8 +103,6 @@ A connection to a database
 
 </para>
 
-@:
-
 @gdaconnection: the object which received the signal.
 
 <!-- ##### ARG GdaConnection:auth-string ##### -->
diff --git a/doc/C/tmpl/gda-data-access-wrapper.sgml b/doc/C/tmpl/gda-data-access-wrapper.sgml
index ab1634a..2dca8cd 100644
--- a/doc/C/tmpl/gda-data-access-wrapper.sgml
+++ b/doc/C/tmpl/gda-data-access-wrapper.sgml
@@ -38,10 +38,6 @@ and allows data to be accessed in a random way while remaining memory efficient
 </para>
 
 @parent_class: 
- _gda_reserved1: 
- _gda_reserved2: 
- _gda_reserved3: 
- _gda_reserved4: 
 
 <!-- ##### STRUCT GdaDataAccessWrapperPrivate ##### -->
 <para>
diff --git a/doc/C/tmpl/gda-data-model-array.sgml b/doc/C/tmpl/gda-data-model-array.sgml
index 941bdbc..e18c223 100644
--- a/doc/C/tmpl/gda-data-model-array.sgml
+++ b/doc/C/tmpl/gda-data-model-array.sgml
@@ -78,6 +78,16 @@ An implementation of #GdaDataModel based on a #GArray.
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_data_model_array_new_with_g_types_v ##### -->
+<para>
+
+</para>
+
+ cols: 
+ types: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_data_model_array_copy_model ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-data-model-iter.sgml b/doc/C/tmpl/gda-data-model-iter.sgml
index 9e47dde..f11bba0 100644
--- a/doc/C/tmpl/gda-data-model-iter.sgml
+++ b/doc/C/tmpl/gda-data-model-iter.sgml
@@ -81,8 +81,6 @@ any case it will not prevent the data model from being destroyed).
 
 </para>
 
-@:
-
 @gdadatamodeliter: the object which received the signal.
 @arg1: 
 
diff --git a/doc/C/tmpl/gda-data-model-ldap.sgml b/doc/C/tmpl/gda-data-model-ldap.sgml
new file mode 100644
index 0000000..f8a3b3c
--- /dev/null
+++ b/doc/C/tmpl/gda-data-model-ldap.sgml
@@ -0,0 +1,98 @@
+<!-- ##### SECTION Title ##### -->
+GdaDataModelLdap
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT GdaDataModelLdap ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ARG GdaDataModelLdap:attributes ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GdaDataModelLdap:base ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GdaDataModelLdap:cnc ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GdaDataModelLdap:filter ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GdaDataModelLdap:scope ##### -->
+<para>
+
+</para>
+
+<!-- ##### STRUCT GdaDataModelLdapClass ##### -->
+<para>
+
+</para>
+
+ parent_class: 
+
+<!-- ##### STRUCT GdaDataModelLdapPrivate ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ENUM GdaLdapSearchScope ##### -->
+<para>
+
+</para>
+
+ GDA_LDAP_SEARCH_BASE: 
+ GDA_LDAP_SEARCH_ONELEVEL: 
+ GDA_LDAP_SEARCH_SUBTREE: 
+
+<!-- ##### FUNCTION gda_data_model_ldap_new ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ base_dn: 
+ filter: 
+ attributes: 
+ scope: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_data_model_ldap_compute_columns ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ attributes: 
+ Returns: 
+
+
diff --git a/doc/C/tmpl/gda-data-model.sgml b/doc/C/tmpl/gda-data-model.sgml
index c7118bb..ab12f52 100644
--- a/doc/C/tmpl/gda-data-model.sgml
+++ b/doc/C/tmpl/gda-data-model.sgml
@@ -118,6 +118,8 @@ values are accessible at the provided row).
 @GDA_DATA_MODEL_FEATURE_NON_SUPPORTED_ERROR: 
 @GDA_DATA_MODEL_FILE_EXIST_ERROR: 
 @GDA_DATA_MODEL_XML_FORMAT_ERROR: 
+ GDA_DATA_MODEL_TRUNCATED_ERROR: 
+ GDA_DATA_MODEL_OTHER_ERROR: 
 
 <!-- ##### FUNCTION gda_data_model_get_n_rows ##### -->
 <para>
@@ -137,6 +139,15 @@ values are accessible at the provided row).
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_data_model_get_exceptions ##### -->
+<para>
+
+</para>
+
+ model: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_data_model_describe_column ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-data-proxy.sgml b/doc/C/tmpl/gda-data-proxy.sgml
index a997ca3..0db259f 100644
--- a/doc/C/tmpl/gda-data-proxy.sgml
+++ b/doc/C/tmpl/gda-data-proxy.sgml
@@ -130,8 +130,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@:
-
 @gdadataproxy: the object which received the signal.
 
 <!-- ##### SIGNAL GdaDataProxy::row-changes-applied ##### -->
@@ -139,10 +137,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@: 
-@: 
-@:
-
 @gdadataproxy: the object which received the signal.
 @arg1: 
 @arg2: 
@@ -152,10 +146,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@: 
-@: 
-@:
-
 @gdadataproxy: the object which received the signal.
 @arg1: 
 @arg2: 
@@ -165,10 +155,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@: 
-@: 
-@:
-
 @gdadataproxy: the object which received the signal.
 @arg1: 
 @arg2: 
@@ -178,9 +164,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@: 
-@:
-
 @gdadataproxy: the object which received the signal.
 @arg1: 
 
@@ -189,10 +172,6 @@ Proxy to hold modifications for any #GdaDataModel, and provides the #GdaDataMode
 
 </para>
 
-@: 
-@: 
-@:
-
 @Returns: 
 @Param2: 
 @Param3: 
diff --git a/doc/C/tmpl/gda-data-select.sgml b/doc/C/tmpl/gda-data-select.sgml
index 8d7d064..663ef42 100644
--- a/doc/C/tmpl/gda-data-select.sgml
+++ b/doc/C/tmpl/gda-data-select.sgml
@@ -197,3 +197,12 @@ Data models returned by the execution of a SELECT statement
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_data_select_add_exception ##### -->
+<para>
+
+</para>
+
+ model: 
+ error: 
+
+
diff --git a/doc/C/tmpl/gda-ldap-connection.sgml b/doc/C/tmpl/gda-ldap-connection.sgml
new file mode 100644
index 0000000..d0d9860
--- /dev/null
+++ b/doc/C/tmpl/gda-ldap-connection.sgml
@@ -0,0 +1,207 @@
+<!-- ##### SECTION Title ##### -->
+GdaLdapConnection
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT GdaLdapConnection ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ARG GdaLdapConnection:startup-file ##### -->
+<para>
+
+</para>
+
+<!-- ##### STRUCT GdaLdapConnectionClass ##### -->
+<para>
+
+</para>
+
+ parent_class: 
+
+<!-- ##### STRUCT GdaLdapConnectionPrivate ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION gda_ldap_connection_get_base_dn ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_connection_declare_table ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ table_name: 
+ base_dn: 
+ filter: 
+ attributes: 
+ scope: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_connection_undeclare_table ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ table_name: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_dn_split ##### -->
+<para>
+
+</para>
+
+ dn: 
+ all: 
+ Returns: 
+
+
+<!-- ##### STRUCT GdaLdapAttribute ##### -->
+<para>
+
+</para>
+
+ attr_name: 
+ nb_values: 
+ values: 
+
+<!-- ##### STRUCT GdaLdapEntry ##### -->
+<para>
+
+</para>
+
+ dn: 
+ nb_attributes: 
+ attributes: 
+ attributes_hash: 
+
+<!-- ##### FUNCTION gda_ldap_is_dn ##### -->
+<para>
+
+</para>
+
+ dn: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_dn_split ##### -->
+<para>
+
+</para>
+
+ dn: 
+ all: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_describe_entry ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ dn: 
+ error: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_entry_free ##### -->
+<para>
+
+</para>
+
+ entry: 
+
+
+<!-- ##### FUNCTION gda_ldap_get_entry_children ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ dn: 
+ attributes: 
+ error: 
+ Returns: 
+
+
+<!-- ##### ENUM GdaLdapClassKind ##### -->
+<para>
+
+</para>
+
+ GDA_LDAP_CLASS_KIND_ABSTRACT: 
+ GDA_LDAP_CLASS_KIND_STRUTURAL: 
+ GDA_LDAP_CLASS_KIND_AUXILIARY: 
+ GDA_LDAP_CLASS_KIND_UNKNOWN: 
+
+<!-- ##### STRUCT GdaLdapClass ##### -->
+<para>
+
+</para>
+
+ oid: 
+ nb_names: 
+ names: 
+ description: 
+ kind: 
+ obsolete: 
+ nb_req_attributes: 
+ req_attributes: 
+ nb_opt_attributes: 
+ opt_attributes: 
+ parents: 
+ children: 
+
+<!-- ##### FUNCTION gda_ldap_get_class_info ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ classname: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gda_ldap_get_top_classes ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ Returns: 
+
+
diff --git a/doc/C/tmpl/gda-meta-store.sgml b/doc/C/tmpl/gda-meta-store.sgml
index 13842fd..62c7494 100644
--- a/doc/C/tmpl/gda-meta-store.sgml
+++ b/doc/C/tmpl/gda-meta-store.sgml
@@ -180,6 +180,18 @@ Dictionary object
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_meta_store_extract_v ##### -->
+<para>
+
+</para>
+
+ store: 
+ select_sql: 
+ vars: 
+ error: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_meta_store_schema_get_structure ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-repetitive-statement.sgml b/doc/C/tmpl/gda-repetitive-statement.sgml
index d03af70..d51925a 100644
--- a/doc/C/tmpl/gda-repetitive-statement.sgml
+++ b/doc/C/tmpl/gda-repetitive-statement.sgml
@@ -30,7 +30,6 @@ Execute the same statement several times with different values
 
 </para>
 
- parent_instance: 
 
 <!-- ##### FUNCTION gda_repetitive_statement_new ##### -->
 <para>
diff --git a/doc/C/tmpl/gda-server-operation.sgml b/doc/C/tmpl/gda-server-operation.sgml
index 86b7484..079fa0b 100644
--- a/doc/C/tmpl/gda-server-operation.sgml
+++ b/doc/C/tmpl/gda-server-operation.sgml
@@ -147,6 +147,16 @@ Handles any DDL query in an abstract way
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_server_operation_get_value_at_path ##### -->
+<para>
+
+</para>
+
+ op: 
+ path: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_server_operation_get_sql_identifier_at ##### -->
 <para>
 
@@ -160,6 +170,18 @@ Handles any DDL query in an abstract way
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_server_operation_get_sql_identifier_at_path ##### -->
+<para>
+
+</para>
+
+ op: 
+ cnc: 
+ prov: 
+ path: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_server_operation_set_value_at ##### -->
 <para>
 
@@ -173,6 +195,18 @@ Handles any DDL query in an abstract way
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_server_operation_set_value_at_path ##### -->
+<para>
+
+</para>
+
+ op: 
+ value: 
+ path: 
+ error: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_server_operation_save_data_to_xml ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-tree-mgr-ldap.sgml b/doc/C/tmpl/gda-tree-mgr-ldap.sgml
new file mode 100644
index 0000000..d57bd94
--- /dev/null
+++ b/doc/C/tmpl/gda-tree-mgr-ldap.sgml
@@ -0,0 +1,48 @@
+<!-- ##### SECTION Title ##### -->
+GdaTreeMgrLdap
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT GdaTreeMgrLdap ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### ARG GdaTreeMgrLdap:connection ##### -->
+<para>
+
+</para>
+
+<!-- ##### ARG GdaTreeMgrLdap:dn ##### -->
+<para>
+
+</para>
+
+<!-- ##### FUNCTION gda_tree_mgr_ldap_new ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ dn: 
+ Returns: 
+
+
diff --git a/doc/C/tmpl/gda-tree.sgml b/doc/C/tmpl/gda-tree.sgml
index 3d67e88..633048b 100644
--- a/doc/C/tmpl/gda-tree.sgml
+++ b/doc/C/tmpl/gda-tree.sgml
@@ -116,6 +116,17 @@ A tree-structure
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_tree_update_children ##### -->
+<para>
+
+</para>
+
+ tree: 
+ node: 
+ error: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_tree_get_nodes_in_path ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gda-value.sgml b/doc/C/tmpl/gda-value.sgml
index 938ed4e..c528267 100644
--- a/doc/C/tmpl/gda-value.sgml
+++ b/doc/C/tmpl/gda-value.sgml
@@ -208,8 +208,6 @@ Assorted functions for dealing with #GValue values.
 
 </para>
 
- data: 
- binary_length: 
 
 <!-- ##### FUNCTION gda_value_new_binary ##### -->
 <para>
@@ -379,8 +377,6 @@ Assorted functions for dealing with #GValue values.
 
 </para>
 
- x: 
- y: 
 
 <!-- ##### FUNCTION gda_geometricpoint_copy ##### -->
 <para>
@@ -446,10 +442,6 @@ Assorted functions for dealing with #GValue values.
 
 </para>
 
- number: 
- precision: 
- width: 
- reserved: 
 
 <!-- ##### FUNCTION gda_numeric_copy ##### -->
 <para>
@@ -491,11 +483,6 @@ Assorted functions for dealing with #GValue values.
 
 </para>
 
- hour: 
- minute: 
- second: 
- fraction: 
- timezone: 
 
 <!-- ##### FUNCTION gda_time_copy ##### -->
 <para>
@@ -546,14 +533,6 @@ Assorted functions for dealing with #GValue values.
 
 </para>
 
- year: representation of the date
- month: month representation of the date, as a number between 1 and 12
- day: day representation of the date, as a number between 1 and 31
- hour: 
- minute: 
- second: 
- fraction: 
- timezone: 
 
 <!-- ##### FUNCTION gda_timestamp_copy ##### -->
 <para>
diff --git a/doc/C/tmpl/gda-vconnection-data-model.sgml b/doc/C/tmpl/gda-vconnection-data-model.sgml
index 1702e55..f493a01 100644
--- a/doc/C/tmpl/gda-vconnection-data-model.sgml
+++ b/doc/C/tmpl/gda-vconnection-data-model.sgml
@@ -28,6 +28,22 @@ The #GdaVproviderDataModel provider to use to create such connection objects.
 </para>
 
 
+<!-- ##### SIGNAL GdaVconnectionDataModel::vtable-created ##### -->
+<para>
+
+</para>
+
+ gdavconnectiondatamodel: the object which received the signal.
+ arg1: 
+
+<!-- ##### SIGNAL GdaVconnectionDataModel::vtable-dropped ##### -->
+<para>
+
+</para>
+
+ gdavconnectiondatamodel: the object which received the signal.
+ arg1: 
+
 <!-- ##### USER_FUNCTION GdaVconnectionDataModelFunc ##### -->
 <para>
 
@@ -124,6 +140,16 @@ The #GdaVproviderDataModel provider to use to create such connection objects.
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_vconnection_data_model_get ##### -->
+<para>
+
+</para>
+
+ cnc: 
+ table_name: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_vconnection_data_model_foreach ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gdaui-rt-editor.sgml b/doc/C/tmpl/gdaui-rt-editor.sgml
index 185505e..bc5672a 100644
--- a/doc/C/tmpl/gdaui-rt-editor.sgml
+++ b/doc/C/tmpl/gdaui-rt-editor.sgml
@@ -54,6 +54,11 @@ Nice Picture: [[[R2RrUAA...y8vLy8tYQwAA]]] Yes
 
 </para>
 
+<!-- ##### ARG GdauiRtEditor:in-scrolled-window ##### -->
+<para>
+
+</para>
+
 <!-- ##### ARG GdauiRtEditor:no-background ##### -->
 <para>
 
diff --git a/doc/C/tmpl/gdaui-tree-store.sgml b/doc/C/tmpl/gdaui-tree-store.sgml
index 898adc6..2703d5e 100644
--- a/doc/C/tmpl/gdaui-tree-store.sgml
+++ b/doc/C/tmpl/gdaui-tree-store.sgml
@@ -103,3 +103,24 @@ Bridge between a GdaTree and a GtkTreeModel
 @Returns: 
 
 
+<!-- ##### FUNCTION gdaui_tree_store_get_node ##### -->
+<para>
+
+</para>
+
+ store: 
+ iter: 
+ Returns: 
+
+
+<!-- ##### FUNCTION gdaui_tree_store_get_iter ##### -->
+<para>
+
+</para>
+
+ store: 
+ iter: 
+ node: 
+ Returns: 
+
+
diff --git a/doc/C/tmpl/provider-support.sgml b/doc/C/tmpl/provider-support.sgml
index 4b0f0d3..e8d03e6 100644
--- a/doc/C/tmpl/provider-support.sgml
+++ b/doc/C/tmpl/provider-support.sgml
@@ -359,6 +359,22 @@ Methods dedicated to implementing providers
 @Returns: 
 
 
+<!-- ##### FUNCTION gda_meta_store_modify_v ##### -->
+<para>
+
+</para>
+
+ store: 
+ table_name: 
+ new_data: 
+ condition: 
+ nvalues: 
+ value_names: 
+ values: 
+ error: 
+ Returns: 
+
+
 <!-- ##### FUNCTION gda_meta_store_modify_with_context ##### -->
 <para>
 
diff --git a/installers/Windows/gda-browser.nsi b/installers/Windows/gda-browser.nsi
index 974f6a4..0b7ba5d 100644
--- a/installers/Windows/gda-browser.nsi
+++ b/installers/Windows/gda-browser.nsi
@@ -18,6 +18,7 @@ SetCompressor lzma
 !include "prov_mysql.nsh"
 !include "prov_postgresql.nsh"
 !include "prov_web.nsh"
+!include "prov_ldap.nsh"
 !include "prov_mdb.nsh"
 !include "prov_oracle.nsh"
 !include "prov_sqlite.nsh"
@@ -117,6 +118,8 @@ LangString DESC_prov_sqlite ${LANG_ENGLISH} "Sqlite database provider"
 LangString DESC_prov_sqlite ${LANG_FRENCH} "Fournisseur pour les bases de données Sqlite"
 LangString DESC_prov_web ${LANG_ENGLISH} "Provider for database accessed through a web server"
 LangString DESC_prov_web ${LANG_FRENCH} "Fournisseur pour les bases de données via un serveur web"
+LangString DESC_prov_ldap ${LANG_ENGLISH} "Provider for LDAP directory"
+LangString DESC_prov_ldap ${LANG_FRENCH} "Fournisseur pour les répertoires LDAP"
 
 
 ; Section descriptions
@@ -129,6 +132,7 @@ LangString DESC_prov_web ${LANG_FRENCH} "Fournisseur pour les bases de donn
   !insertmacro MUI_DESCRIPTION_TEXT ${SEC06} $(DESC_prov_oracle)
   !insertmacro MUI_DESCRIPTION_TEXT ${SEC07} $(DESC_prov_sqlite)
   !insertmacro MUI_DESCRIPTION_TEXT ${SEC08} $(DESC_prov_web)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SEC09} $(DESC_prov_ldap)
 !insertmacro MUI_FUNCTION_DESCRIPTION_END
 
 
diff --git a/installers/Windows/make-zip-setup.sh b/installers/Windows/make-zip-setup.sh
index f2731a9..1b2c07b 100755
--- a/installers/Windows/make-zip-setup.sh
+++ b/installers/Windows/make-zip-setup.sh
@@ -31,13 +31,13 @@ current_dir=`pwd`
 archive=${current_dir}/libgda-${version}.zip
 archive_dev=${current_dir}/libgda-dev-${version}.zip
 archive_ext=${current_dir}/libgda-dep-${version}.zip
-nshfiles=(core.nsh prov_bdb.nsh prov_mdb.nsh prov_mysql.nsh prov_oracle.nsh prov_postgresql.nsh prov_sqlite.nsh prov_web.nsh)
+nshfiles=(core.nsh prov_bdb.nsh prov_mdb.nsh prov_mysql.nsh prov_oracle.nsh prov_postgresql.nsh prov_sqlite.nsh prov_web.nsh prov_ldap.nsh)
 
 # remove current archive if it exists
 rm -f $archive $archive_dev $archive_ext
 rm -f *.nsh *.exe
 
-if test $CLEAN == "yes"
+if test $CLEAN = "yes"
 then
     exit 0
 fi
@@ -213,6 +213,12 @@ Section /o "Web" SEC08
   SetOverwrite try
 EOF
 
+cat > prov_ldap.nsh <<EOF
+Section "Ldap" SEC09
+  SetOutPath "\$INSTDIR\bin"
+  SetOverwrite try
+EOF
+
 cat > config.nsh <<EOF
 !define PRODUCT_VERSION "$version"
 EOF
@@ -256,6 +262,9 @@ files=(iconv.dll libeay32.dll libiconv-2.dll libintl-8.dll libpq.dll libxml2.dll
 add_files_to_zip $archive_ext ${depend_path}/pgsql bin $files
 add_files_to_nsh prov_postgresql ${depend_path}/pgsql bin $files
 
+files=(liblber.dll libldap.dll)
+add_files_to_zip $archive_ext ${depend_path}/ldap bin $files
+add_files_to_nsh prov_ldap ${depend_path}/ldap bin $files
 
 #
 # dependencies from the cross compilation environment
@@ -298,6 +307,9 @@ files=(oracle_specs_dsn.xml oracle_specs_create_table.xml)
 add_files_to_zip $archive $prefix share/libgda-4.0 $files
 add_files_to_nsh prov_oracle $prefix share/libgda-4.0 $files
 
+files=(ldap_specs_auth.xml ldap_specs_dsn.xml)
+add_files_to_zip $archive $prefix share/libgda-4.0 $files
+add_files_to_nsh prov_ldap $prefix share/libgda-4.0 $files
 
 files=(gdaui-generic.png)
 add_files_to_zip $archive $prefix share/libgda-4.0/pixmaps $files
@@ -423,6 +435,10 @@ files=(libgda-web.dll)
 add_files_to_zip $archive $prefix lib/libgda-4.0/providers $files
 add_files_to_nsh prov_web $prefix lib/libgda-4.0/providers $files
 
+files=(libgda-ldap.dll)
+add_files_to_zip $archive $prefix lib/libgda-4.0/providers $files
+add_files_to_nsh prov_ldap $prefix lib/libgda-4.0/providers $files
+
 files=(libgda-oracle.dll)
 add_files_to_zip $archive $prefix lib/libgda-4.0/providers $files
 add_files_to_nsh prov_oracle $prefix lib/libgda-4.0/providers $files
@@ -485,7 +501,7 @@ add_files_to_zip $archive_dev $prefix share/libgda-4.0/demo $files
 #
 # doc
 #
-add_all_files_to_zip $archive_dev $prefix share/gtk-doc/html/libgda-4.0
+#add_all_files_to_zip $archive_dev $prefix share/gtk-doc/html/libgda-4.0
 
 #
 # translations
diff --git a/libgda-report/engine/Makefile.am b/libgda-report/engine/Makefile.am
index ce341c2..826df41 100644
--- a/libgda-report/engine/Makefile.am
+++ b/libgda-report/engine/Makefile.am
@@ -8,6 +8,7 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/libgda \
 	-I$(top_srcdir)/libgda/sqlite \
+	-I$(top_builddir)/libgda/sqlite \
 	$(LIBGDA_CFLAGS) \
 	$(GDKPIXBUF_CFLAGS) \
 	$(LIBGDA_WFLAGS)
diff --git a/libgda-ui/gdaui-tree-store.c b/libgda-ui/gdaui-tree-store.c
index f8fb59d..afbaf42 100644
--- a/libgda-ui/gdaui-tree-store.c
+++ b/libgda-ui/gdaui-tree-store.c
@@ -1,5 +1,8 @@
-/* 
- * Copyright (C) 2009 Vivien Malerba <malerba gnome-db org>
+/*
+ * Copyright (C) 2009 - 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
  *
  * This Library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public License as
@@ -23,6 +26,7 @@
 #include <libgda/gda-tree-node.h>
 #include <libgda/gda-tree-manager.h>
 #include <libgda/gda-value.h>
+#include <libgda/gda-attributes-manager.h>
 #include <gtk/gtk.h>
 #include "marshallers/gdaui-marshal.h"
 
@@ -39,6 +43,8 @@ static void gdaui_tree_store_get_property (GObject *object,
 					   GValue *value,
 					   GParamSpec *pspec);
 
+#define NOT_A_NODE ((GdaTreeNode*) 0x01)
+
 /* signals */
 enum
 {
@@ -366,9 +372,9 @@ gdaui_tree_store_set_property (GObject *object,
 
 static void
 gdaui_tree_store_get_property (GObject *object,
-				  guint param_id,
-				  GValue *value,
-				  GParamSpec *pspec)
+			       guint param_id,
+			       GValue *value,
+			       GParamSpec *pspec)
 {
 	GdauiTreeStore *store;
 
@@ -438,7 +444,7 @@ gdaui_tree_store_new (GdaTree *tree, guint n_columns, ...)
 }
 
 /**
- * gdaui_tree_store_newv
+ * gdaui_tree_store_newv:
  * @tree: a #GdaTree object
  * @n_columns: number of columns in the tree store
  * @types: an array of @n_columns GType to specify the type of each column
@@ -486,10 +492,81 @@ gdaui_tree_store_newv (GdaTree *tree, guint n_columns, GType *types, const gchar
  *
  * REM about the GtkTreeIter:
  *     iter->user_data <==> GdaTreeNode for the row
- *     iter->user_data2 <==> Next GdaTreeNode
+ *     iter->user_data2 <==> parent GdaTreeNode if @user_data == NOT_A_NODE
  *     iter->stamp is reset any time the model changes, 0 means invalid iter
  */
 
+/**
+ * gdaui_tree_store_get_node:
+ * @store: a #GdauiTreeStore object
+ * @iter: a valid #GtkTreeIter
+ *
+ * Get the  #GdaTreeNode represented by @iter.
+ *
+ * Returns: (transfer none): the #GdaTreeNode represented by @iter, or %NULL if an error occurred
+ *
+ * Since: 4.2.8
+ */
+GdaTreeNode *
+gdaui_tree_store_get_node (GdauiTreeStore *store, GtkTreeIter *iter)
+{
+	g_return_val_if_fail (GDAUI_IS_TREE_STORE (store), NULL);
+	g_return_val_if_fail (iter, NULL);
+        g_return_val_if_fail (iter->stamp == store->priv->stamp, NULL);
+
+	GdaTreeNode *node;
+	node = (GdaTreeNode*) iter->user_data;
+	if (node == NOT_A_NODE)
+		return NULL;
+	return node;
+}
+
+
+/**
+ * gdaui_tree_store_get_iter:
+ * @store: a #GdauiTreeStore object
+ * @iter: a pointer to a #GtkTreeIter
+ * @node: a #GdaTreeNode in @store
+ *
+ * Sets @iter to represent @node in the tree.
+ *
+ * Returns: %TRUE if no error occurred and @iter is valid
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gdaui_tree_store_get_iter (GdauiTreeStore *store, GtkTreeIter *iter, GdaTreeNode *node)
+{
+	g_return_val_if_fail (GDAUI_IS_TREE_STORE (store), FALSE);
+	g_return_val_if_fail (GDA_IS_TREE_NODE (node), FALSE);
+
+	GdaTreeNode *parent = NULL;
+	GSList *rootnodes;
+
+	rootnodes = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
+	if (rootnodes) {
+		for (parent = node;
+		     parent;
+		     parent = gda_tree_node_get_parent (parent)) {
+			if (g_slist_find (rootnodes, parent))
+				break;
+		}
+		g_slist_free (rootnodes);
+	}
+
+	iter->user_data2 = NULL;
+	if (parent) {
+		iter->stamp = store->priv->stamp;
+		iter->user_data = (gpointer) node;
+		return TRUE;
+	}
+	else {
+		iter->stamp = 0;
+		iter->user_data = NULL;
+		return FALSE;
+	}
+}
+
 static GtkTreeModelFlags
 tree_store_get_flags (GtkTreeModel *tree_model)
 {
@@ -552,12 +629,37 @@ tree_store_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *p
 	if (node) {
 		iter->stamp = store->priv->stamp;
                 iter->user_data = (gpointer) node;
+                iter->user_data2 = NULL;
 		return TRUE;
 	}
 	else {
+		GtkTreePath *path2;
+		path2 = gtk_tree_path_copy (path);
+		if (gtk_tree_path_up (path2)) {
+			path_str = gtk_tree_path_to_string (path2);
+			node = gda_tree_get_node (store->priv->tree, path_str, FALSE);
+			/*g_print ("Path2 %s => node %p\n", path_str, node);*/
+			g_free (path_str);
+		}
+		gtk_tree_path_free (path2);
+
+		if (node) {
+			const GValue *cv;
+			cv = gda_tree_node_get_node_attribute (node,
+							       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+			if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+			    g_value_get_boolean (cv)) {
+				iter->stamp = store->priv->stamp;
+				iter->user_data = (gpointer) NOT_A_NODE;
+				iter->user_data2 = (gpointer) node;
+				return TRUE;
+			}
+		}
+
 		iter->stamp = 0;
 		iter->user_data = NULL;
-                return FALSE;
+                iter->user_data2 = NULL;
+		return FALSE;
 	}
 }
 
@@ -565,17 +667,33 @@ static GtkTreePath *
 tree_store_get_path (GtkTreeModel *tree_model, GtkTreeIter *iter)
 {
         GdauiTreeStore *store;
-        GtkTreePath *path;
-	gchar *path_str;
+        GtkTreePath *path = NULL;
+	gchar *path_str = NULL;
+	GdaTreeNode *node;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), NULL);
         store = GDAUI_TREE_STORE (tree_model);
         g_return_val_if_fail (iter, NULL);
         g_return_val_if_fail (iter->stamp == store->priv->stamp, NULL);
 
-	path_str = gda_tree_get_node_path (store->priv->tree, GDA_TREE_NODE (iter->user_data));
+	node = (GdaTreeNode*) iter->user_data;
+	if (node == NOT_A_NODE) {
+		GtkTreeIter iter2;
+		gchar *tmp;
+
+		iter2 = *iter;
+		g_assert (gtk_tree_model_iter_parent (tree_model, &iter2, iter));
+		path_str = gda_tree_get_node_path (store->priv->tree,
+						   (GdaTreeNode*) iter2.user_data);
+		tmp = g_strdup_printf ("%s:0", path_str);
+		g_free (path_str);
+		path_str = tmp;
+	}
+	else
+		path_str = gda_tree_get_node_path (store->priv->tree, node);
+
 	/*g_print ("Node %p => path %s\n", iter->user_data, path_str);*/
-        path = gtk_tree_path_new_from_string (path_str);
+	path = gtk_tree_path_new_from_string (path_str);
 	g_free (path_str);
 
         return path;
@@ -585,8 +703,9 @@ static void
 tree_store_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value)
 {
 	GdauiTreeStore *store;
-	const GValue *tmp;
+	const GValue *tmp = NULL;
 	ColumnSpec *cs;
+	GdaTreeNode *node;
 	
 	g_return_if_fail (GDAUI_IS_TREE_STORE (tree_model));
         store = GDAUI_TREE_STORE (tree_model);
@@ -603,7 +722,11 @@ tree_store_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column,
 		gda_value_set_null (value);
 		return;
 	}
-	tmp = gda_tree_node_fetch_attribute (GDA_TREE_NODE (iter->user_data), cs->attribute_name);
+
+	node = (GdaTreeNode*) iter->user_data;
+	if (node != NOT_A_NODE)
+		tmp = gda_tree_node_fetch_attribute (node, cs->attribute_name);
+
 	if (!tmp) {
 		g_value_init (value, cs->type);
 		return;
@@ -626,7 +749,7 @@ tree_store_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter)
 {
         GdauiTreeStore *store;
 	GdaTreeNode *parent;
-	GSList *list, *current;
+	GdaTreeNode *node;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), FALSE);
         store = GDAUI_TREE_STORE (tree_model);
@@ -634,71 +757,99 @@ tree_store_iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter)
         g_return_val_if_fail (iter, FALSE);
         g_return_val_if_fail (iter->stamp == store->priv->stamp, FALSE);
 
-	parent = gda_tree_node_get_parent (GDA_TREE_NODE (iter->user_data));
-	if (parent) 
-		list = gda_tree_node_get_children (parent);
-	else
-		list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
-	
-	current = g_slist_find (list, iter->user_data);
-	g_assert (current);
-	if (current->next) {
-#ifdef GDA_DEBUG_NO
-#define GDA_ATTRIBUTE_NAME "__gda_attr_name"
-		g_print ("Next %s(%p) => %s(%p)\n",
-			 gda_value_stringify (gda_tree_node_fetch_attribute (GDA_TREE_NODE (iter->user_data), GDA_ATTRIBUTE_NAME)),
-			 iter->user_data,
-			 gda_value_stringify (gda_tree_node_fetch_attribute (GDA_TREE_NODE (current->next->data), GDA_ATTRIBUTE_NAME)),
-			 current->next->data);
-#endif
-		iter->user_data = (gpointer) current->next->data;
-		g_slist_free (list);
-		return TRUE;
-	}
-	else {
+	node = (GdaTreeNode*) iter->user_data;
+	if (node == NOT_A_NODE) {
 		iter->stamp = 0;
 		iter->user_data = NULL;
-		g_slist_free (list);
+                iter->user_data2 = NULL;
 		return FALSE;
 	}
+	else {
+		GSList *list, *current;
+		parent = gda_tree_node_get_parent (node);
+		if (parent) 
+			list = gda_tree_node_get_children (parent);
+		else
+			list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
+		
+		current = g_slist_find (list, iter->user_data);
+		g_assert (current);
+		if (current->next) {
+#ifdef GDA_DEBUG_NO
+#define GDA_ATTRIBUTE_NAME "__gda_attr_name"
+			g_print ("Next %s(%p) => %s(%p)\n",
+				 gda_value_stringify (gda_tree_node_fetch_attribute (GDA_TREE_NODE (iter->user_data), GDA_ATTRIBUTE_NAME)),
+				 iter->user_data,
+				 gda_value_stringify (gda_tree_node_fetch_attribute (GDA_TREE_NODE (current->next->data), GDA_ATTRIBUTE_NAME)),
+				 current->next->data);
+#endif
+			iter->user_data = (gpointer) current->next->data;
+			iter->user_data2 = NULL;
+			g_slist_free (list);
+			return TRUE;
+		}
+		else {
+			iter->stamp = 0;
+			iter->user_data = NULL;
+			iter->user_data2 = NULL;
+			g_slist_free (list);
+			return FALSE;
+		}
+	}
 }
 
 static gboolean
 tree_store_iter_children (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent)
 {
         GdauiTreeStore *store;
-	GSList *list;
+	GSList *list = NULL;
+	GdaTreeNode *node = NULL;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), FALSE);
         store = GDAUI_TREE_STORE (tree_model);
         g_return_val_if_fail (store->priv->tree, FALSE);
         g_return_val_if_fail (iter, FALSE);
 
-	if (!parent)
-		list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
-	else {
+	if (parent) {
 		g_return_val_if_fail (parent->stamp == store->priv->stamp, FALSE);
-		list = gda_tree_node_get_children (GDA_TREE_NODE (parent->user_data));
+		node = (GdaTreeNode*) parent->user_data;
+		if (node != NOT_A_NODE)
+			list = gda_tree_node_get_children (node);
 	}
+	else
+		list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
 
 	if (list) {
 		iter->stamp = store->priv->stamp;
 		iter->user_data = (gpointer) list->data;
+                iter->user_data2 = NULL;
 		g_slist_free (list);
 		return TRUE;
 	}
-	else {
-		iter->stamp = 0;
-		iter->user_data = NULL;
-		return FALSE;
+	else if (node != NOT_A_NODE) {
+		const GValue *cv;
+		cv = gda_tree_node_get_node_attribute (node,
+						       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+		if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+		    g_value_get_boolean (cv)) {
+			iter->stamp = store->priv->stamp;
+			iter->user_data = (gpointer) NOT_A_NODE;
+			iter->user_data2 = (gpointer) node;
+			return TRUE;
+		}
 	}
+
+	iter->stamp = 0;
+	iter->user_data = NULL;
+	iter->user_data2 = NULL;
+	return FALSE;
 }
 
 static gboolean
 tree_store_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
 {
 	GdauiTreeStore *store;
-	GSList *list;
+	GdaTreeNode *node;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), FALSE);
         store = GDAUI_TREE_STORE (tree_model);
@@ -706,46 +857,66 @@ tree_store_iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter)
         g_return_val_if_fail (iter, FALSE);
 	g_return_val_if_fail (iter->stamp == store->priv->stamp, FALSE);
 
-	list = gda_tree_node_get_children (GDA_TREE_NODE (iter->user_data));
-	if (list) {
-		g_slist_free (list);
+	node = (GdaTreeNode*) iter->user_data;
+	if (node == NOT_A_NODE)
+		return FALSE;
+	if (gda_tree_node_get_child_index (node, 0))
 		return TRUE;
+	else {
+		const GValue *cv;
+		cv = gda_tree_node_get_node_attribute (node,
+						       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+		if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+		    g_value_get_boolean (cv))
+			return TRUE;
+		else
+			return FALSE;
 	}
-	else
-		return FALSE;
 }
 
 static gint
 tree_store_iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter)
 {
         GdauiTreeStore *store;
-	GSList *list;
+	GSList *list = NULL;
+	GdaTreeNode *node = NULL;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), -1);
         store = GDAUI_TREE_STORE (tree_model);
         g_return_val_if_fail (store->priv->tree, 0);
 
-        if (!iter)
-                list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
-        else {
+        if (iter) {
 		g_return_val_if_fail (iter->stamp == store->priv->stamp, FALSE);
-                list = gda_tree_node_get_children (GDA_TREE_NODE (iter->user_data));
+		node = (GdaTreeNode*) iter->user_data;
+		if (node != NOT_A_NODE)
+			list = gda_tree_node_get_children (GDA_TREE_NODE (iter->user_data));
 	}
+	else
+                list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
+
 	if (list) {
 		gint retval;
 		retval = g_slist_length (list);
 		g_slist_free (list);
 		return retval;
 	}
-	else
-		return 0;
+	else if (node != NOT_A_NODE) {
+		const GValue *cv;
+		cv = gda_tree_node_get_node_attribute (node,
+						       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+		if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+		    g_value_get_boolean (cv))
+			return 1;
+	}
+	return 0;
 }
 
 static gboolean
 tree_store_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n)
 {
 	GdauiTreeStore *store;
-	GSList *list, *current;
+	GSList *list = NULL;
+	GdaTreeNode *node = NULL;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), FALSE);
         store = GDAUI_TREE_STORE (tree_model);
@@ -753,32 +924,47 @@ tree_store_iter_nth_child (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeI
         g_return_val_if_fail (iter, FALSE);
 	if (parent) {
 		g_return_val_if_fail (parent->stamp == store->priv->stamp, FALSE);
-		list = gda_tree_node_get_children (GDA_TREE_NODE (parent->user_data));
+		node = (GdaTreeNode*) parent->user_data;
+		if (node != NOT_A_NODE)
+			list = gda_tree_node_get_children (node);
 	}
 	else
 		list = gda_tree_get_nodes_in_path (store->priv->tree, NULL, FALSE);
 	
-	current = g_slist_nth (list, n);
-	if (current) {
-		iter->stamp = store->priv->stamp;
-		iter->user_data = (gpointer) current->data;
+	if (list) {
+		node = (GdaTreeNode*) g_slist_nth_data (list, n);
 		g_slist_free (list);
-		return TRUE;
+		if (node) {
+			iter->stamp = store->priv->stamp;
+			iter->user_data = (gpointer) node;
+			iter->user_data2 = NULL;
+			return TRUE;
+		}
 	}
-	else {
-		if (list)
-			g_slist_free (list);
-		iter->stamp = 0;
-		iter->user_data = NULL;
-		return FALSE;
+	else if ((node != NOT_A_NODE) && (n == 0)) {
+		const GValue *cv;
+		cv = gda_tree_node_get_node_attribute (node,
+						       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+		if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+		    g_value_get_boolean (cv)) {
+			iter->stamp = store->priv->stamp;
+			iter->user_data = (gpointer) NOT_A_NODE;
+			iter->user_data2 = (gpointer) node;
+			return TRUE;
+		}
 	}
+
+	iter->stamp = 0;
+	iter->user_data = NULL;
+	iter->user_data2 = NULL;
+	return FALSE;
 }
 
 static gboolean
 tree_store_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child)
 {
         GdauiTreeStore *store;
-	GdaTreeNode *parent;
+	GdaTreeNode *node, *parent;
 
         g_return_val_if_fail (GDAUI_IS_TREE_STORE (tree_model), FALSE);
         store = GDAUI_TREE_STORE (tree_model);
@@ -787,17 +973,25 @@ tree_store_iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter
         g_return_val_if_fail (child, FALSE);
         g_return_val_if_fail (child->stamp == store->priv->stamp, FALSE);
 
-	parent = gda_tree_node_get_parent (GDA_TREE_NODE (child->user_data));
+	node = (GdaTreeNode*) child->user_data;
+	if (node == NOT_A_NODE) {
+		parent = (GdaTreeNode*) child->user_data2;
+		g_assert (GDA_IS_TREE_NODE (parent));
+	}
+	else
+		parent = gda_tree_node_get_parent (node);
+
 	if (parent) {
 		iter->stamp = store->priv->stamp;
 		iter->user_data = (gpointer) parent;
+                iter->user_data2 = NULL;
 		return TRUE;
 	}
-	else {
-		iter->stamp = 0;
-		iter->user_data = NULL;
-		return FALSE;
-	}
+
+	iter->stamp = 0;
+	iter->user_data = NULL;
+	iter->user_data2 = NULL;
+	return FALSE;
 }
 
 
@@ -817,6 +1011,7 @@ tree_node_changed_cb (GdaTree *tree, GdaTreeNode *node, GdauiTreeStore *store)
 	memset (&iter, 0, sizeof (GtkTreeIter));
 	iter.stamp = store->priv->stamp;
 	iter.user_data = (gpointer) node;
+	iter.user_data2 = NULL;
 	
 	gtk_tree_model_row_changed (GTK_TREE_MODEL (store), path, &iter);
 	/*g_print ("GdauiTreeStore::changed %s (node %p)\n", gtk_tree_path_to_string (path), node);*/
@@ -839,6 +1034,7 @@ tree_node_inserted_cb (GdaTree *tree, GdaTreeNode *node, GdauiTreeStore *store)
 	memset (&iter, 0, sizeof (GtkTreeIter));
 	iter.stamp = store->priv->stamp;
 	iter.user_data = (gpointer) node;
+	iter.user_data2 = NULL;
 	
 	gtk_tree_model_row_inserted (GTK_TREE_MODEL (store), path, &iter);
 	/*g_print ("GdauiTreeStore::row_inserted %s (node %p)\n", gtk_tree_path_to_string (path), node);*/
@@ -861,6 +1057,7 @@ tree_node_has_child_toggled_cb (GdaTree *tree, GdaTreeNode *node, GdauiTreeStore
 	memset (&iter, 0, sizeof (GtkTreeIter));
 	iter.stamp = store->priv->stamp;
 	iter.user_data = (gpointer) node;
+	iter.user_data2 = NULL;
 	
 	gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (store), path, &iter);
 	/*g_print ("GdauiTreeStore::row_has_child %s (node %p)\n", gtk_tree_path_to_string (path), node);*/
diff --git a/libgda-ui/gdaui-tree-store.h b/libgda-ui/gdaui-tree-store.h
index ac15664..f708794 100644
--- a/libgda-ui/gdaui-tree-store.h
+++ b/libgda-ui/gdaui-tree-store.h
@@ -56,11 +56,13 @@ struct _GdauiTreeStoreClass
 	gboolean           (*drag_delete)   (GdauiTreeStore *store, const gchar *path);
 };
 
-GType           gdaui_tree_store_get_type             (void) G_GNUC_CONST;
+GType           gdaui_tree_store_get_type  (void) G_GNUC_CONST;
 
-GtkTreeModel   *gdaui_tree_store_new                  (GdaTree *tree, guint n_columns, ...);
-GtkTreeModel   *gdaui_tree_store_newv                 (GdaTree *tree, guint n_columns,
-						       GType *types, const gchar **attribute_names);
+GtkTreeModel   *gdaui_tree_store_new       (GdaTree *tree, guint n_columns, ...);
+GtkTreeModel   *gdaui_tree_store_newv      (GdaTree *tree, guint n_columns,
+					   GType *types, const gchar **attribute_names);
+GdaTreeNode    *gdaui_tree_store_get_node  (GdauiTreeStore *store, GtkTreeIter *iter);
+gboolean        gdaui_tree_store_get_iter  (GdauiTreeStore *store, GtkTreeIter *iter, GdaTreeNode *node);
 
 G_END_DECLS
 
diff --git a/libgda-ui/libgda-ui.symbols b/libgda-ui/libgda-ui.symbols
index fd3fb72..97ec1d8 100644
--- a/libgda-ui/libgda-ui.symbols
+++ b/libgda-ui/libgda-ui.symbols
@@ -172,6 +172,8 @@
 	gdaui_server_operation_get_type
 	gdaui_server_operation_new
 	gdaui_server_operation_new_in_dialog
+	gdaui_tree_store_get_iter
+	gdaui_tree_store_get_node
 	gdaui_tree_store_get_type
 	gdaui_tree_store_new
 	gdaui_tree_store_newv
diff --git a/libgda/Makefile.am b/libgda/Makefile.am
index 5143f6f..2b7e204 100644
--- a/libgda/Makefile.am
+++ b/libgda/Makefile.am
@@ -4,10 +4,17 @@ lib_LTLIBRARIES = libgda-4.0.la
 
 SUBDIRS = sqlite handlers binreloc sql-parser providers-support thread-wrapper
 
+DEF_FLAGS=
 if BDB 
 GDA_BDB_H=gda-data-model-bdb.h
 GDA_BDB_S=gda-data-model-bdb.c
-DEF_FLAGS=-DHAVE_BDB
+DEF_FLAGS+=-DHAVE_BDB
+endif
+
+if LDAP 
+GDA_LDAP_H=gda-data-model-ldap.h gda-tree-mgr-ldap.h
+GDA_LDAP_S=gda-data-model-ldap.c gda-tree-mgr-ldap.c
+DEF_FLAGS+=-DHAVE_LDAP
 endif
 
 GLOBAL_CFLAGS = \
@@ -15,6 +22,7 @@ GLOBAL_CFLAGS = \
 	-I$(top_builddir) \
 	-I$(top_srcdir)/libgda/sqlite \
 	-I$(top_srcdir)/libgda \
+	-I$(top_builddir)/libgda/sqlite \
 	-DABI_VERSION=\""$(GDA_ABI_VERSION)"\" \
 	$(LIBGDA_CFLAGS) \
 	$(BDB_CFLAGS) \
@@ -52,6 +60,7 @@ gda_headers = \
 	gda-data-model-array.h \
 	gda-data-model.h \
 	$(GDA_BDB_H) \
+	$(GDA_LDAP_H) \
 	gda-data-model-dir.h \
 	gda-data-model-extra.h \
 	gda-data-model-import.h \
@@ -111,6 +120,7 @@ gda_sources= \
 	gda-data-handler.c \
 	gda-data-model-array.c \
 	$(GDA_BDB_S) \
+	$(GDA_LDAP_S) \
 	gda-data-model.c \
 	gda-data-model-dir.c \
 	gda-data-model-dsn-list.c \
diff --git a/libgda/gda-attributes-manager.h b/libgda/gda-attributes-manager.h
index ee6e564..44ef4d8 100644
--- a/libgda/gda-attributes-manager.h
+++ b/libgda/gda-attributes-manager.h
@@ -47,6 +47,12 @@ void                  gda_attributes_manager_clear       (GdaAttributesManager *
 void                  gda_attributes_manager_foreach     (GdaAttributesManager *mgr, gpointer ptr, 
 							  GdaAttributesManagerFunc func, gpointer data);
 
+/**
+ * GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN:
+ * This attribute, if %TRUE specifies that a tree node may or may not have any children nodes (value has a G_TYPE_BOOLEAN type).
+ */
+#define GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN "__gda_attr_tnuchild"
+
 
 /* possible predefined attribute names for gda_holder_get_attribute() or gda_column_get_attribute() */
 #define GDA_ATTRIBUTE_DESCRIPTION "__gda_attr_descr" /* G_TYPE_STRING */
diff --git a/libgda/gda-data-model-ldap.c b/libgda/gda-data-model-ldap.c
new file mode 100644
index 0000000..72cb987
--- /dev/null
+++ b/libgda/gda-data-model-ldap.c
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <libgda/gda-data-model-ldap.h>
+#include <libgda/gda-connection.h>
+#include <libgda/gda-config.h>
+#include <virtual/gda-ldap-connection.h>
+#include <gmodule.h>
+#include <glib/gi18n-lib.h>
+
+enum {
+	PROP_0,
+	PROP_CNC,
+	PROP_BASE,
+	PROP_FILTER,
+	PROP_ATTRIBUTES,
+	PROP_SCOPE
+};
+
+static void
+gda_data_model_ldap_set_property (G_GNUC_UNUSED GObject *object,
+				  G_GNUC_UNUSED guint param_id,
+				  G_GNUC_UNUSED const GValue *value,
+				  G_GNUC_UNUSED GParamSpec *pspec)
+{
+}
+
+static void
+gda_data_model_ldap_get_property (G_GNUC_UNUSED GObject *object,
+				  G_GNUC_UNUSED guint param_id,
+				  G_GNUC_UNUSED GValue *value,
+				  G_GNUC_UNUSED GParamSpec *pspec)
+{
+}
+
+
+static void
+dummy_gda_data_model_ldap_class_init (GdaDataModelLdapClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	/* properties */
+        object_class->set_property = gda_data_model_ldap_set_property;
+        object_class->get_property = gda_data_model_ldap_get_property;
+
+        g_object_class_install_property (object_class, PROP_CNC,
+                                         g_param_spec_object ("cnc", NULL, "LDAP connection",
+							      GDA_TYPE_CONNECTION,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class, PROP_BASE,
+                                         g_param_spec_string ("base", NULL, "Base DN", NULL,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class, PROP_FILTER,
+                                         g_param_spec_string ("filter", NULL, "LDAP filter", NULL,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class, PROP_ATTRIBUTES,
+                                         g_param_spec_string ("attributes", NULL, "LDAP attributes", NULL,
+                                                              G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class, PROP_SCOPE,
+                                         g_param_spec_int ("scope", NULL, "LDAP search scope",
+							   GDA_LDAP_SEARCH_BASE,
+							   GDA_LDAP_SEARCH_SUBTREE,
+							   GDA_LDAP_SEARCH_BASE,
+							   G_PARAM_WRITABLE | G_PARAM_READABLE |
+							   G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+dummy_gda_data_model_ldap_data_model_init (GdaDataModelIface *iface)
+{
+	iface->i_get_n_rows = NULL;
+}
+
+static GModule *ldap_prov_module = NULL;
+
+static void
+load_ldap_module (void)
+{
+	if (ldap_prov_module)
+		return;
+
+	GdaProviderInfo *pinfo;
+	pinfo = gda_config_get_provider_info ("Ldap");
+	if (!pinfo)
+		return;
+	ldap_prov_module = g_module_open (pinfo->location, 0);
+}
+
+/**
+ * gda_data_model_ldap_get_type:
+ *
+ * Since: 4.2.8
+ */
+GType
+gda_data_model_ldap_get_type (void)
+{
+        static GType type = 0;
+	if (!type) {
+		typedef GType (*Func) (void);
+		Func func;
+
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			goto dummy;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_data_model_ldap_get_type", (void **) &func))
+			goto dummy;
+
+		type = func ();
+		return type;
+
+	dummy:
+		if (!type) {
+			/* dummy setup to enable GIR compilation */
+			g_warning (_("Dummy GdaDataModelLdap object: if you see this message in your application "
+				     "then it's likely that there is an installation problem with the "
+				     "LDAP provider. In any case the GdaDataModelLdap object won't be useable."));
+			static const GTypeInfo info = {
+				sizeof (GdaDataModelLdapClass),
+				(GBaseInitFunc) NULL,
+				(GBaseFinalizeFunc) NULL,
+				(GClassInitFunc) dummy_gda_data_model_ldap_class_init,
+				NULL,
+				NULL,
+				sizeof (GdaDataModelLdap),
+				0,
+				(GInstanceInitFunc) NULL,
+				0
+			};
+			static const GInterfaceInfo data_model_info = {
+				(GInterfaceInitFunc) dummy_gda_data_model_ldap_data_model_init,
+				NULL,
+				NULL
+			};
+			
+			if (type == 0) {
+				type = g_type_register_static (G_TYPE_OBJECT, "GdaDataModelLdap", &info, 0);
+				g_type_add_interface_static (type, GDA_TYPE_DATA_MODEL, &data_model_info);
+			}
+		}
+
+	}
+	return type;
+}
+
+/**
+ * gda_data_model_ldap_new:
+ * @cnc: an LDAP opened connection (must be a balid #GdaLdapConnection)
+ * @base_dn: (allow-none): the base DN to search on, or %NULL
+ * @filter: (allow-none): an LDAP filter, for example "(objectClass=*)"
+ * @attributes: (allow-none): the list of attributes to fetch, each in the format &lt;attname&gt;[::&lt;GType&gt;] (CSV)
+ * @scope: the search scope
+ *
+ * Creates a new #GdaDataModel object to extract some LDAP contents. The returned data model will
+ * contain one row for each LDAP entry returned by the search, and will
+ * always return the DN (Distinguished Name) of the LDAP entry as first column. Other atttibutes
+ * may be mapped to other columns, see the @attributes argument.
+ *
+ * Note that the actual LDAP search command is not executed until necessary (when using the returned
+ * data model).
+ *
+ * The @base_dn is the point in the LDAP's DIT (Directory Information Tree) from where the search will
+ * occur, for example "dc=gda,dc=org". A %NULL value indicates that the starting point for the
+ * search will be the one specified when opening the LDAP connection.
+ *
+ * The @filter argument is a valid LDAP filter string, for example "(uidNumber=1001)". If %NULL, then
+ * a default search filter of "(objectClass=*)" will be used.
+ *
+ * @attributes specifies which LDAP attributes the search must return. It is a comma separated list
+ * of attribute names, for example "uidNumber, mail, uid, jpegPhoto" (spaces between attribute names
+ * are ignored). If %NULL, then no attribute will be fetched. See gda_ldap_connection_declare_table()
+ * for more information about this argument.
+ *
+ * @scope is the scope of search specified when the LDAP search is actually executed.
+ *
+ * In case of multi valued attributes, an error will be returned when trying to read the attribute:
+ * gda_data_model_iter_get_value_at() will return %NULL when using an iterator.
+ *
+ * Returns: a new #GdaDataModel
+ *
+ * Since: 4.2.8
+ */
+GdaDataModel *
+gda_data_model_ldap_new (GdaConnection *cnc,
+			 const gchar *base_dn, const gchar *filter,
+			 const gchar *attributes, GdaLdapSearchScope scope)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	return (GdaDataModel*) g_object_new (GDA_TYPE_DATA_MODEL_LDAP,
+					     "cnc", cnc, "base", base_dn,
+					     "filter", filter, "attributes", attributes,
+					     "scope", scope, NULL);
+}
+
+/**
+ * gda_data_model_ldap_compute_columns:
+ * @cnc: a #GdaConnection
+ * @attributes: (allow-none): a string describing which LDAP attributes to retreive, or %NULL
+ *
+ * Computes the #GdaColumn of the data model which would be created using @attributes when calling
+ * gda_data_model_ldap_new().
+ *
+ * Returns: (transfer full) (element-type GdaColumn): a list of #GdaColumn objects
+ *
+ * Since: 4.2.8
+ */
+GList *
+gda_data_model_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef GList *(*Func) (GdaConnection*, const gchar *);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_data_model_ldap_compute_columns", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc, attributes);
+}
+
+/*
+ * _gda_ldap_describe_entry:
+ * proxy for gda_ldap_describe_entry().
+ */
+GdaLdapEntry *
+_gda_ldap_describe_entry (GdaLdapConnection *cnc, const gchar *dn, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef GdaLdapEntry *(*Func) (GdaLdapConnection*, const gchar *, GError **);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_describe_entry", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc, dn, error);
+}
+
+/*
+ * _gda_ldap_get_entry_children:
+ * proxy for gda_ldap_get_entry_children().
+ */
+GdaLdapEntry **
+_gda_ldap_get_entry_children (GdaLdapConnection *cnc, const gchar *dn,
+			      gchar **attributes, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef GdaLdapEntry **(*Func) (GdaLdapConnection*, const gchar *, gchar **, GError **);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_get_entry_children", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc, dn, attributes, error);
+}
+
+/*
+ * _gda_ldap_dn_split:
+ * proxy for gda_ldap_dn_split().
+ */
+gchar **
+_gda_ldap_dn_split (const gchar *dn, gboolean all)
+{
+	typedef gchar **(*Func) (const gchar *, gboolean);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_dn_split", (void **) &func))
+			return NULL;
+	}
+	
+	return func (dn, all);
+}
+
+/*
+ * _gda_ldap_is_dn:
+ * proxy for gda_ldap_dn_split().
+ */
+gboolean
+_gda_ldap_is_dn (const gchar *dn)
+{
+	typedef gboolean (*Func) (const gchar *);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return FALSE;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_is_dn", (void **) &func))
+			return FALSE;
+	}
+	
+	return func (dn);
+}
+
+/*
+ * _gda_ldap_get_base_dn:
+ * proxy for gda_ldap_get_base_dn().
+ */
+const gchar *
+_gda_ldap_get_base_dn (GdaLdapConnection *cnc)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef const gchar *(*Func) (GdaLdapConnection *);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_get_base_dn", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc);
+}
+
+/*
+ * _gda_ldap_get_class_info:
+ * proxy for gda_ldap_get_class_info()
+ */
+GdaLdapClass *
+_gda_ldap_get_class_info (GdaLdapConnection *cnc, const gchar *classname)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef GdaLdapClass *(*Func) (GdaLdapConnection *, const gchar *);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_get_class_info", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc, classname);
+}
+
+/*
+ * _gda_ldap_get_top_classes:
+ * proxy for gda_ldap_get_top_classes()
+ */
+const GSList *
+_gda_ldap_get_top_classes (GdaLdapConnection *cnc)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	typedef const GSList *(*Func) (GdaLdapConnection *);
+	static Func func = NULL;
+
+	if (!func) {
+		load_ldap_module ();
+		if (!ldap_prov_module)
+			return NULL;
+		
+		if (!g_module_symbol (ldap_prov_module, "gdaprov_ldap_get_top_classes", (void **) &func))
+			return NULL;
+	}
+	
+	return func (cnc);
+}
diff --git a/libgda/gda-data-model-ldap.h b/libgda/gda-data-model-ldap.h
new file mode 100644
index 0000000..0c1e6ac
--- /dev/null
+++ b/libgda/gda-data-model-ldap.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_DATA_MODEL_LDAP_H__
+#define __GDA_DATA_MODEL_LDAP_H__
+
+#include <libgda/gda-data-model.h>
+
+G_BEGIN_DECLS
+
+#define GDA_TYPE_DATA_MODEL_LDAP            (gda_data_model_ldap_get_type())
+#define GDA_DATA_MODEL_LDAP(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_DATA_MODEL_LDAP, GdaDataModelLdap))
+#define GDA_DATA_MODEL_LDAP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_DATA_MODEL_LDAP, GdaDataModelLdapClass))
+#define GDA_IS_DATA_MODEL_LDAP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE(obj, GDA_TYPE_DATA_MODEL_LDAP))
+#define GDA_IS_DATA_MODEL_LDAP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GDA_TYPE_DATA_MODEL_LDAP))
+
+typedef struct _GdaDataModelLdap        GdaDataModelLdap;
+typedef struct _GdaDataModelLdapClass   GdaDataModelLdapClass;
+typedef struct _GdaDataModelLdapPrivate GdaDataModelLdapPrivate;
+
+struct _GdaDataModelLdap {
+	GObject                  object;
+	GdaDataModelLdapPrivate *priv;
+};
+
+struct _GdaDataModelLdapClass {
+	GObjectClass             parent_class;
+
+	/*< private >*/
+        /* Padding for future expansion */
+        void (*_gda_reserved1) (void);
+        void (*_gda_reserved2) (void);
+        void (*_gda_reserved3) (void);
+        void (*_gda_reserved4) (void);
+};
+
+/**
+ * GdaLdapSearchScope:
+ * @GDA_LDAP_SEARCH_BASE: search of the base object only
+ * @GDA_LDAP_SEARCH_ONELEVEL: search of immediate children of the base object, but does not include the base object itself
+ * @GDA_LDAP_SEARCH_SUBTREE: search of the base object and the entire subtree below the base object
+ *
+ * Defines the search scope of an LDAP search command, relative to the base object.
+ */
+typedef enum {	
+	GDA_LDAP_SEARCH_BASE     = 1,
+	GDA_LDAP_SEARCH_ONELEVEL = 2,
+	GDA_LDAP_SEARCH_SUBTREE  = 3
+} GdaLdapSearchScope;
+
+/**
+ * SECTION:gda-data-model-ldap
+ * @short_description: GdaDataModel to extract LDAP information
+ * @title: GdaDataModelLdap
+ * @stability: Unstable
+ * @see_also: #GdaDataModel
+ *
+ * The #GdaDataModelLdap object allows to perform LDAP searches.
+ *
+ * Note: this type of data model is available only if the LDAP library was found at compilation time and
+ * if the LDAP provider is correctly installed.
+ */
+
+GType         gda_data_model_ldap_get_type     (void) G_GNUC_CONST;
+GdaDataModel *gda_data_model_ldap_new          (GdaConnection *cnc,
+						const gchar *base_dn, const gchar *filter,
+						const gchar *attributes, GdaLdapSearchScope scope);
+
+GList        *gda_data_model_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes);
+
+G_END_DECLS
+
+#endif
diff --git a/libgda/gda-data-model.h b/libgda/gda-data-model.h
index 2615ec9..c15683a 100644
--- a/libgda/gda-data-model.h
+++ b/libgda/gda-data-model.h
@@ -77,7 +77,8 @@ typedef enum {
 	GDA_DATA_MODEL_FILE_EXIST_ERROR,
 	GDA_DATA_MODEL_XML_FORMAT_ERROR,
 
-	GDA_DATA_MODEL_TRUNCATED_ERROR
+	GDA_DATA_MODEL_TRUNCATED_ERROR,
+	GDA_DATA_MODEL_OTHER_ERROR
 } GdaDataModelError;
 
 /* struct for the interface */
diff --git a/libgda/gda-tree-mgr-ldap.c b/libgda/gda-tree-mgr-ldap.c
new file mode 100644
index 0000000..8c45213
--- /dev/null
+++ b/libgda/gda-tree-mgr-ldap.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <libgda/libgda.h>
+#include "gda-tree-mgr-ldap.h"
+#include "gda-tree-node.h"
+#include <sqlite/virtual/gda-ldap-connection.h>
+
+struct _GdaTreeMgrLdapPriv {
+	GdaLdapConnection *cnc;
+	gchar             *dn;
+};
+
+static void gda_tree_mgr_ldap_class_init (GdaTreeMgrLdapClass *klass);
+static void gda_tree_mgr_ldap_init       (GdaTreeMgrLdap *tmgr1, GdaTreeMgrLdapClass *klass);
+static void gda_tree_mgr_ldap_dispose    (GObject *object);
+static void gda_tree_mgr_ldap_set_property (GObject *object,
+					    guint param_id,
+					    const GValue *value,
+					    GParamSpec *pspec);
+static void gda_tree_mgr_ldap_get_property (GObject *object,
+					    guint param_id,
+					    GValue *value,
+					    GParamSpec *pspec);
+
+/* virtual methods */
+static GSList *gda_tree_mgr_ldap_update_children (GdaTreeManager *manager, GdaTreeNode *node, const GSList *children_nodes,
+						  gboolean *out_error, GError **error);
+
+static GObjectClass *parent_class = NULL;
+
+/* properties */
+enum {
+        PROP_0,
+	PROP_CNC,
+	PROP_DN,
+};
+
+/*
+ * GdaTreeMgrLdap class implementation
+ * @klass:
+ */
+static void
+gda_tree_mgr_ldap_class_init (GdaTreeMgrLdapClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* virtual methods */
+	((GdaTreeManagerClass*) klass)->update_children = gda_tree_mgr_ldap_update_children;
+
+	/* Properties */
+        object_class->set_property = gda_tree_mgr_ldap_set_property;
+        object_class->get_property = gda_tree_mgr_ldap_get_property;
+
+	/**
+	 * GdaTreeMgrLdap:connection:
+	 *
+	 * Defines the #GdaLdapConnection to get information from.
+	 */
+	g_object_class_install_property (object_class, PROP_CNC,
+                                         g_param_spec_object ("connection", NULL, "Connection to use",
+                                                              GDA_TYPE_LDAP_CONNECTION,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+	
+	/**
+	 * GdaTreeMgrLdap:dn:
+	 *
+	 * Defines the Distinguised Name of the LDAP entry to list children from
+	 */
+	g_object_class_install_property (object_class, PROP_DN,
+                                         g_param_spec_string ("dn", NULL, "Distinguised Name",
+							      NULL,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+	object_class->dispose = gda_tree_mgr_ldap_dispose;
+}
+
+static void
+gda_tree_mgr_ldap_init (GdaTreeMgrLdap *mgr, G_GNUC_UNUSED GdaTreeMgrLdapClass *klass)
+{
+	g_return_if_fail (GDA_IS_TREE_MGR_LDAP (mgr));
+	mgr->priv = g_new0 (GdaTreeMgrLdapPriv, 1);
+}
+
+static void
+gda_tree_mgr_ldap_dispose (GObject *object)
+{
+	GdaTreeMgrLdap *mgr = (GdaTreeMgrLdap *) object;
+
+	g_return_if_fail (GDA_IS_TREE_MGR_LDAP (mgr));
+
+	if (mgr->priv) {
+		if (mgr->priv->cnc)
+			g_object_unref (mgr->priv->cnc);
+		g_free (mgr->priv->dn);
+		g_free (mgr->priv);
+		mgr->priv = NULL;
+	}
+
+	/* chain to parent class */
+	parent_class->dispose (object);
+}
+
+/**
+ * gda_tree_mgr_select_get_type:
+ *
+ * Returns: the GType
+ *
+ * Since: 4.2.8
+ */
+GType
+gda_tree_mgr_ldap_get_type (void)
+{
+        static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+                static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+                static const GTypeInfo info = {
+                        sizeof (GdaTreeMgrLdapClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) gda_tree_mgr_ldap_class_init,
+                        NULL,
+                        NULL,
+                        sizeof (GdaTreeMgrLdap),
+                        0,
+                        (GInstanceInitFunc) gda_tree_mgr_ldap_init,
+			0
+                };
+
+                g_static_mutex_lock (&registering);
+                if (type == 0)
+                        type = g_type_register_static (GDA_TYPE_TREE_MANAGER, "GdaTreeMgrLdap", &info, 0);
+                g_static_mutex_unlock (&registering);
+        }
+        return type;
+}
+
+static void
+gda_tree_mgr_ldap_set_property (GObject *object,
+				guint param_id,
+				const GValue *value,
+				GParamSpec *pspec)
+{
+        GdaTreeMgrLdap *mgr;
+
+        mgr = GDA_TREE_MGR_LDAP (object);
+        if (mgr->priv) {
+                switch (param_id) {
+		case PROP_CNC:
+			mgr->priv->cnc = (GdaLdapConnection*) g_value_get_object (value);
+			if (mgr->priv->cnc)
+				g_object_ref (mgr->priv->cnc);
+			break;
+		case PROP_DN:
+                        mgr->priv->dn = g_value_dup_string (value);
+                        break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+			break;
+                }
+        }
+}
+
+static void
+gda_tree_mgr_ldap_get_property (GObject *object,
+				guint param_id,
+				GValue *value,
+				GParamSpec *pspec)
+{
+        GdaTreeMgrLdap *mgr;
+
+        mgr = GDA_TREE_MGR_LDAP (object);
+        if (mgr->priv) {
+                switch (param_id) {
+		case PROP_CNC:
+			g_value_set_object (value, mgr->priv->cnc);
+			break;
+		case PROP_DN:
+			g_value_set_string (value, mgr->priv->dn);
+                        break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+			break;
+                }
+        }
+}
+
+/**
+ * gda_tree_mgr_ldap_new:
+ * @cnc: a #GdaConnection object
+ * @dn: (allow-none): an LDAP Distinguished Name or %NULL
+ *
+ * Creates a new #GdaTreeManager object which will list the children of the LDAP entry which Distinguished name
+ * is @dn. If @dn is %NULL, then the tree manager will look in the tree itself for an attribute named "dn" and
+ * use it.
+ *
+ * Returns: (transfer full): a new #GdaTreeManager object
+ *
+ * Since: 4.2.8
+ */
+GdaTreeManager*
+gda_tree_mgr_ldap_new (GdaConnection *cnc, const gchar *dn)
+{
+	GdaTreeMgrLdap *mgr;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+
+	mgr = (GdaTreeMgrLdap*) g_object_new (GDA_TYPE_TREE_MGR_LDAP,
+					      "connection", cnc, 
+					      "dn", dn, NULL);
+	return (GdaTreeManager*) mgr;
+}
+
+static GSList *
+gda_tree_mgr_ldap_update_children (GdaTreeManager *manager, GdaTreeNode *node,
+				   G_GNUC_UNUSED const GSList *children_nodes, gboolean *out_error,
+				   GError **error)
+{
+	GdaTreeMgrLdap *mgr = GDA_TREE_MGR_LDAP (manager);
+	gchar *real_dn = NULL;
+
+	if (!mgr->priv->cnc) {
+		g_set_error (error, GDA_TREE_MANAGER_ERROR, GDA_TREE_MANAGER_UNKNOWN_ERROR,
+			     _("No LDAP connection specified"));
+		if (out_error)
+			*out_error = TRUE;
+		return NULL;
+	}
+
+	if (mgr->priv->dn)
+		real_dn = g_strdup (mgr->priv->dn);
+	else if (node) {
+		/* looking for a dn in @node's attributes */
+		const GValue *cvalue;
+		cvalue = gda_tree_node_fetch_attribute (node, "dn");
+		if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING))
+			real_dn = g_value_dup_string (cvalue);
+	}
+
+	GdaLdapEntry **entries;
+	entries = gda_ldap_get_entry_children (mgr->priv->cnc, real_dn, NULL, error);
+	g_free (real_dn);
+	if (entries) {
+		gint i;
+		GSList *list = NULL;
+		for (i = 0; entries [i]; i++) {
+			GdaTreeNode* snode;
+			GValue *dnv;
+			GdaLdapEntry *lentry;
+			lentry = entries [i];
+			snode = gda_tree_manager_create_node (manager, node, lentry->dn);
+
+			/* full DN */
+			g_value_set_string ((dnv = gda_value_new (G_TYPE_STRING)), lentry->dn);
+			gda_tree_node_set_node_attribute (snode, "dn", dnv, NULL);
+			gda_value_free (dnv);
+
+			/* RDN */
+                        gchar **array;
+                        array = gda_ldap_dn_split (lentry->dn, FALSE);
+                        if (array) {
+                                g_value_set_string ((dnv = gda_value_new (G_TYPE_STRING)), array [0]);
+                                gda_tree_node_set_node_attribute (snode, "rdn", dnv, NULL);
+				gda_value_free (dnv);
+                                g_strfreev (array);
+                        }
+
+			if (gda_tree_manager_get_managers (manager)) {
+				g_value_set_boolean ((dnv = gda_value_new (G_TYPE_BOOLEAN)), TRUE);
+				gda_tree_node_set_node_attribute (snode,
+								  GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN,
+								  dnv, NULL);
+				gda_value_free (dnv);
+			}
+
+			list = g_slist_prepend (list, snode);
+			gda_ldap_entry_free (lentry);
+		}
+		g_free (entries);
+
+		if (node)
+			gda_tree_node_set_node_attribute (node,
+							  GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN,
+							  NULL, NULL);
+		return list;
+	}
+	else {
+		if (out_error)
+			*out_error = TRUE;
+		return NULL;
+	}
+}
diff --git a/libgda/gda-tree-mgr-ldap.h b/libgda/gda-tree-mgr-ldap.h
new file mode 100644
index 0000000..d079107
--- /dev/null
+++ b/libgda/gda-tree-mgr-ldap.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_TREE_MGR_LDAP_H__
+#define __GDA_TREE_MGR_LDAP_H__
+
+#include <libgda/gda-connection.h>
+#include "gda-tree-manager.h"
+
+G_BEGIN_DECLS
+
+#define GDA_TYPE_TREE_MGR_LDAP            (gda_tree_mgr_ldap_get_type())
+#define GDA_TREE_MGR_LDAP(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_TREE_MGR_LDAP, GdaTreeMgrLdap))
+#define GDA_TREE_MGR_LDAP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_TREE_MGR_LDAP, GdaTreeMgrLdapClass))
+#define GDA_IS_TREE_MGR_LDAP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE(obj, GDA_TYPE_TREE_MGR_LDAP))
+#define GDA_IS_TREE_MGR_LDAP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GDA_TYPE_TREE_MGR_LDAP))
+#define GDA_TREE_MGR_LDAP_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), GDA_TYPE_TREE_MGR_LDAP, GdaTreeMgrLdapClass))
+
+typedef struct _GdaTreeMgrLdap GdaTreeMgrLdap;
+typedef struct _GdaTreeMgrLdapPriv GdaTreeMgrLdapPriv;
+typedef struct _GdaTreeMgrLdapClass GdaTreeMgrLdapClass;
+
+struct _GdaTreeMgrLdap {
+	GdaTreeManager      object;
+	GdaTreeMgrLdapPriv *priv;
+};
+
+struct _GdaTreeMgrLdapClass {
+	GdaTreeManagerClass object_class;
+};
+
+/**
+ * SECTION:gda-tree-mgr-ldap
+ * @short_description: A tree manager which creates a node for each child entry of an LDAP entry
+ * @title: GdaTreeMgrLdap
+ * @stability: Stable
+ * @see_also:
+ *
+ * The #GdaTreeMgrLdap is a #GdaTreeManager object which creates a node for
+ * each child entry of an LDAP entry.
+ *
+ * Note: this type of tree manager is available only if the LDAP library was found at compilation time and
+ * if the LDAP provider is correctly installed.
+ */
+
+GType              gda_tree_mgr_ldap_get_type  (void) G_GNUC_CONST;
+GdaTreeManager*    gda_tree_mgr_ldap_new       (GdaConnection *cnc, const gchar *dn);
+
+G_END_DECLS
+
+#endif
diff --git a/libgda/gda-tree-node.c b/libgda/gda-tree-node.c
index 755edec..a4f890e 100644
--- a/libgda/gda-tree-node.c
+++ b/libgda/gda-tree-node.c
@@ -750,13 +750,35 @@ gda_tree_node_set_node_attribute (GdaTreeNode *node, const gchar *attribute, con
 {
 	const GValue *cvalue;
 	g_return_if_fail (GDA_IS_TREE_NODE (node));
+	g_return_if_fail (attribute);
 
 	cvalue = gda_attributes_manager_get (gda_tree_node_attributes_manager, node, attribute);
 	if ((value && cvalue && !gda_value_differ (cvalue, value)) ||
 	    (!value && !cvalue))
 		return;
 
-	gda_attributes_manager_set_full (gda_tree_node_attributes_manager, node, attribute, value, destroy);
+	if (!strcmp (attribute, GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN) &&
+	    (!value || (G_VALUE_TYPE (value) == G_TYPE_BOOLEAN))) {
+		gboolean ouc = FALSE;
+		gboolean nuc = FALSE;
+		if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_BOOLEAN) &&
+		    g_value_get_boolean (cvalue))
+			ouc = TRUE;
+
+		if (value && g_value_get_boolean (value))
+			nuc = TRUE;
+
+		if (ouc != nuc) {
+			gda_attributes_manager_set_full (gda_tree_node_attributes_manager, node,
+							 attribute, value, destroy);
+			g_signal_emit (node, gda_tree_node_signals[NODE_HAS_CHILD_TOGGLED], 0, node);
+			g_signal_emit (node, gda_tree_node_signals[NODE_CHANGED], 0, node);
+			return;
+		}
+	}
+
+	gda_attributes_manager_set_full (gda_tree_node_attributes_manager, node, attribute,
+					 value, destroy);
 	g_signal_emit (node, gda_tree_node_signals[NODE_CHANGED], 0, node);
 }
 
diff --git a/libgda/gda-tree.c b/libgda/gda-tree.c
index 31c33ee..cf57bca 100644
--- a/libgda/gda-tree.c
+++ b/libgda/gda-tree.c
@@ -1,5 +1,5 @@
-/* GDA library
- * Copyright (C) 2009 - 2010 The GNOME Foundation.
+/*
+ * Copyright (C) 2009 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *      Vivien Malerba <malerba gnome-db org>
@@ -32,10 +32,6 @@
 struct _GdaTreePrivate {
 	GSList      *managers; /* list of GdaTreeManager */
 	GdaTreeNode *root;
-
-	gboolean     update_on_searching; /* set to FALSE if GdaTree's contents is supposed to be constant
-					   * needs a PROPRERTY because it's now a constant, or even
-					   * maybe move it to each GdaTreeManager */
 };
 
 static void gda_tree_class_init (GdaTreeClass *klass);
@@ -191,8 +187,6 @@ gda_tree_init (GdaTree *tree, G_GNUC_UNUSED GdaTreeClass *klass)
 	tree->priv->managers = NULL;
 
 	take_root_node (tree, gda_tree_node_new (NULL));
-
-	tree->priv->update_on_searching = FALSE;
 }
 
 static void
@@ -331,7 +325,7 @@ gda_tree_new (void)
 /**
  * gda_tree_add_manager:
  * @tree: a #GdaTree object
- * @manager: a #GdaTreeManager object
+ * @manager: (transfer none): a #GdaTreeManager object
  * 
  * Sets @manager as a top #GdaTreeManager object, which will be responsible for creating top level nodes in @tree.
  *
@@ -421,22 +415,71 @@ gboolean
 gda_tree_update_part (GdaTree *tree, GdaTreeNode *node, GError **error)
 {
 	GSList *mgrlist;
+	GdaTreeManager *mgr;
+	GdaTreeNode *top;
 
 	g_return_val_if_fail (GDA_IS_TREE (tree), FALSE);
 	g_return_val_if_fail (GDA_IS_TREE_NODE (node), FALSE);
-	
-	mgrlist = _gda_tree_node_get_managers_for_children (node);
+
+	top = gda_tree_node_get_parent (node);
+	if (!top)
+		top = tree->priv->root;
+	mgr = _gda_tree_node_get_manager_for_child (top, node);
+	mgrlist = (GSList*) gda_tree_manager_get_managers (mgr);
 
 	if (mgrlist) {
 		gboolean res;
 		res = create_or_update_children (mgrlist, node, FALSE, error);
-		g_slist_free (mgrlist);
 		return res;
 	}
 	return TRUE;
 }
 
 /**
+ * gda_tree_update_children:
+ * @tree: a #GdaTree object
+ * @node: (allow-none): a #GdaTreeNode node in @tree
+ * @error: (allow-none): a place to store errors, or %NULL
+ *
+ * Update the children of @node in @tree (not recursively, to update recursively, use
+ * gda_tree_update_part()). If @node is %NULL then the top level nodes are updated.
+ *
+ * Returns: TRUE if no error occurred.
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gda_tree_update_children (GdaTree *tree, GdaTreeNode *node, GError **error)
+{
+	GSList *mgrlist;
+	GdaTreeManager *mgr;
+	GdaTreeNode *top;
+
+	g_return_val_if_fail (GDA_IS_TREE (tree), FALSE);
+	g_return_val_if_fail (! node || GDA_IS_TREE_NODE (node), FALSE);
+
+	if (node) {
+		top = gda_tree_node_get_parent (node);
+		if (!top)
+			top = tree->priv->root;
+		mgr = _gda_tree_node_get_manager_for_child (top, node);
+		mgrlist = (GSList*) gda_tree_manager_get_managers (mgr);
+
+		if (mgrlist) {
+			gboolean res;
+			res = create_or_update_children (mgrlist, node, TRUE, error);
+			return res;
+		}
+	}
+	else {
+		/* update top level nodes */
+		create_or_update_children (tree->priv->managers, tree->priv->root, TRUE, error);
+	}
+
+	return TRUE;
+}
+
+/**
  * gda_tree_dump:
  * @tree: a #GdaTree
  * @node: a #GdaTreeNode to start the dump from, or %NULL for a full dump
@@ -523,10 +566,6 @@ gda_tree_get_nodes_in_path (GdaTree *tree, const gchar *tree_path, gboolean use_
 static GSList *
 real_gda_tree_get_nodes_in_path (GdaTree *tree, GSList *segments, gboolean use_names, GdaTreeNode **out_last_node)
 {
-	/* update 1st level if necessary */
-	if (tree->priv->update_on_searching)
-		create_or_update_children (tree->priv->managers, tree->priv->root, TRUE, NULL);
-
 	if (out_last_node)
 		*out_last_node = NULL;
 
@@ -542,7 +581,6 @@ real_gda_tree_get_nodes_in_path (GdaTree *tree, GSList *segments, gboolean use_n
 	GSList *seglist;
 	GdaTreeNode *node;
 	GdaTreeNode *parent;
-	GSList *mgrlist;
 	for (seglist = segments, parent = tree->priv->root;
 	     seglist;
 	     seglist = seglist->next, parent = node) {
@@ -550,32 +588,10 @@ real_gda_tree_get_nodes_in_path (GdaTree *tree, GSList *segments, gboolean use_n
 			node = gda_tree_node_get_child_name (parent, (gchar *) seglist->data);
 		else
 			node = gda_tree_node_get_child_index (parent, atoi ((gchar *) seglist->data)); /* Flawfinder: ignore */
-		if (!node && tree->priv->update_on_searching) {
-			/* update level if necessary */
-			mgrlist = _gda_tree_node_get_managers_for_children (parent);
-
-			if (mgrlist) {
-				create_or_update_children (mgrlist, parent, TRUE, NULL);
-				g_slist_free (mgrlist);
-			}
-
-			/* try again now */
-			if (use_names)
-				node = gda_tree_node_get_child_name (parent, (gchar *) seglist->data);
-			else
-				node = gda_tree_node_get_child_index (parent, atoi ((gchar *) seglist->data)); /* Flawfinder: ignore */
-		}
 		if (!node) 
 			return NULL;
 	}
 
-	if (tree->priv->update_on_searching) {
-		mgrlist = _gda_tree_node_get_managers_for_children (node);
-		if (mgrlist) {
-			create_or_update_children (mgrlist, node, TRUE, NULL);
-			g_slist_free (mgrlist);
-		}
-	}
 	if (out_last_node) {
 		*out_last_node = node;
 		return NULL;
diff --git a/libgda/gda-tree.h b/libgda/gda-tree.h
index d10efde..050a2b5 100644
--- a/libgda/gda-tree.h
+++ b/libgda/gda-tree.h
@@ -73,6 +73,7 @@ void               gda_tree_add_manager   (GdaTree *tree, GdaTreeManager *manage
 void               gda_tree_clean         (GdaTree *tree);
 gboolean           gda_tree_update_all    (GdaTree *tree, GError **error);
 gboolean           gda_tree_update_part   (GdaTree *tree, GdaTreeNode *node, GError **error);
+gboolean           gda_tree_update_children (GdaTree *tree, GdaTreeNode *node, GError **error);
 
 GSList            *gda_tree_get_nodes_in_path (GdaTree *tree, const gchar *tree_path, gboolean use_names);
 GdaTreeNode       *gda_tree_get_node      (GdaTree *tree, const gchar *tree_path, gboolean use_names);
diff --git a/libgda/libgda.h.in b/libgda/libgda.h.in
index 2e384d4..ae6a57b 100644
--- a/libgda/libgda.h.in
+++ b/libgda/libgda.h.in
@@ -36,6 +36,8 @@
 #include <libgda/gda-data-comparator.h>
 #include <libgda/gda-data-model-array.h>
 @LIBGDA_BDB_INC@
+ LIBGDA_LDAP_INC@
+ LIBGDA_LDAP_INC2@
 #include <libgda/gda-data-model.h>
 #include <libgda/gda-data-model-iter.h>
 #include <libgda/gda-data-model-import.h>
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index 05242ca..53c5ef7 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -269,6 +269,11 @@
 	gda_data_model_iter_move_to_row
 	gda_data_model_iter_move_to_row_default
 	gda_data_model_iter_set_value_at
+#ifdef HAVE_LDAP
+	gda_data_model_ldap_compute_columns
+	gda_data_model_ldap_get_type
+	gda_data_model_ldap_new
+#endif
 	gda_data_model_remove_row
 	gda_data_model_reset
 	gda_data_model_row_inserted
@@ -405,6 +410,20 @@
 	gda_insert_row_into_table
 	gda_insert_row_into_table_v
 	gda_lang_locale
+#ifdef HAVE_LDAP
+	gda_ldap_connection_get_type
+	gda_ldap_get_class_info
+	gda_ldap_get_top_classes
+	gda_ldap_connection_declare_table
+	gda_ldap_connection_describe_table
+	gda_ldap_connection_get_base_dn
+	gda_ldap_connection_undeclare_table
+	gda_ldap_describe_entry
+	gda_ldap_dn_split
+	gda_ldap_entry_free
+	gda_ldap_get_entry_children
+	gda_ldap_is_dn
+#endif
 	gda_locale_changed
 	gda_lockable_get_type
 	gda_lockable_lock
@@ -862,6 +881,10 @@
 	gda_tree_mgr_columns_new
 	gda_tree_mgr_label_get_type
 	gda_tree_mgr_label_new
+#ifdef HAVE_LDAP
+	gda_tree_mgr_ldap_get_type
+	gda_tree_mgr_ldap_new
+#endif
 	gda_tree_mgr_schemas_get_type
 	gda_tree_mgr_schemas_new
 	gda_tree_mgr_select_get_type
@@ -883,6 +906,7 @@
 	gda_tree_node_set_node_attribute
 	gda_tree_set_attribute
 	gda_tree_update_all
+	gda_tree_update_children
 	gda_tree_update_part
 	gda_update_row_in_table
 	gda_update_row_in_table_v
diff --git a/libgda/sqlite/gda-sqlite-provider.c b/libgda/sqlite/gda-sqlite-provider.c
index f3d6b1b..dde1f7a 100644
--- a/libgda/sqlite/gda-sqlite-provider.c
+++ b/libgda/sqlite/gda-sqlite-provider.c
@@ -2935,13 +2935,26 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
 			flags = GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD;
 
                 data_model = (GObject *) _gda_sqlite_recordset_new (cnc, ps, params, flags, col_types, empty_rs);
-		gda_connection_internal_statement_executed (cnc, stmt, params, NULL);
-		if (new_ps)
-			g_object_unref (ps);
-		if (allow_noparam)
-			g_object_set (data_model, "auto-reset", TRUE, NULL);
-		pending_blobs_free_list (blobs_list);
-		return data_model;
+		GError **exceptions;
+		exceptions = gda_data_model_get_exceptions (GDA_DATA_MODEL (data_model));
+		if (exceptions && exceptions[0]) {
+			GError *e;
+			e = g_error_copy (exceptions[0]);
+			event = gda_connection_point_available_event (cnc, GDA_CONNECTION_EVENT_ERROR);
+			gda_connection_event_set_description (event, e->message ? e->message : _("No detail"));
+			g_propagate_error (error, e);
+			g_object_unref (data_model);
+			return NULL;
+		}
+		else {
+			gda_connection_internal_statement_executed (cnc, stmt, params, NULL);
+			if (new_ps)
+				g_object_unref (ps);
+			if (allow_noparam)
+				g_object_set (data_model, "auto-reset", TRUE, NULL);
+			pending_blobs_free_list (blobs_list);
+			return data_model;
+		}
         }
 	else {
                 int status, changes;
diff --git a/libgda/sqlite/gda-sqlite-recordset.c b/libgda/sqlite/gda-sqlite-recordset.c
index ff7c890..60bcab7 100644
--- a/libgda/sqlite/gda-sqlite-recordset.c
+++ b/libgda/sqlite/gda-sqlite-recordset.c
@@ -558,10 +558,6 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
 	case SQLITE_BUSY:
 		/* nothing to do */
 		break;
-	case SQLITE_ERROR:
-		g_set_error (error, GDA_SERVER_PROVIDER_ERROR,
-			     GDA_SERVER_PROVIDER_INTERNAL_ERROR,  "%s", SQLITE3_CALL (sqlite3_errmsg) (cdata->connection));
-		break;
 	case SQLITE_DONE:
 		GDA_DATA_SELECT (model)->advertized_nrows = model->priv->next_row_num;
 		SQLITE3_CALL (sqlite3_reset) (ps->sqlite_stmt);
@@ -572,8 +568,10 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
 			     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
 			      "%s", _("SQLite provider fatal internal error"));
 		break;
+	case SQLITE_ERROR:
 	default: {
 		GError *lerror = NULL;
+		SQLITE3_CALL (sqlite3_reset) (ps->sqlite_stmt);
 		if (rc == SQLITE_IOERR_TRUNCATE)
 			g_set_error (&lerror, GDA_DATA_MODEL_ERROR,
 				     GDA_DATA_MODEL_TRUNCATED_ERROR, _("Tuncated data"));
@@ -582,9 +580,9 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
 				     GDA_SERVER_PROVIDER_INTERNAL_ERROR, 
 				     "%s", SQLITE3_CALL (sqlite3_errmsg) (cdata->connection));
 		gda_data_select_add_exception (GDA_DATA_SELECT (model), lerror);
-
+		if (rc == SQLITE_ERROR)
+			g_propagate_error (error, g_error_copy (lerror));
 		GDA_DATA_SELECT (model)->advertized_nrows = model->priv->next_row_num;
-		SQLITE3_CALL (sqlite3_reset) (ps->sqlite_stmt);
 		break;
 	}
 	}
diff --git a/libgda/sqlite/virtual/.gitignore b/libgda/sqlite/virtual/.gitignore
new file mode 100644
index 0000000..62d0d62
--- /dev/null
+++ b/libgda/sqlite/virtual/.gitignore
@@ -0,0 +1 @@
+libgda-virtual.h
diff --git a/libgda/sqlite/virtual/Makefile.am b/libgda/sqlite/virtual/Makefile.am
index 1864efd..34bc089 100644
--- a/libgda/sqlite/virtual/Makefile.am
+++ b/libgda/sqlite/virtual/Makefile.am
@@ -9,6 +9,13 @@ endif
 
 noinst_LTLIBRARIES = libgda-virtual-4.0.la
 
+if LDAP
+GDA_LDAP_H=gda-ldap-connection.h
+GDA_LDAP_S=gda-ldap-connection.c
+DEF_FLAGS=-DHAVE_LDAP
+endif
+
+
 AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_builddir) \
@@ -27,6 +34,7 @@ virtual_headers = \
 	gda-vprovider-hub.h \
 	gda-virtual-connection.h \
 	gda-virtual-provider.h \
+	$(GDA_LDAP_H) \
 	libgda-virtual.h
 
 libgda_virtual_4_0_la_SOURCES = \
@@ -37,7 +45,8 @@ libgda_virtual_4_0_la_SOURCES = \
 	gda-vprovider-data-model.c \
 	gda-vprovider-hub.c \
 	gda-virtual-connection.c \
-	gda-virtual-provider.c 
+	$(GDA_LDAP_S) \
+	gda-virtual-provider.c
 
 gdaincludedir=$(includedir)/libgda-$(GDA_ABI_MAJOR_VERSION).$(GDA_ABI_MINOR_VERSION)/libgda/virtual
 gdainclude_HEADERS=$(virtual_headers)
diff --git a/libgda/sqlite/virtual/gda-ldap-connection.c b/libgda/sqlite/virtual/gda-ldap-connection.c
new file mode 100644
index 0000000..ea9ccdb
--- /dev/null
+++ b/libgda/sqlite/virtual/gda-ldap-connection.c
@@ -0,0 +1,1025 @@
+/* 
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gda-util.h>
+#include "gda-ldap-connection.h"
+#include <libgda/gda-connection-private.h>
+#include <sql-parser/gda-sql-parser.h>
+
+/* "inherits" GdaVconnectionDataModelSpec */
+typedef struct {
+	GdaVconnectionDataModelSpec  spec;
+	GdaConnection               *ldap_cnc;
+	gchar                       *table_name;
+	gchar                       *base_dn;
+	gchar                       *filter;
+	gchar                       *attributes;
+	GList                       *columns;
+	GdaLdapSearchScope           scope;
+
+	GHashTable                  *filters_hash; /* key = string; value = a ComputedFilter pointer */
+} LdapTableMap;
+
+static void
+_ldap_table_map_free (LdapTableMap *map)
+{
+	if (map->ldap_cnc)
+		g_object_unref (map->ldap_cnc);
+	g_free (map->table_name);
+	g_free (map->base_dn);
+	g_free (map->filter);
+	g_free (map->attributes);
+	if (map->columns) {
+		g_list_foreach (map->columns, (GFunc) g_object_unref, NULL);
+		g_list_free (map->columns);
+	}
+	if (map->filters_hash)
+		g_hash_table_destroy (map->filters_hash);
+	g_free (map);
+}
+
+struct _GdaLdapConnectionPrivate {
+	GSList   *maps; /* list of #LdapTableMap, no ref held there */
+	gchar    *startup_file;
+	gboolean  loading_startup_file;
+};
+
+static void gda_ldap_connection_class_init (GdaLdapConnectionClass *klass);
+static void gda_ldap_connection_init       (GdaLdapConnection *cnc, GdaLdapConnectionClass *klass);
+static void gda_ldap_connection_dispose    (GObject *object);
+static void gda_ldap_connection_set_property (GObject *object,
+					      guint param_id,
+					      const GValue *value,
+					      GParamSpec *pspec);
+static void gda_ldap_connection_get_property (GObject *object,
+					      guint param_id,
+					      GValue *value,
+					      GParamSpec *pspec);
+
+static GObjectClass *parent_class = NULL;
+
+/* properties */
+enum
+{
+        PROP_0,
+	PROP_STARTUP_FILE
+};
+
+static void
+update_connection_startup_file (GdaLdapConnection *cnc)
+{
+	if (! cnc->priv->startup_file || cnc->priv->loading_startup_file)
+		return;
+
+	GSList *list;
+	GString *string = NULL;
+	GError *lerror = NULL;
+
+	string = g_string_new ("");
+	for (list = cnc->priv->maps; list; list = list->next) {
+		LdapTableMap *map = (LdapTableMap*) list->data;
+		g_string_append_printf (string, "CREATE LDAP TABLE %s ", map->table_name);
+		if (map->base_dn)
+			g_string_append_printf (string, "BASE='%s' ", map->base_dn);
+		if (map->filter)
+			g_string_append_printf (string, "FILTER='%s' ", map->filter);
+		if (map->attributes)
+			g_string_append_printf (string, "ATTRIBUTES='%s' ", map->attributes);
+		g_string_append (string, "SCOPE=");
+		switch (map->scope) {
+		case GDA_LDAP_SEARCH_BASE:
+			g_string_append (string, "'BASE';\n");
+			break;
+		case GDA_LDAP_SEARCH_ONELEVEL:
+			g_string_append (string, "'ONELEVEL';\n");
+			break;
+		case GDA_LDAP_SEARCH_SUBTREE:
+			g_string_append (string, "'SUBTREE';\n");
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+	}
+	if (! g_file_set_contents (cnc->priv->startup_file, string->str, -1, &lerror)) {
+		GdaConnectionEvent *event;
+		gchar *msg;
+		event = gda_connection_point_available_event (GDA_CONNECTION (cnc),
+							      GDA_CONNECTION_EVENT_WARNING);
+		msg = g_strdup_printf (_("Error storing list of created LDAP tables: %s"),
+				       lerror && lerror->message ? lerror->message : _("No detail"));
+		gda_connection_event_set_description (event, msg);
+		gda_connection_add_event (GDA_CONNECTION (cnc), event);
+		g_free (msg);
+		g_clear_error (&lerror);
+	}
+}
+
+#ifdef GDA_DEBUG_NO
+static void
+dump_vtables (GdaLdapConnection *cnc)
+{
+	GSList *list;
+	g_print ("LDAP tables: %d\n", g_slist_length (cnc->priv->maps));
+	for (list = cnc->priv->maps; list; list = list->next) {
+		LdapTableMap *map = (LdapTableMap*) list->data;
+		g_print ("    LDAP Vtable: %s (map %p)\n", map->table_name, map);
+	}
+}
+#endif
+
+static void
+vtable_created (GdaVconnectionDataModel *cnc, const gchar *table_name)
+{
+#ifdef GDA_DEBUG_NO
+	g_print ("VTable created: %s\n", table_name);
+	dump_vtables (GDA_LDAP_CONNECTION (cnc));
+#endif
+	if (GDA_VCONNECTION_DATA_MODEL_CLASS (parent_class)->vtable_created)
+		GDA_VCONNECTION_DATA_MODEL_CLASS (parent_class)->vtable_created (cnc, table_name);
+	update_connection_startup_file (GDA_LDAP_CONNECTION (cnc));
+}
+
+static void
+vtable_dropped (GdaVconnectionDataModel *cnc, const gchar *table_name)
+{
+	GdaLdapConnection *lcnc;
+	LdapTableMap *map = NULL;
+	GSList *list;
+
+	lcnc = GDA_LDAP_CONNECTION (cnc);
+	for (list = lcnc->priv->maps; list; list = list->next) {
+		if (!strcmp (((LdapTableMap*)list->data)->table_name, table_name)) {
+			map = (LdapTableMap*)list->data;
+			break;
+		}
+	}
+	if (map) {
+		lcnc->priv->maps = g_slist_remove (lcnc->priv->maps, map);
+#ifdef GDA_DEBUG_NO
+		g_print ("VTable dropped: %s\n", table_name);
+		dump_vtables (lcnc);
+#endif
+	}
+	if (GDA_VCONNECTION_DATA_MODEL_CLASS (parent_class)->vtable_dropped)
+		GDA_VCONNECTION_DATA_MODEL_CLASS (parent_class)->vtable_dropped (cnc, table_name);
+	update_connection_startup_file (GDA_LDAP_CONNECTION (cnc));
+}
+
+/*
+ * GdaLdapConnection class implementation
+ */
+static void
+gda_ldap_connection_class_init (GdaLdapConnectionClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+	object_class->dispose = gda_ldap_connection_dispose;
+	GDA_VCONNECTION_DATA_MODEL_CLASS (klass)->vtable_created = vtable_created;
+	GDA_VCONNECTION_DATA_MODEL_CLASS (klass)->vtable_dropped = vtable_dropped;
+
+	/* Properties */
+        object_class->set_property = gda_ldap_connection_set_property;
+        object_class->get_property = gda_ldap_connection_get_property;
+	g_object_class_install_property (object_class, PROP_STARTUP_FILE,
+                                         g_param_spec_string ("startup-file", NULL, _("File used to store startup data"), NULL,
+                                                              (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+}
+
+static void
+dsn_set_cb (GdaLdapConnection *cnc, G_GNUC_UNUSED GParamSpec *pspec, G_GNUC_UNUSED gpointer data)
+{
+	gchar *fname, *tmp, *dsn;
+	g_object_get (cnc, "dsn", &dsn, NULL);
+	tmp = g_strdup_printf ("ldap-%s.start", dsn);
+	g_free (dsn);
+	fname = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (),
+			      "libgda", tmp, NULL);
+	g_free (tmp);
+	g_free (cnc->priv->startup_file);
+	cnc->priv->startup_file = fname;
+}
+
+static void
+conn_opened_cb (GdaLdapConnection *cnc, G_GNUC_UNUSED gpointer data)
+{
+	if (!cnc->priv->startup_file)
+		return;
+
+	cnc->priv->loading_startup_file = TRUE;
+
+	GdaSqlParser *parser;
+	GdaBatch *batch;
+	GError *lerror = NULL;
+	parser = gda_connection_create_parser (GDA_CONNECTION (cnc));
+	if (!parser)
+		parser = gda_sql_parser_new ();
+	batch = gda_sql_parser_parse_file_as_batch (parser, cnc->priv->startup_file, &lerror);
+	if (batch) {
+		GSList *list;
+		list = gda_connection_batch_execute (GDA_CONNECTION (cnc), batch, NULL, 0, &lerror);
+		g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+		g_slist_free (list);
+		g_object_unref (batch);
+	}
+	if (lerror) {
+		GdaConnectionEvent *event;
+		gchar *msg;
+		event = gda_connection_point_available_event (GDA_CONNECTION (cnc),
+							      GDA_CONNECTION_EVENT_WARNING);
+		msg = g_strdup_printf (_("Error recreating LDAP tables: %s"),
+				       lerror && lerror->message ? lerror->message : _("No detail"));
+		gda_connection_event_set_description (event, msg);
+		gda_connection_add_event (GDA_CONNECTION (cnc), event);
+		g_free (msg);
+		g_clear_error (&lerror);
+	}
+	g_object_unref (parser);
+
+	cnc->priv->loading_startup_file = FALSE;
+}
+
+static void
+gda_ldap_connection_init (GdaLdapConnection *cnc, G_GNUC_UNUSED GdaLdapConnectionClass *klass)
+{
+	cnc->priv = g_new (GdaLdapConnectionPrivate, 1);
+	cnc->priv->maps = NULL;
+	cnc->priv->startup_file = NULL;
+	cnc->priv->loading_startup_file = FALSE;
+
+	g_signal_connect (cnc, "notify::dsn",
+			  G_CALLBACK (dsn_set_cb), NULL);
+	g_signal_connect (cnc, "conn-opened",
+			  G_CALLBACK (conn_opened_cb), NULL);
+}
+
+static void
+gda_ldap_connection_dispose (GObject *object)
+{
+	GdaLdapConnection *cnc = (GdaLdapConnection *) object;
+
+	g_return_if_fail (GDA_IS_LDAP_CONNECTION (cnc));
+
+	/* free memory */
+	if (cnc->priv) {
+		if (cnc->priv->maps)
+			g_slist_free (cnc->priv->maps);
+		g_free (cnc->priv->startup_file);
+		g_free (cnc->priv);
+		cnc->priv = NULL;
+	}
+
+	/* chain to parent class */
+	parent_class->dispose (object);
+}
+
+GType
+gda_ldap_connection_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		if (type == 0) {
+			static GTypeInfo info = {
+				sizeof (GdaLdapConnectionClass),
+				(GBaseInitFunc) NULL,
+				(GBaseFinalizeFunc) NULL,
+				(GClassInitFunc) gda_ldap_connection_class_init,
+				NULL, NULL,
+				sizeof (GdaLdapConnection),
+				0,
+				(GInstanceInitFunc) gda_ldap_connection_init,
+				0
+			};
+			
+		g_static_mutex_lock (&registering);
+		if (type == 0)
+			type = g_type_register_static (GDA_TYPE_VCONNECTION_DATA_MODEL, "GdaLdapConnection", &info, 0);
+		g_static_mutex_unlock (&registering);
+		}
+	}
+
+	return type;
+}
+
+static void
+gda_ldap_connection_set_property (GObject *object,
+				  guint param_id,
+				  const GValue *value,
+				  GParamSpec *pspec)
+{
+        GdaLdapConnection *cnc;
+        cnc = GDA_LDAP_CONNECTION (object);
+        if (cnc->priv) {
+                switch (param_id) {
+                case PROP_STARTUP_FILE: {
+			if (cnc->priv->startup_file) {
+				/* don't override any preexisting setting from a DSN */
+				gchar *dsn;
+				g_object_get (cnc, "dsn", &dsn, NULL);
+				if (dsn)
+					g_free (dsn);
+				else {
+					g_free (cnc->priv->startup_file);
+					cnc->priv->startup_file = NULL;
+				}
+			}
+			if (! cnc->priv->startup_file) {
+				if (g_value_get_string (value))
+					cnc->priv->startup_file = g_value_dup_string (value);
+			}
+			break;
+		}
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                        break;
+                }
+	}
+}
+
+static void
+gda_ldap_connection_get_property (GObject *object,
+				  guint param_id,
+				  GValue *value,
+				  GParamSpec *pspec)
+{
+	GdaLdapConnection *cnc;
+        cnc = GDA_LDAP_CONNECTION (object);
+        if (cnc->priv) {
+                switch (param_id) {
+                case PROP_STARTUP_FILE:
+			g_value_set_string (value, cnc->priv->startup_file);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+                        break;
+                }
+	}	
+}
+
+static GList *
+_ldap_create_columns_func (GdaVconnectionDataModelSpec *spec, G_GNUC_UNUSED GError **error)
+{
+	LdapTableMap *map = (LdapTableMap *) spec;
+	if (!map->columns)
+		map->columns = gda_data_model_ldap_compute_columns (map->ldap_cnc, map->attributes);
+	g_list_foreach (map->columns, (GFunc) g_object_ref, NULL);
+	return g_list_copy (map->columns);
+}
+
+static gchar *
+make_string_for_filter (GdaVconnectionDataModelFilter *info)
+{
+	GString *string;
+	gint i;
+
+	string = g_string_new ("");
+	for (i = 0; i < info->nConstraint; i++) {
+		const struct GdaVirtualConstraint *cons;
+		cons = &(info->aConstraint [i]);
+		g_string_append_printf (string, "|%d,%d", cons->iColumn, cons->op);
+	}
+	return g_string_free (string, FALSE);
+}
+
+typedef struct {
+	gint dn_constindex; /* constraint number to set the DN from when actually executing the LDAP search, or -1 */
+	gchar *ldap_filter; /* in LDAP format, with @xxx@ to bind values */
+	struct GdaVirtualConstraintUsage *out_const;
+} ComputedFilter;
+
+static void
+computed_filter_free (ComputedFilter *filter)
+{
+	g_free (filter->out_const);
+	g_free (filter->ldap_filter);
+	g_free (filter);
+}
+
+#define MARKER_ESCAPE_CHAR 1
+#define MARKER_GLOB_CHAR 2
+static void
+_ldap_table_create_filter (GdaVconnectionDataModelSpec *spec, GdaVconnectionDataModelFilter *info)
+{
+	/*
+	 * REM:
+	 *   - LDAP does not allow filtering on the DN => filter only is some cases
+	 *   - LDAP does not handle ordering of results => the 'ORDER BY' constraint is ignored
+	 *   - the '>' and '<' operators are not allowed in search strings => using '>=' and '<=' and
+	 *     have SQLite do the actual check
+	 */
+	LdapTableMap *map = (LdapTableMap *) spec;
+	GString *filter_string = NULL;
+	gint i, ncols;
+	gint dn_constindex = -1;
+	gchar *hash;
+	
+	info->orderByConsumed = FALSE;
+	hash = make_string_for_filter (info);
+	if (map->filters_hash) {
+		ComputedFilter *filter;
+		filter = g_hash_table_lookup (map->filters_hash, hash);
+		if (filter) {
+			info->idxPointer = (gpointer) filter;
+			info->orderByConsumed = FALSE;
+			memcpy (info->aConstraintUsage,
+				filter->out_const,
+				sizeof (struct GdaVirtualConstraintUsage) * info->nConstraint);
+			/*g_print ("Reusing filter %p, hash=[%s]\n", filter, hash);*/
+			g_free (hash);
+			return;
+		}
+	}
+
+	if (!map->columns)
+		map->columns = gda_data_model_ldap_compute_columns (map->ldap_cnc, map->attributes);
+
+	ncols = g_list_length (map->columns);
+	for (i = 0; i < info->nConstraint; i++) {
+		const struct GdaVirtualConstraint *cons;
+		cons = &(info->aConstraint [i]);
+		const gchar *attrname;
+
+		info->aConstraintUsage[i].argvIndex = i+1;
+		info->aConstraintUsage[i].omit = TRUE;
+
+		if (cons->iColumn < 0) {
+			g_warning ("Internal error: negative column number!");
+			goto nofilter;
+		}
+		if (cons->iColumn >= ncols) {
+			g_warning ("Internal error: SQLite's virtual table column %d is not known for "
+				   "table '%s', which has %d column(s)", cons->iColumn, map->table_name,
+				   ncols);
+			goto nofilter;
+		}
+		if (cons->iColumn == 0) {
+			/* try to optimize on the DN column */
+			if ((map->scope == GDA_LDAP_SEARCH_BASE) ||
+			    (map->scope == GDA_LDAP_SEARCH_ONELEVEL))
+				goto nofilter;
+			if (cons->op != GDA_SQL_OPERATOR_TYPE_EQ)
+				goto nofilter;
+			if (dn_constindex != -1) /* DN is already filtered by a constraint */
+				goto nofilter;
+			dn_constindex = i;
+			continue;
+		}
+
+		attrname = gda_column_get_name (GDA_COLUMN (g_list_nth_data (map->columns, cons->iColumn)));
+		if (! filter_string) {
+			if ((info->nConstraint > 1) || map->filter)
+				filter_string = g_string_new ("(& ");
+			else
+				filter_string = g_string_new ("");
+			if (map->filter)
+				g_string_append (filter_string, map->filter);
+		}
+		switch (cons->op) {
+		case GDA_SQL_OPERATOR_TYPE_EQ:
+			g_string_append_printf (filter_string, "(%s=%c)", attrname, MARKER_ESCAPE_CHAR);
+			break;
+		case GDA_SQL_OPERATOR_TYPE_GT:
+			g_string_append_printf (filter_string, "(%s>=%c)", attrname, MARKER_ESCAPE_CHAR);
+			info->aConstraintUsage[i].omit = FALSE;
+			break;
+		case GDA_SQL_OPERATOR_TYPE_LEQ:
+			g_string_append_printf (filter_string, "(%s<=%c)", attrname, MARKER_ESCAPE_CHAR);
+			info->aConstraintUsage[i].omit = FALSE;
+			break;
+		case GDA_SQL_OPERATOR_TYPE_LT:
+			g_string_append_printf (filter_string, "(%s<=%c)", attrname, MARKER_ESCAPE_CHAR);
+			break;
+		case GDA_SQL_OPERATOR_TYPE_GEQ:
+			g_string_append_printf (filter_string, "(%s>=%c)", attrname, MARKER_ESCAPE_CHAR);
+			break;
+		case GDA_SQL_OPERATOR_TYPE_REGEXP:
+			g_string_append_printf (filter_string, "(%s=%c)", attrname, MARKER_GLOB_CHAR);
+			break;
+		default:
+			/* Can't be done with LDAP */
+			goto nofilter;
+		}
+	}
+
+	if (!filter_string && (dn_constindex == -1))
+		goto nofilter;
+
+	if (filter_string && ((info->nConstraint > 1) || map->filter))
+		g_string_append_c (filter_string, ')');
+	/*g_print ("FILTER: [%s]\n", filter_string->str);*/
+	
+	ComputedFilter *filter;
+	filter = g_new0 (ComputedFilter, 1);
+	filter->dn_constindex = dn_constindex;
+	if (filter_string)
+		filter->ldap_filter = g_string_free (filter_string, FALSE);
+	else if (map->filter)
+		filter->ldap_filter = g_strdup (map->filter);
+	filter->out_const = g_new (struct GdaVirtualConstraintUsage,  info->nConstraint);
+	memcpy (filter->out_const,
+		info->aConstraintUsage,
+		sizeof (struct GdaVirtualConstraintUsage) * info->nConstraint);
+	
+	info->idxPointer = (gpointer) filter;
+	if (! map->filters_hash)
+		map->filters_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+							   g_free,
+							   (GDestroyNotify) computed_filter_free);
+	g_hash_table_insert (map->filters_hash, hash, filter);
+	/*g_print ("There are now %d statements in store...\n", g_hash_table_size (map->filters_hash));*/
+	return;
+
+ nofilter:
+	if (filter_string)
+		g_string_free (filter_string, TRUE);
+	for (i = 0; i < info->nConstraint; i++) {
+		info->aConstraintUsage[i].argvIndex = 0;
+		info->aConstraintUsage[i].omit = TRUE;
+	}
+	info->idxPointer = NULL;
+}
+
+static GdaDataModel *
+_ldap_table_create_model_func (GdaVconnectionDataModelSpec *spec, G_GNUC_UNUSED int idxNum, const char *idxStr,
+			       int argc, GValue **argv)
+{
+	LdapTableMap *map = (LdapTableMap *) spec;
+	GdaDataModel *model;
+
+	if (idxStr) {
+		const gchar *ptr;
+		gint pos;
+		GString *real_filter = NULL;
+		ComputedFilter *filter = (ComputedFilter *) idxStr;
+		
+		for (pos = 0, ptr = filter->ldap_filter ? filter->ldap_filter : ""; *ptr; ptr++) {
+			if (pos == filter->dn_constindex)
+				pos++; /* skip this constraint position */
+			if (! real_filter)
+				real_filter = g_string_new ("");
+			if ((*ptr == MARKER_ESCAPE_CHAR) || (*ptr == MARKER_GLOB_CHAR)){
+				gchar *str, *sptr;
+				gchar marker;
+				marker = *ptr;
+				g_assert (pos < argc);
+				str = gda_value_stringify (argv[pos]);
+				for (sptr = str; *sptr; sptr++) {
+					if ((*sptr == ')') || (*sptr == '(') || (*sptr == '\\') || (*sptr == '*'))
+						break;
+					if ((marker == MARKER_GLOB_CHAR) && (*sptr == '%'))
+						break;
+				}
+				if (*sptr) {
+					/* make the substitutions */
+					GString *string;
+					string = g_string_new ("");
+					for (sptr = str; *sptr; sptr++) {
+						if (*sptr == ')')
+							g_string_append (string, "\\29");
+						else if (*sptr == '(')
+							g_string_append (string, "\\28");
+						else if (*sptr == '\\')
+							g_string_append (string, "\\5c");
+						else if (*sptr == '*')
+							g_string_append (string, "\\2a");
+						else if ((marker == MARKER_GLOB_CHAR) && (*sptr == '%'))
+							g_string_append_c (string, '*');
+						else
+							g_string_append_c (string, *sptr);
+					}
+					g_free (str);
+					str = g_string_free (string, FALSE);
+				}
+				g_string_append (real_filter, str);
+				g_free (str);
+				pos++;
+			}
+			else
+				g_string_append_c (real_filter, *ptr);
+		}
+
+		gchar *real_dn = NULL;
+		GdaLdapSearchScope real_scope = map->scope;
+		if (filter->dn_constindex != -1) {
+			/* check that the DN is a child of the data model's base DN */
+			const gchar *bdn;
+			gchar *tmp;
+			bdn = map->base_dn;
+			if (!bdn)
+				bdn = gda_ldap_connection_get_base_dn (GDA_LDAP_CONNECTION (map->ldap_cnc));
+			g_assert (bdn);
+			tmp = gda_value_stringify (argv[filter->dn_constindex]);
+			if (g_str_has_suffix (tmp, bdn)) {
+				real_scope = GDA_LDAP_SEARCH_BASE;
+				real_dn = gda_value_stringify (argv[filter->dn_constindex]);
+			}
+			else {
+				/* return empty set */
+				if (real_filter)
+					g_string_free (real_filter, TRUE);
+				real_filter = g_string_new ("(objectClass=)");
+			}
+			g_free (tmp);
+		}
+			
+		/*g_print ("FILTER to use: LDAPFilter=> [%s] LDAPDn => [%s] SCOPE => [%d]\n",
+			 real_filter ? real_filter->str : NULL,
+			 real_dn ? real_dn : map->base_dn, real_scope);*/
+		model = gda_data_model_ldap_new (map->ldap_cnc,
+						 real_dn ? real_dn : map->base_dn,
+						 real_filter ? real_filter->str : NULL,
+						 map->attributes, real_scope);
+		if (real_filter)
+			g_string_free (real_filter, TRUE);
+		g_free (real_dn);
+	}
+	else
+		model = gda_data_model_ldap_new (map->ldap_cnc, map->base_dn, map->filter,
+						 map->attributes, map->scope);
+
+	return model;
+}
+
+/**
+ * gda_ldap_connection_declare_table:
+ * @cnc: a #GdaLdapConnection
+ * @table_name: a table name, not %NULL
+ * @base_dn: (allow-none): the base DN of the LDAP search, or %NULL for @cnc's own base DN
+ * @filter: (allow-none): the search filter of the LDAP search, or %NULL for a default filter of "(ObjectClass=*)"
+ * @attributes: (allow-none): the search attributes of the LDAP search, or %NULL if only the DN is required
+ * @scope: the search scope of the LDAP search
+ * @error: a place to store errors, or %NULL
+ *
+ * Declare a virtual table based on an LDAP search.
+ *
+ * The @filter argument, if not %NULL, must be a valid LDAP filter string (including the opening and
+ * closing parenthesis).
+ *
+ * The @attribute, if not %NULL, is a list of comma separated LDAP entry attribute names. For each attribute
+ * it is also possible to specify a requested #GType, and how to behave in case of multi valued attributes
+ * So the general format for an attribute is:
+ * "&lt;attribute name&gt;[::&lt;type&gt;][::&lt;muti value handler&gt;]", where:
+ * <itemizedlist>
+ * <listitem><para>"::&lt;type&gt;" is optional, see gda_g_type_from_string() for more
+ *     information about valie values for &lt;type&gt;</para></listitem>
+ * <listitem><para>"::&lt;muti value handler&gt;" is also optional and specifies how a multi
+ *    values attributed is treated. The possibilities for &lt;muti value handler&gt; are:
+ *    <itemizedlist>
+ *         <listitem><para>"NULL" or "0": a NULL value will be returned</para></listitem>
+ *         <listitem><para>"CSV": a comma separated value with all the values of the attribute will be
+ *           returned. This only works for G_TYPE_STRING attribute types.</para></listitem>
+ *         <listitem><para>"MULT" of "*": a row will be returned for each value of the attribute, effectively
+ *           multiplying the number of returned rows</para></listitem>
+ *         <listitem><para>"1": only the first vakue of the attribute will be used, the other values ignored</para></listitem>
+ *         <listitem><para>"CONCAT": the attributes' values are concatenated (with a newline char between each value)</para></listitem>
+ *         <listitem><para>"ERROR": an error value will be returned (this is the default behaviour)</para></listitem>
+ *    </itemizedlist>
+ * </para></listitem>
+ * </itemizedlist>
+ *
+ *  After each attribute
+ * name (and before the comma for the next attribute if any), it is possible to specify the #GType type using
+ * the "::&lt;type&gt;" syntax (see gda_g_type_from_string() for more information). 
+ *
+ * The following example specifies the "uidNumber" attribute to be returned as a string, the "mail" attribute,
+ * and the "objectClass" attribute to be handled as one row per value of that attribute:
+ * <programlisting>"uidNumber::string,mail,objectClass::*"</programlisting>
+ *
+ * Returns: %TRUE if no error occurred
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gda_ldap_connection_declare_table (GdaLdapConnection *cnc, const gchar *table_name,
+				   const gchar *base_dn, const gchar *filter,
+				   const gchar *attributes, GdaLdapSearchScope scope,
+				   GError **error)
+{
+	LdapTableMap *map;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (table_name && *table_name, FALSE);
+	
+	map = g_new0 (LdapTableMap, 1);
+	GDA_VCONNECTION_DATA_MODEL_SPEC (map)->data_model = NULL;
+        GDA_VCONNECTION_DATA_MODEL_SPEC (map)->create_columns_func = (GdaVconnectionDataModelCreateColumnsFunc) _ldap_create_columns_func;
+        GDA_VCONNECTION_DATA_MODEL_SPEC (map)->create_model_func = NULL;
+        GDA_VCONNECTION_DATA_MODEL_SPEC (map)->create_filter_func = _ldap_table_create_filter;
+        GDA_VCONNECTION_DATA_MODEL_SPEC (map)->create_filtered_model_func = _ldap_table_create_model_func;
+	map->ldap_cnc = g_object_ref (cnc);
+	map->table_name = gda_sql_identifier_quote (table_name, GDA_CONNECTION (cnc), NULL, TRUE, FALSE);
+	map->filters_hash = NULL;
+	if (base_dn)
+		map->base_dn = g_strdup (base_dn);
+	if (filter)
+		map->filter = g_strdup (filter);
+	if (attributes)
+		map->attributes = g_strdup (attributes);
+	map->scope = scope ? scope : GDA_LDAP_SEARCH_BASE;
+
+	cnc->priv->maps = g_slist_append (cnc->priv->maps, map);	
+	if (!gda_vconnection_data_model_add (GDA_VCONNECTION_DATA_MODEL (cnc),
+					     (GdaVconnectionDataModelSpec*) map,
+                                             (GDestroyNotify) _ldap_table_map_free, table_name, error)) {
+		cnc->priv->maps = g_slist_remove (cnc->priv->maps, map);
+                return FALSE;
+	}
+
+	return TRUE;
+}
+
+/**
+ * gda_ldap_connection_undeclare_table:
+ * @cnc: a #GdaLdapConnection
+ * @table_name: a table name, not %NULL
+ * @error: a place to store errors, or %NULL
+ *
+ * Remove a table which has been declared using gda_ldap_connection_declare_table().
+ *
+ * Returns: %TRUE if no error occurred
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gda_ldap_connection_undeclare_table (GdaLdapConnection *cnc, const gchar *table_name, GError **error)
+{
+	GdaVconnectionDataModelSpec *specs;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (table_name && *table_name, FALSE);
+
+	specs =  gda_vconnection_data_model_get (GDA_VCONNECTION_DATA_MODEL (cnc), table_name);
+	if (specs && ! g_slist_find (cnc->priv->maps, specs)) {
+		g_set_error (error, 0, 0,
+			     _("Can't remove non LDAP virtual table"));
+		return FALSE;
+	}
+	return gda_vconnection_data_model_remove (GDA_VCONNECTION_DATA_MODEL (cnc), table_name, error);
+}
+
+/**
+ * gda_ldap_connection_describe_table:
+ * @cnc: a #GdaLdapConnection
+ * @table_name: a table name, not %NULL
+ * @out_base_dn: (allow-none) (transfer none): a place to store the LDAP search base DN, or %NULL
+ * @out_filter: (allow-none) (transfer none): a place to store the LDAP search filter, or %NULL
+ * @out_attributes: (allow-none) (transfer none): a place to store the LDAP search attributes, or %NULL
+ * @out_scope: (allow-none) (transfer none): a place to store the LDAP search scope, or %NULL
+ * @error: a place to store errors, or %NULL
+ *
+ * Get information about a virtual table, the information which has been passed to
+ * gda_ldap_connection_declare_table() when the table was created.
+ *
+ * Returns: %TRUE if no error occurred
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gda_ldap_connection_describe_table (GdaLdapConnection *cnc, const gchar *table_name,
+				    const gchar **out_base_dn, const gchar **out_filter,
+				    const gchar **out_attributes,
+				    GdaLdapSearchScope *out_scope, GError **error)
+{
+	GdaVconnectionDataModelSpec *specs;
+	LdapTableMap *map;
+
+	if (out_base_dn)
+		*out_base_dn = NULL;
+	if (out_filter)
+		*out_filter = NULL;
+	if (out_attributes)
+		*out_attributes = NULL;
+	if (out_scope)
+		*out_scope = GDA_LDAP_SEARCH_BASE;
+
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), FALSE);
+	g_return_val_if_fail (table_name && *table_name, FALSE);
+
+	specs =  gda_vconnection_data_model_get (GDA_VCONNECTION_DATA_MODEL (cnc), table_name);
+	if (specs && ! g_slist_find (cnc->priv->maps, specs)) {
+		g_set_error (error, 0, 0,
+			     _("Can't describe non LDAP virtual table"));
+		return FALSE;
+	}
+	
+	if (!specs) {
+		g_set_error (error, 0, 0,
+			     _("Unknown LDAP virtual table"));
+		return FALSE;
+	}
+
+	map = (LdapTableMap*) specs;
+	if (out_base_dn)
+		*out_base_dn = map->base_dn;
+	if (out_filter)
+		*out_filter = map->filter;
+	if (out_attributes)
+		*out_attributes = map->attributes;
+	if (out_scope)
+		*out_scope = map->scope;
+	return TRUE;
+}
+
+static void
+gda_ldap_attribute_free (GdaLdapAttribute *attr)
+{
+	if (attr) {
+		gint i;
+		g_free (attr->attr_name);
+		for (i = 0; attr->values[i]; i++)
+			gda_value_free (attr->values[i]);
+		g_free (attr->values);
+	}
+}
+
+/**
+ * gda_ldap_entry_free:
+ * @entry: (transfer full): a #GdaLdapEntry pointer
+ *
+ * Frees @entry
+ *
+ * Since: 4.2.8
+ */
+void
+gda_ldap_entry_free (GdaLdapEntry *entry)
+{
+	if (entry) {
+		g_free (entry->dn);
+		if (entry->attributes) {
+			gint i;
+			for (i = 0; entry->attributes[i]; i++)
+				gda_ldap_attribute_free (entry->attributes[i]);
+			g_free (entry->attributes);
+		}
+		if (entry->attributes_hash)
+			g_hash_table_destroy (entry->attributes_hash);
+		g_free (entry);
+	}
+}
+
+/* proxy declaration */
+GdaLdapEntry *_gda_ldap_describe_entry (GdaLdapConnection *cnc, const gchar *dn, GError **error);
+GdaLdapEntry **_gda_ldap_get_entry_children (GdaLdapConnection *cnc, const gchar *dn, gchar **attributes, GError **error);
+
+/**
+ * gda_ldap_describe_entry:
+ * @cnc: a #GdaLdapConnection connection
+ * @dn: (allow-none): the Distinguished Name of the LDAP entry to describe
+ * @error: (allow-none): a place to store errors, or %NULL
+ *
+ * Describes the LDAP entry which DN is @dn. If @dn is %NULL, then the top entry (as specified when 
+ * the LDAP connection was opened) is described.
+ *
+ * Returns: (transfer full) (Free-function: gda_ldap_entry_free): a new #GdaLdapEntry, or %NULL if an error occurred or if the @dn entry does not exist
+ *
+ * Since: 4.2.8
+ */
+GdaLdapEntry *
+gda_ldap_describe_entry (GdaLdapConnection *cnc, const gchar *dn, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), FALSE);
+
+	return _gda_ldap_describe_entry (cnc, dn, error);
+}
+
+/**
+ * gda_ldap_get_entry_children:
+ * @cnc: a #GdaLdapConnection connection
+ * @dn: (allow-none): the Distinguished Name of the LDAP entry to get children from
+ * @attributes: (allow-none) (array zero-terminated=1) (element-type gchar*): a %NULL terminated array of attributes to fetch for each child, or %NULL if no attribute is requested
+ * @error: (allow-none): a place to store errors, or %NULL
+ *
+ * Get the list of children entries for the LDAP entry which DN is @dn. If the @dn entry does not have any
+ * child, then this function returns an array which first element is %NULL.
+ *
+ * If @dn is %NULL, then the top entry (as specified when the LDAP connection was opened) is used.
+ *
+ * Returns: (transfer full) (element_type GdaLdapEntry) (array zero-terminated=1): a %NULL terminated array of #GdaLdapEntry for each child entry, or %NULL if an error occurred or if the @dn entry does not exist
+ *
+ * Since: 4.2.8
+ */
+GdaLdapEntry **
+gda_ldap_get_entry_children (GdaLdapConnection *cnc, const gchar *dn, gchar **attributes, GError **error)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), FALSE);
+
+	return _gda_ldap_get_entry_children (cnc, dn, attributes, error);
+}
+
+gchar **_gda_ldap_dn_split (const gchar *dn, gboolean all);
+
+/**
+ * gda_ldap_dn_split
+ * @dn: a Distinguished Name string
+ * @all: set to %FALSE to split @dn into its RND and its parent DN, or %TRUE to completely split @dn
+ *
+ * Splits @dn into its components.
+ *
+ * Returns: (transfer full) (Free-function: g_strfreev): a %NULL terminated array containing the DN parts (free using g_strfreev()), or %NULL if an error occurred because @dn is not a valid DN expression
+ *
+ * Since: 4.2.8
+ */
+gchar **
+gda_ldap_dn_split (const gchar *dn, gboolean all)
+{
+	return _gda_ldap_dn_split (dn, all);
+}
+
+gboolean _gda_ldap_is_dn (const gchar *dn);
+
+/**
+ * gda_ldap_is_dn:
+ * @dn: a Distinguished Name string
+ *
+ * Tells if @dn represents a distinguished name (it only checks for the syntax, not for
+ * the actual existence of the entry with that distinguished name).
+ *
+ * Returns: %TRUE if @dn is a valid representation of a distinguished name
+ *
+ * Since: 4.2.8
+ */
+gboolean
+gda_ldap_is_dn (const gchar *dn)
+{
+	return _gda_ldap_is_dn (dn);
+}
+
+const gchar *_gda_ldap_get_base_dn (GdaLdapConnection *cnc);
+
+/**
+ * gda_ldap_connection_get_base_dn:
+ * @cnc: a #GdaLdapConnection
+ *
+ * Get the base DN which was used when the LDAP connection was opened
+ *
+ * Returns: the base DN, or %NULL
+ *
+ * Since: 4.2.8
+ */
+const gchar *
+gda_ldap_connection_get_base_dn (GdaLdapConnection *cnc)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+
+	return _gda_ldap_get_base_dn (cnc);
+}
+
+GdaLdapClass *_gda_ldap_get_class_info (GdaLdapConnection *cnc, const gchar *classname);
+/**
+ * gda_ldap_get_class_info:
+ * @cnc: a #GdaLdapConnection
+ * @classname: an LDAP class name
+ *
+ * Get information about an LDAP class
+ *
+ * Returns: (transfer none): a #GdaLdapClass
+ *
+ * Since: 4.2.8
+ */
+GdaLdapClass*
+gda_ldap_get_class_info (GdaLdapConnection *cnc, const gchar *classname)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+
+	return _gda_ldap_get_class_info (cnc, classname);
+}
+
+const GSList *_gda_ldap_get_top_classes (GdaLdapConnection *cnc);
+/**
+ * gda_ldap_get_top_classes:
+ * @cnc: a #GdaLdapConnection
+ *
+ * get a list of the top level LDAP classes (ie. classes which don't have any parent)
+ *
+ * Returns: (transfer none) (element-type GdaLdapClass): a list of #GdaLdapClass pointers (don't modify it)
+ *
+ * Since: 4.2.8
+ */
+const GSList *
+gda_ldap_get_top_classes (GdaLdapConnection *cnc)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+
+	return _gda_ldap_get_top_classes (cnc);
+}
diff --git a/libgda/sqlite/virtual/gda-ldap-connection.h b/libgda/sqlite/virtual/gda-ldap-connection.h
new file mode 100644
index 0000000..d792486
--- /dev/null
+++ b/libgda/sqlite/virtual/gda-ldap-connection.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GDA_LDAP_CONNECTION_H__
+#define __GDA_LDAP_CONNECTION_H__
+
+#include <virtual/gda-vconnection-data-model.h>
+#include <libgda/gda-data-model-ldap.h>
+
+#define GDA_TYPE_LDAP_CONNECTION            (gda_ldap_connection_get_type())
+#define GDA_LDAP_CONNECTION(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_LDAP_CONNECTION, GdaLdapConnection))
+#define GDA_LDAP_CONNECTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_LDAP_CONNECTION, GdaLdapConnectionClass))
+#define GDA_IS_LDAP_CONNECTION(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, GDA_TYPE_LDAP_CONNECTION))
+#define GDA_IS_LDAP_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDA_TYPE_LDAP_CONNECTION))
+
+G_BEGIN_DECLS
+
+typedef struct _GdaLdapConnection      GdaLdapConnection;
+typedef struct _GdaLdapConnectionClass GdaLdapConnectionClass;
+typedef struct _GdaLdapConnectionPrivate GdaLdapConnectionPrivate;
+
+struct _GdaLdapConnection {
+	GdaVconnectionDataModel      parent;
+	GdaLdapConnectionPrivate    *priv;
+};
+
+struct _GdaLdapConnectionClass {
+	GdaVconnectionDataModelClass parent_class;
+
+	/*< private >*/
+	/* Padding for future expansion */
+	void (*_gda_reserved1) (void);
+	void (*_gda_reserved2) (void);
+	void (*_gda_reserved3) (void);
+	void (*_gda_reserved4) (void);
+};
+
+/**
+ * SECTION:gda-ldap-connection
+ * @short_description: LDAP connection objects
+ * @title: GdaLdapConnection
+ * @stability: Stable
+ * @see_also: 
+ *
+ * This is a connection, as opened by the LDAP provider. Use
+ * gda_connection_open_from_string() or gda_connection_open_from_dsn() to create
+ * a #GdaLdapConnection connection.
+ *
+ * Warning: if you create a #GdaLdapConnection using g_object_new(), then the resulting
+ * object won't be functionnal.
+ *
+ * A #GdaLdapConnection is a virtual connection which accepts any of SQLite's SQL dialect.
+ * However, some SQL commands have been added to manipulate virtual tables mapped to
+ * LDAP searches. These commands are:
+ * <itemizedlist>
+ *   <listitem>
+ *      <para>The CREATE LDAP TABLE: <synopsis>CREATE LDAP TABLE &lt;table name&gt; [BASE=&lt;base DN&gt;] [FILTER=&lt;LDAP filter&gt;] [ATTRIBUTES=&lt;LDAP attributes&gt;] [SCOPE=&lt;search scope&gt;]</synopsis>
+ *      </para>
+ *      <para>Each of the BASE, FILTER, ATTRIBUTES and SCOPE specifications is optional. Use this command to declare a table, for example: <programlisting>CREATE LDAP TABLE users FILTER='(cn=*doe*)' SCOPE= 'SUBTREE';</programlisting>.
+ *        The allowed SCOPE values are: 'BASE', 'ONELEVEL' and 'SUBTREE'.
+ *      </para>
+ *      <para>See the <link linkend="gda-ldap-connection-declare-table">gda_ldap_connection_declare_table()</link>
+ *        for more information about the ATTRIBUTES syntax.
+ *      </para>
+ *   </listitem>
+ *   <listitem>
+ *      <para>The DROP LDAP TABLE: <synopsis>DROP LDAP TABLE &lt;table name&gt;</synopsis>
+ *      </para>
+ *      <para>Use this command to undeclare a table, for example: <programlisting>DROP LDAP TABLE users;</programlisting> Note that it is also possible to use the normal command to remove a table: <programlisting>DROP TABLE users;</programlisting>
+ *      </para>
+ *   </listitem>
+ *   <listitem>
+ *      <para>The ALTER LDAP TABLE: <synopsis>ALTER LDAP TABLE &lt;table name&gt;</synopsis> or
+ *            <synopsis>ALTER LDAP TABLE &lt;table name&gt; [BASE=&lt;base DN&gt;] [FILTER=&lt;LDAP filter&gt;] [ATTRIBUTES=&lt;LDAP attributes&gt;] [SCOPE=&lt;search scope&gt;]</synopsis>
+ *      </para>
+ *      <para>Use this command to modify the definition of a virtual table, for example: <programlisting>ALTER LDAP TABLE users FILTER='(cn=*doe*)' SCOPE='BASE';</programlisting> If no argument is specified after the table name, then
+ *        the definition of the virtual table is returned instead. When altering the virtual table, only the
+ *        specified parameters are altered (ie. you don't need to repeat the parameters you don't want to
+ *        be modified)
+ *      </para>
+ *   </listitem>
+ *   <listitem>
+ *      <para>The DESCRIBE LDAP TABLE: <synopsis>DESCRIBE LDAP TABLE &lt;table name&gt;</synopsis> 
+ *      </para>
+ *      <para>Use this command to get the definition of the virtual table.
+ *      </para>
+ *   </listitem>
+ * </itemizedlist>
+ *
+ * Each "LDAP" table can then be used like any other table, but you should keep in mind that &LIBGDA;
+ * can optimize the LDAP search command executed when the table's data is actually read if you use
+ * a "WHERE" clause which involves a search criteria on an LDAP attribute. For example the following
+ * SQL: <programlisting>SELECT * FROM users WHERE cn MATCH '%doe%';</programlisting> will actually
+ * be optimized by requesting an LDAP search with the filter <programlisting>(cn=*doe*)</programlisting>
+ * Optimizations can be done on MATCH, =, &lt;, &lt;=, &gt; and &gt;= operators, the LIKE operator
+ * will not be optimized.
+ *
+ * However a command like <programlisting>SELECT * FROM users WHERE cn MATCH '%doe%' OR uid=123;</programlisting>
+ * can't be optimized (because of the "OR") whereas the
+ * <programlisting>SELECT * FROM users WHERE cn MATCH '%doe%' AND uid=123;</programlisting>
+ * will be optimized because of the "AND".
+ */
+
+GType          gda_ldap_connection_get_type        (void) G_GNUC_CONST;
+
+const gchar   *gda_ldap_connection_get_base_dn     (GdaLdapConnection *cnc);
+gboolean       gda_ldap_connection_declare_table   (GdaLdapConnection *cnc, const gchar *table_name,
+						    const gchar *base_dn, const gchar *filter,
+						    const gchar *attributes, GdaLdapSearchScope scope,
+						    GError **error);
+gboolean       gda_ldap_connection_undeclare_table (GdaLdapConnection *cnc, const gchar *table_name, GError **error);
+
+gboolean       gda_ldap_connection_describe_table  (GdaLdapConnection *cnc, const gchar *table_name,
+						    const gchar **out_base_dn, const gchar **out_filter,
+						    const gchar **out_attributes,
+						    GdaLdapSearchScope *out_scope, GError **error);
+
+
+/**
+ * GdaLdapAttribute:
+ * @attr_name: the name of the attribute
+ * @nb_values: the number of values in @values, or %0
+ * @values: (allow-none) (array length=nb_values): the attribute' values as #GValue values, (terminated by a %NULL)
+ *
+ * This structure holds information about the values of a single attribute (of a single LDAP entry).
+ */
+typedef struct {
+	gchar   *attr_name;
+	guint    nb_values;
+	GValue **values;
+} GdaLdapAttribute;
+
+/**
+ * GdaLdapEntry:
+ * @dn: the Distinguished Name of the entry
+ * @nb_attributes: the number of attributes in @attributes, or %0
+ * @attributes: (allow-none) (array length=nb_attributes): the entry's attributes, (terminated by a %NULL)
+ * @attributes_hash: a hash table where the key is an attribute name, and the value the correcponding #GdaLdapAttribute
+ *
+ * This structure holds information about the attributes of a single LDAP entry.
+ */
+typedef struct {
+	gchar             *dn;
+	guint              nb_attributes;
+	GdaLdapAttribute **attributes;
+	GHashTable        *attributes_hash;
+} GdaLdapEntry;
+
+void           gda_ldap_entry_free                 (GdaLdapEntry *entry);
+GdaLdapEntry  *gda_ldap_describe_entry             (GdaLdapConnection *cnc, const gchar *dn, GError **error);
+GdaLdapEntry **gda_ldap_get_entry_children         (GdaLdapConnection *cnc, const gchar *dn,
+						    gchar **attributes, GError **error);
+
+gchar        **gda_ldap_dn_split                   (const gchar *dn, gboolean all);
+gboolean       gda_ldap_is_dn                      (const gchar *dn);
+
+
+/**
+ * GdaLdapClassKind:
+ * @GDA_LDAP_CLASS_KIND_ABSTRACT: the LDAP class is an abstract class
+ * @GDA_LDAP_CLASS_KIND_STRUTURAL: the LDAP class is a structural class
+ * @GDA_LDAP_CLASS_KIND_AUXILIARY: the LDAP class is auxilliary
+ * @GDA_LDAP_CLASS_KIND_UNKNOWN: the LDAP class type is not known
+ *
+ * Defines the LDAP class type
+ */
+typedef enum {
+	GDA_LDAP_CLASS_KIND_ABSTRACT  = 1,
+	GDA_LDAP_CLASS_KIND_STRUTURAL = 2,
+	GDA_LDAP_CLASS_KIND_AUXILIARY = 3,
+	GDA_LDAP_CLASS_KIND_UNKNOWN   = 4
+} GdaLdapClassKind;
+
+/**
+ * GdaLdapClass:
+ * @oid: the OID of the class
+ * @nb_names: the number of values in @values (always &gt;= 1)
+ * @names: all the class names
+ * @description: (allow-none): the class's description, or %NULL
+ * @kind: the class kind
+ * @obsolete: defines is LDAP class is obsolete
+ * @nb_req_attributes: the number of values in @req_attributes
+ * @req_attributes: names of required attributes in class
+ * @nb_opt_attributes: the number of values in @opt_attributes
+ * @opt_attributes: names of optional attributes in class
+ * @parents: #GSList of the parent classes (pointers to #GdaLdapClass)
+ * @children: #GSList of the children classes (pointers to #GdaLdapClass)
+ *
+ * Represents an LDAP declared class.
+ */
+typedef struct {
+	gchar            *oid;
+	guint             nb_names; /* always >= 1 */
+	gchar           **names;
+	gchar            *description;
+	GdaLdapClassKind  kind;
+	gboolean          obsolete;
+
+	guint             nb_req_attributes;
+	gchar           **req_attributes;
+	guint             nb_opt_attributes;
+	gchar           **opt_attributes;
+
+	GSList           *parents; /* list of #LdapClass */
+	GSList           *children; /* list of #LdapClass */
+} GdaLdapClass;
+
+GdaLdapClass   *gda_ldap_get_class_info (GdaLdapConnection *cnc, const gchar *classname);
+const GSList   *gda_ldap_get_top_classes (GdaLdapConnection *cnc);
+
+G_END_DECLS
+
+#endif
diff --git a/libgda/sqlite/virtual/gda-vprovider-data-model.c b/libgda/sqlite/virtual/gda-vprovider-data-model.c
index 95ffb9a..e520fff 100644
--- a/libgda/sqlite/virtual/gda-vprovider-data-model.c
+++ b/libgda/sqlite/virtual/gda-vprovider-data-model.c
@@ -245,6 +245,48 @@ static sqlite3_module Module = {
 	virtualRename                 /* Rename - Notification that the table will be given a new name */
 };
 
+/*
+ * handle data model exceptions and return appropriate code
+ */
+static int
+handle_data_model_exception (sqlite3_vtab *pVtab, GdaDataModel *model)
+{
+	GError **exceptions;
+	gint i;
+	exceptions = gda_data_model_get_exceptions (model);
+	if (!exceptions)
+		return SQLITE_OK;
+
+	GError *trunc_error = NULL;
+	GError *fatal_error = NULL;
+	for (i = 0; exceptions [i]; i++) {
+		GError *e;
+		e = exceptions [i];
+		if ((e->domain == GDA_DATA_MODEL_ERROR) &&
+		    (e->code == GDA_DATA_MODEL_TRUNCATED_ERROR))
+			trunc_error = e;
+		else {
+			fatal_error = e;
+			break;
+		}
+	}
+	if (fatal_error || trunc_error) {
+		GError *e;
+		e = fatal_error;
+		if (!e)
+			e = trunc_error;
+		if (pVtab->zErrMsg)
+			SQLITE3_CALL (sqlite3_free) (pVtab->zErrMsg);
+		pVtab->zErrMsg = SQLITE3_CALL (sqlite3_mprintf)
+			(e->message ? e->message : _("No detail"));
+		if (fatal_error)
+			return SQLITE_ERROR;
+		else
+			return SQLITE_IOERR_TRUNCATE;
+	}
+	return SQLITE_OK;
+}
+
 static GdaConnection *
 gda_vprovider_data_model_create_connection (GdaServerProvider *provider)
 {
@@ -573,7 +615,7 @@ virtualDestroy (sqlite3_vtab *pVtab)
 }
 
 static int
-virtualOpen (sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor)
+virtualOpen (G_GNUC_UNUSED sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor)
 {
 	VirtualCursor *cursor;
 
@@ -629,10 +671,8 @@ virtualNext (sqlite3_vtab_cursor *cur)
 	if (!gda_data_model_iter_move_next (cursor->iter)) {
 		if (gda_data_model_iter_is_valid (cursor->iter))
 			return SQLITE_IOERR;
-		else
-			return SQLITE_OK;
 	}
-	return SQLITE_OK;
+	return handle_data_model_exception (cur->pVtab, cursor->model);
 }
 
 static int
@@ -890,7 +930,8 @@ virtualFilter (sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr,
 
 	cursor->model = g_object_ref (vtable->td->real_model);
 	gda_data_model_iter_move_next (cursor->iter);
-	return SQLITE_OK;
+
+	return handle_data_model_exception (pVtabCursor->pVtab, cursor->model);
 }
 
 #ifdef GDA_DEBUG_VIRTUAL
@@ -1040,7 +1081,6 @@ virtualBestIndex (sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo)
 #endif
 	}
 
-
 	return SQLITE_OK;
 }
 
@@ -1258,7 +1298,7 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 		sql = gda_statement_to_sql (stmt, NULL, NULL);
 		g_print ("SQL: [%s] ", sql);
 		g_free (sql);
-		sql = gda_statement_to_sql_extended (stmt, cnc, params, GDA_STATEMENT_SQL_PRETTY, NULL, &lerror);
+		sql = gda_statement_to_sql_extended (stmt, cnc, vtable->td->modif_params [ptype], GDA_STATEMENT_SQL_PRETTY, NULL, &lerror);
 		if (sql) {
 			g_print ("With params: [%s]\n", sql);
 			g_free (sql);
@@ -1366,7 +1406,7 @@ virtualUpdate (sqlite3_vtab *tab, int nData, sqlite3_value **apData, sqlite_int6
 }
 
 static int
-virtualBegin (sqlite3_vtab *tab)
+virtualBegin (G_GNUC_UNUSED sqlite3_vtab *tab)
 {
 	TRACE (tab, NULL);
 	/* no documentation currently available, don't do anything */
@@ -1405,7 +1445,7 @@ virtualRollback (G_GNUC_UNUSED sqlite3_vtab *tab)
 }
 
 static int
-virtualRename (sqlite3_vtab *pVtab, G_GNUC_UNUSED const char *zNew)
+virtualRename (G_GNUC_UNUSED sqlite3_vtab *pVtab, G_GNUC_UNUSED const char *zNew)
 {
 	TRACE (pVtab, NULL);
 	/* not yet analysed and implemented */
diff --git a/libgda/sqlite/virtual/libgda-virtual.h b/libgda/sqlite/virtual/libgda-virtual.h.in
similarity index 94%
copy from libgda/sqlite/virtual/libgda-virtual.h
copy to libgda/sqlite/virtual/libgda-virtual.h.in
index 2986ccf..1e25751 100644
--- a/libgda/sqlite/virtual/libgda-virtual.h
+++ b/libgda/sqlite/virtual/libgda-virtual.h.in
@@ -1,5 +1,5 @@
-/* GDA library
- * Copyright (C) 2007 The GNOME Foundation.
+/*
+ * Copyright (C) 2007 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *      Vivien Malerba <malerba gnome-db org>
@@ -31,4 +31,6 @@
 #include <virtual/gda-vconnection-data-model.h>
 #include <virtual/gda-vconnection-hub.h>
 
+ LIBGDA_LDAP_VINC@
+
 #endif
diff --git a/m4/ldap.m4 b/m4/ldap.m4
new file mode 100644
index 0000000..09da463
--- /dev/null
+++ b/m4/ldap.m4
@@ -0,0 +1,181 @@
+dnl -*- mode: autoconf -*-
+dnl Copyright 2010 Vivien Malerba
+dnl
+dnl SYNOPSIS
+dnl
+dnl   LDAP_CHECK([libdirname])
+dnl
+dnl   [libdirname]: defaults to "lib". Can be overridden by the --with-bdb-libdir-name option
+dnl
+dnl DESCRIPTION
+dnl
+dnl   This macro tries to find the LDAP libraries and header files
+dnl
+dnl   It defines two options:
+dnl   --with-ldap=yes/no/<directory>
+dnl   --with-ldap-libdir-name=<dir. name>
+dnl
+dnl   If the 1st option is "yes" then the macro in several well known directories
+dnl
+dnl   If the 1st option is "no" then the macro does not attempt at locating the
+dnl   LDAP package
+dnl
+dnl   If the 1st option is a directory name, then the macro tries to locate the LDAP package
+dnl   in the specified directory.
+dnl
+dnl   If the macro has to try to locate the LDAP package in one or more directories, it will
+dnl   try to locate the header files in $dir/include and the library files in $dir/lib, unless
+dnl   the second option is used to specify a directory name to be used instead of "lib" (for
+dnl   example lib64).
+dnl
+dnl USED VARIABLES
+dnl
+dnl   $linklibext: contains the library suffix (like ".so"). If not specified ".so" is used.
+dnl   $platform_win32: contains "yes" on Windows platforms. If not specified, assumes "no"
+dnl
+dnl
+dnl DEFINED VARIABLES
+dnl
+dnl   This macro always calls:
+dnl
+dnl    AC_SUBST(LDAP_LIBS)
+dnl    AC_SUBST(LDAP_LIB)
+dnl    AC_SUBST(LDAP_CFLAGS)
+dnl    AC_SUBST(LIBGDA_LDAP_INC)
+dnl    AC_SUBST(LIBGDA_LDAP_INC2)
+dnl    AC_SUBST(LIBGDA_LDAP_VINC)
+dnl    AC_SUBST(LIBGDA_LDAP_TYPE)
+dnl    AC_SUBST(LIBGDA_LDAP_TYPE2)
+dnl    AC_SUBST(LIBGDA_LDAP_TYPE3)
+dnl    ldap_found=yes/no
+dnl
+dnl   and if the ldap package is found:
+dnl
+dnl    AM_CONDITIONAL(LDAP, true)
+dnl
+dnl
+dnl LICENSE
+dnl
+dnl This file is free software; the author(s) gives unlimited
+dnl permission to copy and/or distribute it, with or without
+dnl modifications, as long as this notice is preserved.
+dnl
+
+m4_define([_LDAP_CHECK_INTERNAL],
+[
+    AC_BEFORE([AC_PROG_LIBTOOL],[$0])dnl setup libtool first
+    AC_BEFORE([AM_PROG_LIBTOOL],[$0])dnl setup libtool first
+    AC_BEFORE([LT_INIT],[$0])dnl setup libtool first
+
+    ldap_loclibdir=$1
+    if test "x$ldap_loclibdir" = x
+    then
+        if test "x$platform_win32" = xyes
+	then
+	    ldap_loclibdir=bin
+	else
+	    ldap_loclibdir=lib
+	fi
+    fi
+
+    # determine if Ldap should be searched for
+    # and use pkg-config if the "yes" option is used
+    ldap_found=no
+    try_ldap=true
+    LDAP_LIBS=""
+    ldap_test_dir="/usr /usr/local /local"
+    AC_ARG_WITH(ldap,
+              AS_HELP_STRING([--with-ldap[=@<:@yes/no/<directory>@:>@]],
+                             [Locate LDAP client library]),[
+			     if test $withval = no
+			     then
+			         try_ldap=false
+			     elif test $withval != yes
+			     then
+			         ldap_test_dir=$withval
+			     fi])
+    AC_ARG_WITH(ldap-libdir-name,
+              AS_HELP_STRING([--with-ldap-libdir-name[=@<:@<dir. name>@:>@]],
+                             [Locate LDAP library file, related to the prefix specified from --with-ldap]),
+			     [ldap_loclibdir=$withval])
+
+    # try to locate files
+    if test $try_ldap = true
+    then
+	if test "x$linklibext" = x
+	then
+	    ldap_libext=".so"
+	else
+	    ldap_libext="$linklibext"
+	fi
+	ldapdir=""
+	for d in $ldap_test_dir
+	do
+	    ldapdir=""
+	    AC_MSG_CHECKING([for LDAP files in $d])
+
+	    if test -f $d/include/ldap.h
+	    then
+  	        save_CFLAGS="$CFLAGS"
+	        CFLAGS="$CFLAGS -I$d/include"
+  	        save_LIBS="$LIBS"
+	        LIBS="$LIBS -L$d/$ldap_loclibdir -lldap -llber"
+   	        AC_LINK_IFELSE([[
+#include <ldap.h>
+#include <lber.h>
+#include <ldap_schema.h>
+int main() {
+    printf("%p,%p", ldap_initialize, ldap_str2attributetype);
+    printf("%p", ber_free);
+    return 0;
+}
+]],
+	                     ldapdir=$d)
+	        CFLAGS="$save_CFLAGS"
+  	        LIBS="$save_LIBS"
+	    fi
+
+            if test x$ldapdir != x
+	    then
+	        AC_MSG_RESULT([found])
+	        LDAP_CFLAGS=-I${ldapdir}/include
+	        LDAP_LIBS="-L${ldapdir}/$ldap_loclibdir -lldap -llber"
+		break
+  	    else
+	        AC_MSG_RESULT([not found])
+	    fi
+	done
+
+	if test "x$LDAP_LIBS" = x
+	then
+	    AC_MSG_NOTICE([LDAP backend not used])
+	else
+	    LIBGDA_LDAP_INC="#include <libgda/gda-data-model-ldap.h>"
+	    LIBGDA_LDAP_INC2="#include <libgda/gda-tree-mgr-ldap.h>"
+	    LIBGDA_LDAP_VINC="#include <virtual/gda-ldap-connection.h>"
+            LIBGDA_LDAP_TYPE="gda_data_model_ldap_get_type"
+            LIBGDA_LDAP_TYPE2="gda_ldap_connection_get_type"
+            LIBGDA_LDAP_TYPE3="gda_tree_mgr_ldap_get_type"
+    	    ldap_found=yes
+	fi
+    fi
+
+    AM_CONDITIONAL(LDAP,[test "$ldap_found" = "yes"])
+    AC_SUBST(LDAP_LIBS)
+    AC_SUBST(LDAP_CFLAGS)
+    AC_SUBST(LIBGDA_LDAP_INC)
+    AC_SUBST(LIBGDA_LDAP_INC2)
+    AC_SUBST(LIBGDA_LDAP_VINC)
+    AC_SUBST(LIBGDA_LDAP_TYPE)
+    AC_SUBST(LIBGDA_LDAP_TYPE2)
+    AC_SUBST(LIBGDA_LDAP_TYPE3)
+])
+
+
+dnl Usage:
+dnl   LDAP_CHECK([libdirname])
+
+AC_DEFUN([LDAP_CHECK],
+[
+    _LDAP_CHECK_INTERNAL([$1])
+])
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1ec1c64..ee08a76 100755
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,7 @@ libgda/gda-data-model-dir.c
 libgda/gda-data-model-dsn-list.c
 libgda/gda-data-model-import.c
 libgda/gda-data-model-iter.c
+libgda/gda-data-model-ldap.c
 libgda/gda-data-proxy.c
 libgda/gda-data-select.c
 libgda/gda-easy.c
@@ -37,6 +38,7 @@ libgda/gda-sql-builder.c
 libgda/gda-statement.c
 libgda/gda-tree-mgr-columns.c
 libgda/gda-tree-mgr-label.c
+libgda/gda-tree-mgr-ldap.c
 libgda/gda-tree-mgr-schemas.c
 libgda/gda-tree-mgr-select.c
 libgda/gda-tree-mgr-tables.c
@@ -59,6 +61,7 @@ libgda/sqlite/gda-sqlite-provider.c
 libgda/sqlite/gda-sqlite-pstmt.c
 libgda/sqlite/gda-sqlite-recordset.c
 libgda/sqlite/gda-sqlite-util.c
+libgda/sqlite/virtual/gda-ldap-connection.c
 libgda/sqlite/virtual/gda-vconnection-data-model.c
 libgda/sqlite/virtual/gda-vconnection-hub.c
 libgda/sqlite/virtual/gda-virtual-connection.c
@@ -150,6 +153,12 @@ providers/jdbc/gda-jdbc-util.c
 providers/jdbc/jdbc_specs_create_table.xml.in
 providers/jdbc/jdbc_specs_dsn.xml.in
 providers/jdbc/libmain.c
+providers/ldap/gda-ldap-provider.c
+providers/ldap/gda-ldap-util.c
+providers/ldap/gdaprov-data-model-ldap.c
+providers/ldap/ldap_specs_auth.xml.in
+providers/ldap/ldap_specs_dsn.xml.in
+providers/ldap/libmain.c
 providers/mdb/gda-mdb-provider.c
 providers/mdb/libmain.c
 providers/mdb/mdb_specs_dsn.xml.in
@@ -277,8 +286,20 @@ tools/browser/data-manager/data-widget.c
 tools/browser/data-manager/perspective-main.c
 tools/browser/data-manager/ui-spec-editor.c
 tools/browser/data-manager/xml-spec-editor.c
+tools/browser/ldap-browser/class-properties.c
+tools/browser/ldap-browser/entry-properties.c
+tools/browser/ldap-browser/filter-editor.c
+tools/browser/ldap-browser/hierarchy-view.c
+tools/browser/ldap-browser/ldap-browser-perspective.c
+tools/browser/ldap-browser/ldap-classes-page.c
+tools/browser/ldap-browser/ldap-entries-page.c
+tools/browser/ldap-browser/ldap-favorite-selector.c
+tools/browser/ldap-browser/ldap-search-page.c
+tools/browser/ldap-browser/mgr-ldap-classes.c
+tools/browser/ldap-browser/perspective-main.c
+tools/browser/ldap-browser/vtable-dialog.c
 tools/browser/query-exec/perspective-main.c
-tools/browser/query-exec/query-console.c
+tools/browser/query-exec/query-console-page.c
 tools/browser/query-exec/query-editor.c
 tools/browser/query-exec/query-exec-perspective.c
 tools/browser/query-exec/query-favorite-selector.c
diff --git a/providers/Makefile.am b/providers/Makefile.am
index 80aa813..b8fa6fe 100644
--- a/providers/Makefile.am
+++ b/providers/Makefile.am
@@ -38,6 +38,10 @@ if HAVE_LIBCRYPTO
 GDA_SQLCIPHER_SERVER=sqlcipher
 endif
 
+if LDAP
+GDA_LDAP_SERVER=ldap
+endif
+
 SUBDIRS = \
 	reuseable \
 	sqlite \
@@ -50,6 +54,7 @@ SUBDIRS = \
 	$(GDA_JAVA_SERVER) \
 	$(GDA_ORACLE_SERVER) \
 	$(GDA_WEB_SERVER) \
-	$(GDA_SQLCIPHER_SERVER)
+	$(GDA_SQLCIPHER_SERVER) \
+	$(GDA_LDAP_SERVER)
 #	$(GDA_FIREBIRD_SERVER) 
 
diff --git a/providers/ldap/Makefile.am b/providers/ldap/Makefile.am
new file mode 100644
index 0000000..66d19ca
--- /dev/null
+++ b/providers/ldap/Makefile.am
@@ -0,0 +1,40 @@
+providerdir=$(libdir)/libgda-$(GDA_ABI_MAJOR_VERSION).$(GDA_ABI_MINOR_VERSION)/providers
+provider_LTLIBRARIES = libgda-ldap.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/libgda/sqlite \
+	-I$(top_srcdir)/libgda \
+	-I$(top_builddir) \
+	$(LIBGDA_CFLAGS) \
+	$(LDAP_CFLAGS)
+
+libgda_ldap_la_SOURCES = \
+	gdaprov-data-model-ldap.c \
+	gdaprov-data-model-ldap.h \
+	gda-ldap-provider.c \
+	gda-ldap-provider.h \
+	gda-ldap-util.c \
+	gda-ldap-util.h \
+	gda-ldap.h \
+	libmain.c
+
+libgda_ldap_la_LDFLAGS = -export-dynamic -module -avoid-version $(NO_UNDEFINED) $(LIBTOOL_PROV_EXPORT_OPTIONS)
+libgda_ldap_la_LIBADD = \
+	$(top_builddir)/libgda/libgda-4.0.la \
+	$(LIBGDA_LIBS) \
+	$(LDAP_LIBS)
+
+xmldir   = $(datadir)/libgda-4.0
+xml_in_files = ldap_specs_dsn.xml.in ldap_specs_auth.xml.in
+
+ INTLTOOL_XML_RULE@
+
+xml_DATA = $(xml_in_files:.xml.in=.xml)
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libgda-ldap-4.0.pc
+
+EXTRA_DIST = $(xml_in_files) libgda-ldap-4.0.pc.in
+DISTCLEANFILES = $(xml_DATA)
+
diff --git a/providers/ldap/gda-ldap-provider.c b/providers/ldap/gda-ldap-provider.c
new file mode 100644
index 0000000..7689191
--- /dev/null
+++ b/providers/ldap/gda-ldap-provider.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <virtual/gda-ldap-connection.h>
+#include <libgda/gda-connection-private.h>
+#include <libgda/gda-data-model-iter.h>
+#include <libgda/gda-util.h>
+#include <libgda/gda-data-model-array.h>
+#include <libgda/sql-parser/gda-sql-parser.h>
+#include "gda-ldap.h"
+#include "gda-ldap-provider.h"
+#include "gdaprov-data-model-ldap.h"
+#include "gda-ldap-util.h"
+
+static void gda_ldap_provider_class_init (GdaLdapProviderClass *klass);
+static void gda_ldap_provider_init       (GdaLdapProvider *provider,
+					  GdaLdapProviderClass *klass);
+static void gda_ldap_provider_finalize   (GObject *object);
+
+static const gchar *gda_ldap_provider_get_name (GdaServerProvider *provider);
+static const gchar *gda_ldap_provider_get_version (GdaServerProvider *provider);
+static GdaConnection *gda_ldap_provider_create_connection (GdaServerProvider *provider);
+static gboolean gda_ldap_provider_open_connection (GdaServerProvider *provider, GdaConnection *cnc, 
+						   GdaQuarkList *params, GdaQuarkList *auth,
+						   guint *task_id, GdaServerProviderAsyncCallback async_cb, gpointer cb_data);
+static GObject *gda_ldap_provider_statement_execute (GdaServerProvider *provider, GdaConnection *cnc,
+						     GdaStatement *stmt, GdaSet *params,
+						     GdaStatementModelUsage model_usage,
+						     GType *col_types, GdaSet **last_inserted_row,
+						     guint *task_id, GdaServerProviderExecCallback async_cb,
+						     gpointer cb_data, GError **error);
+static const gchar *gda_ldap_provider_get_server_version (GdaServerProvider *provider,
+							  GdaConnection *cnc);
+static const gchar *gda_ldap_provider_get_database (GdaServerProvider *provider, GdaConnection *cnc);
+
+static GObjectClass *parent_class = NULL;
+
+/* 
+ * private connection data destroy 
+ */
+static void gda_ldap_free_cnc_data (LdapConnectionData *cdata);
+
+/*
+ * GdaLdapProvider class implementation
+ */
+static void
+gda_ldap_provider_class_init (GdaLdapProviderClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	GdaServerProviderClass *provider_class = GDA_SERVER_PROVIDER_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->finalize = gda_ldap_provider_finalize;
+	provider_class->create_connection = gda_ldap_provider_create_connection;
+
+	provider_class->get_name = gda_ldap_provider_get_name;
+	provider_class->get_version = gda_ldap_provider_get_version;
+	provider_class->open_connection = gda_ldap_provider_open_connection;
+	provider_class->get_server_version = gda_ldap_provider_get_server_version;
+	provider_class->get_database = gda_ldap_provider_get_database;
+	provider_class->statement_execute = gda_ldap_provider_statement_execute;
+}
+
+static void
+gda_ldap_provider_init (G_GNUC_UNUSED GdaLdapProvider *pg_prv,
+			G_GNUC_UNUSED GdaLdapProviderClass *klass)
+{
+	/* nothing specific there */
+}
+
+static void
+gda_ldap_provider_finalize (GObject *object)
+{
+	GdaLdapProvider *pg_prv = (GdaLdapProvider *) object;
+
+	g_return_if_fail (GDA_IS_LDAP_PROVIDER (pg_prv));
+
+	/* chain to parent class */
+	parent_class->finalize(object);
+}
+
+GType
+gda_ldap_provider_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static GTypeInfo info = {
+			sizeof (GdaLdapProviderClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gda_ldap_provider_class_init,
+			NULL, NULL,
+			sizeof (GdaLdapProvider),
+			0,
+			(GInstanceInitFunc) gda_ldap_provider_init,
+			0
+		};
+		g_static_mutex_lock (&registering);
+		if (type == 0)
+			type = g_type_register_static (GDA_TYPE_VPROVIDER_DATA_MODEL, "GdaLdapProvider", &info, 0);
+		g_static_mutex_unlock (&registering);
+	}
+
+	return type;
+}
+
+/*
+ * Get provider name request
+ */
+static const gchar *
+gda_ldap_provider_get_name (G_GNUC_UNUSED GdaServerProvider *provider)
+{
+	return LDAP_PROVIDER_NAME;
+}
+
+/* 
+ * Get version request
+ */
+static const gchar *
+gda_ldap_provider_get_version (G_GNUC_UNUSED GdaServerProvider *provider)
+{
+	return PACKAGE_VERSION;
+}
+
+static GdaConnection *
+gda_ldap_provider_create_connection (GdaServerProvider *provider)
+{
+	GdaConnection *cnc;
+        g_return_val_if_fail (GDA_IS_LDAP_PROVIDER (provider), NULL);
+
+        cnc = g_object_new (GDA_TYPE_LDAP_CONNECTION, "provider", provider, NULL);
+
+        return cnc;
+}
+
+/*
+ * compute cache file's absolute name
+ */
+static gchar *
+compute_data_file_name (GdaQuarkList *params, gboolean is_cache, const gchar *data_type)
+{
+	/* real cache file name */
+	GString *string;
+	gchar *cfile, *evalue;
+	const gchar *base_dn;
+	const gchar *host;
+	const gchar *require_ssl;
+	const gchar *port;
+	gint rport;
+	gboolean use_ssl;
+
+        base_dn = gda_quark_list_find (params, "DB_NAME");
+	host = gda_quark_list_find (params, "HOST");
+	if (!host)
+		host = "127.0.0.1";
+        port = gda_quark_list_find (params, "PORT");
+        require_ssl = gda_quark_list_find (params, "USE_SSL");
+	use_ssl = (require_ssl && ((*require_ssl == 't') || (*require_ssl == 'T'))) ? TRUE : FALSE;
+	if (port && *port)
+		rport = atoi (port);
+	else {
+		if (use_ssl)
+			rport = LDAPS_PORT;
+		else
+			rport = LDAP_PORT;
+	}
+	string = g_string_new ("");
+	evalue = gda_rfc1738_encode (host);
+	g_string_append_printf (string, ",=%s", evalue);
+	g_free (evalue);
+	g_string_append_printf (string, ";PORT=%d", rport);
+	if (base_dn) {
+		evalue = gda_rfc1738_encode (base_dn);
+		g_string_append_printf (string, ";BASE_DN,=%s", evalue);
+		g_free (evalue);
+	}
+	evalue = g_compute_checksum_for_string (G_CHECKSUM_SHA1, string->str, -1);
+	g_string_free (string, TRUE);
+	if (is_cache)
+		cfile = g_strdup_printf ("%s_%s", evalue, data_type);
+	else
+		cfile = g_strdup_printf ("ldap-%s.%s", evalue, data_type);
+	g_free (evalue);
+	
+	gchar *fname;
+	if (is_cache)
+		fname = g_build_path (G_DIR_SEPARATOR_S, g_get_user_cache_dir (),
+				      "libgda", "ldap", cfile, NULL);
+	else
+		fname = g_build_path (G_DIR_SEPARATOR_S, g_get_user_data_dir (),
+				      "libgda", cfile, NULL);
+
+	g_free (cfile);
+	return fname;
+}
+
+/* 
+ * Open connection request
+ *
+ * In this function, the following _must_ be done:
+ *   - check for the presence and validify of the parameters required to actually open a connection,
+ *     using @params
+ *   - open the real connection to the database using the parameters previously checked, create one or
+ *     more GdaDataModel objects and declare them to the virtual connection with table names
+ *   - open virtual (SQLite) connection
+ *   - create a LdapConnectionData structure and associate it to @cnc
+ *
+ * Returns: TRUE if no error occurred, or FALSE otherwise (and an ERROR connection event must be added to @cnc)
+ */
+static gboolean
+gda_ldap_provider_open_connection (GdaServerProvider *provider, GdaConnection *cnc,
+				   GdaQuarkList *params, GdaQuarkList *auth,
+				   G_GNUC_UNUSED guint *task_id, GdaServerProviderAsyncCallback async_cb,
+				   G_GNUC_UNUSED gpointer cb_data)
+{
+	g_return_val_if_fail (GDA_IS_LDAP_PROVIDER (provider), FALSE);
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), FALSE);
+
+	/* Don't allow asynchronous connection opening for virtual providers */
+	if (async_cb) {
+		gda_connection_add_event_string (cnc, _("Provider does not support asynchronous connection open"));
+                return FALSE;
+	}
+
+	/* Check for connection parameters */
+	const gchar *base_dn;
+	const gchar *host;
+	const gchar *tmp;
+	const gchar *port;
+	const gchar *user = NULL;
+        const gchar *pwd = NULL;
+	gint rport;
+	gboolean use_ssl, use_cache;
+
+        base_dn = gda_quark_list_find (params, "DB_NAME");
+        if (!base_dn) {
+                gda_connection_add_event_string (cnc,
+                                                 _("The connection string must contain the DB_NAME value"));
+                return FALSE;
+        }
+	host = gda_quark_list_find (params, "HOST");
+	if (!host)
+		host = "127.0.0.1";
+        port = gda_quark_list_find (params, "PORT");
+        tmp = gda_quark_list_find (params, "USE_SSL");
+	use_ssl = (tmp && ((*tmp == 't') || (*tmp == 'T'))) ? TRUE : FALSE;
+	tmp = gda_quark_list_find (params, "USE_CACHE");
+	use_cache = (!tmp || ((*tmp == 't') || (*tmp == 'T'))) ? TRUE : FALSE;
+	if (port && *port)
+		rport = atoi (port);
+	else {
+		if (use_ssl)
+			rport = LDAPS_PORT;
+		else
+			rport = LDAP_PORT;
+	}
+	user = gda_quark_list_find (auth, "USERNAME");
+        if (!user)
+                user = gda_quark_list_find (params, "USERNAME");
+        pwd = gda_quark_list_find (auth, "PASSWORD");
+        if (!pwd)
+                pwd = gda_quark_list_find (params, "PASSWORD");
+
+	/* open LDAP connection */
+	LdapConnectionData *cdata;
+	LDAP *ld;
+        int res;
+	gchar *url;
+	if (use_ssl)
+		url = g_strdup_printf ("ldaps://%s:%d", host, rport);
+	else
+		url = g_strdup_printf ("ldap://%s:%d";, host, rport);
+	res = ldap_initialize (&ld, url);
+
+        if (res != LDAP_SUCCESS) {
+		gda_connection_add_event_string (cnc, ldap_err2string (res));
+		g_free (url);
+                return FALSE;
+        }
+
+	cdata = g_new0 (LdapConnectionData, 1);
+	cdata->handle = ld;
+	cdata->url = url;
+	cdata->base_dn = g_strdup (base_dn);
+	if (use_cache)
+		cdata->attributes_cache_file = compute_data_file_name (params, TRUE, "attrs");
+
+	/* set protocol version to 3 by default */
+	int version = LDAP_VERSION3;
+	res = ldap_set_option (cdata->handle, LDAP_OPT_PROTOCOL_VERSION, &version);
+        if (res != LDAP_SUCCESS) {
+		if (res == LDAP_PROTOCOL_ERROR) {
+			version = LDAP_VERSION2;
+			res = ldap_set_option (cdata->handle, LDAP_OPT_PROTOCOL_VERSION, &version);
+		}
+		if (res != LDAP_SUCCESS) {
+			gda_connection_add_event_string (cnc, ldap_err2string (res));
+			gda_ldap_free_cnc_data (cdata);
+			return FALSE;
+		}
+        }
+	int param = LDAP_OPT_ON;
+	res = ldap_set_option (cdata->handle, LDAP_OPT_RESTART, &param);
+
+#ifdef NO
+	if (use_ssl) {
+		/* Configuring SSL/TLS options:
+		 * this is for texting purpose only, and should actually be done through LDAP's conf.
+		 * files, see: man 5 ldap.conf
+		 *
+		 * For example ~/.ldaprc can contain:
+		 * TLS_REQCERT demand
+		 * TLS_CACERT /usr/share/ca-certificates/mozilla/Thawte_Premium_Server_CA.crt
+		 *
+		 * Note: if server certificate verification fails,
+		 * the error message is: "Can't contact LDAP server"
+		 */
+		int opt = LDAP_OPT_X_TLS_DEMAND;
+		res = ldap_set_option (cdata->handle, LDAP_OPT_X_TLS_REQUIRE_CERT, &opt);
+		if (res != LDAP_SUCCESS) {
+			gda_connection_add_event_string (cnc, ldap_err2string (res));
+			gda_ldap_free_cnc_data (cdata);
+			return FALSE;
+		}
+	}
+#endif
+
+	/* authentication */
+	struct berval cred;
+        memset (&cred, 0, sizeof (cred));
+        cred.bv_len = pwd && *pwd ? strlen (pwd) : 0;
+        cred.bv_val = pwd && *pwd ? (char *) pwd : NULL;
+        res = ldap_sasl_bind_s (ld, user, NULL, &cred, NULL, NULL, NULL);
+	if (res != LDAP_SUCCESS) {
+		gda_connection_add_event_string (cnc, ldap_err2string (res));
+		gda_ldap_free_cnc_data (cdata);
+                return FALSE;
+	}
+	if (pwd)
+		cdata->pass = g_strdup (pwd);
+	if (user)
+		cdata->user = g_strdup (user);
+
+	/* set startup file name */
+	gchar *fname;
+	fname = compute_data_file_name (params, FALSE, "start");
+	g_object_set ((GObject*) cnc, "startup-file", fname, NULL);
+	g_free (fname);
+
+	/* open virtual connection */
+	g_object_set_data ((GObject*) cnc, "__gda_connection_LDAP", (gpointer) 0x01);
+	gda_virtual_connection_internal_set_provider_data (GDA_VIRTUAL_CONNECTION (cnc), 
+							   cdata, (GDestroyNotify) gda_ldap_free_cnc_data);
+	if (! GDA_SERVER_PROVIDER_CLASS (parent_class)->open_connection (GDA_SERVER_PROVIDER (provider), cnc, params,
+                                                                         NULL, NULL, NULL, NULL)) {
+		gda_virtual_connection_internal_set_provider_data (GDA_VIRTUAL_CONNECTION (cnc), NULL, NULL);
+                gda_connection_add_event_string (cnc, _("Can't open virtual connection"));
+		gda_ldap_free_cnc_data (cdata);
+                return FALSE;
+        }
+
+	return TRUE;
+}
+
+/*
+ * Reopens a connection after the server has closed it (possibly because of a timeout)
+ *
+ * If it fails, then @cdata is left unchanged, otherwise it is modified to be useable again.
+ */
+gboolean
+gda_ldap_silently_rebind (LdapConnectionData *cdata)
+{
+	if (!cdata)
+		return FALSE;
+
+	/*g_print ("Trying to reconnect...\n");*/
+	LDAP *ld;
+	int res;
+	res = ldap_initialize (&ld, cdata->url);
+	if (res != LDAP_SUCCESS)
+		return FALSE;
+
+	/* set protocol version to 3 by default */
+	int version = LDAP_VERSION3;
+	res = ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+        if (res != LDAP_SUCCESS) {
+		if (res == LDAP_PROTOCOL_ERROR) {
+			version = LDAP_VERSION2;
+			res = ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+		}
+		if (res != LDAP_SUCCESS) {
+			ldap_unbind_ext (ld, NULL, NULL);
+			return FALSE;
+		}
+        }
+
+	/* authentication */
+	struct berval cred;
+	const gchar *pwd;
+	pwd = cdata->pass;
+        memset (&cred, 0, sizeof (cred));
+        cred.bv_len = pwd && *pwd ? strlen (pwd) : 0;
+        cred.bv_val = pwd && *pwd ? (char *) pwd : NULL;
+	res = ldap_sasl_bind_s (ld, cdata->user, NULL, &cred, NULL, NULL, NULL);
+	if (res != LDAP_SUCCESS) {
+		ldap_unbind_ext (ld, NULL, NULL);
+                return FALSE;
+	}
+
+	/* all ok */
+	if (cdata->handle) {
+		/* don't call ldap_unbind_ext() as it often crashed the application */
+		/*ldap_unbind_ext (cdata->handle, NULL, NULL);*/
+	}
+	cdata->handle = ld;
+
+	/*g_print ("Reconnected!\n");*/
+	return TRUE;
+}
+
+/*
+ * Server version request
+ */
+static const gchar *
+gda_ldap_provider_get_server_version (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	LdapConnectionData *cdata;
+
+        g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
+        g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, NULL);
+
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+                return FALSE;
+
+	if (! cdata->server_version) {
+		/* FIXME: don't know how to get information about the LDAP server! */
+	}
+        return cdata->server_version;
+}
+
+/*
+ * Get database request
+ */
+static const gchar *
+gda_ldap_provider_get_database (GdaServerProvider *provider, GdaConnection *cnc)
+{
+	LdapConnectionData *cdata;
+
+        g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
+        g_return_val_if_fail (gda_connection_get_provider (cnc) == provider, NULL);
+
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+                return NULL;
+        TO_IMPLEMENT;
+        return NULL;
+}
+
+/*
+ * Extra SQL
+ */
+typedef struct {
+	gchar             *table_name;
+	gboolean           other_args; /* set to %TRUE if any of the arguments below have been specified */
+	gchar             *base_dn;
+	gchar             *filter;
+	gchar             *attributes;
+	GdaLdapSearchScope scope;
+} ExtraSqlCommand;
+static void extra_sql_command_free (ExtraSqlCommand *cmde);
+
+#define NOT_AN_EXTRA_SQL_COMMAND (ExtraSqlCommand*) 0x01
+#define SKIP_SPACES(x) for (; *(x) && (g_ascii_isspace (*(x)) || (*(x)=='\n')); (x)++)
+static ExtraSqlCommand *parse_extra_sql_command (gchar *cmd, const gchar *cmde_name,
+						 GError **error);
+static GdaDataModel *table_parameters_describe (const gchar *base_dn, const gchar *filter,
+						const gchar *attributes,
+						GdaLdapSearchScope scope);
+static GObject *
+gda_ldap_provider_statement_execute (GdaServerProvider *provider, GdaConnection *cnc,
+				     GdaStatement *stmt, GdaSet *params,
+				     GdaStatementModelUsage model_usage,
+				     GType *col_types, GdaSet **last_inserted_row,
+				     guint *task_id, GdaServerProviderExecCallback async_cb,
+				     gpointer cb_data, GError **error)
+{
+	if (async_cb) {
+                g_set_error (error, GDA_SERVER_PROVIDER_ERROR, GDA_SERVER_PROVIDER_METHOD_NON_IMPLEMENTED_ERROR,
+			     "%s", _("Provider does not support asynchronous statement execution"));
+                return NULL;
+        }
+	gchar *sql;
+	sql = gda_statement_to_sql (stmt, params, NULL);
+	if (sql) {
+		/* parse SQL:
+		 * CREATE LDAP TABLE <table name> WITH BASE='base_dn' FILTER='filter'
+		 *              ATTRIBUTES='attributes' SCOPE='scope'
+		 */
+		gchar *ssql = sql;
+		SKIP_SPACES (ssql);
+		if (! g_ascii_strncasecmp (ssql, "CREATE", 6)) {
+			ExtraSqlCommand *cmde;
+			GError *lerror = NULL;
+			GObject *retval = NULL;
+			cmde = parse_extra_sql_command (ssql, "CREATE", &lerror);
+			if (cmde != NOT_AN_EXTRA_SQL_COMMAND) {
+				GdaConnectionEvent *event = NULL;
+				if (cmde) {
+					if (gda_ldap_connection_declare_table (GDA_LDAP_CONNECTION (cnc),
+									       cmde->table_name, cmde->base_dn,
+									       cmde->filter, cmde->attributes,
+									       cmde->scope, &lerror))
+					retval = (GObject*) gda_set_new (NULL);
+					else {
+						event = gda_connection_point_available_event (cnc,
+											      GDA_CONNECTION_EVENT_ERROR);
+						gda_connection_event_set_description (event, lerror && lerror->message ? 
+										      lerror->message : _("No detail"));
+						gda_connection_add_event (cnc, event);
+						g_propagate_error (error, lerror);
+					}
+					extra_sql_command_free (cmde);
+				}
+				else {
+					event = gda_connection_point_available_event (cnc,
+										      GDA_CONNECTION_EVENT_ERROR);
+					gda_connection_event_set_description (event, lerror && lerror->message ? 
+									      lerror->message : _("No detail"));
+					gda_connection_add_event (cnc, event);
+					g_propagate_error (error, lerror);
+				}
+
+				gda_connection_internal_statement_executed (cnc, stmt, params, event);
+				g_free (sql);
+				return retval;
+			}
+		}
+
+		/* parse SQL:
+		 * DROP LDAP TABLE <table name>
+		 */
+		else if (! g_ascii_strncasecmp (ssql, "DROP", 4)) {
+			ExtraSqlCommand *cmde;
+			GError *lerror = NULL;
+			GObject *retval = NULL;
+			cmde = parse_extra_sql_command (ssql, "DROP", &lerror);
+			if ((cmde != NOT_AN_EXTRA_SQL_COMMAND) && !cmde->other_args) {
+				GdaConnectionEvent *event = NULL;
+				if (cmde) {
+					if (gda_ldap_connection_undeclare_table (GDA_LDAP_CONNECTION (cnc),
+										 cmde->table_name, &lerror))
+						retval = (GObject*) gda_set_new (NULL);
+					else {
+						event = gda_connection_point_available_event (cnc,
+											      GDA_CONNECTION_EVENT_ERROR);
+						gda_connection_event_set_description (event, lerror && lerror->message ? 
+										      lerror->message : _("No detail"));
+						gda_connection_add_event (cnc, event);
+						g_propagate_error (error, lerror);
+					}
+					extra_sql_command_free (cmde);
+				}
+				gda_connection_internal_statement_executed (cnc, stmt, params, event);
+				g_free (sql);
+				return retval;
+			}
+		}
+		/* parse SQL:
+		 * ALTER LDAP TABLE <table name> [...]
+		 * DESCRIBE LDAP TABLE <table name>
+		 */
+		else if (! g_ascii_strncasecmp (ssql, "ALTER", 5) ||
+			 ! g_ascii_strncasecmp (ssql, "DESCRIBE", 8)) {
+			ExtraSqlCommand *cmde;
+			GError *lerror = NULL;
+			GObject *retval = NULL;
+			gboolean alter;
+
+			alter = g_ascii_strncasecmp (ssql, "ALTER", 5) ? FALSE : TRUE;
+			    
+			cmde = parse_extra_sql_command (ssql, alter ? "ALTER" : "DESCRIBE", &lerror);
+			if ((cmde != NOT_AN_EXTRA_SQL_COMMAND) &&
+			    (alter || (!alter && !cmde->other_args))) {
+				GdaConnectionEvent *event = NULL;
+				if (cmde) {
+					const gchar *base_dn, *filter, *attributes;
+					GdaLdapSearchScope scope;
+					if (gda_ldap_connection_describe_table (GDA_LDAP_CONNECTION (cnc),
+										cmde->table_name,
+										&base_dn, &filter,
+										&attributes, &scope, &lerror)) {
+						if (cmde->other_args) {
+							if (! cmde->base_dn && base_dn)
+								cmde->base_dn = g_strdup (base_dn);
+							if (! cmde->filter && filter)
+								cmde->filter = g_strdup (filter);
+							if (! cmde->attributes && attributes)
+								cmde->attributes = g_strdup (attributes);
+							if (! cmde->scope)
+								cmde->scope = scope;
+							if (gda_ldap_connection_undeclare_table (GDA_LDAP_CONNECTION (cnc),
+												 cmde->table_name, &lerror) &&
+							    gda_ldap_connection_declare_table (GDA_LDAP_CONNECTION (cnc),
+											       cmde->table_name, cmde->base_dn,
+											       cmde->filter, cmde->attributes,
+											       cmde->scope, &lerror))
+								retval = (GObject*) gda_set_new (NULL);
+						}
+						else {
+							GdaDataModel *array;
+							array = table_parameters_describe (base_dn, filter,
+											   attributes, scope);
+							retval = (GObject*) array;
+						}
+					}
+					if (!retval) {
+						event = gda_connection_point_available_event (cnc,
+											      GDA_CONNECTION_EVENT_ERROR);
+						gda_connection_event_set_description (event, lerror && lerror->message ? 
+										      lerror->message : _("No detail"));
+						gda_connection_add_event (cnc, event);
+						g_propagate_error (error, lerror);
+					}
+					extra_sql_command_free (cmde);
+				}
+				gda_connection_internal_statement_executed (cnc, stmt, params, event);
+				g_free (sql);
+				return retval;
+			}
+		}
+		g_free (sql);
+	}
+	return GDA_SERVER_PROVIDER_CLASS (parent_class)->statement_execute (provider, cnc, stmt, params,
+									    model_usage, col_types,
+									    last_inserted_row, task_id, 
+									    async_cb, cb_data, error);
+}
+
+/*
+ * Free connection's specific data
+ */
+static void
+gda_ldap_free_cnc_data (LdapConnectionData *cdata)
+{
+	if (cdata->handle)
+                ldap_unbind_ext (cdata->handle, NULL, NULL);
+	if (cdata->attributes_hash)
+		g_hash_table_destroy (cdata->attributes_hash);
+	g_free (cdata->attributes_cache_file);
+	g_free (cdata->base_dn);
+	g_free (cdata->server_version);
+	g_free (cdata->url);
+	g_free (cdata->user);
+	g_free (cdata->pass);
+	g_free (cdata);
+}
+
+static const gchar *
+scope_to_string (GdaLdapSearchScope scope)
+{
+	switch (scope) {
+	case 0:
+		return _("Unknown");
+	case GDA_LDAP_SEARCH_BASE:
+		return "BASE";
+	case GDA_LDAP_SEARCH_ONELEVEL:
+		return "ONELEVEL";
+	case GDA_LDAP_SEARCH_SUBTREE:
+		return "SUBTREE";
+	default:
+		g_assert_not_reached();
+		return NULL;
+	}
+}
+
+static GdaDataModel *
+table_parameters_describe (const gchar *base_dn, const gchar *filter, const gchar *attributes,
+			   GdaLdapSearchScope scope)
+{
+	GdaDataModel *array;
+	GValue *v1, *v2;
+	GList *list;
+	array = gda_data_model_array_new_with_g_types (2, G_TYPE_STRING,
+						       G_TYPE_STRING);
+	gda_data_model_set_column_title (array, 0, _("Parameter"));
+	gda_data_model_set_column_title (array, 1, _("Value"));
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "BASE");
+	g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), base_dn);
+	list = g_list_append (NULL, v1);
+	list = g_list_append (list, v2);
+	gda_data_model_append_values (array, list, NULL);
+	g_list_free (list);
+	gda_value_free (v1);
+	gda_value_free (v2);
+	
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "FILTER");
+	g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), filter);
+	list = g_list_append (NULL, v1);
+	list = g_list_append (list, v2);
+	gda_data_model_append_values (array, list, NULL);
+	g_list_free (list);
+	gda_value_free (v1);
+	gda_value_free (v2);
+	
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "ATTRIBUTES");
+	g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), attributes);
+	list = g_list_append (NULL, v1);
+	list = g_list_append (list, v2);
+	gda_data_model_append_values (array, list, NULL);
+	g_list_free (list);
+	gda_value_free (v1);
+	gda_value_free (v2);
+	
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "SCOPE");
+	g_value_set_string ((v2 = gda_value_new (G_TYPE_STRING)), scope_to_string (scope));
+	list = g_list_append (NULL, v1);
+	list = g_list_append (list, v2);
+	gda_data_model_append_values (array, list, NULL);
+	g_list_free (list);
+	gda_value_free (v1);
+	gda_value_free (v2);
+	return array;
+}
+
+/*
+ * Extra commands parsing
+ */
+/*
+ * consumes @str for a singly quoted string
+ * Returns: a pointer to the next non analysed char
+ */
+static gchar *
+parse_string (gchar *str, gchar **out_part)
+{
+	gchar *ptr = str;
+	*out_part = NULL;
+
+	SKIP_SPACES (ptr);
+	if (!*ptr)
+		return NULL;
+	if (*ptr != '\'') {
+		if (!g_ascii_strncasecmp (ptr, "null", 4)) {
+			ptr += 4;
+			return ptr;
+		}
+		else
+			return NULL;
+	}
+	ptr++;
+	*out_part = ptr;
+	for (; *ptr && (*ptr != '\''); ptr++);
+	if (!*ptr)
+		return NULL;
+	*ptr = 0;
+	ptr++;
+	return ptr;
+}
+
+/*
+ * consumes @str for an identifier
+ * Returns: a pointer to the next non analysed char
+ */
+static gchar *
+parse_ident (gchar *str, gchar **out_part)
+{
+	gchar *ptr = str;
+	*out_part = NULL;
+
+	SKIP_SPACES (ptr);
+	*out_part = ptr;
+	for (; *ptr && (g_ascii_isalnum (*ptr) || (*ptr == '_')) ; ptr++);
+	if (ptr == *out_part) {
+		*out_part = NULL;
+		return NULL;
+	}
+	return ptr;
+}
+
+static void
+extra_sql_command_free (ExtraSqlCommand *cmde)
+{
+	g_free (cmde->table_name);
+	g_free (cmde->base_dn);
+	g_free (cmde->filter);
+	g_free (cmde->attributes);
+	g_free (cmde);
+}
+
+/*
+ * CREATE LDAP TABLE <table name> BASE='base_dn' FILTER='filter' ATTRIBUTES='attributes' SCOPE='scope'
+ * DROP LDAP TABLE <table name>
+ * ALTER LDAP TABLE <table name>
+ * ALTER LDAP TABLE <table name> BASE='base_dn' FILTER='filter' ATTRIBUTES='attributes' SCOPE='scope'
+ *
+ * Returns: a #ExtraSqlCommand pointer, or %NULL, or NOT_AN_EXTRA_SQL_COMMAND (if not "CREATE LDAP...")
+ */
+static ExtraSqlCommand *
+parse_extra_sql_command (gchar *cmd, const gchar *cmde_name, GError **error)
+{
+	ExtraSqlCommand *args;
+	gchar *ptr, *errptr, *part, *tmp;
+	args = g_new0 (ExtraSqlCommand, 1);
+	args->other_args = FALSE;
+	
+	ptr = cmd + strlen (cmde_name);
+	
+	/* make sure about complete command */
+	errptr = ptr;
+	if (! (ptr = parse_ident (ptr, &part)))
+		return NOT_AN_EXTRA_SQL_COMMAND;
+	if (!part || g_ascii_strncasecmp (part, "ldap", 4))
+		return NOT_AN_EXTRA_SQL_COMMAND;
+
+	errptr = ptr;
+	if (! (ptr = parse_ident (ptr, &part)))
+		goto onerror;
+	if (!part || g_ascii_strncasecmp (part, "table", 5))
+		goto onerror;
+
+	/* table name */
+	SKIP_SPACES (ptr);
+	errptr = ptr;
+	if (! (ptr = parse_ident (ptr, &part)))
+		goto onerror;
+	tmp = g_strndup (part, ptr-part);
+	args->table_name = g_ascii_strdown (tmp, -1);
+	g_free (tmp);
+
+	/* key=value arguments */
+	while (TRUE) {
+		errptr = ptr;
+		SKIP_SPACES (ptr);
+		if (! (ptr = parse_ident (ptr, &part))) {
+			ptr = errptr;
+			break;
+		}
+		if (part) {
+			gchar **where = NULL;
+			if (!g_ascii_strncasecmp (part, "base", 4))
+				where = &(args->base_dn);
+			else if (!g_ascii_strncasecmp (part, "filter", 6))
+				where = &(args->filter);
+			else if (!g_ascii_strncasecmp (part, "attributes", 10))
+				where = &(args->attributes);
+			else if (!g_ascii_strncasecmp (part, "scope", 5))
+				where = NULL;
+			else
+				goto onerror;
+			
+			/* = */
+			errptr = ptr;
+			SKIP_SPACES (ptr);
+			if (*ptr != '=')
+				goto onerror;
+			ptr++;
+			
+			/* value */
+			errptr = ptr;
+			SKIP_SPACES (ptr);
+			if (! (ptr = parse_string (ptr, &part)))
+				goto onerror;
+			if (part) {
+				if (where)
+					*where = g_strdup (part);
+				else {
+					if (!g_ascii_strcasecmp (part, "base"))
+						args->scope = GDA_LDAP_SEARCH_BASE;
+					else if (!g_ascii_strcasecmp (part, "onelevel"))
+						args->scope = GDA_LDAP_SEARCH_ONELEVEL;
+					else if (!g_ascii_strcasecmp (part, "subtree"))
+						args->scope = GDA_LDAP_SEARCH_SUBTREE;
+					else
+						goto onerror;
+				}
+				args->other_args = TRUE;
+			}
+			else
+				goto onerror;
+		}
+		else
+			break;
+	}
+
+	/* end */
+	SKIP_SPACES (ptr);
+	if (*ptr && (*ptr != ';'))
+		goto onerror;
+#ifdef GDA_DEBUG_NO
+	g_print ("TABLE=>%s, BASE=>%s, FILTER=>%s, ATTRIBUTES=>%s, SCOPE=>%d\n", args->table_name,
+		 args->base_dn, args->filter,
+		 args->attributes, args->scope);
+#endif
+
+	return args;
+
+ onerror:
+	SKIP_SPACES (errptr);
+	g_set_error (error, GDA_SQL_PARSER_ERROR, GDA_SQL_PARSER_SYNTAX_ERROR,
+		     _("near \"%s\": syntax error"), errptr);
+	extra_sql_command_free (args);
+	return NULL;
+}
diff --git a/providers/ldap/gda-ldap-provider.h b/providers/ldap/gda-ldap-provider.h
new file mode 100644
index 0000000..ad15222
--- /dev/null
+++ b/providers/ldap/gda-ldap-provider.h
@@ -0,0 +1,50 @@
+/* GDA Ldap provider
+ * Copyright (C) 2008 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      TO_ADD: your name and email
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __GDA_LDAP_PROVIDER_H__
+#define __GDA_LDAP_PROVIDER_H__
+
+#include <virtual/gda-vprovider-data-model.h>
+
+#define GDA_TYPE_LDAP_PROVIDER            (gda_ldap_provider_get_type())
+#define GDA_LDAP_PROVIDER(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, GDA_TYPE_LDAP_PROVIDER, GdaLdapProvider))
+#define GDA_LDAP_PROVIDER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, GDA_TYPE_LDAP_PROVIDER, GdaLdapProviderClass))
+#define GDA_IS_LDAP_PROVIDER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, GDA_TYPE_LDAP_PROVIDER))
+#define GDA_IS_LDAP_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDA_TYPE_LDAP_PROVIDER))
+
+typedef struct _GdaLdapProvider      GdaLdapProvider;
+typedef struct _GdaLdapProviderClass GdaLdapProviderClass;
+
+struct _GdaLdapProvider {
+	GdaVproviderDataModel      provider;
+};
+
+struct _GdaLdapProviderClass {
+	GdaVproviderDataModelClass parent_class;
+};
+
+G_BEGIN_DECLS
+
+GType gda_ldap_provider_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif
diff --git a/providers/ldap/gda-ldap-util.c b/providers/ldap/gda-ldap-util.c
new file mode 100644
index 0000000..fbf5f8d
--- /dev/null
+++ b/providers/ldap/gda-ldap-util.c
@@ -0,0 +1,1410 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <glib-object.h>
+#include <libgda/gda-value.h>
+#include <glib/gi18n-lib.h>
+#include "gda-ldap.h"
+#include "gda-ldap-util.h"
+#include <sqlite/virtual/gda-ldap-connection.h>
+#include <gda-util.h>
+
+static void
+ldap_attribute_free (LdapAttribute *lat)
+{
+	g_free (lat->name);
+	g_free (lat);
+}
+
+static void
+ldap_class_free (GdaLdapClass *lcl)
+{
+	g_free (lcl->oid);
+	g_strfreev (lcl->names);
+	g_free (lcl->description);
+	
+	if (lcl->req_attributes)
+		g_strfreev (lcl->req_attributes);
+
+	if (lcl->opt_attributes)
+		g_strfreev (lcl->opt_attributes);
+	g_slist_free (lcl->parents);
+	g_slist_free (lcl->children);
+	g_free (lcl);
+}
+
+/*
+ * Data copied from GQ's sources and transformed,
+ * see ftp://ftp.rfc-editor.org/in-notes/rfc2252.txt
+ */
+static LdapAttrType ldap_types [] = {
+	{ "1.3.6.1.4.1.1466.115.121.1.1",
+	  "ACI Item",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.2",
+	  "Access Point",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.3",
+	  "Attribute Type Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.4",
+	  "Audio",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.5",
+	  "Binary",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.6",
+	  "Bit String",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.7",
+	  "Boolean",
+	  G_TYPE_BOOLEAN
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.8",
+	  "Certificate",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.9",
+	  "Certificate List",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.10",
+	  "Certificate Pair",
+	  -1 /*GDA_TYPE_BINARY*/,
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.11",
+	  "Country String",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.12",
+	  "DN",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.13",
+	  "Data Quality Syntax",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.14",
+	  "Delivery Method",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.15",
+	  "Directory String",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.16",
+	  "DIT Content Rule Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.17",
+	  "DIT Structure Rule Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.18",
+	  "DL Submit Permission",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.19",
+	  "DSA Quality Syntax",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.20",
+	  "DSE Type",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.21",
+	  "Enhanced Guide",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.22",
+	  "Facsimile Telephone Number",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.23",
+	  "Fax",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.24",
+	  "Generalized Time",
+	  -4 /*GDA_TYPE_TIMESTAMP*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.25",
+	  "Guide",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.26",
+	  "IA5 String",
+	  G_TYPE_STRING /* as ASCII */
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.27",
+	  "INTEGER",
+	  G_TYPE_INT
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.28",
+	  "JPEG",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.29",
+	  "Master And Shadow Access Points",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.30",
+	  "Matching Rule Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.31",
+	  "Matching Rule Use Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.32",
+	  "Mail Preference",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.33",
+	  "MHS OR Address",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.34",
+	  "Name And Optional UID",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.35",
+	  "Name Form Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.36",
+	  "Numeric String",
+	  -3 /*GDA_TYPE_NUMERIC*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.37",
+	  "Object Class Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.38",
+	  "OID",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.39",
+	  "Other Mailbox",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.40",
+	  "Octet String",
+	  -1 /*GDA_TYPE_BINARY*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.41",
+	  "Postal Address",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.42",
+	  "Protocol Information",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.43",
+	  "Presentation Address",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.44",
+	  "Printable String",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.45",
+	  "Subtree Specification",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.46",
+	  "Supplier Information",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.47",
+	  "Supplier Or Consumer",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.48",
+	  "Supplier And Consumer",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.49",
+	  "Supported Algorithm",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.50",
+	  "Telephone Number",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.51",
+	  "Teletex Terminal Identifier",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.52",
+	  "Telex Number",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.53",
+	  "UTC Time",
+	  -2 /*GDA_TYPE_TIME*/
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.54",
+	  "LDAP Syntax Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.55",
+	  "Modify Rights",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.56",
+	  "LDAP Schema Definition",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.57",
+	  "LDAP Schema Description",
+	  G_TYPE_STRING
+	},
+	{ "1.3.6.1.4.1.1466.115.121.1.58",
+	  "Substring Assertion",
+	  G_TYPE_STRING
+	}
+};
+
+LdapAttrType unknown_type = {
+	"", "Unknown type", G_TYPE_STRING
+};
+
+/**
+ * gda_ldap_get_type_info:
+ *
+ * Returns: the #LdapAttrType associated to @oid, NEVER NULL
+ */
+LdapAttrType *
+gda_ldap_get_type_info (const gchar *oid)
+{
+	static GHashTable *hash = NULL;
+	LdapAttrType *retval = NULL;
+	if (hash) {
+		if (oid)
+			retval = g_hash_table_lookup (hash, oid);
+	}
+	else {
+		hash = g_hash_table_new (g_str_hash, g_str_equal);
+		gint i, nb;
+		nb = sizeof (ldap_types) / sizeof (LdapAttrType);
+		for (i = 0; i < nb; i++) {
+			LdapAttrType *type;
+			type = & (ldap_types[i]);
+			if (type->gtype == -1)
+				type->gtype = GDA_TYPE_BINARY;
+			else if (type->gtype == -2)
+				type->gtype = GDA_TYPE_TIME;
+			else if (type->gtype == -3)
+				type->gtype = GDA_TYPE_NUMERIC;
+			else if (type->gtype == -4)
+				type->gtype = GDA_TYPE_TIMESTAMP;
+			g_hash_table_insert (hash, type->oid, type);
+		}
+		if (oid)
+			retval = g_hash_table_lookup (hash, oid);
+	}
+	return retval ? retval : &unknown_type;
+}
+
+/**
+ * gda_ldap_get_attr_info:
+ *
+ * Returns: the #LdapAttribute for @attribute, or %NULL
+ */
+LdapAttribute *
+gda_ldap_get_attr_info (LdapConnectionData *cdata, const gchar *attribute)
+{
+	LdapAttribute *retval = NULL;
+	if (! attribute || !cdata)
+		return NULL;
+
+	if (cdata->attributes_hash)
+		return g_hash_table_lookup (cdata->attributes_hash, attribute);
+
+	/* initialize known types */
+	cdata->attributes_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+							NULL,
+							(GDestroyNotify) ldap_attribute_free);
+	
+
+	if (cdata->attributes_cache_file) {
+		/* try to load from cache file, which must contain one line per attribute:
+		 * <syntax oid>,0|1,<attribute name>
+		 */
+		gchar *data;
+		if (g_file_get_contents (cdata->attributes_cache_file, &data, NULL, NULL)) {
+			gchar *start, *ptr;
+			gchar **array;
+			start = data;
+			while (1) {
+				gboolean done = FALSE;
+				for (ptr = start; *ptr && (*ptr != '\n'); ptr++);
+				if (*ptr == '\n')
+					*ptr = 0;
+				else
+					done = TRUE;
+				
+				if (*start && (*start != '#')) {
+					array = g_strsplit (start, ",", 3);
+					if (array[0] && array[1] && array[2]) {
+						LdapAttribute *lat;
+						lat = g_new (LdapAttribute, 1);
+						lat->name = g_strdup (array[2]);
+						lat->type = gda_ldap_get_type_info (array[0]);
+						lat->single_value = (*array[1] == '0' ? FALSE : TRUE);
+						g_hash_table_insert (cdata->attributes_hash,
+								     lat->name, lat);
+						/*g_print ("CACHE ADDED [%s][%p][%d] for OID %s\n",
+						  lat->name, lat->type, lat->single_value,
+						  array[0]);*/
+					}
+					g_strfreev (array);
+				}
+				if (done)
+					break;
+				else
+					start = ptr+1;
+			}
+			g_free (data);
+			return g_hash_table_lookup (cdata->attributes_hash, attribute);
+		}
+	}
+
+	GString *string = NULL;
+	LDAPMessage *msg, *entry;
+	int res;
+	gchar *subschema = NULL;
+
+	char *subschemasubentry[] = {"subschemaSubentry", NULL};
+	char *schema_attrs[] = {"attributeTypes", NULL};
+	
+	/* look for subschema */
+	res = ldap_search_ext_s (cdata->handle, "", LDAP_SCOPE_BASE,
+				 "(objectclass=*)",
+				 subschemasubentry, 0,
+				 NULL, NULL, NULL, 0,
+				 &msg);
+	if (res != LDAP_SUCCESS)
+		return NULL;
+
+	if ((entry = ldap_first_entry (cdata->handle, msg))) {
+		char *attr;
+		BerElement *ber;
+		if ((attr = ldap_first_attribute (cdata->handle, entry, &ber))) {
+			BerValue **bvals;
+			if ((bvals = ldap_get_values_len (cdata->handle, entry, attr))) {
+				subschema = g_strdup (bvals[0]->bv_val);
+				ldap_value_free_len (bvals);
+			}
+			ldap_memfree (attr);
+		}
+		if (ber)
+			ber_free (ber, 0);
+	}
+	ldap_msgfree (msg);
+
+	if (! subschema)
+		return NULL;
+
+	/* look for attributeTypes */
+	res = ldap_search_ext_s (cdata->handle, subschema, LDAP_SCOPE_BASE,
+				 "(objectclass=*)",
+				 schema_attrs, 0,
+				 NULL, NULL, NULL, 0,
+				 &msg);
+	g_free (subschema);
+	if (res != LDAP_SUCCESS)
+		return NULL;
+
+	if (cdata->attributes_cache_file)
+		string = g_string_new ("# Cache file. This file can safely be removed, in this case\n"
+				       "# it will be automatically recreated.\n"
+				       "# DO NOT MODIFY\n");
+	for (entry = ldap_first_entry (cdata->handle, msg);
+	     entry;
+	     entry = ldap_next_entry (cdata->handle, msg)) {
+		char *attr;
+		BerElement *ber;
+		for (attr = ldap_first_attribute (cdata->handle, msg, &ber);
+		     attr;
+		     attr = ldap_next_attribute (cdata->handle, msg, ber)) {
+			if (strcasecmp(attr, "attributeTypes")) {
+				ldap_memfree (attr);
+				continue;
+			}
+
+			BerValue **bvals;
+			bvals = ldap_get_values_len (cdata->handle, entry, attr);
+			if (bvals) {
+				gint i;
+				for (i = 0; bvals[i]; i++) {
+					LDAPAttributeType *at;
+					const char *errp;
+					int retcode;
+					at = ldap_str2attributetype (bvals[i]->bv_val, &retcode,
+								     &errp,
+								     LDAP_SCHEMA_ALLOW_ALL);
+					if (at && at->at_names && at->at_syntax_oid &&
+					    at->at_names[0] && *(at->at_names[0])) {
+						LdapAttribute *lat;
+						lat = g_new (LdapAttribute, 1);
+						lat->name = g_strdup (at->at_names [0]);
+						lat->type = gda_ldap_get_type_info (at->at_syntax_oid);
+						lat->single_value = (at->at_single_value == 0 ? FALSE : TRUE);
+						g_hash_table_insert (cdata->attributes_hash,
+								     lat->name, lat);
+						/*g_print ("ADDED [%s][%p][%d] for OID %s\n",
+						  lat->name, lat->type, lat->single_value,
+						  at->at_syntax_oid);*/
+						if (string)
+							g_string_append_printf (string, "%s,%d,%s\n",
+										at->at_syntax_oid,
+										lat->single_value,
+										lat->name);
+									  
+					}
+					if (at)
+						ldap_memfree (at);
+				}
+				ldap_value_free_len (bvals);
+			}
+			  
+			ldap_memfree (attr);
+		}
+		if (ber)
+			ber_free (ber, 0);
+	}
+	ldap_msgfree (msg);
+
+	if (string) {
+		if (! g_file_set_contents (cdata->attributes_cache_file, string->str, -1, NULL)) {
+			gchar *dirname;
+			dirname = g_path_get_dirname (cdata->attributes_cache_file);
+			g_mkdir_with_parents (dirname, 0700);
+			g_free (dirname);
+			g_file_set_contents (cdata->attributes_cache_file, string->str, -1, NULL);
+		}
+		g_string_free (string, TRUE);
+	}
+
+	retval = g_hash_table_lookup (cdata->attributes_hash, attribute);
+	return retval;
+}
+
+/*
+ * Classes
+ */
+static gchar **make_array_from_strv (char **values, guint *out_size);
+static void classes_h_func (GdaLdapClass *lcl, gchar **supclasses, LdapConnectionData *cdata);
+static gint classes_sort (GdaLdapClass *lcl1, GdaLdapClass *lcl2);
+
+/**
+ * gdaprov_ldap_get_class_info:
+ * @cdata:
+ * @class:
+ *
+ * Returns: the #GdaLdapClass for @classname, or %NULL
+ */
+GdaLdapClass *
+gdaprov_ldap_get_class_info (GdaLdapConnection *cnc, const gchar *classname)
+{
+	GdaLdapClass *retval = NULL;
+	LdapConnectionData *cdata;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	g_return_val_if_fail (classname, NULL);
+	
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+		return NULL;
+
+	if (cdata->classes_hash)
+		return g_hash_table_lookup (cdata->classes_hash, classname);
+
+	/* initialize known classes */
+	cdata->classes_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
+						     NULL,
+						     (GDestroyNotify) ldap_class_free);
+
+	LDAPMessage *msg, *entry;
+	int res;
+	gchar *subschema = NULL;
+
+	char *subschemasubentry[] = {"subschemaSubentry", NULL};
+	char *schema_attrs[] = {"objectClasses", NULL};
+	
+	/* look for subschema */
+	res = ldap_search_ext_s (cdata->handle, "", LDAP_SCOPE_BASE,
+				 "(objectclass=*)",
+				 subschemasubentry, 0,
+				 NULL, NULL, NULL, 0,
+				 &msg);
+	if (res != LDAP_SUCCESS)
+		return NULL;
+
+	if ((entry = ldap_first_entry (cdata->handle, msg))) {
+		char *attr;
+		BerElement *ber;
+		if ((attr = ldap_first_attribute (cdata->handle, entry, &ber))) {
+			BerValue **bvals;
+			if ((bvals = ldap_get_values_len (cdata->handle, entry, attr))) {
+				subschema = g_strdup (bvals[0]->bv_val);
+				ldap_value_free_len (bvals);
+			}
+			ldap_memfree (attr);
+		}
+		if (ber)
+			ber_free (ber, 0);
+	}
+	ldap_msgfree (msg);
+
+	if (! subschema)
+		return NULL;
+
+	/* look for attributeTypes */
+	res = ldap_search_ext_s (cdata->handle, subschema, LDAP_SCOPE_BASE,
+				 "(objectclass=*)",
+				 schema_attrs, 0,
+				 NULL, NULL, NULL, 0,
+				 &msg);
+	g_free (subschema);
+	if (res != LDAP_SUCCESS)
+		return NULL;
+
+	GHashTable *h_refs;
+	h_refs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_strfreev);
+	for (entry = ldap_first_entry (cdata->handle, msg);
+	     entry;
+	     entry = ldap_next_entry (cdata->handle, msg)) {
+		char *attr;
+		BerElement *ber;
+		for (attr = ldap_first_attribute (cdata->handle, msg, &ber);
+		     attr;
+		     attr = ldap_next_attribute (cdata->handle, msg, ber)) {
+			if (strcasecmp(attr, "objectClasses")) {
+				ldap_memfree (attr);
+				continue;
+			}
+
+			BerValue **bvals;
+			bvals = ldap_get_values_len (cdata->handle, entry, attr);
+			if (bvals) {
+				gint i;
+				for (i = 0; bvals[i]; i++) {
+					LDAPObjectClass *oc;
+					const char *errp;
+					int retcode;
+					oc = ldap_str2objectclass (bvals[i]->bv_val, &retcode,
+								   &errp,
+								   LDAP_SCHEMA_ALLOW_ALL);
+					if (oc && oc->oc_oid && oc->oc_names && oc->oc_names[0]) {
+						GdaLdapClass *lcl;
+						guint k;
+						lcl = g_new0 (GdaLdapClass, 1);
+						lcl->oid = g_strdup (oc->oc_oid);
+//#define CLASS_DEBUG
+#ifdef CLASS_DEBUG
+						g_print ("FOUND CLASS\n");
+#endif
+						lcl->names = make_array_from_strv (oc->oc_names,
+										   &(lcl->nb_names));
+						for (k = 0; lcl->names[k]; k++) {
+#ifdef CLASS_DEBUG
+							g_print ("  oc_names[%d] = %s\n",
+								 k, lcl->names[k]);
+#endif
+							g_hash_table_insert (cdata->classes_hash,
+									     lcl->names[k],
+									     lcl);
+						}
+						if (oc->oc_desc) {
+#ifdef CLASS_DEBUG
+							g_print ("  oc_desc = %s\n", oc->oc_desc);
+#endif
+							lcl->description = g_strdup (oc->oc_desc);
+						}
+#ifdef CLASS_DEBUG
+						g_print ("  oc_kind = %d\n", oc->oc_kind);
+#endif
+						switch (oc->oc_kind) {
+						case 0:
+							lcl->kind = GDA_LDAP_CLASS_KIND_ABSTRACT;
+							break;
+						case 1:
+							lcl->kind = GDA_LDAP_CLASS_KIND_STRUTURAL;
+							break;
+						case 2:
+							lcl->kind = GDA_LDAP_CLASS_KIND_AUXILIARY;
+							break;
+						default:
+							lcl->kind = GDA_LDAP_CLASS_KIND_UNKNOWN;
+							break;
+						}
+						lcl->obsolete = oc->oc_obsolete;
+#ifdef CLASS_DEBUG
+						g_print ("  oc_obsolete = %d\n", oc->oc_obsolete);
+
+#endif
+						gchar **refs;
+						refs = make_array_from_strv (oc->oc_sup_oids, NULL);
+						if (refs)
+							g_hash_table_insert (h_refs, lcl, refs);
+						else
+							cdata->top_classes = g_slist_insert_sorted (cdata->top_classes,
+									     lcl, (GCompareFunc) classes_sort);
+#ifdef CLASS_DEBUG
+						for (k = 0; oc->oc_sup_oids && oc->oc_sup_oids[k]; k++)
+							g_print ("  oc_sup_oids[0] = %s\n",
+								 oc->oc_sup_oids[k]);
+#endif
+
+						lcl->req_attributes =
+							make_array_from_strv (oc->oc_at_oids_must,
+									      &(lcl->nb_req_attributes));
+#ifdef CLASS_DEBUG
+						for (k = 0; oc->oc_at_oids_must && oc->oc_at_oids_must[k]; k++)
+							g_print ("  oc_at_oids_must[0] = %s\n",
+								 oc->oc_at_oids_must[k]);
+#endif
+						lcl->opt_attributes =
+							make_array_from_strv (oc->oc_at_oids_may,
+									      &(lcl->nb_opt_attributes));
+#ifdef CLASS_DEBUG
+						for (k = 0; oc->oc_at_oids_may && oc->oc_at_oids_may[k]; k++)
+							g_print ("  oc_at_oids_may[0] = %s\n",
+								 oc->oc_at_oids_may[k]);
+#endif
+						  
+					}
+					if (oc)
+						ldap_memfree (oc);
+				}
+				ldap_value_free_len (bvals);
+			}
+			  
+			ldap_memfree (attr);
+		}
+		if (ber)
+			ber_free (ber, 0);
+	}
+	ldap_msgfree (msg);
+
+	/* create hierarchy */
+	g_hash_table_foreach (h_refs, (GHFunc) classes_h_func, cdata);
+	g_hash_table_destroy (h_refs);
+
+	retval = g_hash_table_lookup (cdata->classes_hash, classname);
+	return retval;
+}
+
+static gchar **
+make_array_from_strv (char **values, guint *out_size)
+{
+	if (out_size)
+		*out_size = 0;
+	if (!values)
+		return NULL;
+	GArray *array;
+	gint i;
+	array = g_array_new (TRUE, FALSE, sizeof (gchar*));
+	for (i = 0; values[i]; i++) {
+		gchar *tmp;
+		tmp = g_strdup (values [i]);
+		g_array_append_val (array, tmp);
+	}
+	if (out_size)
+		*out_size = array->len;
+
+	return (gchar**) g_array_free (array, FALSE);
+}
+
+static gint
+classes_sort (GdaLdapClass *lcl1, GdaLdapClass *lcl2)
+{
+	return g_ascii_strcasecmp (lcl1->names[0], lcl2->names[0]);
+}
+
+static void
+classes_h_func (GdaLdapClass *lcl, gchar **supclasses, LdapConnectionData *cdata)
+{
+	gint i;
+	for (i = 0; supclasses [i]; i++) {
+		GdaLdapClass *parent;
+		gchar *clname = supclasses [i];
+#ifdef CLASS_DEBUG
+		g_print ("class [%s] inherits [%s]\n", lcl->names[0], clname);
+#endif
+		parent = g_hash_table_lookup (cdata->classes_hash, clname);
+		if (!parent)
+			continue;
+		lcl->parents = g_slist_insert_sorted (lcl->parents, parent, (GCompareFunc) classes_sort);
+		parent->children = g_slist_insert_sorted (parent->children, lcl, (GCompareFunc) classes_sort);
+	}
+	if ((i == 0) && !g_slist_find (cdata->top_classes, lcl))
+		cdata->top_classes = g_slist_insert_sorted (cdata->top_classes, lcl, (GCompareFunc) classes_sort);
+}
+
+/*
+ * _gda_ldap_get_top_classes
+ */
+const GSList *
+gdaprov_ldap_get_top_classes (GdaLdapConnection *cnc)
+{
+	LdapConnectionData *cdata;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+		return NULL;
+	if (! cdata->classes_hash) {
+		/* force classes init */
+		GdaLdapClass *lcl;
+		lcl = gdaprov_ldap_get_class_info (cnc, "top");
+	}
+	return cdata->top_classes;
+}
+
+/*
+ * gda_ldap_get_g_type
+ *
+ * Compute the GType either from a specified GType of from the attribute name (giving precedence
+ * to the specified GType if any)
+ */
+GType
+gda_ldap_get_g_type (LdapConnectionData *cdata, const gchar *attribute_name, const gchar *specified_gtype)
+{
+	GType coltype = GDA_TYPE_NULL;
+	if (specified_gtype)
+		coltype = gda_g_type_from_string (specified_gtype);
+	if (coltype == GDA_TYPE_NULL) {
+		LdapAttribute *lat;
+		lat = gda_ldap_get_attr_info (cdata, attribute_name);
+		if (lat)
+			coltype = lat->type->gtype;
+	}
+	if (coltype == GDA_TYPE_NULL)
+		coltype = G_TYPE_STRING;
+	return coltype;
+}
+
+/*
+ * gda_ldap_attr_value_to_g_value:
+ * Converts a #BerValue to a new #GValue
+ *
+ * Returns: a new #GValue, or %NULL on error
+ */
+GValue *
+gda_ldap_attr_value_to_g_value (LdapConnectionData *cdata, GType type, BerValue *bv)
+{
+	GValue *value = NULL;
+	if ((type == GDA_TYPE_TIMESTAMP) ||
+	    (type == G_TYPE_DATE)) {
+		/* see ftp://ftp.rfc-editor.org/in-notes/rfc4517.txt,
+		 * section 3.3.13: Generalized Time
+		 */
+		GTimeVal tv;
+		gboolean conv;
+		if (! (conv = g_time_val_from_iso8601 (bv->bv_val,
+						       &tv))) {
+			/* Add the 'T' char */
+			gchar *tmp, *str;
+			gint i, len;
+			str = bv->bv_val;
+			len = strlen (str);
+			if (len > 8) {
+				tmp = g_new (gchar, len + 2);
+				for (i = 0; i < 8; i++)
+					tmp[i] = str[i];
+				tmp [8] = 'T';
+				for (i = 9; str[i]; i++)
+					tmp[i] = str[i-1];
+				tmp[i] = 0;
+				conv = g_time_val_from_iso8601 (tmp, &tv);
+				g_free (tmp);
+			}
+		}
+		if (conv) {
+			struct tm *ptm;
+			ptm = localtime (&(tv.tv_sec));
+			if (type == GDA_TYPE_TIMESTAMP) {
+				GdaTimestamp ts;
+				ts.year = ptm->tm_year + 1900;
+				ts.month = ptm->tm_mon + 1;
+				ts.day = ptm->tm_mday;
+				ts.hour = ptm->tm_hour;
+				ts.minute = ptm->tm_min;
+				ts.second = ptm->tm_sec;
+				ts.timezone = GDA_TIMEZONE_INVALID;
+				value = gda_value_new (type);
+				gda_value_set_timestamp (value, &ts);
+			}
+			else {
+				GDate *date;
+				date = g_date_new ();
+				g_date_set_time_val (date, &tv);
+				value = gda_value_new (type);
+				g_value_take_boxed (value, date);
+			}
+		}
+	}
+	else if (type == GDA_TYPE_BINARY) {
+		GdaBinary *bin;
+		bin = g_new (GdaBinary, 1);
+		bin->data = g_new (guchar, bv->bv_len);
+
+		bin->binary_length = bv->bv_len;
+		memcpy (bin->data, bv->bv_val,
+			sizeof (gchar) * bin->binary_length);
+		value = gda_value_new (GDA_TYPE_BINARY);
+		gda_value_take_binary (value, bin);
+	}
+	else
+		value = gda_value_new_from_string (bv->bv_val, type);
+
+	return value;
+}
+
+/*
+ * make sure we respect http://www.faqs.org/rfcs/rfc2253.html
+ */
+static gchar *
+rewrite_dn_component (const char *str, guint len)
+{
+	guint i;
+	gint nbrewrite = 0;
+	for (i = 0; i < len; i++) {
+		// "," / "=" / "+" / "<" /  ">" / "#" / ";"
+		if ((str[i] == ',') || (str[i] == '=') || (str[i] == '+') ||
+		    (str[i] == '<') || (str[i] == '>') || (str[i] == '#') || (str[i] == ';'))
+			nbrewrite++;
+	}
+	if (nbrewrite == 0)
+		return NULL;
+
+	gchar *tmp, *ptr;
+	tmp = g_new (gchar, len + 2*nbrewrite + 1);
+	for (i = 0, ptr = tmp; i < len; i++) {
+		if ((str[i] == ',') || (str[i] == '=') || (str[i] == '+') ||
+		    (str[i] == '<') || (str[i] == '>') || (str[i] == '#') || (str[i] == ';')) {
+			int t;
+			*ptr = '\\';
+			ptr++;
+			t = str[i] / 16;
+			if (t < 10)
+				*ptr = '0' + t;
+			else
+				*ptr = 'A' + t - 10;
+			ptr++;
+			t = str[i] % 16;
+			if (t < 10)
+				*ptr = '0' + t;
+			else
+				*ptr = 'A' + t - 10;
+		}
+		else
+			*ptr = str[i];
+		ptr++;
+	}
+	*ptr = 0;
+	return tmp;
+}
+
+/**
+ * _gda_Rdn2str:
+ *
+ * Returns: a new string
+ */
+static gchar *
+_gda_Rdn2str (LDAPRDN rdn)
+{
+	if (!rdn)
+		return NULL;
+	gint i;
+	GString *string = NULL;
+	for (i = 0; rdn[i]; i++) {
+		LDAPAVA *ava = rdn [i];
+		if (g_utf8_validate (ava->la_attr.bv_val, ava->la_attr.bv_len, NULL) &&
+		    g_utf8_validate (ava->la_value.bv_val, ava->la_value.bv_len, NULL)) {
+			gchar *tmp;
+			if (string)
+				g_string_append_c (string, '+');
+			else
+				string = g_string_new ("");
+
+			/* attr name */
+			tmp = rewrite_dn_component (ava->la_attr.bv_val, ava->la_attr.bv_len);
+			if (tmp) {
+				g_string_append (string, tmp);
+				g_free (tmp);
+			}
+			else
+				g_string_append_len (string, ava->la_attr.bv_val, ava->la_attr.bv_len);
+			g_string_append_c (string, '=');
+
+			/* attr value */
+			tmp = rewrite_dn_component (ava->la_value.bv_val, ava->la_value.bv_len);
+			if (tmp) {
+				g_string_append (string, tmp);
+				g_free (tmp);
+			}
+			else
+				g_string_append_len (string, ava->la_value.bv_val, ava->la_value.bv_len);
+		}
+		else {
+			if (string) {
+				g_string_free (string, TRUE);
+				return NULL;
+			}
+		}
+	}
+	return g_string_free (string, FALSE);
+}
+
+
+/**
+ * _gda_dn2str:
+ *
+ * Returns: a new string
+ */
+static gchar *
+_gda_dn2str (LDAPDN dn)
+{
+	if (!dn)
+		return NULL;
+
+	gint i;
+	GString *string = NULL;
+	for (i = 0; dn[i]; i++) {
+		LDAPRDN rdn = dn[i];
+		gchar *tmp;
+		tmp = _gda_Rdn2str (rdn);
+		if (tmp) {
+			if (string)
+				g_string_append_c (string, ',');
+			else
+				string = g_string_new ("");
+			g_string_append (string, tmp);
+			g_free (tmp);
+		}
+		else {
+			if (string) {
+				g_string_free (string, TRUE);
+				return NULL;
+			}
+		}
+	}
+	return g_string_free (string, FALSE);
+}
+
+/*
+ * parse_dn
+ *
+ * Parse and reconstruct @attr if @out_userdn is not %NULL
+ *
+ * Returns: %TRUE if all OK
+ */
+gboolean
+gda_ldap_parse_dn (const char *attr, gchar **out_userdn)
+{
+	LDAPDN tmpDN;
+
+	if (out_userdn)
+		*out_userdn = NULL;
+
+	/* decoding */
+	if (ldap_str2dn (attr, &tmpDN, LDAP_DN_FORMAT_LDAPV3) != LDAP_SUCCESS) {
+		if (ldap_str2dn (attr, &tmpDN, LDAP_DN_FORMAT_LDAPV2) != LDAP_SUCCESS) {
+			if (ldap_str2dn (attr, &tmpDN, LDAP_DN_FORMAT_DCE) != LDAP_SUCCESS)
+				return FALSE;
+		}
+	}
+
+	if (out_userdn) {
+		gchar *userdn;
+		userdn = _gda_dn2str (tmpDN);
+		ldap_dnfree (tmpDN);
+		if (userdn)
+			*out_userdn = userdn;
+		else
+			return FALSE;
+	}
+	else
+		ldap_dnfree (tmpDN);
+
+	return TRUE;
+}
+
+/*
+ * Proxy functions
+ */
+
+static gint
+attr_array_sort_func (gconstpointer a, gconstpointer b)
+{
+	GdaLdapAttribute *att1, *att2;
+	att1 = *((GdaLdapAttribute**) a);
+	att2 = *((GdaLdapAttribute**) b);
+	return strcmp (att1->attr_name, att2->attr_name);
+}
+
+GdaLdapEntry *
+gdaprov_ldap_describe_entry (GdaLdapConnection *cnc, const gchar *dn, GError **error)
+{
+	LdapConnectionData *cdata;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	g_return_val_if_fail (!dn || (dn && *dn), NULL);
+
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+                return NULL;
+
+	int res;
+	LDAPMessage *msg = NULL;
+	const gchar *real_dn;
+	real_dn = dn ? dn : cdata->base_dn;
+ retry:
+	res = ldap_search_ext_s (cdata->handle, real_dn, LDAP_SCOPE_BASE,
+				 "(objectClass=*)", NULL, 0,
+				 NULL, NULL, NULL, -1,
+				 &msg);
+	switch (res) {
+	case LDAP_SUCCESS:
+	case LDAP_NO_SUCH_OBJECT: {
+		gint nb_entries;
+		LDAPMessage *ldap_row;
+		char *attr;
+		BerElement* ber;
+		GdaLdapEntry *lentry;
+		GArray *array = NULL;
+
+		nb_entries = ldap_count_entries (cdata->handle, msg);
+		if (nb_entries == 0) {
+			ldap_msgfree (msg);
+			return NULL;
+		}
+		else if (nb_entries > 1) {
+			g_set_error (error, 0, 0,
+				     _("LDAP server returned more than one entry with DN '%s'"), real_dn);
+			return NULL;
+		}
+
+		lentry = g_new0 (GdaLdapEntry, 1);
+		lentry->dn = g_strdup (real_dn);
+		lentry->attributes_hash = g_hash_table_new (g_str_hash, g_str_equal);
+		array = g_array_new (TRUE, FALSE, sizeof (GdaLdapAttribute*));
+		ldap_row = ldap_first_entry (cdata->handle, msg);
+		for (attr = ldap_first_attribute (cdata->handle, ldap_row, &ber);
+		     attr;
+		     attr = ldap_next_attribute (cdata->handle, ldap_row, ber)) {
+			BerValue **bvals;
+			GArray *varray = NULL;
+			bvals = ldap_get_values_len (cdata->handle, ldap_row, attr);
+			if (bvals) {
+				gint i;
+				for (i = 0; bvals [i]; i++) {
+					if (!varray)
+						varray = g_array_new (TRUE, FALSE, sizeof (GValue *));
+					GValue *value;
+					GType type;
+					type = gda_ldap_get_g_type (cdata, attr, NULL);
+					/*g_print ("Type for attr %s is %s\n", attr, gda_g_type_to_string (type)); */
+					value = gda_ldap_attr_value_to_g_value (cdata, type, bvals[i]);
+					g_array_append_val (varray, value);
+				}
+				ldap_value_free_len (bvals);
+			}
+			if (varray) {
+				GdaLdapAttribute *lattr = NULL;
+				lattr = g_new0 (GdaLdapAttribute, 1);
+				lattr->attr_name = g_strdup (attr);
+				lattr->values = (GValue**) varray->data;
+				lattr->nb_values = varray->len;
+				g_array_free (varray, FALSE);
+
+				g_array_append_val (array, lattr);
+				g_hash_table_insert (lentry->attributes_hash, lattr->attr_name, lattr);
+			}
+			ldap_memfree (attr);
+		}
+		if (ber)
+			ber_free (ber, 0);
+		ldap_msgfree (msg);
+		if (array) {
+			g_array_sort (array, (GCompareFunc) attr_array_sort_func);
+			lentry->attributes = (GdaLdapAttribute**) array->data;
+			lentry->nb_attributes = array->len;
+			g_array_free (array, FALSE);
+		}
+		return lentry;
+	}
+	case LDAP_SERVER_DOWN: {
+		gint i;
+		for (i = 0; i < 5; i++) {
+			if (gda_ldap_silently_rebind (cdata))
+				goto retry;
+			g_usleep (G_USEC_PER_SEC * 2);
+		}
+	}
+	default: {
+		/* error */
+		int ldap_errno;
+		ldap_get_option (cdata->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_OTHER_ERROR,
+			     "%s", ldap_err2string(ldap_errno));
+		return NULL;
+	}
+	}
+}
+
+static gint
+entry_array_sort_func (gconstpointer a, gconstpointer b)
+{
+	GdaLdapEntry *e1, *e2;
+	e1 = *((GdaLdapEntry**) a);
+	e2 = *((GdaLdapEntry**) b);
+	return strcmp (e2->dn, e1->dn);
+}
+
+GdaLdapEntry **
+gdaprov_ldap_get_entry_children (GdaLdapConnection *cnc, const gchar *dn, gchar **attributes, GError **error)
+{
+	LdapConnectionData *cdata;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+	g_return_val_if_fail (!dn || (dn && *dn), NULL);
+
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+                return NULL;
+
+	int res;
+	LDAPMessage *msg = NULL;
+ retry:
+	res = ldap_search_ext_s (cdata->handle, dn ? dn : cdata->base_dn, LDAP_SCOPE_ONELEVEL,
+				 "(objectClass=*)", attributes, 0,
+				 NULL, NULL, NULL, -1,
+				 &msg);
+
+	switch (res) {
+	case LDAP_SUCCESS:
+	case LDAP_NO_SUCH_OBJECT: {
+		LDAPMessage *ldap_row;
+		GArray *children;
+
+		children = g_array_new (TRUE, FALSE, sizeof (GdaLdapEntry *));
+		for (ldap_row = ldap_first_entry (cdata->handle, msg);
+		     ldap_row;
+		     ldap_row = ldap_next_entry (cdata->handle, ldap_row)) {
+			char *attr;
+			GdaLdapEntry *lentry = NULL;
+			attr = ldap_get_dn (cdata->handle, ldap_row);
+			if (attr) {
+				gchar *userdn = NULL;
+				if (gda_ldap_parse_dn (attr, &userdn)) {
+					lentry = g_new0 (GdaLdapEntry, 1);
+					lentry->dn = userdn;
+				}
+				ldap_memfree (attr);
+			}
+			
+			if (!lentry) {
+				gint i;
+				for (i = 0; i < children->len; i++) {
+					GdaLdapEntry *lentry;
+					lentry = g_array_index (children, GdaLdapEntry*, i);
+					gda_ldap_entry_free (lentry);
+				}
+				g_array_free (children, TRUE);
+				children = NULL;
+				g_set_error (error, 0, 0,
+					     _("Could not parse distinguished name returned by LDAP server"));
+				break;
+			}
+			else if (attributes) {
+				BerElement* ber;
+				GArray *array; /* array of GdaLdapAttribute pointers */
+				lentry->attributes_hash = g_hash_table_new (g_str_hash, g_str_equal);
+				array = g_array_new (TRUE, FALSE, sizeof (GdaLdapAttribute*));
+				for (attr = ldap_first_attribute (cdata->handle, ldap_row, &ber);
+				     attr;
+				     attr = ldap_next_attribute (cdata->handle, ldap_row, ber)) {
+					BerValue **bvals;
+					GArray *varray = NULL;
+					bvals = ldap_get_values_len (cdata->handle, ldap_row, attr);
+					if (bvals) {
+						gint i;
+						for (i = 0; bvals [i]; i++) {
+							if (!varray)
+								varray = g_array_new (TRUE, FALSE, sizeof (GValue *));
+							GValue *value;
+							GType type;
+							type = gda_ldap_get_g_type (cdata, attr, NULL);
+							/*g_print ("%d Type for attr %s is %s\n", i, attr, gda_g_type_to_string (type));*/
+							value = gda_ldap_attr_value_to_g_value (cdata, type, bvals[i]);
+							g_array_append_val (varray, value);
+						}
+						ldap_value_free_len (bvals);
+					}
+					if (varray) {
+						GdaLdapAttribute *lattr = NULL;
+						lattr = g_new0 (GdaLdapAttribute, 1);
+						lattr->attr_name = g_strdup (attr);
+						lattr->values = (GValue**) varray->data;
+						lattr->nb_values = varray->len;
+						g_array_free (varray, FALSE);
+						
+						g_array_append_val (array, lattr);
+						g_hash_table_insert (lentry->attributes_hash, lattr->attr_name, lattr);
+					}
+					ldap_memfree (attr);
+				}
+				if (ber)
+					ber_free (ber, 0);
+				if (array) {
+					g_array_sort (array, (GCompareFunc) attr_array_sort_func);
+					lentry->attributes = (GdaLdapAttribute**) array->data;
+					lentry->nb_attributes = array->len;
+					g_array_free (array, FALSE);
+				}
+			}
+			g_array_append_val (children, lentry);
+		}
+		ldap_msgfree (msg);
+		if (children) {
+			g_array_sort (children, (GCompareFunc) entry_array_sort_func);
+			return (GdaLdapEntry**) g_array_free (children, FALSE);
+		}
+		else
+			return NULL;
+	}
+	case LDAP_SERVER_DOWN: {
+		gint i;
+		for (i = 0; i < 5; i++) {
+			if (gda_ldap_silently_rebind (cdata))
+				goto retry;
+			g_usleep (G_USEC_PER_SEC * 2);
+		}
+	}
+	default: {
+		/* error */
+		int ldap_errno;
+		ldap_get_option (cdata->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+		g_set_error (error, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_OTHER_ERROR,
+			     "%s", ldap_err2string(ldap_errno));
+		return NULL;
+	}
+	}
+}
+
+gchar **
+gdaprov_ldap_dn_split (const gchar *dn, gboolean all)
+{
+	LDAPDN tmpDN;
+	GArray *array;
+	gint imax = G_MAXINT;
+
+	g_return_val_if_fail (dn && *dn, NULL);
+
+	/*g_print ("%s (%s, %d)\n", __FUNCTION__, dn, all);*/
+
+	/* decoding */
+	if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_LDAPV3) != LDAP_SUCCESS) {
+		if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_LDAPV2) != LDAP_SUCCESS) {
+			if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_DCE) != LDAP_SUCCESS)
+				return NULL;
+		}
+	}
+
+	/* encoding */
+	array = g_array_new (TRUE, FALSE, sizeof (gchar*));
+	if (!all)
+		imax = 1;
+
+	gint i;
+	LDAPRDN *rdn = tmpDN;
+	for (i = 0; rdn [i] && (i < imax); i++) {
+		gchar *tmp;
+		tmp = _gda_Rdn2str (rdn [i]);
+		if (tmp) {
+			g_array_append_val (array, tmp);
+			/*g_print ("\t[%s]\n", value);*/
+		}
+		else
+			goto onerror;
+	}
+
+	if (!all && (i == 1) && rdn [1]) {
+		gchar *tmp;
+		tmp = _gda_dn2str (rdn+1);
+		if (tmp) {
+			g_array_append_val (array, tmp);
+			/*g_print ("\t[%s]\n", value);*/
+		}
+		else
+			goto onerror;
+	}
+	ldap_dnfree (tmpDN);
+
+	return (gchar**) g_array_free (array, FALSE);
+
+ onerror:
+	for (i = 0; i < array->len; i++) {
+		gchar *tmp;
+		tmp = g_array_index (array, gchar*, i);
+		g_free (tmp);
+	}
+	g_array_free (array, TRUE);
+	return NULL;
+}
+
+gboolean
+gdaprov_ldap_is_dn (const gchar *dn)
+{
+	LDAPDN tmpDN;
+
+	g_return_val_if_fail (dn && *dn, FALSE);
+	if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_LDAPV3) != LDAP_SUCCESS) {
+		if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_LDAPV2) != LDAP_SUCCESS) {
+			if (ldap_str2dn (dn, &tmpDN, LDAP_DN_FORMAT_DCE) != LDAP_SUCCESS)
+				return FALSE;
+		}
+	}
+	ldap_dnfree (tmpDN);
+	return TRUE;
+}
+
+const gchar *
+gdaprov_ldap_get_base_dn (GdaLdapConnection *cnc)
+{
+	LdapConnectionData *cdata;
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+
+        cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+        if (!cdata)
+                return NULL;
+	else
+		return cdata->base_dn;
+}
diff --git a/providers/ldap/gda-ldap-util.h b/providers/ldap/gda-ldap-util.h
new file mode 100644
index 0000000..57bd095
--- /dev/null
+++ b/providers/ldap/gda-ldap-util.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Some data copied from GQ's sources and transformed
+ */
+
+#ifndef __GDA_LDAP_UTIL_H__
+#define __GDA_LDAP_UTIL_H__
+
+#include <glib.h>
+#include "gda-ldap.h"
+
+/*
+ * Attributes
+ */
+typedef struct {
+	gchar *oid;
+	gchar *descr;
+	GType  gtype;
+} LdapAttrType;
+
+typedef struct {
+	gchar        *name;
+	LdapAttrType *type; /* never NULL */
+	gboolean      single_value;
+} LdapAttribute;
+
+LdapAttrType  *gda_ldap_get_type_info (const gchar *oid);
+LdapAttribute *gda_ldap_get_attr_info (LdapConnectionData *cdata, const gchar *attribute);
+GType          gda_ldap_get_g_type    (LdapConnectionData *cdata, const gchar *attribute, const gchar *specified_gtype);
+
+/*
+ * Misc.
+ */
+GValue        *gda_ldap_attr_value_to_g_value (LdapConnectionData *cdata, GType type, BerValue *bv);
+gboolean       gda_ldap_parse_dn (const char *attr, gchar **out_userdn);
+
+#endif
diff --git a/providers/ldap/gda-ldap.h b/providers/ldap/gda-ldap.h
new file mode 100644
index 0000000..8a2fe13
--- /dev/null
+++ b/providers/ldap/gda-ldap.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __GDA_LDAP_H__
+#define __GDA_LDAP_H__
+
+/*
+ * Provider name
+ */
+#define LDAP_PROVIDER_NAME "Ldap"
+
+#include <ldap.h>
+#include <ldap_schema.h>
+#include <glib.h>
+
+/*
+ * Provider's specific connection data
+ */
+typedef struct {
+	LDAP         *handle;
+	gchar        *base_dn;
+	gchar        *server_version;
+	gchar        *url;
+	gchar        *user;
+	gchar        *pass;
+
+	GHashTable   *attributes_hash; /* key = attribute name, value = a #LdapAttribute */
+	gchar        *attributes_cache_file;
+
+	GSList       *top_classes; /* list of #LdapClass (no ref held) which have no parent */
+	GHashTable   *classes_hash; /* key = class name, value = a #LdapClass */
+} LdapConnectionData;
+
+gboolean gda_ldap_silently_rebind (LdapConnectionData *cdata);
+
+#endif
diff --git a/providers/ldap/gdaprov-data-model-ldap.c b/providers/ldap/gdaprov-data-model-ldap.c
new file mode 100644
index 0000000..b65037f
--- /dev/null
+++ b/providers/ldap/gdaprov-data-model-ldap.c
@@ -0,0 +1,1447 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <libgda/gda-data-model-ldap.h>
+#include <libgda/gda-connection.h>
+#include <libgda/gda-data-model-iter.h>
+#include <libgda/gda-holder.h>
+#include <libgda/gda-util.h>
+#include <libgda/sqlite/virtual/gda-virtual-connection.h>
+#include <libgda/sqlite/virtual/gda-ldap-connection.h>
+#include "gda-ldap.h"
+#include "gda-ldap-util.h"
+#include "gdaprov-data-model-ldap.h"
+
+#define GDA_DEBUG_SUBSEARCHES
+#undef GDA_DEBUG_SUBSEARCHES
+
+/*
+ * What to do in case of a multi value in a cell
+ */
+typedef enum {
+	MULTIPLE_VALUE_ACTION_SET_NULL,
+	MULTIPLE_VALUE_ACTION_CSV_STRING,
+	MULTIPLE_VALUE_ACTION_MULTIPLY,
+	MULTIPLE_VALUE_ACTION_SET_INVALID,
+	MULTIPLE_VALUE_ACTION_FIRST,
+	MULTIPLE_VALUE_ACTION_CONCAT
+} MultipleValueAction;
+
+typedef struct _LdapPart LdapPart;
+struct _LdapPart {
+	gchar           *base_dn;
+	GdaLdapSearchScope scope;
+	gboolean         executed; /* %TRUE if @ldap_msg of @children have already had the
+				    * opportunity of being computed */
+
+	/* result of execution, both %NULL if data was truncated when executing */
+	LDAPMessage     *ldap_msg;
+	gint             nb_entries;
+	LDAPMessage     *ldap_row; /* no ref! */
+
+	/* tree-like structure */
+	GSList          *children; /* list of #LdapPart, ref held there */
+	LdapPart        *parent; /* no ref held */
+};
+#define LDAP_PART(x) ((LdapPart*)(x))
+
+static LdapPart *ldap_part_new (LdapPart *parent, const gchar *base_dn, GdaLdapSearchScope scope);
+static void ldap_part_free (LdapPart *part);
+static gboolean ldap_part_split (LdapPart *part, GdaDataModelLdap *model, gboolean *out_error);
+static LdapPart *ldap_part_next (LdapPart *part, gboolean executed);
+#ifdef GDA_DEBUG_SUBSEARCHES
+static void ldap_part_dump (LdapPart *part);
+#endif
+
+static void add_exception (GdaDataModelLdap *model, GError *e);
+
+typedef struct {
+	GdaHolder *holder;
+	gint       index;
+	GArray    *values; /* array of #GValue, or %NULL on error */
+} ColumnMultiplier;
+static ColumnMultiplier *column_multiplier_new (GdaHolder *holder,
+						const GValue *value);
+
+typedef struct {
+	GArray *cms; /* array of ColumnMultiplier pointers, stores in reverse order */
+} RowMultiplier;
+static RowMultiplier *row_multiplier_new (void);
+static gboolean row_multiplier_index_next (RowMultiplier *rm);
+static void row_multiplier_free (RowMultiplier *rm);
+static void row_multiplier_set_holders (RowMultiplier *rm);
+
+struct _GdaDataModelLdapPrivate {
+	GdaConnection      *cnc;
+	gchar              *base_dn;
+	gchar              *filter;
+	GArray             *attributes;
+	GdaLdapSearchScope  scope;
+	MultipleValueAction default_mv_action;
+	GList              *columns;
+	GArray             *column_mv_actions; /* array of #MultipleValueAction, notincluding column 0 */
+	gint                n_columns; /* length of @columns */
+	gint                n_rows;
+	gboolean            truncated;
+
+	gint                iter_row;
+	LdapPart           *top_exec; /* ref held */
+	LdapPart           *current_exec; /* no ref held, only a pointer in the @top_exec tree */
+
+	RowMultiplier      *row_mult;
+
+	GArray             *exceptions; /* array of GError pointers */
+};
+
+/* properties */
+enum {
+	PROP_0,
+	PROP_CNC,
+	PROP_BASE,
+	PROP_FILTER,
+	PROP_ATTRIBUTES,
+	PROP_SCOPE
+};
+
+static void gda_data_model_ldap_class_init (GdaDataModelLdapClass *klass);
+static void gda_data_model_ldap_init       (GdaDataModelLdap *model,
+					    GdaDataModelLdapClass *klass);
+static void gda_data_model_ldap_dispose    (GObject *object);
+
+static void gda_data_model_ldap_set_property (GObject *object,
+					      guint param_id,
+					      const GValue *value,
+					      GParamSpec *pspec);
+static void gda_data_model_ldap_get_property (GObject *object,
+					      guint param_id,
+					      GValue *value,
+					      GParamSpec *pspec);
+
+static GList *_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes,
+				     GArray **out_attrs_array,
+				     MultipleValueAction default_mva, GArray **out_mv_actions);
+
+/* GdaDataModel interface */
+static void                 gda_data_model_ldap_data_model_init (GdaDataModelIface *iface);
+static gint                 gda_data_model_ldap_get_n_rows      (GdaDataModel *model);
+static gint                 gda_data_model_ldap_get_n_columns   (GdaDataModel *model);
+static GdaColumn           *gda_data_model_ldap_describe_column (GdaDataModel *model, gint col);
+static GdaDataModelAccessFlags gda_data_model_ldap_get_access_flags(GdaDataModel *model);
+static gboolean             gda_data_model_ldap_iter_next       (GdaDataModel *model, GdaDataModelIter *iter);
+static GdaValueAttribute    gda_data_model_ldap_get_attributes_at (GdaDataModel *model, gint col, gint row);
+static GError             **gda_data_model_ldap_get_exceptions  (GdaDataModel *model);
+
+static GObjectClass *parent_class = NULL;
+#define CLASS(model) (GDA_DATA_MODEL_LDAP_CLASS (G_OBJECT_GET_CLASS (model)))
+
+/*
+ * Object init and dispose
+ */
+static void
+gda_data_model_ldap_data_model_init (GdaDataModelIface *iface)
+{
+        iface->i_get_n_rows = gda_data_model_ldap_get_n_rows;
+        iface->i_get_n_columns = gda_data_model_ldap_get_n_columns;
+        iface->i_describe_column = gda_data_model_ldap_describe_column;
+        iface->i_get_access_flags = gda_data_model_ldap_get_access_flags;
+        iface->i_get_value_at = NULL;
+        iface->i_get_attributes_at = gda_data_model_ldap_get_attributes_at;
+
+        iface->i_create_iter = NULL;
+        iface->i_iter_at_row = NULL;
+        iface->i_iter_next = gda_data_model_ldap_iter_next;
+        iface->i_iter_prev = NULL;
+
+        iface->i_set_value_at = NULL;
+	iface->i_iter_set_value = NULL;
+        iface->i_set_values = NULL;
+        iface->i_append_values = NULL;
+        iface->i_append_row = NULL;
+        iface->i_remove_row = NULL;
+        iface->i_find_row = NULL;
+
+        iface->i_set_notify = NULL;
+        iface->i_get_notify = NULL;
+        iface->i_send_hint = NULL;
+
+	iface->i_get_exceptions = gda_data_model_ldap_get_exceptions;
+}
+
+static void
+gda_data_model_ldap_init (GdaDataModelLdap *model,
+			  G_GNUC_UNUSED GdaDataModelLdapClass *klass)
+{
+	GdaColumn *col;
+
+	g_return_if_fail (GDA_IS_DATA_MODEL_LDAP (model));
+
+	model->priv = g_new0 (GdaDataModelLdapPrivate, 1);
+	model->priv->cnc = NULL;
+	model->priv->filter = g_strdup ("(objectClass=*)");
+	model->priv->iter_row = -1;
+	model->priv->default_mv_action = MULTIPLE_VALUE_ACTION_SET_INVALID;
+	model->priv->top_exec = NULL;
+	model->priv->current_exec = NULL;
+	model->priv->attributes = NULL;
+	model->priv->truncated = FALSE;
+	model->priv->exceptions = NULL;
+	model->priv->row_mult = NULL;
+
+	/* add the "dn" column */
+	col = gda_column_new ();
+	gda_column_set_name (col, "dn");
+	gda_column_set_g_type (col, G_TYPE_STRING);
+	gda_column_set_allow_null (col, FALSE);
+	gda_column_set_description (col, _("Distinguished name"));
+	model->priv->columns = g_list_prepend (NULL, col);
+	model->priv->column_mv_actions = g_array_new (FALSE, FALSE, sizeof (MultipleValueAction));
+	
+	model->priv->n_columns = g_list_length (model->priv->columns);
+	model->priv->scope = GDA_LDAP_SEARCH_BASE;
+}
+
+static void
+gda_data_model_ldap_class_init (GdaDataModelLdapClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* properties */
+        object_class->set_property = gda_data_model_ldap_set_property;
+        object_class->get_property = gda_data_model_ldap_get_property;
+        g_object_class_install_property (object_class, PROP_CNC,
+                                         g_param_spec_object ("cnc", NULL, "LDAP connection",
+							      GDA_TYPE_LDAP_CONNECTION,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class, PROP_BASE,
+                                         g_param_spec_string ("base", NULL, "Base DN", NULL,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class, PROP_FILTER,
+                                         g_param_spec_string ("filter", NULL, "LDAP filter", NULL,
+                                                              G_PARAM_READABLE | G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class, PROP_ATTRIBUTES,
+                                         g_param_spec_string ("attributes", NULL, "LDAP attributes", NULL,
+                                                              G_PARAM_WRITABLE |
+                                                              G_PARAM_CONSTRUCT_ONLY));
+	g_object_class_install_property (object_class, PROP_SCOPE,
+                                         g_param_spec_int ("scope", NULL, "LDAP search scope",
+							   GDA_LDAP_SEARCH_BASE,
+							   GDA_LDAP_SEARCH_SUBTREE,
+							   GDA_LDAP_SEARCH_BASE,
+							   G_PARAM_WRITABLE | G_PARAM_READABLE |
+							   G_PARAM_CONSTRUCT_ONLY));
+
+	/* virtual functions */
+	object_class->dispose = gda_data_model_ldap_dispose;
+}
+
+static void
+gda_data_model_ldap_dispose (GObject * object)
+{
+	GdaDataModelLdap *model = (GdaDataModelLdap *) object;
+
+	g_return_if_fail (GDA_IS_DATA_MODEL_LDAP (model));
+
+	if (model->priv) {
+		if (model->priv->row_mult)
+			row_multiplier_free (model->priv->row_mult);
+		if (model->priv->cnc)
+			g_object_unref (model->priv->cnc);
+		if (model->priv->columns) {
+                        g_list_foreach (model->priv->columns, (GFunc) g_object_unref, NULL);
+                        g_list_free (model->priv->columns);
+                        model->priv->columns = NULL;
+                }
+		if (model->priv->attributes) {
+			gint i;
+			for (i = 0; i < model->priv->attributes->len; i++) {
+				gchar *tmp;
+				tmp = g_array_index (model->priv->attributes, gchar*, i);
+				g_free (tmp);
+			}
+			g_array_free (model->priv->attributes, TRUE);
+		}
+		if (model->priv->column_mv_actions)
+			g_array_free (model->priv->column_mv_actions, TRUE);
+
+		if (model->priv->top_exec)
+			ldap_part_free (model->priv->top_exec);
+
+		g_free (model->priv->base_dn);
+		g_free (model->priv->filter);
+
+		if (model->priv->exceptions) {
+			gint i; 
+			for (i = 0; i < model->priv->exceptions->len; i++) {
+				GError *e;
+				e = g_array_index (model->priv->exceptions, GError*, i);
+				g_error_free (e);
+			}
+			g_array_free (model->priv->exceptions, TRUE);
+		}
+
+		g_free (model->priv);
+		model->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+gdaprov_data_model_ldap_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static const GTypeInfo info = {
+			sizeof (GdaDataModelLdapClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gda_data_model_ldap_class_init,
+			NULL,
+			NULL,
+			sizeof (GdaDataModelLdap),
+			0,
+			(GInstanceInitFunc) gda_data_model_ldap_init,
+			0
+		};
+		static const GInterfaceInfo data_model_info = {
+                        (GInterfaceInitFunc) gda_data_model_ldap_data_model_init,
+                        NULL,
+                        NULL
+                };
+
+		g_static_mutex_lock (&registering);
+		if (type == 0) {
+			type = g_type_register_static (G_TYPE_OBJECT, "GdaDataModelLdap", &info, 0);
+			g_type_add_interface_static (type, GDA_TYPE_DATA_MODEL, &data_model_info);
+		}
+		g_static_mutex_unlock (&registering);
+	}
+	return type;
+}
+
+static void
+gda_data_model_ldap_set_property (GObject *object,
+				  guint param_id,
+				  const GValue *value,
+				  GParamSpec *pspec)
+{
+        GdaDataModelLdap *model;
+        const gchar *string;
+
+        model = GDA_DATA_MODEL_LDAP (object);
+        if (model->priv) {
+                switch (param_id) {
+                case PROP_CNC: {
+			GdaConnection *cnc;
+			cnc = g_value_get_object (value);
+			if (cnc) {
+				if (g_object_get_data ((GObject*) cnc,
+						       "__gda_connection_LDAP") != (gpointer) 0x01) {
+					g_warning ("cnc is not an LDAP connection");
+					break;
+				}
+				model->priv->cnc = g_object_ref (cnc);
+			}
+			break;
+		}
+                case PROP_BASE:
+			string = g_value_get_string (value);
+			if (string)
+				model->priv->base_dn = g_strdup (string);
+			break;
+		case PROP_FILTER:
+			string = g_value_get_string (value);
+			if (string) {
+				g_free (model->priv->filter);
+				model->priv->filter = g_strdup (string);
+			}
+			break;
+		case PROP_ATTRIBUTES: {
+			const gchar *csv;
+			csv = g_value_get_string (value);
+			if (csv && *csv) {
+				if (model->priv->columns) {
+					g_list_foreach (model->priv->columns, (GFunc) g_object_unref, NULL);
+					g_list_free (model->priv->columns);
+				}
+				if (model->priv->column_mv_actions) {
+					g_array_free (model->priv->column_mv_actions, TRUE);
+					model->priv->column_mv_actions = NULL;
+				}
+
+				model->priv->columns = _ldap_compute_columns (model->priv->cnc, csv,
+									      &model->priv->attributes,
+									      model->priv->default_mv_action,
+									      &model->priv->column_mv_actions);
+				model->priv->n_columns = g_list_length (model->priv->columns);
+			}
+			break;
+		}
+		case PROP_SCOPE:
+			model->priv->scope = g_value_get_int (value);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+			break;
+		}
+	}
+}
+
+static void
+gda_data_model_ldap_get_property (GObject *object,
+				  guint param_id,
+				  GValue *value,
+				  GParamSpec *pspec)
+{
+        GdaDataModelLdap *model;
+
+        model = GDA_DATA_MODEL_LDAP (object);
+        if (model->priv) {
+                switch (param_id) {
+                case PROP_CNC:
+			g_value_set_object (value, model->priv->cnc);
+			break;
+                case PROP_BASE:
+			g_value_set_string (value, model->priv->base_dn);
+			break;
+                case PROP_FILTER:
+			g_value_set_string (value, model->priv->filter);
+			break;
+		case PROP_SCOPE:
+			g_value_set_int (value, model->priv->scope);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+			break;
+		}
+	}
+}
+
+GList *
+gdaprov_data_model_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes)
+{
+	return _ldap_compute_columns (cnc, attributes, NULL, MULTIPLE_VALUE_ACTION_SET_INVALID,
+				      NULL);
+}
+
+/*
+ * _ldap_compute_columns
+ * @cnc: a #GdaConnection
+ * @attributes: a string
+ * @out_attrs_array: a place to store an array of strings (terminated by a %NULL), or %NULL
+ * @default_mva: the default #MultipleValueAction if none speficied
+ * @out_mv_actions: a place to store an array of MultipleValueAction, or %NULL
+ *
+ * Returns: (transfer full) (element-type GdaColumn): a list of #GdaColumn objects
+ */
+static GList *
+_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes,
+		       GArray **out_attrs_array,
+		       MultipleValueAction default_mva, GArray **out_mv_actions)
+{
+	gchar **array;
+	gint i;
+	GdaColumn *col;
+	LdapConnectionData *cdata = NULL;
+	GList *columns = NULL;
+	GArray *attrs = NULL, *mva = NULL;
+	GHashTable *colnames; /* key = column name, 0x01 */
+	colnames = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+	if (out_attrs_array) {
+		attrs = g_array_new (TRUE, FALSE, sizeof (gchar*));
+		*out_attrs_array = attrs;
+	}
+	if (out_mv_actions) {
+		mva = g_array_new (FALSE, FALSE, sizeof (MultipleValueAction));
+		*out_mv_actions = mva;
+	}
+
+	/* always add the DN column */
+	col = gda_column_new ();
+	gda_column_set_name (col, "dn");
+	gda_column_set_g_type (col, G_TYPE_STRING);
+	gda_column_set_allow_null (col, FALSE);
+	gda_column_set_description (col, _("Distinguished name"));
+	columns = g_list_prepend (NULL, col);
+	g_hash_table_insert (colnames, g_strdup ("dn"), (gpointer) 0x01);
+
+	if (!attributes || !*attributes)
+		return columns;
+
+	/* parse input string */
+	if (cnc) {
+		g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (cnc), NULL);
+		cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (cnc));
+	}
+	array = g_strsplit (attributes, ",", 0);
+	for (i = 0; array [i]; i++) {
+		GType coltype = GDA_TYPE_NULL;
+		gchar **sub, *tmp;
+		const gchar *mvaspec = NULL;
+		MultipleValueAction act = default_mva;
+		
+		g_strstrip (array [i]);
+		sub = g_strsplit (array[i], "::", 3);
+		g_strstrip (sub [0]);
+		if (sub [1]) {
+			g_strstrip (sub [1]);
+			if (sub [2]) {
+				g_strstrip (sub [2]);
+				mvaspec = sub [2];
+			}
+		}
+
+		coltype = gda_ldap_get_g_type (cdata, sub [0], sub [1]);
+		tmp = g_strdup (sub [0]);
+		if (attrs)
+			g_array_append_val (attrs, tmp);
+		if (g_hash_table_lookup (colnames, sub [0])) {
+			/* can't have twice the same LDAP attribute */
+			g_strfreev (sub);
+			continue;
+		}
+		col = gda_column_new ();
+		gda_column_set_name (col, sub [0]);
+		g_hash_table_insert (colnames, g_strdup (sub [0]), (gpointer) 0x01);
+		gda_column_set_g_type (col, coltype);
+		gda_column_set_allow_null (col, TRUE);
+		columns = g_list_prepend (columns, col);
+		if (mva) {
+			if (! mvaspec && sub [1] && (gda_g_type_from_string (sub [1]) == G_TYPE_INVALID))
+				mvaspec = sub [1];
+			if (mvaspec) {
+				if ((*mvaspec == '0' && !mvaspec[1]) || !g_ascii_strcasecmp (mvaspec, "null"))
+					act = MULTIPLE_VALUE_ACTION_SET_NULL;
+				else if (!g_ascii_strcasecmp (mvaspec, "csv"))
+					act = MULTIPLE_VALUE_ACTION_CSV_STRING;
+				if ((*mvaspec == '*' && !mvaspec[1]) || !g_ascii_strncasecmp (mvaspec, "mult", 4))
+					act = MULTIPLE_VALUE_ACTION_MULTIPLY;
+				else if (!g_ascii_strcasecmp (mvaspec, "error"))
+					act = MULTIPLE_VALUE_ACTION_SET_INVALID;
+				else if (!strcmp (mvaspec, "1"))
+					act = MULTIPLE_VALUE_ACTION_FIRST;
+				else if (!g_ascii_strcasecmp (mvaspec, "concat"))
+					act = MULTIPLE_VALUE_ACTION_CONCAT;
+			}
+			g_array_append_val (mva, act);
+		}
+		/*g_print ("Defined model column %s (type=>%s) (mva=>%d)\n", array[i],
+		  gda_g_type_to_string (coltype), act);*/
+		g_strfreev (sub);
+	} 
+	g_strfreev (array);
+	g_hash_table_destroy (colnames);
+	return g_list_reverse (columns);
+}
+
+
+/*
+ * _gdaprov_data_model_ldap_new:
+ * @cnc: an LDAP opened connection
+ * @base_dn: the base DN to search on, or %NULL
+ * @filter: an LDAP filter (for example "(objectClass=*)");
+ * @attributes: the list of attributes to fetch, each in the format <attname>[::<GType>] (+CSV,...)
+ * @scope: the search scope
+ *
+ * Creates a new #GdaDataModel object to extract some LDAP contents
+ *
+ * Returns: a new #GdaDataModel
+ */
+GdaDataModel *
+_gdaprov_data_model_ldap_new (GdaConnection *cnc, const gchar *base_dn, const gchar *filter,
+			      const gchar *attributes, GdaLdapSearchScope scope)
+{
+	g_return_val_if_fail (GDA_IS_CONNECTION (cnc), NULL);
+
+	return (GdaDataModel *) g_object_new (GDA_TYPE_DATA_MODEL_LDAP, "cnc", cnc, 
+					      "base", base_dn,
+					      "filter", filter, "attributes", attributes,
+					      "scope", scope,
+					      NULL);
+}
+
+static gint
+gda_data_model_ldap_get_n_rows (GdaDataModel *model)
+{
+	GdaDataModelLdap *imodel = (GdaDataModelLdap *) model;
+
+	g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (imodel), -1);
+	g_return_val_if_fail (imodel->priv != NULL, -1);
+
+	return -1;
+}
+
+static gint
+gda_data_model_ldap_get_n_columns (GdaDataModel *model)
+{
+	GdaDataModelLdap *imodel;
+        g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), 0);
+        imodel = GDA_DATA_MODEL_LDAP (model);
+        g_return_val_if_fail (imodel->priv, 0);
+
+        if (imodel->priv->columns)
+                return imodel->priv->n_columns;
+        else
+                return 0;
+}
+
+static GdaColumn *
+gda_data_model_ldap_describe_column (GdaDataModel *model, gint col)
+{
+	GdaDataModelLdap *imodel;
+        g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), NULL);
+        imodel = GDA_DATA_MODEL_LDAP (model);
+        g_return_val_if_fail (imodel->priv, NULL);
+
+        if (imodel->priv->columns)
+                return g_list_nth_data (imodel->priv->columns, col);
+        else
+                return NULL;
+}
+
+static GdaDataModelAccessFlags
+gda_data_model_ldap_get_access_flags (GdaDataModel *model)
+{
+	GdaDataModelLdap *imodel;
+        GdaDataModelAccessFlags flags;
+
+        g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), 0);
+        imodel = GDA_DATA_MODEL_LDAP (model);
+        g_return_val_if_fail (imodel->priv, 0);
+
+	flags = GDA_DATA_MODEL_ACCESS_CURSOR_FORWARD;
+
+        return flags;
+}
+
+static gchar *
+csv_quote (const gchar *string)
+{
+	gchar *retval, *ptrd;
+	const gchar *ptrs;
+	retval = g_new (gchar, strlen (string) * 2 + 3);
+	*retval = '"';
+	for (ptrd = retval + 1, ptrs = string; *ptrs; ptrs++) {
+		if (*ptrs == '"') {
+			*ptrd = '"';
+			ptrd++;
+		}
+		*ptrd = *ptrs;
+		ptrd++;
+	}
+	*ptrd = '"';
+	ptrd++;
+	*ptrd = 0;
+	return retval;
+}
+
+static void
+update_iter_from_ldap_row (GdaDataModelLdap *imodel, GdaDataModelIter *iter)
+{
+	gboolean update_model;
+	BerElement* ber;
+	char *attr;
+	GdaHolder *holder;
+	gint j, nb;
+	LdapConnectionData *cdata;
+	GSList *holders_set = NULL;
+	cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (imodel->priv->cnc));
+	g_return_if_fail (cdata);
+
+	g_object_get (G_OBJECT (iter), "update-model", &update_model, NULL);
+	g_object_set (G_OBJECT (iter), "update-model", FALSE, NULL);
+		
+	/* column 0 is the DN */
+	holder = GDA_HOLDER (GDA_SET (iter)->holders->data);
+	attr = ldap_get_dn (cdata->handle, imodel->priv->current_exec->ldap_row);
+	if (attr) {
+		gchar *userdn;
+		if (gda_ldap_parse_dn (attr, &userdn)) {
+			gda_holder_set_value_str (holder, NULL, userdn, NULL);
+			g_free (userdn);
+		}
+		else
+			gda_holder_force_invalid (holder);
+		ldap_memfree (attr);
+	}
+	else
+		gda_holder_force_invalid (holder);
+
+	nb = g_slist_length (((GdaSet*) iter)->holders);
+	for (j = 1; j < nb; j++) {
+		holder = (GdaHolder*) (g_slist_nth_data (((GdaSet*) iter)->holders, j));
+		gda_holder_set_value (holder, NULL, NULL);
+	}
+
+	if (imodel->priv->row_mult)
+		goto out;
+
+	for (attr = ldap_first_attribute (cdata->handle, imodel->priv->current_exec->ldap_row, &ber);
+	     attr;
+	     attr = ldap_next_attribute (cdata->handle, imodel->priv->current_exec->ldap_row, ber)) {
+		BerValue **bvals;
+		gboolean holder_added_to_cm = FALSE;
+
+		holder = gda_set_get_holder ((GdaSet*) iter, attr);
+		if (!holder)
+			continue;
+
+		j = g_slist_index (((GdaSet*) iter)->holders, holder);
+
+		bvals = ldap_get_values_len (cdata->handle,
+					     imodel->priv->current_exec->ldap_row, attr);
+		if (bvals) {
+			if (bvals[0] && bvals[1]) {
+				/* multiple values */
+				MultipleValueAction act;
+				act = g_array_index (imodel->priv->column_mv_actions,
+						     MultipleValueAction, j-1);
+				switch (act) {
+				case MULTIPLE_VALUE_ACTION_SET_NULL:
+					gda_holder_set_value (holder, NULL, NULL);
+					break;
+				case MULTIPLE_VALUE_ACTION_CSV_STRING:
+					if ((gda_holder_get_g_type (holder) == G_TYPE_STRING)) {
+						GString *string = NULL;
+						gint i;
+						GValue *value;
+						for (i = 0; bvals[i]; i++) {
+							gchar *tmp;
+							if (string)
+								g_string_append_c (string, ',');
+							else
+								string = g_string_new ("");
+							tmp = csv_quote (bvals[i]->bv_val);
+							g_string_append (string, tmp);
+							g_free (tmp);
+						}
+						value = gda_value_new (G_TYPE_STRING);
+						g_value_take_string (value, string->str);
+						g_string_free (string, FALSE);
+						gda_holder_take_value (holder, value, NULL);
+					}
+					else
+						gda_holder_force_invalid (holder);
+					break;
+				case MULTIPLE_VALUE_ACTION_MULTIPLY: {
+					ColumnMultiplier *cm;
+					if (! imodel->priv->row_mult) {
+						imodel->priv->row_mult = row_multiplier_new ();
+						/* create @cm for the previous columns */
+						GSList *list;
+						for (list = holders_set; list; list = list->next) {
+							GdaHolder *ch;
+							ch = (GdaHolder*) list->data;
+							cm = column_multiplier_new (ch,
+							               gda_holder_get_value (ch));
+							g_array_append_val (imodel->priv->row_mult->cms, cm);
+						}
+					}
+					/* add new @cm */
+					cm = column_multiplier_new (holder, NULL);
+					gint i;
+					for (i = 0; bvals[i]; i++) {
+						GValue *value;
+						value = gda_ldap_attr_value_to_g_value (cdata,
+											gda_holder_get_g_type (holder),
+											bvals[i]);
+						g_array_append_val (cm->values, value); /* value can be %NULL */
+					}
+					g_array_append_val (imodel->priv->row_mult->cms, cm);
+					holder_added_to_cm = TRUE;
+					break;
+				}
+				case MULTIPLE_VALUE_ACTION_FIRST:
+					if ((gda_holder_get_g_type (holder) == G_TYPE_STRING)) {
+						GValue *value;
+						value = gda_value_new (G_TYPE_STRING);
+						g_value_set_string (value, bvals[0]->bv_val);
+						gda_holder_take_value (holder, value, NULL);
+					}
+					else
+						gda_holder_force_invalid (holder);
+					break;
+				case MULTIPLE_VALUE_ACTION_CONCAT:
+					if ((gda_holder_get_g_type (holder) == G_TYPE_STRING)) {
+						GString *string = NULL;
+						gint i;
+						GValue *value;
+						for (i = 0; bvals[i]; i++) {
+							if (string)
+								g_string_append_c (string, '\n');
+							else
+								string = g_string_new ("");
+							g_string_append (string, bvals[i]->bv_val);
+						}
+						value = gda_value_new (G_TYPE_STRING);
+						g_value_take_string (value, string->str);
+						g_string_free (string, FALSE);
+						gda_holder_take_value (holder, value, NULL);
+					}
+					else
+						gda_holder_force_invalid (holder);
+					break;
+				case MULTIPLE_VALUE_ACTION_SET_INVALID:
+				default:
+					gda_holder_force_invalid (holder);
+					break;
+				}
+			}
+			else if (bvals[0]) {
+				/* convert string to the correct type */
+				GValue *value;
+				value = gda_ldap_attr_value_to_g_value (cdata, gda_holder_get_g_type (holder),
+									bvals[0]);
+				if (value)
+					gda_holder_take_value (holder, value, NULL);
+				else
+					gda_holder_force_invalid (holder);
+			}
+			else
+				gda_holder_set_value (holder, NULL, NULL);
+			ldap_value_free_len (bvals);
+		}
+		else
+			gda_holder_set_value (holder, NULL, NULL);
+		ldap_memfree (attr);
+		holders_set = g_slist_prepend (holders_set, holder);
+		if (imodel->priv->row_mult && !holder_added_to_cm) {
+			ColumnMultiplier *cm;
+			cm = column_multiplier_new (holder, gda_holder_get_value (holder));
+			g_array_append_val (imodel->priv->row_mult->cms, cm);
+		}
+	}
+	if (holders_set)
+		g_slist_free (holders_set);
+
+	if (ber)
+		ber_free (ber, 0);
+
+ out:
+	if (imodel->priv->row_mult)
+		row_multiplier_set_holders (imodel->priv->row_mult);
+
+	if (gda_data_model_iter_is_valid (iter)) {
+		imodel->priv->iter_row ++;
+		if ((imodel->priv->iter_row == imodel->priv->n_rows - 1) && imodel->priv->truncated) {
+			GError *e;
+			g_set_error (&e, GDA_DATA_MODEL_ERROR,
+				     GDA_DATA_MODEL_TRUNCATED_ERROR,
+				     _("Truncated result because LDAP server limit encountered"));
+			add_exception (imodel, e);
+		}
+	}
+	else
+		imodel->priv->iter_row = 0;
+
+	g_object_set (G_OBJECT (iter), "current-row", imodel->priv->iter_row,
+		      "update-model", update_model, NULL);
+}
+
+/*
+ * Add an exception to @model
+ * WARNING: steals @e
+ */
+static void
+add_exception (GdaDataModelLdap *model, GError *e)
+{
+	if (!model->priv->exceptions)
+		model->priv->exceptions = g_array_new (TRUE, FALSE, sizeof (GError*));
+	g_array_append_val (model->priv->exceptions, e);
+}
+
+/*
+ * Execute model->priv->current_exec and either:
+ *     - sets model->priv->current_exec->ldap_msg, or
+ *     - create some children and execute one, or
+ *     - sets model->priv->current_exec->ldap_msg to %NULL if an error occurred
+ *
+ * In any case model->priv->current_exec->executed is set to %TRUE and should be %FALSE when entering
+ * the function (ie. for any LdapConnectionData this function has to be called at most once)
+ */
+static void
+execute_ldap_search (GdaDataModelLdap *model)
+{
+	LDAPMessage *msg = NULL;
+	int lscope, res = 0;
+	LdapConnectionData *cdata;
+	cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (model->priv->cnc));
+	g_return_if_fail (cdata);
+
+	g_assert (model->priv->current_exec);
+	g_assert (! model->priv->current_exec->executed);
+
+	switch (model->priv->current_exec->scope) {
+	default:
+	case GDA_LDAP_SEARCH_BASE:
+		lscope = LDAP_SCOPE_BASE;
+		break;
+	case GDA_LDAP_SEARCH_ONELEVEL:
+		lscope = LDAP_SCOPE_ONELEVEL;
+		break;
+	case GDA_LDAP_SEARCH_SUBTREE:
+		lscope = LDAP_SCOPE_SUBTREE;
+		break;
+	}
+	
+#ifdef GDA_DEBUG_SUBSEARCHES
+	if (model->priv->scope == GDA_LDAP_SEARCH_SUBTREE) {
+		g_print ("Model %p model->priv->top_exec:\n", model);
+		ldap_part_dump (model->priv->top_exec);
+	}
+#endif
+	retry:
+#ifdef GDA_DEBUG_SUBSEARCHES_FORCE
+	/* force sub searches for 2 levels */
+	static gint sims = 10;
+	if ((sims > 0) &&
+	    (model->priv->scope == GDA_LDAP_SEARCH_SUBTREE) &&
+	    (! model->priv->current_exec->parent || ! model->priv->current_exec->parent->parent)) {
+		g_print ("Simulating LDAP_ADMINLIMIT_EXCEEDED\n");
+		res = LDAP_ADMINLIMIT_EXCEEDED;
+		sims --;
+	}
+	if (res == 0)
+#endif
+
+	res = ldap_search_ext_s (cdata->handle, model->priv->current_exec->base_dn, lscope,
+				 model->priv->filter,
+				 (char**) model->priv->attributes->data, 0,
+				 NULL, NULL, NULL, -1,
+				 &msg);
+	model->priv->current_exec->executed = TRUE;
+
+	switch (res) {
+	case LDAP_SUCCESS:
+	case LDAP_NO_SUCH_OBJECT:
+		/* all Ok */
+		model->priv->current_exec->ldap_msg = msg;
+		model->priv->current_exec->nb_entries = ldap_count_entries (cdata->handle, msg);
+#ifdef GDA_DEBUG_SUBSEARCHES
+		g_print ("model->priv->current_exec->nb_entries = %d\n",
+			 model->priv->current_exec->nb_entries);
+#endif
+		break;
+
+	case LDAP_ADMINLIMIT_EXCEEDED:
+	case LDAP_SIZELIMIT_EXCEEDED:
+	case LDAP_TIMELIMIT_EXCEEDED: {
+#ifdef GDA_DEBUG_SUBSEARCHES
+		g_print ("LIMIT_EXCEEDED!\n");
+#endif
+		gboolean handled = FALSE;
+		if (model->priv->scope == GDA_LDAP_SEARCH_SUBTREE) {
+			gboolean split_error;
+			if (ldap_part_split (model->priv->current_exec, model, &split_error)) {
+				/* create some children to re-run the search */
+				if (msg)
+					ldap_msgfree (msg);
+				model->priv->current_exec = LDAP_PART (model->priv->current_exec->children->data);
+				execute_ldap_search (model);
+				handled = TRUE;
+			}
+			else if (!split_error) {
+				LdapPart *next;
+				next = ldap_part_next (model->priv->current_exec, FALSE);
+				if (next) {
+					model->priv->current_exec = next;
+					execute_ldap_search (model);
+					handled = TRUE;
+				}
+			}
+		}
+		if (!handled) {
+			/* truncated output */
+#ifdef GDA_DEBUG_SUBSEARCHES
+			g_print ("Output truncated!\n");
+#endif
+			model->priv->truncated = TRUE;
+			model->priv->current_exec->ldap_msg = msg;
+			model->priv->current_exec->nb_entries = ldap_count_entries (cdata->handle, msg);
+		}
+		break;
+	}
+	case LDAP_SERVER_DOWN: {
+		gint i;
+		for (i = 0; i < 5; i++) {
+			if (gda_ldap_silently_rebind (cdata))
+				goto retry;
+			g_usleep (G_USEC_PER_SEC * 2);
+		}
+	}
+	default: {
+		/* error */
+		GError *e = NULL;
+		int ldap_errno;
+		ldap_get_option (cdata->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);
+		g_set_error (&e, GDA_DATA_MODEL_ERROR, GDA_DATA_MODEL_OTHER_ERROR,
+			     "%s", ldap_err2string(ldap_errno));
+		add_exception (model, e);
+		return;
+	}
+	}
+
+	if (model->priv->truncated) {
+		/* compute totel number of rows now that we know it */
+		if (model->priv->top_exec->ldap_msg)
+			model->priv->n_rows = model->priv->current_exec->nb_entries;
+		else {
+			LdapPart *iter;
+			model->priv->n_rows = 0;
+			for (iter = model->priv->top_exec; iter; iter = ldap_part_next (iter, TRUE))
+				model->priv->n_rows += iter->nb_entries;
+		}
+	}
+#ifdef GDA_DEBUG_NO
+	gint tmpnb = 0;
+	if (model->priv->top_exec->ldap_msg)
+		tmpnb = model->priv->current_exec->nb_entries;
+	else {
+		LdapPart *iter;
+		for (iter = model->priv->top_exec; iter; iter = ldap_part_next (iter, TRUE))
+			tmpnb += iter->nb_entries;
+	}
+	g_print ("So far found %d\n", tmpnb);
+#endif
+}
+
+static gboolean
+gda_data_model_ldap_iter_next (GdaDataModel *model, GdaDataModelIter *iter)
+{
+	GdaDataModelLdap *imodel;
+	LdapConnectionData *cdata;
+	LdapPart *cpart;
+
+        g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), FALSE);
+        g_return_val_if_fail (GDA_IS_DATA_MODEL_ITER (iter), FALSE);
+        imodel = GDA_DATA_MODEL_LDAP (model);
+        g_return_val_if_fail (imodel->priv, FALSE);
+
+	if (! imodel->priv->cnc) {
+		/* error */
+		return FALSE;
+	}
+
+	cdata = (LdapConnectionData*) gda_virtual_connection_internal_get_provider_data (GDA_VIRTUAL_CONNECTION (imodel->priv->cnc));
+	if (!cdata) {
+		/* error */
+		return FALSE;
+	}
+
+	/* initialize LDAP search if necessary */
+	if (! imodel->priv->base_dn)
+		imodel->priv->base_dn = g_strdup (cdata->base_dn);
+	if (! imodel->priv->attributes)
+		imodel->priv->attributes = g_array_new (TRUE, FALSE, sizeof (gchar*));
+	if (! imodel->priv->top_exec) {
+		imodel->priv->top_exec = ldap_part_new (NULL, imodel->priv->base_dn, imodel->priv->scope);
+		imodel->priv->current_exec = imodel->priv->top_exec;
+	}
+
+	while (imodel->priv->current_exec) {
+		cpart = imodel->priv->current_exec;
+		if (! cpart->executed)
+			execute_ldap_search (imodel);
+		cpart = imodel->priv->current_exec;
+		if (! cpart->ldap_msg) {
+			/* error somewhere */
+			return FALSE;
+		}
+
+		if (! cpart->ldap_row)
+			/* not yet on 1st row */
+			cpart->ldap_row = ldap_first_entry (cdata->handle, cpart->ldap_msg);
+		else {
+			if (imodel->priv->row_mult) {
+				if (! row_multiplier_index_next (imodel->priv->row_mult)) {
+					row_multiplier_free (imodel->priv->row_mult);
+					imodel->priv->row_mult = NULL;
+				}
+			}
+			if (! imodel->priv->row_mult) {
+				/* move to the next row */
+				cpart->ldap_row = ldap_next_entry (cdata->handle, cpart->ldap_row);
+			}
+		}
+		
+		if (cpart->ldap_row) {
+			update_iter_from_ldap_row (imodel, iter);
+			break;
+		}
+		else
+			/* nothing more for this part, switch to the next one */
+			imodel->priv->current_exec = ldap_part_next (cpart, FALSE);
+	}
+
+	if (!imodel->priv->current_exec) {
+		/* execution is over */
+		g_signal_emit_by_name (iter, "end-of-data");
+                g_object_set (G_OBJECT (iter), "current-row", -1, NULL);
+		if (imodel->priv->truncated) {
+			GError *e;
+			g_set_error (&e, GDA_DATA_MODEL_ERROR,
+				     GDA_DATA_MODEL_TRUNCATED_ERROR,
+				     _("Truncated result because LDAP server limit encountered"));
+			add_exception (imodel, e);
+		}
+                return FALSE;
+	}
+
+	return TRUE;
+}
+
+static GdaValueAttribute
+gda_data_model_ldap_get_attributes_at (GdaDataModel *model, gint col, G_GNUC_UNUSED gint row)
+{
+	GdaDataModelLdap *imodel;
+	GdaValueAttribute flags;
+	GdaColumn *column;
+	g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), 0);
+        imodel = GDA_DATA_MODEL_LDAP (model);
+
+	if ((col < 0) || (col > imodel->priv->n_columns)) {
+		/* error */
+		/*
+		gchar *tmp;
+		tmp = g_strdup_printf (_("Column %d out of range (0-%d)"), col, imodel->priv->n_columns - 1);
+		add_error (imodel, tmp);
+		g_free (tmp);
+		*/
+		return 0;
+	}
+
+	flags = GDA_VALUE_ATTR_NO_MODIF;
+	column = g_list_nth_data (imodel->priv->columns, col);
+	if (gda_column_get_allow_null (column))
+		flags |= GDA_VALUE_ATTR_CAN_BE_NULL;
+
+	return flags;
+}
+
+static GError **
+gda_data_model_ldap_get_exceptions (GdaDataModel *model)
+{
+	GdaDataModelLdap *imodel;
+	g_return_val_if_fail (GDA_IS_DATA_MODEL_LDAP (model), NULL);
+	imodel = GDA_DATA_MODEL_LDAP (model);
+	if (imodel->priv->exceptions)
+		return (GError**) imodel->priv->exceptions->data;
+	else
+		return NULL;
+}
+
+/*
+ * Parts manipulations
+ */
+static LdapPart *
+ldap_part_new (LdapPart *parent, const gchar *base_dn, GdaLdapSearchScope scope)
+{
+	LdapPart *part;
+	if (!base_dn || !*base_dn)
+		return NULL;
+	part = g_new0 (LdapPart, 1);
+	part->base_dn = g_strdup (base_dn);
+	part->scope = scope;
+	part->ldap_msg = NULL;
+	part->ldap_row = NULL;
+	part->children = NULL;
+	part->parent = parent;
+	return part;
+}
+
+static void
+ldap_part_free (LdapPart *part)
+{
+	g_assert (part);
+	g_free (part->base_dn);
+	if (part->children) {
+		g_slist_foreach (part->children, (GFunc) ldap_part_free, NULL);
+		g_slist_free (part->children);
+	}
+	if (part->ldap_msg)
+		ldap_msgfree (part->ldap_msg);
+	g_free (part);
+}
+
+/*
+ * Go to the next part in the tree
+ */
+static LdapPart *
+ldap_part_next (LdapPart *part, gboolean executed)
+{
+	LdapPart *parent, *retval = NULL;
+	if (part->children)
+		retval = LDAP_PART (part->children->data);
+	else {
+		LdapPart *tmp;
+		tmp = part;
+		for (parent = tmp->parent; parent; parent = tmp->parent) {
+			gint i;
+			i = g_slist_index (parent->children, tmp);
+			tmp = g_slist_nth_data (parent->children, i+1);
+			if (tmp) {
+				retval = tmp;
+				break;
+			}
+			else
+				tmp = parent;
+		}
+	}
+	
+	if (retval) {
+		if (executed && !retval->executed)
+			return ldap_part_next (retval, executed);
+		else if (!executed && retval->executed)
+			return ldap_part_next (retval, executed);
+	}
+
+	if (retval == part) {
+		TO_IMPLEMENT;
+	}
+	g_assert (retval != part);
+	return retval;
+}
+
+/*
+ * Creates sub parts of @part, one for each immediate child pf @part->base_dn
+ *
+ * Returns: %TRUE if part->children is not %NULL
+ */
+static gboolean
+ldap_part_split (LdapPart *part, GdaDataModelLdap *model, gboolean *out_error)
+{
+	/* fetch all children entries of @part, and build a child part
+	 * for each of them */
+	GdaDataModel *mparts;
+	GdaDataModelIter *iter;
+	
+	if (out_error)
+		*out_error = FALSE;
+	g_assert (!part->children);
+	mparts = _gdaprov_data_model_ldap_new (model->priv->cnc, part->base_dn, NULL, NULL,
+					      GDA_LDAP_SEARCH_ONELEVEL);
+	if (!mparts) {
+		if (out_error)
+			*out_error = TRUE;
+		return FALSE;
+	}
+
+	if (part->scope == GDA_LDAP_SEARCH_SUBTREE) {
+		/* create a part for the node itself */
+		LdapPart *sub = NULL;
+		sub = ldap_part_new (part, part->base_dn, GDA_LDAP_SEARCH_BASE);
+		g_assert (sub);
+		part->children = g_slist_prepend (part->children, sub);
+	}
+
+        iter = gda_data_model_create_iter (mparts);
+        for (; gda_data_model_iter_move_next (iter); ) {
+		const GValue *cvalue;
+		LdapPart *sub = NULL;
+		cvalue = gda_data_model_iter_get_value_at (iter, 0);
+		if (cvalue) {
+			gchar *tmp;
+			tmp = gda_value_stringify (cvalue);
+			sub = ldap_part_new (part, tmp, part->scope);
+			if (sub)
+				part->children = g_slist_prepend (part->children, sub);
+                        g_free (tmp);
+		}
+		if (!sub) {
+			/* error */
+			g_slist_foreach (part->children, (GFunc) ldap_part_free, NULL);
+			g_slist_free (part->children);
+			part->children = NULL;
+			break;
+                }
+        }
+	g_object_unref (mparts);
+
+	return part->children ? TRUE : FALSE;
+}
+
+#ifdef GDA_DEBUG_SUBSEARCHES
+static void
+ldap_part_dump_to_string (LdapPart *part, GString *string, gint index, gboolean recurs)
+{
+	gchar *indent;
+	indent = g_new (gchar, index * 4 + 1);
+	memset (indent, ' ', sizeof (gchar) * index * 4);
+	indent [index * 4] = 0;
+	g_string_append (string, indent);
+	g_free (indent);
+
+	g_string_append_printf (string, "part[%p] scope[%d] base[%s] %s nb_entries[%d]\n", part,
+				part->scope, part->base_dn,
+				part->executed ? "EXEC" : "non exec", part->nb_entries);
+	
+	if (recurs && part->children) {
+		GSList *list;
+		for (list = part->children; list; list = list->next)
+			ldap_part_dump_to_string (LDAP_PART (list->data), string, index+1, recurs);
+	}
+}
+
+static void
+ldap_part_dump (LdapPart *part)
+{
+	GString *string;
+	string = g_string_new ("");
+	ldap_part_dump_to_string (part, string, 0, TRUE);
+	g_print ("%s\n", string->str);
+	g_string_free (string, TRUE);
+}
+#endif
+
+
+/*
+ * Row & column multiplier
+ */
+
+static ColumnMultiplier *
+column_multiplier_new (GdaHolder *holder, const GValue *value)
+{
+	ColumnMultiplier *cm;
+	cm = g_new0 (ColumnMultiplier, 1);
+	cm->holder = g_object_ref (holder);
+	cm->index = 0;
+	cm->values = g_array_new (FALSE, FALSE, sizeof (GValue*));
+	if (value) {
+		GValue *copy;
+		copy = gda_value_copy (value);
+		g_array_append_val (cm->values, copy);
+	}
+	return cm;
+}
+
+static RowMultiplier *
+row_multiplier_new (void)
+{
+	RowMultiplier *rm;
+	rm = g_new0 (RowMultiplier, 1);
+	rm->cms = g_array_new (FALSE, FALSE, sizeof (ColumnMultiplier*));
+	return rm;
+}
+
+static void
+row_multiplier_set_holders (RowMultiplier *rm)
+{
+	gint i;
+	for (i = 0; i < rm->cms->len; i++) {
+		ColumnMultiplier *cm;
+		GValue *value;
+		cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+		value = g_array_index (cm->values, GValue *, cm->index);
+		if (value)
+			gda_holder_set_value (cm->holder, value, NULL);
+		else
+			gda_holder_force_invalid (cm->holder);
+	}
+}
+
+static void
+row_multiplier_free (RowMultiplier *rm)
+{
+	gint i;
+	for (i = 0; i < rm->cms->len; i++) {
+		ColumnMultiplier *cm;
+		cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+		gint j;
+		for (j = 0; j < cm->values->len; j++) {
+			GValue *value;
+			value = g_array_index (cm->values, GValue *, j);
+			if (value)
+				gda_value_free (value);
+		}
+		g_array_free (cm->values, TRUE);
+		g_object_unref (cm->holder);
+		g_free (cm);
+	}
+	g_array_free (rm->cms, TRUE);
+	g_free (rm);
+}
+
+/*
+ * move indexes one step forward
+ */
+static gboolean
+row_multiplier_index_next (RowMultiplier *rm)
+{
+	gint i;
+#ifdef GDA_DEBUG_NO
+	g_print ("RM %p before:\n", rm);
+	for (i = 0; i < rm->cms->len; i++) {
+		ColumnMultiplier *cm;
+		cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+		g_print (" %d ", cm->index);
+	}
+	g_print ("\n");
+#endif
+
+	for (i = 0; ; i++) {
+		ColumnMultiplier *cm;
+		if (i == rm->cms->len) {
+#ifdef GDA_DEBUG_NO
+			g_print ("RM %p [FALSE]:\n", rm);
+			for (i = 0; i < rm->cms->len; i++) {
+				ColumnMultiplier *cm;
+				cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+				g_print (" %d ", cm->index);
+			}
+			g_print ("\n");
+#endif
+			return FALSE;
+		}
+
+		cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+		if (cm->index < (cm->values->len - 1)) {
+			cm->index ++;
+#ifdef GDA_DEBUG_NO
+			g_print ("RM %p [TRUE]:\n", rm);
+			for (i = 0; i < rm->cms->len; i++) {
+				ColumnMultiplier *cm;
+				cm = g_array_index (rm->cms, ColumnMultiplier*, i);
+				g_print (" %d ", cm->index);
+			}
+			g_print ("\n");
+#endif
+			return TRUE;
+		}
+		else {
+			gint j;
+			for (j = 0; j < i; j++) {
+				cm = g_array_index (rm->cms, ColumnMultiplier*, j);
+				cm->index = 0;
+			}
+		}
+	}
+}
diff --git a/libgda/sqlite/virtual/libgda-virtual.h b/providers/ldap/gdaprov-data-model-ldap.h
similarity index 60%
rename from libgda/sqlite/virtual/libgda-virtual.h
rename to providers/ldap/gdaprov-data-model-ldap.h
index 2986ccf..bba6074 100644
--- a/libgda/sqlite/virtual/libgda-virtual.h
+++ b/providers/ldap/gdaprov-data-model-ldap.h
@@ -1,5 +1,5 @@
-/* GDA library
- * Copyright (C) 2007 The GNOME Foundation.
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *      Vivien Malerba <malerba gnome-db org>
@@ -20,15 +20,19 @@
  * Boston, MA 02111-1307, USA.
  */
 
-#ifndef __LIBGDA_VIRTUAL_H__
-#define __LIBGDA_VIRTUAL_H__
+#ifndef __GDA_PROXY_DATA_MODEL_LDAP_H__
+#define __GDA_PROXY_DATA_MODEL_LDAP_H__
 
-#include <virtual/gda-virtual-provider.h>
-#include <virtual/gda-vprovider-data-model.h>
-#include <virtual/gda-vprovider-hub.h>
+#include <libgda/gda-data-model-ldap.h>
 
-#include <virtual/gda-virtual-connection.h>
-#include <virtual/gda-vconnection-data-model.h>
-#include <virtual/gda-vconnection-hub.h>
+G_BEGIN_DECLS
+
+GType         gdaprov_data_model_ldap_get_type  (void) G_GNUC_CONST;
+GdaDataModel *_gdaprov_data_model_ldap_new       (GdaConnection *cnc,
+						  const gchar *base_dn, const gchar *filter,
+						  const gchar *attributes, GdaLdapSearchScope scope);
+GList        *gdaprov_data_model_ldap_compute_columns (GdaConnection *cnc, const gchar *attributes);
+
+G_END_DECLS
 
 #endif
diff --git a/providers/ldap/ldap_specs_auth.xml.in b/providers/ldap/ldap_specs_auth.xml.in
new file mode 100644
index 0000000..60ce8f5
--- /dev/null
+++ b/providers/ldap/ldap_specs_auth.xml.in
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<data-set-spec>
+  <parameters>
+    <parameter id="USERNAME" _name="Username" _descr="User name" gdatype="gchararray" nullok="TRUE"/>
+    <parameter id="PASSWORD" _name="Password" _descr="Password" gdatype="gchararray" nullok="TRUE" plugin="string:HIDDEN=true"/>
+  </parameters>
+</data-set-spec>
diff --git a/providers/ldap/ldap_specs_dsn.xml.in b/providers/ldap/ldap_specs_dsn.xml.in
new file mode 100644
index 0000000..9e2ca99
--- /dev/null
+++ b/providers/ldap/ldap_specs_dsn.xml.in
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<data-set-spec>
+  <parameters>
+    <parameter id="DB_NAME" _name="Base name" _descr="Base distinguished name, starting point for the searches" gdatype="gchararray" nullok="FALSE"/>
+    <parameter id="HOST" _name="Database server" _descr="Host on which the LDAP server is running" gdatype="gchararray"/>
+    <parameter id="PORT" _name="Port" _descr="Database server port (leave this field empty to use the default port)" gdatype="gint"/>
+    <parameter id="USE_SSL" _name="Require SSL" _descr="Whether or not to use SSL to establish the connection" gdatype="gboolean"/>
+    <parameter id="USE_CACHE" _name="Cache server data" _descr="Use a cache to store some static server data (the cached files are in the user's cache directory), default is TRUE" gdatype="gboolean">
+      <gda_value>TRUE</gda_value>
+    </parameter>
+  </parameters>
+</data-set-spec>
diff --git a/providers/ldap/libgda-ldap-4.0.pc.in b/providers/ldap/libgda-ldap-4.0.pc.in
new file mode 100644
index 0000000..21c7a96
--- /dev/null
+++ b/providers/ldap/libgda-ldap-4.0.pc.in
@@ -0,0 +1,9 @@
+prefix= prefix@
+exec_prefix= exec_prefix@
+libdir= libdir@
+includedir= includedir@
+
+Name: libgda-ldap- GDA_ABI_MAJOR_VERSION@  GDA_ABI_MINOR_VERSION@
+Description: GDA Ldap provider
+Requires: libgda- GDA_ABI_MAJOR_VERSION@  GDA_ABI_MINOR_VERSION@
+Version: @VERSION@
diff --git a/providers/ldap/libmain.c b/providers/ldap/libmain.c
new file mode 100644
index 0000000..82ac438
--- /dev/null
+++ b/providers/ldap/libmain.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <libgda/gda-config.h>
+#include <libgda/gda-server-provider-extra.h>
+#include <libgda/binreloc/gda-binreloc.h>
+#include "gda-ldap.h"
+#include "gda-ldap-provider.h"
+
+static gchar      *module_path = NULL;
+const gchar       *plugin_get_name (void);
+const gchar       *plugin_get_description (void);
+gchar             *plugin_get_dsn_spec (void);
+GdaServerProvider *plugin_create_provider (void);
+
+/*
+ * Functions executed when calling g_module_open() and g_module_close()
+ */
+const gchar *
+g_module_check_init (G_GNUC_UNUSED GModule *module)
+{
+        return NULL;
+}
+
+void
+g_module_unload (G_GNUC_UNUSED GModule *module)
+{
+        g_free (module_path);
+        module_path = NULL;
+}
+
+/*
+ * Normal plugin functions 
+ */
+void
+plugin_init (const gchar *real_path)
+{
+        if (real_path)
+                module_path = g_strdup (real_path);
+}
+
+const gchar *
+plugin_get_name (void)
+{
+	return LDAP_PROVIDER_NAME;
+}
+
+const gchar *
+plugin_get_description (void)
+{
+	return _("Provider for database where tables are based on data contained in an LDAP directory");
+}
+
+gchar *
+plugin_get_dsn_spec (void)
+{
+	gchar *ret, *dir;
+
+	dir = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, NULL);
+	ret = gda_server_provider_load_file_contents (module_path, dir, "ldap_specs_dsn.xml");
+	g_free (dir);
+	return ret;
+}
+
+gchar *
+plugin_get_auth_spec (void)
+{
+	gchar *ret, *dir;
+
+	dir = gda_gbr_get_file_path (GDA_DATA_DIR, LIBGDA_ABI_NAME, NULL);
+	ret = gda_server_provider_load_file_contents (module_path, dir, "ldap_specs_auth.xml");
+	g_free (dir);
+	return ret;
+}
+
+GdaServerProvider *
+plugin_create_provider (void)
+{
+	GdaServerProvider *prov;
+
+        prov = (GdaServerProvider *) g_object_new (GDA_TYPE_LDAP_PROVIDER, NULL);
+        g_object_set_data ((GObject *) prov, "GDA_PROVIDER_DIR", module_path);
+        return prov;
+}
diff --git a/samples/LdapBrowser/README b/samples/LdapBrowser/README
new file mode 100644
index 0000000..2149c49
--- /dev/null
+++ b/samples/LdapBrowser/README
@@ -0,0 +1,22 @@
+LDAP browser demo
+=================
+
+Description:
+------------
+
+The example in this directory is a very simple LDAP tree browser, illustrating the usage of
+an LDAP connection and the GdaTree and GdaTreeMgrLdap objects.
+
+Compiling and running:
+----------------------
+
+To compile (make sure Libgda is installed prior to this):
+> make
+
+and to run:
+> ./ldap-browser [-h <server>] [-u <username>] [-p <password>] [-b <base>] [-s]
+
+Results:
+--------
+Opens a window with the LDAP entries hierarchy, showing only the RDN of each entry. Selecting
+an entry displays its RND and complete DN in the standard output stream.
\ No newline at end of file
diff --git a/samples/LdapBrowser/ldap-browser.c b/samples/LdapBrowser/ldap-browser.c
new file mode 100644
index 0000000..6296092
--- /dev/null
+++ b/samples/LdapBrowser/ldap-browser.c
@@ -0,0 +1,276 @@
+#include <gtk/gtk.h>
+#include <string.h>
+#include <libgda-ui/libgda-ui.h>
+#include <libgda/libgda.h>
+#include <libgda/virtual/gda-ldap-connection.h>
+
+gchar *host = NULL;
+gchar *base = NULL;
+gchar *user = NULL;
+gchar *password = NULL;
+gboolean usessl = FALSE;
+
+static GOptionEntry entries[] = {
+        { "server", 'h', 0, G_OPTION_ARG_STRING, &host, "Server", NULL},
+        { "basename", 'b', 0, G_OPTION_ARG_STRING, &base, "Base name", NULL},
+        { "user", 'u', 0, G_OPTION_ARG_STRING, &user, "User", NULL},
+        { "password", 'p', 0, G_OPTION_ARG_STRING, &password, "Password", NULL},
+        { "usessl", 's', 0, G_OPTION_ARG_NONE, &usessl, "Use SSL", NULL},
+        { NULL }
+};
+
+
+static GdaConnection *
+open_ldap_connection ()
+{
+	GString *cncstring = NULL;
+	gchar *enc;
+	GdaConnection *cnc;
+	GError *error = NULL;
+
+	if (host) {
+		cncstring = g_string_new ("");
+		enc = gda_rfc1738_encode (host);
+		g_string_append_printf (cncstring, "HOST=%s", enc);
+		g_free (enc);
+	}
+
+	if (base) {
+		if (cncstring)
+			g_string_append_c (cncstring, ';');
+		else
+			cncstring = g_string_new ("");
+		enc = gda_rfc1738_encode (base);
+		g_string_append_printf (cncstring, "DB_NAME=%s", enc);
+		g_free (enc);
+	}
+
+	if (user) {
+		if (cncstring)
+			g_string_append_c (cncstring, ';');
+		else
+			cncstring = g_string_new ("");
+		enc = gda_rfc1738_encode (user);
+		g_string_append_printf (cncstring, "USERNAME=%s", enc);
+		g_free (enc);
+	}
+
+	if (password) {
+		if (cncstring)
+			g_string_append_c (cncstring, ';');
+		else
+			cncstring = g_string_new ("");
+		enc = gda_rfc1738_encode (password);
+		g_string_append_printf (cncstring, "PASSWORD=%s", enc);
+		g_free (enc);
+	}
+
+	if (usessl) {
+		if (cncstring)
+			g_string_append_c (cncstring, ';');
+		else
+			cncstring = g_string_new ("");
+		g_string_append (cncstring, "USER_SSL=TRUE");
+	}
+	
+	if (! cncstring) {
+		g_print ("No connection specified!\n");
+		exit (1);
+	}
+
+	g_print ("Using connection string: %s\n", cncstring->str);
+        cnc = gda_connection_open_from_string ("Ldap", cncstring->str,
+                                               NULL,
+                                               GDA_CONNECTION_OPTIONS_NONE, &error);
+        if (!cnc) {
+                g_print ("Error opening connection (cncstring=[%s]): %s\n",
+                         cncstring->str,
+                         error && error->message ? error->message : "No detail");
+                exit (1);
+        }
+        g_string_free (cncstring, TRUE);
+
+	return cnc;
+}
+
+typedef struct {
+	GtkTreeView *tview;
+	GdauiTreeStore *store;
+	GdaTree *tree;
+	GdaTreeNode *node;
+} IdleData;
+
+static void
+idle_data_free (IdleData *data)
+{
+	g_object_unref (data->tview);
+	g_object_unref (data->store);
+	g_object_unref (data->tree);
+	g_object_unref (data->node);
+	g_free (data);
+}
+
+static gboolean ldap_update_tree_part (IdleData *data);
+static gboolean
+test_expand_row_cb (GtkTreeView *tree_view, GtkTreeIter *iter,
+                    G_GNUC_UNUSED GtkTreePath *path, GdaTree *tree)
+{
+        GdaTreeNode *node;
+	GtkTreeModel *store;
+
+	store = gtk_tree_view_get_model (tree_view);
+        node = gdaui_tree_store_get_node (GDAUI_TREE_STORE (store), iter);
+        if (gda_tree_node_get_child_index (node, 0))
+                return FALSE;
+
+	const GValue *cv;
+	cv = gda_tree_node_get_node_attribute (node,
+					       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+	if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+	    g_value_get_boolean (cv)) {
+		IdleData *data;
+		data = g_new (IdleData, 1);
+		data->tview = g_object_ref (G_OBJECT (tree_view));
+		data->store = g_object_ref (G_OBJECT (store));
+		data->tree = g_object_ref (G_OBJECT (tree));
+		data->node = g_object_ref (G_OBJECT (node));
+
+		g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc) ldap_update_tree_part,
+				 data, (GDestroyNotify) idle_data_free);
+	}
+	return TRUE;
+}
+
+static gboolean
+ldap_update_tree_part (IdleData *data)
+{
+        gda_tree_update_children (data->tree, data->node, NULL);
+
+	GtkTreeIter iter;
+	if (gdaui_tree_store_get_iter (data->store, &iter, data->node) &&
+	    gda_tree_node_get_child_index (data->node, 0)) {
+		GtkTreePath *path;
+		path = gtk_tree_model_get_path (GTK_TREE_MODEL (data->store), &iter);
+		g_signal_handlers_block_by_func (data->tview,
+						 G_CALLBACK (test_expand_row_cb), data->tree);
+		gtk_tree_view_expand_row (data->tview, path, FALSE);
+		g_signal_handlers_unblock_by_func (data->tview,
+						   G_CALLBACK (test_expand_row_cb), data->tree);
+		gtk_tree_path_free (path);
+	}
+
+	return FALSE;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *sel, GdauiTreeStore *store)
+{
+	GtkTreeIter iter;
+	if (gtk_tree_selection_get_selected (sel, NULL, &iter)) {
+		gchar *rdn;
+		gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &rdn, -1);
+		g_print ("Selected RDN: %s\n", rdn);
+		g_free (rdn);
+
+		GdaTreeNode *node;
+		const GValue *cvalue;
+		node = gdaui_tree_store_get_node (store, &iter);
+		g_assert (node);
+		cvalue = gda_tree_node_get_node_attribute (node, "dn");
+		g_assert (cvalue);
+		g_print ("         DN:  %s\n", g_value_get_string (cvalue));
+	}
+}
+
+static GtkWidget *
+create_window (GdaConnection *cnc)
+{
+	GtkWidget *win = NULL;
+
+	/* Window */
+	win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_title  (GTK_WINDOW (win), "LDAP Browser");
+	gtk_window_set_default_size (GTK_WINDOW (win), 640, 480);
+	gtk_container_set_border_width (GTK_CONTAINER (win), 5);
+	gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
+	g_signal_connect (G_OBJECT (win), "destroy", gtk_main_quit, NULL);
+
+	/* container */
+	GtkWidget *vb, *hp;
+	vb = gtk_vbox_new (FALSE, 0);
+	gtk_container_add (GTK_CONTAINER (win), vb);
+	hp = gtk_hpaned_new ();
+        gtk_box_pack_start (GTK_BOX (vb), hp, TRUE, TRUE, 0);
+
+	GdaTree *tree;
+	GdaTreeManager *mgr;
+	GtkTreeModel *store;
+	GtkWidget *tview, *sw;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	
+	tree = gda_tree_new ();
+	mgr = gda_tree_mgr_ldap_new (cnc, NULL);
+	gda_tree_add_manager (tree, mgr);
+	gda_tree_manager_add_manager (mgr, mgr);
+	g_object_unref (mgr);
+
+	gda_tree_update_children (tree, NULL, NULL);
+
+	store = gdaui_tree_store_new (tree, 1, G_TYPE_STRING, "rdn");
+	g_object_unref (tree);
+	tview = gtk_tree_view_new_with_model (store);
+	g_signal_connect (tview, "test-expand-row",
+                          G_CALLBACK (test_expand_row_cb), tree);
+
+	renderer = gtk_cell_renderer_text_new ();
+        column = gtk_tree_view_column_new_with_attributes ("DN", renderer, "text", 0, NULL);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (tview), column);
+        gtk_tree_view_set_expander_column (GTK_TREE_VIEW (tview), column);
+
+	sw = gtk_scrolled_window_new (NULL, NULL);
+        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
+                                        GTK_POLICY_AUTOMATIC);
+        gtk_container_add (GTK_CONTAINER (sw), tview);
+        gtk_paned_add1 (GTK_PANED (hp), sw);
+
+	/* selection */
+	GtkTreeSelection *sel;
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tview));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	g_signal_connect (sel, "changed",
+			  G_CALLBACK (selection_changed_cb), store);
+
+	gtk_widget_show_all (vb);
+
+	return win;
+}
+
+/*
+ * Entree du programme:
+ */
+int main (int argc, char ** argv)
+{
+	GdaConnection *cnc;
+	GOptionContext *context;
+	GError *error = NULL;
+
+	context = g_option_context_new (NULL);
+        g_option_context_add_main_entries (context, entries, NULL);
+        if (!g_option_context_parse (context, &argc, &argv, &error)) {
+                g_print ("Can't parse arguments: %s", error->message);
+                exit (1);
+        }
+        g_option_context_free (context);
+
+	gtk_init (& argc, & argv);
+	gdaui_init ();
+
+	cnc = open_ldap_connection ();
+	gtk_widget_show (create_window (cnc));
+	gtk_main ();
+
+	g_object_unref (cnc);
+
+	return 0;
+}
diff --git a/samples/Makefile b/samples/Makefile
index 3252d92..dea2227 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -1,5 +1,5 @@
 SHELL= /bin/sh 
-SUBDIRS = BDB DDL DirDataModel F-Spot Report SimpleExample SqlParserConsole TableCopy Virtual XSLT MetaStore Tree SqlBuilder AsyncExec
+SUBDIRS = BDB DDL DirDataModel F-Spot Report SimpleExample SqlParserConsole TableCopy Virtual XSLT MetaStore Tree SqlBuilder AsyncExec LdapBrowser
 all:
 	for dir in ${SUBDIRS} ; do ( cd $$dir ; ${MAKE} ) ; done
 clean:
diff --git a/testing/Makefile.am b/testing/Makefile.am
index fe8de0f..50683f5 100644
--- a/testing/Makefile.am
+++ b/testing/Makefile.am
@@ -2,6 +2,7 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/libgda \
 	-I$(top_srcdir)/libgda/sqlite \
+	-I$(top_builddir)/libgda/sqlite \
 	-I$(top_builddir) \
 	$(LIBGDA_CFLAGS) \
 	$(LIBGDA_WFLAGS)
diff --git a/tests/data-models/check_pmodel.c b/tests/data-models/check_pmodel.c
index a3260c5..925a462 100644
--- a/tests/data-models/check_pmodel.c
+++ b/tests/data-models/check_pmodel.c
@@ -811,7 +811,7 @@ test7 (GdaConnection *cnc)
 {
 	GError *error = NULL;
 	GdaDataModel *model = NULL;
-	GdaStatement *stmt;
+	GdaStatement *stmt = NULL;
 	gint nfailed = 0;
 	GdaSet *params;
 	GValue *value;
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 652329b..1d89909 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -15,6 +15,7 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir) \
 	-I$(top_builddir) \
 	-I$(top_srcdir)/libgda/sqlite \
+	-I$(top_builddir)/libgda/sqlite \
 	-I$(top_srcdir)/libgda \
 	-I. \
 	$(LIBGDA_CFLAGS) \
diff --git a/tools/browser/Makefile.am b/tools/browser/Makefile.am
index 4dc77dd..3712e8a 100644
--- a/tools/browser/Makefile.am
+++ b/tools/browser/Makefile.am
@@ -2,6 +2,13 @@ bin_PROGRAMS=gda-browser-4.0
 noinst_LTLIBRARIES = libbrowser.la
 
 SUBDIRS = data common schema-browser query-exec data-manager dummy-perspective
+
+if LDAP
+ldap_flags=-DHAVE_LDAP
+SUBDIRS+=ldap-browser
+LDAP_LDADD=$(top_builddir)/tools/browser/ldap-browser/libperspective.la
+endif
+
 if HAVE_GOOCANVAS
 SUBDIRS+=canvas
 noinst_PROGRAMS=canvas-example
@@ -15,6 +22,7 @@ AM_CPPFLAGS = \
         -I$(top_srcdir) \
         -I$(top_srcdir)/libgda \
         -I$(top_srcdir)/libgda/sqlite \
+	-I$(top_builddir)/libgda/sqlite \
         -I$(top_builddir) \
         $(LIBGDA_CFLAGS) \
 	$(LIBGDA_WFLAGS) \
@@ -24,7 +32,8 @@ AM_CPPFLAGS = \
         -DPREFIX=\""$(prefix)"\" \
         -DSYSCONFDIR=\""$(sysconfdir)"\" \
         -DDATADIR=\""$(datadir)"\" \
-        -DLIBDIR=\""$(libdir)"\"
+        -DLIBDIR=\""$(libdir)"\" \
+	$(ldap_flags)
 
 marshal.h: marshal.list $(GLIB_GENMARSHAL)
 	$(GLIB_GENMARSHAL) $< --header --prefix=_marshal > $@
@@ -104,6 +113,7 @@ gda_browser_4_0_LDADD=\
 	schema-browser/libperspective.la \
 	query-exec/libperspective.la \
 	data-manager/libperspective.la \
+	$(LDAP_LDADD) \
 	libbrowser.la \
 	$(top_builddir)/libgda-ui/internal/libgda-ui-internal.la \
 	$(CANVAS_LDADD) \
@@ -167,7 +177,15 @@ icons_DATA= \
 	gda-browser-action.png \
 	gda-browser-form.png \
 	gda-browser-grid.png \
-	gda-browser-menu-ind.png
+	gda-browser-menu-ind.png \
+	gda-browser-ldap-entry.png \
+	gda-browser-ldap-group.png \
+	gda-browser-ldap-organization.png \
+	gda-browser-ldap-person.png \
+	gda-browser-ldap-class-a.png \
+	gda-browser-ldap-class-s.png \
+	gda-browser-ldap-class-x.png \
+	gda-browser-ldap-class-u.png
 
 # app icon
 appiconsdir=$(datadir)/pixmaps
diff --git a/tools/browser/auth-dialog.c b/tools/browser/auth-dialog.c
index 6e509f1..7879b97 100644
--- a/tools/browser/auth-dialog.c
+++ b/tools/browser/auth-dialog.c
@@ -310,6 +310,13 @@ sub_thread_open_cnc (AuthData *ad, GError **error)
 						       GDA_CONNECTION_OPTIONS_THREAD_SAFE |
 						       GDA_CONNECTION_OPTIONS_AUTO_META_DATA,
 						       error);
+#ifdef HAVE_LDAP
+	if (cnc && GDA_IS_LDAP_CONNECTION (cnc)) {
+		/* force classes init */
+		GdaLdapClass *lcl;
+		lcl = gda_ldap_get_class_info (GDA_LDAP_CONNECTION (cnc), "top");
+	}
+#endif
 	return cnc;
 #else
 	sleep (5);
diff --git a/tools/browser/browser-connection.c b/tools/browser/browser-connection.c
index dc1b7ad..4a3fd6f 100644
--- a/tools/browser/browser-connection.c
+++ b/tools/browser/browser-connection.c
@@ -70,13 +70,18 @@ static gboolean check_for_wrapper_result (BrowserConnection *bcnc);
 typedef enum {
 	JOB_TYPE_META_STORE_UPDATE,
 	JOB_TYPE_META_STRUCT_SYNC,
-	JOB_TYPE_STATEMENT_EXECUTE
+	JOB_TYPE_STATEMENT_EXECUTE,
+	JOB_TYPE_CALLBACK
 } JobType;
 
 typedef struct {
 	guint    job_id;
 	JobType  job_type;
 	gchar   *reason;
+	
+	/* the following may be %NULL for stmt execution and meta store updates */
+	BrowserConnectionJobCallback callback;
+	gpointer cb_data;
 } WrapperJob;
 
 static void
@@ -90,7 +95,8 @@ wrapper_job_free (WrapperJob *wj)
  * Pushes a job which has been asked to be exected in a sub thread using gda_thread_wrapper_execute()
  */
 static void
-push_wrapper_job (BrowserConnection *bcnc, guint job_id, JobType job_type, const gchar *reason)
+push_wrapper_job (BrowserConnection *bcnc, guint job_id, JobType job_type, const gchar *reason,
+		  BrowserConnectionJobCallback callback, gpointer cb_data)
 {
 	/* setup timer */
 	if (bcnc->priv->wrapper_results_timer == 0) {
@@ -116,6 +122,8 @@ push_wrapper_job (BrowserConnection *bcnc, guint job_id, JobType job_type, const
 	wj->job_type = job_type;
 	if (reason)
 		wj->reason = g_strdup (reason);
+	wj->callback = callback;
+	wj->cb_data = cb_data;
 
 	bcnc->priv->wrapper_jobs = g_slist_append (bcnc->priv->wrapper_jobs, wj);
 
@@ -355,7 +363,7 @@ meta_changed_cb (G_GNUC_UNUSED GdaThreadWrapper *wrapper,
 					     g_object_ref (bcnc), g_object_unref, &lerror);
 	if (job_id > 0)
 		push_wrapper_job (bcnc, job_id, JOB_TYPE_META_STRUCT_SYNC,
-				  _("Analysing database schema"));
+				  _("Analysing database schema"), NULL, NULL);
 	else if (lerror) {
 		browser_show_error (NULL, _("Error while fetching meta data from the connection: %s"),
 				    lerror->message ? lerror->message : _("No detail"));
@@ -442,7 +450,8 @@ browser_connection_set_property (GObject *object,
 								     g_object_ref (bcnc), g_object_unref, &lerror);
 				if (job_id > 0)
 					push_wrapper_job (bcnc, job_id, JOB_TYPE_META_STORE_UPDATE,
-							  _("Getting database schema information"));
+							  _("Getting database schema information"),
+							  NULL, NULL);
 				else if (lerror) {
 					browser_show_error (NULL, _("Error while fetching meta data from the connection: %s"),
 							    lerror->message ? lerror->message : _("No detail"));
@@ -462,7 +471,8 @@ browser_connection_set_property (GObject *object,
 								     g_object_ref (bcnc), g_object_unref, &lerror);
 				if (job_id > 0)
 					push_wrapper_job (bcnc, job_id, JOB_TYPE_META_STRUCT_SYNC,
-							  _("Analysing database schema"));
+							  _("Analysing database schema"),
+							  NULL, NULL);
 				else if (lerror) {
 					browser_show_error (NULL, _("Error while fetching meta data from the connection: %s"),
 							    lerror->message ? lerror->message : _("No detail"));
@@ -475,6 +485,7 @@ browser_connection_set_property (GObject *object,
 								FALSE, FALSE,
 								(GdaThreadWrapperCallback) meta_changed_cb,
 								bcnc);
+
                         break;
 		default:
 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
@@ -729,6 +740,16 @@ check_for_wrapper_result (BrowserConnection *bcnc)
 			g_hash_table_insert (bcnc->priv->executed_statements, id, res);
 			break;
 		}
+		case JOB_TYPE_CALLBACK:
+			if (wj->callback) {
+				wj->callback (bcnc, exec_res == (gpointer) 0x01 ? NULL : exec_res,
+					      wj->cb_data, lerror);
+				g_clear_error (&lerror);
+			}
+			break;
+		default:
+			g_assert_not_reached ();
+			break;
 		}
 
 		pop_wrapper_job (bcnc, wj);
@@ -900,7 +921,7 @@ browser_connection_update_meta_data (BrowserConnection *bcnc)
 					     g_object_ref (bcnc), g_object_unref, &lerror);
 	if (job_id > 0)
 		push_wrapper_job (bcnc, job_id, JOB_TYPE_META_STORE_UPDATE,
-				  _("Getting database schema information"));
+				  _("Getting database schema information"), NULL, NULL);
 	else if (lerror) {
 		browser_show_error (NULL, _("Error while fetching meta data from the connection: %s"),
 				    lerror->message ? lerror->message : _("No detail"));
@@ -1011,13 +1032,13 @@ browser_connection_rollback (BrowserConnection *bcnc, GError **error)
  *
  * Get @bcnc's favorites handler
  *
- * Returns: the #BrowserFavorites used by @bcnc
+ * Returns: (transfer none): the #BrowserFavorites used by @bcnc
  */
 BrowserFavorites *
 browser_connection_get_favorites (BrowserConnection *bcnc)
 {
 	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
-	if (!bcnc->priv->bfav) {
+	if (!bcnc->priv->bfav && !BROWSER_IS_VIRTUAL_CONNECTION (bcnc)) {
 		bcnc->priv->bfav = browser_favorites_new (gda_connection_get_meta_store (bcnc->priv->cnc));
 		g_signal_connect (bcnc->priv->bfav, "favorites-changed",
 				  G_CALLBACK (fav_changed_cb), bcnc);
@@ -1100,10 +1121,11 @@ wrapper_statement_execute (StmtExecData *data, GError **error)
 {
 	GObject *obj;
 	GdaSet *last_insert_row = NULL;
+	GError *lerror = NULL;
 	obj = gda_connection_statement_execute (data->cnc, data->stmt,
 						data->params, data->model_usage,
 						data->need_last_insert_row ? &last_insert_row : NULL,
-						error);
+						&lerror);
 	if (obj) {
 		if (GDA_IS_DATA_MODEL (obj))
 			/* force loading of rows if necessary */
@@ -1111,6 +1133,15 @@ wrapper_statement_execute (StmtExecData *data, GError **error)
 		else if (last_insert_row)
 			g_object_set_data (obj, "__bcnc_last_inserted_row", last_insert_row);
 	}
+	else {
+		if (lerror)
+			g_propagate_error (error, lerror);
+		else {
+			g_warning (_("Execution reported an undefined error, please report error to "
+				     "http://bugzilla.gnome.org/ for the \"libgda\" product"));
+			g_set_error (error, 0, 0, _("No detail"));
+		}
+	}
 	return obj ? obj : (gpointer) 0x01;
 }
 
@@ -1156,7 +1187,7 @@ browser_connection_execute_statement (BrowserConnection *bcnc,
 					     data, (GDestroyNotify) g_free, error);
 	if (job_id > 0)
 		push_wrapper_job (bcnc, job_id, JOB_TYPE_STATEMENT_EXECUTE,
-				  _("Executing a query"));
+				  _("Executing a query"), NULL, NULL);
 
 	return job_id;
 }
@@ -1206,7 +1237,7 @@ browser_connection_rerun_select (BrowserConnection *bcnc,
 					     data, (GDestroyNotify) g_free, error);
 	if (job_id > 0)
 		push_wrapper_job (bcnc, job_id, JOB_TYPE_STATEMENT_EXECUTE,
-				  _("Executing a query"));
+				  _("Executing a query"), NULL, NULL);
 
 	return job_id;
 }
@@ -1261,6 +1292,23 @@ browser_connection_execution_get_result (BrowserConnection *bcnc, guint exec_id,
 	return retval;
 }
 
+/**
+ * browser_connection_job_cancel:
+ * @bcnc: a #BrowserConnection
+ * @job_id: the job_id to cancel
+ *
+ * Cancel a job, from the job ID returned by browser_connection_ldap_describe_entry()
+ * or browser_connection_ldap_get_entry_children().
+ */
+void
+browser_connection_job_cancel (BrowserConnection *bcnc, guint job_id)
+{
+	g_return_if_fail (BROWSER_IS_CONNECTION (bcnc));
+	g_return_if_fail (job_id > 0);
+
+	TO_IMPLEMENT;
+}
+
 static gboolean query_exec_fetch_cb (BrowserConnection *bcnc);
 
 typedef struct {
@@ -2143,3 +2191,442 @@ browser_connection_load_variables (BrowserConnection *bcnc, GdaSet *set)
 		}
 	}	
 }
+
+/**
+ * browser_connection_is_ldap:
+ * @bcnc: a #BrowserConnection
+ *
+ * Returns: %TRUE if @bcnc proxies an LDAP connection
+ */
+gboolean
+browser_connection_is_ldap (BrowserConnection *bcnc)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), FALSE);
+
+#ifdef HAVE_LDAP
+	return GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc) ? TRUE : FALSE;
+#endif
+	return FALSE;
+}
+
+#ifdef HAVE_LDAP
+
+typedef struct {
+	GdaConnection *cnc;
+	gchar *base_dn;
+	gchar *attributes;
+	gchar *filter;
+	GdaLdapSearchScope scope;
+} LdapSearchData;
+static void
+ldap_search_data_free (LdapSearchData *data)
+{
+	g_free (data->base_dn);
+	g_free (data->filter);
+	g_free (data->attributes);
+	g_free (data);
+}
+
+/* executed in sub @bcnc->priv->wrapper's thread */
+static gpointer
+wrapper_ldap_search (LdapSearchData *data, GError **error)
+{
+	GdaDataModel *model;
+	model = gda_data_model_ldap_new (GDA_CONNECTION (data->cnc), data->base_dn,
+					 data->filter, data->attributes, data->scope);
+	if (!model) {
+		g_set_error (error, 0, 0,
+			     _("Could not execute LDAP search"));
+		return (gpointer) 0x01;
+	}
+	else {
+		GdaDataModel *wrapped;
+		gint nb;
+		wrapped = gda_data_access_wrapper_new (model);
+		g_object_unref (model);
+		/* force loading all the LDAP entries in memory to avoid
+		 * having the GTK thread lock on LDAP searches */
+		nb = gda_data_model_get_n_rows (wrapped);
+		return wrapped;
+	}
+}
+
+/**
+ * browser_connection_ldap_search:
+ *
+ * Executes an LDAP search. Wrapper around gda_data_model_ldap_new()
+ *
+ * Returns: a job ID, or %0 if an error occurred
+ */
+guint
+browser_connection_ldap_search (BrowserConnection *bcnc,
+				const gchar *base_dn, const gchar *filter,
+				const gchar *attributes, GdaLdapSearchScope scope,
+				BrowserConnectionJobCallback callback,
+				gpointer cb_data, GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), 0);
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc), 0);
+
+	LdapSearchData *data;
+	guint job_id;
+	data = g_new0 (LdapSearchData, 1);
+	data->cnc = bcnc->priv->cnc;
+	data->base_dn = g_strdup (base_dn);
+	data->filter = g_strdup (filter);
+	data->attributes = g_strdup (attributes);
+	data->scope = scope;
+
+	job_id = gda_thread_wrapper_execute (bcnc->priv->wrapper,
+					     (GdaThreadWrapperFunc) wrapper_ldap_search,
+					     data, (GDestroyNotify) ldap_search_data_free, error);
+	if (job_id > 0)
+		push_wrapper_job (bcnc, job_id, JOB_TYPE_CALLBACK,
+				  _("Executing LDAP search"), callback, cb_data);
+	return job_id;
+}
+
+
+typedef struct {
+	GdaConnection *cnc;
+	gchar *dn;
+	gchar **attributes;
+} LdapData;
+static void
+ldap_data_free (LdapData *data)
+{
+	g_free (data->dn);
+	if (data->attributes)
+		g_strfreev (data->attributes);
+	g_free (data);
+}
+
+/* executed in sub @bcnc->priv->wrapper's thread */
+static gpointer
+wrapper_ldap_describe_entry (LdapData *data, GError **error)
+{
+	GdaLdapEntry *lentry;
+	lentry = gda_ldap_describe_entry (GDA_LDAP_CONNECTION (data->cnc), data->dn, error);
+	return lentry ? lentry : (gpointer) 0x01;
+}
+
+/**
+ * browser_connection_ldap_describe_entry:
+ * @bcnc: a #BrowserConnection
+ * @dn: the DN of the entry to describe
+ * @callback: the callback to execute with the results
+ * @cb_data: a pointer to pass to @callback
+ * @error: a place to store errors, or %NULL
+ *
+ * Wrapper around gda_ldap_describe_entry().
+ *
+ * Returns: a job ID, or %0 if an error occurred
+ */
+guint
+browser_connection_ldap_describe_entry (BrowserConnection *bcnc, const gchar *dn,
+					BrowserConnectionJobCallback callback,
+					gpointer cb_data, GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), 0);
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc), 0);
+
+	LdapData *data;
+	guint job_id;
+
+	data = g_new0 (LdapData, 1);
+	data->cnc = bcnc->priv->cnc;
+	data->dn = g_strdup (dn);
+
+	job_id = gda_thread_wrapper_execute (bcnc->priv->wrapper,
+					     (GdaThreadWrapperFunc) wrapper_ldap_describe_entry,
+					     data, (GDestroyNotify) ldap_data_free, error);
+	if (job_id > 0)
+		push_wrapper_job (bcnc, job_id, JOB_TYPE_CALLBACK,
+				  _("Fetching LDAP entry's attributes"), callback, cb_data);
+
+	return job_id;
+}
+
+/* executed in sub @bcnc->priv->wrapper's thread */
+static gpointer
+wrapper_ldap_get_entry_children (LdapData *data, GError **error)
+{
+	GdaLdapEntry **array;
+	array = gda_ldap_get_entry_children (GDA_LDAP_CONNECTION (data->cnc), data->dn, data->attributes, error);
+	return array ? array : (gpointer) 0x01;
+}
+
+/**
+ * browser_connection_ldap_get_entry_children:
+ * @bcnc: a #BrowserConnection
+ * @dn: the DN of the entry to get children from
+ * @callback: the callback to execute with the results
+ * @cb_data: a pointer to pass to @callback
+ * @error: a place to store errors, or %NULL
+ *
+ * Wrapper around gda_ldap_get_entry_children().
+ *
+ * Returns: a job ID, or %0 if an error occurred
+ */
+guint
+browser_connection_ldap_get_entry_children (BrowserConnection *bcnc, const gchar *dn,
+					    gchar **attributes,
+					    BrowserConnectionJobCallback callback,
+					    gpointer cb_data, GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), 0);
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc), 0);
+
+	LdapData *data;
+	guint job_id;
+
+	data = g_new0 (LdapData, 1);
+	data->cnc = bcnc->priv->cnc;
+	data->dn = g_strdup (dn);
+	if (attributes) {
+		gint i;
+		GArray *array = NULL;
+		for (i = 0; attributes [i]; i++) {
+			gchar *tmp;
+			if (! array)
+				array = g_array_new (TRUE, FALSE, sizeof (gchar*));
+			tmp = g_strdup (attributes [i]);
+			g_array_append_val (array, tmp);
+		}
+		if (array)
+			data->attributes = (gchar**) g_array_free (array, FALSE);
+	}
+
+	job_id = gda_thread_wrapper_execute (bcnc->priv->wrapper,
+					     (GdaThreadWrapperFunc) wrapper_ldap_get_entry_children,
+					     data, (GDestroyNotify) ldap_data_free, error);
+	if (job_id > 0)
+		push_wrapper_job (bcnc, job_id, JOB_TYPE_CALLBACK,
+				  _("Fetching LDAP entry's children"), callback, cb_data);
+
+	return job_id;
+}
+
+/**
+ * browser_connection_ldap_icon_for_class:
+ * @objectclass: objectClass attribute
+ *
+ * Returns: the correct icon, or %NULL if it could not be determined
+ */
+GdkPixbuf *
+browser_connection_ldap_icon_for_class (GdaLdapAttribute *objectclass)
+{
+	gint type = 0;
+	if (objectclass) {
+		guint i;
+		for (i = 0; i < objectclass->nb_values; i++) {
+			const gchar *class = g_value_get_string (objectclass->values[i]);
+			if (!class)
+				continue;
+			else if (!strcmp (class, "organization"))
+				type = MAX(type, 1);
+			else if (!strcmp (class, "groupOfNames") ||
+				 !strcmp (class, "posixGroup"))
+				type = MAX(type, 2);
+			else if (!strcmp (class, "account") ||
+				 !strcmp (class, "mailUser") ||
+				 !strcmp (class, "organizationalPerson") ||
+				 !strcmp (class, "person") ||
+				 !strcmp (class, "pilotPerson") ||
+				 !strcmp (class, "newPilotPerson") ||
+				 !strcmp (class, "pkiUser") ||
+				 !strcmp (class, "posixUser") ||
+				 !strcmp (class, "posixAccount") ||
+				 !strcmp (class, "residentalPerson") ||
+				 !strcmp (class, "shadowAccount") ||
+				 !strcmp (class, "strongAuthenticationUser") ||
+				 !strcmp (class, "inetOrgPerson"))
+				type = MAX(type, 3);
+		}
+	}
+
+	switch (type) {
+	case 0:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_ENTRY);
+	case 1:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_ORGANIZATION);
+	case 2:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_GROUP);
+	case 3:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_PERSON);
+	default:
+		g_assert_not_reached ();
+	}
+	return NULL;
+}
+
+typedef struct {
+	BrowserConnectionJobCallback callback;
+	gpointer cb_data;
+} IconTypeData;
+
+static void
+fetch_classes_cb (G_GNUC_UNUSED BrowserConnection *bcnc,
+		  gpointer out_result, IconTypeData *data, G_GNUC_UNUSED GError *error)
+{
+	GdkPixbuf *pixbuf = NULL;
+	if (out_result) {
+		GdaLdapEntry *lentry = (GdaLdapEntry*) out_result;
+		GdaLdapAttribute *attr;
+		attr = g_hash_table_lookup (lentry->attributes_hash, "objectClass");
+		pixbuf = browser_connection_ldap_icon_for_class (attr);
+		gda_ldap_entry_free (lentry);
+	}
+	if (data->callback)
+		data->callback (bcnc, pixbuf, data->cb_data, error);
+
+	g_free (data);
+}
+
+/**
+ * browser_connection_ldap_icon_for_dn:
+ * @bcnc: a #BrowserConnection
+ * @dn: the DN of the entry
+ * @callback: the callback to execute with the results
+ * @cb_data: a pointer to pass to @callback
+ * @error: a place to store errors, or %NULL
+ *
+ * Determines the correct icon type for @dn based on which class it belongs to
+ *
+ * Returns: a job ID, or %0 if an error occurred
+ */
+guint
+browser_connection_ldap_icon_for_dn (BrowserConnection *bcnc, const gchar *dn,
+				     BrowserConnectionJobCallback callback,
+				     gpointer cb_data, GError **error)
+{
+	IconTypeData *data;
+	guint job_id;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), 0);
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc), 0);
+	g_return_val_if_fail (dn && *dn, 0);
+
+	data = g_new (IconTypeData, 1);
+	data->callback = callback;
+	data->cb_data = cb_data;
+	job_id = browser_connection_ldap_describe_entry (bcnc, dn,
+							 BROWSER_CONNECTION_JOB_CALLBACK (fetch_classes_cb),
+							 data, error);
+	return job_id;
+}
+
+/**
+ * browser_connection_ldap_get_base_dn:
+ * @bcnc: a #BrowserConnection
+ *
+ * wrapper for gda_ldap_connection_get_base_dn()
+ *
+ * Returns: the base DN or %NULL
+ */
+const gchar *
+browser_connection_ldap_get_base_dn (BrowserConnection *bcnc)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+	g_return_val_if_fail (GDA_IS_LDAP_CONNECTION (bcnc->priv->cnc), NULL);
+
+	return gda_ldap_connection_get_base_dn (GDA_LDAP_CONNECTION (bcnc->priv->cnc));
+}
+
+/**
+ * browser_connection_describe_table:
+ * @bcnc: a #BrowserConnection
+ * @table_name: a table name, not %NULL
+ * @out_base_dn: (allow-none) (transfer none): a place to store the LDAP search base DN, or %NULL
+ * @out_filter: (allow-none) (transfer none): a place to store the LDAP search filter, or %NULL
+ * @out_attributes: (allow-none) (transfer none): a place to store the LDAP search attributes, or %NULL
+ * @out_scope: (allow-none) (transfer none): a place to store the LDAP search scope, or %NULL
+ * @error: a place to store errors, or %NULL
+ */
+gboolean
+browser_connection_describe_table  (BrowserConnection *bcnc, const gchar *table_name,
+				    const gchar **out_base_dn, const gchar **out_filter,
+				    const gchar **out_attributes,
+				    GdaLdapSearchScope *out_scope, GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), FALSE);
+	g_return_val_if_fail (browser_connection_is_ldap (bcnc), FALSE);
+	g_return_val_if_fail (table_name && *table_name, FALSE);
+
+	return gda_ldap_connection_describe_table (GDA_LDAP_CONNECTION (bcnc->priv->cnc),
+						   table_name, out_base_dn, out_filter,
+						   out_attributes, out_scope, error);
+}
+
+/**
+ * browser_connection_get_class_info:
+ * @bcnc: a #BrowserConnection
+ * @classname: a class name
+ *
+ * proxy for gda_ldap_get_class_info.
+ */
+GdaLdapClass *
+browser_connection_get_class_info (BrowserConnection *bcnc, const gchar *classname)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+	g_return_val_if_fail (browser_connection_is_ldap (bcnc), NULL);
+
+	return gda_ldap_get_class_info (GDA_LDAP_CONNECTION (bcnc->priv->cnc), classname);
+}
+
+/**
+ * browser_connection_get_top_classes:
+ * @bcnc: a #BrowserConnection
+ *
+ * proxy for gda_ldap_get_top_classes.
+ */
+const GSList *
+browser_connection_get_top_classes (BrowserConnection *bcnc)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+	g_return_val_if_fail (browser_connection_is_ldap (bcnc), NULL);
+
+	return gda_ldap_get_top_classes (GDA_LDAP_CONNECTION (bcnc->priv->cnc));
+}
+
+/**
+ * browser_connection_declare_table:
+ * @bcnc: a #BrowserConnection
+ *
+ * Wrapper around gda_ldap_connection_declare_table()
+ */
+gboolean
+browser_connection_declare_table (BrowserConnection *bcnc,
+				  const gchar *table_name,
+				  const gchar *base_dn,
+				  const gchar *filter,
+				  const gchar *attributes,
+				  GdaLdapSearchScope scope,
+				  GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), FALSE);
+	g_return_val_if_fail (browser_connection_is_ldap (bcnc), FALSE);
+
+	return gda_ldap_connection_declare_table (GDA_LDAP_CONNECTION (bcnc->priv->cnc),
+						  table_name, base_dn, filter,
+						  attributes, scope, error);
+}
+
+/**
+ * browser_connection_undeclare_table:
+ * @bcnc: a #BrowserConnection
+ *
+ * Wrapper around gda_ldap_connection_undeclare_table()
+ */
+gboolean
+browser_connection_undeclare_table (BrowserConnection *bcnc,
+				    const gchar *table_name,
+				    GError **error)
+{
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), FALSE);
+	g_return_val_if_fail (browser_connection_is_ldap (bcnc), FALSE);
+
+	return gda_ldap_connection_undeclare_table (GDA_LDAP_CONNECTION (bcnc->priv->cnc),
+						    table_name, error);
+}
+
+#endif /* HAVE_LDAP */
diff --git a/tools/browser/browser-connection.h b/tools/browser/browser-connection.h
index 065ed39..2a0ffc1 100644
--- a/tools/browser/browser-connection.h
+++ b/tools/browser/browser-connection.h
@@ -24,6 +24,10 @@
 #include <libgda/libgda.h>
 #include "browser-favorites.h"
 #include "decl.h"
+#include "support.h"
+#ifdef HAVE_LDAP
+#include <libgda/sqlite/virtual/gda-ldap-connection.h>
+#endif
 
 G_BEGIN_DECLS
 
@@ -76,6 +80,24 @@ BrowserFavorites   *browser_connection_get_favorites          (BrowserConnection
 gchar             **browser_connection_get_completions        (BrowserConnection *bcnc, const gchar *sql,
 							       gint start, gint end);
 
+/**
+ * BrowserConnectionJobCallback:
+ * @bcnc: the #BrowserConnection
+ * @out_result: the execution result
+ * @data: a pointer passed when calling the execution function such as browser_connection_ldap_describe_entry()
+ * @error: the error returned, if any
+ *
+ * Callback function called when a job (not a statement execution job) is finished.
+ * the out_result is not used by the BrowserConnection anymore after this function has been
+ * called, so you need to keep it or free it.
+ *
+ * @error should not be modified.
+ */
+typedef void (*BrowserConnectionJobCallback) (BrowserConnection *bcnc,
+					      gpointer out_result, gpointer data, GError *error);
+#define BROWSER_CONNECTION_JOB_CALLBACK(x) ((BrowserConnectionJobCallback)(x))
+void                browser_connection_job_cancel             (BrowserConnection *bcnc, guint job_id);
+
 /*
  * statements's manipulations
  */
@@ -162,6 +184,49 @@ void                 browser_connection_define_ui_plugins_for_stmt (BrowserConne
 void                 browser_connection_keep_variables (BrowserConnection *bcnc, GdaSet *set);
 void                 browser_connection_load_variables (BrowserConnection *bcnc, GdaSet *set);
 
+/*
+ * LDAP
+ */
+gboolean             browser_connection_is_ldap        (BrowserConnection *bcnc);
+#ifdef HAVE_LDAP
+const         gchar *browser_connection_ldap_get_base_dn (BrowserConnection *bcnc);
+guint                browser_connection_ldap_search (BrowserConnection *bcnc,
+						     const gchar *base_dn, const gchar *filter,
+						     const gchar *attributes, GdaLdapSearchScope scope,
+						     BrowserConnectionJobCallback callback,
+						     gpointer cb_data, GError **error);
+guint                browser_connection_ldap_describe_entry (BrowserConnection *bcnc, const gchar *dn,
+							     BrowserConnectionJobCallback callback,
+							     gpointer cb_data, GError **error);
+guint                browser_connection_ldap_get_entry_children (BrowserConnection *bcnc, const gchar *dn,
+								 gchar **attributes,
+								 BrowserConnectionJobCallback callback,
+								 gpointer cb_data, GError **error);
+guint                browser_connection_ldap_icon_for_dn (BrowserConnection *bcnc, const gchar *dn,
+							  BrowserConnectionJobCallback callback,
+							  gpointer cb_data, GError **error);
+GdkPixbuf           *browser_connection_ldap_icon_for_class (GdaLdapAttribute *objectclass);
+gboolean             browser_connection_describe_table  (BrowserConnection *bcnc, const gchar *table_name,
+							 const gchar **out_base_dn, const gchar **out_filter,
+							 const gchar **out_attributes,
+							 GdaLdapSearchScope *out_scope, GError **error);
+
+GdaLdapClass        *browser_connection_get_class_info (BrowserConnection *bcnc, const gchar *classname);
+const GSList        *browser_connection_get_top_classes (BrowserConnection *bcnc);
+
+gboolean             browser_connection_declare_table   (BrowserConnection *bcnc,
+                                                         const gchar *table_name,
+                                                         const gchar *base_dn,
+                                                         const gchar *filter,
+                                                         const gchar *attributes,
+                                                         GdaLdapSearchScope scope,
+                                                         GError **error);
+gboolean             browser_connection_undeclare_table   (BrowserConnection *bcnc,
+							   const gchar *table_name,
+							   GError **error);
+
+#endif
+
 G_END_DECLS
 
 #endif
diff --git a/tools/browser/browser-favorites.c b/tools/browser/browser-favorites.c
index d90587f..1de1673 100644
--- a/tools/browser/browser-favorites.c
+++ b/tools/browser/browser-favorites.c
@@ -201,8 +201,14 @@ meta_store_addons_init (BrowserFavorites *bfav, GError **error)
 	return TRUE;
 }
 
-static const gchar *
-favorite_type_to_string (BrowserFavoritesType type)
+/**
+ * browser_favorites_type_to_string:
+ * @type: a #BrowserFavoritesType
+ *
+ * Returns: a string representing @type
+ */
+const gchar *
+browser_favorites_type_to_string (BrowserFavoritesType type)
 {
 	switch (type) {
 	case BROWSER_FAVORITES_TABLES:
@@ -215,6 +221,10 @@ favorite_type_to_string (BrowserFavoritesType type)
 		return "DATAMAN";
 	case BROWSER_FAVORITES_ACTIONS:
 		return "ACTION";
+	case BROWSER_FAVORITES_LDAP_DN:
+		return "LDAP_DN";
+	case BROWSER_FAVORITES_LDAP_CLASS:
+		return "LDAP_CLASS";
 	default:
 		g_warning ("Unknown type of favorite");
 	}
@@ -237,6 +247,12 @@ favorite_string_to_type (const gchar *str)
 		return BROWSER_FAVORITES_QUERIES;
 	else if (*str == 'A')
 		return BROWSER_FAVORITES_ACTIONS;
+	else if (*str == 'L') {
+		if (strlen (str) == 7)
+			return BROWSER_FAVORITES_LDAP_DN;
+		else
+			return BROWSER_FAVORITES_LDAP_CLASS;
+	}
 	else
 		g_warning ("Unknown type '%s' of favorite", str);
 	return 0;
@@ -555,7 +571,7 @@ browser_favorites_add (BrowserFavorites *bfav, guint session_id,
 	params = gda_set_new_inline (8,
 				     "session", G_TYPE_INT, session_id,
 				     "id", G_TYPE_INT, fav->id,
-				     "type", G_TYPE_STRING, favorite_type_to_string (rtype),
+				     "type", G_TYPE_STRING, browser_favorites_type_to_string (rtype),
 				     "name", G_TYPE_STRING, fav->name ? fav->name : efav.name,
 				     "contents", G_TYPE_STRING, fav->contents,
 				     "rank", G_TYPE_INT, pos,
@@ -719,7 +735,7 @@ browser_favorites_add (BrowserFavorites *bfav, guint session_id,
 		g_object_unref (params);
 	gda_lockable_unlock (GDA_LOCKABLE (store_cnc));
 	g_signal_emit (bfav, browser_favorites_signals [FAV_CHANGED],
-		       g_quark_from_string (favorite_type_to_string (rtype)));
+		       g_quark_from_string (browser_favorites_type_to_string (rtype)));
 	return TRUE;
 
  err:
@@ -842,7 +858,7 @@ browser_favorites_list (BrowserFavorites *bfav, guint session_id, BrowserFavorit
 	for (i = 0, flag = 1; i < BROWSER_FAVORITES_NB_TYPES; i++, flag <<= 1) {
 		if (type & flag) {
 			gchar *str;
-			str = g_strdup_printf ("'%s'", favorite_type_to_string (flag));
+			str = g_strdup_printf ("'%s'", browser_favorites_type_to_string (flag));
 			or_cond_ids [or_cond_size] = gda_sql_builder_add_cond (b, GDA_SQL_OPERATOR_TYPE_EQ,
 							     gda_sql_builder_add_id (b, "fav.type"),
 							     gda_sql_builder_add_id (b, str),
@@ -1037,7 +1053,7 @@ browser_favorites_delete (BrowserFavorites *bfav, guint session_id,
 	gda_lockable_unlock (GDA_LOCKABLE (bfav->priv->store_cnc));
 	if (retval)
 		g_signal_emit (bfav, browser_favorites_signals [FAV_CHANGED],
-			       g_quark_from_string (favorite_type_to_string (efav.type)));
+			       g_quark_from_string (browser_favorites_type_to_string (efav.type)));
 	browser_favorites_reset_attributes (&efav);
 	if (params)
 		g_object_unref (G_OBJECT (params));
diff --git a/tools/browser/browser-favorites.h b/tools/browser/browser-favorites.h
index 65e3756..e050364 100644
--- a/tools/browser/browser-favorites.h
+++ b/tools/browser/browser-favorites.h
@@ -47,9 +47,16 @@ typedef enum {
 	BROWSER_FAVORITES_DIAGRAMS = 1 << 1,
 	BROWSER_FAVORITES_QUERIES  = 1 << 2,
 	BROWSER_FAVORITES_DATA_MANAGERS  = 1 << 3,
-	BROWSER_FAVORITES_ACTIONS  = 1 << 4
+	BROWSER_FAVORITES_ACTIONS  = 1 << 4,
+	BROWSER_FAVORITES_LDAP_DN  = 1 << 5,
+	BROWSER_FAVORITES_LDAP_CLASS = 1 << 6
 } BrowserFavoritesType;
-#define BROWSER_FAVORITES_NB_TYPES 5
+#define BROWSER_FAVORITES_NB_TYPES 7
+
+#define ORDER_KEY_SCHEMA 1
+#define ORDER_KEY_QUERIES 2
+#define ORDER_KEY_DATA_MANAGERS 3
+#define ORDER_KEY_LDAP 4
 
 /**
  * BrowserFavoritesAttributes:
@@ -85,13 +92,14 @@ struct _BrowserFavoritesClass
 GType               browser_favorites_get_type               (void) G_GNUC_CONST;
 
 BrowserFavorites   *browser_favorites_new                    (GdaMetaStore *store);
-
+const gchar        *browser_favorites_type_to_string (BrowserFavoritesType type);
 gboolean            browser_favorites_add          (BrowserFavorites *bfav, guint session_id,
 						    BrowserFavoritesAttributes *fav,
 						    gint order_key, gint pos,
 						    GError **error);
 GSList             *browser_favorites_list         (BrowserFavorites *bfav, guint session_id, 
 						    BrowserFavoritesType type, gint order_key, GError **error);
+
 gboolean            browser_favorites_delete       (BrowserFavorites *bfav, guint session_id,
 						    BrowserFavoritesAttributes *fav, GError **error);
 void                browser_favorites_free_list    (GSList *fav_list);
diff --git a/tools/browser/browser-perspective.c b/tools/browser/browser-perspective.c
index 97dd40b..ef73be8 100644
--- a/tools/browser/browser-perspective.c
+++ b/tools/browser/browser-perspective.c
@@ -22,6 +22,8 @@
 
 #include "browser-perspective.h"
 #include "browser-page.h"
+#include "browser-window.h"
+#include "support.h"
 
 static GStaticRecMutex init_mutex = G_STATIC_REC_MUTEX_INIT;
 static void browser_perspective_class_init (gpointer g_class);
@@ -150,3 +152,139 @@ browser_perspective_page_tab_label_change (BrowserPerspective *pers, BrowserPage
 	if (BROWSER_PERSPECTIVE_GET_CLASS (pers)->i_page_tab_label_change)
 		(BROWSER_PERSPECTIVE_GET_CLASS (pers)->i_page_tab_label_change) (pers, page);
 }
+
+/**
+ * browser_perspective_get_window:
+ * @pers: an object implementing the #BrowserPerspective interface
+ *
+ * Returns: (transfer none): the #BrowserWindow @perspective is in
+ */
+BrowserWindow *
+browser_perspective_get_window (BrowserPerspective *pers)
+{
+	g_return_val_if_fail (IS_BROWSER_PERSPECTIVE (pers), NULL);
+	if (BROWSER_PERSPECTIVE_GET_CLASS (pers)->i_get_window)
+		return (BROWSER_PERSPECTIVE_GET_CLASS (pers)->i_get_window) (pers);
+	else
+		return (BrowserWindow*) browser_find_parent_widget (GTK_WIDGET (pers),
+								    BROWSER_PERSPECTIVE_TYPE);
+}
+
+static void nb_page_added_or_removed_cb (GtkNotebook *nb, GtkWidget *child, guint page_num,
+					 BrowserPerspective *perspective);
+static void nb_switch_page_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
+			       BrowserPerspective *perspective);
+static void adapt_notebook_for_fullscreen (BrowserPerspective *perspective);;
+static void fullscreen_changed_cb (BrowserWindow *bwin, gboolean fullscreen, BrowserPerspective *perspective);
+
+/**
+ * browser_perspective_declare_notebook:
+ * @pers: an object implementing the #BrowserPerspective interface
+ * @nb: a #GtkNotebook
+ *
+ * Internally used by browser's perspectives to declare they internally use a notebook
+ * which state is modified when switching to and from fullscreen
+ */
+void
+browser_perspective_declare_notebook (BrowserPerspective *perspective, GtkNotebook *nb)
+{
+	BrowserWindow *bwin;
+	g_return_if_fail (IS_BROWSER_PERSPECTIVE (perspective));
+	g_return_if_fail (! nb || GTK_IS_NOTEBOOK (nb));
+	
+	bwin = browser_perspective_get_window (perspective);
+	if (!bwin)
+		return;
+
+	GtkNotebook *onb;
+	onb = g_object_get_data (G_OBJECT (perspective), "fullscreen_nb");
+	if (onb) {
+		g_signal_handlers_disconnect_by_func (onb,
+						      G_CALLBACK (nb_page_added_or_removed_cb),
+						      perspective);
+		g_signal_handlers_disconnect_by_func (onb,
+						      G_CALLBACK (nb_switch_page_cb),
+						      perspective);
+		g_signal_handlers_disconnect_by_func (bwin,
+						      G_CALLBACK (fullscreen_changed_cb), perspective);
+
+	}
+
+	g_object_set_data (G_OBJECT (perspective), "fullscreen_nb", nb);
+	if (nb) {
+		g_signal_connect (bwin, "fullscreen-changed",
+				  G_CALLBACK (fullscreen_changed_cb), perspective);
+		g_signal_connect (nb, "page-added",
+				  G_CALLBACK (nb_page_added_or_removed_cb), perspective);
+		g_signal_connect (nb, "page-removed",
+				  G_CALLBACK (nb_page_added_or_removed_cb), perspective);
+		g_signal_connect (nb, "switch-page",
+				  G_CALLBACK (nb_switch_page_cb), perspective);
+		adapt_notebook_for_fullscreen (perspective);
+	}
+}
+
+static void
+nb_switch_page_cb (GtkNotebook *nb, GtkWidget *page, gint page_num, BrowserPerspective *perspective)
+{
+	GtkWidget *page_contents;
+	GtkActionGroup *actions = NULL;
+	const gchar *ui = NULL;
+	BrowserWindow *bwin;
+
+	page_contents = gtk_notebook_get_nth_page (nb, page_num);
+	if (IS_BROWSER_PAGE (page_contents)) {
+		actions = browser_page_get_actions_group (BROWSER_PAGE (page_contents));
+		ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
+	}
+
+	bwin = browser_perspective_get_window (perspective);
+	if (bwin)
+		browser_window_customize_perspective_ui (bwin, perspective, actions, ui);
+	if (actions)
+		g_object_unref (actions);
+}
+
+static void
+nb_page_added_or_removed_cb (GtkNotebook *nb, GtkWidget *child, guint page_num,
+			     BrowserPerspective *perspective)
+{
+	adapt_notebook_for_fullscreen (perspective);
+	if (gtk_notebook_get_n_pages (nb) == 0) {
+		BrowserWindow *bwin;
+		bwin = browser_perspective_get_window (perspective);
+		if (!bwin)
+			return;
+		browser_window_customize_perspective_ui (bwin,
+							 BROWSER_PERSPECTIVE (perspective),
+							 NULL, NULL);
+	}
+}
+
+static void
+fullscreen_changed_cb (G_GNUC_UNUSED BrowserWindow *bwin, gboolean fullscreen,
+		       BrowserPerspective *perspective)
+{
+	adapt_notebook_for_fullscreen (perspective);
+}
+
+static void
+adapt_notebook_for_fullscreen (BrowserPerspective *perspective)
+{
+	gboolean showtabs = TRUE;
+	gboolean fullscreen;
+	BrowserWindow *bwin;
+	GtkNotebook *nb;
+
+	bwin = browser_perspective_get_window (perspective);
+	if (!bwin)
+		return;
+	nb = g_object_get_data (G_OBJECT (perspective), "fullscreen_nb");
+	if (!nb)
+		return;
+
+	fullscreen = browser_window_is_fullscreen (bwin);
+	if (fullscreen && gtk_notebook_get_n_pages (nb) == 1)
+		showtabs = FALSE;
+	gtk_notebook_set_show_tabs (nb, showtabs);
+}
diff --git a/tools/browser/browser-perspective.h b/tools/browser/browser-perspective.h
index e6e865b..e68233f 100644
--- a/tools/browser/browser-perspective.h
+++ b/tools/browser/browser-perspective.h
@@ -38,6 +38,7 @@ struct _BrowserPerspectiveIface {
 	GTypeInterface           g_iface;
 
 	/* virtual table */
+	BrowserWindow       *(* i_get_window) (BrowserPerspective *perspective);
 	GtkActionGroup      *(* i_get_actions_group) (BrowserPerspective *perspective);
 	const gchar         *(* i_get_actions_ui) (BrowserPerspective *perspective);
 	void                 (* i_get_current_customization) (BrowserPerspective *perspective,
@@ -56,6 +57,9 @@ void            browser_perspective_get_current_customization (BrowserPerspectiv
 void            browser_perspective_page_tab_label_change (BrowserPerspective *perspective,
 							   BrowserPage *page);
 
+BrowserWindow  *browser_perspective_get_window (BrowserPerspective *perspective);
+void            browser_perspective_declare_notebook (BrowserPerspective *perspective, GtkNotebook *nb);
+
 G_END_DECLS
 
 #endif
diff --git a/tools/browser/browser-stock-icons.c b/tools/browser/browser-stock-icons.c
index 0fa9d4d..965d779 100644
--- a/tools/browser/browser-stock-icons.c
+++ b/tools/browser/browser-stock-icons.c
@@ -1,6 +1,6 @@
 /*
  *  Copyright © 2002 Jorn Baayen
- *  Copyright © 2009 Vivien Malerba <malerba nome-db org>
+ *  Copyright © 2009 - 2011 Vivien Malerba <malerba nome-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
@@ -48,6 +48,8 @@ browser_stock_icons_init (void)
 		{ BROWSER_STOCK_COMMIT, N_("Commit"), 0, 0, NULL },
 		{ BROWSER_STOCK_ROLLBACK, N_("Rollback"), 0, 0, NULL },
 		{ BROWSER_STOCK_BUILDER, N_("Builder"), 0, 0, NULL },
+		{ BROWSER_STOCK_LDAP_ENTRIES, N_("Ldap entries"), 0, 0, NULL },
+		{ BROWSER_STOCK_TABLE_ADD, N_("Add table"), 0, 0, NULL},
 	};
 
 	factory = gtk_icon_factory_new ();
diff --git a/tools/browser/browser-stock-icons.h b/tools/browser/browser-stock-icons.h
index 4f132d4..6b1b230 100644
--- a/tools/browser/browser-stock-icons.h
+++ b/tools/browser/browser-stock-icons.h
@@ -1,6 +1,6 @@
 /*
  *  Copyright © 2002 Jorn Baayen
- *  Copyright © 2009 Vivien Malerba <malerba nome-db org>
+ *  Copyright © 2009 - 2011 Vivien Malerba <malerba nome-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
@@ -28,6 +28,9 @@ G_BEGIN_DECLS
 #define BROWSER_STOCK_COMMIT          "transaction-commit"
 #define BROWSER_STOCK_ROLLBACK        "transaction-rollback"
 #define BROWSER_STOCK_BUILDER         "glade"
+#define BROWSER_STOCK_LDAP_ENTRIES    "ldap-entries"
+#define BROWSER_STOCK_TABLE_ADD       "table-add"
+
 
 /* Named icons defined in fd.o Icon Naming Spec */
 #define STOCK_NEW_WINDOW           "window-new"
diff --git a/tools/browser/browser-window.c b/tools/browser/browser-window.c
index 911bd8b..6081ae3 100644
--- a/tools/browser/browser-window.c
+++ b/tools/browser/browser-window.c
@@ -578,8 +578,11 @@ browser_window_new (BrowserConnection *bcnc, BrowserPerspectiveFactory *factory)
 	for (plist = browser_core_get_factories (); plist; plist = plist->next) {
 		GtkAction *action;
 		const gchar *name;
-		
+
 		name = BROWSER_PERSPECTIVE_FACTORY (plist->data)->perspective_name;
+		if (!strcmp (name, _("LDAP browser")) && !browser_connection_is_ldap (bcnc))
+			continue;
+
 		action = GTK_ACTION (gtk_radio_action_new (name, name, NULL, NULL, FALSE));
 
 		if (!active_action && 
@@ -990,8 +993,8 @@ toolbar_hide_timeout_cb (BrowserWindow *bwin)
 		return TRUE;
 }
 
-#define BWIN_WINDOW_FULLSCREEN_POPUP_THRESHOLD 5
-#define BWIN_WINDOW_FULLSCREEN_POPUP_TIMER 1
+#define BWIN_WINDOW_FULLSCREEN_POPUP_THRESHOLD 15
+#define BWIN_WINDOW_FULLSCREEN_POPUP_TIMER 5
 
 static gboolean
 fullscreen_motion_notify_cb (GtkWidget *widget, GdkEventMotion *event, G_GNUC_UNUSED gpointer user_data)
diff --git a/tools/browser/common/Makefile.am b/tools/browser/common/Makefile.am
index 147883e..e2dc7f0 100644
--- a/tools/browser/common/Makefile.am
+++ b/tools/browser/common/Makefile.am
@@ -1,13 +1,19 @@
 noinst_LTLIBRARIES = libcommon.la
 
+if LDAP
+ldap_flags=-DHAVE_LDAP
+endif
+
 AM_CPPFLAGS = \
 	-I$(top_builddir) \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/libgda \
+	-I$(top_srcdir)/libgda/sqlite \
 	$(LIBGDA_CFLAGS) \
 	$(LIBGDA_WFLAGS) \
 	$(GTK_CFLAGS) \
-	$(MAC_INTEGRATION_CFLAGS)
+	$(MAC_INTEGRATION_CFLAGS) \
+	$(ldap_flags)
 
 marshal.h: marshal.list $(GLIB_GENMARSHAL)
 	$(GLIB_GENMARSHAL) $< --header --prefix=_common_marshal > $@
diff --git a/tools/browser/common/ui-formgrid.c b/tools/browser/common/ui-formgrid.c
index d6541ea..9d953be 100644
--- a/tools/browser/common/ui-formgrid.c
+++ b/tools/browser/common/ui-formgrid.c
@@ -27,6 +27,9 @@
 #include "../support.h"
 #include "../browser-window.h"
 #include <libgda/gda-data-model-extra.h>
+#ifdef HAVE_LDAP
+#include "../ldap-browser/ldap-browser-perspective.h"
+#endif
 
 static void ui_formgrid_class_init (UiFormGridClass * class);
 static void ui_formgrid_init (UiFormGrid *wid);
@@ -283,7 +286,8 @@ form_grid_toggled_cb (GtkToggleButton *button, UiFormGrid *formgrid)
 		gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_pixbuf (pixbuf));
 
 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (formgrid->priv->raw_grid));
-		row = gda_data_model_iter_get_row (iter);
+		if (iter)
+			row = gda_data_model_iter_get_row (iter);
 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (formgrid->priv->raw_form));
 	}
 	else {
@@ -301,11 +305,13 @@ form_grid_toggled_cb (GtkToggleButton *button, UiFormGrid *formgrid)
 		gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_pixbuf (pixbuf));
 
 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (formgrid->priv->raw_form));
-		row = gda_data_model_iter_get_row (iter);
+		if (iter)
+			row = gda_data_model_iter_get_row (iter);
 		iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (formgrid->priv->raw_grid));
 	}
 
-	gda_data_model_iter_move_to_row (iter, row >= 0 ? row : 0);
+	if (iter)
+		gda_data_model_iter_move_to_row (iter, row >= 0 ? row : 0);
 }
 
 static BrowserConnection *
@@ -325,13 +331,15 @@ get_browser_connection (UiFormGrid *formgrid)
 
 
 static void execute_action_mitem_cb (GtkMenuItem *menuitem, UiFormGrid *formgrid);
+#ifdef HAVE_LDAP
+static void ldap_view_dn_mitem_cb (GtkMenuItem *menuitem, UiFormGrid *formgrid);
+#endif
 
 static void
 form_grid_populate_popup_cb (G_GNUC_UNUSED GtkWidget *wid, GtkMenu *menu, UiFormGrid *formgrid)
 {
 	/* add actions to execute to menu */
 	GdaDataModelIter *iter;
-	GSList *actions_list, *list;
 	BrowserConnection *bcnc = NULL;
 
 	bcnc = get_browser_connection (formgrid);
@@ -339,30 +347,71 @@ form_grid_populate_popup_cb (G_GNUC_UNUSED GtkWidget *wid, GtkMenu *menu, UiForm
 		return;
 	
 	iter = gdaui_data_selector_get_data_set (GDAUI_DATA_SELECTOR (formgrid->priv->raw_grid));
+
+	/* actions */
+	GSList *actions_list, *list;
 	actions_list = browser_favorites_get_actions (browser_connection_get_favorites (bcnc),
 						      bcnc, GDA_SET (iter));
-	if (! actions_list)
-		return;
-
-	GtkWidget *mitem, *submenu;
-	mitem = gtk_menu_item_new_with_label (_("Execute action"));
-	gtk_widget_show (mitem);
-	gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mitem);
-
-	submenu = gtk_menu_new ();
-	gtk_menu_item_set_submenu (GTK_MENU_ITEM (mitem), submenu);
-	for (list = actions_list; list; list = list->next) {
-		BrowserFavoriteAction *act = (BrowserFavoriteAction*) list->data;
-		mitem = gtk_menu_item_new_with_label (act->name);
+	if (actions_list) {
+		GtkWidget *mitem, *submenu;
+		mitem = gtk_menu_item_new_with_label (_("Execute action"));
 		gtk_widget_show (mitem);
-		gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mitem);
-		g_object_set_data_full (G_OBJECT (mitem), "action", act,
-					(GDestroyNotify) browser_favorites_free_action);
-		g_signal_connect (mitem, "activate",
-				  G_CALLBACK (execute_action_mitem_cb), formgrid);
+		gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mitem);
+		
+		submenu = gtk_menu_new ();
+		gtk_menu_item_set_submenu (GTK_MENU_ITEM (mitem), submenu);
+		for (list = actions_list; list; list = list->next) {
+			BrowserFavoriteAction *act = (BrowserFavoriteAction*) list->data;
+			mitem = gtk_menu_item_new_with_label (act->name);
+			gtk_widget_show (mitem);
+			gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mitem);
+			g_object_set_data_full (G_OBJECT (mitem), "action", act,
+						(GDestroyNotify) browser_favorites_free_action);
+			g_signal_connect (mitem, "activate",
+					  G_CALLBACK (execute_action_mitem_cb), formgrid);
+		}
+		g_slist_free (actions_list);
 	}
 
-	g_slist_free (actions_list);
+#ifdef HAVE_LDAP
+	/* LDAP specific */
+	if (browser_connection_is_ldap (bcnc)) {
+		GdaHolder *dnh;
+		dnh = gda_set_get_holder (GDA_SET (iter), "dn");
+		if (dnh) {
+			const GValue *cvalue;
+			cvalue = gda_holder_get_value (GDA_HOLDER (dnh));
+			if (!cvalue && (G_VALUE_TYPE (cvalue) != G_TYPE_STRING))
+				dnh = NULL;
+		}
+		if (!dnh) {
+			GSList *list;
+			for (list = GDA_SET (iter)->holders; list; list = list->next) {
+				const GValue *cvalue;
+				cvalue = gda_holder_get_value (GDA_HOLDER (list->data));
+				if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING) &&
+				    gda_ldap_is_dn (g_value_get_string (cvalue))) {
+					dnh = GDA_HOLDER (list->data);
+					break;
+				}
+			}
+		}
+
+		if (dnh) {
+			const GValue *cvalue;
+			cvalue = gda_holder_get_value (dnh);
+
+			GtkWidget *mitem;
+			mitem = gtk_menu_item_new_with_label (_("View LDAP entry's details"));
+			gtk_widget_show (mitem);
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mitem);
+			g_object_set_data_full (G_OBJECT (mitem), "dn",
+						g_value_dup_string (cvalue), g_free);
+			g_signal_connect (mitem, "activate",
+					  G_CALLBACK (ldap_view_dn_mitem_cb), formgrid);
+		}
+	}
+#endif
 }
 
 typedef struct {
@@ -598,6 +647,21 @@ execute_action_mitem_cb (GtkMenuItem *menuitem, UiFormGrid *formgrid)
 	}
 }
 
+#ifdef HAVE_LDAP
+static void ldap_view_dn_mitem_cb (GtkMenuItem *menuitem, UiFormGrid *formgrid)
+{
+	const gchar *dn;
+	BrowserWindow *bwin;
+        BrowserPerspective *pers;
+
+	dn = g_object_get_data (G_OBJECT (menuitem), "dn");
+        bwin = (BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) formgrid);
+        pers = browser_window_change_perspective (bwin, _("LDAP browser"));
+
+	ldap_browser_perspective_display_ldap_entry (LDAP_BROWSER_PERSPECTIVE (pers), dn);
+}
+#endif
+
 /**
  * ui_formgrid_new
  * @model: a #GdaDataModel
@@ -630,7 +694,8 @@ ui_formgrid_new (GdaDataModel *model, gboolean scroll_form, GdauiDataProxyInfoFl
 		      GDAUI_DATA_PROXY_INFO_CHUNCK_CHANGE_BUTTONS, NULL);
 
 	/* no more than 300 rows at a time */
-	gda_data_proxy_set_sample_size (proxy, 300);
+	if (model)
+		gda_data_proxy_set_sample_size (proxy, 300);
 
 	return (GtkWidget *) formgrid;
 }
diff --git a/tools/browser/data-manager/data-manager-perspective.c b/tools/browser/data-manager/data-manager-perspective.c
index 07649bd..08a7ec8 100644
--- a/tools/browser/data-manager/data-manager-perspective.c
+++ b/tools/browser/data-manager/data-manager-perspective.c
@@ -36,13 +36,12 @@ static void data_manager_perspective_grab_focus (GtkWidget *widget);
 
 /* BrowserPerspective interface */
 static void                 data_manager_perspective_perspective_init (BrowserPerspectiveIface *iface);
+static BrowserWindow       *data_manager_perspective_get_window (BrowserPerspective *perspective);
 static GtkActionGroup      *data_manager_perspective_get_actions_group (BrowserPerspective *perspective);
 static const gchar         *data_manager_perspective_get_actions_ui (BrowserPerspective *perspective);
 static void                 data_manager_perspective_get_current_customization (BrowserPerspective *perspective,
 										GtkActionGroup **out_agroup,
 										const gchar **out_ui);
-static void                 adapt_notebook_for_fullscreen (DataManagerPerspective *perspective);
-
 
 /* get a pointer to the parents to be able to call their destructor */
 static GObjectClass  *parent_class = NULL;
@@ -53,7 +52,6 @@ struct _DataManagerPerspectivePriv {
 	gboolean favorites_shown;
 	BrowserWindow *bwin;
         BrowserConnection *bcnc;
-	gboolean fullscreen;
 };
 
 GType
@@ -115,6 +113,7 @@ data_manager_perspective_grab_focus (GtkWidget *widget)
 static void
 data_manager_perspective_perspective_init (BrowserPerspectiveIface *iface)
 {
+	iface->i_get_window = data_manager_perspective_get_window;
 	iface->i_get_actions_group = data_manager_perspective_get_actions_group;
 	iface->i_get_actions_ui = data_manager_perspective_get_actions_ui;
 	iface->i_get_current_customization = data_manager_perspective_get_current_customization;
@@ -126,20 +125,12 @@ data_manager_perspective_init (DataManagerPerspective *perspective)
 {
 	perspective->priv = g_new0 (DataManagerPerspectivePriv, 1);
 	perspective->priv->favorites_shown = TRUE;
-	perspective->priv->fullscreen = FALSE;
 }
 
 static void fav_selection_changed_cb (GtkWidget *widget, gint fav_id, BrowserFavoritesType fav_type,
                                       const gchar *selection, DataManagerPerspective *perspective);
-static void nb_switch_page_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-                               DataManagerPerspective *perspective);
-static void nb_page_removed_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-				DataManagerPerspective *perspective);
-static void nb_page_added_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-			      DataManagerPerspective *perspective);
 static void close_button_clicked_cb (GtkWidget *wid, GtkWidget *page_widget);
 
-static void fullscreen_changed_cb (BrowserWindow *bwin, gboolean fullscreen, DataManagerPerspective *perspective);
 
 /**
  * data_manager_perspective_new
@@ -152,37 +143,32 @@ data_manager_perspective_new (BrowserWindow *bwin)
 	BrowserConnection *bcnc;
 	BrowserPerspective *bpers;
 	DataManagerPerspective *perspective;
+	gboolean fav_supported;
+
 	bpers = (BrowserPerspective*) g_object_new (TYPE_DATA_MANAGER_PERSPECTIVE, NULL);
 	perspective = (DataManagerPerspective*) bpers;
-
 	perspective->priv->bwin = bwin;
-	g_signal_connect (bwin, "fullscreen-changed",
-			  G_CALLBACK (fullscreen_changed_cb), bpers);
         bcnc = browser_window_get_connection (bwin);
         perspective->priv->bcnc = g_object_ref (bcnc);
-	perspective->priv->fullscreen = browser_window_is_fullscreen (bwin);
+	fav_supported = browser_connection_get_favorites (bcnc) ? TRUE : FALSE;
 
 	/* contents */
         GtkWidget *paned, *nb, *wid;
         paned = gtk_hpaned_new ();
-        wid = data_favorite_selector_new (bcnc);
-	g_signal_connect (wid, "selection-changed",
-			  G_CALLBACK (fav_selection_changed_cb), bpers);
-        gtk_paned_pack1 (GTK_PANED (paned), wid, FALSE, TRUE);
-	gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
-	perspective->priv->favorites = wid;
+	if (fav_supported) {
+		wid = data_favorite_selector_new (bcnc);
+		g_signal_connect (wid, "selection-changed",
+				  G_CALLBACK (fav_selection_changed_cb), bpers);
+		gtk_paned_pack1 (GTK_PANED (paned), wid, FALSE, TRUE);
+		gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
+		perspective->priv->favorites = wid;
+	}
 
 	nb = gtk_notebook_new ();
         perspective->priv->notebook = nb;
         gtk_paned_pack2 (GTK_PANED (paned), nb, TRUE, TRUE);
         gtk_notebook_set_scrollable (GTK_NOTEBOOK (nb), TRUE);
         gtk_notebook_popup_enable (GTK_NOTEBOOK (nb));
-        g_signal_connect (G_OBJECT (nb), "switch-page",
-                          G_CALLBACK (nb_switch_page_cb), perspective);
-        g_signal_connect (G_OBJECT (nb), "page-removed",
-                          G_CALLBACK (nb_page_removed_cb), perspective);
-        g_signal_connect (G_OBJECT (nb), "page-added",
-                          G_CALLBACK (nb_page_added_cb), perspective);
 
 	GtkWidget *page, *tlabel, *button;
 	page = data_console_new (bcnc);
@@ -203,13 +189,12 @@ data_manager_perspective_new (BrowserWindow *bwin)
 	gtk_box_pack_start (GTK_BOX (bpers), paned, TRUE, TRUE, 0);
 	gtk_widget_show_all (paned);
 
-	if (!perspective->priv->favorites_shown)
+	if (perspective->priv->favorites && !perspective->priv->favorites_shown)
 		gtk_widget_hide (perspective->priv->favorites);
 
 	gtk_widget_grab_focus (page);
 
-	if (perspective->priv->fullscreen)
-		adapt_notebook_for_fullscreen (perspective);
+	browser_perspective_declare_notebook (bpers, GTK_NOTEBOOK (perspective->priv->notebook));
 
 	return bpers;
 }
@@ -239,7 +224,6 @@ add_new_data_console (BrowserPerspective *bpers, gint fav_id)
 
 	tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), NULL);
         gtk_notebook_set_menu_label (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
-	adapt_notebook_for_fullscreen (perspective);
 
 	return DATA_CONSOLE (page);
 }
@@ -296,69 +280,12 @@ fav_selection_changed_cb (G_GNUC_UNUSED GtkWidget *widget, gint fav_id, BrowserF
 }
 
 static void
-nb_switch_page_cb (GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page, gint page_num,
-                   DataManagerPerspective *perspective)
-{
-        GtkWidget *page_contents;
-        GtkActionGroup *actions = NULL;
-        const gchar *ui = NULL;
-
-        page_contents = gtk_notebook_get_nth_page (nb, page_num);
-        if (IS_BROWSER_PAGE (page_contents)) {
-                actions = browser_page_get_actions_group (BROWSER_PAGE (page_contents));
-                ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
-        }
-        browser_window_customize_perspective_ui (perspective->priv->bwin,
-                                                 BROWSER_PERSPECTIVE (perspective), actions,
-                                                 ui);
-        if (actions)
-                g_object_unref (actions);
-}
-
-static void
-nb_page_removed_cb (GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page, G_GNUC_UNUSED gint page_num,
-                    DataManagerPerspective *perspective)
-{
-        if (gtk_notebook_get_n_pages (nb) == 0) {
-                browser_window_customize_perspective_ui (perspective->priv->bwin,
-                                                         BROWSER_PERSPECTIVE (perspective),
-                                                         NULL, NULL);
-        }
-	adapt_notebook_for_fullscreen (perspective);
-}
-
-static void
-nb_page_added_cb (G_GNUC_UNUSED GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page,
-		  G_GNUC_UNUSED gint page_num, DataManagerPerspective *perspective)
-{
-	adapt_notebook_for_fullscreen (perspective);
-}
-
-static void
 close_button_clicked_cb (G_GNUC_UNUSED GtkWidget *wid, GtkWidget *page_widget)
 {
         gtk_widget_destroy (page_widget);
 }
 
 static void
-adapt_notebook_for_fullscreen (DataManagerPerspective *perspective)
-{
-	gboolean showtabs = TRUE;
-	
-	if (perspective->priv->fullscreen && 
-	    gtk_notebook_get_n_pages (GTK_NOTEBOOK (perspective->priv->notebook)) == 1)
-		showtabs = FALSE;
-	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (perspective->priv->notebook), showtabs);
-}
-
-static void
-fullscreen_changed_cb (G_GNUC_UNUSED BrowserWindow *bwin, gboolean fullscreen, DataManagerPerspective *perspective)
-{
-	perspective->priv->fullscreen = fullscreen;
-	adapt_notebook_for_fullscreen (perspective);
-}
-
-static void
 data_manager_perspective_dispose (GObject *object)
 {
 	DataManagerPerspective *perspective;
@@ -368,15 +295,10 @@ data_manager_perspective_dispose (GObject *object)
 
 	perspective = DATA_MANAGER_PERSPECTIVE (object);
 	if (perspective->priv) {
+		browser_perspective_declare_notebook ((BrowserPerspective*) perspective, NULL);
                 if (perspective->priv->bcnc)
                         g_object_unref (perspective->priv->bcnc);
 
-                g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-                                                      G_CALLBACK (nb_page_removed_cb), perspective);
-                g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-                                                      G_CALLBACK (nb_page_added_cb), perspective);
-                g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-                                                      G_CALLBACK (nb_switch_page_cb), perspective);
                 g_free (perspective->priv);
                 perspective->priv = NULL;
         }
@@ -396,6 +318,9 @@ favorites_toggle_cb (GtkToggleAction *action, BrowserPerspective *bpers)
 {
 	DataManagerPerspective *perspective;
 	perspective = DATA_MANAGER_PERSPECTIVE (bpers);
+
+	if (! perspective->priv->favorites)
+		return;
 	perspective->priv->favorites_shown = gtk_toggle_action_get_active (action);
 	if (perspective->priv->favorites_shown)
 		gtk_widget_show (perspective->priv->favorites);
@@ -433,18 +358,24 @@ static const gchar *ui_actions_info =
         "</ui>";
 
 static GtkActionGroup *
-data_manager_perspective_get_actions_group (BrowserPerspective *bpers)
+data_manager_perspective_get_actions_group (BrowserPerspective *perspective)
 {
 	GtkActionGroup *agroup;
+	DataManagerPerspective *bpers;
+	bpers = DATA_MANAGER_PERSPECTIVE (perspective);
 	agroup = gtk_action_group_new ("DataManagerActions");
 	gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
 	gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), bpers);
-	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions, G_N_ELEMENTS (ui_toggle_actions), bpers);
+	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions,
+					     G_N_ELEMENTS (ui_toggle_actions), bpers);
 
 	GtkAction *action;
 	action = gtk_action_group_get_action (agroup, "DataManagerFavoritesShow");
-	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
-				      DATA_MANAGER_PERSPECTIVE (bpers)->priv->favorites_shown);
+	if (bpers->priv->favorites)
+		gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+					      bpers->priv->favorites_shown);
+	else
+		gtk_action_set_sensitive (GTK_ACTION (action), FALSE);
 
 	return agroup;
 }
@@ -511,3 +442,11 @@ data_manager_perspective_get_current_customization (BrowserPerspective *perspect
 		*out_ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
 	}
 }
+
+static BrowserWindow *
+data_manager_perspective_get_window (BrowserPerspective *perspective)
+{
+	DataManagerPerspective *bpers;
+	bpers = DATA_MANAGER_PERSPECTIVE (perspective);
+	return bpers->priv->bwin;
+}
diff --git a/tools/browser/data/Makefile.am b/tools/browser/data/Makefile.am
index bea8cab..dc306d8 100644
--- a/tools/browser/data/Makefile.am
+++ b/tools/browser/data/Makefile.am
@@ -20,9 +20,12 @@ private_icons = \
 	hicolor_actions_24x24_transaction-commit.png \
 	hicolor_actions_24x24_transaction-rollback.png \
 	hicolor_actions_24x24_glade.png \
+	hicolor_actions_24x24_table-add.png \
 	hicolor_actions_32x32_bookmark-view.png \
 	hicolor_actions_32x32_history-view.png \
 	hicolor_actions_32x32_glade.png \
+	hicolor_actions_32x32_ldap-entries.png \
+	hicolor_actions_32x32_table-add.png \
 	hicolor_actions_scalable_bookmark-view.svg \
 	hicolor_actions_scalable_history-view.svg \
 	hicolor_actions_scalable_glade.svg \
diff --git a/tools/browser/data/hicolor_actions_24x24_table-add.png b/tools/browser/data/hicolor_actions_24x24_table-add.png
new file mode 100644
index 0000000..352e7b3
Binary files /dev/null and b/tools/browser/data/hicolor_actions_24x24_table-add.png differ
diff --git a/tools/browser/data/hicolor_actions_32x32_ldap-entries.png b/tools/browser/data/hicolor_actions_32x32_ldap-entries.png
new file mode 100644
index 0000000..7b47f46
Binary files /dev/null and b/tools/browser/data/hicolor_actions_32x32_ldap-entries.png differ
diff --git a/tools/browser/data/hicolor_actions_32x32_table-add.png b/tools/browser/data/hicolor_actions_32x32_table-add.png
new file mode 100644
index 0000000..40ea139
Binary files /dev/null and b/tools/browser/data/hicolor_actions_32x32_table-add.png differ
diff --git a/tools/browser/decl.h b/tools/browser/decl.h
index 09eb70b..0719d3e 100644
--- a/tools/browser/decl.h
+++ b/tools/browser/decl.h
@@ -1,5 +1,5 @@
 /* 
- * Copyright (C) 2009 - 2010 The GNOME Foundation.
+ * Copyright (C) 2009 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  * 	Vivien Malerba <malerba gnome-db org>
@@ -45,10 +45,6 @@ typedef struct {
 } BrowserPerspectiveFactory;
 #define BROWSER_PERSPECTIVE_FACTORY(x) ((BrowserPerspectiveFactory*)(x))
 
-#define ORDER_KEY_SCHEMA 1
-#define ORDER_KEY_QUERIES 2
-#define ORDER_KEY_DATA_MANAGERS 3
-
 #define DEFAULT_FAVORITES_SIZE 150
 
 #define DEFAULT_DATA_SELECT_LIMIT 500
diff --git a/tools/browser/doc/Makefile.am b/tools/browser/doc/Makefile.am
index d4b470f..9947a4a 100644
--- a/tools/browser/doc/Makefile.am
+++ b/tools/browser/doc/Makefile.am
@@ -40,19 +40,25 @@ GTKDOC_CFLAGS = -I$(top_srcdir) \
 	$(GTKSOURCEVIEW_CFLAGS) \
 	-DGETTEXT_PACKAGE=\""$(GETTEXT_PACKAGE)"\"
 
-ADDLIBS=
+ADDCANVASLIBS=
 if HAVE_GOOCANVAS
-ADDLIBS+=$(top_builddir)/tools/browser/canvas/libcanvas.la
+ADDCANVASLIBS+=$(top_builddir)/tools/browser/canvas/libcanvas.la
+endif
+
+ADDLDAPLIBS=
+if LDAP
+ADDLDAPLIBS+=$(top_builddir)/tools/browser/ldap-browser/libperspective.la
 endif
 
 GTKDOC_LIBS = \
 	$(top_builddir)/tools/browser/data-manager/libperspective.la \
 	$(top_builddir)/tools/browser/schema-browser/libperspective.la \
-	$(ADDLIBS) \
+	$(ADDCANVASLIBS) \
 	$(top_builddir)/tools/browser/query-exec/libperspective.la \
 	$(top_builddir)/tools/browser/libbrowser.la \
 	$(top_builddir)/libgda-ui/internal/libgda-ui-internal.la \
 	$(top_builddir)/tools/browser/common/libcommon.la \
+	$(ADDLDAPLIBS) \
 	$(top_builddir)/libgda/libgda-4.0.la \
 	$(top_builddir)/libgda-ui/libgda-ui-4.0.la \
 	$(LIBGDA_LIBS) \
diff --git a/tools/browser/doc/gda-browser-sections.txt b/tools/browser/doc/gda-browser-sections.txt
index 7ffd91d..ffc8ad8 100644
--- a/tools/browser/doc/gda-browser-sections.txt
+++ b/tools/browser/doc/gda-browser-sections.txt
@@ -128,6 +128,7 @@ BrowserFavoritesAttributes
 <TITLE>BrowserFavorites</TITLE>
 BrowserFavorites
 browser_favorites_new
+browser_favorites_type_to_string
 browser_favorites_add
 browser_favorites_list
 browser_favorites_delete
@@ -156,10 +157,13 @@ IS_BROWSER_PERSPECTIVE
 BROWSER_PERSPECTIVE_GET_CLASS
 BrowserPerspective
 browser_perspective_get_type
+browser_perspective_get_window
 browser_perspective_get_actions_group
 browser_perspective_get_actions_ui
 browser_perspective_get_current_customization
 browser_perspective_page_tab_label_change
+<SUBSECTION>
+browser_perspective_declare_notebook
 </SECTION>
 
 <SECTION>
diff --git a/tools/browser/doc/tmpl/browser-connection.sgml b/tools/browser/doc/tmpl/browser-connection.sgml
index c5c352a..dc97cec 100644
--- a/tools/browser/doc/tmpl/browser-connection.sgml
+++ b/tools/browser/doc/tmpl/browser-connection.sgml
@@ -133,6 +133,15 @@ An opened connection
 @Returns: 
 
 
+<!-- ##### FUNCTION browser_connection_is_virtual ##### -->
+<para>
+
+</para>
+
+ bcnc: 
+ Returns: 
+
+
 <!-- ##### FUNCTION browser_connection_is_busy ##### -->
 <para>
 
diff --git a/tools/browser/doc/tmpl/browser-favorites.sgml b/tools/browser/doc/tmpl/browser-favorites.sgml
index b588221..1277a37 100644
--- a/tools/browser/doc/tmpl/browser-favorites.sgml
+++ b/tools/browser/doc/tmpl/browser-favorites.sgml
@@ -39,6 +39,8 @@ Favorites management
 @BROWSER_FAVORITES_QUERIES: 
 @BROWSER_FAVORITES_DATA_MANAGERS: 
 @BROWSER_FAVORITES_ACTIONS: 
+ BROWSER_FAVORITES_LDAP_DN: 
+ BROWSER_FAVORITES_LDAP_CLASS: 
 
 <!-- ##### MACRO BROWSER_FAVORITES_NB_TYPES ##### -->
 <para>
@@ -80,6 +82,15 @@ Favorites management
 @Returns: 
 
 
+<!-- ##### FUNCTION browser_favorites_type_to_string ##### -->
+<para>
+
+</para>
+
+ type: 
+ Returns: 
+
+
 <!-- ##### FUNCTION browser_favorites_add ##### -->
 <para>
 
diff --git a/tools/browser/doc/tmpl/browser-perspective.sgml b/tools/browser/doc/tmpl/browser-perspective.sgml
index 7538085..b201df4 100644
--- a/tools/browser/doc/tmpl/browser-perspective.sgml
+++ b/tools/browser/doc/tmpl/browser-perspective.sgml
@@ -67,6 +67,15 @@ A "perspective" in a #BrowserWindow window
 @Returns: 
 
 
+<!-- ##### FUNCTION browser_perspective_get_window ##### -->
+<para>
+
+</para>
+
+ perspective: 
+ Returns: 
+
+
 <!-- ##### FUNCTION browser_perspective_get_actions_group ##### -->
 <para>
 
@@ -104,3 +113,12 @@ A "perspective" in a #BrowserWindow window
 @page: 
 
 
+<!-- ##### FUNCTION browser_perspective_declare_notebook ##### -->
+<para>
+
+</para>
+
+ perspective: 
+ nb: 
+
+
diff --git a/tools/browser/doc/tmpl/support.sgml b/tools/browser/doc/tmpl/support.sgml
index 9050768..14bb302 100644
--- a/tools/browser/doc/tmpl/support.sgml
+++ b/tools/browser/doc/tmpl/support.sgml
@@ -144,6 +144,14 @@ Misc. functions for various situations
 @BROWSER_ICON_GRID: 
 @BROWSER_ICON_FORM: 
 @BROWSER_ICON_MENU_INDICATOR: 
+ BROWSER_ICON_LDAP_ENTRY: 
+ BROWSER_ICON_LDAP_GROUP: 
+ BROWSER_ICON_LDAP_ORGANIZATION: 
+ BROWSER_ICON_LDAP_PERSON: 
+ BROWSER_ICON_LDAP_CLASS_STRUCTURAL: 
+ BROWSER_ICON_LDAP_CLASS_ABSTRACT: 
+ BROWSER_ICON_LDAP_CLASS_AUXILIARY: 
+ BROWSER_ICON_LDAP_CLASS_UNKNOWN: 
 @BROWSER_ICON_LAST: 
 
 <!-- ##### FUNCTION browser_get_pixbuf_icon ##### -->
diff --git a/tools/browser/doc/tmpl/ui-formgrid.sgml b/tools/browser/doc/tmpl/ui-formgrid.sgml
index cc213be..2613cb9 100644
--- a/tools/browser/doc/tmpl/ui-formgrid.sgml
+++ b/tools/browser/doc/tmpl/ui-formgrid.sgml
@@ -36,6 +36,11 @@ Widget embedding both a form and a grid to display a #GdaDataModel's contents
 
 </para>
 
+<!-- ##### ARG UiFormGrid:scroll-form ##### -->
+<para>
+
+</para>
+
 <!-- ##### ARG UiFormGrid:widget-info ##### -->
 <para>
 
@@ -47,6 +52,7 @@ Widget embedding both a form and a grid to display a #GdaDataModel's contents
 </para>
 
 @model: 
+ scroll_form: 
 @flags: 
 @Returns: 
 
diff --git a/tools/browser/dummy-perspective/dummy-perspective.c b/tools/browser/dummy-perspective/dummy-perspective.c
index fd6a0d8..fae2298 100644
--- a/tools/browser/dummy-perspective/dummy-perspective.c
+++ b/tools/browser/dummy-perspective/dummy-perspective.c
@@ -25,11 +25,12 @@
  * Main static functions 
  */
 static void dummy_perspective_class_init (DummyPerspectiveClass *klass);
-static void dummy_perspective_init (DummyPerspective *stmt);
+static void dummy_perspective_init (DummyPerspective *pers);
 static void dummy_perspective_dispose (GObject *object);
 
 /* BrowserPerspective interface */
 static void                 dummy_perspective_perspective_init (BrowserPerspectiveIface *iface);
+static BrowserWindow       *dummy_perspective_get_window (BrowserPerspective *perspective);
 static GtkActionGroup      *dummy_perspective_get_actions_group (BrowserPerspective *perspective);
 static const gchar         *dummy_perspective_get_actions_ui (BrowserPerspective *perspective);
 /* get a pointer to the parents to be able to call their destructor */
@@ -84,6 +85,7 @@ dummy_perspective_class_init (DummyPerspectiveClass * klass)
 static void
 dummy_perspective_perspective_init (BrowserPerspectiveIface *iface)
 {
+	iface->i_get_window = dummy_perspective_get_window;
 	iface->i_get_actions_group = dummy_perspective_get_actions_group;
 	iface->i_get_actions_ui = dummy_perspective_get_actions_ui;
 }
@@ -113,6 +115,10 @@ dummy_perspective_new (G_GNUC_UNUSED BrowserWindow *bwin)
 	BrowserPerspective *bpers;
 	bpers = (BrowserPerspective*) g_object_new (TYPE_DUMMY_PERSPECTIVE, NULL);
 
+	/* if there is a notebook to store pages, use: 
+	browser_perspective_declare_notebook (bpers, GTK_NOTEBOOK (perspective->priv->notebook));
+	*/
+
 	return bpers;
 }
 
@@ -126,6 +132,7 @@ dummy_perspective_dispose (GObject *object)
 	g_return_if_fail (IS_DUMMY_PERSPECTIVE (object));
 
 	perspective = DUMMY_PERSPECTIVE (object);
+	browser_perspective_declare_notebook ((BrowserPerspective*) perspective, NULL);
 
 	/* parent class */
 	parent_class->dispose (object);
@@ -184,3 +191,11 @@ dummy_perspective_get_actions_ui (G_GNUC_UNUSED BrowserPerspective *bpers)
 {
 	return ui_actions_info;
 }
+
+static BrowserWindow *
+dummy_perspective_get_window (BrowserPerspective *perspective)
+{
+	DummyPerspective *bpers;
+	bpers = DUMMY_PERSPECTIVE (perspective);
+	return NULL;/*bpers->priv->bwin;*/
+}
diff --git a/tools/browser/gda-browser-ldap-class-a.png b/tools/browser/gda-browser-ldap-class-a.png
new file mode 100644
index 0000000..1521a5b
Binary files /dev/null and b/tools/browser/gda-browser-ldap-class-a.png differ
diff --git a/tools/browser/gda-browser-ldap-class-s.png b/tools/browser/gda-browser-ldap-class-s.png
new file mode 100644
index 0000000..0f2b630
Binary files /dev/null and b/tools/browser/gda-browser-ldap-class-s.png differ
diff --git a/tools/browser/gda-browser-ldap-class-u.png b/tools/browser/gda-browser-ldap-class-u.png
new file mode 100644
index 0000000..3dca45a
Binary files /dev/null and b/tools/browser/gda-browser-ldap-class-u.png differ
diff --git a/tools/browser/gda-browser-ldap-class-x.png b/tools/browser/gda-browser-ldap-class-x.png
new file mode 100644
index 0000000..ba9c76c
Binary files /dev/null and b/tools/browser/gda-browser-ldap-class-x.png differ
diff --git a/tools/browser/gda-browser-ldap-entry.png b/tools/browser/gda-browser-ldap-entry.png
new file mode 100644
index 0000000..55ea0c6
Binary files /dev/null and b/tools/browser/gda-browser-ldap-entry.png differ
diff --git a/tools/browser/gda-browser-ldap-group.png b/tools/browser/gda-browser-ldap-group.png
new file mode 100644
index 0000000..a14a75b
Binary files /dev/null and b/tools/browser/gda-browser-ldap-group.png differ
diff --git a/tools/browser/gda-browser-ldap-organization.png b/tools/browser/gda-browser-ldap-organization.png
new file mode 100644
index 0000000..f3fb913
Binary files /dev/null and b/tools/browser/gda-browser-ldap-organization.png differ
diff --git a/tools/browser/gda-browser-ldap-person.png b/tools/browser/gda-browser-ldap-person.png
new file mode 100644
index 0000000..397cd73
Binary files /dev/null and b/tools/browser/gda-browser-ldap-person.png differ
diff --git a/tools/browser/help/C/features.page b/tools/browser/help/C/features.page
index 9019a20..c8b0a5a 100644
--- a/tools/browser/help/C/features.page
+++ b/tools/browser/help/C/features.page
@@ -39,6 +39,8 @@
       <link xref="query-execution-perspective">Query execution perspective</link></p></item>
       <item><p>analyse the table's contents, see the
       <link xref="data-manager-perspective">Data manager perspective</link></p></item>
+      <item><p>for <link xref="ldap-connection">LDAP connections</link>, manage the hierarchical data in the LDAP tree, see the
+      <link xref="ldap-browser-perspective">LDAP browser perspective</link></p></item>
     </list>
     <p>
       See also the section about <link xref="actions">actions</link>
diff --git a/tools/browser/help/C/figures/data-man-persp-multi.png b/tools/browser/help/C/figures/data-man-persp-multi.png
new file mode 100644
index 0000000..96ea545
Binary files /dev/null and b/tools/browser/help/C/figures/data-man-persp-multi.png differ
diff --git a/tools/browser/help/C/figures/ldap-browser-persp.png b/tools/browser/help/C/figures/ldap-browser-persp.png
new file mode 100644
index 0000000..50d5434
Binary files /dev/null and b/tools/browser/help/C/figures/ldap-browser-persp.png differ
diff --git a/tools/browser/help/C/figures/ldap-classes.png b/tools/browser/help/C/figures/ldap-classes.png
new file mode 100644
index 0000000..f0c3a2d
Binary files /dev/null and b/tools/browser/help/C/figures/ldap-classes.png differ
diff --git a/tools/browser/help/C/figures/ldap-search.png b/tools/browser/help/C/figures/ldap-search.png
new file mode 100644
index 0000000..96752b0
Binary files /dev/null and b/tools/browser/help/C/figures/ldap-search.png differ
diff --git a/tools/browser/help/C/figures/ldap-table-mapping.png b/tools/browser/help/C/figures/ldap-table-mapping.png
new file mode 100644
index 0000000..dd36e32
Binary files /dev/null and b/tools/browser/help/C/figures/ldap-table-mapping.png differ
diff --git a/tools/browser/help/C/index.page b/tools/browser/help/C/index.page
index 5db715c..8a1f5f2 100644
--- a/tools/browser/help/C/index.page
+++ b/tools/browser/help/C/index.page
@@ -20,7 +20,8 @@
     for which a database driver (provider) exists in <app>libgda</app>
     (<link href="http://www.oracle.com";>Oracle</link>, <link href="http://www.mysql.org";>MySQL</link>,
     <link href="http://www.postgresql.org/";>PostgreSQL</link>, <link href="http://www.sqlite.org";>SQLite</link>,
-    MS Access (through the <link href="http://sourceforge.net/projects/mdbtools/";>MDBTools</link> library) and
+    MS Access (through the <link href="http://sourceforge.net/projects/mdbtools/";>MDBTools</link> library),
+    <link href="http://en.wikipedia.org/wiki/LDAP";>LDAP</link> and
     <link href="http://java.sun.com/docs/books/tutorial/jdbc/index.html";>JDBC</link> are supported at
     the moment). </p>
 
diff --git a/tools/browser/help/C/ldap-browser-perspective.page b/tools/browser/help/C/ldap-browser-perspective.page
new file mode 100644
index 0000000..5c50841
--- /dev/null
+++ b/tools/browser/help/C/ldap-browser-perspective.page
@@ -0,0 +1,61 @@
+<page xmlns="http://projectmallard.org/1.0/";
+      type="topic"
+      id="ldap-browser-perspective">
+<info>
+  <title type="sort">1</title>
+  <link type="guide" xref="index#perspectives"/>
+</info>
+<title>The LDAP browser perspective</title>
+<p>
+  Use the LDAP browser perspective to view and manipulate data stored in an LDAP directory. To switch
+  to this perspective, use the <guiseq><gui>Perspective</gui><gui>LDAP browser</gui></guiseq> menu, or
+  the <keyseq><key>Ctrl</key><key>P</key></keyseq> shortcut. This perspective is of course only available when the opened connection is an <link xref="ldap-connection">LDAP connection</link>.
+</p>
+<p>
+  The perspective is divided in two horizontal panes: the left pane for the user defined 
+  favorites (to hold references to specific LDAP entries or specific LDAP classes), and
+  the right pane being the action area.
+</p>
+<figure>
+  <title>LDAP browser's entry tab</title>
+  <desc>LDAP entries tab</desc>
+  <media type="image" mime="image/png" src="figures/ldap-browser-persp.png"/>
+</figure>
+
+<p>
+  The left
+  part of the perspective lists the favorite LDAP entries or classes. Double clicking on a favorite
+  opens its properties in the right pane. Note that the LDAP entries favorites will always appear
+  before the classes favorites.
+</p>
+<p>
+  The right pane is composed of several types of tabs:
+</p>
+<list>
+  <item><p>tabs to explore the LDAP DIT (Directory Information Tree): when an entry is selected from the
+  tree, its attributes are displayed (only the valued attributes are displayed, the ones with no value
+  are hidden), and the entry's DN (Distinguished Name) is always displayed first. Also note that the children
+  of each entry are only fetched when necessary to avoid unnecessary requests to the LDAP server.</p></item>
+  <item><p>tabs to explore the LDAP's classes, see figure below. For a selected class, all the information
+  regarding the class is displayed (description, OID, type, ...)</p></item>
+  <item><p>tabs to perform LDAP searches, see figure below. An LDAP search definition can be saved as a virtual
+  table, see the <link xref="ldap-connection#ldap-table-mapping">LDAP table's mapping</link>.</p></item>
+</list>
+<figure>
+  <title>LDAP browser's classes tab</title>
+  <desc>LDAP classes tab</desc>
+  <media type="image" mime="image/png" src="figures/ldap-classes.png"/>
+</figure>
+<figure>
+  <title>LDAP browser's search tab</title>
+  <desc>LDAP search tab</desc>
+  <media type="image" mime="image/png" src="figures/ldap-search.png"/>
+</figure>
+<p>
+  Links in these tabs (identified by blue and underlined text) open a new tab, or use the first tab next
+  to the current tab to display information about the selected link. For example in the figure above illustrating
+  an LDAP entries tab, clicking on
+  the "inetOrgPerson" will open a new "LDAP classes" tab and disply the information about that class.
+</p>
+
+</page>
diff --git a/tools/browser/help/C/ldap-connections.page b/tools/browser/help/C/ldap-connections.page
new file mode 100644
index 0000000..234e446
--- /dev/null
+++ b/tools/browser/help/C/ldap-connections.page
@@ -0,0 +1,132 @@
+<page xmlns="http://projectmallard.org/1.0/";
+      type="topic"
+      id="ldap-connection">
+  <info>
+    <title type="sort">1</title>
+    <link type="guide" xref="features"/>
+    <link type="guide" xref="index#connections"/>
+  </info>
+  <title>LDAP connections</title>
+  <p>
+    LDAP connections are different than other connections in a way that an LDAP database stores data in
+    a hierarchical way, in the DIT (Directory Information Tree), whereas other databases acessible using
+    the <app>gda-browser</app> application are relational databases.
+  </p>
+  <p>
+    As a consequence, LDAP connections are treaded specially: as normal connections with tables (see the
+    table mapping explained next), and through the presence of the <link xref="ldap-browser-perspective">
+    LDAP browser perspective</link>
+  </p>
+  <p>
+    Note that LDAP connections may not be available is either the LDAP database provider is not installed
+    or if the LDAP support was disabled during the compilation.
+  </p>
+
+  <section id="ldap-table-mapping">
+    <title>LDAP table's mapping</title>
+    <p>
+      Within an LDAP connection, it is possible to declare virtual tables which are mapped to an LDAP search.
+      These virtual tables can then later be used like any other table. The first column of each LDAP virtual
+      table will always be the DN (Distinguished Name) of the entry represented in the row; the other columns
+      depend on the table definition.
+    </p>
+    <p>
+      An LDAP virtual table is defined by the following attributes:
+    </p>
+    <list>
+      <item><p>a table name</p></item>
+      <item><p>the base DN for the search: the LDAP entry at which the search begins (if not specified
+      then the top level entry of the LDAP connection is used)</p></item>
+      <item><p>the search filter: a valid LDAP search filter (if none is provided then the default 
+      search filter is "(objectClass=*)", requesting any LDAP entry).</p></item>
+      <item><p>the attributes: the attributes to retreive, each attribute will be mapped to a column of the
+      table. The attributes must be a comma separated list of attributes, where each attribute can optionally
+      be assigned a type and a multi value option (see below).</p></item>
+      <item><p>the scope: the search scope, "subtree" (to search the base DN and the entire sub tree below),
+      "onelevel" (to search the immediate children of the base DN entry only), or
+      "base" (to search the base DN only)</p></item>
+    </list>
+    <figure>
+      <title>LDAP table's properties</title>
+      <desc>LDAP table's properties</desc>
+      <media type="image" mime="image/png" src="figures/ldap-table-mapping.png"/>
+    </figure>
+    <p>
+      For example in the figure above, the "users" table will "contain" all the LDAP entries from
+      the top level LDAP entry of the connection, and have 3 columns: the DN, the "cn" and the "jpegPhoto". 
+    </p>
+  </section>
+
+  <section id="ldap-columns-mapping">
+    <title>Attributes to columns mapping</title>
+    <p>
+      As mentionned in the previous section, each attribute will be mapped to a column. The column type
+      is normally automatically determined (string, number, ...) but can be forced by appending to the attribute
+      name the "::&lt;type&gt;" for the requested type.
+    </p>
+    <p>
+      Also, because
+      some attributes can have multiple values, the table construction handles multi-valued attributes in
+      different ways, depending on each attribute's options, an option can be specified by appending the
+      "::&lt;option&gt;" to the attribute name. Valid options are:
+    </p>
+    <list>
+      <item><p>"NULL" or "0": a NULL value will be returned for the attribute</p></item>
+      <item><p>"CSV": a comma separated value with all the values of the attribute will be returned.
+      This only works for string attribute types.</p></item>
+      <item><p>"MULT" or "*": a row will be returned for each value of the attribute, effectively
+      multiplying the number of returned rows</p></item>
+      <item><p>"1": only the first vakue of the attribute will be used, the other values ignored</p></item>
+      <item><p>"CONCAT": the attributes' values are concatenated (with a newline char
+      between each value)</p></item>
+      <item><p>"ERROR": an error value will be returned (this is the default behaviour)</p></item>
+    </list>
+  </section>
+
+  <section id="ldap-ddl-sql">
+    <title>SQL useable with LDAP connections</title>
+    <p>
+      You can use the SQL understood by <link href="http://sqlite.org/lang.html";>SQLite</link> in any LDAP
+      connection. Be aware however that if you define database objects (outside of the extended SQL
+      presented next section), they will be lost the next time the LDAP connection is opened.
+    </p>
+    <p>
+      So it is perfectly safe for example to create a table to store some LDAP data which
+      may require a long operation to obtain, but after closing the LDAP connection, the table
+      and its data will be lost.
+    </p>
+    <p>
+      See the <link xref="sql-sqlite">SQL understood by LDAP connections and virtual connections</link>
+      section for more information.
+    </p>
+  </section>
+
+  <section id="ldap-sql">
+    <title>SQL extension to hande LDAP tables</title>
+    <p>
+      LDAP tables can be created using an extended set of SQL commands:
+    </p>
+    <list>
+      <item><p><code>CREATE LDAP TABLE &lt;table_name&gt; [BASE='&lt;base_dn&gt;'] [FILTER='&lt;filter&gt;'] [ATTRIBUTES='&lt;filter&gt;'] [SCOPE='&lt;filter&gt;']</code> to declare a new LDAP virtual table</p></item>
+      <item><p><code>DESCRIBE LDAP TABLE &lt;table_name&gt;</code> to show LDAP virtual table's definition</p></item>
+      <item><p><code>ALTER LDAP TABLE &lt;table_name&gt; [BASE='&lt;base_dn&gt;'] [FILTER='&lt;filter&gt;'] [ATTRIBUTES='&lt;filter&gt;'] [SCOPE='&lt;filter&gt;']</code> to modify an LDAP virtual table's definition (only the specified part is actually modified</p></item>
+      <item><p><code>DROP LDAP TABLE &lt;table_name&gt;</code> to remove an LDAP virtual table. Note that the
+      usual <code>DROP TABLE &lt;table_name&gt;</code> can also be used instead.</p></item>
+    </list>
+    <p>
+      For example the following SQL code:
+    </p>
+    <code>
+      CREATE LDAP TABLE users FILTER='(objectClass=inetOrgPerson)'
+              ATTRIBUTES='cn,sn,givenName,seeAlso::*' SCOPE='subtree';
+      SELECT * FROM users WHERE cn like '%john%';
+      ALTER LDAP TABLE users FILTER='(&amp;(objectClass=inetOrgPerson)(cn=*john*))';
+      SELECT * FROM users;
+      DROP LDAP TABLE users;
+    </code>
+    <p>
+    should display twice the same results, which list all the LDAP entries of the "inetOrgPerson" class with
+    a CommonName (cn) containing the "john" string.
+    </p>
+  </section>
+</page>
diff --git a/tools/browser/help/C/sql-sqlite.page b/tools/browser/help/C/sql-sqlite.page
new file mode 100644
index 0000000..4f92bb1
--- /dev/null
+++ b/tools/browser/help/C/sql-sqlite.page
@@ -0,0 +1,100 @@
+<page xmlns="http://projectmallard.org/1.0/";
+      type="topic"
+      id="sql-sqlite">
+  <info>
+    <title type="sort">1</title>
+  </info>
+  <title>SQL understood by LDAP connections and virtual connections</title>
+  <p>
+    Both virtual connections in <app>gda-browser</app> and LDAP connections internally rely on
+    <link href="http://www.sqlite.org/vtab.html";>SQLite's virtual tables</link> mechanism, and
+    as a consequence, the SQL to use in these connections is the same and the limitations
+    are the same.
+  </p>
+  <p>
+    All the SQL understood by <link href="http://sqlite.org/lang.html";>SQLite</link> is useable
+    in these connections, with the limitations and modifications detailled in the next sections.
+  </p>
+
+  <section id="sql-sqlite-dbobjects">
+    <title>Database objects</title>
+    <p>
+      It is possible to create database objects (tables, views, ...) but these will be lost when the
+      connection is closed. The only "persistant" objects are the ones automatically created
+      when the connection is opened (and they contain no data as they are merely proxies to
+      other sources of data).
+    </p>
+  </section>
+
+  <section id="sql-sqlite-extrafunctions">
+    <title>Extra available functions</title>
+    <p>
+      All the functions provided by <link href="http://sqlite.org/lang_corefunc.html";>SQLite</link>
+      are useable in these connections, and a few other ones have been added:
+    </p>
+    <list>
+      <item>
+	<p>
+	  the <cmd>gda_file_exists()</cmd> function accepts a filename as argument,
+	  and returns 0 if the file with that filename does not
+	  exist, or 1 if it does.
+	</p>
+      </item>
+
+      <item>
+	<p>
+	  the <cmd>gda_hex_print()</cmd> function
+	  accepts at most 2 arguments, in that order:
+	</p>
+	<list>
+	  <item><p>a blob value</p></item>
+	  <item><p>a length (not mandatory)</p></item>
+	</list>
+	<p>
+	  It returns a string suitable to be printed (where all the non ascii characters are converted to
+	  the "\xyz" syntax where "xyz" is the decimal value of the character), limited to the specified
+	  length if any.
+	</p>
+      </item>
+
+
+      <item>
+	<p>
+	  the <cmd>gda_hex()</cmd> function
+	  accepts at most 2 arguments, in that order:
+	</p>
+	<list>
+	  <item><p>a blob value</p></item>
+	  <item><p>a length (not mandatory)</p></item>
+	</list>
+	<p>It returns a hex dump string of the blob value, limited to the specified length if any</p>
+      </item>
+
+      <item>
+	<p>
+	  the <cmd>gda_rmdiacr()</cmd> function
+	  accepts at most 2 arguments, in that order:
+	</p>
+	<list>
+	  <item><p>a string value</p></item>
+	  <item><p>a case conversion to do (not mandatory), as a string which must be
+	  'upper' or 'lower'</p></item>
+	</list>
+	<p>
+	  It returns a string where all the diacritical signs (for example accents) from the input string,
+	  and optionally converts the string to upper or lower case if specified. This function takes into
+	  account the current locale and is useful to do some text search.
+	</p>
+      </item>
+
+      <item>
+	<p>
+	  the <cmd>gda_upper()</cmd> and <cmd>gda_lower()</cmd> functions
+	  accept one string argument and convert it to upper or lower case, taking into account
+	  the locale (the standard SQLite <cmd>upper()</cmd> and <cmd>lower()</cmd>
+	  functions only operating on ASCII characters).
+	</p>
+      </item>
+    </list>
+  </section>
+</page>
diff --git a/tools/browser/help/C/virtual-connections.page b/tools/browser/help/C/virtual-connections.page
index 45a0a7e..2afaf22 100644
--- a/tools/browser/help/C/virtual-connections.page
+++ b/tools/browser/help/C/virtual-connections.page
@@ -123,6 +123,18 @@ WHERE "Error code"=0
       <title>New opened virtual connection</title>
       <media type="image" mime="image/png" src="figures/virtual-cnc-3.png"/>
     </figure>
+  </section>
 
+  <section id="virtual-ddl-sql">
+    <title>SQL useable with virtual connections</title>
+    <p>
+      You can use the SQL understood by <link href="http://sqlite.org/lang.html";>SQLite</link> in any virtual
+      connection. Be aware however that if you define database objects (outside of the extended SQL
+      presented next section), they will be lost the next time the virtual connection is opened.
+    </p>
+    <p>
+      See the <link xref="sql-sqlite">SQL understood by LDAP connections and virtual connections</link>
+      section for more information.
+    </p>
   </section>
 </page>
diff --git a/tools/browser/help/Makefile.am b/tools/browser/help/Makefile.am
index b196d02..8d560c3 100644
--- a/tools/browser/help/Makefile.am
+++ b/tools/browser/help/Makefile.am
@@ -27,7 +27,11 @@ DOC_FIGURES = \
 	figures/virtual-cnc-2.png \
 	figures/virtual-cnc-3.png \
 	figures/virtual-cnc-4.png \
-	figures/virtual-cnc-5.png
+	figures/virtual-cnc-5.png \
+	figures/ldap-browser-persp.png \
+	figures/ldap-classes.png \
+	figures/ldap-search.png \
+	figures/ldap-table-mapping.png
 
 DOC_PAGES = \
 	actions.page \
@@ -45,7 +49,9 @@ DOC_PAGES = \
 	transactions.page \
 	table-insert-data.page \
 	variables.page \
-	virtual-connections.page
+	virtual-connections.page \
+	ldap-browser-perspective.page \
+	ldap-connections.page
 
 DOC_LINGUAS = de es sl
 
diff --git a/tools/browser/ldap-browser/Makefile.am b/tools/browser/ldap-browser/Makefile.am
new file mode 100644
index 0000000..c2a8c07
--- /dev/null
+++ b/tools/browser/ldap-browser/Makefile.am
@@ -0,0 +1,58 @@
+noinst_LTLIBRARIES = libperspective.la
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/tools/browser \
+	-I$(top_builddir) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/libgda \
+	-I$(top_srcdir)/libgda/sqlite \
+	$(LIBGDA_CFLAGS) \
+	$(LIBGDA_WFLAGS) \
+	$(GTK_CFLAGS) -DHAVE_LDAP \
+	$(MAC_INTEGRATION_CFLAGS)
+
+marshal.h: marshal.list $(GLIB_GENMARSHAL)
+	$(GLIB_GENMARSHAL) $< --header --prefix=_ldap_marshal > $@
+marshal.c: marshal.list $(GLIB_GENMARSHAL) marshal.h
+	$(GLIB_GENMARSHAL) $< --body --prefix=_ldap_marshal > $@
+
+libperspective_la_SOURCES = \
+	marshal.c \
+	marshal.h \
+	perspective-main.h \
+	perspective-main.c \
+	ldap-browser-perspective.h \
+	ldap-browser-perspective.c \
+	ldap-favorite-selector.h \
+	ldap-favorite-selector.c \
+	ldap-entries-page.c \
+	ldap-entries-page.h \
+	mgr-ldap-entries.h \
+	mgr-ldap-entries.c \
+	hierarchy-view.h \
+	hierarchy-view.c \
+	entry-properties.h \
+	entry-properties.c \
+	mgr-ldap-classes.h \
+	mgr-ldap-classes.c \
+	classes-view.h \
+	classes-view.c \
+	ldap-classes-page.h \
+	ldap-classes-page.c \
+	class-properties.h \
+	class-properties.c \
+	filter-editor.h \
+	filter-editor.c \
+	ldap-search-page.h \
+	ldap-search-page.c \
+	vtable-dialog.h \
+	vtable-dialog.c
+
+$(OBJECTS): marshal.c marshal.h
+
+EXTRA_DIST = \
+	marshal.list
+
+CLEANFILES = \
+	marshal.h \
+	marshal.c
diff --git a/tools/browser/ldap-browser/class-properties.c b/tools/browser/ldap-browser/class-properties.c
new file mode 100644
index 0000000..ab4973a
--- /dev/null
+++ b/tools/browser/ldap-browser/class-properties.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include "class-properties.h"
+#include "marshal.h"
+#include <gdk/gdkkeysyms.h>
+
+struct _ClassPropertiesPrivate {
+	BrowserConnection *bcnc;
+
+	GtkTextBuffer *text;
+	gboolean hovering_over_link;
+};
+
+static void class_properties_class_init (ClassPropertiesClass *klass);
+static void class_properties_init       (ClassProperties *eprop, ClassPropertiesClass *klass);
+static void class_properties_dispose   (GObject *object);
+
+static GObjectClass *parent_class = NULL;
+
+/* signals */
+enum {
+        OPEN_CLASS,
+        LAST_SIGNAL
+};
+
+gint class_properties_signals [LAST_SIGNAL] = { 0 };
+
+/*
+ * ClassProperties class implementation
+ */
+
+static void
+class_properties_class_init (ClassPropertiesClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	class_properties_signals [OPEN_CLASS] =
+		g_signal_new ("open-class",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (ClassPropertiesClass, open_class),
+                              NULL, NULL,
+                              _ldap_marshal_VOID__STRING, G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+	klass->open_class = NULL;
+
+	object_class->dispose = class_properties_dispose;
+}
+
+
+static void
+class_properties_init (ClassProperties *eprop, G_GNUC_UNUSED ClassPropertiesClass *klass)
+{
+	eprop->priv = g_new0 (ClassPropertiesPrivate, 1);
+	eprop->priv->hovering_over_link = FALSE;
+}
+
+static void
+class_properties_dispose (GObject *object)
+{
+	ClassProperties *eprop = (ClassProperties *) object;
+
+	/* free memory */
+	if (eprop->priv) {
+		if (eprop->priv->bcnc) {
+			g_object_unref (eprop->priv->bcnc);
+		}
+		g_free (eprop->priv);
+		eprop->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+class_properties_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo columns = {
+			sizeof (ClassPropertiesClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) class_properties_class_init,
+			NULL,
+			NULL,
+			sizeof (ClassProperties),
+			0,
+			(GInstanceInitFunc) class_properties_init,
+			0
+		};
+		type = g_type_register_static (GTK_TYPE_VBOX, "ClassProperties", &columns, 0);
+	}
+	return type;
+}
+
+static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, ClassProperties *eprop);
+static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, ClassProperties *eprop);
+static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, ClassProperties *eprop);
+static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, ClassProperties *eprop);
+
+/**
+ * class_properties_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+class_properties_new (BrowserConnection *bcnc)
+{
+	ClassProperties *eprop;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	eprop = CLASS_PROPERTIES (g_object_new (CLASS_PROPERTIES_TYPE, NULL));
+	eprop->priv->bcnc = g_object_ref (bcnc);
+	
+	GtkWidget *sw;
+        sw = gtk_scrolled_window_new (NULL, NULL);
+        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
+        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                        GTK_POLICY_AUTOMATIC,
+                                        GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start (GTK_BOX (eprop), sw, TRUE, TRUE, 0);
+
+	GtkWidget *textview;
+	textview = gtk_text_view_new ();
+        gtk_container_add (GTK_CONTAINER (sw), textview);
+        gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
+        gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
+        gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
+	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (textview), FALSE);
+        eprop->priv->text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+        gtk_widget_show_all (sw);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "section",
+                                    "weight", PANGO_WEIGHT_BOLD,
+                                    "foreground", "blue", NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "error",
+                                    "foreground", "red", NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "data",
+                                    "left-margin", 20, NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "starter",
+                                    "indent", -10,
+                                    "left-margin", 20, NULL);
+
+	g_signal_connect (textview, "key-press-event", 
+			  G_CALLBACK (key_press_event), eprop);
+	g_signal_connect (textview, "event-after", 
+			  G_CALLBACK (event_after), eprop);
+	g_signal_connect (textview, "motion-notify-event", 
+			  G_CALLBACK (motion_notify_event), eprop);
+	g_signal_connect (textview, "visibility-notify-event", 
+			  G_CALLBACK (visibility_notify_event), eprop);
+
+	class_properties_set_class (eprop, NULL);
+
+	return (GtkWidget*) eprop;
+}
+
+static GdkCursor *hand_cursor = NULL;
+static GdkCursor *regular_cursor = NULL;
+
+/* Looks at all tags covering the position (x, y) in the text view, 
+ * and if one of them is a link, change the cursor to the "hands" cursor
+ * typically used by web browsers.
+ */
+static void
+set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, ClassProperties *eprop)
+{
+	GSList *tags = NULL, *tagp = NULL;
+	GtkTextIter iter;
+	gboolean hovering = FALSE;
+	
+	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+	
+	tags = gtk_text_iter_get_tags (&iter);
+	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
+		GtkTextTag *tag = tagp->data;
+
+		if (g_object_get_data (G_OBJECT (tag), "class")) {
+			hovering = TRUE;
+			break;
+		}
+	}
+	
+	if (hovering != eprop->priv->hovering_over_link) {
+		eprop->priv->hovering_over_link = hovering;
+		
+		if (eprop->priv->hovering_over_link) {
+			if (! hand_cursor)
+				hand_cursor = gdk_cursor_new (GDK_HAND2);
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+									 GTK_TEXT_WINDOW_TEXT),
+					       hand_cursor);
+		}
+		else {
+			if (!regular_cursor)
+				regular_cursor = gdk_cursor_new (GDK_XTERM);
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+									 GTK_TEXT_WINDOW_TEXT),
+					       regular_cursor);
+		}
+	}
+	
+	if (tags) 
+		g_slist_free (tags);
+}
+
+/* 
+ * Also update the cursor image if the window becomes visible
+ * (e.g. when a window covering it got iconified).
+ */
+static gboolean
+visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event,
+			 ClassProperties *eprop)
+{
+	gint wx, wy, bx, by;
+	
+	gdk_window_get_pointer (gtk_widget_get_window (text_view), &wx, &wy, NULL);
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       wx, wy, &bx, &by);
+	
+	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, eprop);
+	
+	return FALSE;
+}
+
+/*
+ * Update the cursor image if the pointer moved. 
+ */
+static gboolean
+motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, ClassProperties *eprop)
+{
+	gint x, y;
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       event->x, event->y, &x, &y);
+	
+	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, eprop);
+
+	gdk_window_get_pointer (gtk_widget_get_window (text_view), NULL, NULL, NULL);
+
+	return FALSE;
+}
+
+/* Looks at all tags covering the position of iter in the text view, 
+ * and if one of them is a link, follow it by showing the page identified
+ * by the data attached to it.
+ */
+static void
+follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, ClassProperties *eprop)
+{
+	GSList *tags = NULL, *tagp = NULL;
+	
+	tags = gtk_text_iter_get_tags (iter);
+	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
+		GtkTextTag *tag = tagp->data;
+		const gchar *dn;
+		
+		dn = g_object_get_data (G_OBJECT (tag), "class");
+		if (dn)
+			g_signal_emit (eprop, class_properties_signals [OPEN_CLASS], 0, dn);
+        }
+
+	if (tags) 
+		g_slist_free (tags);
+}
+
+
+/*
+ * Links can also be activated by clicking.
+ */
+static gboolean
+event_after (GtkWidget *text_view, GdkEvent *ev, ClassProperties *eprop)
+{
+	GtkTextIter start, end, iter;
+	GtkTextBuffer *buffer;
+	GdkEventButton *event;
+	gint x, y;
+	
+	if (ev->type != GDK_BUTTON_RELEASE)
+		return FALSE;
+	
+	event = (GdkEventButton *)ev;
+	
+	if (event->button != 1)
+		return FALSE;
+	
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+	
+	/* we shouldn't follow a link if the user has selected something */
+	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
+		return FALSE;
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       event->x, event->y, &x, &y);
+	
+	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
+	
+	follow_if_link (text_view, &iter, eprop);
+	
+	return FALSE;
+}
+
+/* 
+ * Links can be activated by pressing Enter.
+ */
+static gboolean
+key_press_event (GtkWidget *text_view, GdkEventKey *event, ClassProperties *eprop)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buffer;
+	
+	switch (event->keyval) {
+	case GDK_Return: 
+	case GDK_KP_Enter:
+		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+		gtk_text_buffer_get_iter_at_mark (buffer, &iter, 
+						  gtk_text_buffer_get_insert (buffer));
+		follow_if_link (text_view, &iter, eprop);
+		break;
+		
+	default:
+		break;
+	}
+	return FALSE;
+}
+
+/**
+ * class_properties_set_class:
+ * @eprop: a #ClassProperties widget
+ * @classname: a DN to display information for
+ *
+ * Adjusts the display to show @classname's properties
+ */
+void
+class_properties_set_class (ClassProperties *eprop, const gchar *classname)
+{
+	g_return_if_fail (IS_CLASS_PROPERTIES (eprop));
+
+	GtkTextBuffer *tbuffer;
+	GtkTextIter start, end;
+	GdaLdapClass *lcl;
+	GtkTextIter current;
+	gint i;
+
+	tbuffer = eprop->priv->text;
+	gtk_text_buffer_get_start_iter (tbuffer, &start);
+        gtk_text_buffer_get_end_iter (tbuffer, &end);
+        gtk_text_buffer_delete (tbuffer, &start, &end);
+
+	if (!classname || !*classname)
+		return;
+
+	lcl = browser_connection_get_class_info (eprop->priv->bcnc, classname);
+	if (!lcl) {
+		browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
+				      "%s", _("Could not get information about LDAP class"));
+		return;
+	}
+
+	gtk_text_buffer_get_start_iter (tbuffer, &current);
+
+	/* Description */
+	if (lcl->description) {
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  _("Description:"), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, lcl->description, -1,
+							  "data", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+	}
+
+	/* OID */
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+						  _("Class OID:"), -1,
+						  "section", NULL);
+	gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, lcl->oid, -1,
+						  "data", NULL);
+	gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+
+	/* Kind */
+	const gchar *kind;
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+						  _("Class kind:"), -1,
+						  "section", NULL);
+	gtk_text_buffer_insert (tbuffer, &current, "\n", 1);
+
+	gtk_text_buffer_insert_pixbuf (tbuffer, &current, browser_get_pixbuf_for_ldap_class (lcl->kind)); 
+
+	kind = browser_get_kind_for_ldap_class (lcl->kind);
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, kind, -1,
+						  "data", NULL);
+	gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+
+	/* Class name */
+	gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+						  ngettext ("Class name:", "Class names:", lcl->nb_names), -1,
+						  "section", NULL);
+	gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+	for (i = 0; i < lcl->nb_names; i++) {
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, lcl->names[i], -1,
+							  "data", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+	}
+
+	/* obsolete */
+	if (lcl->obsolete) {
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  _("This LDAP class is obsolete"), -1,
+							  "error", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+	}
+	
+	/* req. attributes */
+	if (lcl->nb_req_attributes > 0) {
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  ngettext ("Required attribute:",
+								    "Required attributes:",
+								    lcl->nb_req_attributes), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		for (i = 0; i < lcl->nb_req_attributes; i++) {
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, lcl->req_attributes[i], -1,
+								  "data", NULL);
+			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		}
+	}
+
+	/* opt. attributes */
+	if (lcl->nb_opt_attributes > 0) {
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  ngettext ("Optional attribute:",
+								    "Optional attributes:",
+								    lcl->nb_opt_attributes), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		for (i = 0; i < lcl->nb_opt_attributes; i++) {
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, lcl->opt_attributes[i], -1,
+								  "data", NULL);
+			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		}
+	}
+
+	/* children */
+	if (lcl->children) {
+		gint nb;
+		GSList *list;
+		nb = g_slist_length (lcl->children);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  ngettext ("Children class:",
+								    "Children classes:",
+								    nb), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		for (list = lcl->children; list; list = list->next) {
+			GdaLdapClass *olcl;
+			gchar *tmp;
+			GtkTextTag *tag;
+			olcl = (GdaLdapClass*) list->data;
+			gtk_text_buffer_insert_pixbuf (tbuffer, &current,
+						       browser_get_pixbuf_for_ldap_class (olcl->kind)); 
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+			tag = gtk_text_buffer_create_tag (tbuffer, NULL,
+							  "foreground", "blue",
+							  "weight", PANGO_WEIGHT_NORMAL,
+							  "underline", PANGO_UNDERLINE_SINGLE,
+							  NULL);
+			tmp = olcl->names [0];
+			g_object_set_data_full (G_OBJECT (tag), "class",
+						g_strdup (tmp), g_free);
+			gtk_text_buffer_insert_with_tags (tbuffer, &current,
+							  tmp, -1,
+							  tag, NULL);
+			if (olcl->description) {
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  " (", -1,
+									  "data", NULL);
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  olcl->description, -1,
+									  "data", NULL);
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  ")", -1,
+									  "data", NULL);
+			}
+			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		}
+	}
+
+	/* parents */
+	if (lcl->parents) {
+		gint nb;
+		GSList *list;
+		nb = g_slist_length (lcl->parents);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+							  ngettext ("Inherited class:",
+								    "Inherited classes:",
+								    nb), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		for (list = lcl->parents; list; list = list->next) {
+			GdaLdapClass *olcl;
+			gchar *tmp;
+			GtkTextTag *tag;
+			olcl = (GdaLdapClass*) list->data;
+
+			gtk_text_buffer_insert_pixbuf (tbuffer, &current,
+						       browser_get_pixbuf_for_ldap_class (olcl->kind)); 
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+			tag = gtk_text_buffer_create_tag (tbuffer, NULL,
+							  "foreground", "blue",
+							  "weight", PANGO_WEIGHT_NORMAL,
+							  "underline", PANGO_UNDERLINE_SINGLE,
+							  NULL);
+			tmp = olcl->names [0];
+			g_object_set_data_full (G_OBJECT (tag), "class",
+						g_strdup (tmp), g_free);
+			gtk_text_buffer_insert_with_tags (tbuffer, &current,
+							  tmp, -1,
+							  tag, NULL);
+
+			if (olcl->description) {
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  " (", -1,
+									  "data", NULL);
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  olcl->description, -1,
+									  "data", NULL);
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+									  ")", -1,
+									  "data", NULL);
+			}
+
+			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		}
+	}
+}
diff --git a/tools/browser/ldap-browser/class-properties.h b/tools/browser/ldap-browser/class-properties.h
new file mode 100644
index 0000000..ddb5e08
--- /dev/null
+++ b/tools/browser/ldap-browser/class-properties.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CLASS_PROPERTIES_H__
+#define __CLASS_PROPERTIES_H__
+
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define CLASS_PROPERTIES_TYPE            (class_properties_get_type())
+#define CLASS_PROPERTIES(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, CLASS_PROPERTIES_TYPE, ClassProperties))
+#define CLASS_PROPERTIES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, CLASS_PROPERTIES_TYPE, ClassPropertiesClass))
+#define IS_CLASS_PROPERTIES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, CLASS_PROPERTIES_TYPE))
+#define IS_CLASS_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLASS_PROPERTIES_TYPE))
+
+typedef struct _ClassProperties        ClassProperties;
+typedef struct _ClassPropertiesClass   ClassPropertiesClass;
+typedef struct _ClassPropertiesPrivate ClassPropertiesPrivate;
+
+struct _ClassProperties {
+	GtkVBox                 parent;
+	ClassPropertiesPrivate *priv;
+};
+
+struct _ClassPropertiesClass {
+	GtkVBoxClass            parent_class;
+
+	/* signals */
+	void                  (*open_class) (ClassProperties *eprop, const gchar *dn);
+};
+
+GType                    class_properties_get_type  (void) G_GNUC_CONST;
+
+GtkWidget               *class_properties_new       (BrowserConnection *bcnc);
+void                     class_properties_set_class (ClassProperties *eprop, const gchar *classname);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/classes-view.c b/tools/browser/ldap-browser/classes-view.c
new file mode 100644
index 0000000..b9087db
--- /dev/null
+++ b/tools/browser/ldap-browser/classes-view.c
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "classes-view.h"
+#include "../dnd.h"
+#include "../support.h"
+#include "../cc-gray-bar.h"
+#include "../browser-stock-icons.h"
+#include <virtual/gda-ldap-connection.h>
+#include "mgr-ldap-classes.h"
+#include <libgda-ui/gdaui-tree-store.h>
+
+struct _ClassesViewPrivate {
+	BrowserConnection *bcnc;
+
+	GdaTree           *classes_tree;
+	GdauiTreeStore    *classes_store;
+
+	gchar             *current_class;
+};
+
+static void classes_view_class_init (ClassesViewClass *klass);
+static void classes_view_init       (ClassesView *eview, ClassesViewClass *klass);
+static void classes_view_dispose   (GObject *object);
+static void classes_view_set_property (GObject *object,
+				       guint param_id,
+				       const GValue *value,
+				       GParamSpec *pspec);
+static void classes_view_get_property (GObject *object,
+				       guint param_id,
+				       GValue *value,
+				       GParamSpec *pspec);
+
+/* properties */
+enum {
+        PROP_0,
+};
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * ClassesView class implementation
+ */
+
+static void
+classes_view_class_init (ClassesViewClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Properties */
+        object_class->set_property = classes_view_set_property;
+        object_class->get_property = classes_view_get_property;
+
+	object_class->dispose = classes_view_dispose;
+}
+
+static void
+classes_view_init (ClassesView *eview, G_GNUC_UNUSED ClassesViewClass *klass)
+{
+	eview->priv = g_new0 (ClassesViewPrivate, 1);
+	eview->priv->current_class = NULL;
+}
+
+static void
+classes_view_dispose (GObject *object)
+{
+	ClassesView *eview = (ClassesView *) object;
+
+	/* free memory */
+	if (eview->priv) {
+		if (eview->priv->bcnc)
+			g_object_unref (eview->priv->bcnc);
+		if (eview->priv->classes_tree)
+			g_object_unref (eview->priv->classes_tree);
+
+		g_free (eview->priv);
+		eview->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+classes_view_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (ClassesViewClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) classes_view_class_init,
+			NULL,
+			NULL,
+			sizeof (ClassesView),
+			0,
+			(GInstanceInitFunc) classes_view_init,
+			0
+		};
+
+		type = g_type_register_static (GTK_TYPE_TREE_VIEW, "ClassesView", &info, 0);
+	}
+	return type;
+}
+
+static void
+classes_view_set_property (GObject *object,
+			   guint param_id,
+			   G_GNUC_UNUSED const GValue *value,
+			   GParamSpec *pspec)
+{
+	ClassesView *eview;
+	eview = CLASSES_VIEW (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+classes_view_get_property (GObject *object,
+			   guint param_id,
+			   G_GNUC_UNUSED GValue *value,
+			   GParamSpec *pspec)
+{
+	ClassesView *eview;
+	eview = CLASSES_VIEW (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static gchar *
+classes_view_to_selection (G_GNUC_UNUSED ClassesView *eview)
+{
+	/*
+	  GString *string;
+	  gchar *tmp;
+	  string = g_string_new ("OBJ_TYPE=classes");
+	  tmp = gda_rfc1738_encode (eview->priv->schema);
+	  g_string_append_printf (string, ";OBJ_SCHEMA=%s", tmp);
+	  g_free (tmp);
+	  tmp = gda_rfc1738_encode (eview->priv->classes_name);
+	  g_string_append_printf (string, ";OBJ_NAME=%s", tmp);
+	  g_free (tmp);
+	  tmp = gda_rfc1738_encode (eview->priv->classes_short_name);
+	  g_string_append_printf (string, ";OBJ_SHORT_NAME=%s", tmp);
+	  g_free (tmp);
+	  return g_string_free (string, FALSE);
+	*/
+	if (eview->priv->current_class)
+		return g_strdup (eview->priv->current_class);
+	else
+		return NULL;
+}
+
+static void
+source_drag_data_get_cb (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkDragContext *context,
+			 GtkSelectionData *selection_data,
+			 guint view, G_GNUC_UNUSED guint time, ClassesView *eview)
+{
+	switch (view) {
+	case TARGET_KEY_VALUE: {
+		gchar *str;
+		str = classes_view_to_selection (eview);
+		gtk_selection_data_set (selection_data,
+					gtk_selection_data_get_target (selection_data), 8, (guchar*) str,
+					strlen (str));
+		g_free (str);
+		break;
+	}
+	default:
+	case TARGET_PLAIN: {
+		gtk_selection_data_set_text (selection_data, classes_view_get_current_class (eview), -1);
+		break;
+	}
+	case TARGET_ROOTWIN:
+		TO_IMPLEMENT; /* dropping on the Root Window => create a file */
+		break;
+	}
+}
+
+static void selection_changed_cb (GtkTreeSelection *sel, ClassesView *eview);
+
+enum {
+	COLUMN_CLASS,
+	COLUMN_ICON,
+	COLUMN_NAME,
+	NUM_COLUMNS
+};
+
+static void
+text_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
+		     GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
+{
+	gchar *tmp;
+	gtk_tree_model_get (tree_model, iter,
+                            COLUMN_CLASS, &tmp, -1);
+	if (tmp) {
+		g_object_set ((GObject*) cell, "text", tmp,
+			      "weight-set", FALSE,
+			      "background-set", FALSE,
+			      NULL);
+		g_free (tmp);
+	}
+	else {
+		gtk_tree_model_get (tree_model, iter,
+				    COLUMN_NAME, &tmp, -1);
+		g_object_set ((GObject*) cell, "text", tmp,
+			      "weight", PANGO_WEIGHT_BOLD,
+			      "background", "grey", NULL);
+		g_free (tmp);
+	}
+}
+
+/**
+ * classes_view_new
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+classes_view_new (BrowserConnection *bcnc, const gchar *classname)
+{
+	ClassesView *eview;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	eview = CLASSES_VIEW (g_object_new (CLASSES_VIEW_TYPE, NULL));
+	eview->priv->bcnc = g_object_ref ((GObject*) bcnc);
+	g_signal_connect (eview, "drag-data-get",
+			  G_CALLBACK (source_drag_data_get_cb), eview);
+
+	GdaTreeManager *mgr;
+	GtkTreeModel *store;
+	GtkCellRenderer *renderer;
+        GtkTreeViewColumn *column;
+	eview->priv->classes_tree = gda_tree_new ();
+	mgr = mgr_ldap_classes_new (eview->priv->bcnc, FALSE, NULL);
+	gda_tree_add_manager (eview->priv->classes_tree, mgr);
+	gda_tree_manager_add_manager (mgr, mgr);
+	gda_tree_update_all (eview->priv->classes_tree, NULL);
+	g_object_unref (mgr);
+
+	store = gdaui_tree_store_new (eview->priv->classes_tree, NUM_COLUMNS,
+				      G_TYPE_STRING, "class",
+				      G_TYPE_OBJECT, "icon",
+				      G_TYPE_STRING, GDA_ATTRIBUTE_NAME);
+	gtk_tree_view_set_model (GTK_TREE_VIEW (eview), GTK_TREE_MODEL (store));
+
+	eview->priv->classes_store = GDAUI_TREE_STORE (store);
+	g_object_unref (G_OBJECT (store));
+
+	column = gtk_tree_view_column_new ();
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_column_add_attribute (column, renderer, "pixbuf", COLUMN_ICON);
+        g_object_set ((GObject*) renderer, "yalign", 0., NULL);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) text_cell_data_func,
+						 NULL, NULL);
+
+        gtk_tree_view_append_column (GTK_TREE_VIEW (eview), column);
+	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (eview), column);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (eview), FALSE);
+
+	/* tree selection */
+	GtkTreeSelection *sel;
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (eview));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	g_signal_connect (sel, "changed",
+			  G_CALLBACK (selection_changed_cb), eview);
+
+	if (classname)
+		classes_view_set_current_class (eview, classname);
+
+	return (GtkWidget*) eview;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *sel, ClassesView *eview)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
+		GdaTreeNode *node;
+		const GValue *cvalue;
+		node = gdaui_tree_store_get_node (GDAUI_TREE_STORE (model), &iter);
+		g_assert (node);
+		cvalue = gda_tree_node_get_node_attribute (node, "class");
+		g_free (eview->priv->current_class);
+		if (cvalue)
+			eview->priv->current_class = g_value_dup_string (cvalue);
+		else
+			eview->priv->current_class = NULL;
+	}
+}
+
+/**
+ * classes_view_get_current_class:
+ */
+const gchar *
+classes_view_get_current_class (ClassesView *eview)
+{
+	g_return_val_if_fail (IS_CLASSES_VIEW (eview), NULL);
+	return eview->priv->current_class;
+}
+
+static GtkTreePath *
+search_for_class (GtkTreeModel *model, const gchar *classname, GtkTreeIter *iter)
+{
+#ifdef GDA_DEBUG_NO
+	GtkTreePath *debug;
+	if (iter) {
+		debug = gtk_tree_model_get_path (model, iter);
+		g_print ("%s (%s)\n", __FUNCTION__, gtk_tree_path_to_string (debug));
+		gtk_tree_path_free (debug);
+	}
+	else
+		g_print ("%s (TOP)\n", __FUNCTION__);
+#endif
+
+	if (iter) {
+		/* look in the node itself */
+		gchar *cln = NULL;
+		gtk_tree_model_get (model, iter, COLUMN_CLASS, &cln, -1);
+		if (cln && !strcmp (cln, classname)) {
+			g_free (cln);
+			return gtk_tree_model_get_path (model, iter);
+		}
+		g_free (cln);
+	}
+
+	/* look at the children */
+	GtkTreeIter chiter;
+	if (gtk_tree_model_iter_children (model, &chiter, iter)) {
+		GtkTreePath *path;
+		path = search_for_class (model, classname, &chiter);
+		if (path)
+			return path;
+
+		for (; gtk_tree_model_iter_next (model, &chiter); ) {
+			GtkTreePath *path;
+			path = search_for_class (model, classname, &chiter);
+			if (path)
+				return path;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * classes_view_set_current_class:
+ */
+void
+classes_view_set_current_class (ClassesView *eview, const gchar *classname)
+{
+	GtkTreePath *path = NULL;
+	g_return_if_fail (IS_CLASSES_VIEW (eview));
+	g_return_if_fail (classname && *classname);
+
+	path = search_for_class (GTK_TREE_MODEL (eview->priv->classes_store), classname, NULL);
+	if (path) {
+		GtkTreeSelection *sel;
+
+#ifdef GDA_DEBUG_NO
+		g_print ("Found class [%s] at %s\n", classname, gtk_tree_path_to_string (path));
+#endif
+		gtk_tree_view_expand_to_path (GTK_TREE_VIEW (eview), path);
+		gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (eview), path, NULL, TRUE, .5, 0.);
+
+		sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (eview));
+		gtk_tree_selection_select_path (sel, path);
+		gtk_tree_path_free (path);
+	}
+}
diff --git a/tools/browser/ldap-browser/classes-view.h b/tools/browser/ldap-browser/classes-view.h
new file mode 100644
index 0000000..7139979
--- /dev/null
+++ b/tools/browser/ldap-browser/classes-view.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __CLASSES_VIEW_H__
+#define __CLASSES_VIEW_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define CLASSES_VIEW_TYPE            (classes_view_get_type())
+#define CLASSES_VIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, CLASSES_VIEW_TYPE, ClassesView))
+#define CLASSES_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, CLASSES_VIEW_TYPE, ClassesViewClass))
+#define IS_CLASSES_VIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, CLASSES_VIEW_TYPE))
+#define IS_CLASSES_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CLASSES_VIEW_TYPE))
+
+typedef struct _ClassesView        ClassesView;
+typedef struct _ClassesViewClass   ClassesViewClass;
+typedef struct _ClassesViewPrivate ClassesViewPrivate;
+
+struct _ClassesView {
+	GtkTreeView           parent;
+	ClassesViewPrivate *priv;
+};
+
+struct _ClassesViewClass {
+	GtkTreeViewClass      parent_class;
+};
+
+GType        classes_view_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *classes_view_new            (BrowserConnection *bcnc, const gchar *classname);
+const gchar *classes_view_get_current_class (ClassesView *classes_view);
+void         classes_view_set_current_class (ClassesView *classes_view, const gchar *classname);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/entry-properties.c b/tools/browser/ldap-browser/entry-properties.c
new file mode 100644
index 0000000..3bccc9f
--- /dev/null
+++ b/tools/browser/ldap-browser/entry-properties.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include "entry-properties.h"
+#include "marshal.h"
+#include <time.h>
+
+struct _EntryPropertiesPrivate {
+	BrowserConnection *bcnc;
+
+	GtkTextView *view;
+	GtkTextBuffer *text;
+	gboolean hovering_over_link;
+
+	/* coordinates of mouse */
+	gint bx;
+	gint by;
+};
+
+static void entry_properties_class_init (EntryPropertiesClass *klass);
+static void entry_properties_init       (EntryProperties *eprop, EntryPropertiesClass *klass);
+static void entry_properties_dispose   (GObject *object);
+
+static GObjectClass *parent_class = NULL;
+
+/* signals */
+enum {
+        OPEN_DN,
+	OPEN_CLASS,
+        LAST_SIGNAL
+};
+
+gint entry_properties_signals [LAST_SIGNAL] = { 0, 0 };
+
+/*
+ * EntryProperties class implementation
+ */
+
+static void
+entry_properties_class_init (EntryPropertiesClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	entry_properties_signals [OPEN_DN] =
+		g_signal_new ("open-dn",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (EntryPropertiesClass, open_dn),
+                              NULL, NULL,
+                              _ldap_marshal_VOID__STRING, G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+	entry_properties_signals [OPEN_CLASS] =
+		g_signal_new ("open-class",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (EntryPropertiesClass, open_class),
+                              NULL, NULL,
+                              _ldap_marshal_VOID__STRING, G_TYPE_NONE,
+                              1, G_TYPE_STRING);
+	klass->open_dn = NULL;
+	klass->open_class = NULL;
+
+	object_class->dispose = entry_properties_dispose;
+}
+
+
+static void
+entry_properties_init (EntryProperties *eprop, G_GNUC_UNUSED EntryPropertiesClass *klass)
+{
+	eprop->priv = g_new0 (EntryPropertiesPrivate, 1);
+	eprop->priv->hovering_over_link = FALSE;
+}
+
+static void
+entry_properties_dispose (GObject *object)
+{
+	EntryProperties *eprop = (EntryProperties *) object;
+
+	/* free memory */
+	if (eprop->priv) {
+		if (eprop->priv->bcnc) {
+			g_object_unref (eprop->priv->bcnc);
+		}
+		g_free (eprop->priv);
+		eprop->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+entry_properties_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo columns = {
+			sizeof (EntryPropertiesClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) entry_properties_class_init,
+			NULL,
+			NULL,
+			sizeof (EntryProperties),
+			0,
+			(GInstanceInitFunc) entry_properties_init,
+			0
+		};
+		type = g_type_register_static (GTK_TYPE_VBOX, "EntryProperties", &columns, 0);
+	}
+	return type;
+}
+
+static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop);
+static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop);
+static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop);
+static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, EntryProperties *eprop);
+static void populate_popup_cb (GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop);
+
+/**
+ * entry_properties_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+entry_properties_new (BrowserConnection *bcnc)
+{
+	EntryProperties *eprop;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	eprop = ENTRY_PROPERTIES (g_object_new (ENTRY_PROPERTIES_TYPE, NULL));
+	eprop->priv->bcnc = g_object_ref (bcnc);
+	
+	GtkWidget *sw;
+        sw = gtk_scrolled_window_new (NULL, NULL);
+        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
+        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                        GTK_POLICY_AUTOMATIC,
+                                        GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start (GTK_BOX (eprop), sw, TRUE, TRUE, 0);
+
+	GtkWidget *textview;
+	textview = gtk_text_view_new ();
+        gtk_container_add (GTK_CONTAINER (sw), textview);
+        gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
+        gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
+        gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
+	gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (textview), FALSE);
+        eprop->priv->text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+	eprop->priv->view = GTK_TEXT_VIEW (textview);
+        gtk_widget_show_all (sw);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "section",
+                                    "weight", PANGO_WEIGHT_BOLD,
+                                    "foreground", "blue", NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "error",
+                                    "foreground", "red", NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "data",
+				    "left-margin", 20, NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "convdata",
+				    "style", PANGO_STYLE_ITALIC,
+				    "background", "lightgray",
+                                    "left-margin", 20, NULL);
+
+        gtk_text_buffer_create_tag (eprop->priv->text, "starter",
+                                    "indent", -10,
+                                    "left-margin", 20, NULL);
+
+	g_signal_connect (textview, "key-press-event", 
+			  G_CALLBACK (key_press_event), eprop);
+	g_signal_connect (textview, "event-after", 
+			  G_CALLBACK (event_after), eprop);
+	g_signal_connect (textview, "motion-notify-event", 
+			  G_CALLBACK (motion_notify_event), eprop);
+	g_signal_connect (textview, "visibility-notify-event", 
+			  G_CALLBACK (visibility_notify_event), eprop);
+	g_signal_connect (textview, "populate-popup",
+			  G_CALLBACK (populate_popup_cb), eprop);
+
+	entry_properties_set_dn (eprop, NULL);
+
+	return (GtkWidget*) eprop;
+}
+
+static void
+data_save_cb (GtkWidget *mitem, EntryProperties *eprop)
+{
+	GtkWidget *dialog;
+
+	dialog = gtk_file_chooser_dialog_new (_("Select the file to save data to"),
+					      (GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
+					      GTK_FILE_CHOOSER_ACTION_SAVE,
+					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+					      NULL);
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+		char *filename;
+		GValue *binvalue;
+		GError *lerror;
+		const GdaBinary *bin = NULL;
+
+		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+		binvalue = g_object_get_data (G_OBJECT (mitem), "binvalue");
+		if (binvalue)
+			bin = gda_value_get_binary (binvalue);
+		if (!bin || !g_file_set_contents (filename, (gchar*) bin->data,
+						  bin->binary_length, &lerror)) {
+			browser_show_error ((GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
+					    _("Could not save data: %s"),
+					    lerror && lerror->message ? lerror->message : _("No detail"));
+			g_clear_error (&lerror);
+		}
+		g_free (filename);
+	}
+	gtk_widget_destroy (dialog);
+}
+
+static void
+populate_popup_cb (G_GNUC_UNUSED GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop)
+{
+	GtkTextIter iter;
+	gtk_text_view_get_iter_at_position (eprop->priv->view, &iter, NULL,
+					    eprop->priv->bx, eprop->priv->by);
+
+	GSList *tags = NULL, *tagp = NULL;
+	
+	tags = gtk_text_iter_get_tags (&iter);
+	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
+		GtkTextTag *tag = tagp->data;
+		GValue *binvalue;
+		
+		binvalue = g_object_get_data (G_OBJECT (tag), "binvalue");
+		if (binvalue) {
+			GtkWidget *item;
+
+			item = gtk_separator_menu_item_new ();
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+			gtk_widget_show (item);
+			
+			item = gtk_menu_item_new_with_label (_("Save"));
+			gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+			g_signal_connect (G_OBJECT (item), "activate",
+					  G_CALLBACK (data_save_cb), eprop);
+			g_object_set_data (G_OBJECT (item), "binvalue", binvalue);
+			gtk_widget_show (item);
+
+			break;
+		}
+        }
+
+	if (tags) 
+		g_slist_free (tags);
+}
+
+static GdkCursor *hand_cursor = NULL;
+static GdkCursor *regular_cursor = NULL;
+
+/* Looks at all tags covering the position (x, y) in the text view, 
+ * and if one of them is a link, change the cursor to the "hands" cursor
+ * typically used by web browsers.
+ */
+static void
+set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, EntryProperties *eprop)
+{
+	GSList *tags = NULL, *tagp = NULL;
+	GtkTextIter iter;
+	gboolean hovering = FALSE;
+	
+	gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
+	
+	tags = gtk_text_iter_get_tags (&iter);
+	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
+		GtkTextTag *tag = tagp->data;
+
+		if (g_object_get_data (G_OBJECT (tag), "dn") ||
+		    g_object_get_data (G_OBJECT (tag), "class")) {
+			hovering = TRUE;
+			break;
+		}
+	}
+	
+	if (hovering != eprop->priv->hovering_over_link) {
+		eprop->priv->hovering_over_link = hovering;
+		
+		if (eprop->priv->hovering_over_link) {
+			if (! hand_cursor)
+				hand_cursor = gdk_cursor_new (GDK_HAND2);
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+									 GTK_TEXT_WINDOW_TEXT),
+					       hand_cursor);
+		}
+		else {
+			if (!regular_cursor)
+				regular_cursor = gdk_cursor_new (GDK_XTERM);
+			gdk_window_set_cursor (gtk_text_view_get_window (text_view,
+									 GTK_TEXT_WINDOW_TEXT),
+					       regular_cursor);
+		}
+	}
+	
+	if (tags) 
+		g_slist_free (tags);
+}
+
+/* 
+ * Also update the cursor image if the window becomes visible
+ * (e.g. when a window covering it got iconified).
+ */
+static gboolean
+visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event,
+			 EntryProperties *eprop)
+{
+	gint wx, wy, bx, by;
+	
+	gdk_window_get_pointer (gtk_widget_get_window (text_view), &wx, &wy, NULL);
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       wx, wy, &bx, &by);
+	
+	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, eprop);
+	
+	return FALSE;
+}
+
+/*
+ * Update the cursor image if the pointer moved. 
+ */
+static gboolean
+motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop)
+{
+	gint x, y;
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       event->x, event->y, &x, &y);
+	
+	set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, eprop);
+
+	gdk_window_get_pointer (gtk_widget_get_window (text_view), NULL, NULL, NULL);
+
+	/* store coordinates */
+	eprop->priv->bx = x;
+	eprop->priv->by = y;
+
+	return FALSE;
+}
+
+/* Looks at all tags covering the position of iter in the text view, 
+ * and if one of them is a link, follow it by showing the page identified
+ * by the data attached to it.
+ */
+static void
+follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, EntryProperties *eprop)
+{
+	GSList *tags = NULL, *tagp = NULL;
+	
+	tags = gtk_text_iter_get_tags (iter);
+	for (tagp = tags;  tagp != NULL;  tagp = tagp->next) {
+		GtkTextTag *tag = tagp->data;
+		const gchar *dn;
+		
+		dn = g_object_get_data (G_OBJECT (tag), "dn");
+		if (dn)
+			g_signal_emit (eprop, entry_properties_signals [OPEN_DN], 0, dn);
+		dn = g_object_get_data (G_OBJECT (tag), "class");
+		if (dn)
+			g_signal_emit (eprop, entry_properties_signals [OPEN_CLASS], 0, dn);
+        }
+
+	if (tags) 
+		g_slist_free (tags);
+}
+
+
+/*
+ * Links can also be activated by clicking.
+ */
+static gboolean
+event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop)
+{
+	GtkTextIter start, end, iter;
+	GtkTextBuffer *buffer;
+	GdkEventButton *event;
+	gint x, y;
+	
+	if (ev->type != GDK_BUTTON_RELEASE)
+		return FALSE;
+	
+	event = (GdkEventButton *)ev;
+	
+	if (event->button != 1)
+		return FALSE;
+	
+	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+	
+	/* we shouldn't follow a link if the user has selected something */
+	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+	if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
+		return FALSE;
+	
+	gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), 
+					       GTK_TEXT_WINDOW_WIDGET,
+					       event->x, event->y, &x, &y);
+	
+	gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
+	
+	follow_if_link (text_view, &iter, eprop);
+	
+	return FALSE;
+}
+
+/* 
+ * Links can be activated by pressing Enter.
+ */
+static gboolean
+key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop)
+{
+	GtkTextIter iter;
+	GtkTextBuffer *buffer;
+	
+	switch (event->keyval) {
+	case GDK_Return: 
+	case GDK_KP_Enter:
+		buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
+		gtk_text_buffer_get_iter_at_mark (buffer, &iter, 
+						  gtk_text_buffer_get_insert (buffer));
+		follow_if_link (text_view, &iter, eprop);
+		break;
+		
+	default:
+		break;
+	}
+	return FALSE;
+}
+
+static GdkPixbuf *
+data_to_pixbuf (const GValue *cvalue)
+{
+	GdkPixbuf *retpixbuf = NULL;
+
+	if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
+		const GdaBinary *bin;
+		GdkPixbufLoader *loader;
+
+		bin = gda_value_get_binary (cvalue);
+		if (!bin->data)
+			goto out;
+
+		loader = gdk_pixbuf_loader_new ();
+		if (gdk_pixbuf_loader_write (loader, bin->data, bin->binary_length, NULL)) {
+			if (gdk_pixbuf_loader_close (loader, NULL)) {
+				retpixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+				g_object_ref (retpixbuf);
+			}
+			else
+				gdk_pixbuf_loader_close (loader, NULL);
+		}
+		else
+			gdk_pixbuf_loader_close (loader, NULL);
+		g_object_unref (loader);
+	}
+
+ out:
+	return retpixbuf;
+}
+
+static gchar *
+unix_shadow_to_string (const gchar *value, const gchar *attname)
+{
+	/* value is the number of days since 1970-01-01 */
+	gint64 i64;
+	gchar *endptr [1];
+
+	if (!value || !*value)
+		return NULL;
+
+	i64 = g_ascii_strtoll (value, endptr, 10);
+	if (**endptr != '\0')
+		return NULL;
+
+	if ((i64 == -1) &&
+	    (!strcmp (attname, "shadowInactive") ||
+	     !strcmp (attname, "shadowMin") ||
+	     !strcmp (attname, "shadowExpire")))
+		return g_strdup (_("Non activated"));
+	else if ((i64 == 99999) && !strcmp (attname, "shadowMax"))
+		return g_strdup ("Always valid");
+	if ((i64 >= G_MAXUINT) || (i64 < 0))
+		return NULL;
+
+	if (!strcmp (attname, "shadowMax") ||
+	    !strcmp (attname, "shadowMin") ||
+	    !strcmp (attname, "shadowInactive"))
+		return NULL;
+
+	GDate *date;
+	date = g_date_new_dmy (1, 1, 1970);
+	g_date_add_days (date, (guint) i64);
+	if (! g_date_valid (date)) {
+		g_date_free (date);
+		return NULL;
+	}
+
+	GdaDataHandler *dh;
+	GValue tvalue;
+	gchar *str;
+	 
+	memset (&tvalue, 0, sizeof (GValue));
+	g_value_init (&tvalue, G_TYPE_DATE);
+	g_value_take_boxed (&tvalue, date);
+	dh = gda_data_handler_get_default (G_TYPE_DATE);
+	str = gda_data_handler_get_str_from_value (dh, &tvalue);
+	g_value_reset (&tvalue);
+
+	return str;
+}
+
+static gchar *
+ad_1601_timestamp_to_string (const gchar *value, const gchar *attname)
+{
+	/* value is the number of 100 nanoseconds since 1601-01-01 UTC */
+	gint64 i64;
+	gchar *endptr [1];
+
+	if (!value || !*value)
+		return NULL;
+
+	i64 = g_ascii_strtoll (value, endptr, 10);
+	if (**endptr != '\0')
+		return NULL;
+
+	if (i64 == 0x7FFFFFFFFFFFFFFF)
+		return g_strdup (_("Never"));
+
+	if (i64 == 0 && attname) {
+		if (!strcmp (attname, "accountExpires"))
+			return g_strdup (_("Never"));
+		else
+			return g_strdup (_("Unknown"));
+	}
+
+	i64 = (i64 / (guint64) 10000000);
+	if (i64 < (gint64) 11644473600)
+		return NULL;
+	i64 = i64 - (guint64) 11644473600;
+	if (i64 >= G_MAXINT)
+		return NULL;
+
+	GdaDataHandler *dh;
+	struct tm stm;
+	GValue tvalue;
+	GdaTimestamp ts;
+	time_t nsec = (time_t) i64;
+	gchar *str;
+	 
+	localtime_r (&nsec, &stm);
+	memset (&ts, 0, sizeof (GdaTimestamp));
+	ts.year = stm.tm_year + 1900;
+	ts.month = stm.tm_mon + 1;
+	ts.day = stm.tm_mday;
+	ts.hour = stm.tm_hour;
+	ts.minute = stm.tm_min;
+	ts.second = stm.tm_sec;
+	ts.timezone = GDA_TIMEZONE_INVALID;
+	memset (&tvalue, 0, sizeof (GValue));
+	gda_value_set_timestamp (&tvalue, &ts);
+	dh = gda_data_handler_get_default (GDA_TYPE_TIMESTAMP);
+	str = gda_data_handler_get_str_from_value (dh, &tvalue);
+	g_value_reset (&tvalue);
+
+	return str;
+}
+
+typedef struct {
+	guint mask;
+	gchar *descr;
+} ADUACData;
+
+ADUACData uac_data[] = {
+	{0x00000001, "Logon script is executed"},
+	{0x00000002, "Account disabled"},
+	{0x00000008, "Home directory required"},
+	{0x00000010, "Account locked out"},
+	{0x00000020, "No password required"},
+	{0x00000040, "User cannot change password"},
+	{0x00000080, "User can send an encrypted password"},
+	{0x00000100, "Duplicate account (local user account)"},
+	{0x00000200, "Default account type"},
+	{0x00000800, "Permit to trust account for a system domain that trusts other domains"},
+	{0x00001000, "Account for a computer"},
+	{0x00002000, "Account for a system backup domain controller"},
+	{0x00010000, "Account never expires"},
+	{0x00020000, "Majority Node Set (MNS) logon account"},
+	{0x00040000, "User must log on using a smart card"},
+	{0x00080000, "Service account for trusted for Kerberos delegation"},
+	{0x00100000, "Security context not delegated"},
+	{0x00200000, "Only Data Encryption Standard (DES) encryption for keys"},
+	{0x00400000, "Kerberos pre-authentication not required"},
+	{0x00800000, "User password expired"},
+	{0x01000000, "Account enabled for delegation"}
+};
+
+static gchar *
+ad_1601_uac_to_string (const gchar *value)
+{
+	gint64 i64;
+	gchar *endptr [1];
+
+	if (!value || !*value)
+		return NULL;
+
+	i64 = g_ascii_strtoll (value, endptr, 10);
+	if (**endptr != '\0')
+		return NULL;
+	if (i64 < 0)
+		return NULL;
+	if (i64 > G_MAXUINT32)
+		return NULL;
+
+	GString *string = NULL;
+	guint i;
+	guint32 v;
+	v = (guint32) i64;
+	for (i = 0; i < sizeof (uac_data) / sizeof (ADUACData); i++) {
+		ADUACData *d;
+		d = & (uac_data [i]);
+		if (v & d->mask) {
+			if (string)
+				g_string_append (string, " / ");
+			else
+				string = g_string_new ("");
+			g_string_append (string, d->descr);
+		}
+	}
+	if (string)
+		return g_string_free (string, FALSE);	
+	else
+		return NULL;
+}
+
+static gchar *
+ad_sam_account_type_to_string (const gchar *value)
+{
+	gint64 i64;
+	gchar *endptr [1];
+
+	if (!value || !*value)
+		return NULL;
+
+	i64 = g_ascii_strtoll (value, endptr, 10);
+	if (**endptr != '\0')
+		return NULL;
+	if (i64 < 0)
+		return NULL;
+
+	switch (i64) {
+	case 0x0:
+		return g_strdup ("SAM_DOMAIN_OBJECT");
+	case 0x10000000:
+		return g_strdup ("SAM_GROUP_OBJECT");
+	case 0x10000001:
+		return g_strdup ("SAM_NON_SECURITY_GROUP_OBJECT");
+	case 0x20000000:
+		return g_strdup ("SAM_ALIAS_OBJECT");
+	case 0x20000001:
+		return g_strdup ("SAM_NON_SECURITY_ALIAS_OBJECT");
+	case 0x30000000:
+		return g_strdup ("SAM_NORMAL_USER_ACCOUNT");
+	case 0x30000001:
+		return g_strdup ("SAM_MACHINE_ACCOUNT");
+	case 0x30000002:
+		return g_strdup ("SAM_TRUST_ACCOUNT");
+	case 0x40000000:
+		return g_strdup ("SAM_APP_BASIC_GROUP");
+	case 0x40000001:
+		return g_strdup ("SAM_APP_QUERY_GROUP");
+	case 0x7fffffff:
+		return g_strdup ("SAM_ACCOUNT_TYPE_MAX");
+	default:
+		return NULL;
+	}
+}
+
+static void
+info_fetch_cb (BrowserConnection *bcnc, gpointer out_result, EntryProperties *eprop, G_GNUC_UNUSED GError *error)
+{
+	if (out_result) {
+		GtkTextBuffer *tbuffer;
+		GtkTextIter start, end;
+		
+		tbuffer = eprop->priv->text;
+		gtk_text_buffer_get_start_iter (tbuffer, &start);
+		gtk_text_buffer_get_end_iter (tbuffer, &end);
+		gtk_text_buffer_delete (tbuffer, &start, &end);
+
+		GdaLdapEntry *entry = (GdaLdapEntry*) out_result;
+		guint i;
+		GtkTextIter current;
+
+		gtk_text_buffer_get_start_iter (tbuffer, &current);
+
+		/* DN */
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, _("Distinguished Name:"), -1,
+							  "section", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1, "starter", NULL);
+		gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, entry->dn, -1,
+							  "data", NULL);
+		gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+
+		/* other attributes */
+		const gchar *basedn;
+		GdaDataHandler *ts_dh = NULL;
+		basedn = browser_connection_ldap_get_base_dn (bcnc);
+
+		for (i = 0; i < entry->nb_attributes; i++) {
+			GdaLdapAttribute *attr;
+			gchar *tmp;
+			attr = entry->attributes [i];
+			tmp = g_strdup_printf ("%s:", attr->attr_name);
+			gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, tmp, -1, "section", NULL);
+			g_free (tmp);
+			gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+
+			guint j;
+			for (j = 0; j < attr->nb_values; j++) {
+				const GValue *cvalue;
+				cvalue = attr->values [j];
+
+				gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, " ", -1,
+									  "starter", NULL);
+
+				if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
+					GValue *copyvalue;
+					GtkTextTagTable *table;
+					GtkTextTag *tag;
+					GtkTextMark *mark;
+
+					copyvalue = gda_value_copy (cvalue);
+					table = gtk_text_buffer_get_tag_table (tbuffer);
+					tag = gtk_text_tag_new (NULL);
+					gtk_text_tag_table_add (table, tag);
+					g_object_set_data_full ((GObject*) tag, "binvalue",
+								copyvalue, (GDestroyNotify) gda_value_free);
+					g_object_unref ((GObject*) tag);
+
+					mark = gtk_text_buffer_create_mark (tbuffer, NULL, &current, TRUE);
+
+					GdkPixbuf *pixbuf;
+					pixbuf = data_to_pixbuf (cvalue);
+					if (pixbuf) {
+						gtk_text_buffer_insert_pixbuf (tbuffer, &current, pixbuf); 
+						g_object_unref (pixbuf);
+											}
+					else {
+						GdaDataHandler *dh;
+						dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
+						if (dh)
+							tmp = gda_data_handler_get_str_from_value (dh, cvalue);
+						else
+							tmp = gda_value_stringify (cvalue);
+						gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+											  tmp, -1,
+											  "data", NULL);
+						g_free (tmp);
+					}
+					GtkTextIter before;
+					gtk_text_buffer_get_iter_at_mark (tbuffer, &before, mark);
+					gtk_text_buffer_apply_tag (tbuffer, tag, &before, &current);
+					gtk_text_buffer_delete_mark (tbuffer, mark);
+					
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "\n", 1,
+										  "data", NULL);
+				}
+				else {
+					GdaDataHandler *dh;
+					dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
+					if (dh)
+						tmp = gda_data_handler_get_str_from_value (dh, cvalue);
+					else
+						tmp = gda_value_stringify (cvalue);
+					if (tmp) {
+						if (*tmp &&
+						    ((basedn && g_str_has_suffix (tmp, basedn)) || !basedn) &&
+						    gda_ldap_is_dn (tmp)) {
+							/* we have a DN */
+							GtkTextTag *tag;
+							tag = gtk_text_buffer_create_tag (tbuffer, NULL,
+											  "foreground", "blue",
+											  "weight", PANGO_WEIGHT_NORMAL,
+											  "underline", PANGO_UNDERLINE_SINGLE,
+											  NULL);
+							g_object_set_data_full (G_OBJECT (tag), "dn",
+										g_strdup (tmp), g_free);
+							gtk_text_buffer_insert_with_tags (tbuffer, &current,
+											  tmp, -1,
+											  tag, NULL);
+						}
+						else if (attr->attr_name &&
+							 !g_ascii_strcasecmp (attr->attr_name, "objectClass")) {
+							GtkTextTag *tag;
+							tag = gtk_text_buffer_create_tag (tbuffer, NULL,
+											  "foreground", "blue",
+											  "weight", PANGO_WEIGHT_NORMAL,
+											  "underline", PANGO_UNDERLINE_SINGLE,
+											  NULL);
+							g_object_set_data_full (G_OBJECT (tag), "class",
+										g_strdup (tmp), g_free);
+							gtk_text_buffer_insert_with_tags (tbuffer, &current,
+											  tmp, -1,
+											  tag, NULL);
+						}
+						else
+							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, tmp, -1,
+												  "data", NULL);
+
+						gchar *extrainfo = NULL;
+						if (!strncmp (attr->attr_name, "shadow", 6) &&
+						    (!strcmp (attr->attr_name, "shadowLastChange") ||
+						     !strcmp (attr->attr_name, "shadowMax") ||
+						     !strcmp (attr->attr_name, "shadowMin") ||
+						     !strcmp (attr->attr_name, "shadowInactive") ||
+						     !strcmp (attr->attr_name, "shadowExpire")))
+							extrainfo = unix_shadow_to_string (tmp, attr->attr_name);
+						else if (!strcmp (attr->attr_name, "badPasswordTime") ||
+							 !strcmp (attr->attr_name, "lastLogon") ||
+							 !strcmp (attr->attr_name, "pwdLastSet") ||
+							 !strcmp (attr->attr_name, "accountExpires") ||
+							 !strcmp (attr->attr_name, "lockoutTime") ||
+							 !strcmp (attr->attr_name, "lastLogonTimestamp"))
+							extrainfo = ad_1601_timestamp_to_string (tmp, attr->attr_name);
+						else if (!strcmp (attr->attr_name, "userAccountControl"))
+							extrainfo = ad_1601_uac_to_string (tmp);
+						else if (!strcmp (attr->attr_name, "sAMAccountType"))
+							extrainfo = ad_sam_account_type_to_string (tmp);
+
+						if (extrainfo) {
+							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+												  " ", 1,
+												  "data", NULL);
+							gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+												  extrainfo, -1,
+												  "convdata", NULL);
+							g_free (extrainfo);
+						}
+						g_free (tmp);
+					}
+					else {
+						gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current, _("Can't display attribute value"), -1,
+											  "error", NULL);
+					}
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "\n", 1,
+										  "data", NULL);
+				}
+			}
+		}
+		if (ts_dh)
+			g_object_unref (ts_dh);
+		gda_ldap_entry_free (entry);
+	}
+	else
+		browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
+				      "%s", _("Could not get information about LDAP entry"));
+
+	g_object_unref (eprop);
+}
+
+/**
+ * entry_properties_set_dn:
+ * @eprop: a #EntryProperties widget
+ * @dn: a DN to display information for
+ *
+ * Adjusts the display to show @dn's properties
+ */
+void
+entry_properties_set_dn (EntryProperties *eprop, const gchar *dn)
+{
+	g_return_if_fail (IS_ENTRY_PROPERTIES (eprop));
+
+	GtkTextBuffer *tbuffer;
+	GtkTextIter start, end;
+
+	tbuffer = eprop->priv->text;
+	gtk_text_buffer_get_start_iter (tbuffer, &start);
+        gtk_text_buffer_get_end_iter (tbuffer, &end);
+        gtk_text_buffer_delete (tbuffer, &start, &end);
+
+	if (dn && *dn) {
+		guint id;
+		id = browser_connection_ldap_describe_entry (eprop->priv->bcnc, dn,
+							     (BrowserConnectionJobCallback) info_fetch_cb,
+							     g_object_ref (eprop), NULL);
+		if (id == 0)
+			browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
+					      "%s", _("Could not get information about LDAP entry"));
+	}
+}
diff --git a/tools/browser/ldap-browser/entry-properties.h b/tools/browser/ldap-browser/entry-properties.h
new file mode 100644
index 0000000..80cf885
--- /dev/null
+++ b/tools/browser/ldap-browser/entry-properties.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __ENTRY_PROPERTIES_H__
+#define __ENTRY_PROPERTIES_H__
+
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define ENTRY_PROPERTIES_TYPE            (entry_properties_get_type())
+#define ENTRY_PROPERTIES(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, ENTRY_PROPERTIES_TYPE, EntryProperties))
+#define ENTRY_PROPERTIES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, ENTRY_PROPERTIES_TYPE, EntryPropertiesClass))
+#define IS_ENTRY_PROPERTIES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, ENTRY_PROPERTIES_TYPE))
+#define IS_ENTRY_PROPERTIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ENTRY_PROPERTIES_TYPE))
+
+typedef struct _EntryProperties        EntryProperties;
+typedef struct _EntryPropertiesClass   EntryPropertiesClass;
+typedef struct _EntryPropertiesPrivate EntryPropertiesPrivate;
+
+struct _EntryProperties {
+	GtkVBox                 parent;
+	EntryPropertiesPrivate *priv;
+};
+
+struct _EntryPropertiesClass {
+	GtkVBoxClass            parent_class;
+
+	/* signals */
+	void                  (*open_dn) (EntryProperties *eprop, const gchar *dn);
+	void                  (*open_class) (EntryProperties *eprop, const gchar *classname);
+};
+
+GType                    entry_properties_get_type (void) G_GNUC_CONST;
+
+GtkWidget               *entry_properties_new      (BrowserConnection *bcnc);
+void                     entry_properties_set_dn   (EntryProperties *eprop, const gchar *dn);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/filter-editor.c b/tools/browser/ldap-browser/filter-editor.c
new file mode 100644
index 0000000..14a1328
--- /dev/null
+++ b/tools/browser/ldap-browser/filter-editor.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "filter-editor.h"
+#include <libgda-ui/gdaui-combo.h>
+#include <libgda-ui/gdaui-data-selector.h>
+
+struct _FilterEditorPrivate {
+	BrowserConnection *bcnc;
+	GtkWidget *base_dn;
+	GtkWidget *filter;
+	GtkWidget *attributes;
+	GtkWidget *scope;
+
+	GdaLdapSearchScope default_scope;
+};
+
+static void filter_editor_class_init (FilterEditorClass *klass);
+static void filter_editor_init       (FilterEditor *feditor, FilterEditorClass *klass);
+static void filter_editor_dispose    (GObject *object);
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * FilterEditor class implementation
+ */
+
+static void
+filter_editor_class_init (FilterEditorClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = filter_editor_dispose;
+}
+
+static void
+filter_editor_init (FilterEditor *feditor, G_GNUC_UNUSED FilterEditorClass *klass)
+{
+	feditor->priv = g_new0 (FilterEditorPrivate, 1);
+	feditor->priv->bcnc = NULL;
+	feditor->priv->default_scope = GDA_LDAP_SEARCH_SUBTREE;
+}
+
+static void
+filter_editor_dispose (GObject *object)
+{
+	FilterEditor *feditor = (FilterEditor *) object;
+
+	/* free memory */
+	if (feditor->priv) {
+		if (feditor->priv->bcnc)
+			g_object_unref (feditor->priv->bcnc);
+		g_free (feditor->priv);
+		feditor->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+filter_editor_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (FilterEditorClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) filter_editor_class_init,
+			NULL,
+			NULL,
+			sizeof (FilterEditor),
+			0,
+			(GInstanceInitFunc) filter_editor_init,
+			0
+		};
+
+		type = g_type_register_static (GTK_TYPE_VBOX, "FilterEditor", &info, 0);
+	}
+	return type;
+}
+
+/**
+ * filter_editor_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+filter_editor_new (BrowserConnection *bcnc)
+{
+	FilterEditor *feditor;
+	GtkWidget *table, *label, *entry;
+	GdaDataModel *model;
+	GList *values;
+	GValue *v1, *v2;
+	gfloat ya;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	feditor = FILTER_EDITOR (g_object_new (FILTER_EDITOR_TYPE, NULL));
+	feditor->priv->bcnc = g_object_ref ((GObject*) bcnc);
+
+	table = gtk_table_new (4, 2, FALSE);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, 5);
+	gtk_box_pack_start (GTK_BOX (feditor), table, TRUE, TRUE, 0);
+	
+	label = gtk_label_new (_("Base DN:"));
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, GTK_SHRINK, 0, 0);
+	label = gtk_label_new (_("Filter expression:"));
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, GTK_SHRINK, 0, 0);
+	label = gtk_label_new (_("Attributes to fetch:"));
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3, GTK_FILL, GTK_SHRINK, 0, 0);
+	label = gtk_label_new (_("Search scope:"));
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, GTK_FILL, GTK_SHRINK, 0, 0);
+	
+	entry = gtk_entry_new ();
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+	feditor->priv->base_dn = entry;
+
+	entry = gtk_entry_new ();
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 1, 2);
+	feditor->priv->filter = entry;
+
+	entry = gtk_entry_new ();
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 2, 3);
+	feditor->priv->attributes = entry;
+
+	model = gda_data_model_array_new_with_g_types (2, G_TYPE_INT, G_TYPE_STRING);
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "Base (search the base DN only)");
+	values = g_list_prepend (NULL, v1);
+	g_value_set_int ((v2 = gda_value_new (G_TYPE_INT)), GDA_LDAP_SEARCH_BASE);
+	values = g_list_prepend (values, v2);
+	g_assert (gda_data_model_append_values (model, values, NULL) >= 0);
+	gda_value_free (v1);
+	gda_value_free (v2);
+
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "Onelevel (search immediate children of base DN only)");
+	values = g_list_prepend (NULL, v1);
+	g_value_set_int ((v2 = gda_value_new (G_TYPE_INT)), GDA_LDAP_SEARCH_ONELEVEL);
+	values = g_list_prepend (values, v2);
+	g_assert (gda_data_model_append_values (model, values, NULL) >= 0);
+	gda_value_free (v1);
+	gda_value_free (v2);
+
+	g_value_set_string ((v1 = gda_value_new (G_TYPE_STRING)), "Subtree (search of the base DN and the entire subtree below)");
+	values = g_list_prepend (NULL, v1);
+	g_value_set_int ((v2 = gda_value_new (G_TYPE_INT)), GDA_LDAP_SEARCH_SUBTREE);
+	values = g_list_prepend (values, v2);
+	g_assert (gda_data_model_append_values (model, values, NULL) >= 0);
+	gda_value_free (v1);
+	gda_value_free (v2);
+
+	gint cols[] = {1};
+	entry = gdaui_combo_new_with_model (model, 1, cols);
+	g_object_unref (model);
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 3, 4);
+	feditor->priv->scope = entry;
+	filter_editor_clear (feditor);
+
+	gtk_widget_show_all (table);
+	return (GtkWidget*) feditor;
+}
+
+/**
+ * filter_editor_clear:
+ */
+void
+filter_editor_clear (FilterEditor *fedit)
+{
+	g_return_if_fail (IS_FILTER_EDITOR (fedit));
+	filter_editor_set_settings (fedit, NULL, NULL, NULL, fedit->priv->default_scope);
+}
+
+/**
+ * filter_editor_set_settings:
+ */
+void
+filter_editor_set_settings (FilterEditor *fedit,
+			    const gchar *base_dn, const gchar *filter,
+			    const gchar *attributes, GdaLdapSearchScope scope)
+{
+	g_return_if_fail (IS_FILTER_EDITOR (fedit));
+
+	gtk_entry_set_text (GTK_ENTRY (fedit->priv->base_dn), base_dn ? base_dn : "");
+	gtk_entry_set_text (GTK_ENTRY (fedit->priv->filter), filter ? filter : "");
+	gtk_entry_set_text (GTK_ENTRY (fedit->priv->attributes), attributes ? attributes : "");
+	gdaui_data_selector_select_row (GDAUI_DATA_SELECTOR (fedit->priv->scope), scope - 1);
+}
+
+/**
+ * filter_editor_get_settings:
+ */
+void
+filter_editor_get_settings (FilterEditor *fedit,
+			    gchar **out_base_dn, gchar **out_filter,
+			    gchar **out_attributes, GdaLdapSearchScope *out_scope)
+{
+	const gchar *tmp;
+	g_return_if_fail (IS_FILTER_EDITOR (fedit));
+	if (out_base_dn) {
+		tmp = gtk_entry_get_text (GTK_ENTRY (fedit->priv->base_dn));
+		*out_base_dn = tmp && *tmp ? g_strdup (tmp) : NULL;
+	}
+	if (out_filter) {
+		tmp = gtk_entry_get_text (GTK_ENTRY (fedit->priv->filter));
+		if (tmp && *tmp) {
+			/* add surrounding parenthesis if not yet there */
+			if (*tmp != '(') {
+				gint len;
+				len = strlen (tmp);
+				if (tmp [len-1] != ')')
+					*out_filter = g_strdup_printf ("(%s)", tmp);
+				else
+					*out_filter = g_strdup (tmp);/* may result in an error when executed */	
+			}
+			else
+				*out_filter = g_strdup (tmp);
+
+		}
+		else
+			*out_filter = NULL;
+	}
+	if (out_attributes) {
+		tmp = gtk_entry_get_text (GTK_ENTRY (fedit->priv->attributes));
+		*out_attributes = tmp && *tmp ? g_strdup (tmp) : NULL;
+	}
+	if (out_scope)
+		*out_scope = gtk_combo_box_get_active (GTK_COMBO_BOX (fedit->priv->scope)) + 1;
+}
diff --git a/tools/browser/ldap-browser/filter-editor.h b/tools/browser/ldap-browser/filter-editor.h
new file mode 100644
index 0000000..fcfbd72
--- /dev/null
+++ b/tools/browser/ldap-browser/filter-editor.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __FILTER_EDITOR_H__
+#define __FILTER_EDITOR_H__
+
+#include <gtk/gtk.h>
+#include <libgda/libgda.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define FILTER_EDITOR_TYPE            (filter_editor_get_type())
+#define FILTER_EDITOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, FILTER_EDITOR_TYPE, FilterEditor))
+#define FILTER_EDITOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, FILTER_EDITOR_TYPE, FilterEditorClass))
+#define IS_FILTER_EDITOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, FILTER_EDITOR_TYPE))
+#define IS_FILTER_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), FILTER_EDITOR_TYPE))
+
+typedef struct _FilterEditor        FilterEditor;
+typedef struct _FilterEditorClass   FilterEditorClass;
+typedef struct _FilterEditorPrivate FilterEditorPrivate;
+
+struct _FilterEditor {
+	GtkVBox              parent;
+	FilterEditorPrivate *priv;
+};
+
+struct _FilterEditorClass {
+	GtkVBoxClass         parent_class;
+};
+
+GType        filter_editor_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *filter_editor_new            (BrowserConnection *bcnc);
+void         filter_editor_clear          (FilterEditor *fedit);
+void         filter_editor_set_settings   (FilterEditor *fedit,
+					   const gchar *base_dn, const gchar *filter,
+					   const gchar *attributes, GdaLdapSearchScope scope);
+void         filter_editor_get_settings   (FilterEditor *fedit,
+					   gchar **out_base_dn, gchar **out_filter,
+					   gchar **out_attributes, GdaLdapSearchScope *out_scope);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/hierarchy-view.c b/tools/browser/ldap-browser/hierarchy-view.c
new file mode 100644
index 0000000..a2870bd
--- /dev/null
+++ b/tools/browser/ldap-browser/hierarchy-view.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "hierarchy-view.h"
+#include "../dnd.h"
+#include "../support.h"
+#include "../cc-gray-bar.h"
+#include "../browser-stock-icons.h"
+#include <virtual/gda-ldap-connection.h>
+#include "mgr-ldap-entries.h"
+#include <libgda-ui/gdaui-tree-store.h>
+
+struct _HierarchyViewPrivate {
+	BrowserConnection *bcnc;
+
+	GdaTree           *ldap_tree;
+	GdauiTreeStore    *ldap_store;
+
+	gchar             *current_dn;
+	gchar             *current_cn;
+
+	GArray            *to_show;
+};
+
+static void hierarchy_view_class_init (HierarchyViewClass *klass);
+static void hierarchy_view_init       (HierarchyView *eview, HierarchyViewClass *klass);
+static void hierarchy_view_dispose   (GObject *object);
+static void hierarchy_view_set_property (GObject *object,
+					 guint param_id,
+					 const GValue *value,
+					 GParamSpec *pspec);
+static void hierarchy_view_get_property (GObject *object,
+					 guint param_id,
+					 GValue *value,
+					 GParamSpec *pspec);
+
+/* properties */
+enum {
+        PROP_0,
+};
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * HierarchyView class implementation
+ */
+
+static void
+hierarchy_view_class_init (HierarchyViewClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Properties */
+        object_class->set_property = hierarchy_view_set_property;
+        object_class->get_property = hierarchy_view_get_property;
+
+	object_class->dispose = hierarchy_view_dispose;
+}
+
+static void
+hierarchy_view_init (HierarchyView *eview, G_GNUC_UNUSED HierarchyViewClass *klass)
+{
+	eview->priv = g_new0 (HierarchyViewPrivate, 1);
+	eview->priv->current_dn = NULL;
+	eview->priv->current_cn = NULL;
+}
+
+static void
+hierarchy_view_dispose (GObject *object)
+{
+	HierarchyView *eview = (HierarchyView *) object;
+
+	/* free memory */
+	if (eview->priv) {
+		if (eview->priv->bcnc)
+			g_object_unref (eview->priv->bcnc);
+		if (eview->priv->ldap_tree)
+			g_object_unref (eview->priv->ldap_tree);
+		if (eview->priv->to_show) {
+			guint i;
+			for (i = 0; i < eview->priv->to_show->len; i++) {
+				gchar *tmp;
+				tmp = (gchar*) g_array_index (eview->priv->to_show, gchar*, i);
+				g_free (tmp);
+			}
+			g_array_free (eview->priv->to_show, TRUE);
+		}
+
+		g_free (eview->priv);
+		eview->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+hierarchy_view_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (HierarchyViewClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) hierarchy_view_class_init,
+			NULL,
+			NULL,
+			sizeof (HierarchyView),
+			0,
+			(GInstanceInitFunc) hierarchy_view_init,
+			0
+		};
+
+		type = g_type_register_static (GTK_TYPE_TREE_VIEW, "HierarchyView", &info, 0);
+	}
+	return type;
+}
+
+static void
+hierarchy_view_set_property (GObject *object,
+			     guint param_id,
+			     G_GNUC_UNUSED const GValue *value,
+			     GParamSpec *pspec)
+{
+	HierarchyView *eview;
+	eview = HIERARCHY_VIEW (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+hierarchy_view_get_property (GObject *object,
+			     guint param_id,
+			     G_GNUC_UNUSED GValue *value,
+			     GParamSpec *pspec)
+{
+	HierarchyView *eview;
+	eview = HIERARCHY_VIEW (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static gchar *
+hierarchy_view_to_selection (G_GNUC_UNUSED HierarchyView *eview)
+{
+	/*
+	  GString *string;
+	  gchar *tmp;
+	  string = g_string_new ("OBJ_TYPE=hierarchy");
+	  tmp = gda_rfc1738_encode (eview->priv->schema);
+	  g_string_append_printf (string, ";OBJ_SCHEMA=%s", tmp);
+	  g_free (tmp);
+	  tmp = gda_rfc1738_encode (eview->priv->hierarchy_name);
+	  g_string_append_printf (string, ";OBJ_NAME=%s", tmp);
+	  g_free (tmp);
+	  tmp = gda_rfc1738_encode (eview->priv->hierarchy_short_name);
+	  g_string_append_printf (string, ";OBJ_SHORT_NAME=%s", tmp);
+	  g_free (tmp);
+	  return g_string_free (string, FALSE);
+	*/
+	if (eview->priv->current_dn)
+		return g_strdup (eview->priv->current_dn);
+	else
+		return NULL;
+}
+
+static void
+source_drag_data_get_cb (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkDragContext *context,
+			 GtkSelectionData *selection_data,
+			 guint view, G_GNUC_UNUSED guint time, HierarchyView *eview)
+{
+	switch (view) {
+	case TARGET_KEY_VALUE: {
+		gchar *str;
+		str = hierarchy_view_to_selection (eview);
+		gtk_selection_data_set (selection_data,
+					gtk_selection_data_get_target (selection_data), 8, (guchar*) str,
+					strlen (str));
+		g_free (str);
+		break;
+	}
+	default:
+	case TARGET_PLAIN: {
+		gtk_selection_data_set_text (selection_data, hierarchy_view_get_current_dn (eview, NULL), -1);
+		break;
+	}
+	case TARGET_ROOTWIN:
+		TO_IMPLEMENT; /* dropping on the Root Window => create a file */
+		break;
+	}
+}
+
+static gboolean test_expand_row_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, HierarchyView *eview);
+static void selection_changed_cb (GtkTreeSelection *sel, HierarchyView *eview);
+static gboolean make_dn_hierarchy (const gchar *basedn, const gchar *dn, GArray *in);
+static void go_to_row (HierarchyView *eview, GtkTreePath *path);
+
+enum
+{
+        COLUMN_RDN,
+	COLUMN_ICON,
+	COLUMN_DN,
+	COLUMN_CN,
+        NUM_COLUMNS
+};
+
+static void
+text_cell_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
+		     GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
+{
+	gchar *rdn, *cn;
+	gtk_tree_model_get (tree_model, iter,
+                            COLUMN_RDN, &rdn, COLUMN_CN, &cn, -1);
+	g_object_set ((GObject*) cell, "text", cn && *cn ? cn : rdn, NULL);
+        g_free (cn);
+        g_free (rdn);
+}
+
+/**
+ * hierarchy_view_new
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+hierarchy_view_new (BrowserConnection *bcnc, const gchar *dn)
+{
+	HierarchyView *eview;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	eview = HIERARCHY_VIEW (g_object_new (HIERARCHY_VIEW_TYPE, NULL));
+	eview->priv->bcnc = g_object_ref ((GObject*) bcnc);
+	g_signal_connect (eview, "drag-data-get",
+			  G_CALLBACK (source_drag_data_get_cb), eview);
+
+	GdaTreeManager *mgr;
+	GtkTreeModel *store;
+	GtkCellRenderer *renderer;
+        GtkTreeViewColumn *column;
+	eview->priv->ldap_tree = gda_tree_new ();
+	mgr = mgr_ldap_entries_new (eview->priv->bcnc, NULL);
+	gda_tree_add_manager (eview->priv->ldap_tree, mgr);
+	gda_tree_manager_add_manager (mgr, mgr);
+	gda_tree_update_children (eview->priv->ldap_tree, NULL, NULL);
+	g_object_unref (mgr);
+
+	store = gdaui_tree_store_new (eview->priv->ldap_tree, NUM_COLUMNS,
+				      G_TYPE_STRING, "rdn",
+				      G_TYPE_OBJECT, "icon",
+				      G_TYPE_STRING, "dn",
+				      G_TYPE_STRING, "cn");
+	gtk_tree_view_set_model (GTK_TREE_VIEW (eview), GTK_TREE_MODEL (store));
+
+	eview->priv->ldap_store = GDAUI_TREE_STORE (store);
+	g_object_unref (G_OBJECT (store));
+	g_signal_connect (eview, "test-expand-row",
+			  G_CALLBACK (test_expand_row_cb), eview);
+
+	column = gtk_tree_view_column_new ();
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+        gtk_tree_view_column_pack_start (column, renderer, FALSE);
+        gtk_tree_view_column_add_attribute (column, renderer, "pixbuf", COLUMN_ICON);
+        g_object_set ((GObject*) renderer, "yalign", 0., NULL);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_set_cell_data_func (column, renderer,
+						 (GtkTreeCellDataFunc) text_cell_data_func,
+						 NULL, NULL);
+        //gtk_tree_view_column_add_attribute (column, renderer, "text", COLUMN_RDN);
+
+        gtk_tree_view_append_column (GTK_TREE_VIEW (eview), column);
+	gtk_tree_view_set_expander_column (GTK_TREE_VIEW (eview), column);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (eview), FALSE);
+
+	/* tree selection */
+	GtkTreeSelection *sel;
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (eview));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	g_signal_connect (sel, "changed",
+			  G_CALLBACK (selection_changed_cb), eview);
+
+	if (dn) {
+		const gchar *basedn;
+		GArray *array;
+
+		basedn = browser_connection_ldap_get_base_dn (eview->priv->bcnc);
+		array = g_array_new (TRUE, FALSE, sizeof (gchar*));
+		make_dn_hierarchy (basedn, dn, array);
+		if (array->len > 0) {
+			eview->priv->to_show = array;
+			go_to_row (eview, NULL);
+		}
+		else
+			g_array_free (array, TRUE);
+	}
+
+	return (GtkWidget*) eview;
+}
+
+
+static gboolean
+make_dn_hierarchy (const gchar *basedn, const gchar *dn, GArray *in)
+{
+	if (basedn && !strcmp (basedn, dn))
+		return TRUE;
+
+	gchar **split;
+	gchar *tmp;
+	tmp = g_strdup (dn);
+	g_array_prepend_val (in, tmp);
+	split = gda_ldap_dn_split (dn, FALSE);
+	if (split) {
+		gboolean retval = TRUE;
+		if (split [0] && split [1])
+			retval = make_dn_hierarchy (basedn, split [1], in);
+		g_strfreev (split);
+		return retval;
+	}
+	else
+		return FALSE;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *sel, HierarchyView *eview)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
+		GdaTreeNode *node;
+		const GValue *cvalue;
+		node = gdaui_tree_store_get_node (GDAUI_TREE_STORE (model), &iter);
+		g_assert (node);
+		cvalue = gda_tree_node_get_node_attribute (node, "dn");
+		g_assert (cvalue);
+
+		g_free (eview->priv->current_dn);
+		eview->priv->current_dn = g_value_dup_string (cvalue);
+		g_free (eview->priv->current_cn);
+		eview->priv->current_cn = NULL;
+		cvalue = gda_tree_node_get_node_attribute (node, "cn");
+		if (cvalue)
+			eview->priv->current_cn = g_value_dup_string (cvalue);
+	}
+}
+
+typedef struct {
+	GtkTreeView *tview;
+	GdauiTreeStore *store;
+	GdaTree *tree;
+	GdaTreeNode *node;
+} IdleData;
+
+static void
+idle_data_free (IdleData *data)
+{
+	g_object_unref (data->tview);
+	g_object_unref (data->store);
+	g_object_unref (data->tree);
+	g_object_unref (data->node);
+	g_free (data);
+}
+
+static gboolean ldap_update_tree_part (IdleData *data);
+static gboolean
+test_expand_row_cb (GtkTreeView *tree_view, GtkTreeIter *iter,
+		    G_GNUC_UNUSED GtkTreePath *path, HierarchyView *eview)
+{
+	GdaTreeNode *node;
+	GtkTreeModel *store;
+#ifdef DEBUG_GOTO
+	g_print ("%s (path => %s)\n", __FUNCTION__, gtk_tree_path_to_string (path));
+#endif
+	store = gtk_tree_view_get_model (tree_view);
+        node = gdaui_tree_store_get_node (GDAUI_TREE_STORE (store), iter);
+        if (!node || gda_tree_node_get_child_index (node, 0))
+                return FALSE;
+
+	const GValue *cv;
+	cv = gda_tree_node_get_node_attribute (node,
+					       GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN);
+	if (cv && (G_VALUE_TYPE (cv) == G_TYPE_BOOLEAN) &&
+	    g_value_get_boolean (cv)) {
+		IdleData *data;
+		data = g_new (IdleData, 1);
+		data->tview = g_object_ref (G_OBJECT (tree_view));
+		data->store = g_object_ref (G_OBJECT (store));
+		data->tree = g_object_ref (G_OBJECT (eview->priv->ldap_tree));
+		data->node = g_object_ref (G_OBJECT (node));
+
+		g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc) ldap_update_tree_part,
+				 data, (GDestroyNotify) idle_data_free);
+	}
+	return TRUE;
+}
+
+static gboolean
+ldap_update_tree_part (IdleData *data)
+{
+	gda_tree_update_children (data->tree, data->node, NULL);
+
+	GtkTreeIter iter;
+	if (gdaui_tree_store_get_iter (data->store, &iter, data->node)) {
+		if (gda_tree_node_get_child_index (data->node, 0)) {
+			/* actually expand the row */
+			GtkTreePath *path;
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (data->store), &iter);
+			g_signal_handlers_block_by_func (data->tview,
+							 G_CALLBACK (test_expand_row_cb), data->tree);
+			gtk_tree_view_expand_row (data->tview, path, FALSE);
+			gtk_tree_view_scroll_to_cell (data->tview,
+						      path, NULL, TRUE, 0.5, 0.5);
+			g_signal_handlers_unblock_by_func (data->tview,
+							   G_CALLBACK (test_expand_row_cb), data->tree);
+			gtk_tree_path_free (path);
+		}
+		if (HIERARCHY_VIEW (data->tview)->priv->to_show) {
+			GtkTreePath *path;
+			path = gtk_tree_model_get_path (GTK_TREE_MODEL (data->store), &iter);
+			go_to_row (HIERARCHY_VIEW (data->tview), path);
+			gtk_tree_path_free (path);
+		}
+	}
+
+	return FALSE; /* => remove IDLE call */
+}
+
+static void
+go_to_row (HierarchyView *eview, GtkTreePath *path)
+{
+	GtkTreePath *lpath = NULL;
+	gchar *tofind = NULL;
+	if (path)
+		lpath = gtk_tree_path_copy (path);
+#ifdef DEBUG_GOTO
+	g_print ("%s (starting from path=>%s)\n", __FUNCTION__, path ? gtk_tree_path_to_string (path) : "null");
+#endif
+	while (1) {
+		if (!eview->priv->to_show)
+			goto out;
+		if (eview->priv->to_show->len == 0) {
+			g_array_free (eview->priv->to_show, TRUE);
+			eview->priv->to_show = NULL;
+			goto out;
+		}
+
+		g_free (tofind);
+		tofind = g_array_index (eview->priv->to_show, gchar*, 0);
+		g_array_remove_index (eview->priv->to_show, 0);
+		
+#ifdef DEBUG_GOTO
+		g_print ("tofind: [%s] starting from [%s]\n", tofind, lpath ? gtk_tree_path_to_string (lpath) : "null");
+#endif
+		GtkTreeModel *model;
+		GtkTreeIter iter, parent;
+		gboolean found = FALSE;
+		model = GTK_TREE_MODEL (eview->priv->ldap_store);
+		if ((lpath && gtk_tree_model_get_iter (model, &parent, lpath) &&
+		     gtk_tree_model_iter_children (model, &iter, &parent)) ||
+		    (!lpath && gtk_tree_model_get_iter_first (model, &iter))) {
+			gboolean init = TRUE;
+			for (; init || (!init && gtk_tree_model_iter_next (model, &iter)); init = FALSE) {
+				gchar *dn;
+				gtk_tree_model_get (model, &iter, COLUMN_DN, &dn, -1);
+				if (dn) {
+					if (!strcmp (dn, tofind)) {
+						found = TRUE;
+						g_free (dn);
+						break;
+					}
+					g_free (dn);
+				}
+			}
+		}
+		if (!found) {
+			/* perform the same search but case insensitive */
+			if ((lpath && gtk_tree_model_get_iter (model, &parent, lpath) &&
+			     gtk_tree_model_iter_children (model, &iter, &parent)) ||
+			    (!lpath && gtk_tree_model_get_iter_first (model, &iter))) {
+				gboolean init = TRUE;
+				for (; init || (!init && gtk_tree_model_iter_next (model, &iter)); init = FALSE) {
+					gchar *dn;
+					gtk_tree_model_get (model, &iter, COLUMN_DN, &dn, -1);
+					if (dn) {
+						if (!g_ascii_strcasecmp (dn, tofind)) {
+							found = TRUE;
+							break;
+						}
+						g_free (dn);
+					}
+				}
+			}
+		}
+		if (lpath) {
+			gtk_tree_path_free (lpath);
+			lpath = NULL;
+		}
+
+		if (found) {
+			/* iter is pointing to the requested DN */
+			lpath = gtk_tree_model_get_path (model, &iter);
+			if (eview->priv->to_show->len == 0) {
+				/* no more after => set selection */
+				GtkTreeSelection *sel;
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (eview));
+				gtk_tree_selection_select_path (sel, lpath);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (eview),
+							      lpath, NULL, TRUE, 0.5, 0.5);
+#ifdef DEBUG_GOTO
+				g_print ("SELECTED %s\n", gtk_tree_path_to_string (lpath));
+#endif
+				goto out;
+			}
+			else {
+				/* more to find */
+				if (! gtk_tree_view_row_expanded (GTK_TREE_VIEW (eview), lpath)) {
+					GdaTreeNode *node;
+					node = gdaui_tree_store_get_node (eview->priv->ldap_store, &iter);
+					if (node && gda_tree_node_get_child_index (node, 0))
+						gtk_tree_view_expand_row (GTK_TREE_VIEW (eview), lpath, FALSE);
+					else {
+
+#ifdef DEBUG_GOTO
+						g_print (">>REQUEST ROW EXP for path %s\n", gtk_tree_path_to_string (lpath));
+#endif
+						gtk_tree_view_expand_row (GTK_TREE_VIEW (eview), lpath, FALSE);
+						
+#ifdef DEBUG_GOTO
+						g_print ("<<REQUEST ROW EXP for path %s\n", gtk_tree_path_to_string (lpath));
+#endif
+						goto out;
+					}
+				}
+			}
+		}
+		else {
+			/* DN not found! */
+			browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eview)),
+					      _("Could not find LDAP entry whith DN '%s'"), tofind);
+			goto out;
+		}
+	}
+ out:
+	g_free (tofind);
+	if (lpath)
+		gtk_tree_path_free (lpath);
+}
+
+
+/**
+ * hierarchy_view_get_current_dn:
+ */
+const gchar *
+hierarchy_view_get_current_dn (HierarchyView *eview, const gchar **out_current_cn)
+{
+	g_return_val_if_fail (IS_HIERARCHY_VIEW (eview), NULL);
+	if (out_current_cn)
+		*out_current_cn = eview->priv->current_cn;
+	return eview->priv->current_dn;
+}
+
+/**
+ * hierarchy_view_set_current_dn:
+ */
+void
+hierarchy_view_set_current_dn (HierarchyView *hierarchy_view, const gchar *dn)
+{
+	const gchar *basedn;
+	GArray *array;
+
+	g_return_if_fail (IS_HIERARCHY_VIEW (hierarchy_view));
+	g_return_if_fail (dn && *dn);
+
+	if (hierarchy_view->priv->to_show) {
+		guint i;
+		for (i = 0; i < hierarchy_view->priv->to_show->len; i++) {
+			gchar *tmp;
+			tmp = (gchar*) g_array_index (hierarchy_view->priv->to_show, gchar*, i);
+			g_free (tmp);
+		}
+		g_array_free (hierarchy_view->priv->to_show, TRUE);
+		hierarchy_view->priv->to_show = NULL;
+	}
+
+	basedn = browser_connection_ldap_get_base_dn (hierarchy_view->priv->bcnc);
+	array = g_array_new (TRUE, FALSE, sizeof (gchar*));
+	make_dn_hierarchy (basedn, dn, array);
+	if (array->len > 0) {
+		hierarchy_view->priv->to_show = array;
+		go_to_row (hierarchy_view, NULL);
+	}
+	else
+		g_array_free (array, TRUE);
+}
diff --git a/tools/browser/ldap-browser/hierarchy-view.h b/tools/browser/ldap-browser/hierarchy-view.h
new file mode 100644
index 0000000..8ed357b
--- /dev/null
+++ b/tools/browser/ldap-browser/hierarchy-view.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __HIERARCHY_VIEW_H__
+#define __HIERARCHY_VIEW_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define HIERARCHY_VIEW_TYPE            (hierarchy_view_get_type())
+#define HIERARCHY_VIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, HIERARCHY_VIEW_TYPE, HierarchyView))
+#define HIERARCHY_VIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, HIERARCHY_VIEW_TYPE, HierarchyViewClass))
+#define IS_HIERARCHY_VIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, HIERARCHY_VIEW_TYPE))
+#define IS_HIERARCHY_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), HIERARCHY_VIEW_TYPE))
+
+typedef struct _HierarchyView        HierarchyView;
+typedef struct _HierarchyViewClass   HierarchyViewClass;
+typedef struct _HierarchyViewPrivate HierarchyViewPrivate;
+
+struct _HierarchyView {
+	GtkTreeView           parent;
+	HierarchyViewPrivate *priv;
+};
+
+struct _HierarchyViewClass {
+	GtkTreeViewClass      parent_class;
+};
+
+GType        hierarchy_view_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *hierarchy_view_new            (BrowserConnection *bcnc, const gchar *dn);
+const gchar *hierarchy_view_get_current_dn (HierarchyView *hierarchy_view, const gchar **out_current_cn);
+void         hierarchy_view_set_current_dn (HierarchyView *hierarchy_view, const gchar *dn);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/ldap-browser-perspective.c b/tools/browser/ldap-browser/ldap-browser-perspective.c
new file mode 100644
index 0000000..3d93f7c
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-browser-perspective.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2011 Vivien Malerba
+ *
+ * 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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "ldap-browser-perspective.h"
+#include "../browser-window.h"
+#include "ldap-entries-page.h"
+#include "ldap-classes-page.h"
+#include "ldap-search-page.h"
+#include "../support.h"
+#include "../browser-page.h"
+#include "../browser-favorites.h"
+#include "ldap-favorite-selector.h"
+#include "../browser-stock-icons.h"
+
+/* 
+ * Main static functions 
+ */
+static void ldap_browser_perspective_class_init (LdapBrowserPerspectiveClass *klass);
+static void ldap_browser_perspective_init (LdapBrowserPerspective *stmt);
+static void ldap_browser_perspective_dispose (GObject *object);
+
+/* BrowserPerspective interface */
+static void                 ldap_browser_perspective_perspective_init (BrowserPerspectiveIface *iface);
+static BrowserWindow       *ldap_browser_perspective_get_window (BrowserPerspective *perspective);
+static GtkActionGroup      *ldap_browser_perspective_get_actions_group (BrowserPerspective *perspective);
+static const gchar         *ldap_browser_perspective_get_actions_ui (BrowserPerspective *perspective);
+static void                 ldap_browser_perspective_page_tab_label_change (BrowserPerspective *perspective, BrowserPage *page);
+static void                 ldap_browser_perspective_get_current_customization (BrowserPerspective *perspective,
+										GtkActionGroup **out_agroup,
+										const gchar **out_ui);
+
+/* get a pointer to the parents to be able to call their destructor */
+static GObjectClass  *parent_class = NULL;
+
+struct _LdapBrowserPerspectivePrivate {
+	GtkWidget *notebook;
+	GtkWidget *favorites;
+	gboolean favorites_shown;
+	BrowserWindow *bwin;
+};
+
+GType
+ldap_browser_perspective_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+		static const GTypeInfo info = {
+			sizeof (LdapBrowserPerspectiveClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ldap_browser_perspective_class_init,
+			NULL,
+			NULL,
+			sizeof (LdapBrowserPerspective),
+			0,
+			(GInstanceInitFunc) ldap_browser_perspective_init,
+			0
+		};
+
+		static GInterfaceInfo perspective_info = {
+                        (GInterfaceInitFunc) ldap_browser_perspective_perspective_init,
+			NULL,
+                        NULL
+                };
+		
+		g_static_mutex_lock (&registering);
+		if (type == 0) {
+			type = g_type_register_static (GTK_TYPE_VBOX, "LdapBrowserPerspective", &info, 0);
+			g_type_add_interface_static (type, BROWSER_PERSPECTIVE_TYPE, &perspective_info);
+		}
+		g_static_mutex_unlock (&registering);
+	}
+	return type;
+}
+
+static void
+ldap_browser_perspective_class_init (LdapBrowserPerspectiveClass * klass)
+{
+	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = ldap_browser_perspective_dispose;
+}
+
+static void
+ldap_browser_perspective_perspective_init (BrowserPerspectiveIface *iface)
+{
+	iface->i_get_window = ldap_browser_perspective_get_window;
+	iface->i_get_actions_group = ldap_browser_perspective_get_actions_group;
+	iface->i_get_actions_ui = ldap_browser_perspective_get_actions_ui;
+	iface->i_page_tab_label_change = ldap_browser_perspective_page_tab_label_change;
+	iface->i_get_current_customization = ldap_browser_perspective_get_current_customization;
+}
+
+
+static void
+ldap_browser_perspective_init (LdapBrowserPerspective *perspective)
+{
+	perspective->priv = g_new0 (LdapBrowserPerspectivePrivate, 1);
+	perspective->priv->favorites_shown = TRUE;
+}
+
+static void
+close_button_clicked_cb (G_GNUC_UNUSED GtkWidget *wid, GtkWidget *page_widget)
+{
+	gtk_widget_destroy (page_widget);
+}
+
+static void fav_selection_changed_cb (GtkWidget *widget, gint fav_id, BrowserFavoritesType fav_type,
+				      const gchar *selection, LdapBrowserPerspective *bpers);
+/**
+ * ldap_browser_perspective_new
+ *
+ * Creates new #BrowserPerspective widget which 
+ */
+BrowserPerspective *
+ldap_browser_perspective_new (BrowserWindow *bwin)
+{
+	BrowserConnection *bcnc;
+	BrowserPerspective *bpers;
+	LdapBrowserPerspective *perspective;
+
+	bpers = (BrowserPerspective*) g_object_new (TYPE_LDAP_BROWSER_PERSPECTIVE, NULL);
+	perspective = (LdapBrowserPerspective*) bpers;
+
+	perspective->priv->bwin = bwin;
+
+	/* contents */
+	GtkWidget *paned, *wid, *nb, *button, *tlabel;
+	bcnc = browser_window_get_connection (bwin);
+	paned = gtk_hpaned_new ();
+	wid = ldap_favorite_selector_new (bcnc);
+	g_signal_connect (wid, "selection-changed",
+			  G_CALLBACK (fav_selection_changed_cb), bpers);
+	gtk_paned_add1 (GTK_PANED (paned), wid);
+	gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
+	perspective->priv->favorites = wid;
+
+	nb = gtk_notebook_new ();
+	perspective->priv->notebook = nb;
+	gtk_paned_add2 (GTK_PANED (paned), nb);
+	gtk_notebook_set_scrollable (GTK_NOTEBOOK (nb), TRUE);
+	gtk_notebook_popup_enable (GTK_NOTEBOOK (nb));
+
+	wid = ldap_entries_page_new (bcnc, NULL);
+	tlabel = browser_page_get_tab_label (BROWSER_PAGE (wid), &button);
+        g_signal_connect (button, "clicked",
+                          G_CALLBACK (close_button_clicked_cb), wid);
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (nb), wid, tlabel);
+
+	gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (nb), wid, TRUE);
+	gtk_notebook_set_group (GTK_NOTEBOOK (nb), bcnc + 0x03); /* add 0x01 to differentiate it from others */
+	gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (nb), wid, TRUE);
+
+	gtk_box_pack_start (GTK_BOX (bpers), paned, TRUE, TRUE, 0);
+	gtk_widget_show_all (paned);
+
+	if (!perspective->priv->favorites_shown)
+		gtk_widget_hide (perspective->priv->favorites);
+
+	browser_perspective_declare_notebook (bpers, GTK_NOTEBOOK (perspective->priv->notebook));
+
+	return bpers;
+}
+
+static void
+fav_selection_changed_cb (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gint fav_id,
+			  BrowserFavoritesType fav_type,
+			  const gchar *selection, LdapBrowserPerspective *bpers)
+{
+	if (fav_type == BROWSER_FAVORITES_LDAP_DN) {
+		ldap_browser_perspective_display_ldap_entry (bpers, selection);
+	}
+	if (fav_type == BROWSER_FAVORITES_LDAP_CLASS) {
+		ldap_browser_perspective_display_ldap_class (bpers, selection);
+	}
+#ifdef GDA_DEBUG_NO
+	g_print ("Reacted to selection fav_id=>%d type=>%u, contents=>%s\n", fav_id, fav_type, selection);	
+#endif
+}
+
+static void
+ldap_browser_perspective_dispose (GObject *object)
+{
+	LdapBrowserPerspective *perspective;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (IS_LDAP_BROWSER_PERSPECTIVE (object));
+
+	perspective = LDAP_BROWSER_PERSPECTIVE (object);
+	if (perspective->priv) {
+		browser_perspective_declare_notebook ((BrowserPerspective*) perspective, NULL);
+		g_free (perspective->priv);
+		perspective->priv = NULL;
+	}
+
+	/* parent class */
+	parent_class->dispose (object);
+}
+
+static void
+favorites_toggle_cb (GtkToggleAction *action, BrowserPerspective *bpers)
+{
+	LdapBrowserPerspective *perspective;
+	perspective = LDAP_BROWSER_PERSPECTIVE (bpers);
+	perspective->priv->favorites_shown = gtk_toggle_action_get_active (action);
+	if (perspective->priv->favorites_shown)
+		gtk_widget_show (perspective->priv->favorites);
+	else
+		gtk_widget_hide (perspective->priv->favorites);
+}
+
+static void
+ldab_ldap_entries_page_add_cb (G_GNUC_UNUSED GtkAction *action, BrowserPerspective *bpers)
+{
+        GtkWidget *page, *tlabel, *button;
+        LdapBrowserPerspective *perspective;
+        BrowserConnection *bcnc;
+        gint i;
+
+        perspective = LDAP_BROWSER_PERSPECTIVE (bpers);
+        bcnc = browser_window_get_connection (perspective->priv->bwin);
+
+        page = ldap_entries_page_new (bcnc, NULL);
+        gtk_widget_show (page);
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), &button);
+        g_signal_connect (button, "clicked",
+                          G_CALLBACK (close_button_clicked_cb), page);
+
+        i = gtk_notebook_append_page (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (perspective->priv->notebook), i);
+
+        gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (perspective->priv->notebook), page, TRUE);
+        gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (perspective->priv->notebook), page,
+                                         TRUE);
+
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), NULL);
+        gtk_notebook_set_menu_label (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+
+        gtk_widget_grab_focus (page);
+}
+
+static void
+ldab_ldap_classes_page_add_cb (G_GNUC_UNUSED GtkAction *action, BrowserPerspective *bpers)
+{
+        GtkWidget *page, *tlabel, *button;
+        LdapBrowserPerspective *perspective;
+        BrowserConnection *bcnc;
+        gint i;
+
+        perspective = LDAP_BROWSER_PERSPECTIVE (bpers);
+        bcnc = browser_window_get_connection (perspective->priv->bwin);
+
+        page = ldap_classes_page_new (bcnc, NULL);
+        gtk_widget_show (page);
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), &button);
+        g_signal_connect (button, "clicked",
+                          G_CALLBACK (close_button_clicked_cb), page);
+
+        i = gtk_notebook_append_page (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (perspective->priv->notebook), i);
+
+        gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (perspective->priv->notebook), page, TRUE);
+        gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (perspective->priv->notebook), page,
+                                         TRUE);
+
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), NULL);
+        gtk_notebook_set_menu_label (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+
+        gtk_widget_grab_focus (page);
+}
+
+static void
+ldab_search_add_cb (G_GNUC_UNUSED GtkAction *action, BrowserPerspective *bpers)
+{
+        GtkWidget *page, *tlabel, *button;
+        LdapBrowserPerspective *perspective;
+        BrowserConnection *bcnc;
+        gint i;
+
+        perspective = LDAP_BROWSER_PERSPECTIVE (bpers);
+        bcnc = browser_window_get_connection (perspective->priv->bwin);
+
+	i = gtk_notebook_get_current_page (GTK_NOTEBOOK (perspective->priv->notebook));
+	GtkWidget *child;
+	const gchar *dn = NULL;
+	child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (perspective->priv->notebook), i);
+	if (IS_LDAP_ENTRIES_PAGE (child))
+		dn = ldap_entries_page_get_current_dn (LDAP_ENTRIES_PAGE (child));
+
+        page = ldap_search_page_new (bcnc, dn);
+        gtk_widget_show (page);
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), &button);
+        g_signal_connect (button, "clicked",
+                          G_CALLBACK (close_button_clicked_cb), page);
+
+        i = gtk_notebook_append_page (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+        gtk_notebook_set_current_page (GTK_NOTEBOOK (perspective->priv->notebook), i);
+
+        gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (perspective->priv->notebook), page, TRUE);
+        gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (perspective->priv->notebook), page,
+                                         TRUE);
+
+        tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), NULL);
+        gtk_notebook_set_menu_label (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
+
+        gtk_widget_grab_focus (page);
+}
+
+static const GtkToggleActionEntry ui_toggle_actions [] =
+	{
+		{ "LdapBrowserFavoritesShow", NULL, N_("_Show favorites"), "F9", N_("Show or hide favorites"), G_CALLBACK (favorites_toggle_cb), FALSE }
+	};
+
+static GtkActionEntry ui_actions[] = {
+	{ "LDAP", NULL, N_("_LDAP"), NULL, N_("LDAP"), NULL },
+        { "LdapLdapEntriesPageNew", BROWSER_STOCK_LDAP_ENTRIES, N_("_New LDAP entries browser"), "<control>T", N_("Open a new LDAP entries browser"),
+          G_CALLBACK (ldab_ldap_entries_page_add_cb)},
+        { "LdapLdapClassesPageNew", NULL, N_("_New LDAP classes browser"), "<control>C", N_("Open a new LDAP classes browser"),
+	  G_CALLBACK (ldab_ldap_classes_page_add_cb)},
+        { "LdapSearchNew", GTK_STOCK_FIND, N_("_New LDAP search"), "<control>F", N_("Open a new LDAP search form"),
+	  G_CALLBACK (ldab_search_add_cb)},
+};
+
+static const gchar *ui_actions_info =
+        "<ui>"
+        "  <menubar name='MenuBar'>"
+	"    <menu name='Display' action='Display'>"
+	"      <menuitem name='LdapBrowserFavoritesShow' action='LdapBrowserFavoritesShow'/>"
+        "    </menu>"
+	"    <placeholder name='MenuExtension'>"
+        "      <menu name='LDAP' action='LDAP'>"
+        "        <menuitem name='LdapSearchNew' action= 'LdapSearchNew'/>"
+        "        <menuitem name='LdapLdapEntriesPageNew' action= 'LdapLdapEntriesPageNew'/>"
+        "        <menuitem name='LdapLdapClassesPageNew' action= 'LdapLdapClassesPageNew'/>"
+        "      </menu>"
+        "    </placeholder>"
+        "  </menubar>"
+	"  <toolbar name='ToolBar'>"
+        "    <separator/>"
+        "    <toolitem action='LdapSearchNew'/>"
+        "    <toolitem action='LdapLdapEntriesPageNew'/>"
+        "  </toolbar>"
+        "</ui>";
+
+static GtkActionGroup *
+ldap_browser_perspective_get_actions_group (BrowserPerspective *bpers)
+{
+	GtkActionGroup *agroup;
+	agroup = gtk_action_group_new ("LdapBrowserActions");
+	gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
+
+	gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), bpers);
+	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions, G_N_ELEMENTS (ui_toggle_actions),
+					     bpers);
+	GtkAction *action;
+	action = gtk_action_group_get_action (agroup, "LdapBrowserFavoritesShow");
+	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+				      LDAP_BROWSER_PERSPECTIVE (bpers)->priv->favorites_shown);	
+
+	return agroup;
+}
+
+static const gchar *
+ldap_browser_perspective_get_actions_ui (G_GNUC_UNUSED BrowserPerspective *bpers)
+{
+	return ui_actions_info;
+}
+
+static void
+ldap_browser_perspective_page_tab_label_change (BrowserPerspective *perspective, BrowserPage *page)
+{
+	LdapBrowserPerspective *bpers;
+	GtkWidget *tab_label;
+	GtkWidget *close_btn;
+	
+	bpers = LDAP_BROWSER_PERSPECTIVE (perspective);
+	tab_label = browser_page_get_tab_label (page, &close_btn);
+	if (tab_label) {
+		gtk_notebook_set_tab_label (GTK_NOTEBOOK (bpers->priv->notebook),
+					    GTK_WIDGET (page), tab_label);
+		g_signal_connect (close_btn, "clicked",
+				  G_CALLBACK (close_button_clicked_cb), page);
+		
+		tab_label = browser_page_get_tab_label (page, NULL);
+		gtk_notebook_set_menu_label (GTK_NOTEBOOK (bpers->priv->notebook),
+					     GTK_WIDGET (page), tab_label);
+	}
+}
+
+/**
+ * ldap_browser_perspective_display_ldap_entry
+ *
+ * Display (and create if necessary) a new page for the LDAP entry's properties
+ */
+void
+ldap_browser_perspective_display_ldap_entry (LdapBrowserPerspective *bpers, const gchar *dn)
+{
+	g_return_if_fail (IS_LDAP_BROWSER_PERSPECTIVE (bpers));
+
+	gint ntabs, i;
+	ntabs = gtk_notebook_get_n_pages (GTK_NOTEBOOK (bpers->priv->notebook));
+	i = gtk_notebook_get_current_page (GTK_NOTEBOOK (bpers->priv->notebook));
+	for (; i < ntabs; i++) {
+		GtkWidget *child;
+		child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+		if (IS_LDAP_ENTRIES_PAGE (child)) {
+			ldap_entries_page_set_current_dn (LDAP_ENTRIES_PAGE (child), dn);
+			gtk_notebook_set_current_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+			return;
+		}
+	}
+
+	/* open a new page */
+	GtkWidget *ti;
+	ti = ldap_entries_page_new (browser_window_get_connection (bpers->priv->bwin), dn);
+	if (ti) {
+		GtkWidget *close_btn;
+		GtkWidget *tab_label;
+		gint i;
+		
+		tab_label = browser_page_get_tab_label (BROWSER_PAGE (ti), &close_btn);
+		i = gtk_notebook_append_page (GTK_NOTEBOOK (bpers->priv->notebook), ti, tab_label);
+		g_signal_connect (close_btn, "clicked",
+				  G_CALLBACK (close_button_clicked_cb), ti);
+		
+		gtk_widget_show (ti);
+		
+		tab_label = browser_page_get_tab_label (BROWSER_PAGE (ti), NULL);
+		gtk_notebook_set_menu_label (GTK_NOTEBOOK (bpers->priv->notebook), ti, tab_label);
+		
+		gtk_notebook_set_current_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+		gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (bpers->priv->notebook), ti,
+						  TRUE);
+		gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (bpers->priv->notebook), ti,
+						 TRUE);
+	}
+}
+
+/**
+ * ldap_browser_perspective_display_ldap_class
+ *
+ * Display (and create if necessary) a new page for the LDAP class's properties
+ */
+void
+ldap_browser_perspective_display_ldap_class (LdapBrowserPerspective *bpers, const gchar *classname)
+{
+	g_return_if_fail (IS_LDAP_BROWSER_PERSPECTIVE (bpers));
+
+	gint ntabs, i;
+	ntabs = gtk_notebook_get_n_pages (GTK_NOTEBOOK (bpers->priv->notebook));
+	i = gtk_notebook_get_current_page (GTK_NOTEBOOK (bpers->priv->notebook));
+	for (; i < ntabs; i++) {
+		GtkWidget *child;
+		child = gtk_notebook_get_nth_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+		if (IS_LDAP_CLASSES_PAGE (child)) {
+			ldap_classes_page_set_current_class (LDAP_CLASSES_PAGE (child), classname);
+			gtk_notebook_set_current_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+			return;
+		}
+	}
+
+	/* open a new page */
+	GtkWidget *ti;
+	ti = ldap_classes_page_new (browser_window_get_connection (bpers->priv->bwin), classname);
+	if (ti) {
+		GtkWidget *close_btn;
+		GtkWidget *tab_label;
+		gint i;
+		
+		tab_label = browser_page_get_tab_label (BROWSER_PAGE (ti), &close_btn);
+		i = gtk_notebook_append_page (GTK_NOTEBOOK (bpers->priv->notebook), ti, tab_label);
+		g_signal_connect (close_btn, "clicked",
+				  G_CALLBACK (close_button_clicked_cb), ti);
+		
+		gtk_widget_show (ti);
+		
+		tab_label = browser_page_get_tab_label (BROWSER_PAGE (ti), NULL);
+		gtk_notebook_set_menu_label (GTK_NOTEBOOK (bpers->priv->notebook), ti, tab_label);
+		
+		gtk_notebook_set_current_page (GTK_NOTEBOOK (bpers->priv->notebook), i);
+		gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (bpers->priv->notebook), ti,
+						  TRUE);
+		gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (bpers->priv->notebook), ti,
+						 TRUE);
+	}
+}
+
+static void
+ldap_browser_perspective_get_current_customization (BrowserPerspective *perspective,
+						    GtkActionGroup **out_agroup,
+						    const gchar **out_ui)
+{
+	LdapBrowserPerspective *bpers;
+	GtkWidget *page_contents;
+
+	bpers = LDAP_BROWSER_PERSPECTIVE (perspective);
+	page_contents = gtk_notebook_get_nth_page (GTK_NOTEBOOK (bpers->priv->notebook),
+						   gtk_notebook_get_current_page (GTK_NOTEBOOK (bpers->priv->notebook)));
+	if (IS_BROWSER_PAGE (page_contents)) {
+		*out_agroup = browser_page_get_actions_group (BROWSER_PAGE (page_contents));
+		*out_ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
+	}
+}
+
+static BrowserWindow *
+ldap_browser_perspective_get_window (BrowserPerspective *perspective)
+{
+	LdapBrowserPerspective *bpers;
+	bpers = LDAP_BROWSER_PERSPECTIVE (perspective);
+	return bpers->priv->bwin;
+}
diff --git a/tools/browser/ldap-browser/ldap-browser-perspective.h b/tools/browser/ldap-browser/ldap-browser-perspective.h
new file mode 100644
index 0000000..5d86097
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-browser-perspective.h
@@ -0,0 +1,69 @@
+/* 
+ * Copyright (C) 2011 Vivien Malerba
+ *
+ * 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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __LDAP_BROWSER_PERSPECTIVE_H_
+#define __LDAP_BROWSER_PERSPECTIVE_H_
+
+#include <glib-object.h>
+#include "../browser-perspective.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_LDAP_BROWSER_PERSPECTIVE          (ldap_browser_perspective_get_type())
+#define LDAP_BROWSER_PERSPECTIVE(obj)          G_TYPE_CHECK_INSTANCE_CAST (obj, ldap_browser_perspective_get_type(), LdapBrowserPerspective)
+#define LDAP_BROWSER_PERSPECTIVE_CLASS(klass)  G_TYPE_CHECK_CLASS_CAST (klass, ldap_browser_perspective_get_type (), LdapBrowserPerspectiveClass)
+#define IS_LDAP_BROWSER_PERSPECTIVE(obj)       G_TYPE_CHECK_INSTANCE_TYPE (obj, ldap_browser_perspective_get_type ())
+
+typedef struct _LdapBrowserPerspective LdapBrowserPerspective;
+typedef struct _LdapBrowserPerspectiveClass LdapBrowserPerspectiveClass;
+typedef struct _LdapBrowserPerspectivePrivate LdapBrowserPerspectivePrivate;
+
+/* struct for the object's data */
+struct _LdapBrowserPerspective
+{
+	GtkVBox              object;
+	LdapBrowserPerspectivePrivate *priv;
+};
+
+/* struct for the object's class */
+struct _LdapBrowserPerspectiveClass
+{
+	GtkVBoxClass         parent_class;
+};
+
+/**
+ * SECTION:ldap-browser-perspective
+ * @short_description: Perspective to analyse the database's ldap
+ * @title: Ldap Browser perspective
+ * @stability: Stable
+ * @see_also:
+ */
+
+GType                ldap_browser_perspective_get_type               (void) G_GNUC_CONST;
+BrowserPerspective  *ldap_browser_perspective_new                    (BrowserWindow *bwin);
+
+void                 ldap_browser_perspective_display_ldap_entry     (LdapBrowserPerspective *bpers,
+								      const gchar *dn);
+void                 ldap_browser_perspective_display_ldap_class     (LdapBrowserPerspective *bpers,
+								      const gchar *classname);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/ldap-classes-page.c b/tools/browser/ldap-browser/ldap-classes-page.c
new file mode 100644
index 0000000..4b677cf
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-classes-page.c
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "ldap-classes-page.h"
+#include "classes-view.h"
+#include "class-properties.h"
+#include "../dnd.h"
+#include "../support.h"
+#include "../cc-gray-bar.h"
+#include "../browser-page.h"
+#include "../browser-stock-icons.h"
+#include "../browser-window.h"
+#include "../browser-connection.h"
+#include <virtual/gda-ldap-connection.h>
+#include "mgr-ldap-classes.h"
+#include <libgda-ui/gdaui-tree-store.h>
+
+typedef struct {
+	gchar *classname;
+	GtkTreeRowReference *reference;
+} HistoryItem;
+
+static void
+history_item_free (HistoryItem *item)
+{
+	g_free (item->classname);
+	gtk_tree_row_reference_free (item->reference);
+	g_free (item);
+}
+
+struct _LdapClassesPagePrivate {
+	BrowserConnection *bcnc;
+
+	GtkWidget *classes_view;
+	GtkWidget *entry_props;
+
+	GtkActionGroup *agroup;
+	GArray *history_items; /* array of @HistoryItem */
+	guint history_max_len; /* max allowed length of @history_items */
+	gint current_hitem; /* index in @history_items, or -1 */
+	gboolean add_hist_item;
+};
+
+static void ldap_classes_page_class_init (LdapClassesPageClass *klass);
+static void ldap_classes_page_init       (LdapClassesPage *ebrowser, LdapClassesPageClass *klass);
+static void ldap_classes_page_dispose   (GObject *object);
+static void ldap_classes_page_set_property (GObject *object,
+					  guint param_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void ldap_classes_page_get_property (GObject *object,
+					  guint param_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+/* BrowserPage interface */
+static void                 ldap_classes_page_page_init (BrowserPageIface *iface);
+static GtkActionGroup      *ldap_classes_page_page_get_actions_group (BrowserPage *page);
+static const gchar         *ldap_classes_page_page_get_actions_ui (BrowserPage *page);
+static GtkWidget           *ldap_classes_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
+
+
+/* properties */
+enum {
+        PROP_0,
+};
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * LdapClassesPage class implementation
+ */
+
+static void
+ldap_classes_page_class_init (LdapClassesPageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Properties */
+        object_class->set_property = ldap_classes_page_set_property;
+        object_class->get_property = ldap_classes_page_get_property;
+
+	object_class->dispose = ldap_classes_page_dispose;
+}
+
+static void
+ldap_classes_page_page_init (BrowserPageIface *iface)
+{
+	iface->i_get_actions_group = ldap_classes_page_page_get_actions_group;
+	iface->i_get_actions_ui = ldap_classes_page_page_get_actions_ui;
+	iface->i_get_tab_label = ldap_classes_page_page_get_tab_label;
+}
+
+static void
+ldap_classes_page_init (LdapClassesPage *ebrowser, G_GNUC_UNUSED LdapClassesPageClass *klass)
+{
+	ebrowser->priv = g_new0 (LdapClassesPagePrivate, 1);
+	ebrowser->priv->history_items = g_array_new (FALSE, FALSE, sizeof (HistoryItem*));
+	ebrowser->priv->history_max_len = 20;
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static void
+ldap_classes_page_dispose (GObject *object)
+{
+	LdapClassesPage *ebrowser = (LdapClassesPage *) object;
+
+	/* free memory */
+	if (ebrowser->priv) {
+		if (ebrowser->priv->bcnc)
+			g_object_unref (ebrowser->priv->bcnc);
+		if (ebrowser->priv->agroup)
+			g_object_unref (ebrowser->priv->agroup);
+		if (ebrowser->priv->history_items) {
+			guint i;
+			for (i = 0; i < ebrowser->priv->history_items->len; i++) {
+				HistoryItem *hitem = g_array_index (ebrowser->priv->history_items,
+								    HistoryItem*, i);
+				history_item_free (hitem);
+			}
+			g_array_free (ebrowser->priv->history_items, TRUE);
+		}
+		g_free (ebrowser->priv);
+		ebrowser->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+ldap_classes_page_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (LdapClassesPageClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ldap_classes_page_class_init,
+			NULL,
+			NULL,
+			sizeof (LdapClassesPage),
+			0,
+			(GInstanceInitFunc) ldap_classes_page_init,
+			0
+		};
+
+		static GInterfaceInfo page_info = {
+                        (GInterfaceInitFunc) ldap_classes_page_page_init,
+			NULL,
+                        NULL
+                };
+
+		type = g_type_register_static (GTK_TYPE_VBOX, "LdapClassesPage", &info, 0);
+		g_type_add_interface_static (type, BROWSER_PAGE_TYPE, &page_info);
+	}
+	return type;
+}
+
+static void
+ldap_classes_page_set_property (GObject *object,
+			      guint param_id,
+			      G_GNUC_UNUSED const GValue *value,
+			      GParamSpec *pspec)
+{
+	LdapClassesPage *ebrowser;
+	ebrowser = LDAP_CLASSES_PAGE (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+ldap_classes_page_get_property (GObject *object,
+			      guint param_id,
+			      G_GNUC_UNUSED GValue *value,
+			      GParamSpec *pspec)
+{
+	LdapClassesPage *ebrowser;
+	ebrowser = LDAP_CLASSES_PAGE (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static gchar *
+ldap_classes_page_to_selection (G_GNUC_UNUSED LdapClassesPage *ebrowser)
+{
+	const gchar *current_classname;
+	current_classname = classes_view_get_current_class (CLASSES_VIEW (ebrowser->priv->classes_view));
+	if (current_classname)
+		return g_strdup (current_classname);
+	else
+		return NULL;
+}
+
+static void
+source_drag_data_get_cb (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkDragContext *context,
+			 GtkSelectionData *selection_data,
+			 guint browser, G_GNUC_UNUSED guint time, LdapClassesPage *ebrowser)
+{
+	switch (browser) {
+	case TARGET_KEY_VALUE: {
+		gchar *str;
+		str = ldap_classes_page_to_selection (ebrowser);
+		gtk_selection_data_set (selection_data,
+					gtk_selection_data_get_target (selection_data), 8, (guchar*) str,
+					strlen (str));
+		g_free (str);
+		break;
+	}
+	default:
+	case TARGET_PLAIN: {
+		gtk_selection_data_set_text (selection_data, ldap_classes_page_get_current_class (ebrowser), -1);
+		break;
+	}
+	case TARGET_ROOTWIN:
+		TO_IMPLEMENT; /* dropping on the Root Window => create a file */
+		break;
+	}
+}
+
+static void
+update_history_actions (LdapClassesPage *ebrowser)
+{
+	if (!ebrowser->priv->agroup)
+		return;
+
+	gboolean is_first = TRUE;
+	gboolean is_last = TRUE;
+	const gchar *current_classname;
+	ebrowser->priv->current_hitem = -1;
+	current_classname = ldap_classes_page_get_current_class (ebrowser);
+	if (current_classname) {
+		guint i;
+		for (i = 0; i < ebrowser->priv->history_items->len; i++) {
+			HistoryItem *hitem;
+			hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, i);
+			if (!strcmp (hitem->classname, current_classname)) {
+				if (i != 0)
+					is_first = FALSE;
+				if (i+1 < ebrowser->priv->history_items->len)
+					is_last = FALSE;
+				ebrowser->priv->current_hitem = (gint) i;
+				break;
+			}
+		}
+	}
+
+	GtkAction *action;
+	action = gtk_action_group_get_action (ebrowser->priv->agroup, "DnBack");
+	gtk_action_set_sensitive (action, !is_first);
+	action = gtk_action_group_get_action (ebrowser->priv->agroup, "DnForward");
+	gtk_action_set_sensitive (action, !is_last);
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *sel, LdapClassesPage *ebrowser)
+{
+	const gchar *current_classname;
+	current_classname = classes_view_get_current_class (CLASSES_VIEW (ebrowser->priv->classes_view));
+	class_properties_set_class (CLASS_PROPERTIES (ebrowser->priv->entry_props), current_classname);
+
+	if (!current_classname)
+		return;
+
+	if (ebrowser->priv->agroup) {
+		GtkAction *action;
+		action = gtk_action_group_get_action (ebrowser->priv->agroup, "AddToFav");
+		current_classname = ldap_classes_page_get_current_class (ebrowser);
+		gtk_action_set_sensitive (action, (current_classname && *current_classname) ? TRUE : FALSE);
+	}
+
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	if (ebrowser->priv->add_hist_item && gtk_tree_selection_get_selected (sel, &model, &iter)) {
+		GtkTreePath *path;
+		HistoryItem *hitem;
+		hitem = g_new (HistoryItem, 1);
+		hitem->classname = g_strdup (current_classname);
+		
+		path = gtk_tree_model_get_path (model, &iter);
+		if (path) {
+			hitem->reference = gtk_tree_row_reference_new (model, path);
+			gtk_tree_path_free (path);
+		}
+		else
+			hitem->reference = NULL;
+
+		if (ebrowser->priv->current_hitem >= 0) {
+			guint i;
+			for (i = (guint) ebrowser->priv->current_hitem + 1;
+			     i < ebrowser->priv->history_items->len; ) {
+				HistoryItem *tmphitem;
+				tmphitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, i);
+				history_item_free (tmphitem);
+				g_array_remove_index (ebrowser->priv->history_items, i);
+			}
+		}
+		g_array_append_val (ebrowser->priv->history_items, hitem);
+		if (ebrowser->priv->history_items->len > ebrowser->priv->history_max_len) {
+			hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, 0);
+			history_item_free (hitem);
+			g_array_remove_index (ebrowser->priv->history_items, 0);
+		}
+		ebrowser->priv->current_hitem = ebrowser->priv->history_items->len -1;
+	}
+	update_history_actions (ebrowser);
+}
+
+static void
+open_classname_requested_cb (ClassProperties *eprop, const gchar *classname, LdapClassesPage *ebrowser)
+{
+	ldap_classes_page_set_current_class (ebrowser, classname);
+}
+
+/**
+ * ldap_classes_page_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+ldap_classes_page_new (BrowserConnection *bcnc, const gchar *classname)
+{
+	LdapClassesPage *ebrowser;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	ebrowser = LDAP_CLASSES_PAGE (g_object_new (LDAP_CLASSES_PAGE_TYPE, NULL));
+	ebrowser->priv->bcnc = g_object_ref ((GObject*) bcnc);
+
+	/* header */
+        GtkWidget *label;
+	gchar *str;
+
+	str = g_strdup_printf ("<b>%s</b>", _("LDAP classes browser"));
+	label = cc_gray_bar_new (str);
+	g_free (str);
+        gtk_box_pack_start (GTK_BOX (ebrowser), label, FALSE, FALSE, 0);
+        gtk_widget_show (label);
+	g_signal_connect (label, "drag-data-get",
+			  G_CALLBACK (source_drag_data_get_cb), ebrowser);
+
+	/* VPaned widget */
+	GtkWidget *hp;
+	hp = gtk_hpaned_new ();
+	gtk_box_pack_start (GTK_BOX (ebrowser), hp, TRUE, TRUE, 0);
+
+	/* tree */
+	GtkWidget *vbox, *hview, *sw;
+	gfloat yalign;
+
+	vbox = gtk_vbox_new (FALSE, FALSE);
+	gtk_paned_add1 (GTK_PANED (hp), vbox);
+	
+	str = g_strdup_printf ("<b>%s:</b>", _("LDAP classes"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), str);
+	g_free (str);
+        gtk_misc_get_alignment (GTK_MISC (label), NULL, &yalign);
+        gtk_misc_set_alignment (GTK_MISC (label), 0., yalign);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+	hview = classes_view_new (bcnc, NULL);
+	ebrowser->priv->classes_view = hview;
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
+					GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (sw), hview);
+	gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
+
+	/* tree selection */
+	GtkTreeSelection *sel;
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->classes_view));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	g_signal_connect (sel, "changed",
+			  G_CALLBACK (selection_changed_cb), ebrowser);
+
+	/* details */
+	vbox = gtk_vbox_new (FALSE, FALSE);
+	gtk_paned_add2 (GTK_PANED (hp), vbox);
+
+	str = g_strdup_printf ("<b>%s:</b>", _("LDAP class's properties"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), str);
+	g_free (str);
+        gtk_misc_get_alignment (GTK_MISC (label), NULL, &yalign);
+        gtk_misc_set_alignment (GTK_MISC (label), 0., yalign);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+	GtkWidget *props;
+	props = class_properties_new (bcnc);
+	gtk_box_pack_start (GTK_BOX (vbox), props, TRUE, TRUE, 0);
+	ebrowser->priv->entry_props = props;
+	g_signal_connect (props, "open-class",
+			  G_CALLBACK (open_classname_requested_cb), ebrowser);
+
+	gtk_paned_set_position (GTK_PANED (hp), 250);
+
+	gtk_widget_show_all (hp);
+
+	if (classname)
+		classes_view_set_current_class (CLASSES_VIEW (ebrowser->priv->classes_view), classname);
+
+	return (GtkWidget*) ebrowser;
+}
+
+/**
+ * ldap_classes_page_get_current_class:
+ */
+const gchar *
+ldap_classes_page_get_current_class (LdapClassesPage *ldap_classes_page)
+{
+	g_return_val_if_fail (IS_LDAP_CLASSES_PAGE (ldap_classes_page), NULL);
+	return classes_view_get_current_class (CLASSES_VIEW (ldap_classes_page->priv->classes_view));
+}
+
+/**
+ * ldap_classes_page_set_current_class:
+ */
+void
+ldap_classes_page_set_current_class (LdapClassesPage *ldap_classes_page, const gchar *classname)
+{
+	g_return_if_fail (IS_LDAP_CLASSES_PAGE (ldap_classes_page));
+	classes_view_set_current_class (CLASSES_VIEW (ldap_classes_page->priv->classes_view), classname);
+}
+
+
+/*
+ * UI actions
+ */
+static void
+action_add_to_fav_cb (G_GNUC_UNUSED GtkAction *action, LdapClassesPage *ebrowser)
+{
+	BrowserFavorites *bfav;
+        BrowserFavoritesAttributes fav;
+        GError *error = NULL;
+	const gchar *tmp;
+
+	tmp = classes_view_get_current_class (CLASSES_VIEW (ebrowser->priv->classes_view));
+        memset (&fav, 0, sizeof (BrowserFavoritesAttributes));
+        fav.id = -1;
+        fav.type = BROWSER_FAVORITES_LDAP_CLASS;
+        fav.name = ldap_classes_page_to_selection (ebrowser);
+        fav.descr = NULL;
+        fav.contents = ldap_classes_page_to_selection (ebrowser);
+
+        bfav = browser_connection_get_favorites (ebrowser->priv->bcnc);
+        if (! browser_favorites_add (bfav, 0, &fav, ORDER_KEY_LDAP, G_MAXINT, &error)) {
+                browser_show_error ((GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) ebrowser),
+                                    _("Could not add favorite: %s"),
+                                    error && error->message ? error->message : _("No detail"));
+                if (error)
+                        g_error_free (error);
+        }
+	g_free (fav.contents);
+}
+
+static void
+action_class_back_cb (G_GNUC_UNUSED GtkAction *action, LdapClassesPage *ebrowser)
+{
+	ebrowser->priv->add_hist_item = FALSE;
+	if (ebrowser->priv->current_hitem > 0) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*,
+				       ebrowser->priv->current_hitem - 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->classes_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ebrowser->priv->classes_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			classes_view_set_current_class (CLASSES_VIEW (ebrowser->priv->classes_view),
+							hitem->classname);
+	}
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static void
+action_class_forward_cb (G_GNUC_UNUSED GtkAction *action, LdapClassesPage *ebrowser)
+{
+	ebrowser->priv->add_hist_item = FALSE;
+	if ((ebrowser->priv->current_hitem >=0) &&
+	    ((guint) ebrowser->priv->current_hitem < ebrowser->priv->history_items->len)) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*,
+				       ebrowser->priv->current_hitem + 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->classes_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ebrowser->priv->classes_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			classes_view_set_current_class (CLASSES_VIEW (ebrowser->priv->classes_view),
+							hitem->classname);
+	}
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static GtkActionEntry ui_actions[] = {
+	{ "LDAP", NULL, N_("_LDAP"), NULL, N_("LDAP"), NULL },
+	{ "AddToFav", STOCK_ADD_BOOKMARK, N_("Add to _Favorites"), NULL, N_("Add class to favorites"),
+	  G_CALLBACK (action_add_to_fav_cb)},
+	{ "DnBack", GTK_STOCK_GO_BACK, N_("Previous class"), NULL, N_("Move back to previous LDAP class"),
+	  G_CALLBACK (action_class_back_cb)},
+	{ "DnForward", GTK_STOCK_GO_FORWARD, N_("Next class"), NULL, N_("Move to next LDAP class"),
+	  G_CALLBACK (action_class_forward_cb)},
+};
+static const gchar *ui_actions_browser =
+	"<ui>"
+	"  <menubar name='MenuBar'>"
+	"    <placeholder name='MenuExtension'>"
+        "      <menu name='LDAP' action='LDAP'>"
+        "        <menuitem name='AddToFav' action= 'AddToFav'/>"
+	"        <separator/>"
+        "        <menuitem name='DnBack' action= 'DnBack'/>"
+        "        <menuitem name='DnForward' action= 'DnForward'/>"
+        "      </menu>"
+        "    </placeholder>"
+	"  </menubar>"
+	"  <toolbar name='ToolBar'>"
+	"    <separator/>"
+	"    <toolitem action='AddToFav'/>"
+	"    <toolitem action='DnBack'/>"
+	"    <toolitem action='DnForward'/>"
+	"  </toolbar>"
+	"</ui>";
+
+static GtkActionGroup *
+ldap_classes_page_page_get_actions_group (BrowserPage *page)
+{
+	LdapClassesPage *ebrowser;
+
+	ebrowser = LDAP_CLASSES_PAGE (page);
+	if (! ebrowser->priv->agroup) {
+		GtkActionGroup *agroup;
+		agroup = gtk_action_group_new ("LdapLdapClassesPageActions");
+		gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
+		gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), page);
+		ebrowser->priv->agroup = agroup;
+
+		GtkAction *action;
+		const gchar *current_classname;
+		action = gtk_action_group_get_action (agroup, "AddToFav");
+		current_classname = ldap_classes_page_get_current_class (ebrowser);
+		gtk_action_set_sensitive (action, (current_classname && *current_classname) ? TRUE : FALSE);
+
+		update_history_actions (ebrowser);
+	}
+	
+	return g_object_ref (ebrowser->priv->agroup);
+}
+
+static const gchar *
+ldap_classes_page_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
+{
+	return ui_actions_browser;
+}
+
+static GtkWidget *
+ldap_classes_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
+{
+	LdapClassesPage *ebrowser;
+	const gchar *tab_name;
+	GdkPixbuf *classes_pixbuf;
+
+	ebrowser = LDAP_CLASSES_PAGE (page);
+	classes_pixbuf = browser_get_pixbuf_icon (BROWSER_ICON_LDAP_CLASS_STRUCTURAL);
+	tab_name = _("LDAP classes");
+	return browser_make_tab_label_with_pixbuf (tab_name,
+						   classes_pixbuf,
+						   out_close_button ? TRUE : FALSE, out_close_button);
+}
diff --git a/tools/browser/ldap-browser/ldap-classes-page.h b/tools/browser/ldap-browser/ldap-classes-page.h
new file mode 100644
index 0000000..59c2bd9
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-classes-page.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LDAP_CLASSES_PAGE_H__
+#define __LDAP_CLASSES_PAGE_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define LDAP_CLASSES_PAGE_TYPE            (ldap_classes_page_get_type())
+#define LDAP_CLASSES_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, LDAP_CLASSES_PAGE_TYPE, LdapClassesPage))
+#define LDAP_CLASSES_PAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, LDAP_CLASSES_PAGE_TYPE, LdapClassesPageClass))
+#define IS_LDAP_CLASSES_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, LDAP_CLASSES_PAGE_TYPE))
+#define IS_LDAP_CLASSES_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LDAP_CLASSES_PAGE_TYPE))
+
+typedef struct _LdapClassesPage        LdapClassesPage;
+typedef struct _LdapClassesPageClass   LdapClassesPageClass;
+typedef struct _LdapClassesPagePrivate LdapClassesPagePrivate;
+
+struct _LdapClassesPage {
+	GtkVBox                parent;
+	LdapClassesPagePrivate *priv;
+};
+
+struct _LdapClassesPageClass {
+	GtkVBoxClass           parent_class;
+};
+
+GType        ldap_classes_page_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *ldap_classes_page_new            (BrowserConnection *bcnc, const gchar *dn);
+const gchar *ldap_classes_page_get_current_class (LdapClassesPage *ldap_classes_page);
+void         ldap_classes_page_set_current_class (LdapClassesPage *ldap_classes_page, const gchar *classname);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/ldap-entries-page.c b/tools/browser/ldap-browser/ldap-entries-page.c
new file mode 100644
index 0000000..42cc7c8
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-entries-page.c
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "ldap-entries-page.h"
+#include "hierarchy-view.h"
+#include "entry-properties.h"
+#include "../dnd.h"
+#include "../support.h"
+#include "../cc-gray-bar.h"
+#include "../browser-page.h"
+#include "../browser-stock-icons.h"
+#include "../browser-window.h"
+#include "../browser-connection.h"
+#include <virtual/gda-ldap-connection.h>
+#include "mgr-ldap-entries.h"
+#include <libgda-ui/gdaui-tree-store.h>
+#include "ldap-browser-perspective.h"
+
+typedef struct {
+	gchar *dn;
+	GtkTreeRowReference *reference;
+} HistoryItem;
+
+static void
+history_item_free (HistoryItem *item)
+{
+	g_free (item->dn);
+	gtk_tree_row_reference_free (item->reference);
+	g_free (item);
+}
+
+struct _LdapEntriesPagePrivate {
+	BrowserConnection *bcnc;
+
+	GtkWidget *entries_view;
+	GtkWidget *entry_props;
+
+	GtkActionGroup *agroup;
+	GArray *history_items; /* array of @HistoryItem */
+	guint history_max_len; /* max allowed length of @history_items */
+	gint current_hitem; /* index in @history_items, or -1 */
+	gboolean add_hist_item;
+};
+
+static void ldap_entries_page_class_init (LdapEntriesPageClass *klass);
+static void ldap_entries_page_init       (LdapEntriesPage *ebrowser, LdapEntriesPageClass *klass);
+static void ldap_entries_page_dispose   (GObject *object);
+static void ldap_entries_page_set_property (GObject *object,
+					  guint param_id,
+					  const GValue *value,
+					  GParamSpec *pspec);
+static void ldap_entries_page_get_property (GObject *object,
+					  guint param_id,
+					  GValue *value,
+					  GParamSpec *pspec);
+/* BrowserPage interface */
+static void                 ldap_entries_page_page_init (BrowserPageIface *iface);
+static GtkActionGroup      *ldap_entries_page_page_get_actions_group (BrowserPage *page);
+static const gchar         *ldap_entries_page_page_get_actions_ui (BrowserPage *page);
+static GtkWidget           *ldap_entries_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
+
+
+/* properties */
+enum {
+        PROP_0,
+};
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * LdapEntriesPage class implementation
+ */
+
+static void
+ldap_entries_page_class_init (LdapEntriesPageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Properties */
+        object_class->set_property = ldap_entries_page_set_property;
+        object_class->get_property = ldap_entries_page_get_property;
+
+	object_class->dispose = ldap_entries_page_dispose;
+}
+
+static void
+ldap_entries_page_page_init (BrowserPageIface *iface)
+{
+	iface->i_get_actions_group = ldap_entries_page_page_get_actions_group;
+	iface->i_get_actions_ui = ldap_entries_page_page_get_actions_ui;
+	iface->i_get_tab_label = ldap_entries_page_page_get_tab_label;
+}
+
+static void
+ldap_entries_page_init (LdapEntriesPage *ebrowser, G_GNUC_UNUSED LdapEntriesPageClass *klass)
+{
+	ebrowser->priv = g_new0 (LdapEntriesPagePrivate, 1);
+	ebrowser->priv->history_items = g_array_new (FALSE, FALSE, sizeof (HistoryItem*));
+	ebrowser->priv->history_max_len = 20;
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static void
+ldap_entries_page_dispose (GObject *object)
+{
+	LdapEntriesPage *ebrowser = (LdapEntriesPage *) object;
+
+	/* free memory */
+	if (ebrowser->priv) {
+		if (ebrowser->priv->bcnc)
+			g_object_unref (ebrowser->priv->bcnc);
+		if (ebrowser->priv->agroup)
+			g_object_unref (ebrowser->priv->agroup);
+		if (ebrowser->priv->history_items) {
+			guint i;
+			for (i = 0; i < ebrowser->priv->history_items->len; i++) {
+				HistoryItem *hitem = g_array_index (ebrowser->priv->history_items,
+								    HistoryItem*, i);
+				history_item_free (hitem);
+			}
+			g_array_free (ebrowser->priv->history_items, TRUE);
+		}
+		g_free (ebrowser->priv);
+		ebrowser->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+ldap_entries_page_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (LdapEntriesPageClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ldap_entries_page_class_init,
+			NULL,
+			NULL,
+			sizeof (LdapEntriesPage),
+			0,
+			(GInstanceInitFunc) ldap_entries_page_init,
+			0
+		};
+
+		static GInterfaceInfo page_info = {
+                        (GInterfaceInitFunc) ldap_entries_page_page_init,
+			NULL,
+                        NULL
+                };
+
+		type = g_type_register_static (GTK_TYPE_VBOX, "LdapEntriesPage", &info, 0);
+		g_type_add_interface_static (type, BROWSER_PAGE_TYPE, &page_info);
+	}
+	return type;
+}
+
+static void
+ldap_entries_page_set_property (GObject *object,
+			      guint param_id,
+			      G_GNUC_UNUSED const GValue *value,
+			      GParamSpec *pspec)
+{
+	LdapEntriesPage *ebrowser;
+	ebrowser = LDAP_ENTRIES_PAGE (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static void
+ldap_entries_page_get_property (GObject *object,
+			      guint param_id,
+			      G_GNUC_UNUSED GValue *value,
+			      GParamSpec *pspec)
+{
+	LdapEntriesPage *ebrowser;
+	ebrowser = LDAP_ENTRIES_PAGE (object);
+	switch (param_id) {
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+		break;
+	}
+}
+
+static gchar *
+ldap_entries_page_to_selection (G_GNUC_UNUSED LdapEntriesPage *ebrowser)
+{
+	const gchar *current_dn;
+	current_dn = hierarchy_view_get_current_dn (HIERARCHY_VIEW (ebrowser->priv->entries_view), NULL);
+	if (current_dn)
+		return g_strdup (current_dn);
+	else
+		return NULL;
+}
+
+static void
+source_drag_data_get_cb (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkDragContext *context,
+			 GtkSelectionData *selection_data,
+			 guint browser, G_GNUC_UNUSED guint time, LdapEntriesPage *ebrowser)
+{
+	switch (browser) {
+	case TARGET_KEY_VALUE: {
+		gchar *str;
+		str = ldap_entries_page_to_selection (ebrowser);
+		gtk_selection_data_set (selection_data,
+					gtk_selection_data_get_target (selection_data), 8, (guchar*) str,
+					strlen (str));
+		g_free (str);
+		break;
+	}
+	default:
+	case TARGET_PLAIN: {
+		gtk_selection_data_set_text (selection_data, ldap_entries_page_get_current_dn (ebrowser), -1);
+		break;
+	}
+	case TARGET_ROOTWIN:
+		TO_IMPLEMENT; /* dropping on the Root Window => create a file */
+		break;
+	}
+}
+
+static void
+update_history_actions (LdapEntriesPage *ebrowser)
+{
+	if (!ebrowser->priv->agroup)
+		return;
+
+	gboolean is_first = TRUE;
+	gboolean is_last = TRUE;
+	const gchar *current_dn;
+	ebrowser->priv->current_hitem = -1;
+	current_dn = ldap_entries_page_get_current_dn (ebrowser);
+	if (current_dn) {
+		guint i;
+		for (i = 0; i < ebrowser->priv->history_items->len; i++) {
+			HistoryItem *hitem;
+			hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, i);
+			if (!strcmp (hitem->dn, current_dn)) {
+				if (i != 0)
+					is_first = FALSE;
+				if (i+1 < ebrowser->priv->history_items->len)
+					is_last = FALSE;
+				ebrowser->priv->current_hitem = (gint) i;
+				break;
+			}
+		}
+	}
+
+	GtkAction *action;
+	action = gtk_action_group_get_action (ebrowser->priv->agroup, "DnBack");
+	gtk_action_set_sensitive (action, !is_first);
+	action = gtk_action_group_get_action (ebrowser->priv->agroup, "DnForward");
+	gtk_action_set_sensitive (action, !is_last);
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *sel, LdapEntriesPage *ebrowser)
+{
+	const gchar *current_dn;
+	current_dn = hierarchy_view_get_current_dn (HIERARCHY_VIEW (ebrowser->priv->entries_view), NULL);
+	entry_properties_set_dn (ENTRY_PROPERTIES (ebrowser->priv->entry_props), current_dn);
+
+	if (ebrowser->priv->agroup) {
+		GtkAction *action;
+		action = gtk_action_group_get_action (ebrowser->priv->agroup, "AddToFav");
+		current_dn = ldap_entries_page_get_current_dn (ebrowser);
+		gtk_action_set_sensitive (action, (current_dn && *current_dn) ? TRUE : FALSE);
+	}
+
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	if (ebrowser->priv->add_hist_item && gtk_tree_selection_get_selected (sel, &model, &iter)) {
+		GtkTreePath *path;
+		HistoryItem *hitem;
+		hitem = g_new (HistoryItem, 1);
+		hitem->dn = g_strdup (current_dn);
+		
+		path = gtk_tree_model_get_path (model, &iter);
+		if (path) {
+			hitem->reference = gtk_tree_row_reference_new (model, path);
+			gtk_tree_path_free (path);
+		}
+		else
+			hitem->reference = NULL;
+
+		if (ebrowser->priv->current_hitem >= 0) {
+			guint i;
+			for (i = (guint) ebrowser->priv->current_hitem + 1;
+			     i < ebrowser->priv->history_items->len; ) {
+				HistoryItem *tmphitem;
+				tmphitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, i);
+				history_item_free (tmphitem);
+				g_array_remove_index (ebrowser->priv->history_items, i);
+			}
+		}
+		g_array_append_val (ebrowser->priv->history_items, hitem);
+		if (ebrowser->priv->history_items->len > ebrowser->priv->history_max_len) {
+			hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*, 0);
+			history_item_free (hitem);
+			g_array_remove_index (ebrowser->priv->history_items, 0);
+		}
+		ebrowser->priv->current_hitem = ebrowser->priv->history_items->len -1;
+	}
+	update_history_actions (ebrowser);
+}
+
+static void
+open_dn_requested_cb (EntryProperties *eprop, const gchar *dn, LdapEntriesPage *ebrowser)
+{
+	ldap_entries_page_set_current_dn (ebrowser, dn);
+}
+
+static void
+open_class_requested_cb (EntryProperties *eprop, const gchar *classname, LdapEntriesPage *ebrowser)
+{
+	BrowserPerspective *bpers;
+	bpers = browser_page_get_perspective (BROWSER_PAGE (ebrowser));
+	ldap_browser_perspective_display_ldap_class (LDAP_BROWSER_PERSPECTIVE (bpers), classname);
+}
+
+/**
+ * ldap_entries_page_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+ldap_entries_page_new (BrowserConnection *bcnc, const gchar *dn)
+{
+	LdapEntriesPage *ebrowser;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	ebrowser = LDAP_ENTRIES_PAGE (g_object_new (LDAP_ENTRIES_PAGE_TYPE, NULL));
+	ebrowser->priv->bcnc = g_object_ref ((GObject*) bcnc);
+
+	/* header */
+        GtkWidget *label;
+	gchar *str;
+
+	str = g_strdup_printf ("<b>%s</b>", _("LDAP entries browser"));
+	label = cc_gray_bar_new (str);
+	g_free (str);
+        gtk_box_pack_start (GTK_BOX (ebrowser), label, FALSE, FALSE, 0);
+        gtk_widget_show (label);
+	g_signal_connect (label, "drag-data-get",
+			  G_CALLBACK (source_drag_data_get_cb), ebrowser);
+
+	/* VPaned widget */
+	GtkWidget *hp;
+	hp = gtk_hpaned_new ();
+	gtk_box_pack_start (GTK_BOX (ebrowser), hp, TRUE, TRUE, 0);
+
+	/* tree */
+	GtkWidget *vbox, *hview, *sw;
+	gfloat yalign;
+
+	vbox = gtk_vbox_new (FALSE, FALSE);
+	gtk_paned_add1 (GTK_PANED (hp), vbox);
+	
+	str = g_strdup_printf ("<b>%s:</b>", _("LDAP hierarchy"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), str);
+	g_free (str);
+        gtk_misc_get_alignment (GTK_MISC (label), NULL, &yalign);
+        gtk_misc_set_alignment (GTK_MISC (label), 0., yalign);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+	hview = hierarchy_view_new (bcnc, dn);
+	ebrowser->priv->entries_view = hview;
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC,
+					GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (sw), hview);
+	gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
+
+	/* tree selection */
+	GtkTreeSelection *sel;
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->entries_view));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
+	g_signal_connect (sel, "changed",
+			  G_CALLBACK (selection_changed_cb), ebrowser);
+
+	/* details */
+	vbox = gtk_vbox_new (FALSE, FALSE);
+	gtk_paned_add2 (GTK_PANED (hp), vbox);
+
+	str = g_strdup_printf ("<b>%s:</b>", _("LDAP entry's details"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), str);
+	g_free (str);
+        gtk_misc_get_alignment (GTK_MISC (label), NULL, &yalign);
+        gtk_misc_set_alignment (GTK_MISC (label), 0., yalign);
+	gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+
+	GtkWidget *props;
+	props = entry_properties_new (bcnc);
+	gtk_box_pack_start (GTK_BOX (vbox), props, TRUE, TRUE, 0);
+	ebrowser->priv->entry_props = props;
+	g_signal_connect (props, "open-dn",
+			  G_CALLBACK (open_dn_requested_cb), ebrowser);
+	g_signal_connect (props, "open-class",
+			  G_CALLBACK (open_class_requested_cb), ebrowser);
+
+	gtk_paned_set_position (GTK_PANED (hp), 250);
+
+	gtk_widget_show_all (hp);
+
+	return (GtkWidget*) ebrowser;
+}
+
+/**
+ * ldap_entries_page_get_current_dn:
+ */
+const gchar *
+ldap_entries_page_get_current_dn (LdapEntriesPage *ldap_entries_page)
+{
+	g_return_val_if_fail (IS_LDAP_ENTRIES_PAGE (ldap_entries_page), NULL);
+	return hierarchy_view_get_current_dn (HIERARCHY_VIEW (ldap_entries_page->priv->entries_view), NULL);
+}
+
+/**
+ * ldap_entries_page_set_current_dn:
+ */
+void
+ldap_entries_page_set_current_dn (LdapEntriesPage *ldap_entries_page, const gchar *dn)
+{
+	g_return_if_fail (IS_LDAP_ENTRIES_PAGE (ldap_entries_page));
+	hierarchy_view_set_current_dn (HIERARCHY_VIEW (ldap_entries_page->priv->entries_view), dn);
+}
+
+
+/*
+ * UI actions
+ */
+static void
+action_add_to_fav_cb (G_GNUC_UNUSED GtkAction *action, LdapEntriesPage *ebrowser)
+{
+	BrowserFavorites *bfav;
+        BrowserFavoritesAttributes fav;
+        GError *error = NULL;
+	const gchar *tmp, *cn;
+
+	tmp = hierarchy_view_get_current_dn (HIERARCHY_VIEW (ebrowser->priv->entries_view), &cn);
+        memset (&fav, 0, sizeof (BrowserFavoritesAttributes));
+        fav.id = -1;
+        fav.type = BROWSER_FAVORITES_LDAP_DN;
+        fav.name = ldap_entries_page_to_selection (ebrowser);
+        fav.descr = (gchar*) cn;
+        fav.contents = ldap_entries_page_to_selection (ebrowser);
+
+        bfav = browser_connection_get_favorites (ebrowser->priv->bcnc);
+        if (! browser_favorites_add (bfav, 0, &fav, ORDER_KEY_LDAP, G_MAXINT, &error)) {
+                browser_show_error ((GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) ebrowser),
+                                    _("Could not add favorite: %s"),
+                                    error && error->message ? error->message : _("No detail"));
+                if (error)
+                        g_error_free (error);
+        }
+	g_free (fav.contents);
+}
+
+static void
+action_dn_back_cb (G_GNUC_UNUSED GtkAction *action, LdapEntriesPage *ebrowser)
+{
+	ebrowser->priv->add_hist_item = FALSE;
+	if (ebrowser->priv->current_hitem > 0) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*,
+				       ebrowser->priv->current_hitem - 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->entries_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ebrowser->priv->entries_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			hierarchy_view_set_current_dn (HIERARCHY_VIEW (ebrowser->priv->entries_view),
+						       hitem->dn);
+	}
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static void
+action_dn_forward_cb (G_GNUC_UNUSED GtkAction *action, LdapEntriesPage *ebrowser)
+{
+	ebrowser->priv->add_hist_item = FALSE;
+	if ((ebrowser->priv->current_hitem >=0) &&
+	    ((guint) ebrowser->priv->current_hitem < ebrowser->priv->history_items->len)) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (ebrowser->priv->history_items, HistoryItem*,
+				       ebrowser->priv->current_hitem + 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (ebrowser->priv->entries_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ebrowser->priv->entries_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			hierarchy_view_set_current_dn (HIERARCHY_VIEW (ebrowser->priv->entries_view),
+						       hitem->dn);
+	}
+	ebrowser->priv->add_hist_item = TRUE;
+}
+
+static GtkActionEntry ui_actions[] = {
+	{ "LDAP", NULL, N_("_LDAP"), NULL, N_("LDAP"), NULL },
+	{ "AddToFav", STOCK_ADD_BOOKMARK, N_("Add to _Favorites"), NULL, N_("Add entry to favorites"),
+	  G_CALLBACK (action_add_to_fav_cb)},
+	{ "DnBack", GTK_STOCK_GO_BACK, N_("Previous entry"), NULL, N_("Move back to previous LDAP entry"),
+	  G_CALLBACK (action_dn_back_cb)},
+	{ "DnForward", GTK_STOCK_GO_FORWARD, N_("Next entry"), NULL, N_("Move to next LDAP entry"),
+	  G_CALLBACK (action_dn_forward_cb)},
+};
+static const gchar *ui_actions_browser =
+	"<ui>"
+	"  <menubar name='MenuBar'>"
+	"    <placeholder name='MenuExtension'>"
+        "      <menu name='LDAP' action='LDAP'>"
+        "        <menuitem name='AddToFav' action= 'AddToFav'/>"
+	"        <separator/>"
+        "        <menuitem name='DnBack' action= 'DnBack'/>"
+        "        <menuitem name='DnForward' action= 'DnForward'/>"
+        "      </menu>"
+        "    </placeholder>"
+	"  </menubar>"
+	"  <toolbar name='ToolBar'>"
+	"    <separator/>"
+	"    <toolitem action='AddToFav'/>"
+	"    <toolitem action='DnBack'/>"
+	"    <toolitem action='DnForward'/>"
+	"  </toolbar>"
+	"</ui>";
+
+static GtkActionGroup *
+ldap_entries_page_page_get_actions_group (BrowserPage *page)
+{
+	LdapEntriesPage *ebrowser;
+
+	ebrowser = LDAP_ENTRIES_PAGE (page);
+	if (! ebrowser->priv->agroup) {
+		GtkActionGroup *agroup;
+		agroup = gtk_action_group_new ("LdapLdapEntriesPageActions");
+		gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
+		gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), page);
+		ebrowser->priv->agroup = agroup;
+
+		GtkAction *action;
+		const gchar *current_dn;
+		action = gtk_action_group_get_action (agroup, "AddToFav");
+		current_dn = ldap_entries_page_get_current_dn (ebrowser);
+		gtk_action_set_sensitive (action, (current_dn && *current_dn) ? TRUE : FALSE);
+
+		update_history_actions (ebrowser);
+	}
+	
+	return g_object_ref (ebrowser->priv->agroup);
+}
+
+static const gchar *
+ldap_entries_page_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
+{
+	return ui_actions_browser;
+}
+
+static GtkWidget *
+ldap_entries_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
+{
+	LdapEntriesPage *ebrowser;
+	const gchar *tab_name;
+	GdkPixbuf *entries_pixbuf;
+
+	ebrowser = LDAP_ENTRIES_PAGE (page);
+	entries_pixbuf = browser_get_pixbuf_icon (BROWSER_ICON_LDAP_ORGANIZATION);
+	tab_name = _("LDAP entries");
+	return browser_make_tab_label_with_pixbuf (tab_name,
+						   entries_pixbuf,
+						   out_close_button ? TRUE : FALSE, out_close_button);
+}
diff --git a/tools/browser/ldap-browser/ldap-entries-page.h b/tools/browser/ldap-browser/ldap-entries-page.h
new file mode 100644
index 0000000..6d3c46e
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-entries-page.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LDAP_ENTRIES_PAGE_H__
+#define __LDAP_ENTRIES_PAGE_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define LDAP_ENTRIES_PAGE_TYPE            (ldap_entries_page_get_type())
+#define LDAP_ENTRIES_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, LDAP_ENTRIES_PAGE_TYPE, LdapEntriesPage))
+#define LDAP_ENTRIES_PAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, LDAP_ENTRIES_PAGE_TYPE, LdapEntriesPageClass))
+#define IS_LDAP_ENTRIES_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, LDAP_ENTRIES_PAGE_TYPE))
+#define IS_LDAP_ENTRIES_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LDAP_ENTRIES_PAGE_TYPE))
+
+typedef struct _LdapEntriesPage        LdapEntriesPage;
+typedef struct _LdapEntriesPageClass   LdapEntriesPageClass;
+typedef struct _LdapEntriesPagePrivate LdapEntriesPagePrivate;
+
+struct _LdapEntriesPage {
+	GtkVBox                parent;
+	LdapEntriesPagePrivate *priv;
+};
+
+struct _LdapEntriesPageClass {
+	GtkVBoxClass           parent_class;
+};
+
+GType        ldap_entries_page_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *ldap_entries_page_new            (BrowserConnection *bcnc, const gchar *dn);
+const gchar *ldap_entries_page_get_current_dn (LdapEntriesPage *ldap_entries_page);
+void         ldap_entries_page_set_current_dn (LdapEntriesPage *ldap_entries_page, const gchar *dn);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/ldap-favorite-selector.c b/tools/browser/ldap-browser/ldap-favorite-selector.c
new file mode 100644
index 0000000..6c949be
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-favorite-selector.c
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <libgda/gda-tree.h>
+#include "ldap-favorite-selector.h"
+#include "../mgr-favorites.h"
+#include <libgda-ui/gdaui-tree-store.h>
+#include "../dnd.h"
+#include "../support.h"
+#include "marshal.h"
+#include "../cc-gray-bar.h"
+#include "../browser-favorites.h"
+#include <gdk/gdkkeysyms.h>
+#include <libgda-ui/internal/popup-container.h>
+
+struct _LdapFavoriteSelectorPrivate {
+	BrowserConnection *bcnc;
+	GdaTree *tree;
+	GtkWidget *treeview;
+	guint idle_update_favorites;
+
+	GtkWidget *popup_menu;
+	GtkWidget *popup_properties;
+	GtkWidget *properties_name;
+	GtkWidget *properties_descr;
+	gint       properties_id;
+	gint       properties_position;
+	guint      prop_save_timeout;
+};
+
+static void ldap_favorite_selector_class_init (LdapFavoriteSelectorClass *klass);
+static void ldap_favorite_selector_init       (LdapFavoriteSelector *fsel,
+					       LdapFavoriteSelectorClass *klass);
+static void ldap_favorite_selector_dispose   (GObject *object);
+
+static void favorites_changed_cb (BrowserFavorites *bfav, LdapFavoriteSelector *fsel);
+
+enum {
+	SELECTION_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint ldap_favorite_selector_signals[LAST_SIGNAL] = { 0 };
+static GObjectClass *parent_class = NULL;
+
+/* columns of the resulting GtkTreeModel */
+enum {
+	COLUMN_ID = 0,
+	COLUMN_NAME = 1,
+	COLUMN_ICON = 2,
+	COLUMN_MARKUP = 3,
+	COLUMN_POSITION = 4,
+	COLUMN_DESCR = 5,
+	COLUMN_FAVTYPE = 6,
+	COLUMN_LAST
+};
+
+
+/*
+ * LdapFavoriteSelector class implementation
+ */
+
+static void
+ldap_favorite_selector_class_init (LdapFavoriteSelectorClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* signals */
+	ldap_favorite_selector_signals [SELECTION_CHANGED] =
+                g_signal_new ("selection-changed",
+                              G_TYPE_FROM_CLASS (object_class),
+                              G_SIGNAL_RUN_FIRST,
+                              G_STRUCT_OFFSET (LdapFavoriteSelectorClass, selection_changed),
+                              NULL, NULL,
+                              _ldap_marshal_VOID__INT_ENUM_STRING, G_TYPE_NONE,
+                              3, G_TYPE_INT, G_TYPE_UINT, G_TYPE_STRING);
+	klass->selection_changed = NULL;
+
+	object_class->dispose = ldap_favorite_selector_dispose;
+}
+
+
+static void
+ldap_favorite_selector_init (LdapFavoriteSelector *fsel, G_GNUC_UNUSED LdapFavoriteSelectorClass *klass)
+{
+	fsel->priv = g_new0 (LdapFavoriteSelectorPrivate, 1);
+	fsel->priv->idle_update_favorites = 0;
+	fsel->priv->prop_save_timeout = 0;
+}
+
+static void
+ldap_favorite_selector_dispose (GObject *object)
+{
+	LdapFavoriteSelector *fsel = (LdapFavoriteSelector *) object;
+
+	/* free memory */
+	if (fsel->priv) {
+		if (fsel->priv->idle_update_favorites != 0)
+			g_source_remove (fsel->priv->idle_update_favorites);
+		if (fsel->priv->prop_save_timeout)
+			g_source_remove (fsel->priv->prop_save_timeout);
+
+		if (fsel->priv->tree)
+			g_object_unref (fsel->priv->tree);
+
+		if (fsel->priv->bcnc) {
+			g_signal_handlers_disconnect_by_func (browser_connection_get_favorites (fsel->priv->bcnc),
+							      G_CALLBACK (favorites_changed_cb), fsel);
+			g_object_unref (fsel->priv->bcnc);
+		}
+		
+		if (fsel->priv->popup_properties)
+			gtk_widget_destroy (fsel->priv->popup_properties);
+		if (fsel->priv->popup_menu)
+			gtk_widget_destroy (fsel->priv->popup_menu);
+
+		g_free (fsel->priv);
+		fsel->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+ldap_favorite_selector_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (LdapFavoriteSelectorClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ldap_favorite_selector_class_init,
+			NULL,
+			NULL,
+			sizeof (LdapFavoriteSelector),
+			0,
+			(GInstanceInitFunc) ldap_favorite_selector_init,
+			0
+		};
+		type = g_type_register_static (GTK_TYPE_VBOX, "LdapFavoriteSelector",
+					       &info, 0);
+	}
+	return type;
+}
+
+static void
+favorite_delete_selected (LdapFavoriteSelector *fsel)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *select;
+	GtkTreeIter iter;
+	
+	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (fsel->priv->treeview));
+	if (gtk_tree_selection_get_selected (select, &model, &iter)) {
+		BrowserFavorites *bfav;
+		BrowserFavoritesAttributes fav;
+		GError *lerror = NULL;
+		
+		memset (&fav, 0, sizeof (BrowserFavoritesAttributes));
+		gtk_tree_model_get (model, &iter,
+				    COLUMN_ID, &(fav.id), -1);
+		bfav = browser_connection_get_favorites (fsel->priv->bcnc);
+		if (!browser_favorites_delete (bfav, 0, &fav, NULL)) {
+			browser_show_error ((GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*)fsel),
+					    _("Could not remove favorite: %s"),
+					    lerror && lerror->message ? lerror->message : _("No detail"));
+			if (lerror)
+				g_error_free (lerror);
+		}
+	}
+}
+
+static gboolean
+key_press_event_cb (GtkTreeView *treeview, GdkEventKey *event, LdapFavoriteSelector *fsel)
+{
+	if (event->keyval == GDK_Delete) {
+		favorite_delete_selected (fsel);
+		return TRUE;
+	}
+	return FALSE; /* not handled */
+}
+
+
+static void
+selection_changed_cb (GtkTreeView *treeview, G_GNUC_UNUSED GtkTreePath *path,
+		      G_GNUC_UNUSED GtkTreeViewColumn *column, LdapFavoriteSelector *fsel)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *select;
+	GtkTreeIter iter;
+	
+	select = gtk_tree_view_get_selection (treeview);
+	if (gtk_tree_selection_get_selected (select, &model, &iter)) {
+		gchar *str;
+		gint fav_id;
+		BrowserFavoritesType fav_type;
+		gtk_tree_model_get (model, &iter,
+				    COLUMN_ID, &fav_id,
+				    COLUMN_FAVTYPE, &fav_type,
+				    COLUMN_NAME, &str, -1);
+		g_signal_emit (fsel, ldap_favorite_selector_signals [SELECTION_CHANGED], 0, fav_id,
+			       fav_type, str);
+		g_free (str);
+	}
+}
+
+static gboolean
+prop_save_timeout (LdapFavoriteSelector *fsel)
+{
+	BrowserFavorites *bfav;
+	BrowserFavoritesAttributes fav;
+	GError *error = NULL;
+	gboolean allok;
+
+	bfav = browser_connection_get_favorites (fsel->priv->bcnc);
+
+	memset (&fav, 0, sizeof (BrowserFavoritesAttributes));
+	fav.id = fsel->priv->properties_id;
+	fav.type = BROWSER_FAVORITES_LDAP_DN;
+	fav.name = (gchar*) gtk_entry_get_text (GTK_ENTRY (fsel->priv->properties_name));
+	fav.descr = (gchar*) gtk_entry_get_text (GTK_ENTRY (fsel->priv->properties_descr));
+	fav.contents = (gchar*) gtk_entry_get_text (GTK_ENTRY (fsel->priv->properties_name));
+
+	allok = browser_favorites_add (bfav, 0, &fav, ORDER_KEY_LDAP,
+				       fsel->priv->properties_position, &error);
+	if (! allok) {
+		browser_show_error ((GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) fsel),
+				    _("Could not add favorite: %s"),
+				    error && error->message ? error->message : _("No detail"));
+		if (error)
+			g_error_free (error);
+	}
+
+	fsel->priv->prop_save_timeout = 0;
+	return FALSE; /* remove timeout */
+}
+
+static void
+property_changed_cb (G_GNUC_UNUSED GtkWidget *multiple, LdapFavoriteSelector *fsel)
+{
+	if (fsel->priv->prop_save_timeout)
+		g_source_remove (fsel->priv->prop_save_timeout);
+	fsel->priv->prop_save_timeout = g_timeout_add (200, (GSourceFunc) prop_save_timeout, fsel);
+}
+
+static void
+properties_activated_cb (GtkMenuItem *mitem, LdapFavoriteSelector *fsel)
+{
+	if (! fsel->priv->popup_properties) {
+		GtkWidget *pcont, *vbox, *hbox, *label, *entry, *table;
+		gchar *str;
+		gfloat align;
+		
+		pcont = popup_container_new (GTK_WIDGET (mitem));
+		vbox = gtk_vbox_new (FALSE, 0);
+		gtk_container_add (GTK_CONTAINER (pcont), vbox);
+		
+		label = gtk_label_new ("");
+		str = g_strdup_printf ("<b>%s:</b>", _("Favorite's properties"));
+		gtk_label_set_markup (GTK_LABEL (label), str);
+		g_free (str);
+		gtk_misc_get_alignment (GTK_MISC (label), NULL, &align);
+		gtk_misc_set_alignment (GTK_MISC (label), 0., align);
+		gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+		
+		hbox = gtk_hbox_new (FALSE, 0); /* HIG */
+		gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 5);
+		label = gtk_label_new ("      ");
+		gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+		
+		table = gtk_table_new (2, 2, FALSE);
+		gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0);
+		
+		label = gtk_label_new ("");
+		str = g_strdup_printf ("<b>%s:</b>", _("Name"));
+		gtk_label_set_markup (GTK_LABEL (label), str);
+		g_free (str);
+		gtk_misc_get_alignment (GTK_MISC (label), NULL, &align);
+		gtk_misc_set_alignment (GTK_MISC (label), 0., align);
+		gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+		
+		label = gtk_label_new ("");
+		str = g_strdup_printf ("<b>%s:</b>", _("Description"));
+		gtk_label_set_markup (GTK_LABEL (label), str);
+		g_free (str);
+		gtk_misc_set_alignment (GTK_MISC (label), 0., 0.);
+		gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
+		
+		entry = gtk_entry_new ();
+		gtk_editable_set_editable (GTK_EDITABLE (entry), FALSE);
+		gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+		fsel->priv->properties_name = entry;
+
+		entry = gtk_entry_new ();
+		gtk_widget_set_size_request (entry, 200, -1);
+		gtk_table_attach (GTK_TABLE (table), entry, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
+		fsel->priv->properties_descr = entry;
+		g_signal_connect (entry, "changed",
+				  G_CALLBACK (property_changed_cb), fsel);
+
+		fsel->priv->popup_properties = pcont;
+		gtk_widget_show_all (vbox);
+	}
+
+	/* adjust contents */
+	GtkTreeSelection *selection;
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (fsel->priv->treeview));
+	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+		gchar *name, *descr;
+		
+		gtk_tree_model_get (model, &iter,
+				    COLUMN_ID, &(fsel->priv->properties_id),
+				    COLUMN_POSITION, &(fsel->priv->properties_position),
+				    COLUMN_NAME, &name,
+				    COLUMN_DESCR, &descr, -1);
+
+		if (name) {
+			gtk_entry_set_text (GTK_ENTRY (fsel->priv->properties_name), name);
+			g_free (name);
+		}
+
+		g_signal_handlers_block_by_func (fsel->priv->properties_descr,
+						 G_CALLBACK (property_changed_cb), fsel);
+		gtk_entry_set_text (GTK_ENTRY (fsel->priv->properties_descr), descr ? descr : "");
+		g_signal_handlers_unblock_by_func (fsel->priv->properties_descr,
+						   G_CALLBACK (property_changed_cb), fsel);
+		g_free (descr);
+
+		gtk_widget_show (fsel->priv->popup_properties);
+	}
+}
+
+static void
+delete_activated_cb (G_GNUC_UNUSED GtkMenuItem *mitem, LdapFavoriteSelector *fsel)
+{
+	favorite_delete_selected (fsel);
+}
+
+static void
+do_popup_menu (G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, LdapFavoriteSelector *fsel)
+{
+	int button, event_time;
+
+	if (! fsel->priv->popup_menu) {
+		GtkWidget *menu, *mitem;
+		
+		menu = gtk_menu_new ();
+		g_signal_connect (menu, "deactivate", 
+				  G_CALLBACK (gtk_widget_hide), NULL);
+		
+		mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PROPERTIES, NULL);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
+		gtk_widget_show (mitem);
+		g_signal_connect (mitem, "activate",
+				  G_CALLBACK (properties_activated_cb), fsel);
+
+		mitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_DELETE, NULL);
+		gtk_menu_shell_append (GTK_MENU_SHELL (menu), mitem);
+		gtk_widget_show (mitem);
+		g_signal_connect (mitem, "activate",
+				  G_CALLBACK (delete_activated_cb), fsel);
+
+		fsel->priv->popup_menu = menu;
+	}
+		
+	if (event) {
+		button = event->button;
+		event_time = event->time;
+	}
+	else {
+		button = 0;
+		event_time = gtk_get_current_event_time ();
+	}
+
+	gtk_menu_popup (GTK_MENU (fsel->priv->popup_menu), NULL, NULL, NULL, NULL, 
+			button, event_time);
+}
+
+
+static gboolean
+popup_menu_cb (GtkWidget *widget, LdapFavoriteSelector *fsel)
+{
+	do_popup_menu (widget, NULL, fsel);
+	return TRUE;
+}
+
+static gboolean
+button_press_event_cb (GtkTreeView *treeview, GdkEventButton *event, LdapFavoriteSelector *fsel)
+{
+	if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
+		do_popup_menu ((GtkWidget*) treeview, event, fsel);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean idle_update_favorites (LdapFavoriteSelector *fsel);
+static gboolean tree_store_drag_drop_cb (GdauiTreeStore *store, const gchar *path,
+					 GtkSelectionData *selection_ldap, LdapFavoriteSelector *fsel);
+static gboolean tree_store_drag_can_drag_cb (GdauiTreeStore *store, const gchar *path,
+					     LdapFavoriteSelector *fsel);
+static gboolean tree_store_drag_get_cb (GdauiTreeStore *store, const gchar *path,
+					GtkSelectionData *selection_ldap, LdapFavoriteSelector *fsel);
+
+/**
+ * ldap_favorite_selector_new
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+ldap_favorite_selector_new (BrowserConnection *bcnc)
+{
+	LdapFavoriteSelector *fsel;
+	GdaTreeManager *manager;
+	gchar *signame;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+	fsel = LDAP_FAVORITE_SELECTOR (g_object_new (LDAP_FAVORITE_SELECTOR_TYPE, NULL));
+
+	fsel->priv->bcnc = g_object_ref (bcnc);
+	signame = g_strdup_printf ("favorites-changed::%s",
+				   browser_favorites_type_to_string (BROWSER_FAVORITES_LDAP_DN));
+	g_signal_connect (browser_connection_get_favorites (fsel->priv->bcnc), signame,
+			  G_CALLBACK (favorites_changed_cb), fsel);
+	g_free (signame);
+	signame = g_strdup_printf ("favorites-changed::%s",
+				   browser_favorites_type_to_string (BROWSER_FAVORITES_LDAP_CLASS));
+	g_signal_connect (browser_connection_get_favorites (fsel->priv->bcnc), signame,
+			  G_CALLBACK (favorites_changed_cb), fsel);
+	g_free (signame);
+	
+	/* create tree managers */
+	fsel->priv->tree = gda_tree_new ();
+	manager = mgr_favorites_new (bcnc, BROWSER_FAVORITES_LDAP_DN, ORDER_KEY_LDAP);
+        gda_tree_add_manager (fsel->priv->tree, manager);
+	g_object_unref (manager);
+	manager = mgr_favorites_new (bcnc, BROWSER_FAVORITES_LDAP_CLASS, ORDER_KEY_LDAP);
+        gda_tree_add_manager (fsel->priv->tree, manager);
+	g_object_unref (manager);
+
+	/* update the tree's contents */
+	if (! gda_tree_update_all (fsel->priv->tree, NULL)) {
+		if (fsel->priv->idle_update_favorites == 0)
+			fsel->priv->idle_update_favorites = g_idle_add ((GSourceFunc) idle_update_favorites, fsel);
+	}
+
+	/* header */
+	GtkWidget *label;
+	gchar *str;
+	str = g_strdup_printf ("<b>%s</b>", _("Favorites"));
+	label = cc_gray_bar_new (str);
+	g_free (str);
+	cc_gray_bar_set_icon_from_pixbuf (CC_GRAY_BAR (label), browser_get_pixbuf_icon (BROWSER_ICON_BOOKMARK));
+        gtk_box_pack_start (GTK_BOX (fsel), label, FALSE, FALSE, 0);
+        gtk_widget_show (label);
+
+	/* tree model & tree view */
+	GtkTreeModel *model;
+	GtkWidget *treeview;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+
+	model = gdaui_tree_store_new (fsel->priv->tree, COLUMN_LAST,
+				      G_TYPE_INT, MGR_FAVORITES_ID_ATT_NAME,
+				      G_TYPE_STRING, MGR_FAVORITES_CONTENTS_ATT_NAME,
+				      G_TYPE_OBJECT, "icon",
+				      G_TYPE_STRING, "markup",
+				      G_TYPE_INT, MGR_FAVORITES_ID_ATT_NAME,
+				      G_TYPE_STRING, "descr",
+				      G_TYPE_UINT, MGR_FAVORITES_TYPE_ATT_NAME);
+
+	treeview = browser_make_tree_view (model);
+	fsel->priv->treeview = treeview;
+	g_object_unref (model);
+
+	g_signal_connect (G_OBJECT (treeview), "row-activated",
+			  G_CALLBACK (selection_changed_cb), fsel);
+	g_signal_connect (G_OBJECT (treeview), "key-press-event",
+			  G_CALLBACK (key_press_event_cb), fsel);
+	g_signal_connect (G_OBJECT (treeview), "popup-menu",
+			  G_CALLBACK (popup_menu_cb), fsel);
+	g_signal_connect (G_OBJECT (treeview), "button-press-event",
+			  G_CALLBACK (button_press_event_cb), fsel);
+
+	/* icon */
+	column = gtk_tree_view_column_new ();
+
+	renderer = gtk_cell_renderer_pixbuf_new ();
+	gtk_tree_view_column_pack_start (column, renderer, FALSE);
+	gtk_tree_view_column_add_attribute (column, renderer, "pixbuf", COLUMN_ICON);
+	g_object_set ((GObject*) renderer, "yalign", 0., NULL);
+
+	/* text */
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_tree_view_column_pack_start (column, renderer, TRUE);
+	gtk_tree_view_column_add_attribute (column, renderer, "markup", COLUMN_MARKUP);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+	gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+	
+	/* scrolled window packing */
+	GtkWidget *sw;
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+					     GTK_SHADOW_ETCHED_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+					GTK_POLICY_NEVER,
+					GTK_POLICY_AUTOMATIC);
+	gtk_container_add (GTK_CONTAINER (sw), treeview);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);	
+
+	gtk_box_pack_start (GTK_BOX (fsel), sw, TRUE, TRUE, 0);
+	gtk_widget_show_all (sw);
+
+	/* DnD */
+	gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (treeview), dbo_table, G_N_ELEMENTS (dbo_table),
+					      GDK_ACTION_COPY);
+	gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (treeview), GDK_BUTTON1_MASK,
+						dbo_table, G_N_ELEMENTS (dbo_table),
+						GDK_ACTION_COPY | GDK_ACTION_MOVE);
+	g_signal_connect (model, "drag-drop",
+			  G_CALLBACK (tree_store_drag_drop_cb), fsel);
+	g_signal_connect (model, "drag-can-drag",
+			  G_CALLBACK (tree_store_drag_can_drag_cb), fsel);
+	g_signal_connect (model, "drag-get",
+			  G_CALLBACK (tree_store_drag_get_cb), fsel);
+
+	return (GtkWidget*) fsel;
+}
+
+static gboolean
+idle_update_favorites (LdapFavoriteSelector *fsel)
+{
+	gboolean done;
+	g_print ("%s()\n", __FUNCTION__);
+	done = gda_tree_update_all (fsel->priv->tree, NULL);
+	if (done)
+		fsel->priv->idle_update_favorites = 0;
+	else
+		fsel->priv->idle_update_favorites = g_timeout_add_seconds (1, (GSourceFunc) idle_update_favorites,
+									   fsel);
+	return FALSE;
+}
+
+static gboolean
+tree_store_drag_drop_cb (G_GNUC_UNUSED GdauiTreeStore *store, const gchar *path,
+			 GtkSelectionData *selection_ldap, LdapFavoriteSelector *fsel)
+{
+	BrowserFavorites *bfav;
+	BrowserFavoritesAttributes fav;
+	GError *error = NULL;
+	gint pos;
+	gboolean retval = TRUE;
+	gint id;
+	bfav = browser_connection_get_favorites (fsel->priv->bcnc);
+
+	id = browser_favorites_find (bfav, 0, (gchar*) gtk_selection_data_get_data (selection_ldap),
+				     &fav, NULL);
+	if (id < 0) {
+		memset (&fav, 0, sizeof (BrowserFavoritesAttributes));
+		fav.id = -1;
+		fav.type = BROWSER_FAVORITES_LDAP_DN;
+		fav.name = (gchar*) gtk_selection_data_get_data (selection_ldap);
+		fav.descr = NULL;
+		fav.contents = (gchar*) gtk_selection_data_get_data (selection_ldap);
+	}
+
+	pos = atoi (path);
+	/*g_print ("%s() path => %s, pos: %d\n", __FUNCTION__, path, pos);*/
+	
+	if (! browser_favorites_add (bfav, 0, &fav, ORDER_KEY_LDAP, pos, &error)) {
+		browser_show_error ((GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) fsel),
+				    _("Could not add favorite: %s"),
+				    error && error->message ? error->message : _("No detail"));
+		if (error)
+			g_error_free (error);
+		retval = FALSE;
+	}
+	
+	if (id >= 0)
+		browser_favorites_reset_attributes (&fav);
+
+	return retval;
+}
+
+static gboolean
+tree_store_drag_can_drag_cb (G_GNUC_UNUSED GdauiTreeStore *store, const gchar *path,
+			     LdapFavoriteSelector *fsel)
+{
+	GdaTreeNode *node;
+	node = gda_tree_get_node (fsel->priv->tree, path, FALSE);
+	if (node) {
+		const GValue *cvalue;
+		cvalue = gda_tree_node_get_node_attribute (node, "fav_contents");
+		if (cvalue)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+tree_store_drag_get_cb (G_GNUC_UNUSED GdauiTreeStore *store, const gchar *path,
+			GtkSelectionData *selection_ldap, LdapFavoriteSelector *fsel)
+{
+	GdaTreeNode *node;
+	node = gda_tree_get_node (fsel->priv->tree, path, FALSE);
+	if (node) {
+		const GValue *cvalue;
+		cvalue = gda_tree_node_get_node_attribute (node, "fav_contents");
+		if (cvalue) {
+			const gchar *str;
+			str = g_value_get_string (cvalue);
+			gtk_selection_data_set (selection_ldap, gtk_selection_data_get_target (selection_ldap),
+						8, (guchar*) str, strlen (str));
+			return TRUE;
+		}
+	}
+	return FALSE;
+}
+
+static void
+favorites_changed_cb (G_GNUC_UNUSED BrowserFavorites *bfav, LdapFavoriteSelector *fsel)
+{
+	if (! gda_tree_update_all (fsel->priv->tree, NULL)) {
+		if (fsel->priv->idle_update_favorites == 0)
+			fsel->priv->idle_update_favorites = g_idle_add ((GSourceFunc) idle_update_favorites, fsel);
+
+	}
+}
diff --git a/tools/browser/ldap-browser/ldap-favorite-selector.h b/tools/browser/ldap-browser/ldap-favorite-selector.h
new file mode 100644
index 0000000..2539f94
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-favorite-selector.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LDAP_FAVORITE_SELECTOR_H__
+#define __LDAP_FAVORITE_SELECTOR_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define LDAP_FAVORITE_SELECTOR_TYPE            (ldap_favorite_selector_get_type())
+#define LDAP_FAVORITE_SELECTOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, LDAP_FAVORITE_SELECTOR_TYPE, LdapFavoriteSelector))
+#define LDAP_FAVORITE_SELECTOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, LDAP_FAVORITE_SELECTOR_TYPE, LdapFavoriteSelectorClass))
+#define IS_LDAP_FAVORITE_SELECTOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, LDAP_FAVORITE_SELECTOR_TYPE))
+#define IS_LDAP_FAVORITE_SELECTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LDAP_FAVORITE_SELECTOR_TYPE))
+
+typedef struct _LdapFavoriteSelector        LdapFavoriteSelector;
+typedef struct _LdapFavoriteSelectorClass   LdapFavoriteSelectorClass;
+typedef struct _LdapFavoriteSelectorPrivate LdapFavoriteSelectorPrivate;
+
+struct _LdapFavoriteSelector {
+	GtkVBox               parent;
+	LdapFavoriteSelectorPrivate *priv;
+};
+
+struct _LdapFavoriteSelectorClass {
+	GtkVBoxClass          parent_class;
+
+	void                (*selection_changed) (LdapFavoriteSelector *sel, gint fav_id,
+						  BrowserFavoritesType fav_type, const gchar *fav_contents);
+};
+
+GType                    ldap_favorite_selector_get_type (void) G_GNUC_CONST;
+
+GtkWidget               *ldap_favorite_selector_new      (BrowserConnection *bcnc);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/ldap-search-page.c b/tools/browser/ldap-browser/ldap-search-page.c
new file mode 100644
index 0000000..6f18198
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-search-page.c
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include "ldap-search-page.h"
+#include "filter-editor.h"
+#include "../cc-gray-bar.h"
+#include "../browser-page.h"
+#include "../browser-window.h"
+#include "../browser-connection.h"
+#include <virtual/gda-ldap-connection.h>
+#include <common/ui-formgrid.h>
+#include "../browser-stock-icons.h"
+#include "vtable-dialog.h"
+
+typedef struct {
+	gchar              *base_dn;
+	gchar              *filter;
+	gchar              *attributes;
+	GdaLdapSearchScope  scope;
+	GdaDataModel       *result;
+} HistoryItem;
+
+static void
+history_item_free (HistoryItem *item)
+{
+	g_free (item->base_dn);
+	g_free (item->filter);
+	g_free (item->attributes);
+	if (item->result)
+		g_object_unref (item->result);
+	g_free (item);
+}
+
+struct _LdapSearchPagePrivate {
+	BrowserConnection *bcnc;
+
+	GtkWidget *search_entry;
+	GtkWidget *result_view;
+
+	GtkActionGroup *agroup;
+	GArray *history_items; /* array of @HistoryItem */
+	guint history_max_len; /* max allowed length of @history_items */
+	gint current_hitem; /* index in @history_items, or -1 */
+	gboolean add_hist_item;
+};
+
+static void ldap_search_page_class_init (LdapSearchPageClass *klass);
+static void ldap_search_page_init       (LdapSearchPage *epage, LdapSearchPageClass *klass);
+static void ldap_search_page_dispose   (GObject *object);
+
+/* BrowserPage interface */
+static void                 ldap_search_page_page_init (BrowserPageIface *iface);
+static GtkActionGroup      *ldap_search_page_page_get_actions_group (BrowserPage *page);
+static const gchar         *ldap_search_page_page_get_actions_ui (BrowserPage *page);
+static GtkWidget           *ldap_search_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * LdapSearchPage class implementation
+ */
+
+static void
+ldap_search_page_class_init (LdapSearchPageClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = ldap_search_page_dispose;
+}
+
+static void
+ldap_search_page_page_init (BrowserPageIface *iface)
+{
+	iface->i_get_actions_group = ldap_search_page_page_get_actions_group;
+	iface->i_get_actions_ui = ldap_search_page_page_get_actions_ui;
+	iface->i_get_tab_label = ldap_search_page_page_get_tab_label;
+}
+
+static void
+ldap_search_page_init (LdapSearchPage *epage, G_GNUC_UNUSED LdapSearchPageClass *klass)
+{
+	epage->priv = g_new0 (LdapSearchPagePrivate, 1);
+	epage->priv->history_items = g_array_new (FALSE, FALSE, sizeof (HistoryItem*));
+	epage->priv->history_max_len = 20;
+	epage->priv->add_hist_item = TRUE;
+}
+
+static void
+ldap_search_page_dispose (GObject *object)
+{
+	LdapSearchPage *epage = (LdapSearchPage *) object;
+
+	/* free memory */
+	if (epage->priv) {
+		if (epage->priv->bcnc)
+			g_object_unref (epage->priv->bcnc);
+		if (epage->priv->agroup)
+			g_object_unref (epage->priv->agroup);
+		if (epage->priv->history_items) {
+			guint i;
+			for (i = 0; i < epage->priv->history_items->len; i++) {
+				HistoryItem *hitem = g_array_index (epage->priv->history_items,
+								    HistoryItem*, i);
+				history_item_free (hitem);
+			}
+			g_array_free (epage->priv->history_items, TRUE);
+		}
+		g_free (epage->priv);
+		epage->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+ldap_search_page_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo info = {
+			sizeof (LdapSearchPageClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) ldap_search_page_class_init,
+			NULL,
+			NULL,
+			sizeof (LdapSearchPage),
+			0,
+			(GInstanceInitFunc) ldap_search_page_init,
+			0
+		};
+
+		static GInterfaceInfo page_info = {
+                        (GInterfaceInitFunc) ldap_search_page_page_init,
+			NULL,
+                        NULL
+                };
+
+		type = g_type_register_static (GTK_TYPE_VBOX, "LdapSearchPage", &info, 0);
+		g_type_add_interface_static (type, BROWSER_PAGE_TYPE, &page_info);
+	}
+	return type;
+}
+
+static void
+update_history_actions (LdapSearchPage *epage)
+{
+	if (!epage->priv->agroup)
+		return;
+	/*
+	gboolean is_first = TRUE;
+	gboolean is_last = TRUE;
+	const gchar *current_classname;
+	epage->priv->current_hitem = -1;
+	current_classname = ldap_search_page_get_current_class (epage);
+	if (current_classname) {
+		guint i;
+		for (i = 0; i < epage->priv->history_items->len; i++) {
+			HistoryItem *hitem;
+			hitem = g_array_index (epage->priv->history_items, HistoryItem*, i);
+			if (!strcmp (hitem->classname, current_classname)) {
+				if (i != 0)
+					is_first = FALSE;
+				if (i+1 < epage->priv->history_items->len)
+					is_last = FALSE;
+				epage->priv->current_hitem = (gint) i;
+				break;
+			}
+		}
+	}
+
+	GtkAction *action;
+	action = gtk_action_group_get_action (epage->priv->agroup, "DnBack");
+	gtk_action_set_sensitive (action, !is_first);
+	action = gtk_action_group_get_action (epage->priv->agroup, "DnForward");
+	gtk_action_set_sensitive (action, !is_last);
+	*/
+}
+
+static void
+search_done_cb (G_GNUC_UNUSED BrowserConnection *bcnc,
+		gpointer out_result, LdapSearchPage *epage, GError *error)
+{
+	if (epage->priv->result_view) {
+		gtk_widget_destroy (epage->priv->result_view);
+		epage->priv->result_view = NULL;
+	}
+	if (out_result == (gpointer) 0x01) {
+		TO_IMPLEMENT;
+	}
+	else {
+		GdaDataModel *model;
+		GtkWidget *wid;
+		model = GDA_DATA_MODEL (out_result);
+		wid = ui_formgrid_new (model, TRUE, GDAUI_DATA_PROXY_INFO_CURRENT_ROW);
+		g_object_unref (model);
+		gtk_box_pack_start (GTK_BOX (epage), wid, TRUE, TRUE, 0);
+		epage->priv->result_view = wid;
+		gtk_widget_show (wid);
+	}
+	g_object_unref (G_OBJECT (epage));
+}
+
+static void
+filter_exec_clicked_cb (GtkWidget *button, LdapSearchPage *epage)
+{
+	guint id;
+	gchar *base_dn, *filter, *attributes;
+	GdaLdapSearchScope scope;
+	GError *lerror = NULL;
+	filter_editor_get_settings (FILTER_EDITOR (epage->priv->search_entry),
+				    &base_dn, &filter, &attributes, &scope);
+
+	id = browser_connection_ldap_search (epage->priv->bcnc,
+					     base_dn, filter,
+					     attributes, scope,
+					     BROWSER_CONNECTION_JOB_CALLBACK (search_done_cb),
+					     g_object_ref (G_OBJECT (epage)), &lerror);
+
+	if (id == 0) {
+		TO_IMPLEMENT;
+		g_clear_error (&lerror);
+	}
+
+	g_free (base_dn);
+	g_free (filter);
+	g_free (attributes);
+}
+
+static void
+filter_clear_clicked_cb (GtkWidget *button, LdapSearchPage *epage)
+{
+	filter_editor_clear (FILTER_EDITOR (epage->priv->search_entry));
+}
+
+/**
+ * ldap_search_page_new:
+ * @bcnc:
+ * @base_dn: the base DN to put in the search page by default
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+ldap_search_page_new (BrowserConnection *bcnc, const gchar *base_dn)
+{
+	LdapSearchPage *epage;
+
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	epage = LDAP_SEARCH_PAGE (g_object_new (LDAP_SEARCH_PAGE_TYPE, NULL));
+	epage->priv->bcnc = g_object_ref ((GObject*) bcnc);
+
+	/* header */
+        GtkWidget *label;
+	gchar *str;
+
+	str = g_strdup_printf ("<b>%s</b>", _("LDAP search page"));
+	label = cc_gray_bar_new (str);
+	g_free (str);
+        gtk_box_pack_start (GTK_BOX (epage), label, FALSE, FALSE, 0);
+        gtk_widget_show (label);
+
+	/* search filter settings */
+	GtkWidget *hb, *bb, *button, *wid;
+	gchar *tmp;
+	gfloat ya;
+	tmp = g_markup_printf_escaped ("<b>%s:</b>", _("LDAP search settings"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), tmp);
+	g_free (tmp);
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_box_pack_start (GTK_BOX (epage), label, FALSE, FALSE, 0);
+
+	hb = gtk_hbox_new (FALSE, 0);
+	gtk_box_pack_start (GTK_BOX (epage), hb, FALSE, FALSE, 3);
+
+	wid = filter_editor_new (bcnc);
+	if (!base_dn)
+		base_dn = browser_connection_ldap_get_base_dn (bcnc);
+	filter_editor_set_settings (FILTER_EDITOR (wid), base_dn, NULL, NULL,
+				    GDA_LDAP_SEARCH_SUBTREE);
+	gtk_box_pack_start (GTK_BOX (hb), wid, TRUE, TRUE, 0);
+	epage->priv->search_entry = wid;
+
+	bb = gtk_vbutton_box_new ();
+	gtk_button_box_set_layout (GTK_BUTTON_BOX (bb), GTK_BUTTONBOX_END);
+	gtk_box_pack_start (GTK_BOX (hb), bb, FALSE, FALSE, 5);
+
+	button = browser_make_small_button (FALSE, FALSE, _("Clear"),
+                                            GTK_STOCK_CLEAR, _("Clear the search settings"));
+	gtk_box_pack_start (GTK_BOX (bb), button, TRUE, TRUE, 0);
+	g_signal_connect (button, "clicked",
+			  G_CALLBACK (filter_clear_clicked_cb), epage);
+
+	button = browser_make_small_button (FALSE, FALSE, _("Execute"),
+					    GTK_STOCK_EXECUTE, _("Execute LDAP search"));
+	gtk_box_pack_start (GTK_BOX (bb), button, TRUE, TRUE, 0);
+	g_signal_connect (button, "clicked",
+			  G_CALLBACK (filter_exec_clicked_cb), epage);
+
+	/* results */
+	tmp = g_markup_printf_escaped ("<b>%s:</b>", _("Results"));
+	label = gtk_label_new ("");
+	gtk_label_set_markup (GTK_LABEL (label), tmp);
+	g_free (tmp);
+	gtk_misc_get_alignment (GTK_MISC (label), NULL, &ya);
+	gtk_misc_set_alignment (GTK_MISC (label), 0., ya);
+	gtk_box_pack_start (GTK_BOX (epage), label, FALSE, FALSE, 5);
+
+	wid = ui_formgrid_new (NULL, TRUE, 0);
+	gtk_box_pack_start (GTK_BOX (epage), wid, TRUE, TRUE, 0);
+	epage->priv->result_view = wid;
+
+	gtk_widget_show_all ((GtkWidget*) epage);
+	gtk_widget_hide ((GtkWidget*) epage);
+
+	return (GtkWidget*) epage;
+}
+
+/*
+ * UI actions
+ */
+static void
+action_define_as_table_cb (G_GNUC_UNUSED GtkAction *action, LdapSearchPage *epage)
+{
+	GtkWidget *dlg;
+	gint res;
+	GtkWindow *parent;
+
+	parent = (GtkWindow*) gtk_widget_get_toplevel ((GtkWidget*) epage);
+
+	dlg = vtable_dialog_new (parent, epage->priv->bcnc);
+	res = gtk_dialog_run (GTK_DIALOG (dlg));
+	gtk_widget_hide (dlg);
+	if (res == GTK_RESPONSE_OK) {
+		gchar *base_dn, *filter, *attributes;
+		GdaLdapSearchScope scope;
+		GError *lerror = NULL;
+		const gchar *tname;
+		gboolean replace;
+		filter_editor_get_settings (FILTER_EDITOR (epage->priv->search_entry),
+					    &base_dn, &filter, &attributes, &scope);
+		tname = vtable_dialog_get_table_name (VTABLE_DIALOG (dlg));
+		replace = vtable_dialog_get_replace_if_exists (VTABLE_DIALOG (dlg));
+		if (replace)
+			browser_connection_undeclare_table (epage->priv->bcnc, tname, NULL);
+		if (! browser_connection_declare_table (epage->priv->bcnc, tname, base_dn, filter,
+							attributes, scope, &lerror)) {
+			browser_show_error (parent,
+					    _("Could not define virtual table for this LDAP search: %s"),
+					    lerror && lerror->message ? lerror->message : _("No detail"));
+			g_clear_error (&lerror);
+		}
+		else
+			browser_show_message (parent,
+					      _("Virtual table '%s' for this LDAP search has been defined"),
+					      tname);
+	}
+	gtk_widget_destroy (dlg);
+}
+/*
+static void
+action_class_back_cb (G_GNUC_UNUSED GtkAction *action, LdapSearchPage *epage)
+{
+	epage->priv->add_hist_item = FALSE;
+	if (epage->priv->current_hitem > 0) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (epage->priv->history_items, HistoryItem*,
+				       epage->priv->current_hitem - 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (epage->priv->search_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (epage->priv->search_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			search_view_set_current_class (SEARCH_VIEW (epage->priv->search_view),
+						       hitem->classname);
+	}
+	epage->priv->add_hist_item = TRUE;
+}
+
+static void
+action_class_forward_cb (G_GNUC_UNUSED GtkAction *action, LdapSearchPage *epage)
+{
+	epage->priv->add_hist_item = FALSE;
+	if ((epage->priv->current_hitem >=0) &&
+	    ((guint) epage->priv->current_hitem < epage->priv->history_items->len)) {
+		HistoryItem *hitem = NULL;
+		gboolean use_dn = TRUE;
+		hitem = g_array_index (epage->priv->history_items, HistoryItem*,
+				       epage->priv->current_hitem + 1);
+		if (hitem->reference) {
+			if (gtk_tree_row_reference_valid (hitem->reference)) {
+				GtkTreeSelection *sel;
+				GtkTreePath *path;
+				path = gtk_tree_row_reference_get_path (hitem->reference);
+				sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (epage->priv->search_view));
+				gtk_tree_selection_select_path (sel, path);
+				gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (epage->priv->search_view),
+							      path, NULL, TRUE, 0.5, 0.5);
+				gtk_tree_path_free (path);
+				use_dn = FALSE;
+			}
+			else {
+				gtk_tree_row_reference_free (hitem->reference);
+				hitem->reference = NULL;
+			}
+		}
+		if (use_dn)
+			search_view_set_current_class (SEARCH_VIEW (epage->priv->search_view),
+						       hitem->classname);
+	}
+	epage->priv->add_hist_item = TRUE;
+}
+
+*/
+static GtkActionEntry ui_actions[] = {
+	{ "LDAP", NULL, N_("_LDAP"), NULL, N_("LDAP"), NULL },
+	{ "DefineAsTable", BROWSER_STOCK_TABLE_ADD, N_("Define as table"), NULL, N_("Define search as a virtual table"),
+	  G_CALLBACK (action_define_as_table_cb)},
+	/*
+	{ "DnBack", GTK_STOCK_GO_BACK, N_("Previous class"), NULL, N_("Move back to previous LDAP class"),
+	  G_CALLBACK (action_class_back_cb)},
+	{ "DnForward", GTK_STOCK_GO_FORWARD, N_("Next class"), NULL, N_("Move to next LDAP class"),
+	  G_CALLBACK (action_class_forward_cb)},
+	*/
+};
+static const gchar *ui_actions_page =
+	"<ui>"
+	"  <menubar name='MenuBar'>"
+	"    <placeholder name='MenuExtension'>"
+        "      <menu name='LDAP' action='LDAP'>"
+        "        <menuitem name='DefineAsTable' action= 'DefineAsTable'/>"
+	/*"        <separator/>"
+        "        <menuitem name='DnBack' action= 'DnBack'/>"
+	  "        <menuitem name='DnForward' action= 'DnForward'/>"*/
+        "      </menu>"
+        "    </placeholder>"
+	"  </menubar>"
+	"  <toolbar name='ToolBar'>"
+	"    <separator/>"
+	"    <toolitem action='DefineAsTable'/>"
+	/*"    <toolitem action='DnBack'/>"
+	  "    <toolitem action='DnForward'/>"*/
+	"  </toolbar>"
+	"</ui>";
+
+static GtkActionGroup *
+ldap_search_page_page_get_actions_group (BrowserPage *page)
+{
+	LdapSearchPage *epage;
+
+	epage = LDAP_SEARCH_PAGE (page);
+	if (! epage->priv->agroup) {
+		GtkActionGroup *agroup;
+		agroup = gtk_action_group_new ("LdapLdapSearchPageActions");
+		gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
+		gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), page);
+		epage->priv->agroup = agroup;
+
+		GtkAction *action;
+		gchar *base_dn, *filter, *attributes;
+		gboolean act = FALSE;
+		action = gtk_action_group_get_action (agroup, "DefineAsTable");
+		filter_editor_get_settings (FILTER_EDITOR (epage->priv->search_entry),
+					    &base_dn, &filter, &attributes, NULL);
+		if ((base_dn && *base_dn) || (filter && *filter) || (attributes && *attributes))
+			act = TRUE;
+		g_free (base_dn);
+		g_free (filter);
+		g_free (attributes);
+		gtk_action_set_sensitive (action, act);
+
+		update_history_actions (epage);
+	}
+	
+	return g_object_ref (epage->priv->agroup);
+}
+
+static const gchar *
+ldap_search_page_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
+{
+	return ui_actions_page;
+}
+
+static GtkWidget *
+ldap_search_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
+{
+	LdapSearchPage *epage;
+	const gchar *tab_name;
+	GdkPixbuf *search_pixbuf;
+
+	epage = LDAP_SEARCH_PAGE (page);
+	search_pixbuf = gtk_widget_render_icon (GTK_WIDGET (page), GTK_STOCK_FIND, GTK_ICON_SIZE_MENU, NULL);
+	tab_name = _("LDAP search");
+	return browser_make_tab_label_with_pixbuf (tab_name,
+						   search_pixbuf,
+						   out_close_button ? TRUE : FALSE, out_close_button);
+}
diff --git a/tools/browser/ldap-browser/ldap-search-page.h b/tools/browser/ldap-browser/ldap-search-page.h
new file mode 100644
index 0000000..4d9d513
--- /dev/null
+++ b/tools/browser/ldap-browser/ldap-search-page.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __LDAP_SEARCH_PAGE_H__
+#define __LDAP_SEARCH_PAGE_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define LDAP_SEARCH_PAGE_TYPE            (ldap_search_page_get_type())
+#define LDAP_SEARCH_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, LDAP_SEARCH_PAGE_TYPE, LdapSearchPage))
+#define LDAP_LDAP_SEARCH_PAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, LDAP_SEARCH_PAGE_TYPE, LdapSearchPageClass))
+#define IS_LDAP_LDAP_SEARCH_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, LDAP_SEARCH_PAGE_TYPE))
+#define IS_LDAP_LDAP_LDAP_SEARCH_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LDAP_SEARCH_PAGE_TYPE))
+
+typedef struct _LdapSearchPage        LdapSearchPage;
+typedef struct _LdapSearchPageClass   LdapSearchPageClass;
+typedef struct _LdapSearchPagePrivate LdapSearchPagePrivate;
+
+struct _LdapSearchPage {
+	GtkVBox            parent;
+	LdapSearchPagePrivate *priv;
+};
+
+struct _LdapSearchPageClass {
+	GtkVBoxClass       parent_class;
+};
+
+GType        ldap_search_page_get_type       (void) G_GNUC_CONST;
+
+GtkWidget   *ldap_search_page_new            (BrowserConnection *bcnc, const gchar *base_dn);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/marshal.list b/tools/browser/ldap-browser/marshal.list
new file mode 100644
index 0000000..fc405d8
--- /dev/null
+++ b/tools/browser/ldap-browser/marshal.list
@@ -0,0 +1,27 @@
+# see glib-genmarshal(1) for a detailed description of the file format,
+# possible parameter types are:
+#   VOID        indicates   no   return   type,  or  no  extra
+#               parameters. if VOID is used as  the  parameter
+#               list, no additional parameters may be present.
+#   BOOLEAN     for boolean types (gboolean)
+#   CHAR        for signed char types (gchar)
+#   UCHAR       for unsigned char types (guchar)
+#   INT         for signed integer types (gint)
+#   UINT        for unsigned integer types (guint)
+#   LONG        for signed long integer types (glong)
+#   ULONG       for unsigned long integer types (gulong)
+#   ENUM        for enumeration types (gint)
+#   FLAGS       for flag enumeration types (guint)
+#   FLOAT       for single-precision float types (gfloat)
+#   DOUBLE      for double-precision float types (gdouble)
+#   STRING      for string types (gchar*)
+#   PARAM       for GParamSpec or derived types  (GParamSpec*)
+#   BOXED       for boxed (anonymous but reference counted) types (GBoxed*)
+#   POINTER     for anonymous pointer types (gpointer)
+#   OBJECT      for GObject or derived types (GObject*)
+#   NONE        deprecated alias for VOID
+#   BOOL        deprecated alias for BOOLEAN
+
+VOID:ENUM,STRING
+VOID:INT,ENUM,STRING
+VOID:STRING
diff --git a/tools/browser/ldap-browser/mgr-ldap-classes.c b/tools/browser/ldap-browser/mgr-ldap-classes.c
new file mode 100644
index 0000000..a554db7
--- /dev/null
+++ b/tools/browser/ldap-browser/mgr-ldap-classes.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <libgda/libgda.h>
+#include "mgr-ldap-classes.h"
+#include "gda-tree-node.h"
+#include <sqlite/virtual/gda-ldap-connection.h>
+
+struct _MgrLdapClassesPriv {
+	BrowserConnection *bcnc;
+	gchar *class;
+	gboolean flat;
+};
+
+static void mgr_ldap_classes_class_init (MgrLdapClassesClass *klass);
+static void mgr_ldap_classes_init       (MgrLdapClasses *tmgr1, MgrLdapClassesClass *klass);
+static void mgr_ldap_classes_dispose    (GObject *object);
+
+/* virtual methods */
+static GSList *mgr_ldap_classes_update_children (GdaTreeManager *manager, GdaTreeNode *node,
+						   const GSList *children_nodes,
+						   gboolean *out_error, GError **error);
+
+static GObjectClass *parent_class = NULL;
+
+/*
+ * MgrLdapClasses class implementation
+ * @klass:
+ */
+static void
+mgr_ldap_classes_class_init (MgrLdapClassesClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* virtual methods */
+	((GdaTreeManagerClass*) klass)->update_children = mgr_ldap_classes_update_children;
+
+	object_class->dispose = mgr_ldap_classes_dispose;
+}
+
+static void
+mgr_ldap_classes_init (MgrLdapClasses *mgr, G_GNUC_UNUSED MgrLdapClassesClass *klass)
+{
+	g_return_if_fail (MGR_IS_LDAP_CLASSES (mgr));
+	mgr->priv = g_new0 (MgrLdapClassesPriv, 1);
+}
+
+static void
+mgr_ldap_classes_dispose (GObject *object)
+{
+	MgrLdapClasses *mgr = (MgrLdapClasses *) object;
+
+	g_return_if_fail (MGR_IS_LDAP_CLASSES (mgr));
+
+	if (mgr->priv) {
+		if (mgr->priv->bcnc)
+			g_object_unref (mgr->priv->bcnc);
+		g_free (mgr->priv->class);
+		g_free (mgr->priv);
+		mgr->priv = NULL;
+	}
+
+	/* chain to parent class */
+	parent_class->dispose (object);
+}
+
+/**
+ * mgr_ldap_classes_get_type:
+ *
+ * Returns: the GType
+ */
+GType
+mgr_ldap_classes_get_type (void)
+{
+        static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+                static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+                static const GTypeInfo info = {
+                        sizeof (MgrLdapClassesClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) mgr_ldap_classes_class_init,
+                        NULL,
+                        NULL,
+                        sizeof (MgrLdapClasses),
+                        0,
+                        (GInstanceInitFunc) mgr_ldap_classes_init,
+			0
+                };
+
+                g_static_mutex_lock (&registering);
+                if (type == 0)
+                        type = g_type_register_static (GDA_TYPE_TREE_MANAGER, "_MgrLdapClasses", &info, 0);
+                g_static_mutex_unlock (&registering);
+        }
+        return type;
+}
+
+/*
+ * mgr_ldap_classes_new:
+ * @cnc: a #GdaConnection object
+ * @flat: %TRUE if listing all the classes, if %TRUE, then @classname is ignored.
+ * @classname: (allow-none): an LDAP class or %NULL for the "top" class
+ *
+ * Creates a new #GdaTreeManager object which will list the children classes
+ *
+ * Returns: (transfer full): a new #GdaTreeManager object
+ */
+GdaTreeManager*
+mgr_ldap_classes_new (BrowserConnection *bcnc, gboolean flat, const gchar *classname)
+{
+	MgrLdapClasses *mgr;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	mgr = (MgrLdapClasses*) g_object_new (MGR_TYPE_LDAP_CLASSES, NULL);
+
+	mgr->priv->bcnc = g_object_ref (bcnc);
+	mgr->priv->flat = flat;
+	if (!flat) {
+		if (classname)
+			mgr->priv->class = g_strdup (classname);
+	}
+		
+	return (GdaTreeManager*) mgr;
+}
+
+static GSList *
+mgr_ldap_classes_update_children_flat (MgrLdapClasses *mgr, GdaTreeNode *node,
+					 gboolean *out_error,
+					 GError **error);
+static GSList *
+mgr_ldap_classes_update_children_nonflat (MgrLdapClasses *mgr, GdaTreeNode *node,
+					    gboolean *out_error,
+					    GError **error);
+
+static GSList *
+mgr_ldap_classes_update_children (GdaTreeManager *manager, GdaTreeNode *node,
+				    G_GNUC_UNUSED const GSList *children_nodes, gboolean *out_error,
+				    GError **error)
+{
+	MgrLdapClasses *mgr = MGR_LDAP_CLASSES (manager);
+
+	if (mgr->priv->flat)
+		return mgr_ldap_classes_update_children_flat (mgr, node, out_error, error);
+	else
+		return mgr_ldap_classes_update_children_nonflat (mgr, node, out_error, error);
+
+}
+
+static gint
+class_sort_func (GdaLdapClass *lcl1, GdaLdapClass *lcl2)
+{
+	if (lcl1->kind == lcl2->kind)
+		return g_ascii_strcasecmp (lcl1->names[0], lcl2->names[0]);
+	else
+		return lcl1->kind - lcl2->kind;
+}
+
+static GSList *
+mgr_ldap_classes_update_children_nonflat (MgrLdapClasses *mgr, GdaTreeNode *node,
+					    gboolean *out_error,
+					    GError **error)
+{
+	gchar *real_class = NULL;
+	if (!mgr->priv->bcnc) {
+		g_set_error (error, GDA_TREE_MANAGER_ERROR, GDA_TREE_MANAGER_UNKNOWN_ERROR,
+			     _("No LDAP connection specified"));
+		if (out_error)
+			*out_error = TRUE;
+		goto onerror;
+	}
+
+	const GValue *cvalue;
+	cvalue = gda_tree_node_fetch_attribute (node, "kind");
+	if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_BOOLEAN) && g_value_get_boolean (cvalue))
+		return NULL;
+
+	if (node) {
+		/* looking for a dn in @node's attributes */
+		cvalue = gda_tree_node_fetch_attribute (node, "class");
+		if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING))
+			real_class = g_value_dup_string (cvalue);
+	}
+	if (!real_class)
+		real_class = g_strdup (mgr->priv->class);
+
+	GSList *classes_list;
+	gboolean list_to_free = FALSE;
+	if (real_class) {
+		GdaLdapClass *lcl;
+		lcl = browser_connection_get_class_info (mgr->priv->bcnc, real_class);
+		if (!lcl)
+			goto onerror;
+		classes_list = (GSList*) lcl->children;
+	}
+	else {
+		/* sort by kind */
+		classes_list = g_slist_copy ((GSList*) browser_connection_get_top_classes (mgr->priv->bcnc));
+		classes_list = g_slist_sort (classes_list, (GCompareFunc) class_sort_func);
+		list_to_free = TRUE;
+	}
+
+	GSList *list = NULL;
+	const GSList *child;
+	for (child = classes_list; child; child = child->next) {
+		GdaLdapClass *sub;
+		GdaTreeNode* snode;
+		GValue *value;
+		GdkPixbuf *pixbuf;
+
+		sub = (GdaLdapClass*) child->data;
+
+		snode = gda_tree_manager_create_node ((GdaTreeManager*) mgr, node, sub->names[0]);
+
+		/* class name */
+		g_value_set_string ((value = gda_value_new (G_TYPE_STRING)), sub->names[0]);
+		gda_tree_node_set_node_attribute (snode, "class", value, NULL);
+		gda_value_free (value);
+
+		/* icon */
+		pixbuf = browser_get_pixbuf_for_ldap_class (sub->kind);
+		value = gda_value_new (G_TYPE_OBJECT);
+		g_value_set_object (value, pixbuf);
+		gda_tree_node_set_node_attribute (snode, "icon", value, NULL);
+		gda_value_free (value);		
+
+		list = g_slist_prepend (list, snode);
+	}
+	if (list_to_free)
+		g_slist_free (classes_list);
+	return g_slist_reverse (list);
+
+ onerror:
+	g_free (real_class);
+	if (out_error)
+		*out_error = TRUE;
+	return NULL;
+}
+
+static void
+classes_foreach_func (GdaLdapClass *lcl, GSList **list)
+{
+	if (!g_slist_find (*list, lcl))
+		*list = g_slist_insert_sorted (*list, lcl, (GCompareFunc) class_sort_func);
+	g_slist_foreach (lcl->children, (GFunc) classes_foreach_func, list);
+}
+
+static GSList *
+mgr_ldap_classes_update_children_flat (MgrLdapClasses *mgr, GdaTreeNode *node,
+					 gboolean *out_error,
+					 GError **error)
+{
+	if (!mgr->priv->bcnc) {
+		g_set_error (error, GDA_TREE_MANAGER_ERROR, GDA_TREE_MANAGER_UNKNOWN_ERROR,
+			     _("No LDAP connection specified"));
+		if (out_error)
+			*out_error = TRUE;
+		goto onerror;
+	}
+
+	const GSList *top_classes_list;
+	GSList *list = NULL, *classes_list = NULL;
+	top_classes_list = browser_connection_get_top_classes (mgr->priv->bcnc);
+	for (list = (GSList*) top_classes_list; list; list = list->next) {
+		GdaLdapClass *lcl;
+		lcl = (GdaLdapClass*) list->data;
+		classes_list = g_slist_insert_sorted (classes_list, lcl, (GCompareFunc) class_sort_func);
+		g_slist_foreach (lcl->children, (GFunc) classes_foreach_func, &classes_list);
+	}
+
+	GSList *child;
+	GdaLdapClassKind kind = 0;
+	for (list = NULL, child = classes_list; child; child = child->next) {
+		GdaLdapClass *sub;
+		GdaTreeNode* snode;
+		GValue *value;
+		GdkPixbuf *pixbuf;
+
+		sub = (GdaLdapClass*) child->data;
+
+		if (kind != sub->kind) {
+			/* add extra node as separator */
+			const gchar *tmp;
+			tmp = browser_get_kind_for_ldap_class (sub->kind);
+			snode = gda_tree_manager_create_node ((GdaTreeManager*) mgr, node, tmp);
+			list = g_slist_prepend (list, snode);
+			kind = sub->kind;
+
+			/* marker */
+			g_value_set_boolean ((value = gda_value_new (G_TYPE_BOOLEAN)), TRUE);
+			gda_tree_node_set_node_attribute (snode, "kind", value, NULL);
+			gda_value_free (value);
+
+			/* icon */
+			pixbuf = browser_get_pixbuf_for_ldap_class (sub->kind);
+			value = gda_value_new (G_TYPE_OBJECT);
+			g_value_set_object (value, pixbuf);
+			gda_tree_node_set_node_attribute (snode, "icon", value, NULL);
+			gda_value_free (value);
+		}
+		snode = gda_tree_manager_create_node ((GdaTreeManager*) mgr, node, sub->names[0]);
+
+		/* class name */
+		g_value_set_string ((value = gda_value_new (G_TYPE_STRING)), sub->names[0]);
+		gda_tree_node_set_node_attribute (snode, "class", value, NULL);
+		gda_value_free (value);
+		
+		/* icon */
+		pixbuf = browser_get_pixbuf_for_ldap_class (sub->kind);
+		value = gda_value_new (G_TYPE_OBJECT);
+		g_value_set_object (value, pixbuf);
+		gda_tree_node_set_node_attribute (snode, "icon", value, NULL);
+		gda_value_free (value);	
+
+		list = g_slist_prepend (list, snode);
+	}
+	g_slist_free (classes_list);
+	return g_slist_reverse (list);
+
+ onerror:
+	if (out_error)
+		*out_error = TRUE;
+	return NULL;
+}
diff --git a/tools/browser/ldap-browser/mgr-ldap-classes.h b/tools/browser/ldap-browser/mgr-ldap-classes.h
new file mode 100644
index 0000000..d2329d9
--- /dev/null
+++ b/tools/browser/ldap-browser/mgr-ldap-classes.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MGR_LDAP_CLASSES_H__
+#define __MGR_LDAP_CLASSES_H__
+
+#include "../browser-connection.h"
+#include "gda-tree-manager.h"
+
+G_BEGIN_DECLS
+
+#define MGR_TYPE_LDAP_CLASSES            (mgr_ldap_classes_get_type())
+#define MGR_LDAP_CLASSES(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, MGR_TYPE_LDAP_CLASSES, MgrLdapClasses))
+#define MGR_LDAP_CLASSES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, MGR_TYPE_LDAP_CLASSES, MgrLdapClassesClass))
+#define MGR_IS_LDAP_CLASSES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE(obj, MGR_TYPE_LDAP_CLASSES))
+#define MGR_IS_LDAP_CLASSES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MGR_TYPE_LDAP_CLASSES))
+#define MGR_LDAP_CLASSES_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), MGR_TYPE_LDAP_CLASSES, MgrLdapClassesClass))
+
+typedef struct _MgrLdapClasses MgrLdapClasses;
+typedef struct _MgrLdapClassesPriv MgrLdapClassesPriv;
+typedef struct _MgrLdapClassesClass MgrLdapClassesClass;
+
+struct _MgrLdapClasses {
+	GdaTreeManager      object;
+	MgrLdapClassesPriv *priv;
+};
+
+struct _MgrLdapClassesClass {
+	GdaTreeManagerClass object_class;
+};
+
+GType              mgr_ldap_classes_get_type  (void) G_GNUC_CONST;
+GdaTreeManager*    mgr_ldap_classes_new       (BrowserConnection *bcnc, gboolean flat, const gchar *classname);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/mgr-ldap-entries.c b/tools/browser/ldap-browser/mgr-ldap-entries.c
new file mode 100644
index 0000000..44b3ed6
--- /dev/null
+++ b/tools/browser/ldap-browser/mgr-ldap-entries.c
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <libgda/libgda.h>
+#include "mgr-ldap-entries.h"
+#include <libgda/gda-tree-node.h>
+
+struct _MgrLdapEntriesPriv {
+	BrowserConnection *bcnc;
+	gchar             *dn;
+};
+
+static void mgr_ldap_entries_class_init (MgrLdapEntriesClass *klass);
+static void mgr_ldap_entries_init       (MgrLdapEntries *tmgr1, MgrLdapEntriesClass *klass);
+static void mgr_ldap_entries_dispose    (GObject *object);
+
+/* virtual methods */
+static GSList *mgr_ldap_entries_update_children (GdaTreeManager *manager, GdaTreeNode *node,
+						      const GSList *children_nodes,
+						      gboolean *out_error, GError **error);
+
+static GObjectClass *parent_class = NULL;
+
+/*
+ * MgrLdapEntries class implementation
+ * @klass:
+ */
+static void
+mgr_ldap_entries_class_init (MgrLdapEntriesClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* virtual methods */
+	((GdaTreeManagerClass*) klass)->update_children = mgr_ldap_entries_update_children;
+
+	/* Properties */
+	object_class->dispose = mgr_ldap_entries_dispose;
+}
+
+static void
+mgr_ldap_entries_init (MgrLdapEntries *mgr, G_GNUC_UNUSED MgrLdapEntriesClass *klass)
+{
+	g_return_if_fail (MGR_IS_LDAP_ENTRIES (mgr));
+	mgr->priv = g_new0 (MgrLdapEntriesPriv, 1);
+}
+
+static void
+mgr_ldap_entries_dispose (GObject *object)
+{
+	MgrLdapEntries *mgr = (MgrLdapEntries *) object;
+
+	g_return_if_fail (MGR_IS_LDAP_ENTRIES (mgr));
+
+	if (mgr->priv) {
+		if (mgr->priv->bcnc)
+			g_object_unref (mgr->priv->bcnc);
+		g_free (mgr->priv->dn);
+		g_free (mgr->priv);
+		mgr->priv = NULL;
+	}
+
+	/* chain to parent class */
+	parent_class->dispose (object);
+}
+
+/**
+ * browser_tree_mgr_select_get_type:
+ *
+ * Returns: the GType
+ */
+GType
+mgr_ldap_entries_get_type (void)
+{
+        static GType type = 0;
+
+        if (G_UNLIKELY (type == 0)) {
+                static GStaticMutex registering = G_STATIC_MUTEX_INIT;
+                static const GTypeInfo info = {
+                        sizeof (MgrLdapEntriesClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) mgr_ldap_entries_class_init,
+                        NULL,
+                        NULL,
+                        sizeof (MgrLdapEntries),
+                        0,
+                        (GInstanceInitFunc) mgr_ldap_entries_init,
+			0
+                };
+
+                g_static_mutex_lock (&registering);
+                if (type == 0)
+                        type = g_type_register_static (GDA_TYPE_TREE_MANAGER, "MgrLdapEntries", &info, 0);
+                g_static_mutex_unlock (&registering);
+        }
+        return type;
+}
+
+/**
+ * mgr_ldap_entries_new:
+ * @cnc: a #BrowserConnection object
+ * @dn: (allow-none): a schema name or %NULL
+ *
+ * Creates a new #BrowserTreeManager object which will list the children of the LDAP entry which Distinguished name
+ * is @dn. If @dn is %NULL, then the tree manager will look in the tree itself for an attribute named "dn" and
+ * use it.
+ *
+ * Returns: (transfer full): a new #BrowserTreeManager object
+ */
+GdaTreeManager*
+mgr_ldap_entries_new (BrowserConnection *bcnc, const gchar *dn)
+{
+	MgrLdapEntries *mgr;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	mgr = (MgrLdapEntries*) g_object_new (MGR_TYPE_LDAP_ENTRIES, NULL);
+
+	mgr->priv->bcnc = g_object_ref (bcnc);
+	if (dn)
+		mgr->priv->dn = g_strdup (dn);
+
+	return (GdaTreeManager*) mgr;
+}
+
+typedef struct {
+	GMainLoop     *loop;
+	GdaLdapEntry **entries;
+	GError        *error;
+} AsyncExecData;
+
+static void
+update_children_cb (G_GNUC_UNUSED BrowserConnection *bcnc,
+		    gpointer out_result, AsyncExecData *data, GError *error)
+{
+	data->entries = (GdaLdapEntry **) out_result;
+	if (! data->entries && error)
+		data->error = g_error_copy (error);
+	g_main_loop_quit (data->loop);
+}
+
+static gint
+lentry_array_sort_func (gconstpointer a, gconstpointer b)
+{
+        GdaLdapEntry *e1, *e2;
+        e1 = *((GdaLdapEntry**) a);
+        e2 = *((GdaLdapEntry**) b);
+	GdaLdapAttribute *cna1, *cna2;
+	const gchar *str1 = NULL, *str2 = NULL;
+
+	cna1 = g_hash_table_lookup (e1->attributes_hash, "cn");
+	cna2 = g_hash_table_lookup (e2->attributes_hash, "cn");
+	if (cna1 && (cna1->nb_values > 0) && (G_VALUE_TYPE (cna1->values[0]) == G_TYPE_STRING))
+		str1 = g_value_get_string (cna1->values[0]);
+	else
+		str1 = e1->dn ? e1->dn : "";
+	if (cna2 && (cna2->nb_values > 0) && (G_VALUE_TYPE (cna2->values[0]) == G_TYPE_STRING))
+		str2 = g_value_get_string (cna2->values[0]);
+	else
+		str2 = e2->dn ? e2->dn : "";
+
+	return strcmp (str2, str1);
+}
+
+static GSList *
+mgr_ldap_entries_update_children (GdaTreeManager *manager, GdaTreeNode *node,
+				       G_GNUC_UNUSED const GSList *children_nodes, gboolean *out_error,
+				       GError **error)
+{
+	MgrLdapEntries *mgr = MGR_LDAP_ENTRIES (manager);
+	gchar *real_dn = NULL;
+
+	g_return_val_if_fail (mgr->priv->bcnc, NULL);
+
+	if (mgr->priv->dn)
+		real_dn = g_strdup (mgr->priv->dn);
+	else if (node) {
+		/* looking for a dn in @node's attributes */
+		const GValue *cvalue;
+		cvalue = gda_tree_node_fetch_attribute (node, "dn");
+		if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING))
+			real_dn = g_value_dup_string (cvalue);
+	}
+
+	AsyncExecData data;
+	guint id;
+	data.loop = NULL;
+	data.entries = NULL;
+	data.error = NULL;
+	gchar *attrs[] = {"objectClass", "cn", NULL};
+	
+	id = browser_connection_ldap_get_entry_children (mgr->priv->bcnc, real_dn, attrs,
+							 BROWSER_CONNECTION_JOB_CALLBACK (update_children_cb),
+							 &data, error);
+	g_free (real_dn);
+	if (id == 0) {
+		if (out_error)
+			*out_error = TRUE;
+		return NULL;
+	}
+	data.loop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (data.loop);
+	g_main_loop_unref (data.loop);
+	
+	if (data.entries) {
+		gint i;
+		GSList *list = NULL;
+		GArray *sorted_array;
+		sorted_array = g_array_new (FALSE, FALSE, sizeof (GdaLdapEntry*));
+		for (i = 0; data.entries [i]; i++) {
+			GdaLdapEntry *lentry;
+			lentry = data.entries [i];
+			g_array_prepend_val (sorted_array, lentry);
+		}
+		g_free (data.entries);
+
+		g_array_sort (sorted_array, (GCompareFunc) lentry_array_sort_func);
+
+		for (i = 0; i < sorted_array->len; i++) {
+			GdaTreeNode* snode;
+			GValue *dnv;
+			GdaLdapEntry *lentry;
+
+			lentry = g_array_index (sorted_array, GdaLdapEntry*, i);
+			snode = gda_tree_manager_create_node (manager, node, lentry->dn);
+
+			/* full DN */
+			g_value_set_string ((dnv = gda_value_new (G_TYPE_STRING)), lentry->dn);
+			gda_tree_node_set_node_attribute (snode, "dn", dnv, NULL);
+			gda_value_free (dnv);
+
+			/* RDN */
+                        gchar **array;
+                        array = gda_ldap_dn_split (lentry->dn, FALSE);
+                        if (array) {
+                                g_value_set_string ((dnv = gda_value_new (G_TYPE_STRING)), array [0]);
+                                gda_tree_node_set_node_attribute (snode, "rdn", dnv, NULL);
+				gda_value_free (dnv);
+                                g_strfreev (array);
+                        }
+
+			/* CN */
+			GdaLdapAttribute *attr;
+			attr = g_hash_table_lookup (lentry->attributes_hash, "cn");
+			if (attr && (attr->nb_values >= 1)) {
+				const GValue *cvalue;
+				cvalue = attr->values [0];
+				if (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING))
+					gda_tree_node_set_node_attribute (snode, "cn", cvalue, NULL);
+			}
+
+			/* icon */
+			GdkPixbuf *pixbuf;
+			attr = g_hash_table_lookup (lentry->attributes_hash, "objectClass");
+			pixbuf = browser_connection_ldap_icon_for_class (attr);
+
+			dnv = gda_value_new (G_TYPE_OBJECT);
+			g_value_set_object (dnv, pixbuf);
+			gda_tree_node_set_node_attribute (snode, "icon", dnv, NULL);
+			gda_value_free (dnv);
+
+			if (gda_tree_manager_get_managers (manager)) {
+				g_value_set_boolean ((dnv = gda_value_new (G_TYPE_BOOLEAN)), TRUE);
+				gda_tree_node_set_node_attribute (snode,
+								  GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN,
+								  dnv, NULL);
+				gda_value_free (dnv);
+			}
+
+			list = g_slist_prepend (list, snode);
+			gda_ldap_entry_free (lentry);
+		}
+		g_array_free (sorted_array, TRUE);
+
+		if (node)
+			gda_tree_node_set_node_attribute (node,
+							  GDA_ATTRIBUTE_TREE_NODE_UNKNOWN_CHILDREN,
+							  NULL, NULL);
+
+		return list;
+	}
+	else {
+		g_propagate_error (error, data.error);
+		if (out_error)
+			*out_error = TRUE;
+		return NULL;
+	}
+}
diff --git a/tools/browser/ldap-browser/mgr-ldap-entries.h b/tools/browser/ldap-browser/mgr-ldap-entries.h
new file mode 100644
index 0000000..09dbc71
--- /dev/null
+++ b/tools/browser/ldap-browser/mgr-ldap-entries.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MGR_LDAP_ENTRIES_H__
+#define __MGR_LDAP_ENTRIES_H__
+
+#include "../browser-connection.h"
+#include <libgda/gda-tree-manager.h>
+
+G_BEGIN_DECLS
+
+#define MGR_TYPE_LDAP_ENTRIES            (mgr_ldap_entries_get_type())
+#define MGR_LDAP_ENTRIES(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, MGR_TYPE_LDAP_ENTRIES, MgrLdapEntries))
+#define MGR_LDAP_ENTRIES_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, MGR_TYPE_LDAP_ENTRIES, MgrLdapEntriesClass))
+#define MGR_IS_LDAP_ENTRIES(obj)         (G_TYPE_CHECK_INSTANCE_TYPE(obj, MGR_TYPE_LDAP_ENTRIES))
+#define MGR_IS_LDAP_ENTRIES_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MGR_TYPE_LDAP_ENTRIES))
+#define MGR_LDAP_ENTRIES_GET_CLASS(o)    (G_TYPE_INSTANCE_GET_CLASS ((o), MGR_TYPE_LDAP_ENTRIES, MgrLdapEntriesClass))
+
+typedef struct _MgrLdapEntries MgrLdapEntries;
+typedef struct _MgrLdapEntriesPriv MgrLdapEntriesPriv;
+typedef struct _MgrLdapEntriesClass MgrLdapEntriesClass;
+
+struct _MgrLdapEntries {
+	GdaTreeManager          object;
+	MgrLdapEntriesPriv *priv;
+};
+
+struct _MgrLdapEntriesClass {
+	GdaTreeManagerClass     object_class;
+};
+
+GType           mgr_ldap_entries_get_type  (void) G_GNUC_CONST;
+GdaTreeManager* mgr_ldap_entries_new       (BrowserConnection *bcnc, const gchar *dn);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/ldap-browser/perspective-main.c b/tools/browser/ldap-browser/perspective-main.c
new file mode 100644
index 0000000..770bf5a
--- /dev/null
+++ b/tools/browser/ldap-browser/perspective-main.c
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include "perspective-main.h"
+#include "ldap-browser-perspective.h"
+
+static BrowserPerspectiveFactory bfact;
+
+BrowserPerspectiveFactory *
+ldap_browser_perspective_get_factory (void)
+{
+	bfact.perspective_name = _("LDAP browser");
+	bfact.menu_shortcut = "<control>P";
+	bfact.perspective_create = ldap_browser_perspective_new;
+
+	return &bfact;
+}
diff --git a/tools/browser/ldap-browser/perspective-main.h b/tools/browser/ldap-browser/perspective-main.h
new file mode 100644
index 0000000..1a29d5b
--- /dev/null
+++ b/tools/browser/ldap-browser/perspective-main.h
@@ -0,0 +1,29 @@
+/* 
+ * Copyright (C) 2011 The GNOME Foundation.
+ *
+ * AUTHORS:
+ * 	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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "../decl.h"
+
+G_BEGIN_DECLS
+
+BrowserPerspectiveFactory *ldap_browser_perspective_get_factory (void);
+
+G_END_DECLS
+
diff --git a/tools/browser/ldap-browser/vtable-dialog.c b/tools/browser/ldap-browser/vtable-dialog.c
new file mode 100644
index 0000000..277b8f9
--- /dev/null
+++ b/tools/browser/ldap-browser/vtable-dialog.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include "vtable-dialog.h"
+
+#define SPACING 3
+
+struct _VtableDialogPrivate {
+	BrowserConnection *bcnc;
+	GtkWidget *tname_entry;
+	GtkWidget *tname_replace;
+};
+
+static void vtable_dialog_class_init (VtableDialogClass *klass);
+static void vtable_dialog_init       (VtableDialog *dlg, VtableDialogClass *klass);
+static void vtable_dialog_dispose   (GObject *object);
+
+static GObjectClass *parent_class = NULL;
+
+
+/*
+ * VtableDialog class implementation
+ */
+
+static void
+vtable_dialog_class_init (VtableDialogClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->dispose = vtable_dialog_dispose;
+}
+
+
+static void
+vtable_dialog_init (VtableDialog *dlg, G_GNUC_UNUSED VtableDialogClass *klass)
+{
+	dlg->priv = g_new0 (VtableDialogPrivate, 1);
+}
+
+static void
+vtable_dialog_dispose (GObject *object)
+{
+	VtableDialog *dlg = (VtableDialog *) object;
+
+	/* free memory */
+	if (dlg->priv) {
+		if (dlg->priv->bcnc)
+			g_object_unref (dlg->priv->bcnc);
+		g_free (dlg->priv);
+		dlg->priv = NULL;
+	}
+
+	parent_class->dispose (object);
+}
+
+GType
+vtable_dialog_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo columns = {
+			sizeof (VtableDialogClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) vtable_dialog_class_init,
+			NULL,
+			NULL,
+			sizeof (VtableDialog),
+			0,
+			(GInstanceInitFunc) vtable_dialog_init,
+			0
+		};
+		type = g_type_register_static (GTK_TYPE_DIALOG, "VtableDialog", &columns, 0);
+	}
+	return type;
+}
+
+/**
+ * vtable_dialog_new:
+ *
+ * Returns: a new #GtkWidget
+ */
+GtkWidget *
+vtable_dialog_new (GtkWindow *parent, BrowserConnection *bcnc)
+{
+	VtableDialog *dlg;
+	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
+
+	dlg = VTABLE_DIALOG (g_object_new (VTABLE_DIALOG_TYPE, NULL));
+	dlg->priv->bcnc = g_object_ref (bcnc);
+
+	if (parent)
+		gtk_window_set_transient_for (GTK_WINDOW (dlg), parent);
+	gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+	gtk_container_set_border_width (GTK_CONTAINER (dlg), SPACING * 2);
+	gtk_window_set_title (GTK_WINDOW (dlg), _("Define LDAP search as a virtual table"));
+
+	GtkWidget *dcontents;
+	GtkWidget *label, *entry, *table, *button;
+	gchar *str;
+	dcontents = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
+	label = gtk_label_new (NULL);
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	str = g_markup_printf_escaped ("<b>%s:</b>\n<small>%s</small>",
+				       _("Name of the virtual LDAP table to create"),
+				       _("Everytime data is selected from the virtual table which will "
+					 "be created, the LDAP search will be executed and data "
+					 "returned as the contents of the table."));
+	gtk_label_set_markup (GTK_LABEL (label), str);
+	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+	g_free (str);
+	gtk_box_pack_start (GTK_BOX (dcontents), label, FALSE, FALSE, SPACING);
+
+	table = gtk_table_new (2, 2, FALSE);
+	gtk_table_set_col_spacing (GTK_TABLE (table), 0, SPACING);
+	gtk_table_set_row_spacing (GTK_TABLE (table), 0, SPACING);
+	gtk_box_pack_start (GTK_BOX (dcontents), table, FALSE, FALSE, SPACING);
+
+	label = gtk_label_new (_("Table name:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+	entry = gtk_entry_new ();
+	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
+	dlg->priv->tname_entry = entry;
+
+	label = gtk_label_new (_("Replace if exists:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+	gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+	button = gtk_check_button_new ();
+	gtk_table_attach_defaults (GTK_TABLE (table), button, 1, 2, 1, 2);
+	dlg->priv->tname_replace = button;
+
+	gtk_widget_show_all (dcontents);
+	gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+				GTK_STOCK_OK, GTK_RESPONSE_OK,
+				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+
+	return (GtkWidget*) dlg;
+}
+
+/**
+ * vtable_dialog_get_table_name:
+ *
+ */
+const gchar *
+vtable_dialog_get_table_name (VtableDialog *dlg)
+{
+	g_return_val_if_fail (IS_VTABLE_DIALOG (dlg), NULL);
+	return gtk_entry_get_text (GTK_ENTRY (dlg->priv->tname_entry));
+}
+
+/**
+ * vtable_dialog_get_replace_if_exists:
+ */
+gboolean
+vtable_dialog_get_replace_if_exists (VtableDialog *dlg)
+{
+	g_return_val_if_fail (IS_VTABLE_DIALOG (dlg), FALSE);
+	return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dlg->priv->tname_replace));
+}
diff --git a/tools/browser/ldap-browser/vtable-dialog.h b/tools/browser/ldap-browser/vtable-dialog.h
new file mode 100644
index 0000000..db265f9
--- /dev/null
+++ b/tools/browser/ldap-browser/vtable-dialog.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __VTABLE_DIALOG_H__
+#define __VTABLE_DIALOG_H__
+
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define VTABLE_DIALOG_TYPE            (vtable_dialog_get_type())
+#define VTABLE_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, VTABLE_DIALOG_TYPE, VtableDialog))
+#define VTABLE_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, VTABLE_DIALOG_TYPE, VtableDialogClass))
+#define IS_VTABLE_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, VTABLE_DIALOG_TYPE))
+#define IS_VTABLE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), VTABLE_DIALOG_TYPE))
+
+typedef struct _VtableDialog        VtableDialog;
+typedef struct _VtableDialogClass   VtableDialogClass;
+typedef struct _VtableDialogPrivate VtableDialogPrivate;
+
+struct _VtableDialog {
+	GtkDialog            parent;
+	VtableDialogPrivate *priv;
+};
+
+struct _VtableDialogClass {
+	GtkDialogClass       parent_class;
+};
+
+GType           vtable_dialog_get_type       (void) G_GNUC_CONST;
+
+GtkWidget      *vtable_dialog_new            (GtkWindow *parent, BrowserConnection *bcnc);
+const gchar    *vtable_dialog_get_table_name (VtableDialog *dlg);
+gboolean        vtable_dialog_get_replace_if_exists (VtableDialog *dlg);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/main.c b/tools/browser/main.c
index ccc78a5..76a9571 100644
--- a/tools/browser/main.c
+++ b/tools/browser/main.c
@@ -41,6 +41,9 @@
 #include "schema-browser/perspective-main.h"
 #include "query-exec/perspective-main.h"
 #include "data-manager/perspective-main.h"
+#ifdef HAVE_LDAP
+#include "ldap-browser/perspective-main.h"
+#endif
 /* #include "dummy-perspective/perspective-main.h" */
 
 extern BrowserCoreInitFactories browser_core_init_factories;
@@ -52,6 +55,9 @@ main_browser_core_init_factories (void)
 	factories = g_slist_append (factories, schema_browser_perspective_get_factory ());
 	factories = g_slist_append (factories, query_exec_perspective_get_factory ());
 	factories = g_slist_append (factories, data_manager_perspective_get_factory ());
+#ifdef HAVE_LDAP
+	factories = g_slist_append (factories, ldap_browser_perspective_get_factory ());
+#endif
 	/* factories = g_slist_append (factories, dummy_perspective_get_factory ()); */
 	return factories;
 }
@@ -246,7 +252,7 @@ main (int argc, char *argv[])
 					}
 				}
 			}
-			gtk_widget_destroy (dialog);
+			gtk_widget_destroy ((GtkWidget*) dialog);
 		}
 	}
 	
diff --git a/tools/browser/mgr-favorites.c b/tools/browser/mgr-favorites.c
index 52a796a..0c38004 100644
--- a/tools/browser/mgr-favorites.c
+++ b/tools/browser/mgr-favorites.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 The GNOME Foundation.
+ * Copyright (C) 2009 - 2011 The GNOME Foundation.
  *
  * AUTHORS:
  *      Vivien Malerba <malerba gnome-db org>
@@ -27,10 +27,26 @@
 #include "mgr-favorites.h"
 #include "support.h"
 
+/* asynchronous (in idle loop) icon resolution */
+typedef struct {
+        GdaTreeNode *node;
+        gchar *dn;
+} IconResolutionData;
+static void
+icon_resolution_data_free (IconResolutionData *data)
+{
+        g_object_unref (G_OBJECT (data->node));
+        g_free (data->dn);
+        g_free (data);
+}
+
 struct _MgrFavoritesPriv {
 	BrowserConnection    *bcnc;
 	BrowserFavoritesType  fav_type;
 	gint                  order_key;
+
+	GSList               *icons_resol_list; /* list of IconResolutionData pointers */
+        guint                 icons_resol_timer;
 };
 
 static void mgr_favorites_class_init (MgrFavoritesClass *klass);
@@ -97,6 +113,17 @@ mgr_favorites_dispose (GObject *object)
 		if (mgr->priv->bcnc)
 			g_object_unref (mgr->priv->bcnc);
 
+		if (mgr->priv->icons_resol_timer) {
+                        g_source_remove (mgr->priv->icons_resol_timer);
+                        mgr->priv->icons_resol_timer = 0;
+                }
+
+                if (mgr->priv->icons_resol_list) {
+                        g_slist_foreach (mgr->priv->icons_resol_list, (GFunc) icon_resolution_data_free, NULL);
+                        g_slist_free (mgr->priv->icons_resol_list);
+                        mgr->priv->icons_resol_list = NULL;
+                }
+
 		g_free (mgr->priv);
 		mgr->priv = NULL;
 	}
@@ -328,6 +355,8 @@ hash_for_existing_nodes (const GSList *nodes)
 	return hash;
 }
 
+static gboolean icons_resol_cb (MgrFavorites *mgr);
+
 static GSList *
 mgr_favorites_update_children (GdaTreeManager *manager, GdaTreeNode *node, const GSList *children_nodes,
 			       gboolean *out_error, GError **error)
@@ -356,14 +385,17 @@ mgr_favorites_update_children (GdaTreeManager *manager, GdaTreeNode *node, const
 			BrowserFavoritesAttributes *fav = (BrowserFavoritesAttributes *) list->data;
 			GdaTreeNode* snode = NULL;
 			GValue *av;
+			gboolean newsnode = TRUE;
 
 			if (ehash)
 				snode = g_hash_table_lookup (ehash, &(fav->id));
 
 			
-			if (snode)
+			if (snode) {
 				/* use the same node */
 				g_object_ref (G_OBJECT (snode));
+				newsnode = FALSE;
+			}
 
 			if (fav->type == BROWSER_FAVORITES_TABLES) {
 				if (!snode) {
@@ -557,6 +589,159 @@ mgr_favorites_update_children (GdaTreeManager *manager, GdaTreeNode *node, const
 								  av, NULL);
 				gda_value_free (av);
 			}
+#ifdef HAVE_LDAP
+			else if (fav->type == BROWSER_FAVORITES_LDAP_DN) {
+				if (!snode) {
+					/* favorite ID */
+					snode = gda_tree_manager_create_node (manager, node, NULL);
+
+					g_value_set_int ((av = gda_value_new (G_TYPE_INT)), fav->id);
+					gda_tree_node_set_node_attribute (snode,
+									  MGR_FAVORITES_ID_ATT_NAME,
+									  av, NULL);
+					gda_value_free (av);
+
+					/* icon */
+					GdkPixbuf *pixbuf;
+					pixbuf = browser_get_pixbuf_icon (BROWSER_ICON_LDAP_ENTRY);
+					
+					av = gda_value_new (G_TYPE_OBJECT);
+					g_value_set_object (av, pixbuf);
+					gda_tree_node_set_node_attribute (snode, "icon", av, NULL);
+					gda_value_free (av);
+
+					g_value_set_uint ((av = gda_value_new (G_TYPE_UINT)), fav->type);
+					gda_tree_node_set_node_attribute (snode,
+									  MGR_FAVORITES_TYPE_ATT_NAME,
+									  av, NULL);
+                                        gda_value_free (av);
+					
+					IconResolutionData *data;
+					data = g_new0 (IconResolutionData, 1);
+					data->node = g_object_ref (snode);
+					data->dn = g_strdup (fav->name);
+					mgr->priv->icons_resol_list = g_slist_prepend (mgr->priv->icons_resol_list, data);
+					if (mgr->priv->icons_resol_timer == 0)
+						mgr->priv->icons_resol_timer = g_idle_add ((GSourceFunc) icons_resol_cb, mgr);
+				}
+
+				gchar **dna;
+				GString *tmpstring;
+				dna = gda_ldap_dn_split (fav->name, FALSE);
+				tmpstring = g_string_new ("");
+				if (dna) {
+					if (dna[0]) {
+						if (dna[1])
+							g_string_append_printf (tmpstring,
+										"<b>%s</b>,%s",
+										dna[0], dna[1]);
+						else
+							g_string_append_printf (tmpstring,
+										"<b>%s</b>",
+										dna[0]);
+					}
+					else {
+						gchar *tmp;
+						tmp = g_markup_escape_text (fav->name, -1);
+						g_string_append (tmpstring, tmp);
+						g_free (tmp);
+					}
+					g_strfreev (dna);
+				}
+				else {
+					gchar *tmp;
+					tmp = g_markup_escape_text (fav->name, -1);
+					g_string_append (tmpstring, tmp);
+					g_free (tmp);
+				}
+				
+				if (fav->descr && *fav->descr) {
+					gchar *tmp;
+					tmp = g_markup_escape_text (fav->descr, -1);
+					g_string_append_printf (tmpstring,
+								"\n<small>%s</small>", tmp);
+					g_free (tmp);
+
+					g_value_set_string ((av = gda_value_new (G_TYPE_STRING)),
+							    fav->descr);
+					gda_tree_node_set_node_attribute (snode, "descr", av, NULL);
+					gda_value_free (av);
+				}
+				
+				g_value_take_string ((av = gda_value_new (G_TYPE_STRING)),
+						     g_string_free (tmpstring, FALSE));
+				gda_tree_node_set_node_attribute (snode, "markup", av, NULL);
+				gda_value_free (av);				
+
+				g_value_set_string ((av = gda_value_new (G_TYPE_STRING)),
+						    fav->name);
+				gda_tree_node_set_node_attribute (snode,
+								  MGR_FAVORITES_CONTENTS_ATT_NAME,
+								  av, NULL);
+				gda_value_free (av);
+			}
+			else if (fav->type == BROWSER_FAVORITES_LDAP_CLASS) {
+				if (!snode) {
+					/* favorite ID */
+					snode = gda_tree_manager_create_node (manager, node, NULL);
+
+					g_value_set_int ((av = gda_value_new (G_TYPE_INT)), fav->id);
+					gda_tree_node_set_node_attribute (snode,
+									  MGR_FAVORITES_ID_ATT_NAME,
+									  av, NULL);
+					gda_value_free (av);
+
+					/* icon */
+					GdkPixbuf *pixbuf;
+					GdaLdapClass *lcl;
+					lcl = browser_connection_get_class_info (bcnc, fav->name);
+					pixbuf = browser_get_pixbuf_for_ldap_class (lcl ? lcl->kind : GDA_LDAP_CLASS_KIND_UNKNOWN);					
+					av = gda_value_new (G_TYPE_OBJECT);
+					g_value_set_object (av, pixbuf);
+					gda_tree_node_set_node_attribute (snode, "icon", av, NULL);
+					gda_value_free (av);
+
+					g_value_set_uint ((av = gda_value_new (G_TYPE_UINT)), fav->type);
+                                                gda_tree_node_set_node_attribute (snode,
+                                                                                  MGR_FAVORITES_TYPE_ATT_NAME,
+                                                                                  av, NULL);
+                                        gda_value_free (av);
+				}
+
+				GString *tmpstring;
+				gchar *tmp;
+				
+				tmpstring = g_string_new ("");
+				tmp = g_markup_escape_text (fav->name, -1);
+				g_string_append (tmpstring, tmp);
+				g_free (tmp);
+				
+				if (fav->descr && *fav->descr) {
+					gchar *tmp;
+					tmp = g_markup_escape_text (fav->descr, -1);
+					g_string_append_printf (tmpstring,
+								"\n<small>%s</small>", tmp);
+					g_free (tmp);
+
+					g_value_set_string ((av = gda_value_new (G_TYPE_STRING)),
+							    fav->descr);
+					gda_tree_node_set_node_attribute (snode, "descr", av, NULL);
+					gda_value_free (av);
+				}
+				
+				g_value_take_string ((av = gda_value_new (G_TYPE_STRING)),
+						     g_string_free (tmpstring, FALSE));
+				gda_tree_node_set_node_attribute (snode, "markup", av, NULL);
+				gda_value_free (av);				
+
+				g_value_set_string ((av = gda_value_new (G_TYPE_STRING)),
+						    fav->name);
+				gda_tree_node_set_node_attribute (snode,
+								  MGR_FAVORITES_CONTENTS_ATT_NAME,
+								  av, NULL);
+				gda_value_free (av);
+			}
+#endif
 			else {
 				TO_IMPLEMENT;
 			}
@@ -599,3 +784,45 @@ mgr_favorites_update_children (GdaTreeManager *manager, GdaTreeNode *node, const
 
 	return g_slist_reverse (nodes_list);
 }
+
+static void
+icon_fetched_cb (G_GNUC_UNUSED BrowserConnection *bcnc,
+		 GdkPixbuf *pixbuf, GdaTreeNode *node, G_GNUC_UNUSED GError *error)
+{
+	if (pixbuf) {
+		GValue *av;
+		av = gda_value_new (G_TYPE_OBJECT);
+		g_value_set_object (av, pixbuf);
+		gda_tree_node_set_node_attribute (node, "icon", av, NULL);
+		gda_value_free (av);
+	}
+	g_object_unref (node);
+}
+
+#ifdef HAVE_LDAP
+static gboolean
+icons_resol_cb (MgrFavorites *mgr)
+{
+        if (mgr->priv->icons_resol_timer == 0)
+                return FALSE;
+        if (mgr->priv->icons_resol_list) {
+                IconResolutionData *data;
+                data = (IconResolutionData*) mgr->priv->icons_resol_list->data;
+                mgr->priv->icons_resol_list = g_slist_delete_link (mgr->priv->icons_resol_list,
+                                                                   mgr->priv->icons_resol_list);
+
+		if (browser_connection_ldap_icon_for_dn (mgr->priv->bcnc, data->dn,
+							 (BrowserConnectionJobCallback) icon_fetched_cb,
+							 g_object_ref (data->node), NULL) == 0)
+			g_object_unref (data->node);
+                icon_resolution_data_free (data);
+        }
+
+        if (! mgr->priv->icons_resol_list) {
+                mgr->priv->icons_resol_timer = 0;
+                return FALSE;
+        }
+        else
+                return TRUE;
+}
+#endif
diff --git a/tools/browser/query-exec/Makefile.am b/tools/browser/query-exec/Makefile.am
index 255e174..813dc15 100644
--- a/tools/browser/query-exec/Makefile.am
+++ b/tools/browser/query-exec/Makefile.am
@@ -19,8 +19,8 @@ marshal.c: marshal.list $(GLIB_GENMARSHAL) marshal.h
 libperspective_la_SOURCES = \
         marshal.c \
         marshal.h \
-	query-console.c \
-	query-console.h \
+	query-console-page.c \
+	query-console-page.h \
 	query-editor.c \
 	query-editor.h \
 	query-favorite-selector.c \
diff --git a/tools/browser/query-exec/query-console.c b/tools/browser/query-exec/query-console-page.c
similarity index 90%
rename from tools/browser/query-exec/query-console.c
rename to tools/browser/query-exec/query-console-page.c
index 5b9dd79..eff528e 100644
--- a/tools/browser/query-exec/query-console.c
+++ b/tools/browser/query-exec/query-console-page.c
@@ -22,7 +22,7 @@
 
 #include <glib/gi18n-lib.h>
 #include <string.h>
-#include "query-console.h"
+#include "query-console-page.h"
 #include "../dnd.h"
 #include "../support.h"
 #include "../cc-gray-bar.h"
@@ -93,7 +93,7 @@ execution_statement_free (ExecutionStatement *estmt)
 	}
 }
 
-struct _QueryConsolePrivate {
+struct _QueryConsolePagePrivate {
 	BrowserConnection *bcnc;
 	GdaSqlParser *parser;
 
@@ -127,40 +127,40 @@ struct _QueryConsolePrivate {
 	GtkWidget *favorites_menu;
 };
 
-static void query_console_class_init (QueryConsoleClass *klass);
-static void query_console_init       (QueryConsole *tconsole, QueryConsoleClass *klass);
-static void query_console_dispose   (GObject *object);
-static void query_console_show_all (GtkWidget *widget);
-static void query_console_grab_focus (GtkWidget *widget);
+static void query_console_page_class_init (QueryConsolePageClass *klass);
+static void query_console_page_init       (QueryConsolePage *tconsole, QueryConsolePageClass *klass);
+static void query_console_page_dispose   (GObject *object);
+static void query_console_page_show_all (GtkWidget *widget);
+static void query_console_page_grab_focus (GtkWidget *widget);
 
 /* BrowserPage interface */
-static void                 query_console_page_init (BrowserPageIface *iface);
-static GtkActionGroup      *query_console_page_get_actions_group (BrowserPage *page);
-static const gchar         *query_console_page_get_actions_ui (BrowserPage *page);
-static GtkWidget           *query_console_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
+static void                 query_console_page_page_init (BrowserPageIface *iface);
+static GtkActionGroup      *query_console_page_page_get_actions_group (BrowserPage *page);
+static const gchar         *query_console_page_page_get_actions_ui (BrowserPage *page);
+static GtkWidget           *query_console_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button);
 
 static GObjectClass *parent_class = NULL;
 
 /*
- * QueryConsole class implementation
+ * QueryConsolePage class implementation
  */
 
 static void
-query_console_class_init (QueryConsoleClass *klass)
+query_console_page_class_init (QueryConsolePageClass *klass)
 {
 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
 	parent_class = g_type_class_peek_parent (klass);
 
-	object_class->dispose = query_console_dispose;
-	GTK_WIDGET_CLASS (klass)->show_all = query_console_show_all;
-	GTK_WIDGET_CLASS (klass)->grab_focus = query_console_grab_focus;
+	object_class->dispose = query_console_page_dispose;
+	GTK_WIDGET_CLASS (klass)->show_all = query_console_page_show_all;
+	GTK_WIDGET_CLASS (klass)->grab_focus = query_console_page_grab_focus;
 }
 
 static void
-query_console_show_all (GtkWidget *widget)
+query_console_page_show_all (GtkWidget *widget)
 {
-	QueryConsole *tconsole = (QueryConsole *) widget;
+	QueryConsolePage *tconsole = (QueryConsolePage *) widget;
 	GTK_WIDGET_CLASS (parent_class)->show_all (widget);
 
 	if (gtk_toggle_button_get_active (tconsole->priv->params_toggle))
@@ -170,17 +170,17 @@ query_console_show_all (GtkWidget *widget)
 }
 
 static void
-query_console_page_init (BrowserPageIface *iface)
+query_console_page_page_init (BrowserPageIface *iface)
 {
-	iface->i_get_actions_group = query_console_page_get_actions_group;
-	iface->i_get_actions_ui = query_console_page_get_actions_ui;
-	iface->i_get_tab_label = query_console_page_get_tab_label;
+	iface->i_get_actions_group = query_console_page_page_get_actions_group;
+	iface->i_get_actions_ui = query_console_page_page_get_actions_ui;
+	iface->i_get_tab_label = query_console_page_page_get_tab_label;
 }
 
 static void
-query_console_init (QueryConsole *tconsole, G_GNUC_UNUSED QueryConsoleClass *klass)
+query_console_page_init (QueryConsolePage *tconsole, G_GNUC_UNUSED QueryConsolePageClass *klass)
 {
-	tconsole->priv = g_new0 (QueryConsolePrivate, 1);
+	tconsole->priv = g_new0 (QueryConsolePagePrivate, 1);
 	tconsole->priv->parser = NULL;
 	tconsole->priv->params_compute_id = 0;
 	tconsole->priv->params = NULL;
@@ -189,11 +189,11 @@ query_console_init (QueryConsole *tconsole, G_GNUC_UNUSED QueryConsoleClass *kla
 	tconsole->priv->fav_id = -1;
 }
 static void connection_busy_cb (BrowserConnection *bcnc, gboolean is_busy,
-				gchar *reason, QueryConsole *tconsole);
+				gchar *reason, QueryConsolePage *tconsole);
 static void
-query_console_dispose (GObject *object)
+query_console_page_dispose (GObject *object)
 {
-	QueryConsole *tconsole = (QueryConsole *) object;
+	QueryConsolePage *tconsole = (QueryConsolePage *) object;
 
 	/* free memory */
 	if (tconsole->priv) {
@@ -227,60 +227,60 @@ query_console_dispose (GObject *object)
 }
 
 GType
-query_console_get_type (void)
+query_console_page_get_type (void)
 {
 	static GType type = 0;
 
 	if (G_UNLIKELY (type == 0)) {
 		static const GTypeInfo console = {
-			sizeof (QueryConsoleClass),
+			sizeof (QueryConsolePageClass),
 			(GBaseInitFunc) NULL,
 			(GBaseFinalizeFunc) NULL,
-			(GClassInitFunc) query_console_class_init,
+			(GClassInitFunc) query_console_page_class_init,
 			NULL,
 			NULL,
-			sizeof (QueryConsole),
+			sizeof (QueryConsolePage),
 			0,
-			(GInstanceInitFunc) query_console_init,
+			(GInstanceInitFunc) query_console_page_init,
 			0
 		};
 
 		static GInterfaceInfo page_console = {
-                        (GInterfaceInitFunc) query_console_page_init,
+                        (GInterfaceInitFunc) query_console_page_page_init,
 			NULL,
                         NULL
                 };
 
-		type = g_type_register_static (GTK_TYPE_VBOX, "QueryConsole", &console, 0);
+		type = g_type_register_static (GTK_TYPE_VBOX, "QueryConsolePage", &console, 0);
 		g_type_add_interface_static (type, BROWSER_PAGE_TYPE, &page_console);
 	}
 	return type;
 }
 
-static void editor_changed_cb (QueryEditor *editor, QueryConsole *tconsole);
-static void editor_execute_request_cb (QueryEditor *editor, QueryConsole *tconsole);
-static void sql_clear_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-static void sql_variables_clicked_cb (GtkToggleButton *button, QueryConsole *tconsole);
-static void sql_execute_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-static void sql_indent_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-static void sql_favorite_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-
-static void history_copy_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-static void history_clear_clicked_cb (GtkButton *button, QueryConsole *tconsole);
-static void history_changed_cb (QueryEditor *history, QueryConsole *tconsole);
+static void editor_changed_cb (QueryEditor *editor, QueryConsolePage *tconsole);
+static void editor_execute_request_cb (QueryEditor *editor, QueryConsolePage *tconsole);
+static void sql_clear_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+static void sql_variables_clicked_cb (GtkToggleButton *button, QueryConsolePage *tconsole);
+static void sql_execute_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+static void sql_indent_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+static void sql_favorite_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+
+static void history_copy_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+static void history_clear_clicked_cb (GtkButton *button, QueryConsolePage *tconsole);
+static void history_changed_cb (QueryEditor *history, QueryConsolePage *tconsole);
 /**
- * query_console_new
+ * query_console_page_new
  *
  * Returns: a new #GtkWidget
  */
 GtkWidget *
-query_console_new (BrowserConnection *bcnc)
+query_console_page_new (BrowserConnection *bcnc)
 {
-	QueryConsole *tconsole;
+	QueryConsolePage *tconsole;
 
 	g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
 
-	tconsole = QUERY_CONSOLE (g_object_new (QUERY_CONSOLE_TYPE, NULL));
+	tconsole = QUERY_CONSOLE_PAGE (g_object_new (QUERY_CONSOLE_PAGE_TYPE, NULL));
 
 	tconsole->priv->bcnc = g_object_ref (bcnc);
 	
@@ -470,7 +470,7 @@ query_console_new (BrowserConnection *bcnc)
 }
 
 static void
-connection_busy_cb (G_GNUC_UNUSED BrowserConnection *bcnc, gboolean is_busy, G_GNUC_UNUSED gchar *reason, QueryConsole *tconsole)
+connection_busy_cb (G_GNUC_UNUSED BrowserConnection *bcnc, gboolean is_busy, G_GNUC_UNUSED gchar *reason, QueryConsolePage *tconsole)
 {
 	gtk_widget_set_sensitive (tconsole->priv->exec_button, !is_busy);
 	gtk_widget_set_sensitive (tconsole->priv->indent_button, !is_busy);
@@ -483,7 +483,7 @@ connection_busy_cb (G_GNUC_UNUSED BrowserConnection *bcnc, gboolean is_busy, G_G
 }
 
 static void
-history_changed_cb (G_GNUC_UNUSED QueryEditor *history, QueryConsole *tconsole)
+history_changed_cb (G_GNUC_UNUSED QueryEditor *history, QueryConsolePage *tconsole)
 {
 	gboolean act = FALSE;
 	QueryEditor *qe;
@@ -507,13 +507,13 @@ history_changed_cb (G_GNUC_UNUSED QueryEditor *history, QueryConsole *tconsole)
 }
 
 static void
-history_clear_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+history_clear_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	query_editor_del_all_history_items (tconsole->priv->history);
 }
 
 static void
-history_copy_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+history_copy_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	QueryEditorHistoryItem *qih;
 	QueryEditor *qe;
@@ -544,7 +544,7 @@ history_copy_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole
 
 
 static gboolean
-compute_params (QueryConsole *tconsole)
+compute_params (QueryConsolePage *tconsole)
 {
 	gchar *sql;
 	GdaBatch *batch;
@@ -614,7 +614,7 @@ compute_params (QueryConsole *tconsole)
 }
 
 static void
-editor_changed_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsole *tconsole)
+editor_changed_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsolePage *tconsole)
 {
 	if (tconsole->priv->params_compute_id)
 		g_source_remove (tconsole->priv->params_compute_id);
@@ -622,7 +622,7 @@ editor_changed_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsole *tconsole)
 }
 
 static void
-editor_execute_request_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsole *tconsole)
+editor_execute_request_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsolePage *tconsole)
 {
 	gboolean sensitive;
 	g_object_get (tconsole->priv->exec_button, "sensitive", &sensitive, NULL);
@@ -631,7 +631,7 @@ editor_execute_request_cb (G_GNUC_UNUSED QueryEditor *editor, QueryConsole *tcon
 }
 	
 static void
-sql_variables_clicked_cb (GtkToggleButton *button, QueryConsole *tconsole)
+sql_variables_clicked_cb (GtkToggleButton *button, QueryConsolePage *tconsole)
 {
 	if (gtk_toggle_button_get_active (button))
 		gtk_widget_show (tconsole->priv->params_top);
@@ -640,7 +640,7 @@ sql_variables_clicked_cb (GtkToggleButton *button, QueryConsole *tconsole)
 }
 
 static void
-sql_clear_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+sql_clear_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	query_editor_set_text (tconsole->priv->editor, NULL);
 	tconsole->priv->fav_id = -1;
@@ -648,7 +648,7 @@ sql_clear_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
 }
 
 static void
-sql_indent_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+sql_indent_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	gchar *sql;
 	GdaBatch *batch;
@@ -682,11 +682,11 @@ sql_indent_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
 	}
 }
 
-static void sql_favorite_new_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsole *tconsole);
-static void sql_favorite_modify_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsole *tconsole);
+static void sql_favorite_new_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsolePage *tconsole);
+static void sql_favorite_modify_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsolePage *tconsole);
 
 static void
-sql_favorite_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+sql_favorite_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	GtkWidget *menu, *mitem;
 	BrowserFavorites *bfav;
@@ -761,7 +761,7 @@ sql_favorite_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole
 }
 
 static void
-sql_favorite_new_mitem_cb (GtkMenuItem *mitem, QueryConsole *tconsole)
+sql_favorite_new_mitem_cb (GtkMenuItem *mitem, QueryConsolePage *tconsole)
 {
 	BrowserFavorites *bfav;
 	BrowserFavoritesAttributes fav;
@@ -809,7 +809,7 @@ sql_favorite_new_mitem_cb (GtkMenuItem *mitem, QueryConsole *tconsole)
 }
 
 static void
-sql_favorite_modify_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsole *tconsole)
+sql_favorite_modify_mitem_cb (G_GNUC_UNUSED GtkMenuItem *mitem, QueryConsolePage *tconsole)
 {
 	BrowserFavorites *bfav;
 	BrowserFavoritesAttributes fav;
@@ -868,7 +868,7 @@ popup_container_position_func (PopupContainer *cont, gint *out_x, gint *out_y)
 
 static void
 params_form_changed_cb (GdauiBasicForm *form, G_GNUC_UNUSED GdaHolder *param,
-			G_GNUC_UNUSED gboolean is_user_modif, QueryConsole *tconsole)
+			G_GNUC_UNUSED gboolean is_user_modif, QueryConsolePage *tconsole)
 {
 	/* if all params are valid => authorize the execute button */
 	GtkWidget *button;
@@ -878,10 +878,10 @@ params_form_changed_cb (GdauiBasicForm *form, G_GNUC_UNUSED GdaHolder *param,
 				  gdaui_basic_form_is_valid (form));
 }
 
-static gboolean query_exec_fetch_cb (QueryConsole *tconsole);
+static gboolean query_exec_fetch_cb (QueryConsolePage *tconsole);
 
 static void
-sql_execute_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
+sql_execute_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsolePage *tconsole)
 {
 	gchar *sql;
 	const gchar *remain;
@@ -1026,7 +1026,7 @@ sql_execute_clicked_cb (G_GNUC_UNUSED GtkButton *button, QueryConsole *tconsole)
 }
 
 static gboolean
-query_exec_fetch_cb (QueryConsole *tconsole)
+query_exec_fetch_cb (QueryConsolePage *tconsole)
 {
 	gboolean alldone = TRUE;
 	
@@ -1074,7 +1074,7 @@ query_exec_fetch_cb (QueryConsole *tconsole)
 				}
 				else
 					browser_window_push_status (BROWSER_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) tconsole)),
-								    "QueryConsole", _("Statement executed"), TRUE);
+								    "QueryConsolePage", _("Statement executed"), TRUE);
 
 				/* display a message if a transaction has been started */
 				if (! history->within_transaction &&
@@ -1140,17 +1140,17 @@ query_exec_fetch_cb (QueryConsole *tconsole)
 }
 
 /**
- * query_console_set_text
- * @console: a #QueryConsole
+ * query_console_page_set_text
+ * @console: a #QueryConsolePage
  * @text: the new text
  * @fav_id: the favorite ID or -1 if not a favorite.
  *
  * Replaces the edited SQL with @text in @console
  */
 void
-query_console_set_text (QueryConsole *console, const gchar *text, gint fav_id)
+query_console_page_set_text (QueryConsolePage *console, const gchar *text, gint fav_id)
 {
-	g_return_if_fail (IS_QUERY_CONSOLE (console));
+	g_return_if_fail (IS_QUERY_CONSOLE_PAGE_PAGE (console));
 	console->priv->fav_id = fav_id;
 	query_editor_set_text (console->priv->editor, text);
 }
@@ -1159,14 +1159,14 @@ query_console_set_text (QueryConsole *console, const gchar *text, gint fav_id)
  * UI actions
  */
 static void
-query_execute_cb (G_GNUC_UNUSED GtkAction *action, QueryConsole *tconsole)
+query_execute_cb (G_GNUC_UNUSED GtkAction *action, QueryConsolePage *tconsole)
 {
 	sql_execute_clicked_cb (NULL, tconsole);
 }
 
 #ifdef HAVE_GTKSOURCEVIEW
 static void
-editor_undo_cb (G_GNUC_UNUSED GtkAction *action, G_GNUC_UNUSED QueryConsole *tconsole)
+editor_undo_cb (G_GNUC_UNUSED GtkAction *action, G_GNUC_UNUSED QueryConsolePage *tconsole)
 {
 	TO_IMPLEMENT;
 }
@@ -1194,10 +1194,10 @@ static const gchar *ui_actions_console =
 	"</ui>";
 
 static GtkActionGroup *
-query_console_page_get_actions_group (BrowserPage *page)
+query_console_page_page_get_actions_group (BrowserPage *page)
 {
-	QueryConsole *tconsole;
-	tconsole = QUERY_CONSOLE (page);
+	QueryConsolePage *tconsole;
+	tconsole = QUERY_CONSOLE_PAGE (page);
 	if (! tconsole->priv->agroup) {
 		tconsole->priv->agroup = gtk_action_group_new ("QueryExecConsoleActions");
 		gtk_action_group_set_translation_domain (tconsole->priv->agroup, GETTEXT_PACKAGE);
@@ -1212,18 +1212,18 @@ query_console_page_get_actions_group (BrowserPage *page)
 }
 
 static const gchar *
-query_console_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
+query_console_page_page_get_actions_ui (G_GNUC_UNUSED BrowserPage *page)
 {
 	return ui_actions_console;
 }
 
 static GtkWidget *
-query_console_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
+query_console_page_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_button)
 {
-	QueryConsole *tconsole;
+	QueryConsolePage *tconsole;
 	const gchar *tab_name;
 
-	tconsole = QUERY_CONSOLE (page);
+	tconsole = QUERY_CONSOLE_PAGE (page);
 	tab_name = _("Query editor");
 	return browser_make_tab_label_with_stock (tab_name,
 						  STOCK_CONSOLE,
@@ -1231,10 +1231,10 @@ query_console_page_get_tab_label (BrowserPage *page, GtkWidget **out_close_butto
 }
 
 static void
-query_console_grab_focus (GtkWidget *widget)
+query_console_page_grab_focus (GtkWidget *widget)
 {
-	QueryConsole *tconsole;
+	QueryConsolePage *tconsole;
 
-	tconsole = QUERY_CONSOLE (widget);
+	tconsole = QUERY_CONSOLE_PAGE (widget);
 	gtk_widget_grab_focus (GTK_WIDGET (tconsole->priv->editor));
 }
diff --git a/tools/browser/query-exec/query-console-page.h b/tools/browser/query-exec/query-console-page.h
new file mode 100644
index 0000000..032830a
--- /dev/null
+++ b/tools/browser/query-exec/query-console-page.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009 - 2011 The GNOME Foundation
+ *
+ * AUTHORS:
+ *      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 Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __QUERY_CONSOLE_PAGE_H__
+#define __QUERY_CONSOLE_PAGE_H__
+
+#include <gtk/gtk.h>
+#include "../browser-connection.h"
+
+G_BEGIN_DECLS
+
+#define QUERY_CONSOLE_PAGE_TYPE            (query_console_page_get_type())
+#define QUERY_CONSOLE_PAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST (obj, QUERY_CONSOLE_PAGE_TYPE, QueryConsolePage))
+#define QUERY_CONSOLE_PAGE_PAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST (klass, QUERY_CONSOLE_PAGE_TYPE, QueryConsolePageClass))
+#define IS_QUERY_CONSOLE_PAGE_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE (obj, QUERY_CONSOLE_PAGE_TYPE))
+#define IS_QUERY_CONSOLE_PAGE_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QUERY_CONSOLE_PAGE_TYPE))
+
+typedef struct _QueryConsolePage        QueryConsolePage;
+typedef struct _QueryConsolePageClass   QueryConsolePageClass;
+typedef struct _QueryConsolePagePrivate QueryConsolePagePrivate;
+
+struct _QueryConsolePage {
+	GtkVBox               parent;
+	QueryConsolePagePrivate     *priv;
+};
+
+struct _QueryConsolePageClass {
+	GtkVBoxClass          parent_class;
+};
+
+GType                    query_console_page_get_type (void) G_GNUC_CONST;
+
+GtkWidget               *query_console_page_new      (BrowserConnection *bcnc);
+void                     query_console_page_set_text (QueryConsolePage *console, const gchar *text, gint fav_id);
+
+G_END_DECLS
+
+#endif
diff --git a/tools/browser/query-exec/query-exec-perspective.c b/tools/browser/query-exec/query-exec-perspective.c
index 68c2010..139e9f4 100644
--- a/tools/browser/query-exec/query-exec-perspective.c
+++ b/tools/browser/query-exec/query-exec-perspective.c
@@ -22,7 +22,7 @@
 #include "query-exec-perspective.h"
 #include "../browser-window.h"
 #include "../browser-page.h"
-#include "query-console.h"
+#include "query-console-page.h"
 #include "../browser-stock-icons.h"
 #include "../support.h"
 #include "query-favorite-selector.h"
@@ -39,6 +39,7 @@ static void query_exec_perspective_grab_focus (GtkWidget *widget);
 
 /* BrowserPerspective interface */
 static void                 query_exec_perspective_perspective_init (BrowserPerspectiveIface *iface);
+static BrowserWindow       *query_exec_perspective_get_window (BrowserPerspective *perspective);
 static GtkActionGroup      *query_exec_perspective_get_actions_group (BrowserPerspective *perspective);
 static const gchar         *query_exec_perspective_get_actions_ui (BrowserPerspective *perspective);
 static void                 query_exec_perspective_get_current_customization (BrowserPerspective *perspective,
@@ -46,7 +47,6 @@ static void                 query_exec_perspective_get_current_customization (Br
 									      const gchar **out_ui);
 static void                 query_exec_perspective_page_tab_label_change (BrowserPerspective *perspective, BrowserPage *page);
 
-static void                 adapt_notebook_for_fullscreen (QueryExecPerspective *perspective);
 
 /* get a pointer to the parents to be able to call their destructor */
 static GObjectClass  *parent_class = NULL;
@@ -57,9 +57,6 @@ struct _QueryExecPerspectivePrivate {
 	gboolean favorites_shown;
 	BrowserWindow *bwin;
 	BrowserConnection *bcnc;
-	
-	GtkActionGroup *action_group;
-	gboolean fullscreen;
 };
 
 GType
@@ -117,10 +114,10 @@ query_exec_perspective_grab_focus (GtkWidget *widget)
 	gtk_widget_grab_focus (gtk_notebook_get_nth_page (nb,
 							  gtk_notebook_get_current_page (nb)));
 }
-
 static void
 query_exec_perspective_perspective_init (BrowserPerspectiveIface *iface)
 {
+	iface->i_get_window = query_exec_perspective_get_window;
 	iface->i_get_actions_group = query_exec_perspective_get_actions_group;
 	iface->i_get_actions_ui = query_exec_perspective_get_actions_ui;
 	iface->i_get_current_customization = query_exec_perspective_get_current_customization;
@@ -131,21 +128,12 @@ static void
 query_exec_perspective_init (QueryExecPerspective *perspective)
 {
 	perspective->priv = g_new0 (QueryExecPerspectivePrivate, 1);
-	perspective->priv->action_group = NULL;
 	perspective->priv->favorites_shown = TRUE;
-	perspective->priv->fullscreen = FALSE;
 }
 
 static void fav_selection_changed_cb (GtkWidget *widget, gint fav_id, BrowserFavoritesType fav_type,
                                       const gchar *selection, QueryExecPerspective *perspective);
-static void nb_switch_page_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-			       QueryExecPerspective *perspective);
-static void nb_page_removed_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-				QueryExecPerspective *perspective);
-static void nb_page_added_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-			      QueryExecPerspective *perspective);
 static void close_button_clicked_cb (GtkWidget *wid, GtkWidget *page_widget);
-static void fullscreen_changed_cb (BrowserWindow *bwin, gboolean fullscreen, QueryExecPerspective *perspective);
 
 /**
  * query_exec_perspective_new
@@ -158,26 +146,27 @@ query_exec_perspective_new (BrowserWindow *bwin)
 	BrowserConnection *bcnc;
 	BrowserPerspective *bpers;
 	QueryExecPerspective *perspective;
+	gboolean fav_supported;
 
 	bpers = (BrowserPerspective*) g_object_new (TYPE_QUERY_EXEC_PERSPECTIVE, NULL);
 	perspective = (QueryExecPerspective*) bpers;
 
 	perspective->priv->bwin = bwin;
-	g_signal_connect (bwin, "fullscreen-changed",
-			  G_CALLBACK (fullscreen_changed_cb), bpers);
 	bcnc = browser_window_get_connection (bwin);
 	perspective->priv->bcnc = g_object_ref (bcnc);
-	perspective->priv->fullscreen = browser_window_is_fullscreen (bwin);
+	fav_supported = browser_connection_get_favorites (bcnc) ? TRUE : FALSE;
 
 	/* contents */
 	GtkWidget *paned, *nb, *wid;
 	paned = gtk_hpaned_new ();
-	wid = query_favorite_selector_new (bcnc);
-	g_signal_connect (wid, "selection-changed",
-			  G_CALLBACK (fav_selection_changed_cb), bpers);
-	gtk_paned_pack1 (GTK_PANED (paned), wid, FALSE, TRUE);
-	gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
-	perspective->priv->favorites = wid;
+	if (fav_supported) {
+		wid = query_favorite_selector_new (bcnc);
+		g_signal_connect (wid, "selection-changed",
+				  G_CALLBACK (fav_selection_changed_cb), bpers);
+		gtk_paned_pack1 (GTK_PANED (paned), wid, FALSE, TRUE);
+		gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
+		perspective->priv->favorites = wid;
+	}
 
 	nb = gtk_notebook_new ();
 	perspective->priv->notebook = nb;
@@ -187,7 +176,7 @@ query_exec_perspective_new (BrowserWindow *bwin)
 
 	GtkWidget *page, *tlabel, *button;
 
-	page = query_console_new (bcnc);
+	page = query_console_page_new (bcnc);
 	tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), &button);
 	g_signal_connect (button, "clicked",
 			  G_CALLBACK (close_button_clicked_cb), page);
@@ -205,20 +194,11 @@ query_exec_perspective_new (BrowserWindow *bwin)
 	gtk_box_pack_start (GTK_BOX (bpers), paned, TRUE, TRUE, 0);
 	gtk_widget_show_all (paned);
 
-	if (!perspective->priv->favorites_shown)
+	if (perspective->priv->favorites && !perspective->priv->favorites_shown)
 		gtk_widget_hide (perspective->priv->favorites);
 	gtk_widget_grab_focus (page);
 
-	/* signals to customize perspective */
-	g_signal_connect (G_OBJECT (nb), "switch-page",
-			  G_CALLBACK (nb_switch_page_cb), perspective);
-	g_signal_connect (G_OBJECT (nb), "page-removed",
-			  G_CALLBACK (nb_page_removed_cb), perspective);
-	g_signal_connect (G_OBJECT (nb), "page-added",
-			  G_CALLBACK (nb_page_added_cb), perspective);
-
-	if (perspective->priv->fullscreen)
-		adapt_notebook_for_fullscreen (perspective);
+	browser_perspective_declare_notebook (bpers, GTK_NOTEBOOK (perspective->priv->notebook));
 
 	return bpers;
 }
@@ -235,8 +215,8 @@ fav_selection_changed_cb (G_GNUC_UNUSED GtkWidget *widget, gint fav_id,
 	page = gtk_notebook_get_nth_page (nb, gtk_notebook_get_current_page (nb));
 	if (!page)
 		return;
-	if (IS_QUERY_CONSOLE (page)) {
-		query_console_set_text (QUERY_CONSOLE (page), selection, fav_id);
+	if (IS_QUERY_CONSOLE_PAGE_PAGE (page)) {
+		query_console_page_set_text (QUERY_CONSOLE_PAGE (page), selection, fav_id);
 		gtk_widget_grab_focus (page);
 	}
 	else {
@@ -245,67 +225,11 @@ fav_selection_changed_cb (G_GNUC_UNUSED GtkWidget *widget, gint fav_id,
 }
 
 static void
-nb_switch_page_cb (GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page, gint page_num,
-		   QueryExecPerspective *perspective)
-{
-	GtkWidget *page_contents;
-	GtkActionGroup *actions = NULL;
-	const gchar *ui = NULL;
-
-	page_contents = gtk_notebook_get_nth_page (nb, page_num);
-	if (IS_BROWSER_PAGE (page_contents)) {
-		actions = browser_page_get_actions_group (BROWSER_PAGE (page_contents));
-		ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
-	}
-	browser_window_customize_perspective_ui (perspective->priv->bwin,
-						 BROWSER_PERSPECTIVE (perspective), actions, 
-						 ui);
-	if (actions)
-		g_object_unref (actions);
-}
-
-static void
-nb_page_removed_cb (GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page, G_GNUC_UNUSED gint page_num,
-		    QueryExecPerspective *perspective)
-{
-	if (gtk_notebook_get_n_pages (nb) == 0) {
-		browser_window_customize_perspective_ui (perspective->priv->bwin,
-							 BROWSER_PERSPECTIVE (perspective),
-							 NULL, NULL);
-	}
-	adapt_notebook_for_fullscreen (perspective);
-}
-
-static void
-nb_page_added_cb (G_GNUC_UNUSED GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page,
-		  G_GNUC_UNUSED gint page_num, QueryExecPerspective *perspective)
-{
-	adapt_notebook_for_fullscreen (perspective);
-}
-
-static void
 close_button_clicked_cb (G_GNUC_UNUSED GtkWidget *wid, GtkWidget *page_widget)
 {
 	gtk_widget_destroy (page_widget);
 }
 
-static void
-adapt_notebook_for_fullscreen (QueryExecPerspective *perspective)
-{
-	gboolean showtabs = TRUE;
-	
-	if (perspective->priv->fullscreen && 
-	    gtk_notebook_get_n_pages (GTK_NOTEBOOK (perspective->priv->notebook)) == 1)
-		showtabs = FALSE;
-	gtk_notebook_set_show_tabs (GTK_NOTEBOOK (perspective->priv->notebook), showtabs);
-}
-
-static void
-fullscreen_changed_cb (G_GNUC_UNUSED BrowserWindow *bwin, gboolean fullscreen, QueryExecPerspective *perspective)
-{
-	perspective->priv->fullscreen = fullscreen;
-	adapt_notebook_for_fullscreen (perspective);
-}
 
 static void
 query_exec_perspective_dispose (GObject *object)
@@ -317,18 +241,10 @@ query_exec_perspective_dispose (GObject *object)
 
 	perspective = QUERY_EXEC_PERSPECTIVE (object);
 	if (perspective->priv) {
+		browser_perspective_declare_notebook ((BrowserPerspective*) perspective, NULL);
 		if (perspective->priv->bcnc)
 			g_object_unref (perspective->priv->bcnc);
 
-		if (perspective->priv->action_group)
-			g_object_unref (perspective->priv->action_group);
-
-		g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-						      G_CALLBACK (nb_page_removed_cb), perspective);
-		g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-						      G_CALLBACK (nb_page_added_cb), perspective);
-		g_signal_handlers_disconnect_by_func (perspective->priv->notebook,
-						      G_CALLBACK (nb_switch_page_cb), perspective);
 		g_free (perspective->priv);
 		perspective->priv = NULL;
 	}
@@ -348,7 +264,7 @@ query_exec_add_cb (G_GNUC_UNUSED GtkAction *action, BrowserPerspective *bpers)
 	perspective = QUERY_EXEC_PERSPECTIVE (bpers);
 	bcnc = perspective->priv->bcnc;
 
-	page = query_console_new (bcnc);
+	page = query_console_page_new (bcnc);
 	gtk_widget_show (page);
 	tlabel = browser_page_get_tab_label (BROWSER_PAGE (page), &button);
 	g_signal_connect (button, "clicked",
@@ -366,8 +282,6 @@ query_exec_add_cb (G_GNUC_UNUSED GtkAction *action, BrowserPerspective *bpers)
 	gtk_notebook_set_menu_label (GTK_NOTEBOOK (perspective->priv->notebook), page, tlabel);
 
 	gtk_widget_grab_focus (page);
-
-	adapt_notebook_for_fullscreen (perspective);
 }
 
 static void
@@ -375,6 +289,9 @@ favorites_toggle_cb (GtkToggleAction *action, BrowserPerspective *bpers)
 {
 	QueryExecPerspective *perspective;
 	perspective = QUERY_EXEC_PERSPECTIVE (bpers);
+	if (!perspective->priv->favorites)
+		return;
+
 	perspective->priv->favorites_shown = gtk_toggle_action_get_active (action);
 	if (perspective->priv->favorites_shown)
 		gtk_widget_show (perspective->priv->favorites);
@@ -415,24 +332,24 @@ static GtkActionGroup *
 query_exec_perspective_get_actions_group (BrowserPerspective *perspective)
 {
 	QueryExecPerspective *bpers;
+	GtkActionGroup *agroup;
 	bpers = QUERY_EXEC_PERSPECTIVE (perspective);
-
-	if (!bpers->priv->action_group) {
-		GtkActionGroup *agroup;
-		agroup = gtk_action_group_new ("QueryExecActions");
-		gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
-		gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), bpers);
-		bpers->priv->action_group = g_object_ref (agroup);
-
-		gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions, G_N_ELEMENTS (ui_toggle_actions),
-						     bpers);
-		GtkAction *action;
-		action = gtk_action_group_get_action (agroup, "QueryExecFavoritesShow");
+	agroup = gtk_action_group_new ("QueryExecActions");
+	gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
+	gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), bpers);
+	
+	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions,
+					     G_N_ELEMENTS (ui_toggle_actions),
+					     bpers);
+	GtkAction *action;
+	action = gtk_action_group_get_action (agroup, "QueryExecFavoritesShow");
+	if (bpers->priv->favorites)
 		gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
-					      QUERY_EXEC_PERSPECTIVE (bpers)->priv->favorites_shown);
-	}
+					      bpers->priv->favorites_shown);
+	else
+		gtk_action_set_sensitive (GTK_ACTION (action), FALSE);
 	
-	return bpers->priv->action_group;
+	return agroup;
 }
 
 static const gchar *
@@ -478,3 +395,11 @@ query_exec_perspective_get_current_customization (BrowserPerspective *perspectiv
 		*out_ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
 	}
 }
+
+static BrowserWindow *
+query_exec_perspective_get_window (BrowserPerspective *perspective)
+{
+	QueryExecPerspective *bpers;
+	bpers = QUERY_EXEC_PERSPECTIVE (perspective);
+	return bpers->priv->bwin;
+}
diff --git a/tools/browser/schema-browser/Makefile.am b/tools/browser/schema-browser/Makefile.am
index f720ab1..75ceb9d 100644
--- a/tools/browser/schema-browser/Makefile.am
+++ b/tools/browser/schema-browser/Makefile.am
@@ -1,10 +1,16 @@
 noinst_LTLIBRARIES = libperspective.la
 
+if LDAP
+ldap_flags=-DHAVE_LDAP
+endif
+
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/tools/browser \
 	-I$(top_builddir) \
 	-I$(top_srcdir) \
 	-I$(top_srcdir)/libgda \
+	-I$(top_srcdir)/libgda/sqlite \
+	$(ldap_flags) \
 	$(LIBGDA_CFLAGS) \
 	$(LIBGDA_WFLAGS) \
 	$(GTK_CFLAGS) \
diff --git a/tools/browser/schema-browser/schema-browser-perspective.c b/tools/browser/schema-browser/schema-browser-perspective.c
index 61c630e..1451011 100644
--- a/tools/browser/schema-browser/schema-browser-perspective.c
+++ b/tools/browser/schema-browser/schema-browser-perspective.c
@@ -39,6 +39,7 @@ static void schema_browser_perspective_dispose (GObject *object);
 
 /* BrowserPerspective interface */
 static void                 schema_browser_perspective_perspective_init (BrowserPerspectiveIface *iface);
+static BrowserWindow       *schema_browser_perspective_get_window (BrowserPerspective *perspective);
 static GtkActionGroup      *schema_browser_perspective_get_actions_group (BrowserPerspective *perspective);
 static const gchar         *schema_browser_perspective_get_actions_ui (BrowserPerspective *perspective);
 static void                 schema_browser_perspective_page_tab_label_change (BrowserPerspective *perspective, BrowserPage *page);
@@ -104,6 +105,7 @@ schema_browser_perspective_class_init (SchemaBrowserPerspectiveClass * klass)
 static void
 schema_browser_perspective_perspective_init (BrowserPerspectiveIface *iface)
 {
+	iface->i_get_window = schema_browser_perspective_get_window;
 	iface->i_get_actions_group = schema_browser_perspective_get_actions_group;
 	iface->i_get_actions_ui = schema_browser_perspective_get_actions_ui;
 	iface->i_page_tab_label_change = schema_browser_perspective_page_tab_label_change;
@@ -122,8 +124,6 @@ static void fav_selection_changed_cb (GtkWidget *widget, gint fav_id, BrowserFav
 				      const gchar *selection, SchemaBrowserPerspective *bpers);
 static void objects_index_selection_changed_cb (GtkWidget *widget, BrowserFavoritesType fav_type,
 						const gchar *selection, SchemaBrowserPerspective *bpers);
-static void nb_switch_page_cb (GtkNotebook *nb, GtkWidget *page, gint page_num,
-			       SchemaBrowserPerspective *perspective); 
 /**
  * schema_browser_perspective_new
  *
@@ -135,30 +135,31 @@ schema_browser_perspective_new (BrowserWindow *bwin)
 	BrowserConnection *bcnc;
 	BrowserPerspective *bpers;
 	SchemaBrowserPerspective *perspective;
+	gboolean fav_supported;
 
 	bpers = (BrowserPerspective*) g_object_new (TYPE_SCHEMA_BROWSER_PERSPECTIVE, NULL);
 	perspective = (SchemaBrowserPerspective*) bpers;
-
+	bcnc = browser_window_get_connection (bwin);
+	fav_supported = browser_connection_get_favorites (bcnc) ? TRUE : FALSE;
 	perspective->priv->bwin = bwin;
 
 	/* contents */
 	GtkWidget *paned, *wid, *nb;
-	bcnc = browser_window_get_connection (bwin);
 	paned = gtk_hpaned_new ();
-	wid = favorite_selector_new (bcnc);
-	g_signal_connect (wid, "selection-changed",
-			  G_CALLBACK (fav_selection_changed_cb), bpers);
-	gtk_paned_add1 (GTK_PANED (paned), wid);
-	gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
-	perspective->priv->favorites = wid;
+	if (fav_supported) {
+		wid = favorite_selector_new (bcnc);
+		g_signal_connect (wid, "selection-changed",
+				  G_CALLBACK (fav_selection_changed_cb), bpers);
+		gtk_paned_add1 (GTK_PANED (paned), wid);
+		gtk_paned_set_position (GTK_PANED (paned), DEFAULT_FAVORITES_SIZE);
+		perspective->priv->favorites = wid;
+	}
 
 	nb = gtk_notebook_new ();
 	perspective->priv->notebook = nb;
 	gtk_paned_add2 (GTK_PANED (paned), nb);
 	gtk_notebook_set_scrollable (GTK_NOTEBOOK (nb), TRUE);
 	gtk_notebook_popup_enable (GTK_NOTEBOOK (nb));
-	g_signal_connect (G_OBJECT (nb), "switch-page",
-			  G_CALLBACK (nb_switch_page_cb), perspective);
 
 	wid = objects_index_new (bcnc);
 	g_signal_connect (wid, "selection-changed",
@@ -175,30 +176,12 @@ schema_browser_perspective_new (BrowserWindow *bwin)
 	gtk_box_pack_start (GTK_BOX (bpers), paned, TRUE, TRUE, 0);
 	gtk_widget_show_all (paned);
 
-	if (!perspective->priv->favorites_shown)
+	if (perspective->priv->favorites && !perspective->priv->favorites_shown)
 		gtk_widget_hide (perspective->priv->favorites);
 
-	return bpers;
-}
-
-static void
-nb_switch_page_cb (GtkNotebook *nb, G_GNUC_UNUSED GtkWidget *page, gint page_num,
-		   SchemaBrowserPerspective *perspective)
-{
-	GtkWidget *page_contents;
-	GtkActionGroup *actions = NULL;
-	const gchar *ui = NULL;
+	browser_perspective_declare_notebook (bpers, GTK_NOTEBOOK (perspective->priv->notebook));
 
-	page_contents = gtk_notebook_get_nth_page (nb, page_num);
-	if (IS_BROWSER_PAGE (page_contents)) {
-		actions = browser_page_get_actions_group (BROWSER_PAGE (page_contents));
-		ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
-	}
-	browser_window_customize_perspective_ui (perspective->priv->bwin,
-						 BROWSER_PERSPECTIVE (perspective), actions, 
-						 ui);
-	if (actions)
-		g_object_unref (actions);
+	return bpers;
 }
 
 static void
@@ -285,6 +268,7 @@ schema_browser_perspective_dispose (GObject *object)
 
 	perspective = SCHEMA_BROWSER_PERSPECTIVE (object);
 	if (perspective->priv) {
+		browser_perspective_declare_notebook ((BrowserPerspective*) perspective, NULL);
 		g_free (perspective->priv);
 		perspective->priv = NULL;
 	}
@@ -306,6 +290,9 @@ favorites_toggle_cb (GtkToggleAction *action, BrowserPerspective *bpers)
 {
 	SchemaBrowserPerspective *perspective;
 	perspective = SCHEMA_BROWSER_PERSPECTIVE (bpers);
+	if (! perspective->priv->favorites)
+		return;
+
 	perspective->priv->favorites_shown = gtk_toggle_action_get_active (action);
 	if (perspective->priv->favorites_shown)
 		gtk_widget_show (perspective->priv->favorites);
@@ -341,19 +328,26 @@ static const gchar *ui_actions_info =
         "</ui>";
 
 static GtkActionGroup *
-schema_browser_perspective_get_actions_group (BrowserPerspective *bpers)
+schema_browser_perspective_get_actions_group (BrowserPerspective *perspective)
 {
+	SchemaBrowserPerspective *bpers;
 	GtkActionGroup *agroup;
+	bpers = SCHEMA_BROWSER_PERSPECTIVE (perspective);
 	agroup = gtk_action_group_new ("SchemaBrowserActions");
 	gtk_action_group_set_translation_domain (agroup, GETTEXT_PACKAGE);
 
 	gtk_action_group_add_actions (agroup, ui_actions, G_N_ELEMENTS (ui_actions), bpers);
-	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions, G_N_ELEMENTS (ui_toggle_actions),
+
+	gtk_action_group_add_toggle_actions (agroup, ui_toggle_actions,
+					     G_N_ELEMENTS (ui_toggle_actions),
 					     bpers);
 	GtkAction *action;
 	action = gtk_action_group_get_action (agroup, "SchemaBrowserFavoritesShow");
-	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
-				      SCHEMA_BROWSER_PERSPECTIVE (bpers)->priv->favorites_shown);	
+	if (bpers->priv->favorites)
+		gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+					      bpers->priv->favorites_shown);
+	else
+		gtk_action_set_sensitive (GTK_ACTION (action), FALSE);
 
 	return agroup;
 }
@@ -516,3 +510,11 @@ schema_browser_perspective_get_current_customization (BrowserPerspective *perspe
 		*out_ui = browser_page_get_actions_ui (BROWSER_PAGE (page_contents));
 	}
 }
+
+static BrowserWindow *
+schema_browser_perspective_get_window (BrowserPerspective *perspective)
+{
+	SchemaBrowserPerspective *bpers;
+	bpers = SCHEMA_BROWSER_PERSPECTIVE (perspective);
+	return bpers->priv->bwin;
+}
diff --git a/tools/browser/schema-browser/table-columns.c b/tools/browser/schema-browser/table-columns.c
index 60e98b5..80cdaee 100644
--- a/tools/browser/schema-browser/table-columns.c
+++ b/tools/browser/schema-browser/table-columns.c
@@ -35,6 +35,9 @@
 #include "schema-browser-perspective.h"
 #include "../browser-window.h"
 #include "../common/fk-declare.h"
+#ifdef HAVE_LDAP
+#include "../ldap-browser/ldap-browser-perspective.h"
+#endif
 
 struct _TableColumnsPrivate {
 	BrowserConnection *bcnc;
@@ -44,11 +47,18 @@ struct _TableColumnsPrivate {
 
 	GtkTextBuffer *constraints;
 	gboolean hovering_over_link;
+#ifdef HAVE_LDAP
+	GtkTextBuffer *ldap_def;
+	GtkWidget *ldap_header;
+	GtkWidget *ldap_text;
+	gboolean ldap_props_shown;
+#endif
 };
 
 static void table_columns_class_init (TableColumnsClass *klass);
 static void table_columns_init       (TableColumns *tcolumns, TableColumnsClass *klass);
-static void table_columns_dispose   (GObject *object);
+static void table_columns_dispose    (GObject *object);
+static void table_columns_show_all   (GtkWidget *widget);
 
 static void meta_changed_cb (BrowserConnection *bcnc, GdaMetaStruct *mstruct, TableColumns *tcolumns);
 
@@ -67,6 +77,7 @@ table_columns_class_init (TableColumnsClass *klass)
 	parent_class = g_type_class_peek_parent (klass);
 
 	object_class->dispose = table_columns_dispose;
+	GTK_WIDGET_CLASS (klass)->show_all = table_columns_show_all;
 }
 
 
@@ -101,6 +112,19 @@ table_columns_dispose (GObject *object)
 	parent_class->dispose (object);
 }
 
+static void
+table_columns_show_all (GtkWidget *widget)
+{
+	TableColumns *tcolumns = (TableColumns *) widget;
+        GTK_WIDGET_CLASS (parent_class)->show_all (widget);
+	if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
+		if (! tcolumns->priv->ldap_props_shown) {
+			gtk_widget_hide (tcolumns->priv->ldap_header);
+			gtk_widget_hide (tcolumns->priv->ldap_text);
+		}
+	}
+}
+
 GType
 table_columns_get_type (void)
 {
@@ -157,7 +181,15 @@ meta_changed_cb (G_GNUC_UNUSED BrowserConnection *bcnc, GdaMetaStruct *mstruct,
 	GtkTextBuffer *tbuffer;
 	GtkTextIter start, end;
 
-	/* constraints descr. cleaning */
+	/* cleanups */
+#ifdef HAVE_LDAP
+	if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
+		tbuffer = tcolumns->priv->ldap_def;
+		gtk_text_buffer_get_start_iter (tbuffer, &start);
+		gtk_text_buffer_get_end_iter (tbuffer, &end);
+		gtk_text_buffer_delete (tbuffer, &start, &end);
+	}
+#endif
 	tbuffer = tcolumns->priv->constraints;
 	gtk_text_buffer_get_start_iter (tbuffer, &start);
         gtk_text_buffer_get_end_iter (tbuffer, &end);
@@ -420,6 +452,81 @@ meta_changed_cb (G_GNUC_UNUSED BrowserConnection *bcnc, GdaMetaStruct *mstruct,
 				g_slist_free (rev_list);
 				gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
 			}
+
+#ifdef HAVE_LDAP
+			if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
+				const gchar *base_dn, *filter, *attributes, *scope_str;
+				GdaLdapSearchScope scope;
+				tbuffer = tcolumns->priv->ldap_def;
+				gtk_text_buffer_get_start_iter (tbuffer, &current);
+				if (browser_connection_describe_table  (tcolumns->priv->bcnc, dbo->obj_name,
+									&base_dn, &filter,
+									&attributes, &scope, NULL)) {
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "BASE: ", -1,
+										  "section", NULL);
+					if (base_dn) {
+						GtkTextTag *tag;
+						tag = gtk_text_buffer_create_tag (tbuffer, NULL, 
+										  "foreground", "blue", 
+										  "weight", PANGO_WEIGHT_NORMAL,
+										  "underline", PANGO_UNDERLINE_SINGLE,
+										  NULL);
+						g_object_set_data_full (G_OBJECT (tag), "dn",
+									g_strdup (base_dn), g_free);
+						
+						gtk_text_buffer_insert_with_tags (tbuffer, &current, base_dn, -1,
+										  tag, NULL);
+					}
+					
+					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "FILTER: ", -1,
+										  "section", NULL);
+					if (filter)
+						gtk_text_buffer_insert (tbuffer, &current, filter, -1);
+					
+					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "ATTRIBUTES: ", -1,
+										  "section", NULL);
+					if (attributes)
+						gtk_text_buffer_insert (tbuffer, &current, attributes, -1);
+					
+					gtk_text_buffer_insert (tbuffer, &current, "\n", -1);
+					gtk_text_buffer_insert_with_tags_by_name (tbuffer, &current,
+										  "SCOPE: ", -1,
+										  "section", NULL);
+					
+					switch (scope) {
+					case GDA_LDAP_SEARCH_BASE:
+						scope_str = "base";
+						break;
+					case GDA_LDAP_SEARCH_ONELEVEL:
+						scope_str = "onelevel";
+						break;
+					case GDA_LDAP_SEARCH_SUBTREE:
+						scope_str = "subtree";
+						break;
+					default:
+						TO_IMPLEMENT;
+						scope_str = _("Unknown");
+						break;
+					}
+					gtk_text_buffer_insert (tbuffer, &current, scope_str, -1);
+					
+					tcolumns->priv->ldap_props_shown = TRUE;
+					gtk_widget_show (tcolumns->priv->ldap_header);
+					gtk_widget_show (tcolumns->priv->ldap_text);
+				}
+				else {
+					tcolumns->priv->ldap_props_shown = FALSE;
+					gtk_widget_hide (tcolumns->priv->ldap_header);
+					gtk_widget_hide (tcolumns->priv->ldap_text);
+				}
+			}
+#endif
+
 		}
 
 		if (schema_v)
@@ -566,15 +673,63 @@ table_columns_new (TableInfo *tinfo)
         gtk_container_add (GTK_CONTAINER (sw), treeview);
 	gtk_paned_pack1 (GTK_PANED (paned), sw, TRUE, FALSE);
 	
+	/* Paned, part 2 */
+	GtkWidget *vbox;
+	vbox = gtk_vbox_new (FALSE, 0);
+	gtk_paned_pack2 (GTK_PANED (paned), vbox, TRUE, TRUE);
+
+#ifdef HAVE_LDAP
+	if (browser_connection_is_ldap (tcolumns->priv->bcnc)) {
+		GtkWidget *label;
+		gchar *str;
+		
+		str = g_strdup_printf ("<b>%s</b>", _("LDAP virtual table definition"));
+		label = cc_gray_bar_new (str);
+		g_free (str);
+		gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+		tcolumns->priv->ldap_header = label;
+
+		sw = gtk_scrolled_window_new (NULL, NULL);
+		gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
+		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+						GTK_POLICY_NEVER,
+						GTK_POLICY_AUTOMATIC);
+		gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
+		tcolumns->priv->ldap_text = sw;
+
+		GtkWidget *textview;
+		textview = gtk_text_view_new ();
+		gtk_container_add (GTK_CONTAINER (sw), textview);
+		gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
+		gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
+		gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
+		tcolumns->priv->ldap_def = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
+		gtk_text_buffer_set_text (tcolumns->priv->ldap_def, "aa", -1);
+
+		gtk_text_buffer_create_tag (tcolumns->priv->ldap_def, "section",
+					    "weight", PANGO_WEIGHT_BOLD,
+					    "foreground", "blue", NULL);
+		
+		gtk_text_buffer_create_tag (tcolumns->priv->ldap_def, "warning",
+					    "foreground", "red", NULL);
+
+		g_signal_connect (textview, "key-press-event", 
+				  G_CALLBACK (key_press_event), tcolumns);
+		g_signal_connect (textview, "event-after", 
+				  G_CALLBACK (event_after), tcolumns);
+		g_signal_connect (textview, "motion-notify-event", 
+				  G_CALLBACK (motion_notify_event), tcolumns);
+		g_signal_connect (textview, "visibility-notify-event", 
+				  G_CALLBACK (visibility_notify_event), tcolumns);
+	}
+#endif
+
 	/*
 	 * Constraints
 	 */
-	GtkWidget *vbox, *label;
+	GtkWidget *label;
 	gchar *str;
 
-	vbox = gtk_vbox_new (FALSE, 0);
-	gtk_paned_pack2 (GTK_PANED (paned), vbox, TRUE, TRUE);
-
 	str = g_strdup_printf ("<b>%s</b>", _("Constraints and integrity rules"));
 	label = cc_gray_bar_new (str);
 	g_free (str);
@@ -613,6 +768,8 @@ table_columns_new (TableInfo *tinfo)
 	g_signal_connect (textview, "visibility-notify-event", 
 			  G_CALLBACK (visibility_notify_event), tcolumns);
 
+	gtk_widget_show_all (vbox);
+
 	/*
 	 * initial update
 	 */
@@ -645,7 +802,8 @@ set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, TableColumns
 		GtkTextTag *tag = tagp->data;
 
 		if (g_object_get_data (G_OBJECT (tag), "table_name") ||
-		    g_object_get_data (G_OBJECT (tag), "fk_name")) {
+		    g_object_get_data (G_OBJECT (tag), "fk_name") ||
+		    g_object_get_data (G_OBJECT (tag), "dn")) {
 			hovering = TRUE;
 			break;
 		}
@@ -738,15 +896,17 @@ follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, TableColu
 		const gchar *table_schema;
 		const gchar *table_short_name;
 		const gchar *fk_name;
+		const gchar *dn;
 		SchemaBrowserPerspective *bpers;
 		
 		table_schema = g_object_get_data (G_OBJECT (tag), "table_schema");
 		table_name = g_object_get_data (G_OBJECT (tag), "table_name");
 		table_short_name = g_object_get_data (G_OBJECT (tag), "table_short_name");
 		fk_name = g_object_get_data (G_OBJECT (tag), "fk_name");
+		dn = g_object_get_data (G_OBJECT (tag), "dn");
 
 		bpers = SCHEMA_BROWSER_PERSPECTIVE (browser_find_parent_widget (GTK_WIDGET (tcolumns),
-									      TYPE_SCHEMA_BROWSER_PERSPECTIVE));
+						    TYPE_SCHEMA_BROWSER_PERSPECTIVE));
 		if (table_name && table_schema && table_short_name && bpers) {
 			schema_browser_perspective_display_table_info (bpers,
 								       table_schema,
@@ -808,6 +968,17 @@ follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, TableColu
 							    fk_name);
 			}
 		}
+#ifdef HAVE_LDAP
+		else if (dn) {
+			BrowserWindow *bwin;
+			BrowserPerspective *pers;
+
+			bwin = (BrowserWindow*) gtk_widget_get_toplevel ((GtkWidget*) tcolumns);
+			pers = browser_window_change_perspective (bwin, _("LDAP browser"));
+			
+			ldap_browser_perspective_display_ldap_entry (LDAP_BROWSER_PERSPECTIVE (pers), dn);
+		}
+#endif
         }
 
 	if (tags) 
diff --git a/tools/browser/support.c b/tools/browser/support.c
index b1b885f..5277b54 100644
--- a/tools/browser/support.c
+++ b/tools/browser/support.c
@@ -380,7 +380,7 @@ GdkPixbuf *
 browser_get_pixbuf_icon (BrowserIconType type)
 {
 	static GdkPixbuf **array = NULL;
-	static const gchar* names[] = {
+	static const gchar* names[] = { /* array indexed by BrowserIconType */
 		"gda-browser-bookmark.png",
 		"gda-browser-schema.png",
 		"gda-browser-table.png",
@@ -396,6 +396,14 @@ browser_get_pixbuf_icon (BrowserIconType type)
 		"gda-browser-grid.png",
 		"gda-browser-form.png",
 		"gda-browser-menu-ind.png",
+		"gda-browser-ldap-entry.png",
+		"gda-browser-ldap-group.png",
+		"gda-browser-ldap-organization.png",
+		"gda-browser-ldap-person.png",
+		"gda-browser-ldap-class-s.png",
+		"gda-browser-ldap-class-a.png",
+		"gda-browser-ldap-class-x.png",
+		"gda-browser-ldap-class-u.png",
 	};
 
 	if (!array)
@@ -415,6 +423,45 @@ browser_get_pixbuf_icon (BrowserIconType type)
 		return array [type];
 }
 
+#ifdef HAVE_LDAP
+/**
+ * browser_get_pixbuf_for_ldap_class:
+ */
+GdkPixbuf *
+browser_get_pixbuf_for_ldap_class (GdaLdapClassKind kind)
+{
+	switch (kind) {
+	case GDA_LDAP_CLASS_KIND_ABSTRACT:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_CLASS_ABSTRACT);
+        case GDA_LDAP_CLASS_KIND_STRUTURAL:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_CLASS_STRUCTURAL);
+        case GDA_LDAP_CLASS_KIND_AUXILIARY:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_CLASS_AUXILIARY);
+        default:
+		return browser_get_pixbuf_icon (BROWSER_ICON_LDAP_CLASS_UNKNOWN);
+	}
+}
+
+/**
+ * browser_get_kind_for_ldap_class:
+ */
+const gchar *
+browser_get_kind_for_ldap_class (GdaLdapClassKind kind)
+{
+	switch (kind) {
+        case GDA_LDAP_CLASS_KIND_ABSTRACT:
+                return _("Abstract");
+        case GDA_LDAP_CLASS_KIND_STRUTURAL:
+                return _("Structural");
+        case GDA_LDAP_CLASS_KIND_AUXILIARY:
+                return _("Auxilliary");
+        default:
+                return _("Unknown");
+        }
+}
+#endif
+
+
 /**
  * browser_find_parent_widget
  *
diff --git a/tools/browser/support.h b/tools/browser/support.h
index eec7e75..7647a90 100644
--- a/tools/browser/support.h
+++ b/tools/browser/support.h
@@ -30,6 +30,9 @@
 #include <gtkosxapplication.h>
 extern GtkOSXApplication *theApp;
 #endif
+#ifdef HAVE_LDAP
+#include <libgda/sqlite/virtual/gda-ldap-connection.h>
+#endif
 
 G_BEGIN_DECLS
 
@@ -59,7 +62,7 @@ GtkWidget          *browser_make_tree_view (GtkTreeModel *model);
 GtkWidget          *browser_find_parent_widget (GtkWidget *current, GType requested_type);
 
 /*
- * icons
+ * icons, see browser_get_pixbuf_icon() for the associated icons
  */
 typedef enum {
 	BROWSER_ICON_BOOKMARK,
@@ -80,10 +83,23 @@ typedef enum {
 
 	BROWSER_ICON_MENU_INDICATOR,
 
+	BROWSER_ICON_LDAP_ENTRY,
+	BROWSER_ICON_LDAP_GROUP,
+	BROWSER_ICON_LDAP_ORGANIZATION,
+	BROWSER_ICON_LDAP_PERSON,
+	BROWSER_ICON_LDAP_CLASS_STRUCTURAL,
+	BROWSER_ICON_LDAP_CLASS_ABSTRACT,
+	BROWSER_ICON_LDAP_CLASS_AUXILIARY,
+	BROWSER_ICON_LDAP_CLASS_UNKNOWN,
+
 	BROWSER_ICON_LAST
 } BrowserIconType;
 
 GdkPixbuf          *browser_get_pixbuf_icon (BrowserIconType type);
+#ifdef HAVE_LDAP
+GdkPixbuf          *browser_get_pixbuf_for_ldap_class (GdaLdapClassKind kind);
+const gchar        *browser_get_kind_for_ldap_class (GdaLdapClassKind kind);
+#endif
 
 /*
  * Connections list



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