]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
libempathy: Remove empathy_create_individual_from_tp_contact()
[empathy.git] / libempathy-gtk / empathy-individual-menu.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008-2010 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  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include "config.h"
24 #include "empathy-individual-menu.h"
25
26 #include <glib/gi18n-lib.h>
27 #include <tp-account-widgets/tpaw-camera-monitor.h>
28 #include <telepathy-glib/telepathy-glib-dbus.h>
29
30 #include "empathy-account-selector-dialog.h"
31 #include "empathy-call-utils.h"
32 #include "empathy-chatroom-manager.h"
33 #include "empathy-gtk-enum-types.h"
34 #include "empathy-images.h"
35 #include "empathy-individual-dialogs.h"
36 #include "empathy-individual-dialogs.h"
37 #include "empathy-individual-edit-dialog.h"
38 #include "empathy-individual-information-dialog.h"
39 #include "empathy-individual-manager.h"
40 #include "empathy-individual-store-channel.h"
41 #include "empathy-log-window.h"
42 #include "empathy-request-util.h"
43 #include "empathy-share-my-desktop.h"
44 #include "empathy-ui-utils.h"
45 #include "empathy-utils.h"
46
47 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
48 #include "empathy-debug.h"
49
50 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
51
52 typedef struct {
53   gchar *active_group; /* may be NULL */
54   FolksIndividual *individual; /* owned */
55   EmpathyIndividualFeatureFlags features;
56   EmpathyIndividualStore *store; /* may be NULL */
57 } EmpathyIndividualMenuPriv;
58
59 enum {
60   PROP_ACTIVE_GROUP = 1,
61   PROP_INDIVIDUAL,
62   PROP_FEATURES,
63   PROP_STORE,
64 };
65
66 enum {
67   MENU_ITEM_ACTIVATED,
68   LAST_SIGNAL
69 };
70
71 static guint signals [LAST_SIGNAL] = { 0 };
72
73 G_DEFINE_TYPE (EmpathyIndividualMenu, empathy_individual_menu, GTK_TYPE_MENU);
74
75 static GtkWidget * empathy_individual_chat_menu_item_new (
76     EmpathyIndividualMenu *self, FolksIndividual *individual);
77 static GtkWidget * empathy_individual_sms_menu_item_new (
78     EmpathyIndividualMenu *self, FolksIndividual *individual);
79 static GtkWidget * empathy_individual_log_menu_item_new  (
80     FolksIndividual *individual);
81 static GtkWidget * empathy_individual_info_menu_item_new (
82     FolksIndividual *individual);
83 static GtkWidget * empathy_individual_edit_menu_item_new (
84     FolksIndividual *individual);
85 static GtkWidget * empathy_individual_invite_menu_item_new (
86     FolksIndividual *individual,
87     EmpathyContact *contact);
88 static GtkWidget * empathy_individual_file_transfer_menu_item_new (
89     EmpathyIndividualMenu *self, FolksIndividual *individual);
90 static GtkWidget * empathy_individual_share_my_desktop_menu_item_new (
91     EmpathyIndividualMenu *self, FolksIndividual *individual);
92 static GtkWidget * empathy_individual_favourite_menu_item_new (
93     FolksIndividual *individual);
94 static GtkWidget * empathy_individual_add_menu_item_new (
95     EmpathyIndividualMenu *self,
96     FolksIndividual *individual);
97 static GtkWidget * empathy_individiual_block_menu_item_new (
98     FolksIndividual *individual);
99 static GtkWidget * empathy_individiual_remove_menu_item_new (
100     EmpathyIndividualMenu *self);
101
102 static void
103 individual_menu_add_personas (EmpathyIndividualMenu *self,
104     GtkMenuShell *menu,
105     FolksIndividual *individual,
106     EmpathyIndividualFeatureFlags features)
107 {
108   GtkWidget *item;
109   GeeSet *personas;
110   GeeIterator *iter;
111   guint persona_count = 0;
112
113   g_return_if_fail (GTK_IS_MENU (menu));
114   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
115   g_return_if_fail (empathy_folks_individual_contains_contact (individual));
116
117   personas = folks_individual_get_personas (individual);
118   /* we'll re-use this iterator throughout */
119   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
120
121   /* Make sure we've got enough valid entries for these menu items to add
122    * functionality */
123   while (gee_iterator_next (iter))
124     {
125       FolksPersona *persona = gee_iterator_get (iter);
126       if (empathy_folks_persona_is_interesting (persona))
127         persona_count++;
128
129       g_clear_object (&persona);
130     }
131
132   g_clear_object (&iter);
133
134   /* return early if these entries would add nothing beyond the "quick" items */
135   if (persona_count <= 1)
136     goto out;
137
138   /* add a separator before the list of personas */
139   item = gtk_separator_menu_item_new ();
140   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
141   gtk_widget_show (item);
142
143   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
144   while (gee_iterator_next (iter))
145     {
146       GtkWidget *image;
147       GtkWidget *contact_item;
148       GtkWidget *contact_submenu;
149       TpContact *tp_contact;
150       EmpathyContact *contact;
151       TpfPersona *persona = gee_iterator_get (iter);
152       gchar *label;
153       FolksPersonaStore *store;
154       const gchar *account;
155       GtkWidget *action;
156       /* Individual containing only persona */
157       FolksIndividual *single_individual;
158
159       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
160         goto while_finish;
161
162       tp_contact = tpf_persona_get_contact (persona);
163       if (tp_contact == NULL)
164         goto while_finish;
165
166       contact = empathy_contact_dup_from_tp_contact (tp_contact);
167       single_individual = empathy_ensure_individual_from_tp_contact (
168           tp_contact);
169
170       /* Pretty hacky. Creating single_individual had a side effect to change
171        * persona.individual from individual to single_individual which is not
172        * what we want so we set it back. See bgo#684971 for details. */
173       g_object_set (persona, "individual", individual, NULL);
174
175       store = folks_persona_get_store (FOLKS_PERSONA (persona));
176       account = folks_persona_store_get_display_name (store);
177
178       /* Translators: this is used in the context menu for a contact. The first
179        * parameter is a contact ID (e.g. foo@jabber.org) and the second is one
180        * of the user's account IDs (e.g. me@hotmail.com). */
181       label = g_strdup_printf (_("%s (%s)"),
182           folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
183
184       contact_item = gtk_image_menu_item_new_with_label (label);
185       gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (contact_item),
186                                                  TRUE);
187       contact_submenu = gtk_menu_new ();
188       gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
189       image = gtk_image_new_from_icon_name (
190           empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
191       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
192       gtk_widget_show (image);
193
194       /* Chat */
195       if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
196         {
197           action = empathy_individual_chat_menu_item_new (self,
198               single_individual);
199           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
200           gtk_widget_show (action);
201         }
202
203       /* SMS */
204       if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
205         {
206           action = empathy_individual_sms_menu_item_new (self,
207               single_individual);
208           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
209           gtk_widget_show (action);
210         }
211
212       if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
213         {
214           /* Audio Call */
215           action = empathy_individual_audio_call_menu_item_new (
216               self, single_individual);
217           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
218           gtk_widget_show (action);
219
220           /* Video Call */
221           action = empathy_individual_video_call_menu_item_new (
222               self, single_individual);
223           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
224           gtk_widget_show (action);
225         }
226
227       /* Log */
228       if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
229         {
230           action = empathy_individual_log_menu_item_new (single_individual);
231           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
232           gtk_widget_show (action);
233         }
234
235       /* Invite */
236       action = empathy_individual_invite_menu_item_new (NULL, contact);
237       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
238       gtk_widget_show (action);
239
240       /* File transfer */
241       if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
242         {
243           action = empathy_individual_file_transfer_menu_item_new (
244               self, single_individual);
245           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
246           gtk_widget_show (action);
247         }
248
249       /* Share my desktop */
250       action = empathy_individual_share_my_desktop_menu_item_new (
251           self, single_individual);
252       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
253       gtk_widget_show (action);
254
255       /* Block */
256       if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
257           (item = empathy_individiual_block_menu_item_new (single_individual))
258           != NULL) {
259         GtkWidget *sep;
260
261         sep = gtk_separator_menu_item_new ();
262         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), sep);
263         gtk_widget_show (sep);
264
265         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), item);
266         gtk_widget_show (item);
267       }
268
269       gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
270       gtk_widget_show (contact_item);
271
272       g_free (label);
273       g_object_unref (contact);
274       g_object_unref (single_individual);
275
276 while_finish:
277       g_clear_object (&persona);
278     }
279
280 out:
281   g_clear_object (&iter);
282 }
283
284 static void
285 empathy_individual_menu_init (EmpathyIndividualMenu *self)
286 {
287   EmpathyIndividualMenuPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
288       EMPATHY_TYPE_INDIVIDUAL_MENU, EmpathyIndividualMenuPriv);
289
290   self->priv = priv;
291 }
292
293 static GList *
294 find_phone_accounts (void)
295 {
296   TpAccountManager *am;
297   GList *accounts, *l;
298   GList *found = NULL;
299
300   am = tp_account_manager_dup ();
301   g_return_val_if_fail (am != NULL, NULL);
302
303   accounts = tp_account_manager_dup_valid_accounts (am);
304   for (l = accounts; l != NULL; l = g_list_next (l))
305     {
306       TpAccount *account = l->data;
307
308       if (tp_account_get_connection_status (account, NULL) !=
309           TP_CONNECTION_STATUS_CONNECTED)
310         continue;
311
312       if (!tp_account_associated_with_uri_scheme (account, "tel"))
313         continue;
314
315       found = g_list_prepend (found, g_object_ref (account));
316     }
317
318   g_list_free_full (accounts, g_object_unref);
319   g_object_unref (am);
320
321   return found;
322 }
323
324 static gboolean
325 has_phone_account (void)
326 {
327   GList *accounts;
328   gboolean result;
329
330   accounts = find_phone_accounts ();
331   result = (accounts != NULL);
332
333   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
334
335   return result;
336 }
337
338 static void
339 call_phone_number (FolksPhoneFieldDetails *details,
340     TpAccount *account)
341 {
342   gchar *number;
343
344   number = folks_phone_field_details_get_normalised (details);
345   DEBUG ("Try to call %s", number);
346
347   empathy_call_new_with_streams (number,
348       account, TRUE, FALSE, empathy_get_current_action_time ());
349
350   g_free (number);
351 }
352
353 static void
354 display_call_phone_dialog (FolksPhoneFieldDetails *details,
355     GList *accounts)
356 {
357   GtkWidget *dialog;
358   gint response;
359
360   dialog = empathy_account_selector_dialog_new (accounts);
361
362   gtk_window_set_title (GTK_WINDOW (dialog),
363       _("Select account to use to place the call"));
364
365   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
366       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
367       _("Call"), GTK_RESPONSE_OK,
368       NULL);
369
370   response = gtk_dialog_run (GTK_DIALOG (dialog));
371
372   if (response == GTK_RESPONSE_OK)
373     {
374       TpAccount *account;
375
376       account = empathy_account_selector_dialog_dup_selected (
377            EMPATHY_ACCOUNT_SELECTOR_DIALOG (dialog));
378
379       if (account != NULL)
380         {
381           call_phone_number (details, account);
382
383           g_object_unref (account);
384         }
385     }
386
387   gtk_widget_destroy (dialog);
388 }
389
390 static void
391 call_phone_number_cb (GtkMenuItem *item,
392       FolksPhoneFieldDetails *details)
393 {
394   GList *accounts;
395
396   accounts = find_phone_accounts ();
397   if (accounts == NULL)
398     {
399       DEBUG ("No phone aware account connected; can't call");
400     }
401   else if (g_list_length (accounts) == 1)
402     {
403       call_phone_number (details, accounts->data);
404     }
405   else
406     {
407       /* Ask which account to use */
408       display_call_phone_dialog (details, accounts);
409     }
410
411   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
412 }
413
414 static const gchar *
415 find_phone_type (FolksPhoneFieldDetails *details)
416 {
417   GeeCollection *types;
418   GeeIterator *iter;
419   const gchar *retval = NULL;
420
421   types = folks_abstract_field_details_get_parameter_values (
422       FOLKS_ABSTRACT_FIELD_DETAILS (details), "type");
423
424   if (types == NULL)
425     return NULL;
426
427   iter = gee_iterable_iterator (GEE_ITERABLE (types));
428   while (gee_iterator_next (iter))
429     {
430       gchar *type = gee_iterator_get (iter);
431
432       if (!tp_strdiff (type, "CELL"))
433         retval = _("Mobile");
434       else if (!tp_strdiff (type, "WORK"))
435         retval = _("Work");
436       else if (!tp_strdiff (type, "HOME"))
437         retval = _("HOME");
438
439       g_free (type);
440
441       if (retval != NULL)
442         break;
443     }
444
445   g_object_unref (iter);
446
447   return retval;
448 }
449
450 static void
451 add_phone_numbers (EmpathyIndividualMenu *self)
452 {
453   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
454   GeeSet *all_numbers;
455   GeeIterator *iter;
456   gboolean sensitive;
457
458   all_numbers = folks_phone_details_get_phone_numbers (
459       FOLKS_PHONE_DETAILS (priv->individual));
460
461   sensitive = has_phone_account ();
462
463   iter = gee_iterable_iterator (GEE_ITERABLE (all_numbers));
464   while (gee_iterator_next (iter))
465     {
466       FolksPhoneFieldDetails *details = gee_iterator_get (iter);
467       GtkWidget *item, *image;
468       gchar *tmp, *number;
469       const gchar *type;
470
471       type = find_phone_type (details);
472       number = folks_phone_field_details_get_normalised (details);
473
474       if (type != NULL)
475         {
476           /* translators: first argument is a phone number like +32123456 and
477            * the second one is something like 'home' or 'work'. */
478           tmp = g_strdup_printf (_("Call %s (%s)"), number, type);
479         }
480       else
481         {
482           /* translators: argument is a phone number like +32123456 */
483           tmp = g_strdup_printf (_("Call %s"), number);
484         }
485
486       g_free (number);
487
488       item = gtk_image_menu_item_new_with_mnemonic (tmp);
489       g_free (tmp);
490
491       g_signal_connect_data (item, "activate",
492           G_CALLBACK (call_phone_number_cb), g_object_ref (details),
493           (GClosureNotify) g_object_unref, 0);
494
495       gtk_widget_set_sensitive (item, sensitive);
496
497       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CALL,
498           GTK_ICON_SIZE_MENU);
499       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
500       gtk_widget_show (image);
501
502       gtk_menu_shell_append (GTK_MENU_SHELL (self), item);
503       gtk_widget_show (item);
504
505       g_object_unref (details);
506     }
507
508   g_object_unref (iter);
509 }
510
511 /* return a list of TpContact supporting the blocking iface */
512 static GList *
513 get_contacts_supporting_blocking (FolksIndividual *individual)
514 {
515   GeeSet *personas;
516   GeeIterator *iter;
517   GList *result = NULL;
518
519   personas = folks_individual_get_personas (individual);
520
521   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
522   while (gee_iterator_next (iter))
523     {
524       TpfPersona *persona = gee_iterator_get (iter);
525       TpContact *contact;
526       TpConnection *conn;
527
528       if (!TPF_IS_PERSONA (persona))
529         goto while_next;
530
531       contact = tpf_persona_get_contact (persona);
532       if (contact == NULL)
533         goto while_next;
534
535       conn = tp_contact_get_connection (contact);
536
537       if (tp_proxy_has_interface_by_id (conn,
538         TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
539         result = g_list_prepend (result, contact);
540
541 while_next:
542       g_clear_object (&persona);
543     }
544
545   g_clear_object (&iter);
546
547   return result;
548 }
549
550 typedef struct
551 {
552   gboolean blocked;
553   GtkWidget *parent;
554 } GotAvatarCtx;
555
556 static GotAvatarCtx *
557 got_avatar_ctx_new (gboolean blocked,
558     GtkWidget *parent)
559 {
560   GotAvatarCtx *ctx = g_slice_new0 (GotAvatarCtx);
561
562   ctx->blocked = blocked;
563   ctx->parent = parent != NULL ? g_object_ref (parent) : NULL;
564   return ctx;
565 }
566
567 static void
568 got_avatar_ctx_free (GotAvatarCtx *ctx)
569 {
570   g_clear_object (&ctx->parent);
571   g_slice_free (GotAvatarCtx, ctx);
572 }
573
574 static void
575 got_avatar (GObject *source_object,
576     GAsyncResult *result,
577     gpointer user_data)
578 {
579   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
580   GotAvatarCtx *ctx = user_data;
581   GdkPixbuf *avatar;
582   GError *error = NULL;
583   gboolean abusive = FALSE;
584   EmpathyIndividualManager *manager;
585
586   avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
587       result, &error);
588
589   if (error != NULL)
590     {
591       DEBUG ("Could not get avatar: %s", error->message);
592       g_error_free (error);
593     }
594
595   if (ctx->blocked) {
596     /* confirm the user really wishes to block the contact */
597     if (!empathy_block_individual_dialog_show (GTK_WINDOW (ctx->parent),
598           individual, avatar, &abusive))
599       goto out;
600   }
601
602   manager = empathy_individual_manager_dup_singleton ();
603
604   empathy_individual_manager_set_blocked (manager, individual,
605       ctx->blocked, abusive);
606
607   g_object_unref (manager);
608
609 out:
610   g_clear_object (&avatar);
611   got_avatar_ctx_free (ctx);
612 }
613
614 static void
615 empathy_individual_block_menu_item_toggled (GtkCheckMenuItem *item,
616     FolksIndividual *individual)
617 {
618   GotAvatarCtx *ctx;
619   gboolean blocked;
620   GtkWidget *parent;
621
622   /* @item may be destroyed while the async call is running to get the things
623    * we need from it right now. */
624   blocked = gtk_check_menu_item_get_active (item);
625
626   parent = g_object_get_data (
627     G_OBJECT (gtk_widget_get_parent (GTK_WIDGET (item))),
628     "window");
629
630   ctx = got_avatar_ctx_new (blocked, parent);
631
632   empathy_pixbuf_avatar_from_individual_scaled_async (individual,
633       48, 48, NULL, got_avatar, ctx);
634 }
635
636 static void
637 update_block_menu_item (GtkWidget *item,
638     FolksIndividual *individual)
639 {
640   GList *contacts, *l;
641   gboolean is_blocked = TRUE;
642
643   contacts = get_contacts_supporting_blocking (individual);
644
645   if (contacts == NULL)
646     is_blocked = FALSE;
647
648   /* Check the menu item if all his personas are blocked */
649   for (l = contacts; l != NULL; l = g_list_next (l))
650     {
651       TpContact *contact = l->data;
652
653       if (!tp_contact_is_blocked (contact))
654         {
655           is_blocked = FALSE;
656           break;
657         }
658     }
659
660   g_signal_handlers_block_by_func (item,
661       empathy_individual_block_menu_item_toggled, individual);
662
663   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), is_blocked);
664
665   g_signal_handlers_unblock_by_func (item,
666       empathy_individual_block_menu_item_toggled, individual);
667
668   g_list_free (contacts);
669 }
670
671 static void
672 contact_blocked_changed_cb (TpContact *contact,
673     GParamSpec *spec,
674     GtkWidget *item)
675 {
676   FolksIndividual *individual;
677
678   individual = g_object_get_data (G_OBJECT (item), "individual");
679
680   update_block_menu_item (item, individual);
681 }
682
683 static GtkWidget *
684 empathy_individiual_block_menu_item_new (FolksIndividual *individual)
685 {
686   GtkWidget *item;
687   GList *contacts, *l;
688
689   contacts = get_contacts_supporting_blocking (individual);
690
691   /* Can't block, no persona supports blocking */
692   if (contacts == NULL)
693     return NULL;
694
695   item = gtk_check_menu_item_new_with_mnemonic (_("_Block Contact"));
696
697   g_object_set_data_full (G_OBJECT (item), "individual",
698       g_object_ref (individual), g_object_unref);
699
700   for (l = contacts; l != NULL; l = g_list_next (l))
701     {
702       TpContact *contact = l->data;
703
704       tp_g_signal_connect_object (contact, "notify::is-blocked",
705           G_CALLBACK (contact_blocked_changed_cb), item, 0);
706     }
707
708   g_signal_connect (item, "toggled",
709       G_CALLBACK (empathy_individual_block_menu_item_toggled), individual);
710
711   update_block_menu_item (item, individual);
712
713   g_list_free (contacts);
714
715   return item;
716 }
717
718 enum
719 {
720   REMOVE_DIALOG_RESPONSE_CANCEL = 0,
721   REMOVE_DIALOG_RESPONSE_DELETE,
722   REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
723   REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP
724 };
725
726 static int
727 remove_dialog_show (const gchar *message,
728     const gchar *secondary_text,
729     gboolean show_remove_from_group,
730     gboolean block_button,
731     GdkPixbuf *avatar,
732     const gchar *active_group)
733 {
734   GtkWidget *dialog;
735   gboolean res;
736
737   dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
738       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
739
740   if (avatar != NULL)
741     {
742       GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
743       gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
744       gtk_widget_show (image);
745     }
746
747   if (show_remove_from_group)
748     {
749       GtkWidget *button;
750       gchar *button_text = g_strdup_printf (_("Remove from _Group \'%s\'"),
751           active_group);
752
753       /* gtk_dialog_add_button() doesn't allow us to pass a string with a
754        * mnemonic so we have to create the button manually. */
755       button = gtk_button_new_with_mnemonic (button_text);
756       g_free (button_text);
757
758       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
759           REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP);
760
761       gtk_widget_show (button);
762     }
763
764   if (block_button)
765     {
766       GtkWidget *button;
767
768       /* gtk_dialog_add_button() doesn't allow us to pass a string with a
769        * mnemonic so we have to create the button manually. */
770       button = gtk_button_new_with_mnemonic (
771           _("Delete and _Block"));
772
773       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
774           REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
775
776       gtk_widget_show (button);
777     }
778
779   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
780       GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
781       GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
782   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
783       "%s", secondary_text);
784
785   gtk_widget_show (dialog);
786
787   res = gtk_dialog_run (GTK_DIALOG (dialog));
788   gtk_widget_destroy (dialog);
789
790   return res;
791 }
792
793 static void
794 individual_removed_from_group_cb (GObject *source_object,
795     GAsyncResult *res,
796     gpointer user_data)
797 {
798   GError *error = NULL;
799   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
800
801   folks_group_details_change_group_finish (
802       FOLKS_GROUP_DETAILS (individual), res, &error);
803   if (error != NULL)
804     {
805       DEBUG ("Individual could not be removed from group: %s",
806           error->message);
807       g_error_free (error);
808     }
809 }
810
811 static void
812 remove_got_avatar (GObject *source_object,
813     GAsyncResult *result,
814     gpointer user_data)
815 {
816   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
817   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (user_data);
818   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
819   GdkPixbuf *avatar;
820   EmpathyIndividualManager *manager;
821   gchar *text;
822   GeeSet *personas;
823   guint persona_count = 0;
824   gboolean can_block;
825   GError *error = NULL;
826   gint res;
827   gboolean show_remove_from_group;
828   GeeSet *groups;
829
830   avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
831       result, &error);
832
833   if (error != NULL)
834     {
835       DEBUG ("Could not get avatar: %s", error->message);
836       g_error_free (error);
837     }
838
839   /* We couldn't retrieve the avatar, but that isn't a fatal error,
840    * so we still display the remove dialog. */
841
842   groups = folks_group_details_get_groups (FOLKS_GROUP_DETAILS (individual));
843   show_remove_from_group =
844       gee_collection_get_size (GEE_COLLECTION (groups)) > 1;
845
846   personas = folks_individual_get_personas (individual);
847
848   persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
849
850   /* If we have more than one TpfPersona, display a different message
851    * ensuring the user knows that *all* of the meta-contacts' personas will
852    * be removed. */
853
854   if (persona_count < 2)
855     {
856       /* Not a meta-contact */
857       text =
858           g_strdup_printf (
859               _("Do you really want to remove the contact '%s'?"),
860               folks_alias_details_get_alias (
861                   FOLKS_ALIAS_DETAILS (individual)));
862     }
863   else
864     {
865       /* Meta-contact */
866       text =
867           g_strdup_printf (
868               _("Do you really want to remove the linked contact '%s'? "
869                 "Note that this will remove all the contacts which make up "
870                 "this linked contact."),
871               folks_alias_details_get_alias (
872                   FOLKS_ALIAS_DETAILS (individual)));
873     }
874
875
876   manager = empathy_individual_manager_dup_singleton ();
877   can_block = empathy_individual_manager_supports_blocking (manager,
878       individual);
879   res = remove_dialog_show (_("Removing contact"), text,
880       show_remove_from_group, can_block, avatar, priv->active_group);
881
882   if (res == REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP)
883     {
884       folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
885           priv->active_group, false, individual_removed_from_group_cb, NULL);
886       goto finally;
887     }
888
889   if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
890       res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
891     {
892       gboolean abusive;
893
894       if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
895         {
896           if (!empathy_block_individual_dialog_show (NULL, individual,
897                 avatar, &abusive))
898             goto finally;
899
900           empathy_individual_manager_set_blocked (manager, individual,
901               TRUE, abusive);
902         }
903
904       empathy_individual_manager_remove (manager, individual, "");
905     }
906
907  finally:
908   g_free (text);
909   g_object_unref (manager);
910   g_object_unref (self);
911 }
912
913 static void
914 remove_activate_cb (GtkMenuItem *menuitem,
915     EmpathyIndividualMenu *self)
916 {
917   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
918
919   empathy_pixbuf_avatar_from_individual_scaled_async (priv->individual,
920       48, 48, NULL, remove_got_avatar, g_object_ref (self));
921 }
922
923 static GtkWidget *
924 empathy_individiual_remove_menu_item_new (EmpathyIndividualMenu *self)
925 {
926   GeeSet *personas;
927   GeeIterator *iter;
928   gboolean can_remove = FALSE;
929   GtkWidget *item, *image;
930   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
931
932   /* If any of the Individual's personas can be removed, add an option to
933    * remove. This will act as a best-effort option. If any Personas cannot be
934    * removed from the server, then this option will just be inactive upon
935    * subsequent menu openings */
936   personas = folks_individual_get_personas (priv->individual);
937   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
938   while (!can_remove && gee_iterator_next (iter))
939     {
940       FolksPersona *persona = gee_iterator_get (iter);
941       FolksPersonaStore *store = folks_persona_get_store (persona);
942       FolksMaybeBool maybe_can_remove =
943           folks_persona_store_get_can_remove_personas (store);
944
945       if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
946         can_remove = TRUE;
947
948       g_clear_object (&persona);
949     }
950   g_clear_object (&iter);
951
952   if (!can_remove)
953     return NULL;
954
955   item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
956   image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
957       GTK_ICON_SIZE_MENU);
958   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
959
960   g_signal_connect (item, "activate",
961       G_CALLBACK (remove_activate_cb), self);
962
963   return item;
964 }
965
966 static void
967 constructed (GObject *object)
968 {
969   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (object);
970   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
971   GtkMenuShell *shell;
972   GtkWidget *item;
973   FolksIndividual *individual;
974   EmpathyIndividualFeatureFlags features;
975
976   /* Build the menu */
977   shell = GTK_MENU_SHELL (object);
978   individual = priv->individual;
979   features = priv->features;
980
981   /* Add contact */
982   if (features & EMPATHY_INDIVIDUAL_FEATURE_ADD_CONTACT)
983     {
984       item = empathy_individual_add_menu_item_new (self, individual);
985       if (item != NULL)
986         {
987           gtk_menu_shell_append (GTK_MENU_SHELL (shell), item);
988           gtk_widget_show (item);
989         }
990     }
991
992   /* Chat */
993   if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
994     {
995       item = empathy_individual_chat_menu_item_new (self, individual);
996       if (item != NULL)
997         {
998           gtk_menu_shell_append (shell, item);
999           gtk_widget_show (item);
1000         }
1001     }
1002
1003   /* SMS */
1004   if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
1005     {
1006       item = empathy_individual_sms_menu_item_new (self, individual);
1007       if (item != NULL)
1008         {
1009           gtk_menu_shell_append (shell, item);
1010           gtk_widget_show (item);
1011         }
1012     }
1013
1014   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
1015     {
1016       /* Audio Call */
1017       item = empathy_individual_audio_call_menu_item_new (self, individual);
1018       gtk_menu_shell_append (shell, item);
1019       gtk_widget_show (item);
1020
1021       /* Video Call */
1022       item = empathy_individual_video_call_menu_item_new (self, individual);
1023       gtk_menu_shell_append (shell, item);
1024       gtk_widget_show (item);
1025     }
1026
1027   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE)
1028     add_phone_numbers (self);
1029
1030   /* Invite */
1031   item = empathy_individual_invite_menu_item_new (individual, NULL);
1032   gtk_menu_shell_append (shell, item);
1033   gtk_widget_show (item);
1034
1035   /* File transfer */
1036   if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
1037     {
1038       item = empathy_individual_file_transfer_menu_item_new (self, individual);
1039       gtk_menu_shell_append (shell, item);
1040       gtk_widget_show (item);
1041     }
1042
1043   /* Share my desktop */
1044   /* FIXME we should add the "Share my desktop" menu item if Vino is
1045   a registered handler in MC5 */
1046   item = empathy_individual_share_my_desktop_menu_item_new (self, individual);
1047   gtk_menu_shell_append (shell, item);
1048   gtk_widget_show (item);
1049
1050   /* Menu items to target specific contacts */
1051   individual_menu_add_personas (self, GTK_MENU_SHELL (object),
1052       individual, features);
1053
1054   /* Separator */
1055   if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1056       EMPATHY_INDIVIDUAL_FEATURE_INFO |
1057       EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE))
1058     {
1059       item = gtk_separator_menu_item_new ();
1060       gtk_menu_shell_append (shell, item);
1061       gtk_widget_show (item);
1062     }
1063
1064   /* Edit */
1065   if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
1066     {
1067       item = empathy_individual_edit_menu_item_new (individual);
1068       gtk_menu_shell_append (shell, item);
1069       gtk_widget_show (item);
1070     }
1071
1072   /* Log */
1073   if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
1074     {
1075       item = empathy_individual_log_menu_item_new (individual);
1076       gtk_menu_shell_append (shell, item);
1077       gtk_widget_show (item);
1078     }
1079
1080   /* Info */
1081   if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
1082     {
1083       item = empathy_individual_info_menu_item_new (individual);
1084       gtk_menu_shell_append (shell, item);
1085       gtk_widget_show (item);
1086     }
1087
1088   /* Favorite checkbox */
1089   if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
1090     {
1091       item = empathy_individual_favourite_menu_item_new (individual);
1092       gtk_menu_shell_append (shell, item);
1093       gtk_widget_show (item);
1094     }
1095
1096   /* Separator & Block */
1097   if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
1098       (item = empathy_individiual_block_menu_item_new (individual)) != NULL) {
1099     GtkWidget *sep;
1100
1101     sep = gtk_separator_menu_item_new ();
1102     gtk_menu_shell_append (shell, sep);
1103     gtk_widget_show (sep);
1104
1105     gtk_menu_shell_append (shell, item);
1106     gtk_widget_show (item);
1107   }
1108
1109   /* Separator & Remove */
1110   if (features & EMPATHY_INDIVIDUAL_FEATURE_REMOVE &&
1111       (item = empathy_individiual_remove_menu_item_new (self)) != NULL) {
1112     GtkWidget *sep;
1113
1114     sep = gtk_separator_menu_item_new ();
1115     gtk_menu_shell_append (shell, sep);
1116     gtk_widget_show (sep);
1117
1118     gtk_menu_shell_append (shell, item);
1119     gtk_widget_show (item);
1120   }
1121 }
1122
1123 static void
1124 get_property (GObject *object,
1125     guint param_id,
1126     GValue *value,
1127     GParamSpec *pspec)
1128 {
1129   EmpathyIndividualMenuPriv *priv;
1130
1131   priv = GET_PRIV (object);
1132
1133   switch (param_id)
1134     {
1135       case PROP_ACTIVE_GROUP:
1136         g_value_set_string (value, priv->active_group);
1137         break;
1138       case PROP_INDIVIDUAL:
1139         g_value_set_object (value, priv->individual);
1140         break;
1141       case PROP_FEATURES:
1142         g_value_set_flags (value, priv->features);
1143         break;
1144       case PROP_STORE:
1145         g_value_set_object (value, priv->store);
1146         break;
1147       default:
1148         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1149         break;
1150     }
1151 }
1152
1153 static void
1154 set_property (GObject *object,
1155     guint param_id,
1156     const GValue *value,
1157     GParamSpec *pspec)
1158 {
1159   EmpathyIndividualMenuPriv *priv;
1160
1161   priv = GET_PRIV (object);
1162
1163   switch (param_id)
1164     {
1165       case PROP_ACTIVE_GROUP:
1166         g_assert (priv->active_group == NULL); /* construct only */
1167         priv->active_group = g_value_dup_string (value);
1168         break;
1169       case PROP_INDIVIDUAL:
1170         priv->individual = g_value_dup_object (value);
1171         break;
1172       case PROP_FEATURES:
1173         priv->features = g_value_get_flags (value);
1174         break;
1175       case PROP_STORE:
1176         priv->store = g_value_dup_object (value); /* read only */
1177         break;
1178       default:
1179         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1180         break;
1181     }
1182 }
1183
1184 static void
1185 dispose (GObject *object)
1186 {
1187   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1188
1189   tp_clear_object (&priv->individual);
1190   tp_clear_object (&priv->store);
1191
1192   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
1193 }
1194
1195 static void
1196 finalize (GObject *object)
1197 {
1198   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1199
1200   g_free (priv->active_group);
1201
1202   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->finalize (object);
1203 }
1204
1205 static void
1206 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
1207 {
1208   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1209
1210   object_class->constructed = constructed;
1211   object_class->get_property = get_property;
1212   object_class->set_property = set_property;
1213   object_class->dispose = dispose;
1214   object_class->finalize = finalize;
1215
1216   /**
1217    * gchar *:active-group:
1218    *
1219    * The group the selected roster-contact widget belongs, or NULL.
1220    */
1221   g_object_class_install_property (object_class, PROP_ACTIVE_GROUP,
1222       g_param_spec_string ("active-group",
1223           "Active group",
1224           "The group the selected roster-contact widget belongs, or NULL",
1225           NULL,
1226           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1227
1228   /**
1229    * EmpathyIndividualMenu:individual:
1230    *
1231    * The #FolksIndividual the menu is for.
1232    */
1233   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
1234       g_param_spec_object ("individual",
1235           "Individual",
1236           "The #FolksIndividual the menu is for.",
1237           FOLKS_TYPE_INDIVIDUAL,
1238           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1239
1240   /**
1241    * EmpathyIndividualMenu:features:
1242    *
1243    * A set of feature flags controlling which entries are shown.
1244    */
1245   g_object_class_install_property (object_class, PROP_FEATURES,
1246       g_param_spec_flags ("features",
1247           "Features",
1248           "A set of feature flags controlling which entries are shown.",
1249           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1250           EMPATHY_INDIVIDUAL_FEATURE_NONE,
1251           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1252
1253   g_object_class_install_property (object_class, PROP_STORE,
1254       g_param_spec_object ("store",
1255           "Store",
1256           "The EmpathyIndividualStore to use to get contact owner",
1257           EMPATHY_TYPE_INDIVIDUAL_STORE,
1258           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1259
1260   signals[MENU_ITEM_ACTIVATED] =
1261       g_signal_new ("menu-item-activated",
1262           G_TYPE_FROM_CLASS (klass),
1263           G_SIGNAL_RUN_LAST,
1264           0,
1265           NULL, NULL,
1266           g_cclosure_marshal_generic,
1267           G_TYPE_NONE,
1268           0);
1269
1270   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
1271 }
1272
1273 GtkWidget *
1274 empathy_individual_menu_new (FolksIndividual *individual,
1275     const gchar *active_group,
1276     EmpathyIndividualFeatureFlags features,
1277     EmpathyIndividualStore *store)
1278 {
1279   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1280   g_return_val_if_fail (store == NULL ||
1281       EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1282   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
1283
1284   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
1285       "active-group", active_group,
1286       "individual", individual,
1287       "features", features,
1288       "store", store,
1289       NULL);
1290 }
1291
1292 /* Like menu_item_set_first_contact(), but always operates upon the given
1293  * contact. If the contact is non-NULL, it is assumed that the menu entry should
1294  * be sensitive. */
1295 static gboolean
1296 menu_item_set_contact (GtkWidget *item,
1297     EmpathyContact *contact,
1298     GCallback activate_callback,
1299     EmpathyActionType action_type)
1300 {
1301   gboolean can_do_action = FALSE;
1302
1303   if (contact != NULL)
1304     can_do_action = empathy_contact_can_do_action (contact, action_type);
1305   gtk_widget_set_sensitive (item, can_do_action);
1306
1307   if (can_do_action == TRUE)
1308     {
1309       /* We want to make sure that the EmpathyContact stays alive while the
1310        * signal is connected. */
1311       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
1312           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
1313     }
1314
1315   return can_do_action;
1316 }
1317
1318 /**
1319  * Set the given menu @item to call @activate_callback using the TpContact
1320  * (associated with @individual) with the highest availability who is also valid
1321  * whenever @item is activated.
1322  *
1323  * @action_type is the type of action performed by the menu entry; this is used
1324  * so that only contacts which can perform that action (e.g. are capable of
1325  * receiving video calls) are selected, as appropriate.
1326  */
1327 static GtkWidget *
1328 menu_item_set_first_contact (GtkWidget *item,
1329     FolksIndividual *individual,
1330     GCallback activate_callback,
1331     EmpathyActionType action_type)
1332 {
1333   EmpathyContact *best_contact;
1334
1335   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
1336   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
1337       action_type);
1338   tp_clear_object (&best_contact);
1339
1340   return item;
1341 }
1342
1343 static void
1344 emit_menu_item_activated (GtkMenuItem *item)
1345 {
1346   EmpathyIndividualMenu *self;
1347
1348   self = EMPATHY_INDIVIDUAL_MENU (g_object_get_data (G_OBJECT (item),
1349       "individual-menu"));
1350   g_signal_emit (self, signals [MENU_ITEM_ACTIVATED], 0);
1351 }
1352
1353 static void
1354 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
1355   EmpathyContact *contact)
1356 {
1357   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1358
1359   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
1360
1361   emit_menu_item_activated (item);
1362 }
1363
1364 static GtkWidget *
1365 empathy_individual_chat_menu_item_new (EmpathyIndividualMenu *self,
1366     FolksIndividual *individual)
1367 {
1368   GtkWidget *item;
1369   GtkWidget *image;
1370
1371   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1372       empathy_folks_individual_contains_contact (individual), NULL);
1373
1374   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
1375   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
1376       GTK_ICON_SIZE_MENU);
1377   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1378   gtk_widget_show (image);
1379
1380   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1381   menu_item_set_first_contact (item, individual,
1382       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1383       EMPATHY_ACTION_CHAT);
1384
1385   return item;
1386 }
1387
1388 static void
1389 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
1390   EmpathyContact *contact)
1391 {
1392   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1393
1394   empathy_sms_contact_id (
1395       empathy_contact_get_account (contact),
1396       empathy_contact_get_id (contact),
1397       empathy_get_current_action_time (),
1398       NULL, NULL);
1399
1400   emit_menu_item_activated (item);
1401 }
1402
1403 static GtkWidget *
1404 empathy_individual_sms_menu_item_new (EmpathyIndividualMenu *self,
1405     FolksIndividual *individual)
1406 {
1407   GtkWidget *item;
1408   GtkWidget *image;
1409
1410   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1411       empathy_folks_individual_contains_contact (individual), NULL);
1412
1413   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
1414   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
1415       GTK_ICON_SIZE_MENU);
1416   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1417   gtk_widget_show (image);
1418
1419   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1420   menu_item_set_first_contact (item, individual,
1421       G_CALLBACK (empathy_individual_sms_menu_item_activated),
1422       EMPATHY_ACTION_SMS);
1423
1424   return item;
1425 }
1426
1427 static void
1428 empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
1429   EmpathyContact *contact)
1430 {
1431   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1432
1433   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1434       empathy_contact_get_account (contact),
1435       TRUE, FALSE,
1436       empathy_get_current_action_time ());
1437
1438   emit_menu_item_activated (item);
1439 }
1440
1441 GtkWidget *
1442 empathy_individual_audio_call_menu_item_new (EmpathyIndividualMenu *self,
1443     FolksIndividual *individual)
1444 {
1445   GtkWidget *item;
1446   GtkWidget *image;
1447
1448   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1449
1450   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
1451   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1452   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1453   gtk_widget_show (image);
1454
1455   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1456   menu_item_set_first_contact (item, individual,
1457       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1458       EMPATHY_ACTION_AUDIO_CALL);
1459
1460   return item;
1461 }
1462
1463 static void
1464 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1465   EmpathyContact *contact)
1466 {
1467   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1468
1469   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1470       empathy_contact_get_account (contact),
1471       TRUE, TRUE,
1472       empathy_get_current_action_time ());
1473
1474   emit_menu_item_activated (item);
1475 }
1476
1477 GtkWidget *
1478 empathy_individual_video_call_menu_item_new (EmpathyIndividualMenu *self,
1479     FolksIndividual *individual)
1480 {
1481   GtkWidget *item;
1482   GtkWidget *image;
1483   TpawCameraMonitor *monitor;
1484
1485   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1486
1487   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1488   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1489       GTK_ICON_SIZE_MENU);
1490   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1491   gtk_widget_show (image);
1492
1493   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1494   menu_item_set_first_contact (item, individual,
1495       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1496       EMPATHY_ACTION_VIDEO_CALL);
1497
1498   /* Only follow available cameras if the contact can do Video calls */
1499   if (gtk_widget_get_sensitive (item))
1500     {
1501       monitor = tpaw_camera_monitor_dup_singleton ();
1502       g_object_set_data_full (G_OBJECT (item),
1503           "monitor", monitor, g_object_unref);
1504       g_object_bind_property (monitor, "available", item, "sensitive",
1505           G_BINDING_SYNC_CREATE);
1506     }
1507
1508   return item;
1509 }
1510
1511 static void
1512 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1513   EmpathyContact *contact)
1514 {
1515   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1516
1517   empathy_log_window_show (empathy_contact_get_account (contact),
1518       empathy_contact_get_id (contact), FALSE, NULL);
1519 }
1520
1521 static GtkWidget *
1522 empathy_individual_log_menu_item_new (FolksIndividual *individual)
1523 {
1524   GtkWidget *item;
1525   GtkWidget *image;
1526
1527   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1528
1529   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1530   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1531   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1532   gtk_widget_show (image);
1533
1534   menu_item_set_first_contact (item, individual,
1535       G_CALLBACK (empathy_individual_log_menu_item_activated),
1536       EMPATHY_ACTION_VIEW_LOGS);
1537
1538   return item;
1539 }
1540
1541 static void
1542 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1543     EmpathyContact *contact)
1544 {
1545   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1546
1547   empathy_send_file_with_file_chooser (contact);
1548
1549   emit_menu_item_activated (item);
1550 }
1551
1552 static GtkWidget *
1553 empathy_individual_file_transfer_menu_item_new (EmpathyIndividualMenu *self,
1554     FolksIndividual *individual)
1555 {
1556   GtkWidget *item;
1557   GtkWidget *image;
1558
1559   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1560
1561   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1562   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1563       GTK_ICON_SIZE_MENU);
1564   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1565   gtk_widget_show (image);
1566
1567   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1568   menu_item_set_first_contact (item, individual,
1569       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1570       EMPATHY_ACTION_SEND_FILE);
1571
1572   return item;
1573 }
1574
1575 static void
1576 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1577     EmpathyContact *contact)
1578 {
1579   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1580
1581   empathy_share_my_desktop_share_with_contact (contact);
1582
1583   emit_menu_item_activated (item);
1584 }
1585
1586 static GtkWidget *
1587 empathy_individual_share_my_desktop_menu_item_new (EmpathyIndividualMenu *self,
1588     FolksIndividual *individual)
1589 {
1590   GtkWidget *item;
1591   GtkWidget *image;
1592
1593   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1594
1595   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1596   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1597   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1598   gtk_widget_show (image);
1599
1600   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1601   menu_item_set_first_contact (item, individual,
1602       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1603       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1604
1605   return item;
1606 }
1607
1608 static void
1609 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1610   FolksIndividual *individual)
1611 {
1612   folks_favourite_details_set_is_favourite (
1613       FOLKS_FAVOURITE_DETAILS (individual),
1614       gtk_check_menu_item_get_active (item));
1615 }
1616
1617 static GtkWidget *
1618 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
1619 {
1620   GtkWidget *item;
1621
1622   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1623
1624   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1625       folks_favourite_details_get_is_favourite (
1626           FOLKS_FAVOURITE_DETAILS (individual)));
1627
1628   g_signal_connect (item, "toggled",
1629       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1630
1631   return item;
1632 }
1633
1634 static void
1635 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1636     FolksIndividual *individual)
1637 {
1638   empathy_display_individual_info (individual);
1639 }
1640
1641 static GtkWidget *
1642 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1643 {
1644   GtkWidget *item;
1645   GtkWidget *image;
1646
1647   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1648   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1649       NULL);
1650
1651   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1652   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1653                 GTK_ICON_SIZE_MENU);
1654   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1655   gtk_widget_show (image);
1656
1657   g_signal_connect (item, "activate",
1658           G_CALLBACK (individual_info_menu_item_activate_cb),
1659           individual);
1660
1661   return item;
1662 }
1663
1664 static void
1665 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1666 {
1667   empathy_individual_edit_dialog_show (individual, NULL);
1668 }
1669
1670 static GtkWidget *
1671 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1672 {
1673   EmpathyIndividualManager *manager;
1674   GtkWidget *item;
1675   GtkWidget *image;
1676   gboolean enable = FALSE;
1677   EmpathyContact *contact;
1678
1679   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1680
1681   contact = empathy_contact_dup_from_folks_individual (individual);
1682
1683   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1684
1685   if (empathy_individual_manager_initialized ())
1686     {
1687       TpConnection *connection;
1688
1689       manager = empathy_individual_manager_dup_singleton ();
1690       connection = empathy_contact_get_connection (contact);
1691
1692       enable = (empathy_connection_can_alias_personas (connection,
1693                                                        individual) &&
1694                 empathy_connection_can_group_personas (connection, individual));
1695
1696       g_object_unref (manager);
1697     }
1698
1699   item = gtk_image_menu_item_new_with_mnemonic (
1700       C_("Edit individual (contextual menu)", "_Edit"));
1701   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1702   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1703   gtk_widget_show (image);
1704
1705   gtk_widget_set_sensitive (item, enable);
1706
1707   g_signal_connect_swapped (item, "activate",
1708       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1709
1710   g_object_unref (contact);
1711
1712   return item;
1713 }
1714
1715 typedef struct
1716 {
1717   FolksIndividual *individual;
1718   EmpathyContact *contact;
1719   EmpathyChatroom *chatroom;
1720 } RoomSubMenuData;
1721
1722 static RoomSubMenuData *
1723 room_sub_menu_data_new (FolksIndividual *individual,
1724     EmpathyContact *contact,
1725     EmpathyChatroom *chatroom)
1726 {
1727   RoomSubMenuData *data;
1728
1729   data = g_slice_new0 (RoomSubMenuData);
1730   if (individual != NULL)
1731     data->individual = g_object_ref (individual);
1732   if (contact != NULL)
1733     data->contact = g_object_ref (contact);
1734   data->chatroom = g_object_ref (chatroom);
1735
1736   return data;
1737 }
1738
1739 static void
1740 room_sub_menu_data_free (RoomSubMenuData *data)
1741 {
1742   tp_clear_object (&data->individual);
1743   tp_clear_object (&data->contact);
1744   g_object_unref (data->chatroom);
1745   g_slice_free (RoomSubMenuData, data);
1746 }
1747
1748 static void
1749 room_sub_menu_activate_cb (GtkWidget *item,
1750          RoomSubMenuData *data)
1751 {
1752   EmpathyTpChat *chat;
1753   EmpathyChatroomManager *mgr;
1754   EmpathyContact *contact = NULL;
1755
1756   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1757   if (chat == NULL)
1758     {
1759       /* channel was invalidated. Ignoring */
1760       return;
1761     }
1762
1763   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1764
1765   if (data->contact != NULL)
1766     contact = g_object_ref (data->contact);
1767   else
1768     {
1769       GeeSet *personas;
1770       GeeIterator *iter;
1771
1772       /* find the first of this Individual's contacts who can join this room */
1773       personas = folks_individual_get_personas (data->individual);
1774
1775       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1776       while (gee_iterator_next (iter) && (contact == NULL))
1777         {
1778           TpfPersona *persona = gee_iterator_get (iter);
1779           TpContact *tp_contact;
1780           GList *rooms;
1781
1782           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1783             {
1784               tp_contact = tpf_persona_get_contact (persona);
1785               if (tp_contact != NULL)
1786                 {
1787                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1788
1789                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1790                       empathy_contact_get_account (contact));
1791
1792                   if (g_list_find (rooms, data->chatroom) == NULL)
1793                     g_clear_object (&contact);
1794
1795                   /* if contact != NULL here, we've found our match */
1796
1797                   g_list_free (rooms);
1798                 }
1799             }
1800           g_clear_object (&persona);
1801         }
1802       g_clear_object (&iter);
1803     }
1804
1805   g_object_unref (mgr);
1806
1807   if (contact == NULL)
1808     {
1809       /* contact disappeared. Ignoring */
1810       goto out;
1811     }
1812
1813   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1814
1815   /* send invitation */
1816   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
1817
1818 out:
1819   g_object_unref (contact);
1820 }
1821
1822 static GtkWidget *
1823 create_room_sub_menu (FolksIndividual *individual,
1824                       EmpathyContact *contact,
1825                       EmpathyChatroom *chatroom)
1826 {
1827   GtkWidget *item;
1828   RoomSubMenuData *data;
1829
1830   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1831   data = room_sub_menu_data_new (individual, contact, chatroom);
1832   g_signal_connect_data (item, "activate",
1833       G_CALLBACK (room_sub_menu_activate_cb), data,
1834       (GClosureNotify) room_sub_menu_data_free, 0);
1835
1836   return item;
1837 }
1838
1839 static GtkWidget *
1840 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1841     EmpathyContact *contact)
1842 {
1843   GtkWidget *item;
1844   GtkWidget *image;
1845   GtkWidget *room_item;
1846   EmpathyChatroomManager *mgr;
1847   GList *rooms = NULL;
1848   GList *names = NULL;
1849   GList *l;
1850   GtkWidget *submenu = NULL;
1851   /* map of chat room names to their objects; just a utility to remove
1852    * duplicates and to make construction of the alphabetized list easier */
1853   GHashTable *name_room_map;
1854
1855   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1856       EMPATHY_IS_CONTACT (contact),
1857       NULL);
1858
1859   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1860       g_object_unref);
1861
1862   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1863   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1864       GTK_ICON_SIZE_MENU);
1865   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1866
1867   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1868
1869   if (contact != NULL)
1870     {
1871       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1872           empathy_contact_get_account (contact));
1873     }
1874   else
1875     {
1876       GeeSet *personas;
1877       GeeIterator *iter;
1878
1879       /* find the first of this Individual's contacts who can join this room */
1880       personas = folks_individual_get_personas (individual);
1881       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1882       while (gee_iterator_next (iter))
1883         {
1884           TpfPersona *persona = gee_iterator_get (iter);
1885           GList *rooms_cur;
1886           TpContact *tp_contact;
1887           EmpathyContact *contact_cur;
1888
1889           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1890             {
1891               tp_contact = tpf_persona_get_contact (persona);
1892               if (tp_contact != NULL)
1893                 {
1894                   contact_cur = empathy_contact_dup_from_tp_contact (
1895                       tp_contact);
1896
1897                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1898                       empathy_contact_get_account (contact_cur));
1899                   rooms = g_list_concat (rooms, rooms_cur);
1900
1901                   g_object_unref (contact_cur);
1902                 }
1903             }
1904           g_clear_object (&persona);
1905         }
1906       g_clear_object (&iter);
1907     }
1908
1909   /* alphabetize the rooms */
1910   for (l = rooms; l != NULL; l = g_list_next (l))
1911     {
1912       EmpathyChatroom *chatroom = l->data;
1913       gboolean existed;
1914
1915       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1916         {
1917           const gchar *name;
1918
1919           name = empathy_chatroom_get_name (chatroom);
1920           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1921           g_hash_table_insert (name_room_map, (gpointer) name,
1922               g_object_ref (chatroom));
1923
1924           /* this will take care of duplicates in rooms */
1925           if (!existed)
1926             {
1927               names = g_list_insert_sorted (names, (gpointer) name,
1928                   (GCompareFunc) g_strcmp0);
1929             }
1930         }
1931     }
1932
1933   for (l = names; l != NULL; l = g_list_next (l))
1934     {
1935       const gchar *name = l->data;
1936       EmpathyChatroom *chatroom;
1937
1938       if (G_UNLIKELY (submenu == NULL))
1939         submenu = gtk_menu_new ();
1940
1941       chatroom = g_hash_table_lookup (name_room_map, name);
1942       room_item = create_room_sub_menu (individual, contact, chatroom);
1943       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1944       gtk_widget_show (room_item);
1945     }
1946
1947   if (submenu)
1948     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1949   else
1950     gtk_widget_set_sensitive (item, FALSE);
1951
1952   gtk_widget_show (image);
1953
1954   g_hash_table_unref (name_room_map);
1955   g_object_unref (mgr);
1956   g_list_free (names);
1957   g_list_free (rooms);
1958
1959   return item;
1960 }
1961
1962 static void
1963 add_menu_item_activated (GtkMenuItem *item,
1964     TpContact *tp_contact)
1965 {
1966   GtkWidget *toplevel;
1967   FolksIndividual *individual;
1968
1969   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1970   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1971     toplevel = NULL;
1972
1973   individual = empathy_ensure_individual_from_tp_contact (tp_contact);
1974
1975   empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
1976       individual);
1977
1978   g_object_unref (individual);
1979 }
1980
1981 static GtkWidget *
1982 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1983     FolksIndividual *individual)
1984 {
1985   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1986   GtkWidget *item, *image;
1987   GeeSet *personas;
1988   GeeIterator *iter;
1989   TpContact *to_add = NULL;
1990
1991   /* find the first of this Individual's personas which are not in our contact
1992    * list. */
1993   personas = folks_individual_get_personas (individual);
1994   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1995   while (gee_iterator_next (iter))
1996     {
1997       TpfPersona *persona = gee_iterator_get (iter);
1998       TpContact *contact;
1999       TpConnection *conn;
2000
2001       if (!TPF_IS_PERSONA (persona))
2002         goto next;
2003
2004       contact = tpf_persona_get_contact (persona);
2005       if (contact == NULL)
2006         goto next;
2007
2008       /* be sure to use a not channel specific contact.
2009        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
2010       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
2011         {
2012           TpChannel *channel;
2013           TpChannelGroupFlags flags;
2014
2015           channel = empathy_individual_store_channel_get_channel (
2016               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
2017
2018           flags = tp_channel_group_get_flags (channel);
2019           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
2020             {
2021               /* Channel uses channel specific handles (thanks XMPP...) */
2022               contact = tp_channel_group_get_contact_owner (channel, contact);
2023
2024               /* If we don't know the owner, we can't add the contact */
2025               if (contact == NULL)
2026                 goto next;
2027             }
2028         }
2029
2030       conn = tp_contact_get_connection (contact);
2031       if (conn == NULL)
2032         goto next;
2033
2034       /* No point to try adding a contact if the CM doesn't support it */
2035       if (!tp_connection_get_can_change_contact_list (conn))
2036         goto next;
2037
2038       /* Can't add ourself */
2039       if (tp_connection_get_self_contact (conn) == contact)
2040         goto next;
2041
2042       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
2043         goto next;
2044
2045       g_object_unref (persona);
2046       to_add = contact;
2047       break;
2048
2049 next:
2050       g_object_unref (persona);
2051     }
2052
2053   g_object_unref (iter);
2054
2055   if (to_add == NULL)
2056     return NULL;
2057
2058   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
2059   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
2060   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2061
2062   g_signal_connect_data (item, "activate",
2063       G_CALLBACK (add_menu_item_activated),
2064       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
2065
2066   return item;
2067 }