]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
move 'Remove' item code to individual-menu
[empathy.git] / libempathy-gtk / empathy-individual-menu.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include "config.h"
24
25 #include <string.h>
26
27 #include <glib/gi18n-lib.h>
28 #include <gtk/gtk.h>
29 #include <gio/gdesktopappinfo.h>
30
31 #include <telepathy-glib/util.h>
32
33 #include <folks/folks.h>
34 #include <folks/folks-telepathy.h>
35
36 #include <libempathy/empathy-camera-monitor.h>
37 #include <libempathy/empathy-request-util.h>
38 #include <libempathy/empathy-individual-manager.h>
39 #include <libempathy/empathy-chatroom-manager.h>
40 #include <libempathy/empathy-utils.h>
41 #include <libempathy/empathy-pkg-kit.h>
42
43 #include "empathy-account-selector-dialog.h"
44 #include "empathy-individual-menu.h"
45 #include "empathy-images.h"
46 #include "empathy-log-window.h"
47 #include "empathy-contact-dialogs.h"
48 #include "empathy-gtk-enum-types.h"
49 #include "empathy-individual-dialogs.h"
50 #include "empathy-individual-edit-dialog.h"
51 #include "empathy-ui-utils.h"
52 #include "empathy-share-my-desktop.h"
53 #include "empathy-call-utils.h"
54 #include "empathy-individual-store-channel.h"
55 #include "empathy-individual-information-dialog.h"
56
57 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
58 #include <libempathy/empathy-debug.h>
59
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
61
62 typedef struct {
63   FolksIndividual *individual; /* owned */
64   EmpathyIndividualFeatureFlags features;
65   EmpathyIndividualStore *store; /* may be NULL */
66 } EmpathyIndividualMenuPriv;
67
68 enum {
69   PROP_INDIVIDUAL = 1,
70   PROP_FEATURES,
71   PROP_STORE,
72 };
73
74 G_DEFINE_TYPE (EmpathyIndividualMenu, empathy_individual_menu, GTK_TYPE_MENU);
75
76 static GtkWidget * empathy_individual_chat_menu_item_new (
77     FolksIndividual *individual);
78 static GtkWidget * empathy_individual_sms_menu_item_new (
79     FolksIndividual *individual);
80 static GtkWidget * empathy_individual_log_menu_item_new  (
81     FolksIndividual *individual);
82 static GtkWidget * empathy_individual_info_menu_item_new (
83     FolksIndividual *individual);
84 static GtkWidget * empathy_individual_edit_menu_item_new (
85     FolksIndividual *individual);
86 static GtkWidget * empathy_individual_invite_menu_item_new (
87     FolksIndividual *individual,
88     EmpathyContact *contact);
89 static GtkWidget * empathy_individual_file_transfer_menu_item_new (
90     FolksIndividual *individual);
91 static GtkWidget * empathy_individual_share_my_desktop_menu_item_new (
92     FolksIndividual *individual);
93 static GtkWidget * empathy_individual_favourite_menu_item_new (
94     FolksIndividual *individual);
95 static GtkWidget * empathy_individual_add_menu_item_new (
96     EmpathyIndividualMenu *self,
97     FolksIndividual *individual);
98 static GtkWidget * empathy_individiual_block_menu_item_new (
99     FolksIndividual *individual);
100 static GtkWidget * empathy_individiual_remove_menu_item_new (
101     FolksIndividual *individual);
102
103 static void
104 individual_menu_add_personas (GtkMenuShell *menu,
105     FolksIndividual *individual,
106     EmpathyIndividualFeatureFlags features)
107 {
108   GtkWidget *item;
109   GeeSet *personas;
110   GeeIterator *iter;
111   guint persona_count = 0;
112   gboolean c;
113
114   g_return_if_fail (GTK_IS_MENU (menu));
115   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
116   g_return_if_fail (empathy_folks_individual_contains_contact (individual));
117
118   personas = folks_individual_get_personas (individual);
119   /* we'll re-use this iterator throughout */
120   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
121
122   /* Make sure we've got enough valid entries for these menu items to add
123    * functionality */
124   while (gee_iterator_next (iter))
125     {
126       FolksPersona *persona = gee_iterator_get (iter);
127       if (empathy_folks_persona_is_interesting (persona))
128         persona_count++;
129
130       g_clear_object (&persona);
131     }
132
133   /* return early if these entries would add nothing beyond the "quick" items */
134   if (persona_count <= 1)
135     return;
136
137   /* add a separator before the list of personas */
138   item = gtk_separator_menu_item_new ();
139   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
140   gtk_widget_show (item);
141
142   for (c = gee_iterator_first (iter); c; c = gee_iterator_next (iter))
143     {
144       GtkWidget *image;
145       GtkWidget *contact_item;
146       GtkWidget *contact_submenu;
147       TpContact *tp_contact;
148       EmpathyContact *contact;
149       TpfPersona *persona = gee_iterator_get (iter);
150       gchar *label;
151       FolksPersonaStore *store;
152       const gchar *account;
153       GtkWidget *action;
154       /* Individual containing only persona */
155       FolksIndividual *single_individual;
156
157       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
158         goto while_finish;
159
160       tp_contact = tpf_persona_get_contact (persona);
161       if (tp_contact == NULL)
162         goto while_finish;
163
164       contact = empathy_contact_dup_from_tp_contact (tp_contact);
165       single_individual = empathy_create_individual_from_tp_contact (
166           tp_contact);
167
168       store = folks_persona_get_store (FOLKS_PERSONA (persona));
169       account = folks_persona_store_get_display_name (store);
170
171       /* Translators: this is used in the context menu for a contact. The first
172        * parameter is a contact ID (e.g. foo@jabber.org) and the second is one
173        * of the user's account IDs (e.g. me@hotmail.com). */
174       label = g_strdup_printf (_("%s (%s)"),
175           folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
176
177       contact_item = gtk_image_menu_item_new_with_label (label);
178       gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (contact_item),
179                                                  TRUE);
180       contact_submenu = gtk_menu_new ();
181       gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
182       image = gtk_image_new_from_icon_name (
183           empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
184       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
185       gtk_widget_show (image);
186
187       /* Chat */
188       if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
189         {
190           action = empathy_individual_chat_menu_item_new (single_individual);
191           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
192           gtk_widget_show (action);
193         }
194
195       /* SMS */
196       if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
197         {
198           action = empathy_individual_sms_menu_item_new (single_individual);
199           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
200           gtk_widget_show (action);
201         }
202
203       if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
204         {
205           /* Audio Call */
206           action = empathy_individual_audio_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           /* Video Call */
212           action = empathy_individual_video_call_menu_item_new (
213               single_individual);
214           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
215           gtk_widget_show (action);
216         }
217
218       /* Log */
219       if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
220         {
221           action = empathy_individual_log_menu_item_new (single_individual);
222           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
223           gtk_widget_show (action);
224         }
225
226       /* Invite */
227       action = empathy_individual_invite_menu_item_new (NULL, contact);
228       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
229       gtk_widget_show (action);
230
231       /* File transfer */
232       action = empathy_individual_file_transfer_menu_item_new (
233           single_individual);
234       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
235       gtk_widget_show (action);
236
237       /* Share my desktop */
238       action = empathy_individual_share_my_desktop_menu_item_new (
239           single_individual);
240       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
241       gtk_widget_show (action);
242
243       /* Block */
244       if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
245           (item = empathy_individiual_block_menu_item_new (single_individual))
246           != NULL) {
247         GtkWidget *sep;
248
249         sep = gtk_separator_menu_item_new ();
250         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), sep);
251         gtk_widget_show (sep);
252
253         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), item);
254         gtk_widget_show (item);
255       }
256
257       gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
258       gtk_widget_show (contact_item);
259
260       g_free (label);
261       g_object_unref (contact);
262       g_object_unref (single_individual);
263
264 while_finish:
265       g_clear_object (&persona);
266     }
267
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_get_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 (accounts);
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
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   item = empathy_individual_file_transfer_menu_item_new (individual);
944   gtk_menu_shell_append (shell, item);
945   gtk_widget_show (item);
946
947   /* Share my desktop */
948   /* FIXME we should add the "Share my desktop" menu item if Vino is
949   a registered handler in MC5 */
950   item = empathy_individual_share_my_desktop_menu_item_new (individual);
951   gtk_menu_shell_append (shell, item);
952   gtk_widget_show (item);
953
954   /* Menu items to target specific contacts */
955   individual_menu_add_personas (GTK_MENU_SHELL (object), individual, features);
956
957   /* Separator */
958   if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
959       EMPATHY_INDIVIDUAL_FEATURE_INFO |
960       EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE))
961     {
962       item = gtk_separator_menu_item_new ();
963       gtk_menu_shell_append (shell, item);
964       gtk_widget_show (item);
965     }
966
967   /* Edit */
968   if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
969     {
970       item = empathy_individual_edit_menu_item_new (individual);
971       gtk_menu_shell_append (shell, item);
972       gtk_widget_show (item);
973     }
974
975   /* Log */
976   if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
977     {
978       item = empathy_individual_log_menu_item_new (individual);
979       gtk_menu_shell_append (shell, item);
980       gtk_widget_show (item);
981     }
982
983   /* Info */
984   if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
985     {
986       item = empathy_individual_info_menu_item_new (individual);
987       gtk_menu_shell_append (shell, item);
988       gtk_widget_show (item);
989     }
990
991   /* Favorite checkbox */
992   if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
993     {
994       item = empathy_individual_favourite_menu_item_new (individual);
995       gtk_menu_shell_append (shell, item);
996       gtk_widget_show (item);
997     }
998
999   /* Separator & Block */
1000   if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
1001       (item = empathy_individiual_block_menu_item_new (individual)) != NULL) {
1002     GtkWidget *sep;
1003
1004     sep = gtk_separator_menu_item_new ();
1005     gtk_menu_shell_append (shell, sep);
1006     gtk_widget_show (sep);
1007
1008     gtk_menu_shell_append (shell, item);
1009     gtk_widget_show (item);
1010   }
1011
1012   /* Separator & Remove */
1013   if (features & EMPATHY_INDIVIDUAL_FEATURE_REMOVE &&
1014       (item = empathy_individiual_remove_menu_item_new (individual)) != NULL) {
1015     GtkWidget *sep;
1016
1017     sep = gtk_separator_menu_item_new ();
1018     gtk_menu_shell_append (shell, sep);
1019     gtk_widget_show (sep);
1020
1021     gtk_menu_shell_append (shell, item);
1022     gtk_widget_show (item);
1023   }
1024 }
1025
1026 static void
1027 get_property (GObject *object,
1028     guint param_id,
1029     GValue *value,
1030     GParamSpec *pspec)
1031 {
1032   EmpathyIndividualMenuPriv *priv;
1033
1034   priv = GET_PRIV (object);
1035
1036   switch (param_id)
1037     {
1038       case PROP_INDIVIDUAL:
1039         g_value_set_object (value, priv->individual);
1040         break;
1041       case PROP_FEATURES:
1042         g_value_set_flags (value, priv->features);
1043         break;
1044       case PROP_STORE:
1045         g_value_set_object (value, priv->store);
1046         break;
1047       default:
1048         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1049         break;
1050     }
1051 }
1052
1053 static void
1054 set_property (GObject *object,
1055     guint param_id,
1056     const GValue *value,
1057     GParamSpec *pspec)
1058 {
1059   EmpathyIndividualMenuPriv *priv;
1060
1061   priv = GET_PRIV (object);
1062
1063   switch (param_id)
1064     {
1065       case PROP_INDIVIDUAL:
1066         priv->individual = g_value_dup_object (value);
1067         break;
1068       case PROP_FEATURES:
1069         priv->features = g_value_get_flags (value);
1070         break;
1071       case PROP_STORE:
1072         priv->store = g_value_dup_object (value); /* read only */
1073         break;
1074       default:
1075         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1076         break;
1077     }
1078 }
1079
1080 static void
1081 dispose (GObject *object)
1082 {
1083   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1084
1085   tp_clear_object (&priv->individual);
1086   tp_clear_object (&priv->store);
1087
1088   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
1089 }
1090
1091 static void
1092 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
1093 {
1094   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1095
1096   object_class->constructed = constructed;
1097   object_class->get_property = get_property;
1098   object_class->set_property = set_property;
1099   object_class->dispose = dispose;
1100
1101   /**
1102    * EmpathyIndividualMenu:individual:
1103    *
1104    * The #FolksIndividual the menu is for.
1105    */
1106   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
1107       g_param_spec_object ("individual",
1108           "Individual",
1109           "The #FolksIndividual the menu is for.",
1110           FOLKS_TYPE_INDIVIDUAL,
1111           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1112
1113   /**
1114    * EmpathyIndividualMenu:features:
1115    *
1116    * A set of feature flags controlling which entries are shown.
1117    */
1118   g_object_class_install_property (object_class, PROP_FEATURES,
1119       g_param_spec_flags ("features",
1120           "Features",
1121           "A set of feature flags controlling which entries are shown.",
1122           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1123           EMPATHY_INDIVIDUAL_FEATURE_NONE,
1124           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1125
1126   g_object_class_install_property (object_class, PROP_STORE,
1127       g_param_spec_object ("store",
1128           "Store",
1129           "The EmpathyIndividualStore to use to get contact owner",
1130           EMPATHY_TYPE_INDIVIDUAL_STORE,
1131           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1132
1133   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
1134 }
1135
1136 GtkWidget *
1137 empathy_individual_menu_new (FolksIndividual *individual,
1138     EmpathyIndividualFeatureFlags features,
1139     EmpathyIndividualStore *store)
1140 {
1141   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1142   g_return_val_if_fail (store == NULL ||
1143       EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1144   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
1145
1146   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
1147       "individual", individual,
1148       "features", features,
1149       "store", store,
1150       NULL);
1151 }
1152
1153 /* Like menu_item_set_first_contact(), but always operates upon the given
1154  * contact. If the contact is non-NULL, it is assumed that the menu entry should
1155  * be sensitive. */
1156 static gboolean
1157 menu_item_set_contact (GtkWidget *item,
1158     EmpathyContact *contact,
1159     GCallback activate_callback,
1160     EmpathyActionType action_type)
1161 {
1162   gboolean can_do_action = FALSE;
1163
1164   if (contact != NULL)
1165     can_do_action = empathy_contact_can_do_action (contact, action_type);
1166   gtk_widget_set_sensitive (item, can_do_action);
1167
1168   if (can_do_action == TRUE)
1169     {
1170       /* We want to make sure that the EmpathyContact stays alive while the
1171        * signal is connected. */
1172       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
1173           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
1174     }
1175
1176   return can_do_action;
1177 }
1178
1179 /**
1180  * Set the given menu @item to call @activate_callback using the TpContact
1181  * (associated with @individual) with the highest availability who is also valid
1182  * whenever @item is activated.
1183  *
1184  * @action_type is the type of action performed by the menu entry; this is used
1185  * so that only contacts which can perform that action (e.g. are capable of
1186  * receiving video calls) are selected, as appropriate.
1187  */
1188 static GtkWidget *
1189 menu_item_set_first_contact (GtkWidget *item,
1190     FolksIndividual *individual,
1191     GCallback activate_callback,
1192     EmpathyActionType action_type)
1193 {
1194   EmpathyContact *best_contact;
1195
1196   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
1197   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
1198       action_type);
1199   tp_clear_object (&best_contact);
1200
1201   return item;
1202 }
1203
1204 static void
1205 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
1206   EmpathyContact *contact)
1207 {
1208   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1209
1210   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
1211 }
1212
1213 static GtkWidget *
1214 empathy_individual_chat_menu_item_new (FolksIndividual *individual)
1215 {
1216   GtkWidget *item;
1217   GtkWidget *image;
1218
1219   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1220       empathy_folks_individual_contains_contact (individual), NULL);
1221
1222   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
1223   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
1224       GTK_ICON_SIZE_MENU);
1225   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1226   gtk_widget_show (image);
1227
1228   menu_item_set_first_contact (item, individual,
1229       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1230       EMPATHY_ACTION_CHAT);
1231
1232   return item;
1233 }
1234
1235 static void
1236 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
1237   EmpathyContact *contact)
1238 {
1239   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1240
1241   empathy_sms_contact_id (
1242       empathy_contact_get_account (contact),
1243       empathy_contact_get_id (contact),
1244       empathy_get_current_action_time (),
1245       NULL, NULL);
1246 }
1247
1248 static GtkWidget *
1249 empathy_individual_sms_menu_item_new (FolksIndividual *individual)
1250 {
1251   GtkWidget *item;
1252   GtkWidget *image;
1253
1254   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1255       empathy_folks_individual_contains_contact (individual), NULL);
1256
1257   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
1258   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
1259       GTK_ICON_SIZE_MENU);
1260   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1261   gtk_widget_show (image);
1262
1263   menu_item_set_first_contact (item, individual,
1264       G_CALLBACK (empathy_individual_sms_menu_item_activated),
1265       EMPATHY_ACTION_SMS);
1266
1267   return item;
1268 }
1269
1270 static void
1271 empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
1272   EmpathyContact *contact)
1273 {
1274   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1275
1276   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1277       empathy_contact_get_account (contact),
1278       TRUE, FALSE,
1279       empathy_get_current_action_time ());
1280 }
1281
1282 GtkWidget *
1283 empathy_individual_audio_call_menu_item_new (FolksIndividual *individual)
1284 {
1285   GtkWidget *item;
1286   GtkWidget *image;
1287
1288   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1289
1290   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
1291   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1292   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1293   gtk_widget_show (image);
1294
1295   menu_item_set_first_contact (item, individual,
1296       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1297       EMPATHY_ACTION_AUDIO_CALL);
1298
1299   return item;
1300 }
1301
1302 static void
1303 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1304   EmpathyContact *contact)
1305 {
1306   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1307
1308   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1309       empathy_contact_get_account (contact),
1310       TRUE, TRUE,
1311       empathy_get_current_action_time ());
1312 }
1313
1314 GtkWidget *
1315 empathy_individual_video_call_menu_item_new (FolksIndividual *individual)
1316 {
1317   GtkWidget *item;
1318   GtkWidget *image;
1319   EmpathyCameraMonitor *monitor;
1320
1321   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1322
1323   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1324   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1325       GTK_ICON_SIZE_MENU);
1326   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1327   gtk_widget_show (image);
1328
1329   menu_item_set_first_contact (item, individual,
1330       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1331       EMPATHY_ACTION_VIDEO_CALL);
1332
1333   /* Only follow available cameras if the contact can do Video calls */
1334   if (gtk_widget_get_sensitive (item))
1335     {
1336       monitor = empathy_camera_monitor_dup_singleton ();
1337       g_object_set_data_full (G_OBJECT (item),
1338           "monitor", monitor, g_object_unref);
1339       g_object_bind_property (monitor, "available", item, "sensitive",
1340           G_BINDING_SYNC_CREATE);
1341     }
1342
1343   return item;
1344 }
1345
1346 static void
1347 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1348   EmpathyContact *contact)
1349 {
1350   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1351
1352   empathy_log_window_show (empathy_contact_get_account (contact),
1353       empathy_contact_get_id (contact), FALSE, NULL);
1354 }
1355
1356 static GtkWidget *
1357 empathy_individual_log_menu_item_new (FolksIndividual *individual)
1358 {
1359   GtkWidget *item;
1360   GtkWidget *image;
1361
1362   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1363
1364   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1365   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1366   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1367   gtk_widget_show (image);
1368
1369   menu_item_set_first_contact (item, individual,
1370       G_CALLBACK (empathy_individual_log_menu_item_activated),
1371       EMPATHY_ACTION_VIEW_LOGS);
1372
1373   return item;
1374 }
1375
1376 static void
1377 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1378     EmpathyContact *contact)
1379 {
1380   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1381
1382   empathy_send_file_with_file_chooser (contact);
1383 }
1384
1385 static GtkWidget *
1386 empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual)
1387 {
1388   GtkWidget *item;
1389   GtkWidget *image;
1390
1391   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1392
1393   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1394   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1395       GTK_ICON_SIZE_MENU);
1396   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1397   gtk_widget_show (image);
1398
1399   menu_item_set_first_contact (item, individual,
1400       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1401       EMPATHY_ACTION_SEND_FILE);
1402
1403   return item;
1404 }
1405
1406 static void
1407 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1408     EmpathyContact *contact)
1409 {
1410   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1411
1412   empathy_share_my_desktop_share_with_contact (contact);
1413 }
1414
1415 static GtkWidget *
1416 empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual)
1417 {
1418   GtkWidget *item;
1419   GtkWidget *image;
1420
1421   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1422
1423   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1424   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1425   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1426   gtk_widget_show (image);
1427
1428   menu_item_set_first_contact (item, individual,
1429       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1430       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1431
1432   return item;
1433 }
1434
1435 static void
1436 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1437   FolksIndividual *individual)
1438 {
1439   folks_favourite_details_set_is_favourite (
1440       FOLKS_FAVOURITE_DETAILS (individual),
1441       gtk_check_menu_item_get_active (item));
1442 }
1443
1444 static GtkWidget *
1445 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
1446 {
1447   GtkWidget *item;
1448
1449   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1450
1451   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1452       folks_favourite_details_get_is_favourite (
1453           FOLKS_FAVOURITE_DETAILS (individual)));
1454
1455   g_signal_connect (item, "toggled",
1456       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1457
1458   return item;
1459 }
1460
1461 static void
1462 show_gnome_contacts_error_dialog (void)
1463 {
1464   GtkWidget *dialog;
1465
1466   dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
1467       GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
1468       _("gnome-contacts not installed"));
1469
1470   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1471       _("Please install gnome-contacts to access contacts details."));
1472
1473   g_signal_connect_swapped (dialog, "response",
1474           G_CALLBACK (gtk_widget_destroy), dialog);
1475
1476   gtk_widget_show (dialog);
1477 }
1478
1479 static void
1480 start_gnome_contacts (FolksIndividual *individual,
1481     gboolean try_installing);
1482
1483 static void
1484 install_gnome_contacts_cb (GObject *source,
1485     GAsyncResult *result,
1486     gpointer user_data)
1487 {
1488   FolksIndividual *individual = user_data;
1489   GError *error = NULL;
1490
1491   if (!empathy_pkg_kit_install_packages_finish (result, &error))
1492     {
1493       DEBUG ("Failed to install gnome-contacts: %s", error->message);
1494       g_error_free (error);
1495
1496       show_gnome_contacts_error_dialog ();
1497       goto out;
1498     }
1499
1500   DEBUG ("gnome-contacts installed");
1501
1502   start_gnome_contacts (individual, FALSE);
1503
1504 out:
1505   g_object_unref (individual);
1506 }
1507
1508 static void
1509 start_gnome_contacts (FolksIndividual *individual,
1510     gboolean try_installing)
1511 {
1512   GDesktopAppInfo *desktop_info;
1513   gchar *cmd;
1514   GAppInfo *app_info;
1515   GError *error = NULL;
1516   GdkAppLaunchContext *context = NULL;
1517   GdkDisplay *display;
1518
1519   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
1520
1521   /* Start gnome-contacts */
1522   display = gdk_display_get_default ();
1523   context = gdk_display_get_app_launch_context (display);
1524
1525   desktop_info = g_desktop_app_info_new ("gnome-contacts.desktop");
1526   if (desktop_info == NULL)
1527     {
1528       if (try_installing)
1529         {
1530           const gchar *packages[] = { "gnome-contacts", NULL };
1531
1532           DEBUG ("gnome-contacts not installed; try to install it");
1533
1534           empathy_pkg_kit_install_packages_async (0, packages, NULL,
1535               NULL, install_gnome_contacts_cb, g_object_ref (individual));
1536         }
1537       else
1538         {
1539           show_gnome_contacts_error_dialog ();
1540         }
1541
1542       return;
1543     }
1544
1545   /* glib doesn't have API to start a desktop file with args... (#637875) */
1546   cmd = g_strdup_printf ("%s -i %s", g_app_info_get_commandline (
1547         (GAppInfo *) desktop_info), folks_individual_get_id (individual));
1548
1549   app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
1550   if (app_info == NULL)
1551     {
1552       DEBUG ("Failed to create app_info: %s", error->message);
1553       g_error_free (error);
1554       return;
1555     }
1556
1557   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1558         &error))
1559     {
1560       g_critical ("Failed to start gnome-contacts: %s", error->message);
1561       g_error_free (error);
1562     }
1563
1564   g_object_unref (desktop_info);
1565   g_object_unref (app_info);
1566 }
1567
1568 static void
1569 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1570     FolksIndividual *individual)
1571 {
1572   EmpathyIndividualManager *mgr;
1573
1574   mgr = empathy_individual_manager_dup_singleton ();
1575
1576   /* Only use gnome-contacts if that's a 'real' individual we got from
1577    * Folks (and so the individual manager knows about it). If not that's a
1578    * MUC contact and we use the simple dialog. */
1579   if (empathy_individual_manager_lookup_member (mgr,
1580         folks_individual_get_id (individual)) != NULL)
1581     {
1582       start_gnome_contacts (individual, TRUE);
1583     }
1584   else
1585     {
1586       empathy_individual_information_dialog_show (individual, NULL);
1587     }
1588
1589   g_object_unref (mgr);
1590 }
1591
1592 static GtkWidget *
1593 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1594 {
1595   GtkWidget *item;
1596   GtkWidget *image;
1597
1598   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1599   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1600       NULL);
1601
1602   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1603   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1604                 GTK_ICON_SIZE_MENU);
1605   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1606   gtk_widget_show (image);
1607
1608   g_signal_connect (item, "activate",
1609           G_CALLBACK (individual_info_menu_item_activate_cb),
1610           individual);
1611
1612   return item;
1613 }
1614
1615 static void
1616 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1617 {
1618   empathy_individual_edit_dialog_show (individual, NULL);
1619 }
1620
1621 static GtkWidget *
1622 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1623 {
1624   EmpathyIndividualManager *manager;
1625   GtkWidget *item;
1626   GtkWidget *image;
1627   gboolean enable = FALSE;
1628   EmpathyContact *contact;
1629
1630   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1631
1632   contact = empathy_contact_dup_from_folks_individual (individual);
1633
1634   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1635
1636   if (empathy_individual_manager_initialized ())
1637     {
1638       TpConnection *connection;
1639
1640       manager = empathy_individual_manager_dup_singleton ();
1641       connection = empathy_contact_get_connection (contact);
1642
1643       enable = (empathy_connection_can_alias_personas (connection,
1644                                                        individual) &&
1645                 empathy_connection_can_group_personas (connection, individual));
1646
1647       g_object_unref (manager);
1648     }
1649
1650   item = gtk_image_menu_item_new_with_mnemonic (
1651       C_("Edit individual (contextual menu)", "_Edit"));
1652   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1653   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1654   gtk_widget_show (image);
1655
1656   gtk_widget_set_sensitive (item, enable);
1657
1658   g_signal_connect_swapped (item, "activate",
1659       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1660
1661   g_object_unref (contact);
1662
1663   return item;
1664 }
1665
1666 typedef struct
1667 {
1668   FolksIndividual *individual;
1669   EmpathyContact *contact;
1670   EmpathyChatroom *chatroom;
1671 } RoomSubMenuData;
1672
1673 static RoomSubMenuData *
1674 room_sub_menu_data_new (FolksIndividual *individual,
1675     EmpathyContact *contact,
1676     EmpathyChatroom *chatroom)
1677 {
1678   RoomSubMenuData *data;
1679
1680   data = g_slice_new0 (RoomSubMenuData);
1681   if (individual != NULL)
1682     data->individual = g_object_ref (individual);
1683   if (contact != NULL)
1684     data->contact = g_object_ref (contact);
1685   data->chatroom = g_object_ref (chatroom);
1686
1687   return data;
1688 }
1689
1690 static void
1691 room_sub_menu_data_free (RoomSubMenuData *data)
1692 {
1693   tp_clear_object (&data->individual);
1694   tp_clear_object (&data->contact);
1695   g_object_unref (data->chatroom);
1696   g_slice_free (RoomSubMenuData, data);
1697 }
1698
1699 static void
1700 room_sub_menu_activate_cb (GtkWidget *item,
1701          RoomSubMenuData *data)
1702 {
1703   EmpathyTpChat *chat;
1704   EmpathyChatroomManager *mgr;
1705   EmpathyContact *contact = NULL;
1706
1707   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1708   if (chat == NULL)
1709     {
1710       /* channel was invalidated. Ignoring */
1711       return;
1712     }
1713
1714   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1715
1716   if (data->contact != NULL)
1717     contact = g_object_ref (data->contact);
1718   else
1719     {
1720       GeeSet *personas;
1721       GeeIterator *iter;
1722
1723       /* find the first of this Individual's contacts who can join this room */
1724       personas = folks_individual_get_personas (data->individual);
1725
1726       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1727       while (gee_iterator_next (iter) && (contact == NULL))
1728         {
1729           TpfPersona *persona = gee_iterator_get (iter);
1730           TpContact *tp_contact;
1731           GList *rooms;
1732
1733           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1734             {
1735               tp_contact = tpf_persona_get_contact (persona);
1736               if (tp_contact != NULL)
1737                 {
1738                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1739
1740                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1741                       empathy_contact_get_account (contact));
1742
1743                   if (g_list_find (rooms, data->chatroom) == NULL)
1744                     g_clear_object (&contact);
1745
1746                   /* if contact != NULL here, we've found our match */
1747
1748                   g_list_free (rooms);
1749                 }
1750             }
1751           g_clear_object (&persona);
1752         }
1753       g_clear_object (&iter);
1754     }
1755
1756   g_object_unref (mgr);
1757
1758   if (contact == NULL)
1759     {
1760       /* contact disappeared. Ignoring */
1761       goto out;
1762     }
1763
1764   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1765
1766   /* send invitation */
1767   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
1768
1769 out:
1770   g_object_unref (contact);
1771 }
1772
1773 static GtkWidget *
1774 create_room_sub_menu (FolksIndividual *individual,
1775                       EmpathyContact *contact,
1776                       EmpathyChatroom *chatroom)
1777 {
1778   GtkWidget *item;
1779   RoomSubMenuData *data;
1780
1781   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1782   data = room_sub_menu_data_new (individual, contact, chatroom);
1783   g_signal_connect_data (item, "activate",
1784       G_CALLBACK (room_sub_menu_activate_cb), data,
1785       (GClosureNotify) room_sub_menu_data_free, 0);
1786
1787   return item;
1788 }
1789
1790 static GtkWidget *
1791 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1792     EmpathyContact *contact)
1793 {
1794   GtkWidget *item;
1795   GtkWidget *image;
1796   GtkWidget *room_item;
1797   EmpathyChatroomManager *mgr;
1798   GList *rooms = NULL;
1799   GList *names = NULL;
1800   GList *l;
1801   GtkWidget *submenu = NULL;
1802   /* map of chat room names to their objects; just a utility to remove
1803    * duplicates and to make construction of the alphabetized list easier */
1804   GHashTable *name_room_map;
1805
1806   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1807       EMPATHY_IS_CONTACT (contact),
1808       NULL);
1809
1810   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1811       g_object_unref);
1812
1813   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1814   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1815       GTK_ICON_SIZE_MENU);
1816   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1817
1818   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1819
1820   if (contact != NULL)
1821     {
1822       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1823           empathy_contact_get_account (contact));
1824     }
1825   else
1826     {
1827       GeeSet *personas;
1828       GeeIterator *iter;
1829
1830       /* find the first of this Individual's contacts who can join this room */
1831       personas = folks_individual_get_personas (individual);
1832       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1833       while (gee_iterator_next (iter))
1834         {
1835           TpfPersona *persona = gee_iterator_get (iter);
1836           GList *rooms_cur;
1837           TpContact *tp_contact;
1838           EmpathyContact *contact_cur;
1839
1840           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1841             {
1842               tp_contact = tpf_persona_get_contact (persona);
1843               if (tp_contact != NULL)
1844                 {
1845                   contact_cur = empathy_contact_dup_from_tp_contact (
1846                       tp_contact);
1847
1848                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1849                       empathy_contact_get_account (contact_cur));
1850                   rooms = g_list_concat (rooms, rooms_cur);
1851
1852                   g_object_unref (contact_cur);
1853                 }
1854             }
1855           g_clear_object (&persona);
1856         }
1857       g_clear_object (&iter);
1858     }
1859
1860   /* alphabetize the rooms */
1861   for (l = rooms; l != NULL; l = g_list_next (l))
1862     {
1863       EmpathyChatroom *chatroom = l->data;
1864       gboolean existed;
1865
1866       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1867         {
1868           const gchar *name;
1869
1870           name = empathy_chatroom_get_name (chatroom);
1871           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1872           g_hash_table_insert (name_room_map, (gpointer) name,
1873               g_object_ref (chatroom));
1874
1875           /* this will take care of duplicates in rooms */
1876           if (!existed)
1877             {
1878               names = g_list_insert_sorted (names, (gpointer) name,
1879                   (GCompareFunc) g_strcmp0);
1880             }
1881         }
1882     }
1883
1884   for (l = names; l != NULL; l = g_list_next (l))
1885     {
1886       const gchar *name = l->data;
1887       EmpathyChatroom *chatroom;
1888
1889       if (G_UNLIKELY (submenu == NULL))
1890         submenu = gtk_menu_new ();
1891
1892       chatroom = g_hash_table_lookup (name_room_map, name);
1893       room_item = create_room_sub_menu (individual, contact, chatroom);
1894       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1895       gtk_widget_show (room_item);
1896     }
1897
1898   if (submenu)
1899     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1900   else
1901     gtk_widget_set_sensitive (item, FALSE);
1902
1903   gtk_widget_show (image);
1904
1905   g_hash_table_unref (name_room_map);
1906   g_object_unref (mgr);
1907   g_list_free (names);
1908   g_list_free (rooms);
1909
1910   return item;
1911 }
1912
1913 static void
1914 add_menu_item_activated (GtkMenuItem *item,
1915     TpContact *tp_contact)
1916 {
1917   GtkWidget *toplevel;
1918   EmpathyContact *contact;
1919
1920   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1921   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1922     toplevel = NULL;
1923
1924   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1925
1926   empathy_new_contact_dialog_show_with_contact (GTK_WINDOW (toplevel),
1927       contact);
1928
1929   g_object_unref (contact);
1930 }
1931
1932 static GtkWidget *
1933 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1934     FolksIndividual *individual)
1935 {
1936   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1937   GtkWidget *item, *image;
1938   GeeSet *personas;
1939   GeeIterator *iter;
1940   TpContact *to_add = NULL;
1941
1942   /* find the first of this Individual's personas which are not in our contact
1943    * list. */
1944   personas = folks_individual_get_personas (individual);
1945   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1946   while (gee_iterator_next (iter))
1947     {
1948       TpfPersona *persona = gee_iterator_get (iter);
1949       TpContact *contact;
1950       TpConnection *conn;
1951
1952       if (!TPF_IS_PERSONA (persona))
1953         goto next;
1954
1955       contact = tpf_persona_get_contact (persona);
1956       if (contact == NULL)
1957         goto next;
1958
1959       /* be sure to use a not channel specific contact.
1960        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
1961       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
1962         {
1963           TpChannel *channel;
1964           TpChannelGroupFlags flags;
1965
1966           channel = empathy_individual_store_channel_get_channel (
1967               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
1968
1969           flags = tp_channel_group_get_flags (channel);
1970           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
1971             {
1972               /* Channel uses channel specific handles (thanks XMPP...) */
1973               contact = tp_channel_group_get_contact_owner (channel, contact);
1974
1975               /* If we don't know the owner, we can't add the contact */
1976               if (contact == NULL)
1977                 goto next;
1978             }
1979         }
1980
1981       conn = tp_contact_get_connection (contact);
1982       if (conn == NULL)
1983         goto next;
1984
1985       /* No point to try adding a contact if the CM doesn't support it */
1986       if (!tp_connection_get_can_change_contact_list (conn))
1987         goto next;
1988
1989       /* Can't add ourself */
1990       if (tp_connection_get_self_contact (conn) == contact)
1991         goto next;
1992
1993       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
1994         goto next;
1995
1996       g_object_unref (persona);
1997       to_add = contact;
1998       break;
1999
2000 next:
2001       g_object_unref (persona);
2002     }
2003
2004   g_object_unref (iter);
2005
2006   if (to_add == NULL)
2007     return NULL;
2008
2009   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
2010   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
2011   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2012
2013   g_signal_connect_data (item, "activate",
2014       G_CALLBACK (add_menu_item_activated),
2015       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
2016
2017   return item;
2018 }