/* * 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. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * * Copyright (C) 2011 Dan Vratil */ #ifdef HAVE_CONFIG_H #include #endif #ifdef WITH_CONTACT_MAPS #include "e-contact-map.h" #include #include #include #include #include #include #include #include #include #include "e-util/e-util.h" #define E_CONTACT_MAP_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_CONTACT_MAP, EContactMapPrivate)) typedef struct _AsyncContext AsyncContext; struct _EContactMapPrivate { GHashTable *markers; /* Hash table contact-name -> marker */ ChamplainMarkerLayer *marker_layer; }; struct _AsyncContext { EContactMap *map; ClutterActor *marker; }; enum { CONTACT_ADDED, CONTACT_REMOVED, GEOCODING_STARTED, GEOCODING_FAILED, LAST_SIGNAL }; static gint signals[LAST_SIGNAL] = {0}; G_DEFINE_TYPE (EContactMap, e_contact_map, GTK_CHAMPLAIN_TYPE_EMBED) static void async_context_free (AsyncContext *async_context) { g_clear_object (&async_context->map); g_slice_free (AsyncContext, async_context); } static ClutterActor * texture_new_from_pixbuf (GdkPixbuf *pixbuf, GError **error) { ClutterActor *texture = NULL; const guchar *data; gboolean has_alpha, success; gint 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_map_photo_to_texture (EContactPhoto *photo) { ClutterActor *texture = NULL; GdkPixbuf *pixbuf = NULL; if (photo->type == E_CONTACT_PHOTO_TYPE_INLINED) { 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 != NULL) g_object_ref (pixbuf); g_object_unref (loader); } else if (photo->type == E_CONTACT_PHOTO_TYPE_URI) { pixbuf = gdk_pixbuf_new_from_file (photo->data.uri, NULL); } if (pixbuf != NULL) { texture = texture_new_from_pixbuf (pixbuf, NULL); g_object_unref (pixbuf); } return texture; } static void contact_map_address_resolved_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GHashTable *resolved = NULL; gpointer marker_ptr; AsyncContext *async_context = user_data; ChamplainMarkerLayer *marker_layer; ChamplainMarker *marker; GeocodePlace *place; GeocodeLocation *location; GList *search_results; const gchar *name; GError *local_error = NULL; marker = CHAMPLAIN_MARKER (async_context->marker); marker_layer = async_context->map->priv->marker_layer; name = champlain_label_get_text (CHAMPLAIN_LABEL (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 and pretend nothing happened. */ if (!CHAMPLAIN_IS_MARKER_LAYER (marker_layer)) goto exit; search_results = geocode_forward_search_finish ( GEOCODE_FORWARD (source_object), result, &local_error); /* Sanity check. */ g_warn_if_fail ( ((search_results != NULL) && (local_error == NULL)) || ((search_results == NULL) && (local_error != NULL))); /* Keep quiet if the search just came up empty. */ if (g_error_matches (local_error, GEOCODE_ERROR, GEOCODE_ERROR_NO_MATCHES)) { g_clear_error (&local_error); /* Leave a breadcrumb on the console for any other errors. */ } else if (local_error != NULL) { g_warning ("%s: %s", G_STRFUNC, local_error->message); g_clear_error (&local_error); } if (search_results == NULL) { g_signal_emit ( async_context->map, signals[GEOCODING_FAILED], 0, name); goto exit; } place = GEOCODE_PLACE (search_results->data); location = geocode_place_get_location (place); /* Move the marker to resolved position. */ champlain_location_set_location ( CHAMPLAIN_LOCATION (marker), geocode_location_get_latitude (location), geocode_location_get_longitude (location)); champlain_marker_layer_add_marker (marker_layer, marker); champlain_marker_set_selected (marker, FALSE); /* Do not unref the list elements. */ g_list_free (search_results); /* Store the marker in the hash table, using its label as key. */ marker_ptr = g_hash_table_lookup ( async_context->map->priv->markers, name); if (marker_ptr != NULL) { g_hash_table_remove (async_context->map->priv->markers, name); champlain_marker_layer_remove_marker (marker_layer, marker_ptr); } g_hash_table_insert ( async_context->map->priv->markers, g_strdup (name), marker); g_signal_emit ( async_context->map, signals[CONTACT_ADDED], 0, marker); exit: async_context_free (async_context); if (resolved != NULL) g_hash_table_unref (resolved); } static void add_attr (GHashTable *hash_table, const gchar *key, const gchar *string) { GValue *value; value = g_new0 (GValue, 1); g_value_init (value, G_TYPE_STRING); g_value_set_static_string (value, string); g_hash_table_insert (hash_table, g_strdup (key), value); } static GHashTable * address_to_xep (EContactAddress *address) { GHashTable *hash_table; hash_table = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) g_free); add_attr (hash_table, "postalcode", address->code); add_attr (hash_table, "country", address->country); add_attr (hash_table, "region", address->region); add_attr (hash_table, "locality", address->locality); add_attr (hash_table, "street", address->street); return hash_table; } static void contact_map_finalize (GObject *object) { EContactMapPrivate *priv; priv = E_CONTACT_MAP_GET_PRIVATE (object); g_hash_table_destroy (priv->markers); /* 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 (EContactMapPrivate)); 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) { ChamplainMarkerLayer *layer; ChamplainView *view; map->priv = E_CONTACT_MAP_GET_PRIVATE (map); map->priv->markers = g_hash_table_new_full ( (GHashFunc) g_str_hash, (GEqualFunc) g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); 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_name; const gchar *contact_uid; g_return_if_fail (E_IS_CONTACT_MAP (map)); g_return_if_fail (E_IS_CONTACT (contact)); photo = e_contact_get (contact, E_CONTACT_PHOTO); contact_name = e_contact_get_const (contact, E_CONTACT_FILE_AS); contact_uid = e_contact_get_const (contact, E_CONTACT_UID); address = e_contact_get (contact, E_CONTACT_ADDRESS_HOME); if (address != NULL) { gchar *name; name = g_strdup_printf ( "%s (%s)", contact_name, _("Home")); 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 != NULL) { gchar *name; name = g_strdup_printf ( "%s (%s)", contact_name, _("Work")); e_contact_map_add_marker ( map, name, contact_uid, address, photo); g_free (name); e_contact_address_free (address); } if (photo != NULL) e_contact_photo_free (photo); } void e_contact_map_add_marker (EContactMap *map, const gchar *name, const gchar *contact_uid, EContactAddress *address, EContactPhoto *photo) { ClutterActor *marker; GHashTable *hash_table; GeocodeForward *geocoder; AsyncContext *async_context; g_return_if_fail (E_IS_CONTACT_MAP (map)); g_return_if_fail (name != NULL); g_return_if_fail (contact_uid != NULL); g_return_if_fail (address != NULL); marker = champlain_label_new (); champlain_label_set_text (CHAMPLAIN_LABEL (marker), name); if (photo != NULL) { champlain_label_set_image ( CHAMPLAIN_LABEL (marker), contact_map_photo_to_texture (photo)); } /* Stash the contact UID for EContactMapWindow. */ g_object_set_data_full ( G_OBJECT (marker), "contact-uid", g_strdup (contact_uid), (GDestroyNotify) g_free); hash_table = address_to_xep (address); geocoder = geocode_forward_new_for_params (hash_table); g_hash_table_destroy (hash_table); async_context = g_slice_new0 (AsyncContext); async_context->map = g_object_ref (map); async_context->marker = marker; geocode_forward_search_async ( geocoder, NULL, contact_map_address_resolved_cb, async_context); g_object_unref (geocoder); g_signal_emit (map, signals[GEOCODING_STARTED], 0, marker); } /** * 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 (E_IS_CONTACT_MAP (map)); g_return_if_fail (name != NULL); 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_zoom_on_marker (EContactMap *map, ClutterActor *marker) { ChamplainView *view; gdouble lat, lng; g_return_if_fail (E_IS_CONTACT_MAP (map)); g_return_if_fail (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 */