aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Vrátil <dvratil@redhat.com>2011-06-02 22:57:23 +0800
committerRodrigo Moya <rodrigo@gnome-db.org>2011-06-30 00:42:25 +0800
commit3ce496063229de02670df0b87b2fabef91bae2b7 (patch)
tree8b573e9bc46f56a1b41510f633cb9a28a8e3f0b1
parente529198d32b2e1359350af638a2d980938077c65 (diff)
downloadgsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar.gz
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar.bz2
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar.lz
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar.xz
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.tar.zst
gsoc2013-evolution-3ce496063229de02670df0b87b2fabef91bae2b7.zip
Bug #642557 - Display maps in contact preview
-rw-r--r--addressbook/gui/widgets/Makefile.am6
-rw-r--r--addressbook/gui/widgets/eab-contact-display.c198
-rw-r--r--addressbook/gui/widgets/eab-contact-display.h3
-rw-r--r--configure.ac65
-rw-r--r--modules/addressbook/Makefile.am3
-rw-r--r--modules/addressbook/apps_evolution_addressbook.schemas.in12
-rw-r--r--modules/addressbook/e-book-shell-content.c56
-rw-r--r--modules/addressbook/e-book-shell-content.h5
-rw-r--r--modules/addressbook/e-book-shell-view-actions.c164
-rw-r--r--modules/addressbook/e-book-shell-view-actions.h6
-rw-r--r--plugins/contacts-map/Makefile.am34
-rw-r--r--plugins/contacts-map/contacts-map.c226
-rw-r--r--plugins/contacts-map/geo-utils.c124
-rw-r--r--plugins/contacts-map/geo-utils.h49
-rw-r--r--plugins/contacts-map/org-gnome-contacts-map.eplug.xml19
-rw-r--r--po/POTFILES.in4
-rw-r--r--ui/evolution-contacts.ui6
-rw-r--r--widgets/misc/Makefile.am17
-rw-r--r--widgets/misc/e-contact-map-window.c465
-rw-r--r--widgets/misc/e-contact-map-window.h77
-rw-r--r--widgets/misc/e-contact-map.c394
-rw-r--r--widgets/misc/e-contact-map.h108
-rw-r--r--widgets/misc/e-contact-marker.c600
-rw-r--r--widgets/misc/e-contact-marker.h85
24 files changed, 2221 insertions, 505 deletions
diff --git a/addressbook/gui/widgets/Makefile.am b/addressbook/gui/widgets/Makefile.am
index 511876ad0f..99bbc3af02 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 c970afe968..361988010b 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 e74a65cb38..f7561f8779 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 4d1e0915a5..79132fb670 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 35cf16aa7c..b6dec30b0a 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 03e6006527..e13337e82c 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 bb0c4a0a4d..a86d101e1b 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 0876ec968c..28fef270bd 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 b8e6c4f21c..5116232161 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 ef40dd169f..87eeaa8b17 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/plugins/contacts-map/Makefile.am b/plugins/contacts-map/Makefile.am
deleted file mode 100644
index 2c8a86dd9e..0000000000
--- a/plugins/contacts-map/Makefile.am
+++ /dev/null
@@ -1,34 +0,0 @@
-@EVO_PLUGIN_RULE@
-
-plugin_DATA = org-gnome-contacts-map.eplug
-plugin_LTLIBRARIES = liborg-gnome-contacts-map.la
-
-liborg_gnome_contacts_map_la_CPPFLAGS = \
- $(AM_CPPFLAGS) \
- -I$(top_srcdir) \
- -I$(top_srcdir)/widgets \
- $(GNOME_PLATFORM_CFLAGS) \
- $(EVOLUTION_ADDRESSBOOK_CFLAGS) \
- $(CHAMPLAIN_CFLAGS) \
- $(GEOCLUE_CFLAGS)
-
-liborg_gnome_contacts_map_la_SOURCES = \
- contacts-map.c \
- geo-utils.c \
- geo-utils.h
-
-liborg_gnome_contacts_map_la_LDFLAGS = -module -avoid-version $(NO_UNDEFINED)
-liborg_gnome_contacts_map_la_LIBADD = \
- $(top_builddir)/e-util/libeutil.la \
- $(top_builddir)/shell/libeshell.la \
- $(CHAMPLAIN_LIBS) \
- $(GEOCLUE_LIBS) \
- $(EVOLUTION_ADDRESSBOOK_LIBS) \
- $(GNOME_PLATFORM_LIBS)
-
-EXTRA_DIST = org-gnome-contacts-map.eplug.xml
-
-BUILT_SOURCES = $(plugin_DATA)
-CLEANFILES = $(BUILT_SOURCES)
-
--include $(top_srcdir)/git.mk
diff --git a/plugins/contacts-map/contacts-map.c b/plugins/contacts-map/contacts-map.c
deleted file mode 100644
index 91433f71d4..0000000000
--- a/plugins/contacts-map/contacts-map.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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/>
- *
- *
- * Authors:
- * Cedric Bosdonnat <cedric.bosdonnat@free.fr>
- *
- * Copyright (C) 2009 Cedric Bosdonnat (http://cedric.bosdonnat.free.fr)
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "geo-utils.h"
-
-#include <glib/gi18n.h>
-
-#include <libedataserver/e-source.h>
-#include <libedataserverui/e-source-selector.h>
-
-#include <shell/e-shell-sidebar.h>
-#include <shell/e-shell-view.h>
-#include <shell/e-shell-window.h>
-
-/* Plugin entry points */
-
-gboolean addressbook_map_init (GtkUIManager *ui_manager, EShellView *shell_view);
-void action_show_ebook_map (GtkAction *action, EShellView *shell_view);
-void show_map_general (ESourceSelector *selector);
-
-/* Implementations */
-
-gboolean
-addressbook_map_init (GtkUIManager *ui_manager, EShellView *shell_view)
-{
- EShellWindow *shell_window;
- GtkActionGroup *action_group;
- GtkAction *action;
- GIcon *icon;
- const gchar *tooltip;
- const gchar *name;
- const gchar *label;
-
- shell_window = e_shell_view_get_shell_window (shell_view);
-
- name = "contacts-map";
- label = _("Contacts map");
- tooltip = _("Show a map of all the contacts");
- action = gtk_action_new (name, NULL, tooltip, NULL);
- icon = g_themed_icon_new ("gnome-globe");
- gtk_action_set_gicon (action, icon);
- gtk_action_set_label (action, label);
-
- name = "contacts";
- action_group = e_shell_window_get_action_group (shell_window, name);
- gtk_action_group_add_action (action_group, action);
-
- g_signal_connect (
- action, "activate",
- G_CALLBACK (action_show_ebook_map), shell_view);
-
- g_object_unref (action);
-
- return TRUE;
-}
-
-void
-action_show_ebook_map (GtkAction *action, EShellView *shell_view)
-{
- EShellSidebar *shell_sidebar;
- ESourceSelector *selector = NULL;
-
- shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
- g_object_get (shell_sidebar, "selector", &selector, NULL);
- g_return_if_fail (selector != NULL);
-
- show_map_general (selector);
-
- g_object_unref (selector);
-}
-
-void
-show_map_general (ESourceSelector *selector)
-{
- EBook *book;
- ESource *primary_source;
- EBookQuery *query;
- GList *contacts, *tmp;
- gchar *uri;
-
- GeoclueGeocode *geocoder;
- GeocluePositionFields fields;
- GeoclueAccuracy *accuracy;
-
- gdouble lat = 0;
- gdouble lng = 0;
-
- GtkWidget *map_widget;
- ChamplainView *view;
- ChamplainLayer *layer;
-
- gdouble *min_lat = NULL;
- gdouble *max_lat = NULL;
- gdouble *min_lng = NULL;
- gdouble *max_lng = NULL;
-
- primary_source = (ESource*) e_source_selector_get_primary_selection (selector);
- uri = e_source_get_uri (primary_source);
- book = e_book_new_from_uri (uri, NULL);
-
- if (!book || !e_book_open (book, TRUE, NULL))
- {
- g_warning ("Couldn't load addressbook %s", uri);
- return;
- }
-
- /* Get all the contacts with an address */
- query = e_book_query_field_exists (E_CONTACT_ADDRESS);
- e_book_get_contacts (book, query, &contacts, NULL);
- e_book_query_unref (query);
-
- init_map (&view, &map_widget);
- layer = champlain_selection_layer_new ();
-
- geocoder = NULL;
- geocoder = get_geocoder ();
- if (geocoder != NULL) {
- for (tmp = contacts; tmp; tmp = tmp->next) {
- GError *error = NULL;
- EContact *contact;
- EContactAddress *addr;
- GHashTable *details;
- gint i;
- const gint addr_fields[] = {
- E_CONTACT_ADDRESS_HOME,
- E_CONTACT_ADDRESS_WORK,
- E_CONTACT_ADDRESS_OTHER
- };
-
- contact = tmp->data;
-
- /* Get the lat & lng and add the marker asynchronously */
- i = 0;
- addr = NULL;
- while (!addr && i<G_N_ELEMENTS (addr_fields)) {
- addr = e_contact_get (contact, addr_fields[i]);
- i++;
- }
-
- details = (GHashTable*) get_geoclue_from_address (addr);
- fields = geoclue_geocode_address_to_position (geocoder, details,
- &lat, &lng, NULL, &accuracy, &error);
-
- if (!error &&
- (fields & GEOCLUE_POSITION_FIELDS_LATITUDE) != 0 &&
- (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE) != 0) {
- /* Add the marker to the map */
- add_marker (layer, lat, lng, contact);
- if (!min_lat) {
- min_lat = g_malloc (sizeof (gdouble));
- *min_lat = lat;
- }
- if (!max_lat) {
- max_lat = g_malloc (sizeof (gdouble));
- *max_lat = lat;
- }
- if (!min_lng) {
- min_lng = g_malloc (sizeof (gdouble));
- *min_lng = lng;
- }
- if (!max_lng) {
- max_lng = malloc (sizeof (gdouble));
- *max_lng = lng;
- }
-
- /* Store the min/max lat/lng */
- get_min_max (min_lat, max_lat,
- min_lng, max_lng, lat, lng);
- } else if (error) {
- g_warning ("Error while geocoding: %s\n", error->message);
- g_error_free (error);
- }
-
- g_hash_table_destroy (details);
- g_object_unref (contact);
- }
- }
-
- champlain_view_add_layer (view, layer);
- champlain_layer_show (layer);
- champlain_layer_show_all_markers (CHAMPLAIN_LAYER (layer));
-
- create_map_window (map_widget, _("Contacts map"));
-
- /* Do not ensure something visible is we have nothing */
- if (min_lat && min_lng && max_lat && max_lng)
- champlain_view_ensure_visible (view,
- *min_lat, *min_lng,
- *max_lat, *max_lng, FALSE);
-
- g_free (min_lat);
- g_free (max_lat);
- g_free (min_lng);
- g_free (max_lng);
-
- g_object_unref (geocoder);
-
- if (contacts != NULL)
- g_list_free (contacts);
-
- g_object_unref (book);
- g_free (uri);
-}
diff --git a/plugins/contacts-map/geo-utils.c b/plugins/contacts-map/geo-utils.c
deleted file mode 100644
index ec36b33ab2..0000000000
--- a/plugins/contacts-map/geo-utils.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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/>
- *
- *
- * Authors:
- * Cedric Bosdonnat <cedric.bosdonnat@free.fr>
- *
- * Copyright (C) 2009 Cedric Bosdonnat (http://cedric.bosdonnat.free.fr)
- *
- */
-
-#include "geo-utils.h"
-
-static gboolean is_clutter_initialized = FALSE;
-
-void
-get_min_max (gdouble *min_lat, gdouble *max_lat,
- gdouble *min_lng, gdouble *max_lng,
- gdouble lat, gdouble lng)
-{
- if (lat < *min_lat)
- *min_lat = lat;
- else if (lat > *max_lat)
- *max_lat = lat;
-
- if (lng < *min_lng)
- *min_lng = lng;
- else if (lng > *max_lng)
- *max_lng = lng;
-}
-
-void
-add_marker (ChamplainLayer *layer, gdouble lat, gdouble lng, EContact *contact)
-{
- ClutterActor *marker;
-
- gchar *contact_name = e_contact_get (contact, E_CONTACT_FULL_NAME);
- marker = champlain_marker_new_with_text (contact_name, "Serif 8", NULL, NULL);
- g_free (contact_name);
-
- champlain_marker_set_use_markup (CHAMPLAIN_MARKER (marker), FALSE);
- champlain_base_marker_set_position (CHAMPLAIN_BASE_MARKER (marker), lat, lng);
-
- champlain_layer_add_marker (layer, CHAMPLAIN_BASE_MARKER (marker));
-}
-
-GeoclueGeocode*
-get_geocoder (void)
-{
- GeoclueGeocode *geocoder = NULL;
-
- /* Create new GeoclueGeocode */
- geocoder = geoclue_geocode_new ("org.freedesktop.Geoclue.Providers.Yahoo",
- "/org/freedesktop/Geoclue/Providers/Yahoo");
-
- return geocoder;
-}
-
-GHashTable *
-get_geoclue_from_address (const EContactAddress* addr)
-{
- GHashTable *address = geoclue_address_details_new ();
-
- g_hash_table_insert (address, g_strdup (GEOCLUE_ADDRESS_KEY_POSTALCODE), g_strdup ((*addr).code));
- g_hash_table_insert (address, g_strdup (GEOCLUE_ADDRESS_KEY_COUNTRY), g_strdup ((*addr).country));
- g_hash_table_insert (address, g_strdup (GEOCLUE_ADDRESS_KEY_LOCALITY), g_strdup ((*addr).locality));
- g_hash_table_insert (address, g_strdup (GEOCLUE_ADDRESS_KEY_STREET), g_strdup ((*addr).street));
-
- return address;
-}
-
-void
-init_map (ChamplainView **view, GtkWidget **widget)
-{
- if (!is_clutter_initialized) {
- gtk_clutter_init (NULL, NULL);
- is_clutter_initialized = TRUE;
- }
-
- *widget = gtk_champlain_embed_new ();
- *view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (*widget));
-
- champlain_view_set_show_license (*view, FALSE);
-
- g_object_set (G_OBJECT (*view), "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC,
- "zoom-level", 9, NULL);
-}
-
-void
-create_map_window (GtkWidget *map_widget, const gchar *title)
-{
- GtkWidget *window, *viewport;
-
- /* create the main, top level, window */
- window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
- /* give the window a 10px wide border */
- gtk_container_set_border_width (GTK_CONTAINER (window), 10);
-
- /* give it the title */
- gtk_window_set_title (GTK_WINDOW (window), title );
-
- gtk_widget_set_size_request (map_widget, 300, 300);
-
- viewport = gtk_frame_new (NULL);
- gtk_container_add (GTK_CONTAINER (viewport), map_widget);
-
- /* and insert it into the main window */
- gtk_container_add (GTK_CONTAINER (window), viewport);
-
- /* make sure that everything, window and label, are visible */
- gtk_widget_show_all (window);
-}
diff --git a/plugins/contacts-map/geo-utils.h b/plugins/contacts-map/geo-utils.h
deleted file mode 100644
index aa1ebcc3eb..0000000000
--- a/plugins/contacts-map/geo-utils.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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/>
- *
- *
- * Authors:
- * Cedric Bosdonnat <cedric.bosdonnat@free.fr>
- *
- * Copyright (C) 2009 Cedric Bosdonnat (http://cedric.bosdonnat.free.fr)
- *
- */
-
-#include <gtk/gtk.h>
-
-#include <libebook/e-book.h>
-#include <libebook/e-contact.h>
-
-#include <geoclue/geoclue-geocode.h>
-#include <champlain/champlain.h>
-#include <champlain-gtk/champlain-gtk.h>
-#include <clutter-gtk/clutter-gtk.h>
-
-void
-get_min_max (gdouble *min_lat, gdouble *max_lat,
- gdouble *min_lng, gdouble *max_lng,
- gdouble lat, gdouble lng);
-
-GeoclueGeocode *get_geocoder (void);
-
-void add_marker (
- ChamplainLayer *layer,
- gdouble lat, gdouble lng,
- EContact *contact);
-
-GHashTable *get_geoclue_from_address (const EContactAddress* addr);
-
-void init_map (ChamplainView **view, GtkWidget **widget);
-
-void create_map_window (GtkWidget *map_widget, const gchar *title);
diff --git a/plugins/contacts-map/org-gnome-contacts-map.eplug.xml b/plugins/contacts-map/org-gnome-contacts-map.eplug.xml
deleted file mode 100644
index e695979491..0000000000
--- a/plugins/contacts-map/org-gnome-contacts-map.eplug.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0"?>
-<e-plugin-list>
- <e-plugin id="org.gnome.evolution.contacts_maps" type="shlib" _name="Map for contacts"
- location="@PLUGINDIR@/liborg-gnome-contacts-map@SOEXT@">
- <author name="Cedric Bosdonnat" email="cedric.bosdonnat.ooo@free.fr"/>
- <_description>Add a map showing the location of contacts when possible.</_description>
-
- <hook class="org.gnome.evolution.ui:1.0">
- <ui-manager id="org.gnome.evolution.contacts" callback="addressbook_map_init">
- <!-- Add something for contact-popup -->
- <popup name="address-book-popup">
- <menuitem action="contacts-map"/>
- </popup>
- </ui-manager>
- </hook>
-
- </e-plugin>
-</e-plugin-list>
-
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 700d9a8c81..b11778909d 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 0e0716d0ec..571e825174 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 fdcd5417dd..33bde9301c 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 0000000000..9d9ed9c39e
--- /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 0000000000..ea96d88e39
--- /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 0000000000..d862fc3331
--- /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 0000000000..fd5fabf159
--- /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 0000000000..233ae076be
--- /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 0000000000..946a77305e
--- /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