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