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