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