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