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