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