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 ChamplainMarkerLayer
*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 ClutterActor
* 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 ClutterActor
*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_marker_animate_out (CHAMPLAIN_MARKER (marker
));
128 location
= empathy_contact_get_location (contact
);
130 value
= g_hash_table_lookup (location
, EMPATHY_LOCATION_LAT
);
133 clutter_actor_hide (marker
);
136 lat
= g_value_get_double (value
);
138 value
= g_hash_table_lookup (location
, EMPATHY_LOCATION_LON
);
141 clutter_actor_hide (marker
);
144 lon
= g_value_get_double (value
);
146 champlain_location_set_location (CHAMPLAIN_LOCATION (marker
), lat
, lon
);
147 champlain_marker_animate_in (CHAMPLAIN_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
);
182 champlain_view_ensure_layers_visible (priv
->map_view
, TRUE
);
186 marker_clicked_cb (ChamplainMarker
*marker
,
187 ClutterButtonEvent
*event
,
188 EmpathyMapView
*self
)
191 EmpathyContact
*contact
;
193 if (event
->button
!= 3)
196 contact
= g_object_get_data (G_OBJECT (marker
), "contact");
198 menu
= empathy_contact_menu_new (contact
,
199 EMPATHY_CONTACT_FEATURE_CHAT
|
200 EMPATHY_CONTACT_FEATURE_CALL
|
201 EMPATHY_CONTACT_FEATURE_LOG
|
202 EMPATHY_CONTACT_FEATURE_FT
|
203 EMPATHY_CONTACT_FEATURE_INFO
);
208 gtk_menu_attach_to_widget (GTK_MENU (menu
), GTK_WIDGET (self
), NULL
);
210 gtk_widget_show (menu
);
211 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
,
212 event
->button
, event
->time
);
218 map_view_contacts_update_label (ClutterActor
*marker
)
225 GHashTable
*location
;
226 EmpathyContact
*contact
;
228 contact
= g_object_get_data (G_OBJECT (marker
), "contact");
229 location
= empathy_contact_get_location (contact
);
230 name
= empathy_contact_get_alias (contact
);
231 gtime
= g_hash_table_lookup (location
, EMPATHY_LOCATION_TIMESTAMP
);
238 loctime
= g_value_get_int64 (gtime
);
239 date
= empathy_time_to_string_relative (loctime
);
240 label
= g_strconcat ("<b>", name
, "</b>\n<small>", date
, "</small>", NULL
);
243 now
= g_date_time_new_now_utc ();
244 d
= g_date_time_new_from_unix_utc (loctime
);
245 delta
= g_date_time_difference (now
, d
);
247 /* if location is older than a week */
248 if (delta
> G_TIME_SPAN_DAY
* 7)
249 clutter_actor_set_opacity (marker
, 0.75 * 255);
251 g_date_time_unref (now
);
252 g_date_time_unref (d
);
256 label
= g_strconcat ("<b>", name
, "</b>\n", NULL
);
259 champlain_label_set_use_markup (CHAMPLAIN_LABEL (marker
), TRUE
);
260 champlain_label_set_text (CHAMPLAIN_LABEL (marker
), label
);
265 static ClutterActor
*
266 create_marker (EmpathyMapView
*self
,
267 EmpathyContact
*contact
)
269 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
270 ClutterActor
*marker
;
272 ClutterActor
*texture
= NULL
;
274 avatar
= empathy_pixbuf_avatar_from_contact_scaled (contact
, 32, 32);
277 texture
= gtk_clutter_texture_new ();
279 gtk_clutter_texture_set_from_pixbuf (GTK_CLUTTER_TEXTURE (texture
),
282 g_object_unref (avatar
);
285 marker
= champlain_label_new_with_image (texture
);
287 g_object_set_data_full (G_OBJECT (marker
), "contact",
288 g_object_ref (contact
), g_object_unref
);
290 g_hash_table_insert (priv
->markers
, g_object_ref (contact
), marker
);
292 map_view_contacts_update_label (marker
);
294 clutter_actor_set_reactive (CLUTTER_ACTOR (marker
), TRUE
);
295 g_signal_connect (marker
, "button-release-event",
296 G_CALLBACK (marker_clicked_cb
), self
);
298 champlain_marker_layer_add_marker (priv
->layer
, CHAMPLAIN_MARKER (marker
));
300 DEBUG ("Create marker for %s", empathy_contact_get_id (contact
));
302 tp_clear_object (&texture
);
307 contact_added (EmpathyMapView
*self
,
308 EmpathyContact
*contact
)
310 g_signal_connect (contact
, "notify::location",
311 G_CALLBACK (map_view_contact_location_notify
), self
);
313 map_view_update_contact_position (self
, contact
);
317 map_view_key_press_cb (GtkWidget
*widget
,
321 if ((event
->state
& GDK_CONTROL_MASK
&& event
->keyval
== GDK_KEY_w
)
322 || event
->keyval
== GDK_KEY_Escape
)
324 gtk_widget_destroy (widget
);
332 map_view_tick (EmpathyMapView
*self
)
334 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
337 marker
= champlain_marker_layer_get_markers (priv
->layer
);
339 for (l
= marker
; l
!= NULL
; l
= g_list_next (l
))
340 map_view_contacts_update_label (l
->data
);
342 g_list_free (marker
);
347 contact_removed (EmpathyMapView
*self
,
348 EmpathyContact
*contact
)
350 EmpathyMapViewPriv
*priv
= GET_PRIV (self
);
351 ClutterActor
*marker
;
353 marker
= g_hash_table_lookup (priv
->markers
, contact
);
357 clutter_actor_destroy (marker
);
358 g_hash_table_remove (priv
->markers
, contact
);
362 members_changed_cb (EmpathyContactList
*list
,
363 EmpathyContact
*contact
,
364 EmpathyContact
*actor
,
368 EmpathyMapView
*self
)
372 contact_added (self
, contact
);
376 contact_removed (self
, contact
);
381 empathy_map_view_constructor (GType type
,
382 guint n_construct_params
,
383 GObjectConstructParam
*construct_params
)
385 static GObject
*window
= NULL
;
390 window
= G_OBJECT_CLASS (empathy_map_view_parent_class
)->constructor (
391 type
, n_construct_params
, construct_params
);
393 g_object_add_weak_pointer (window
, (gpointer
) &window
);
399 empathy_map_view_finalize (GObject
*object
)
401 EmpathyMapViewPriv
*priv
= GET_PRIV (object
);
405 g_source_remove (priv
->timeout_id
);
407 g_hash_table_iter_init (&iter
, priv
->markers
);
408 while (g_hash_table_iter_next (&iter
, &contact
, NULL
))
409 g_signal_handlers_disconnect_by_func (contact
,
410 map_view_contact_location_notify
, object
);
412 g_signal_handler_disconnect (priv
->contact_list
,
413 priv
->members_changed_id
);
415 g_hash_table_destroy (priv
->markers
);
416 g_object_unref (priv
->contact_list
);
417 g_object_unref (priv
->layer
);
419 G_OBJECT_CLASS (empathy_map_view_parent_class
)->finalize (object
);
423 empathy_map_view_class_init (EmpathyMapViewClass
*klass
)
425 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
427 object_class
->constructor
= empathy_map_view_constructor
;
428 object_class
->finalize
= empathy_map_view_finalize
;
430 g_type_class_add_private (object_class
, sizeof (EmpathyMapViewPriv
));
434 empathy_map_view_init (EmpathyMapView
*self
)
436 EmpathyMapViewPriv
*priv
;
440 GtkWidget
*throbber_holder
;
443 GtkWidget
*main_vbox
;
445 priv
= self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
446 EMPATHY_TYPE_MAP_VIEW
, EmpathyMapViewPriv
);
448 gtk_window_set_title (GTK_WINDOW (self
), _("Contact Map View"));
449 gtk_window_set_role (GTK_WINDOW (self
), "map_view");
450 gtk_window_set_default_size (GTK_WINDOW (self
), 512, 384);
451 gtk_window_set_position (GTK_WINDOW (self
), GTK_WIN_POS_CENTER
);
453 /* Set up interface */
454 filename
= empathy_file_lookup ("empathy-map-view.ui", "src");
455 gui
= empathy_builder_get_file (filename
,
456 "main_vbox", &main_vbox
,
457 "zoom_in", &priv
->zoom_in
,
458 "zoom_out", &priv
->zoom_out
,
459 "map_scrolledwindow", &sw
,
460 "throbber", &throbber_holder
,
464 gtk_container_add (GTK_CONTAINER (self
), main_vbox
);
466 empathy_builder_connect (gui
, self
,
467 "zoom_in", "clicked", map_view_zoom_in_cb
,
468 "zoom_out", "clicked", map_view_zoom_out_cb
,
469 "zoom_fit", "clicked", map_view_zoom_fit_cb
,
472 g_signal_connect (self
, "key-press-event",
473 G_CALLBACK (map_view_key_press_cb
), self
);
475 g_object_unref (gui
);
477 priv
->contact_list
= EMPATHY_CONTACT_LIST (
478 empathy_contact_manager_dup_singleton ());
480 priv
->members_changed_id
= g_signal_connect (priv
->contact_list
,
481 "members-changed", G_CALLBACK (members_changed_cb
), self
);
483 priv
->throbber
= gtk_spinner_new ();
484 gtk_widget_set_size_request (priv
->throbber
, 16, 16);
485 gtk_container_add (GTK_CONTAINER (throbber_holder
), priv
->throbber
);
487 /* Set up map view */
488 embed
= gtk_champlain_embed_new ();
489 priv
->map_view
= gtk_champlain_embed_get_view (GTK_CHAMPLAIN_EMBED (embed
));
490 g_object_set (G_OBJECT (priv
->map_view
),
492 "kinetic-mode", TRUE
,
494 champlain_view_center_on (priv
->map_view
, 36, 0);
496 gtk_container_add (GTK_CONTAINER (sw
), embed
);
497 gtk_widget_show_all (embed
);
499 priv
->layer
= g_object_ref (champlain_marker_layer_new ());
500 champlain_view_add_layer (priv
->map_view
, CHAMPLAIN_LAYER (priv
->layer
));
502 g_signal_connect (priv
->map_view
, "notify::state",
503 G_CALLBACK (map_view_state_changed
), self
);
505 /* Set up contact list. */
506 priv
->markers
= g_hash_table_new_full (NULL
, NULL
,
507 (GDestroyNotify
) g_object_unref
, NULL
);
509 members
= empathy_contact_list_get_members (
511 for (l
= members
; l
!= NULL
; l
= g_list_next (l
))
513 contact_added (self
, l
->data
);
514 g_object_unref (l
->data
);
516 g_list_free (members
);
518 /* Set up time updating loop */
519 priv
->timeout_id
= g_timeout_add_seconds (5,
520 (GSourceFunc
) map_view_tick
, self
);
524 empathy_map_view_show (void)
528 window
= g_object_new (EMPATHY_TYPE_MAP_VIEW
, NULL
);
529 gtk_widget_show_all (window
);
530 empathy_window_present (GTK_WINDOW (window
));