]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
Only follow available cameras if the contact can do Video calls
[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-individual-menu.h"
41 #include "empathy-images.h"
42 #include "empathy-log-window.h"
43 #include "empathy-contact-dialogs.h"
44 #include "empathy-gtk-enum-types.h"
45 #include "empathy-individual-edit-dialog.h"
46 #include "empathy-individual-information-dialog.h"
47 #include "empathy-ui-utils.h"
48 #include "empathy-share-my-desktop.h"
49 #include "empathy-linking-dialog.h"
50 #include "empathy-call-utils.h"
51
52 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
53
54 typedef struct {
55   FolksIndividual *individual; /* owned */
56   EmpathyIndividualFeatureFlags features;
57 } EmpathyIndividualMenuPriv;
58
59 enum {
60   PROP_INDIVIDUAL = 1,
61   PROP_FEATURES,
62 };
63
64 enum {
65   SIGNAL_LINK_CONTACTS_ACTIVATED,
66   LAST_SIGNAL
67 };
68
69 static guint signals[LAST_SIGNAL];
70
71 G_DEFINE_TYPE (EmpathyIndividualMenu, empathy_individual_menu, GTK_TYPE_MENU);
72
73 static void
74 individual_menu_add_personas (GtkMenuShell *menu,
75     FolksIndividual *individual,
76     EmpathyIndividualFeatureFlags features)
77 {
78   GtkWidget *item;
79   GeeSet *personas;
80   GeeIterator *iter;
81   guint persona_count = 0;
82   gboolean c;
83
84   g_return_if_fail (GTK_IS_MENU (menu));
85   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
86   g_return_if_fail (empathy_folks_individual_contains_contact (individual));
87
88   personas = folks_individual_get_personas (individual);
89   /* we'll re-use this iterator throughout */
90   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
91
92   /* Make sure we've got enough valid entries for these menu items to add
93    * functionality */
94   while (gee_iterator_next (iter))
95     {
96       FolksPersona *persona = gee_iterator_get (iter);
97       if (empathy_folks_persona_is_interesting (persona))
98         persona_count++;
99
100       g_clear_object (&persona);
101     }
102
103   /* return early if these entries would add nothing beyond the "quick" items */
104   if (persona_count <= 1)
105     return;
106
107   /* add a separator before the list of personas */
108   item = gtk_separator_menu_item_new ();
109   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
110   gtk_widget_show (item);
111
112   personas = folks_individual_get_personas (individual);
113   for (c = gee_iterator_first (iter); c; c = gee_iterator_next (iter))
114     {
115       GtkWidget *image;
116       GtkWidget *contact_item;
117       GtkWidget *contact_submenu;
118       TpContact *tp_contact;
119       EmpathyContact *contact;
120       TpfPersona *persona = gee_iterator_get (iter);
121       gchar *label;
122       FolksPersonaStore *store;
123       const gchar *account;
124       GtkWidget *action;
125
126       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
127         goto while_finish;
128
129       tp_contact = tpf_persona_get_contact (persona);
130       contact = empathy_contact_dup_from_tp_contact (tp_contact);
131
132       store = folks_persona_get_store (FOLKS_PERSONA (persona));
133       account = folks_persona_store_get_display_name (store);
134
135       /* Translators: this is used in the context menu for a contact. The first
136        * parameter is a contact ID (e.g. foo@jabber.org) and the second is one
137        * of the user's account IDs (e.g. me@hotmail.com). */
138       label = g_strdup_printf (_("%s (%s)"),
139           folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
140
141       contact_item = gtk_image_menu_item_new_with_label (label);
142       gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (contact_item),
143                                                  TRUE);
144       contact_submenu = gtk_menu_new ();
145       gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
146       image = gtk_image_new_from_icon_name (
147           empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
148       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
149       gtk_widget_show (image);
150
151       /* Chat */
152       if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
153         {
154           action = empathy_individual_chat_menu_item_new (NULL, contact);
155           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
156           gtk_widget_show (action);
157         }
158
159       /* SMS */
160       if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
161         {
162           action = empathy_individual_sms_menu_item_new (NULL, contact);
163           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
164           gtk_widget_show (action);
165         }
166
167       if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
168         {
169           /* Audio Call */
170           action = empathy_individual_audio_call_menu_item_new (NULL, contact);
171           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
172           gtk_widget_show (action);
173
174           /* Video Call */
175           action = empathy_individual_video_call_menu_item_new (NULL, contact);
176           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
177           gtk_widget_show (action);
178         }
179
180       /* Log */
181       if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
182         {
183           action = empathy_individual_log_menu_item_new (NULL, contact);
184           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
185           gtk_widget_show (action);
186         }
187
188       /* Invite */
189       action = empathy_individual_invite_menu_item_new (NULL, contact);
190       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
191       gtk_widget_show (action);
192
193       /* File transfer */
194       action = empathy_individual_file_transfer_menu_item_new (NULL, contact);
195       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
196       gtk_widget_show (action);
197
198       /* Share my desktop */
199       action = empathy_individual_share_my_desktop_menu_item_new (NULL,
200           contact);
201       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
202       gtk_widget_show (action);
203
204       gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
205       gtk_widget_show (contact_item);
206
207       g_free (label);
208       g_object_unref (contact);
209
210 while_finish:
211       g_clear_object (&persona);
212     }
213
214   g_clear_object (&iter);
215 }
216
217 static void
218 individual_link_menu_item_activate_cb (EmpathyIndividualMenu *self)
219 {
220   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
221   GtkWidget *dialog;
222
223   dialog = empathy_linking_dialog_show (priv->individual, NULL);
224   g_signal_emit (self, signals[SIGNAL_LINK_CONTACTS_ACTIVATED], 0, dialog);
225 }
226
227 static void
228 empathy_individual_menu_init (EmpathyIndividualMenu *self)
229 {
230   EmpathyIndividualMenuPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
231       EMPATHY_TYPE_INDIVIDUAL_MENU, EmpathyIndividualMenuPriv);
232
233   self->priv = priv;
234 }
235
236 static void
237 constructed (GObject *object)
238 {
239   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
240   GtkMenuShell *shell;
241   GtkWidget *item;
242   FolksIndividual *individual;
243   EmpathyIndividualFeatureFlags features;
244
245   /* Build the menu */
246   shell = GTK_MENU_SHELL (object);
247   individual = priv->individual;
248   features = priv->features;
249
250   /* Chat */
251   if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
252     {
253       item = empathy_individual_chat_menu_item_new (individual, NULL);
254       if (item != NULL)
255         {
256           gtk_menu_shell_append (shell, item);
257           gtk_widget_show (item);
258         }
259     }
260
261   /* SMS */
262   if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
263     {
264       item = empathy_individual_sms_menu_item_new (individual, NULL);
265       if (item != NULL)
266         {
267           gtk_menu_shell_append (shell, item);
268           gtk_widget_show (item);
269         }
270     }
271
272   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
273     {
274       /* Audio Call */
275       item = empathy_individual_audio_call_menu_item_new (individual, NULL);
276       gtk_menu_shell_append (shell, item);
277       gtk_widget_show (item);
278
279       /* Video Call */
280       item = empathy_individual_video_call_menu_item_new (individual, NULL);
281       gtk_menu_shell_append (shell, item);
282       gtk_widget_show (item);
283     }
284
285   /* Log */
286   if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
287     {
288       item = empathy_individual_log_menu_item_new (individual, NULL);
289       gtk_menu_shell_append (shell, item);
290       gtk_widget_show (item);
291     }
292
293   /* Invite */
294   item = empathy_individual_invite_menu_item_new (individual, NULL);
295   gtk_menu_shell_append (shell, item);
296   gtk_widget_show (item);
297
298   /* File transfer */
299   item = empathy_individual_file_transfer_menu_item_new (individual, NULL);
300   gtk_menu_shell_append (shell, item);
301   gtk_widget_show (item);
302
303   /* Share my desktop */
304   /* FIXME we should add the "Share my desktop" menu item if Vino is
305   a registered handler in MC5 */
306   item = empathy_individual_share_my_desktop_menu_item_new (individual, NULL);
307   gtk_menu_shell_append (shell, item);
308   gtk_widget_show (item);
309
310   /* Menu items to target specific contacts */
311   individual_menu_add_personas (GTK_MENU_SHELL (object), individual, features);
312
313   /* Separator */
314   if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
315       EMPATHY_INDIVIDUAL_FEATURE_INFO |
316       EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE |
317       EMPATHY_INDIVIDUAL_FEATURE_LINK))
318     {
319       item = gtk_separator_menu_item_new ();
320       gtk_menu_shell_append (shell, item);
321       gtk_widget_show (item);
322     }
323
324   /* Edit */
325   if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
326     {
327       item = empathy_individual_edit_menu_item_new (individual);
328       gtk_menu_shell_append (shell, item);
329       gtk_widget_show (item);
330     }
331
332   /* Link */
333   if (features & EMPATHY_INDIVIDUAL_FEATURE_LINK)
334     {
335       item = empathy_individual_link_menu_item_new (individual);
336       gtk_menu_shell_append (shell, item);
337
338       g_signal_connect_swapped (item, "activate",
339           (GCallback) individual_link_menu_item_activate_cb, object);
340
341       gtk_widget_show (item);
342     }
343
344   /* Info */
345   if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
346     {
347       item = empathy_individual_info_menu_item_new (individual);
348       gtk_menu_shell_append (shell, item);
349       gtk_widget_show (item);
350     }
351
352   /* Favorite checkbox */
353   if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
354     {
355       item = empathy_individual_favourite_menu_item_new (individual);
356       gtk_menu_shell_append (shell, item);
357       gtk_widget_show (item);
358     }
359 }
360
361 static void
362 get_property (GObject *object,
363     guint param_id,
364     GValue *value,
365     GParamSpec *pspec)
366 {
367   EmpathyIndividualMenuPriv *priv;
368
369   priv = GET_PRIV (object);
370
371   switch (param_id)
372     {
373       case PROP_INDIVIDUAL:
374         g_value_set_object (value, priv->individual);
375         break;
376       case PROP_FEATURES:
377         g_value_set_flags (value, priv->features);
378         break;
379       default:
380         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
381         break;
382     }
383 }
384
385 static void
386 set_property (GObject *object,
387     guint param_id,
388     const GValue *value,
389     GParamSpec *pspec)
390 {
391   EmpathyIndividualMenuPriv *priv;
392
393   priv = GET_PRIV (object);
394
395   switch (param_id)
396     {
397       case PROP_INDIVIDUAL:
398         priv->individual = g_value_dup_object (value);
399         break;
400       case PROP_FEATURES:
401         priv->features = g_value_get_flags (value);
402         break;
403       default:
404         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
405         break;
406     }
407 }
408
409 static void
410 dispose (GObject *object)
411 {
412   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
413
414   tp_clear_object (&priv->individual);
415
416   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
417 }
418
419 static void
420 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
421 {
422   GObjectClass *object_class = G_OBJECT_CLASS (klass);
423
424   object_class->constructed = constructed;
425   object_class->get_property = get_property;
426   object_class->set_property = set_property;
427   object_class->dispose = dispose;
428
429   /**
430    * EmpathyIndividualMenu:individual:
431    *
432    * The #FolksIndividual the menu is for.
433    */
434   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
435       g_param_spec_object ("individual",
436           "Individual",
437           "The #FolksIndividual the menu is for.",
438           FOLKS_TYPE_INDIVIDUAL,
439           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
440
441   /**
442    * EmpathyIndividualMenu:features:
443    *
444    * A set of feature flags controlling which entries are shown.
445    */
446   g_object_class_install_property (object_class, PROP_FEATURES,
447       g_param_spec_flags ("features",
448           "Features",
449           "A set of feature flags controlling which entries are shown.",
450           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
451           EMPATHY_INDIVIDUAL_FEATURE_NONE,
452           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
453
454   signals[SIGNAL_LINK_CONTACTS_ACTIVATED] =
455       g_signal_new ("link-contacts-activated", G_OBJECT_CLASS_TYPE (klass),
456           G_SIGNAL_RUN_LAST, 0, NULL, NULL,
457           g_cclosure_marshal_VOID__OBJECT,
458           G_TYPE_NONE, 1, EMPATHY_TYPE_LINKING_DIALOG);
459
460   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
461 }
462
463 GtkWidget *
464 empathy_individual_menu_new (FolksIndividual *individual,
465     EmpathyIndividualFeatureFlags features)
466 {
467   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
468   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
469
470   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
471       "individual", individual,
472       "features", features,
473       NULL);
474 }
475
476 /* Like menu_item_set_first_contact(), but always operates upon the given
477  * contact. If the contact is non-NULL, it is assumed that the menu entry should
478  * be sensitive. */
479 static gboolean
480 menu_item_set_contact (GtkWidget *item,
481     EmpathyContact *contact,
482     GCallback activate_callback,
483     EmpathyActionType action_type)
484 {
485   gboolean can_do_action = FALSE;
486
487   if (contact != NULL)
488     can_do_action = empathy_contact_can_do_action (contact, action_type);
489   gtk_widget_set_sensitive (item, can_do_action);
490
491   if (can_do_action == TRUE)
492     {
493       /* We want to make sure that the EmpathyContact stays alive while the
494        * signal is connected. */
495       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
496           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
497     }
498
499   return can_do_action;
500 }
501
502 /**
503  * Set the given menu @item to call @activate_callback using the TpContact
504  * (associated with @individual) with the highest availability who is also valid
505  * whenever @item is activated.
506  *
507  * @action_type is the type of action performed by the menu entry; this is used
508  * so that only contacts which can perform that action (e.g. are capable of
509  * receiving video calls) are selected, as appropriate.
510  */
511 static GtkWidget *
512 menu_item_set_first_contact (GtkWidget *item,
513     FolksIndividual *individual,
514     GCallback activate_callback,
515     EmpathyActionType action_type)
516 {
517   EmpathyContact *best_contact;
518
519   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
520   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
521       action_type);
522   tp_clear_object (&best_contact);
523
524   return item;
525 }
526
527 static void
528 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
529   EmpathyContact *contact)
530 {
531   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
532
533   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
534 }
535
536 GtkWidget *
537 empathy_individual_chat_menu_item_new (FolksIndividual *individual,
538     EmpathyContact *contact)
539 {
540   GtkWidget *item;
541   GtkWidget *image;
542
543   g_return_val_if_fail ((FOLKS_IS_INDIVIDUAL (individual) &&
544       empathy_folks_individual_contains_contact (individual)) ||
545       EMPATHY_IS_CONTACT (contact),
546       NULL);
547
548   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
549   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
550       GTK_ICON_SIZE_MENU);
551   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
552   gtk_widget_show (image);
553
554   if (contact != NULL)
555     {
556       menu_item_set_contact (item, contact,
557           G_CALLBACK (empathy_individual_chat_menu_item_activated),
558           EMPATHY_ACTION_CHAT);
559     }
560   else
561     {
562       menu_item_set_first_contact (item, individual,
563           G_CALLBACK (empathy_individual_chat_menu_item_activated),
564           EMPATHY_ACTION_CHAT);
565     }
566
567   return item;
568 }
569
570 static void
571 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
572   EmpathyContact *contact)
573 {
574   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
575
576   empathy_sms_contact_id (
577       empathy_contact_get_account (contact),
578       empathy_contact_get_id (contact),
579       empathy_get_current_action_time ());
580 }
581
582 GtkWidget *
583 empathy_individual_sms_menu_item_new (FolksIndividual *individual,
584     EmpathyContact *contact)
585 {
586   GtkWidget *item;
587   GtkWidget *image;
588
589   g_return_val_if_fail ((FOLKS_IS_INDIVIDUAL (individual) &&
590       empathy_folks_individual_contains_contact (individual)) ||
591       EMPATHY_IS_CONTACT (contact),
592       NULL);
593
594   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
595   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
596       GTK_ICON_SIZE_MENU);
597   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
598   gtk_widget_show (image);
599
600   if (contact != NULL)
601     {
602       menu_item_set_contact (item, contact,
603           G_CALLBACK (empathy_individual_sms_menu_item_activated),
604           EMPATHY_ACTION_SMS);
605     }
606   else
607     {
608       menu_item_set_first_contact (item, individual,
609           G_CALLBACK (empathy_individual_sms_menu_item_activated),
610           EMPATHY_ACTION_SMS);
611     }
612
613   return item;
614 }
615
616 static void
617 empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
618   EmpathyContact *contact)
619 {
620   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
621
622   empathy_call_new_with_streams (empathy_contact_get_id (contact),
623       empathy_contact_get_account (contact),
624       TRUE, FALSE,
625       empathy_get_current_action_time ());
626 }
627
628 GtkWidget *
629 empathy_individual_audio_call_menu_item_new (FolksIndividual *individual,
630     EmpathyContact *contact)
631 {
632   GtkWidget *item;
633   GtkWidget *image;
634
635   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
636       EMPATHY_IS_CONTACT (contact),
637       NULL);
638
639   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
640   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
641   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
642   gtk_widget_show (image);
643
644   if (contact != NULL)
645     {
646       menu_item_set_contact (item, contact,
647           G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
648           EMPATHY_ACTION_AUDIO_CALL);
649     }
650   else
651     {
652       menu_item_set_first_contact (item, individual,
653           G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
654           EMPATHY_ACTION_AUDIO_CALL);
655     }
656
657   return item;
658 }
659
660 static void
661 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
662   EmpathyContact *contact)
663 {
664   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
665
666   empathy_call_new_with_streams (empathy_contact_get_id (contact),
667       empathy_contact_get_account (contact),
668       TRUE, TRUE,
669       empathy_get_current_action_time ());
670 }
671
672 GtkWidget *
673 empathy_individual_video_call_menu_item_new (FolksIndividual *individual,
674     EmpathyContact *contact)
675 {
676   GtkWidget *item;
677   GtkWidget *image;
678   EmpathyCameraMonitor *monitor;
679
680   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
681       EMPATHY_IS_CONTACT (contact),
682       NULL);
683
684   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
685   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
686       GTK_ICON_SIZE_MENU);
687   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
688   gtk_widget_show (image);
689
690   if (contact != NULL)
691     {
692       menu_item_set_contact (item, contact,
693           G_CALLBACK (empathy_individual_video_call_menu_item_activated),
694           EMPATHY_ACTION_VIDEO_CALL);
695     }
696   else
697     {
698       menu_item_set_first_contact (item, individual,
699           G_CALLBACK (empathy_individual_video_call_menu_item_activated),
700           EMPATHY_ACTION_VIDEO_CALL);
701     }
702
703   /* Only follow available cameras if the contact can do Video calls */
704   if (gtk_widget_get_sensitive (item))
705     {
706       monitor = empathy_camera_monitor_dup_singleton ();
707       g_object_set_data_full (G_OBJECT (item),
708           "monitor", monitor, g_object_unref);
709       g_object_bind_property (monitor, "available", item, "sensitive",
710           G_BINDING_SYNC_CREATE);
711     }
712
713   return item;
714 }
715
716 static void
717 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
718   EmpathyContact *contact)
719 {
720   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
721
722   empathy_log_window_show (empathy_contact_get_account (contact),
723       empathy_contact_get_id (contact), FALSE, NULL);
724 }
725
726 GtkWidget *
727 empathy_individual_log_menu_item_new (FolksIndividual *individual,
728     EmpathyContact *contact)
729 {
730   GtkWidget *item;
731   GtkWidget *image;
732
733   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
734       EMPATHY_IS_CONTACT (contact),
735       NULL);
736
737   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
738   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
739   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
740   gtk_widget_show (image);
741
742   if (contact != NULL)
743     {
744       menu_item_set_contact (item, contact,
745           G_CALLBACK (empathy_individual_log_menu_item_activated),
746           EMPATHY_ACTION_VIEW_LOGS);
747     }
748   else
749     {
750       menu_item_set_first_contact (item, individual,
751           G_CALLBACK (empathy_individual_log_menu_item_activated),
752           EMPATHY_ACTION_VIEW_LOGS);
753     }
754
755   return item;
756 }
757
758 static void
759 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
760     EmpathyContact *contact)
761 {
762   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
763
764   empathy_send_file_with_file_chooser (contact);
765 }
766
767 GtkWidget *
768 empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual,
769     EmpathyContact *contact)
770 {
771   GtkWidget *item;
772   GtkWidget *image;
773
774   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
775       EMPATHY_IS_CONTACT (contact),
776       NULL);
777
778   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
779   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
780       GTK_ICON_SIZE_MENU);
781   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
782   gtk_widget_show (image);
783
784   if (contact != NULL)
785     {
786       menu_item_set_contact (item, contact,
787           G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
788           EMPATHY_ACTION_SEND_FILE);
789     }
790   else
791     {
792       menu_item_set_first_contact (item, individual,
793           G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
794           EMPATHY_ACTION_SEND_FILE);
795     }
796
797   return item;
798 }
799
800 static void
801 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
802     EmpathyContact *contact)
803 {
804   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
805
806   empathy_share_my_desktop_share_with_contact (contact);
807 }
808
809 GtkWidget *
810 empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual,
811     EmpathyContact *contact)
812 {
813   GtkWidget *item;
814   GtkWidget *image;
815
816   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
817       EMPATHY_IS_CONTACT (contact),
818       NULL);
819
820   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
821   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
822   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
823   gtk_widget_show (image);
824
825   if (contact != NULL)
826     {
827       menu_item_set_contact (item, contact,
828           G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
829           EMPATHY_ACTION_SHARE_MY_DESKTOP);
830     }
831   else
832     {
833       menu_item_set_first_contact (item, individual,
834           G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
835           EMPATHY_ACTION_SHARE_MY_DESKTOP);
836     }
837
838   return item;
839 }
840
841 static void
842 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
843   FolksIndividual *individual)
844 {
845   folks_favourite_details_set_is_favourite (
846       FOLKS_FAVOURITE_DETAILS (individual),
847       gtk_check_menu_item_get_active (item));
848 }
849
850 GtkWidget *
851 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
852 {
853   GtkWidget *item;
854
855   item = gtk_check_menu_item_new_with_label (_("Favorite"));
856
857   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
858       folks_favourite_details_get_is_favourite (
859           FOLKS_FAVOURITE_DETAILS (individual)));
860
861   g_signal_connect (item, "toggled",
862       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
863
864   return item;
865 }
866
867 static void
868 individual_info_menu_item_activate_cb (FolksIndividual *individual)
869 {
870   empathy_individual_information_dialog_show (individual, NULL);
871 }
872
873 GtkWidget *
874 empathy_individual_info_menu_item_new (FolksIndividual *individual)
875 {
876   GtkWidget *item;
877   GtkWidget *image;
878
879   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
880   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
881       NULL);
882
883   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
884   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
885                 GTK_ICON_SIZE_MENU);
886   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
887   gtk_widget_show (image);
888
889   g_signal_connect_swapped (item, "activate",
890           G_CALLBACK (individual_info_menu_item_activate_cb),
891           individual);
892
893   return item;
894 }
895
896 static void
897 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
898 {
899   empathy_individual_edit_dialog_show (individual, NULL);
900 }
901
902 GtkWidget *
903 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
904 {
905   EmpathyIndividualManager *manager;
906   GtkWidget *item;
907   GtkWidget *image;
908   gboolean enable = FALSE;
909   EmpathyContact *contact;
910
911   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
912
913   contact = empathy_contact_dup_from_folks_individual (individual);
914
915   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
916
917   if (empathy_individual_manager_initialized ())
918     {
919       TpConnection *connection;
920
921       manager = empathy_individual_manager_dup_singleton ();
922       connection = empathy_contact_get_connection (contact);
923
924       enable = (empathy_connection_can_alias_personas (connection) &&
925                 empathy_connection_can_group_personas (connection));
926
927       g_object_unref (manager);
928     }
929
930   item = gtk_image_menu_item_new_with_mnemonic (
931       C_("Edit individual (contextual menu)", "_Edit"));
932   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
933   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
934   gtk_widget_show (image);
935
936   gtk_widget_set_sensitive (item, enable);
937
938   g_signal_connect_swapped (item, "activate",
939       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
940
941   g_object_unref (contact);
942
943   return item;
944 }
945
946 GtkWidget *
947 empathy_individual_link_menu_item_new (FolksIndividual *individual)
948 {
949   GtkWidget *item;
950   /*GtkWidget *image;*/
951
952   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
953
954   item = gtk_image_menu_item_new_with_mnemonic (
955       /* Translators: this is a verb meaning "to connect two contacts together
956        * to form a meta-contact". */
957       C_("Link individual (contextual menu)", "_Link Contacts…"));
958   /* TODO */
959   /*image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
960   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
961   gtk_widget_show (image);*/
962
963   /* Only allow trusted Individuals to be linked */
964   gtk_widget_set_sensitive (item,
965       folks_individual_get_trust_level (individual) ==
966           FOLKS_TRUST_LEVEL_PERSONAS);
967
968   return item;
969 }
970
971 typedef struct
972 {
973   FolksIndividual *individual;
974   EmpathyContact *contact;
975   EmpathyChatroom *chatroom;
976 } RoomSubMenuData;
977
978 static RoomSubMenuData *
979 room_sub_menu_data_new (FolksIndividual *individual,
980     EmpathyContact *contact,
981     EmpathyChatroom *chatroom)
982 {
983   RoomSubMenuData *data;
984
985   data = g_slice_new0 (RoomSubMenuData);
986   if (individual != NULL)
987     data->individual = g_object_ref (individual);
988   if (contact != NULL)
989     data->contact = g_object_ref (contact);
990   data->chatroom = g_object_ref (chatroom);
991
992   return data;
993 }
994
995 static void
996 room_sub_menu_data_free (RoomSubMenuData *data)
997 {
998   tp_clear_object (&data->individual);
999   tp_clear_object (&data->contact);
1000   g_object_unref (data->chatroom);
1001   g_slice_free (RoomSubMenuData, data);
1002 }
1003
1004 static void
1005 room_sub_menu_activate_cb (GtkWidget *item,
1006          RoomSubMenuData *data)
1007 {
1008   EmpathyTpChat *chat;
1009   EmpathyChatroomManager *mgr;
1010   EmpathyContact *contact = NULL;
1011
1012   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1013   if (chat == NULL)
1014     {
1015       /* channel was invalidated. Ignoring */
1016       return;
1017     }
1018
1019   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1020
1021   if (data->contact != NULL)
1022     contact = g_object_ref (data->contact);
1023   else
1024     {
1025       GeeSet *personas;
1026       GeeIterator *iter;
1027
1028       /* find the first of this Individual's contacts who can join this room */
1029       personas = folks_individual_get_personas (data->individual);
1030
1031       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1032       while (gee_iterator_next (iter) && (contact == NULL))
1033         {
1034           TpfPersona *persona = gee_iterator_get (iter);
1035           TpContact *tp_contact;
1036           GList *rooms;
1037
1038           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1039             {
1040               tp_contact = tpf_persona_get_contact (persona);
1041               contact = empathy_contact_dup_from_tp_contact (tp_contact);
1042
1043               rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1044                   empathy_contact_get_account (contact));
1045
1046               if (g_list_find (rooms, data->chatroom) == NULL)
1047                 g_clear_object (&contact);
1048
1049               /* if contact != NULL here, we've found our match */
1050
1051               g_list_free (rooms);
1052             }
1053           g_clear_object (&persona);
1054         }
1055       g_clear_object (&iter);
1056     }
1057
1058   g_object_unref (mgr);
1059
1060   if (contact == NULL)
1061     {
1062       /* contact disappeared. Ignoring */
1063       goto out;
1064     }
1065
1066   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1067
1068   /* send invitation */
1069   empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat),
1070       contact, _("Inviting you to this room"));
1071
1072 out:
1073   g_object_unref (contact);
1074 }
1075
1076 static GtkWidget *
1077 create_room_sub_menu (FolksIndividual *individual,
1078                       EmpathyContact *contact,
1079                       EmpathyChatroom *chatroom)
1080 {
1081   GtkWidget *item;
1082   RoomSubMenuData *data;
1083
1084   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1085   data = room_sub_menu_data_new (individual, contact, chatroom);
1086   g_signal_connect_data (item, "activate",
1087       G_CALLBACK (room_sub_menu_activate_cb), data,
1088       (GClosureNotify) room_sub_menu_data_free, 0);
1089
1090   return item;
1091 }
1092
1093 GtkWidget *
1094 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1095     EmpathyContact *contact)
1096 {
1097   GtkWidget *item;
1098   GtkWidget *image;
1099   GtkWidget *room_item;
1100   EmpathyChatroomManager *mgr;
1101   GList *rooms = NULL;
1102   GList *names = NULL;
1103   GList *l;
1104   GtkWidget *submenu = NULL;
1105   /* map of chat room names to their objects; just a utility to remove
1106    * duplicates and to make construction of the alphabetized list easier */
1107   GHashTable *name_room_map;
1108
1109   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1110       EMPATHY_IS_CONTACT (contact),
1111       NULL);
1112
1113   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1114       g_object_unref);
1115
1116   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1117   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1118       GTK_ICON_SIZE_MENU);
1119   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1120
1121   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1122
1123   if (contact != NULL)
1124     {
1125       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1126           empathy_contact_get_account (contact));
1127     }
1128   else
1129     {
1130       GeeSet *personas;
1131       GeeIterator *iter;
1132
1133       /* find the first of this Individual's contacts who can join this room */
1134       personas = folks_individual_get_personas (individual);
1135       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1136       while (gee_iterator_next (iter))
1137         {
1138           TpfPersona *persona = gee_iterator_get (iter);
1139           GList *rooms_cur;
1140           TpContact *tp_contact;
1141           EmpathyContact *contact_cur;
1142
1143           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1144             {
1145               tp_contact = tpf_persona_get_contact (persona);
1146               contact_cur = empathy_contact_dup_from_tp_contact (tp_contact);
1147
1148               rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1149                   empathy_contact_get_account (contact_cur));
1150               rooms = g_list_concat (rooms, rooms_cur);
1151
1152               g_object_unref (contact_cur);
1153             }
1154           g_clear_object (&persona);
1155         }
1156       g_clear_object (&iter);
1157     }
1158
1159   /* alphabetize the rooms */
1160   for (l = rooms; l != NULL; l = g_list_next (l))
1161     {
1162       EmpathyChatroom *chatroom = l->data;
1163       gboolean existed;
1164
1165       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1166         {
1167           const gchar *name;
1168
1169           name = empathy_chatroom_get_name (chatroom);
1170           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1171           g_hash_table_insert (name_room_map, (gpointer) name,
1172               g_object_ref (chatroom));
1173
1174           /* this will take care of duplicates in rooms */
1175           if (!existed)
1176             {
1177               names = g_list_insert_sorted (names, (gpointer) name,
1178                   (GCompareFunc) g_strcmp0);
1179             }
1180         }
1181     }
1182
1183   for (l = names; l != NULL; l = g_list_next (l))
1184     {
1185       const gchar *name = l->data;
1186       EmpathyChatroom *chatroom;
1187
1188       if (G_UNLIKELY (submenu == NULL))
1189         submenu = gtk_menu_new ();
1190
1191       chatroom = g_hash_table_lookup (name_room_map, name);
1192       room_item = create_room_sub_menu (individual, contact, chatroom);
1193       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1194       gtk_widget_show (room_item);
1195     }
1196
1197   if (submenu)
1198     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1199   else
1200     gtk_widget_set_sensitive (item, FALSE);
1201
1202   gtk_widget_show (image);
1203
1204   g_hash_table_destroy (name_room_map);
1205   g_object_unref (mgr);
1206   g_list_free (names);
1207   g_list_free (rooms);
1208
1209   return item;
1210 }