diff options
Diffstat (limited to 'widgets/misc')
-rw-r--r-- | widgets/misc/Makefile.am | 17 | ||||
-rw-r--r-- | widgets/misc/e-contact-map-window.c | 465 | ||||
-rw-r--r-- | widgets/misc/e-contact-map-window.h | 77 | ||||
-rw-r--r-- | widgets/misc/e-contact-map.c | 394 | ||||
-rw-r--r-- | widgets/misc/e-contact-map.h | 108 | ||||
-rw-r--r-- | widgets/misc/e-contact-marker.c | 600 | ||||
-rw-r--r-- | widgets/misc/e-contact-marker.h | 85 |
7 files changed, 1744 insertions, 2 deletions
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 |