]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Useless to cast 2 times to the same class
[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-2008 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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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  */
27
28 #include <config.h>
29
30 #include <string.h>
31
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <glade/glade.h>
35 #include <glib/gi18n.h>
36
37 #include <telepathy-glib/util.h>
38 #include <libmissioncontrol/mission-control.h>
39
40 #include <libempathy/empathy-contact-factory.h>
41 #include <libempathy/empathy-contact.h>
42 #include <libempathy/empathy-message.h>
43 #include <libempathy/empathy-dispatcher.h>
44 #include <libempathy/empathy-utils.h>
45
46 #include <libempathy-gtk/empathy-images.h>
47 #include <libempathy-gtk/empathy-conf.h>
48 #include <libempathy-gtk/empathy-contact-dialogs.h>
49 #include <libempathy-gtk/empathy-log-window.h>
50 #include <libempathy-gtk/empathy-geometry.h>
51 #include <libempathy-gtk/empathy-ui-utils.h>
52
53 #include "empathy-chat-window.h"
54 #include "empathy-about-dialog.h"
55
56 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
57 #include <libempathy/empathy-debug.h>
58
59 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
60 typedef struct {
61         EmpathyChat *current_chat;
62         GList       *chats;
63         GList       *chats_new_msg;
64         GList       *chats_composing;
65         gboolean     page_added;
66         gboolean     dnd_same_window;
67         guint        save_geometry_id;
68         GtkWidget   *dialog;
69         GtkWidget   *notebook;
70
71         /* Menu items. */
72         GtkWidget   *menu_conv_clear;
73         GtkWidget   *menu_conv_insert_smiley;
74         GtkWidget   *menu_conv_contact;
75         GtkWidget   *menu_conv_close;
76
77         GtkWidget   *menu_edit_cut;
78         GtkWidget   *menu_edit_copy;
79         GtkWidget   *menu_edit_paste;
80
81         GtkWidget   *menu_tabs_next;
82         GtkWidget   *menu_tabs_prev;
83         GtkWidget   *menu_tabs_left;
84         GtkWidget   *menu_tabs_right;
85         GtkWidget   *menu_tabs_detach;
86         
87         GtkWidget   *menu_help_contents;
88         GtkWidget   *menu_help_about;
89 } EmpathyChatWindowPriv;
90
91 static GList *chat_windows = NULL;
92
93 static const guint tab_accel_keys[] = {
94         GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
95         GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
96 };
97
98 typedef enum {
99         DND_DRAG_TYPE_CONTACT_ID,
100         DND_DRAG_TYPE_TAB
101 } DndDragType;
102
103 static const GtkTargetEntry drag_types_dest[] = {
104         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
105         { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
106 };
107
108 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
109
110 static void
111 chat_window_accel_cb (GtkAccelGroup    *accelgroup,
112                       GObject          *object,
113                       guint             key,
114                       GdkModifierType   mod,
115                       EmpathyChatWindow *window)
116 {
117         EmpathyChatWindowPriv *priv;
118         gint                  num = -1;
119         gint                  i;
120
121         priv = GET_PRIV (window);
122
123         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
124                 if (tab_accel_keys[i] == key) {
125                         num = i;
126                         break;
127                 }
128         }
129
130         if (num != -1) {
131                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
132         }
133 }
134
135 static EmpathyChatWindow *
136 chat_window_find_chat (EmpathyChat *chat)
137 {
138         EmpathyChatWindowPriv *priv;
139         GList                 *l, *ll;
140
141         for (l = chat_windows; l; l = l->next) {
142                 priv = GET_PRIV (l->data);
143                 ll = g_list_find (priv->chats, chat);
144                 if (ll) {
145                         return l->data;
146                 }
147         }
148
149         return NULL;
150 }
151
152 static void
153 chat_window_close_clicked_cb (GtkWidget  *button,
154                               EmpathyChat *chat)
155 {
156         EmpathyChatWindow *window;
157
158         window = chat_window_find_chat (chat);
159         empathy_chat_window_remove_chat (window, chat);
160 }
161
162 static void
163 chat_window_close_button_style_set_cb (GtkWidget *button,
164                                        GtkStyle  *previous_style,
165                                        gpointer   user_data)
166 {
167         gint h, w;
168
169         gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
170                                            GTK_ICON_SIZE_MENU, &w, &h);
171
172         gtk_widget_set_size_request (button, w, h);
173 }
174
175 static GtkWidget *
176 chat_window_create_label (EmpathyChatWindow *window,
177                           EmpathyChat       *chat)
178 {
179         EmpathyChatWindowPriv *priv;
180         GtkWidget            *hbox;
181         GtkWidget            *name_label;
182         GtkWidget            *status_image;
183         GtkWidget            *close_button;
184         GtkWidget            *close_image;
185         GtkWidget            *event_box;
186         GtkWidget            *event_box_hbox;
187         PangoAttrList        *attr_list;
188         PangoAttribute       *attr;
189
190         priv = GET_PRIV (window);
191
192         /* The spacing between the button and the label. */
193         hbox = gtk_hbox_new (FALSE, 0);
194
195         event_box = gtk_event_box_new ();
196         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
197
198         name_label = gtk_label_new (NULL);
199         gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
200
201         attr_list = pango_attr_list_new ();
202         attr = pango_attr_scale_new (1/1.2);
203         attr->start_index = 0;
204         attr->end_index = -1;
205         pango_attr_list_insert (attr_list, attr);
206         gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
207         pango_attr_list_unref (attr_list);
208
209         gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
210         gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
211         g_object_set_data (G_OBJECT (chat), "chat-window-tab-label", name_label);
212
213         status_image = gtk_image_new ();
214
215         /* Spacing between the icon and label. */
216         event_box_hbox = gtk_hbox_new (FALSE, 0);
217
218         gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
219         gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
220
221         g_object_set_data (G_OBJECT (chat), "chat-window-tab-image", status_image);
222         g_object_set_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget", event_box);
223
224         close_button = gtk_button_new ();
225         gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
226
227         /* We don't want focus/keynav for the button to avoid clutter, and
228          * Ctrl-W works anyway.
229          */
230         GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_FOCUS);
231         GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_DEFAULT);
232
233         /* Set the name to make the special rc style match. */
234         gtk_widget_set_name (close_button, "empathy-close-button");
235
236         close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
237
238         gtk_container_add (GTK_CONTAINER (close_button), close_image);
239
240         gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
241         gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
242         gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
243
244         /* React to theme changes and also used to setup the initial size
245          * correctly.
246          */
247         g_signal_connect (close_button,
248                           "style-set",
249                           G_CALLBACK (chat_window_close_button_style_set_cb),
250                           chat);
251
252         g_signal_connect (close_button,
253                           "clicked",
254                           G_CALLBACK (chat_window_close_clicked_cb),
255                           chat);
256
257         gtk_widget_show_all (hbox);
258
259         return hbox;
260 }
261
262 static const gchar *
263 chat_window_get_chat_name (EmpathyChat *chat)
264 {
265         EmpathyContact *remote_contact = NULL;
266         const gchar    *name = NULL;
267
268         name = empathy_chat_get_name (chat);
269         if (!name) {
270                 remote_contact = empathy_chat_get_remote_contact (chat);
271                 if (remote_contact) {
272                         name = empathy_contact_get_name (remote_contact);
273                 }
274         }
275
276         return name ? name : _("Conversation");
277 }
278
279 static void
280 chat_window_update (EmpathyChatWindow *window)
281 {
282         EmpathyChatWindowPriv *priv = GET_PRIV (window);
283         gboolean               first_page;
284         gboolean               last_page;
285         gboolean               is_connected;
286         gint                   num_pages;
287         gint                   page_num;
288         const gchar           *name;
289         guint                  n_chats;
290         GdkPixbuf             *icon;
291         EmpathyContact        *remote_contact;
292         gboolean               avatar_in_icon;
293
294         /* Get information */
295         page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
296         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
297         first_page = (page_num == 0);
298         last_page = (page_num == (num_pages - 1));
299         is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
300         name = chat_window_get_chat_name (priv->current_chat);
301         n_chats = g_list_length (priv->chats);
302
303         DEBUG ("Update window");
304
305         /* Update menu */
306         gtk_widget_set_sensitive (priv->menu_tabs_next, !last_page);
307         gtk_widget_set_sensitive (priv->menu_tabs_prev, !first_page);
308         gtk_widget_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
309         gtk_widget_set_sensitive (priv->menu_tabs_left, !first_page);
310         gtk_widget_set_sensitive (priv->menu_tabs_right, !last_page);
311         gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
312
313         /* Update window title */
314         if (n_chats == 1) {
315                 gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
316         } else {
317                 gchar *title;
318
319                 title = g_strdup_printf (_("Conversations (%d)"), n_chats);
320                 gtk_window_set_title (GTK_WINDOW (priv->dialog), title);
321                 g_free (title);
322         }
323
324         /* Update window icon */
325         if (priv->chats_new_msg) {
326                 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
327                                           EMPATHY_IMAGE_MESSAGE);
328         } else {
329                 empathy_conf_get_bool (empathy_conf_get (),
330                                        EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
331                                        &avatar_in_icon);
332
333                 if (n_chats == 1 && avatar_in_icon) {
334                         remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
335                         icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
336                         gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
337                         g_object_unref (icon);
338                 } else {
339                         gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
340                 }
341         }
342 }
343
344 static void
345 chat_window_update_chat_tab (EmpathyChat *chat)
346 {
347         EmpathyChatWindow     *window;
348         EmpathyChatWindowPriv *priv;
349         EmpathyContact        *remote_contact;
350         const gchar           *name;
351         const gchar           *subject;
352         GtkWidget             *widget;
353         GString               *tooltip;
354         gchar                 *str;
355         const gchar           *icon_name;
356
357         window = chat_window_find_chat (chat);
358         if (!window) {
359                 return;
360         }
361         priv = GET_PRIV (window);
362
363         /* Get information */
364         name = chat_window_get_chat_name (chat);
365         subject = empathy_chat_get_subject (chat);
366         remote_contact = empathy_chat_get_remote_contact (chat);
367
368         DEBUG ("Updating chat tab, name=%s, subject=%s, remote_contact=%p",
369                 name, subject, remote_contact);
370
371         /* Update tab image */
372         if (g_list_find (priv->chats_new_msg, chat)) {
373                 icon_name = EMPATHY_IMAGE_MESSAGE;
374         }
375         else if (g_list_find (priv->chats_composing, chat)) {
376                 icon_name = EMPATHY_IMAGE_TYPING;
377         }
378         else if (remote_contact) {
379                 icon_name = empathy_icon_name_for_contact (remote_contact);
380         } else {
381                 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
382         }
383         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
384         gtk_image_set_from_icon_name (GTK_IMAGE (widget), icon_name, GTK_ICON_SIZE_MENU);
385
386         /* Update tab tooltip */
387         tooltip = g_string_new (NULL);
388         if (remote_contact) {
389                 g_string_append_printf (tooltip, "%s\n%s",
390                                         empathy_contact_get_id (remote_contact),
391                                         empathy_contact_get_status (remote_contact));
392         }
393         else {
394                 g_string_append (tooltip, name);
395         }
396         if (subject) {
397                 g_string_append_printf (tooltip, "\n%s %s", _("Topic:"), subject);
398         }
399         if (g_list_find (priv->chats_composing, chat)) {
400                 g_string_append_printf (tooltip, "\n%s", _("Typing a message."));
401         }
402         str = g_string_free (tooltip, FALSE);
403         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
404         gtk_widget_set_tooltip_text (widget, str);
405         g_free (str);
406
407         /* Update tab label */
408         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
409         gtk_label_set_text (GTK_LABEL (widget), name);
410
411         /* Update the window if it's the current chat */
412         if (priv->current_chat == chat) {
413                 chat_window_update (window);
414         }
415 }
416
417 static void
418 chat_window_chat_notify_cb (EmpathyChat *chat)
419 {
420         EmpathyContact *old_remote_contact;
421         EmpathyContact *remote_contact = NULL;
422
423         old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
424         remote_contact = empathy_chat_get_remote_contact (chat);
425
426         if (old_remote_contact != remote_contact) {
427                 /* The remote-contact associated with the chat changed, we need
428                  * to keep track of any change of that contact and update the
429                  * window each time. */
430                 if (remote_contact) {
431                         g_signal_connect_swapped (remote_contact, "notify",
432                                                   G_CALLBACK (chat_window_update_chat_tab),
433                                                   chat);
434                 }
435                 if (old_remote_contact) {
436                         g_signal_handlers_disconnect_by_func (old_remote_contact,
437                                                               chat_window_update_chat_tab,
438                                                               chat);
439                 }
440
441                 g_object_set_data (G_OBJECT (chat), "chat-window-remote-contact",
442                                    remote_contact);
443         }
444
445         chat_window_update_chat_tab (chat);
446 }
447
448 static void
449 chat_window_insert_smiley_activate_cb (GtkWidget         *menuitem,
450                                        EmpathyChatWindow *window)
451 {
452         EmpathyChatWindowPriv *priv;
453         EmpathyChat           *chat;
454         GtkTextBuffer        *buffer;
455         GtkTextIter           iter;
456         const gchar          *smiley;
457
458         priv = GET_PRIV (window);
459
460         chat = priv->current_chat;
461
462         smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
463
464         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
465         gtk_text_buffer_get_end_iter (buffer, &iter);
466         gtk_text_buffer_insert (buffer, &iter,
467                                 smiley, -1);
468 }
469
470 static void
471 chat_window_conv_activate_cb (GtkWidget         *menuitem,
472                               EmpathyChatWindow *window)
473 {
474         EmpathyChatWindowPriv *priv = GET_PRIV (window);
475         GtkWidget             *submenu = NULL;
476
477         submenu = empathy_chat_get_contact_menu (priv->current_chat);
478         if (submenu) {
479                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_contact),
480                                            submenu);
481                 gtk_widget_show (priv->menu_conv_contact);
482                 gtk_widget_show (submenu);
483         } else {
484                 gtk_widget_hide (priv->menu_conv_contact);
485         }
486 }
487
488 static void
489 chat_window_clear_activate_cb (GtkWidget        *menuitem,
490                                EmpathyChatWindow *window)
491 {
492         EmpathyChatWindowPriv *priv = GET_PRIV (window);
493
494         empathy_chat_clear (priv->current_chat);
495 }
496
497 static const gchar *
498 chat_get_window_id_for_geometry (EmpathyChat *chat)
499 {
500         const gchar *res = NULL;
501         gboolean     separate_windows;
502
503         empathy_conf_get_bool (empathy_conf_get (),
504                                EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
505                                &separate_windows);
506
507         if (separate_windows) {
508                 res = empathy_chat_get_id (chat);
509         }
510
511         return res ? res : "chat-window";
512 }
513
514 static gboolean
515 chat_window_save_geometry_timeout_cb (EmpathyChatWindow *window)
516 {
517         EmpathyChatWindowPriv *priv;
518         gint                  x, y, w, h;
519
520         priv = GET_PRIV (window);
521
522         gtk_window_get_size (GTK_WINDOW (priv->dialog), &w, &h);
523         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
524
525         empathy_geometry_save (chat_get_window_id_for_geometry (priv->current_chat),
526                                x, y, w, h);
527
528         priv->save_geometry_id = 0;
529
530         return FALSE;
531 }
532
533 static gboolean
534 chat_window_configure_event_cb (GtkWidget         *widget,
535                                 GdkEventConfigure *event,
536                                 EmpathyChatWindow  *window)
537 {
538         EmpathyChatWindowPriv *priv;
539
540         priv = GET_PRIV (window);
541
542         if (priv->save_geometry_id != 0) {
543                 g_source_remove (priv->save_geometry_id);
544         }
545
546         priv->save_geometry_id =
547                 g_timeout_add_seconds (1,
548                                        (GSourceFunc) chat_window_save_geometry_timeout_cb,
549                                        window);
550
551         return FALSE;
552 }
553
554 static void
555 chat_window_close_activate_cb (GtkWidget        *menuitem,
556                                EmpathyChatWindow *window)
557 {
558         EmpathyChatWindowPriv *priv;
559
560         priv = GET_PRIV (window);
561
562         g_return_if_fail (priv->current_chat != NULL);
563
564         empathy_chat_window_remove_chat (window, priv->current_chat);
565 }
566
567 static void
568 chat_window_edit_activate_cb (GtkWidget        *menuitem,
569                               EmpathyChatWindow *window)
570 {
571         EmpathyChatWindowPriv *priv;
572         GtkClipboard         *clipboard;
573         GtkTextBuffer        *buffer;
574         gboolean              text_available;
575
576         priv = GET_PRIV (window);
577
578         g_return_if_fail (priv->current_chat != NULL);
579
580         if (!empathy_chat_get_tp_chat (priv->current_chat)) {
581                 gtk_widget_set_sensitive (priv->menu_edit_copy, FALSE);
582                 gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
583                 gtk_widget_set_sensitive (priv->menu_edit_paste, FALSE);
584                 return;
585         }
586
587         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
588         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
589                 gtk_widget_set_sensitive (priv->menu_edit_copy, TRUE);
590                 gtk_widget_set_sensitive (priv->menu_edit_cut, TRUE);
591         } else {
592                 gboolean selection;
593
594                 selection = empathy_chat_view_get_selection_bounds (priv->current_chat->view, 
595                                                                    NULL, NULL);
596
597                 gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
598                 gtk_widget_set_sensitive (priv->menu_edit_copy, selection);
599         }
600
601         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
602         text_available = gtk_clipboard_wait_is_text_available (clipboard);
603         gtk_widget_set_sensitive (priv->menu_edit_paste, text_available);
604 }
605
606 static void
607 chat_window_cut_activate_cb (GtkWidget        *menuitem,
608                              EmpathyChatWindow *window)
609 {
610         EmpathyChatWindowPriv *priv;
611
612         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
613
614         priv = GET_PRIV (window);
615
616         empathy_chat_cut (priv->current_chat);
617 }
618
619 static void
620 chat_window_copy_activate_cb (GtkWidget        *menuitem,
621                               EmpathyChatWindow *window)
622 {
623         EmpathyChatWindowPriv *priv;
624
625         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
626
627         priv = GET_PRIV (window);
628
629         empathy_chat_copy (priv->current_chat);
630 }
631
632 static void
633 chat_window_paste_activate_cb (GtkWidget        *menuitem,
634                                EmpathyChatWindow *window)
635 {
636         EmpathyChatWindowPriv *priv;
637
638         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
639
640         priv = GET_PRIV (window);
641
642         empathy_chat_paste (priv->current_chat);
643 }
644
645 static void
646 chat_window_tabs_left_activate_cb (GtkWidget        *menuitem,
647                                    EmpathyChatWindow *window)
648 {
649         EmpathyChatWindowPriv *priv;
650         EmpathyChat           *chat;
651         gint                  index;
652
653         priv = GET_PRIV (window);
654
655         chat = priv->current_chat;
656         index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
657         if (index <= 0) {
658                 return;
659         }
660
661         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
662                                     GTK_WIDGET (chat),
663                                     index - 1);
664 }
665
666 static void
667 chat_window_tabs_right_activate_cb (GtkWidget        *menuitem,
668                                     EmpathyChatWindow *window)
669 {
670         EmpathyChatWindowPriv *priv;
671         EmpathyChat           *chat;
672         gint                  index;
673
674         priv = GET_PRIV (window);
675
676         chat = priv->current_chat;
677         index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
678
679         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
680                                     GTK_WIDGET (chat),
681                                     index + 1);
682 }
683
684 static void
685 chat_window_detach_activate_cb (GtkWidget        *menuitem,
686                                 EmpathyChatWindow *window)
687 {
688         EmpathyChatWindowPriv *priv;
689         EmpathyChatWindow     *new_window;
690         EmpathyChat           *chat;
691
692         priv = GET_PRIV (window);
693
694         chat = priv->current_chat;
695         new_window = empathy_chat_window_new ();
696
697         empathy_chat_window_move_chat (window, new_window, chat);
698
699         priv = GET_PRIV (new_window);
700         gtk_widget_show (priv->dialog);
701 }
702
703 static void
704 chat_window_help_contents_cb (GtkWidget         *menuitem,
705                               EmpathyChatWindow *window)
706 {
707         empathy_url_show ("ghelp:empathy?chat");
708 }
709
710 static void
711 chat_window_help_about_cb (GtkWidget         *menuitem,
712                            EmpathyChatWindow *window)
713 {
714         EmpathyChatWindowPriv *priv = GET_PRIV (window);
715
716         empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
717 }
718
719 static gboolean
720 chat_window_delete_event_cb (GtkWidget        *dialog,
721                              GdkEvent         *event,
722                              EmpathyChatWindow *window)
723 {
724         EmpathyChatWindowPriv *priv = GET_PRIV (window);
725
726         DEBUG ("Delete event received");
727
728         g_object_ref (window);
729         while (priv->chats) {
730                 empathy_chat_window_remove_chat (window, priv->chats->data);
731         }
732         g_object_unref (window);
733
734         return TRUE;
735 }
736
737 static void
738 chat_window_composing_cb (EmpathyChat       *chat,
739                           gboolean          is_composing,
740                           EmpathyChatWindow *window)
741 {
742         EmpathyChatWindowPriv *priv;
743
744         priv = GET_PRIV (window);
745
746         if (is_composing && !g_list_find (priv->chats_composing, chat)) {
747                 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
748         } else {
749                 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
750         }
751
752         chat_window_update_chat_tab (chat);
753 }
754
755 static void
756 chat_window_set_urgency_hint (EmpathyChatWindow *window,
757                               gboolean          urgent)
758 {
759         EmpathyChatWindowPriv *priv;
760
761         priv = GET_PRIV (window);
762
763         DEBUG ("Turning %s urgency hint", urgent ? "on" : "off");
764         gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
765 }
766
767 static void
768 chat_window_new_message_cb (EmpathyChat       *chat,
769                             EmpathyMessage    *message,
770                             EmpathyChatWindow *window)
771 {
772         EmpathyChatWindowPriv *priv;
773         gboolean              has_focus;
774         gboolean              needs_urgency;
775
776         priv = GET_PRIV (window);
777
778         has_focus = empathy_chat_window_has_focus (window);
779
780         if (has_focus && priv->current_chat == chat) {
781                 return;
782         }
783         
784         if (empathy_chat_get_members_count (chat) > 2) {
785                 needs_urgency = empathy_message_should_highlight (message);
786         } else {
787                 needs_urgency = TRUE;
788         }
789
790         if (needs_urgency && !has_focus) {
791                 chat_window_set_urgency_hint (window, TRUE);
792         }
793
794         if (!g_list_find (priv->chats_new_msg, chat)) {
795                 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
796                 chat_window_update_chat_tab (chat);
797         }
798 }
799
800 static GtkNotebook *
801 chat_window_detach_hook (GtkNotebook *source,
802                          GtkWidget   *page,
803                          gint         x,
804                          gint         y,
805                          gpointer     user_data)
806 {
807         EmpathyChatWindowPriv *priv;
808         EmpathyChatWindow     *window, *new_window;
809         EmpathyChat           *chat;
810
811         chat = EMPATHY_CHAT (page);
812         window = chat_window_find_chat (chat);
813
814         new_window = empathy_chat_window_new ();
815         priv = GET_PRIV (new_window);
816
817         DEBUG ("Detach hook called");
818
819         empathy_chat_window_move_chat (window, new_window, chat);
820
821         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
822         gtk_widget_show (priv->dialog);
823
824         return NULL;
825 }
826
827 static void
828 chat_window_page_switched_cb (GtkNotebook      *notebook,
829                               GtkNotebookPage  *page,
830                               gint              page_num,
831                               EmpathyChatWindow *window)
832 {
833         EmpathyChatWindowPriv *priv;
834         EmpathyChat           *chat;
835         GtkWidget            *child;
836
837         DEBUG ("Page switched");
838
839         priv = GET_PRIV (window);
840
841         child = gtk_notebook_get_nth_page (notebook, page_num);
842         chat = EMPATHY_CHAT (child);
843
844         if (priv->page_added) {
845                 priv->page_added = FALSE;
846                 empathy_chat_scroll_down (chat);
847         }
848         else if (priv->current_chat == chat) {
849                 return;
850         }
851
852         priv->current_chat = chat;
853         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
854
855         chat_window_update_chat_tab (chat);
856 }
857
858 static void
859 chat_window_page_added_cb (GtkNotebook      *notebook,
860                            GtkWidget        *child,
861                            guint             page_num,
862                            EmpathyChatWindow *window)
863 {
864         EmpathyChatWindowPriv *priv;
865         EmpathyChat           *chat;
866
867         priv = GET_PRIV (window);
868
869         /* If we just received DND to the same window, we don't want
870          * to do anything here like removing the tab and then readding
871          * it, so we return here and in "page-added".
872          */
873         if (priv->dnd_same_window) {
874                 DEBUG ("Page added (back to the same window)");
875                 priv->dnd_same_window = FALSE;
876                 return;
877         }
878
879         DEBUG ("Page added");
880
881         /* Get chat object */
882         chat = EMPATHY_CHAT (child);
883
884         /* Connect chat signals for this window */
885         g_signal_connect (chat, "composing",
886                           G_CALLBACK (chat_window_composing_cb),
887                           window);
888         g_signal_connect (chat, "new-message",
889                           G_CALLBACK (chat_window_new_message_cb),
890                           window);
891
892         /* Set flag so we know to perform some special operations on
893          * switch page due to the new page being added.
894          */
895         priv->page_added = TRUE;
896
897         /* Get list of chats up to date */
898         priv->chats = g_list_append (priv->chats, chat);
899
900         chat_window_update_chat_tab (chat);
901 }
902
903 static void
904 chat_window_page_removed_cb (GtkNotebook      *notebook,
905                              GtkWidget        *child,
906                              guint             page_num,
907                              EmpathyChatWindow *window)
908 {
909         EmpathyChatWindowPriv *priv;
910         EmpathyChat           *chat;
911
912         priv = GET_PRIV (window);
913
914         /* If we just received DND to the same window, we don't want
915          * to do anything here like removing the tab and then readding
916          * it, so we return here and in "page-added".
917          */
918         if (priv->dnd_same_window) {
919                 DEBUG ("Page removed (and will be readded to same window)");
920                 return;
921         }
922
923         DEBUG ("Page removed");
924
925         /* Get chat object */
926         chat = EMPATHY_CHAT (child);
927
928         /* Disconnect all signal handlers for this chat and this window */
929         g_signal_handlers_disconnect_by_func (chat,
930                                               G_CALLBACK (chat_window_composing_cb),
931                                               window);
932         g_signal_handlers_disconnect_by_func (chat,
933                                               G_CALLBACK (chat_window_new_message_cb),
934                                               window);
935
936         /* Keep list of chats up to date */
937         priv->chats = g_list_remove (priv->chats, chat);
938         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
939         priv->chats_composing = g_list_remove (priv->chats_composing, chat);
940
941         if (priv->chats == NULL) {
942                 g_object_unref (window);
943         } else {
944                 chat_window_update (window);
945         }
946 }
947
948 static gboolean
949 chat_window_focus_in_event_cb (GtkWidget        *widget,
950                                GdkEvent         *event,
951                                EmpathyChatWindow *window)
952 {
953         EmpathyChatWindowPriv *priv;
954
955         DEBUG ("Focus in event, updating title");
956
957         priv = GET_PRIV (window);
958
959         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
960
961         chat_window_set_urgency_hint (window, FALSE);
962         
963         /* Update the title, since we now mark all unread messages as read. */
964         chat_window_update_chat_tab (priv->current_chat);
965
966         return FALSE;
967 }
968
969 static void
970 chat_window_drag_data_received (GtkWidget        *widget,
971                                 GdkDragContext   *context,
972                                 int               x,
973                                 int               y,
974                                 GtkSelectionData *selection,
975                                 guint             info,
976                                 guint             time,
977                                 EmpathyChatWindow *window)
978 {
979         if (info == DND_DRAG_TYPE_CONTACT_ID) {
980                 EmpathyChat           *chat;
981                 EmpathyChatWindow     *old_window;
982                 McAccount             *account;
983                 const gchar           *id;
984                 gchar                **strv;
985
986                 id = (const gchar*) selection->data;
987
988                 DEBUG ("DND contact from roster with id:'%s'", id);
989                 
990                 strv = g_strsplit (id, "/", 2);
991                 account = mc_account_lookup (strv[0]);
992                 chat = empathy_chat_window_find_chat (account, strv[1]);
993
994                 if (!chat) {
995                         empathy_dispatcher_chat_with_contact_id (account, strv[2]);
996                         g_object_unref (account);
997                         g_strfreev (strv);
998                         return;
999                 }
1000                 g_object_unref (account);
1001                 g_strfreev (strv);
1002
1003                 old_window = chat_window_find_chat (chat);              
1004                 if (old_window) {
1005                         if (old_window == window) {
1006                                 gtk_drag_finish (context, TRUE, FALSE, time);
1007                                 return;
1008                         }
1009                         
1010                         empathy_chat_window_move_chat (old_window, window, chat);
1011                 } else {
1012                         empathy_chat_window_add_chat (window, chat);
1013                 }
1014                 
1015                 /* Added to take care of any outstanding chat events */
1016                 empathy_chat_window_present_chat (chat);
1017
1018                 /* We should return TRUE to remove the data when doing
1019                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1020                  * weird consequences, and we handle that internally
1021                  * anyway with add_chat() and remove_chat().
1022                  */
1023                 gtk_drag_finish (context, TRUE, FALSE, time);
1024         }
1025         else if (info == DND_DRAG_TYPE_TAB) {
1026                 EmpathyChat        **chat;
1027                 EmpathyChatWindow   *old_window = NULL;
1028
1029                 DEBUG ("DND tab");
1030
1031                 chat = (void*) selection->data;
1032                 old_window = chat_window_find_chat (*chat);
1033
1034                 if (old_window) {
1035                         EmpathyChatWindowPriv *priv;
1036
1037                         priv = GET_PRIV (window);
1038
1039                         if (old_window == window) {
1040                                 DEBUG ("DND tab (within same window)");
1041                                 priv->dnd_same_window = TRUE;
1042                                 gtk_drag_finish (context, TRUE, FALSE, time);
1043                                 return;
1044                         }
1045                         
1046                         priv->dnd_same_window = FALSE;
1047                 }
1048
1049                 /* We should return TRUE to remove the data when doing
1050                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1051                  * weird consequences, and we handle that internally
1052                  * anyway with add_chat() and remove_chat().
1053                  */
1054                 gtk_drag_finish (context, TRUE, FALSE, time);
1055         } else {
1056                 DEBUG ("DND from unknown source");
1057                 gtk_drag_finish (context, FALSE, FALSE, time);
1058         }
1059 }
1060
1061 static void
1062 chat_window_finalize (GObject *object)
1063 {
1064         EmpathyChatWindow     *window;
1065         EmpathyChatWindowPriv *priv;
1066
1067         window = EMPATHY_CHAT_WINDOW (object);
1068         priv = GET_PRIV (window);
1069
1070         DEBUG ("Finalized: %p", object);
1071
1072         if (priv->save_geometry_id != 0) {
1073                 g_source_remove (priv->save_geometry_id);
1074         }
1075
1076         chat_windows = g_list_remove (chat_windows, window);
1077         gtk_widget_destroy (priv->dialog);
1078
1079         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1080 }
1081
1082 static void
1083 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1084 {
1085         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1086
1087         object_class->finalize = chat_window_finalize;
1088
1089         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1090
1091         /* Set up a style for the close button with no focus padding. */
1092         gtk_rc_parse_string (
1093                 "style \"empathy-close-button-style\"\n"
1094                 "{\n"
1095                 "  GtkWidget::focus-padding = 0\n"
1096                 "  xthickness = 0\n"
1097                 "  ythickness = 0\n"
1098                 "}\n"
1099                 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1100
1101         gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1102 }
1103
1104 static void
1105 empathy_chat_window_init (EmpathyChatWindow *window)
1106 {
1107         GladeXML              *glade;
1108         GtkAccelGroup         *accel_group;
1109         GClosure              *closure;
1110         GtkWidget             *menu_conv;
1111         GtkWidget             *menu;
1112         gint                   i;
1113         GtkWidget             *chat_vbox;
1114         gchar                 *filename;
1115         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1116                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1117
1118         window->priv = priv;
1119         filename = empathy_file_lookup ("empathy-chat-window.glade", "src");
1120         glade = empathy_glade_get_file (filename,
1121                                        "chat_window",
1122                                        NULL,
1123                                        "chat_window", &priv->dialog,
1124                                        "chat_vbox", &chat_vbox,
1125                                        "menu_conv", &menu_conv,
1126                                        "menu_conv_clear", &priv->menu_conv_clear,
1127                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1128                                        "menu_conv_contact", &priv->menu_conv_contact,
1129                                        "menu_conv_close", &priv->menu_conv_close,
1130                                        "menu_edit_cut", &priv->menu_edit_cut,
1131                                        "menu_edit_copy", &priv->menu_edit_copy,
1132                                        "menu_edit_paste", &priv->menu_edit_paste,
1133                                        "menu_tabs_next", &priv->menu_tabs_next,
1134                                        "menu_tabs_prev", &priv->menu_tabs_prev,
1135                                        "menu_tabs_left", &priv->menu_tabs_left,
1136                                        "menu_tabs_right", &priv->menu_tabs_right,
1137                                        "menu_tabs_detach", &priv->menu_tabs_detach,
1138                                        "menu_help_contents", &priv->menu_help_contents,
1139                                        "menu_help_about", &priv->menu_help_about,
1140                                        NULL);
1141         g_free (filename);
1142
1143         empathy_glade_connect (glade,
1144                               window,
1145                               "chat_window", "configure-event", chat_window_configure_event_cb,
1146                               "menu_conv", "activate", chat_window_conv_activate_cb,
1147                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1148                               "menu_conv_close", "activate", chat_window_close_activate_cb,
1149                               "menu_edit", "activate", chat_window_edit_activate_cb,
1150                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1151                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1152                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1153                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1154                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1155                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1156                               "menu_help_contents", "activate", chat_window_help_contents_cb,
1157                               "menu_help_about", "activate", chat_window_help_about_cb,
1158                               NULL);
1159
1160         g_object_unref (glade);
1161
1162         priv->notebook = gtk_notebook_new ();
1163         gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow"); 
1164         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1165         gtk_widget_show (priv->notebook);
1166
1167         /* Set up accels */
1168         accel_group = gtk_accel_group_new ();
1169         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1170
1171         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1172                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1173                                            window,
1174                                            NULL);
1175                 gtk_accel_group_connect (accel_group,
1176                                          tab_accel_keys[i],
1177                                          GDK_MOD1_MASK,
1178                                          0,
1179                                          closure);
1180         }
1181
1182         g_object_unref (accel_group);
1183
1184         /* Set up smiley menu */
1185         menu = empathy_chat_view_get_smiley_menu (
1186                 G_CALLBACK (chat_window_insert_smiley_activate_cb),
1187                 window);
1188         gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_insert_smiley), menu);
1189
1190         /* Set up signals we can't do with glade since we may need to
1191          * block/unblock them at some later stage.
1192          */
1193
1194         g_signal_connect (priv->dialog,
1195                           "delete_event",
1196                           G_CALLBACK (chat_window_delete_event_cb),
1197                           window);
1198
1199         g_signal_connect_swapped (priv->menu_tabs_prev,
1200                                   "activate",
1201                                   G_CALLBACK (gtk_notebook_prev_page),
1202                                   priv->notebook);
1203         g_signal_connect_swapped (priv->menu_tabs_next,
1204                                   "activate",
1205                                   G_CALLBACK (gtk_notebook_next_page),
1206                                   priv->notebook);
1207
1208         g_signal_connect (priv->dialog,
1209                           "focus_in_event",
1210                           G_CALLBACK (chat_window_focus_in_event_cb),
1211                           window);
1212         g_signal_connect_after (priv->notebook,
1213                                 "switch_page",
1214                                 G_CALLBACK (chat_window_page_switched_cb),
1215                                 window);
1216         g_signal_connect (priv->notebook,
1217                           "page_added",
1218                           G_CALLBACK (chat_window_page_added_cb),
1219                           window);
1220         g_signal_connect (priv->notebook,
1221                           "page_removed",
1222                           G_CALLBACK (chat_window_page_removed_cb),
1223                           window);
1224
1225         /* Set up drag and drop */
1226         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
1227                            GTK_DEST_DEFAULT_ALL,
1228                            drag_types_dest,
1229                            G_N_ELEMENTS (drag_types_dest),
1230                            GDK_ACTION_MOVE);
1231
1232         g_signal_connect (priv->notebook,
1233                           "drag-data-received",
1234                           G_CALLBACK (chat_window_drag_data_received),
1235                           window);
1236
1237         chat_windows = g_list_prepend (chat_windows, window);
1238
1239         /* Set up private details */
1240         priv->chats = NULL;
1241         priv->chats_new_msg = NULL;
1242         priv->chats_composing = NULL;
1243         priv->current_chat = NULL;
1244 }
1245
1246 EmpathyChatWindow *
1247 empathy_chat_window_new (void)
1248 {
1249         return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1250 }
1251
1252 /* Returns the window to open a new tab in if there is only one window
1253  * visble, otherwise, returns NULL indicating that a new window should
1254  * be added.
1255  */
1256 EmpathyChatWindow *
1257 empathy_chat_window_get_default (void)
1258 {
1259         GList    *l;
1260         gboolean  separate_windows = TRUE;
1261
1262         empathy_conf_get_bool (empathy_conf_get (),
1263                               EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
1264                               &separate_windows);
1265
1266         if (separate_windows) {
1267                 /* Always create a new window */
1268                 return NULL;
1269         }
1270
1271         for (l = chat_windows; l; l = l->next) {
1272                 EmpathyChatWindow *chat_window;
1273                 GtkWidget         *dialog;
1274
1275                 chat_window = l->data;
1276
1277                 dialog = empathy_chat_window_get_dialog (chat_window);
1278                 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
1279                         /* Found a visible window on this desktop */
1280                         return chat_window;
1281                 }
1282         }
1283
1284         return NULL;
1285 }
1286
1287 GtkWidget *
1288 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
1289 {
1290         EmpathyChatWindowPriv *priv;
1291
1292         g_return_val_if_fail (window != NULL, NULL);
1293
1294         priv = GET_PRIV (window);
1295
1296         return priv->dialog;
1297 }
1298
1299 void
1300 empathy_chat_window_add_chat (EmpathyChatWindow *window,
1301                               EmpathyChat       *chat)
1302 {
1303         EmpathyChatWindowPriv *priv;
1304         GtkWidget             *label;
1305         GtkWidget             *child;
1306         gint                   x, y, w, h;
1307
1308         g_return_if_fail (window != NULL);
1309         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1310
1311         priv = GET_PRIV (window);
1312
1313         /* Reference the chat object */
1314         g_object_ref (chat);
1315
1316         /* If this window has just been created, position it */
1317         if (priv->chats == NULL) {
1318                 empathy_geometry_load (chat_get_window_id_for_geometry (chat), &x, &y, &w, &h);
1319                 
1320                 if (x >= 0 && y >= 0) {
1321                         /* Let the window manager position it if we don't have
1322                          * good x, y coordinates.
1323                          */
1324                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1325                 }
1326                 
1327                 if (w > 0 && h > 0) {
1328                         /* Use the defaults from the glade file if we don't have
1329                          * good w, h geometry.
1330                          */
1331                         gtk_window_resize (GTK_WINDOW (priv->dialog), w, h);
1332                 }
1333         }
1334
1335         child = GTK_WIDGET (chat);
1336         label = chat_window_create_label (window, chat); 
1337         gtk_widget_show (child);
1338
1339         g_signal_connect (chat, "notify::name",
1340                           G_CALLBACK (chat_window_chat_notify_cb),
1341                           NULL);
1342         g_signal_connect (chat, "notify::subject",
1343                           G_CALLBACK (chat_window_chat_notify_cb),
1344                           NULL);
1345         g_signal_connect (chat, "notify::remote-contact",
1346                           G_CALLBACK (chat_window_chat_notify_cb),
1347                           NULL);
1348         chat_window_chat_notify_cb (chat);
1349
1350         gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), child, label);
1351         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
1352         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
1353         gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
1354                                             TRUE, TRUE, GTK_PACK_START); 
1355
1356         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
1357 }
1358
1359 void
1360 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
1361                                  EmpathyChat       *chat)
1362 {
1363         EmpathyChatWindowPriv *priv;
1364         gint                   position;
1365         EmpathyContact        *remote_contact;
1366
1367         g_return_if_fail (window != NULL);
1368         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1369
1370         priv = GET_PRIV (window);
1371
1372         g_signal_handlers_disconnect_by_func (chat,
1373                                               chat_window_chat_notify_cb,
1374                                               NULL);
1375         remote_contact = g_object_get_data (G_OBJECT (chat),
1376                                             "chat-window-remote-contact");
1377         if (remote_contact) {
1378                 g_signal_handlers_disconnect_by_func (remote_contact,
1379                                                       chat_window_update_chat_tab,
1380                                                       chat);
1381         }
1382
1383         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
1384                                           GTK_WIDGET (chat));
1385         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
1386
1387         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
1388
1389         g_object_unref (chat);
1390 }
1391
1392 void
1393 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
1394                                EmpathyChatWindow *new_window,
1395                                EmpathyChat       *chat)
1396 {
1397         GtkWidget *widget;
1398
1399         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
1400         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
1401         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1402
1403         widget = GTK_WIDGET (chat);
1404
1405         DEBUG ("Chat moving with widget:%p (%d references)", widget,
1406                 G_OBJECT (widget)->ref_count);
1407
1408         /* We reference here to make sure we don't loose the widget
1409          * and the EmpathyChat object during the move.
1410          */
1411         g_object_ref (chat);
1412         g_object_ref (widget);
1413
1414         empathy_chat_window_remove_chat (old_window, chat);
1415         empathy_chat_window_add_chat (new_window, chat);
1416
1417         g_object_unref (widget);
1418         g_object_unref (chat);
1419 }
1420
1421 void
1422 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
1423                                     EmpathyChat       *chat)
1424 {
1425         EmpathyChatWindowPriv *priv;
1426         gint                  page_num;
1427
1428         g_return_if_fail (window != NULL);
1429         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1430
1431         priv = GET_PRIV (window);
1432
1433         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
1434                                           GTK_WIDGET (chat));
1435         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
1436                                        page_num);
1437 }
1438
1439 gboolean
1440 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1441 {
1442         EmpathyChatWindowPriv *priv;
1443         gboolean              has_focus;
1444
1445         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1446
1447         priv = GET_PRIV (window);
1448
1449         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1450
1451         return has_focus;
1452 }
1453
1454 EmpathyChat *
1455 empathy_chat_window_find_chat (McAccount   *account,
1456                                const gchar *id)
1457 {
1458         GList *l;
1459
1460         g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
1461         g_return_val_if_fail (!G_STR_EMPTY (id), NULL);
1462
1463         for (l = chat_windows; l; l = l->next) {
1464                 EmpathyChatWindowPriv *priv;
1465                 EmpathyChatWindow     *window;
1466                 GList                *ll;
1467
1468                 window = l->data;
1469                 priv = GET_PRIV (window);
1470
1471                 for (ll = priv->chats; ll; ll = ll->next) {
1472                         EmpathyChat *chat;
1473
1474                         chat = ll->data;
1475
1476                         if (empathy_account_equal (account, empathy_chat_get_account (chat)) &&
1477                             !tp_strdiff (id, empathy_chat_get_id (chat))) {
1478                                 return chat;
1479                         }
1480                 }
1481         }
1482
1483         return NULL;
1484 }
1485
1486 void
1487 empathy_chat_window_present_chat (EmpathyChat *chat)
1488 {
1489         EmpathyChatWindow     *window;
1490         EmpathyChatWindowPriv *priv;
1491
1492         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1493
1494         window = chat_window_find_chat (chat);
1495
1496         /* If the chat has no window, create one */
1497         if (window == NULL) {
1498                 window = empathy_chat_window_get_default ();
1499                 if (!window) {
1500                         window = empathy_chat_window_new ();
1501                 }
1502
1503                 empathy_chat_window_add_chat (window, chat);
1504         }
1505
1506         priv = GET_PRIV (window);
1507         empathy_chat_window_switch_to_chat (window, chat);
1508         empathy_window_present (GTK_WINDOW (priv->dialog), TRUE);
1509
1510         gtk_widget_grab_focus (chat->input_text_view); 
1511 }
1512
1513 #if 0
1514 static gboolean
1515 chat_window_should_play_sound (EmpathyChatWindow *window)
1516 {
1517         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1518         gboolean               has_focus = FALSE;
1519
1520         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1521
1522         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1523
1524         return !has_focus;
1525 }
1526 #endif