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