]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat.c
add myself to AUTHORS
[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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  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 <telepathy-glib/account-manager.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 #include <libempathy/empathy-dispatcher.h>
44
45 #include "empathy-chat.h"
46 #include "empathy-conf.h"
47 #include "empathy-spell.h"
48 #include "empathy-contact-list-store.h"
49 #include "empathy-contact-list-view.h"
50 #include "empathy-contact-menu.h"
51 #include "empathy-theme-manager.h"
52 #include "empathy-smiley-manager.h"
53 #include "empathy-ui-utils.h"
54
55 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
56 #include <libempathy/empathy-debug.h>
57
58 #define CHAT_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
59 #define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
60 #define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
61 #define MAX_INPUT_HEIGHT 150
62 #define COMPOSING_STOP_TIMEOUT 5
63
64 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChat)
65 typedef struct {
66         EmpathyTpChat     *tp_chat;
67         TpAccount         *account;
68         gchar             *id;
69         gchar             *name;
70         gchar             *subject;
71         EmpathyContact    *remote_contact;
72         gboolean           show_contacts;
73
74         EmpathyLogManager *log_manager;
75         TpAccountManager  *account_manager;
76         GList             *input_history;
77         GList             *input_history_current;
78         GList             *compositors;
79         GCompletion       *completion;
80         guint              composing_stop_timeout_id;
81         guint              block_events_timeout_id;
82         TpHandleType       handle_type;
83         gint               contacts_width;
84         gboolean           has_input_vscroll;
85
86         GtkWidget         *widget;
87         GtkWidget         *hpaned;
88         GtkWidget         *vbox_left;
89         GtkWidget         *scrolled_window_chat;
90         GtkWidget         *scrolled_window_input;
91         GtkWidget         *scrolled_window_contacts;
92         GtkWidget         *hbox_topic;
93         GtkWidget         *label_topic;
94         GtkWidget         *contact_list_view;
95         GtkWidget         *info_bar_vbox;
96
97         guint              unread_messages;
98         /* TRUE if the pending messages can be displayed. This is to avoid to show
99          * pending messages *before* messages from logs. (#603980) */
100         gboolean           can_show_pending;
101 } EmpathyChatPriv;
102
103 typedef struct {
104         gchar *text; /* Original message that was specified
105                       * upon entry creation. */
106         gchar *modified_text; /* Message that was modified by user.
107                                * When no modifications were made, it is NULL */
108 } InputHistoryEntry;
109
110 enum {
111         COMPOSING,
112         NEW_MESSAGE,
113         LAST_SIGNAL
114 };
115
116 enum {
117         PROP_0,
118         PROP_TP_CHAT,
119         PROP_ACCOUNT,
120         PROP_ID,
121         PROP_NAME,
122         PROP_SUBJECT,
123         PROP_REMOTE_CONTACT,
124         PROP_SHOW_CONTACTS,
125 };
126
127 static guint signals[LAST_SIGNAL] = { 0 };
128
129 G_DEFINE_TYPE (EmpathyChat, empathy_chat, GTK_TYPE_BIN);
130
131 static void
132 chat_get_property (GObject    *object,
133                    guint       param_id,
134                    GValue     *value,
135                    GParamSpec *pspec)
136 {
137         EmpathyChat *chat = EMPATHY_CHAT (object);
138         EmpathyChatPriv *priv = GET_PRIV (object);
139
140         switch (param_id) {
141         case PROP_TP_CHAT:
142                 g_value_set_object (value, priv->tp_chat);
143                 break;
144         case PROP_ACCOUNT:
145                 g_value_set_object (value, priv->account);
146                 break;
147         case PROP_NAME:
148                 g_value_set_string (value, empathy_chat_get_name (chat));
149                 break;
150         case PROP_ID:
151                 g_value_set_string (value, priv->id);
152                 break;
153         case PROP_SUBJECT:
154                 g_value_set_string (value, priv->subject);
155                 break;
156         case PROP_REMOTE_CONTACT:
157                 g_value_set_object (value, priv->remote_contact);
158                 break;
159         case PROP_SHOW_CONTACTS:
160                 g_value_set_boolean (value, priv->show_contacts);
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_set_property (GObject      *object,
170                    guint         param_id,
171                    const GValue *value,
172                    GParamSpec   *pspec)
173 {
174         EmpathyChat *chat = EMPATHY_CHAT (object);
175
176         switch (param_id) {
177         case PROP_TP_CHAT:
178                 empathy_chat_set_tp_chat (chat, EMPATHY_TP_CHAT (g_value_get_object (value)));
179                 break;
180         case PROP_SHOW_CONTACTS:
181                 empathy_chat_set_show_contacts (chat, g_value_get_boolean (value));
182                 break;
183         default:
184                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
185                 break;
186         };
187 }
188
189 static void
190 chat_connect_channel_reconnected (EmpathyDispatchOperation *dispatch,
191                                   const GError             *error,
192                                   gpointer                  user_data)
193 {
194         EmpathyChat *chat = EMPATHY_CHAT (user_data);
195         EmpathyTpChat *tpchat;
196
197         if (error != NULL) {
198                 empathy_chat_view_append_event (chat->view,
199                         _("Failed to reconnect this chat"));
200                 return;
201         }
202
203         tpchat = EMPATHY_TP_CHAT (
204                 empathy_dispatch_operation_get_channel_wrapper (dispatch));
205
206         if (empathy_dispatch_operation_claim (dispatch)) {
207                 empathy_chat_set_tp_chat (chat, tpchat);
208         }
209 }
210
211 static void
212 reconnected_connection_ready_cb (TpConnection *connection,
213                         const GError *error,
214                         gpointer user_data)
215 {
216         EmpathyChat *chat = user_data;
217         EmpathyChatPriv *priv = GET_PRIV (chat);
218
219         if (error != NULL) {
220                 DEBUG ("connection is not ready: %s", error->message);
221                 goto out;
222         }
223
224         DEBUG ("Account reconnected, request a new Text channel");
225
226         switch (priv->handle_type) {
227                 case TP_HANDLE_TYPE_CONTACT:
228                         empathy_dispatcher_chat_with_contact_id (
229                                 connection, priv->id,
230                                 chat_connect_channel_reconnected,
231                                 chat);
232                         break;
233                 case TP_HANDLE_TYPE_ROOM:
234                         empathy_dispatcher_join_muc (connection,
235                                 priv->id,
236                                 chat_connect_channel_reconnected,
237                                 chat);
238                         break;
239                 default:
240                         g_assert_not_reached ();
241                         break;
242         }
243
244 out:
245         g_object_unref (chat);
246 }
247
248 static void
249 chat_new_connection_cb (TpAccount   *account,
250                         guint        old_status,
251                         guint        new_status,
252                         guint        reason,
253                         gchar       *dbus_error_name,
254                         GHashTable  *details,
255                         EmpathyChat *chat)
256 {
257         EmpathyChatPriv *priv = GET_PRIV (chat);
258         TpConnection *connection;
259
260         if (new_status != TP_CONNECTION_STATUS_CONNECTED)
261                 return;
262
263         connection = tp_account_get_connection (account);
264
265         if (priv->tp_chat != NULL || account != priv->account ||
266             priv->handle_type == TP_HANDLE_TYPE_NONE ||
267             EMP_STR_EMPTY (priv->id))
268                 return;
269
270         g_object_ref (chat);
271         tp_connection_call_when_ready (connection, reconnected_connection_ready_cb,
272                                    chat);
273 }
274
275 static void
276 chat_composing_remove_timeout (EmpathyChat *chat)
277 {
278         EmpathyChatPriv *priv;
279
280         priv = GET_PRIV (chat);
281
282         if (priv->composing_stop_timeout_id) {
283                 g_source_remove (priv->composing_stop_timeout_id);
284                 priv->composing_stop_timeout_id = 0;
285         }
286 }
287
288 static gboolean
289 chat_composing_stop_timeout_cb (EmpathyChat *chat)
290 {
291         EmpathyChatPriv *priv;
292
293         priv = GET_PRIV (chat);
294
295         priv->composing_stop_timeout_id = 0;
296         empathy_tp_chat_set_state (priv->tp_chat,
297                                    TP_CHANNEL_CHAT_STATE_PAUSED);
298
299         return FALSE;
300 }
301
302 static void
303 chat_composing_start (EmpathyChat *chat)
304 {
305         EmpathyChatPriv *priv;
306
307         priv = GET_PRIV (chat);
308
309         if (priv->composing_stop_timeout_id) {
310                 /* Just restart the timeout */
311                 chat_composing_remove_timeout (chat);
312         } else {
313                 empathy_tp_chat_set_state (priv->tp_chat,
314                                            TP_CHANNEL_CHAT_STATE_COMPOSING);
315         }
316
317         priv->composing_stop_timeout_id = g_timeout_add_seconds (
318                 COMPOSING_STOP_TIMEOUT,
319                 (GSourceFunc) chat_composing_stop_timeout_cb,
320                 chat);
321 }
322
323 static void
324 chat_composing_stop (EmpathyChat *chat)
325 {
326         EmpathyChatPriv *priv;
327
328         priv = GET_PRIV (chat);
329
330         chat_composing_remove_timeout (chat);
331         empathy_tp_chat_set_state (priv->tp_chat,
332                                    TP_CHANNEL_CHAT_STATE_ACTIVE);
333 }
334
335 static gint
336 chat_input_history_entry_cmp (InputHistoryEntry *entry,
337                               const gchar *text)
338 {
339         if (!tp_strdiff (entry->text, text)) {
340                 if (entry->modified_text != NULL) {
341                         /* Modified entry and single string cannot be equal. */
342                         return 1;
343                 }
344                 return 0;
345         }
346         return 1;
347 }
348
349 static InputHistoryEntry *
350 chat_input_history_entry_new_with_text (const gchar *text)
351 {
352         InputHistoryEntry *entry;
353         entry = g_slice_new0 (InputHistoryEntry);
354         entry->text = g_strdup (text);
355
356         return entry;
357 }
358
359 static void
360 chat_input_history_entry_free (InputHistoryEntry *entry)
361 {
362         g_free (entry->text);
363         g_free (entry->modified_text);
364         g_slice_free (InputHistoryEntry, entry);
365 }
366
367 static void
368 chat_input_history_entry_revert (InputHistoryEntry *entry)
369 {
370         g_free (entry->modified_text);
371         entry->modified_text = NULL;
372 }
373
374 static void
375 chat_input_history_entry_update_text (InputHistoryEntry *entry,
376                                       const gchar *text)
377 {
378         gchar *old;
379
380         if (!tp_strdiff (text, entry->text)) {
381                 g_free (entry->modified_text);
382                 entry->modified_text = NULL;
383                 return;
384         }
385
386         old = entry->modified_text;
387         entry->modified_text = g_strdup (text);
388         g_free (old);
389 }
390
391 static const gchar *
392 chat_input_history_entry_get_text (InputHistoryEntry *entry)
393 {
394         if (entry == NULL) {
395                 return NULL;
396         }
397
398         if (entry->modified_text != NULL) {
399                 return entry->modified_text;
400         }
401         return entry->text;
402 }
403
404 static GList *
405 chat_input_history_remove_item (GList *list,
406                                 GList *item)
407 {
408         list = g_list_remove_link (list, item);
409         chat_input_history_entry_free (item->data);
410         g_list_free_1 (item);
411         return list;
412 }
413
414 static void
415 chat_input_history_revert (EmpathyChat *chat)
416 {
417         EmpathyChatPriv   *priv;
418         GList             *list;
419         GList             *item1;
420         GList             *item2;
421         InputHistoryEntry *entry;
422
423         priv = GET_PRIV (chat);
424         list = priv->input_history;
425
426         if (list == NULL) {
427                 DEBUG ("No input history");
428                 return;
429         }
430
431         /* Delete temporary entry */
432         if (priv->input_history_current != NULL) {
433                 item1 = list;
434                 list = chat_input_history_remove_item (list, item1);
435                 if (priv->input_history_current == item1) {
436                         /* Removed temporary entry was current entry */
437                         priv->input_history = list;
438                         priv->input_history_current = NULL;
439                         return;
440                 }
441         }
442         else {
443                 /* There is no entry to revert */
444                 return;
445         }
446
447         /* Restore the current history entry to original value */
448         item1 = priv->input_history_current;
449         entry = item1->data;
450         chat_input_history_entry_revert (entry);
451
452         /* Remove restored entry if there is other occurance before this entry */
453         item2 = g_list_find_custom (list, chat_input_history_entry_get_text (entry),
454                                     (GCompareFunc) chat_input_history_entry_cmp);
455         if (item2 != item1) {
456                 list = chat_input_history_remove_item (list, item1);
457         }
458         else {
459                 /* Remove other occurance of the restored entry */
460                 item2 = g_list_find_custom (item1->next,
461                                             chat_input_history_entry_get_text (entry),
462                                             (GCompareFunc) chat_input_history_entry_cmp);
463                 if (item2 != NULL) {
464                         list = chat_input_history_remove_item (list, item2);
465                 }
466         }
467
468         priv->input_history_current = NULL;
469         priv->input_history = list;
470 }
471
472 static void
473 chat_input_history_add (EmpathyChat  *chat,
474                         const gchar *str,
475                         gboolean temporary)
476 {
477         EmpathyChatPriv   *priv;
478         GList             *list;
479         GList             *item;
480         InputHistoryEntry *entry;
481
482         priv = GET_PRIV (chat);
483
484         list = priv->input_history;
485
486         /* Remove any other occurances of this entry, if not temporary */
487         if (!temporary) {
488                 while ((item = g_list_find_custom (list, str,
489                     (GCompareFunc) chat_input_history_entry_cmp)) != NULL) {
490                         list = chat_input_history_remove_item (list, item);
491                 }
492
493                 /* Trim the list to the last 10 items */
494                 while (g_list_length (list) > 10) {
495                         item = g_list_last (list);
496                         if (item != NULL) {
497                                 list = chat_input_history_remove_item (list, item);
498                         }
499                 }
500         }
501
502
503
504         /* Add new entry */
505         entry = chat_input_history_entry_new_with_text (str);
506         list = g_list_prepend (list, entry);
507
508         /* Set the list and the current item pointer */
509         priv->input_history = list;
510         if (temporary) {
511                 priv->input_history_current = list;
512         }
513         else {
514                 priv->input_history_current = NULL;
515         }
516 }
517
518 static const gchar *
519 chat_input_history_get_next (EmpathyChat *chat)
520 {
521         EmpathyChatPriv *priv;
522         GList           *item;
523         const gchar     *msg;
524
525         priv = GET_PRIV (chat);
526
527         if (priv->input_history == NULL) {
528                 DEBUG ("No input history, next entry is NULL");
529                 return NULL;
530         }
531         g_assert (priv->input_history_current != NULL);
532
533         if ((item = g_list_next (priv->input_history_current)) == NULL)
534         {
535                 item = priv->input_history_current;
536         }
537
538         msg = chat_input_history_entry_get_text (item->data);
539
540         DEBUG ("Returning next entry: '%s'", msg);
541
542         priv->input_history_current = item;
543
544         return msg;
545 }
546
547 static const gchar *
548 chat_input_history_get_prev (EmpathyChat *chat)
549 {
550         EmpathyChatPriv *priv;
551         GList           *item;
552         const gchar     *msg;
553
554         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
555
556         priv = GET_PRIV (chat);
557
558         if (priv->input_history == NULL) {
559                 DEBUG ("No input history, previous entry is NULL");
560                 return NULL;
561         }
562
563         if (priv->input_history_current == NULL)
564         {
565                 return NULL;
566         }
567         else if ((item = g_list_previous (priv->input_history_current)) == NULL)
568         {
569                 item = priv->input_history_current;
570         }
571
572         msg = chat_input_history_entry_get_text (item->data);
573
574         DEBUG ("Returning previous entry: '%s'", msg);
575
576         priv->input_history_current = item;
577
578         return msg;
579 }
580
581 static void
582 chat_input_history_update (EmpathyChat *chat,
583                            GtkTextBuffer *buffer)
584 {
585         EmpathyChatPriv      *priv;
586         GtkTextIter           start, end;
587         gchar                *text;
588         InputHistoryEntry    *entry;
589
590         priv = GET_PRIV (chat);
591
592         gtk_text_buffer_get_bounds (buffer, &start, &end);
593         text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
594
595         if (priv->input_history_current == NULL) {
596                 /* Add the current text temporarily to the history */
597                 chat_input_history_add (chat, text, TRUE);
598                 g_free (text);
599                 return;
600         }
601
602         /* Save the changes in the history */
603         entry = priv->input_history_current->data;
604         if (tp_strdiff (chat_input_history_entry_get_text (entry), text)) {
605                 chat_input_history_entry_update_text (entry, text);
606         }
607
608         g_free (text);
609 }
610
611 static void
612 chat_command_join_cb (EmpathyDispatchOperation *dispatch,
613                       const GError             *error,
614                       gpointer                  user_data)
615 {
616         EmpathyChat *chat = user_data;
617
618         if (error != NULL) {
619                 DEBUG ("Error: %s", error->message);
620                 empathy_chat_view_append_event (chat->view,
621                         _("Failed to join chatroom"));
622         }
623 }
624
625 typedef struct {
626         EmpathyChat *chat;
627         gchar *message;
628 } ChatCommandMsgData;
629
630 static void
631 chat_command_msg_cb (EmpathyDispatchOperation *dispatch,
632                               const GError             *error,
633                               gpointer                  user_data)
634 {
635         ChatCommandMsgData *data = user_data;
636
637         if (error != NULL) {
638                 empathy_chat_view_append_event (data->chat->view,
639                         _("Failed to open private chat"));
640                 goto OUT;
641         }
642
643         if (!EMP_STR_EMPTY (data->message)) {
644                 EmpathyTpChat *tpchat;
645                 EmpathyMessage *message;
646
647                 tpchat = EMPATHY_TP_CHAT (
648                         empathy_dispatch_operation_get_channel_wrapper (dispatch));
649
650                 message = empathy_message_new (data->message);
651                 empathy_tp_chat_send (tpchat, message);
652                 g_object_unref (message);
653         }
654
655 OUT:
656         g_free (data->message);
657         g_slice_free (ChatCommandMsgData, data);
658 }
659
660 static void
661 chat_command_clear (EmpathyChat *chat,
662                     GStrv        strv)
663 {
664         empathy_chat_view_clear (chat->view);
665 }
666
667 static void
668 chat_command_topic (EmpathyChat *chat,
669                     GStrv        strv)
670 {
671         EmpathyChatPriv *priv = GET_PRIV (chat);
672         EmpathyTpChatProperty *property;
673         GValue value = {0, };
674
675         property = empathy_tp_chat_get_property (priv->tp_chat, "subject");
676         if (property == NULL) {
677                 empathy_chat_view_append_event (chat->view,
678                         _("Topic not supported on this conversation"));
679                 return;
680         }
681
682         if (!(property->flags & TP_PROPERTY_FLAG_WRITE)) {
683                 empathy_chat_view_append_event (chat->view,
684                         _("You are not allowed to change the topic"));
685                 return;
686         }
687
688         g_value_init (&value, G_TYPE_STRING);
689         g_value_set_string (&value, strv[1]);
690         empathy_tp_chat_set_property (priv->tp_chat, "subject", &value);
691         g_value_unset (&value);
692 }
693
694 static void
695 chat_command_join (EmpathyChat *chat,
696                    GStrv        strv)
697 {
698         EmpathyChatPriv *priv = GET_PRIV (chat);
699         TpConnection *connection;
700
701         connection = empathy_tp_chat_get_connection (priv->tp_chat);
702         empathy_dispatcher_join_muc (connection, strv[1],
703                                      chat_command_join_cb,
704                                      chat);
705 }
706
707 static void
708 chat_command_msg_internal (EmpathyChat *chat,
709                            const gchar *contact_id,
710                            const gchar *message)
711 {
712         EmpathyChatPriv *priv = GET_PRIV (chat);
713         TpConnection *connection;
714         ChatCommandMsgData *data;
715
716         /* FIXME: We should probably search in members alias. But this
717          * is enough for IRC */
718         data = g_slice_new (ChatCommandMsgData);
719         data->chat = chat;
720         data->message = g_strdup (message);
721         connection = empathy_tp_chat_get_connection (priv->tp_chat);
722         empathy_dispatcher_chat_with_contact_id (connection, contact_id,
723                                                  chat_command_msg_cb,
724                                                  data);
725 }
726
727 static void
728 chat_command_query (EmpathyChat *chat,
729                     GStrv        strv)
730 {
731         /* If <message> part is not defined,
732          * strv[2] will be the terminal NULL */
733         chat_command_msg_internal (chat, strv[1], strv[2]);
734 }
735
736 static void
737 chat_command_msg (EmpathyChat *chat,
738                   GStrv        strv)
739 {
740         chat_command_msg_internal (chat, strv[1], strv[2]);
741 }
742
743 static void
744 chat_command_nick (EmpathyChat *chat,
745                    GStrv        strv)
746 {
747         EmpathyChatPriv *priv = GET_PRIV (chat);
748         TpConnection *connection;
749         GHashTable *new_alias;
750         TpHandle handle;
751
752         connection = tp_account_get_connection (priv->account);
753         handle = tp_connection_get_self_handle (connection);
754         new_alias = g_hash_table_new (g_direct_hash, g_direct_equal);
755         g_hash_table_insert (new_alias, GUINT_TO_POINTER (handle), strv[1]);
756
757         tp_cli_connection_interface_aliasing_call_set_aliases (connection, -1,
758                 new_alias, NULL, NULL, NULL, NULL);
759
760         g_hash_table_destroy (new_alias);
761 }
762
763 static void
764 chat_command_me (EmpathyChat *chat,
765                   GStrv        strv)
766 {
767         EmpathyChatPriv *priv = GET_PRIV (chat);
768         EmpathyMessage *message;
769
770         message = empathy_message_new (strv[1]);
771         empathy_message_set_tptype (message, TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION);
772         empathy_tp_chat_send (priv->tp_chat, message);
773         g_object_unref (message);
774 }
775
776 static void
777 chat_command_say (EmpathyChat *chat,
778                   GStrv        strv)
779 {
780         EmpathyChatPriv *priv = GET_PRIV (chat);
781         EmpathyMessage *message;
782
783         message = empathy_message_new (strv[1]);
784         empathy_tp_chat_send (priv->tp_chat, message);
785         g_object_unref (message);
786 }
787
788 static void chat_command_help (EmpathyChat *chat, GStrv strv);
789
790 typedef void (*ChatCommandFunc) (EmpathyChat *chat, GStrv strv);
791
792 typedef struct {
793         const gchar *prefix;
794         guint min_parts;
795         guint max_parts;
796         ChatCommandFunc func;
797         const gchar *help;
798 } ChatCommandItem;
799
800 static ChatCommandItem commands[] = {
801         {"clear", 1, 1, chat_command_clear,
802          N_("/clear, clear all messages from the current conversation")},
803
804         {"topic", 2, 2, chat_command_topic,
805          N_("/topic <topic>, set the topic of the current conversation")},
806
807         {"join", 2, 2, chat_command_join,
808          N_("/join <chatroom id>, join a new chatroom")},
809
810         {"j", 2, 2, chat_command_join,
811          N_("/j <chatroom id>, join a new chatroom")},
812
813         {"query", 2, 3, chat_command_query,
814          N_("/query <contact id> [<message>], open a private chat")},
815
816         {"msg", 3, 3, chat_command_msg,
817          N_("/msg <contact id> <message>, open a private chat")},
818
819         {"nick", 2, 2, chat_command_nick,
820          N_("/nick <nickname>, change your nickname on current server")},
821
822         {"me", 2, 2, chat_command_me,
823          N_("/me <message>, send an ACTION message to the current conversation")},
824
825         {"say", 2, 2, chat_command_say,
826          N_("/say <message>, send <message> to the current conversation. "
827             "This is used to send a message starting with a '/'. For example: "
828             "\"/say /join is used to join a new chatroom\"")},
829
830         {"help", 1, 2, chat_command_help,
831          N_("/help [<command>], show all supported commands. "
832             "If <command> is defined, show its usage.")},
833 };
834
835 static void
836 chat_command_show_help (EmpathyChat     *chat,
837                         ChatCommandItem *item)
838 {
839         gchar *str;
840
841         str = g_strdup_printf (_("Usage: %s"), _(item->help));
842         empathy_chat_view_append_event (chat->view, str);
843         g_free (str);
844 }
845
846 static void
847 chat_command_help (EmpathyChat *chat,
848                    GStrv        strv)
849 {
850         guint i;
851
852         /* If <command> part is not defined,
853          * strv[1] will be the terminal NULL */
854         if (strv[1] == NULL) {
855                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
856                         empathy_chat_view_append_event (chat->view,
857                                 _(commands[i].help));
858                 }
859                 return;
860         }
861
862         for (i = 0; i < G_N_ELEMENTS (commands); i++) {
863                 if (g_ascii_strcasecmp (strv[1], commands[i].prefix) == 0) {
864                         chat_command_show_help (chat, &commands[i]);
865                         return;
866                 }
867         }
868
869         empathy_chat_view_append_event (chat->view,
870                 _("Unknown command"));
871 }
872
873 static GStrv
874 chat_command_parse (const gchar *text, guint max_parts)
875 {
876         GPtrArray *array;
877         gchar *item;
878
879         DEBUG ("Parse command, parts=%d text=\"%s\":", max_parts, text);
880
881         array = g_ptr_array_sized_new (max_parts + 1);
882         while (max_parts > 1) {
883                 const gchar *end;
884
885                 /* Skip white spaces */
886                 while (g_ascii_isspace (*text)) {
887                         text++;
888                 }
889
890                 /* Search the end of this part, until first space. */
891                 for (end = text; *end != '\0' && !g_ascii_isspace (*end); end++)
892                         /* Do nothing */;
893                 if (*end == '\0') {
894                         break;
895                 }
896
897                 item = g_strndup (text, end - text);
898                 g_ptr_array_add (array, item);
899                 DEBUG ("\tITEM: \"%s\"", item);
900
901                 text = end;
902                 max_parts--;
903         }
904
905         /* Append last part if not empty */
906         item = g_strstrip (g_strdup (text));
907         if (!EMP_STR_EMPTY (item)) {
908                 g_ptr_array_add (array, item);
909                 DEBUG ("\tITEM: \"%s\"", item);
910         } else {
911                 g_free (item);
912         }
913
914         /* Make the array NULL-terminated */
915         g_ptr_array_add (array, NULL);
916
917         return (GStrv) g_ptr_array_free (array, FALSE);
918 }
919
920 static gboolean
921 has_prefix_case (const gchar *s,
922                   const gchar *prefix)
923 {
924         return g_ascii_strncasecmp (s, prefix, strlen (prefix)) == 0;
925 }
926
927 static void
928 chat_send (EmpathyChat  *chat,
929            const gchar *msg)
930 {
931         EmpathyChatPriv *priv;
932         EmpathyMessage  *message;
933         guint            i;
934
935         if (EMP_STR_EMPTY (msg)) {
936                 return;
937         }
938
939         priv = GET_PRIV (chat);
940
941         chat_input_history_add (chat, msg, FALSE);
942
943         if (msg[0] == '/') {
944                 gboolean second_slash = FALSE;
945                 const gchar *iter = msg + 1;
946
947                 for (i = 0; i < G_N_ELEMENTS (commands); i++) {
948                         GStrv strv;
949                         guint strv_len;
950                         gchar c;
951
952                         if (!has_prefix_case (msg + 1, commands[i].prefix)) {
953                                 continue;
954                         }
955                         c = *(msg + 1 + strlen (commands[i].prefix));
956                         if (c != '\0' && !g_ascii_isspace (c)) {
957                                 continue;
958                         }
959
960                         /* We can't use g_strsplit here because it does
961                          * not deal correctly if we have more than one space
962                          * between args */
963                         strv = chat_command_parse (msg + 1, commands[i].max_parts);
964
965                         strv_len = g_strv_length (strv);
966                         if (strv_len < commands[i].min_parts ||
967                             strv_len > commands[i].max_parts) {
968                                 chat_command_show_help (chat, &commands[i]);
969                                 g_strfreev (strv);
970                                 return;
971                         }
972
973                         commands[i].func (chat, strv);
974                         g_strfreev (strv);
975                         return;
976                 }
977
978                 /* Also allow messages with two slashes before the
979                  * first space, so it is possible to send a /unix/path.
980                  * This heuristic is kind of crap. */
981                 while (*iter != '\0' && !g_ascii_isspace (*iter)) {
982                         if (*iter == '/') {
983                                 second_slash = TRUE;
984                                 break;
985                         }
986                         iter++;
987                 }
988
989                 if (!second_slash) {
990                         empathy_chat_view_append_event (chat->view,
991                                 _("Unknown command, see /help for the available"
992                                   " commands"));
993                         return;
994                 }
995         }
996
997         message = empathy_message_new (msg);
998         empathy_tp_chat_send (priv->tp_chat, message);
999         g_object_unref (message);
1000 }
1001
1002 static void
1003 chat_input_text_view_send (EmpathyChat *chat)
1004 {
1005         EmpathyChatPriv *priv;
1006         GtkTextBuffer  *buffer;
1007         GtkTextIter     start, end;
1008         gchar          *msg;
1009
1010         priv = GET_PRIV (chat);
1011
1012         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1013
1014         gtk_text_buffer_get_bounds (buffer, &start, &end);
1015         msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1016
1017         /* clear the input field */
1018         gtk_text_buffer_set_text (buffer, "", -1);
1019         /* delete input history modifications */
1020         chat_input_history_revert (chat);
1021
1022         chat_send (chat, msg);
1023         g_free (msg);
1024 }
1025
1026 static void
1027 chat_state_changed_cb (EmpathyTpChat      *tp_chat,
1028                        EmpathyContact     *contact,
1029                        TpChannelChatState  state,
1030                        EmpathyChat        *chat)
1031 {
1032         EmpathyChatPriv *priv;
1033         GList          *l;
1034         gboolean        was_composing;
1035
1036         priv = GET_PRIV (chat);
1037
1038         if (empathy_contact_is_user (contact)) {
1039                 /* We don't care about our own chat state */
1040                 return;
1041         }
1042
1043         was_composing = (priv->compositors != NULL);
1044
1045         /* Find the contact in the list. After that l is the list elem or NULL */
1046         for (l = priv->compositors; l; l = l->next) {
1047                 if (contact == l->data) {
1048                         break;
1049                 }
1050         }
1051
1052         switch (state) {
1053         case TP_CHANNEL_CHAT_STATE_GONE:
1054         case TP_CHANNEL_CHAT_STATE_INACTIVE:
1055         case TP_CHANNEL_CHAT_STATE_PAUSED:
1056         case TP_CHANNEL_CHAT_STATE_ACTIVE:
1057                 /* Contact is not composing */
1058                 if (l) {
1059                         priv->compositors = g_list_remove_link (priv->compositors, l);
1060                         g_object_unref (l->data);
1061                         g_list_free1 (l);
1062                 }
1063                 break;
1064         case TP_CHANNEL_CHAT_STATE_COMPOSING:
1065                 /* Contact is composing */
1066                 if (!l) {
1067                         priv->compositors = g_list_prepend (priv->compositors,
1068                                                             g_object_ref (contact));
1069                 }
1070                 break;
1071         default:
1072                 g_assert_not_reached ();
1073         }
1074
1075         DEBUG ("Was composing: %s now composing: %s",
1076                 was_composing ? "yes" : "no",
1077                 priv->compositors ? "yes" : "no");
1078
1079         if ((was_composing && !priv->compositors) ||
1080             (!was_composing && priv->compositors)) {
1081                 /* Composing state changed */
1082                 g_signal_emit (chat, signals[COMPOSING], 0,
1083                                priv->compositors != NULL);
1084         }
1085 }
1086
1087 static void
1088 chat_message_received (EmpathyChat *chat, EmpathyMessage *message)
1089 {
1090         EmpathyChatPriv *priv = GET_PRIV (chat);
1091         EmpathyContact  *sender;
1092
1093         sender = empathy_message_get_sender (message);
1094
1095         DEBUG ("Appending new message from %s (%d)",
1096                 empathy_contact_get_name (sender),
1097                 empathy_contact_get_handle (sender));
1098
1099         empathy_chat_view_append_message (chat->view, message);
1100
1101         /* We received a message so the contact is no longer composing */
1102         chat_state_changed_cb (priv->tp_chat, sender,
1103                                TP_CHANNEL_CHAT_STATE_ACTIVE,
1104                                chat);
1105
1106         priv->unread_messages++;
1107         g_signal_emit (chat, signals[NEW_MESSAGE], 0, message);
1108 }
1109
1110 static void
1111 chat_message_received_cb (EmpathyTpChat  *tp_chat,
1112                           EmpathyMessage *message,
1113                           EmpathyChat    *chat)
1114 {
1115         chat_message_received (chat, message);
1116         empathy_tp_chat_acknowledge_message (tp_chat, message);
1117 }
1118
1119 static void
1120 chat_send_error_cb (EmpathyTpChat          *tp_chat,
1121                     const gchar            *message_body,
1122                     TpChannelTextSendError  error_code,
1123                     EmpathyChat            *chat)
1124 {
1125         const gchar *error;
1126         gchar       *str;
1127
1128         switch (error_code) {
1129         case TP_CHANNEL_TEXT_SEND_ERROR_OFFLINE:
1130                 error = _("offline");
1131                 break;
1132         case TP_CHANNEL_TEXT_SEND_ERROR_INVALID_CONTACT:
1133                 error = _("invalid contact");
1134                 break;
1135         case TP_CHANNEL_TEXT_SEND_ERROR_PERMISSION_DENIED:
1136                 error = _("permission denied");
1137                 break;
1138         case TP_CHANNEL_TEXT_SEND_ERROR_TOO_LONG:
1139                 error = _("too long message");
1140                 break;
1141         case TP_CHANNEL_TEXT_SEND_ERROR_NOT_IMPLEMENTED:
1142                 error = _("not implemented");
1143                 break;
1144         default:
1145                 error = _("unknown");
1146                 break;
1147         }
1148
1149         str = g_strdup_printf (_("Error sending message '%s': %s"),
1150                                message_body,
1151                                error);
1152         empathy_chat_view_append_event (chat->view, str);
1153         g_free (str);
1154 }
1155
1156 static void
1157 chat_property_changed_cb (EmpathyTpChat *tp_chat,
1158                           const gchar   *name,
1159                           GValue        *value,
1160                           EmpathyChat   *chat)
1161 {
1162         EmpathyChatPriv *priv = GET_PRIV (chat);
1163
1164         if (!tp_strdiff (name, "subject")) {
1165                 g_free (priv->subject);
1166                 priv->subject = g_value_dup_string (value);
1167                 g_object_notify (G_OBJECT (chat), "subject");
1168
1169                 if (EMP_STR_EMPTY (priv->subject)) {
1170                         gtk_widget_hide (priv->hbox_topic);
1171                 } else {
1172                         gtk_label_set_text (GTK_LABEL (priv->label_topic), priv->subject);
1173                         gtk_widget_show (priv->hbox_topic);
1174                 }
1175                 if (priv->block_events_timeout_id == 0) {
1176                         gchar *str;
1177
1178                         if (!EMP_STR_EMPTY (priv->subject)) {
1179                                 str = g_strdup_printf (_("Topic set to: %s"), priv->subject);
1180                         } else {
1181                                 str = g_strdup (_("No topic defined"));
1182                         }
1183                         empathy_chat_view_append_event (EMPATHY_CHAT (chat)->view, str);
1184                         g_free (str);
1185                 }
1186         }
1187         else if (!tp_strdiff (name, "name")) {
1188                 g_free (priv->name);
1189                 priv->name = g_value_dup_string (value);
1190                 g_object_notify (G_OBJECT (chat), "name");
1191         }
1192 }
1193
1194 static void
1195 chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
1196                                    EmpathyChat    *chat)
1197 {
1198         EmpathyChatPriv *priv;
1199         GtkTextIter     start, end;
1200         gchar          *str;
1201         gboolean        spell_checker = FALSE;
1202
1203         priv = GET_PRIV (chat);
1204
1205         if (gtk_text_buffer_get_char_count (buffer) == 0) {
1206                 chat_composing_stop (chat);
1207         } else {
1208                 chat_composing_start (chat);
1209         }
1210
1211         empathy_conf_get_bool (empathy_conf_get (),
1212                            EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
1213                            &spell_checker);
1214
1215         gtk_text_buffer_get_start_iter (buffer, &start);
1216
1217         if (!spell_checker) {
1218                 gtk_text_buffer_get_end_iter (buffer, &end);
1219                 gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1220                 return;
1221         }
1222
1223         if (!empathy_spell_supported ()) {
1224                 return;
1225         }
1226
1227         /* NOTE: this is really inefficient, we shouldn't have to
1228            reiterate the whole buffer each time and check each work
1229            every time. */
1230         while (TRUE) {
1231                 gboolean correct = FALSE;
1232
1233                 /* if at start */
1234                 if (gtk_text_iter_is_start (&start)) {
1235                         end = start;
1236
1237                         if (!gtk_text_iter_forward_word_end (&end)) {
1238                                 /* no whole word yet */
1239                                 break;
1240                         }
1241                 } else {
1242                         if (!gtk_text_iter_forward_word_end (&end)) {
1243                                 /* must be the end of the buffer */
1244                                 break;
1245                         }
1246
1247                         start = end;
1248                         gtk_text_iter_backward_word_start (&start);
1249                 }
1250
1251                 str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1252
1253                 /* spell check string if not a command */
1254                 if (str[0] != '/') {
1255                         correct = empathy_spell_check (str);
1256                 } else {
1257                         correct = TRUE;
1258                 }
1259
1260                 if (!correct) {
1261                         gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
1262                 } else {
1263                         gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
1264                 }
1265
1266                 g_free (str);
1267
1268                 /* set start iter to the end iters position */
1269                 start = end;
1270         }
1271 }
1272
1273 static gboolean
1274 chat_input_key_press_event_cb (GtkWidget   *widget,
1275                                GdkEventKey *event,
1276                                EmpathyChat *chat)
1277 {
1278         EmpathyChatPriv *priv;
1279         GtkAdjustment  *adj;
1280         gdouble         val;
1281         GtkWidget      *text_view_sw;
1282
1283         priv = GET_PRIV (chat);
1284
1285         /* Catch ctrl+up/down so we can traverse messages we sent */
1286         if ((event->state & GDK_CONTROL_MASK) &&
1287             (event->keyval == GDK_Up ||
1288              event->keyval == GDK_Down)) {
1289                 GtkTextBuffer *buffer;
1290                 const gchar   *str;
1291
1292                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1293                 chat_input_history_update (chat, buffer);
1294
1295                 if (event->keyval == GDK_Up) {
1296                         str = chat_input_history_get_next (chat);
1297                 } else {
1298                         str = chat_input_history_get_prev (chat);
1299                 }
1300
1301                 g_signal_handlers_block_by_func (buffer,
1302                                                  chat_input_text_buffer_changed_cb,
1303                                                  chat);
1304                 gtk_text_buffer_set_text (buffer, str ? str : "", -1);
1305                 g_signal_handlers_unblock_by_func (buffer,
1306                                                    chat_input_text_buffer_changed_cb,
1307                                                    chat);
1308
1309                 return TRUE;
1310         }
1311
1312         /* Catch enter but not ctrl/shift-enter */
1313         if (IS_ENTER (event->keyval) &&
1314             !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1315                 GtkTextView *view;
1316
1317                 /* This is to make sure that kinput2 gets the enter. And if
1318                  * it's handled there we shouldn't send on it. This is because
1319                  * kinput2 uses Enter to commit letters. See:
1320                  * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
1321                  */
1322
1323                 view = GTK_TEXT_VIEW (chat->input_text_view);
1324                 if (gtk_im_context_filter_keypress (view->im_context, event)) {
1325                         GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
1326                         return TRUE;
1327                 }
1328
1329                 chat_input_text_view_send (chat);
1330                 return TRUE;
1331         }
1332
1333         text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
1334
1335         if (IS_ENTER (event->keyval) &&
1336             (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
1337                 /* Newline for shift/control-enter. */
1338                 return FALSE;
1339         }
1340         if (!(event->state & GDK_CONTROL_MASK) &&
1341             event->keyval == GDK_Page_Up) {
1342                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1343                 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) - gtk_adjustment_get_page_size (adj));
1344                 return TRUE;
1345         }
1346         if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
1347             event->keyval == GDK_Page_Down) {
1348                 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
1349                 val = MIN (gtk_adjustment_get_value (adj) + gtk_adjustment_get_page_size (adj),
1350                            gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
1351                 gtk_adjustment_set_value (adj, val);
1352                 return TRUE;
1353         }
1354         if (!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) &&
1355             event->keyval == GDK_Tab) {
1356                 GtkTextBuffer *buffer;
1357                 GtkTextIter    start, current;
1358                 gchar         *nick, *completed;
1359                 GList         *list, *completed_list;
1360                 gboolean       is_start_of_buffer;
1361
1362                 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (EMPATHY_CHAT (chat)->input_text_view));
1363                 gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
1364
1365                 /* Get the start of the nick to complete. */
1366                 gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
1367                 gtk_text_iter_backward_word_start (&start);
1368                 is_start_of_buffer = gtk_text_iter_is_start (&start);
1369
1370                 list = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (priv->tp_chat));
1371                 g_completion_add_items (priv->completion, list);
1372
1373                 nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
1374                 completed_list = g_completion_complete (priv->completion,
1375                                                         nick,
1376                                                         &completed);
1377
1378                 g_free (nick);
1379
1380                 if (completed) {
1381                         guint        len;
1382                         const gchar *text;
1383                         gchar       *complete_char = NULL;
1384
1385                         gtk_text_buffer_delete (buffer, &start, &current);
1386
1387                         len = g_list_length (completed_list);
1388
1389                         if (len == 1) {
1390                                 /* If we only have one hit, use that text
1391                                  * instead of the text in completed since the
1392                                  * completed text will use the typed string
1393                                  * which might be cased all wrong.
1394                                  * Fixes #120876
1395                                  * */
1396                                 text = empathy_contact_get_name (completed_list->data);
1397                         } else {
1398                                 text = completed;
1399                         }
1400
1401                         gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
1402
1403                         if (len == 1 && is_start_of_buffer &&
1404                             empathy_conf_get_string (empathy_conf_get (),
1405                                                      EMPATHY_PREFS_CHAT_NICK_COMPLETION_CHAR,
1406                                                      &complete_char) &&
1407                             complete_char != NULL) {
1408                                 gtk_text_buffer_insert_at_cursor (buffer,
1409                                                                   complete_char,
1410                                                                   strlen (complete_char));
1411                                 gtk_text_buffer_insert_at_cursor (buffer, " ", 1);
1412                                 g_free (complete_char);
1413                         }
1414
1415                         g_free (completed);
1416                 }
1417
1418                 g_completion_clear_items (priv->completion);
1419
1420                 g_list_foreach (list, (GFunc) g_object_unref, NULL);
1421                 g_list_free (list);
1422
1423                 return TRUE;
1424         }
1425
1426         return FALSE;
1427 }
1428
1429 static gboolean
1430 chat_text_view_focus_in_event_cb (GtkWidget  *widget,
1431                                   GdkEvent   *event,
1432                                   EmpathyChat *chat)
1433 {
1434         gtk_widget_grab_focus (chat->input_text_view);
1435
1436         return TRUE;
1437 }
1438
1439 static gboolean
1440 chat_input_set_size_request_idle (gpointer sw)
1441 {
1442         gtk_widget_set_size_request (sw, -1, MAX_INPUT_HEIGHT);
1443
1444         return FALSE;
1445 }
1446
1447 static void
1448 chat_input_size_request_cb (GtkWidget      *widget,
1449                             GtkRequisition *requisition,
1450                             EmpathyChat    *chat)
1451 {
1452         EmpathyChatPriv *priv = GET_PRIV (chat);
1453         GtkWidget       *sw;
1454
1455         sw = gtk_widget_get_parent (widget);
1456         if (requisition->height >= MAX_INPUT_HEIGHT && !priv->has_input_vscroll) {
1457                 g_idle_add (chat_input_set_size_request_idle, sw);
1458                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1459                                                 GTK_POLICY_NEVER,
1460                                                 GTK_POLICY_ALWAYS);
1461                 priv->has_input_vscroll = TRUE;
1462         }
1463
1464         if (requisition->height < MAX_INPUT_HEIGHT && priv->has_input_vscroll) {
1465                 gtk_widget_set_size_request (sw, -1, -1);
1466                 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
1467                                                 GTK_POLICY_NEVER,
1468                                                 GTK_POLICY_NEVER);
1469                 priv->has_input_vscroll = FALSE;
1470         }
1471 }
1472
1473 static void
1474 chat_input_realize_cb (GtkWidget   *widget,
1475                        EmpathyChat *chat)
1476 {
1477         DEBUG ("Setting focus to the input text view");
1478         if (gtk_widget_is_sensitive (widget)) {
1479                 gtk_widget_grab_focus (widget);
1480         }
1481 }
1482
1483 static void
1484 chat_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1485                                 EmpathySmiley        *smiley,
1486                                 gpointer              user_data)
1487 {
1488         EmpathyChat   *chat = EMPATHY_CHAT (user_data);
1489         GtkTextBuffer *buffer;
1490         GtkTextIter    iter;
1491
1492         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1493
1494         gtk_text_buffer_get_end_iter (buffer, &iter);
1495         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1496
1497         gtk_text_buffer_get_end_iter (buffer, &iter);
1498         gtk_text_buffer_insert (buffer, &iter, " ", -1);
1499 }
1500
1501 typedef struct {
1502         EmpathyChat  *chat;
1503         gchar       *word;
1504
1505         GtkTextIter  start;
1506         GtkTextIter  end;
1507 } EmpathyChatSpell;
1508
1509 static EmpathyChatSpell *
1510 chat_spell_new (EmpathyChat  *chat,
1511                 const gchar *word,
1512                 GtkTextIter  start,
1513                 GtkTextIter  end)
1514 {
1515         EmpathyChatSpell *chat_spell;
1516
1517         chat_spell = g_slice_new0 (EmpathyChatSpell);
1518
1519         chat_spell->chat = g_object_ref (chat);
1520         chat_spell->word = g_strdup (word);
1521         chat_spell->start = start;
1522         chat_spell->end = end;
1523
1524         return chat_spell;
1525 }
1526
1527 static void
1528 chat_spell_free (EmpathyChatSpell *chat_spell)
1529 {
1530         g_object_unref (chat_spell->chat);
1531         g_free (chat_spell->word);
1532         g_slice_free (EmpathyChatSpell, chat_spell);
1533 }
1534
1535 static void
1536 chat_spelling_menu_activate_cb (GtkMenuItem     *menu_item,
1537                                                 EmpathyChatSpell *chat_spell)
1538 {
1539     empathy_chat_correct_word (chat_spell->chat,
1540                                &(chat_spell->start),
1541                                &(chat_spell->end),
1542                                gtk_menu_item_get_label (menu_item));
1543 }
1544
1545 static GtkWidget *
1546 chat_spelling_build_menu (EmpathyChatSpell *chat_spell)
1547 {
1548     GtkWidget *menu, *menu_item;
1549     GList     *suggestions, *l;
1550
1551     menu = gtk_menu_new ();
1552     suggestions = empathy_spell_get_suggestions (chat_spell->word);
1553     if (suggestions == NULL) {
1554         menu_item = gtk_menu_item_new_with_label (_("(No Suggestions)"));
1555         gtk_widget_set_sensitive (menu_item, FALSE);
1556         gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1557     } else {
1558         for (l = suggestions; l; l = l->next) {
1559             menu_item = gtk_menu_item_new_with_label (l->data);
1560             g_signal_connect (G_OBJECT (menu_item),
1561                           "activate",
1562                           G_CALLBACK (chat_spelling_menu_activate_cb),
1563                           chat_spell);
1564             gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
1565         }
1566     }
1567     empathy_spell_free_suggestions (suggestions);
1568
1569     gtk_widget_show_all (menu);
1570
1571     return menu;
1572 }
1573
1574 static void
1575 chat_text_send_cb (GtkMenuItem *menuitem,
1576                    EmpathyChat *chat)
1577 {
1578         chat_input_text_view_send (chat);
1579 }
1580
1581 static void
1582 chat_input_populate_popup_cb (GtkTextView *view,
1583                               GtkMenu     *menu,
1584                               EmpathyChat *chat)
1585 {
1586         EmpathyChatPriv      *priv;
1587         GtkTextBuffer        *buffer;
1588         GtkTextTagTable      *table;
1589         GtkTextTag           *tag;
1590         gint                  x, y;
1591         GtkTextIter           iter, start, end;
1592         GtkWidget            *item;
1593         gchar                *str = NULL;
1594         EmpathyChatSpell     *chat_spell;
1595         GtkWidget            *spell_menu;
1596         EmpathySmileyManager *smiley_manager;
1597         GtkWidget            *smiley_menu;
1598         GtkWidget            *image;
1599
1600         priv = GET_PRIV (chat);
1601         buffer = gtk_text_view_get_buffer (view);
1602
1603         /* Add the emoticon menu. */
1604         item = gtk_separator_menu_item_new ();
1605         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1606         gtk_widget_show (item);
1607
1608         item = gtk_image_menu_item_new_with_mnemonic (_("Insert Smiley"));
1609         image = gtk_image_new_from_icon_name ("face-smile",
1610                                               GTK_ICON_SIZE_MENU);
1611         gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1612         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1613         gtk_widget_show (item);
1614
1615         smiley_manager = empathy_smiley_manager_dup_singleton ();
1616         smiley_menu = empathy_smiley_menu_new (smiley_manager,
1617                                                chat_insert_smiley_activate_cb,
1618                                                chat);
1619         gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
1620         g_object_unref (smiley_manager);
1621
1622         /* Add the Send menu item. */
1623         gtk_text_buffer_get_bounds (buffer, &start, &end);
1624         str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1625         if (!EMP_STR_EMPTY (str)) {
1626                 item = gtk_menu_item_new_with_mnemonic (_("_Send"));
1627                 g_signal_connect (G_OBJECT (item), "activate",
1628                                   G_CALLBACK (chat_text_send_cb), chat);
1629                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1630                 gtk_widget_show (item);
1631         }
1632         str = NULL;
1633
1634         /* Add the spell check menu item. */
1635         table = gtk_text_buffer_get_tag_table (buffer);
1636         tag = gtk_text_tag_table_lookup (table, "misspelled");
1637         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
1638         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
1639                                                GTK_TEXT_WINDOW_WIDGET,
1640                                                x, y,
1641                                                &x, &y);
1642         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
1643         start = end = iter;
1644         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
1645             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
1646
1647                 str = gtk_text_buffer_get_text (buffer,
1648                                                 &start, &end, FALSE);
1649         }
1650         if (!EMP_STR_EMPTY (str)) {
1651                 chat_spell = chat_spell_new (chat, str, start, end);
1652                 g_object_set_data_full (G_OBJECT (menu),
1653                                         "chat_spell", chat_spell,
1654                                         (GDestroyNotify) chat_spell_free);
1655
1656                 item = gtk_separator_menu_item_new ();
1657                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1658                 gtk_widget_show (item);
1659
1660                 item = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions"));
1661                 image = gtk_image_new_from_icon_name (GTK_STOCK_SPELL_CHECK,
1662                                                       GTK_ICON_SIZE_MENU);
1663                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1664
1665                 spell_menu = chat_spelling_build_menu (chat_spell);
1666                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), spell_menu);
1667
1668                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
1669                 gtk_widget_show (item);
1670         }
1671 }
1672
1673 static gboolean
1674 chat_log_filter (EmpathyMessage *message,
1675                  gpointer user_data)
1676 {
1677         EmpathyChat *chat = (EmpathyChat *) user_data;
1678         EmpathyChatPriv *priv = GET_PRIV (chat);
1679         const GList *pending;
1680
1681         pending = empathy_tp_chat_get_pending_messages (priv->tp_chat);
1682
1683         for (; pending; pending = g_list_next (pending)) {
1684                 if (empathy_message_equal (message, pending->data)) {
1685                         return FALSE;
1686                 }
1687         }
1688
1689         return TRUE;
1690 }
1691
1692 static void
1693 chat_add_logs (EmpathyChat *chat)
1694 {
1695         EmpathyChatPriv *priv = GET_PRIV (chat);
1696         gboolean         is_chatroom;
1697         GList           *messages, *l;
1698
1699         if (!priv->id) {
1700                 return;
1701         }
1702
1703         /* Turn off scrolling temporarily */
1704         empathy_chat_view_scroll (chat->view, FALSE);
1705
1706         /* Add messages from last conversation */
1707         is_chatroom = priv->handle_type == TP_HANDLE_TYPE_ROOM;
1708
1709         messages = empathy_log_manager_get_filtered_messages (priv->log_manager,
1710                                                               priv->account,
1711                                                               priv->id,
1712                                                               is_chatroom,
1713                                                               5,
1714                                                               chat_log_filter,
1715                                                               chat);
1716
1717         for (l = messages; l; l = g_list_next (l)) {
1718                 empathy_chat_view_append_message (chat->view, l->data);
1719                 g_object_unref (l->data);
1720         }
1721
1722         g_list_free (messages);
1723
1724         /* Turn back on scrolling */
1725         empathy_chat_view_scroll (chat->view, TRUE);
1726 }
1727
1728 static gint
1729 chat_contacts_completion_func (const gchar *s1,
1730                                const gchar *s2,
1731                                gsize        n)
1732 {
1733         gchar *tmp, *nick1, *nick2;
1734         gint   ret;
1735
1736         if (s1 == s2) {
1737                 return 0;
1738         }
1739         if (!s1 || !s2) {
1740                 return s1 ? -1 : +1;
1741         }
1742
1743         tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
1744         nick1 = g_utf8_casefold (tmp, -1);
1745         g_free (tmp);
1746
1747         tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
1748         nick2 = g_utf8_casefold (tmp, -1);
1749         g_free (tmp);
1750
1751         ret = strncmp (nick1, nick2, n);
1752
1753         g_free (nick1);
1754         g_free (nick2);
1755
1756         return ret;
1757 }
1758
1759 static gchar *
1760 build_part_message (guint           reason,
1761                     const gchar    *name,
1762                     EmpathyContact *actor,
1763                     const gchar    *message)
1764 {
1765         GString *s = g_string_new ("");
1766         const gchar *actor_name = NULL;
1767
1768         if (actor != NULL) {
1769                 actor_name = empathy_contact_get_name (actor);
1770         }
1771
1772         /* Having an actor only really makes sense for a few actions... */
1773         switch (reason) {
1774         case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE:
1775                 g_string_append_printf (s, _("%s has disconnected"), name);
1776                 break;
1777         case TP_CHANNEL_GROUP_CHANGE_REASON_KICKED:
1778                 if (actor_name != NULL) {
1779                         /* translators: reverse the order of these arguments
1780                          * if the kicked should come before the kicker in your locale.
1781                          */
1782                         g_string_append_printf (s, _("%1$s was kicked by %2$s"),
1783                                 name, actor_name);
1784                 } else {
1785                         g_string_append_printf (s, _("%s was kicked"), name);
1786                 }
1787                 break;
1788         case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED:
1789                 if (actor_name != NULL) {
1790                         /* translators: reverse the order of these arguments
1791                          * if the banned should come before the banner in your locale.
1792                          */
1793                         g_string_append_printf (s, _("%1$s was banned by %2$s"),
1794                                 name, actor_name);
1795                 } else {
1796                         g_string_append_printf (s, _("%s was banned"), name);
1797                 }
1798                 break;
1799         default:
1800                 g_string_append_printf (s, _("%s has left the room"), name);
1801         }
1802
1803         if (!EMP_STR_EMPTY (message)) {
1804                 /* Note to translators: this string is appended to
1805                  * notifications like "foo has left the room", with the message
1806                  * given by the user living the room. If this poses a problem,
1807                  * please let us know. :-)
1808                  */
1809                 g_string_append_printf (s, _(" (%s)"), message);
1810         }
1811
1812         return g_string_free (s, FALSE);
1813 }
1814
1815 static void
1816 chat_members_changed_cb (EmpathyTpChat  *tp_chat,
1817                          EmpathyContact *contact,
1818                          EmpathyContact *actor,
1819                          guint           reason,
1820                          gchar          *message,
1821                          gboolean        is_member,
1822                          EmpathyChat    *chat)
1823 {
1824         EmpathyChatPriv *priv = GET_PRIV (chat);
1825         const gchar *name = empathy_contact_get_name (contact);
1826         gchar *str;
1827
1828         g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED != reason);
1829
1830         if (priv->block_events_timeout_id != 0)
1831                 return;
1832
1833         if (is_member) {
1834                 str = g_strdup_printf (_("%s has joined the room"),
1835                                        name);
1836         } else {
1837                 str = build_part_message (reason, name, actor, message);
1838         }
1839
1840         empathy_chat_view_append_event (chat->view, str);
1841         g_free (str);
1842 }
1843
1844 static void
1845 chat_member_renamed_cb (EmpathyTpChat  *tp_chat,
1846                          EmpathyContact *old_contact,
1847                          EmpathyContact *new_contact,
1848                          guint           reason,
1849                          gchar          *message,
1850                          EmpathyChat    *chat)
1851 {
1852         EmpathyChatPriv *priv = GET_PRIV (chat);
1853
1854         g_return_if_fail (TP_CHANNEL_GROUP_CHANGE_REASON_RENAMED == reason);
1855
1856         if (priv->block_events_timeout_id == 0) {
1857                 gchar *str;
1858
1859                 str = g_strdup_printf (_("%s is now known as %s"),
1860                                        empathy_contact_get_name (old_contact),
1861                                        empathy_contact_get_name (new_contact));
1862                 empathy_chat_view_append_event (chat->view, str);
1863                 g_free (str);
1864         }
1865
1866 }
1867
1868 static gboolean
1869 chat_reset_size_request (gpointer widget)
1870 {
1871         gtk_widget_set_size_request (widget, -1, -1);
1872
1873         return FALSE;
1874 }
1875
1876 static void
1877 chat_update_contacts_visibility (EmpathyChat *chat,
1878                          gboolean show)
1879 {
1880         EmpathyChatPriv *priv = GET_PRIV (chat);
1881         GtkAllocation allocation;
1882
1883         if (!priv->scrolled_window_contacts) {
1884                 return;
1885         }
1886
1887         if (priv->remote_contact != NULL) {
1888                 show = FALSE;
1889         }
1890
1891         if (show && priv->contact_list_view == NULL) {
1892                 EmpathyContactListStore *store;
1893                 gint                     min_width;
1894
1895                 /* We are adding the contact list to the chat, we don't want the
1896                  * chat view to become too small. If the chat view is already
1897                  * smaller than 250 make sure that size won't change. If the
1898                  * chat view is bigger the contact list will take some space on
1899                  * it but we make sure the chat view don't become smaller than
1900                  * 250. Relax the size request once the resize is done */
1901                 gtk_widget_get_allocation (priv->vbox_left, &allocation);
1902                 min_width = MIN (allocation.width, 250);
1903                 gtk_widget_set_size_request (priv->vbox_left, min_width, -1);
1904                 g_idle_add (chat_reset_size_request, priv->vbox_left);
1905
1906                 if (priv->contacts_width > 0) {
1907                         gtk_paned_set_position (GTK_PANED (priv->hpaned),
1908                                                 priv->contacts_width);
1909                 }
1910
1911                 store = empathy_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
1912                 priv->contact_list_view = GTK_WIDGET (empathy_contact_list_view_new (store,
1913                         EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP,
1914                         EMPATHY_CONTACT_FEATURE_CHAT |
1915                         EMPATHY_CONTACT_FEATURE_CALL |
1916                         EMPATHY_CONTACT_FEATURE_LOG |
1917                         EMPATHY_CONTACT_FEATURE_INFO));
1918                 gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
1919                                    priv->contact_list_view);
1920                 gtk_widget_show (priv->contact_list_view);
1921                 gtk_widget_show (priv->scrolled_window_contacts);
1922                 g_object_unref (store);
1923         } else if (!show) {
1924                 priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
1925                 gtk_widget_hide (priv->scrolled_window_contacts);
1926                 if (priv->contact_list_view != NULL) {
1927                         gtk_widget_destroy (priv->contact_list_view);
1928                         priv->contact_list_view = NULL;
1929                 }
1930         }
1931 }
1932
1933 void
1934 empathy_chat_set_show_contacts (EmpathyChat *chat,
1935                                 gboolean     show)
1936 {
1937         EmpathyChatPriv *priv = GET_PRIV (chat);
1938
1939         priv->show_contacts = show;
1940
1941         chat_update_contacts_visibility (chat, show);
1942
1943         g_object_notify (G_OBJECT (chat), "show-contacts");
1944 }
1945
1946 static void
1947 chat_remote_contact_changed_cb (EmpathyChat *chat)
1948 {
1949         EmpathyChatPriv *priv = GET_PRIV (chat);
1950
1951         if (priv->remote_contact != NULL) {
1952                 g_object_unref (priv->remote_contact);
1953                 priv->remote_contact = NULL;
1954         }
1955
1956         g_free (priv->id);
1957
1958         priv->id = g_strdup (empathy_tp_chat_get_id (priv->tp_chat));
1959         priv->remote_contact = empathy_tp_chat_get_remote_contact (priv->tp_chat);
1960         if (priv->remote_contact != NULL) {
1961                 g_object_ref (priv->remote_contact);
1962                 priv->handle_type = TP_HANDLE_TYPE_CONTACT;
1963         }
1964         else if (priv->tp_chat != NULL) {
1965                 TpChannel *channel;
1966
1967                 channel = empathy_tp_chat_get_channel (priv->tp_chat);
1968                 g_object_get (channel, "handle-type", &priv->handle_type, NULL);
1969         }
1970
1971         chat_update_contacts_visibility (chat, priv->show_contacts);
1972
1973         g_object_notify (G_OBJECT (chat), "remote-contact");
1974         g_object_notify (G_OBJECT (chat), "id");
1975 }
1976
1977 static void
1978 chat_destroy_cb (EmpathyTpChat *tp_chat,
1979                  EmpathyChat   *chat)
1980 {
1981         EmpathyChatPriv *priv;
1982
1983         priv = GET_PRIV (chat);
1984
1985         if (!priv->tp_chat) {
1986                 return;
1987         }
1988
1989         chat_composing_remove_timeout (chat);
1990         g_object_unref (priv->tp_chat);
1991         priv->tp_chat = NULL;
1992         g_object_notify (G_OBJECT (chat), "tp-chat");
1993
1994         empathy_chat_view_append_event (chat->view, _("Disconnected"));
1995         gtk_widget_set_sensitive (chat->input_text_view, FALSE);
1996
1997         chat_update_contacts_visibility (chat, FALSE);
1998 }
1999
2000 static gboolean
2001 chat_hpaned_pos_changed_cb (GtkWidget* hpaned, gpointer user_data)
2002 {
2003         gint hpaned_pos;
2004         hpaned_pos = gtk_paned_get_position (GTK_PANED(hpaned));
2005         empathy_conf_set_int (empathy_conf_get (),
2006                               EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
2007                               hpaned_pos);
2008         return TRUE;
2009 }
2010
2011
2012 static void
2013 show_pending_messages (EmpathyChat *chat) {
2014         EmpathyChatPriv *priv = GET_PRIV (chat);
2015         const GList *messages, *l;
2016
2017         if (chat->view == NULL || priv->tp_chat == NULL)
2018                 return;
2019
2020         if (!priv->can_show_pending)
2021                 return;
2022
2023         messages = empathy_tp_chat_get_pending_messages (priv->tp_chat);
2024
2025         for (l = messages; l != NULL ; l = g_list_next (l)) {
2026                 EmpathyMessage *message = EMPATHY_MESSAGE (l->data);
2027                 chat_message_received (chat, message);
2028         }
2029         empathy_tp_chat_acknowledge_messages (priv->tp_chat, messages);
2030 }
2031
2032 static void
2033 chat_create_ui (EmpathyChat *chat)
2034 {
2035         EmpathyChatPriv *priv = GET_PRIV (chat);
2036         GtkBuilder      *gui;
2037         GList           *list = NULL;
2038         gchar           *filename;
2039         GtkTextBuffer   *buffer;
2040         gint              paned_pos;
2041
2042         filename = empathy_file_lookup ("empathy-chat.ui",
2043                                         "libempathy-gtk");
2044         gui = empathy_builder_get_file (filename,
2045                                         "chat_widget", &priv->widget,
2046                                         "hpaned", &priv->hpaned,
2047                                         "vbox_left", &priv->vbox_left,
2048                                         "scrolled_window_chat", &priv->scrolled_window_chat,
2049                                         "scrolled_window_input", &priv->scrolled_window_input,
2050                                         "hbox_topic", &priv->hbox_topic,
2051                                         "label_topic", &priv->label_topic,
2052                                         "scrolled_window_contacts", &priv->scrolled_window_contacts,
2053                                         "info_bar_vbox", &priv->info_bar_vbox,
2054                                         NULL);
2055         g_free (filename);
2056
2057         /* Add message view. */
2058         chat->view = empathy_theme_manager_create_view (empathy_theme_manager_get ());
2059         /* If this is a GtkTextView, it's set as a drag destination for text/plain
2060            and other types, even though it's non-editable and doesn't accept any
2061            drags.  This steals drag motion for anything inside the scrollbars,
2062            making drag destinations on chat windows far less useful.
2063          */
2064         gtk_drag_dest_unset (GTK_WIDGET (chat->view));
2065         g_signal_connect (chat->view, "focus_in_event",
2066                           G_CALLBACK (chat_text_view_focus_in_event_cb),
2067                           chat);
2068         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
2069                            GTK_WIDGET (chat->view));
2070         gtk_widget_show (GTK_WIDGET (chat->view));
2071
2072         /* Add input GtkTextView */
2073         chat->input_text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
2074                                               "pixels-above-lines", 2,
2075                                               "pixels-below-lines", 2,
2076                                               "pixels-inside-wrap", 1,
2077                                               "right-margin", 2,
2078                                               "left-margin", 2,
2079                                               "wrap-mode", GTK_WRAP_WORD_CHAR,
2080                                               NULL);
2081         g_signal_connect (chat->input_text_view, "key-press-event",
2082                           G_CALLBACK (chat_input_key_press_event_cb),
2083                           chat);
2084         g_signal_connect (chat->input_text_view, "size-request",
2085                           G_CALLBACK (chat_input_size_request_cb),
2086                           chat);
2087         g_signal_connect (chat->input_text_view, "realize",
2088                           G_CALLBACK (chat_input_realize_cb),
2089                           chat);
2090         g_signal_connect (chat->input_text_view, "populate-popup",
2091                           G_CALLBACK (chat_input_populate_popup_cb),
2092                           chat);
2093         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2094         g_signal_connect (buffer, "changed",
2095                           G_CALLBACK (chat_input_text_buffer_changed_cb),
2096                           chat);
2097         gtk_text_buffer_create_tag (buffer, "misspelled",
2098                                     "underline", PANGO_UNDERLINE_ERROR,
2099                                     NULL);
2100         gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
2101                            chat->input_text_view);
2102         gtk_widget_show (chat->input_text_view);
2103
2104         /* Initialy hide the topic, will be shown if not empty */
2105         gtk_widget_hide (priv->hbox_topic);
2106
2107         g_signal_connect (priv->hpaned, "notify::position",
2108                           G_CALLBACK (chat_hpaned_pos_changed_cb),
2109                           NULL);
2110
2111         /* Load the paned position */
2112         if (empathy_conf_get_int (empathy_conf_get (),
2113                                  EMPATHY_PREFS_UI_CHAT_WINDOW_PANED_POS,
2114                                  &paned_pos)
2115                 && paned_pos)
2116                 gtk_paned_set_position (GTK_PANED(priv->hpaned), paned_pos);
2117
2118         /* Set widget focus order */
2119         list = g_list_append (NULL, priv->scrolled_window_input);
2120         gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
2121         g_list_free (list);
2122
2123         list = g_list_append (NULL, priv->vbox_left);
2124         list = g_list_append (list, priv->scrolled_window_contacts);
2125         gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
2126         g_list_free (list);
2127
2128         list = g_list_append (NULL, priv->hpaned);
2129         list = g_list_append (list, priv->hbox_topic);
2130         gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
2131         g_list_free (list);
2132
2133         /* Add the main widget in the chat widget */
2134         gtk_container_add (GTK_CONTAINER (chat), priv->widget);
2135         g_object_unref (gui);
2136 }
2137
2138 static void
2139 chat_size_request (GtkWidget      *widget,
2140                    GtkRequisition *requisition)
2141 {
2142   GtkBin *bin = GTK_BIN (widget);
2143   GtkWidget *child;
2144
2145   requisition->width = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2146   requisition->height = gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2;
2147
2148   child = gtk_bin_get_child (bin);
2149
2150   if (child && gtk_widget_get_visible (child))
2151     {
2152       GtkRequisition child_requisition;
2153
2154       gtk_widget_size_request (child, &child_requisition);
2155
2156       requisition->width += child_requisition.width;
2157       requisition->height += child_requisition.height;
2158     }
2159 }
2160
2161 static void
2162 chat_size_allocate (GtkWidget     *widget,
2163                     GtkAllocation *allocation)
2164 {
2165   GtkBin *bin = GTK_BIN (widget);
2166   GtkAllocation child_allocation;
2167   GtkWidget *child;
2168
2169   gtk_widget_set_allocation (widget, allocation);
2170
2171   child = gtk_bin_get_child (bin);
2172
2173   if (child && gtk_widget_get_visible (child))
2174     {
2175       child_allocation.x = allocation->x + gtk_container_get_border_width (GTK_CONTAINER (widget));
2176       child_allocation.y = allocation->y + gtk_container_get_border_width (GTK_CONTAINER (widget));
2177       child_allocation.width = MAX (allocation->width - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2178       child_allocation.height = MAX (allocation->height - gtk_container_get_border_width (GTK_CONTAINER (widget)) * 2, 0);
2179
2180       gtk_widget_size_allocate (child, &child_allocation);
2181     }
2182 }
2183
2184 static void
2185 chat_finalize (GObject *object)
2186 {
2187         EmpathyChat     *chat;
2188         EmpathyChatPriv *priv;
2189
2190         chat = EMPATHY_CHAT (object);
2191         priv = GET_PRIV (chat);
2192
2193         DEBUG ("Finalized: %p", object);
2194
2195         g_list_foreach (priv->input_history, (GFunc) chat_input_history_entry_free, NULL);
2196         g_list_free (priv->input_history);
2197
2198         g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
2199         g_list_free (priv->compositors);
2200
2201         chat_composing_remove_timeout (chat);
2202
2203         g_object_unref (priv->account_manager);
2204         g_object_unref (priv->log_manager);
2205
2206         if (priv->tp_chat) {
2207                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2208                         chat_destroy_cb, chat);
2209                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2210                         chat_message_received_cb, chat);
2211                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2212                         chat_send_error_cb, chat);
2213                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2214                         chat_state_changed_cb, chat);
2215                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2216                         chat_property_changed_cb, chat);
2217                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2218                         chat_members_changed_cb, chat);
2219                 g_signal_handlers_disconnect_by_func (priv->tp_chat,
2220                         chat_remote_contact_changed_cb, chat);
2221                 empathy_tp_chat_close (priv->tp_chat);
2222                 g_object_unref (priv->tp_chat);
2223         }
2224         if (priv->account) {
2225                 g_object_unref (priv->account);
2226         }
2227         if (priv->remote_contact) {
2228                 g_object_unref (priv->remote_contact);
2229         }
2230
2231         if (priv->block_events_timeout_id) {
2232                 g_source_remove (priv->block_events_timeout_id);
2233         }
2234
2235         g_free (priv->id);
2236         g_free (priv->name);
2237         g_free (priv->subject);
2238         g_completion_free (priv->completion);
2239
2240         G_OBJECT_CLASS (empathy_chat_parent_class)->finalize (object);
2241 }
2242
2243 static void
2244 chat_constructed (GObject *object)
2245 {
2246         EmpathyChat *chat = EMPATHY_CHAT (object);
2247         EmpathyChatPriv *priv = GET_PRIV (chat);
2248
2249         if (priv->handle_type != TP_HANDLE_TYPE_ROOM)
2250                 chat_add_logs (chat);
2251         priv->can_show_pending = TRUE;
2252         show_pending_messages (chat);
2253 }
2254
2255 static void
2256 empathy_chat_class_init (EmpathyChatClass *klass)
2257 {
2258         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2259         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
2260
2261         object_class->finalize = chat_finalize;
2262         object_class->get_property = chat_get_property;
2263         object_class->set_property = chat_set_property;
2264         object_class->constructed = chat_constructed;
2265
2266         widget_class->size_request = chat_size_request;
2267         widget_class->size_allocate = chat_size_allocate;
2268
2269         g_object_class_install_property (object_class,
2270                                          PROP_TP_CHAT,
2271                                          g_param_spec_object ("tp-chat",
2272                                                               "Empathy tp chat",
2273                                                               "The tp chat object",
2274                                                               EMPATHY_TYPE_TP_CHAT,
2275                                                               G_PARAM_CONSTRUCT |
2276                                                               G_PARAM_READWRITE |
2277                                                               G_PARAM_STATIC_STRINGS));
2278         g_object_class_install_property (object_class,
2279                                          PROP_ACCOUNT,
2280                                          g_param_spec_object ("account",
2281                                                               "Account of the chat",
2282                                                               "The account of the chat",
2283                                                               TP_TYPE_ACCOUNT,
2284                                                               G_PARAM_READABLE |
2285                                                               G_PARAM_STATIC_STRINGS));
2286         g_object_class_install_property (object_class,
2287                                          PROP_ID,
2288                                          g_param_spec_string ("id",
2289                                                               "Chat's id",
2290                                                               "The id of the chat",
2291                                                               NULL,
2292                                                               G_PARAM_READABLE |
2293                                                               G_PARAM_STATIC_STRINGS));
2294         g_object_class_install_property (object_class,
2295                                          PROP_NAME,
2296                                          g_param_spec_string ("name",
2297                                                               "Chat's name",
2298                                                               "The name of the chat",
2299                                                               NULL,
2300                                                               G_PARAM_READABLE |
2301                                                               G_PARAM_STATIC_STRINGS));
2302         g_object_class_install_property (object_class,
2303                                          PROP_SUBJECT,
2304                                          g_param_spec_string ("subject",
2305                                                               "Chat's subject",
2306                                                               "The subject or topic of the chat",
2307                                                               NULL,
2308                                                               G_PARAM_READABLE |
2309                                                               G_PARAM_STATIC_STRINGS));
2310         g_object_class_install_property (object_class,
2311                                          PROP_REMOTE_CONTACT,
2312                                          g_param_spec_object ("remote-contact",
2313                                                               "The remote contact",
2314                                                               "The remote contact is any",
2315                                                               EMPATHY_TYPE_CONTACT,
2316                                                               G_PARAM_READABLE |
2317                                                               G_PARAM_STATIC_STRINGS));
2318         g_object_class_install_property (object_class,
2319                                          PROP_SHOW_CONTACTS,
2320                                          g_param_spec_boolean ("show-contacts",
2321                                                                "Contacts' visibility",
2322                                                                "The visibility of the contacts' list",
2323                                                                TRUE,
2324                                                                G_PARAM_READWRITE |
2325                                                                G_PARAM_STATIC_STRINGS));
2326
2327         signals[COMPOSING] =
2328                 g_signal_new ("composing",
2329                               G_OBJECT_CLASS_TYPE (object_class),
2330                               G_SIGNAL_RUN_LAST,
2331                               0,
2332                               NULL, NULL,
2333                               g_cclosure_marshal_VOID__BOOLEAN,
2334                               G_TYPE_NONE,
2335                               1, G_TYPE_BOOLEAN);
2336
2337         signals[NEW_MESSAGE] =
2338                 g_signal_new ("new-message",
2339                               G_OBJECT_CLASS_TYPE (object_class),
2340                               G_SIGNAL_RUN_LAST,
2341                               0,
2342                               NULL, NULL,
2343                               g_cclosure_marshal_VOID__OBJECT,
2344                               G_TYPE_NONE,
2345                               1, EMPATHY_TYPE_MESSAGE);
2346
2347         g_type_class_add_private (object_class, sizeof (EmpathyChatPriv));
2348 }
2349
2350 static gboolean
2351 chat_block_events_timeout_cb (gpointer data)
2352 {
2353         EmpathyChatPriv *priv = GET_PRIV (data);
2354
2355         priv->block_events_timeout_id = 0;
2356
2357         return FALSE;
2358 }
2359
2360 static void
2361 account_manager_prepared_cb (GObject *source_object,
2362                              GAsyncResult *result,
2363                              gpointer user_data)
2364 {
2365         GList *accounts, *l;
2366         TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
2367         EmpathyChat *chat = user_data;
2368         GError *error = NULL;
2369
2370         if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
2371                 DEBUG ("Failed to prepare the account manager: %s", error->message);
2372                 g_error_free (error);
2373                 return;
2374         }
2375
2376         accounts = tp_account_manager_get_valid_accounts (account_manager);
2377
2378         for (l = accounts; l != NULL; l = l->next) {
2379                 TpAccount *account = l->data;
2380                 empathy_signal_connect_weak (account, "status-changed",
2381                                              G_CALLBACK (chat_new_connection_cb),
2382                                              G_OBJECT (chat));
2383         }
2384
2385         g_list_free (accounts);
2386 }
2387
2388 static void
2389 empathy_chat_init (EmpathyChat *chat)
2390 {
2391         EmpathyChatPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chat,
2392                 EMPATHY_TYPE_CHAT, EmpathyChatPriv);
2393
2394         chat->priv = priv;
2395         priv->log_manager = empathy_log_manager_dup_singleton ();
2396         priv->contacts_width = -1;
2397         priv->input_history = NULL;
2398         priv->input_history_current = NULL;
2399         priv->account_manager = tp_account_manager_dup ();
2400
2401         tp_account_manager_prepare_async (priv->account_manager, NULL,
2402                                           account_manager_prepared_cb, chat);
2403
2404         empathy_conf_get_bool (empathy_conf_get (),
2405                                EMPATHY_PREFS_CHAT_SHOW_CONTACTS_IN_ROOMS,
2406                                &priv->show_contacts);
2407
2408         /* Block events for some time to avoid having "has come online" or
2409          * "joined" messages. */
2410         priv->block_events_timeout_id =
2411                 g_timeout_add_seconds (1, chat_block_events_timeout_cb, chat);
2412
2413         /* Add nick name completion */
2414         priv->completion = g_completion_new ((GCompletionFunc) empathy_contact_get_name);
2415         g_completion_set_compare (priv->completion, chat_contacts_completion_func);
2416
2417         chat_create_ui (chat);
2418 }
2419
2420 EmpathyChat *
2421 empathy_chat_new (EmpathyTpChat *tp_chat)
2422 {
2423         return g_object_new (EMPATHY_TYPE_CHAT, "tp-chat", tp_chat, NULL);
2424 }
2425
2426 EmpathyTpChat *
2427 empathy_chat_get_tp_chat (EmpathyChat *chat)
2428 {
2429         EmpathyChatPriv *priv = GET_PRIV (chat);
2430
2431         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2432
2433         return priv->tp_chat;
2434 }
2435
2436 static void display_password_info_bar (EmpathyChat *self,
2437                                        gboolean retry);
2438
2439 static void
2440 provide_password_cb (GObject *tp_chat,
2441                      GAsyncResult *res,
2442                      gpointer user_data)
2443 {
2444         EmpathyChat *self = EMPATHY_CHAT (user_data);
2445         EmpathyChatPriv *priv = GET_PRIV (self);
2446         GError *error = NULL;
2447
2448         if (!empathy_tp_chat_provide_password_finish (EMPATHY_TP_CHAT (tp_chat), res,
2449                                                       &error)) {
2450                 DEBUG ("error: %s", error->message);
2451                 /* FIXME: what should we do if that's another error? Close the channel?
2452                  * Display the raw D-Bus error to the user isn't very useful */
2453                 if (g_error_matches (error, TP_ERRORS, TP_ERROR_AUTHENTICATION_FAILED))
2454                         display_password_info_bar (self, TRUE);
2455                 g_error_free (error);
2456                 return;
2457         }
2458
2459         /* Room joined */
2460         gtk_widget_set_sensitive (priv->hpaned, TRUE);
2461         gtk_widget_grab_focus (self->input_text_view);
2462 }
2463
2464 static void
2465 password_infobar_response_cb (GtkWidget *info_bar,
2466                               gint response_id,
2467                               EmpathyChat *self)
2468 {
2469         EmpathyChatPriv *priv = GET_PRIV (self);
2470         GtkWidget *entry;
2471         const gchar *password;
2472
2473         if (response_id != GTK_RESPONSE_OK)
2474                 goto out;
2475
2476         entry = g_object_get_data (G_OBJECT (info_bar), "password-entry");
2477         g_assert (entry != NULL);
2478
2479         password = gtk_entry_get_text (GTK_ENTRY (entry));
2480
2481         empathy_tp_chat_provide_password_async (priv->tp_chat, password,
2482                                                 provide_password_cb, self);
2483
2484  out:
2485         gtk_widget_destroy (info_bar);
2486 }
2487
2488 static void
2489 password_entry_activate_cb (GtkWidget *entry,
2490                           GtkWidget *info_bar)
2491 {
2492         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2493 }
2494
2495 static void
2496 passwd_join_button_cb (GtkButton *button,
2497                           GtkWidget *info_bar)
2498 {
2499         gtk_info_bar_response (GTK_INFO_BAR (info_bar), GTK_RESPONSE_OK);
2500 }
2501
2502 static void
2503 display_password_info_bar (EmpathyChat *self,
2504                            gboolean retry)
2505 {
2506         EmpathyChatPriv *priv = GET_PRIV (self);
2507         GtkWidget *info_bar;
2508         GtkWidget *content_area;
2509         GtkWidget *hbox;
2510         GtkWidget *image;
2511         GtkWidget *label;
2512         GtkWidget *entry;
2513         GtkWidget *alig;
2514         GtkWidget *button;
2515         GtkMessageType type;
2516         const gchar *msg, *button_label;
2517
2518         if (retry) {
2519                 /* Previous password was wrong */
2520                 type = GTK_MESSAGE_ERROR;
2521                 msg = _("Wrong password; please try again:");
2522                 button_label = _("Retry");
2523         }
2524         else {
2525                 /* First time we're trying to join */
2526                 type = GTK_MESSAGE_QUESTION;
2527                 msg = _("This room is protected by a password:");
2528                 button_label = _("Join");
2529         }
2530
2531         info_bar = gtk_info_bar_new ();
2532         gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), type);
2533
2534         content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
2535
2536         hbox = gtk_hbox_new (FALSE, 3);
2537         gtk_container_add (GTK_CONTAINER (content_area), hbox);
2538
2539         /* Add image */
2540         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
2541                                           GTK_ICON_SIZE_DIALOG);
2542         gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
2543
2544         /* Add message */
2545         label = gtk_label_new (msg);
2546         gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
2547
2548         /* Add password entry */
2549         entry = gtk_entry_new ();
2550         gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
2551         gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0);
2552
2553         g_signal_connect (entry, "activate",
2554                           G_CALLBACK (password_entry_activate_cb), info_bar);
2555
2556         /* Focus the password entry once it's realized */
2557         g_signal_connect (entry, "realize", G_CALLBACK (gtk_widget_grab_focus), NULL);
2558
2559         /* Add 'Join' button */
2560         alig = gtk_alignment_new (0, 0.5, 0, 0);
2561
2562         button = gtk_button_new_with_label (button_label);
2563         gtk_container_add (GTK_CONTAINER (alig), button);
2564         gtk_box_pack_start (GTK_BOX (hbox), alig, FALSE, FALSE, 0);
2565
2566         g_signal_connect (button, "clicked", G_CALLBACK (passwd_join_button_cb),
2567                           info_bar);
2568
2569         g_object_set_data (G_OBJECT (info_bar), "password-entry", entry);
2570
2571         gtk_box_pack_start (GTK_BOX (priv->info_bar_vbox), info_bar,
2572                             FALSE, FALSE, 3);
2573         gtk_widget_show_all (hbox);
2574
2575         g_signal_connect (info_bar, "response",
2576                           G_CALLBACK (password_infobar_response_cb), self);
2577
2578         gtk_widget_show_all (info_bar);
2579 }
2580
2581 static void
2582 chat_password_needed_changed_cb (EmpathyChat *self)
2583 {
2584         EmpathyChatPriv *priv = GET_PRIV (self);
2585
2586         if (empathy_tp_chat_password_needed (priv->tp_chat)) {
2587                 display_password_info_bar (self, FALSE);
2588                 gtk_widget_set_sensitive (priv->hpaned, FALSE);
2589         }
2590 }
2591
2592 void
2593 empathy_chat_set_tp_chat (EmpathyChat   *chat,
2594                           EmpathyTpChat *tp_chat)
2595 {
2596         EmpathyChatPriv *priv = GET_PRIV (chat);
2597         TpConnection    *connection;
2598         GPtrArray       *properties;
2599
2600         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2601         g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
2602         g_return_if_fail (empathy_tp_chat_is_ready (tp_chat));
2603
2604         if (priv->tp_chat) {
2605                 return;
2606         }
2607
2608         if (priv->account) {
2609                 g_object_unref (priv->account);
2610         }
2611
2612         priv->tp_chat = g_object_ref (tp_chat);
2613         connection = empathy_tp_chat_get_connection (priv->tp_chat);
2614         priv->account = g_object_ref (empathy_get_account_for_connection (connection));
2615
2616         g_signal_connect (tp_chat, "destroy",
2617                           G_CALLBACK (chat_destroy_cb),
2618                           chat);
2619         g_signal_connect (tp_chat, "message-received",
2620                           G_CALLBACK (chat_message_received_cb),
2621                           chat);
2622         g_signal_connect (tp_chat, "send-error",
2623                           G_CALLBACK (chat_send_error_cb),
2624                           chat);
2625         g_signal_connect (tp_chat, "chat-state-changed",
2626                           G_CALLBACK (chat_state_changed_cb),
2627                           chat);
2628         g_signal_connect (tp_chat, "property-changed",
2629                           G_CALLBACK (chat_property_changed_cb),
2630                           chat);
2631         g_signal_connect (tp_chat, "members-changed",
2632                           G_CALLBACK (chat_members_changed_cb),
2633                           chat);
2634         g_signal_connect (tp_chat, "member-renamed",
2635                           G_CALLBACK (chat_member_renamed_cb),
2636                           chat);
2637         g_signal_connect_swapped (tp_chat, "notify::remote-contact",
2638                                   G_CALLBACK (chat_remote_contact_changed_cb),
2639                                   chat);
2640         g_signal_connect_swapped (tp_chat, "notify::password-needed",
2641                                   G_CALLBACK (chat_password_needed_changed_cb),
2642                                   chat);
2643
2644         /* Get initial value of properties */
2645         properties = empathy_tp_chat_get_properties (priv->tp_chat);
2646         if (properties != NULL) {
2647                 guint i;
2648
2649                 for (i = 0; i < properties->len; i++) {
2650                         EmpathyTpChatProperty *property;
2651
2652                         property = g_ptr_array_index (properties, i);
2653                         if (property->value == NULL)
2654                                 continue;
2655
2656                         chat_property_changed_cb (priv->tp_chat,
2657                                                   property->name,
2658                                                   property->value,
2659                                                   chat);
2660                 }
2661         }
2662
2663         chat_remote_contact_changed_cb (chat);
2664
2665         if (chat->input_text_view) {
2666                 gtk_widget_set_sensitive (chat->input_text_view, TRUE);
2667                 if (priv->block_events_timeout_id == 0) {
2668                         empathy_chat_view_append_event (chat->view, _("Connected"));
2669                 }
2670         }
2671
2672         g_object_notify (G_OBJECT (chat), "tp-chat");
2673         g_object_notify (G_OBJECT (chat), "id");
2674         g_object_notify (G_OBJECT (chat), "account");
2675
2676         /* This is a noop when tp-chat is set at object construction time and causes
2677          * the pending messages to be show when it's set on the object after it has
2678          * been created */
2679         show_pending_messages (chat);
2680
2681         /* check if a password is needed */
2682         chat_password_needed_changed_cb (chat);
2683 }
2684
2685 TpAccount *
2686 empathy_chat_get_account (EmpathyChat *chat)
2687 {
2688         EmpathyChatPriv *priv = GET_PRIV (chat);
2689
2690         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2691
2692         return priv->account;
2693 }
2694
2695 const gchar *
2696 empathy_chat_get_id (EmpathyChat *chat)
2697 {
2698         EmpathyChatPriv *priv = GET_PRIV (chat);
2699
2700         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2701
2702         return priv->id;
2703 }
2704
2705 const gchar *
2706 empathy_chat_get_name (EmpathyChat *chat)
2707 {
2708         EmpathyChatPriv *priv = GET_PRIV (chat);
2709         const gchar *ret;
2710
2711         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2712
2713         ret = priv->name;
2714         if (!ret && priv->remote_contact) {
2715                 ret = empathy_contact_get_name (priv->remote_contact);
2716         }
2717
2718         if (!ret)
2719                 ret = priv->id;
2720
2721         return ret ? ret : _("Conversation");
2722 }
2723
2724 const gchar *
2725 empathy_chat_get_subject (EmpathyChat *chat)
2726 {
2727         EmpathyChatPriv *priv = GET_PRIV (chat);
2728
2729         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2730
2731         return priv->subject;
2732 }
2733
2734 EmpathyContact *
2735 empathy_chat_get_remote_contact (EmpathyChat *chat)
2736 {
2737         EmpathyChatPriv *priv = GET_PRIV (chat);
2738
2739         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2740
2741         return priv->remote_contact;
2742 }
2743
2744 GtkWidget *
2745 empathy_chat_get_contact_menu (EmpathyChat *chat)
2746 {
2747         EmpathyChatPriv *priv = GET_PRIV (chat);
2748         GtkWidget       *menu = NULL;
2749
2750         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2751
2752         if (priv->remote_contact) {
2753                 menu = empathy_contact_menu_new (priv->remote_contact,
2754                                                  EMPATHY_CONTACT_FEATURE_CALL |
2755                                                  EMPATHY_CONTACT_FEATURE_LOG |
2756                                                  EMPATHY_CONTACT_FEATURE_INFO);
2757         }
2758         else if (priv->contact_list_view) {
2759                 EmpathyContactListView *view;
2760
2761                 view = EMPATHY_CONTACT_LIST_VIEW (priv->contact_list_view);
2762                 menu = empathy_contact_list_view_get_contact_menu (view);
2763         }
2764
2765         return menu;
2766 }
2767
2768 void
2769 empathy_chat_clear (EmpathyChat *chat)
2770 {
2771         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2772
2773         empathy_chat_view_clear (chat->view);
2774 }
2775
2776 void
2777 empathy_chat_scroll_down (EmpathyChat *chat)
2778 {
2779         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2780
2781         empathy_chat_view_scroll_down (chat->view);
2782 }
2783
2784 void
2785 empathy_chat_cut (EmpathyChat *chat)
2786 {
2787         GtkTextBuffer *buffer;
2788
2789         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2790
2791         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2792         if (gtk_text_buffer_get_has_selection (buffer)) {
2793                 GtkClipboard *clipboard;
2794
2795                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2796
2797                 gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
2798         }
2799 }
2800
2801 void
2802 empathy_chat_copy (EmpathyChat *chat)
2803 {
2804         GtkTextBuffer *buffer;
2805
2806         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2807
2808         if (empathy_chat_view_get_has_selection (chat->view)) {
2809                 empathy_chat_view_copy_clipboard (chat->view);
2810                 return;
2811         }
2812
2813         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2814         if (gtk_text_buffer_get_has_selection (buffer)) {
2815                 GtkClipboard *clipboard;
2816
2817                 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2818
2819                 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2820         }
2821 }
2822
2823 void
2824 empathy_chat_paste (EmpathyChat *chat)
2825 {
2826         GtkTextBuffer *buffer;
2827         GtkClipboard  *clipboard;
2828         EmpathyChatPriv *priv;
2829
2830         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2831
2832         priv = GET_PRIV (chat);
2833
2834         if (priv->tp_chat == NULL ||
2835             !GTK_WIDGET_IS_SENSITIVE (chat->input_text_view))
2836                 return;
2837
2838         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2839         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2840
2841         gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
2842 }
2843
2844 void
2845 empathy_chat_correct_word (EmpathyChat  *chat,
2846                           GtkTextIter *start,
2847                           GtkTextIter *end,
2848                           const gchar *new_word)
2849 {
2850         GtkTextBuffer *buffer;
2851
2852         g_return_if_fail (chat != NULL);
2853         g_return_if_fail (new_word != NULL);
2854
2855         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
2856
2857         gtk_text_buffer_delete (buffer, start, end);
2858         gtk_text_buffer_insert (buffer, start,
2859                                 new_word,
2860                                 -1);
2861 }
2862
2863 gboolean
2864 empathy_chat_is_room (EmpathyChat *chat)
2865 {
2866         EmpathyChatPriv *priv = GET_PRIV (chat);
2867
2868         g_return_val_if_fail (EMPATHY_IS_CHAT (chat), FALSE);
2869
2870         return (priv->handle_type == TP_HANDLE_TYPE_ROOM);
2871 }
2872
2873 guint
2874 empathy_chat_get_nb_unread_messages (EmpathyChat *self)
2875 {
2876         EmpathyChatPriv *priv = GET_PRIV (self);
2877
2878         g_return_val_if_fail (EMPATHY_IS_CHAT (self), FALSE);
2879
2880         return priv->unread_messages;
2881 }
2882
2883 /* called when the messages have been read by user */
2884 void
2885 empathy_chat_messages_read (EmpathyChat *self)
2886 {
2887         EmpathyChatPriv *priv = GET_PRIV (self);
2888
2889         g_return_if_fail (EMPATHY_IS_CHAT (self));
2890
2891         priv->unread_messages = 0;
2892 }