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