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