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