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