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