]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
Be more compatible with Facebook emoticon codes
[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, 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       FALSE, empathy_get_current_action_time ());
1436
1437   emit_menu_item_activated (item);
1438 }
1439
1440 GtkWidget *
1441 empathy_individual_audio_call_menu_item_new (EmpathyIndividualMenu *self,
1442     FolksIndividual *individual)
1443 {
1444   GtkWidget *item;
1445   GtkWidget *image;
1446
1447   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1448
1449   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
1450   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1451   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1452   gtk_widget_show (image);
1453
1454   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1455   menu_item_set_first_contact (item, individual,
1456       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1457       EMPATHY_ACTION_AUDIO_CALL);
1458
1459   return item;
1460 }
1461
1462 static void
1463 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1464   EmpathyContact *contact)
1465 {
1466   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1467
1468   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1469       empathy_contact_get_account (contact),
1470       TRUE, empathy_get_current_action_time ());
1471
1472   emit_menu_item_activated (item);
1473 }
1474
1475 GtkWidget *
1476 empathy_individual_video_call_menu_item_new (EmpathyIndividualMenu *self,
1477     FolksIndividual *individual)
1478 {
1479   GtkWidget *item;
1480   GtkWidget *image;
1481   TpawCameraMonitor *monitor;
1482
1483   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1484
1485   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1486   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1487       GTK_ICON_SIZE_MENU);
1488   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1489   gtk_widget_show (image);
1490
1491   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1492   menu_item_set_first_contact (item, individual,
1493       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1494       EMPATHY_ACTION_VIDEO_CALL);
1495
1496   /* Only follow available cameras if the contact can do Video calls */
1497   if (gtk_widget_get_sensitive (item))
1498     {
1499       monitor = tpaw_camera_monitor_dup_singleton ();
1500       g_object_set_data_full (G_OBJECT (item),
1501           "monitor", monitor, g_object_unref);
1502       g_object_bind_property (monitor, "available", item, "sensitive",
1503           G_BINDING_SYNC_CREATE);
1504     }
1505
1506   return item;
1507 }
1508
1509 static void
1510 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1511   EmpathyContact *contact)
1512 {
1513   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1514
1515   empathy_log_window_show (empathy_contact_get_account (contact),
1516       empathy_contact_get_id (contact), FALSE, NULL);
1517 }
1518
1519 static GtkWidget *
1520 empathy_individual_log_menu_item_new (FolksIndividual *individual)
1521 {
1522   GtkWidget *item;
1523   GtkWidget *image;
1524
1525   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1526
1527   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1528   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1529   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1530   gtk_widget_show (image);
1531
1532   menu_item_set_first_contact (item, individual,
1533       G_CALLBACK (empathy_individual_log_menu_item_activated),
1534       EMPATHY_ACTION_VIEW_LOGS);
1535
1536   return item;
1537 }
1538
1539 static void
1540 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1541     EmpathyContact *contact)
1542 {
1543   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1544
1545   empathy_send_file_with_file_chooser (contact);
1546
1547   emit_menu_item_activated (item);
1548 }
1549
1550 static GtkWidget *
1551 empathy_individual_file_transfer_menu_item_new (EmpathyIndividualMenu *self,
1552     FolksIndividual *individual)
1553 {
1554   GtkWidget *item;
1555   GtkWidget *image;
1556
1557   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1558
1559   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1560   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1561       GTK_ICON_SIZE_MENU);
1562   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1563   gtk_widget_show (image);
1564
1565   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1566   menu_item_set_first_contact (item, individual,
1567       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1568       EMPATHY_ACTION_SEND_FILE);
1569
1570   return item;
1571 }
1572
1573 static void
1574 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1575     EmpathyContact *contact)
1576 {
1577   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1578
1579   empathy_share_my_desktop_share_with_contact (contact);
1580
1581   emit_menu_item_activated (item);
1582 }
1583
1584 static GtkWidget *
1585 empathy_individual_share_my_desktop_menu_item_new (EmpathyIndividualMenu *self,
1586     FolksIndividual *individual)
1587 {
1588   GtkWidget *item;
1589   GtkWidget *image;
1590
1591   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1592
1593   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1594   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1595   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1596   gtk_widget_show (image);
1597
1598   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1599   menu_item_set_first_contact (item, individual,
1600       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1601       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1602
1603   return item;
1604 }
1605
1606 static void
1607 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1608   FolksIndividual *individual)
1609 {
1610   folks_favourite_details_set_is_favourite (
1611       FOLKS_FAVOURITE_DETAILS (individual),
1612       gtk_check_menu_item_get_active (item));
1613 }
1614
1615 static GtkWidget *
1616 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
1617 {
1618   GtkWidget *item;
1619
1620   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1621
1622   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1623       folks_favourite_details_get_is_favourite (
1624           FOLKS_FAVOURITE_DETAILS (individual)));
1625
1626   g_signal_connect (item, "toggled",
1627       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1628
1629   return item;
1630 }
1631
1632 static void
1633 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1634     FolksIndividual *individual)
1635 {
1636   empathy_display_individual_info (individual);
1637 }
1638
1639 static GtkWidget *
1640 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1641 {
1642   GtkWidget *item;
1643   GtkWidget *image;
1644
1645   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1646   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1647       NULL);
1648
1649   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1650   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1651                 GTK_ICON_SIZE_MENU);
1652   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1653   gtk_widget_show (image);
1654
1655   g_signal_connect (item, "activate",
1656           G_CALLBACK (individual_info_menu_item_activate_cb),
1657           individual);
1658
1659   return item;
1660 }
1661
1662 static void
1663 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1664 {
1665   empathy_individual_edit_dialog_show (individual, NULL);
1666 }
1667
1668 static GtkWidget *
1669 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1670 {
1671   EmpathyIndividualManager *manager;
1672   GtkWidget *item;
1673   GtkWidget *image;
1674   gboolean enable = FALSE;
1675   EmpathyContact *contact;
1676
1677   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1678
1679   contact = empathy_contact_dup_from_folks_individual (individual);
1680
1681   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1682
1683   if (empathy_individual_manager_initialized ())
1684     {
1685       TpConnection *connection;
1686
1687       manager = empathy_individual_manager_dup_singleton ();
1688       connection = empathy_contact_get_connection (contact);
1689
1690       enable = (empathy_connection_can_alias_personas (connection,
1691                                                        individual) &&
1692                 empathy_connection_can_group_personas (connection, individual));
1693
1694       g_object_unref (manager);
1695     }
1696
1697   item = gtk_image_menu_item_new_with_mnemonic (
1698       C_("Edit individual (contextual menu)", "_Edit"));
1699   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1700   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1701   gtk_widget_show (image);
1702
1703   gtk_widget_set_sensitive (item, enable);
1704
1705   g_signal_connect_swapped (item, "activate",
1706       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1707
1708   g_object_unref (contact);
1709
1710   return item;
1711 }
1712
1713 typedef struct
1714 {
1715   FolksIndividual *individual;
1716   EmpathyContact *contact;
1717   EmpathyChatroom *chatroom;
1718 } RoomSubMenuData;
1719
1720 static RoomSubMenuData *
1721 room_sub_menu_data_new (FolksIndividual *individual,
1722     EmpathyContact *contact,
1723     EmpathyChatroom *chatroom)
1724 {
1725   RoomSubMenuData *data;
1726
1727   data = g_slice_new0 (RoomSubMenuData);
1728   if (individual != NULL)
1729     data->individual = g_object_ref (individual);
1730   if (contact != NULL)
1731     data->contact = g_object_ref (contact);
1732   data->chatroom = g_object_ref (chatroom);
1733
1734   return data;
1735 }
1736
1737 static void
1738 room_sub_menu_data_free (RoomSubMenuData *data)
1739 {
1740   tp_clear_object (&data->individual);
1741   tp_clear_object (&data->contact);
1742   g_object_unref (data->chatroom);
1743   g_slice_free (RoomSubMenuData, data);
1744 }
1745
1746 static void
1747 room_sub_menu_activate_cb (GtkWidget *item,
1748          RoomSubMenuData *data)
1749 {
1750   EmpathyTpChat *chat;
1751   EmpathyChatroomManager *mgr;
1752   EmpathyContact *contact = NULL;
1753
1754   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1755   if (chat == NULL)
1756     {
1757       /* channel was invalidated. Ignoring */
1758       return;
1759     }
1760
1761   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1762
1763   if (data->contact != NULL)
1764     contact = g_object_ref (data->contact);
1765   else
1766     {
1767       GeeSet *personas;
1768       GeeIterator *iter;
1769
1770       /* find the first of this Individual's contacts who can join this room */
1771       personas = folks_individual_get_personas (data->individual);
1772
1773       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1774       while (gee_iterator_next (iter) && (contact == NULL))
1775         {
1776           TpfPersona *persona = gee_iterator_get (iter);
1777           TpContact *tp_contact;
1778           GList *rooms;
1779
1780           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1781             {
1782               tp_contact = tpf_persona_get_contact (persona);
1783               if (tp_contact != NULL)
1784                 {
1785                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1786
1787                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1788                       empathy_contact_get_account (contact));
1789
1790                   if (g_list_find (rooms, data->chatroom) == NULL)
1791                     g_clear_object (&contact);
1792
1793                   /* if contact != NULL here, we've found our match */
1794
1795                   g_list_free (rooms);
1796                 }
1797             }
1798           g_clear_object (&persona);
1799         }
1800       g_clear_object (&iter);
1801     }
1802
1803   g_object_unref (mgr);
1804
1805   if (contact == NULL)
1806     {
1807       /* contact disappeared. Ignoring */
1808       goto out;
1809     }
1810
1811   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1812
1813   /* send invitation */
1814   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
1815
1816 out:
1817   g_object_unref (contact);
1818 }
1819
1820 static GtkWidget *
1821 create_room_sub_menu (FolksIndividual *individual,
1822                       EmpathyContact *contact,
1823                       EmpathyChatroom *chatroom)
1824 {
1825   GtkWidget *item;
1826   RoomSubMenuData *data;
1827
1828   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1829   data = room_sub_menu_data_new (individual, contact, chatroom);
1830   g_signal_connect_data (item, "activate",
1831       G_CALLBACK (room_sub_menu_activate_cb), data,
1832       (GClosureNotify) room_sub_menu_data_free, 0);
1833
1834   return item;
1835 }
1836
1837 static GtkWidget *
1838 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1839     EmpathyContact *contact)
1840 {
1841   GtkWidget *item;
1842   GtkWidget *image;
1843   GtkWidget *room_item;
1844   EmpathyChatroomManager *mgr;
1845   GList *rooms = NULL;
1846   GList *names = NULL;
1847   GList *l;
1848   GtkWidget *submenu = NULL;
1849   /* map of chat room names to their objects; just a utility to remove
1850    * duplicates and to make construction of the alphabetized list easier */
1851   GHashTable *name_room_map;
1852
1853   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1854       EMPATHY_IS_CONTACT (contact),
1855       NULL);
1856
1857   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1858       g_object_unref);
1859
1860   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1861   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1862       GTK_ICON_SIZE_MENU);
1863   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1864
1865   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1866
1867   if (contact != NULL)
1868     {
1869       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1870           empathy_contact_get_account (contact));
1871     }
1872   else
1873     {
1874       GeeSet *personas;
1875       GeeIterator *iter;
1876
1877       /* find the first of this Individual's contacts who can join this room */
1878       personas = folks_individual_get_personas (individual);
1879       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1880       while (gee_iterator_next (iter))
1881         {
1882           TpfPersona *persona = gee_iterator_get (iter);
1883           GList *rooms_cur;
1884           TpContact *tp_contact;
1885           EmpathyContact *contact_cur;
1886
1887           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1888             {
1889               tp_contact = tpf_persona_get_contact (persona);
1890               if (tp_contact != NULL)
1891                 {
1892                   contact_cur = empathy_contact_dup_from_tp_contact (
1893                       tp_contact);
1894
1895                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1896                       empathy_contact_get_account (contact_cur));
1897                   rooms = g_list_concat (rooms, rooms_cur);
1898
1899                   g_object_unref (contact_cur);
1900                 }
1901             }
1902           g_clear_object (&persona);
1903         }
1904       g_clear_object (&iter);
1905     }
1906
1907   /* alphabetize the rooms */
1908   for (l = rooms; l != NULL; l = g_list_next (l))
1909     {
1910       EmpathyChatroom *chatroom = l->data;
1911       gboolean existed;
1912
1913       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1914         {
1915           const gchar *name;
1916
1917           name = empathy_chatroom_get_name (chatroom);
1918           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1919           g_hash_table_insert (name_room_map, (gpointer) name,
1920               g_object_ref (chatroom));
1921
1922           /* this will take care of duplicates in rooms */
1923           if (!existed)
1924             {
1925               names = g_list_insert_sorted (names, (gpointer) name,
1926                   (GCompareFunc) g_strcmp0);
1927             }
1928         }
1929     }
1930
1931   for (l = names; l != NULL; l = g_list_next (l))
1932     {
1933       const gchar *name = l->data;
1934       EmpathyChatroom *chatroom;
1935
1936       if (G_UNLIKELY (submenu == NULL))
1937         submenu = gtk_menu_new ();
1938
1939       chatroom = g_hash_table_lookup (name_room_map, name);
1940       room_item = create_room_sub_menu (individual, contact, chatroom);
1941       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1942       gtk_widget_show (room_item);
1943     }
1944
1945   if (submenu)
1946     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1947   else
1948     gtk_widget_set_sensitive (item, FALSE);
1949
1950   gtk_widget_show (image);
1951
1952   g_hash_table_unref (name_room_map);
1953   g_object_unref (mgr);
1954   g_list_free (names);
1955   g_list_free (rooms);
1956
1957   return item;
1958 }
1959
1960 static void
1961 add_menu_item_activated (GtkMenuItem *item,
1962     TpContact *tp_contact)
1963 {
1964   GtkWidget *toplevel;
1965   FolksIndividual *individual;
1966
1967   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1968   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1969     toplevel = NULL;
1970
1971   individual = empathy_ensure_individual_from_tp_contact (tp_contact);
1972
1973   empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
1974       individual);
1975
1976   g_object_unref (individual);
1977 }
1978
1979 static GtkWidget *
1980 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1981     FolksIndividual *individual)
1982 {
1983   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1984   GtkWidget *item, *image;
1985   GeeSet *personas;
1986   GeeIterator *iter;
1987   TpContact *to_add = NULL;
1988
1989   /* find the first of this Individual's personas which are not in our contact
1990    * list. */
1991   personas = folks_individual_get_personas (individual);
1992   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1993   while (gee_iterator_next (iter))
1994     {
1995       TpfPersona *persona = gee_iterator_get (iter);
1996       TpContact *contact;
1997       TpConnection *conn;
1998
1999       if (!TPF_IS_PERSONA (persona))
2000         goto next;
2001
2002       contact = tpf_persona_get_contact (persona);
2003       if (contact == NULL)
2004         goto next;
2005
2006       /* be sure to use a not channel specific contact.
2007        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
2008       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
2009         {
2010           TpChannel *channel;
2011           TpChannelGroupFlags flags;
2012
2013           channel = empathy_individual_store_channel_get_channel (
2014               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
2015
2016           flags = tp_channel_group_get_flags (channel);
2017           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
2018             {
2019               /* Channel uses channel specific handles (thanks XMPP...) */
2020               contact = tp_channel_group_get_contact_owner (channel, contact);
2021
2022               /* If we don't know the owner, we can't add the contact */
2023               if (contact == NULL)
2024                 goto next;
2025             }
2026         }
2027
2028       conn = tp_contact_get_connection (contact);
2029       if (conn == NULL)
2030         goto next;
2031
2032       /* No point to try adding a contact if the CM doesn't support it */
2033       if (!tp_connection_get_can_change_contact_list (conn))
2034         goto next;
2035
2036       /* Can't add ourself */
2037       if (tp_connection_get_self_contact (conn) == contact)
2038         goto next;
2039
2040       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
2041         goto next;
2042
2043       g_object_unref (persona);
2044       to_add = contact;
2045       break;
2046
2047 next:
2048       g_object_unref (persona);
2049     }
2050
2051   g_object_unref (iter);
2052
2053   if (to_add == NULL)
2054     return NULL;
2055
2056   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
2057   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
2058   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2059
2060   g_signal_connect_data (item, "activate",
2061       G_CALLBACK (add_menu_item_activated),
2062       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
2063
2064   return item;
2065 }