]> git.0d.be Git - empathy.git/blob - src/empathy-map-view.c
Merge branch 'sasl'
[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 #include <gdk/gdkkeysyms.h>
27
28 #include <champlain/champlain.h>
29 #include <champlain-gtk/champlain-gtk.h>
30 #include <clutter-gtk/clutter-gtk.h>
31 #include <telepathy-glib/util.h>
32
33 #include <libempathy/empathy-contact.h>
34 #include <libempathy/empathy-contact-manager.h>
35 #include <libempathy/empathy-utils.h>
36 #include <libempathy/empathy-location.h>
37
38 #include <libempathy-gtk/empathy-contact-menu.h>
39 #include <libempathy-gtk/empathy-ui-utils.h>
40
41 #include "empathy-map-view.h"
42
43 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
44 #include <libempathy/empathy-debug.h>
45
46 G_DEFINE_TYPE (EmpathyMapView, empathy_map_view, GTK_TYPE_WINDOW);
47
48 #define GET_PRIV(self) ((EmpathyMapViewPriv *)((EmpathyMapView *) self)->priv)
49
50 struct _EmpathyMapViewPriv {
51   EmpathyContactList *contact_list;
52
53   GtkWidget *zoom_in;
54   GtkWidget *zoom_out;
55   GtkWidget *throbber;
56   ChamplainView *map_view;
57   ChamplainLayer *layer;
58   guint timeout_id;
59   /* reffed (EmpathyContact *) => borrowed (ChamplainMarker *) */
60   GHashTable *markers;
61   gulong members_changed_id;
62 };
63
64 static void
65 map_view_state_changed (ChamplainView *view,
66     GParamSpec *gobject,
67     EmpathyMapView *self)
68 {
69   ChamplainState state;
70   EmpathyMapViewPriv *priv = GET_PRIV (self);
71
72   g_object_get (G_OBJECT (view), "state", &state, NULL);
73   if (state == CHAMPLAIN_STATE_LOADING)
74     {
75       gtk_spinner_start (GTK_SPINNER (priv->throbber));
76       gtk_widget_show (priv->throbber);
77     }
78   else
79     {
80       gtk_spinner_stop (GTK_SPINNER (priv->throbber));
81       gtk_widget_hide (priv->throbber);
82     }
83 }
84
85 static gboolean
86 contact_has_location (EmpathyContact *contact)
87 {
88   GHashTable *location;
89
90   location = empathy_contact_get_location (contact);
91
92   if (location == NULL || g_hash_table_size (location) == 0)
93     return FALSE;
94
95   return TRUE;
96 }
97
98 static ChamplainMarker * create_marker (EmpathyMapView *window,
99     EmpathyContact *contact);
100
101 static void
102 map_view_update_contact_position (EmpathyMapView *self,
103     EmpathyContact *contact)
104 {
105   EmpathyMapViewPriv *priv = GET_PRIV (self);
106   gdouble lon, lat;
107   GValue *value;
108   GHashTable *location;
109   ChamplainMarker *marker;
110   gboolean has_location;
111
112   has_location = contact_has_location (contact);
113
114   marker = g_hash_table_lookup (priv->markers, contact);
115   if (marker == NULL)
116     {
117       if (!has_location)
118         return;
119
120       marker = create_marker (self, contact);
121     }
122   else if (!has_location)
123     {
124       champlain_base_marker_animate_out (CHAMPLAIN_BASE_MARKER (marker));
125       return;
126     }
127
128   location = empathy_contact_get_location (contact);
129
130   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
131   if (value == NULL)
132     {
133       clutter_actor_hide (CLUTTER_ACTOR (marker));
134       return;
135     }
136   lat = g_value_get_double (value);
137
138   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LON);
139   if (value == NULL)
140     {
141       clutter_actor_hide (CLUTTER_ACTOR (marker));
142       return;
143     }
144   lon = g_value_get_double (value);
145
146   champlain_base_marker_set_position (CHAMPLAIN_BASE_MARKER (marker), lat, lon);
147   champlain_base_marker_animate_in (CHAMPLAIN_BASE_MARKER (marker));
148 }
149
150 static void
151 map_view_contact_location_notify (EmpathyContact *contact,
152     GParamSpec *arg1,
153     EmpathyMapView *self)
154 {
155   map_view_update_contact_position (self, contact);
156 }
157
158 static void
159 map_view_zoom_in_cb (GtkWidget *widget,
160     EmpathyMapView *self)
161 {
162   EmpathyMapViewPriv *priv = GET_PRIV (self);
163
164   champlain_view_zoom_in (priv->map_view);
165 }
166
167 static void
168 map_view_zoom_out_cb (GtkWidget *widget,
169     EmpathyMapView *self)
170 {
171   EmpathyMapViewPriv *priv = GET_PRIV (self);
172
173   champlain_view_zoom_out (priv->map_view);
174 }
175
176 static void
177 map_view_zoom_fit_cb (GtkWidget *widget,
178     EmpathyMapView *self)
179 {
180   EmpathyMapViewPriv *priv = GET_PRIV (self);
181   GList *item, *children;
182   GPtrArray *markers;
183
184   children = clutter_container_get_children (CLUTTER_CONTAINER (priv->layer));
185   markers =  g_ptr_array_sized_new (g_list_length (children) + 1);
186
187   for (item = children; item != NULL; item = g_list_next (item))
188     g_ptr_array_add (markers, (gpointer) item->data);
189
190   g_ptr_array_add (markers, (gpointer) NULL);
191   champlain_view_ensure_markers_visible (priv->map_view,
192     (ChamplainBaseMarker **) markers->pdata,
193     TRUE);
194
195   g_ptr_array_free (markers, TRUE);
196   g_list_free (children);
197 }
198
199 static gboolean
200 marker_clicked_cb (ChamplainMarker *marker,
201     ClutterButtonEvent *event,
202     EmpathyContact *contact)
203 {
204   GtkWidget *menu;
205
206   if (event->button != 3)
207     return FALSE;
208
209   menu = empathy_contact_menu_new (contact,
210       EMPATHY_CONTACT_FEATURE_CHAT |
211       EMPATHY_CONTACT_FEATURE_CALL |
212       EMPATHY_CONTACT_FEATURE_LOG |
213       EMPATHY_CONTACT_FEATURE_INFO);
214
215   if (menu == NULL)
216     return FALSE;
217
218   gtk_widget_show (menu);
219   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
220       event->button, event->time);
221   g_object_ref_sink (menu);
222   g_object_unref (menu);
223
224   return FALSE;
225 }
226
227 static void
228 map_view_contacts_update_label (ChamplainMarker *marker)
229 {
230   const gchar *name;
231   gchar *date;
232   gchar *label;
233   GValue *gtime;
234   time_t loctime;
235   GHashTable *location;
236   EmpathyContact *contact;
237
238   contact = g_object_get_data (G_OBJECT (marker), "contact");
239   location = empathy_contact_get_location (contact);
240   name = empathy_contact_get_alias (contact);
241   gtime = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
242
243   if (gtime != NULL)
244     {
245       time_t now;
246
247       loctime = g_value_get_int64 (gtime);
248       date = empathy_time_to_string_relative (loctime);
249       label = g_strconcat ("<b>", name, "</b>\n<small>", date, "</small>", NULL);
250       g_free (date);
251
252       now = time (NULL);
253
254       /* if location is older than a week */
255       if (now - loctime > (60 * 60 * 24 * 7))
256         clutter_actor_set_opacity (CLUTTER_ACTOR (marker), 0.75 * 255);
257     }
258   else
259     {
260       label = g_strconcat ("<b>", name, "</b>\n", NULL);
261     }
262
263   champlain_marker_set_use_markup (CHAMPLAIN_MARKER (marker), TRUE);
264   champlain_marker_set_text (CHAMPLAIN_MARKER (marker), label);
265
266   g_free (label);
267 }
268
269 static ChamplainMarker *
270 create_marker (EmpathyMapView *self,
271     EmpathyContact *contact)
272 {
273   EmpathyMapViewPriv *priv = GET_PRIV (self);
274   ClutterActor *marker;
275   ClutterActor *texture;
276   GdkPixbuf *avatar;
277
278   marker = champlain_marker_new ();
279
280   avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
281   if (avatar != NULL)
282     {
283       texture = clutter_texture_new ();
284       gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (texture), avatar,
285           NULL);
286       champlain_marker_set_image (CHAMPLAIN_MARKER (marker), texture);
287       g_object_unref (avatar);
288     }
289   else
290     champlain_marker_set_image (CHAMPLAIN_MARKER (marker), NULL);
291
292   g_object_set_data_full (G_OBJECT (marker), "contact",
293       g_object_ref (contact), g_object_unref);
294
295   g_hash_table_insert (priv->markers, g_object_ref (contact), marker);
296
297   map_view_contacts_update_label (CHAMPLAIN_MARKER (marker));
298
299   clutter_actor_set_reactive (CLUTTER_ACTOR (marker), TRUE);
300   g_signal_connect (marker, "button-release-event",
301       G_CALLBACK (marker_clicked_cb), contact);
302
303   clutter_container_add (CLUTTER_CONTAINER (priv->layer), marker, NULL);
304
305   DEBUG ("Create marker for %s", empathy_contact_get_id (contact));
306
307   return CHAMPLAIN_MARKER (marker);
308 }
309
310 static void
311 contact_added (EmpathyMapView *self,
312     EmpathyContact *contact)
313 {
314   g_signal_connect (contact, "notify::location",
315       G_CALLBACK (map_view_contact_location_notify), self);
316
317   map_view_update_contact_position (self, contact);
318 }
319
320 static gboolean
321 map_view_key_press_cb (GtkWidget *widget,
322     GdkEventKey *event,
323     gpointer user_data)
324 {
325   if ((event->state & GDK_CONTROL_MASK && event->keyval == GDK_w)
326       || event->keyval == GDK_Escape)
327     {
328       gtk_widget_destroy (widget);
329       return TRUE;
330     }
331
332   return FALSE;
333 }
334
335 static gboolean
336 map_view_tick (EmpathyMapView *self)
337 {
338   EmpathyMapViewPriv *priv = GET_PRIV (self);
339   GList *marker, *l;
340
341   marker = clutter_container_get_children (CLUTTER_CONTAINER (priv->layer));
342
343   for (l = marker; l != NULL; l = g_list_next (l))
344     map_view_contacts_update_label (l->data);
345
346   g_list_free (marker);
347   return TRUE;
348 }
349
350 static void
351 contact_removed (EmpathyMapView *self,
352     EmpathyContact *contact)
353 {
354   EmpathyMapViewPriv *priv = GET_PRIV (self);
355   ClutterActor *marker;
356
357   marker = g_hash_table_lookup (priv->markers, contact);
358   if (marker == NULL)
359     return;
360
361   clutter_actor_destroy (marker);
362   g_hash_table_remove (priv->markers, contact);
363 }
364
365 static void
366 members_changed_cb (EmpathyContactList *list,
367     EmpathyContact *contact,
368     EmpathyContact *actor,
369     guint reason,
370     gchar *message,
371     gboolean is_member,
372     EmpathyMapView *self)
373 {
374   if (is_member)
375     {
376       contact_added (self, contact);
377     }
378   else
379     {
380       contact_removed (self, contact);
381     }
382 }
383
384 static GObject *
385 empathy_map_view_constructor (GType type,
386     guint n_construct_params,
387     GObjectConstructParam *construct_params)
388 {
389   static GObject *window = NULL;
390
391   if (window != NULL)
392     return window;
393
394   window = G_OBJECT_CLASS (empathy_map_view_parent_class)->constructor (
395       type, n_construct_params, construct_params);
396
397   g_object_add_weak_pointer (window, (gpointer) &window);
398
399   return window;
400 }
401
402 static void
403 empathy_map_view_finalize (GObject *object)
404 {
405   EmpathyMapViewPriv *priv = GET_PRIV (object);
406   GHashTableIter iter;
407   gpointer contact;
408
409   g_source_remove (priv->timeout_id);
410
411   g_hash_table_iter_init (&iter, priv->markers);
412   while (g_hash_table_iter_next (&iter, &contact, NULL))
413     g_signal_handlers_disconnect_by_func (contact,
414         map_view_contact_location_notify, object);
415
416   g_signal_handler_disconnect (priv->contact_list,
417       priv->members_changed_id);
418
419   g_hash_table_destroy (priv->markers);
420   g_object_unref (priv->contact_list);
421   g_object_unref (priv->layer);
422
423   G_OBJECT_CLASS (empathy_map_view_parent_class)->finalize (object);
424 }
425
426 static void
427 empathy_map_view_class_init (EmpathyMapViewClass *klass)
428 {
429   GObjectClass *object_class = G_OBJECT_CLASS (klass);
430
431   object_class->constructor = empathy_map_view_constructor;
432   object_class->finalize = empathy_map_view_finalize;
433
434   g_type_class_add_private (object_class, sizeof (EmpathyMapViewPriv));
435 }
436
437 static void
438 empathy_map_view_init (EmpathyMapView *self)
439 {
440   EmpathyMapViewPriv *priv;
441   GtkBuilder *gui;
442   GtkWidget *sw;
443   GtkWidget *embed;
444   GtkWidget *throbber_holder;
445   gchar *filename;
446   GList *members, *l;
447   GtkWidget *main_vbox;
448
449   priv = self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
450       EMPATHY_TYPE_MAP_VIEW, EmpathyMapViewPriv);
451
452   gtk_window_set_title (GTK_WINDOW (self), _("Contact Map View"));
453   gtk_window_set_role (GTK_WINDOW (self), "map_view");
454   gtk_window_set_default_size (GTK_WINDOW (self), 512, 384);
455   gtk_window_set_position (GTK_WINDOW (self), GTK_WIN_POS_CENTER);
456
457   /* Set up interface */
458   filename = empathy_file_lookup ("empathy-map-view.ui", "src");
459   gui = empathy_builder_get_file (filename,
460      "main_vbox", &main_vbox,
461      "zoom_in", &priv->zoom_in,
462      "zoom_out", &priv->zoom_out,
463      "map_scrolledwindow", &sw,
464      "throbber", &throbber_holder,
465      NULL);
466   g_free (filename);
467
468   gtk_container_add (GTK_CONTAINER (self), main_vbox);
469
470   empathy_builder_connect (gui, self,
471       "zoom_in", "clicked", map_view_zoom_in_cb,
472       "zoom_out", "clicked", map_view_zoom_out_cb,
473       "zoom_fit", "clicked", map_view_zoom_fit_cb,
474       NULL);
475
476   g_signal_connect (self, "key-press-event",
477       G_CALLBACK (map_view_key_press_cb), self);
478
479   g_object_unref (gui);
480
481   priv->contact_list = EMPATHY_CONTACT_LIST (
482       empathy_contact_manager_dup_singleton ());
483
484   priv->members_changed_id = g_signal_connect (priv->contact_list,
485       "members-changed", G_CALLBACK (members_changed_cb), self);
486
487   priv->throbber = gtk_spinner_new ();
488   gtk_widget_set_size_request (priv->throbber, 16, 16);
489   gtk_container_add (GTK_CONTAINER (throbber_holder), priv->throbber);
490
491   /* Set up map view */
492   embed = gtk_champlain_embed_new ();
493   priv->map_view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (embed));
494   g_object_set (G_OBJECT (priv->map_view), "zoom-level", 1,
495      "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC, NULL);
496   champlain_view_center_on (priv->map_view, 36, 0);
497
498   gtk_container_add (GTK_CONTAINER (sw), embed);
499   gtk_widget_show_all (embed);
500
501   priv->layer = g_object_ref (champlain_layer_new ());
502   champlain_view_add_layer (priv->map_view, priv->layer);
503
504   g_signal_connect (priv->map_view, "notify::state",
505       G_CALLBACK (map_view_state_changed), self);
506
507   /* Set up contact list. */
508   priv->markers = g_hash_table_new_full (NULL, NULL,
509       (GDestroyNotify) g_object_unref, NULL);
510
511   members = empathy_contact_list_get_members (
512       priv->contact_list);
513   for (l = members; l != NULL; l = g_list_next (l))
514     {
515       contact_added (self, l->data);
516       g_object_unref (l->data);
517     }
518   g_list_free (members);
519
520   /* Set up time updating loop */
521   priv->timeout_id = g_timeout_add_seconds (5,
522       (GSourceFunc) map_view_tick, self);
523 }
524
525 GtkWidget *
526 empathy_map_view_show (void)
527 {
528   GtkWidget *window;
529
530   window = g_object_new (EMPATHY_TYPE_MAP_VIEW, NULL);
531   gtk_widget_show_all (window);
532   empathy_window_present (GTK_WINDOW (window));
533
534   return window;
535 }