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