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