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