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