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