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