]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-widget.c
Use empathy_contact_call_when_ready instead of _run_ API. Fixes bug #561247 and
[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-2008 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 #include <string.h>
25 #include <stdlib.h>
26
27 #include <gtk/gtk.h>
28 #include <glade/glade.h>
29 #include <glib/gi18n-lib.h>
30
31 #include <libmissioncontrol/mc-account.h>
32 #include <telepathy-glib/util.h>
33
34 #include <libempathy/empathy-contact-factory.h>
35 #include <libempathy/empathy-contact-manager.h>
36 #include <libempathy/empathy-contact-list.h>
37 #include <libempathy/empathy-utils.h>
38
39 #include "empathy-contact-widget.h"
40 #include "empathy-account-chooser.h"
41 #include "empathy-avatar-chooser.h"
42 #include "empathy-avatar-image.h"
43 #include "empathy-ui-utils.h"
44
45 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
46 #include <libempathy/empathy-debug.h>
47
48 /* Delay before updating the widget when the id entry changed (seconds) */
49 #define ID_CHANGED_TIMEOUT 1
50
51 typedef struct
52 {
53   EmpathyContactFactory *factory;
54   EmpathyContactManager *manager;
55   EmpathyContact *contact;
56   EmpathyContactWidgetFlags flags;
57   GtkCellRenderer *renderer;
58   guint widget_id_timeout;
59
60   GtkWidget *vbox_contact_widget;
61
62   /* Contact */
63   GtkWidget *vbox_contact;
64   GtkWidget *widget_avatar;
65   GtkWidget *widget_account;
66   GtkWidget *widget_id;
67   GtkWidget *widget_alias;
68   GtkWidget *label_alias;
69   GtkWidget *entry_alias;
70   GtkWidget *hbox_presence;
71   GtkWidget *image_state;
72   GtkWidget *label_status;
73   GtkWidget *table_contact;
74   GtkWidget *vbox_avatar;
75
76   /* Groups */
77   GtkWidget *vbox_groups;
78   GtkWidget *entry_group;
79   GtkWidget *button_group;
80   GtkWidget *treeview_groups;
81
82   /* Details */
83   GtkWidget *vbox_details;
84   GtkWidget *table_details;
85   GtkWidget *hbox_details_requested;
86
87   /* Client */
88   GtkWidget *vbox_client;
89   GtkWidget *table_client;
90   GtkWidget *hbox_client_requested;
91 } EmpathyContactWidget;
92
93 typedef struct
94 {
95   EmpathyContactWidget *information;
96   const gchar *name;
97   gboolean found;
98   GtkTreeIter found_iter;
99 } FindName;
100
101 static void contact_widget_destroy_cb (GtkWidget *widget,
102     EmpathyContactWidget *information);
103 static void contact_widget_remove_contact (EmpathyContactWidget *information);
104 static void contact_widget_set_contact (EmpathyContactWidget *information,
105     EmpathyContact *contact);
106 static void contact_widget_contact_setup (EmpathyContactWidget *information);
107 static void contact_widget_contact_update (EmpathyContactWidget *information);
108 static void contact_widget_change_contact (EmpathyContactWidget *information);
109 static void contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
110     EmpathyContactWidget *information);
111 static void contact_widget_account_changed_cb (GtkComboBox *widget,
112     EmpathyContactWidget *information);
113 static gboolean contact_widget_id_focus_out_cb (GtkWidget *widget,
114     GdkEventFocus *event, EmpathyContactWidget *information);
115 static gboolean contact_widget_entry_alias_focus_event_cb (
116     GtkEditable *editable, GdkEventFocus *event,
117     EmpathyContactWidget *information);
118 static void contact_widget_name_notify_cb (EmpathyContactWidget *information);
119 static void contact_widget_presence_notify_cb (
120     EmpathyContactWidget *information);
121 static void contact_widget_avatar_notify_cb (
122     EmpathyContactWidget *information);
123 static void contact_widget_groups_setup (
124     EmpathyContactWidget *information);
125 static void contact_widget_groups_update (EmpathyContactWidget *information);
126 static void contact_widget_model_setup (EmpathyContactWidget *information);
127 static void contact_widget_model_populate_columns (
128     EmpathyContactWidget *information);
129 static void contact_widget_groups_populate_data (
130     EmpathyContactWidget *information);
131 static void contact_widget_groups_notify_cb (
132     EmpathyContactWidget *information);
133 static gboolean contact_widget_model_find_name (
134     EmpathyContactWidget *information,const gchar *name, GtkTreeIter *iter);
135 static gboolean contact_widget_model_find_name_foreach (GtkTreeModel *model,
136     GtkTreePath *path, GtkTreeIter *iter, FindName *data);
137 static void contact_widget_cell_toggled (GtkCellRendererToggle *cell,
138     gchar *path_string, EmpathyContactWidget *information);
139 static void contact_widget_entry_group_changed_cb (GtkEditable *editable,
140     EmpathyContactWidget *information);
141 static void contact_widget_entry_group_activate_cb (GtkEntry *entry,
142     EmpathyContactWidget *information);
143 static void contact_widget_button_group_clicked_cb (GtkButton *button,
144     EmpathyContactWidget *information);
145 static void contact_widget_details_setup (EmpathyContactWidget *information);
146 static void contact_widget_details_update (EmpathyContactWidget *information);
147 static void contact_widget_client_setup (EmpathyContactWidget *information);
148 static void contact_widget_client_update (EmpathyContactWidget *information);
149
150 enum
151 {
152   COL_NAME,
153   COL_ENABLED,
154   COL_EDITABLE,
155   COL_COUNT
156 };
157
158 GtkWidget *
159 empathy_contact_widget_new (EmpathyContact *contact,
160                             EmpathyContactWidgetFlags flags)
161 {
162   EmpathyContactWidget *information;
163   GladeXML *glade;
164   gchar *filename;
165
166   information = g_slice_new0 (EmpathyContactWidget);
167   information->flags = flags;
168   information->factory = empathy_contact_factory_dup_singleton ();
169
170   filename = empathy_file_lookup ("empathy-contact-widget.glade",
171       "libempathy-gtk");
172   glade = empathy_glade_get_file (filename,
173       "vbox_contact_widget",
174        NULL,
175        "vbox_contact_widget", &information->vbox_contact_widget,
176        "vbox_contact", &information->vbox_contact,
177        "hbox_presence", &information->hbox_presence,
178        "label_alias", &information->label_alias,
179        "image_state", &information->image_state,
180        "label_status", &information->label_status,
181        "table_contact", &information->table_contact,
182        "vbox_avatar", &information->vbox_avatar,
183        "vbox_groups", &information->vbox_groups,
184        "entry_group", &information->entry_group,
185        "button_group", &information->button_group,
186        "treeview_groups", &information->treeview_groups,
187        "vbox_details", &information->vbox_details,
188        "table_details", &information->table_details,
189        "hbox_details_requested", &information->hbox_details_requested,
190        "vbox_client", &information->vbox_client,
191        "table_client", &information->table_client,
192        "hbox_client_requested", &information->hbox_client_requested,
193        NULL);
194   g_free (filename);
195
196   empathy_glade_connect (glade,
197       information,
198       "vbox_contact_widget", "destroy", contact_widget_destroy_cb,
199       "entry_group", "changed", contact_widget_entry_group_changed_cb,
200       "entry_group", "activate", contact_widget_entry_group_activate_cb,
201       "button_group", "clicked", contact_widget_button_group_clicked_cb,
202       NULL);
203
204   g_object_unref (glade);
205
206   g_object_set_data (G_OBJECT (information->vbox_contact_widget),
207       "EmpathyContactWidget",
208       information);
209
210   /* Create widgets */
211   contact_widget_contact_setup (information);
212   contact_widget_groups_setup (information);
213   contact_widget_details_setup (information);
214   contact_widget_client_setup (information);
215
216   contact_widget_set_contact (information, contact);
217
218   gtk_widget_show (information->vbox_contact_widget);
219
220   return information->vbox_contact_widget;
221 }
222
223 EmpathyContact *
224 empathy_contact_widget_get_contact (GtkWidget *widget)
225 {
226   EmpathyContactWidget *information;
227
228   g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
229
230   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
231   if (!information)
232       return NULL;
233
234   return information->contact;
235 }
236
237 void
238 empathy_contact_widget_set_contact (GtkWidget *widget,
239                                     EmpathyContact *contact)
240 {
241   EmpathyContactWidget *information;
242
243   g_return_if_fail (GTK_IS_WIDGET (widget));
244   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
245
246   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
247   if (!information)
248     return;
249
250   contact_widget_set_contact (information, contact);
251 }
252
253 void
254 empathy_contact_widget_set_account_filter (
255     GtkWidget *widget,
256     EmpathyAccountChooserFilterFunc filter,
257     gpointer user_data)
258 {
259   EmpathyContactWidget *information;
260   EmpathyAccountChooser *chooser;
261
262   g_return_if_fail (GTK_IS_WIDGET (widget));
263
264   information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
265   if (!information)
266     return;
267
268   chooser = EMPATHY_ACCOUNT_CHOOSER (information->widget_account);
269   if (chooser)
270       empathy_account_chooser_set_filter (chooser, filter, user_data);
271 }
272   
273 static void
274 contact_widget_destroy_cb (GtkWidget *widget,
275                            EmpathyContactWidget *information)
276 {
277   contact_widget_remove_contact (information);
278
279   if (information->widget_id_timeout != 0)
280     {
281       g_source_remove (information->widget_id_timeout);
282     }
283   if (information->factory)
284     {
285       g_object_unref (information->factory);
286     }   
287   if (information->manager)
288     {
289       g_object_unref (information->manager);
290     }   
291
292   g_slice_free (EmpathyContactWidget, information);
293 }
294
295 static void
296 contact_widget_remove_contact (EmpathyContactWidget *information)
297 {
298   if (information->contact)
299     {
300       g_signal_handlers_disconnect_by_func (information->contact,
301           contact_widget_name_notify_cb, information);
302       g_signal_handlers_disconnect_by_func (information->contact,
303           contact_widget_presence_notify_cb, information);
304       g_signal_handlers_disconnect_by_func (information->contact,
305           contact_widget_avatar_notify_cb, information);
306       g_signal_handlers_disconnect_by_func (information->contact,
307           contact_widget_groups_notify_cb, information);
308
309       g_object_unref (information->contact);
310       information->contact = NULL;
311     }
312 }
313
314 static void
315 contact_widget_set_contact (EmpathyContactWidget *information,
316                             EmpathyContact *contact)
317 {
318   if (contact == information->contact)
319     return;
320
321   contact_widget_remove_contact (information);
322   if (contact)
323       information->contact = g_object_ref (contact);
324
325   /* Update information for widgets */
326   contact_widget_contact_update (information);
327   contact_widget_groups_update (information);
328   contact_widget_details_update (information);
329   contact_widget_client_update (information);
330 }
331
332 static gboolean
333 contact_widget_id_activate_timeout (EmpathyContactWidget *self)
334 {
335   contact_widget_change_contact (self);
336   return FALSE;
337 }
338
339 static void
340 contact_widget_id_changed_cb (GtkEntry *entry,
341                               EmpathyContactWidget *self)
342 {
343   if (self->widget_id_timeout != 0)
344     {   
345       g_source_remove (self->widget_id_timeout);
346     }
347
348   self->widget_id_timeout =
349     g_timeout_add_seconds (ID_CHANGED_TIMEOUT,
350         (GSourceFunc) contact_widget_id_activate_timeout, self);
351 }
352
353 static void
354 save_avatar_menu_activate_cb (GtkWidget *widget,
355                               EmpathyContactWidget *information)
356 {
357   GtkWidget *dialog;
358   EmpathyAvatar *avatar;
359   gchar *ext = NULL, *filename;
360
361   dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
362       NULL,
363       GTK_FILE_CHOOSER_ACTION_SAVE,
364       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
365       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
366       NULL);
367
368   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
369       TRUE);
370
371   /* look for the avatar extension */
372   avatar = empathy_contact_get_avatar (information->contact);
373   if (avatar->format != NULL)
374     {
375       gchar **splitted;
376
377       splitted = g_strsplit (avatar->format, "/", 2);
378       if (splitted[0] != NULL && splitted[1] != NULL)
379           ext = g_strdup (splitted[1]);
380
381       g_strfreev (splitted);
382     }
383   else
384     {
385       /* Avatar was loaded from the cache so was converted to PNG */
386       ext = g_strdup ("png");
387     }
388
389   if (ext != NULL)
390     {
391       gchar *id;
392
393       id = tp_escape_as_identifier (empathy_contact_get_id (
394             information->contact));
395
396       filename = g_strdup_printf ("%s.%s", id, ext);
397       gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
398
399       g_free (id);
400       g_free (ext);
401       g_free (filename);
402     }
403
404   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
405     {
406       GError *error = NULL;
407
408       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
409
410       if (!empathy_avatar_save_to_file (avatar, filename, &error))
411         {
412           /* Save error */
413           GtkWidget *dialog;
414
415           dialog = gtk_message_dialog_new (NULL, 0,
416               GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 
417               _("Unable to save avatar"));
418
419           gtk_message_dialog_format_secondary_text (
420               GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
421
422           g_signal_connect (dialog, "response",
423               G_CALLBACK (gtk_widget_destroy), NULL);
424
425           gtk_window_present (GTK_WINDOW (dialog));
426
427           g_clear_error (&error);
428         }
429
430       g_free (filename);
431     }
432
433   gtk_widget_destroy (dialog);
434 }
435
436 static void
437 popup_avatar_menu (EmpathyContactWidget *information,
438                    GtkWidget *parent,
439                    GdkEventButton *event)
440 {
441   GtkWidget *menu, *item;
442   gint button, event_time;
443
444   if (information->contact == NULL ||
445       empathy_contact_get_avatar (information->contact) == NULL)
446       return;
447
448   menu = gtk_menu_new ();
449
450   /* Add "Save as..." entry */
451   item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
452   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
453   gtk_widget_show (item);
454
455   g_signal_connect (item, "activate",
456       G_CALLBACK (save_avatar_menu_activate_cb), information);
457
458   if (event)
459     {
460       button = event->button;
461       event_time = event->time;
462     }
463   else
464     {
465       button = 0;
466       event_time = gtk_get_current_event_time ();
467     }
468
469   gtk_menu_attach_to_widget (GTK_MENU (menu), parent, NULL);
470   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 
471       button, event_time);
472 }
473
474 static gboolean
475 widget_avatar_popup_menu_cb (GtkWidget *widget,
476                              EmpathyContactWidget *information)
477 {
478   popup_avatar_menu (information, widget, NULL);
479
480   return TRUE;
481 }
482
483 static gboolean
484 widget_avatar_button_press_event_cb (GtkWidget *widget,
485                                      GdkEventButton *event,
486                                      EmpathyContactWidget *information)
487 {
488   /* Ignore double-clicks and triple-clicks */
489   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
490     {
491       popup_avatar_menu (information, widget, event);
492       return TRUE;
493     }
494
495   return FALSE;
496 }
497
498 static void
499 update_avatar_chooser_account_cb (EmpathyAccountChooser *account_chooser,
500                                   EmpathyAvatarChooser *avatar_chooser)
501 {
502   McAccount *account;
503
504   account = empathy_account_chooser_get_account (account_chooser);
505   g_object_set (avatar_chooser, "account", account, NULL);
506 }
507
508 static void
509 contact_widget_contact_setup (EmpathyContactWidget *information)
510 {
511   /* Setup account label/chooser */
512   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
513     {
514       information->widget_account = empathy_account_chooser_new ();
515
516       g_signal_connect (information->widget_account, "changed",
517             G_CALLBACK (contact_widget_account_changed_cb),
518             information);
519     }
520   else
521     {
522       information->widget_account = gtk_label_new (NULL);
523       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
524         gtk_label_set_selectable (GTK_LABEL (information->widget_account), TRUE);
525       }
526       gtk_misc_set_alignment (GTK_MISC (information->widget_account), 0, 0.5);
527     }
528   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
529            information->widget_account,
530            1, 2, 0, 1);
531   gtk_widget_show (information->widget_account);
532
533   /* Set up avatar chooser/display */
534   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
535     {
536       information->widget_avatar = empathy_avatar_chooser_new ();
537       g_signal_connect (information->widget_avatar, "changed",
538             G_CALLBACK (contact_widget_avatar_changed_cb),
539             information);
540       if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
541         {
542           g_signal_connect (information->widget_account, "changed",
543               G_CALLBACK (update_avatar_chooser_account_cb),
544               information->widget_avatar);
545           update_avatar_chooser_account_cb (
546               EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
547               EMPATHY_AVATAR_CHOOSER (information->widget_avatar));
548         }
549     }
550   else
551     {
552       information->widget_avatar = empathy_avatar_image_new ();
553
554       g_signal_connect (information->widget_avatar, "popup-menu",
555           G_CALLBACK (widget_avatar_popup_menu_cb), information);
556       g_signal_connect (information->widget_avatar, "button-press-event",
557           G_CALLBACK (widget_avatar_button_press_event_cb), information);
558     }
559
560   gtk_box_pack_start (GTK_BOX (information->vbox_avatar),
561           information->widget_avatar,
562           FALSE, FALSE,
563           6);
564   gtk_widget_show (information->widget_avatar);
565
566   /* Setup id label/entry */
567   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
568     {
569       information->widget_id = gtk_entry_new ();
570       g_signal_connect (information->widget_id, "focus-out-event",
571             G_CALLBACK (contact_widget_id_focus_out_cb),
572             information);
573       g_signal_connect (information->widget_id, "changed",
574             G_CALLBACK (contact_widget_id_changed_cb),
575             information);
576     }
577   else
578     {
579       information->widget_id = gtk_label_new (NULL);
580       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
581         gtk_label_set_selectable (GTK_LABEL (information->widget_id), TRUE);
582       }
583       gtk_misc_set_alignment (GTK_MISC (information->widget_id), 0, 0.5);
584     }
585   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
586            information->widget_id,
587            1, 2, 1, 2);
588   gtk_widget_show (information->widget_id);
589
590   /* Setup alias label/entry */
591   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ALIAS)
592     {
593       information->widget_alias = gtk_entry_new ();
594       g_signal_connect (information->widget_alias, "focus-out-event",
595             G_CALLBACK (contact_widget_entry_alias_focus_event_cb),
596             information);
597       /* Make return activate the window default (the Close button) */
598       gtk_entry_set_activates_default (GTK_ENTRY (information->widget_alias),
599           TRUE);
600     }
601   else
602     {
603       information->widget_alias = gtk_label_new (NULL);
604       if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
605         gtk_label_set_selectable (GTK_LABEL (information->widget_alias), TRUE);
606       }
607       gtk_misc_set_alignment (GTK_MISC (information->widget_alias), 0, 0.5);
608     }
609   gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
610            information->widget_alias,
611            1, 2, 2, 3);
612   if (information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) {
613     gtk_label_set_selectable (GTK_LABEL (information->label_status), FALSE);
614   }
615   gtk_widget_show (information->widget_alias);
616 }
617
618 static void
619 contact_widget_contact_update (EmpathyContactWidget *information)
620 {
621   McAccount *account = NULL;
622   const gchar *id = NULL;
623
624   /* Connect and get info from new contact */
625   if (information->contact)
626     {
627       g_signal_connect_swapped (information->contact, "notify::name",
628           G_CALLBACK (contact_widget_name_notify_cb), information);
629       g_signal_connect_swapped (information->contact, "notify::presence",
630           G_CALLBACK (contact_widget_presence_notify_cb), information);
631       g_signal_connect_swapped (information->contact,
632           "notify::presence-message",
633           G_CALLBACK (contact_widget_presence_notify_cb), information);
634       g_signal_connect_swapped (information->contact, "notify::avatar",
635           G_CALLBACK (contact_widget_avatar_notify_cb), information);
636
637       account = empathy_contact_get_account (information->contact);
638       id = empathy_contact_get_id (information->contact);
639     }
640
641   /* Update account widget */
642   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
643     {
644       if (account)
645         {
646           g_signal_handlers_block_by_func (information->widget_account,
647                    contact_widget_account_changed_cb,
648                    information);
649           empathy_account_chooser_set_account (
650               EMPATHY_ACCOUNT_CHOOSER (information->widget_account), account);
651           g_signal_handlers_unblock_by_func (information->widget_account,
652               contact_widget_account_changed_cb, information);
653         }
654     }
655   else
656     {
657       if (account)
658         {
659           const gchar *name;
660
661           name = mc_account_get_display_name (account);
662           gtk_label_set_label (GTK_LABEL (information->widget_account), name);
663         }
664     }
665
666   /* Update id widget */
667   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
668       gtk_entry_set_text (GTK_ENTRY (information->widget_id), id ? id : "");
669   else
670       gtk_label_set_label (GTK_LABEL (information->widget_id), id ? id : "");
671
672   /* Update other widgets */
673   if (information->contact)
674     {
675       contact_widget_name_notify_cb (information);
676       contact_widget_presence_notify_cb (information);
677       contact_widget_avatar_notify_cb (information);
678
679       gtk_widget_show (information->label_alias);
680       gtk_widget_show (information->widget_alias);
681       gtk_widget_show (information->hbox_presence);
682       gtk_widget_show (information->widget_avatar);
683     }
684   else
685     {
686       gtk_widget_hide (information->label_alias);
687       gtk_widget_hide (information->widget_alias);
688       gtk_widget_hide (information->hbox_presence);
689       gtk_widget_hide (information->widget_avatar);
690     }
691 }
692
693 static void
694 contact_widget_change_contact_cb (EmpathyContact *contact,
695                                   const GError *error,
696                                   gpointer information,
697                                   GObject *weak_object)
698 {
699   if (error)
700     DEBUG ("Error: %s", error->message);
701   else
702     contact_widget_set_contact (information, contact);
703   g_object_unref (contact);
704 }
705
706 static void
707 contact_widget_change_contact (EmpathyContactWidget *information)
708 {
709   EmpathyContact *contact;
710   McAccount *account;
711
712   account = empathy_account_chooser_get_account (
713       EMPATHY_ACCOUNT_CHOOSER (information->widget_account));
714   if (!account)
715       return;
716
717   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
718     {
719       const gchar *id;
720
721       id = gtk_entry_get_text (GTK_ENTRY (information->widget_id));
722       if (EMP_STR_EMPTY (id))
723           return;
724
725       contact = empathy_contact_factory_get_from_id (information->factory,
726           account, id);
727     }
728   else
729     {
730       contact = empathy_contact_factory_get_user (information->factory,
731           account);
732     }
733
734   if (contact)
735     {
736       /* Give the contact ref to the callback */
737       empathy_contact_call_when_ready (contact,
738           EMPATHY_CONTACT_READY_HANDLE |
739           EMPATHY_CONTACT_READY_ID,
740           contact_widget_change_contact_cb,
741           information, NULL,
742           G_OBJECT (information->vbox_contact_widget));
743     }
744 }
745
746 static void
747 contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
748                                   EmpathyContactWidget *information)
749 {
750   if (information->contact && empathy_contact_is_user (information->contact))
751     {
752       McAccount *account;
753       const gchar *data;
754       gsize size;
755       const gchar *mime_type;
756
757       account = empathy_contact_get_account (information->contact);
758       empathy_avatar_chooser_get_image_data (
759           EMPATHY_AVATAR_CHOOSER (information->widget_avatar),
760           &data, &size, &mime_type);
761       empathy_contact_factory_set_avatar (information->factory, account,
762           data, size, mime_type);
763     }
764 }
765
766 static void
767 contact_widget_account_changed_cb (GtkComboBox *widget,
768                                    EmpathyContactWidget *information)
769 {
770   contact_widget_change_contact (information);
771 }
772
773 static gboolean
774 contact_widget_id_focus_out_cb (GtkWidget *widget,
775                                 GdkEventFocus *event,
776                                 EmpathyContactWidget *information)
777 {
778   contact_widget_change_contact (information);
779   return FALSE;
780 }
781
782 static gboolean
783 contact_widget_entry_alias_focus_event_cb (GtkEditable *editable,
784                                            GdkEventFocus *event,
785                                            EmpathyContactWidget *information)
786 {
787   if (information->contact)
788     {
789       const gchar *alias;
790
791       alias = gtk_entry_get_text (GTK_ENTRY (editable));
792       empathy_contact_factory_set_alias (information->factory,
793           information->contact, alias);
794     }
795
796   return FALSE;
797 }
798
799 static void
800 contact_widget_name_notify_cb (EmpathyContactWidget *information)
801 {
802   if (GTK_IS_ENTRY (information->widget_alias))
803       gtk_entry_set_text (GTK_ENTRY (information->widget_alias),
804           empathy_contact_get_name (information->contact));
805   else
806       gtk_label_set_label (GTK_LABEL (information->widget_alias),
807           empathy_contact_get_name (information->contact));
808 }
809
810 static void
811 contact_widget_presence_notify_cb (EmpathyContactWidget *information)
812 {
813   gtk_label_set_text (GTK_LABEL (information->label_status),
814       empathy_contact_get_status (information->contact));
815   gtk_image_set_from_icon_name (GTK_IMAGE (information->image_state),
816       empathy_icon_name_for_contact (information->contact),
817       GTK_ICON_SIZE_BUTTON);
818 }
819
820 static void
821 contact_widget_avatar_notify_cb (EmpathyContactWidget *information)
822 {
823   EmpathyAvatar *avatar = NULL;
824
825   if (information->contact)
826       avatar = empathy_contact_get_avatar (information->contact);
827
828   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
829     {
830       g_signal_handlers_block_by_func (information->widget_avatar,
831           contact_widget_avatar_changed_cb,
832           information);
833       empathy_avatar_chooser_set (
834           EMPATHY_AVATAR_CHOOSER (information->widget_avatar), avatar);
835       g_signal_handlers_unblock_by_func (information->widget_avatar,
836           contact_widget_avatar_changed_cb, information);
837     }
838   else
839       empathy_avatar_image_set (
840           EMPATHY_AVATAR_IMAGE (information->widget_avatar), avatar);
841 }
842
843 static void
844 contact_widget_groups_setup (EmpathyContactWidget *information)
845 {
846   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS)
847     {
848       information->manager = empathy_contact_manager_dup_singleton ();
849       contact_widget_model_setup (information);
850     }
851 }
852
853 static void
854 contact_widget_groups_update (EmpathyContactWidget *information)
855 {
856   if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS &&
857       information->contact)
858     {
859       g_signal_connect_swapped (information->contact, "notify::groups",
860           G_CALLBACK (contact_widget_groups_notify_cb), information);
861       contact_widget_groups_populate_data (information);
862
863       gtk_widget_show (information->vbox_groups);
864     }
865   else
866       gtk_widget_hide (information->vbox_groups);
867 }
868
869 static void
870 contact_widget_model_setup (EmpathyContactWidget *information)
871 {
872   GtkTreeView *view;
873   GtkListStore *store;
874   GtkTreeSelection *selection;
875
876   view = GTK_TREE_VIEW (information->treeview_groups);
877
878   store = gtk_list_store_new (COL_COUNT,
879       G_TYPE_STRING,   /* name */
880       G_TYPE_BOOLEAN,  /* enabled */
881       G_TYPE_BOOLEAN); /* editable */
882
883   gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
884
885   selection = gtk_tree_view_get_selection (view);
886   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
887
888   contact_widget_model_populate_columns (information);
889
890   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
891       COL_NAME, GTK_SORT_ASCENDING);
892
893   g_object_unref (store);
894 }
895
896 static void
897 contact_widget_model_populate_columns (EmpathyContactWidget *information)
898 {
899   GtkTreeView *view;
900   GtkTreeModel *model;
901   GtkTreeViewColumn *column;
902   GtkCellRenderer  *renderer;
903   guint col_offset;
904
905   view = GTK_TREE_VIEW (information->treeview_groups);
906   model = gtk_tree_view_get_model (view);
907
908   renderer = gtk_cell_renderer_toggle_new ();
909   g_signal_connect (renderer, "toggled",
910       G_CALLBACK (contact_widget_cell_toggled), information);
911
912   column = gtk_tree_view_column_new_with_attributes (_("Select"), renderer,
913       "active", COL_ENABLED, NULL);
914
915   gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
916   gtk_tree_view_column_set_fixed_width (column, 50);
917   gtk_tree_view_append_column (view, column);
918
919   renderer = gtk_cell_renderer_text_new ();
920   col_offset = gtk_tree_view_insert_column_with_attributes (view,
921       -1, _("Group"),
922       renderer,
923       "text", COL_NAME,
924       /* "editable", COL_EDITABLE, */
925       NULL);
926
927   g_object_set_data (G_OBJECT (renderer),
928       "column", GINT_TO_POINTER (COL_NAME));
929
930   column = gtk_tree_view_get_column (view, col_offset - 1);
931   gtk_tree_view_column_set_sort_column_id (column, COL_NAME);
932   gtk_tree_view_column_set_resizable (column,FALSE);
933   gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
934
935   if (information->renderer)
936       g_object_unref (information->renderer);
937
938   information->renderer = g_object_ref (renderer);
939 }
940
941 static void
942 contact_widget_groups_populate_data (EmpathyContactWidget *information)
943 {
944   GtkTreeView *view;
945   GtkListStore *store;
946   GtkTreeIter iter;
947   GList *my_groups, *l;
948   GList *all_groups;
949
950   view = GTK_TREE_VIEW (information->treeview_groups);
951   store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
952   gtk_list_store_clear (store);
953
954   all_groups = empathy_contact_list_get_all_groups (
955       EMPATHY_CONTACT_LIST (information->manager));
956   my_groups = empathy_contact_list_get_groups (
957       EMPATHY_CONTACT_LIST (information->manager),
958       information->contact);
959
960   for (l = all_groups; l; l = l->next)
961     {
962       const gchar *group_str;
963       gboolean enabled;
964
965       group_str = l->data;
966
967       enabled = g_list_find_custom (my_groups,
968           group_str, (GCompareFunc) strcmp) != NULL;
969
970       gtk_list_store_append (store, &iter);
971       gtk_list_store_set (store, &iter,
972           COL_NAME, group_str,
973           COL_EDITABLE, TRUE,
974           COL_ENABLED, enabled,
975           -1);
976     }
977
978   g_list_foreach (all_groups, (GFunc) g_free, NULL);
979   g_list_foreach (my_groups, (GFunc) g_free, NULL);
980   g_list_free (all_groups);
981   g_list_free (my_groups);
982 }
983
984 static void
985 contact_widget_groups_notify_cb (EmpathyContactWidget *information)
986 {
987   /* FIXME: not implemented */
988 }
989
990 static gboolean
991 contact_widget_model_find_name (EmpathyContactWidget *information,
992                                 const gchar *name,
993                                 GtkTreeIter *iter)
994 {
995   GtkTreeView *view;
996   GtkTreeModel *model;
997   FindName data;
998
999   if (EMP_STR_EMPTY (name))
1000       return FALSE;
1001
1002   data.information = information;
1003   data.name = name;
1004   data.found = FALSE;
1005
1006   view = GTK_TREE_VIEW (information->treeview_groups);
1007   model = gtk_tree_view_get_model (view);
1008
1009   gtk_tree_model_foreach (model,
1010       (GtkTreeModelForeachFunc) contact_widget_model_find_name_foreach,
1011       &data);
1012
1013   if (data.found == TRUE)
1014     {
1015       *iter = data.found_iter;
1016       return TRUE;
1017     }
1018
1019   return FALSE;
1020 }
1021
1022 static gboolean
1023 contact_widget_model_find_name_foreach (GtkTreeModel *model,
1024                                         GtkTreePath *path,
1025                                         GtkTreeIter *iter,
1026                                         FindName *data)
1027 {
1028   gchar *name;
1029
1030   gtk_tree_model_get (model, iter,
1031       COL_NAME, &name,
1032       -1);
1033
1034   if (!name)
1035       return FALSE;
1036
1037   if (data->name && strcmp (data->name, name) == 0)
1038     {
1039       data->found = TRUE;
1040       data->found_iter = *iter;
1041
1042       g_free (name);
1043
1044       return TRUE;
1045     }
1046
1047   g_free (name);
1048
1049   return FALSE;
1050 }
1051
1052 static void
1053 contact_widget_cell_toggled (GtkCellRendererToggle *cell,
1054                              gchar *path_string,
1055                              EmpathyContactWidget *information)
1056 {
1057   GtkTreeView *view;
1058   GtkTreeModel *model;
1059   GtkListStore *store;
1060   GtkTreePath *path;
1061   GtkTreeIter iter;
1062   gboolean enabled;
1063   gchar *group;
1064
1065   view = GTK_TREE_VIEW (information->treeview_groups);
1066   model = gtk_tree_view_get_model (view);
1067   store = GTK_LIST_STORE (model);
1068
1069   path = gtk_tree_path_new_from_string (path_string);
1070
1071   gtk_tree_model_get_iter (model, &iter, path);
1072   gtk_tree_model_get (model, &iter,
1073       COL_ENABLED, &enabled,
1074       COL_NAME, &group,
1075       -1);
1076
1077   gtk_list_store_set (store, &iter, COL_ENABLED, !enabled, -1);
1078   gtk_tree_path_free (path);
1079
1080   if (group)
1081     {
1082       if (enabled)
1083         {
1084           empathy_contact_list_remove_from_group (
1085               EMPATHY_CONTACT_LIST (information->manager), information->contact,
1086               group);
1087         }
1088       else
1089         {
1090           empathy_contact_list_add_to_group (
1091               EMPATHY_CONTACT_LIST (information->manager), information->contact,
1092               group);
1093         }
1094       g_free (group);
1095     }
1096 }
1097
1098 static void
1099 contact_widget_entry_group_changed_cb (GtkEditable *editable,
1100                                        EmpathyContactWidget *information)
1101 {
1102   GtkTreeIter iter;
1103   const gchar *group;
1104
1105   group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
1106
1107   if (contact_widget_model_find_name (information, group, &iter))
1108       gtk_widget_set_sensitive (GTK_WIDGET (information->button_group), FALSE);
1109   else
1110       gtk_widget_set_sensitive (GTK_WIDGET (information->button_group),
1111           !EMP_STR_EMPTY (group));
1112 }
1113
1114 static void
1115 contact_widget_entry_group_activate_cb (GtkEntry *entry,
1116                                         EmpathyContactWidget  *information)
1117 {
1118   gtk_widget_activate (GTK_WIDGET (information->button_group));
1119 }
1120
1121 static void
1122 contact_widget_button_group_clicked_cb (GtkButton *button,
1123                                         EmpathyContactWidget *information)
1124 {
1125   GtkTreeView *view;
1126   GtkListStore *store;
1127   GtkTreeIter iter;
1128   const gchar *group;
1129
1130   view = GTK_TREE_VIEW (information->treeview_groups);
1131   store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
1132
1133   group = gtk_entry_get_text (GTK_ENTRY (information->entry_group));
1134
1135   gtk_list_store_append (store, &iter);
1136   gtk_list_store_set (store, &iter,
1137       COL_NAME, group,
1138       COL_ENABLED, TRUE,
1139       -1);
1140
1141   empathy_contact_list_add_to_group (
1142       EMPATHY_CONTACT_LIST (information->manager), information->contact,
1143       group);
1144 }
1145
1146 static void
1147 contact_widget_details_setup (EmpathyContactWidget *information)
1148 {
1149   /* FIXME: Needs new telepathy spec */
1150   gtk_widget_hide (information->vbox_details);
1151 }
1152
1153 static void
1154 contact_widget_details_update (EmpathyContactWidget *information)
1155 {
1156   /* FIXME: Needs new telepathy spec */
1157 }
1158
1159 static void
1160 contact_widget_client_setup (EmpathyContactWidget *information)
1161 {
1162   /* FIXME: Needs new telepathy spec */
1163   gtk_widget_hide (information->vbox_client);
1164 }
1165
1166 static void
1167 contact_widget_client_update (EmpathyContactWidget *information)
1168 {
1169   /* FIXME: Needs new telepathy spec */
1170 }