diff options
author | Dan Vrátil <dvratil@redhat.com> | 2011-06-02 22:57:23 +0800 |
---|---|---|
committer | Rodrigo Moya <rodrigo@gnome-db.org> | 2011-06-30 00:42:25 +0800 |
commit | 3ce496063229de02670df0b87b2fabef91bae2b7 (patch) | |
tree | 8b573e9bc46f56a1b41510f633cb9a28a8e3f0b1 | |
parent | e529198d32b2e1359350af638a2d980938077c65 (diff) | |
download | gsoc2013-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
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 |