]> git.0d.be Git - empathy.git/blob - src/empathy-map-view.c
Merge branch 'debug-window'
[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 gboolean
134 marker_clicked_cb (ChamplainMarker *marker,
135     ClutterButtonEvent *event,
136     EmpathyContact *contact)
137 {
138   GtkWidget *menu;
139
140   if (event->button != 3)
141     return FALSE;
142
143   menu = empathy_contact_menu_new (contact,
144       EMPATHY_CONTACT_FEATURE_CHAT |
145       EMPATHY_CONTACT_FEATURE_CALL |
146       EMPATHY_CONTACT_FEATURE_LOG |
147       EMPATHY_CONTACT_FEATURE_INFO);
148
149   if (menu == NULL)
150     return FALSE;
151
152   gtk_widget_show (menu);
153   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
154       event->button, event->time);
155
156   return FALSE;
157 }
158
159 static void
160 map_view_contacts_update_label (ChamplainMarker *marker)
161 {
162   const gchar *name;
163   gchar *date;
164   gchar *label;
165   GValue *gtime;
166   time_t loctime;
167   GHashTable *location;
168   EmpathyContact *contact;
169
170   contact = g_object_get_data (G_OBJECT (marker), "contact");
171   location = empathy_contact_get_location (contact);
172   name = empathy_contact_get_name (contact);
173   gtime = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
174
175   if (gtime != NULL)
176     {
177       time_t now;
178
179       loctime = g_value_get_int64 (gtime);
180       date = empathy_time_to_string_relative (loctime);
181       label = g_strconcat ("<b>", name, "</b>\n<small>", date, "</small>", NULL);
182       g_free (date);
183
184       now = time (NULL);
185
186       /* if location is older than a week */
187       if (now - loctime > (60 * 60 * 24 * 7))
188         clutter_actor_set_opacity (CLUTTER_ACTOR (marker), 0.75 * 255);
189     }
190   else
191     {
192       label = g_strconcat ("<b>", name, "</b>\n", NULL);
193     }
194
195   champlain_marker_set_use_markup (CHAMPLAIN_MARKER (marker), TRUE);
196   champlain_marker_set_text (CHAMPLAIN_MARKER (marker), label);
197
198   g_free (label);
199 }
200
201 static gboolean
202 map_view_contacts_foreach (GtkTreeModel *model,
203     GtkTreePath *path,
204     GtkTreeIter *iter,
205     gpointer user_data)
206 {
207   EmpathyMapView *window = (EmpathyMapView *) user_data;
208   EmpathyContact *contact;
209   ClutterActor *marker;
210   ClutterActor *texture;
211   GHashTable *location;
212   GdkPixbuf *avatar;
213
214   gtk_tree_model_get (model, iter, EMPATHY_CONTACT_LIST_STORE_COL_CONTACT,
215      &contact, -1);
216
217   if (contact == NULL)
218     return FALSE;
219
220   location = empathy_contact_get_location (contact);
221
222   if (location == NULL || g_hash_table_size (location) == 0)
223     return FALSE;
224
225   marker = champlain_marker_new ();
226
227   avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
228   if (avatar != NULL)
229     {
230       texture = clutter_texture_new ();
231       gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (texture), avatar,
232           NULL);
233       champlain_marker_set_image (CHAMPLAIN_MARKER (marker), texture);
234       g_object_unref (avatar);
235     }
236   else
237     champlain_marker_set_image (CHAMPLAIN_MARKER (marker), NULL);
238
239   g_object_set_data_full (G_OBJECT (marker), "contact",
240       g_object_ref (contact), g_object_unref);
241
242   map_view_contacts_update_label (CHAMPLAIN_MARKER (marker));
243
244   clutter_actor_set_reactive (CLUTTER_ACTOR (marker), TRUE);
245   g_signal_connect (marker, "button-release-event",
246       G_CALLBACK (marker_clicked_cb), contact);
247
248   clutter_container_add (CLUTTER_CONTAINER (window->layer), marker, NULL);
249
250   g_signal_connect (contact, "notify::location",
251       G_CALLBACK (map_view_contact_location_notify), marker);
252
253   map_view_marker_update_position (CHAMPLAIN_MARKER (marker), contact);
254
255   g_object_unref (contact);
256   return FALSE;
257 }
258
259 static void
260 map_view_destroy_cb (GtkWidget *widget,
261     EmpathyMapView *window)
262 {
263   GList *item;
264
265   g_source_remove (window->timeout_id);
266
267   item = clutter_container_get_children (CLUTTER_CONTAINER (window->layer));
268   while (item != NULL)
269   {
270     EmpathyContact *contact;
271     ChamplainMarker *marker;
272
273     marker = CHAMPLAIN_MARKER (item->data);
274     contact = g_object_get_data (G_OBJECT (marker), "contact");
275     g_signal_handlers_disconnect_by_func (contact,
276         map_view_contact_location_notify, marker);
277
278     item = g_list_next (item);
279   }
280
281   g_object_unref (window->list_store);
282   g_object_unref (window->layer);
283   g_slice_free (EmpathyMapView, window);
284 }
285
286 static gboolean
287 map_view_tick (EmpathyMapView *window)
288 {
289   GList *marker;
290
291   marker = clutter_container_get_children (CLUTTER_CONTAINER (window->layer));
292
293   for (; marker; marker = marker->next)
294     map_view_contacts_update_label (marker->data);
295
296   return TRUE;
297 }
298
299 GtkWidget *
300 empathy_map_view_show (void)
301 {
302   static EmpathyMapView *window = NULL;
303   GtkBuilder *gui;
304   GtkWidget *sw;
305   GtkWidget *embed;
306   GtkWidget *throbber_holder;
307   gchar *filename;
308   GtkTreeModel *model;
309   EmpathyContactList *list_iface;
310   EmpathyContactListStore *list_store;
311
312   if (window)
313     {
314       empathy_window_present (GTK_WINDOW (window->window), TRUE);
315       return window->window;
316     }
317
318   window = g_slice_new0 (EmpathyMapView);
319
320   /* Set up interface */
321   filename = empathy_file_lookup ("empathy-map-view.ui", "src");
322   gui = empathy_builder_get_file (filename,
323      "map_view", &window->window,
324      "zoom_in", &window->zoom_in,
325      "zoom_out", &window->zoom_out,
326      "map_scrolledwindow", &sw,
327      "throbber", &throbber_holder,
328      NULL);
329   g_free (filename);
330
331   empathy_builder_connect (gui, window,
332       "map_view", "destroy", map_view_destroy_cb,
333       "zoom_in", "clicked", map_view_zoom_in_cb,
334       "zoom_out", "clicked", map_view_zoom_out_cb,
335       NULL);
336
337   g_object_unref (gui);
338
339   /* Clear the static pointer to window if the dialog is destroyed */
340   g_object_add_weak_pointer (G_OBJECT (window->window), (gpointer *) &window);
341
342   list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_dup_singleton ());
343   list_store = empathy_contact_list_store_new (list_iface);
344   empathy_contact_list_store_set_show_groups (list_store, FALSE);
345   empathy_contact_list_store_set_show_avatars (list_store, TRUE);
346   g_object_unref (list_iface);
347
348   window->throbber = ephy_spinner_new ();
349   ephy_spinner_set_size (EPHY_SPINNER (window->throbber),
350       GTK_ICON_SIZE_LARGE_TOOLBAR);
351   gtk_widget_show (window->throbber);
352   gtk_container_add (GTK_CONTAINER (throbber_holder), window->throbber);
353
354   window->list_store = list_store;
355
356   /* Set up map view */
357   embed = gtk_champlain_embed_new ();
358   window->map_view = gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (embed));
359   g_object_set (G_OBJECT (window->map_view), "zoom-level", 1,
360      "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC, NULL);
361   champlain_view_center_on (window->map_view, 36, 0);
362
363   gtk_container_add (GTK_CONTAINER (sw), embed);
364   gtk_widget_show_all (embed);
365
366   window->layer = g_object_ref (champlain_layer_new ());
367   champlain_view_add_layer (window->map_view, window->layer);
368
369   g_signal_connect (window->map_view, "notify::state",
370       G_CALLBACK (map_view_state_changed), window);
371
372   /* Set up contact list. */
373   model = GTK_TREE_MODEL (window->list_store);
374   gtk_tree_model_foreach (model, map_view_contacts_foreach, window);
375
376   empathy_window_present (GTK_WINDOW (window->window), TRUE);
377
378   /* Set up time updating loop */
379   window->timeout_id = g_timeout_add_seconds (5,
380       (GSourceFunc) map_view_tick, window);
381
382   return window->window;
383 }
384