[evolution] Bug #642557 - Display maps in contact preview



commit 6dc3c692264a04470d842400e909034c3633bdae
Author: Dan Vrátil <dvratil redhat com>
Date:   Thu Jun 2 16:57:23 2011 +0200

    Bug #642557 - Display maps in contact preview

 addressbook/gui/widgets/Makefile.am                |    6 +-
 addressbook/gui/widgets/eab-contact-display.c      |  198 ++++++-
 addressbook/gui/widgets/eab-contact-display.h      |    3 +
 configure.ac                                       |   65 +--
 modules/addressbook/Makefile.am                    |    3 +-
 .../apps_evolution_addressbook.schemas.in          |   12 +
 modules/addressbook/e-book-shell-content.c         |   56 ++-
 modules/addressbook/e-book-shell-content.h         |    5 +
 modules/addressbook/e-book-shell-view-actions.c    |  164 ++++++-
 modules/addressbook/e-book-shell-view-actions.h    |    6 +
 plugins/contacts-map/Makefile.am                   |   34 --
 plugins/contacts-map/contacts-map.c                |  226 --------
 plugins/contacts-map/geo-utils.c                   |  124 ----
 plugins/contacts-map/geo-utils.h                   |   49 --
 .../contacts-map/org-gnome-contacts-map.eplug.xml  |   19 -
 po/POTFILES.in                                     |    4 +-
 ui/evolution-contacts.ui                           |    6 +
 widgets/misc/Makefile.am                           |   17 +-
 widgets/misc/e-contact-map-window.c                |  465 +++++++++++++++
 widgets/misc/e-contact-map-window.h                |   77 +++
 widgets/misc/e-contact-map.c                       |  394 +++++++++++++
 widgets/misc/e-contact-map.h                       |  108 ++++
 widgets/misc/e-contact-marker.c                    |  600 ++++++++++++++++++++
 widgets/misc/e-contact-marker.h                    |   85 +++
 24 files changed, 2221 insertions(+), 505 deletions(-)
---
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 511876a..99bbc3a 100644
--- a/addressbook/gui/widgets/Makefile.am
+++ b/addressbook/gui/widgets/Makefile.am
@@ -18,7 +18,8 @@ libeabwidgets_la_CPPFLAGS =				\
 	-I$(top_srcdir)/widgets/misc			\
 	-I$(top_builddir)/shell				\
 	$(GNOME_PLATFORM_CFLAGS)			\
-	$(EVOLUTION_ADDRESSBOOK_CFLAGS)
+	$(EVOLUTION_ADDRESSBOOK_CFLAGS)			\
+	$(CHAMPLAIN_CFLAGS)
 
 eabincludedir = $(privincludedir)/addressbook/gui/widgets
 
@@ -68,7 +69,8 @@ libeabwidgets_la_LIBADD =					\
 	$(top_builddir)/widgets/table/libetable.la		\
 	$(top_builddir)/widgets/menus/libmenus.la		\
 	$(top_builddir)/a11y/libevolution-a11y.la		\
-	$(GNOME_PLATFORM_LIBS)
+	$(GNOME_PLATFORM_LIBS)					\
+	$(CHAMPLAIN_LIBS)
 
 dist-hook:
 	cd $(distdir); rm -f $(BUILT_SOURCES)
diff --git a/addressbook/gui/widgets/eab-contact-display.c b/addressbook/gui/widgets/eab-contact-display.c
index c970afe..3619880 100644
--- a/addressbook/gui/widgets/eab-contact-display.c
+++ b/addressbook/gui/widgets/eab-contact-display.c
@@ -32,10 +32,15 @@
 #include "e-util/e-icon-factory.h"
 #include "e-util/e-plugin-ui.h"
 
+#ifdef WITH_CONTACT_MAPS
+#include "widgets/misc/e-contact-map.h"
+#endif
+
 #include <string.h>
 #include <glib/gi18n.h>
 #include <gtkhtml/gtkhtml.h>
 #include <gtkhtml/gtkhtml-stream.h>
+#include <gtkhtml/gtkhtml-embedded.h>
 
 #define TEXT_IS_RIGHT_TO_LEFT \
 	(gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL)
