2 * Copyright (C) 2008, 2009 Collabora Ltd.
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.
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.
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
18 * Authors: Pierre-Luc Beaudoin <pierre-luc.beaudoin@collabora.co.uk>
25 #include <glib/gi18n.h>
26 #include <gdk/gdkkeysyms.h>
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>
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>
38 #include <libempathy-gtk/empathy-contact-menu.h>
39 #include <libempathy-gtk/empathy-ui-utils.h>
41 #include "empathy-map-view.h"
43 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
44 #include <libempathy/empathy-debug.h>
46 G_DEFINE_TYPE (EmpathyMapView
, empathy_map_view
, GTK_TYPE_WINDOW
);
48 #define GET_PRIV(self) ((EmpathyMapViewPriv *)((EmpathyMapView *) self)->priv)
50 struct _EmpathyMapViewPriv
{
51 EmpathyContactList
*contact_list
;
56 ChamplainView
*map_view
;
57 ChamplainLayer
*layer
;
59 /* reffed (EmpathyContact *) => borrowed (ChamplainMarker *) */
61 gulong members_changed_id
;
65 map_view_state_changed (ChamplainView
*view
,
70 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
72 g_object_get (G_OBJECT (view
), "state", &state
, NULL
);
73 if (state
== CHAMPLAIN_STATE_LOADING
)
75 gtk_spinner_start (GTK_SPINNER (priv
->throbber
));
76 gtk_widget_show (priv
->throbber
);
80 gtk_spinner_stop (GTK_SPINNER (priv
->throbber
));
81 gtk_widget_hide (priv
->throbber
);
86 contact_has_location (EmpathyContact
*contact
)
90 location
= empathy_contact_get_location (contact
);
92 if (location
== NULL
|| g_hash_table_size (location
) == 0)
98 static ChamplainMarker
* create_marker (EmpathyMapView
*window
,
99 EmpathyContact
*contact
);
102 map_view_update_contact_position (EmpathyMapView
*self
,
103 EmpathyContact
*contact
)
105 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
108 GHashTable
*location
;
109 ChamplainMarker
*marker
;
110 gboolean has_location
;
112 has_location
= contact_has_location (contact
);
114 marker
= g_hash_table_lookup (priv
->markers
, contact
);
120 marker
= create_marker (self
, contact
);
122 else if (!has_location
)
124 champlain_base_marker_animate_out (CHAMPLAIN_BASE_MARKER (marker
));
128 location
= empathy_contact_get_location (contact
);
130 value
= g_hash_table_lookup (location
, EMPATHY_LOCATION_LAT
);
133 clutter_actor_hide (CLUTTER_ACTOR (marker
));
136 lat
= g_value_get_double (value
);
138 value
= g_hash_table_lookup (location
, EMPATHY_LOCATION_LON
);
141 clutter_actor_hide (CLUTTER_ACTOR (marker
));
144 lon
= g_value_get_double (value
);
146 champlain_base_marker_set_position (CHAMPLAIN_BASE_MARKER (marker
), lat
, lon
);
147 champlain_base_marker_animate_in (CHAMPLAIN_BASE_MARKER (marker
));
151 map_view_contact_location_notify (EmpathyContact
*contact
,
153 EmpathyMapView
*self
)
155 map_view_update_contact_position (self
, contact
);
159 map_view_zoom_in_cb (GtkWidget
*widget
,
160 EmpathyMapView
*self
)
162 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
164 champlain_view_zoom_in (priv
->map_view
);
168 map_view_zoom_out_cb (GtkWidget
*widget
,
169 EmpathyMapView
*self
)
171 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
173 champlain_view_zoom_out (priv
->map_view
);
177 map_view_zoom_fit_cb (GtkWidget
*widget
,
178 EmpathyMapView
*self
)
180 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
181 GList
*item
, *children
;
184 children
= clutter_container_get_children (CLUTTER_CONTAINER (priv
->layer
));
185 markers
= g_ptr_array_sized_new (g_list_length (children
) + 1);
187 for (item
= children
; item
!= NULL
; item
= g_list_next (item
))
188 g_ptr_array_add (markers
, (gpointer
) item
->data
);
190 g_ptr_array_add (markers
, (gpointer
) NULL
);
191 champlain_view_ensure_markers_visible (priv
->map_view
,
192 (ChamplainBaseMarker
**) markers
->pdata
,
195 g_ptr_array_free (markers
, TRUE
);
196 g_list_free (children
);
200 marker_clicked_cb (ChamplainMarker
*marker
,
201 ClutterButtonEvent
*event
,
202 EmpathyContact
*contact
)
206 if (event
->button
!= 3)
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
);
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
);
228 map_view_contacts_update_label (ChamplainMarker
*marker
)
235 GHashTable
*location
;
236 EmpathyContact
*contact
;
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
);
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
);
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);
260 label
= g_strconcat ("<b>", name
, "</b>\n", NULL
);
263 champlain_marker_set_use_markup (CHAMPLAIN_MARKER (marker
), TRUE
);
264 champlain_marker_set_text (CHAMPLAIN_MARKER (marker
), label
);
269 static ChamplainMarker
*
270 create_marker (EmpathyMapView
*self
,
271 EmpathyContact
*contact
)
273 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
274 ClutterActor
*marker
;
275 ClutterActor
*texture
;
278 marker
= champlain_marker_new ();
280 avatar
= empathy_pixbuf_avatar_from_contact_scaled (contact
, 32, 32);
283 texture
= clutter_texture_new ();
284 gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (texture
), avatar
,
286 champlain_marker_set_image (CHAMPLAIN_MARKER (marker
), texture
);
287 g_object_unref (avatar
);
290 champlain_marker_set_image (CHAMPLAIN_MARKER (marker
), NULL
);
292 g_object_set_data_full (G_OBJECT (marker
), "contact",
293 g_object_ref (contact
), g_object_unref
);
295 g_hash_table_insert (priv
->markers
, g_object_ref (contact
), marker
);
297 map_view_contacts_update_label (CHAMPLAIN_MARKER (marker
));
299 clutter_actor_set_reactive (CLUTTER_ACTOR (marker
), TRUE
);
300 g_signal_connect (marker
, "button-release-event",
301 G_CALLBACK (marker_clicked_cb
), contact
);
303 clutter_container_add (CLUTTER_CONTAINER (priv
->layer
), marker
, NULL
);
305 DEBUG ("Create marker for %s", empathy_contact_get_id (contact
));
307 return CHAMPLAIN_MARKER (marker
);
311 contact_added (EmpathyMapView
*self
,
312 EmpathyContact
*contact
)
314 g_signal_connect (contact
, "notify::location",
315 G_CALLBACK (map_view_contact_location_notify
), self
);
317 map_view_update_contact_position (self
, contact
);
321 map_view_key_press_cb (GtkWidget
*widget
,
325 if ((event
->state
& GDK_CONTROL_MASK
&& event
->keyval
== GDK_w
)
326 || event
->keyval
== GDK_Escape
)
328 gtk_widget_destroy (widget
);
336 map_view_tick (EmpathyMapView
*self
)
338 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
341 marker
= clutter_container_get_children (CLUTTER_CONTAINER (priv
->layer
));
343 for (l
= marker
; l
!= NULL
; l
= g_list_next (l
))
344 map_view_contacts_update_label (l
->data
);
346 g_list_free (marker
);
351 contact_removed (EmpathyMapView
*self
,
352 EmpathyContact
*contact
)
354 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
355 ClutterActor
*marker
;
357 marker
= g_hash_table_lookup (priv
->markers
, contact
);
361 clutter_actor_destroy (marker
);
362 g_hash_table_remove (priv
->markers
, contact
);
366 members_changed_cb (EmpathyContactList
*list
,
367 EmpathyContact
*contact
,
368 EmpathyContact
*actor
,
372 EmpathyMapView
*self
)
376 contact_added (self
, contact
);
380 contact_removed (self
, contact
);
385 empathy_map_view_constructor (GType type
,
386 guint n_construct_params
,
387 GObjectConstructParam
*construct_params
)
389 static GObject
*window
= NULL
;
394 window
= G_OBJECT_CLASS (empathy_map_view_parent_class
)->constructor (
395 type
, n_construct_params
, construct_params
);
397 g_object_add_weak_pointer (window
, (gpointer
) &window
);
403 empathy_map_view_finalize (GObject
*object
)
405 EmpathyMapViewPriv
*priv
= GET_PRIV (object
);
409 g_source_remove (priv
->timeout_id
);
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
);
416 g_signal_handler_disconnect (priv
->contact_list
,
417 priv
->members_changed_id
);
419 g_hash_table_destroy (priv
->markers
);
420 g_object_unref (priv
->contact_list
);
421 g_object_unref (priv
->layer
);
423 G_OBJECT_CLASS (empathy_map_view_parent_class
)->finalize (object
);
427 empathy_map_view_class_init (EmpathyMapViewClass
*klass
)
429 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
431 object_class
->constructor
= empathy_map_view_constructor
;
432 object_class
->finalize
= empathy_map_view_finalize
;
434 g_type_class_add_private (object_class
, sizeof (EmpathyMapViewPriv
));
438 empathy_map_view_init (EmpathyMapView
*self
)
440 EmpathyMapViewPriv
*priv
;
444 GtkWidget
*throbber_holder
;
447 GtkWidget
*main_vbox
;
449 priv
= self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
450 EMPATHY_TYPE_MAP_VIEW
, EmpathyMapViewPriv
);
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
);
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
,
468 gtk_container_add (GTK_CONTAINER (self
), main_vbox
);
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
,
476 g_signal_connect (self
, "key-press-event",
477 G_CALLBACK (map_view_key_press_cb
), self
);
479 g_object_unref (gui
);
481 priv
->contact_list
= EMPATHY_CONTACT_LIST (
482 empathy_contact_manager_dup_singleton ());
484 priv
->members_changed_id
= g_signal_connect (priv
->contact_list
,
485 "members-changed", G_CALLBACK (members_changed_cb
), self
);
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
);
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);
498 gtk_container_add (GTK_CONTAINER (sw
), embed
);
499 gtk_widget_show_all (embed
);
501 priv
->layer
= g_object_ref (champlain_layer_new ());
502 champlain_view_add_layer (priv
->map_view
, priv
->layer
);
504 g_signal_connect (priv
->map_view
, "notify::state",
505 G_CALLBACK (map_view_state_changed
), self
);
507 /* Set up contact list. */
508 priv
->markers
= g_hash_table_new_full (NULL
, NULL
,
509 (GDestroyNotify
) g_object_unref
, NULL
);
511 members
= empathy_contact_list_get_members (
513 for (l
= members
; l
!= NULL
; l
= g_list_next (l
))
515 contact_added (self
, l
->data
);
516 g_object_unref (l
->data
);
518 g_list_free (members
);
520 /* Set up time updating loop */
521 priv
->timeout_id
= g_timeout_add_seconds (5,
522 (GSourceFunc
) map_view_tick
, self
);
526 empathy_map_view_show (void)
530 window
= g_object_new (EMPATHY_TYPE_MAP_VIEW
, NULL
);
531 gtk_widget_show_all (window
);
532 empathy_window_present (GTK_WINDOW (window
));