LogWindow: Don't cut the log messages
[empathy-mirror.git] / libempathy-gtk / empathy-contact-widget.c
blob14042bf61f5bd0eebdb261a5cf8cff981c0141dc
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2007-2009 Collabora Ltd.
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.
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.
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
19 * Authors: Xavier Claessens <xclaesse@gmail.com>
22 #include <config.h>
24 #include <string.h>
25 #include <stdlib.h>
27 #include <gtk/gtk.h>
28 #include <glib/gi18n-lib.h>
30 #ifdef HAVE_LIBCHAMPLAIN
31 #include <champlain/champlain.h>
32 #include <champlain-gtk/champlain-gtk.h>
33 #endif
35 #include <telepathy-glib/account.h>
36 #include <telepathy-glib/util.h>
37 #include <telepathy-glib/interfaces.h>
39 #include <libempathy/empathy-tp-contact-factory.h>
40 #include <libempathy/empathy-contact-manager.h>
41 #include <libempathy/empathy-contact-list.h>
42 #include <libempathy/empathy-location.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-time.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-contact-widget.h"
48 #include "empathy-account-chooser.h"
49 #include "empathy-avatar-chooser.h"
50 #include "empathy-avatar-image.h"
51 #include "empathy-groups-widget.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-string-parser.h"
55 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
56 #include <libempathy/empathy-debug.h>
58 /**
59 * SECTION:empathy-contact-widget
60 * @title:EmpathyContactWidget
61 * @short_description: A widget used to display and edit details about a contact
62 * @include: libempathy-empathy-contact-widget.h
64 * #EmpathyContactWidget is a widget which displays appropriate widgets
65 * with details about a contact, also allowing changing these details,
66 * if desired.
69 /**
70 * EmpathyContactWidget:
71 * @parent: parent object
73 * Widget which displays appropriate widgets with details about a contact,
74 * also allowing changing these details, if desired.
77 /* Delay before updating the widget when the id entry changed (seconds) */
78 #define ID_CHANGED_TIMEOUT 1
80 #define DATA_FIELD "contact-info-field"
82 typedef struct
84 EmpathyContactManager *manager;
85 EmpathyContact *contact;
86 EmpathyContactWidgetFlags flags;
87 guint widget_id_timeout;
88 gulong fav_sig_id;
90 GtkWidget *vbox_contact_widget;
92 /* Contact */
93 GtkWidget *hbox_contact;
94 GtkWidget *widget_avatar;
95 GtkWidget *widget_account;
96 GtkWidget *image_account;
97 GtkWidget *label_account;
98 GtkWidget *widget_id;
99 GtkWidget *widget_alias;
100 GtkWidget *label_alias;
101 GtkWidget *hbox_presence;
102 GtkWidget *image_state;
103 GtkWidget *label_status;
104 GtkWidget *table_contact;
105 GtkWidget *vbox_avatar;
106 GtkWidget *favourite_checkbox;
108 /* Location */
109 GtkWidget *vbox_location;
110 GtkWidget *subvbox_location;
111 GtkWidget *table_location;
112 GtkWidget *label_location;
113 #ifdef HAVE_LIBCHAMPLAIN
114 GtkWidget *viewport_map;
115 GtkWidget *map_view_embed;
116 ChamplainView *map_view;
117 #endif
119 /* Groups */
120 GtkWidget *groups_widget;
122 /* Details */
123 GtkWidget *vbox_details;
124 GtkWidget *table_details;
125 GtkWidget *hbox_details_requested;
126 GtkWidget *spinner_details;
127 GList *details_to_set;
128 GCancellable *details_cancellable;
129 gboolean details_changed;
131 /* Client */
132 GtkWidget *vbox_client;
133 GtkWidget *table_client;
134 GtkWidget *hbox_client_requested;
135 } EmpathyContactWidget;
137 typedef struct
139 EmpathyContactWidget *information;
140 const gchar *name;
141 gboolean found;
142 GtkTreeIter found_iter;
143 } FindName;
145 enum
147 COL_NAME,
148 COL_ENABLED,
149 COL_EDITABLE,
150 COL_COUNT
153 static gboolean
154 field_value_is_empty (TpContactInfoField *field)
156 guint i;
158 if (field->field_value == NULL)
159 return TRUE;
161 /* Field is empty if all its values are empty */
162 for (i = 0; field->field_value[i] != NULL; i++)
164 if (!tp_str_empty (field->field_value[i]))
165 return FALSE;
168 return TRUE;
171 static void
172 set_contact_info_cb (GObject *source,
173 GAsyncResult *result,
174 gpointer user_data)
176 GError *error = NULL;
178 if (!tp_connection_set_contact_info_finish (TP_CONNECTION (source), result,
179 &error))
181 DEBUG ("SetContactInfo() failed: %s", error->message);
182 g_error_free (error);
183 return;
186 DEBUG ("SetContactInfo() succeeded");
189 static void
190 contact_widget_save (EmpathyContactWidget *information)
192 TpConnection *connection;
193 GList *l, *next;
195 connection = empathy_contact_get_connection (information->contact);
197 /* Remove empty fields */
198 for (l = information->details_to_set; l != NULL; l = next)
200 TpContactInfoField *field = l->data;
202 next = l->next;
203 if (field_value_is_empty (field))
205 DEBUG ("Drop empty field: %s", field->field_name);
206 tp_contact_info_field_free (field);
207 information->details_to_set =
208 g_list_delete_link (information->details_to_set, l);
212 if (information->details_to_set != NULL)
214 if (information->details_changed)
216 tp_connection_set_contact_info_async (connection,
217 information->details_to_set, set_contact_info_cb, NULL);
220 tp_contact_info_list_free (information->details_to_set);
221 information->details_to_set = NULL;
225 static void
226 contact_widget_details_setup (EmpathyContactWidget *information)
228 gtk_widget_hide (information->vbox_details);
230 information->spinner_details = gtk_spinner_new ();
231 gtk_box_pack_end (GTK_BOX (information->hbox_details_requested),
232 information->spinner_details, TRUE, TRUE, 0);
233 gtk_widget_show (information->spinner_details);
236 static void
237 contact_widget_details_changed_cb (GtkEntry *entry,
238 EmpathyContactWidget *self)
240 const gchar *strv[] = { NULL, NULL };
241 TpContactInfoField *field;
243 self->details_changed = TRUE;
245 field = g_object_get_data ((GObject *) entry, DATA_FIELD);
246 g_assert (field != NULL);
248 strv[0] = gtk_entry_get_text (entry);
250 if (field->field_value != NULL)
251 g_strfreev (field->field_value);
252 field->field_value = g_strdupv ((GStrv) strv);
255 static void
256 contact_widget_bday_changed_cb (GtkCalendar *calendar,
257 EmpathyContactWidget *self)
259 guint year, month, day;
260 GDate *date;
261 gchar tmp[255];
262 const gchar *strv[] = { NULL, NULL };
263 TpContactInfoField *field;
265 self->details_changed = TRUE;
267 field = g_object_get_data ((GObject *) calendar, DATA_FIELD);
268 g_assert (field != NULL);
270 gtk_calendar_get_date (calendar, &year, &month, &day);
271 date = g_date_new_dmy (day, month+1, year);
273 gtk_calendar_clear_marks (calendar);
274 gtk_calendar_mark_day (calendar, g_date_get_day (date));
276 g_date_strftime (tmp, sizeof (tmp), EMPATHY_DATE_FORMAT_DISPLAY_SHORT, date);
277 strv[0] = tmp;
278 if (field->field_value != NULL)
279 g_strfreev (field->field_value);
280 field->field_value = g_strdupv ((GStrv) strv);
282 g_date_free (date);
285 static void contact_widget_details_notify_cb (EmpathyContactWidget *information);
287 typedef gchar * (* FieldFormatFunc) (GStrv);
289 typedef struct
291 const gchar *field_name;
292 const gchar *title;
293 FieldFormatFunc format;
294 } InfoFieldData;
296 static gchar *
297 linkify_first_value (GStrv values)
299 return empathy_add_link_markup (values[0]);
302 static gchar *
303 format_idle_time (GStrv values)
305 const gchar *value = values[0];
306 int duration = strtol (value, NULL, 10);
308 if (duration <= 0)
309 return NULL;
311 return empathy_duration_to_string (duration);
314 static gchar *
315 format_server (GStrv values)
317 g_assert (values[0] != NULL);
319 if (values[1] == NULL)
320 return g_markup_escape_text (values[0], -1);
321 else
322 return g_markup_printf_escaped ("%s (%s)", values[0], values[1]);
325 static gchar *
326 presence_hack (GStrv values)
328 if (tp_str_empty (values[0]))
329 return NULL;
331 return g_markup_escape_text (values[0], -1);
334 static InfoFieldData info_field_datas[] =
336 { "fn", N_("Full name:"), NULL },
337 { "tel", N_("Phone number:"), NULL },
338 { "email", N_("E-mail address:"), linkify_first_value },
339 { "url", N_("Website:"), linkify_first_value },
340 { "bday", N_("Birthday:"), NULL },
342 /* Note to translators: this is the caption for a string of the form "5
343 * minutes ago", and refers to the time since the contact last interacted
344 * with their IM client.
346 { "x-idle-time", N_("Last seen:"), format_idle_time },
347 { "x-irc-server", N_("Server:"), format_server },
348 { "x-host", N_("Connected from:"), format_server },
350 /* FIXME: once Idle implements SimplePresence using this information, we can
351 * and should bin this.
353 { "x-presence-status-message", N_("Away message:"), presence_hack },
355 { NULL, NULL }
358 static InfoFieldData *
359 find_info_field_data (const gchar *field_name)
361 guint i;
363 for (i = 0; info_field_datas[i].field_name != NULL; i++)
365 if (!tp_strdiff (info_field_datas[i].field_name, field_name))
366 return info_field_datas + i;
368 return NULL;
371 static gint
372 contact_info_field_name_cmp (const gchar *name1,
373 const gchar *name2)
375 guint i;
377 if (!tp_strdiff (name1, name2))
378 return 0;
380 /* We use the order of info_field_datas */
381 for (i = 0; info_field_datas[i].field_name != NULL; i++)
383 if (!tp_strdiff (info_field_datas[i].field_name, name1))
384 return -1;
385 if (!tp_strdiff (info_field_datas[i].field_name, name2))
386 return +1;
389 return g_strcmp0 (name1, name2);
392 static gint
393 contact_info_field_cmp (TpContactInfoField *field1,
394 TpContactInfoField *field2)
396 return contact_info_field_name_cmp (field1->field_name, field2->field_name);
399 static gboolean
400 field_name_in_field_list (GList *list,
401 const gchar *name)
403 GList *l;
405 for (l = list; l != NULL; l = g_list_next (l))
407 TpContactInfoField *field = l->data;
409 if (!tp_strdiff (field->field_name, name))
410 return TRUE;
413 return FALSE;
416 static TpContactInfoFieldSpec *
417 get_spec_from_list (GList *list,
418 const gchar *name)
420 GList *l;
422 for (l = list; l != NULL; l = g_list_next (l))
424 TpContactInfoFieldSpec *spec = l->data;
426 if (!tp_strdiff (spec->name, name))
427 return spec;
430 return NULL;
433 static guint
434 contact_widget_details_update_edit (EmpathyContactWidget *information)
436 TpContact *contact;
437 TpConnection *connection;
438 GList *specs, *l;
439 guint n_rows = 0;
440 GList *info;
441 guint i;
443 g_assert (information->details_to_set == NULL);
445 information->details_changed = FALSE;
447 contact = empathy_contact_get_tp_contact (information->contact);
448 connection = tp_contact_get_connection (contact);
450 info = tp_contact_get_contact_info (contact);
452 specs = tp_connection_get_contact_info_supported_fields (connection);
454 /* Look at the fields set in our vCard */
455 for (l = info; l != NULL; l = l->next)
457 TpContactInfoField *field = l->data;
459 /* make a copy for the details_to_set list */
460 field = tp_contact_info_field_copy (field);
461 DEBUG ("Field %s is in our vCard", field->field_name);
463 information->details_to_set = g_list_prepend (information->details_to_set,
464 field);
467 /* Add fields which are supported but not in the vCard */
468 for (i = 0; info_field_datas[i].field_name != NULL; i++)
470 TpContactInfoFieldSpec *spec;
471 TpContactInfoField *field;
473 /* Check if the field was in the vCard */
474 if (field_name_in_field_list (information->details_to_set,
475 info_field_datas[i].field_name))
476 continue;
478 /* Check if the CM supports the field */
479 spec = get_spec_from_list (specs, info_field_datas[i].field_name);
480 if (spec == NULL)
481 continue;
483 /* add an empty field so user can set a value */
484 field = tp_contact_info_field_new (spec->name, spec->parameters, NULL);
486 information->details_to_set = g_list_prepend (information->details_to_set,
487 field);
490 /* Add widgets for supported fields */
491 information->details_to_set = g_list_sort (information->details_to_set,
492 (GCompareFunc) contact_info_field_cmp);
494 for (l = information->details_to_set; l != NULL; l= g_list_next (l))
496 TpContactInfoField *field = l->data;
497 InfoFieldData *field_data;
498 GtkWidget *w;
499 TpContactInfoFieldSpec *spec;
501 field_data = find_info_field_data (field->field_name);
502 if (field_data == NULL)
504 /* Empathy doesn't display this field so we can't change it.
505 * But we put it in the details_to_set list so it won't be erased
506 * when calling SetContactInfo (bgo #630427) */
507 DEBUG ("Unhandled ContactInfo field spec: %s", field->field_name);
508 continue;
511 spec = get_spec_from_list (specs, field->field_name);
512 /* We shouldn't have added the field to details_to_set if it's not
513 * supported by the CM */
514 g_assert (spec != NULL);
516 if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
518 DEBUG ("Ignoring field '%s' due it to having the "
519 "Overwritten_By_Nickname flag", field->field_name);
520 continue;
523 /* Add Title */
524 w = gtk_label_new (_(field_data->title));
525 gtk_table_attach (GTK_TABLE (information->table_details),
526 w, 0, 1, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0);
527 gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
528 gtk_widget_show (w);
530 /* Add Value */
531 if (!tp_strdiff (field->field_name, "bday"))
533 w = gtk_calendar_new ();
534 if (field->field_value[0])
536 GDate date;
538 g_date_set_parse (&date, field->field_value[0]);
539 if (g_date_valid (&date))
541 gtk_calendar_select_day (GTK_CALENDAR (w),
542 g_date_get_day (&date));
543 gtk_calendar_select_month (GTK_CALENDAR (w),
544 g_date_get_month (&date) - 1, g_date_get_year (&date));
545 gtk_calendar_mark_day (GTK_CALENDAR (w),
546 g_date_get_day (&date));
549 gtk_table_attach_defaults (GTK_TABLE (information->table_details),
550 w, 1, 2, n_rows, n_rows + 1);
551 gtk_widget_show (w);
553 g_object_set_data ((GObject *) w, DATA_FIELD, field);
555 g_signal_connect (w, "day-selected-double-click",
556 G_CALLBACK (contact_widget_bday_changed_cb), information);
557 g_signal_connect (w, "month-changed",
558 G_CALLBACK (contact_widget_bday_changed_cb), information);
560 else
562 w = gtk_entry_new ();
563 gtk_entry_set_text (GTK_ENTRY (w),
564 field->field_value[0] ? field->field_value[0] : "");
565 gtk_table_attach_defaults (GTK_TABLE (information->table_details),
566 w, 1, 2, n_rows, n_rows + 1);
567 gtk_widget_show (w);
569 g_object_set_data ((GObject *) w, DATA_FIELD, field);
571 g_signal_connect (w, "changed",
572 G_CALLBACK (contact_widget_details_changed_cb), information);
575 n_rows++;
578 g_list_free (specs);
579 g_list_free (info);
581 return n_rows;
584 static gboolean
585 channel_name_activated_cb (
586 GtkLabel *label,
587 gchar *uri,
588 EmpathyContactWidget *information)
590 TpAccount *account = empathy_contact_get_account (information->contact);
592 empathy_join_muc (account, uri, empathy_get_current_action_time ());
593 return TRUE;
596 static void
597 add_channel_list (
598 EmpathyContactWidget *information,
599 GPtrArray *channels,
600 guint row)
602 GtkWidget *w;
603 GString *label_markup = g_string_new ("");
604 guint i;
606 w = gtk_label_new (_("Channels:"));
607 gtk_table_attach (GTK_TABLE (information->table_details),
608 w, 0, 1, row, row + 1, GTK_FILL, 0, 0, 0);
609 gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
610 gtk_widget_show (w);
612 for (i = 0; i < channels->len; i++)
614 const gchar *channel_name = g_ptr_array_index (channels, i);
615 /* We abuse the URI of the link to hold the channel name. It seems to
616 * be okay to just use it essentially verbatim, rather than trying to
617 * ensure it's actually a valid URI. g_string_append_uri_escaped()
618 * escapes way more than we actually need to; so we're just using
619 * g_markup_escape_text directly.
621 gchar *escaped = g_markup_escape_text (channel_name, -1);
623 if (i > 0)
624 g_string_append (label_markup, ", ");
626 g_string_append_printf (label_markup, "<a href='%s'>%s</a>",
627 escaped, channel_name);
628 g_free (escaped);
631 w = gtk_label_new (NULL);
632 gtk_label_set_markup (GTK_LABEL (w), label_markup->str);
633 gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
634 g_signal_connect (w, "activate-link",
635 (GCallback) channel_name_activated_cb, information);
636 gtk_table_attach_defaults (GTK_TABLE (information->table_details),
637 w, 1, 2, row, row + 1);
638 gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
639 gtk_widget_show (w);
641 g_string_free (label_markup, TRUE);
644 static guint
645 contact_widget_details_update_show (EmpathyContactWidget *information)
647 TpContact *contact;
648 GList *info, *l;
649 guint n_rows = 0;
650 GPtrArray *channels = g_ptr_array_new ();
652 contact = empathy_contact_get_tp_contact (information->contact);
653 info = tp_contact_get_contact_info (contact);
654 info = g_list_sort (info, (GCompareFunc) contact_info_field_cmp);
655 for (l = info; l != NULL; l = l->next)
657 TpContactInfoField *field = l->data;
658 InfoFieldData *field_data;
659 const gchar *value;
660 gchar *markup = NULL;
661 GtkWidget *w;
663 if (field->field_value == NULL || field->field_value[0] == NULL)
664 continue;
666 value = field->field_value[0];
668 if (!tp_strdiff (field->field_name, "x-irc-channel"))
670 g_ptr_array_add (channels, (gpointer) field->field_value[0]);
671 continue;
674 field_data = find_info_field_data (field->field_name);
675 if (field_data == NULL)
677 DEBUG ("Unhandled ContactInfo field: %s", field->field_name);
678 continue;
681 if (field_data->format != NULL)
683 markup = field_data->format (field->field_value);
685 if (markup == NULL)
687 DEBUG ("Invalid value for field '%s' (first element was '%s')",
688 field->field_name, field->field_value[0]);
689 continue;
693 /* Add Title */
694 w = gtk_label_new (_(field_data->title));
695 gtk_table_attach (GTK_TABLE (information->table_details),
696 w, 0, 1, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0);
697 gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
698 gtk_widget_show (w);
700 /* Add Value */
701 w = gtk_label_new (value);
702 if (markup != NULL)
704 gtk_label_set_markup (GTK_LABEL (w), markup);
705 g_free (markup);
708 if ((information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) == 0)
709 gtk_label_set_selectable (GTK_LABEL (w), TRUE);
711 gtk_table_attach_defaults (GTK_TABLE (information->table_details),
712 w, 1, 2, n_rows, n_rows + 1);
713 gtk_misc_set_alignment (GTK_MISC (w), 0, 0.5);
714 gtk_widget_show (w);
716 n_rows++;
718 g_list_free (info);
720 if (channels->len > 0)
722 add_channel_list (information, channels, n_rows);
723 n_rows++;
726 g_ptr_array_unref (channels);
727 return n_rows;
730 static void
731 contact_widget_details_notify_cb (EmpathyContactWidget *information)
733 guint n_rows;
735 gtk_container_foreach (GTK_CONTAINER (information->table_details),
736 (GtkCallback) gtk_widget_destroy, NULL);
738 if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
739 n_rows = contact_widget_details_update_edit (information);
740 else
741 n_rows = contact_widget_details_update_show (information);
743 if (n_rows > 0)
745 gtk_widget_show (information->vbox_details);
746 gtk_widget_show (information->table_details);
748 else
750 gtk_widget_hide (information->vbox_details);
753 gtk_widget_hide (information->hbox_details_requested);
754 gtk_spinner_stop (GTK_SPINNER (information->spinner_details));
757 static void
758 contact_widget_details_request_cb (GObject *object,
759 GAsyncResult *res,
760 gpointer user_data)
762 TpContact *contact = TP_CONTACT (object);
763 EmpathyContactWidget *information = user_data;
764 GError *error = NULL;
766 if (!tp_contact_request_contact_info_finish (contact, res, &error))
768 /* If the request got cancelled it could mean the contact widget is
769 * destroyed, so we should not dereference information */
770 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
772 g_clear_error (&error);
773 return;
776 gtk_widget_hide (information->vbox_details);
777 g_clear_error (&error);
779 else
781 contact_widget_details_notify_cb (information);
784 /* If we are going to edit ContactInfo, we don't want live updates */
785 if ((information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
787 g_signal_connect_swapped (contact, "notify::contact-info",
788 G_CALLBACK (contact_widget_details_notify_cb), information);
791 tp_clear_object (&information->details_cancellable);
794 static void
795 contact_widget_details_feature_prepared_cb (GObject *object,
796 GAsyncResult *res,
797 gpointer user_data)
799 TpConnection *connection = TP_CONNECTION (object);
800 EmpathyContactWidget *information = user_data;
801 TpContact *contact;
802 TpContactInfoFlags flags;
804 if (!tp_proxy_prepare_finish (connection, res, NULL))
806 gtk_widget_hide (information->vbox_details);
807 return;
810 /* If we want to edit info, but connection does not support that, stop */
811 flags = tp_connection_get_contact_info_flags (connection);
812 if ((flags & TP_CONTACT_INFO_FLAG_CAN_SET) == 0 &&
813 (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) != 0)
815 gtk_widget_hide (information->vbox_details);
816 return;
819 /* Request the contact's info */
820 gtk_widget_show (information->vbox_details);
821 gtk_widget_show (information->hbox_details_requested);
822 gtk_widget_hide (information->table_details);
823 gtk_spinner_start (GTK_SPINNER (information->spinner_details));
825 contact = empathy_contact_get_tp_contact (information->contact);
826 g_assert (information->details_cancellable == NULL);
827 information->details_cancellable = g_cancellable_new ();
828 tp_contact_request_contact_info_async (contact,
829 information->details_cancellable, contact_widget_details_request_cb,
830 information);
833 static void
834 contact_widget_details_update (EmpathyContactWidget *information)
836 TpContact *tp_contact = NULL;
838 if ((information->flags & EMPATHY_CONTACT_WIDGET_SHOW_DETAILS) == 0 &&
839 (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_DETAILS) == 0)
840 return;
842 gtk_widget_hide (information->vbox_details);
844 if (information->contact != NULL)
845 tp_contact = empathy_contact_get_tp_contact (information->contact);
847 if (tp_contact != NULL)
849 GQuark features[] = { TP_CONNECTION_FEATURE_CONTACT_INFO, 0 };
850 TpConnection *connection;
852 /* First, make sure the CONTACT_INFO feature is ready on the connection */
853 connection = tp_contact_get_connection (tp_contact);
854 tp_proxy_prepare_async (connection, features,
855 contact_widget_details_feature_prepared_cb, information);
859 static void
860 contact_widget_client_update (EmpathyContactWidget *information)
862 /* FIXME: Needs new telepathy spec */
865 static void
866 contact_widget_client_setup (EmpathyContactWidget *information)
868 /* FIXME: Needs new telepathy spec */
869 gtk_widget_hide (information->vbox_client);
872 static void
873 contact_widget_groups_update (EmpathyContactWidget *information)
875 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_GROUPS &&
876 information->contact != NULL)
878 FolksPersona *persona =
879 empathy_contact_get_persona (information->contact);
881 if (FOLKS_IS_GROUP_DETAILS (persona))
883 empathy_groups_widget_set_group_details (
884 EMPATHY_GROUPS_WIDGET (information->groups_widget),
885 FOLKS_GROUP_DETAILS (persona));
886 gtk_widget_show (information->groups_widget);
888 return;
892 /* In case of failure */
893 gtk_widget_hide (information->groups_widget);
896 /* Converts the Location's GHashTable's key to a user readable string */
897 static const gchar *
898 location_key_to_label (const gchar *key)
900 if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY_CODE) == FALSE)
901 return _("Country ISO Code:");
902 else if (tp_strdiff (key, EMPATHY_LOCATION_COUNTRY) == FALSE)
903 return _("Country:");
904 else if (tp_strdiff (key, EMPATHY_LOCATION_REGION) == FALSE)
905 return _("State:");
906 else if (tp_strdiff (key, EMPATHY_LOCATION_LOCALITY) == FALSE)
907 return _("City:");
908 else if (tp_strdiff (key, EMPATHY_LOCATION_AREA) == FALSE)
909 return _("Area:");
910 else if (tp_strdiff (key, EMPATHY_LOCATION_POSTAL_CODE) == FALSE)
911 return _("Postal Code:");
912 else if (tp_strdiff (key, EMPATHY_LOCATION_STREET) == FALSE)
913 return _("Street:");
914 else if (tp_strdiff (key, EMPATHY_LOCATION_BUILDING) == FALSE)
915 return _("Building:");
916 else if (tp_strdiff (key, EMPATHY_LOCATION_FLOOR) == FALSE)
917 return _("Floor:");
918 else if (tp_strdiff (key, EMPATHY_LOCATION_ROOM) == FALSE)
919 return _("Room:");
920 else if (tp_strdiff (key, EMPATHY_LOCATION_TEXT) == FALSE)
921 return _("Text:");
922 else if (tp_strdiff (key, EMPATHY_LOCATION_DESCRIPTION) == FALSE)
923 return _("Description:");
924 else if (tp_strdiff (key, EMPATHY_LOCATION_URI) == FALSE)
925 return _("URI:");
926 else if (tp_strdiff (key, EMPATHY_LOCATION_ACCURACY_LEVEL) == FALSE)
927 return _("Accuracy Level:");
928 else if (tp_strdiff (key, EMPATHY_LOCATION_ERROR) == FALSE)
929 return _("Error:");
930 else if (tp_strdiff (key, EMPATHY_LOCATION_VERTICAL_ERROR_M) == FALSE)
931 return _("Vertical Error (meters):");
932 else if (tp_strdiff (key, EMPATHY_LOCATION_HORIZONTAL_ERROR_M) == FALSE)
933 return _("Horizontal Error (meters):");
934 else if (tp_strdiff (key, EMPATHY_LOCATION_SPEED) == FALSE)
935 return _("Speed:");
936 else if (tp_strdiff (key, EMPATHY_LOCATION_BEARING) == FALSE)
937 return _("Bearing:");
938 else if (tp_strdiff (key, EMPATHY_LOCATION_CLIMB) == FALSE)
939 return _("Climb Speed:");
940 else if (tp_strdiff (key, EMPATHY_LOCATION_TIMESTAMP) == FALSE)
941 return _("Last Updated on:");
942 else if (tp_strdiff (key, EMPATHY_LOCATION_LON) == FALSE)
943 return _("Longitude:");
944 else if (tp_strdiff (key, EMPATHY_LOCATION_LAT) == FALSE)
945 return _("Latitude:");
946 else if (tp_strdiff (key, EMPATHY_LOCATION_ALT) == FALSE)
947 return _("Altitude:");
948 else
950 DEBUG ("Unexpected Location key: %s", key);
951 return key;
955 static void
956 contact_widget_location_update (EmpathyContactWidget *information)
958 GHashTable *location;
959 GValue *value;
960 #ifdef HAVE_LIBCHAMPLAIN
961 gdouble lat = 0.0, lon = 0.0;
962 gboolean has_position = TRUE;
963 #endif
964 GtkWidget *label;
965 guint row = 0;
966 static const gchar* ordered_geolocation_keys[] = {
967 EMPATHY_LOCATION_TEXT,
968 EMPATHY_LOCATION_URI,
969 EMPATHY_LOCATION_DESCRIPTION,
970 EMPATHY_LOCATION_BUILDING,
971 EMPATHY_LOCATION_FLOOR,
972 EMPATHY_LOCATION_ROOM,
973 EMPATHY_LOCATION_STREET,
974 EMPATHY_LOCATION_AREA,
975 EMPATHY_LOCATION_LOCALITY,
976 EMPATHY_LOCATION_REGION,
977 EMPATHY_LOCATION_COUNTRY,
978 NULL
980 int i;
981 const gchar *skey;
982 gboolean display_map = FALSE;
984 if (!(information->flags & EMPATHY_CONTACT_WIDGET_SHOW_LOCATION))
986 gtk_widget_hide (information->vbox_location);
987 return;
990 location = empathy_contact_get_location (information->contact);
991 if (location == NULL || g_hash_table_size (location) == 0)
993 gtk_widget_hide (information->vbox_location);
994 return;
997 value = g_hash_table_lookup (location, EMPATHY_LOCATION_TIMESTAMP);
998 if (value == NULL)
1000 gchar *loc = g_strdup_printf ("<b>%s</b>", _("Location"));
1001 gtk_label_set_markup (GTK_LABEL (information->label_location), loc);
1002 g_free (loc);
1004 else
1006 gchar *user_date;
1007 gchar *text;
1008 gint64 stamp;
1009 gchar *tmp;
1011 stamp = g_value_get_int64 (value);
1013 user_date = empathy_time_to_string_relative (stamp);
1015 tmp = g_strdup_printf ("<b>%s</b>", _("Location"));
1016 /* translators: format is "Location, $date" */
1017 text = g_strdup_printf (_("%s, %s"), tmp, user_date);
1018 g_free (tmp);
1019 gtk_label_set_markup (GTK_LABEL (information->label_location), text);
1020 g_free (user_date);
1021 g_free (text);
1025 /* Prepare the location information table */
1026 if (information->table_location != NULL)
1028 gtk_widget_destroy (information->table_location);
1031 information->table_location = gtk_table_new (1, 2, FALSE);
1032 gtk_box_pack_start (GTK_BOX (information->subvbox_location),
1033 information->table_location, FALSE, FALSE, 5);
1036 for (i = 0; (skey = ordered_geolocation_keys[i]); i++)
1038 const gchar* user_label;
1039 GValue *gvalue;
1040 char *svalue = NULL;
1042 gvalue = g_hash_table_lookup (location, (gpointer) skey);
1043 if (gvalue == NULL)
1044 continue;
1046 user_label = location_key_to_label (skey);
1048 label = gtk_label_new (user_label);
1049 gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1050 gtk_table_attach (GTK_TABLE (information->table_location),
1051 label, 0, 1, row, row + 1, GTK_FILL, GTK_FILL, 10, 0);
1052 gtk_widget_show (label);
1054 if (G_VALUE_TYPE (gvalue) == G_TYPE_DOUBLE)
1056 gdouble dvalue;
1057 dvalue = g_value_get_double (gvalue);
1058 svalue = g_strdup_printf ("%f", dvalue);
1060 else if (G_VALUE_TYPE (gvalue) == G_TYPE_STRING)
1062 svalue = g_value_dup_string (gvalue);
1064 else if (G_VALUE_TYPE (gvalue) == G_TYPE_INT64)
1066 gint64 time_;
1068 time_ = g_value_get_int64 (value);
1069 svalue = empathy_time_to_string_utc (time_, _("%B %e, %Y at %R UTC"));
1072 if (svalue != NULL)
1074 label = gtk_label_new (svalue);
1075 gtk_table_attach_defaults (GTK_TABLE (information->table_location),
1076 label, 1, 2, row, row + 1);
1077 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
1078 gtk_widget_show (label);
1080 if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
1081 gtk_label_set_selectable (GTK_LABEL (label), TRUE);
1084 g_free (svalue);
1085 row++;
1088 #ifdef HAVE_LIBCHAMPLAIN
1089 if (has_position &&
1090 !(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
1092 /* Cannot be displayed in tooltips until Clutter-Gtk can deal with such
1093 * windows */
1094 display_map = TRUE;
1096 #endif
1098 if (row > 0)
1100 /* We can display some fields */
1101 gtk_widget_show (information->table_location);
1103 else if (!display_map)
1105 /* Can't display either fields or map */
1106 gtk_widget_hide (information->vbox_location);
1107 return;
1110 #ifdef HAVE_LIBCHAMPLAIN
1111 if (display_map)
1113 ClutterActor *marker;
1114 ChamplainMarkerLayer *layer;
1116 information->map_view_embed = gtk_champlain_embed_new ();
1117 information->map_view = gtk_champlain_embed_get_view (
1118 GTK_CHAMPLAIN_EMBED (information->map_view_embed));
1120 gtk_container_add (GTK_CONTAINER (information->viewport_map),
1121 information->map_view_embed);
1122 g_object_set (G_OBJECT (information->map_view),
1123 "kinetic-mode", TRUE,
1124 "zoom-level", 10,
1125 NULL);
1127 layer = champlain_marker_layer_new ();
1128 champlain_view_add_layer (information->map_view, CHAMPLAIN_LAYER (layer));
1130 marker = champlain_label_new_with_text (
1131 empathy_contact_get_alias (information->contact), NULL, NULL, NULL);
1132 champlain_location_set_location (CHAMPLAIN_LOCATION (marker), lat, lon);
1133 champlain_marker_layer_add_marker (layer, CHAMPLAIN_MARKER (marker));
1135 champlain_view_center_on (information->map_view, lat, lon);
1136 gtk_widget_show_all (information->viewport_map);
1138 #endif
1140 gtk_widget_show (information->vbox_location);
1143 static void
1144 save_avatar_menu_activate_cb (GtkWidget *widget,
1145 EmpathyContactWidget *information)
1147 GtkWidget *dialog;
1148 EmpathyAvatar *avatar;
1149 gchar *ext = NULL, *filename;
1151 dialog = gtk_file_chooser_dialog_new (_("Save Avatar"),
1152 NULL,
1153 GTK_FILE_CHOOSER_ACTION_SAVE,
1154 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1155 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1156 NULL);
1158 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
1159 TRUE);
1161 /* look for the avatar extension */
1162 avatar = empathy_contact_get_avatar (information->contact);
1163 if (avatar->format != NULL)
1165 gchar **splitted;
1167 splitted = g_strsplit (avatar->format, "/", 2);
1168 if (splitted[0] != NULL && splitted[1] != NULL)
1169 ext = g_strdup (splitted[1]);
1171 g_strfreev (splitted);
1173 else
1175 /* Avatar was loaded from the cache so was converted to PNG */
1176 ext = g_strdup ("png");
1179 if (ext != NULL)
1181 gchar *id;
1183 id = tp_escape_as_identifier (empathy_contact_get_id (
1184 information->contact));
1186 filename = g_strdup_printf ("%s.%s", id, ext);
1187 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
1189 g_free (id);
1190 g_free (ext);
1191 g_free (filename);
1194 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
1196 GError *error = NULL;
1198 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1200 if (!empathy_avatar_save_to_file (avatar, filename, &error))
1202 /* Save error */
1203 GtkWidget *error_dialog;
1205 error_dialog = gtk_message_dialog_new (NULL, 0,
1206 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1207 _("Unable to save avatar"));
1209 gtk_message_dialog_format_secondary_text (
1210 GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
1212 g_signal_connect (error_dialog, "response",
1213 G_CALLBACK (gtk_widget_destroy), NULL);
1215 gtk_window_present (GTK_WINDOW (error_dialog));
1217 g_clear_error (&error);
1220 g_free (filename);
1223 gtk_widget_destroy (dialog);
1226 static void
1227 popup_avatar_menu (EmpathyContactWidget *information,
1228 GtkWidget *parent,
1229 GdkEventButton *event)
1231 GtkWidget *menu, *item;
1232 gint button, event_time;
1234 if (information->contact == NULL ||
1235 empathy_contact_get_avatar (information->contact) == NULL)
1236 return;
1238 menu = empathy_context_menu_new (parent);
1240 /* Add "Save as..." entry */
1241 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_SAVE_AS, NULL);
1242 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1243 gtk_widget_show (item);
1245 g_signal_connect (item, "activate",
1246 G_CALLBACK (save_avatar_menu_activate_cb), information);
1248 if (event)
1250 button = event->button;
1251 event_time = event->time;
1253 else
1255 button = 0;
1256 event_time = gtk_get_current_event_time ();
1259 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1260 button, event_time);
1263 static gboolean
1264 widget_avatar_popup_menu_cb (GtkWidget *widget,
1265 EmpathyContactWidget *information)
1267 popup_avatar_menu (information, widget, NULL);
1269 return TRUE;
1272 static gboolean
1273 widget_avatar_button_press_event_cb (GtkWidget *widget,
1274 GdkEventButton *event,
1275 EmpathyContactWidget *information)
1277 /* Ignore double-clicks and triple-clicks */
1278 if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1280 popup_avatar_menu (information, widget, event);
1281 return TRUE;
1284 return FALSE;
1287 static void
1288 set_avatar_cb (GObject *source,
1289 GAsyncResult *res,
1290 gpointer user_data)
1292 GError *error = NULL;
1294 if (!tp_account_set_avatar_finish (TP_ACCOUNT (source), res, &error)) {
1295 DEBUG ("Failed to set Account.Avatar: %s", error->message);
1296 g_error_free (error);
1300 static void
1301 set_avatar_on_account (TpAccount *account,
1302 const gchar *data,
1303 gsize size,
1304 const gchar *mime_type)
1306 DEBUG ("%s Account.Avatar on %s", size > 0 ? "Set": "Clear",
1307 tp_proxy_get_object_path (account));
1309 tp_account_set_avatar_async (account, (const guchar *) data, size,
1310 mime_type, set_avatar_cb, NULL);
1313 static void
1314 contact_widget_avatar_changed_cb (EmpathyAvatarChooser *chooser,
1315 EmpathyContactWidget *information)
1317 const gchar *data;
1318 gsize size;
1319 const gchar *mime_type;
1320 TpAccount *account;
1322 empathy_avatar_chooser_get_image_data (
1323 EMPATHY_AVATAR_CHOOSER (information->widget_avatar),
1324 &data, &size, &mime_type);
1326 account = empathy_contact_get_account (information->contact);
1327 set_avatar_on_account (account, data, size, mime_type);
1330 static void
1331 set_nickname_cb (GObject *source,
1332 GAsyncResult *res,
1333 gpointer user_data)
1335 GError *error = NULL;
1337 if (!tp_account_set_nickname_finish (TP_ACCOUNT (source), res, &error))
1339 DEBUG ("Failed to set Account.Nickname: %s", error->message);
1340 g_error_free (error);
1344 /* Update all the contact info fields having the
1345 * Overwritten_By_Nickname flag to the new alias. This avoid accidentally
1346 * reseting the alias when calling SetContactInfo(). See bgo #644298 for
1347 * details. */
1348 static void
1349 update_nickname_in_contact_info (EmpathyContactWidget *self,
1350 TpConnection *connection,
1351 const gchar *nickname)
1353 GList *specs, *l;
1355 specs = tp_connection_get_contact_info_supported_fields (connection);
1357 for (l = self->details_to_set; l != NULL; l= g_list_next (l))
1359 TpContactInfoField *field = l->data;
1360 TpContactInfoFieldSpec *spec;
1362 spec = get_spec_from_list (specs, field->field_name);
1363 /* We shouldn't have added the field to details_to_set if it's not
1364 * supported by the CM */
1365 g_assert (spec != NULL);
1367 if (spec->flags & TP_CONTACT_INFO_FIELD_FLAG_OVERWRITTEN_BY_NICKNAME)
1369 const gchar *strv[] = { nickname, NULL };
1371 DEBUG ("Updating field '%s' to '%s' as it has the "
1372 "Overwritten_By_Nickname flag and Account.Nickname has "
1373 "been updated", field->field_name, nickname);
1375 if (field->field_value != NULL)
1376 g_strfreev (field->field_value);
1377 field->field_value = g_strdupv ((GStrv) strv);
1381 g_list_free (specs);
1384 static gboolean
1385 contact_widget_entry_alias_focus_event_cb (GtkEditable *editable,
1386 GdkEventFocus *event,
1387 EmpathyContactWidget *information)
1389 if (information->contact)
1391 const gchar *alias;
1393 alias = gtk_entry_get_text (GTK_ENTRY (editable));
1395 if (empathy_contact_is_user (information->contact))
1397 TpAccount * account;
1398 const gchar *current_nickname;
1400 account = empathy_contact_get_account (information->contact);
1401 current_nickname = tp_account_get_nickname (account);
1403 if (tp_strdiff (current_nickname, alias))
1405 DEBUG ("Set Account.Nickname to %s", alias);
1407 tp_account_set_nickname_async (account, alias, set_nickname_cb,
1408 NULL);
1410 update_nickname_in_contact_info (information,
1411 empathy_contact_get_connection (information->contact), alias);
1414 else
1416 empathy_contact_set_alias (information->contact, alias);
1420 return FALSE;
1423 static void
1424 update_avatar_chooser_account_cb (EmpathyAccountChooser *account_chooser,
1425 EmpathyAvatarChooser *avatar_chooser)
1427 TpAccount *account;
1429 account = empathy_account_chooser_get_account (account_chooser);
1430 if (account == NULL)
1431 return;
1433 empathy_avatar_chooser_set_account (avatar_chooser, account);
1436 static void
1437 contact_widget_avatar_notify_cb (EmpathyContactWidget *information)
1439 EmpathyAvatar *avatar = NULL;
1441 if (information->contact)
1442 avatar = empathy_contact_get_avatar (information->contact);
1444 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
1446 g_signal_handlers_block_by_func (information->widget_avatar,
1447 contact_widget_avatar_changed_cb,
1448 information);
1449 empathy_avatar_chooser_set (
1450 EMPATHY_AVATAR_CHOOSER (information->widget_avatar), avatar);
1451 g_signal_handlers_unblock_by_func (information->widget_avatar,
1452 contact_widget_avatar_changed_cb, information);
1454 else
1455 empathy_avatar_image_set (
1456 EMPATHY_AVATAR_IMAGE (information->widget_avatar), avatar);
1459 static void
1460 contact_widget_name_notify_cb (EmpathyContactWidget *information)
1462 if (GTK_IS_ENTRY (information->widget_alias))
1463 gtk_entry_set_text (GTK_ENTRY (information->widget_alias),
1464 empathy_contact_get_alias (information->contact));
1465 else
1466 gtk_label_set_label (GTK_LABEL (information->widget_alias),
1467 empathy_contact_get_alias (information->contact));
1470 static void
1471 contact_widget_presence_notify_cb (EmpathyContactWidget *information)
1473 const gchar *status;
1474 gchar *markup_text = NULL;
1476 status = empathy_contact_get_status (information->contact);
1477 if (status != NULL)
1478 markup_text = empathy_add_link_markup (status);
1479 gtk_label_set_markup (GTK_LABEL (information->label_status), markup_text);
1480 g_free (markup_text);
1482 gtk_image_set_from_icon_name (GTK_IMAGE (information->image_state),
1483 empathy_icon_name_for_contact (information->contact),
1484 GTK_ICON_SIZE_BUTTON);
1485 gtk_widget_show (information->image_state);
1488 static void
1489 contact_widget_favourites_changed_cb (EmpathyContactManager *manager,
1490 EmpathyContact *contact,
1491 gboolean is_favourite,
1492 EmpathyContactWidget *information)
1494 if (contact != information->contact)
1495 return;
1497 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (
1498 information->favourite_checkbox), is_favourite);
1501 static void
1502 contact_widget_remove_contact (EmpathyContactWidget *information)
1504 if (information->contact)
1506 TpContact *tp_contact;
1508 contact_widget_save (information);
1510 g_signal_handlers_disconnect_by_func (information->contact,
1511 contact_widget_name_notify_cb, information);
1512 g_signal_handlers_disconnect_by_func (information->contact,
1513 contact_widget_presence_notify_cb, information);
1514 g_signal_handlers_disconnect_by_func (information->contact,
1515 contact_widget_avatar_notify_cb, information);
1517 tp_contact = empathy_contact_get_tp_contact (information->contact);
1518 if (tp_contact != NULL)
1520 g_signal_handlers_disconnect_by_func (tp_contact,
1521 contact_widget_details_notify_cb, information);
1524 g_object_unref (information->contact);
1525 information->contact = NULL;
1528 if (information->details_cancellable != NULL)
1530 g_cancellable_cancel (information->details_cancellable);
1531 tp_clear_object (&information->details_cancellable);
1535 static void contact_widget_change_contact (EmpathyContactWidget *information);
1537 static void
1538 contact_widget_contact_update (EmpathyContactWidget *information)
1540 TpAccount *account = NULL;
1541 const gchar *id = NULL;
1543 /* Connect and get info from new contact */
1544 if (information->contact)
1546 g_signal_connect_swapped (information->contact, "notify::name",
1547 G_CALLBACK (contact_widget_name_notify_cb), information);
1548 g_signal_connect_swapped (information->contact, "notify::presence",
1549 G_CALLBACK (contact_widget_presence_notify_cb), information);
1550 g_signal_connect_swapped (information->contact,
1551 "notify::presence-message",
1552 G_CALLBACK (contact_widget_presence_notify_cb), information);
1553 g_signal_connect_swapped (information->contact, "notify::avatar",
1554 G_CALLBACK (contact_widget_avatar_notify_cb), information);
1556 account = empathy_contact_get_account (information->contact);
1557 id = empathy_contact_get_id (information->contact);
1560 /* Update account widget */
1561 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1563 if (account)
1565 g_signal_handlers_block_by_func (information->widget_account,
1566 contact_widget_change_contact,
1567 information);
1568 empathy_account_chooser_set_account (
1569 EMPATHY_ACCOUNT_CHOOSER (information->widget_account), account);
1570 g_signal_handlers_unblock_by_func (information->widget_account,
1571 contact_widget_change_contact, information);
1574 else
1576 if (account)
1578 const gchar *name;
1580 name = tp_account_get_display_name (account);
1581 gtk_label_set_label (GTK_LABEL (information->label_account), name);
1583 name = tp_account_get_icon_name (account);
1584 gtk_image_set_from_icon_name (GTK_IMAGE (information->image_account),
1585 name, GTK_ICON_SIZE_MENU);
1589 /* Update id widget */
1590 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1591 gtk_entry_set_text (GTK_ENTRY (information->widget_id), id ? id : "");
1592 else
1593 gtk_label_set_label (GTK_LABEL (information->widget_id), id ? id : "");
1595 /* Update other widgets */
1596 if (information->contact)
1598 contact_widget_name_notify_cb (information);
1599 contact_widget_presence_notify_cb (information);
1600 contact_widget_avatar_notify_cb (information);
1602 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE)
1604 FolksPersona *persona = empathy_contact_get_persona (
1605 information->contact);
1607 if (persona != NULL && FOLKS_IS_FAVOURITE_DETAILS (persona))
1609 gboolean is_favourite = folks_favourite_details_get_is_favourite (
1610 FOLKS_FAVOURITE_DETAILS (persona));
1611 contact_widget_favourites_changed_cb (information->manager,
1612 information->contact, is_favourite, information);
1616 gtk_widget_show (information->label_alias);
1617 gtk_widget_show (information->widget_alias);
1618 gtk_widget_show (information->hbox_presence);
1619 gtk_widget_show (information->widget_avatar);
1621 else
1623 gtk_widget_hide (information->label_alias);
1624 gtk_widget_hide (information->widget_alias);
1625 gtk_widget_hide (information->hbox_presence);
1626 gtk_widget_hide (information->widget_avatar);
1630 static void
1631 contact_widget_set_contact (EmpathyContactWidget *information,
1632 EmpathyContact *contact)
1634 if (contact == information->contact)
1635 return;
1637 contact_widget_remove_contact (information);
1638 if (contact)
1639 information->contact = g_object_ref (contact);
1641 /* set the selected account to be the account this contact came from */
1642 if (contact && EMPATHY_IS_ACCOUNT_CHOOSER (information->widget_account)) {
1643 empathy_account_chooser_set_account (
1644 EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
1645 empathy_contact_get_account (contact));
1648 /* Update information for widgets */
1649 contact_widget_contact_update (information);
1650 contact_widget_groups_update (information);
1651 contact_widget_details_update (information);
1652 contact_widget_client_update (information);
1653 contact_widget_location_update (information);
1656 static void
1657 contact_widget_got_contact_cb (TpConnection *connection,
1658 EmpathyContact *contact,
1659 const GError *error,
1660 gpointer user_data,
1661 GObject *weak_object)
1663 EmpathyContactWidget *information = user_data;
1665 if (error != NULL)
1667 DEBUG ("Error: %s", error->message);
1668 return;
1671 contact_widget_set_contact (information, contact);
1674 static void
1675 contact_widget_change_contact (EmpathyContactWidget *information)
1677 TpConnection *connection;
1679 connection = empathy_account_chooser_get_connection (
1680 EMPATHY_ACCOUNT_CHOOSER (information->widget_account));
1681 if (!connection)
1682 return;
1684 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1686 const gchar *id;
1688 id = gtk_entry_get_text (GTK_ENTRY (information->widget_id));
1689 if (!EMP_STR_EMPTY (id))
1691 empathy_tp_contact_factory_get_from_id (connection, id,
1692 contact_widget_got_contact_cb, information, NULL,
1693 G_OBJECT (information->vbox_contact_widget));
1696 else
1698 empathy_tp_contact_factory_get_from_handle (connection,
1699 tp_connection_get_self_handle (connection),
1700 contact_widget_got_contact_cb, information, NULL,
1701 G_OBJECT (information->vbox_contact_widget));
1705 static gboolean
1706 contact_widget_id_activate_timeout (EmpathyContactWidget *self)
1708 contact_widget_change_contact (self);
1709 return FALSE;
1712 static void
1713 contact_widget_id_changed_cb (GtkEntry *entry,
1714 EmpathyContactWidget *self)
1716 if (self->widget_id_timeout != 0)
1718 g_source_remove (self->widget_id_timeout);
1721 self->widget_id_timeout =
1722 g_timeout_add_seconds (ID_CHANGED_TIMEOUT,
1723 (GSourceFunc) contact_widget_id_activate_timeout, self);
1726 static gboolean
1727 contact_widget_id_focus_out_cb (GtkWidget *widget,
1728 GdkEventFocus *event,
1729 EmpathyContactWidget *information)
1731 contact_widget_change_contact (information);
1732 return FALSE;
1735 static void
1736 favourite_toggled_cb (GtkToggleButton *button,
1737 EmpathyContactWidget *information)
1739 FolksPersona *persona = empathy_contact_get_persona (information->contact);
1741 if (persona != NULL && FOLKS_IS_FAVOURITE_DETAILS (persona))
1743 gboolean active = gtk_toggle_button_get_active (button);
1744 folks_favourite_details_set_is_favourite (
1745 FOLKS_FAVOURITE_DETAILS (persona), active);
1749 static void
1750 contact_widget_contact_setup (EmpathyContactWidget *information)
1752 information->label_status = gtk_label_new ("");
1753 gtk_label_set_line_wrap_mode (GTK_LABEL (information->label_status),
1754 PANGO_WRAP_WORD_CHAR);
1755 gtk_label_set_line_wrap (GTK_LABEL (information->label_status),
1756 TRUE);
1758 if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP))
1759 gtk_label_set_selectable (GTK_LABEL (information->label_status), TRUE);
1761 gtk_box_pack_start (GTK_BOX (information->hbox_presence),
1762 information->label_status, TRUE, TRUE, 0);
1763 gtk_widget_show (information->label_status);
1765 /* Setup account label/chooser */
1766 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1768 information->widget_account = empathy_account_chooser_new ();
1770 g_signal_connect_swapped (information->widget_account, "changed",
1771 G_CALLBACK (contact_widget_change_contact),
1772 information);
1774 else
1776 /* Pack the protocol icon with the account name in an hbox */
1777 information->widget_account = gtk_hbox_new (FALSE, 6);
1779 information->label_account = gtk_label_new (NULL);
1780 if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1781 gtk_label_set_selectable (GTK_LABEL (information->label_account), TRUE);
1783 gtk_misc_set_alignment (GTK_MISC (information->label_account), 0, 0.5);
1784 gtk_widget_show (information->label_account);
1786 information->image_account = gtk_image_new ();
1787 gtk_widget_show (information->image_account);
1789 gtk_box_pack_start (GTK_BOX (information->widget_account),
1790 information->image_account, FALSE, FALSE, 0);
1791 gtk_box_pack_start (GTK_BOX (information->widget_account),
1792 information->label_account, FALSE, TRUE, 0);
1794 gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
1795 information->widget_account,
1796 1, 2, 0, 1);
1797 gtk_widget_show (information->widget_account);
1799 /* Set up avatar chooser/display */
1800 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_AVATAR)
1802 information->widget_avatar = empathy_avatar_chooser_new ();
1803 g_signal_connect (information->widget_avatar, "changed",
1804 G_CALLBACK (contact_widget_avatar_changed_cb),
1805 information);
1806 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT)
1808 g_signal_connect (information->widget_account, "changed",
1809 G_CALLBACK (update_avatar_chooser_account_cb),
1810 information->widget_avatar);
1811 update_avatar_chooser_account_cb (
1812 EMPATHY_ACCOUNT_CHOOSER (information->widget_account),
1813 EMPATHY_AVATAR_CHOOSER (information->widget_avatar));
1816 else
1818 information->widget_avatar = empathy_avatar_image_new ();
1820 g_signal_connect (information->widget_avatar, "popup-menu",
1821 G_CALLBACK (widget_avatar_popup_menu_cb), information);
1822 g_signal_connect (information->widget_avatar, "button-press-event",
1823 G_CALLBACK (widget_avatar_button_press_event_cb), information);
1826 gtk_box_pack_start (GTK_BOX (information->vbox_avatar),
1827 information->widget_avatar,
1828 FALSE, FALSE,
1830 gtk_widget_show (information->widget_avatar);
1832 /* Setup id label/entry */
1833 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1835 information->widget_id = gtk_entry_new ();
1836 g_signal_connect (information->widget_id, "focus-out-event",
1837 G_CALLBACK (contact_widget_id_focus_out_cb),
1838 information);
1839 g_signal_connect (information->widget_id, "changed",
1840 G_CALLBACK (contact_widget_id_changed_cb),
1841 information);
1843 else
1845 information->widget_id = gtk_label_new (NULL);
1846 if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1847 gtk_label_set_selectable (GTK_LABEL (information->widget_id), TRUE);
1849 gtk_misc_set_alignment (GTK_MISC (information->widget_id), 0, 0.5);
1851 gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
1852 information->widget_id,
1853 1, 2, 1, 2);
1854 gtk_widget_show (information->widget_id);
1856 /* Setup alias label/entry */
1857 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ALIAS)
1859 information->widget_alias = gtk_entry_new ();
1861 if (!(information->flags & EMPATHY_CONTACT_WIDGET_NO_SET_ALIAS))
1862 g_signal_connect (information->widget_alias, "focus-out-event",
1863 G_CALLBACK (contact_widget_entry_alias_focus_event_cb),
1864 information);
1866 /* Make return activate the window default (the Close button) */
1867 gtk_entry_set_activates_default (GTK_ENTRY (information->widget_alias),
1868 TRUE);
1870 else
1872 information->widget_alias = gtk_label_new (NULL);
1873 if (!(information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP)) {
1874 gtk_label_set_selectable (GTK_LABEL (information->widget_alias), TRUE);
1876 gtk_misc_set_alignment (GTK_MISC (information->widget_alias), 0, 0.5);
1878 gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
1879 information->widget_alias,
1880 1, 2, 2, 3);
1881 if (information->flags & EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP) {
1882 gtk_label_set_selectable (GTK_LABEL (information->label_status), FALSE);
1884 gtk_widget_show (information->widget_alias);
1886 /* Favorite */
1887 if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE)
1889 information->favourite_checkbox = gtk_check_button_new_with_label (
1890 _("Favorite"));
1892 g_signal_connect (information->favourite_checkbox, "toggled",
1893 G_CALLBACK (favourite_toggled_cb), information);
1895 gtk_table_attach_defaults (GTK_TABLE (information->table_contact),
1896 information->favourite_checkbox, 0, 2, 3, 4);
1898 information->fav_sig_id = g_signal_connect (information->manager,
1899 "favourites-changed",
1900 G_CALLBACK (contact_widget_favourites_changed_cb), information);
1902 gtk_widget_show (information->favourite_checkbox);
1906 static void
1907 contact_widget_destroy_cb (GtkWidget *widget,
1908 EmpathyContactWidget *information)
1910 contact_widget_remove_contact (information);
1912 if (information->widget_id_timeout != 0)
1914 g_source_remove (information->widget_id_timeout);
1917 if (information->fav_sig_id != 0)
1918 g_signal_handler_disconnect (information->manager, information->fav_sig_id);
1920 g_object_unref (information->manager);
1922 g_slice_free (EmpathyContactWidget, information);
1926 * empathy_contact_widget_new:
1927 * @contact: an #EmpathyContact
1928 * @flags: #EmpathyContactWidgetFlags for the new contact widget
1930 * Creates a new #EmpathyContactWidget.
1932 * Return value: a new #EmpathyContactWidget
1934 GtkWidget *
1935 empathy_contact_widget_new (EmpathyContact *contact,
1936 EmpathyContactWidgetFlags flags)
1938 EmpathyContactWidget *information;
1939 GtkBuilder *gui;
1940 gchar *filename;
1942 g_return_val_if_fail (contact == NULL || EMPATHY_IS_CONTACT (contact), NULL);
1944 information = g_slice_new0 (EmpathyContactWidget);
1945 information->flags = flags;
1947 filename = empathy_file_lookup ("empathy-contact-widget.ui",
1948 "libempathy-gtk");
1949 gui = empathy_builder_get_file (filename,
1950 "vbox_contact_widget", &information->vbox_contact_widget,
1951 "hbox_contact", &information->hbox_contact,
1952 "hbox_presence", &information->hbox_presence,
1953 "label_alias", &information->label_alias,
1954 "image_state", &information->image_state,
1955 "table_contact", &information->table_contact,
1956 "vbox_avatar", &information->vbox_avatar,
1957 "vbox_location", &information->vbox_location,
1958 "subvbox_location", &information->subvbox_location,
1959 "label_location", &information->label_location,
1960 #ifdef HAVE_LIBCHAMPLAIN
1961 "viewport_map", &information->viewport_map,
1962 #endif
1963 "groups_widget", &information->groups_widget,
1964 "vbox_details", &information->vbox_details,
1965 "table_details", &information->table_details,
1966 "hbox_details_requested", &information->hbox_details_requested,
1967 "vbox_client", &information->vbox_client,
1968 "table_client", &information->table_client,
1969 "hbox_client_requested", &information->hbox_client_requested,
1970 NULL);
1971 g_free (filename);
1973 empathy_builder_connect (gui, information,
1974 "vbox_contact_widget", "destroy", contact_widget_destroy_cb,
1975 NULL);
1976 information->table_location = NULL;
1978 g_object_set_data (G_OBJECT (information->vbox_contact_widget),
1979 "EmpathyContactWidget",
1980 information);
1982 information->manager = empathy_contact_manager_dup_singleton ();
1984 /* Create widgets */
1985 contact_widget_contact_setup (information);
1986 contact_widget_details_setup (information);
1987 contact_widget_client_setup (information);
1989 if (contact != NULL)
1990 contact_widget_set_contact (information, contact);
1991 else if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT ||
1992 information->flags & EMPATHY_CONTACT_WIDGET_EDIT_ID)
1993 contact_widget_change_contact (information);
1995 return empathy_builder_unref_and_keep_widget (gui,
1996 information->vbox_contact_widget);
2000 * empathy_contact_widget_get_contact:
2001 * @widget: an #EmpathyContactWidget
2003 * Get the #EmpathyContact related with the #EmpathyContactWidget @widget.
2005 * Returns: the #EmpathyContact associated with @widget
2007 EmpathyContact *
2008 empathy_contact_widget_get_contact (GtkWidget *widget)
2010 EmpathyContactWidget *information;
2012 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
2014 information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
2015 if (!information)
2016 return NULL;
2018 return information->contact;
2021 const gchar *
2022 empathy_contact_widget_get_alias (GtkWidget *widget)
2024 EmpathyContactWidget *information;
2026 g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
2028 information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
2029 if (!information)
2030 return NULL;
2032 return gtk_entry_get_text (GTK_ENTRY (information->widget_alias));
2036 * empathy_contact_widget_set_contact:
2037 * @widget: an #EmpathyContactWidget
2038 * @contact: a different #EmpathyContact
2040 * Change the #EmpathyContact related with the #EmpathyContactWidget @widget.
2042 void
2043 empathy_contact_widget_set_contact (GtkWidget *widget,
2044 EmpathyContact *contact)
2046 EmpathyContactWidget *information;
2048 g_return_if_fail (GTK_IS_WIDGET (widget));
2049 g_return_if_fail (EMPATHY_IS_CONTACT (contact));
2051 information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
2052 if (!information)
2053 return;
2055 contact_widget_set_contact (information, contact);
2059 * empathy_contact_widget_set_account_filter:
2060 * @widget: an #EmpathyContactWidget
2061 * @filter: a #EmpathyAccountChooserFilterFunc
2062 * @user_data: user data to pass to @filter, or %NULL
2064 * Set a filter on the #EmpathyAccountChooser included in the
2065 * #EmpathyContactWidget.
2067 void
2068 empathy_contact_widget_set_account_filter (
2069 GtkWidget *widget,
2070 EmpathyAccountChooserFilterFunc filter,
2071 gpointer user_data)
2073 EmpathyContactWidget *information;
2074 EmpathyAccountChooser *chooser;
2076 g_return_if_fail (GTK_IS_WIDGET (widget));
2078 information = g_object_get_data (G_OBJECT (widget), "EmpathyContactWidget");
2079 if (!information)
2080 return;
2082 chooser = EMPATHY_ACCOUNT_CHOOSER (information->widget_account);
2083 if (chooser)
2084 empathy_account_chooser_set_filter (chooser, filter, user_data);