@@ -44,13 +49,15 @@ struct _EABContactDisplayPrivate {
 	EContact *contact;
 	EABContactDisplayMode mode;
 	GtkOrientation orientation;
+	gboolean show_maps;
 };
 
 enum {
 	PROP_0,
 	PROP_CONTACT,
 	PROP_MODE,
-	PROP_ORIENTATION
+	PROP_ORIENTATION,
+	PROP_SHOW_MAPS
 };
 
 enum {
@@ -183,6 +190,34 @@ static GtkActionEntry internal_mailto_entries[] = {
 };
 
 static void
+render_address_link (GString *buffer, EContact *contact, int map_type)
+{
+	EContactAddress *adr;
+	GString *link = g_string_new ("");
+
+	adr = e_contact_get (contact, map_type);
+	if (adr &&
+	    (adr->street || adr->locality || adr->region || adr->country)) {
+
+		if (adr->street && *adr->street) g_string_append_printf (link, "%s, ", adr->street);
+		if (adr->locality && *adr->locality) g_string_append_printf (link, "%s, ", adr->locality);
+		if (adr->region && *adr->region) g_string_append_printf (link, "%s, ", adr->region);
+		if (adr->country && *adr->country) g_string_append_printf (link, "%s", adr->country);
+
+		g_string_assign (link, g_uri_escape_string (link->str, NULL, TRUE));
+
+		g_string_prepend (link, "<a href=\"http://maps.google.com?q=";);
+		g_string_append_printf (link, "\">%s</a>", _("Open map"));
+	}
+
+	if (adr)
+		e_contact_address_free (adr);
+
+	g_string_append (buffer, link->str);
+	g_string_free (link, TRUE);
+}
+
+static void
 accum_address (GString *buffer,
                EContact *contact,
                const gchar *html_label,
@@ -191,17 +226,21 @@ accum_address (GString *buffer,
 {
 	EContactAddress *adr;
 	const gchar *label;
+	GString *map_link = g_string_new ("<br>");
+
+	render_address_link (map_link, contact, adr_field);
 
 	label = e_contact_get_const (contact, label_field);
 	if (label) {
 		gchar *html = e_text_to_html (label, E_TEXT_TO_HTML_CONVERT_NL);
 
 		if (TEXT_IS_RIGHT_TO_LEFT)
-			g_string_append_printf (buffer, "<tr><td align=\"right\" valign=\"top\" nowrap>%s</td><td valign=\"top\" width=\"100\" align=\"right\"><font color=" HEADER_COLOR ">%s:</font></td><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td></tr>", html, html_label);
+			g_string_append_printf (buffer, "<tr><td align=\"right\" valign=\"top\" nowrap>%s</td><td valign=\"top\" width=\"100\" align=\"right\" nowrap><font color=" HEADER_COLOR ">%s:</font>%s</td><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td></tr>", html, html_label, map_link->str);
 		else
-			g_string_append_printf (buffer, "<tr><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td><td valign=\"top\" width=\"100\"><font color=" HEADER_COLOR ">%s:</font></td><td valign=\"top\" nowrap>%s</td></tr>", html_label, html);
+			g_string_append_printf (buffer, "<tr><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td><td valign=\"top\" width=\"100\" nowrap><font color=" HEADER_COLOR ">%s:</font>%s</td><td valign=\"top\" nowrap>%s</td></tr>", html_label, map_link->str, html);
 
 		g_free (html);
+		g_string_free (map_link, TRUE);
 		return;
 	}
 
@@ -211,7 +250,7 @@ accum_address (GString *buffer,
 		if (TEXT_IS_RIGHT_TO_LEFT)
 			g_string_append_printf (buffer, "<tr><td align=\"right\" valign=\"top\" nowrap>");
 		else
-			g_string_append_printf (buffer, "<tr><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td><td valign=\"top\" width=\"100\"><font color=" HEADER_COLOR ">%s:</font></td><td valign=\"top\" nowrap>", html_label);
+			g_string_append_printf (buffer, "<tr><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td><td valign=\"top\" width=\"100\"><font color=" HEADER_COLOR ">%s:</font>%s</td><td valign=\"top\" nowrap>", html_label, map_link->str);
 
 		if (adr->po && *adr->po) g_string_append_printf (buffer, "%s<br>", adr->po);
 		if (adr->ext && *adr->ext) g_string_append_printf (buffer, "%s<br>", adr->ext);
@@ -222,12 +261,14 @@ accum_address (GString *buffer,
 		if (adr->country && *adr->country) g_string_append_printf (buffer, "%s<br>", adr->country);
 
 		if (TEXT_IS_RIGHT_TO_LEFT)
-			g_string_append_printf (buffer, "</td><td valign=\"top\" width=\"100\" align=\"right\"><font color=" HEADER_COLOR ">%s:</font></td><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td></tr>", html_label);
+			g_string_append_printf (buffer, "</td><td valign=\"top\" width=\"100\" align=\"right\"><font color=" HEADER_COLOR ">%s:</font>%s</td><td valign=\"top\" width=\"" IMAGE_COL_WIDTH "\"></td></tr>", html_label, map_link->str);
 		else
 			g_string_append_printf (buffer, "</td></tr>");
 	}
 	if (adr)
 		e_contact_address_free (adr);
+
+	g_string_free (map_link, TRUE);
 }
 
 static void
@@ -602,7 +643,19 @@ render_note_block (GString *buffer, EContact *contact)
 }
 
 static void
-render_contact_horizontal (GString *buffer, EContact *contact)
+render_address_map (GString *buffer, EContact *contact, int map_type)
+{
+#ifdef WITH_CONTACT_MAPS
+	if (map_type == E_CONTACT_ADDRESS_WORK) {
+		g_string_append (buffer, "<object classid=\"address-map-work\"></object>");
+ 	 } else {
+ 		g_string_append (buffer, "<object classid=\"address-map-home\"></object>");
+ 	 }
+#endif
+}
+
+static void
+render_contact_horizontal (GString *buffer, EContact *contact, gboolean show_maps)
 {
 	g_string_append (buffer, "<table border=\"0\">");
 	render_title_block (buffer, contact);
@@ -611,7 +664,15 @@ render_contact_horizontal (GString *buffer, EContact *contact)
 	g_string_append (buffer, "<table border=\"0\">");
 	render_contact_block (buffer, contact);
 	render_work_block (buffer, contact);
+	g_string_append (buffer, "<tr><td></td><td colspan=\"2\">");
+	if (show_maps)
+		render_address_map (buffer, contact, E_CONTACT_ADDRESS_WORK);
+	g_string_append (buffer, "<br></td></tr>");
 	render_personal_block (buffer, contact);
+	g_string_append (buffer, "<tr><td></td><td colspan=\"2\">");
+	if (show_maps)
+		render_address_map (buffer, contact, E_CONTACT_ADDRESS_HOME);
+	g_string_append (buffer, "<br></td></tr>");
 	g_string_append (buffer, "</table>");
 
 	g_string_append (buffer, "<table border=\"0\">");
@@ -620,7 +681,7 @@ render_contact_horizontal (GString *buffer, EContact *contact)
 }
 
 static void
-render_contact_vertical (GString *buffer, EContact *contact)
+render_contact_vertical (GString *buffer, EContact *contact, gboolean show_maps)
 {
 	/* First row: photo & name */
 	g_string_append (buffer, "<tr><td colspan=\"3\">");
@@ -640,12 +701,16 @@ render_contact_vertical (GString *buffer, EContact *contact)
 	g_string_append (buffer, "<td width=\"30\"></td><td valign=\"top\"><table border=\"0\">");
 	render_work_block (buffer, contact);
 	g_string_append (buffer, "</table>");
+	if (show_maps)
+		render_address_map (buffer, contact, E_CONTACT_ADDRESS_WORK);
 	g_string_append (buffer, "</td>");
 
 	/* Third column: Personal */
 	g_string_append (buffer, "<td width=\"30\"></td><td valign=\"top\"><table border=\"0\">");
 	render_personal_block (buffer, contact);
 	g_string_append (buffer, "</table>");
+	if (show_maps)
+		render_address_map (buffer, contact, E_CONTACT_ADDRESS_HOME);
 	g_string_append (buffer, "</td>");
 
 	/* Third row: note */
@@ -657,12 +722,12 @@ render_contact_vertical (GString *buffer, EContact *contact)
 }
 
 static void
-render_contact (GString *buffer, EContact *contact, GtkOrientation orientation)
+render_contact (GString *buffer, EContact *contact, GtkOrientation orientation, gboolean show_maps)
 {
 	if (orientation == GTK_ORIENTATION_VERTICAL)
-		render_contact_vertical (buffer, contact);
+		render_contact_vertical (buffer, contact, show_maps);
 	else
-		render_contact_horizontal (buffer, contact);
+		render_contact_horizontal (buffer, contact, show_maps);
 }
 
 static void
@@ -686,7 +751,7 @@ eab_contact_display_render_normal (EABContactDisplay *display,
 		if (e_contact_get (contact, E_CONTACT_IS_LIST))
 			render_contact_list (buffer, contact);
 		else
-			render_contact (buffer, contact, orientation);
+			render_contact (buffer, contact, orientation, display->priv->show_maps);
 
 	}
 
@@ -910,6 +975,12 @@ contact_display_set_property (GObject *object,
 				EAB_CONTACT_DISPLAY (object),
 				g_value_get_int (value));
 			return;
+
+		case PROP_SHOW_MAPS:
+			eab_contact_display_set_show_maps (
+				EAB_CONTACT_DISPLAY (object),
+				g_value_get_boolean (value));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -933,11 +1004,18 @@ contact_display_get_property (GObject *object,
 				value, eab_contact_display_get_mode (
 				EAB_CONTACT_DISPLAY (object)));
 			return;
+
 		case PROP_ORIENTATION:
 			g_value_set_int (
 				value, eab_contact_display_get_orientation (
 				EAB_CONTACT_DISPLAY (object)));
 			return;
+
+		case PROP_SHOW_MAPS:
+			g_value_set_boolean (
+				value, eab_contact_display_get_show_maps (
+				EAB_CONTACT_DISPLAY (object)));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -1086,6 +1164,55 @@ contact_display_link_clicked (EWebView *web_view,
 	E_WEB_VIEW_CLASS (parent_class)->link_clicked (web_view, uri);
 }
 
+#ifdef WITH_CONTACT_MAPS
+/**
+ * Clutter event handling workaround. Clutter-gtk propagates events down to parent widgets.
+ * In this case it leads to GtkHTML scrolling up and down while user's trying to zoom in the
+ * champlain widget. This workaround stops the propagation from map widget down to GtkHTML
+ */
+static gboolean
+handle_map_scroll_event (GtkWidget *widget, GdkEvent *event)
+{
+	return TRUE;
+}
+
+static void
+contact_display_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, EABContactDisplay *display)
+{
+    	EContact *contact = display->priv->contact;
+    	const gchar *name = e_contact_get_const (contact, E_CONTACT_FILE_AS);
+    	const gchar *contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+    	gchar *full_name;
+    	EContactAddress *address;
+
+	if (g_ascii_strcasecmp (eb->classid, "address-map-work") == 0) {
+		address = e_contact_get (contact, E_CONTACT_ADDRESS_WORK);
+		full_name = g_strconcat (name, " (", _("Work"), ")", NULL);
+	} else {
+		address = e_contact_get (contact, E_CONTACT_ADDRESS_HOME);
+		full_name = g_strconcat (name, " (", _("Home"), ")", NULL);
+	}
+
+	if (address) {
+	    	GtkWidget *map = e_contact_map_new ();
+		gtk_container_add (GTK_CONTAINER (eb), map);
+		gtk_widget_set_size_request (map, 250, 250);
+		g_signal_connect (E_CONTACT_MAP (map), "contact-added",
+			G_CALLBACK (e_contact_map_zoom_on_marker), NULL);
+		g_signal_connect_swapped (E_CONTACT_MAP (map), "contact-added",
+			G_CALLBACK (gtk_widget_show_all), map);
+		g_signal_connect (GTK_CHAMPLAIN_EMBED (map), "scroll-event",
+			G_CALLBACK (handle_map_scroll_event), NULL);
+
+				/* No need to display photo in contact preview ------------------v */
+		e_contact_map_add_marker (E_CONTACT_MAP (map), full_name, contact_uid, address, NULL);
+	}
+
+	g_free (full_name);
+	e_contact_address_free (address);
+}
+#endif
+
 static void
 contact_display_update_actions (EWebView *web_view)
 {
@@ -1174,6 +1301,16 @@ eab_contact_display_class_init (EABContactDisplayClass *class)
 			GTK_ORIENTATION_HORIZONTAL,
 			G_PARAM_READWRITE));
 
+	g_object_class_install_property	(
+		object_class,
+		PROP_SHOW_MAPS,
+		g_param_spec_boolean (
+			"show-maps",
+			NULL,
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
+
 	signals[SEND_MESSAGE] = g_signal_new (
 		"send-message",
 		G_OBJECT_CLASS_TYPE (class),
@@ -1198,10 +1335,16 @@ eab_contact_display_init (EABContactDisplay *display)
 		display, EAB_TYPE_CONTACT_DISPLAY, EABContactDisplayPrivate);
 	display->priv->mode = EAB_CONTACT_DISPLAY_RENDER_NORMAL;
 	display->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+	display->priv->show_maps = FALSE;
 
 	web_view = E_WEB_VIEW (display);
 	ui_manager = e_web_view_get_ui_manager (web_view);
 
+#ifdef WITH_CONTACT_MAPS
+	g_signal_connect (web_view, "object-requested",
+       	G_CALLBACK (contact_display_object_requested), display);
+#endif
+
 	action_group = gtk_action_group_new ("internal-mailto");
 	gtk_action_group_set_translation_domain (action_group, domain);
 	gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
@@ -1352,3 +1495,36 @@ eab_contact_display_set_orientation (EABContactDisplay *display, GtkOrientation
 
 	g_object_notify (G_OBJECT (display), "orientation");
 }
+
+gboolean
+eab_contact_display_get_show_maps (EABContactDisplay *display)
+{
+	g_return_val_if_fail (EAB_IS_CONTACT_DISPLAY (display), FALSE);
+
+	return display->priv->show_maps;
+}
+
+void
+eab_contact_display_set_show_maps (EABContactDisplay *display, gboolean show_maps)
+{
+	EABContactDisplayMode mode;
+	EContact *contact;
+
+	g_return_if_fail (EAB_IS_CONTACT_DISPLAY (display));
+
+	display->priv->show_maps = show_maps;
+	contact = eab_contact_display_get_contact (display);
+	mode = eab_contact_display_get_mode (display);
+
+	switch (mode) {
+		case EAB_CONTACT_DISPLAY_RENDER_NORMAL:
+			eab_contact_display_render_normal (display, contact);
+			break;
+
+		case EAB_CONTACT_DISPLAY_RENDER_COMPACT:
+			eab_contact_display_render_compact (display, contact);
+			break;
+	}
+
+	g_object_notify (G_OBJECT (display), "show-maps");
+}
diff --git a/addressbook/gui/widgets/eab-contact-display.h b/addressbook/gui/widgets/eab-contact-display.h
index e74a65c..f7561f8 100644
--- a/addressbook/gui/widgets/eab-contact-display.h
+++ b/addressbook/gui/widgets/eab-contact-display.h
@@ -93,6 +93,9 @@ void		eab_contact_display_set_orientation
 						(EABContactDisplay *display,
 						 GtkOrientation orientation);
 
+gboolean eab_contact_display_get_show_maps (EABContactDisplay *display);
+void eab_contact_display_set_show_maps (EABContactDisplay *display, gboolean display_maps);
+
 G_END_DECLS
 
 #endif /* EAB_CONTACT_DISPLAY_H */
diff --git a/configure.ac b/configure.ac
index 4d1e091..79132fb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -50,7 +50,7 @@ m4_define([libpst_minimum_version], [0.6.41])
 m4_define([libnotify_minimum_version], [0.5.1])
 
 dnl Optional Packages
-m4_define([champlain_minimum_version], [0.8])
+m4_define([champlain_minimum_version], [0.10])
 m4_define([clutter_gtk_minimum_version], [0.90])
 m4_define([geoclue_minimum_version], [0.11.1])
 m4_define([gladeui_minimum_version], [3.10.0])
@@ -1414,7 +1414,7 @@ dist_plugins_standard="$plugins_standard audio-inline image-inline pst-import"
 plugins_experimental_always="external-editor"
 
 plugins_experimental="$plugins_experimental_always $TNEF_ATTACHMENTS"
-dist_plugins_experimental="$plugins_experimental_always tnef-attachments contacts-map"
+dist_plugins_experimental="$plugins_experimental_always tnef-attachments"
 
 dnl ******************************************************************
 dnl The following plugins have additional library dependencies.
@@ -1462,42 +1462,42 @@ if test "x$enable_weather" = "xyes"; then
 	fi
 fi
 
-dnl ******************************************************
-dnl contacts-map plugin requires champlain-gtk and geoclue
-dnl N.B. The plugin is experimental so only require these
-dnl      packages if we are building experimental plugins.
-dnl ******************************************************
-if test "x$enable_plugins" = "xexperimental"; then
-	AC_ARG_ENABLE([contacts-map],
-		[AS_HELP_STRING([--enable-contacts-map],
-		[Enable contacts-map plugin @<:@default=yes@:>@])],
-		[enable_contacts_map="$enableval"], [enable_contacts_map=yes])
-
-	if test "x$enable_contacts_map" = "xyes"; then
-		PKG_CHECK_MODULES([CHAMPLAIN], [champlain-gtk-0.8 >= champlain_minimum_version], [have_champlain=yes], [have_champlain=no])
-		AC_SUBST(CHAMPLAIN_CFLAGS)
-		AC_SUBST(CHAMPLAIN_LIBS)
-
-		if test "x$have_champlain" = "xno"; then
-			AC_MSG_ERROR([champlain-gtk-0.8 >= champlain_minimum_version is required for the contacts-map plugin.  Use --disable-contacts-map to exclude the plugin.])
-		fi
+dnl ********************************************************************
+dnl maps in contacts preview requires champlain-gtk, geoclue and clutter
+dnl ********************************************************************
+AC_ARG_ENABLE([contact-maps],
+	[AS_HELP_STRING([--enable-contact-maps],
+	[Enable contact maps @<:@default=no@:>@])],
+	[enable_contact_maps="$enableval"], [enable_contact_maps="no"])
+
+if test "x$enable_contact_maps" = "xyes"; then
+	if test "x$with_clutter" = "xno"; then
+		AC_MSG_ERROR([Clutter is required for maps in contacts. Use --with-clutter=yes to enable clutter.])
+	fi
 
-		PKG_CHECK_MODULES([GEOCLUE], [geoclue >= geoclue_minimum_version], [have_geoclue=yes], [have_geoclue=no])
-		AC_SUBST(GEOCLUE_CFLAGS)
-		AC_SUBST(GEOCLUE_LIBS)
+	PKG_CHECK_MODULES([CHAMPLAIN], [champlain-gtk-0.10 >= champlain_minimum_version], [have_champlain=yes], [have_champlain=no])
+	AC_SUBST(CHAMPLAIN_CFLAGS)
+	AC_SUBST(CHAMPLAIN_LIBS)
 
-		if test "x$have_geoclue" = "xno"; then
-			AC_MSG_ERROR([geoclue is required for the contacts-map plugin.  Use --disable-contacts-map to exclude the plugin.])
-		fi
+	if test "x$have_champlain" = "xno"; then
+		AC_MSG_ERROR([champlain-gtk-0.10 >= champlain_minimum_version is required for maps in contacts preview.])
+	fi
 
-		PKG_CHECK_MODULES([CLUTTER_GTK], [clutter-gtk-1.0 >= clutter_gtk_minimum_version], [have_clutter_gtk="yes"], [have_clutter_gtk="no"] )
+	PKG_CHECK_MODULES([GEOCLUE], [geoclue >= geoclue_minimum_version], [have_geoclue=yes], [have_geoclue=no])
+	AC_SUBST(GEOCLUE_CFLAGS)
+	AC_SUBST(GEOCLUE_LIBS)
 
-		if test "x$have_clutter_gtk" = "xno"; then
-			AC_MSG_ERROR([clutter-gtk-1.0 is required for the contacts-map plugin.  Use --disable-contacts-map to exclude the plugin.])
-		fi
+	if test "x$have_geoclue" = "xno"; then
+		AC_MSG_ERROR([geoclue is required for maps in contacts preview.])
+	fi
 
-		plugins_standard="$plugins_standard contacts-map"
+	PKG_CHECK_MODULES([CLUTTER_GTK], [clutter-gtk-1.0 >= clutter_gtk_minimum_version], [have_clutter_gtk="yes"], [have_clutter_gtk="no"] )
+
+	if test "x$have_clutter_gtk" = "xno"; then
+		AC_MSG_ERROR([clutter-gtk-1.0 is required for maps in contacts preview.])
 	fi
+
+	AC_DEFINE(WITH_CONTACT_MAPS, 1, [When defined contacts preview will contain maps])
 fi
 
 dnl *****************************************
@@ -1767,7 +1767,6 @@ plugins/templates/Makefile
 plugins/tnef-attachments/Makefile
 plugins/vcard-inline/Makefile
 plugins/webdav-account-setup/Makefile
-plugins/contacts-map/Makefile
 smclient/Makefile
 smime/Makefile
 smime/lib/Makefile
diff --git a/modules/addressbook/Makefile.am b/modules/addressbook/Makefile.am
index 35cf16a..b6dec30 100644
--- a/modules/addressbook/Makefile.am
+++ b/modules/addressbook/Makefile.am
@@ -21,7 +21,8 @@ libevolution_module_addressbook_la_CPPFLAGS =			\
 	-DPREFIX=\""$(prefix)"\"				\
 	$(GNOME_PLATFORM_CFLAGS)				\
 	$(EVOLUTION_ADDRESSBOOK_CFLAGS)				\
-	$(LDAP_CFLAGS)
+	$(LDAP_CFLAGS)						\
+	$(CHAMPLAIN_CFLAGS)
 
 libevolution_module_addressbook_la_SOURCES = \
 	evolution-module-addressbook.c				\
diff --git a/modules/addressbook/apps_evolution_addressbook.schemas.in b/modules/addressbook/apps_evolution_addressbook.schemas.in
index 03e6006..e13337e 100644
--- a/modules/addressbook/apps_evolution_addressbook.schemas.in
+++ b/modules/addressbook/apps_evolution_addressbook.schemas.in
@@ -125,6 +125,18 @@
          <long>Whether to show the preview pane.</long>
       </locale>
     </schema>
+    
+    <schema>
+      <key>/schemas/apps/evolution/addressbook/display/preview_show_maps</key>
+      <applyto>/apps/evolution/addressbook/display/preview_show_maps</applyto>
+      <owner>evolution-addressbook</owner>
+      <type>bool</type>
+      <default>false</default>
+      <locale name="C">
+      	<short>Show maps</short>
+      	<long>Whether to show maps in preview pane.</long>
+      </locale>
+    </schema>
 
   </schemalist>
 </gconfschemafile>
diff --git a/modules/addressbook/e-book-shell-content.c b/modules/addressbook/e-book-shell-content.c
index bb0c4a0..a86d101 100644
--- a/modules/addressbook/e-book-shell-content.c
+++ b/modules/addressbook/e-book-shell-content.c
@@ -41,6 +41,7 @@ struct _EBookShellContentPrivate {
 
 	GtkOrientation orientation;
 
+	gboolean preview_show_maps;
 	guint preview_visible	: 1;
 };
 
@@ -49,7 +50,8 @@ enum {
 	PROP_CURRENT_VIEW,
 	PROP_ORIENTATION,
 	PROP_PREVIEW_CONTACT,
-	PROP_PREVIEW_VISIBLE
+	PROP_PREVIEW_VISIBLE,
+	PROP_PREVIEW_SHOW_MAPS
 };
 
 static gpointer parent_class;
@@ -144,6 +146,12 @@ book_shell_content_set_property (GObject *object,
 				E_BOOK_SHELL_CONTENT (object),
 				g_value_get_boolean (value));
 			return;
+
+		case PROP_PREVIEW_SHOW_MAPS:
+			e_book_shell_content_set_preview_show_maps (
+				E_BOOK_SHELL_CONTENT (object),
+				g_value_get_boolean (value));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -183,6 +191,13 @@ book_shell_content_get_property (GObject *object,
 				e_book_shell_content_get_preview_visible (
 				E_BOOK_SHELL_CONTENT (object)));
 			return;
+
+		case PROP_PREVIEW_SHOW_MAPS:
+			g_value_set_boolean (
+				value,
+				e_book_shell_content_get_preview_show_maps (
+				E_BOOK_SHELL_CONTENT (object)));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -265,11 +280,20 @@ book_shell_content_constructed (GObject *object)
 		EAB_CONTACT_DISPLAY (widget),
 		priv->orientation);
 
+	eab_contact_display_set_show_maps (
+		EAB_CONTACT_DISPLAY (widget),
+		priv->preview_show_maps);
+
 	g_object_bind_property (
 		object, "orientation",
 		widget, "orientation",
 		G_BINDING_SYNC_CREATE);
 
+	g_object_bind_property (
+		object, "preview-show-maps",
+		widget, "show-maps",
+		G_BINDING_SYNC_CREATE);
+
 	gtk_widget_show (widget);
 
 	g_signal_connect_swapped (
@@ -448,6 +472,16 @@ book_shell_content_class_init (EBookShellContentClass *class)
 
 	g_object_class_override_property (
 		object_class, PROP_ORIENTATION, "orientation");
+
+	g_object_class_install_property (
+		object_class,
+		PROP_PREVIEW_SHOW_MAPS,
+		g_param_spec_boolean (
+			"preview-show-maps",
+			NULL,
+			NULL,
+			FALSE,
+			G_PARAM_READWRITE));
 }
 
 static void
@@ -686,6 +720,26 @@ e_book_shell_content_set_preview_visible (EBookShellContent *book_shell_content,
 	g_object_notify (G_OBJECT (book_shell_content), "preview-visible");
 }
 
+gboolean
+e_book_shell_content_get_preview_show_maps (EBookShellContent *book_shell_content)
+{
+	g_return_val_if_fail (
+		E_IS_BOOK_SHELL_CONTENT (book_shell_content), FALSE);
+
+	return book_shell_content->priv->preview_show_maps;
+}
+
+void
+e_book_shell_content_set_preview_show_maps (EBookShellContent *book_shell_content,
+                                            gboolean show_maps)
+{
+	g_return_if_fail (E_IS_BOOK_SHELL_CONTENT (book_shell_content));
+
+	book_shell_content->priv->preview_show_maps = show_maps;
+
+	g_object_notify (G_OBJECT (book_shell_content), "preview-show-maps");
+}
+
 EShellSearchbar *
 e_book_shell_content_get_searchbar (EBookShellContent *book_shell_content)
 {
diff --git a/modules/addressbook/e-book-shell-content.h b/modules/addressbook/e-book-shell-content.h
index 0876ec9..28fef27 100644
--- a/modules/addressbook/e-book-shell-content.h
+++ b/modules/addressbook/e-book-shell-content.h
@@ -105,6 +105,11 @@ gboolean	e_book_shell_content_get_preview_visible
 void		e_book_shell_content_set_preview_visible
 					(EBookShellContent *book_shell_content,
 					 gboolean preview_visible);
+gboolean	e_book_shell_content_get_preview_show_maps
+					(EBookShellContent *book_shell_content);
+void		e_book_shell_content_set_preview_show_maps
+					(EBookShellContent *book_shell_content,
+					 gboolean show_maps);
 EShellSearchbar *
 		e_book_shell_content_get_searchbar
 					(EBookShellContent *book_shell_content);
diff --git a/modules/addressbook/e-book-shell-view-actions.c b/modules/addressbook/e-book-shell-view-actions.c
index b8e6c4f..5116232 100644
--- a/modules/addressbook/e-book-shell-view-actions.c
+++ b/modules/addressbook/e-book-shell-view-actions.c
@@ -29,6 +29,10 @@
 #include <e-util/e-util.h>
 #include <filter/e-filter-rule.h>
 
+#ifdef WITH_CONTACT_MAPS
+#include <widgets/misc/e-contact-map-window.h>
+#endif
+
 #include <addressbook-config.h>
 
 static void
@@ -211,6 +215,118 @@ action_address_book_properties_cb (GtkAction *action,
 	gtk_window_present (GTK_WINDOW (closure->editor));
 }
 
+#ifdef WITH_CONTACT_MAPS
+static void
+contact_editor_contact_modified_cb (EABEditor *editor,
+				    const GError *error,
+				    EContact *contact,
+				    gpointer user_data)
+{
+	EContactMapWindow *window = user_data;
+	EContactMap *map;
+	const gchar *contact_uid;
+
+	if (error) {
+		g_warning ("Error modifying contact: %s", error->message);
+		return;
+	}
+
+	map = e_contact_map_window_get_map (window);
+
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+
+	e_contact_map_remove_contact (map, contact_uid);
+	e_contact_map_add_contact (map, contact);
+}
+
+static void
+map_window_show_contact_editor_cb (EContactMapWindow *window,
+				   const gchar *contact_uid,
+			  	   gpointer user_data)
+{
+	EShell *shell = e_shell_get_default();
+	EBookShellView *book_shell_view = user_data;
+	EBookShellSidebar *book_shell_sidebar;
+	ESource *source;
+	ESourceSelector *selector;
+	EBook *book;
+	EContact *contact;
+	EABEditor *editor;
+	GError *error = NULL;
+
+	book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+	selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+	source = e_source_selector_get_primary_selection (selector);
+
+	g_return_if_fail (source != NULL);
+	book = e_book_new (source, &error);
+	if (error) {
+		g_warning ("Error loading addressbook: %s", error->message);
+		g_error_free (error);
+		g_object_unref (book);
+		return;
+	}
+
+	e_book_get_contact (book, contact_uid, &contact, &error);
+	if (error) {
+		g_warning ("Error getting contact from addressbook: %s", error->message);
+		g_error_free (error);
+		g_object_unref (book);
+		return;
+	}
+
+	editor = e_contact_editor_new (shell, book, contact, FALSE, TRUE);
+
+	g_signal_connect (editor, "contact-modified",
+		G_CALLBACK (contact_editor_contact_modified_cb), window);
+	g_signal_connect_swapped (editor, "editor-closed",
+		G_CALLBACK (g_object_unref), editor);
+
+	eab_editor_show (editor);
+	g_object_unref (book);
+}
+#endif
+
+/* We need this function to he defined all the time. */
+static void
+action_address_book_map_cb (GtkAction *action,
+			    EBookShellView *book_shell_view)
+{
+#ifdef WITH_CONTACT_MAPS
+	EContactMapWindow *map_window;
+        EBookShellSidebar *book_shell_sidebar;
+        ESource *source;
+        ESourceSelector *selector;
+        EBook *book;
+        GError *error = NULL;
+
+        book_shell_sidebar = book_shell_view->priv->book_shell_sidebar;
+        selector = e_book_shell_sidebar_get_selector (book_shell_sidebar);
+        source = e_source_selector_get_primary_selection (selector);
+
+        g_return_if_fail (source != NULL);
+        book = e_book_new (source, &error);
+        if (error != NULL) {
+		g_warning ("Error loading addressbook: %s", error->message);
+                g_error_free (error);
+		return;
+	}
+
+	map_window = e_contact_map_window_new ();
+	e_contact_map_window_load_addressbook (map_window, book);
+
+	/* Free the map_window automatically when it is closed */
+	g_signal_connect_swapped (GTK_WIDGET (map_window), "hide",
+		G_CALLBACK (gtk_widget_destroy), GTK_WIDGET (map_window));
+	g_signal_connect (map_window, "show-contact-editor",
+		G_CALLBACK (map_window_show_contact_editor_cb), book_shell_view);
+
+	gtk_widget_show_all (GTK_WIDGET (map_window));
+
+	g_object_unref (book);
+#endif
+}
+
 static void
 action_address_book_rename_cb (GtkAction *action,
                                EBookShellView *book_shell_view)
@@ -503,6 +619,18 @@ action_contact_preview_cb (GtkToggleAction *action,
 }
 
 static void
+action_contact_preview_show_maps_cb (GtkToggleAction *action,
+                                     EBookShellView *book_shell_view)
+{
+	EBookShellContent *book_shell_content;
+	gboolean show_maps;
+
+	book_shell_content = book_shell_view->priv->book_shell_content;
+	show_maps = gtk_toggle_action_get_active (action);
+	e_book_shell_content_set_preview_show_maps (book_shell_content, show_maps);
+}
+
+static void
 action_contact_print_cb (GtkAction *action,
                          EBookShellView *book_shell_view)
 {
@@ -714,6 +842,13 @@ static GtkActionEntry contact_entries[] = {
 	  N_("Show properties of the selected address book"),
 	  G_CALLBACK (action_address_book_properties_cb) },
 
+	{ "address-book-map",
+	  NULL,
+	  N_("Address Book _Map"),
+	  NULL,
+	  N_("Show map with all contacts from selected address book"),
+	  G_CALLBACK (action_address_book_map_cb) },
+
 	{ "address-book-rename",
 	  NULL,
 	  N_("_Rename..."),
@@ -818,6 +953,10 @@ static EPopupActionEntry contact_popup_entries[] = {
 	  N_("_Properties"),
 	  "address-book-properties" },
 
+	{ "address-book-popup-map",
+	  N_("Addressbook Map"),
+	  "address-book-map" },
+
 	{ "address-book-popup-rename",
 	  NULL,
 	  "address-book-rename" },
@@ -851,7 +990,15 @@ static GtkToggleActionEntry contact_toggle_entries[] = {
 	  "<Control>m",
 	  N_("Show contact preview window"),
 	  G_CALLBACK (action_contact_preview_cb),
-	  TRUE }
+	  TRUE },
+
+	{ "contact-preview-show-maps",
+	  NULL,
+	  N_("Show _Maps"),
+	  NULL,
+	  N_("Show maps in contact preview window"),
+	  G_CALLBACK (action_contact_preview_show_maps_cb),
+	  FALSE }
 };
 
 static GtkRadioActionEntry contact_view_entries[] = {
@@ -1071,6 +1218,10 @@ e_book_shell_view_actions_init (EBookShellView *book_shell_view)
 	key = "/apps/evolution/addressbook/display/layout";
 	gconf_bridge_bind_property (bridge, key, object, "current-value");
 
+	object = G_OBJECT (ACTION (CONTACT_PREVIEW_SHOW_MAPS));
+	key = "/apps/evolution/addressbook/display/preview_show_maps";
+	gconf_bridge_bind_property (bridge, key, object, "active");
+
 	/* Fine tuning. */
 
 	g_signal_connect (
@@ -1087,9 +1238,20 @@ e_book_shell_view_actions_init (EBookShellView *book_shell_view)
 		ACTION (CONTACT_VIEW_VERTICAL), "sensitive",
 		G_BINDING_SYNC_CREATE);
 
+	g_object_bind_property (
+		ACTION (CONTACT_PREVIEW), "active",
+		ACTION (CONTACT_PREVIEW_SHOW_MAPS), "sensitive",
+		G_BINDING_SYNC_CREATE);
+
 	e_web_view_set_open_proxy (web_view, ACTION (CONTACT_OPEN));
 	e_web_view_set_print_proxy (web_view, ACTION (CONTACT_PRINT));
 	e_web_view_set_save_as_proxy (web_view, ACTION (CONTACT_SAVE_AS));
+
+#ifndef WITH_CONTACT_MAPS
+	gtk_action_set_visible (ACTION (CONTACT_PREVIEW_SHOW_MAPS), FALSE);
+	gtk_action_set_visible (ACTION (ADDRESS_BOOK_MAP), FALSE);
+	gtk_action_set_visible (ACTION (ADDRESS_BOOK_POPUP_MAP), FALSE);
+#endif
 }
 
 void
diff --git a/modules/addressbook/e-book-shell-view-actions.h b/modules/addressbook/e-book-shell-view-actions.h
index ef40dd1..87eeaa8 100644
--- a/modules/addressbook/e-book-shell-view-actions.h
+++ b/modules/addressbook/e-book-shell-view-actions.h
@@ -43,6 +43,10 @@
 	E_SHELL_WINDOW_ACTION ((window), "address-book-save-as")
 #define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_STOP(window) \
 	E_SHELL_WINDOW_ACTION ((window), "address-book-stop")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_MAP(window) \
+	E_SHELL_WINDOW_ACTION ((window), "address-book-map")
+#define E_SHELL_WINDOW_ACTION_ADDRESS_BOOK_POPUP_MAP(window) \
+	E_SHELL_WINDOW_ACTION ((window), "address-book-popup-map")
 
 /* Contact Actions */
 #define E_SHELL_WINDOW_ACTION_CONTACT_COPY(window) \
@@ -63,6 +67,8 @@
 	E_SHELL_WINDOW_ACTION ((window), "contact-open")
 #define E_SHELL_WINDOW_ACTION_CONTACT_PREVIEW(window) \
 	E_SHELL_WINDOW_ACTION ((window), "contact-preview")
+#define E_SHELL_WINDOW_ACTION_CONTACT_PREVIEW_SHOW_MAPS(window) \
+	E_SHELL_WINDOW_ACTION ((window), "contact-preview-show-maps")
 #define E_SHELL_WINDOW_ACTION_CONTACT_PRINT(window) \
 	E_SHELL_WINDOW_ACTION ((window), "contact-print")
 #define E_SHELL_WINDOW_ACTION_CONTACT_SAVE_AS(window) \
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 700d9a8..b117789 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -304,8 +304,6 @@ plugins/calendar-http/calendar-http.c
 plugins/calendar-http/org-gnome-calendar-http.eplug.xml
 plugins/calendar-weather/calendar-weather.c
 plugins/calendar-weather/org-gnome-calendar-weather.eplug.xml
-plugins/contacts-map/contacts-map.c
-plugins/contacts-map/org-gnome-contacts-map.eplug.xml
 plugins/dbx-import/dbx-importer.c
 plugins/dbx-import/org-gnome-dbx-import.eplug.xml
 plugins/default-source/default-source.c
@@ -431,6 +429,8 @@ widgets/misc/e-calendar.c
 widgets/misc/e-canvas-background.c
 widgets/misc/e-canvas-vbox.c
 widgets/misc/e-charset-combo-box.c
+widgets/misc/e-contact-map.c
+widgets/misc/e-contact-map-window.c
 widgets/misc/e-dateedit.c
 widgets/misc/e-focus-tracker.c
 widgets/misc/e-image-chooser.c
diff --git a/ui/evolution-contacts.ui b/ui/evolution-contacts.ui
index 0e0716d..571e825 100644
--- a/ui/evolution-contacts.ui
+++ b/ui/evolution-contacts.ui
@@ -28,6 +28,7 @@
       <placeholder name='view-custom-menus'>
         <menu action='contact-preview-menu'>
           <menuitem action='contact-preview'/>
+          <menuitem action='contact-preview-show-maps'/>
           <separator/>
           <menuitem action='contact-view-classic'/>
           <menuitem action='contact-view-vertical'/>
@@ -46,6 +47,8 @@
         <menuitem action='address-book-copy'/>
         <menuitem action='address-book-move'/>
         <separator/>
+        <menuitem action='address-book-map'/>
+        <separator/>
         <menuitem action='address-book-properties'/>
       </menu>
     </placeholder>
@@ -65,6 +68,9 @@
     <menuitem action='address-book-popup-delete'/>
     <placeholder name='address-book-popup-actions'/>
     <separator/>
+    <separator/>
+    <menuitem action='address-book-popup-map'/>
+    <separator/>
     <menuitem action='address-book-popup-properties'/>
   </popup>
   <popup name='contact-popup'>
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index fdcd541..33bde93 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -33,6 +33,9 @@ widgetsinclude_HEADERS =			\
 	e-cell-renderer-combo.h			\
 	e-charset-combo-box.h			\
 	e-combo-cell-editable.h			\
+	e-contact-map.h				\
+	e-contact-map-window.h			\
+	e-contact-marker.h			\
 	e-dateedit.h				\
 	e-focus-tracker.h			\
 	e-hinted-entry.h			\
@@ -79,7 +82,11 @@ libemiscwidgets_la_CPPFLAGS =						\
 	-DEVOLUTION_UIDIR=\""$(uidir)"\"				\
 	-DG_LOG_DOMAIN=__FILE__						\
 	$(EVOLUTION_MAIL_CFLAGS)					\
-	$(GNOME_PLATFORM_CFLAGS)
+	$(GNOME_PLATFORM_CFLAGS)					\
+	$(CHAMPLAIN_CFLAGS)						\
+	$(GEOCLUE_CFLAGS)						\
+	$(CLUTTER_CFLAGS)
+	
 
 libemiscwidgets_la_SOURCES =			\
 	$(widgetsinclude_HEADERS)		\
@@ -111,6 +118,9 @@ libemiscwidgets_la_SOURCES =			\
 	e-cell-renderer-combo.c			\
 	e-charset-combo-box.c			\
 	e-combo-cell-editable.c			\
+	e-contact-map.c				\
+	e-contact-map-window.c			\
+	e-contact-marker.c			\
 	e-dateedit.c				\
 	e-focus-tracker.c			\
 	e-hinted-entry.c			\
@@ -158,7 +168,10 @@ libemiscwidgets_la_LIBADD =					\
 	$(EVOLUTION_MAIL_LIBS)					\
 	$(GNOME_PLATFORM_LIBS)					\
 	$(MATH_LIB)						\
-	$(ICONV_LIBS)
+	$(ICONV_LIBS)						\
+	$(CHAMPLAIN_LIBS)					\
+	$(GEOCLUE_LIBS)						\
+	$(CLUTTER_LIBS)
 
 noinst_PROGRAMS = 			\
 	test-calendar			\
diff --git a/widgets/misc/e-contact-map-window.c b/widgets/misc/e-contact-map-window.c
new file mode 100644
index 0000000..9d9ed9c
--- /dev/null
+++ b/widgets/misc/e-contact-map-window.c
@@ -0,0 +1,465 @@
+/*
+ * e-contact-map-window.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-map.h"
+#include "e-contact-map-window.h"
+#include "e-contact-marker.h"
+
+#include <champlain/champlain.h>
+
+#include <libebook/e-book.h>
+#include <libebook/e-contact.h>
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+
+G_DEFINE_TYPE (EContactMapWindow, e_contact_map_window, GTK_TYPE_WINDOW)
+
+struct _EContactMapWindowPrivate {
+	EContactMap *map;
+
+	GtkWidget *zoom_in_btn;
+	GtkWidget *zoom_out_btn;
+
+	GtkWidget *search_entry;
+	GtkListStore *completion_model;
+
+	GHashTable *hash_table;		/* Hash table contact-name -> marker */
+
+	GtkWidget *spinner;
+	gint tasks_cnt;
+};
+
+enum {
+	SHOW_CONTACT_EDITOR,
+	LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+static void
+marker_doubleclick_cb (ClutterActor *marker,
+		       gpointer user_data)
+{
+	EContactMapWindow *window = user_data;
+	const gchar *contact_uid = e_contact_marker_get_contact_uid (E_CONTACT_MARKER (marker));
+
+	g_signal_emit (window, signals[SHOW_CONTACT_EDITOR], 0, contact_uid);
+}
+
+static void
+book_contacts_received_cb (EBook *book,
+			   const GError *error,
+			   GList *list,
+			   gpointer closure)
+{
+	EContactMapWindow *window = closure;
+	GList *p;
+
+	g_return_if_fail (error == NULL);
+
+	for (p = list; p; p = p->next) {
+
+		e_contact_map_add_contact (window->priv->map, (EContact*)p->data);
+
+	}
+
+	g_list_foreach (list, (GFunc) g_object_unref, NULL);
+	g_list_free (list);
+	g_object_unref (book);
+}
+
+static void
+contact_map_window_zoom_in_cb (GtkButton *button,
+			       gpointer user_data)
+{
+	EContactMapWindow *window = user_data;
+	ChamplainView *view;
+
+	view = e_contact_map_get_view (window->priv->map);
+
+	champlain_view_zoom_in (view);
+}
+
+static void
+contact_map_window_zoom_out_cb (GtkButton *button,
+				gpointer user_data)
+{
+	EContactMapWindow *window = user_data;
+	ChamplainView *view;
+
+	view = e_contact_map_get_view (window->priv->map);
+
+	champlain_view_zoom_out (view);
+}
+static void
+zoom_level_changed_cb (ChamplainView *view,
+		       gint bzoom_level,
+		       gpointer user_data)
+{
+	EContactMapWindow *window = user_data;
+	gint zoom_level = champlain_view_get_zoom_level (view);
+
+	gtk_widget_set_sensitive (window->priv->zoom_in_btn,
+		(zoom_level < champlain_view_get_max_zoom_level (view)));
+
+	gtk_widget_set_sensitive (window->priv->zoom_out_btn,
+		(zoom_level > champlain_view_get_min_zoom_level (view)));
+}
+
+/**
+ * Add contact to hash_table only when EContactMap tells us
+ * that the contact has really been added to map.
+ */
+static void
+map_contact_added_cb (EContactMap *map,
+		      ClutterActor *marker,
+		      gpointer user_data)
+{
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+	const gchar *name;
+	GtkTreeIter iter;
+
+	name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+
+	g_hash_table_insert (priv->hash_table,
+		g_strdup (name), marker);
+
+	gtk_list_store_append (priv->completion_model, &iter);
+	gtk_list_store_set (priv->completion_model, &iter,
+		0, name, -1);
+
+	g_signal_connect (E_CONTACT_MARKER (marker), "double-clicked",
+		G_CALLBACK (marker_doubleclick_cb), user_data);
+
+	priv->tasks_cnt--;
+	if (priv->tasks_cnt == 0) {
+		gtk_spinner_stop(GTK_SPINNER (priv->spinner));
+		gtk_widget_hide (priv->spinner);
+	}
+}
+
+static void
+map_contact_removed_cb (EContactMap *map,
+		        const gchar *name,
+		        gpointer user_data)
+{
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+	GtkTreeIter iter;
+	GtkTreeModel *model = GTK_TREE_MODEL (priv->completion_model);
+
+	g_hash_table_remove (priv->hash_table, name);
+
+	if (gtk_tree_model_get_iter_first (model, &iter)) {
+		do {
+			gchar *name_str;
+			gtk_tree_model_get (model, &iter, 0, &name_str, -1);
+			if (g_ascii_strcasecmp (name_str, name) == 0) {
+				gtk_list_store_remove (priv->completion_model, &iter);
+				break;
+			}
+			g_free (name_str);
+		} while (gtk_tree_model_iter_next (model, &iter));
+	}
+}
+
+static void
+map_contact_geocoding_started_cb (EContactMap *map,
+				 ClutterActor *marker,
+				 gpointer user_data)
+{
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+
+	gtk_spinner_start (GTK_SPINNER (priv->spinner));
+	gtk_widget_show (priv->spinner);
+
+	priv->tasks_cnt++;
+}
+
+static void
+map_contact_geocoding_failed_cb (EContactMap *map,
+				 const gchar *name,
+				 gpointer user_data)
+{
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+
+	priv->tasks_cnt--;
+
+	if (priv->tasks_cnt == 0) {
+		gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+		gtk_widget_hide (priv->spinner);
+	}
+}
+
+static void
+contact_map_window_find_contact_cb (GtkButton *button,
+				    gpointer user_data)
+{
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+	ClutterActor *marker;
+
+	marker = g_hash_table_lookup (priv->hash_table,
+				gtk_entry_get_text (GTK_ENTRY (priv->search_entry)));
+
+	if (marker)
+		e_contact_map_zoom_on_marker (priv->map, marker);
+}
+
+static gboolean
+contact_map_window_entry_key_pressed_cb (GtkWidget *entry,
+					 GdkEventKey *event,
+					 gpointer user_data)
+{
+	if (event->keyval == GDK_KEY_Return)
+		contact_map_window_find_contact_cb (NULL, user_data);
+
+	return FALSE;
+}
+
+static gboolean
+entry_completion_match_selected_cb (GtkEntryCompletion *widget,
+				    GtkTreeModel* model,
+				    GtkTreeIter *iter,
+				    gpointer user_data)
+{
+	GValue name_val = {0};
+	EContactMapWindowPrivate *priv = E_CONTACT_MAP_WINDOW (user_data)->priv;
+	const gchar *name;
+
+	gtk_tree_model_get_value (model, iter, 0, &name_val);
+	g_return_val_if_fail (G_VALUE_HOLDS_STRING (&name_val), FALSE);
+
+	name = g_value_get_string (&name_val);
+	gtk_entry_set_text (GTK_ENTRY (priv->search_entry), name);
+
+	contact_map_window_find_contact_cb (NULL, user_data);
+
+	return TRUE;
+}
+
+static void
+contact_map_window_finalize (GObject *object)
+{
+	EContactMapWindowPrivate *priv;
+
+	priv = E_CONTACT_MAP_WINDOW (object)->priv;
+
+	if (priv->hash_table) {
+		g_hash_table_destroy (priv->hash_table);
+		priv->hash_table = NULL;
+	}
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_contact_map_window_parent_class)->finalize (object);
+}
+
+static void
+contact_map_window_dispose (GObject *object)
+{
+	EContactMapWindowPrivate *priv;
+
+	priv = E_CONTACT_MAP_WINDOW (object)->priv;
+
+	if (priv->map) {
+		gtk_widget_destroy (GTK_WIDGET (priv->map));
+		priv->map = NULL;
+	}
+
+	if (priv->completion_model) {
+		g_object_unref (priv->completion_model);
+		priv->completion_model = NULL;
+	}
+
+	G_OBJECT_CLASS (e_contact_map_window_parent_class)->dispose (object);
+}
+
+static void
+e_contact_map_window_class_init (EContactMapWindowClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EContactMapWindowPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = contact_map_window_finalize;
+	object_class->dispose = contact_map_window_dispose;
+
+	signals[SHOW_CONTACT_EDITOR] = g_signal_new (
+		"show-contact-editor",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapWindowClass, show_contact_editor),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+e_contact_map_window_init (EContactMapWindow *window)
+{
+	EContactMapWindowPrivate *priv;
+	GtkWidget *map;
+	GtkWidget *button, *entry;
+	GtkWidget *hbox, *vbox, *viewport;
+	GtkEntryCompletion *entry_completion;
+	GtkListStore *completion_model;
+	ChamplainView *view;
+	GHashTable *hash_table;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (
+		window, E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowPrivate);
+	window->priv = priv;
+
+	priv->tasks_cnt = 0;
+
+	hash_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free, NULL);
+	priv->hash_table = hash_table;
+
+	gtk_window_set_title (GTK_WINDOW (window), _("Contacts Map"));
+	gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+	gtk_widget_set_size_request (GTK_WIDGET (window), 800, 600);
+
+	/* The map view itself */
+	map = e_contact_map_new ();
+	view = e_contact_map_get_view (E_CONTACT_MAP (map));
+	champlain_view_set_zoom_level (view, 2);
+	priv->map = E_CONTACT_MAP (map);
+	g_signal_connect (view, "notify::zoom-level",
+		G_CALLBACK (zoom_level_changed_cb), window);
+	g_signal_connect (map, "contact-added",
+		G_CALLBACK (map_contact_added_cb), window);
+	g_signal_connect (map, "contact-removed",
+		G_CALLBACK (map_contact_removed_cb), window);
+	g_signal_connect (map, "geocoding-started",
+		G_CALLBACK (map_contact_geocoding_started_cb), window);
+	g_signal_connect (map, "geocoding-failed",
+		G_CALLBACK (map_contact_geocoding_failed_cb), window);
+
+	/* HBox container */
+	hbox = gtk_hbox_new (FALSE, 7);
+
+	/* Spinner */
+	button = gtk_spinner_new ();
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+	gtk_widget_hide (button);
+	priv->spinner = button;
+
+	/* Zoom-in button */
+	button = gtk_button_new_from_stock (GTK_STOCK_ZOOM_IN);
+	g_signal_connect (button, "clicked",
+		G_CALLBACK (contact_map_window_zoom_in_cb), window);
+	priv->zoom_in_btn = button;
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+
+	/* Zoom-out button */
+	button = gtk_button_new_from_stock (GTK_STOCK_ZOOM_OUT);
+	g_signal_connect (button, "clicked",
+		G_CALLBACK (contact_map_window_zoom_out_cb), window);
+	priv->zoom_out_btn = button;
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+
+	/* Completion model */
+	completion_model = gtk_list_store_new (1, G_TYPE_STRING);
+	priv->completion_model = completion_model;
+
+	/* Entry completion */
+	entry_completion = gtk_entry_completion_new ();
+	gtk_entry_completion_set_model (entry_completion, GTK_TREE_MODEL (completion_model));
+	gtk_entry_completion_set_text_column (entry_completion, 0);
+	g_signal_connect (entry_completion, "match-selected",
+		G_CALLBACK (entry_completion_match_selected_cb), window);
+
+	/* Search entry */
+	entry = gtk_entry_new ();
+	gtk_entry_set_completion (GTK_ENTRY (entry), entry_completion);
+	g_signal_connect (entry, "key-press-event",
+		G_CALLBACK (contact_map_window_entry_key_pressed_cb), window);
+	window->priv->search_entry = entry;
+	gtk_container_add (GTK_CONTAINER (hbox), entry);
+
+	/* Search button */
+	button = gtk_button_new_from_stock (GTK_STOCK_FIND);
+	g_signal_connect (button, "clicked",
+		G_CALLBACK (contact_map_window_find_contact_cb), window);
+	gtk_container_add (GTK_CONTAINER (hbox), button);
+
+	viewport = gtk_frame_new (NULL);
+	gtk_container_add (GTK_CONTAINER (viewport), map);
+
+	vbox = gtk_vbox_new (FALSE, 6);
+	gtk_container_add (GTK_CONTAINER (vbox), viewport);
+	gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
+
+	gtk_container_add (GTK_CONTAINER (window), vbox);
+
+	gtk_widget_show_all (vbox);
+	gtk_widget_hide (priv->spinner);
+}
+
+EContactMapWindow *
+e_contact_map_window_new (void)
+{
+	return g_object_new (
+		E_TYPE_CONTACT_MAP_WINDOW, NULL);
+}
+
+/**
+ * Gets all contacts from @book and puts them
+ * on the map view
+ */
+void
+e_contact_map_window_load_addressbook (EContactMapWindow *map,
+				       EBook *book)
+{
+	EBookQuery *book_query;
+
+	/* Reference book, so that it does not get deleted before the callback is
+	   involved. The book is unrefed in the callback */
+	g_object_ref (book);
+
+	g_return_if_fail (E_IS_CONTACT_MAP_WINDOW (map));
+	g_return_if_fail (E_IS_BOOK (book));
+
+	book_query = e_book_query_field_exists (E_CONTACT_ADDRESS);
+
+	e_book_get_contacts_async (book, book_query,
+		(EBookListAsyncCallback) book_contacts_received_cb, map);
+
+	e_book_query_unref (book_query);
+}
+
+EContactMap*
+e_contact_map_window_get_map (EContactMapWindow *window)
+{
+	g_return_val_if_fail (E_IS_CONTACT_MAP_WINDOW (window), NULL);
+
+	return window->priv->map;
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/widgets/misc/e-contact-map-window.h b/widgets/misc/e-contact-map-window.h
new file mode 100644
index 0000000..ea96d88
--- /dev/null
+++ b/widgets/misc/e-contact-map-window.h
@@ -0,0 +1,77 @@
+/*
+ * e-contact-map-window.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#ifndef E_CONTACT_MAP_WINDOW_H
+#define E_CONTACT_MAP_WINDOW_H
+
+#include <gtk/gtk.h>
+
+#include <libebook/e-book.h>
+
+#include <e-contact-map.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_MAP_WINDOW \
+	(e_contact_map_window_get_type ())
+#define E_CONTACT_MAP_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindow))
+#define E_CONTACT_MAP_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowClass))
+#define E_IS_CONTACT_MAP_WINDOW(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW))
+#define E_IS_CONTACT_MAP_WINDOW_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CONTACT_MAP_WINDOW))
+#define E_CONTACT_MAP_WINDOW_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CONTACT_MAP_WINDOW, EContactMapWindowClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactMapWindow EContactMapWindow;
+typedef struct _EContactMapWindowClass EContactMapWindowClass;
+typedef struct _EContactMapWindowPrivate EContactMapWindowPrivate;
+
+struct _EContactMapWindow {
+	GtkWindow parent;
+	EContactMapWindowPrivate *priv;
+};
+
+struct _EContactMapWindowClass {
+	GtkWindowClass parent_class;
+
+	void (*show_contact_editor)	(EContactMapWindow *window,
+					 const gchar *contact_uid);
+};
+
+GType			e_contact_map_window_get_type		(void) G_GNUC_CONST;
+EContactMapWindow*	e_contact_map_window_new		(void);
+
+void			e_contact_map_window_load_addressbook	(EContactMapWindow *window,
+								 EBook *book);
+
+EContactMap*		e_contact_map_window_get_map		(EContactMapWindow *window);
+
+G_END_DECLS
+
+#endif
diff --git a/widgets/misc/e-contact-map.c b/widgets/misc/e-contact-map.c
new file mode 100644
index 0000000..d862fc3
--- /dev/null
+++ b/widgets/misc/e-contact-map.c
@@ -0,0 +1,394 @@
+/*
+ * e-contact-map.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-map.h"
+#include "e-contact-marker.h"
+
+#include <e-util/e-marshal.h>
+
+#include <champlain/champlain.h>
+#include <champlain-gtk/champlain-gtk.h>
+#include <geoclue/geoclue-address.h>
+#include <geoclue/geoclue-position.h>
+#include <geoclue/geoclue-geocode.h>
+
+#include <clutter/clutter.h>
+
+#include <libebook/e-contact.h>
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <math.h>
+
+G_DEFINE_TYPE (EContactMap, e_contact_map, GTK_TYPE_CHAMPLAIN_EMBED)
+
+struct _EContactMapPrivate {
+	GHashTable *markers; /* Hash table contact-name -> marker */
+
+	ChamplainMarkerLayer *marker_layer;
+};
+
+struct GeoclueCallbackData {
+	EContactMap *map;
+	EContactMarker *marker;
+};
+
+enum {
+	CONTACT_ADDED,
+	CONTACT_REMOVED,
+	GEOCODING_STARTED,
+	GEOCODING_FAILED,
+	LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+static GHashTable *
+contact_map_geocode_address (EContactAddress *address)
+{
+	GHashTable *details;
+
+	g_return_val_if_fail (address, NULL);
+
+	details = geoclue_address_details_new ();
+	g_hash_table_insert (details, g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), g_strdup (address->code));
+	g_hash_table_insert (details, g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), g_strdup (address->country));
+	g_hash_table_insert (details, g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), g_strdup (address->locality));
+	g_hash_table_insert (details, g_strdup (GEOCLUE_ADDRESS_KEY_STREET), g_strdup (address->street));
+
+	return details;
+}
+
+static void
+contact_map_address_resolved_cb (GeoclueGeocode *geocode,
+				 GeocluePositionFields fields,
+				 double latitude,
+				 double longitude,
+				 double altitude,
+				 GeoclueAccuracy *accuracy,
+				 GError *error,
+				 struct GeoclueCallbackData *data)
+{
+	EContactMapPrivate *priv;
+	gpointer marker_ptr;
+	const gchar *name;
+
+	g_return_if_fail (data);
+	g_return_if_fail (data->map && E_IS_CONTACT_MAP (data->map));
+	g_return_if_fail (data->map->priv);
+	g_return_if_fail (data->marker && E_IS_CONTACT_MARKER (data->marker));
+
+	/* If the marker_layer does not exist anymore, the map has probably been destroyed before this
+	   callback was launched. It's not a failure, just silently clean up what was left behind
+	   a pretend nothing happend */
+
+	if (!data->map->priv->marker_layer || !CHAMPLAIN_IS_MARKER_LAYER (data->map->priv->marker_layer)) {
+		goto exit;
+	}
+
+	if (error ||
+	    (((fields & GEOCLUE_POSITION_FIELDS_LATITUDE) == 0) && ((fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) == 0))) {
+	    	const gchar *name;
+	     	if (error)
+	     		g_error_free (error);
+		name = champlain_label_get_text (CHAMPLAIN_LABEL (data->marker));
+	     	g_signal_emit (data->map, signals[GEOCODING_FAILED], 0, name);
+	     	goto exit;
+	}
+
+	priv = data->map->priv;
+
+	/* Move the marker to resolved position */
+	champlain_location_set_location (CHAMPLAIN_LOCATION (data->marker),
+		latitude, longitude);
+	champlain_marker_layer_add_marker (data->map->priv->marker_layer,
+		CHAMPLAIN_MARKER (data->marker));
+
+	/* Store the marker in the hash table. Use it's label as key */
+	name = champlain_label_get_text (CHAMPLAIN_LABEL (data->marker));
+	marker_ptr = g_hash_table_lookup (priv->markers, name);
+
+	if (marker_ptr) {
+		g_hash_table_remove (priv->markers, name);
+		champlain_marker_layer_remove_marker (priv->marker_layer, marker_ptr);
+	}
+	g_hash_table_insert (priv->markers,
+		g_strdup (name), data->marker);
+
+	g_signal_emit (data->map, signals[CONTACT_ADDED], 0, data->marker);
+
+exit:
+     	g_object_unref (data->map);
+	g_free (data);
+
+	if (geocode)
+		g_object_unref (geocode);
+}
+
+static void
+resolve_marker_position (EContactMap *map,
+			 EContactMarker *marker,
+			 EContactAddress *address)
+{
+	GHashTable *details;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	details = contact_map_geocode_address (address);
+
+	if (details) {
+		GeoclueGeocode *geocoder;
+		struct GeoclueCallbackData *callback_data = g_new0 (struct GeoclueCallbackData, 1);
+
+		callback_data->map = map;
+		callback_data->marker = marker;
+
+		/* Make sure the map won't cease to exist before the address
+		   is resolved */
+		g_object_ref (map);
+
+		geocoder = geoclue_geocode_new ("org.freedesktop.Geoclue.Providers.Yahoo",
+				"/org/freedesktop/Geoclue/Providers/Yahoo");
+
+		geoclue_geocode_address_to_position_async (geocoder, details,
+			(GeoclueGeocodeCallback) contact_map_address_resolved_cb,
+			callback_data);
+
+		g_hash_table_destroy (details);
+
+		g_signal_emit (map, signals[GEOCODING_STARTED], 0, marker);
+	}
+}
+
+static void
+contact_map_finalize (GObject *object)
+{
+	EContactMapPrivate *priv;
+
+	priv = E_CONTACT_MAP (object)->priv;
+
+	if (priv->markers) {
+		g_hash_table_destroy (priv->markers);
+		priv->markers = NULL;
+	}
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_contact_map_parent_class)->finalize (object);
+}
+
+static void
+e_contact_map_class_init (EContactMapClass *class)
+{
+	GObjectClass *object_class;
+
+	g_type_class_add_private (class, sizeof (EContactMap));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = contact_map_finalize;
+
+	signals[CONTACT_ADDED] = g_signal_new (
+		"contact-added",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, contact_added),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[CONTACT_REMOVED] = g_signal_new (
+		"contact-removed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, contact_removed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	signals[GEOCODING_STARTED] = g_signal_new (
+		"geocoding-started",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, geocoding_started),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__OBJECT,
+		G_TYPE_NONE, 1, G_TYPE_OBJECT);
+
+	signals[GEOCODING_FAILED] = g_signal_new (
+		"geocoding-failed",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMapClass, geocoding_failed),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+e_contact_map_init (EContactMap *map)
+{
+	GHashTable *hash_table;
+	ChamplainMarkerLayer *layer;
+	ChamplainView *view;
+
+	map->priv =  G_TYPE_INSTANCE_GET_PRIVATE (
+		map, E_TYPE_CONTACT_MAP, EContactMapPrivate);
+
+	hash_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+			(GDestroyNotify) g_free, NULL);
+
+	map->priv->markers = hash_table;
+
+	view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+	/* This feature is somehow broken sometimes, so disable it for now */
+	champlain_view_set_zoom_on_double_click (view, FALSE);
+	layer = champlain_marker_layer_new_full (CHAMPLAIN_SELECTION_SINGLE);
+	champlain_view_add_layer (view, CHAMPLAIN_LAYER (layer));
+	map->priv->marker_layer = layer;
+}
+
+GtkWidget*
+e_contact_map_new (void)
+{
+	return g_object_new (
+		E_TYPE_CONTACT_MAP,NULL);
+}
+
+void
+e_contact_map_add_contact (EContactMap *map,
+			   EContact *contact)
+{
+	EContactAddress *address;
+	EContactPhoto *photo;
+	const gchar *contact_uid;
+	gchar *name;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (contact && E_IS_CONTACT (contact));
+
+	photo = e_contact_get (contact, E_CONTACT_PHOTO);
+	contact_uid = e_contact_get_const (contact, E_CONTACT_UID);
+
+	address = e_contact_get (contact, E_CONTACT_ADDRESS_HOME);
+	if (address) {
+		name = g_strconcat (e_contact_get_const (contact, E_CONTACT_FILE_AS), " (", _("Home"), ")", NULL);
+		e_contact_map_add_marker (map, name, contact_uid, address, photo);
+		g_free (name);
+		e_contact_address_free (address);
+	}
+
+	address = e_contact_get (contact, E_CONTACT_ADDRESS_WORK);
+	if (address) {
+		name = g_strconcat (e_contact_get_const (contact, E_CONTACT_FILE_AS), " (", _("Work"), ")", NULL);
+		e_contact_map_add_marker (map, name, contact_uid, address, photo);
+		g_free (name);
+		e_contact_address_free (address);
+	}
+
+	if (photo)
+		e_contact_photo_free (photo);
+}
+
+void
+e_contact_map_add_marker (EContactMap *map,
+			  const gchar *name,
+			  const gchar *contact_uid,
+			  EContactAddress *address,
+			  EContactPhoto *photo)
+{
+	EContactMarker *marker;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (name && *name);
+	g_return_if_fail (contact_uid && *contact_uid);
+	g_return_if_fail (address);
+
+	marker = E_CONTACT_MARKER (e_contact_marker_new (name, contact_uid, photo));
+
+	resolve_marker_position (map, marker, address);
+}
+
+/**
+ * The \name parameter must match the label of the
+ * marker (for example "John Smith (work)")
+ */
+void
+e_contact_map_remove_contact (EContactMap *map,
+			      const gchar *name)
+{
+	ChamplainMarker *marker;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (name && *name);
+
+	marker = g_hash_table_lookup (map->priv->markers, name);
+
+	champlain_marker_layer_remove_marker (map->priv->marker_layer, marker);
+
+	g_hash_table_remove (map->priv->markers, name);
+
+	g_signal_emit (map, signals[CONTACT_REMOVED], 0, name);
+}
+
+void
+e_contact_map_remove_marker (EContactMap *map,
+			     ClutterActor *marker)
+{
+	const gchar *name;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (marker && CLUTTER_IS_ACTOR (marker));
+
+	name = champlain_label_get_text (CHAMPLAIN_LABEL (marker));
+
+	e_contact_map_remove_contact (map, name);
+}
+
+void
+e_contact_map_zoom_on_marker (EContactMap *map,
+			      ClutterActor *marker)
+{
+	ChamplainView *view;
+	double lat, lng;
+
+	g_return_if_fail (map && E_IS_CONTACT_MAP (map));
+	g_return_if_fail (marker && CLUTTER_IS_ACTOR (marker));
+
+	lat = champlain_location_get_latitude (CHAMPLAIN_LOCATION (marker));
+	lng = champlain_location_get_longitude (CHAMPLAIN_LOCATION (marker));
+
+	view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+
+	champlain_view_center_on (view, lat, lng);
+	champlain_view_set_zoom_level (view, 15);
+}
+
+ChamplainView*
+e_contact_map_get_view (EContactMap *map)
+{
+	g_return_val_if_fail (E_IS_CONTACT_MAP (map), NULL);
+
+	return gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (map));
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/widgets/misc/e-contact-map.h b/widgets/misc/e-contact-map.h
new file mode 100644
index 0000000..fd5fabf
--- /dev/null
+++ b/widgets/misc/e-contact-map.h
@@ -0,0 +1,108 @@
+/*
+ * e-contact-map.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#ifndef E_CONTACT_MAP_H
+#define E_CONTACT_MAP_H
+
+#ifdef WITH_CONTACT_MAPS
+
+#include <gtk/gtk.h>
+
+#include <champlain/champlain.h>
+#include <champlain-gtk/champlain-gtk.h>
+
+#include <libebook/e-contact.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CONTACT_MAP \
+	(e_contact_map_get_type ())
+#define E_CONTACT_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_CONTACT_MAP, EContactMap))
+#define E_CONTACT_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_CONTACT_MAP, EContactMapClass))
+#define E_IS_CONTACT_MAP(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_CONTACT_MAP))
+#define E_IS_CONTACT_MAP_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_CONTACT_MAP))
+#define E_CONTACT_MAP_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_CONTACT_MAP, EContactMapClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EContactMap EContactMap;
+typedef struct _EContactMapClass EContactMapClass;
+typedef struct _EContactMapPrivate EContactMapPrivate;
+
+struct _EContactMap {
+	GtkChamplainEmbed parent;
+	EContactMapPrivate *priv;
+};
+
+struct _EContactMapClass {
+	GtkWindowClass parent_class;
+
+	void (*contact_added)	   (EContactMap *map,
+				    ClutterActor *marker);
+
+	void (*contact_removed)	  (EContactMap *map,
+				   const gchar *name);
+
+	void (*geocoding_started)	(EContactMap *map,
+					 ClutterActor *marker);
+
+	void (*geocoding_failed)	(EContactMap *map,
+					 const gchar *name);
+};
+
+GType		e_contact_map_get_type		(void) G_GNUC_CONST;
+GtkWidget *	e_contact_map_new		(void);
+
+
+void		e_contact_map_add_contact	(EContactMap *map,
+						 EContact *contact);
+
+void		e_contact_map_add_marker 	(EContactMap *map,
+					  	 const gchar *name,
+					  	 const gchar *contact_uid,
+					  	 EContactAddress *address,
+					  	 EContactPhoto *photo);
+
+void		e_contact_map_remove_contact	(EContactMap *map,
+						 const gchar *name);
+
+void 		e_contact_map_remove_marker	(EContactMap *map,
+						 ClutterActor *marker);
+
+void		e_contact_map_zoom_on_marker	(EContactMap *map,
+						 ClutterActor *marker);
+
+ChamplainView*  e_contact_map_get_view		(EContactMap *map);
+
+
+G_END_DECLS
+
+#endif /* WITH_CONTACT_MAPS */
+
+#endif
diff --git a/widgets/misc/e-contact-marker.c b/widgets/misc/e-contact-marker.c
new file mode 100644
index 0000000..233ae07
--- /dev/null
+++ b/widgets/misc/e-contact-marker.c
@@ -0,0 +1,600 @@
+/*
+ * e-contact-marker.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2008 Pierre-Luc Beaudoin <pierre-luc pierlux com>
+ * Copyright (C) 2011 Jiri Techet <techet gmail com>
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef WITH_CONTACT_MAPS
+
+#include "e-contact-marker.h"
+
+#include <champlain/champlain.h>
+#include <gtk/gtk.h>
+#include <clutter/clutter.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <cairo.h>
+#include <math.h>
+#include <string.h>
+
+G_DEFINE_TYPE (EContactMarker, e_contact_marker, CHAMPLAIN_TYPE_LABEL);
+
+struct _EContactMarkerPrivate
+{
+	gchar *contact_uid;
+
+  	ClutterActor *image;
+  	ClutterActor *text_actor;
+
+  	ClutterActor *shadow;
+  	ClutterActor *background;
+
+  	guint total_width;
+  	guint total_height;
+
+  	ClutterGroup *content_group;
+
+  	guint redraw_id;
+};
+
+enum {
+	DOUBLE_CLICKED,
+	LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+#define DEFAULT_FONT_NAME "Serif 9"
+
+static ClutterColor DEFAULT_COLOR = { 0x33, 0x33, 0x33, 0xff };
+
+#define RADIUS 10
+#define PADDING (RADIUS / 2)
+
+static gboolean
+contact_marker_clicked_cb (ClutterActor *actor,
+			   ClutterEvent *event,
+			   gpointer user_data)
+{
+	gint click_count = clutter_event_get_click_count (event);
+
+	if (click_count == 2)
+		g_signal_emit (E_CONTACT_MARKER (actor), signals[DOUBLE_CLICKED], 0);
+
+	return TRUE;
+}
+
+static ClutterActor *
+texture_new_from_pixbuf (GdkPixbuf *pixbuf,
+			 GError **error)
+{
+	ClutterActor *texture = NULL;
+	const guchar *data;
+	gboolean has_alpha, success;
+	int width, height, rowstride;
+	ClutterTextureFlags flags = 0;
+
+	data = gdk_pixbuf_get_pixels (pixbuf);
+	width = gdk_pixbuf_get_width (pixbuf);
+	height = gdk_pixbuf_get_height (pixbuf);
+	has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
+	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+
+	texture = clutter_texture_new ();
+	success = clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (texture),
+		data, has_alpha, width, height, rowstride,
+		(has_alpha ? 4: 3), flags, NULL);
+
+	if (!success) {
+		clutter_actor_destroy (CLUTTER_ACTOR (texture));
+		texture = NULL;
+	}
+
+	return texture;
+}
+
+
+static ClutterActor*
+contact_photo_to_texture (EContactPhoto *photo)
+{
+	GdkPixbuf *pixbuf;
+
+	if  (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
+		GError *error = NULL;
+
+		GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
+		gdk_pixbuf_loader_write (loader, photo->data.inlined.data, photo->data.inlined.length, NULL);
+		gdk_pixbuf_loader_close (loader, NULL);
+	        pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+		if (pixbuf)
+			g_object_ref (pixbuf);
+		g_object_unref (loader);
+
+		if (error) {
+			g_error_free (error);
+			return NULL;
+		}
+	} else if (photo->type == E_CONTACT_PHOTO_TYPE_URI) {
+		GError *error = NULL;
+
+		pixbuf = gdk_pixbuf_new_from_file (photo->data.uri, &error);
+
+		if (error) {
+			g_error_free (error);
+			return NULL;
+		}
+	} else
+		return NULL;
+
+	if (pixbuf) {
+		ClutterActor *texture;
+		GError *error = NULL;
+
+		texture = texture_new_from_pixbuf (pixbuf, &error);
+		if (error) {
+			g_error_free (error);
+			g_object_unref (pixbuf);
+			return NULL;
+		}
+
+		g_object_unref (pixbuf);
+		return texture;
+	}
+
+	return NULL;
+}
+
+static void
+draw_box (cairo_t *cr,
+	  gint width,
+	  gint height,
+	  gint point)
+{
+      cairo_move_to (cr, RADIUS, 0);
+      cairo_line_to (cr, width - RADIUS, 0);
+      cairo_arc (cr, width - RADIUS, RADIUS, RADIUS - 1, 3 * M_PI / 2.0, 0);
+      cairo_line_to (cr, width, height - RADIUS);
+      cairo_arc (cr, width - RADIUS, height - RADIUS, RADIUS - 1, 0, M_PI / 2.0);
+      cairo_line_to (cr, point, height);
+      cairo_line_to (cr, 0, height + point);
+      cairo_arc (cr, RADIUS, RADIUS, RADIUS - 1, M_PI, 3 * M_PI / 2.0);
+      cairo_close_path (cr);
+}
+
+
+static void
+draw_shadow (EContactMarker *marker,
+	     gint width,
+	     gint height,
+	     gint point)
+{
+	EContactMarkerPrivate *priv = marker->priv;
+	ClutterActor *shadow = NULL;
+	cairo_t *cr;
+	gdouble slope;
+	gdouble scaling;
+	gint x;
+	cairo_matrix_t matrix;
+
+	slope = -0.3;
+	scaling = 0.65;
+	x = -40 * slope;
+
+  	shadow = clutter_cairo_texture_new (width + x, (height + point));
+  	cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (shadow));
+
+  	cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+  	cairo_paint (cr);
+  	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+  	cairo_matrix_init (&matrix, 1, 0, slope, scaling, x, 0);
+  	cairo_set_matrix (cr, &matrix);
+
+  	draw_box (cr, width, height, point);
+
+  	cairo_set_source_rgba (cr, 0, 0, 0, 0.15);
+  	cairo_fill (cr);
+
+  	cairo_destroy (cr);
+
+  	clutter_actor_set_position (shadow, 0, height / 2.0);
+
+  	clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), shadow);
+
+  	if (priv->shadow != NULL) {
+      		clutter_container_remove_actor (CLUTTER_CONTAINER (priv->content_group),
+          		priv->shadow);
+    	}
+
+  	priv->shadow = shadow;
+}
+
+
+static void
+draw_background (EContactMarker *marker,
+		 gint width,
+		 gint height,
+		 gint point)
+{
+	EContactMarkerPrivate *priv = marker->priv;
+  	ClutterActor *bg = NULL;
+	const ClutterColor *color;
+  	ClutterColor darker_color;
+  	cairo_t *cr;
+
+  	bg = clutter_cairo_texture_new (width, height + point);
+  	cr = clutter_cairo_texture_create (CLUTTER_CAIRO_TEXTURE (bg));
+
+  	cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+  	cairo_paint (cr);
+  	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+  	/* If selected, add the selection color to the marker's color */
+  	if (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker)))
+    		color = champlain_marker_get_selection_color ();
+  	else
+    		color = &DEFAULT_COLOR;
+
+  	draw_box (cr, width, height, point);
+
+  	clutter_color_darken (color, &darker_color);
+
+  	cairo_set_source_rgba (cr,
+      		color->red / 255.0,
+      		color->green / 255.0,
+      		color->blue / 255.0,
+      		color->alpha / 255.0);
+  	cairo_fill_preserve (cr);
+
+  	cairo_set_line_width (cr, 1.0);
+  	cairo_set_source_rgba (cr,
+      		darker_color.red / 255.0,
+      		darker_color.green / 255.0,
+      		darker_color.blue / 255.0,
+      		darker_color.alpha / 255.0);
+  	cairo_stroke (cr);
+  	cairo_destroy (cr);
+
+  	clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), bg);
+
+  	if (priv->background != NULL) {
+      		clutter_container_remove_actor (CLUTTER_CONTAINER (priv->content_group),
+          		priv->background);
+    	}
+
+  	priv->background = bg;
+}
+
+
+static void
+draw_marker (EContactMarker *marker)
+{
+  	EContactMarkerPrivate *priv = marker->priv;
+	ChamplainLabel *label = CHAMPLAIN_LABEL (marker);
+	guint height = 0, point = 0;
+  	guint total_width = 0, total_height = 0;
+	ClutterText *text;
+
+	if (priv->image) {
+		clutter_actor_set_position (priv->image, 2*PADDING, 2*PADDING);
+		if (clutter_actor_get_parent (priv->image) == NULL)
+       			clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), priv->image);
+       	}
+
+	if (priv->text_actor == NULL) {
+		priv->text_actor = clutter_text_new_with_text ("Serif 8",
+					champlain_label_get_text (label));
+		champlain_label_set_font_name (label, "Serif 8");
+	}
+
+	text = CLUTTER_TEXT (priv->text_actor);
+      	clutter_text_set_text (text,
+      		champlain_label_get_text (label));
+	clutter_text_set_font_name (text,
+		champlain_label_get_font_name (label));
+	clutter_text_set_line_alignment (text, PANGO_ALIGN_CENTER);
+	clutter_text_set_line_wrap (text, TRUE);
+	clutter_text_set_line_wrap_mode (text, PANGO_WRAP_WORD);
+	clutter_text_set_ellipsize (text,
+		champlain_label_get_ellipsize (label));
+	clutter_text_set_attributes (text,
+		champlain_label_get_attributes (label));
+	clutter_text_set_use_markup (text,
+		champlain_label_get_use_markup (label));
+
+	if (priv->image) {
+      		clutter_actor_set_width (priv->text_actor,
+      			clutter_actor_get_width (priv->image));
+		total_height = clutter_actor_get_height (priv->image) + 2*PADDING +
+			       clutter_actor_get_height (priv->text_actor) + 2*PADDING;
+		total_width = clutter_actor_get_width (priv->image) + 4*PADDING;
+        	clutter_actor_set_position (priv->text_actor, PADDING,
+        		clutter_actor_get_height (priv->image)+2*PADDING+3);
+	} else {
+		total_height = clutter_actor_get_height (priv->text_actor) + 2*PADDING;
+		total_width = clutter_actor_get_width (priv->text_actor) + 4*PADDING;
+        	clutter_actor_set_position (priv->text_actor, 2 * PADDING, PADDING);
+	}
+
+      	height += 2 * PADDING;
+      	if (height > total_height)
+        	total_height = height;
+
+      	clutter_text_set_color (CLUTTER_TEXT (priv->text_actor),
+        	  (champlain_marker_get_selected (CHAMPLAIN_MARKER (marker)) ?
+        	  	champlain_marker_get_selection_text_color () :
+        	  	champlain_label_get_text_color (CHAMPLAIN_LABEL (marker))));
+      	if (clutter_actor_get_parent (priv->text_actor) == NULL)
+        	clutter_container_add_actor (CLUTTER_CONTAINER (priv->content_group), priv->text_actor);
+
+	if (priv->text_actor == NULL && priv->image == NULL) {
+      		total_width = 6 * PADDING;
+      		total_height = 6 * PADDING;
+    	}
+
+  	point = (total_height + 2 * PADDING) / 4.0;
+  	priv->total_width = total_width;
+  	priv->total_height = total_height;
+
+      	draw_shadow (marker, total_width, total_height, point);
+      	draw_background (marker, total_width, total_height, point);
+
+  	if (priv->text_actor != NULL && priv->background != NULL)
+    		clutter_actor_raise (priv->text_actor, priv->background);
+  	if (priv->image != NULL && priv->background != NULL)
+    		clutter_actor_raise (priv->image, priv->background);
+
+        clutter_actor_set_anchor_point (CLUTTER_ACTOR (marker), 0, total_height + point);
+}
+
+static gboolean
+redraw_on_idle (gpointer gobject)
+{
+	EContactMarker *marker = E_CONTACT_MARKER (gobject);
+
+	draw_marker (marker);
+	marker->priv->redraw_id = 0;
+        return FALSE;
+}
+
+static void
+queue_redraw (EContactMarker *marker)
+{
+	EContactMarkerPrivate *priv = marker->priv;
+
+	if (!priv->redraw_id) {
+		priv->redraw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
+			(GSourceFunc) redraw_on_idle,
+			g_object_ref (marker),
+			(GDestroyNotify) g_object_unref);
+	}
+}
+
+static void
+allocate (ClutterActor *self,
+	  const ClutterActorBox *box,
+	  ClutterAllocationFlags flags)
+{
+	ClutterActorBox child_box;
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+	CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->allocate (self, box, flags);
+
+	child_box.x1 = 0;
+        child_box.x2 = box->x2 - box->x1;
+        child_box.y1 = 0;
+        child_box.y2 = box->y2 - box->y1;
+        clutter_actor_allocate (CLUTTER_ACTOR (priv->content_group), &child_box, flags);
+}
+
+static void
+paint (ClutterActor *self)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+	clutter_actor_paint (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+map (ClutterActor *self)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+	CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->map (self);
+
+    	clutter_actor_map (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+unmap (ClutterActor *self)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+
+	CLUTTER_ACTOR_CLASS (e_contact_marker_parent_class)->unmap (self);
+
+	clutter_actor_unmap (CLUTTER_ACTOR (priv->content_group));
+}
+
+static void
+pick (ClutterActor *self,
+      const ClutterColor *color)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (self)->priv;
+        gfloat width, height;
+
+	if (!clutter_actor_should_pick_paint (self))
+		return;
+
+	width = priv->total_width;
+	height = priv->total_height;
+
+	cogl_path_new ();
+
+        cogl_set_source_color4ub (color->red,
+        	color->green,
+                color->blue,
+                color->alpha);
+
+	cogl_path_move_to (RADIUS, 0);
+        cogl_path_line_to (width - RADIUS, 0);
+        cogl_path_arc (width - RADIUS, RADIUS, RADIUS, RADIUS, -90, 0);
+        cogl_path_line_to (width, height - RADIUS);
+        cogl_path_arc (width - RADIUS, height - RADIUS, RADIUS, RADIUS, 0, 90);
+        cogl_path_line_to (RADIUS, height);
+        cogl_path_arc (RADIUS, height - RADIUS, RADIUS, RADIUS, 90, 180);
+        cogl_path_line_to (0, RADIUS);
+        cogl_path_arc (RADIUS, RADIUS, RADIUS, RADIUS, 180, 270);
+        cogl_path_close ();
+        cogl_path_fill ();
+}
+
+static void
+notify_selected (GObject *gobject,
+    		 G_GNUC_UNUSED GParamSpec *pspec,
+    		 G_GNUC_UNUSED gpointer user_data)
+{
+	queue_redraw (E_CONTACT_MARKER (gobject));
+}
+
+static void
+e_contact_marker_finalize (GObject *object)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv;
+
+	if (priv->contact_uid) {
+		g_free (priv->contact_uid);
+		priv->contact_uid = NULL;
+	}
+
+	if (priv->redraw_id) {
+		g_source_remove (priv->redraw_id);
+		priv->redraw_id = 0;
+	}
+
+	G_OBJECT_CLASS (e_contact_marker_parent_class)->finalize (object);
+}
+
+static void
+e_contact_marker_dispose (GObject *object)
+{
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (object)->priv;
+
+	priv->background = NULL;
+	priv->shadow = NULL;
+	priv->text_actor = NULL;
+
+	if (priv->content_group) {
+      		clutter_actor_unparent (CLUTTER_ACTOR (priv->content_group));
+      		priv->content_group = NULL;
+    	}
+
+  	G_OBJECT_CLASS (e_contact_marker_parent_class)->dispose (object);
+}
+
+static void
+e_contact_marker_class_init (EContactMarkerClass *class)
+{
+	ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (class);
+  	GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  	g_type_class_add_private (class, sizeof (EContactMarkerPrivate));
+
+	object_class->dispose = e_contact_marker_dispose;
+	object_class->finalize = e_contact_marker_finalize;
+
+	actor_class->paint = paint;
+	actor_class->allocate = allocate;
+	actor_class->map = map;
+	actor_class->unmap = unmap;
+	actor_class->pick = pick;
+
+	signals[DOUBLE_CLICKED] = g_signal_new (
+		"double-clicked",
+		G_TYPE_FROM_CLASS (class),
+		G_SIGNAL_RUN_FIRST,
+		G_STRUCT_OFFSET (EContactMarkerClass, double_clicked),
+		NULL, NULL,
+		g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+}
+
+static void
+e_contact_marker_init (EContactMarker *marker)
+{
+	EContactMarkerPrivate *priv;
+
+	priv =  G_TYPE_INSTANCE_GET_PRIVATE (
+	               	marker, E_TYPE_CONTACT_MARKER, EContactMarkerPrivate);
+
+	marker->priv = priv;
+	priv->contact_uid = NULL;
+	priv->image = NULL;
+	priv->background = NULL;
+	priv->shadow = NULL;
+	priv->text_actor = NULL;
+	priv->content_group = CLUTTER_GROUP (clutter_group_new ());
+	priv->redraw_id = 0;
+
+  	clutter_actor_set_parent (CLUTTER_ACTOR (priv->content_group), CLUTTER_ACTOR (marker));
+	clutter_actor_queue_relayout (CLUTTER_ACTOR (marker));
+
+  	priv->total_width = 0;
+  	priv->total_height = 0;
+
+	g_signal_connect (marker, "notify::selected",
+		G_CALLBACK (notify_selected), NULL);
+	g_signal_connect (CLUTTER_ACTOR (marker), "button-release-event",
+		G_CALLBACK (contact_marker_clicked_cb), NULL);
+}
+
+ClutterActor *
+e_contact_marker_new (const gchar *name,
+		      const gchar *contact_uid,
+     		      EContactPhoto *photo)
+{
+	ClutterActor *marker = CLUTTER_ACTOR (g_object_new (E_TYPE_CONTACT_MARKER, NULL));
+	EContactMarkerPrivate *priv = E_CONTACT_MARKER (marker)->priv;
+
+	g_return_val_if_fail (name && *name, NULL);
+	g_return_val_if_fail (contact_uid && *contact_uid, NULL);
+
+	champlain_label_set_text (CHAMPLAIN_LABEL (marker), name);
+	priv->contact_uid = g_strdup (contact_uid);
+	if (photo)
+		priv->image = contact_photo_to_texture (photo);
+
+	queue_redraw (E_CONTACT_MARKER (marker));
+
+	return marker;
+}
+
+const gchar*
+e_contact_marker_get_contact_uid (EContactMarker *marker)
+{
+	g_return_val_if_fail (marker && E_IS_CONTACT_MARKER (marker), NULL);
+
+	return marker->priv->contact_uid;
+}
+
+#endif /* WITH_CONTACT_MAPS */
diff --git a/widgets/misc/e-contact-marker.h b/widgets/misc/e-contact-marker.h
new file mode 100644
index 0000000..946a773
--- /dev/null
+++ b/widgets/misc/e-contact-marker.h
@@ -0,0 +1,85 @@
+/*
+ * e-contact-marker.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Copyright (C) 2008 Pierre-Luc Beaudoin <pierre-luc pierlux com>
+ * Copyright (C) 2011 Jiri Techet <techet gmail com>
+ * Copyright (C) 2011 Dan Vratil <dvratil redhat com>
+ *
+ */
+
+#ifndef E_CONTACT_MARKER_H
+#define E_CONTACT_MARKER_H
+
+#ifdef WITH_CONTACT_MAPS
+
+#include <libebook/e-contact.h>
+
+#include <champlain/champlain.h>
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_CONTACT_MARKER e_contact_marker_get_type ()
+
+#define E_CONTACT_MARKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_CONTACT_MARKER, EContactMarker))
+
+#define E_CONTACT_MARKER_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_CONTACT_MARKER, EContactMarkerClass))
+
+#define E_IS_CONTACT_MARKER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_CONTACT_MARKER))
+
+#define E_IS_CONTACT_MARKER_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_CONTACT_MARKER))
+
+#define E_CONTACT_MARKER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_CONTACT_MARKER, EContactMarkerClass))
+
+typedef struct _EContactMarkerPrivate EContactMarkerPrivate;
+
+typedef struct _EContactMarker EContactMarker;
+typedef struct _EContactMarkerClass EContactMarkerClass;
+
+struct _EContactMarker
+{
+  	ChamplainLabel parent;
+  	EContactMarkerPrivate *priv;
+};
+
+struct _EContactMarkerClass
+{
+	ChamplainLabelClass parent_class;
+
+	void (*double_clicked)	(ClutterActor *actor);
+};
+
+
+GType e_contact_marker_get_type 		(void);
+
+ClutterActor* e_contact_marker_new 		(const gchar *name,
+				    		 const gchar *contact_uid,
+				    		 EContactPhoto *photo);
+
+const gchar* e_contact_marker_get_contact_uid 	(EContactMarker *marker);
+
+G_END_DECLS
+
+#endif /* WITH_CONTACT_MAPS */
+
+#endif



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