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