]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-widget.c
Update to use libchamplain 0.3's API
[empathy.git] / libempathy-gtk / empathy-contact-widget.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2007-2009 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  */
21
22 #include <config.h>
23
24 #define _GNU_SOURCE
25 #include <string.h>
26 #include <stdlib.h>
27 #include <time.h>
28
29 #include <gtk/gtk.h>
30 #include <glib/gi18n-lib.h>
31
32 #if HAVE_LIBCHAMPLAIN
33 #include <champlain/champlain.h>
34 #include <champlain-gtk/champlain-gtk.h>
35 #endif
36
37 #include <libmissioncontrol/mc-account.h>
38 #include <telepathy-glib/util.h>
39
40 #include <libempathy/empathy-tp-contact-factory.h>
41 #include <libempathy/empathy-contact-manager.h>
42 #include <libempathy/empathy-contact-list.h>
43 #include <libempathy/empathy-location.h>
44 #include <libempathy/empathy-utils.h>
45
46 #include "empathy-contact-widget.h"
47 #include "empathy-account-chooser.h"
48 #include "empathy-avatar-chooser.h"
49 #include "empathy-avatar-image.h"
50 #include "empathy-ui-utils.h"
51
52 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
53 #include <libempathy/empathy-debug.h>
54
55 /**
56  * SECTION:empathy-contact-widget
57  * @title:EmpathyContactWidget
58  * @short_description: A widget used to display and edit details about a contact
59  * @include: libempathy-empathy-contact-widget.h
60  *
61  * #EmpathyContactWidget is a widget which displays appropriate widgets
62  * with details about a contact, also allowing changing these details,
63  * if desired.
64  */
65
66 /**
67  * EmpathyContactWidget:
68  * @parent: parent object
69  *
70  * Widget which displays appropriate widgets with details about a contact,
71  * also allowing changing these details, if desired.
72  */
73
74 /* Delay before updating the widget when the id entry changed (seconds) */
75 #define ID_CHANGED_TIMEOUT 1
76
77 typedef struct
78 {
79   EmpathyTpContactFactory *factory;
80   EmpathyContactManager *manager;
81   EmpathyContact *contact;
82   EmpathyContactWidgetFlags flags;
83   guint widget_id_timeout;
84
85   GtkWidget *vbox_contact_widget;
86
87   /* Contact */
88   GtkWidget *vbox_contact;
89   GtkWidget *widget_avatar;
90   GtkWidget *widget_account;
91   GtkWidget *widget_id;
92   GtkWidget *widget_alias;
93   GtkWidget *label_alias;
94   GtkWidget *entry_alias;
95   GtkWidget *hbox_presence;
96   GtkWidget *image_state;
97   GtkWidget *label_status;
98   GtkWidget *table_contact;
99   GtkWidget *vbox_avatar;
100
101   /* Location */
102   GtkWidget *vbox_location;
103   GtkWidget *subvbox_location;
104   GtkWidget *table_location;
105   GtkWidget *label_location;
106 #if HAVE_LIBCHAMPLAIN
107   GtkWidget *viewport_map;
108   GtkWidget *map_view_embed;
109   ClutterActor *map_view;
110 #endif
111
112   /* Groups */
113   GtkWidget *vbox_groups;
114   GtkWidget *entry_group;
115   GtkWidget *button_group;
116   GtkWidget *treeview_groups;
117
118   /* Details */
119   GtkWidget *vbox_details;
120   GtkWidget *table_details;
121   GtkWidget *hbox_details_requested;
122
123   /* Client */
124   GtkWidget *vbox_client;
125   GtkWidget *table_client;
126   GtkWidget *hbox_client_requested;
127 } EmpathyContactWidget;
128
129 typedef struct
130 {
131   EmpathyContactWidget *information;
132   const gchar *name;
133   gboolean found;
134   GtkTreeIter found_iter;
135 } FindName;
136
137 static void contact_widget_destroy_cb (GtkWidget *widget,
138     EmpathyContactWidget *information);
139 static void contact_widget_remove_contact (EmpathyContactWidget *information);
140 static void contact_widget_set_contact (EmpathyContactWidget *information,
141     EmpathyContact *contact);
142 static void contact_widget_contact_setup (EmpathyContactWidget *information);
143 static void contact_widget_contact_update (EmpathyContactWidget *information);
144 static void contact_widget_change_contact (EmpathyContactWidget *information);
145 static void contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
146     EmpathyContactWidget *information);
147 static gboolean contact_widget_id_focus_out_cb (GtkWidget *widget,
148     GdkEventFocus *event, EmpathyContactWidget *information);
149 static gboolean contact_widget_entry_alias_focus_event_cb (
150     GtkEditable *editable, GdkEventFocus *event,
151     EmpathyContactWidget *information);
152 static void contact_widget_name_notify_cb (EmpathyContactWidget *information);
153 static void contact_widget_presence_notify_cb (
154     EmpathyContactWidget *information);
155 static void contact_widget_avatar_notify_cb (
156     EmpathyContactWidget *information);
157 static void contact_widget_groups_setup (
158     EmpathyContactWidget *information);
159 static void contact_widget_groups_update (EmpathyContactWidget *information);
160 static void contact_widget_model_setup (EmpathyContactWidget *information);
161 static void contact_widget_model_populate_columns (
162     EmpathyContactWidget *information);
163 static void contact_widget_groups_populate_data (
164     EmpathyContactWidget *information);
165 static void contact_widget_groups_notify_cb (
166     EmpathyContactWidget *information);
167 static gboolean contact_widget_model_find_name (
168     EmpathyContactWidget *information,const gchar *name, GtkTreeIter *iter);
169 static gboolean contact_widget_model_find_name_foreach (GtkTreeModel *model,
170     GtkTreePath *path, GtkTreeIter *iter, FindName *data);
171 static void contact_widget_cell_toggled (GtkCellRendererToggle *cell,
172     gchar *path_string, EmpathyContactWidget *information);
173 static void contact_widget_entry_group_changed_cb (GtkEditable *editable,
174     EmpathyContactWidget *information);
175 static void contact_widget_entry_group_activate_cb (GtkEntry *entry,
176     EmpathyContactWidget *information);
177 static void contact_widget_button_group_clicked_cb (GtkButton *button,
178     EmpathyContactWidget *information);
179 static void contact_widget_details_setup (EmpathyContactWidget *information);
180 static void contact_widget_details_update (EmpathyContactWidget *information);
181 static void contact_widget_client_setup (EmpathyContactWidget *information);
182 static void contact_widget_client_update (EmpathyContactWidget *information);
183 static void contact_widget_location_update (EmpathyContactWidget *information);
184
185 enum
186 {
187   COL_NAME,
188   COL_ENABLED,
189   COL_EDITABLE,
190   COL_COUNT
191 };
192
193 /**
194  * empathy_contact_widget_new:
195  * @contact: an #EmpathyContact
196  * @flags: #EmpathyContactWidgetFlags for the new contact widget
197  *
198  * Creates a new #EmpathyContactWidget.
199  *
200  * Return value: a new #EmpathyContactWidget
201  */
202 GtkWidget *
203 empathy_contact_widget_new (EmpathyContact *contact,
204                             EmpathyContactWidgetFlags flags)
205 {
206   EmpathyContactWidget *information;
207   GtkBuilder *gui;
208   gchar *filename;
209
210   g_return_val_if_fail (contact == NULL || EMPATHY_IS_CONTACT (contact), NULL);
211
212   information = g_slice_new0 (EmpathyContactWidget);
213   information->flags = flags;
214
215   filename = empathy_file_lookup ("empathy-contact-widget.ui",
216       "libempathy-gtk");
217   gui = empathy_builder_get_file (filename,
218        "vbox_contact_widget", &information->vbox_contact_widget,
219        "vbox_contact", &information->vbox_contact,
220        "hbox_presence", &information->hbox_presence,
221        "label_alias", &information->label_alias,
222        "image_state", &information->image_state,
223        "label_status", &information->label_status,
224        "table_contact", &information->table_contact,
225        "vbox_avatar", &information->vbox_avatar,
226        "vbox_location", &information->vbox_location,
227        "subvbox_location", &information->subvbox_location,
228        "label_location", &information->label_location,
229 #if HAVE_LIBCHAMPLAIN
230        "viewport_map", &information->viewport_map,
231 #endif
232        "vbox_groups", &information->vbox_groups,
233        "entry_group", &information->entry_group,
234        "button_group", &information->button_group,
235        "treeview_groups", &information->treeview_groups,
236        "vbox_details", &information->vbox_details,
237        "table_details", &information->table_details,
238        "hbox_details_requested", &information->hbox_details_requested,
239        "vbox_client", &information->vbox_client,
240        "table_client", &information->table_client,
241        "hbox_client_requested", &information->hbox_client_requested,
242        NULL);
243   g_free (filename);
244
245   empathy_builder_connect (gui, information,
246       "vbox_contact_widget", "destroy", contact_widget_destroy_cb,
247       "entry_group", "changed", contact_widget_entry_group_changed_cb,
248       "entry_group", "activate", contact_widget_entry_group_activate_cb,
249       "button_group", "clicked", contact_widget_button_group_clicked_cb,
250       NULL);
251   information->table_location = NULL;
252
253   g_object_set_data (G_OBJECT (information->vbox_contact_widget),
254       "EmpathyContactWidget",
255       information);
256
257   /* Create widgets */
258   contact_widget_contact_setup (information);
259   contact_widget_groups_setup (information);
260   contact_widget_details_setup (information);
261   contact_widget_client_setup (information);
262
263   if (contact != NULL)
264     contact_widget_set_contact (information, contact);
265
266   else if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT ||
267       information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
268     contact_widget_change_contact (information);
269
270   return empathy_builder_unref_and_keep_widget (gui,
271     information->vbox_contact_widget);
272 }
273
274 /**
275  * empathy_contact_widget_get_contact:
276  * @widget: an #EmpathyContactWidget
277  *
278  * Get the #EmpathyContact related with the #EmpathyContactWidget @widget.
279  *
280  * Returns: the #EmpathyContact associated with @widget
281  */
282 EmpathyContact *
283 empathy_contact_widget_get_contact (GtkWidget *widget)
284 {
285   EmpathyContactWidget *information;
286
287   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
288
289   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
290   if (!information)
291       return NULL;
292
293   return information->contact;
294 }
295
296 /**
297  * empathy_contact_widget_set_contact:
298  * @widget: an #EmpathyContactWidget
299  * @contact: a different #EmpathyContact
300  *
301  * Change the #EmpathyContact related with the #EmpathyContactWidget @widget.
302  */
303 void
304 empathy_contact_widget_set_contact (GtkWidget *widget,
305                                     EmpathyContact *contact)
306 {
307   EmpathyContactWidget *information;
308
309   g_return_if_fail (GTK_IS_WIDGET (widget));
310   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
311
312   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
313   if (!information)
314     return;
315
316   contact_widget_set_contact (information, contact);
317 }
318
319 /**
320  * empathy_contact_widget_set_account_filter:
321  * @widget: an #EmpathyContactWidget
322  * @filter: a #EmpathyAccountChooserFilterFunc
323  * @user_data: user data to pass to @filter, or %NULL
324  *
325  * Set a filter on the #EmpathyAccountChooser included in the
326  * #EmpathyContactWidget.
327  */
328 void
329 empathy_contact_widget_set_account_filter (
330     GtkWidget *widget,
331     EmpathyAccountChooserFilterFunc filter,
332     gpointer user_data)
333 {
334   EmpathyContactWidget *information;
335   EmpathyAccountChooser *chooser;
336
337   g_return_if_fail (GTK_IS_WIDGET (widget));
338
339   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
340   if (!information)
341     return;
342
343   chooser = EMPATHY_ACCOUNT_CHOOSER (information->widget_account);
344   if (chooser)
345       empathy_account_chooser_set_filter (chooser, filter, user_data);
346 }
347
348 static void
349 contact_widget_destroy_cb (GtkWidget *widget,
350                            EmpathyContactWidget *information)
351 {
352   contact_widget_remove_contact (information);
353
354   if (information->widget_id_timeout != 0)
355     {
356       g_source_remove (information->widget_id_timeout);
357     }
358   if (information->manager)
359     {
360       g_object_unref (information->manager);
361     }
362
363   g_slice_free (EmpathyContactWidget, information);
364 }
365
366 static void
367 contact_widget_remove_contact (EmpathyContactWidget *information)
368 {
369   if (information->contact)
370     {
371       g_signal_handlers_disconnect_by_func (information->contact,
372           contact_widget_name_notify_cb, information);
373       g_signal_handlers_disconnect_by_func (information->contact,
374           contact_widget_presence_notify_cb, information);
375       g_signal_handlers_disconnect_by_func (information->contact,
376           contact_widget_avatar_notify_cb, information);
377       g_signal_handlers_disconnect_by_func (information->contact,
378           contact_widget_groups_notify_cb, information);
379
380       g_object_unref (information->contact);
381       g_object_unref (information->factory);
382       information->contact = NULL;
383       information->factory = NULL;
384     }
385 }
386
387 static void
388 contact_widget_set_contact (EmpathyContactWidget *information,
389                             EmpathyContact *contact)
390 {
391   if (contact == information->contact)
392     return;
393
394   contact_widget_remove_contact (information);
395   if (contact)
396     {
397       TpConnection *connection;
398
399       connection = empathy_contact_get_connection (contact);
400       information->contact = g_object_ref (contact);
401       information->factory = empathy_tp_contact_factory_dup_singleton (connection);
402     }
403
404   /* Update information for widgets */
405   contact_widget_contact_update (information);
406   contact_widget_groups_update (information);
407   contact_widget_details_update (information);
408   contact_widget_client_update (information);
409   contact_widget_location_update (information);
410 }
411
412 static gboolean
413 contact_widget_id_activate_timeout (EmpathyContactWidget *self)
414 {
415   contact_widget_change_contact (self);
416   return FALSE;
417 }
418
419 static void
420 contact_widget_id_changed_cb (GtkEntry *entry,
421                               EmpathyContactWidget *self)
422 {
423   if (self->widget_id_timeout != 0)
424     {
425       g_source_remove (self->widget_id_timeout);
426     }
427
428   self->widget_id_timeout =
429     g_timeout_add_seconds (ID_CHANGED_TIMEOUT,
430         (GSourceFunc) contact_widget_id_activate_timeout, self);
431 }
432
433 static void
434 save_avatar_menu_activate_cb (GtkWidget *widget,
435                               EmpathyContactWidget *information)
436 {
437   GtkWidget *dialog;
438   EmpathyAvatar *avatar;
439   gchar *ext = NULL, *filename;
440
441   dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
442       NULL,
443       GTK_FILE_CHOOSER_ACTION_SAVE,
444       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
445       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
446       NULL);
447
448   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
449       TRUE);
450
451   /* look for the avatar extension */
452   avatar = empathy_contact_get_avatar (information->contact);
453   if (avatar->format != NULL)
454     {
455       gchar **splitted;
456
457       splitted = g_strsplit (avatar->format, "/", 2);
458       if (splitted[0] != NULL && splitted[1] != NULL)
459           ext = g_strdup (splitted[1]);
460
461       g_strfreev (splitted);
462     }
463   else
464     {
465       /* Avatar was loaded from the cache so was converted to PNG */
466       ext = g_strdup ("png");
467     }
468
469   if (ext != NULL)
470     {
471       gchar *id;
472
473       id = tp_escape_as_identifier (empathy_contact_get_id (
474             information->contact));
475
476       filename = g_strdup_printf ("%s.%s", id, ext);
477       gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
478
479       g_free (id);
480       g_free (ext);
481       g_free (filename);
482     }
483
484   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
485     {
486       GError *error = NULL;
487
488       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
489
490       if (!empathy_avatar_save_to_file (avatar, filename, &error))
491         {
492           /* Save error */
493           GtkWidget *dialog;
494
495           dialog = gtk_message_dialog_new (NULL, 0,
496               GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
497               _("Unable to save avatar"));
498
499           gtk_message_dialog_format_secondary_text (
500               GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
501
502           g_signal_connect (dialog, "response",
503               G_CALLBACK (gtk_widget_destroy), NULL);
504
505           gtk_window_present (GTK_WINDOW (dialog));
506
507           g_clear_error (&error);
508         }
509
510       g_free (filename);
511     }
512
513   gtk_widget_destroy (dialog);
514 }
515
516 static void
517 popup_avatar_menu (EmpathyContactWidget *information,
518                    GtkWidget *parent,
519                    GdkEventButton *event)
520 {
521   GtkWidget *menu, *item;
522   gint button, event_time;
523
524   if (information->contact == NULL ||
525       empathy_contact_get_avatar (information->contact) == NULL)
526       return;
527
528   menu = gtk_menu_new ();
529
530   /* Add "Save as..." entry */
531   item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
532   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
533   gtk_widget_show (item);
534
535   g_signal_connect (item, "activate",
536       G_CALLBACK (save_avatar_menu_activate_cb), information);
537
538   if (event)
539     {
540       button = event->button;
541       event_time = event->time;
542     }
543   else
544     {
545       button = 0;
546       event_time = gtk_get_current_event_time ();
547     }
548
549   gtk_menu_attach_to_widget (GTK_MENU (menu), parent, NULL);
550   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
551       button, event_time);
552 }
553
554 static gboolean
555 widget_avatar_popup_menu_cb (GtkWidget *widget,
556                              EmpathyContactWidget *information)
557 {
558   popup_avatar_menu (information, widget, NULL);
559
560   return TRUE;
561 }
562
563 static gboolean
564 widget_avatar_button_press_event_cb (GtkWidget *widget,
565                                      GdkEventButton *event,
566                                      EmpathyContactWidget *information)
567 {
568   /* Ignore double-clicks and triple-clicks */
569   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
570     {
571       popup_avatar_menu (information, widget, event);
572       return TRUE;
573     }
574
575   return FALSE;
576 }
577
578 static void
579 update_avatar_chooser_account_cb (EmpathyAccountChooser *account_chooser,
580                                   EmpathyAvatarChooser *avatar_chooser)
581 {
582   TpConnection *connection;
583
584   connection = empathy_account_chooser_get_connection (account_chooser);
585   g_object_set (avatar_chooser, "connection", connection, NULL);
586 }
587
588 static void
589 contact_widget_contact_setup (EmpathyContactWidget *information)
590 {
591   /* Setup account label/chooser */
592   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
593     {
594       information->widget_account = empathy_account_chooser_new ();
595
596       g_signal_connect_swapped (information->widget_account, "changed",
597             G_CALLBACK (contact_widget_change_contact),
598             information);
599     }
600   else
601     {
602       information->widget_account = gtk_label_new (NULL);
603       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
604         gtk_label_set_selectable (GTK_LABEL (information->widget_account), TRUE);
605       }
606       gtk_misc_set_alignment (GTK_MISC (information->widget_account), 0, 0.5);
607     }
608   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
609            information->widget_account,
610            1, 2, 0, 1);
611   gtk_widget_show (information->widget_account);
612
613   /* Set up avatar chooser/display */
614   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
615     {
616       information->widget_avatar = empathy_avatar_chooser_new ();
617       g_signal_connect (information->widget_avatar, "changed",
618             G_CALLBACK (contact_widget_avatar_changed_cb),
619             information);
620       if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
621         {
622           g_signal_connect (information->widget_account, "changed",
623               G_CALLBACK (update_avatar_chooser_account_cb),
624               information->widget_avatar);
625           update_avatar_chooser_account_cb (
626               EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
627               EMPATHY_AVATAR_CHOOSER (information->widget_avatar));
628         }
629     }
630   else
631     {
632       information->widget_avatar = empathy_avatar_image_new ();
633
634       g_signal_connect (information->widget_avatar, "popup-menu",
635           G_CALLBACK (widget_avatar_popup_menu_cb), information);
636       g_signal_connect (information->widget_avatar, "button-press-event",
637           G_CALLBACK (widget_avatar_button_press_event_cb), information);
638     }
639
640   gtk_box_pack_start (GTK_BOX (information->vbox_avatar),
641           information->widget_avatar,
642           FALSE, FALSE,
643           6);
644   gtk_widget_show (information->widget_avatar);
645
646   /* Setup id label/entry */
647   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
648     {
649       information->widget_id = gtk_entry_new ();
650       g_signal_connect (information->widget_id, "focus-out-event",
651             G_CALLBACK (contact_widget_id_focus_out_cb),
652             information);
653       g_signal_connect (information->widget_id, "changed",
654             G_CALLBACK (contact_widget_id_changed_cb),
655             information);
656     }
657   else
658     {
659       information->widget_id = gtk_label_new (NULL);
660       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
661         gtk_label_set_selectable (GTK_LABEL (information->widget_id), TRUE);
662       }
663       gtk_misc_set_alignment (GTK_MISC (information->widget_id), 0, 0.5);
664     }
665   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
666            information->widget_id,
667            1, 2, 1, 2);
668   gtk_widget_show (information->widget_id);
669
670   /* Setup alias label/entry */
671   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ALIAS)
672     {
673       information->widget_alias = gtk_entry_new ();
674       g_signal_connect (information->widget_alias, "focus-out-event",
675             G_CALLBACK (contact_widget_entry_alias_focus_event_cb),
676             information);
677       /* Make return activate the window default (the Close button) */
678       gtk_entry_set_activates_default (GTK_ENTRY (information->widget_alias),
679           TRUE);
680     }
681   else
682     {
683       information->widget_alias = gtk_label_new (NULL);
684       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
685         gtk_label_set_selectable (GTK_LABEL (information->widget_alias), TRUE);
686       }
687       gtk_misc_set_alignment (GTK_MISC (information->widget_alias), 0, 0.5);
688     }
689   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
690            information->widget_alias,
691            1, 2, 2, 3);
692   if (information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) {
693     gtk_label_set_selectable (GTK_LABEL (information->label_status), FALSE);
694   }
695   gtk_widget_show (information->widget_alias);
696 }
697
698 static void
699 contact_widget_contact_update (EmpathyContactWidget *information)
700 {
701   McAccount *account = NULL;
702   const gchar *id = NULL;
703
704   /* Connect and get info from new contact */
705   if (information->contact)
706     {
707       g_signal_connect_swapped (information->contact, "notify::name",
708           G_CALLBACK (contact_widget_name_notify_cb), information);
709       g_signal_connect_swapped (information->contact, "notify::presence",
710           G_CALLBACK (contact_widget_presence_notify_cb), information);
711       g_signal_connect_swapped (information->contact,
712           "notify::presence-message",
713           G_CALLBACK (contact_widget_presence_notify_cb), information);
714       g_signal_connect_swapped (information->contact, "notify::avatar",
715           G_CALLBACK (contact_widget_avatar_notify_cb), information);
716
717       account = empathy_contact_get_account (information->contact);
718       id = empathy_contact_get_id (information->contact);
719     }
720
721   /* Update account widget */
722   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
723     {
724       if (account)
725         {
726           g_signal_handlers_block_by_func (information->widget_account,
727                    contact_widget_change_contact,
728                    information);
729           empathy_account_chooser_set_account (
730               EMPATHY_ACCOUNT_CHOOSER (information->widget_account), account);
731           g_signal_handlers_unblock_by_func (information->widget_account,
732               contact_widget_change_contact, information);
733         }
734     }
735   else
736     {
737       if (account)
738         {
739           const gchar *name;
740
741           name = mc_account_get_display_name (account);
742           gtk_label_set_label (GTK_LABEL (information->widget_account), name);
743         }
744     }
745
746   /* Update id widget */
747   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
748       gtk_entry_set_text (GTK_ENTRY (information->widget_id), id ? id : "");
749   else
750       gtk_label_set_label (GTK_LABEL (information->widget_id), id ? id : "");
751
752   /* Update other widgets */
753   if (information->contact)
754     {
755       contact_widget_name_notify_cb (information);
756       contact_widget_presence_notify_cb (information);
757       contact_widget_avatar_notify_cb (information);
758
759       gtk_widget_show (information->label_alias);
760       gtk_widget_show (information->widget_alias);
761       gtk_widget_show (information->hbox_presence);
762       gtk_widget_show (information->widget_avatar);
763     }
764   else
765     {
766       gtk_widget_hide (information->label_alias);
767       gtk_widget_hide (information->widget_alias);
768       gtk_widget_hide (information->hbox_presence);
769       gtk_widget_hide (information->widget_avatar);
770     }
771 }
772
773 static void
774 contact_widget_got_contact_cb (EmpathyTpContactFactory *factory,
775                                EmpathyContact *contact,
776                                const GError *error,
777                                gpointer user_data,
778                                GObject *weak_object)
779 {
780   EmpathyContactWidget *information = user_data;
781
782   if (error != NULL)
783     {
784       DEBUG ("Error: %s", error->message);
785       return;
786     }
787
788   contact_widget_set_contact (information, contact);
789 }
790
791 static void
792 contact_widget_change_contact (EmpathyContactWidget *information)
793 {
794   EmpathyTpContactFactory *factory;
795   TpConnection *connection;
796
797   connection = empathy_account_chooser_get_connection (
798       EMPATHY_ACCOUNT_CHOOSER (information->widget_account));
799   if (!connection)
800       return;
801
802   factory = empathy_tp_contact_factory_dup_singleton (connection);
803   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
804     {
805       const gchar *id;
806
807       id = gtk_entry_get_text (GTK_ENTRY (information->widget_id));
808       if (!EMP_STR_EMPTY (id))
809         {
810           empathy_tp_contact_factory_get_from_id (factory, id,
811               contact_widget_got_contact_cb, information, NULL,
812               G_OBJECT (information->vbox_contact_widget));
813         }
814     }
815   else
816     {
817       empathy_tp_contact_factory_get_from_handle (factory,
818           tp_connection_get_self_handle (connection),
819           contact_widget_got_contact_cb, information, NULL,
820           G_OBJECT (information->vbox_contact_widget));
821     }
822
823   g_object_unref (factory);
824 }
825
826 static void
827 contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
828                                   EmpathyContactWidget *information)
829 {
830   const gchar *data;
831   gsize size;
832   const gchar *mime_type;
833
834   empathy_avatar_chooser_get_image_data (
835       EMPATHY_AVATAR_CHOOSER (information->widget_avatar),
836       &data, &size, &mime_type);
837   empathy_tp_contact_factory_set_avatar (information->factory,
838       data, size, mime_type);
839 }
840
841 static gboolean
842 contact_widget_id_focus_out_cb (GtkWidget *widget,
843                                 GdkEventFocus *event,
844                                 EmpathyContactWidget *information)
845 {
846   contact_widget_change_contact (information);
847   return FALSE;
848 }
849
850 static gboolean
851 contact_widget_entry_alias_focus_event_cb (GtkEditable *editable,
852                                            GdkEventFocus *event,
853                                            EmpathyContactWidget *information)
854 {
855   if (information->contact)
856     {
857       const gchar *alias;
858
859       alias = gtk_entry_get_text (GTK_ENTRY (editable));
860       empathy_tp_contact_factory_set_alias (information->factory,
861           information->contact, alias);
862     }
863
864   return FALSE;
865 }
866
867 static void
868 contact_widget_name_notify_cb (EmpathyContactWidget *information)
869 {
870   if (GTK_IS_ENTRY (information->widget_alias))
871       gtk_entry_set_text (GTK_ENTRY (information->widget_alias),
872           empathy_contact_get_name (information->contact));
873   else
874       gtk_label_set_label (GTK_LABEL (information->widget_alias),
875           empathy_contact_get_name (information->contact));
876 }
877
878 static void
879 contact_widget_presence_notify_cb (EmpathyContactWidget *information)
880 {
881   gtk_label_set_text (GTK_LABEL (information->label_status),
882       empathy_contact_get_status (information->contact));
883   gtk_image_set_from_icon_name (GTK_IMAGE (information->image_state),
884       empathy_icon_name_for_contact (information->contact),
885       GTK_ICON_SIZE_BUTTON);
886 }
887
888 static void
889 contact_widget_avatar_notify_cb (EmpathyContactWidget *information)
890 {
891   EmpathyAvatar *avatar = NULL;
892
893   if (information->contact)
894       avatar = empathy_contact_get_avatar (information->contact);
895
896   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
897     {
898       g_signal_handlers_block_by_func (information->widget_avatar,
899           contact_widget_avatar_changed_cb,
900           information);
901       empathy_avatar_chooser_set (
902           EMPATHY_AVATAR_CHOOSER (information->widget_avatar), avatar);
903       g_signal_handlers_unblock_by_func (information->widget_avatar,
904           contact_widget_avatar_changed_cb, information);
905     }
906   else
907       empathy_avatar_image_set (
908           EMPATHY_AVATAR_IMAGE (information->widget_avatar), avatar);
909 }
910
911 static void
912 contact_widget_groups_setup (EmpathyContactWidget *information)
913 {
914   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS)
915     {
916       information->manager = empathy_contact_manager_dup_singleton ();
917       contact_widget_model_setup (information);
918     }
919 }
920
921 static void
922 contact_widget_groups_update (EmpathyContactWidget *information)
923 {
924   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS &&
925       information->contact)
926     {
927       g_signal_connect_swapped (information->contact, "notify::groups",
928           G_CALLBACK (contact_widget_groups_notify_cb), information);
929       contact_widget_groups_populate_data (information);
930
931       gtk_widget_show (information->vbox_groups);
932     }
933   else
934       gtk_widget_hide (information->vbox_groups);
935 }
936
937 static void
938 contact_widget_model_setup (EmpathyContactWidget *information)
939 {
940   GtkTreeView *view;
941   GtkListStore *store;
942   GtkTreeSelection *selection;
943
944   view = GTK_TREE_VIEW (information->treeview_groups);
945
946   store = gtk_list_store_new (COL_COUNT,
947       G_TYPE_STRING,   /* name */
948       G_TYPE_BOOLEAN,  /* enabled */
949       G_TYPE_BOOLEAN); /* editable */
950
951   gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
952
953   selection = gtk_tree_view_get_selection (view);
954   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
955
956   contact_widget_model_populate_columns (information);
957
958   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
959       COL_NAME, GTK_SORT_ASCENDING);
960
961   g_object_unref (store);
962 }
963
964 static void
965 contact_widget_model_populate_columns (EmpathyContactWidget *information)
966 {
967   GtkTreeView *view;
968   GtkTreeModel *model;
969   GtkTreeViewColumn *column;
970   GtkCellRenderer  *renderer;
971   guint col_offset;
972
973   view = GTK_TREE_VIEW (information->treeview_groups);
974   model = gtk_tree_view_get_model (view);
975
976   renderer = gtk_cell_renderer_toggle_new ();
977   g_signal_connect (renderer, "toggled",
978       G_CALLBACK (contact_widget_cell_toggled), information);
979
980   column = gtk_tree_view_column_new_with_attributes (_("Select"), renderer,
981       "active", COL_ENABLED, NULL);
982
983   gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
984   gtk_tree_view_column_set_fixed_width (column, 50);
985   gtk_tree_view_append_column (view, column);
986
987   renderer = gtk_cell_renderer_text_new ();
988   col_offset = gtk_tree_view_insert_column_with_attributes (view,
989       -1, _("Group"),
990       renderer,
991       "text", COL_NAME,
992       /* "editable", COL_EDITABLE, */
993       NULL);
994
995   g_object_set_data (G_OBJECT (renderer),
996       "column", GINT_TO_POINTER (COL_NAME));
997
998   column = gtk_tree_view_get_column (view, col_offset - 1);
999   gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
1000   gtk_tree_view_column_set_resizable (column,FALSE);
1001   gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
1002 }
1003
1004 static void
1005 contact_widget_groups_populate_data (EmpathyContactWidget *information)
1006 {
1007   GtkTreeView *view;
1008   GtkListStore *store;
1009   GtkTreeIter iter;
1010   GList *my_groups, *l;
1011   GList *all_groups;
1012
1013   view = GTK_TREE_VIEW (information->treeview_groups);
1014   store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
1015   gtk_list_store_clear (store);
1016
1017   all_groups = empathy_contact_list_get_all_groups (
1018       EMPATHY_CONTACT_LIST (information->manager));
1019   my_groups = empathy_contact_list_get_groups (
1020       EMPATHY_CONTACT_LIST (information->manager),
1021       information->contact);
1022
1023   for (l = all_groups; l; l = l->next)
1024     {
1025       const gchar *group_str;
1026       gboolean enabled;
1027
1028       group_str = l->data;
1029
1030       enabled = g_list_find_custom (my_groups,
1031           group_str, (GCompareFunc) strcmp) != NULL;
1032
1033       gtk_list_store_append (store, &iter);
1034       gtk_list_store_set (store, &iter,
1035           COL_NAME, group_str,
1036           COL_EDITABLE, TRUE,
1037           COL_ENABLED, enabled,
1038           -1);
1039     }
1040
1041   g_list_foreach (all_groups, (GFunc) g_free, NULL);
1042   g_list_foreach (my_groups, (GFunc) g_free, NULL);
1043   g_list_free (all_groups);
1044   g_list_free (my_groups);
1045 }
1046
1047 static void
1048 contact_widget_groups_notify_cb (EmpathyContactWidget *information)
1049 {
1050   /* FIXME: not implemented */
1051 }
1052
1053 static gboolean
1054 contact_widget_model_find_name (EmpathyContactWidget *information,
1055                                 const gchar *name,
1056                                 GtkTreeIter *iter)
1057 {
1058   GtkTreeView *view;
1059   GtkTreeModel *model;
1060   FindName data;
1061
1062   if (EMP_STR_EMPTY (name))
1063       return FALSE;
1064
1065   data.information = information;
1066   data.name = name;
1067   data.found = FALSE;
1068
1069   view = GTK_TREE_VIEW (information->treeview_groups);
1070   model = gtk_tree_view_get_model (view);
1071
1072   gtk_tree_model_foreach (model,
1073       (GtkTreeModelForeachFunc) contact_widget_model_find_name_foreach,
1074       &data);
1075
1076   if (data.found == TRUE)
1077     {
1078       *iter = data.found_iter;
1079       return TRUE;
1080     }
1081
1082   return FALSE;
1083 }
1084
1085 static gboolean
1086 contact_widget_model_find_name_foreach (GtkTreeModel *model,
1087                                         GtkTreePath *path,
1088                                         GtkTreeIter *iter,
1089                                         FindName *data)
1090 {
1091   gchar *name;
1092
1093   gtk_tree_model_get (model, iter,
1094       COL_NAME, &name,
1095       -1);
1096
1097   if (!name)
1098       return FALSE;
1099
1100   if (data->name && strcmp (data->name, name) == 0)
1101     {
1102       data->found = TRUE;
1103       data->found_iter = *iter;
1104
1105       g_free (name);
1106
1107       return TRUE;
1108     }
1109
1110   g_free (name);
1111
1112   return FALSE;
1113 }
1114
1115 static void
1116 contact_widget_cell_toggled (GtkCellRendererToggle *cell,
1117                              gchar *path_string,
1118                              EmpathyContactWidget *information)
1119 {
1120   GtkTreeView *view;
1121   GtkTreeModel *model;
1122   GtkListStore *store;
1123   GtkTreePath *path;
1124   GtkTreeIter iter;
1125   gboolean enabled;
1126   gchar *group;
1127
1128   view = GTK_TREE_VIEW (information->treeview_groups);
1129   model = gtk_tree_view_get_model (view);
1130   store = GTK_LIST_STORE (model);
1131
1132   path = gtk_tree_path_new_from_string (path_string);
1133
1134   gtk_tree_model_get_iter (model, &iter, path);
1135   gtk_tree_model_get (model, &iter,
1136       COL_ENABLED, &enabled,
1137       COL_NAME, &group,
1138       -1);
1139
1140   gtk_list_store_set (store, &iter, COL_ENABLED, !enabled, -1);
1141   gtk_tree_path_free (path);
1142
1143   if (group)
1144     {
1145       if (enabled)
1146         {
1147           empathy_contact_list_remove_from_group (
1148               EMPATHY_CONTACT_LIST (information->manager), information->contact,
1149               group);
1150         }
1151       else
1152         {
1153           empathy_contact_list_add_to_group (
1154               EMPATHY_CONTACT_LIST (information->manager), information->contact,
1155               group);
1156         }
1157       g_free (group);
1158     }
1159 }
1160
1161 static void
1162 contact_widget_entry_group_changed_cb (GtkEditable *editable,
1163                                        EmpathyContactWidget *information)
1164 {
1165   GtkTreeIter iter;
1166   const gchar *group;
1167
1168   group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
1169
1170   if (contact_widget_model_find_name (information, group, &iter))
1171       gtk_widget_set_sensitive (GTK_WIDGET (information->button_group), FALSE);
1172   else
1173       gtk_widget_set_sensitive (GTK_WIDGET (information->button_group),
1174           !EMP_STR_EMPTY (group));
1175 }
1176
1177 static void
1178 contact_widget_entry_group_activate_cb (GtkEntry *entry,
1179                                         EmpathyContactWidget  *information)
1180 {
1181   gtk_widget_activate (GTK_WIDGET (information->button_group));
1182 }
1183
1184 static void
1185 contact_widget_button_group_clicked_cb (GtkButton *button,
1186                                         EmpathyContactWidget *information)
1187 {
1188   GtkTreeView *view;
1189   GtkListStore *store;
1190   GtkTreeIter iter;
1191   const gchar *group;
1192
1193   view = GTK_TREE_VIEW (information->treeview_groups);
1194   store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
1195
1196   group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
1197
1198   gtk_list_store_append (store, &iter);
1199   gtk_list_store_set (store, &iter,
1200       COL_NAME, group,
1201       COL_ENABLED, TRUE,
1202       -1);
1203
1204   empathy_contact_list_add_to_group (
1205       EMPATHY_CONTACT_LIST (information->manager), information->contact,
1206       group);
1207 }
1208
1209 static void
1210 contact_widget_details_setup (EmpathyContactWidget *information)
1211 {
1212   /* FIXME: Needs new telepathy spec */
1213   gtk_widget_hide (information->vbox_details);
1214 }
1215
1216 static void
1217 contact_widget_details_update (EmpathyContactWidget *information)
1218 {
1219   /* FIXME: Needs new telepathy spec */
1220 }
1221
1222 static void
1223 contact_widget_client_setup (EmpathyContactWidget *information)
1224 {
1225   /* FIXME: Needs new telepathy spec */
1226   gtk_widget_hide (information->vbox_client);
1227 }
1228
1229 static void
1230 contact_widget_client_update (EmpathyContactWidget *information)
1231 {
1232   /* FIXME: Needs new telepathy spec */
1233 }
1234
1235 static void
1236 contact_widget_location_update (EmpathyContactWidget *information)
1237 {
1238   GHashTable *location;
1239   GValue *value;
1240   gdouble lat, lon;
1241
1242   location = empathy_contact_get_location (information->contact);
1243   if (location == NULL)
1244     {
1245       gtk_widget_hide (information->vbox_location);
1246       return;
1247     }
1248
1249   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LAT);
1250   if (value == NULL)
1251     {
1252       gtk_widget_hide (information->vbox_location);
1253       return;
1254     }
1255   lat = g_value_get_double (value);
1256
1257   value = g_hash_table_lookup (location, EMPATHY_LOCATION_LON);
1258   if (value == NULL)
1259     {
1260       gtk_widget_hide (information->vbox_location);
1261       return;
1262     }
1263   lon = g_value_get_double (value);
1264
1265   value = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
1266   if (value == NULL)
1267     gtk_label_set_markup (GTK_LABEL (information->label_location), _("<b>Location</b>"));
1268   else
1269     {
1270       gchar user_date [100];
1271       gint64 stamp;
1272       struct tm *ptm;
1273       time_t time;
1274
1275       stamp = g_value_get_int64 (value);
1276       time = stamp;
1277       ptm = gmtime (&time);
1278
1279       if (strftime (user_date, 100, _("%B %e, %Y at %R UTC"), ptm) > 0)
1280         {
1281           gchar *text;
1282           text = g_strconcat ( _("<b>Location</b> on "), user_date, NULL);
1283           gtk_label_set_markup (GTK_LABEL (information->label_location), text);
1284           g_free (text);
1285         }
1286       else
1287         gtk_label_set_markup (GTK_LABEL (information->label_location), _("<b>Location</b>"));
1288     }
1289
1290   if (information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP ||
1291       information->flags & EMPATHY_CONTACT_WIDGET_SHOW_LOCATION)
1292     {
1293       GtkWidget *label;
1294       guint row = 0;
1295       GHashTableIter iter;
1296       gpointer key, value;
1297
1298       if (information->table_location != NULL)
1299         {
1300           gtk_widget_destroy (information->table_location);
1301         }
1302
1303       information->table_location = gtk_table_new (1, 2, FALSE);
1304       gtk_box_pack_start (GTK_BOX (information->subvbox_location),
1305           information->table_location, FALSE, FALSE, 5);
1306
1307       g_hash_table_iter_init (&iter, location);
1308       while (g_hash_table_iter_next (&iter, &key, &value))
1309         {
1310           const gchar *skey;
1311           GValue *gvalue;
1312           char *svalue = NULL;
1313
1314           skey = (const gchar *) key;
1315           gvalue = (GValue*) value;
1316
1317           label = gtk_label_new (_(skey));
1318           gtk_table_attach_defaults (GTK_TABLE (information->table_location),
1319               label, 0, 1, row, row + 1);
1320           gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1321           gtk_widget_show (label);
1322
1323           if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE)
1324             {
1325               gdouble dvalue;
1326               dvalue = g_value_get_double (gvalue);
1327               svalue = g_strdup_printf ("%f", dvalue);
1328             }
1329           else if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
1330             {
1331               svalue = g_value_dup_string (gvalue);
1332             }
1333
1334           if (svalue != NULL)
1335             {
1336               label = gtk_label_new (svalue);
1337               gtk_table_attach_defaults (GTK_TABLE (information->table_location),
1338                   label, 1, 2, row, row + 1);
1339               gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1340               gtk_widget_show (label);
1341             }
1342
1343           g_free (svalue);
1344           row++;
1345         }
1346
1347       gtk_widget_show (information->table_location);
1348     }
1349
1350   if (/* information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP || */
1351       information->flags & EMPATHY_CONTACT_WIDGET_SHOW_LOCATION)
1352     {
1353       ClutterActor *marker;
1354       ChamplainLayer *layer;
1355
1356       information->map_view = champlain_view_new ();
1357       information->map_view_embed = champlain_view_embed_new (
1358           CHAMPLAIN_VIEW (information->map_view));
1359
1360       gtk_container_add (GTK_CONTAINER (information->viewport_map),
1361           information->map_view_embed);
1362       g_object_set (G_OBJECT (information->map_view), "show-license", FALSE,
1363           "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC,
1364           NULL);
1365
1366       layer = champlain_layer_new ();
1367       champlain_view_add_layer (CHAMPLAIN_VIEW (information->map_view), layer);
1368
1369       marker = champlain_marker_new_with_text (
1370           empathy_contact_get_name (information->contact), NULL, NULL, NULL);
1371       champlain_base_marker_set_position (CHAMPLAIN_BASE_MARKER (marker), lat, lon);
1372       clutter_container_add (CLUTTER_CONTAINER (layer), marker, NULL);
1373
1374       champlain_view_center_on (CHAMPLAIN_VIEW(information->map_view), lat, lon);
1375     }
1376
1377     gtk_widget_show (information->vbox_location);
1378 }