046a5507cb72cda62f177fd0086d8bf457f3b17e
[empathy.git] / libempathy-gtk / gossip-chat-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002-2007 Imendio AB
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  * 
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  */
24
25 #include "config.h"
26
27 #include <sys/types.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include <glib/gi18n.h>
32 #include <gtk/gtkbutton.h>
33 #include <gtk/gtkimage.h>
34 #include <gtk/gtkmenu.h>
35 #include <gtk/gtkmenuitem.h>
36 #include <gtk/gtkimagemenuitem.h>
37 #include <gtk/gtkstock.h>
38 #include <gtk/gtkscrolledwindow.h>
39 #include <gtk/gtksizegroup.h>
40 #include <glade/glade.h>
41
42 #include <libmissioncontrol/mc-account.h>
43
44 #include <libempathy/gossip-utils.h>
45 #include <libempathy/gossip-debug.h>
46 #include <libempathy/gossip-conf.h>
47
48 #include "gossip-chat-view.h"
49 #include "gossip-chat.h"
50 #include "gossip-preferences.h"
51 #include "gossip-theme-manager.h"
52 #include "gossip-ui-utils.h"
53
54 #define DEBUG_DOMAIN "ChatView"
55
56 /* Number of seconds between timestamps when using normal mode, 5 minutes. */
57 #define TIMESTAMP_INTERVAL 300
58
59 #define MAX_LINES 800
60
61 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
62
63 typedef enum {
64         BLOCK_TYPE_NONE,
65         BLOCK_TYPE_SELF,
66         BLOCK_TYPE_OTHER,
67         BLOCK_TYPE_EVENT,
68         BLOCK_TYPE_TIME,
69         BLOCK_TYPE_INVITE
70 } BlockType;
71
72 struct _GossipChatViewPriv {
73         GtkTextBuffer *buffer;
74
75         gboolean       irc_style;
76         time_t         last_timestamp;
77         BlockType      last_block_type;
78
79         gboolean       allow_scrolling;
80         gboolean       is_group_chat;
81
82         GtkTextMark   *find_mark_previous;
83         GtkTextMark   *find_mark_next;
84         gboolean       find_wrapped;
85         gboolean       find_last_direction;
86
87         /* This is for the group chat so we know if the "other" last contact
88          * changed, so we know whether to insert a header or not.
89          */
90         GossipContact *last_contact;
91
92         guint          notify_system_fonts_id;
93         guint          notify_show_avatars_id;
94 };
95
96 typedef struct {
97         GossipSmiley  smiley;
98         const gchar  *pattern;
99 } GossipSmileyPattern;
100
101 static const GossipSmileyPattern smileys[] = {
102         /* Forward smileys. */
103         { GOSSIP_SMILEY_NORMAL,       ":)"  },
104         { GOSSIP_SMILEY_WINK,         ";)"  },
105         { GOSSIP_SMILEY_WINK,         ";-)" },
106         { GOSSIP_SMILEY_BIGEYE,       "=)"  },
107         { GOSSIP_SMILEY_NOSE,         ":-)" },
108         { GOSSIP_SMILEY_CRY,          ":'(" },
109         { GOSSIP_SMILEY_SAD,          ":("  },
110         { GOSSIP_SMILEY_SAD,          ":-(" },
111         { GOSSIP_SMILEY_SCEPTICAL,    ":/"  },
112         { GOSSIP_SMILEY_SCEPTICAL,    ":\\" },
113         { GOSSIP_SMILEY_BIGSMILE,     ":D"  },
114         { GOSSIP_SMILEY_BIGSMILE,     ":-D" },
115         { GOSSIP_SMILEY_INDIFFERENT,  ":|"  },
116         { GOSSIP_SMILEY_TOUNGE,       ":p"  },
117         { GOSSIP_SMILEY_TOUNGE,       ":-p" },
118         { GOSSIP_SMILEY_TOUNGE,       ":P"  },
119         { GOSSIP_SMILEY_TOUNGE,       ":-P" },
120         { GOSSIP_SMILEY_TOUNGE,       ";p"  },
121         { GOSSIP_SMILEY_TOUNGE,       ";-p" },
122         { GOSSIP_SMILEY_TOUNGE,       ";P"  },
123         { GOSSIP_SMILEY_TOUNGE,       ";-P" },
124         { GOSSIP_SMILEY_SHOCKED,      ":o"  },
125         { GOSSIP_SMILEY_SHOCKED,      ":-o" },
126         { GOSSIP_SMILEY_SHOCKED,      ":O"  },
127         { GOSSIP_SMILEY_SHOCKED,      ":-O" },
128         { GOSSIP_SMILEY_COOL,         "8)"  },
129         { GOSSIP_SMILEY_COOL,         "B)"  },
130         { GOSSIP_SMILEY_SORRY,        "*|"  },
131         { GOSSIP_SMILEY_KISS,         ":*"  },
132         { GOSSIP_SMILEY_SHUTUP,       ":#"  },
133         { GOSSIP_SMILEY_SHUTUP,       ":-#" },
134         { GOSSIP_SMILEY_YAWN,         "|O"  },
135         { GOSSIP_SMILEY_CONFUSED,     ":S"  },
136         { GOSSIP_SMILEY_CONFUSED,     ":s"  },
137         { GOSSIP_SMILEY_ANGEL,        "<)"  },
138         { GOSSIP_SMILEY_OOOH,         ":x"  },
139         { GOSSIP_SMILEY_LOOKAWAY,     "*)"  },
140         { GOSSIP_SMILEY_LOOKAWAY,     "*-)" },
141         { GOSSIP_SMILEY_BLUSH,        "*S"  },
142         { GOSSIP_SMILEY_BLUSH,        "*s"  },
143         { GOSSIP_SMILEY_BLUSH,        "*$"  },
144         { GOSSIP_SMILEY_COOLBIGSMILE, "8D"  },
145         { GOSSIP_SMILEY_ANGRY,        ":@"  },
146         { GOSSIP_SMILEY_BOSS,         "@)"  },
147         { GOSSIP_SMILEY_MONKEY,       "#)"  },
148         { GOSSIP_SMILEY_SILLY,        "O)"  },
149         { GOSSIP_SMILEY_SICK,         "+o(" },
150
151         /* Backward smileys. */
152         { GOSSIP_SMILEY_NORMAL,       "(:"  },
153         { GOSSIP_SMILEY_WINK,         "(;"  },
154         { GOSSIP_SMILEY_WINK,         "(-;" },
155         { GOSSIP_SMILEY_BIGEYE,       "(="  },
156         { GOSSIP_SMILEY_NOSE,         "(-:" },
157         { GOSSIP_SMILEY_CRY,          ")':" },
158         { GOSSIP_SMILEY_SAD,          "):"  },
159         { GOSSIP_SMILEY_SAD,          ")-:" },
160         { GOSSIP_SMILEY_SCEPTICAL,    "/:"  },
161         { GOSSIP_SMILEY_SCEPTICAL,    "//:" },
162         { GOSSIP_SMILEY_INDIFFERENT,  "|:"  },
163         { GOSSIP_SMILEY_TOUNGE,       "d:"  },
164         { GOSSIP_SMILEY_TOUNGE,       "d-:" },
165         { GOSSIP_SMILEY_TOUNGE,       "d;"  },
166         { GOSSIP_SMILEY_TOUNGE,       "d-;" },
167         { GOSSIP_SMILEY_SHOCKED,      "o:"  },
168         { GOSSIP_SMILEY_SHOCKED,      "O:"  },
169         { GOSSIP_SMILEY_COOL,         "(8"  },
170         { GOSSIP_SMILEY_COOL,         "(B"  },
171         { GOSSIP_SMILEY_SORRY,        "|*"  },
172         { GOSSIP_SMILEY_KISS,         "*:"  },
173         { GOSSIP_SMILEY_SHUTUP,       "#:"  },
174         { GOSSIP_SMILEY_SHUTUP,       "#-:" },
175         { GOSSIP_SMILEY_YAWN,         "O|"  },
176         { GOSSIP_SMILEY_CONFUSED,     "S:"  },
177         { GOSSIP_SMILEY_CONFUSED,     "s:"  },
178         { GOSSIP_SMILEY_ANGEL,        "(>"  },
179         { GOSSIP_SMILEY_OOOH,         "x:"  },
180         { GOSSIP_SMILEY_LOOKAWAY,     "(*"  },
181         { GOSSIP_SMILEY_LOOKAWAY,     "(-*" },
182         { GOSSIP_SMILEY_BLUSH,        "S*"  },
183         { GOSSIP_SMILEY_BLUSH,        "s*"  },
184         { GOSSIP_SMILEY_BLUSH,        "$*"  },
185         { GOSSIP_SMILEY_ANGRY,        "@:"  },
186         { GOSSIP_SMILEY_BOSS,         "(@"  },
187         { GOSSIP_SMILEY_MONKEY,       "#)"  },
188         { GOSSIP_SMILEY_SILLY,        "(O"  },
189         { GOSSIP_SMILEY_SICK,         ")o+" }
190 };
191
192 static void     gossip_chat_view_class_init          (GossipChatViewClass      *klass);
193 static void     gossip_chat_view_init                (GossipChatView           *view);
194 static void     chat_view_finalize                   (GObject                  *object);
195 static gboolean chat_view_drag_motion                (GtkWidget                *widget,
196                                                       GdkDragContext           *context,
197                                                       gint                      x,
198                                                       gint                      y,
199                                                       guint                     time);
200 static void     chat_view_size_allocate              (GtkWidget                *widget,
201                                                       GtkAllocation            *alloc);
202 static void     chat_view_setup_tags                 (GossipChatView           *view);
203 static void     chat_view_system_font_update         (GossipChatView           *view);
204 static void     chat_view_notify_system_font_cb      (GossipConf               *conf,
205                                                       const gchar              *key,
206                                                       gpointer                  user_data);
207 static void     chat_view_notify_show_avatars_cb     (GossipConf               *conf,
208                                                       const gchar              *key,
209                                                       gpointer                  user_data);
210 static void     chat_view_populate_popup             (GossipChatView           *view,
211                                                       GtkMenu                  *menu,
212                                                       gpointer                  user_data);
213 static gboolean chat_view_event_cb                   (GossipChatView           *view,
214                                                       GdkEventMotion           *event,
215                                                       GtkTextTag               *tag);
216 static gboolean chat_view_url_event_cb               (GtkTextTag               *tag,
217                                                       GObject                  *object,
218                                                       GdkEvent                 *event,
219                                                       GtkTextIter              *iter,
220                                                       GtkTextBuffer            *buffer);
221 static void     chat_view_open_address_cb            (GtkMenuItem              *menuitem,
222                                                       const gchar              *url);
223 static void     chat_view_copy_address_cb            (GtkMenuItem              *menuitem,
224                                                       const gchar              *url);
225 static void     chat_view_clear_view_cb              (GtkMenuItem              *menuitem,
226                                                       GossipChatView           *view);
227 static void     chat_view_insert_text_with_emoticons (GtkTextBuffer            *buf,
228                                                       GtkTextIter              *iter,
229                                                       const gchar              *str);
230 static gboolean chat_view_is_scrolled_down           (GossipChatView           *view);
231 static void     chat_view_theme_changed_cb           (GossipThemeManager       *manager,
232                                                       GossipChatView           *view);
233 static void     chat_view_maybe_append_date_and_time (GossipChatView           *view,
234                                                       GossipMessage            *msg);
235 static void     chat_view_append_spacing             (GossipChatView           *view);
236 static void     chat_view_append_text                (GossipChatView           *view,
237                                                       const gchar              *body,
238                                                       const gchar              *tag);
239 static void     chat_view_maybe_append_fancy_header  (GossipChatView           *view,
240                                                       GossipMessage            *msg);
241 static void     chat_view_append_irc_action          (GossipChatView           *view,
242                                                       GossipMessage            *msg);
243 static void     chat_view_append_fancy_action        (GossipChatView           *view,
244                                                       GossipMessage            *msg);
245 static void     chat_view_append_irc_message         (GossipChatView           *view,
246                                                       GossipMessage            *msg);
247 static void     chat_view_append_fancy_message       (GossipChatView           *view,
248                                                       GossipMessage            *msg);
249 static GdkPixbuf *chat_view_pad_to_size              (GdkPixbuf                *pixbuf,
250                                                       gint                      width,
251                                                       gint                      height,
252                                                       gint                      extra_padding_right);
253
254 G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
255
256 static void
257 gossip_chat_view_class_init (GossipChatViewClass *klass)
258 {
259         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
260         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
261
262         object_class->finalize = chat_view_finalize;
263         widget_class->size_allocate = chat_view_size_allocate;
264         widget_class->drag_motion = chat_view_drag_motion; 
265
266         g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
267 }
268
269 static void
270 gossip_chat_view_init (GossipChatView *view)
271 {
272         GossipChatViewPriv *priv;
273         gboolean            show_avatars;
274
275         priv = GET_PRIV (view);
276
277         priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
278
279         priv->last_block_type = BLOCK_TYPE_NONE;
280         priv->last_timestamp = 0;
281
282         priv->allow_scrolling = TRUE;
283
284         priv->is_group_chat = FALSE;
285
286         g_object_set (view,
287                       "wrap-mode", GTK_WRAP_WORD_CHAR,
288                       "editable", FALSE,
289                       "cursor-visible", FALSE,
290                       NULL);
291
292         priv->notify_system_fonts_id =
293                 gossip_conf_notify_add (gossip_conf_get (),
294                                          "/desktop/gnome/interface/document_font_name",
295                                          chat_view_notify_system_font_cb,
296                                          view);
297         chat_view_system_font_update (view);
298
299         priv->notify_show_avatars_id =
300                 gossip_conf_notify_add (gossip_conf_get (),
301                                          GOSSIP_PREFS_UI_SHOW_AVATARS,
302                                          chat_view_notify_show_avatars_cb,
303                                          view);
304
305         chat_view_setup_tags (view);
306
307         gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
308
309         show_avatars = FALSE;
310         gossip_conf_get_bool (gossip_conf_get (),
311                                GOSSIP_PREFS_UI_SHOW_AVATARS,
312                                &show_avatars);
313
314         gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
315                                                   view, show_avatars);
316
317         g_signal_connect (view,
318                           "populate-popup",
319                           G_CALLBACK (chat_view_populate_popup),
320                           NULL);
321
322         g_signal_connect_object (gossip_theme_manager_get (),
323                                  "theme-changed",
324                                  G_CALLBACK (chat_view_theme_changed_cb),
325                                  view,
326                                  0);
327 }
328
329 static void
330 chat_view_finalize (GObject *object)
331 {
332         GossipChatView     *view;
333         GossipChatViewPriv *priv;
334
335         view = GOSSIP_CHAT_VIEW (object);
336         priv = GET_PRIV (view);
337
338         gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
339
340         gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
341         gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
342
343         if (priv->last_contact) {
344                 g_object_unref (priv->last_contact);
345         }
346
347         G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
348 }
349
350 static gboolean
351 chat_view_drag_motion (GtkWidget        *widget,
352                        GdkDragContext   *context,
353                        gint              x,
354                        gint              y,
355                        guint             time)
356 {
357         /* Don't handle drag motion, since we don't want the view to scroll as
358          * the result of dragging something across it.
359          */
360
361         return FALSE;
362 }
363
364 static void
365 chat_view_size_allocate (GtkWidget     *widget,
366                          GtkAllocation *alloc)
367 {
368         gboolean down;
369
370         down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
371
372         GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
373
374         if (down) {
375                 gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
376         }
377 }
378
379 static void
380 chat_view_setup_tags (GossipChatView *view)
381 {
382         GossipChatViewPriv *priv;
383         GtkTextTag         *tag;
384
385         priv = GET_PRIV (view);
386
387         gtk_text_buffer_create_tag (priv->buffer,
388                                     "cut",
389                                     NULL);
390
391         /* FIXME: Move to the theme and come up with something that looks a bit
392          * nicer.
393          */
394         gtk_text_buffer_create_tag (priv->buffer,
395                                     "highlight",
396                                     "background", "yellow",
397                                     NULL);
398
399         tag = gtk_text_buffer_create_tag (priv->buffer,
400                                           "link",
401                                           NULL);
402
403         g_signal_connect (tag,
404                           "event",
405                           G_CALLBACK (chat_view_url_event_cb),
406                           priv->buffer);
407
408         g_signal_connect (view,
409                           "motion-notify-event",
410                           G_CALLBACK (chat_view_event_cb),
411                           tag);
412 }
413
414 static void
415 chat_view_system_font_update (GossipChatView *view)
416 {
417         PangoFontDescription *font_description = NULL;
418         gchar                *font_name;
419
420         if (gossip_conf_get_string (gossip_conf_get (),
421                                      "/desktop/gnome/interface/document_font_name",
422                                      &font_name) && font_name) {
423                 font_description = pango_font_description_from_string (font_name);
424                 g_free (font_name);
425         } else {
426                 font_description = NULL;
427         }
428
429         gtk_widget_modify_font (GTK_WIDGET (view), font_description);
430
431         if (font_description) {
432                 pango_font_description_free (font_description);
433         }
434 }
435
436 static void
437 chat_view_notify_system_font_cb (GossipConf  *conf,
438                                  const gchar *key,
439                                  gpointer     user_data)
440 {
441         GossipChatView *view;
442         gboolean        show_avatars = FALSE;
443
444         view = user_data;
445
446         chat_view_system_font_update (view);
447
448         /* Ugly, again, to adjust the vertical position of the nick... Will fix
449          * this when reworking the theme manager so that view register
450          * themselves with it instead of the other way around.
451          */
452         gossip_conf_get_bool (conf,
453                                GOSSIP_PREFS_UI_SHOW_AVATARS,
454                                &show_avatars);
455
456         gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
457                                                   view, show_avatars);
458 }
459
460 static void
461 chat_view_notify_show_avatars_cb (GossipConf  *conf,
462                                   const gchar *key,
463                                   gpointer     user_data)
464 {
465         GossipChatView     *view;
466         GossipChatViewPriv *priv;
467         gboolean            show_avatars = FALSE;
468
469         view = user_data;
470         priv = GET_PRIV (view);
471
472         gossip_conf_get_bool (conf, key, &show_avatars);
473
474         gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
475                                                   view, show_avatars);
476 }
477
478 static void
479 chat_view_populate_popup (GossipChatView *view,
480                           GtkMenu        *menu,
481                           gpointer        user_data)
482 {
483         GossipChatViewPriv *priv;
484         GtkTextTagTable    *table;
485         GtkTextTag         *tag;
486         gint                x, y;
487         GtkTextIter         iter, start, end;
488         GtkWidget          *item;
489         gchar              *str = NULL;
490
491         priv = GET_PRIV (view);
492
493         /* Clear menu item */
494         if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
495                 item = gtk_menu_item_new ();
496                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
497                 gtk_widget_show (item);
498
499                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
500                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
501                 gtk_widget_show (item);
502
503                 g_signal_connect (item,
504                                   "activate",
505                                   G_CALLBACK (chat_view_clear_view_cb),
506                                   view);
507         }
508
509         /* Link context menu items */
510         table = gtk_text_buffer_get_tag_table (priv->buffer);
511         tag = gtk_text_tag_table_lookup (table, "link");
512
513         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
514
515         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
516                                                GTK_TEXT_WINDOW_WIDGET,
517                                                x, y,
518                                                &x, &y);
519
520         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
521
522         start = end = iter;
523
524         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
525             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
526                 str = gtk_text_buffer_get_text (priv->buffer,
527                                                 &start, &end, FALSE);
528         }
529
530         if (G_STR_EMPTY (str)) {
531                 g_free (str);
532                 return;
533         }
534
535         /* NOTE: Set data just to get the string freed when not needed. */
536         g_object_set_data_full (G_OBJECT (menu),
537                                 "url", str,
538                                 (GDestroyNotify) g_free);
539
540         item = gtk_menu_item_new ();
541         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
542         gtk_widget_show (item);
543
544         item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
545         g_signal_connect (item,
546                           "activate",
547                           G_CALLBACK (chat_view_copy_address_cb),
548                           str);
549         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
550         gtk_widget_show (item);
551
552         item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
553         g_signal_connect (item,
554                           "activate",
555                           G_CALLBACK (chat_view_open_address_cb),
556                           str);
557         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
558         gtk_widget_show (item);
559 }
560
561 static gboolean
562 chat_view_event_cb (GossipChatView *view,
563                     GdkEventMotion *event,
564                     GtkTextTag     *tag)
565 {
566         static GdkCursor  *hand = NULL;
567         static GdkCursor  *beam = NULL;
568         GtkTextWindowType  type;
569         GtkTextIter        iter;
570         GdkWindow         *win;
571         gint               x, y, buf_x, buf_y;
572
573         type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
574                                               event->window);
575
576         if (type != GTK_TEXT_WINDOW_TEXT) {
577                 return FALSE;
578         }
579
580         /* Get where the pointer really is. */
581         win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
582         if (!win) {
583                 return FALSE;
584         }
585
586         gdk_window_get_pointer (win, &x, &y, NULL);
587
588         /* Get the iter where the cursor is at */
589         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
590                                                x, y,
591                                                &buf_x, &buf_y);
592
593         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
594                                             &iter,
595                                             buf_x, buf_y);
596
597         if (gtk_text_iter_has_tag (&iter, tag)) {
598                 if (!hand) {
599                         hand = gdk_cursor_new (GDK_HAND2);
600                         beam = gdk_cursor_new (GDK_XTERM);
601                 }
602                 gdk_window_set_cursor (win, hand);
603         } else {
604                 if (!beam) {
605                         beam = gdk_cursor_new (GDK_XTERM);
606                 }
607                 gdk_window_set_cursor (win, beam);
608         }
609
610         return FALSE;
611 }
612
613 static gboolean
614 chat_view_url_event_cb (GtkTextTag    *tag,
615                         GObject       *object,
616                         GdkEvent      *event,
617                         GtkTextIter   *iter,
618                         GtkTextBuffer *buffer)
619 {
620         GtkTextIter  start, end;
621         gchar       *str;
622
623         /* If the link is being selected, don't do anything. */
624         gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
625         if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
626                 return FALSE;
627         }
628
629         if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
630                 start = end = *iter;
631
632                 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
633                     gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
634                         str = gtk_text_buffer_get_text (buffer,
635                                                         &start,
636                                                         &end,
637                                                         FALSE);
638
639                         gossip_url_show (str);
640                         g_free (str);
641                 }
642         }
643
644         return FALSE;
645 }
646
647 static void
648 chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
649 {
650         gossip_url_show (url);
651 }
652
653 static void
654 chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
655 {
656         GtkClipboard *clipboard;
657
658         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
659         gtk_clipboard_set_text (clipboard, url, -1);
660
661         clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
662         gtk_clipboard_set_text (clipboard, url, -1);
663 }
664
665 static void
666 chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
667 {
668         gossip_chat_view_clear (view);
669 }
670
671 static void
672 chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
673                                       GtkTextIter   *iter,
674                                       const gchar   *str)
675 {
676         const gchar *p;
677         gunichar     c, prev_c;
678         gint         i;
679         gint         match;
680         gint         submatch;
681         gboolean     use_smileys = FALSE;
682
683         gossip_conf_get_bool (gossip_conf_get (),
684                                GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
685                                &use_smileys);
686
687         if (!use_smileys) {
688                 gtk_text_buffer_insert (buf, iter, str, -1);
689                 return;
690         }
691
692         while (*str) {
693                 gint         smileys_index[G_N_ELEMENTS (smileys)];
694                 GdkPixbuf   *pixbuf;
695                 gint         len;
696                 const gchar *start;
697
698                 memset (smileys_index, 0, sizeof (smileys_index));
699
700                 match = -1;
701                 submatch = -1;
702                 p = str;
703                 prev_c = 0;
704
705                 while (*p) {
706                         c = g_utf8_get_char (p);
707
708                         if (match != -1 && g_unichar_isspace (c)) {
709                                 break;
710                         } else {
711                                 match = -1;
712                         }
713
714                         if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
715                                 submatch = -1;
716
717                                 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
718                                         /* Only try to match if we already have
719                                          * a beginning match for the pattern, or
720                                          * if it's the first character in the
721                                          * pattern, if it's not in the middle of
722                                          * a word.
723                                          */
724                                         if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
725                                              smileys_index[i] > 0) &&
726                                             smileys[i].pattern[smileys_index[i]] == c) {
727                                                 submatch = i;
728
729                                                 smileys_index[i]++;
730                                                 if (!smileys[i].pattern[smileys_index[i]]) {
731                                                         match = i;
732                                                 }
733                                         } else {
734                                                 smileys_index[i] = 0;
735                                         }
736                                 }
737                         }
738
739                         prev_c = c;
740                         p = g_utf8_next_char (p);
741                 }
742
743                 if (match == -1) {
744                         gtk_text_buffer_insert (buf, iter, str, -1);
745                         return;
746                 }
747
748                 start = p - strlen (smileys[match].pattern);
749
750                 if (start > str) {
751                         len = start - str;
752                         gtk_text_buffer_insert (buf, iter, str, len);
753                 }
754
755                 pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
756                 gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
757
758                 gtk_text_buffer_insert (buf, iter, " ", 1);
759
760                 str = g_utf8_find_next_char (p, NULL);
761         }
762 }
763
764 static gboolean
765 chat_view_is_scrolled_down (GossipChatView *view)
766 {
767         GtkWidget *sw;
768
769         sw = gtk_widget_get_parent (GTK_WIDGET (view));
770         if (GTK_IS_SCROLLED_WINDOW (sw)) {
771                 GtkAdjustment *vadj;
772
773                 vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
774
775                 if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
776                         return FALSE;
777                 }
778         }
779
780         return TRUE;
781 }
782
783 static void
784 chat_view_maybe_trim_buffer (GossipChatView *view)
785 {
786         GossipChatViewPriv *priv;
787         GtkTextIter         top, bottom;
788         gint                line;
789         gint                remove;
790         GtkTextTagTable    *table;
791         GtkTextTag         *tag;
792
793         priv = GET_PRIV (view);
794
795         gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
796         line = gtk_text_iter_get_line (&bottom);
797         if (line < MAX_LINES) {
798                 return;
799         }
800
801         remove = line - MAX_LINES;
802         gtk_text_buffer_get_start_iter (priv->buffer, &top);
803
804         bottom = top;
805         if (!gtk_text_iter_forward_lines (&bottom, remove)) {
806                 return;
807         }
808
809         /* Track backwords to a place where we can safely cut, we don't do it in
810          * the middle of a tag.
811          */
812         table = gtk_text_buffer_get_tag_table (priv->buffer);
813         tag = gtk_text_tag_table_lookup (table, "cut");
814         if (!tag) {
815                 return;
816         }
817
818         if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
819                 return;
820         }
821
822         if (!gtk_text_iter_equal (&top, &bottom)) {
823                 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
824         }
825 }
826
827 static void
828 chat_view_maybe_append_date_and_time (GossipChatView *view,
829                                       GossipMessage  *msg)
830 {
831         GossipChatViewPriv *priv;
832         const gchar        *tag;
833         time_t              timestamp;
834         GDate              *date, *last_date;
835         GtkTextIter         iter;
836         gboolean            append_date, append_time;
837         GString            *str;
838
839         priv = GET_PRIV (view);
840
841         if (priv->irc_style) {
842                 tag = "irc-time";
843         } else {
844                 tag = "fancy-time";
845         }
846
847         if (priv->last_block_type == BLOCK_TYPE_TIME) {
848                 return;
849         }
850
851         str = g_string_new (NULL);
852
853         timestamp = 0;
854         if (msg) {
855                 timestamp = gossip_message_get_timestamp (msg);
856         }
857
858         if (timestamp <= 0) {
859                 timestamp = gossip_time_get_current ();
860         }
861
862         date = g_date_new ();
863         g_date_set_time (date, timestamp);
864
865         last_date = g_date_new ();
866         g_date_set_time (last_date, priv->last_timestamp);
867
868         append_date = FALSE;
869         append_time = FALSE;
870
871         if (g_date_compare (date, last_date) > 0) {
872                 append_date = TRUE;
873                 append_time = TRUE;
874         }
875
876         if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
877                 append_time = TRUE;
878         }
879
880         if (append_time || append_date) {
881                 chat_view_append_spacing (view);
882
883                 g_string_append (str, "- ");
884         }
885
886         if (append_date) {
887                 gchar buf[256];
888
889                 g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
890                 g_string_append (str, buf);
891
892                 if (append_time) {
893                         g_string_append (str, ", ");
894                 }
895         }
896
897         g_date_free (date);
898         g_date_free (last_date);
899
900         if (append_time) {
901                 gchar *tmp;
902
903                 tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
904                 g_string_append (str, tmp);
905                 g_free (tmp);
906         }
907
908         if (append_time || append_date) {
909                 g_string_append (str, " -\n");
910
911                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
912                 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
913                                                           &iter,
914                                                           str->str, -1,
915                                                           tag,
916                                                           NULL);
917
918                 priv->last_block_type = BLOCK_TYPE_TIME;
919                 priv->last_timestamp = timestamp;
920         }
921
922         g_string_free (str, TRUE);
923 }
924
925 static void
926 chat_view_append_spacing (GossipChatView *view)
927 {
928         GossipChatViewPriv *priv;
929         const gchar        *tag;
930         GtkTextIter         iter;
931
932         priv = GET_PRIV (view);
933
934         if (priv->irc_style) {
935                 tag = "irc-spacing";
936         } else {
937                 tag = "fancy-spacing";
938         }
939
940         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
941         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
942                                                   &iter,
943                                                   "\n",
944                                                   -1,
945                                                   "cut",
946                                                   tag,
947                                                   NULL);
948 }
949
950 static void
951 chat_view_append_text (GossipChatView *view,
952                        const gchar    *body,
953                        const gchar    *tag)
954 {
955         GossipChatViewPriv *priv;
956         GtkTextIter         start_iter, end_iter;
957         GtkTextMark        *mark;
958         GtkTextIter         iter;
959         gint                num_matches, i;
960         GArray             *start, *end;
961         const gchar        *link_tag;
962
963         priv = GET_PRIV (view);
964
965         if (priv->irc_style) {
966                 link_tag = "irc-link";
967         } else {
968                 link_tag = "fancy-link";
969         }
970
971         gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
972         mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
973
974         start = g_array_new (FALSE, FALSE, sizeof (gint));
975         end = g_array_new (FALSE, FALSE, sizeof (gint));
976
977         num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
978
979         if (num_matches == 0) {
980                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
981                 chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
982         } else {
983                 gint   last = 0;
984                 gint   s = 0, e = 0;
985                 gchar *tmp;
986
987                 for (i = 0; i < num_matches; i++) {
988                         s = g_array_index (start, gint, i);
989                         e = g_array_index (end, gint, i);
990
991                         if (s > last) {
992                                 tmp = gossip_substring (body, last, s);
993
994                                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
995                                 chat_view_insert_text_with_emoticons (priv->buffer,
996                                                                       &iter,
997                                                                       tmp);
998                                 g_free (tmp);
999                         }
1000
1001                         tmp = gossip_substring (body, s, e);
1002
1003                         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1004                         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1005                                                                   &iter,
1006                                                                   tmp,
1007                                                                   -1,
1008                                                                   link_tag,
1009                                                                   "link",
1010                                                                   NULL);
1011
1012                         g_free (tmp);
1013
1014                         last = e;
1015                 }
1016
1017                 if (e < strlen (body)) {
1018                         tmp = gossip_substring (body, e, strlen (body));
1019
1020                         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1021                         chat_view_insert_text_with_emoticons (priv->buffer,
1022                                                               &iter,
1023                                                               tmp);
1024                         g_free (tmp);
1025                 }
1026         }
1027
1028         g_array_free (start, TRUE);
1029         g_array_free (end, TRUE);
1030
1031         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1032         gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1033
1034         /* Apply the style to the inserted text. */
1035         gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1036         gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
1037
1038         gtk_text_buffer_apply_tag_by_name (priv->buffer,
1039                                            tag,
1040                                            &start_iter,
1041                                            &end_iter);
1042
1043         gtk_text_buffer_delete_mark (priv->buffer, mark);
1044 }
1045
1046 static void
1047 chat_view_maybe_append_fancy_header (GossipChatView *view,
1048                                      GossipMessage  *msg)
1049 {
1050         GossipChatViewPriv *priv;
1051         GossipContact      *sender;
1052         GossipContact      *my_contact;
1053         const gchar        *name;
1054         gboolean            header;
1055         GtkTextIter         iter;
1056         gchar              *tmp;
1057         const gchar        *tag;
1058         const gchar        *avatar_tag;
1059         const gchar        *line_top_tag;
1060         const gchar        *line_bottom_tag;
1061         gboolean            from_self;
1062         GdkPixbuf          *pixbuf = NULL;
1063         GdkPixbuf          *avatar = NULL;
1064
1065         priv = GET_PRIV (view);
1066
1067         sender = gossip_message_get_sender (msg);
1068         my_contact = gossip_get_own_contact_from_contact (sender);
1069         name = gossip_contact_get_name (sender);
1070         from_self = gossip_contact_equal (sender, my_contact);
1071
1072         gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
1073
1074         if (from_self) {
1075                 tag = "fancy-header-self";
1076                 line_top_tag = "fancy-line-top-self";
1077                 line_bottom_tag = "fancy-line-bottom-self";
1078         } else {
1079                 tag = "fancy-header-other";
1080                 line_top_tag = "fancy-line-top-other";
1081                 line_bottom_tag = "fancy-line-bottom-other";
1082         }
1083
1084         header = FALSE;
1085
1086         /* Only insert a header if the previously inserted block is not the same
1087          * as this one. This catches all the different cases:
1088          */
1089         if (priv->last_block_type != BLOCK_TYPE_SELF &&
1090             priv->last_block_type != BLOCK_TYPE_OTHER) {
1091                 header = TRUE;
1092         }
1093         else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
1094                 header = TRUE;
1095         }
1096         else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
1097                 header = TRUE;
1098         }
1099         else if (!from_self &&
1100                  (!priv->last_contact ||
1101                   !gossip_contact_equal (sender, priv->last_contact))) {
1102                 header = TRUE;
1103         }
1104
1105         if (!header) {
1106                 return;
1107         }
1108
1109         chat_view_append_spacing (view);
1110
1111         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1112         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1113                                                   &iter,
1114                                                   "\n",
1115                                                   -1,
1116                                                   line_top_tag,
1117                                                   NULL);
1118
1119         /* FIXME: we should have a cash of avatar pixbufs */
1120         pixbuf = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
1121         if (pixbuf) {
1122                 avatar = chat_view_pad_to_size (pixbuf, 32, 32, 6);
1123                 g_object_unref (pixbuf);
1124         }
1125
1126         if (avatar) {
1127                 GtkTextIter start;
1128
1129                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1130                 gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
1131
1132                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1133                 start = iter;
1134                 gtk_text_iter_backward_char (&start);
1135
1136                 if (from_self) {
1137                         gtk_text_buffer_apply_tag_by_name (priv->buffer,
1138                                                            "fancy-avatar-self",
1139                                                            &start, &iter);
1140                         avatar_tag = "fancy-header-self-avatar";
1141                 } else {
1142                         gtk_text_buffer_apply_tag_by_name (priv->buffer,
1143                                                            "fancy-avatar-other",
1144                                                            &start, &iter);
1145                         avatar_tag = "fancy-header-other-avatar";
1146                 }
1147
1148                 g_object_unref (avatar);
1149         } else {
1150                 avatar_tag = NULL;
1151         }
1152
1153         tmp = g_strdup_printf ("%s\n", name);
1154
1155         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1156         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1157                                                   &iter,
1158                                                   tmp,
1159                                                   -1,
1160                                                   tag,
1161                                                   avatar_tag,
1162                                                   NULL);
1163         g_free (tmp);
1164
1165         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1166         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1167                                                   &iter,
1168                                                   "\n",
1169                                                   -1,
1170                                                   line_bottom_tag,
1171                                                   NULL);
1172 }
1173
1174 static void
1175 chat_view_append_irc_action (GossipChatView *view,
1176                              GossipMessage  *msg)
1177 {
1178         GossipChatViewPriv *priv;
1179         GossipContact      *my_contact;
1180         GossipContact      *sender;
1181         const gchar        *name;
1182         GtkTextIter         iter;
1183         const gchar        *body;
1184         gchar              *tmp;
1185         const gchar        *tag;
1186
1187         priv = GET_PRIV (view);
1188
1189         gossip_debug (DEBUG_DOMAIN, "Add IRC action");
1190
1191         sender = gossip_message_get_sender (msg);
1192         my_contact = gossip_get_own_contact_from_contact (sender);
1193         name = gossip_contact_get_name (sender);
1194
1195         /* Skip the "/me ". */
1196         if (gossip_contact_equal (sender, my_contact)) {
1197                 tag = "irc-action-self";
1198         } else {
1199                 tag = "irc-action-other";
1200         }
1201
1202         if (priv->last_block_type != BLOCK_TYPE_SELF &&
1203             priv->last_block_type != BLOCK_TYPE_OTHER) {
1204                 chat_view_append_spacing (view);
1205         }
1206
1207         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1208
1209         tmp = g_strdup_printf (" * %s ", name);
1210         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1211                                                   &iter,
1212                                                   tmp,
1213                                                   -1,
1214                                                   "cut",
1215                                                   tag,
1216                                                   NULL);
1217         g_free (tmp);
1218
1219         body = gossip_message_get_body (msg);
1220         chat_view_append_text (view, body, tag);
1221 }
1222
1223 static void
1224 chat_view_append_fancy_action (GossipChatView *view,
1225                                GossipMessage  *msg)
1226 {
1227         GossipChatViewPriv *priv;
1228         GossipContact      *sender;
1229         GossipContact      *my_contact;
1230         const gchar        *name;
1231         const gchar        *body;
1232         GtkTextIter         iter;
1233         gchar              *tmp;
1234         const gchar        *tag;
1235         const gchar        *line_tag;
1236
1237         priv = GET_PRIV (view);
1238
1239         gossip_debug (DEBUG_DOMAIN, "Add fancy action");
1240
1241         sender = gossip_message_get_sender (msg);
1242         my_contact = gossip_get_own_contact_from_contact (sender);
1243         name = gossip_contact_get_name (sender);
1244
1245         if (gossip_contact_equal (sender, my_contact)) {
1246                 tag = "fancy-action-self";
1247                 line_tag = "fancy-line-self";
1248         } else {
1249                 tag = "fancy-action-other";
1250                 line_tag = "fancy-line-other";
1251         }
1252
1253         tmp = g_strdup_printf (" * %s ", name);
1254         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1255         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1256                                                   &iter,
1257                                                   tmp,
1258                                                   -1,
1259                                                   tag,
1260                                                   NULL);
1261         g_free (tmp);
1262
1263         body = gossip_message_get_body (msg);
1264         chat_view_append_text (view, body, tag);
1265 }
1266
1267 static void
1268 chat_view_append_irc_message (GossipChatView *view,
1269                               GossipMessage  *msg)
1270 {
1271         GossipChatViewPriv *priv;
1272         GossipContact      *sender;
1273         GossipContact      *my_contact;
1274         const gchar        *name;
1275         const gchar        *body;
1276         const gchar        *nick_tag;
1277         const gchar        *body_tag;
1278         GtkTextIter         iter;
1279         gchar              *tmp;
1280
1281         priv = GET_PRIV (view);
1282
1283         gossip_debug (DEBUG_DOMAIN, "Add IRC message");
1284
1285         body = gossip_message_get_body (msg);
1286         sender = gossip_message_get_sender (msg);
1287         my_contact = gossip_get_own_contact_from_contact (sender);
1288         name = gossip_contact_get_name (sender);
1289
1290         if (gossip_contact_equal (sender, my_contact)) {
1291                 nick_tag = "irc-nick-self";
1292                 body_tag = "irc-body-self";
1293         } else {
1294                 if (gossip_chat_should_highlight_nick (msg)) {
1295                         nick_tag = "irc-nick-highlight";
1296                 } else {
1297                         nick_tag = "irc-nick-other";
1298                 }
1299
1300                 body_tag = "irc-body-other";
1301         }
1302
1303         if (priv->last_block_type != BLOCK_TYPE_SELF &&
1304             priv->last_block_type != BLOCK_TYPE_OTHER) {
1305                 chat_view_append_spacing (view);
1306         }
1307
1308         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1309
1310         /* The nickname. */
1311         tmp = g_strdup_printf ("%s: ", name);
1312         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1313                                                   &iter,
1314                                                   tmp,
1315                                                   -1,
1316                                                   "cut",
1317                                                   nick_tag,
1318                                                   NULL);
1319         g_free (tmp);
1320
1321         /* The text body. */
1322         chat_view_append_text (view, body, body_tag);
1323 }
1324
1325 static void
1326 chat_view_append_fancy_message (GossipChatView *view,
1327                                 GossipMessage  *msg)
1328 {
1329         GossipChatViewPriv *priv;
1330         GossipContact      *sender;
1331         GossipContact      *my_contact;
1332         const gchar        *body;
1333         const gchar        *tag;
1334
1335         priv = GET_PRIV (view);
1336
1337         sender = gossip_message_get_sender (msg);
1338         my_contact = gossip_get_own_contact_from_contact (sender);
1339
1340         if (gossip_contact_equal (sender, my_contact)) {
1341                 tag = "fancy-body-self";
1342         } else {
1343                 tag = "fancy-body-other";
1344
1345                 /* FIXME: Might want to support nick highlighting here... */
1346         }
1347
1348         body = gossip_message_get_body (msg);
1349         chat_view_append_text (view, body, tag);
1350 }
1351
1352 static void
1353 chat_view_theme_changed_cb (GossipThemeManager *manager,
1354                             GossipChatView     *view)
1355 {
1356         GossipChatViewPriv *priv;
1357         gboolean            show_avatars = FALSE;
1358         gboolean            theme_rooms = FALSE;
1359
1360         priv = GET_PRIV (view);
1361
1362         priv->last_block_type = BLOCK_TYPE_NONE;
1363
1364         gossip_conf_get_bool (gossip_conf_get (),
1365                               GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
1366                               &theme_rooms);
1367         if (!theme_rooms && priv->is_group_chat) {
1368                 gossip_theme_manager_apply (manager, view, NULL);
1369         } else {
1370                 gossip_theme_manager_apply_saved (manager, view);
1371         }
1372
1373         /* Needed for now to update the "rise" property of the names to get it
1374          * vertically centered.
1375          */
1376         gossip_conf_get_bool (gossip_conf_get (),
1377                                GOSSIP_PREFS_UI_SHOW_AVATARS,
1378                                &show_avatars);
1379         gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
1380 }
1381
1382 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
1383  * pixbuf. Returns a new ref.
1384  */
1385 static GdkPixbuf *
1386 chat_view_pad_to_size (GdkPixbuf *pixbuf,
1387                        gint       width,
1388                        gint       height,
1389                        gint       extra_padding_right)
1390 {
1391         gint       src_width, src_height;
1392         GdkPixbuf *padded;
1393         gint       x_offset, y_offset;
1394
1395         src_width = gdk_pixbuf_get_width (pixbuf);
1396         src_height = gdk_pixbuf_get_height (pixbuf);
1397
1398         x_offset = (width - src_width) / 2;
1399         y_offset = (height - src_height) / 2;
1400
1401         padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
1402                                  TRUE, /* alpha */
1403                                  gdk_pixbuf_get_bits_per_sample (pixbuf),
1404                                  width + extra_padding_right,
1405                                  height);
1406
1407         gdk_pixbuf_fill (padded, 0);
1408
1409         gdk_pixbuf_copy_area (pixbuf,
1410                               0, /* source coords */
1411                               0,
1412                               src_width,
1413                               src_height,
1414                               padded,
1415                               x_offset, /* dest coords */
1416                               y_offset);
1417
1418         return padded;
1419 }
1420
1421 GossipChatView *
1422 gossip_chat_view_new (void)
1423 {
1424         return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
1425 }
1426
1427 /* The name is optional, if NULL, the sender for msg is used. */
1428 void
1429 gossip_chat_view_append_message (GossipChatView *view,
1430                                  GossipMessage  *msg)
1431 {
1432         GossipChatViewPriv *priv;
1433         GossipContact      *sender;
1434         const gchar        *body;
1435         gboolean            scroll_down;
1436
1437         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1438         g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
1439
1440         priv = GET_PRIV (view);
1441
1442         body = gossip_message_get_body (msg);
1443         if (!body) {
1444                 return;
1445         }
1446
1447         scroll_down = chat_view_is_scrolled_down (view);
1448
1449         chat_view_maybe_trim_buffer (view);
1450         chat_view_maybe_append_date_and_time (view, msg);
1451
1452         sender = gossip_message_get_sender (msg);
1453
1454         if (!priv->irc_style) {
1455                 chat_view_maybe_append_fancy_header (view, msg);
1456         }
1457
1458         if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
1459                 if (priv->irc_style) {
1460                         chat_view_append_irc_action (view, msg);
1461                 } else {
1462                         chat_view_append_fancy_action (view, msg);
1463                 }
1464         } else {
1465                 if (priv->irc_style) {
1466                         chat_view_append_irc_message (view, msg);
1467                 } else {
1468                         chat_view_append_fancy_message (view, msg);
1469                 }
1470         }
1471
1472         priv->last_block_type = BLOCK_TYPE_SELF;
1473
1474         /* Reset the last inserted contact, since it was from self. */
1475         if (priv->last_contact) {
1476                 g_object_unref (priv->last_contact);
1477                 priv->last_contact = NULL;
1478         }
1479
1480         if (scroll_down) {
1481                 gossip_chat_view_scroll_down (view);
1482         }
1483 }
1484
1485 void
1486 gossip_chat_view_append_event (GossipChatView *view,
1487                                const gchar    *str)
1488 {
1489         GossipChatViewPriv *priv;
1490         gboolean            bottom;
1491         GtkTextIter         iter;
1492         gchar              *msg;
1493         const gchar        *tag;
1494
1495         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1496         g_return_if_fail (!G_STR_EMPTY (str));
1497
1498         priv = GET_PRIV (view);
1499
1500         bottom = chat_view_is_scrolled_down (view);
1501
1502         chat_view_maybe_trim_buffer (view);
1503
1504         if (priv->irc_style) {
1505                 tag = "irc-event";
1506                 msg = g_strdup_printf (" - %s\n", str);
1507         } else {
1508                 tag = "fancy-event";
1509                 msg = g_strdup_printf (" - %s\n", str);
1510         }
1511
1512         if (priv->last_block_type != BLOCK_TYPE_EVENT) {
1513                 /* Comment out for now. */
1514                 /*chat_view_append_spacing (view);*/
1515         }
1516
1517         chat_view_maybe_append_date_and_time (view, NULL);
1518
1519         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1520
1521         gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
1522                                                   msg, -1,
1523                                                   tag,
1524                                                   NULL);
1525         g_free (msg);
1526
1527         if (bottom) {
1528                 gossip_chat_view_scroll_down (view);
1529         }
1530
1531         priv->last_block_type = BLOCK_TYPE_EVENT;
1532 }
1533
1534 void
1535 gossip_chat_view_append_button (GossipChatView *view,
1536                                 const gchar    *message,
1537                                 GtkWidget      *button1,
1538                                 GtkWidget      *button2)
1539 {
1540         GossipChatViewPriv   *priv;
1541         GtkTextChildAnchor   *anchor;
1542         GtkTextIter           iter;
1543         gboolean              bottom;
1544         const gchar          *tag;
1545
1546         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1547         g_return_if_fail (button1 != NULL);
1548
1549         priv = GET_PRIV (view);
1550
1551         if (priv->irc_style) {
1552                 tag = "irc-invite";
1553         } else {
1554                 tag = "fancy-invite";
1555         }
1556
1557         bottom = chat_view_is_scrolled_down (view);
1558
1559         chat_view_maybe_append_date_and_time (view, NULL);
1560
1561         if (message) {
1562                 chat_view_append_text (view, message, tag);
1563         }
1564
1565         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1566
1567         anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1568         gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
1569         gtk_widget_show (button1);
1570
1571         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1572                                                   &iter,
1573                                                   " ",
1574                                                   1,
1575                                                   tag,
1576                                                   NULL);
1577
1578         if (button2) {
1579                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1580                 
1581                 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1582                 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
1583                 gtk_widget_show (button2);
1584                 
1585                 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1586                                                           &iter,
1587                                                           " ",
1588                                                           1,
1589                                                           tag,
1590                                                           NULL);
1591         }
1592
1593         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1594         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1595                                                   &iter,
1596                                                   "\n\n",
1597                                                   2,
1598                                                   tag,
1599                                                   NULL);
1600
1601         if (bottom) {
1602                 gossip_chat_view_scroll_down (view);
1603         }
1604
1605         priv->last_block_type = BLOCK_TYPE_INVITE;
1606 }
1607
1608 void
1609 gossip_chat_view_scroll (GossipChatView *view,
1610                          gboolean        allow_scrolling)
1611 {
1612         GossipChatViewPriv *priv;
1613
1614         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1615
1616         priv = GET_PRIV (view);
1617
1618         priv->allow_scrolling = allow_scrolling;
1619
1620         gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
1621                       allow_scrolling ? "enabled" : "disabled");
1622 }
1623
1624 void
1625 gossip_chat_view_scroll_down (GossipChatView *view)
1626 {
1627         GossipChatViewPriv *priv;
1628         GtkTextBuffer      *buffer;
1629         GtkTextIter         iter;
1630         GtkTextMark        *mark;
1631
1632         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1633
1634         priv = GET_PRIV (view);
1635
1636         if (!priv->allow_scrolling) {
1637                 return;
1638         }
1639
1640         gossip_debug (DEBUG_DOMAIN, "Scrolling down");
1641
1642         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1643
1644         gtk_text_buffer_get_end_iter (buffer, &iter);
1645         mark = gtk_text_buffer_create_mark (buffer,
1646                                             NULL,
1647                                             &iter,
1648                                             FALSE);
1649
1650         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1651                                       mark,
1652                                       0.0,
1653                                       FALSE,
1654                                       0,
1655                                       0);
1656
1657         gtk_text_buffer_delete_mark (buffer, mark);
1658 }
1659
1660 gboolean
1661 gossip_chat_view_get_selection_bounds (GossipChatView *view,
1662                                        GtkTextIter    *start,
1663                                        GtkTextIter    *end)
1664 {
1665         GtkTextBuffer *buffer;
1666
1667         g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1668
1669         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1670
1671         return gtk_text_buffer_get_selection_bounds (buffer, start, end);
1672 }
1673
1674 void
1675 gossip_chat_view_clear (GossipChatView *view)
1676 {
1677         GtkTextBuffer      *buffer;
1678         GossipChatViewPriv *priv;
1679
1680         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1681
1682         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1683         gtk_text_buffer_set_text (buffer, "", -1);
1684
1685         /* We set these back to the initial values so we get
1686          * timestamps when clearing the window to know when
1687          * conversations start.
1688          */
1689         priv = GET_PRIV (view);
1690
1691         priv->last_block_type = BLOCK_TYPE_NONE;
1692         priv->last_timestamp = 0;
1693 }
1694
1695 gboolean
1696 gossip_chat_view_find_previous (GossipChatView *view,
1697                                 const gchar    *search_criteria,
1698                                 gboolean        new_search)
1699 {
1700         GossipChatViewPriv *priv;
1701         GtkTextBuffer      *buffer;
1702         GtkTextIter         iter_at_mark;
1703         GtkTextIter         iter_match_start;
1704         GtkTextIter         iter_match_end;
1705         gboolean            found;
1706         gboolean            from_start = FALSE;
1707
1708         g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1709         g_return_val_if_fail (search_criteria != NULL, FALSE);
1710
1711         priv = GET_PRIV (view);
1712
1713         buffer = priv->buffer;
1714
1715         if (G_STR_EMPTY (search_criteria)) {
1716                 if (priv->find_mark_previous) {
1717                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1718
1719                         gtk_text_buffer_move_mark (buffer,
1720                                                    priv->find_mark_previous,
1721                                                    &iter_at_mark);
1722                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1723                                                       priv->find_mark_previous,
1724                                                       0.0,
1725                                                       TRUE,
1726                                                       0.0,
1727                                                       0.0);
1728                         gtk_text_buffer_select_range (buffer,
1729                                                       &iter_at_mark,
1730                                                       &iter_at_mark);
1731                 }
1732
1733                 return FALSE;
1734         }
1735
1736         if (new_search) {
1737                 from_start = TRUE;
1738         }
1739
1740         if (priv->find_mark_previous) {
1741                 gtk_text_buffer_get_iter_at_mark (buffer,
1742                                                   &iter_at_mark,
1743                                                   priv->find_mark_previous);
1744         } else {
1745                 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1746                 from_start = TRUE;
1747         }
1748
1749         priv->find_last_direction = FALSE;
1750
1751         found = gossip_text_iter_backward_search (&iter_at_mark,
1752                                                   search_criteria,
1753                                                   &iter_match_start,
1754                                                   &iter_match_end,
1755                                                   NULL);
1756
1757         if (!found) {
1758                 gboolean result = FALSE;
1759
1760                 if (from_start) {
1761                         return result;
1762                 }
1763
1764                 /* Here we wrap around. */
1765                 if (!new_search && !priv->find_wrapped) {
1766                         priv->find_wrapped = TRUE;
1767                         result = gossip_chat_view_find_previous (view, 
1768                                                                  search_criteria, 
1769                                                                  FALSE);
1770                         priv->find_wrapped = FALSE;
1771                 }
1772
1773                 return result;
1774         }
1775
1776         /* Set new mark and show on screen */
1777         if (!priv->find_mark_previous) {
1778                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1779                                                                         &iter_match_start,
1780                                                                         TRUE);
1781         } else {
1782                 gtk_text_buffer_move_mark (buffer,
1783                                            priv->find_mark_previous,
1784                                            &iter_match_start);
1785         }
1786
1787         if (!priv->find_mark_next) {
1788                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1789                                                                     &iter_match_end,
1790                                                                     TRUE);
1791         } else {
1792                 gtk_text_buffer_move_mark (buffer,
1793                                            priv->find_mark_next,
1794                                            &iter_match_end);
1795         }
1796
1797         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1798                                       priv->find_mark_previous,
1799                                       0.0,
1800                                       TRUE,
1801                                       0.5,
1802                                       0.5);
1803
1804         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1805         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1806
1807         return TRUE;
1808 }
1809
1810 gboolean
1811 gossip_chat_view_find_next (GossipChatView *view,
1812                             const gchar    *search_criteria,
1813                             gboolean        new_search)
1814 {
1815         GossipChatViewPriv *priv;
1816         GtkTextBuffer      *buffer;
1817         GtkTextIter         iter_at_mark;
1818         GtkTextIter         iter_match_start;
1819         GtkTextIter         iter_match_end;
1820         gboolean            found;
1821         gboolean            from_start = FALSE;
1822
1823         g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1824         g_return_val_if_fail (search_criteria != NULL, FALSE);
1825
1826         priv = GET_PRIV (view);
1827
1828         buffer = priv->buffer;
1829
1830         if (G_STR_EMPTY (search_criteria)) {
1831                 if (priv->find_mark_next) {
1832                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1833
1834                         gtk_text_buffer_move_mark (buffer,
1835                                                    priv->find_mark_next,
1836                                                    &iter_at_mark);
1837                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1838                                                       priv->find_mark_next,
1839                                                       0.0,
1840                                                       TRUE,
1841                                                       0.0,
1842                                                       0.0);
1843                         gtk_text_buffer_select_range (buffer,
1844                                                       &iter_at_mark,
1845                                                       &iter_at_mark);
1846                 }
1847
1848                 return FALSE;
1849         }
1850
1851         if (new_search) {
1852                 from_start = TRUE;
1853         }
1854
1855         if (priv->find_mark_next) {
1856                 gtk_text_buffer_get_iter_at_mark (buffer,
1857                                                   &iter_at_mark,
1858                                                   priv->find_mark_next);
1859         } else {
1860                 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1861                 from_start = TRUE;
1862         }
1863
1864         priv->find_last_direction = TRUE;
1865
1866         found = gossip_text_iter_forward_search (&iter_at_mark,
1867                                                  search_criteria,
1868                                                  &iter_match_start,
1869                                                  &iter_match_end,
1870                                                  NULL);
1871
1872         if (!found) {
1873                 gboolean result = FALSE;
1874
1875                 if (from_start) {
1876                         return result;
1877                 }
1878
1879                 /* Here we wrap around. */
1880                 if (!new_search && !priv->find_wrapped) {
1881                         priv->find_wrapped = TRUE;
1882                         result = gossip_chat_view_find_next (view, 
1883                                                              search_criteria, 
1884                                                              FALSE);
1885                         priv->find_wrapped = FALSE;
1886                 }
1887
1888                 return result;
1889         }
1890
1891         /* Set new mark and show on screen */
1892         if (!priv->find_mark_next) {
1893                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1894                                                                &iter_match_end,
1895                                                                TRUE);
1896         } else {
1897                 gtk_text_buffer_move_mark (buffer,
1898                                            priv->find_mark_next,
1899                                            &iter_match_end);
1900         }
1901
1902         if (!priv->find_mark_previous) {
1903                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1904                                                                         &iter_match_start,
1905                                                                         TRUE);
1906         } else {
1907                 gtk_text_buffer_move_mark (buffer,
1908                                            priv->find_mark_previous,
1909                                            &iter_match_start);
1910         }
1911
1912         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1913                                       priv->find_mark_next,
1914                                       0.0,
1915                                       TRUE,
1916                                       0.5,
1917                                       0.5);
1918
1919         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1920         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1921
1922         return TRUE;
1923 }
1924
1925
1926 void
1927 gossip_chat_view_find_abilities (GossipChatView *view,
1928                                  const gchar    *search_criteria,
1929                                  gboolean       *can_do_previous,
1930                                  gboolean       *can_do_next)
1931 {
1932         GossipChatViewPriv *priv;
1933         GtkTextBuffer      *buffer;
1934         GtkTextIter         iter_at_mark;
1935         GtkTextIter         iter_match_start;
1936         GtkTextIter         iter_match_end;
1937
1938         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1939         g_return_if_fail (search_criteria != NULL);
1940         g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1941
1942         priv = GET_PRIV (view);
1943
1944         buffer = priv->buffer;
1945
1946         if (can_do_previous) {
1947                 if (priv->find_mark_previous) {
1948                         gtk_text_buffer_get_iter_at_mark (buffer,
1949                                                           &iter_at_mark,
1950                                                           priv->find_mark_previous);
1951                 } else {
1952                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1953                 }
1954                 
1955                 *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
1956                                                                      search_criteria,
1957                                                                      &iter_match_start,
1958                                                                      &iter_match_end,
1959                                                                      NULL);
1960         }
1961
1962         if (can_do_next) {
1963                 if (priv->find_mark_next) {
1964                         gtk_text_buffer_get_iter_at_mark (buffer,
1965                                                           &iter_at_mark,
1966                                                           priv->find_mark_next);
1967                 } else {
1968                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1969                 }
1970                 
1971                 *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
1972                                                                 search_criteria,
1973                                                                 &iter_match_start,
1974                                                                 &iter_match_end,
1975                                                                 NULL);
1976         }
1977 }
1978
1979 void
1980 gossip_chat_view_highlight (GossipChatView *view,
1981                             const gchar    *text)
1982 {
1983         GtkTextBuffer *buffer;
1984         GtkTextIter    iter;
1985         GtkTextIter    iter_start;
1986         GtkTextIter    iter_end;
1987         GtkTextIter    iter_match_start;
1988         GtkTextIter    iter_match_end;
1989         gboolean       found;
1990
1991         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1992
1993         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1994
1995         gtk_text_buffer_get_start_iter (buffer, &iter);
1996
1997         gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
1998         gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
1999                                             &iter_start,
2000                                             &iter_end);
2001
2002         if (G_STR_EMPTY (text)) {
2003                 return;
2004         }
2005
2006         while (1) {
2007                 found = gossip_text_iter_forward_search (&iter,
2008                                                          text,
2009                                                          &iter_match_start,
2010                                                          &iter_match_end,
2011                                                          NULL);
2012
2013                 if (!found) {
2014                         break;
2015                 }
2016
2017                 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
2018                                                    &iter_match_start,
2019                                                    &iter_match_end);
2020
2021                 iter = iter_match_end;
2022                 gtk_text_iter_forward_char (&iter);
2023         }
2024 }
2025
2026 void
2027 gossip_chat_view_copy_clipboard (GossipChatView *view)
2028 {
2029         GtkTextBuffer *buffer;
2030         GtkClipboard  *clipboard;
2031
2032         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2033
2034         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2035         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2036
2037         gtk_text_buffer_copy_clipboard (buffer, clipboard);
2038 }
2039
2040 gboolean
2041 gossip_chat_view_get_irc_style (GossipChatView *view)
2042 {
2043         GossipChatViewPriv *priv;
2044
2045         g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
2046
2047         priv = GET_PRIV (view);
2048
2049         return priv->irc_style;
2050 }
2051
2052 void
2053 gossip_chat_view_set_irc_style (GossipChatView *view,
2054                                 gboolean        irc_style)
2055 {
2056         GossipChatViewPriv *priv;
2057
2058         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2059
2060         priv = GET_PRIV (view);
2061
2062         priv->irc_style = irc_style;
2063 }
2064
2065 void
2066 gossip_chat_view_set_margin (GossipChatView *view,
2067                              gint            margin)
2068 {
2069         GossipChatViewPriv *priv;
2070
2071         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2072
2073         priv = GET_PRIV (view);
2074
2075         g_object_set (view,
2076                       "left-margin", margin,
2077                       "right-margin", margin,
2078                       NULL);
2079 }
2080
2081 GdkPixbuf *
2082 gossip_chat_view_get_smiley_image (GossipSmiley smiley)
2083 {
2084         static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
2085         static gboolean   inited = FALSE;
2086
2087         if (!inited) {
2088                 gint i;
2089
2090                 for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2091                         pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
2092                 }
2093
2094                 inited = TRUE;
2095         }
2096
2097         return pixbufs[smiley];
2098 }
2099
2100 const gchar *
2101 gossip_chat_view_get_smiley_text (GossipSmiley smiley)
2102 {
2103         gint i;
2104
2105         for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
2106                 if (smileys[i].smiley != smiley) {
2107                         continue;
2108                 }
2109
2110                 return smileys[i].pattern;
2111         }
2112
2113         return NULL;
2114 }
2115
2116 GtkWidget *
2117 gossip_chat_view_get_smiley_menu (GCallback    callback,
2118                                   gpointer     user_data,
2119                                   GtkTooltips *tooltips)
2120 {
2121         GtkWidget *menu;
2122         gint       x;
2123         gint       y;
2124         gint       i;
2125
2126         g_return_val_if_fail (callback != NULL, NULL);
2127
2128         menu = gtk_menu_new ();
2129
2130         for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2131                 GtkWidget   *item;
2132                 GtkWidget   *image;
2133                 GdkPixbuf   *pixbuf;
2134                 const gchar *smiley_text;
2135
2136                 pixbuf = gossip_chat_view_get_smiley_image (i);
2137                 if (!pixbuf) {
2138                         continue;
2139                 }
2140
2141                 image = gtk_image_new_from_pixbuf (pixbuf);
2142
2143                 item = gtk_image_menu_item_new_with_label ("");
2144                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2145
2146                 gtk_menu_attach (GTK_MENU (menu), item,
2147                                  x, x + 1, y, y + 1);
2148
2149                 smiley_text = gossip_chat_view_get_smiley_text (i);
2150
2151                 gtk_tooltips_set_tip (tooltips,
2152                                       item,
2153                                       smiley_text,
2154                                       NULL);
2155
2156                 g_object_set_data  (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
2157                 g_signal_connect (item, "activate", callback, user_data);
2158
2159                 if (x > 3) {
2160                         y++;
2161                         x = 0;
2162                 } else {
2163                         x++;
2164                 }
2165         }
2166
2167         gtk_widget_show_all (menu);
2168
2169         return menu;
2170 }
2171
2172 /* FIXME: Do we really need this? Better to do it internally only at setup time,
2173  * we will never change it on the fly.
2174  */
2175 void
2176 gossip_chat_view_set_is_group_chat (GossipChatView *view,
2177                                     gboolean        is_group_chat)
2178 {
2179         GossipChatViewPriv *priv;
2180         gboolean            theme_rooms = FALSE;
2181
2182         g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2183
2184         priv = GET_PRIV (view);
2185
2186         priv->is_group_chat = is_group_chat;
2187
2188         gossip_conf_get_bool (gossip_conf_get (),
2189                               GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
2190                               &theme_rooms);
2191
2192         if (!theme_rooms && is_group_chat) {
2193                 gossip_theme_manager_apply (gossip_theme_manager_get (),
2194                                             view,
2195                                             NULL);
2196         } else {
2197                 gossip_theme_manager_apply_saved (gossip_theme_manager_get (),
2198                                                   view);
2199         }
2200 }