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