]> git.0d.be Git - empathy.git/blob - src/empathy-map-view.c
Merge branch 'slatez-applets'
[empathy.git] / src / empathy-map-view.c
1 /*
2  * Copyright (C) 2008, 2009 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Pierre-Luc Beaudoin <pierre-luc.beaudoin@collabora.co.uk>
19  */
20
21 #include <config.h>
22
23 #include <sys/stat.h>
24 #include <gtk/gtk.h>
25 #include <glib/gi18n.h>
26
27 #include <champlain/champlain.h>
28 #include <champlain-gtk/champlain-gtk.h>
29 #include <clutter-gtk/clutter-gtk.h>
30 #include <telepathy-glib/util.h>
31
32 #include <libempathy/empathy-contact.h>
33 #include <libempathy/empathy-contact-manager.h>
34 #include <libempathy/empathy-utils.h>
35 #include <libempathy/empathy-location.h>
36
37 #include <libempathy-gtk/empathy-contact-list-store.h>
38 #include <libempathy-gtk/empathy-contact-list-view.h>
39 #include <libempathy-gtk/empathy-presence-chooser.h>
40 #include <libempathy-gtk/empathy-ui-utils.h>
41
42 #include "empathy-map-view.h"
43 #include "ephy-spinner.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
46 #include <libempathy/empathy-debug.h>
47
48 typedef struct {
49   EmpathyContactListStore *list_store;
50
51   GtkWidget *window;
52   GtkWidget *zoom_in;
53   GtkWidget *zoom_out;
54   GtkWidget *throbber;
55   ChamplainView *map_view;
56   ChamplainLayer *layer;
57   guint timeout_id;
58 } EmpathyMapView;
59
60 static void
61 map_view_state_changed (ChamplainView *view,
62     GParamSpec *gobject,
63     EmpathyMapView *window)
64 {
65   ChamplainState state;
66
67   g_object_get (G_OBJECT (view), "state", &state, NULL);
68   if (state == CHAMPLAIN_STATE_LOADING)
69     ephy_spinner_start (EPHY_SPINNER (window->throbber));
70   else
71     ephy_spinner_stop (EPHY_SPINNER (window->throbber));
72 }
73
74 static void
75 map_view_marker_update_position (ChamplainMarker *marker,
76     EmpathyContact *contact)
77 {
78   gdouble lon, lat;
79   GValue *value;
80   GHashTable *location;
81
82   location = empathy_contact_get_location (contact);
83
84   if (location == NULL ||
85       g_hash_table_size (location) == 0)
86   {
87     champlain_base_marker_animate_out (CHAMPLAIN_BASE_MARKER (marker));
88     return;
89   }
90
91   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
92   if (value == NULL)
93     {
94       clutter_actor_hide (CLUTTER_ACTOR (marker));
95       return;
96     }
97   lat = g_value_get_double (value);
98
99   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LON);
100   if (value == NULL)
101     {
102       clutter_actor_hide (CLUTTER_ACTOR (marker));
103       return;
104     }
105   lon = g_value_get_double (value);
106
107   champlain_base_marker_set_position (CHAMPLAIN_BASE_MARKER (marker), lat, lon);
108   champlain_base_marker_animate_in (CHAMPLAIN_BASE_MARKER (marker));
109 }
110
111 static void
112 map_view_contact_location_notify (EmpathyContact *contact,
113     GParamSpec *arg1,
114     ChamplainMarker *marker)
115 {
116   map_view_marker_update_position (marker, contact);
117 }
118
119 static void
120 map_view_zoom_in_cb (GtkWidget *widget,
121     EmpathyMapView *window)
122 {
123   champlain_view_zoom_in (window->map_view);
124 }
125
126 static void
127 map_view_zoom_out_cb (GtkWidget *widget,
128     EmpathyMapView *window)
129 {
130   champlain_view_zoom_out (window->map_view);
131 }
132
133 static void
134 map_view_zoom_fit_cb (GtkWidget *widget,
135     EmpathyMapView *window)
136 {
137   GList *item, *children;
138   GPtrArray *markers;
139
140   children = clutter_container_get_children (CLUTTER_CONTAINER (window->layer));
141   markers =  g_ptr_array_sized_new (g_list_length (children) + 1);
142
143   for (item = children; item != NULL; item = g_list_next (item))
144     g_ptr_array_add (markers, (gpointer) item->data);
145
146   g_ptr_array_add (markers, (gpointer) NULL);
147   champlain_view_ensure_markers_visible (window->map_view,
148     (ChamplainBaseMarker **) markers->pdata,
149     TRUE);
150
151   g_ptr_array_free (markers, TRUE);
152   g_list_free (children);
153 }
154
155 static gboolean
156 marker_clicked_cb (ChamplainMarker *marker,
157     ClutterButtonEvent *event,
158     EmpathyContact *contact)
159 {
160   GtkWidget *menu;
161
162   if (event->button != 3)
163     return FALSE;
164
165   menu = empathy_contact_menu_new (contact,
166       EMPATHY_CONTACT_FEATURE_CHAT |
167       EMPATHY_CONTACT_FEATURE_CALL |
168       EMPATHY_CONTACT_FEATURE_LOG |
169       EMPATHY_CONTACT_FEATURE_INFO);
170
171   if (menu == NULL)
172     return FALSE;
173
174   gtk_widget_show (menu);
175   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
176       event->button, event->time);
177
178   return FALSE;
179 }
180
181 static void
182 map_view_contacts_update_label (ChamplainMarker *marker)
183 {
184   const gchar *name;
185   gchar *date;
186   gchar *label;
187   GValue *gtime;
188   time_t loctime;
189   GHashTable *location;
190   EmpathyContact *contact;
191
192   contact = g_object_get_data (G_OBJECT (marker), "contact");
193   location = empathy_contact_get_location (contact);
194   name = empathy_contact_get_name (contact);
195   gtime = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
196
197   if (gtime != NULL)
198     {
199       time_t now;
200
201       loctime = g_value_get_int64 (gtime);
202       date = empathy_time_to_string_relative (loctime);
203       label = g_strconcat ("<b>", name, "</b>\n<small>", date, "</small>", NULL);
204       g_free (date);
205
206       now = time (NULL);
207
208       /* if location is older than a week */
209       if (now - loctime > (60 * 60 * 24 * 7))
210         clutter_actor_set_opacity (CLUTTER_ACTOR (marker), 0.75 * 255);
211     }
212   else
213     {
214       label = g_strconcat ("<b>", name, "</b>\n", NULL);
215     }
216
217   champlain_marker_set_use_markup (CHAMPLAIN_MARKER (marker), TRUE);
218   champlain_marker_set_text (CHAMPLAIN_MARKER (marker), label);
219
220   g_free (label);
221 }
222
223 static gboolean
224 map_view_contacts_foreach (GtkTreeModel *model,
225     GtkTreePath *path,
226     GtkTreeIter *iter,
227     gpointer user_data)
228 {
229   EmpathyMapView *window = (EmpathyMapView *) user_data;
230   EmpathyContact *contact;
231   ClutterActor *marker;
232   ClutterActor *texture;
233   GHashTable *location;
234   GdkPixbuf *avatar;
235
236   gtk_tree_model_get (model, iter, EMPATHY_CONTACT_LIST_STORE_COL_CONTACT,
237      &contact, -1);
238
239   if (contact == NULL)
240     return FALSE;
241
242   location = empathy_contact_get_location (contact);
243
244   if (location == NULL || g_hash_table_size (location) == 0)
245     return FALSE;
246
247   marker = champlain_marker_new ();
248
249   avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
250   if (avatar != NULL)
251     {
252       texture = clutter_texture_new ();
253       gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (texture), avatar,
254           NULL);
255       champlain_marker_set_image (CHAMPLAIN_MARKER (marker), texture);
256       g_object_unref (avatar);
257     }
258   else
259     champlain_marker_set_image (CHAMPLAIN_MARKER (marker), NULL);
260
261   g_object_set_data_full (G_OBJECT (marker), "contact",
262       g_object_ref (contact), g_object_unref);
263
264   map_view_contacts_update_label (CHAMPLAIN_MARKER (marker));
265
266   clutter_actor_set_reactive (CLUTTER_ACTOR (marker), TRUE);
267   g_signal_connect (marker, "button-release-event",
268       G_CALLBACK (marker_clicked_cb), contact);
269
270   clutter_container_add (CLUTTER_CONTAINER (window->layer), marker, NULL);
271
272   g_signal_connect (contact, "notify::location",
273       G_CALLBACK (map_view_contact_location_notify), marker);
274
275   map_view_marker_update_position (CHAMPLAIN_MARKER (marker), contact);
276
277   g_object_unref (contact);
278   return FALSE;
279 }
280
281 static void
282 map_view_destroy_cb (GtkWidget *widget,
283     EmpathyMapView *window)
284 {
285   GList *item;
286
287   g_source_remove (window->timeout_id);
288
289   item = clutter_container_get_children (CLUTTER_CONTAINER (window->layer));
290   while (item != NULL)
291   {
292     EmpathyContact *contact;
293     ChamplainMarker *marker;
294
295     marker = CHAMPLAIN_MARKER (item->data);
296     contact = g_object_get_data (G_OBJECT (marker), "contact");
297     g_signal_handlers_disconnect_by_func (contact,
298         map_view_contact_location_notify, marker);
299
300     item = g_list_next (item);
301   }
302
303   g_object_unref (window->list_store);
304   g_object_unref (window->layer);
305   g_slice_free (EmpathyMapView, window);
306 }
307
308 static gboolean
309 map_view_tick (EmpathyMapView *window)
310 {
311   GList *marker;
312
313   marker = clutter_container_get_children (CLUTTER_CONTAINER (window->layer));
314
315   for (; marker; marker = marker->next)
316     map_view_contacts_update_label (marker->data);
317
318   return TRUE;
319 }
320
321 GtkWidget *
322 empathy_map_view_show (void)
323 {
324   static EmpathyMapView *window = NULL;
325   GtkBuilder *gui;
326   GtkWidget *sw;
327   GtkWidget *embed;
328   GtkWidget *throbber_holder;
329   gchar *filename;
330   GtkTreeModel *model;
331   EmpathyContactList *list_iface;
332   EmpathyContactListStore *list_store;
333
334   if (window)
335     {
336       empathy_window_present (GTK_WINDOW (window->window), TRUE);
337       return window->window;
338     }
339
340   window = g_slice_new0 (EmpathyMapView);
341
342   /* Set up interface */
343   filename = empathy_file_lookup ("empathy-map-view.ui", "src");
344   gui = empathy_builder_get_file (filename,
345      "map_view", &window->window,
346      "zoom_in", &window->zoom_in,
347      "zoom_out", &window->zoom_out,
348      "map_scrolledwindow", &sw,
349      "throbber", &throbber_holder,
350      NULL);
351   g_free (filename);
352
353   empathy_builder_connect (gui, window,
354       "map_view", "destroy", map_view_destroy_cb,
355       "zoom_in", "clicked", map_view_zoom_in_cb,
356       "zoom_out", "clicked", map_view_zoom_out_cb,
357       "zoom_fit", "clicked", map_view_zoom_fit_cb,
358       NULL);
359
360   g_object_unref (gui);
361
362   /* Clear the static pointer to window if the dialog is destroyed */
363   g_object_add_weak_pointer (G_OBJECT (window->window), (gpointer *) &window);
364
365   list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_dup_singleton ());
366   list_store = empathy_contact_list_store_new (list_iface);
367   empathy_contact_list_store_set_show_groups (list_store, FALSE);
368   empathy_contact_list_store_set_show_avatars (list_store, TRUE);
369   g_object_unref (list_iface);
370
371   window->throbber = ephy_spinner_new ();
372   ephy_spinner_set_size (EPHY_SPINNER (window->throbber),
373       GTK_ICON_SIZE_LARGE_TOOLBAR);
374   gtk_widget_show (window->throbber);
375   gtk_container_add (GTK_CONTAINER (throbber_holder), window->throbber);
376
377   window->list_store = list_store;
378
379   /* Set up map view */
380   embed = gtk_champlain_embed_new ();
381   window->map_view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (embed));
382   g_object_set (G_OBJECT (window->map_view), "zoom-level", 1,
383      "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC, NULL);
384   champlain_view_center_on (window->map_view, 36, 0);
385
386   gtk_container_add (GTK_CONTAINER (sw), embed);
387   gtk_widget_show_all (embed);
388
389   window->layer = g_object_ref (champlain_layer_new ());
390   champlain_view_add_layer (window->map_view, window->layer);
391
392   g_signal_connect (window->map_view, "notify::state",
393       G_CALLBACK (map_view_state_changed), window);
394
395   /* Set up contact list. */
396   model = GTK_TREE_MODEL (window->list_store);
397   gtk_tree_model_foreach (model, map_view_contacts_foreach, window);
398
399   empathy_window_present (GTK_WINDOW (window->window), TRUE);
400
401   /* Set up time updating loop */
402   window->timeout_id = g_timeout_add_seconds (5,
403       (GSourceFunc) map_view_tick, window);
404
405   return window->window;
406 }
407