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