]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
Install property active-group in EmpathyIndividualMenu
[empathy.git] / libempathy-gtk / empathy-individual-menu.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include "config.h"
24 #include "empathy-individual-menu.h"
25
26 #include <glib/gi18n-lib.h>
27
28 #include "empathy-account-selector-dialog.h"
29 #include "empathy-call-utils.h"
30 #include "empathy-camera-monitor.h"
31 #include "empathy-chatroom-manager.h"
32 #include "empathy-gtk-enum-types.h"
33 #include "empathy-images.h"
34 #include "empathy-individual-dialogs.h"
35 #include "empathy-individual-dialogs.h"
36 #include "empathy-individual-edit-dialog.h"
37 #include "empathy-individual-information-dialog.h"
38 #include "empathy-individual-manager.h"
39 #include "empathy-individual-store-channel.h"
40 #include "empathy-log-window.h"
41 #include "empathy-request-util.h"
42 #include "empathy-share-my-desktop.h"
43 #include "empathy-ui-utils.h"
44 #include "empathy-utils.h"
45
46 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
47 #include "empathy-debug.h"
48
49 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
50
51 typedef struct {
52   gchar *active_group; /* may be NULL */
53   FolksIndividual *individual; /* owned */
54   EmpathyIndividualFeatureFlags features;
55   EmpathyIndividualStore *store; /* may be NULL */
56 } EmpathyIndividualMenuPriv;
57
58 enum {
59   PROP_ACTIVE_GROUP = 1,
60   PROP_INDIVIDUAL,
61   PROP_FEATURES,
62   PROP_STORE,
63 };
64
65 G_DEFINE_TYPE (EmpathyIndividualMenu, empathy_individual_menu, GTK_TYPE_MENU);
66
67 static GtkWidget * empathy_individual_chat_menu_item_new (
68     FolksIndividual *individual);
69 static GtkWidget * empathy_individual_sms_menu_item_new (
70     FolksIndividual *individual);
71 static GtkWidget * empathy_individual_log_menu_item_new  (
72     FolksIndividual *individual);
73 static GtkWidget * empathy_individual_info_menu_item_new (
74     FolksIndividual *individual);
75 static GtkWidget * empathy_individual_edit_menu_item_new (
76     FolksIndividual *individual);
77 static GtkWidget * empathy_individual_invite_menu_item_new (
78     FolksIndividual *individual,
79     EmpathyContact *contact);
80 static GtkWidget * empathy_individual_file_transfer_menu_item_new (
81     FolksIndividual *individual);
82 static GtkWidget * empathy_individual_share_my_desktop_menu_item_new (
83     FolksIndividual *individual);
84 static GtkWidget * empathy_individual_favourite_menu_item_new (
85     FolksIndividual *individual);
86 static GtkWidget * empathy_individual_add_menu_item_new (
87     EmpathyIndividualMenu *self,
88     FolksIndividual *individual);
89 static GtkWidget * empathy_individiual_block_menu_item_new (
90     FolksIndividual *individual);
91 static GtkWidget * empathy_individiual_remove_menu_item_new (
92     FolksIndividual *individual);
93
94 static void
95 individual_menu_add_personas (GtkMenuShell *menu,
96     FolksIndividual *individual,
97     EmpathyIndividualFeatureFlags features)
98 {
99   GtkWidget *item;
100   GeeSet *personas;
101   GeeIterator *iter;
102   guint persona_count = 0;
103
104   g_return_if_fail (GTK_IS_MENU (menu));
105   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
106   g_return_if_fail (empathy_folks_individual_contains_contact (individual));
107
108   personas = folks_individual_get_personas (individual);
109   /* we'll re-use this iterator throughout */
110   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
111
112   /* Make sure we've got enough valid entries for these menu items to add
113    * functionality */
114   while (gee_iterator_next (iter))
115     {
116       FolksPersona *persona = gee_iterator_get (iter);
117       if (empathy_folks_persona_is_interesting (persona))
118         persona_count++;
119
120       g_clear_object (&persona);
121     }
122
123   /* return early if these entries would add nothing beyond the "quick" items */
124   if (persona_count <= 1)
125     goto out;
126
127   /* add a separator before the list of personas */
128   item = gtk_separator_menu_item_new ();
129   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
130   gtk_widget_show (item);
131
132   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
133   while (gee_iterator_next (iter))
134     {
135       GtkWidget *image;
136       GtkWidget *contact_item;
137       GtkWidget *contact_submenu;
138       TpContact *tp_contact;
139       EmpathyContact *contact;
140       TpfPersona *persona = gee_iterator_get (iter);
141       gchar *label;
142       FolksPersonaStore *store;
143       const gchar *account;
144       GtkWidget *action;
145       /* Individual containing only persona */
146       FolksIndividual *single_individual;
147
148       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
149         goto while_finish;
150
151       tp_contact = tpf_persona_get_contact (persona);
152       if (tp_contact == NULL)
153         goto while_finish;
154
155       contact = empathy_contact_dup_from_tp_contact (tp_contact);
156       single_individual = empathy_create_individual_from_tp_contact (
157           tp_contact);
158
159       /* Pretty hacky. Creating single_individual had a side effect to change
160        * persona.individual from individual to single_individual which is not
161        * what we want so we set it back. See bgo#684971 for details. */
162       g_object_set (persona, "individual", individual, NULL);
163
164       store = folks_persona_get_store (FOLKS_PERSONA (persona));
165       account = folks_persona_store_get_display_name (store);
166
167       /* Translators: this is used in the context menu for a contact. The first
168        * parameter is a contact ID (e.g. foo@jabber.org) and the second is one
169        * of the user's account IDs (e.g. me@hotmail.com). */
170       label = g_strdup_printf (_("%s (%s)"),
171           folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
172
173       contact_item = gtk_image_menu_item_new_with_label (label);
174       gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (contact_item),
175                                                  TRUE);
176       contact_submenu = gtk_menu_new ();
177       gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
178       image = gtk_image_new_from_icon_name (
179           empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
180       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
181       gtk_widget_show (image);
182
183       /* Chat */
184       if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
185         {
186           action = empathy_individual_chat_menu_item_new (single_individual);
187           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
188           gtk_widget_show (action);
189         }
190
191       /* SMS */
192       if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
193         {
194           action = empathy_individual_sms_menu_item_new (single_individual);
195           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
196           gtk_widget_show (action);
197         }
198
199       if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
200         {
201           /* Audio Call */
202           action = empathy_individual_audio_call_menu_item_new (
203               single_individual);
204           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
205           gtk_widget_show (action);
206
207           /* Video Call */
208           action = empathy_individual_video_call_menu_item_new (
209               single_individual);
210           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
211           gtk_widget_show (action);
212         }
213
214       /* Log */
215       if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
216         {
217           action = empathy_individual_log_menu_item_new (single_individual);
218           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
219           gtk_widget_show (action);
220         }
221
222       /* Invite */
223       action = empathy_individual_invite_menu_item_new (NULL, contact);
224       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
225       gtk_widget_show (action);
226
227       /* File transfer */
228       if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
229         {
230           action = empathy_individual_file_transfer_menu_item_new (
231               single_individual);
232           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
233           gtk_widget_show (action);
234         }
235
236       /* Share my desktop */
237       action = empathy_individual_share_my_desktop_menu_item_new (
238           single_individual);
239       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
240       gtk_widget_show (action);
241
242       /* Block */
243       if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
244           (item = empathy_individiual_block_menu_item_new (single_individual))
245           != NULL) {
246         GtkWidget *sep;
247
248         sep = gtk_separator_menu_item_new ();
249         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), sep);
250         gtk_widget_show (sep);
251
252         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), item);
253         gtk_widget_show (item);
254       }
255
256       gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
257       gtk_widget_show (contact_item);
258
259       g_free (label);
260       g_object_unref (contact);
261       g_object_unref (single_individual);
262
263 while_finish:
264       g_clear_object (&persona);
265     }
266
267 out:
268   g_clear_object (&iter);
269 }
270
271 static void
272 empathy_individual_menu_init (EmpathyIndividualMenu *self)
273 {
274   EmpathyIndividualMenuPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
275       EMPATHY_TYPE_INDIVIDUAL_MENU, EmpathyIndividualMenuPriv);
276
277   self->priv = priv;
278 }
279
280 static GList *
281 find_phone_accounts (void)
282 {
283   TpAccountManager *am;
284   GList *accounts, *l;
285   GList *found = NULL;
286
287   am = tp_account_manager_dup ();
288   g_return_val_if_fail (am != NULL, NULL);
289
290   accounts = tp_account_manager_dup_valid_accounts (am);
291   for (l = accounts; l != NULL; l = g_list_next (l))
292     {
293       TpAccount *account = l->data;
294
295       if (tp_account_get_connection_status (account, NULL) !=
296           TP_CONNECTION_STATUS_CONNECTED)
297         continue;
298
299       if (!empathy_account_has_uri_scheme_tel (account))
300         continue;
301
302       found = g_list_prepend (found, g_object_ref (account));
303     }
304
305   g_list_free_full (accounts, g_object_unref);
306   g_object_unref (am);
307
308   return found;
309 }
310
311 static gboolean
312 has_phone_account (void)
313 {
314   GList *accounts;
315   gboolean result;
316
317   accounts = find_phone_accounts ();
318   result = (accounts != NULL);
319
320   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
321
322   return result;
323 }
324
325 static void
326 call_phone_number (FolksPhoneFieldDetails *details,
327     TpAccount *account)
328 {
329   DEBUG ("Try to call %s", folks_phone_field_details_get_normalised (details));
330
331   empathy_call_new_with_streams (
332       folks_phone_field_details_get_normalised (details),
333       account, TRUE, FALSE, empathy_get_current_action_time ());
334 }
335
336 static void
337 display_call_phone_dialog (FolksPhoneFieldDetails *details,
338     GList *accounts)
339 {
340   GtkWidget *dialog;
341   gint response;
342
343   dialog = empathy_account_selector_dialog_new (accounts);
344
345   gtk_window_set_title (GTK_WINDOW (dialog),
346       _("Select account to use to place the call"));
347
348   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
349       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
350       _("Call"), GTK_RESPONSE_OK,
351       NULL);
352
353   response = gtk_dialog_run (GTK_DIALOG (dialog));
354
355   if (response == GTK_RESPONSE_OK)
356     {
357       TpAccount *account;
358
359       account = empathy_account_selector_dialog_dup_selected (
360            EMPATHY_ACCOUNT_SELECTOR_DIALOG (dialog));
361
362       if (account != NULL)
363         {
364           call_phone_number (details, account);
365
366           g_object_unref (account);
367         }
368     }
369
370   gtk_widget_destroy (dialog);
371 }
372
373 static void
374 call_phone_number_cb (GtkMenuItem *item,
375       FolksPhoneFieldDetails *details)
376 {
377   GList *accounts;
378
379   accounts = find_phone_accounts ();
380   if (accounts == NULL)
381     {
382       DEBUG ("No phone aware account connected; can't call");
383     }
384   else if (g_list_length (accounts) == 1)
385     {
386       call_phone_number (details, accounts->data);
387     }
388   else
389     {
390       /* Ask which account to use */
391       display_call_phone_dialog (details, accounts);
392     }
393
394   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
395 }
396
397 static const gchar *
398 find_phone_type (FolksPhoneFieldDetails *details)
399 {
400   GeeCollection *types;
401   GeeIterator *iter;
402
403   types = folks_abstract_field_details_get_parameter_values (
404       FOLKS_ABSTRACT_FIELD_DETAILS (details), "type");
405
406   if (types == NULL)
407     return NULL;
408
409   iter = gee_iterable_iterator (GEE_ITERABLE (types));
410   while (gee_iterator_next (iter))
411     {
412       const gchar *type = gee_iterator_get (iter);
413
414       if (!tp_strdiff (type, "CELL"))
415         return _("Mobile");
416       else if (!tp_strdiff (type, "WORK"))
417         return _("Work");
418       else if (!tp_strdiff (type, "HOME"))
419         return _("HOME");
420     }
421
422   return NULL;
423 }
424
425 static void
426 add_phone_numbers (EmpathyIndividualMenu *self)
427 {
428   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
429   GeeSet *all_numbers;
430   GeeIterator *iter;
431   gboolean sensitive;
432
433   all_numbers = folks_phone_details_get_phone_numbers (
434       FOLKS_PHONE_DETAILS (priv->individual));
435
436   sensitive = has_phone_account ();
437
438   iter = gee_iterable_iterator (GEE_ITERABLE (all_numbers));
439   while (gee_iterator_next (iter))
440     {
441       FolksPhoneFieldDetails *details = gee_iterator_get (iter);
442       GtkWidget *item, *image;
443       gchar *tmp;
444       const gchar *type;
445
446       type = find_phone_type (details);
447
448       if (type != NULL)
449         {
450           tmp = g_strdup_printf ("Call %s (%s)",
451               folks_phone_field_details_get_normalised (details),
452               type);
453         }
454       else
455         {
456           tmp = g_strdup_printf ("Call %s",
457               folks_phone_field_details_get_normalised (details));
458         }
459
460       item = gtk_image_menu_item_new_with_mnemonic (tmp);
461       g_free (tmp);
462
463       g_signal_connect_data (item, "activate",
464           G_CALLBACK (call_phone_number_cb), g_object_ref (details),
465           (GClosureNotify) g_object_unref, 0);
466
467       gtk_widget_set_sensitive (item, sensitive);
468
469       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CALL,
470           GTK_ICON_SIZE_MENU);
471       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
472       gtk_widget_show (image);
473
474       gtk_menu_shell_append (GTK_MENU_SHELL (self), item);
475       gtk_widget_show (item);
476     }
477
478   g_object_unref (iter);
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_ACTIVE_GROUP:
1044         g_value_set_string (value, priv->active_group);
1045         break;
1046       case PROP_INDIVIDUAL:
1047         g_value_set_object (value, priv->individual);
1048         break;
1049       case PROP_FEATURES:
1050         g_value_set_flags (value, priv->features);
1051         break;
1052       case PROP_STORE:
1053         g_value_set_object (value, priv->store);
1054         break;
1055       default:
1056         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1057         break;
1058     }
1059 }
1060
1061 static void
1062 set_property (GObject *object,
1063     guint param_id,
1064     const GValue *value,
1065     GParamSpec *pspec)
1066 {
1067   EmpathyIndividualMenuPriv *priv;
1068
1069   priv = GET_PRIV (object);
1070
1071   switch (param_id)
1072     {
1073       case PROP_ACTIVE_GROUP:
1074         g_assert (priv->active_group == NULL); /* construct only */
1075         priv->active_group = g_value_dup_string (value);
1076         break;
1077       case PROP_INDIVIDUAL:
1078         priv->individual = g_value_dup_object (value);
1079         break;
1080       case PROP_FEATURES:
1081         priv->features = g_value_get_flags (value);
1082         break;
1083       case PROP_STORE:
1084         priv->store = g_value_dup_object (value); /* read only */
1085         break;
1086       default:
1087         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1088         break;
1089     }
1090 }
1091
1092 static void
1093 dispose (GObject *object)
1094 {
1095   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1096
1097   tp_clear_object (&priv->individual);
1098   tp_clear_object (&priv->store);
1099
1100   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
1101 }
1102
1103 static void
1104 finalize (GObject *object)
1105 {
1106   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1107
1108   g_free (priv->active_group);
1109
1110   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->finalize (object);
1111 }
1112
1113 static void
1114 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
1115 {
1116   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1117
1118   object_class->constructed = constructed;
1119   object_class->get_property = get_property;
1120   object_class->set_property = set_property;
1121   object_class->dispose = dispose;
1122   object_class->finalize = finalize;
1123
1124   /**
1125    * gchar *:active-group:
1126    *
1127    * The group the selected roster-contact widget belongs, or NULL.
1128    */
1129   g_object_class_install_property (object_class, PROP_ACTIVE_GROUP,
1130       g_param_spec_string ("active-group",
1131           "Active group",
1132           "The group the selected roster-contact widget belongs, or NULL",
1133           NULL,
1134           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1135
1136   /**
1137    * EmpathyIndividualMenu:individual:
1138    *
1139    * The #FolksIndividual the menu is for.
1140    */
1141   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
1142       g_param_spec_object ("individual",
1143           "Individual",
1144           "The #FolksIndividual the menu is for.",
1145           FOLKS_TYPE_INDIVIDUAL,
1146           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1147
1148   /**
1149    * EmpathyIndividualMenu:features:
1150    *
1151    * A set of feature flags controlling which entries are shown.
1152    */
1153   g_object_class_install_property (object_class, PROP_FEATURES,
1154       g_param_spec_flags ("features",
1155           "Features",
1156           "A set of feature flags controlling which entries are shown.",
1157           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1158           EMPATHY_INDIVIDUAL_FEATURE_NONE,
1159           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1160
1161   g_object_class_install_property (object_class, PROP_STORE,
1162       g_param_spec_object ("store",
1163           "Store",
1164           "The EmpathyIndividualStore to use to get contact owner",
1165           EMPATHY_TYPE_INDIVIDUAL_STORE,
1166           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1167
1168   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
1169 }
1170
1171 GtkWidget *
1172 empathy_individual_menu_new (FolksIndividual *individual,
1173     const gchar *active_group,
1174     EmpathyIndividualFeatureFlags features,
1175     EmpathyIndividualStore *store)
1176 {
1177   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1178   g_return_val_if_fail (store == NULL ||
1179       EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1180   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
1181
1182   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
1183       "active-group", active_group,
1184       "individual", individual,
1185       "features", features,
1186       "store", store,
1187       NULL);
1188 }
1189
1190 /* Like menu_item_set_first_contact(), but always operates upon the given
1191  * contact. If the contact is non-NULL, it is assumed that the menu entry should
1192  * be sensitive. */
1193 static gboolean
1194 menu_item_set_contact (GtkWidget *item,
1195     EmpathyContact *contact,
1196     GCallback activate_callback,
1197     EmpathyActionType action_type)
1198 {
1199   gboolean can_do_action = FALSE;
1200
1201   if (contact != NULL)
1202     can_do_action = empathy_contact_can_do_action (contact, action_type);
1203   gtk_widget_set_sensitive (item, can_do_action);
1204
1205   if (can_do_action == TRUE)
1206     {
1207       /* We want to make sure that the EmpathyContact stays alive while the
1208        * signal is connected. */
1209       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
1210           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
1211     }
1212
1213   return can_do_action;
1214 }
1215
1216 /**
1217  * Set the given menu @item to call @activate_callback using the TpContact
1218  * (associated with @individual) with the highest availability who is also valid
1219  * whenever @item is activated.
1220  *
1221  * @action_type is the type of action performed by the menu entry; this is used
1222  * so that only contacts which can perform that action (e.g. are capable of
1223  * receiving video calls) are selected, as appropriate.
1224  */
1225 static GtkWidget *
1226 menu_item_set_first_contact (GtkWidget *item,
1227     FolksIndividual *individual,
1228     GCallback activate_callback,
1229     EmpathyActionType action_type)
1230 {
1231   EmpathyContact *best_contact;
1232
1233   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
1234   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
1235       action_type);
1236   tp_clear_object (&best_contact);
1237
1238   return item;
1239 }
1240
1241 static void
1242 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
1243   EmpathyContact *contact)
1244 {
1245   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1246
1247   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
1248 }
1249
1250 static GtkWidget *
1251 empathy_individual_chat_menu_item_new (FolksIndividual *individual)
1252 {
1253   GtkWidget *item;
1254   GtkWidget *image;
1255
1256   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1257       empathy_folks_individual_contains_contact (individual), NULL);
1258
1259   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
1260   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
1261       GTK_ICON_SIZE_MENU);
1262   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1263   gtk_widget_show (image);
1264
1265   menu_item_set_first_contact (item, individual,
1266       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1267       EMPATHY_ACTION_CHAT);
1268
1269   return item;
1270 }
1271
1272 static void
1273 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
1274   EmpathyContact *contact)
1275 {
1276   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1277
1278   empathy_sms_contact_id (
1279       empathy_contact_get_account (contact),
1280       empathy_contact_get_id (contact),
1281       empathy_get_current_action_time (),
1282       NULL, NULL);
1283 }
1284
1285 static GtkWidget *
1286 empathy_individual_sms_menu_item_new (FolksIndividual *individual)
1287 {
1288   GtkWidget *item;
1289   GtkWidget *image;
1290
1291   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1292       empathy_folks_individual_contains_contact (individual), NULL);
1293
1294   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
1295   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
1296       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_sms_menu_item_activated),
1302       EMPATHY_ACTION_SMS);
1303
1304   return item;
1305 }
1306
1307 static void
1308 empathy_individual_audio_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, FALSE,
1316       empathy_get_current_action_time ());
1317 }
1318
1319 GtkWidget *
1320 empathy_individual_audio_call_menu_item_new (FolksIndividual *individual)
1321 {
1322   GtkWidget *item;
1323   GtkWidget *image;
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", "_Audio Call"));
1328   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1329   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1330   gtk_widget_show (image);
1331
1332   menu_item_set_first_contact (item, individual,
1333       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1334       EMPATHY_ACTION_AUDIO_CALL);
1335
1336   return item;
1337 }
1338
1339 static void
1340 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1341   EmpathyContact *contact)
1342 {
1343   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1344
1345   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1346       empathy_contact_get_account (contact),
1347       TRUE, TRUE,
1348       empathy_get_current_action_time ());
1349 }
1350
1351 GtkWidget *
1352 empathy_individual_video_call_menu_item_new (FolksIndividual *individual)
1353 {
1354   GtkWidget *item;
1355   GtkWidget *image;
1356   EmpathyCameraMonitor *monitor;
1357
1358   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1359
1360   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1361   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1362       GTK_ICON_SIZE_MENU);
1363   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1364   gtk_widget_show (image);
1365
1366   menu_item_set_first_contact (item, individual,
1367       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1368       EMPATHY_ACTION_VIDEO_CALL);
1369
1370   /* Only follow available cameras if the contact can do Video calls */
1371   if (gtk_widget_get_sensitive (item))
1372     {
1373       monitor = empathy_camera_monitor_dup_singleton ();
1374       g_object_set_data_full (G_OBJECT (item),
1375           "monitor", monitor, g_object_unref);
1376       g_object_bind_property (monitor, "available", item, "sensitive",
1377           G_BINDING_SYNC_CREATE);
1378     }
1379
1380   return item;
1381 }
1382
1383 static void
1384 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1385   EmpathyContact *contact)
1386 {
1387   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1388
1389   empathy_log_window_show (empathy_contact_get_account (contact),
1390       empathy_contact_get_id (contact), FALSE, NULL);
1391 }
1392
1393 static GtkWidget *
1394 empathy_individual_log_menu_item_new (FolksIndividual *individual)
1395 {
1396   GtkWidget *item;
1397   GtkWidget *image;
1398
1399   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1400
1401   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1402   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1403   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1404   gtk_widget_show (image);
1405
1406   menu_item_set_first_contact (item, individual,
1407       G_CALLBACK (empathy_individual_log_menu_item_activated),
1408       EMPATHY_ACTION_VIEW_LOGS);
1409
1410   return item;
1411 }
1412
1413 static void
1414 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1415     EmpathyContact *contact)
1416 {
1417   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1418
1419   empathy_send_file_with_file_chooser (contact);
1420 }
1421
1422 static GtkWidget *
1423 empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual)
1424 {
1425   GtkWidget *item;
1426   GtkWidget *image;
1427
1428   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1429
1430   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1431   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1432       GTK_ICON_SIZE_MENU);
1433   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1434   gtk_widget_show (image);
1435
1436   menu_item_set_first_contact (item, individual,
1437       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1438       EMPATHY_ACTION_SEND_FILE);
1439
1440   return item;
1441 }
1442
1443 static void
1444 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1445     EmpathyContact *contact)
1446 {
1447   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1448
1449   empathy_share_my_desktop_share_with_contact (contact);
1450 }
1451
1452 static GtkWidget *
1453 empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual)
1454 {
1455   GtkWidget *item;
1456   GtkWidget *image;
1457
1458   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1459
1460   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1461   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1462   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1463   gtk_widget_show (image);
1464
1465   menu_item_set_first_contact (item, individual,
1466       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1467       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1468
1469   return item;
1470 }
1471
1472 static void
1473 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1474   FolksIndividual *individual)
1475 {
1476   folks_favourite_details_set_is_favourite (
1477       FOLKS_FAVOURITE_DETAILS (individual),
1478       gtk_check_menu_item_get_active (item));
1479 }
1480
1481 static GtkWidget *
1482 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
1483 {
1484   GtkWidget *item;
1485
1486   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1487
1488   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1489       folks_favourite_details_get_is_favourite (
1490           FOLKS_FAVOURITE_DETAILS (individual)));
1491
1492   g_signal_connect (item, "toggled",
1493       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1494
1495   return item;
1496 }
1497
1498 static void
1499 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1500     FolksIndividual *individual)
1501 {
1502   empathy_display_individual_info (individual);
1503 }
1504
1505 static GtkWidget *
1506 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1507 {
1508   GtkWidget *item;
1509   GtkWidget *image;
1510
1511   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1512   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1513       NULL);
1514
1515   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1516   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1517                 GTK_ICON_SIZE_MENU);
1518   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1519   gtk_widget_show (image);
1520
1521   g_signal_connect (item, "activate",
1522           G_CALLBACK (individual_info_menu_item_activate_cb),
1523           individual);
1524
1525   return item;
1526 }
1527
1528 static void
1529 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1530 {
1531   empathy_individual_edit_dialog_show (individual, NULL);
1532 }
1533
1534 static GtkWidget *
1535 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1536 {
1537   EmpathyIndividualManager *manager;
1538   GtkWidget *item;
1539   GtkWidget *image;
1540   gboolean enable = FALSE;
1541   EmpathyContact *contact;
1542
1543   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1544
1545   contact = empathy_contact_dup_from_folks_individual (individual);
1546
1547   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1548
1549   if (empathy_individual_manager_initialized ())
1550     {
1551       TpConnection *connection;
1552
1553       manager = empathy_individual_manager_dup_singleton ();
1554       connection = empathy_contact_get_connection (contact);
1555
1556       enable = (empathy_connection_can_alias_personas (connection,
1557                                                        individual) &&
1558                 empathy_connection_can_group_personas (connection, individual));
1559
1560       g_object_unref (manager);
1561     }
1562
1563   item = gtk_image_menu_item_new_with_mnemonic (
1564       C_("Edit individual (contextual menu)", "_Edit"));
1565   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1566   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1567   gtk_widget_show (image);
1568
1569   gtk_widget_set_sensitive (item, enable);
1570
1571   g_signal_connect_swapped (item, "activate",
1572       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1573
1574   g_object_unref (contact);
1575
1576   return item;
1577 }
1578
1579 typedef struct
1580 {
1581   FolksIndividual *individual;
1582   EmpathyContact *contact;
1583   EmpathyChatroom *chatroom;
1584 } RoomSubMenuData;
1585
1586 static RoomSubMenuData *
1587 room_sub_menu_data_new (FolksIndividual *individual,
1588     EmpathyContact *contact,
1589     EmpathyChatroom *chatroom)
1590 {
1591   RoomSubMenuData *data;
1592
1593   data = g_slice_new0 (RoomSubMenuData);
1594   if (individual != NULL)
1595     data->individual = g_object_ref (individual);
1596   if (contact != NULL)
1597     data->contact = g_object_ref (contact);
1598   data->chatroom = g_object_ref (chatroom);
1599
1600   return data;
1601 }
1602
1603 static void
1604 room_sub_menu_data_free (RoomSubMenuData *data)
1605 {
1606   tp_clear_object (&data->individual);
1607   tp_clear_object (&data->contact);
1608   g_object_unref (data->chatroom);
1609   g_slice_free (RoomSubMenuData, data);
1610 }
1611
1612 static void
1613 room_sub_menu_activate_cb (GtkWidget *item,
1614          RoomSubMenuData *data)
1615 {
1616   EmpathyTpChat *chat;
1617   EmpathyChatroomManager *mgr;
1618   EmpathyContact *contact = NULL;
1619
1620   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1621   if (chat == NULL)
1622     {
1623       /* channel was invalidated. Ignoring */
1624       return;
1625     }
1626
1627   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1628
1629   if (data->contact != NULL)
1630     contact = g_object_ref (data->contact);
1631   else
1632     {
1633       GeeSet *personas;
1634       GeeIterator *iter;
1635
1636       /* find the first of this Individual's contacts who can join this room */
1637       personas = folks_individual_get_personas (data->individual);
1638
1639       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1640       while (gee_iterator_next (iter) && (contact == NULL))
1641         {
1642           TpfPersona *persona = gee_iterator_get (iter);
1643           TpContact *tp_contact;
1644           GList *rooms;
1645
1646           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1647             {
1648               tp_contact = tpf_persona_get_contact (persona);
1649               if (tp_contact != NULL)
1650                 {
1651                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1652
1653                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1654                       empathy_contact_get_account (contact));
1655
1656                   if (g_list_find (rooms, data->chatroom) == NULL)
1657                     g_clear_object (&contact);
1658
1659                   /* if contact != NULL here, we've found our match */
1660
1661                   g_list_free (rooms);
1662                 }
1663             }
1664           g_clear_object (&persona);
1665         }
1666       g_clear_object (&iter);
1667     }
1668
1669   g_object_unref (mgr);
1670
1671   if (contact == NULL)
1672     {
1673       /* contact disappeared. Ignoring */
1674       goto out;
1675     }
1676
1677   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1678
1679   /* send invitation */
1680   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
1681
1682 out:
1683   g_object_unref (contact);
1684 }
1685
1686 static GtkWidget *
1687 create_room_sub_menu (FolksIndividual *individual,
1688                       EmpathyContact *contact,
1689                       EmpathyChatroom *chatroom)
1690 {
1691   GtkWidget *item;
1692   RoomSubMenuData *data;
1693
1694   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1695   data = room_sub_menu_data_new (individual, contact, chatroom);
1696   g_signal_connect_data (item, "activate",
1697       G_CALLBACK (room_sub_menu_activate_cb), data,
1698       (GClosureNotify) room_sub_menu_data_free, 0);
1699
1700   return item;
1701 }
1702
1703 static GtkWidget *
1704 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1705     EmpathyContact *contact)
1706 {
1707   GtkWidget *item;
1708   GtkWidget *image;
1709   GtkWidget *room_item;
1710   EmpathyChatroomManager *mgr;
1711   GList *rooms = NULL;
1712   GList *names = NULL;
1713   GList *l;
1714   GtkWidget *submenu = NULL;
1715   /* map of chat room names to their objects; just a utility to remove
1716    * duplicates and to make construction of the alphabetized list easier */
1717   GHashTable *name_room_map;
1718
1719   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1720       EMPATHY_IS_CONTACT (contact),
1721       NULL);
1722
1723   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1724       g_object_unref);
1725
1726   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1727   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1728       GTK_ICON_SIZE_MENU);
1729   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1730
1731   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1732
1733   if (contact != NULL)
1734     {
1735       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1736           empathy_contact_get_account (contact));
1737     }
1738   else
1739     {
1740       GeeSet *personas;
1741       GeeIterator *iter;
1742
1743       /* find the first of this Individual's contacts who can join this room */
1744       personas = folks_individual_get_personas (individual);
1745       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1746       while (gee_iterator_next (iter))
1747         {
1748           TpfPersona *persona = gee_iterator_get (iter);
1749           GList *rooms_cur;
1750           TpContact *tp_contact;
1751           EmpathyContact *contact_cur;
1752
1753           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1754             {
1755               tp_contact = tpf_persona_get_contact (persona);
1756               if (tp_contact != NULL)
1757                 {
1758                   contact_cur = empathy_contact_dup_from_tp_contact (
1759                       tp_contact);
1760
1761                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1762                       empathy_contact_get_account (contact_cur));
1763                   rooms = g_list_concat (rooms, rooms_cur);
1764
1765                   g_object_unref (contact_cur);
1766                 }
1767             }
1768           g_clear_object (&persona);
1769         }
1770       g_clear_object (&iter);
1771     }
1772
1773   /* alphabetize the rooms */
1774   for (l = rooms; l != NULL; l = g_list_next (l))
1775     {
1776       EmpathyChatroom *chatroom = l->data;
1777       gboolean existed;
1778
1779       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1780         {
1781           const gchar *name;
1782
1783           name = empathy_chatroom_get_name (chatroom);
1784           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1785           g_hash_table_insert (name_room_map, (gpointer) name,
1786               g_object_ref (chatroom));
1787
1788           /* this will take care of duplicates in rooms */
1789           if (!existed)
1790             {
1791               names = g_list_insert_sorted (names, (gpointer) name,
1792                   (GCompareFunc) g_strcmp0);
1793             }
1794         }
1795     }
1796
1797   for (l = names; l != NULL; l = g_list_next (l))
1798     {
1799       const gchar *name = l->data;
1800       EmpathyChatroom *chatroom;
1801
1802       if (G_UNLIKELY (submenu == NULL))
1803         submenu = gtk_menu_new ();
1804
1805       chatroom = g_hash_table_lookup (name_room_map, name);
1806       room_item = create_room_sub_menu (individual, contact, chatroom);
1807       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1808       gtk_widget_show (room_item);
1809     }
1810
1811   if (submenu)
1812     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1813   else
1814     gtk_widget_set_sensitive (item, FALSE);
1815
1816   gtk_widget_show (image);
1817
1818   g_hash_table_unref (name_room_map);
1819   g_object_unref (mgr);
1820   g_list_free (names);
1821   g_list_free (rooms);
1822
1823   return item;
1824 }
1825
1826 static void
1827 add_menu_item_activated (GtkMenuItem *item,
1828     TpContact *tp_contact)
1829 {
1830   GtkWidget *toplevel;
1831   FolksIndividual *individual;
1832
1833   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1834   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1835     toplevel = NULL;
1836
1837   individual = empathy_ensure_individual_from_tp_contact (tp_contact);
1838
1839   empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
1840       individual);
1841
1842   g_object_unref (individual);
1843 }
1844
1845 static GtkWidget *
1846 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1847     FolksIndividual *individual)
1848 {
1849   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1850   GtkWidget *item, *image;
1851   GeeSet *personas;
1852   GeeIterator *iter;
1853   TpContact *to_add = NULL;
1854
1855   /* find the first of this Individual's personas which are not in our contact
1856    * list. */
1857   personas = folks_individual_get_personas (individual);
1858   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1859   while (gee_iterator_next (iter))
1860     {
1861       TpfPersona *persona = gee_iterator_get (iter);
1862       TpContact *contact;
1863       TpConnection *conn;
1864
1865       if (!TPF_IS_PERSONA (persona))
1866         goto next;
1867
1868       contact = tpf_persona_get_contact (persona);
1869       if (contact == NULL)
1870         goto next;
1871
1872       /* be sure to use a not channel specific contact.
1873        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
1874       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
1875         {
1876           TpChannel *channel;
1877           TpChannelGroupFlags flags;
1878
1879           channel = empathy_individual_store_channel_get_channel (
1880               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
1881
1882           flags = tp_channel_group_get_flags (channel);
1883           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
1884             {
1885               /* Channel uses channel specific handles (thanks XMPP...) */
1886               contact = tp_channel_group_get_contact_owner (channel, contact);
1887
1888               /* If we don't know the owner, we can't add the contact */
1889               if (contact == NULL)
1890                 goto next;
1891             }
1892         }
1893
1894       conn = tp_contact_get_connection (contact);
1895       if (conn == NULL)
1896         goto next;
1897
1898       /* No point to try adding a contact if the CM doesn't support it */
1899       if (!tp_connection_get_can_change_contact_list (conn))
1900         goto next;
1901
1902       /* Can't add ourself */
1903       if (tp_connection_get_self_contact (conn) == contact)
1904         goto next;
1905
1906       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
1907         goto next;
1908
1909       g_object_unref (persona);
1910       to_add = contact;
1911       break;
1912
1913 next:
1914       g_object_unref (persona);
1915     }
1916
1917   g_object_unref (iter);
1918
1919   if (to_add == NULL)
1920     return NULL;
1921
1922   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
1923   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1924   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1925
1926   g_signal_connect_data (item, "activate",
1927       G_CALLBACK (add_menu_item_activated),
1928       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
1929
1930   return item;
1931 }