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