1b2fcccb2a9a09dab1ecf1df96c908e348b16d53
[empathy.git] / libempathy-gtk / empathy-chat.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-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 #include <stdlib.h>
32
33 #include <gdk/gdkkeysyms.h>
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36
37 #include <libmissioncontrol/mission-control.h>
38
39 #include <libempathy/empathy-contact-manager.h>
40 #include <libempathy/empathy-log-manager.h>
41 #include <libempathy/empathy-debug.h>
42 #include <libempathy/empathy-utils.h>
43
44 #include "empathy-chat.h"
45 #include "empathy-chat-window.h"
46 #include "empathy-geometry.h"
47 #include "empathy-conf.h"
48 #include "empathy-preferences.h"
49 #include "empathy-spell.h"
50 #include "empathy-spell-dialog.h"
51 #include "empathy-ui-utils.h"
52 #include "empathy-gtk-marshal.h"
53
54 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CHAT, EmpathyChatPriv))
55
56 #define DEBUG_DOMAIN "Chat"
57
58 #define CHAT_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
59 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
60
61 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
62
63 #define MAX_INPUT_HEIGHT 150
64
65 #define COMPOSING_STOP_TIMEOUT 5
66
67 struct _EmpathyChatPriv {
68         EmpathyLogManager     *log_manager;
69         EmpathyTpChat         *tp_chat;
70         EmpathyChatWindow     *window;
71         McAccount             *account;
72         MissionControl        *mc;
73         guint                  composing_stop_timeout_id;
74         gboolean               sensitive;
75         gchar                 *id;
76         GSList                *sent_messages;
77         gint                   sent_messages_index;
78         GList                 *compositors;
79         guint                  scroll_idle_id;
80         gboolean               first_tp_chat;
81         time_t                 last_log_timestamp;
82         gboolean               is_first_char;
83         guint                  block_events_timeout_id;
84         /* Used to automatically shrink a window that has temporarily
85          * grown due to long input. 
86          */
87         gint                   padding_height;
88         gint                   default_window_height;
89         gint                   last_input_height;
90         gboolean               vscroll_visible;
91 };
92
93 typedef struct {
94         EmpathyChat  *chat;
95         gchar       *word;
96
97         GtkTextIter  start;
98         GtkTextIter  end;
99 } EmpathyChatSpell;
100
101 static void             empathy_chat_class_init           (EmpathyChatClass       *klass);
102 static void             empathy_chat_init                 (EmpathyChat            *chat);
103 static void             chat_finalize                     (GObject                *object);
104 static void             chat_destroy_cb                   (EmpathyTpChat          *tp_chat,
105                                                            EmpathyChat            *chat);
106 static void             chat_send                         (EmpathyChat            *chat,
107                                                            const gchar            *msg);
108 static void             chat_input_text_view_send         (EmpathyChat            *chat);
109 static void             chat_message_received_cb          (EmpathyTpChat          *tp_chat,
110                                                            EmpathyMessage         *message,
111                                                            EmpathyChat            *chat);
112 static void             chat_send_error_cb                (EmpathyTpChat          *tp_chat,
113                                                            EmpathyMessage         *message,
114                                                            TpChannelTextSendError  error_code,
115                                                            EmpathyChat            *chat);
116 static void             chat_sent_message_add             (EmpathyChat            *chat,
117                                                            const gchar            *str);
118 static const gchar *    chat_sent_message_get_next        (EmpathyChat            *chat);
119 static const gchar *    chat_sent_message_get_last        (EmpathyChat            *chat);
120 static gboolean         chat_input_key_press_event_cb     (GtkWidget              *widget,
121                                                            GdkEventKey            *event,
122                                                            EmpathyChat            *chat);
123 static void             chat_input_text_buffer_changed_cb (GtkTextBuffer          *buffer,
124                                                            EmpathyChat            *chat);
125 static gboolean         chat_text_view_focus_in_event_cb  (GtkWidget              *widget,
126                                                            GdkEvent               *event,
127                                                            EmpathyChat            *chat);
128 static void             chat_text_view_scroll_hide_cb     (GtkWidget              *widget,
129                                                            EmpathyChat            *chat);
130 static void             chat_text_view_size_allocate_cb   (GtkWidget              *widget,
131                                                            GtkAllocation          *allocation,
132                                                            EmpathyChat            *chat);
133 static void             chat_text_view_realize_cb         (GtkWidget              *widget,
134                                                            EmpathyChat            *chat);
135 static void             chat_text_populate_popup_cb       (GtkTextView            *view,
136                                                            GtkMenu                *menu,
137                                                            EmpathyChat            *chat);
138 static void             chat_text_check_word_spelling_cb  (GtkMenuItem            *menuitem,
139                                                            EmpathyChatSpell       *chat_spell);
140 static EmpathyChatSpell *chat_spell_new                   (EmpathyChat            *chat,
141                                                            const gchar            *word,
142                                                            GtkTextIter             start,
143                                                            GtkTextIter             end);
144 static void             chat_spell_free                   (EmpathyChatSpell       *chat_spell);
145 static void             chat_composing_start              (EmpathyChat            *chat);
146 static void             chat_composing_stop               (EmpathyChat            *chat);
147 static void             chat_composing_remove_timeout     (EmpathyChat            *chat);
148 static gboolean         chat_composing_stop_timeout_cb    (EmpathyChat            *chat);
149 static void             chat_state_changed_cb             (EmpathyTpChat          *tp_chat,
150                                                            EmpathyContact         *contact,
151                                                            TpChannelChatState      state,
152                                                            EmpathyChat            *chat);
153 static void             chat_add_logs                     (EmpathyChat            *chat);
154 static gboolean         chat_scroll_down_idle_func        (EmpathyChat            *chat);
155
156 enum {
157         COMPOSING,
158         NEW_MESSAGE,
159         NAME_CHANGED,
160         STATUS_CHANGED,
161         LAST_SIGNAL
162 };
163
164 enum {
165         PROP_0,
166         PROP_TP_CHAT
167 };
168
169 static guint signals[LAST_SIGNAL] = { 0 };
170
171 G_DEFINE_TYPE (EmpathyChat, empathy_chat, G_TYPE_OBJECT);
172
173 static void
174 chat_get_property (GObject    *object,
175                    guint       param_id,
176                    GValue     *value,
177                    GParamSpec *pspec)
178 {
179         EmpathyChatPriv *priv = GET_PRIV (object);
180
181         switch (param_id) {
182         case PROP_TP_CHAT:
183                 g_value_set_object (value, priv->tp_chat);
184                 break;
185         default:
186                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
187                 break;
188         };
189 }
190
191 static void
192 chat_set_property (GObject      *object,
193                    guint         param_id,
194                    const GValue *value,
195                    GParamSpec   *pspec)
196 {
197         EmpathyChat *chat = EMPATHY_CHAT (object);
198
199         switch (param_id) {
200         case PROP_TP_CHAT:
201                 empathy_chat_set_tp_chat (chat,
202                                           EMPATHY_TP_CHAT (g_value_get_object (value)));
203                 break;
204         default:
205                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
206                 break;
207         };
208 }
209
210 static void
211 chat_status_changed_cb (MissionControl           *mc,
212                         TpConnectionStatus        status,
213                         McPresence                presence,
214                         TpConnectionStatusReason  reason,
215                         const gchar              *unique_name,
216                         EmpathyChat              *chat)
217 {
218         EmpathyChatPriv *priv = GET_PRIV (chat);
219         McAccount       *account;
220
221         account = mc_account_lookup (unique_name);
222
223         if (status == TP_CONNECTION_STATUS_CONNECTED && !priv->tp_chat &&
224             empathy_account_equal (account, priv->account)) {
225                 TpHandleType handle_type;
226
227                 empathy_debug (DEBUG_DOMAIN,
228                                "Account reconnected, request a new Text channel");
229
230                 if (empathy_chat_is_group_chat (chat)) {
231                         handle_type = TP_HANDLE_TYPE_ROOM;
232                 } else {
233                         handle_type = TP_HANDLE_TYPE_CONTACT;
234                 }
235
236                 mission_control_request_channel_with_string_handle (mc,
237                                                                     priv->account,
238                                                                     TP_IFACE_CHANNEL_TYPE_TEXT,
239                                                                     priv->id,
240                                                                     handle_type,
241                                                                     NULL, NULL);
242         }
243
244         g_object_unref (account);
245 }
246
247 static void
248 empathy_chat_class_init (EmpathyChatClass *klass)
249 {
250         GObjectClass *object_class;
251
252         object_class = G_OBJECT_CLASS (klass);
253
254         object_class->finalize = chat_finalize;
255         object_class->get_property = chat_get_property;
256         object_class->set_property = chat_set_property;
257
258         g_object_class_install_property (object_class,
259                                          PROP_TP_CHAT,
260                                          g_param_spec_object ("tp-chat",
261                                                               "Empathy tp chat",
262                                                               "The tp chat object",
263                                                               EMPATHY_TYPE_TP_CHAT,
264                                                               G_PARAM_CONSTRUCT |
265                                                               G_PARAM_READWRITE));
266
267         signals[COMPOSING] =
268                 g_signal_new ("composing",
269                               G_OBJECT_CLASS_TYPE (object_class),
270                               G_SIGNAL_RUN_LAST,
271                               0,
272                               NULL, NULL,
273                               g_cclosure_marshal_VOID__BOOLEAN,
274                               G_TYPE_NONE,
275                               1, G_TYPE_BOOLEAN);
276
277         signals[NEW_MESSAGE] =
278                 g_signal_new ("new-message",
279                               G_OBJECT_CLASS_TYPE (object_class),
280                               G_SIGNAL_RUN_LAST,
281                               0,
282                               NULL, NULL,
283                               _empathy_gtk_marshal_VOID__OBJECT_BOOLEAN,
284                               G_TYPE_NONE,
285                               2, EMPATHY_TYPE_MESSAGE, G_TYPE_BOOLEAN);
286
287         signals[NAME_CHANGED] =
288                 g_signal_new ("name-changed",
289                               G_OBJECT_CLASS_TYPE (object_class),
290                               G_SIGNAL_RUN_LAST,
291                               0,
292                               NULL, NULL,
293                               g_cclosure_marshal_VOID__POINTER,
294                               G_TYPE_NONE,
295                               1, G_TYPE_POINTER);
296
297         signals[STATUS_CHANGED] =
298                 g_signal_new ("status-changed",
299                               G_OBJECT_CLASS_TYPE (object_class),
300                               G_SIGNAL_RUN_LAST,
301                               0,
302                               NULL, NULL,
303                               g_cclosure_marshal_VOID__VOID,
304                               G_TYPE_NONE,
305                               0);
306
307         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
308 }
309
310 static void
311 empathy_chat_init (EmpathyChat *chat)
312 {
313         EmpathyChatPriv *priv = GET_PRIV (chat);
314         GtkTextBuffer  *buffer;
315
316         chat->view = empathy_chat_view_new ();
317         chat->input_text_view = gtk_text_view_new ();
318
319         priv->is_first_char = TRUE;
320
321         g_object_set (chat->input_text_view,
322                       "pixels-above-lines", 2,
323                       "pixels-below-lines", 2,
324                       "pixels-inside-wrap", 1,
325                       "right-margin", 2,
326                       "left-margin", 2,
327                       "wrap-mode", GTK_WRAP_WORD_CHAR,
328                       NULL);
329
330         priv->log_manager = empathy_log_manager_new ();
331         priv->default_window_height = -1;
332         priv->vscroll_visible = FALSE;
333         priv->sensitive = TRUE;
334         priv->sent_messages = NULL;
335         priv->sent_messages_index = -1;
336         priv->first_tp_chat = TRUE;
337         priv->mc = empathy_mission_control_new ();
338
339         dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc), "AccountStatusChanged",
340                                      G_CALLBACK (chat_status_changed_cb),
341                                      chat, NULL);
342
343         g_signal_connect (chat->input_text_view,
344                           "key_press_event",
345                           G_CALLBACK (chat_input_key_press_event_cb),
346                           chat);
347
348         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
349         g_signal_connect (buffer,
350                           "changed",
351                           G_CALLBACK (chat_input_text_buffer_changed_cb),
352                           chat);
353         g_signal_connect (chat->view,
354                           "focus_in_event",
355                           G_CALLBACK (chat_text_view_focus_in_event_cb),
356                           chat);
357
358         g_signal_connect (chat->input_text_view,
359                           "size_allocate",
360                           G_CALLBACK (chat_text_view_size_allocate_cb),
361                           chat);
362
363         g_signal_connect (chat->input_text_view,
364                           "realize",
365                           G_CALLBACK (chat_text_view_realize_cb),
366                           chat);
367
368         g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
369                           "populate_popup",
370                           G_CALLBACK (chat_text_populate_popup_cb),
371                           chat);
372
373         /* create misspelt words identification tag */
374         gtk_text_buffer_create_tag (buffer,
375                                     "misspelled",
376                                     "underline", PANGO_UNDERLINE_ERROR,
377                                     NULL);
378 }
379
380 static void
381 chat_finalize (GObject *object)
382 {
383         EmpathyChat     *chat;
384         EmpathyChatPriv *priv;
385
386         chat = EMPATHY_CHAT (object);
387         priv = GET_PRIV (chat);
388
389         empathy_debug (DEBUG_DOMAIN, "Finalized: %p", object);
390
391         g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
392         g_slist_free (priv->sent_messages);
393
394         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
395         g_list_free (priv->compositors);
396
397         chat_composing_remove_timeout (chat);
398         g_object_unref (priv->log_manager);
399
400         dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc), "AccountStatusChanged",
401                                         G_CALLBACK (chat_status_changed_cb),
402                                         chat);
403         g_object_unref (priv->mc);
404
405
406         if (priv->tp_chat) {
407                 g_object_unref (priv->tp_chat);
408         }
409
410         if (priv->account) {
411                 g_object_unref (priv->account);
412         }
413
414         if (priv->scroll_idle_id) {
415                 g_source_remove (priv->scroll_idle_id);
416         }
417
418         if (priv->block_events_timeout_id) {
419                 g_source_remove (priv->block_events_timeout_id);
420         }
421
422         g_free (priv->id);
423
424         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
425 }
426
427 static void
428 chat_destroy_cb (EmpathyTpChat *tp_chat,
429                  EmpathyChat    *chat)
430 {
431         EmpathyChatPriv *priv;
432
433         priv = GET_PRIV (chat);
434
435         if (priv->tp_chat) {
436                 g_object_unref (priv->tp_chat);
437                 priv->tp_chat = NULL;
438         }
439         priv->sensitive = FALSE;
440
441         empathy_chat_view_append_event (chat->view, _("Disconnected"));
442         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
443
444         if (priv->block_events_timeout_id != 0) {
445                 g_source_remove (priv->block_events_timeout_id);
446         }
447
448         if (EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat) {
449                 EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat (chat, NULL);
450         }
451 }
452
453 static void
454 chat_send (EmpathyChat  *chat,
455            const gchar *msg)
456 {
457         EmpathyChatPriv *priv;
458         EmpathyMessage  *message;
459
460         priv = GET_PRIV (chat);
461
462         if (G_STR_EMPTY (msg)) {
463                 return;
464         }
465
466         chat_sent_message_add (chat, msg);
467
468         if (g_str_has_prefix (msg, "/clear")) {
469                 empathy_chat_view_clear (chat->view);
470                 return;
471         }
472
473         /* FIXME: add here something to let group/privrate chat handle
474          *        some special messages */
475
476         message = empathy_message_new (msg);
477
478         empathy_tp_chat_send (priv->tp_chat, message);
479
480         g_object_unref (message);
481 }
482
483 static void
484 chat_input_text_view_send (EmpathyChat *chat)
485 {
486         EmpathyChatPriv *priv;
487         GtkTextBuffer  *buffer;
488         GtkTextIter     start, end;
489         gchar          *msg;
490
491         priv = GET_PRIV (chat);
492
493         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
494
495         gtk_text_buffer_get_bounds (buffer, &start, &end);
496         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
497
498         /* clear the input field */
499         gtk_text_buffer_set_text (buffer, "", -1);
500
501         chat_send (chat, msg);
502
503         g_free (msg);
504
505         priv->is_first_char = TRUE;
506 }
507
508 static void
509 chat_message_received_cb (EmpathyTpChat  *tp_chat,
510                           EmpathyMessage *message,
511                           EmpathyChat    *chat)
512 {
513         EmpathyChatPriv *priv;
514         EmpathyContact  *sender;
515         time_t           timestamp;
516
517         priv = GET_PRIV (chat);
518
519         timestamp = empathy_message_get_timestamp (message);
520         if (timestamp <= priv->last_log_timestamp) {
521                 /* Do not take care of messages anterior of the last
522                  * logged message. Some Jabber chatroom sends messages
523                  * received before we joined the room, this avoid
524                  * displaying those messages if we already logged them
525                  * last time we joined that room. */
526                 empathy_debug (DEBUG_DOMAIN, "Skipping message because it is "
527                                "anterior of last logged message.");
528                 return;
529         }
530
531         sender = empathy_message_get_sender (message);
532         empathy_debug (DEBUG_DOMAIN, "Appending message ('%s')",
533                       empathy_contact_get_name (sender));
534
535         empathy_log_manager_add_message (priv->log_manager,
536                                          empathy_chat_get_id (chat),
537                                          empathy_chat_is_group_chat (chat),
538                                          message);
539
540         empathy_chat_view_append_message (chat->view, message);
541
542         if (empathy_chat_should_play_sound (chat)) {
543                 // FIXME: empathy_sound_play (EMPATHY_SOUND_CHAT);
544         }
545
546         /* We received a message so the contact is no more composing */
547         chat_state_changed_cb (tp_chat, sender,
548                                TP_CHANNEL_CHAT_STATE_ACTIVE,
549                                chat);
550
551         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, FALSE);
552 }
553
554 static void
555 chat_send_error_cb (EmpathyTpChat          *tp_chat,
556                     EmpathyMessage         *message,
557                     TpChannelTextSendError  error_code,
558                     EmpathyChat            *chat)
559 {
560         const gchar *error;
561         gchar       *str;
562
563         switch (error_code) {
564         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
565                 error = _("offline");
566                 break;
567         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
568                 error = _("invalid contact");
569                 break;
570         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
571                 error = _("permission denied");
572                 break;
573         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
574                 error = _("too long message");
575                 break;
576         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
577                 error = _("not implemented");
578                 break;
579         default:
580                 error = _("unknown");
581                 break;
582         }
583
584         str = g_strdup_printf (_("Error sending message '%s': %s"),
585                                empathy_message_get_body (message),
586                                error);
587         empathy_chat_view_append_event (chat->view, str);
588         g_free (str);
589 }
590
591 static void 
592 chat_sent_message_add (EmpathyChat  *chat,
593                        const gchar *str)
594 {
595         EmpathyChatPriv *priv;
596         GSList         *list;
597         GSList         *item;
598
599         priv = GET_PRIV (chat);
600
601         /* Save the sent message in our repeat buffer */
602         list = priv->sent_messages;
603         
604         /* Remove any other occurances of this msg */
605         while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
606                 list = g_slist_remove_link (list, item);
607                 g_free (item->data);
608                 g_slist_free1 (item);
609         }
610
611         /* Trim the list to the last 10 items */
612         while (g_slist_length (list) > 10) {
613                 item = g_slist_last (list);
614                 if (item) {
615                         list = g_slist_remove_link (list, item);
616                         g_free (item->data);
617                         g_slist_free1 (item);
618                 }
619         }
620
621         /* Add new message */
622         list = g_slist_prepend (list, g_strdup (str));
623
624         /* Set list and reset the index */
625         priv->sent_messages = list;
626         priv->sent_messages_index = -1;
627 }
628
629 static const gchar *
630 chat_sent_message_get_next (EmpathyChat *chat)
631 {
632         EmpathyChatPriv *priv;
633         gint            max;
634         
635         priv = GET_PRIV (chat);
636
637         if (!priv->sent_messages) {
638                 empathy_debug (DEBUG_DOMAIN, 
639                               "No sent messages, next message is NULL");
640                 return NULL;
641         }
642
643         max = g_slist_length (priv->sent_messages) - 1;
644
645         if (priv->sent_messages_index < max) {
646                 priv->sent_messages_index++;
647         }
648         
649         empathy_debug (DEBUG_DOMAIN, 
650                       "Returning next message index:%d",
651                       priv->sent_messages_index);
652
653         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
654 }
655
656 static const gchar *
657 chat_sent_message_get_last (EmpathyChat *chat)
658 {
659         EmpathyChatPriv *priv;
660
661         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
662
663         priv = GET_PRIV (chat);
664         
665         if (!priv->sent_messages) {
666                 empathy_debug (DEBUG_DOMAIN, 
667                               "No sent messages, last message is NULL");
668                 return NULL;
669         }
670
671         if (priv->sent_messages_index >= 0) {
672                 priv->sent_messages_index--;
673         }
674
675         empathy_debug (DEBUG_DOMAIN, 
676                       "Returning last message index:%d",
677                       priv->sent_messages_index);
678
679         return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
680 }
681
682 static gboolean
683 chat_input_key_press_event_cb (GtkWidget   *widget,
684                                GdkEventKey *event,
685                                EmpathyChat *chat)
686 {
687         EmpathyChatPriv *priv;
688         GtkAdjustment  *adj;
689         gdouble         val;
690         GtkWidget      *text_view_sw;
691
692         priv = GET_PRIV (chat);
693
694         /* Catch ctrl+up/down so we can traverse messages we sent */
695         if ((event->state & GDK_CONTROL_MASK) && 
696             (event->keyval == GDK_Up || 
697              event->keyval == GDK_Down)) {
698                 GtkTextBuffer *buffer;
699                 const gchar   *str;
700
701                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
702
703                 if (event->keyval == GDK_Up) {
704                         str = chat_sent_message_get_next (chat);
705                 } else {
706                         str = chat_sent_message_get_last (chat);
707                 }
708
709                 g_signal_handlers_block_by_func (buffer, 
710                                                  chat_input_text_buffer_changed_cb,
711                                                  chat);
712                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
713                 g_signal_handlers_unblock_by_func (buffer, 
714                                                    chat_input_text_buffer_changed_cb,
715                                                    chat);
716
717                 return TRUE;    
718         }
719
720         /* Catch enter but not ctrl/shift-enter */
721         if (IS_ENTER (event->keyval) &&
722             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
723                 GtkTextView *view;
724
725                 /* This is to make sure that kinput2 gets the enter. And if
726                  * it's handled there we shouldn't send on it. This is because
727                  * kinput2 uses Enter to commit letters. See:
728                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
729                  */
730
731                 view = GTK_TEXT_VIEW (chat->input_text_view);
732                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
733                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
734                         return TRUE;
735                 }
736
737                 chat_input_text_view_send (chat);
738                 return TRUE;
739         }
740
741         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
742
743         if (IS_ENTER (event->keyval) &&
744             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
745                 /* Newline for shift/control-enter. */
746                 return FALSE;
747         }
748         else if (!(event->state & GDK_CONTROL_MASK) &&
749                  event->keyval == GDK_Page_Up) {
750                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
751                 gtk_adjustment_set_value (adj, adj->value - adj->page_size);
752
753                 return TRUE;
754         }
755         else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
756                  event->keyval == GDK_Page_Down) {
757                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
758                 val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
759                 gtk_adjustment_set_value (adj, val);
760
761                 return TRUE;
762         }
763
764         if (EMPATHY_CHAT_GET_CLASS (chat)->key_press_event) {
765                 return EMPATHY_CHAT_GET_CLASS (chat)->key_press_event (chat, event);
766         }
767
768         return FALSE;
769 }
770
771 static gboolean
772 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
773                                   GdkEvent   *event,
774                                   EmpathyChat *chat)
775 {
776         gtk_widget_grab_focus (chat->input_text_view);
777
778         return TRUE;
779 }
780
781 static void
782 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
783                                    EmpathyChat    *chat)
784 {
785         EmpathyChatPriv *priv;
786         GtkTextIter     start, end;
787         gchar          *str;
788         gboolean        spell_checker = FALSE;
789
790         priv = GET_PRIV (chat);
791
792         if (gtk_text_buffer_get_char_count (buffer) == 0) {
793                 chat_composing_stop (chat);
794         } else {
795                 chat_composing_start (chat);
796         }
797
798         empathy_conf_get_bool (empathy_conf_get (),
799                               EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
800                               &spell_checker);
801
802         if (priv->is_first_char) {
803                 GtkRequisition  req;
804                 gint            window_height;
805                 GtkWidget      *dialog;
806                 GtkAllocation  *allocation;
807
808                 /* Save the window's size */
809                 dialog = empathy_chat_window_get_dialog (priv->window);
810                 gtk_window_get_size (GTK_WINDOW (dialog),
811                                      NULL, &window_height);
812
813                 gtk_widget_size_request (chat->input_text_view, &req);
814
815                 allocation = &GTK_WIDGET (chat->view)->allocation;
816
817                 priv->default_window_height = window_height;
818                 priv->last_input_height = req.height;
819                 priv->padding_height = window_height - req.height - allocation->height;
820
821                 priv->is_first_char = FALSE;
822         }
823
824         gtk_text_buffer_get_start_iter (buffer, &start);
825
826         if (!spell_checker) {
827                 gtk_text_buffer_get_end_iter (buffer, &end);
828                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
829                 return;
830         }
831
832         if (!empathy_spell_supported ()) {
833                 return;
834         }
835
836         /* NOTE: this is really inefficient, we shouldn't have to
837            reiterate the whole buffer each time and check each work
838            every time. */
839         while (TRUE) {
840                 gboolean correct = FALSE;
841
842                 /* if at start */
843                 if (gtk_text_iter_is_start (&start)) {
844                         end = start;
845
846                         if (!gtk_text_iter_forward_word_end (&end)) {
847                                 /* no whole word yet */
848                                 break;
849                         }
850                 } else {
851                         if (!gtk_text_iter_forward_word_end (&end)) {
852                                 /* must be the end of the buffer */
853                                 break;
854                         }
855
856                         start = end;
857                         gtk_text_iter_backward_word_start (&start);
858                 }
859
860                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
861
862                 /* spell check string */
863                 if (!empathy_chat_get_is_command (str)) {
864                         correct = empathy_spell_check (str);
865                 } else {
866                         correct = TRUE;
867                 }
868
869                 if (!correct) {
870                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
871                 } else {
872                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
873                 }
874
875                 g_free (str);
876
877                 /* set start iter to the end iters position */
878                 start = end;
879         }
880 }
881
882 typedef struct {
883         GtkWidget *window;
884         gint       width;
885         gint       height;
886 } ChangeSizeData;
887
888 static gboolean
889 chat_change_size_in_idle_cb (ChangeSizeData *data)
890 {
891         gtk_window_resize (GTK_WINDOW (data->window),
892                            data->width, data->height);
893
894         return FALSE;
895 }
896
897 static void
898 chat_text_view_scroll_hide_cb (GtkWidget  *widget,
899                                EmpathyChat *chat)
900 {
901         EmpathyChatPriv *priv;
902         GtkWidget      *sw;
903
904         priv = GET_PRIV (chat);
905
906         priv->vscroll_visible = FALSE;
907         g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
908
909         sw = gtk_widget_get_parent (chat->input_text_view);
910         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
911                                         GTK_POLICY_NEVER,
912                                         GTK_POLICY_NEVER);
913         g_object_set (sw, "height-request", -1, NULL);
914 }
915
916 static void
917 chat_text_view_size_allocate_cb (GtkWidget     *widget,
918                                  GtkAllocation *allocation,
919                                  EmpathyChat    *chat)
920 {
921         EmpathyChatPriv *priv;
922         gint            width;
923         GtkWidget      *dialog;
924         ChangeSizeData *data;
925         gint            window_height;
926         gint            new_height;
927         GtkAllocation  *view_allocation;
928         gint            current_height;
929         gint            diff;
930         GtkWidget      *sw;
931
932         priv = GET_PRIV (chat);
933
934         if (priv->default_window_height <= 0) {
935                 return;
936         }
937
938         sw = gtk_widget_get_parent (widget);
939         if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
940                 GtkWidget *vscroll;
941
942                 priv->vscroll_visible = TRUE;
943                 gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
944                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
945                                                 GTK_POLICY_NEVER,
946                                                 GTK_POLICY_AUTOMATIC);
947                 vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
948                 g_signal_connect (vscroll, "hide",
949                                   G_CALLBACK (chat_text_view_scroll_hide_cb),
950                                   chat);
951         }
952
953         if (priv->last_input_height <= allocation->height) {
954                 priv->last_input_height = allocation->height;
955                 return;
956         }
957
958         diff = priv->last_input_height - allocation->height;
959         priv->last_input_height = allocation->height;
960
961         view_allocation = &GTK_WIDGET (chat->view)->allocation;
962
963         dialog = empathy_chat_window_get_dialog (priv->window);
964         gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
965
966         new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
967
968         if (new_height <= priv->default_window_height) {
969                 window_height = priv->default_window_height;
970         } else {
971                 window_height = new_height;
972         }
973
974         if (current_height <= window_height) {
975                 return;
976         }
977
978         /* Restore the window's size */
979         gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
980
981         data = g_new0 (ChangeSizeData, 1);
982         data->window = dialog;
983         data->width  = width;
984         data->height = window_height;
985
986         g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
987                          (GSourceFunc) chat_change_size_in_idle_cb,
988                          data, g_free);
989 }
990
991 static void
992 chat_text_view_realize_cb (GtkWidget  *widget,
993                            EmpathyChat *chat)
994 {
995         empathy_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
996         gtk_widget_grab_focus (widget);
997 }
998
999 static void
1000 chat_insert_smiley_activate_cb (GtkWidget   *menuitem,
1001                                 EmpathyChat *chat)
1002 {
1003         GtkTextBuffer *buffer;
1004         GtkTextIter    iter;
1005         const gchar   *smiley;
1006
1007         smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
1008
1009         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1010
1011         gtk_text_buffer_get_end_iter (buffer, &iter);
1012         gtk_text_buffer_insert (buffer, &iter, smiley, -1);
1013
1014         gtk_text_buffer_get_end_iter (buffer, &iter);
1015         gtk_text_buffer_insert (buffer, &iter, " ", -1);
1016 }
1017
1018 static void
1019 chat_text_populate_popup_cb (GtkTextView *view,
1020                              GtkMenu     *menu,
1021                              EmpathyChat  *chat)
1022 {
1023         EmpathyChatPriv  *priv;
1024         GtkTextBuffer   *buffer;
1025         GtkTextTagTable *table;
1026         GtkTextTag      *tag;
1027         gint             x, y;
1028         GtkTextIter      iter, start, end;
1029         GtkWidget       *item;
1030         gchar           *str = NULL;
1031         EmpathyChatSpell *chat_spell;
1032         GtkWidget       *smiley_menu;
1033
1034         priv = GET_PRIV (chat);
1035
1036         /* Add the emoticon menu. */
1037         item = gtk_separator_menu_item_new ();
1038         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1039         gtk_widget_show (item);
1040
1041         item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
1042         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1043         gtk_widget_show (item);
1044
1045         smiley_menu = empathy_chat_view_get_smiley_menu (
1046                 G_CALLBACK (chat_insert_smiley_activate_cb),
1047                 chat);
1048         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1049
1050         /* Add the spell check menu item. */
1051         buffer = gtk_text_view_get_buffer (view);
1052         table = gtk_text_buffer_get_tag_table (buffer);
1053
1054         tag = gtk_text_tag_table_lookup (table, "misspelled");
1055
1056         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1057
1058         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1059                                                GTK_TEXT_WINDOW_WIDGET,
1060                                                x, y,
1061                                                &x, &y);
1062
1063         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1064
1065         start = end = iter;
1066
1067         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1068             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1069
1070                 str = gtk_text_buffer_get_text (buffer,
1071                                                 &start, &end, FALSE);
1072         }
1073
1074         if (G_STR_EMPTY (str)) {
1075                 return;
1076         }
1077
1078         chat_spell = chat_spell_new (chat, str, start, end);
1079
1080         g_object_set_data_full (G_OBJECT (menu),
1081                                 "chat_spell", chat_spell,
1082                                 (GDestroyNotify) chat_spell_free);
1083
1084         item = gtk_separator_menu_item_new ();
1085         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1086         gtk_widget_show (item);
1087
1088         item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
1089         g_signal_connect (item,
1090                           "activate",
1091                           G_CALLBACK (chat_text_check_word_spelling_cb),
1092                           chat_spell);
1093         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1094         gtk_widget_show (item);
1095 }
1096
1097 static void
1098 chat_text_check_word_spelling_cb (GtkMenuItem     *menuitem,
1099                                   EmpathyChatSpell *chat_spell)
1100 {
1101         empathy_spell_dialog_show (chat_spell->chat,
1102                                   chat_spell->start,
1103                                   chat_spell->end,
1104                                   chat_spell->word);
1105 }
1106
1107 static EmpathyChatSpell *
1108 chat_spell_new (EmpathyChat  *chat,
1109                 const gchar *word,
1110                 GtkTextIter  start,
1111                 GtkTextIter  end)
1112 {
1113         EmpathyChatSpell *chat_spell;
1114
1115         chat_spell = g_new0 (EmpathyChatSpell, 1);
1116
1117         chat_spell->chat = g_object_ref (chat);
1118         chat_spell->word = g_strdup (word);
1119         chat_spell->start = start;
1120         chat_spell->end = end;
1121
1122         return chat_spell;
1123 }
1124
1125 static void
1126 chat_spell_free (EmpathyChatSpell *chat_spell)
1127 {
1128         g_object_unref (chat_spell->chat);
1129         g_free (chat_spell->word);
1130         g_free (chat_spell);
1131 }
1132
1133 static void
1134 chat_composing_start (EmpathyChat *chat)
1135 {
1136         EmpathyChatPriv *priv;
1137
1138         priv = GET_PRIV (chat);
1139
1140         if (priv->composing_stop_timeout_id) {
1141                 /* Just restart the timeout */
1142                 chat_composing_remove_timeout (chat);
1143         } else {
1144                 empathy_tp_chat_set_state (priv->tp_chat,
1145                                            TP_CHANNEL_CHAT_STATE_COMPOSING);
1146         }
1147
1148         priv->composing_stop_timeout_id = g_timeout_add_seconds (
1149                 COMPOSING_STOP_TIMEOUT,
1150                 (GSourceFunc) chat_composing_stop_timeout_cb,
1151                 chat);
1152 }
1153
1154 static void
1155 chat_composing_stop (EmpathyChat *chat)
1156 {
1157         EmpathyChatPriv *priv;
1158
1159         priv = GET_PRIV (chat);
1160
1161         chat_composing_remove_timeout (chat);
1162         empathy_tp_chat_set_state (priv->tp_chat,
1163                                    TP_CHANNEL_CHAT_STATE_ACTIVE);
1164 }
1165
1166 static void
1167 chat_composing_remove_timeout (EmpathyChat *chat)
1168 {
1169         EmpathyChatPriv *priv;
1170
1171         priv = GET_PRIV (chat);
1172
1173         if (priv->composing_stop_timeout_id) {
1174                 g_source_remove (priv->composing_stop_timeout_id);
1175                 priv->composing_stop_timeout_id = 0;
1176         }
1177 }
1178
1179 static gboolean
1180 chat_composing_stop_timeout_cb (EmpathyChat *chat)
1181 {
1182         EmpathyChatPriv *priv;
1183
1184         priv = GET_PRIV (chat);
1185
1186         priv->composing_stop_timeout_id = 0;
1187         empathy_tp_chat_set_state (priv->tp_chat,
1188                                    TP_CHANNEL_CHAT_STATE_PAUSED);
1189
1190         return FALSE;
1191 }
1192
1193 static void
1194 chat_state_changed_cb (EmpathyTpChat      *tp_chat,
1195                        EmpathyContact     *contact,
1196                        TpChannelChatState  state,
1197                        EmpathyChat        *chat)
1198 {
1199         EmpathyChatPriv *priv;
1200         GList          *l;
1201         gboolean        was_composing;
1202
1203         priv = GET_PRIV (chat);
1204
1205         if (empathy_contact_is_user (contact)) {
1206                 /* We don't care about our own chat state */
1207                 return;
1208         }
1209
1210         was_composing = (priv->compositors != NULL);
1211
1212         /* Find the contact in the list. After that l is the list elem or NULL */
1213         for (l = priv->compositors; l; l = l->next) {
1214                 if (contact == l->data) {
1215                         break;
1216                 }
1217         }
1218
1219         switch (state) {
1220         case TP_CHANNEL_CHAT_STATE_GONE:
1221         case TP_CHANNEL_CHAT_STATE_INACTIVE:
1222         case TP_CHANNEL_CHAT_STATE_PAUSED:
1223         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1224                 /* Contact is not composing */
1225                 if (l) {
1226                         priv->compositors = g_list_remove_link (priv->compositors, l);
1227                         g_object_unref (l->data);
1228                         g_list_free1 (l);
1229                 }
1230                 break;
1231         case TP_CHANNEL_CHAT_STATE_COMPOSING:
1232                 /* Contact is composing */
1233                 if (!l) {
1234                         priv->compositors = g_list_prepend (priv->compositors,
1235                                                             g_object_ref (contact));
1236                 }
1237                 break;
1238         default:
1239                 g_assert_not_reached ();
1240         }
1241
1242         empathy_debug (DEBUG_DOMAIN, "Was composing: %s now composing: %s",
1243                       was_composing ? "yes" : "no",
1244                       priv->compositors ? "yes" : "no");
1245
1246         if ((was_composing && !priv->compositors) ||
1247             (!was_composing && priv->compositors)) {
1248                 /* Composing state changed */
1249                 g_signal_emit (chat, signals[COMPOSING], 0,
1250                                priv->compositors != NULL);
1251         }
1252 }
1253
1254 static void
1255 chat_add_logs (EmpathyChat *chat)
1256 {
1257         EmpathyChatPriv *priv;
1258         GList          *messages, *l;
1259         guint           num_messages;
1260         guint           i;
1261
1262         priv = GET_PRIV (chat);
1263
1264         /* Turn off scrolling temporarily */
1265         empathy_chat_view_scroll (chat->view, FALSE);
1266
1267         /* Add messages from last conversation */
1268         messages = empathy_log_manager_get_last_messages (priv->log_manager,
1269                                                           priv->account,
1270                                                           empathy_chat_get_id (chat),
1271                                                           empathy_chat_is_group_chat (chat));
1272         num_messages  = g_list_length (messages);
1273
1274         for (l = messages, i = 0; l; l = l->next, i++) {
1275                 EmpathyMessage *message;
1276
1277                 message = l->data;
1278
1279                 /* Only add 10 last messages */
1280                 if (num_messages - i > 10) {
1281                         g_object_unref (message);
1282                         continue;
1283                 }
1284
1285                 priv->last_log_timestamp = empathy_message_get_timestamp (message);
1286                 empathy_chat_view_append_message (chat->view, message);
1287
1288                 g_object_unref (message);
1289         }
1290         g_list_free (messages);
1291
1292         /* Turn back on scrolling */
1293         empathy_chat_view_scroll (chat->view, TRUE);
1294
1295         /* Scroll to the most recent messages, we reference the chat
1296          * for the duration of the scroll func.
1297          */
1298         priv->scroll_idle_id = g_idle_add ((GSourceFunc) chat_scroll_down_idle_func, 
1299                                            g_object_ref (chat));
1300 }
1301
1302 /* Scroll down after the back-log has been received. */
1303 static gboolean
1304 chat_scroll_down_idle_func (EmpathyChat *chat)
1305 {
1306         EmpathyChatPriv *priv;
1307
1308         priv = GET_PRIV (chat);
1309
1310         empathy_chat_scroll_down (chat);
1311         g_object_unref (chat);
1312
1313         priv->scroll_idle_id = 0;
1314
1315         return FALSE;
1316 }
1317
1318 gboolean
1319 empathy_chat_get_is_command (const gchar *str)
1320 {
1321         g_return_val_if_fail (str != NULL, FALSE);
1322
1323         if (str[0] != '/') {
1324                 return FALSE;
1325         }
1326
1327         if (g_str_has_prefix (str, "/me")) {
1328                 return TRUE;
1329         }
1330         else if (g_str_has_prefix (str, "/nick")) {
1331                 return TRUE;
1332         }
1333         else if (g_str_has_prefix (str, "/topic")) {
1334                 return TRUE;
1335         }
1336
1337         return FALSE;
1338 }
1339
1340 void
1341 empathy_chat_correct_word (EmpathyChat  *chat,
1342                           GtkTextIter  start,
1343                           GtkTextIter  end,
1344                           const gchar *new_word)
1345 {
1346         GtkTextBuffer *buffer;
1347
1348         g_return_if_fail (chat != NULL);
1349         g_return_if_fail (new_word != NULL);
1350
1351         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1352
1353         gtk_text_buffer_delete (buffer, &start, &end);
1354         gtk_text_buffer_insert (buffer, &start,
1355                                 new_word,
1356                                 -1);
1357 }
1358
1359 const gchar *
1360 empathy_chat_get_name (EmpathyChat *chat)
1361 {
1362         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1363
1364         if (EMPATHY_CHAT_GET_CLASS (chat)->get_name) {
1365                 return EMPATHY_CHAT_GET_CLASS (chat)->get_name (chat);
1366         }
1367
1368         return NULL;
1369 }
1370
1371 gchar *
1372 empathy_chat_get_tooltip (EmpathyChat *chat)
1373 {
1374         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1375
1376         if (EMPATHY_CHAT_GET_CLASS (chat)->get_tooltip) {
1377                 return EMPATHY_CHAT_GET_CLASS (chat)->get_tooltip (chat);
1378         }
1379
1380         return NULL;
1381 }
1382
1383 const gchar *
1384 empathy_chat_get_status_icon_name (EmpathyChat *chat)
1385 {
1386         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1387
1388         if (EMPATHY_CHAT_GET_CLASS (chat)->get_status_icon_name) {
1389                 return EMPATHY_CHAT_GET_CLASS (chat)->get_status_icon_name (chat);
1390         }
1391
1392         return NULL;
1393 }
1394
1395 GtkWidget *
1396 empathy_chat_get_widget (EmpathyChat *chat)
1397 {
1398         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
1399
1400         if (EMPATHY_CHAT_GET_CLASS (chat)->get_widget) {
1401                 return EMPATHY_CHAT_GET_CLASS (chat)->get_widget (chat);
1402         }
1403
1404         return NULL;
1405 }
1406
1407 gboolean
1408 empathy_chat_is_group_chat (EmpathyChat *chat)
1409 {
1410         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1411
1412         if (EMPATHY_CHAT_GET_CLASS (chat)->is_group_chat) {
1413                 return EMPATHY_CHAT_GET_CLASS (chat)->is_group_chat (chat);
1414         }
1415
1416         return FALSE;
1417 }
1418
1419 gboolean 
1420 empathy_chat_is_connected (EmpathyChat *chat)
1421 {
1422         EmpathyChatPriv *priv;
1423
1424         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1425
1426         priv = GET_PRIV (chat);
1427
1428         return (priv->tp_chat != NULL);
1429 }
1430
1431 static const gchar *
1432 chat_get_window_id_for_geometry (EmpathyChat *chat)
1433 {
1434         gboolean separate_windows;
1435
1436         empathy_conf_get_bool (empathy_conf_get (),
1437                                EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
1438                                &separate_windows);
1439
1440         if (separate_windows) {
1441                 return empathy_chat_get_id (chat);
1442         } else {
1443                 return "chat-window";
1444         }
1445 }
1446
1447 void
1448 empathy_chat_save_geometry (EmpathyChat *chat,
1449                            gint        x,
1450                            gint        y,
1451                            gint        w,
1452                            gint        h)
1453 {
1454         empathy_geometry_save (chat_get_window_id_for_geometry (chat), x, y, w, h);
1455 }
1456
1457 void
1458 empathy_chat_load_geometry (EmpathyChat *chat,
1459                            gint       *x,
1460                            gint       *y,
1461                            gint       *w,
1462                            gint       *h)
1463 {
1464         empathy_geometry_load (chat_get_window_id_for_geometry (chat), x, y, w, h);
1465 }
1466
1467 static gboolean
1468 chat_block_events_timeout_cb (gpointer data)
1469 {
1470         EmpathyChat     *chat = EMPATHY_CHAT (data);
1471         EmpathyChatPriv *priv = GET_PRIV (chat);
1472
1473         chat->block_events = FALSE;
1474         priv->block_events_timeout_id = 0;
1475
1476         return FALSE;
1477 }
1478
1479 void
1480 empathy_chat_set_tp_chat (EmpathyChat   *chat,
1481                           EmpathyTpChat *tp_chat)
1482 {
1483         EmpathyChatPriv *priv;
1484
1485         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1486         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
1487
1488         priv = GET_PRIV (chat);
1489
1490         if (tp_chat == priv->tp_chat) {
1491                 return;
1492         }
1493
1494         /* Block events for some time to avoid having "has come online" or
1495          * "joined" messages. */
1496         chat->block_events = TRUE;
1497         if (priv->block_events_timeout_id != 0) {
1498                 g_source_remove (priv->block_events_timeout_id);
1499         }
1500         priv->block_events_timeout_id =
1501                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
1502
1503         if (priv->tp_chat) {
1504                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1505                                                       chat_message_received_cb,
1506                                                       chat);
1507                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1508                                                       chat_send_error_cb,
1509                                                       chat);
1510                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
1511                                                       chat_destroy_cb,
1512                                                       chat);
1513                 g_object_unref (priv->tp_chat);
1514         }
1515         if (priv->account) {
1516                 g_object_unref (priv->account);
1517         }
1518
1519         g_free (priv->id);
1520         priv->tp_chat = g_object_ref (tp_chat);
1521         priv->id = g_strdup (empathy_tp_chat_get_id (tp_chat));
1522         priv->account = g_object_ref (empathy_tp_chat_get_account (tp_chat));
1523         empathy_tp_chat_set_acknowledge (tp_chat, TRUE);
1524
1525         if (priv->first_tp_chat) {
1526                 chat_add_logs (chat);
1527                 priv->first_tp_chat = FALSE;
1528         }
1529
1530         g_signal_connect (tp_chat, "message-received",
1531                           G_CALLBACK (chat_message_received_cb),
1532                           chat);
1533         g_signal_connect (tp_chat, "send-error",
1534                           G_CALLBACK (chat_send_error_cb),
1535                           chat);
1536         g_signal_connect (tp_chat, "chat-state-changed",
1537                           G_CALLBACK (chat_state_changed_cb),
1538                           chat);
1539         g_signal_connect (tp_chat, "destroy",
1540                           G_CALLBACK (chat_destroy_cb),
1541                           chat);
1542
1543         if (!priv->sensitive) {
1544                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
1545                 empathy_chat_view_append_event (chat->view, _("Connected"));
1546                 priv->sensitive = TRUE;
1547         }
1548
1549         if (EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat) {
1550                 EMPATHY_CHAT_GET_CLASS (chat)->set_tp_chat (chat, tp_chat);
1551         }
1552
1553         g_object_notify (G_OBJECT (chat), "tp-chat");
1554 }
1555
1556 const gchar *
1557 empathy_chat_get_id (EmpathyChat *chat)
1558 {
1559         EmpathyChatPriv *priv;
1560
1561         priv = GET_PRIV (chat);
1562
1563         return priv->id;
1564 }
1565
1566 McAccount *
1567 empathy_chat_get_account (EmpathyChat *chat)
1568 {
1569         EmpathyChatPriv *priv = GET_PRIV (chat);
1570
1571         return priv->account;
1572 }
1573
1574 void
1575 empathy_chat_clear (EmpathyChat *chat)
1576 {
1577         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1578
1579         empathy_chat_view_clear (chat->view);
1580 }
1581
1582 void
1583 empathy_chat_set_window (EmpathyChat       *chat,
1584                         EmpathyChatWindow *window)
1585 {
1586         EmpathyChatPriv *priv;
1587
1588         priv = GET_PRIV (chat);
1589         priv->window = window;
1590 }
1591
1592 EmpathyChatWindow *
1593 empathy_chat_get_window (EmpathyChat *chat)
1594 {
1595         EmpathyChatPriv *priv;
1596
1597         priv = GET_PRIV (chat);
1598
1599         return priv->window;
1600 }
1601
1602 void
1603 empathy_chat_scroll_down (EmpathyChat *chat)
1604 {
1605         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1606
1607         empathy_chat_view_scroll_down (chat->view);
1608 }
1609
1610 void
1611 empathy_chat_cut (EmpathyChat *chat)
1612 {
1613         GtkTextBuffer *buffer;
1614
1615         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1616
1617         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1618         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1619                 GtkClipboard *clipboard;
1620
1621                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1622
1623                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
1624         }
1625 }
1626
1627 void
1628 empathy_chat_copy (EmpathyChat *chat)
1629 {
1630         GtkTextBuffer *buffer;
1631
1632         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1633
1634         if (empathy_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
1635                 empathy_chat_view_copy_clipboard (chat->view);
1636                 return;
1637         }
1638
1639         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1640         if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
1641                 GtkClipboard *clipboard;
1642
1643                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1644
1645                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1646         }
1647 }
1648
1649 void
1650 empathy_chat_paste (EmpathyChat *chat)
1651 {
1652         GtkTextBuffer *buffer;
1653         GtkClipboard  *clipboard;
1654
1655         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1656
1657         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1658         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1659
1660         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
1661 }
1662
1663 void
1664 empathy_chat_present (EmpathyChat *chat)
1665 {
1666         EmpathyChatPriv *priv;
1667
1668         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1669
1670         priv = GET_PRIV (chat);
1671
1672         if (priv->window == NULL) {
1673                 EmpathyChatWindow *window;
1674
1675                 window = empathy_chat_window_get_default ();
1676                 if (!window) {
1677                         window = empathy_chat_window_new ();
1678                 }
1679
1680                 empathy_chat_window_add_chat (window, chat);
1681         }
1682
1683         empathy_chat_window_switch_to_chat (priv->window, chat);
1684         empathy_window_present (
1685                 GTK_WINDOW (empathy_chat_window_get_dialog (priv->window)),
1686                 TRUE);
1687
1688         gtk_widget_grab_focus (chat->input_text_view); 
1689 }
1690
1691 gboolean
1692 empathy_chat_should_play_sound (EmpathyChat *chat)
1693 {
1694         EmpathyChatWindow *window;
1695         gboolean          play = TRUE;
1696
1697         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
1698
1699         window = empathy_chat_get_window (chat);
1700         if (!window) {
1701                 return TRUE;
1702         }
1703
1704         play = !empathy_chat_window_has_focus (window);
1705
1706         return play;
1707 }
1708
1709 gboolean
1710 empathy_chat_should_highlight_nick (EmpathyMessage *message)
1711 {
1712         EmpathyContact *contact;
1713         const gchar   *msg, *to;
1714         gchar         *cf_msg, *cf_to;
1715         gchar         *ch;
1716         gboolean       ret_val;
1717
1718         g_return_val_if_fail (EMPATHY_IS_MESSAGE (message), FALSE);
1719
1720         empathy_debug (DEBUG_DOMAIN, "Highlighting nickname");
1721
1722         ret_val = FALSE;
1723
1724         msg = empathy_message_get_body (message);
1725         if (!msg) {
1726                 return FALSE;
1727         }
1728
1729         contact = empathy_message_get_receiver (message);
1730         if (!contact || !empathy_contact_is_user (contact)) {
1731                 return FALSE;
1732         }
1733
1734         to = empathy_contact_get_name (contact);
1735         if (!to) {
1736                 return FALSE;
1737         }
1738
1739         cf_msg = g_utf8_casefold (msg, -1);
1740         cf_to = g_utf8_casefold (to, -1);
1741
1742         ch = strstr (cf_msg, cf_to);
1743         if (ch == NULL) {
1744                 goto finished;
1745         }
1746
1747         if (ch != cf_msg) {
1748                 /* Not first in the message */
1749                 if ((*(ch - 1) != ' ') &&
1750                     (*(ch - 1) != ',') &&
1751                     (*(ch - 1) != '.')) {
1752                         goto finished;
1753                 }
1754         }
1755
1756         ch = ch + strlen (cf_to);
1757         if (ch >= cf_msg + strlen (cf_msg)) {
1758                 ret_val = TRUE;
1759                 goto finished;
1760         }
1761
1762         if ((*ch == ' ') ||
1763             (*ch == ',') ||
1764             (*ch == '.') ||
1765             (*ch == ':')) {
1766                 ret_val = TRUE;
1767                 goto finished;
1768         }
1769
1770 finished:
1771         g_free (cf_msg);
1772         g_free (cf_to);
1773
1774         return ret_val;
1775 }
1776