]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Fix assertion failure on opening conversation menu in 1-1 chats
[empathy.git] / src / empathy-chat-window.c
1 /*
2  * Copyright (C) 2003-2007 Imendio AB
3  * Copyright (C) 2007-2012 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  *          Rômulo Fernandes Machado <romulo@castorgroup.net>
26  */
27
28 #include "config.h"
29 #include "empathy-chat-window.h"
30
31 #include <glib/gi18n.h>
32
33 #include "empathy-about-dialog.h"
34 #include "empathy-chat-manager.h"
35 #include "empathy-chatroom-manager.h"
36 #include "empathy-client-factory.h"
37 #include "empathy-geometry.h"
38 #include "empathy-gsettings.h"
39 #include "empathy-images.h"
40 #include "empathy-invite-participant-dialog.h"
41 #include "empathy-notify-manager.h"
42 #include "empathy-request-util.h"
43 #include "empathy-smiley-manager.h"
44 #include "empathy-sound-manager.h"
45 #include "empathy-ui-utils.h"
46 #include "empathy-utils.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
49 #include "empathy-debug.h"
50
51 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
52  */
53 #define X_EARLIER_OR_EQL(t1, t2) \
54   ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2))  \
55     || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
56   )
57
58 enum
59 {
60   PROP_INDIVIDUAL_MGR = 1
61 };
62
63 struct _EmpathyChatWindowPriv
64 {
65   EmpathyChat *current_chat;
66   GList *chats;
67   gboolean page_added;
68   gboolean dnd_same_window;
69   EmpathyChatroomManager *chatroom_manager;
70   EmpathyNotifyManager *notify_mgr;
71   EmpathyIndividualManager *individual_mgr;
72   GtkWidget *notebook;
73   NotifyNotification *notification;
74
75   GtkTargetList *contact_targets;
76   GtkTargetList *file_targets;
77
78   EmpathyChatManager *chat_manager;
79   gulong chat_manager_chats_changed_id;
80
81   /* Menu items. */
82   GtkUIManager *ui_manager;
83   GtkAction *menu_conv_insert_smiley;
84   GtkAction *menu_conv_favorite;
85   GtkAction *menu_conv_join_chat;
86   GtkAction *menu_conv_leave_chat;
87   GtkAction *menu_conv_always_urgent;
88   GtkAction *menu_conv_toggle_contacts;
89
90   GtkAction *menu_edit_cut;
91   GtkAction *menu_edit_copy;
92   GtkAction *menu_edit_paste;
93   GtkAction *menu_edit_find;
94
95   GtkAction *menu_tabs_next;
96   GtkAction *menu_tabs_prev;
97   GtkAction *menu_tabs_undo_close_tab;
98   GtkAction *menu_tabs_left;
99   GtkAction *menu_tabs_right;
100   GtkAction *menu_tabs_detach;
101
102   /* Last user action time we acted upon to show a tab */
103   guint32 x_user_action_time;
104
105   GSettings *gsettings_chat;
106   GSettings *gsettings_notif;
107   GSettings *gsettings_ui;
108
109   EmpathySoundManager *sound_mgr;
110
111   gboolean updating_menu;
112 };
113
114 static GList *chat_windows = NULL;
115
116 static const guint tab_accel_keys[] =
117 {
118   GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
119   GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
120 };
121
122 typedef enum
123 {
124   DND_DRAG_TYPE_CONTACT_ID,
125   DND_DRAG_TYPE_INDIVIDUAL_ID,
126   DND_DRAG_TYPE_URI_LIST,
127   DND_DRAG_TYPE_TAB
128 } DndDragType;
129
130 static const GtkTargetEntry drag_types_dest[] =
131 {
132   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
133   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
134   { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
135   /* FIXME: disabled because of bug #640513
136   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
137   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
138   */
139 };
140
141 static const GtkTargetEntry drag_types_dest_contact[] =
142 {
143   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
144   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
145 };
146
147 static const GtkTargetEntry drag_types_dest_file[] =
148 {
149   /* must be first to be prioritized, in order to receive the
150    * note's file path from Tomboy instead of an URI */
151   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
152   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
153 };
154
155 static void chat_window_update (EmpathyChatWindow *window,
156     gboolean update_contact_menu);
157
158 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
159     EmpathyChat *chat);
160
161 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
162     EmpathyChat *chat);
163
164 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
165     EmpathyChatWindow *new_window,
166     EmpathyChat *chat);
167
168 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
169     guint *nb_rooms,
170     guint *nb_private);
171
172 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
173
174 static void
175 chat_window_accel_cb (GtkAccelGroup *accelgroup,
176     GObject *object,
177     guint key,
178     GdkModifierType mod,
179     EmpathyChatWindow *self)
180 {
181   gint num = -1;
182   guint i;
183
184   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
185     {
186       if (tab_accel_keys[i] == key)
187         {
188           num = i;
189           break;
190         }
191     }
192
193   if (num != -1)
194     gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
195 }
196
197 static EmpathyChatWindow *
198 chat_window_find_chat (EmpathyChat *chat)
199 {
200   GList *l, *ll;
201
202   for (l = chat_windows; l; l = l->next)
203     {
204       EmpathyChatWindow *window = l->data;
205
206       ll = g_list_find (window->priv->chats, chat);
207       if (ll)
208         return l->data;
209     }
210
211   return NULL;
212 }
213
214 static void
215 remove_all_chats (EmpathyChatWindow *self)
216 {
217   g_object_ref (self);
218
219   while (self->priv->chats)
220     empathy_chat_window_remove_chat (self, self->priv->chats->data);
221
222   g_object_unref (self);
223 }
224
225 static void
226 confirm_close_response_cb (GtkWidget *dialog,
227     int response,
228     EmpathyChatWindow *window)
229 {
230   EmpathyChat *chat;
231
232   chat = g_object_get_data (G_OBJECT (dialog), "chat");
233
234   gtk_widget_destroy (dialog);
235
236   if (response != GTK_RESPONSE_ACCEPT)
237     return;
238
239   if (chat != NULL)
240     empathy_chat_window_remove_chat (window, chat);
241   else
242     remove_all_chats (window);
243 }
244
245 static void
246 confirm_close (EmpathyChatWindow *self,
247     gboolean close_window,
248     guint n_rooms,
249     EmpathyChat *chat)
250 {
251   GtkWidget *dialog;
252   gchar *primary, *secondary;
253
254   g_return_if_fail (n_rooms > 0);
255
256   if (n_rooms > 1)
257     g_return_if_fail (chat == NULL);
258   else
259     g_return_if_fail (chat != NULL);
260
261   /* If there are no chats in this window, how could we possibly have got
262    * here?
263    */
264   g_return_if_fail (self->priv->chats != NULL);
265
266   /* Treat closing a window which only has one tab exactly like closing
267    * that tab.
268    */
269   if (close_window && self->priv->chats->next == NULL)
270     {
271       close_window = FALSE;
272       chat = self->priv->chats->data;
273     }
274
275   if (close_window)
276     {
277       primary = g_strdup (_("Close this window?"));
278
279       if (n_rooms == 1)
280         {
281           gchar *chat_name = empathy_chat_dup_name (chat);
282           secondary = g_strdup_printf (
283             _("Closing this window will leave %s. You will "
284               "not receive any further messages until you "
285               "rejoin it."),
286             chat_name);
287           g_free (chat_name);
288         }
289       else
290         {
291           secondary = g_strdup_printf (
292             /* Note to translators: the number of chats will
293              * always be at least 2.
294              */
295             ngettext (
296               "Closing this window will leave a chat room. You will "
297               "not receive any further messages until you rejoin it.",
298               "Closing this window will leave %u chat rooms. You will "
299               "not receive any further messages until you rejoin them.",
300               n_rooms),
301             n_rooms);
302         }
303     }
304   else
305     {
306       gchar *chat_name = empathy_chat_dup_name (chat);
307       primary = g_strdup_printf (_("Leave %s?"), chat_name);
308       secondary = g_strdup (
309           _("You will not receive any further messages from this chat "
310             "room until you rejoin it."));
311       g_free (chat_name);
312     }
313
314   dialog = gtk_message_dialog_new (
315     GTK_WINDOW (self),
316     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
317     GTK_MESSAGE_WARNING,
318     GTK_BUTTONS_CANCEL,
319     "%s", primary);
320
321   gtk_window_set_title (GTK_WINDOW (dialog), "");
322   g_object_set (dialog, "secondary-text", secondary, NULL);
323
324   g_free (primary);
325   g_free (secondary);
326
327   gtk_dialog_add_button (GTK_DIALOG (dialog),
328     close_window ? _("Close window") : _("Leave room"),
329     GTK_RESPONSE_ACCEPT);
330   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
331     GTK_RESPONSE_ACCEPT);
332
333   if (!close_window)
334     g_object_set_data (G_OBJECT (dialog), "chat", chat);
335
336   g_signal_connect (dialog, "response",
337     G_CALLBACK (confirm_close_response_cb), self);
338
339   gtk_window_present (GTK_WINDOW (dialog));
340 }
341
342 /* Returns TRUE if we should check if the user really wants to leave. If it's
343  * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
344  * the user is actually in the room as opposed to having been kicked or gone
345  * offline or something), then we should check.
346  */
347 static gboolean
348 chat_needs_close_confirmation (EmpathyChat *chat)
349 {
350   return (empathy_chat_is_room (chat) &&
351       empathy_chat_get_tp_chat (chat) != NULL);
352 }
353
354 static void
355 maybe_close_chat (EmpathyChatWindow *window,
356     EmpathyChat *chat)
357 {
358   g_return_if_fail (chat != NULL);
359
360   if (chat_needs_close_confirmation (chat))
361     confirm_close (window, FALSE, 1, chat);
362   else
363     empathy_chat_window_remove_chat (window, chat);
364 }
365
366 static void
367 chat_window_close_clicked_cb (GtkAction *action,
368     EmpathyChat *chat)
369 {
370   EmpathyChatWindow *window;
371
372   window = chat_window_find_chat (chat);
373   maybe_close_chat (window, chat);
374 }
375
376 static void
377 chat_tab_style_updated_cb (GtkWidget *hbox,
378     gpointer user_data)
379 {
380   GtkWidget *button;
381   int char_width, h, w;
382   PangoContext *context;
383   const PangoFontDescription *font_desc;
384   PangoFontMetrics *metrics;
385
386   button = g_object_get_data (G_OBJECT (user_data),
387     "chat-window-tab-close-button");
388   context = gtk_widget_get_pango_context (hbox);
389
390   font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
391       GTK_STATE_FLAG_NORMAL);
392
393   metrics = pango_context_get_metrics (context, font_desc,
394     pango_context_get_language (context));
395   char_width = pango_font_metrics_get_approximate_char_width (metrics);
396   pango_font_metrics_unref (metrics);
397
398   gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
399       GTK_ICON_SIZE_MENU, &w, &h);
400
401   /* Request at least about 12 chars width plus at least space for the status
402    * image and the close button */
403   gtk_widget_set_size_request (hbox,
404     12 * PANGO_PIXELS (char_width) + 2 * w, -1);
405
406   gtk_widget_set_size_request (button, w, h);
407 }
408
409 static GtkWidget *
410 create_close_button (void)
411 {
412   GtkWidget *button, *image;
413   GtkStyleContext *context;
414
415   button = gtk_button_new ();
416
417   context = gtk_widget_get_style_context (button);
418   gtk_style_context_add_class (context, "empathy-tab-close-button");
419
420   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
421   gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
422
423   /* We don't want focus/keynav for the button to avoid clutter, and
424    * Ctrl-W works anyway.
425    */
426   gtk_widget_set_can_focus (button, FALSE);
427   gtk_widget_set_can_default (button, FALSE);
428
429   image = gtk_image_new_from_icon_name ("window-close-symbolic",
430       GTK_ICON_SIZE_MENU);
431   gtk_widget_show (image);
432
433   gtk_container_add (GTK_CONTAINER (button), image);
434
435   return button;
436 }
437
438 static GtkWidget *
439 chat_window_create_label (EmpathyChatWindow *window,
440     EmpathyChat *chat,
441     gboolean is_tab_label)
442 {
443   GtkWidget *hbox;
444   GtkWidget *name_label;
445   GtkWidget *status_image;
446   GtkWidget *event_box;
447   GtkWidget *event_box_hbox;
448   PangoAttrList *attr_list;
449   PangoAttribute *attr;
450
451   /* The spacing between the button and the label. */
452   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
453
454   event_box = gtk_event_box_new ();
455   gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
456
457   name_label = gtk_label_new (NULL);
458   if (is_tab_label)
459     gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
460
461   attr_list = pango_attr_list_new ();
462   attr = pango_attr_scale_new (1/1.2);
463   attr->start_index = 0;
464   attr->end_index = -1;
465   pango_attr_list_insert (attr_list, attr);
466   gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
467   pango_attr_list_unref (attr_list);
468
469   gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
470   gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
471   g_object_set_data (G_OBJECT (chat),
472     is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
473     name_label);
474
475   status_image = gtk_image_new ();
476
477   /* Spacing between the icon and label. */
478   event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
479
480   gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
481   gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
482
483   g_object_set_data (G_OBJECT (chat),
484     is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
485     status_image);
486   g_object_set_data (G_OBJECT (chat),
487     is_tab_label ? "chat-window-tab-tooltip-widget" :
488       "chat-window-menu-tooltip-widget",
489     event_box);
490
491   gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
492   gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
493
494   if (is_tab_label)
495     {
496       GtkWidget *close_button;
497       GtkWidget *sending_spinner;
498
499       sending_spinner = gtk_spinner_new ();
500
501       gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
502         FALSE, FALSE, 0);
503       g_object_set_data (G_OBJECT (chat),
504         "chat-window-tab-sending-spinner",
505         sending_spinner);
506
507       close_button = create_close_button ();
508       g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
509           close_button);
510
511       gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
512
513       g_signal_connect (close_button,
514           "clicked",
515           G_CALLBACK (chat_window_close_clicked_cb), chat);
516
517       /* React to theme changes and also setup the size correctly. */
518       g_signal_connect (hbox, "style-updated",
519           G_CALLBACK (chat_tab_style_updated_cb), chat);
520     }
521
522   gtk_widget_show_all (hbox);
523
524   return hbox;
525 }
526
527 static void
528 _submenu_notify_visible_changed_cb (GObject *object,
529     GParamSpec *pspec,
530     gpointer userdata)
531 {
532   g_signal_handlers_disconnect_by_func (object,
533       _submenu_notify_visible_changed_cb, userdata);
534
535   chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
536 }
537
538 static void
539 chat_window_menu_context_update (EmpathyChatWindow *self,
540     gint num_pages)
541 {
542   gboolean first_page;
543   gboolean last_page;
544   gboolean wrap_around;
545   gboolean is_connected;
546   gint page_num;
547
548   page_num = gtk_notebook_get_current_page (
549       GTK_NOTEBOOK (self->priv->notebook));
550   first_page = (page_num == 0);
551   last_page = (page_num == (num_pages - 1));
552   g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
553       &wrap_around, NULL);
554   is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
555
556   gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
557         wrap_around));
558   gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
559         wrap_around));
560   gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
561   gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
562   gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
563   gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
564 }
565
566 static void
567 chat_window_conversation_menu_update (EmpathyChatWindow *self)
568 {
569   EmpathyTpChat *tp_chat;
570   TpConnection *connection;
571   GtkAction *action;
572   gboolean sensitive = FALSE;
573
574   g_return_if_fail (self->priv->current_chat != NULL);
575
576   action = gtk_ui_manager_get_action (self->priv->ui_manager,
577     "/chats_menubar/menu_conv/menu_conv_invite_participant");
578   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
579
580   if (tp_chat != NULL)
581     {
582       connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
583
584       sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
585         (tp_connection_get_status (connection, NULL) ==
586          TP_CONNECTION_STATUS_CONNECTED);
587     }
588
589   gtk_action_set_sensitive (action, sensitive);
590 }
591
592 static void
593 chat_window_contact_menu_update (EmpathyChatWindow *self)
594 {
595   GtkWidget *menu, *submenu, *orig_submenu;
596
597   if (self->priv->updating_menu)
598     return;
599   self->priv->updating_menu = TRUE;
600
601   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
602     "/chats_menubar/menu_contact");
603   orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
604
605   if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
606     {
607       submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
608
609       if (submenu != NULL)
610         {
611           /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612           g_object_set_data (G_OBJECT (submenu), "window", self);
613
614           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615           gtk_widget_show (menu);
616           gtk_widget_set_sensitive (menu, TRUE);
617         }
618       else
619         {
620           gtk_widget_set_sensitive (menu, FALSE);
621         }
622     }
623   else
624     {
625       tp_g_signal_connect_object (orig_submenu,
626           "notify::visible",
627           (GCallback)_submenu_notify_visible_changed_cb, self, 0);
628     }
629
630   self->priv->updating_menu = FALSE;
631 }
632
633 static guint
634 get_all_unread_messages (EmpathyChatWindow *self)
635 {
636   GList *l;
637   guint nb = 0;
638
639   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
640     nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
641
642   return nb;
643 }
644
645 static gchar *
646 get_window_title_name (EmpathyChatWindow *self)
647 {
648   gchar *active_name, *ret;
649   guint nb_chats;
650   guint current_unread_msgs;
651
652   nb_chats = g_list_length (self->priv->chats);
653   g_assert (nb_chats > 0);
654
655   active_name = empathy_chat_dup_name (self->priv->current_chat);
656
657   current_unread_msgs = empathy_chat_get_nb_unread_messages (
658       self->priv->current_chat);
659
660   if (nb_chats == 1)
661     {
662       /* only one tab */
663       if (current_unread_msgs == 0)
664         ret = g_strdup (active_name);
665       else
666         ret = g_strdup_printf (ngettext (
667           "%s (%d unread)",
668           "%s (%d unread)", current_unread_msgs),
669           active_name, current_unread_msgs);
670     }
671   else
672     {
673       guint nb_others = nb_chats - 1;
674       guint all_unread_msgs;
675
676       all_unread_msgs = get_all_unread_messages (self);
677
678       if (all_unread_msgs == 0)
679         {
680           /* no unread message */
681           ret = g_strdup_printf (ngettext (
682             "%s (and %u other)",
683             "%s (and %u others)", nb_others),
684             active_name, nb_others);
685         }
686       else if (all_unread_msgs == current_unread_msgs)
687         {
688           /* unread messages are in the current tab */
689           ret = g_strdup_printf (ngettext (
690             "%s (%d unread)",
691             "%s (%d unread)", current_unread_msgs),
692             active_name, current_unread_msgs);
693         }
694       else if (current_unread_msgs == 0)
695         {
696           /* unread messages are in other tabs */
697           ret = g_strdup_printf (ngettext (
698             "%s (%d unread from others)",
699             "%s (%d unread from others)",
700             all_unread_msgs),
701             active_name, all_unread_msgs);
702         }
703       else
704         {
705           /* unread messages are in all the tabs */
706           ret = g_strdup_printf (ngettext (
707             "%s (%d unread from all)",
708             "%s (%d unread from all)",
709             all_unread_msgs),
710             active_name, all_unread_msgs);
711         }
712     }
713
714   g_free (active_name);
715
716   return ret;
717 }
718
719 static void
720 chat_window_title_update (EmpathyChatWindow *self)
721 {
722   gchar *name;
723
724   name = get_window_title_name (self);
725   gtk_window_set_title (GTK_WINDOW (self), name);
726   g_free (name);
727 }
728
729 static void
730 chat_window_icon_update (EmpathyChatWindow *self,
731     gboolean new_messages)
732 {
733   GdkPixbuf *icon;
734   EmpathyContact *remote_contact;
735   gboolean avatar_in_icon;
736   guint n_chats;
737
738   n_chats = g_list_length (self->priv->chats);
739
740   /* Update window icon */
741   if (new_messages)
742     {
743       gtk_window_set_icon_name (GTK_WINDOW (self),
744           EMPATHY_IMAGE_MESSAGE);
745     }
746   else
747     {
748       avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
749           EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
750
751       if (n_chats == 1 && avatar_in_icon)
752         {
753           remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
754           icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
755               0, 0);
756           gtk_window_set_icon (GTK_WINDOW (self), icon);
757
758           if (icon != NULL)
759             g_object_unref (icon);
760         }
761       else
762         {
763           gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
764         }
765     }
766 }
767
768 static void
769 chat_window_close_button_update (EmpathyChatWindow *self,
770     gint num_pages)
771 {
772   GtkWidget *chat;
773   GtkWidget *chat_close_button;
774   gint i;
775
776   if (num_pages == 1)
777     {
778       chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
779       chat_close_button = g_object_get_data (G_OBJECT (chat),
780           "chat-window-tab-close-button");
781       gtk_widget_hide (chat_close_button);
782     }
783   else
784     {
785       for (i=0; i<num_pages; i++)
786         {
787           chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
788           chat_close_button = g_object_get_data (G_OBJECT (chat),
789               "chat-window-tab-close-button");
790           gtk_widget_show (chat_close_button);
791         }
792     }
793 }
794
795 static void
796 chat_window_update (EmpathyChatWindow *self,
797     gboolean update_contact_menu)
798 {
799   gint num_pages;
800
801   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
802
803   /* Update Tab menu */
804   chat_window_menu_context_update (self, num_pages);
805
806   chat_window_conversation_menu_update (self);
807
808   /* If this update is due to a focus-in event, we know the menu will be
809      the same as when we last left it, so no work to do. Besides, if we
810      swap out the menu on a focus-in, we may confuse any external global
811      menu watching. */
812   if (update_contact_menu)
813     {
814       chat_window_contact_menu_update (self);
815     }
816
817   chat_window_title_update (self);
818
819   chat_window_icon_update (self, get_all_unread_messages (self) > 0);
820
821   chat_window_close_button_update (self, num_pages);
822 }
823
824 static void
825 append_markup_printf (GString *string,
826     const char *format,
827     ...)
828 {
829   gchar *tmp;
830   va_list args;
831
832   va_start (args, format);
833
834   tmp = g_markup_vprintf_escaped (format, args);
835   g_string_append (string, tmp);
836   g_free (tmp);
837
838   va_end (args);
839 }
840
841 static void
842 chat_window_update_chat_tab_full (EmpathyChat *chat,
843     gboolean update_contact_menu)
844 {
845   EmpathyChatWindow *self;
846   EmpathyContact *remote_contact;
847   gchar *name;
848   const gchar *id;
849   TpAccount *account;
850   const gchar *subject;
851   const gchar *status = NULL;
852   GtkWidget *widget;
853   GString *tooltip;
854   gchar *markup;
855   const gchar *icon_name;
856   GtkWidget *tab_image;
857   GtkWidget *menu_image;
858   GtkWidget *sending_spinner;
859   guint nb_sending;
860
861   self = chat_window_find_chat (chat);
862   if (!self)
863     return;
864
865   /* Get information */
866   name = empathy_chat_dup_name (chat);
867   account = empathy_chat_get_account (chat);
868   subject = empathy_chat_get_subject (chat);
869   remote_contact = empathy_chat_get_remote_contact (chat);
870
871   DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
872       "remote_contact=%p",
873     name, tp_proxy_get_object_path (account), subject, remote_contact);
874
875   /* Update tab image */
876   if (empathy_chat_get_tp_chat (chat) == NULL)
877     {
878       /* No TpChat, we are disconnected */
879       icon_name = NULL;
880     }
881   else if (empathy_chat_get_nb_unread_messages (chat) > 0)
882     {
883       icon_name = EMPATHY_IMAGE_MESSAGE;
884     }
885   else if (remote_contact && empathy_chat_is_composing (chat))
886     {
887       icon_name = EMPATHY_IMAGE_TYPING;
888     }
889   else if (empathy_chat_is_sms_channel (chat))
890     {
891       icon_name = EMPATHY_IMAGE_SMS;
892     }
893   else if (remote_contact)
894     {
895       icon_name = empathy_icon_name_for_contact (remote_contact);
896     }
897   else
898     {
899       icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
900     }
901
902   tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
903   menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
904
905   if (icon_name != NULL)
906     {
907       gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
908           GTK_ICON_SIZE_MENU);
909       gtk_widget_show (tab_image);
910       gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
911           GTK_ICON_SIZE_MENU);
912       gtk_widget_show (menu_image);
913     }
914   else
915     {
916       gtk_widget_hide (tab_image);
917       gtk_widget_hide (menu_image);
918     }
919
920   /* Update the sending spinner */
921   nb_sending = empathy_chat_get_n_messages_sending (chat);
922   sending_spinner = g_object_get_data (G_OBJECT (chat),
923     "chat-window-tab-sending-spinner");
924
925   g_object_set (sending_spinner,
926     "active", nb_sending > 0,
927     "visible", nb_sending > 0,
928     NULL);
929
930   /* Update tab tooltip */
931   tooltip = g_string_new (NULL);
932
933   if (remote_contact)
934     {
935       id = empathy_contact_get_id (remote_contact);
936       status = empathy_contact_get_presence_message (remote_contact);
937     }
938   else
939     {
940       id = name;
941     }
942
943   if (empathy_chat_is_sms_channel (chat))
944     append_markup_printf (tooltip, "%s ", _("SMS:"));
945
946   append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
947       id, tp_account_get_display_name (account));
948
949   if (nb_sending > 0)
950     {
951       char *tmp = g_strdup_printf (
952         ngettext ("Sending %d message",
953             "Sending %d messages",
954             nb_sending),
955         nb_sending);
956
957       g_string_append (tooltip, "\n");
958       g_string_append (tooltip, tmp);
959
960       gtk_widget_set_tooltip_text (sending_spinner, tmp);
961       g_free (tmp);
962     }
963
964   if (!EMP_STR_EMPTY (status))
965     append_markup_printf (tooltip, "\n<i>%s</i>", status);
966
967   if (!EMP_STR_EMPTY (subject))
968     append_markup_printf (tooltip, "\n<b>%s</b> %s",
969         _("Topic:"), subject);
970
971   if (remote_contact && empathy_chat_is_composing (chat))
972     append_markup_printf (tooltip, "\n%s", _("Typing a message."));
973
974   if (remote_contact != NULL)
975     {
976       const gchar * const *types;
977
978       types = empathy_contact_get_client_types (remote_contact);
979       if (empathy_client_types_contains_mobile_device ((GStrv) types))
980         {
981           /* I'm on a mobile device ! */
982           gchar *tmp = name;
983
984           name = g_strdup_printf ("☎ %s", name);
985           g_free (tmp);
986         }
987     }
988
989   markup = g_string_free (tooltip, FALSE);
990   widget = g_object_get_data (G_OBJECT (chat),
991       "chat-window-tab-tooltip-widget");
992   gtk_widget_set_tooltip_markup (widget, markup);
993
994   widget = g_object_get_data (G_OBJECT (chat),
995       "chat-window-menu-tooltip-widget");
996   gtk_widget_set_tooltip_markup (widget, markup);
997   g_free (markup);
998
999   /* Update tab and menu label */
1000   if (empathy_chat_is_highlighted (chat))
1001     {
1002       markup = g_markup_printf_escaped (
1003         "<span color=\"red\" weight=\"bold\">%s</span>",
1004         name);
1005     }
1006   else
1007     {
1008       markup = g_markup_escape_text (name, -1);
1009     }
1010
1011   widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1012   gtk_label_set_markup (GTK_LABEL (widget), markup);
1013   widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1014   gtk_label_set_markup (GTK_LABEL (widget), markup);
1015   g_free (markup);
1016
1017   /* Update the window if it's the current chat */
1018   if (self->priv->current_chat == chat)
1019     chat_window_update (self, update_contact_menu);
1020
1021   g_free (name);
1022 }
1023
1024 static void
1025 chat_window_update_chat_tab (EmpathyChat *chat)
1026 {
1027   chat_window_update_chat_tab_full (chat, TRUE);
1028 }
1029
1030 static void
1031 chat_window_chat_notify_cb (EmpathyChat *chat)
1032 {
1033   EmpathyChatWindow *window;
1034   EmpathyContact *old_remote_contact;
1035   EmpathyContact *remote_contact = NULL;
1036
1037   old_remote_contact = g_object_get_data (G_OBJECT (chat),
1038       "chat-window-remote-contact");
1039   remote_contact = empathy_chat_get_remote_contact (chat);
1040
1041   if (old_remote_contact != remote_contact)
1042     {
1043       /* The remote-contact associated with the chat changed, we need
1044        * to keep track of any change of that contact and update the
1045        * window each time. */
1046       if (remote_contact)
1047         g_signal_connect_swapped (remote_contact, "notify",
1048             G_CALLBACK (chat_window_update_chat_tab), chat);
1049
1050       if (old_remote_contact)
1051         g_signal_handlers_disconnect_by_func (old_remote_contact,
1052             chat_window_update_chat_tab, chat);
1053
1054       g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1055           g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1056     }
1057
1058   chat_window_update_chat_tab (chat);
1059
1060   window = chat_window_find_chat (chat);
1061   if (window != NULL)
1062     chat_window_update (window, FALSE);
1063 }
1064
1065 static void
1066 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1067     EmpathySmiley *smiley,
1068     gpointer user_data)
1069 {
1070   EmpathyChatWindow *self = user_data;
1071   EmpathyChat *chat;
1072   GtkTextBuffer *buffer;
1073   GtkTextIter iter;
1074
1075   chat = self->priv->current_chat;
1076
1077   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1078   gtk_text_buffer_get_end_iter (buffer, &iter);
1079   gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1080 }
1081
1082 static void
1083 chat_window_conv_activate_cb (GtkAction *action,
1084     EmpathyChatWindow *self)
1085 {
1086   gboolean is_room;
1087   gboolean active;
1088   EmpathyContact *remote_contact = NULL;
1089   gboolean disconnected;
1090
1091   /* Favorite room menu */
1092   is_room = empathy_chat_is_room (self->priv->current_chat);
1093   if (is_room)
1094     {
1095       const gchar *room;
1096       TpAccount *account;
1097       gboolean found = FALSE;
1098       EmpathyChatroom *chatroom;
1099
1100       room = empathy_chat_get_id (self->priv->current_chat);
1101       account = empathy_chat_get_account (self->priv->current_chat);
1102       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1103           account, room);
1104
1105       if (chatroom != NULL)
1106         found = empathy_chatroom_is_favorite (chatroom);
1107
1108       DEBUG ("This room %s favorite", found ? "is" : "is not");
1109       gtk_toggle_action_set_active (
1110         GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1111
1112       if (chatroom != NULL)
1113         found = empathy_chatroom_is_always_urgent (chatroom);
1114
1115       gtk_toggle_action_set_active (
1116           GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1117     }
1118
1119   gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1120   gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1121
1122   /* Show contacts menu */
1123   g_object_get (self->priv->current_chat,
1124       "remote-contact", &remote_contact,
1125       "show-contacts", &active,
1126       NULL);
1127
1128   if (remote_contact == NULL)
1129     {
1130       gtk_toggle_action_set_active (
1131         GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1132     }
1133
1134   /* Menu-items to be visible for MUCs only */
1135   gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1136       (remote_contact == NULL));
1137
1138   disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1139   if (disconnected)
1140     {
1141       gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1142       gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1143     }
1144   else
1145     {
1146       TpChannel *channel = NULL;
1147       TpContact *self_contact = NULL;
1148       TpHandle  self_handle = 0;
1149
1150       channel = (TpChannel *) (empathy_chat_get_tp_chat (
1151           self->priv->current_chat));
1152       self_contact = tp_channel_group_get_self_contact (channel);
1153       if (self_contact == NULL)
1154       {
1155         /* The channel may not be a group */
1156         gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1157       }
1158       else
1159       {
1160         self_handle = tp_contact_get_handle (self_contact);
1161         /* There is sometimes a lag between the members-changed signal
1162            emitted on tp-chat and invalidated signal being emitted on the channel.
1163            Leave Chat menu-item should be sensitive only till our self-handle is
1164            a part of channel-members */
1165         gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1166             self_handle != 0);
1167       }
1168
1169       /* Join Chat is insensitive for a connected chat */
1170       gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1171     }
1172
1173   if (remote_contact != NULL)
1174     g_object_unref (remote_contact);
1175 }
1176
1177 static void
1178 chat_window_clear_activate_cb (GtkAction *action,
1179     EmpathyChatWindow *self)
1180 {
1181   empathy_chat_clear (self->priv->current_chat);
1182 }
1183
1184 static void
1185 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1186     EmpathyChatWindow *self)
1187 {
1188   gboolean active;
1189   TpAccount *account;
1190   gchar *name;
1191   const gchar *room;
1192   EmpathyChatroom *chatroom;
1193
1194   active = gtk_toggle_action_get_active (toggle_action);
1195   account = empathy_chat_get_account (self->priv->current_chat);
1196   room = empathy_chat_get_id (self->priv->current_chat);
1197   name = empathy_chat_dup_name (self->priv->current_chat);
1198
1199   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1200       account, room, name);
1201
1202   empathy_chatroom_set_favorite (chatroom, active);
1203   g_object_unref (chatroom);
1204   g_free (name);
1205 }
1206
1207 static void
1208 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1209     EmpathyChatWindow *self)
1210 {
1211   gboolean active;
1212   TpAccount *account;
1213   gchar *name;
1214   const gchar *room;
1215   EmpathyChatroom *chatroom;
1216
1217   active = gtk_toggle_action_get_active (toggle_action);
1218   account = empathy_chat_get_account (self->priv->current_chat);
1219   room = empathy_chat_get_id (self->priv->current_chat);
1220   name = empathy_chat_dup_name (self->priv->current_chat);
1221
1222   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1223       account, room, name);
1224
1225   empathy_chatroom_set_always_urgent (chatroom, active);
1226   g_object_unref (chatroom);
1227   g_free (name);
1228 }
1229
1230 static void
1231 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1232     EmpathyChatWindow *self)
1233 {
1234   gboolean active;
1235
1236   active = gtk_toggle_action_get_active (toggle_action);
1237
1238   empathy_chat_set_show_contacts (self->priv->current_chat, active);
1239 }
1240
1241 static void
1242 chat_window_invite_participant_activate_cb (GtkAction *action,
1243     EmpathyChatWindow *self)
1244 {
1245   GtkWidget *dialog;
1246   EmpathyTpChat *tp_chat;
1247   int response;
1248
1249   g_return_if_fail (self->priv->current_chat != NULL);
1250
1251   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1252
1253   dialog = empathy_invite_participant_dialog_new (
1254       GTK_WINDOW (self), tp_chat);
1255
1256   gtk_widget_show (dialog);
1257
1258   response = gtk_dialog_run (GTK_DIALOG (dialog));
1259
1260   if (response == GTK_RESPONSE_ACCEPT)
1261     {
1262       TpContact *tp_contact;
1263       EmpathyContact *contact;
1264
1265       tp_contact = empathy_invite_participant_dialog_get_selected (
1266         EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1267       if (tp_contact == NULL)
1268         goto out;
1269
1270       contact = empathy_contact_dup_from_tp_contact (tp_contact);
1271
1272       empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1273
1274       g_object_unref (contact);
1275     }
1276
1277 out:
1278   gtk_widget_destroy (dialog);
1279 }
1280
1281 static void
1282 chat_window_join_chat_activate_cb (GtkAction *action,
1283     EmpathyChatWindow *self)
1284 {
1285     g_return_if_fail (self->priv->current_chat != NULL);
1286
1287     empathy_chat_join_muc (self->priv->current_chat,
1288         empathy_chat_get_id (self->priv->current_chat));
1289 }
1290
1291 static void
1292 chat_window_leave_chat_activate_cb (GtkAction *action,
1293     EmpathyChatWindow *self)
1294 {
1295     EmpathyTpChat * tp_chat;
1296
1297     g_return_if_fail (self->priv->current_chat != NULL);
1298
1299     tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1300     if (tp_chat != NULL)
1301         empathy_tp_chat_leave (tp_chat, "");
1302 }
1303
1304 static void
1305 chat_window_close_activate_cb (GtkAction *action,
1306     EmpathyChatWindow *self)
1307 {
1308   g_return_if_fail (self->priv->current_chat != NULL);
1309
1310   maybe_close_chat (self, self->priv->current_chat);
1311 }
1312
1313 static void
1314 chat_window_edit_activate_cb (GtkAction *action,
1315     EmpathyChatWindow *self)
1316 {
1317   GtkClipboard *clipboard;
1318   GtkTextBuffer *buffer;
1319   gboolean text_available;
1320
1321   g_return_if_fail (self->priv->current_chat != NULL);
1322
1323   if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1324     {
1325       gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1326       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1327       gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1328       return;
1329     }
1330
1331   buffer = gtk_text_view_get_buffer (
1332       GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1333
1334   if (gtk_text_buffer_get_has_selection (buffer))
1335     {
1336       gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1337       gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1338     }
1339   else
1340     {
1341       gboolean selection;
1342
1343       selection = empathy_theme_adium_get_has_selection (
1344           self->priv->current_chat->view);
1345
1346       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1347       gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1348     }
1349
1350   clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1351   text_available = gtk_clipboard_wait_is_text_available (clipboard);
1352   gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1353 }
1354
1355 static void
1356 chat_window_cut_activate_cb (GtkAction *action,
1357     EmpathyChatWindow *self)
1358 {
1359   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1360
1361   empathy_chat_cut (self->priv->current_chat);
1362 }
1363
1364 static void
1365 chat_window_copy_activate_cb (GtkAction *action,
1366     EmpathyChatWindow *self)
1367 {
1368   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1369
1370   empathy_chat_copy (self->priv->current_chat);
1371 }
1372
1373 static void
1374 chat_window_paste_activate_cb (GtkAction *action,
1375     EmpathyChatWindow *self)
1376 {
1377   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1378
1379   empathy_chat_paste (self->priv->current_chat);
1380 }
1381
1382 static void
1383 chat_window_find_activate_cb (GtkAction *action,
1384     EmpathyChatWindow *self)
1385 {
1386   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1387
1388   empathy_chat_find (self->priv->current_chat);
1389 }
1390
1391 static void
1392 chat_window_tabs_next_activate_cb (GtkAction *action,
1393     EmpathyChatWindow *self)
1394 {
1395   gint index_, numPages;
1396   gboolean wrap_around;
1397
1398   g_object_get (gtk_settings_get_default (),
1399       "gtk-keynav-wrap-around", &wrap_around,
1400       NULL);
1401
1402   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1403   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1404
1405   if (index_ == (numPages - 1) && wrap_around)
1406     {
1407       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1408       return;
1409     }
1410
1411   gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1412 }
1413
1414 static void
1415 chat_window_tabs_previous_activate_cb (GtkAction *action,
1416     EmpathyChatWindow *self)
1417 {
1418   gint index_, numPages;
1419   gboolean wrap_around;
1420
1421   g_object_get (gtk_settings_get_default (),
1422       "gtk-keynav-wrap-around", &wrap_around,
1423       NULL);
1424
1425   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1426   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1427
1428   if (index_ <= 0 && wrap_around)
1429     {
1430       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1431           numPages - 1);
1432       return;
1433     }
1434
1435   gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1436 }
1437
1438 static void
1439 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1440     EmpathyChatWindow *self)
1441 {
1442   empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1443       empathy_get_current_action_time ());
1444 }
1445
1446 static void
1447 chat_window_tabs_left_activate_cb (GtkAction *action,
1448     EmpathyChatWindow *self)
1449 {
1450   EmpathyChat *chat;
1451   gint index_, num_pages;
1452
1453   chat = self->priv->current_chat;
1454   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1455   if (index_ <= 0)
1456     return;
1457
1458   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1459       index_ - 1);
1460
1461   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1462   chat_window_menu_context_update (self, num_pages);
1463 }
1464
1465 static void
1466 chat_window_tabs_right_activate_cb (GtkAction *action,
1467     EmpathyChatWindow *self)
1468 {
1469   EmpathyChat *chat;
1470   gint index_, num_pages;
1471
1472   chat = self->priv->current_chat;
1473   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1474
1475   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1476       index_ + 1);
1477
1478   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1479   chat_window_menu_context_update (self, num_pages);
1480 }
1481
1482 static EmpathyChatWindow *
1483 empathy_chat_window_new (void)
1484 {
1485   return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1486       "default-width", 580,
1487       "default-height", 480,
1488       "title", _("Chat"),
1489       "role", "chat",
1490       NULL);
1491 }
1492
1493 static void
1494 chat_window_detach_activate_cb (GtkAction *action,
1495     EmpathyChatWindow *self)
1496 {
1497   EmpathyChatWindow *new_window;
1498   EmpathyChat *chat;
1499
1500   chat = self->priv->current_chat;
1501   new_window = empathy_chat_window_new ();
1502
1503   empathy_chat_window_move_chat (self, new_window, chat);
1504
1505   gtk_widget_show (GTK_WIDGET (new_window));
1506 }
1507
1508 static void
1509 chat_window_help_contents_activate_cb (GtkAction *action,
1510     EmpathyChatWindow *self)
1511 {
1512   empathy_url_show (GTK_WIDGET (self), "help:empathy");
1513 }
1514
1515 static void
1516 chat_window_help_about_activate_cb (GtkAction *action,
1517     EmpathyChatWindow *self)
1518 {
1519   empathy_about_dialog_new (GTK_WINDOW (self));
1520 }
1521
1522 static gboolean
1523 chat_window_delete_event_cb (GtkWidget *dialog,
1524     GdkEvent *event,
1525     EmpathyChatWindow *self)
1526 {
1527   EmpathyChat *chat = NULL;
1528   guint n_rooms = 0;
1529   GList *l;
1530
1531   DEBUG ("Delete event received");
1532
1533   for (l = self->priv->chats; l != NULL; l = l->next)
1534     {
1535       if (chat_needs_close_confirmation (l->data))
1536         {
1537           chat = l->data;
1538           n_rooms++;
1539         }
1540     }
1541
1542   if (n_rooms > 0)
1543     {
1544       confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1545     }
1546   else
1547     {
1548       remove_all_chats (self);
1549     }
1550
1551   return TRUE;
1552 }
1553
1554 static void
1555 chat_window_composing_cb (EmpathyChat *chat,
1556     gboolean is_composing,
1557     EmpathyChatWindow *self)
1558 {
1559   chat_window_update_chat_tab (chat);
1560 }
1561
1562 static void
1563 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1564     gboolean urgent)
1565 {
1566   gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1567 }
1568
1569 static void
1570 chat_window_notification_closed_cb (NotifyNotification *notify,
1571     EmpathyChatWindow *self)
1572 {
1573   g_object_unref (notify);
1574   if (self->priv->notification == notify)
1575     self->priv->notification = NULL;
1576 }
1577
1578 static void
1579 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1580     EmpathyMessage *message,
1581     EmpathyChat *chat)
1582 {
1583   EmpathyContact *sender;
1584   const gchar *header;
1585   char *escaped;
1586   const char *body;
1587   GdkPixbuf *pixbuf;
1588   gboolean res, has_x_canonical_append;
1589   NotifyNotification *notification = self->priv->notification;
1590
1591   if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1592     return;
1593
1594   res = g_settings_get_boolean (self->priv->gsettings_notif,
1595       EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1596
1597   if (!res)
1598     return;
1599
1600   sender = empathy_message_get_sender (message);
1601   header = empathy_contact_get_alias (sender);
1602   body = empathy_message_get_body (message);
1603   escaped = g_markup_escape_text (body, -1);
1604
1605   has_x_canonical_append = empathy_notify_manager_has_capability (
1606     self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1607
1608   if (notification != NULL && !has_x_canonical_append)
1609     {
1610       /* if the notification server supports x-canonical-append, it is
1611          better to not use notify_notification_update to avoid
1612          overwriting the current notification message */
1613       notify_notification_update (notification,
1614                 header, escaped, NULL);
1615     }
1616   else
1617     {
1618       /* if the notification server supports x-canonical-append,
1619          the hint will be added, so that the message from the
1620          just created notification will be automatically appended
1621          to an existing notification with the same title.
1622          In this way the previous message will not be lost: the new
1623          message will appear below it, in the same notification */
1624       const gchar *category = empathy_chat_is_room (chat)
1625         ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1626         : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1627
1628       notification = empathy_notify_manager_create_notification (header,
1629           escaped, NULL);
1630
1631       if (self->priv->notification == NULL)
1632         self->priv->notification = notification;
1633
1634       tp_g_signal_connect_object (notification, "closed",
1635             G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1636
1637       if (has_x_canonical_append)
1638         {
1639           /* We have to set a not empty string to keep libnotify happy */
1640           notify_notification_set_hint_string (notification,
1641             EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1642         }
1643
1644       notify_notification_set_hint (notification,
1645           EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1646     }
1647
1648   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1649     sender, EMPATHY_IMAGE_NEW_MESSAGE);
1650
1651   if (pixbuf != NULL)
1652     {
1653       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1654       g_object_unref (pixbuf);
1655     }
1656
1657   notify_notification_show (notification, NULL);
1658
1659   g_free (escaped);
1660 }
1661
1662 static gboolean
1663 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1664 {
1665   gboolean has_focus;
1666
1667   g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1668
1669   g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1670
1671   return has_focus;
1672 }
1673
1674 static void
1675 chat_window_new_message_cb (EmpathyChat *chat,
1676     EmpathyMessage *message,
1677     gboolean pending,
1678     gboolean should_highlight,
1679     EmpathyChatWindow *self)
1680 {
1681   gboolean has_focus;
1682   gboolean needs_urgency;
1683   EmpathyContact *sender;
1684
1685   has_focus = empathy_chat_window_has_focus (self);
1686
1687   /* - if we're the sender, we play the sound if it's specified in the
1688    *   preferences and we're not away.
1689    * - if we receive a message, we play the sound if it's specified in the
1690    *   preferences and the window does not have focus on the chat receiving
1691    *   the message.
1692    */
1693
1694   sender = empathy_message_get_sender (message);
1695
1696   if (empathy_contact_is_user (sender))
1697     {
1698       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1699           EMPATHY_SOUND_MESSAGE_OUTGOING);
1700       return;
1701     }
1702
1703   if (has_focus && self->priv->current_chat == chat)
1704     {
1705       /* window and tab are focused so consider the message to be read */
1706
1707       /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1708       empathy_chat_messages_read (chat);
1709       return;
1710     }
1711
1712   /* Update the chat tab if this is the first unread message */
1713   if (empathy_chat_get_nb_unread_messages (chat) == 1)
1714     {
1715       chat_window_update_chat_tab (chat);
1716     }
1717
1718   /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1719    * If empathy_chat_get_remote_contact () returns NULL, that means it's
1720    * an unamed MUC (msn-like).
1721    * In case of a MUC, we set urgency if either:
1722    *   a) the chatroom's always_urgent property is TRUE
1723    *   b) the message contains our alias
1724    */
1725   if (empathy_chat_is_room (chat))
1726     {
1727       TpAccount *account;
1728       const gchar *room;
1729       EmpathyChatroom *chatroom;
1730
1731       account = empathy_chat_get_account (chat);
1732       room = empathy_chat_get_id (chat);
1733
1734       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1735           account, room);
1736
1737       if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1738         needs_urgency = TRUE;
1739       else
1740         needs_urgency = should_highlight;
1741     }
1742   else
1743     {
1744       needs_urgency = TRUE;
1745     }
1746
1747   if (needs_urgency)
1748     {
1749       if (!has_focus)
1750         chat_window_set_urgency_hint (self, TRUE);
1751
1752       /* Pending messages have already been displayed and notified in the
1753       * approver, so we don't display a notification and play a sound
1754       * for those */
1755       if (!pending)
1756         {
1757           empathy_sound_manager_play (self->priv->sound_mgr,
1758               GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1759
1760           chat_window_show_or_update_notification (self, message, chat);
1761         }
1762     }
1763
1764   /* update the number of unread messages and the window icon */
1765   chat_window_title_update (self);
1766   chat_window_icon_update (self, TRUE);
1767 }
1768
1769 static void
1770 chat_window_command_part (EmpathyChat *chat,
1771     GStrv strv)
1772 {
1773   EmpathyChat *chat_to_be_parted;
1774   EmpathyTpChat *tp_chat = NULL;
1775
1776   if (strv[1] == NULL)
1777     {
1778       /* No chatroom ID specified */
1779       tp_chat = empathy_chat_get_tp_chat (chat);
1780
1781       if (tp_chat)
1782         empathy_tp_chat_leave (tp_chat, "");
1783
1784       return;
1785     }
1786
1787   chat_to_be_parted = empathy_chat_window_find_chat (
1788     empathy_chat_get_account (chat), strv[1], FALSE);
1789
1790   if (chat_to_be_parted != NULL)
1791     {
1792       /* Found a chatroom matching the specified ID */
1793       tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1794
1795       if (tp_chat)
1796         empathy_tp_chat_leave (tp_chat, strv[2]);
1797     }
1798   else
1799     {
1800       gchar *message;
1801
1802       /* Going by the syntax of PART command:
1803        *
1804        * /PART [<chatroom-ID>] [<reason>]
1805        *
1806        * Chatroom-ID is not a must to specify a reason.
1807        * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1808        * MUC then the current chatroom should be parted and srtv[1] should
1809        * be treated as part of the optional part-message. */
1810       message = g_strconcat (strv[1], " ", strv[2], NULL);
1811       tp_chat = empathy_chat_get_tp_chat (chat);
1812
1813       if (tp_chat)
1814         empathy_tp_chat_leave (tp_chat, message);
1815
1816       g_free (message);
1817     }
1818 }
1819
1820 static GtkNotebook *
1821 notebook_create_window_cb (GtkNotebook *source,
1822     GtkWidget *page,
1823     gint x,
1824     gint y,
1825     gpointer user_data)
1826 {
1827   EmpathyChatWindow *window, *new_window;
1828   EmpathyChat *chat;
1829
1830   chat = EMPATHY_CHAT (page);
1831   window = chat_window_find_chat (chat);
1832
1833   new_window = empathy_chat_window_new ();
1834
1835   DEBUG ("Detach hook called");
1836
1837   empathy_chat_window_move_chat (window, new_window, chat);
1838
1839   gtk_widget_show (GTK_WIDGET (new_window));
1840   gtk_window_move (GTK_WINDOW (new_window), x, y);
1841
1842   return NULL;
1843 }
1844
1845 static void
1846 chat_window_page_switched_cb (GtkNotebook *notebook,
1847     GtkWidget *child,
1848     gint page_num,
1849     EmpathyChatWindow *self)
1850 {
1851   EmpathyChat *chat = EMPATHY_CHAT (child);
1852
1853   DEBUG ("Page switched");
1854
1855   if (self->priv->page_added)
1856     {
1857       self->priv->page_added = FALSE;
1858       empathy_chat_scroll_down (chat);
1859     }
1860   else if (self->priv->current_chat == chat)
1861     {
1862       return;
1863     }
1864
1865   self->priv->current_chat = chat;
1866   empathy_chat_messages_read (chat);
1867
1868   chat_window_update_chat_tab (chat);
1869 }
1870
1871 static void
1872 chat_window_page_added_cb (GtkNotebook *notebook,
1873     GtkWidget *child,
1874     guint page_num,
1875     EmpathyChatWindow *self)
1876 {
1877   EmpathyChat *chat;
1878
1879   /* If we just received DND to the same window, we don't want
1880    * to do anything here like removing the tab and then readding
1881    * it, so we return here and in "page-added".
1882    */
1883   if (self->priv->dnd_same_window)
1884     {
1885       DEBUG ("Page added (back to the same window)");
1886       self->priv->dnd_same_window = FALSE;
1887       return;
1888     }
1889
1890   DEBUG ("Page added");
1891
1892   /* Get chat object */
1893   chat = EMPATHY_CHAT (child);
1894
1895   /* Connect chat signals for this window */
1896   g_signal_connect (chat, "composing",
1897       G_CALLBACK (chat_window_composing_cb), self);
1898   g_signal_connect (chat, "new-message",
1899       G_CALLBACK (chat_window_new_message_cb), self);
1900   g_signal_connect (chat, "part-command-entered",
1901       G_CALLBACK (chat_window_command_part), NULL);
1902   g_signal_connect (chat, "notify::tp-chat",
1903       G_CALLBACK (chat_window_update_chat_tab), self);
1904
1905   /* Set flag so we know to perform some special operations on
1906    * switch page due to the new page being added.
1907    */
1908   self->priv->page_added = TRUE;
1909
1910   /* Get list of chats up to date */
1911   self->priv->chats = g_list_append (self->priv->chats, chat);
1912
1913   chat_window_update_chat_tab (chat);
1914 }
1915
1916 static void
1917 chat_window_page_removed_cb (GtkNotebook *notebook,
1918     GtkWidget *child,
1919     guint page_num,
1920     EmpathyChatWindow *self)
1921 {
1922   EmpathyChat *chat;
1923
1924   /* If we just received DND to the same window, we don't want
1925    * to do anything here like removing the tab and then readding
1926    * it, so we return here and in "page-added".
1927    */
1928   if (self->priv->dnd_same_window)
1929     {
1930       DEBUG ("Page removed (and will be readded to same window)");
1931       return;
1932     }
1933
1934   DEBUG ("Page removed");
1935
1936   /* Get chat object */
1937   chat = EMPATHY_CHAT (child);
1938
1939   /* Disconnect all signal handlers for this chat and this window */
1940   g_signal_handlers_disconnect_by_func (chat,
1941       G_CALLBACK (chat_window_composing_cb), self);
1942   g_signal_handlers_disconnect_by_func (chat,
1943       G_CALLBACK (chat_window_new_message_cb), self);
1944   g_signal_handlers_disconnect_by_func (chat,
1945       G_CALLBACK (chat_window_update_chat_tab), self);
1946
1947   /* Keep list of chats up to date */
1948   self->priv->chats = g_list_remove (self->priv->chats, chat);
1949   empathy_chat_messages_read (chat);
1950
1951   if (self->priv->chats == NULL)
1952     {
1953       gtk_widget_destroy (GTK_WIDGET (self));
1954     }
1955   else
1956     {
1957       chat_window_update (self, TRUE);
1958     }
1959 }
1960
1961 static gboolean
1962 chat_window_focus_in_event_cb (GtkWidget *widget,
1963     GdkEvent *event,
1964     EmpathyChatWindow *self)
1965 {
1966   empathy_chat_messages_read (self->priv->current_chat);
1967
1968   chat_window_set_urgency_hint (self, FALSE);
1969
1970   /* Update the title, since we now mark all unread messages as read. */
1971   chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1972
1973   return FALSE;
1974 }
1975
1976 static void
1977 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1978     EmpathyChatWindow *self)
1979 {
1980   chat_window_contact_menu_update (self);
1981 }
1982
1983 static gboolean
1984 chat_window_focus_out_event_cb (GtkWidget *widget,
1985     GdkEvent *event,
1986     EmpathyChatWindow *self)
1987 {
1988   if (self->priv->individual_mgr != NULL)
1989     return FALSE;
1990
1991   /* Keep the individual manager alive so we won't fetch everything from Folks
1992    * each time we need to use it. Loading FolksAggregator can takes quite a
1993    * while (if user has a huge LDAP abook for example) and it blocks
1994    * the mainloop during most of this loading. We workaround this by loading
1995    * it when the chat window has been unfocused and so, hopefully, not impact
1996    * the reactivity of the chat window too much.
1997    *
1998    * The individual manager (and so Folks) is needed to know to which
1999    * FolksIndividual a TpContact belongs, including:
2000    * - empathy_chat_get_contact_menu: to list all the personas of the contact
2001    * - empathy_display_individual_info: to invoke gnome-contacts with the
2002    *   FolksIndividual.id of the contact
2003    * - drag_data_received_individual_id: to find the individual associated
2004    *   with the ID we received from the DnD in order to invite him.
2005    */
2006   self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2007
2008   if (!empathy_individual_manager_get_contacts_loaded (
2009       self->priv->individual_mgr))
2010     {
2011       /* We want to update the contact menu when Folks is loaded so we can
2012        * list all the personas of the contact. */
2013       tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2014           G_CALLBACK (contacts_loaded_cb), self, 0);
2015     }
2016
2017   g_object_notify (G_OBJECT (self), "individual-manager");
2018
2019   return FALSE;
2020 }
2021
2022 static gboolean
2023 chat_window_drag_drop (GtkWidget *widget,
2024     GdkDragContext *context,
2025     int x,
2026     int y,
2027     guint time_,
2028     EmpathyChatWindow *self)
2029 {
2030   GdkAtom target;
2031
2032   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2033   if (target == GDK_NONE)
2034     target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2035
2036   if (target != GDK_NONE)
2037     {
2038       gtk_drag_get_data (widget, context, target, time_);
2039       return TRUE;
2040     }
2041
2042   return FALSE;
2043 }
2044
2045 static gboolean
2046 chat_window_drag_motion (GtkWidget *widget,
2047     GdkDragContext *context,
2048     int x,
2049     int y,
2050     guint time_,
2051     EmpathyChatWindow *self)
2052 {
2053   GdkAtom target;
2054
2055   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2056
2057   if (target != GDK_NONE)
2058     {
2059       /* This is a file drag. Ensure the contact is online and set the
2060          drag type to COPY. Note that it's possible that the tab will
2061          be switched by GTK+ after a timeout from drag_motion without
2062          getting another drag_motion to disable the drop. You have
2063          to hold your mouse really still.
2064        */
2065       EmpathyContact *contact;
2066
2067       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2068
2069       /* contact is NULL for multi-user chats. We don't do
2070        * file transfers to MUCs. We also don't send files
2071        * to offline contacts or contacts that don't support
2072        * file transfer.
2073        */
2074       if ((contact == NULL) || !empathy_contact_is_online (contact))
2075         {
2076           gdk_drag_status (context, 0, time_);
2077           return FALSE;
2078         }
2079
2080       if (!(empathy_contact_get_capabilities (contact)
2081            & EMPATHY_CAPABILITIES_FT))
2082         {
2083           gdk_drag_status (context, 0, time_);
2084           return FALSE;
2085         }
2086
2087       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2088       return TRUE;
2089     }
2090
2091   target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2092   if (target != GDK_NONE)
2093     {
2094       /* This is a drag of a contact from a contact list. Set to COPY.
2095          FIXME: If this drag is to a MUC window, it invites the user.
2096          Otherwise, it opens a chat. Should we use a different drag
2097          type for invites? Should we allow ASK?
2098        */
2099       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2100       return TRUE;
2101     }
2102
2103   return FALSE;
2104 }
2105
2106 static void
2107 drag_data_received_individual_id (EmpathyChatWindow *self,
2108     GtkWidget *widget,
2109     GdkDragContext *context,
2110     int x,
2111     int y,
2112     GtkSelectionData *selection,
2113     guint info,
2114     guint time_)
2115 {
2116   const gchar *id;
2117   FolksIndividual *individual;
2118   EmpathyTpChat *chat;
2119   TpContact *tp_contact;
2120   TpConnection *conn;
2121   EmpathyContact *contact;
2122
2123   id = (const gchar *) gtk_selection_data_get_data (selection);
2124
2125   DEBUG ("DND invididual %s", id);
2126
2127   if (self->priv->current_chat == NULL)
2128     goto out;
2129
2130   chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2131   if (chat == NULL)
2132     goto out;
2133
2134   if (!empathy_tp_chat_can_add_contact (chat))
2135     {
2136       DEBUG ("Can't invite contact to %s",
2137           tp_proxy_get_object_path (chat));
2138       goto out;
2139     }
2140
2141   if (self->priv->individual_mgr == NULL)
2142     /* Not likely as we have to focus out the chat window in order to start
2143      * the DnD but best to be safe. */
2144     goto out;
2145
2146   individual = empathy_individual_manager_lookup_member (
2147           self->priv->individual_mgr, id);
2148   if (individual == NULL)
2149     {
2150       DEBUG ("Failed to find individual %s", id);
2151       goto out;
2152     }
2153
2154   conn = tp_channel_get_connection ((TpChannel *) chat);
2155   tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2156   if (tp_contact == NULL)
2157     {
2158       DEBUG ("Can't find a TpContact on connection %s for %s",
2159           tp_proxy_get_object_path (conn), id);
2160       goto out;
2161     }
2162
2163   DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2164       tp_channel_get_identifier ((TpChannel *) chat));
2165
2166   contact = empathy_contact_dup_from_tp_contact (tp_contact);
2167   empathy_tp_chat_add (chat, contact, NULL);
2168   g_object_unref (contact);
2169
2170 out:
2171   gtk_drag_finish (context, TRUE, FALSE, time_);
2172 }
2173
2174 static void
2175 chat_window_drag_data_received (GtkWidget *widget,
2176     GdkDragContext *context,
2177     int x,
2178     int y,
2179     GtkSelectionData *selection,
2180     guint info,
2181     guint time_,
2182     EmpathyChatWindow *self)
2183 {
2184   if (info == DND_DRAG_TYPE_CONTACT_ID)
2185     {
2186       EmpathyChat *chat = NULL;
2187       EmpathyChatWindow *old_window;
2188       TpAccount *account = NULL;
2189       EmpathyClientFactory *factory;
2190       const gchar *id;
2191       gchar **strv;
2192       const gchar *account_id;
2193       const gchar *contact_id;
2194
2195       id = (const gchar*) gtk_selection_data_get_data (selection);
2196
2197       factory = empathy_client_factory_dup ();
2198
2199       DEBUG ("DND contact from roster with id:'%s'", id);
2200
2201       strv = g_strsplit (id, ":", 2);
2202       if (g_strv_length (strv) == 2)
2203         {
2204           account_id = strv[0];
2205           contact_id = strv[1];
2206
2207           account = tp_simple_client_factory_ensure_account (
2208               TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2209
2210           g_object_unref (factory);
2211           if (account != NULL)
2212             chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2213         }
2214
2215       if (account == NULL)
2216         {
2217           g_strfreev (strv);
2218           gtk_drag_finish (context, FALSE, FALSE, time_);
2219           return;
2220         }
2221
2222       if (!chat)
2223         {
2224           empathy_chat_with_contact_id (account, contact_id,
2225               empathy_get_current_action_time (), NULL, NULL);
2226
2227           g_strfreev (strv);
2228           return;
2229         }
2230
2231       g_strfreev (strv);
2232
2233       old_window = chat_window_find_chat (chat);
2234       if (old_window)
2235         {
2236           if (old_window == self)
2237             {
2238               gtk_drag_finish (context, TRUE, FALSE, time_);
2239               return;
2240             }
2241
2242           empathy_chat_window_move_chat (old_window, self, chat);
2243         }
2244       else
2245         {
2246           empathy_chat_window_add_chat (self, chat);
2247         }
2248
2249       /* Added to take care of any outstanding chat events */
2250       empathy_chat_window_present_chat (chat,
2251           TP_USER_ACTION_TIME_NOT_USER_ACTION);
2252
2253       /* We should return TRUE to remove the data when doing
2254        * GDK_ACTION_MOVE, but we don't here otherwise it has
2255        * weird consequences, and we handle that internally
2256        * anyway with add_chat () and remove_chat ().
2257        */
2258       gtk_drag_finish (context, TRUE, FALSE, time_);
2259     }
2260   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2261     {
2262       drag_data_received_individual_id (self, widget, context, x, y,
2263           selection, info, time_);
2264     }
2265   else if (info == DND_DRAG_TYPE_URI_LIST)
2266     {
2267       EmpathyContact *contact;
2268       const gchar *data;
2269
2270       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2271
2272       /* contact is NULL when current_chat is a multi-user chat.
2273        * We don't do file transfers to MUCs, so just cancel the drag.
2274        */
2275       if (contact == NULL)
2276         {
2277           gtk_drag_finish (context, TRUE, FALSE, time_);
2278           return;
2279         }
2280
2281       data = (const gchar *) gtk_selection_data_get_data (selection);
2282       empathy_send_file_from_uri_list (contact, data);
2283
2284       gtk_drag_finish (context, TRUE, FALSE, time_);
2285     }
2286   else if (info == DND_DRAG_TYPE_TAB)
2287     {
2288       EmpathyChat **chat;
2289       EmpathyChatWindow *old_window = NULL;
2290
2291       DEBUG ("DND tab");
2292
2293       chat = (void *) gtk_selection_data_get_data (selection);
2294       old_window = chat_window_find_chat (*chat);
2295
2296       if (old_window)
2297         {
2298           self->priv->dnd_same_window = (old_window == self);
2299
2300           DEBUG ("DND tab (within same window: %s)",
2301             self->priv->dnd_same_window ? "Yes" : "No");
2302         }
2303     }
2304   else
2305     {
2306       DEBUG ("DND from unknown source");
2307       gtk_drag_finish (context, FALSE, FALSE, time_);
2308     }
2309 }
2310
2311 static void
2312 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2313     guint num_chats_in_manager,
2314     EmpathyChatWindow *self)
2315 {
2316   gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2317       num_chats_in_manager > 0);
2318 }
2319
2320 static void
2321 chat_window_finalize (GObject *object)
2322 {
2323   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2324
2325   DEBUG ("Finalized: %p", object);
2326
2327   g_object_unref (self->priv->ui_manager);
2328   g_object_unref (self->priv->chatroom_manager);
2329   g_object_unref (self->priv->notify_mgr);
2330   g_object_unref (self->priv->gsettings_chat);
2331   g_object_unref (self->priv->gsettings_notif);
2332   g_object_unref (self->priv->gsettings_ui);
2333   g_object_unref (self->priv->sound_mgr);
2334   g_clear_object (&self->priv->individual_mgr);
2335
2336   if (self->priv->notification != NULL)
2337     {
2338       notify_notification_close (self->priv->notification, NULL);
2339       self->priv->notification = NULL;
2340     }
2341
2342   if (self->priv->contact_targets)
2343     gtk_target_list_unref (self->priv->contact_targets);
2344
2345   if (self->priv->file_targets)
2346     gtk_target_list_unref (self->priv->file_targets);
2347
2348   if (self->priv->chat_manager)
2349     {
2350       g_signal_handler_disconnect (self->priv->chat_manager,
2351                  self->priv->chat_manager_chats_changed_id);
2352       g_object_unref (self->priv->chat_manager);
2353       self->priv->chat_manager = NULL;
2354     }
2355
2356   chat_windows = g_list_remove (chat_windows, self);
2357
2358   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2359 }
2360
2361 static void
2362 chat_window_get_property (GObject *object,
2363     guint property_id,
2364     GValue *value,
2365     GParamSpec *pspec)
2366 {
2367   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2368
2369   switch (property_id)
2370     {
2371       case PROP_INDIVIDUAL_MGR:
2372         g_value_set_object (value, self->priv->individual_mgr);
2373       default:
2374         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2375         break;
2376     }
2377 }
2378
2379 static void
2380 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2381 {
2382   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2383   GParamSpec *spec;
2384
2385   object_class->get_property = chat_window_get_property;
2386   object_class->finalize = chat_window_finalize;
2387
2388   spec = g_param_spec_object ("individual-manager", "individual-manager",
2389       "EmpathyIndividualManager",
2390       EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2391       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2392   g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2393
2394   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2395 }
2396
2397 static void
2398 empathy_chat_window_init (EmpathyChatWindow *self)
2399 {
2400   GtkBuilder *gui;
2401   GtkAccelGroup *accel_group;
2402   GClosure *closure;
2403   GtkWidget *menu;
2404   GtkWidget *submenu;
2405   guint i;
2406   GtkWidget *chat_vbox;
2407   gchar *filename;
2408   EmpathySmileyManager *smiley_manager;
2409
2410   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2411     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2412
2413   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2414   gui = empathy_builder_get_file (filename,
2415       "chat_vbox", &chat_vbox,
2416       "ui_manager", &self->priv->ui_manager,
2417       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2418       "menu_conv_favorite", &self->priv->menu_conv_favorite,
2419       "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2420       "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2421       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2422       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2423       "menu_edit_cut", &self->priv->menu_edit_cut,
2424       "menu_edit_copy", &self->priv->menu_edit_copy,
2425       "menu_edit_paste", &self->priv->menu_edit_paste,
2426       "menu_edit_find", &self->priv->menu_edit_find,
2427       "menu_tabs_next", &self->priv->menu_tabs_next,
2428       "menu_tabs_prev", &self->priv->menu_tabs_prev,
2429       "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2430       "menu_tabs_left", &self->priv->menu_tabs_left,
2431       "menu_tabs_right", &self->priv->menu_tabs_right,
2432        "menu_tabs_detach", &self->priv->menu_tabs_detach,
2433       NULL);
2434   g_free (filename);
2435
2436   empathy_builder_connect (gui, self,
2437       "menu_conv", "activate", chat_window_conv_activate_cb,
2438       "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2439       "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2440       "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2441       "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2442       "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2443       "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2444       "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2445       "menu_conv_close", "activate", chat_window_close_activate_cb,
2446       "menu_edit", "activate", chat_window_edit_activate_cb,
2447       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2448       "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2449       "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2450       "menu_edit_find", "activate", chat_window_find_activate_cb,
2451       "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2452       "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2453       "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2454       "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2455       "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2456       "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2457       "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2458       "menu_help_about", "activate", chat_window_help_about_activate_cb,
2459       NULL);
2460
2461   empathy_set_css_provider (GTK_WIDGET (self));
2462
2463   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2464   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2465   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2466   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2467
2468   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2469
2470   self->priv->notebook = gtk_notebook_new ();
2471
2472   g_signal_connect (self->priv->notebook, "create-window",
2473       G_CALLBACK (notebook_create_window_cb), self);
2474
2475   gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2476
2477   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2478     "EmpathyChatWindow");
2479   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2480   gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2481   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2482   gtk_widget_show (self->priv->notebook);
2483
2484   /* Set up accels */
2485   accel_group = gtk_accel_group_new ();
2486   gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2487
2488   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2489     {
2490       closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2491           NULL);
2492
2493       gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2494           closure);
2495     }
2496
2497   g_object_unref (accel_group);
2498
2499   /* Set up drag target lists */
2500   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2501       G_N_ELEMENTS (drag_types_dest_contact));
2502
2503   self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2504       G_N_ELEMENTS (drag_types_dest_file));
2505
2506   /* Set up smiley menu */
2507   smiley_manager = empathy_smiley_manager_dup_singleton ();
2508   submenu = empathy_smiley_menu_new (smiley_manager,
2509       chat_window_insert_smiley_activate_cb, self);
2510
2511   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2512     "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2513   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2514   g_object_unref (smiley_manager);
2515
2516   /* Set up signals we can't do with ui file since we may need to
2517    * block/unblock them at some later stage.
2518    */
2519
2520   g_signal_connect (self, "delete_event",
2521       G_CALLBACK (chat_window_delete_event_cb), self);
2522   g_signal_connect (self, "focus_in_event",
2523       G_CALLBACK (chat_window_focus_in_event_cb), self);
2524   g_signal_connect (self, "focus_out_event",
2525       G_CALLBACK (chat_window_focus_out_event_cb), self);
2526   g_signal_connect_after (self->priv->notebook, "switch_page",
2527       G_CALLBACK (chat_window_page_switched_cb), self);
2528   g_signal_connect (self->priv->notebook, "page_added",
2529       G_CALLBACK (chat_window_page_added_cb), self);
2530   g_signal_connect (self->priv->notebook, "page_removed",
2531       G_CALLBACK (chat_window_page_removed_cb), self);
2532
2533   /* Set up drag and drop */
2534   gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2535       GTK_DEST_DEFAULT_HIGHLIGHT,
2536       drag_types_dest,
2537       G_N_ELEMENTS (drag_types_dest),
2538       GDK_ACTION_MOVE | GDK_ACTION_COPY);
2539
2540   /* connect_after to allow GtkNotebook's built-in tab switching */
2541   g_signal_connect_after (self->priv->notebook, "drag-motion",
2542       G_CALLBACK (chat_window_drag_motion), self);
2543   g_signal_connect (self->priv->notebook, "drag-data-received",
2544       G_CALLBACK (chat_window_drag_data_received), self);
2545   g_signal_connect (self->priv->notebook, "drag-drop",
2546       G_CALLBACK (chat_window_drag_drop), self);
2547
2548   chat_windows = g_list_prepend (chat_windows, self);
2549
2550   /* Set up private details */
2551   self->priv->chats = NULL;
2552   self->priv->current_chat = NULL;
2553   self->priv->notification = NULL;
2554
2555   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2556
2557   self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2558   self->priv->chat_manager_chats_changed_id = g_signal_connect (
2559       self->priv->chat_manager, "closed-chats-changed",
2560       G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2561
2562   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2563       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2564
2565   g_object_ref (self->priv->ui_manager);
2566   g_object_unref (gui);
2567 }
2568
2569 /* Returns the window to open a new tab in if there is a suitable window,
2570  * otherwise, returns NULL indicating that a new window should be added.
2571  */
2572 static EmpathyChatWindow *
2573 empathy_chat_window_get_default (gboolean room)
2574 {
2575   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2576   GList *l;
2577   gboolean separate_windows = TRUE;
2578
2579   separate_windows = g_settings_get_boolean (gsettings,
2580       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2581
2582   g_object_unref (gsettings);
2583
2584   if (separate_windows)
2585     /* Always create a new window */
2586     return NULL;
2587
2588   for (l = chat_windows; l; l = l->next)
2589     {
2590       EmpathyChatWindow *chat_window;
2591       guint nb_rooms, nb_private;
2592
2593       chat_window = l->data;
2594
2595       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2596
2597       /* Skip the window if there aren't any rooms in it */
2598       if (room && nb_rooms == 0)
2599         continue;
2600
2601       /* Skip the window if there aren't any 1-1 chats in it */
2602       if (!room && nb_private == 0)
2603         continue;
2604
2605       return chat_window;
2606     }
2607
2608   return NULL;
2609 }
2610
2611 static void
2612 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2613     EmpathyChat *chat)
2614 {
2615   GtkWidget *label;
2616   GtkWidget *popup_label;
2617   GtkWidget *child;
2618   GValue value = { 0, };
2619
2620   g_return_if_fail (self != NULL);
2621   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2622
2623   /* Reference the chat object */
2624   g_object_ref (chat);
2625
2626   /* If this window has just been created, position it */
2627   if (self->priv->chats == NULL)
2628     {
2629       const gchar *name = "chat-window";
2630       gboolean separate_windows;
2631
2632       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2633           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2634
2635       if (empathy_chat_is_room (chat))
2636         name = "room-window";
2637
2638       if (separate_windows)
2639         {
2640           gint x, y;
2641
2642           /* Save current position of the window */
2643           gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2644
2645           /* First bind to the 'generic' name. So new window for which we didn't
2646           * save a geometry yet will have the geometry of the last saved
2647           * window (bgo #601191). */
2648           empathy_geometry_bind (GTK_WINDOW (self), name);
2649
2650           /* Restore previous position of the window so the newly created window
2651           * won't be in the same position as the latest saved window and so
2652           * completely hide it. */
2653           gtk_window_move (GTK_WINDOW (self), x, y);
2654
2655           /* Then bind it to the name of the contact/room so we'll save the
2656           * geometry specific to this window */
2657           name = empathy_chat_get_id (chat);
2658         }
2659
2660       empathy_geometry_bind (GTK_WINDOW (self), name);
2661     }
2662
2663   child = GTK_WIDGET (chat);
2664   label = chat_window_create_label (self, chat, TRUE);
2665   popup_label = chat_window_create_label (self, chat, FALSE);
2666   gtk_widget_show (child);
2667
2668   g_signal_connect (chat, "notify::name",
2669       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2670   g_signal_connect (chat, "notify::subject",
2671       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2672   g_signal_connect (chat, "notify::remote-contact",
2673       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2674   g_signal_connect (chat, "notify::sms-channel",
2675       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2676   g_signal_connect (chat, "notify::n-messages-sending",
2677       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2678   g_signal_connect (chat, "notify::nb-unread-messages",
2679       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2680   chat_window_chat_notify_cb (chat);
2681
2682   gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2683       popup_label);
2684   gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2685   gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2686   g_value_init (&value, G_TYPE_BOOLEAN);
2687   g_value_set_boolean (&value, TRUE);
2688   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2689       child, "tab-expand" , &value);
2690   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2691       child,  "tab-fill" , &value);
2692   g_value_unset (&value);
2693
2694   DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2695 }
2696
2697 static void
2698 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2699     EmpathyChat *chat)
2700 {
2701   gint position;
2702   EmpathyContact *remote_contact;
2703   EmpathyChatManager *chat_manager;
2704
2705   g_return_if_fail (self != NULL);
2706   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2707
2708   g_signal_handlers_disconnect_by_func (chat,
2709       chat_window_chat_notify_cb, NULL);
2710
2711   remote_contact = g_object_get_data (G_OBJECT (chat),
2712       "chat-window-remote-contact");
2713
2714   if (remote_contact)
2715     {
2716       g_signal_handlers_disconnect_by_func (remote_contact,
2717           chat_window_update_chat_tab, chat);
2718     }
2719
2720   chat_manager = empathy_chat_manager_dup_singleton ();
2721   empathy_chat_manager_closed_chat (chat_manager, chat);
2722   g_object_unref (chat_manager);
2723
2724   position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2725       GTK_WIDGET (chat));
2726   gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2727
2728   DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2729
2730   g_object_unref (chat);
2731 }
2732
2733 static void
2734 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2735     EmpathyChatWindow *new_window,
2736     EmpathyChat *chat)
2737 {
2738   GtkWidget *widget;
2739
2740   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2741   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2742   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2743
2744   widget = GTK_WIDGET (chat);
2745
2746   DEBUG ("Chat moving with widget:%p (%d references)", widget,
2747       G_OBJECT (widget)->ref_count);
2748
2749   /* We reference here to make sure we don't loose the widget
2750    * and the EmpathyChat object during the move.
2751    */
2752   g_object_ref (chat);
2753   g_object_ref (widget);
2754
2755   empathy_chat_window_remove_chat (old_window, chat);
2756   empathy_chat_window_add_chat (new_window, chat);
2757
2758   g_object_unref (widget);
2759   g_object_unref (chat);
2760 }
2761
2762 static void
2763 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2764     EmpathyChat *chat)
2765 {
2766   gint page_num;
2767
2768   g_return_if_fail (self != NULL);
2769   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2770
2771   page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2772       GTK_WIDGET (chat));
2773
2774   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2775       page_num);
2776 }
2777
2778 EmpathyChat *
2779 empathy_chat_window_find_chat (TpAccount *account,
2780     const gchar *id,
2781     gboolean sms_channel)
2782 {
2783   GList *l;
2784
2785   g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2786
2787   for (l = chat_windows; l; l = l->next)
2788     {
2789       EmpathyChatWindow *window = l->data;
2790       GList *ll;
2791
2792       for (ll = window->priv->chats; ll; ll = ll->next)
2793         {
2794           EmpathyChat *chat;
2795
2796           chat = ll->data;
2797
2798           if (account == empathy_chat_get_account (chat) &&
2799               !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2800               sms_channel == empathy_chat_is_sms_channel (chat))
2801             return chat;
2802         }
2803     }
2804
2805   return NULL;
2806 }
2807
2808 EmpathyChatWindow *
2809 empathy_chat_window_present_chat (EmpathyChat *chat,
2810     gint64 timestamp)
2811 {
2812   EmpathyChatWindow *self;
2813   guint32 x_timestamp;
2814
2815   g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2816
2817   self = chat_window_find_chat (chat);
2818
2819   /* If the chat has no window, create one */
2820   if (self == NULL)
2821     {
2822       self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2823       if (!self)
2824         {
2825           self = empathy_chat_window_new ();
2826
2827           /* we want to display the newly created window even if we
2828            * don't present it */
2829           gtk_widget_show (GTK_WIDGET (self));
2830         }
2831
2832       empathy_chat_window_add_chat (self, chat);
2833     }
2834
2835   /* Don't force the window to show itself when it wasn't
2836    * an action by the user
2837    */
2838   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2839     return self;
2840
2841   if (x_timestamp != GDK_CURRENT_TIME)
2842     {
2843       /* Don't present or switch tab if the action was earlier than the
2844        * last actions X time, accounting for overflow and the first ever
2845       * presentation */
2846
2847       if (self->priv->x_user_action_time != 0
2848         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2849         return self;
2850
2851       self->priv->x_user_action_time = x_timestamp;
2852     }
2853
2854   empathy_chat_window_switch_to_chat (self, chat);
2855
2856   /* Don't use empathy_window_present_with_time () which would move the window
2857    * to our current desktop but move to the window's desktop instead. This is
2858    * more coherent with Shell's 'app is ready' notication which moves the view
2859    * to the app desktop rather than moving the app itself. */
2860   empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2861
2862   gtk_widget_grab_focus (chat->input_text_view);
2863   return self;
2864 }
2865
2866 static void
2867 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2868     guint *nb_rooms,
2869     guint *nb_private)
2870 {
2871   GList *l;
2872   guint _nb_rooms = 0, _nb_private = 0;
2873
2874   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2875     {
2876       if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2877         _nb_rooms++;
2878       else
2879         _nb_private++;
2880     }
2881
2882   if (nb_rooms != NULL)
2883     *nb_rooms = _nb_rooms;
2884   if (nb_private != NULL)
2885     *nb_private = _nb_private;
2886 }
2887
2888 EmpathyIndividualManager *
2889 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2890 {
2891   return self->priv->individual_mgr;
2892 }