]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-chat-view.c
af523180dadc178fa24fe87bc5fac8ac9718d98a
[empathy.git] / libempathy-gtk / empathy-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 <telepathy-glib/util.h>
43 #include <libmissioncontrol/mc-account.h>
44
45 #include <libempathy/empathy-utils.h>
46 #include <libempathy/empathy-debug.h>
47
48 #include "empathy-chat-view.h"
49 #include "empathy-chat.h"
50 #include "empathy-conf.h"
51 #include "empathy-preferences.h"
52 #include "empathy-theme-manager.h"
53 #include "empathy-ui-utils.h"
54 #include "empathy-smiley-manager.h"
55
56 #define DEBUG_DOMAIN "ChatView"
57
58 /* Number of seconds between timestamps when using normal mode, 5 minutes. */
59 #define TIMESTAMP_INTERVAL 300
60
61 #define MAX_LINES 800
62 #define MAX_SCROLL_TIME 0.4 /* seconds */
63 #define SCROLL_DELAY 33     /* milliseconds */
64
65 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CHAT_VIEW, EmpathyChatViewPriv))
66
67 struct _EmpathyChatViewPriv {
68         GtkTextBuffer *buffer;
69
70         EmpathyTheme   *theme;
71
72         time_t         last_timestamp;
73         EmpathyChatViewBlock last_block_type;
74
75         gboolean       allow_scrolling;
76         guint          scroll_timeout;
77         GTimer        *scroll_time;
78         gboolean       is_group_chat;
79
80         GtkTextMark   *find_mark_previous;
81         GtkTextMark   *find_mark_next;
82         gboolean       find_wrapped;
83         gboolean       find_last_direction;
84
85         /* This is for the group chat so we know if the "other" last contact
86          * changed, so we know whether to insert a header or not.
87          */
88         EmpathyContact *last_contact;
89
90         guint          notify_system_fonts_id;
91         guint          notify_show_avatars_id;
92 };
93
94 static void     empathy_chat_view_class_init          (EmpathyChatViewClass      *klass);
95 static void     empathy_chat_view_init                (EmpathyChatView           *view);
96 static void     chat_view_finalize                   (GObject                  *object);
97 static gboolean chat_view_drag_motion                (GtkWidget                *widget,
98                                                       GdkDragContext           *context,
99                                                       gint                      x,
100                                                       gint                      y,
101                                                       guint                     time);
102 static void     chat_view_size_allocate              (GtkWidget                *widget,
103                                                       GtkAllocation            *alloc);
104 static void     chat_view_setup_tags                 (EmpathyChatView           *view);
105 static void     chat_view_system_font_update         (EmpathyChatView           *view);
106 static void     chat_view_notify_system_font_cb      (EmpathyConf               *conf,
107                                                       const gchar              *key,
108                                                       gpointer                  user_data);
109 static void     chat_view_notify_show_avatars_cb     (EmpathyConf               *conf,
110                                                       const gchar              *key,
111                                                       gpointer                  user_data);
112 static void     chat_view_populate_popup             (EmpathyChatView           *view,
113                                                       GtkMenu                  *menu,
114                                                       gpointer                  user_data);
115 static gboolean chat_view_event_cb                   (EmpathyChatView           *view,
116                                                       GdkEventMotion           *event,
117                                                       GtkTextTag               *tag);
118 static gboolean chat_view_url_event_cb               (GtkTextTag               *tag,
119                                                       GObject                  *object,
120                                                       GdkEvent                 *event,
121                                                       GtkTextIter              *iter,
122                                                       GtkTextBuffer            *buffer);
123 static void     chat_view_open_address_cb            (GtkMenuItem              *menuitem,
124                                                       const gchar              *url);
125 static void     chat_view_copy_address_cb            (GtkMenuItem              *menuitem,
126                                                       const gchar              *url);
127 static void     chat_view_clear_view_cb              (GtkMenuItem              *menuitem,
128                                                       EmpathyChatView           *view);
129 static gboolean chat_view_is_scrolled_down           (EmpathyChatView           *view);
130 static void     chat_view_theme_changed_cb           (EmpathyThemeManager       *manager,
131                                                       EmpathyChatView           *view);
132 static void     chat_view_theme_notify_cb            (EmpathyTheme              *theme,
133                                                       GParamSpec                *param,
134                                                       EmpathyChatView           *view);
135
136 G_DEFINE_TYPE (EmpathyChatView, empathy_chat_view, GTK_TYPE_TEXT_VIEW);
137
138 static void
139 empathy_chat_view_class_init (EmpathyChatViewClass *klass)
140 {
141         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
142         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
143
144         object_class->finalize = chat_view_finalize;
145         widget_class->size_allocate = chat_view_size_allocate;
146         widget_class->drag_motion = chat_view_drag_motion; 
147
148         g_type_class_add_private (object_class, sizeof (EmpathyChatViewPriv));
149 }
150
151 static void
152 empathy_chat_view_init (EmpathyChatView *view)
153 {
154         EmpathyChatViewPriv *priv;
155         gboolean            show_avatars;
156
157         priv = GET_PRIV (view);
158
159         priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
160
161         priv->last_block_type = EMPATHY_CHAT_VIEW_BLOCK_NONE;
162         priv->last_timestamp = 0;
163
164         priv->allow_scrolling = TRUE;
165
166         priv->is_group_chat = FALSE;
167
168         g_object_set (view,
169                       "wrap-mode", GTK_WRAP_WORD_CHAR,
170                       "editable", FALSE,
171                       "cursor-visible", FALSE,
172                       NULL);
173
174         priv->notify_system_fonts_id =
175                 empathy_conf_notify_add (empathy_conf_get (),
176                                          "/desktop/gnome/interface/document_font_name",
177                                          chat_view_notify_system_font_cb,
178                                          view);
179         chat_view_system_font_update (view);
180
181         priv->notify_show_avatars_id =
182                 empathy_conf_notify_add (empathy_conf_get (),
183                                          EMPATHY_PREFS_UI_SHOW_AVATARS,
184                                          chat_view_notify_show_avatars_cb,
185                                          view);
186
187         chat_view_setup_tags (view);
188
189         empathy_theme_manager_apply_saved (empathy_theme_manager_get (), view);
190
191         show_avatars = FALSE;
192         empathy_conf_get_bool (empathy_conf_get (),
193                                EMPATHY_PREFS_UI_SHOW_AVATARS,
194                                &show_avatars);
195
196         empathy_theme_set_show_avatars (priv->theme, show_avatars);
197
198         g_signal_connect (view,
199                           "populate-popup",
200                           G_CALLBACK (chat_view_populate_popup),
201                           NULL);
202
203         g_signal_connect_object (empathy_theme_manager_get (),
204                                  "theme-changed",
205                                  G_CALLBACK (chat_view_theme_changed_cb),
206                                  view,
207                                  0);
208 }
209
210 static void
211 chat_view_finalize (GObject *object)
212 {
213         EmpathyChatView     *view;
214         EmpathyChatViewPriv *priv;
215
216         view = EMPATHY_CHAT_VIEW (object);
217         priv = GET_PRIV (view);
218
219         empathy_debug (DEBUG_DOMAIN, "finalize: %p", object);
220
221         empathy_conf_notify_remove (empathy_conf_get (), priv->notify_system_fonts_id);
222         empathy_conf_notify_remove (empathy_conf_get (), priv->notify_show_avatars_id);
223
224         if (priv->last_contact) {
225                 g_object_unref (priv->last_contact);
226         }
227         if (priv->scroll_time) {
228                 g_timer_destroy (priv->scroll_time);
229         }
230         if (priv->scroll_timeout) {
231                 g_source_remove (priv->scroll_timeout);
232         }
233
234         if (priv->theme) {
235                 g_signal_handlers_disconnect_by_func (priv->theme,
236                                                       chat_view_theme_notify_cb,
237                                                       view);
238                 g_object_unref (priv->theme);
239         }
240
241         G_OBJECT_CLASS (empathy_chat_view_parent_class)->finalize (object);
242 }
243
244 static gboolean
245 chat_view_drag_motion (GtkWidget        *widget,
246                        GdkDragContext   *context,
247                        gint              x,
248                        gint              y,
249                        guint             time)
250 {
251         /* Don't handle drag motion, since we don't want the view to scroll as
252          * the result of dragging something across it.
253          */
254
255         return FALSE;
256 }
257
258 static void
259 chat_view_size_allocate (GtkWidget     *widget,
260                          GtkAllocation *alloc)
261 {
262         gboolean down;
263
264         down = chat_view_is_scrolled_down (EMPATHY_CHAT_VIEW (widget));
265
266         GTK_WIDGET_CLASS (empathy_chat_view_parent_class)->size_allocate (widget, alloc);
267
268         if (down) {
269                 GtkAdjustment *adj;
270
271                 adj = GTK_TEXT_VIEW (widget)->vadjustment;
272                 gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
273         }
274 }
275
276 static void
277 chat_view_setup_tags (EmpathyChatView *view)
278 {
279         EmpathyChatViewPriv *priv;
280         GtkTextTag         *tag;
281
282         priv = GET_PRIV (view);
283
284         gtk_text_buffer_create_tag (priv->buffer,
285                                     "cut",
286                                     NULL);
287
288         /* FIXME: Move to the theme and come up with something that looks a bit
289          * nicer.
290          */
291         gtk_text_buffer_create_tag (priv->buffer,
292                                     "highlight",
293                                     "background", "yellow",
294                                     NULL);
295
296         tag = gtk_text_buffer_create_tag (priv->buffer,
297                                           "link",
298                                           NULL);
299
300         g_signal_connect (tag,
301                           "event",
302                           G_CALLBACK (chat_view_url_event_cb),
303                           priv->buffer);
304
305         g_signal_connect (view,
306                           "motion-notify-event",
307                           G_CALLBACK (chat_view_event_cb),
308                           tag);
309 }
310
311 static void
312 chat_view_system_font_update (EmpathyChatView *view)
313 {
314         PangoFontDescription *font_description = NULL;
315         gchar                *font_name;
316
317         if (empathy_conf_get_string (empathy_conf_get (),
318                                      "/desktop/gnome/interface/document_font_name",
319                                      &font_name) && font_name) {
320                 font_description = pango_font_description_from_string (font_name);
321                 g_free (font_name);
322         } else {
323                 font_description = NULL;
324         }
325
326         gtk_widget_modify_font (GTK_WIDGET (view), font_description);
327
328         if (font_description) {
329                 pango_font_description_free (font_description);
330         }
331 }
332
333 static void
334 chat_view_notify_system_font_cb (EmpathyConf  *conf,
335                                  const gchar *key,
336                                  gpointer     user_data)
337 {
338         EmpathyChatView *view;
339         EmpathyChatViewPriv *priv;
340         gboolean        show_avatars = FALSE;
341
342         view = user_data;
343         priv = GET_PRIV (view);
344
345         chat_view_system_font_update (view);
346
347         /* Ugly, again, to adjust the vertical position of the nick... Will fix
348          * this when reworking the theme manager so that view register
349          * themselves with it instead of the other way around.
350          */
351         empathy_conf_get_bool (conf,
352                                EMPATHY_PREFS_UI_SHOW_AVATARS,
353                                &show_avatars);
354
355         empathy_theme_set_show_avatars (priv->theme, show_avatars);
356 }
357
358 static void
359 chat_view_notify_show_avatars_cb (EmpathyConf  *conf,
360                                   const gchar *key,
361                                   gpointer     user_data)
362 {
363         EmpathyChatView     *view;
364         EmpathyChatViewPriv *priv;
365         gboolean            show_avatars = FALSE;
366
367         view = user_data;
368         priv = GET_PRIV (view);
369
370         empathy_conf_get_bool (conf, key, &show_avatars);
371
372         empathy_theme_set_show_avatars (priv->theme, show_avatars);
373 }
374
375 static void
376 chat_view_populate_popup (EmpathyChatView *view,
377                           GtkMenu        *menu,
378                           gpointer        user_data)
379 {
380         EmpathyChatViewPriv *priv;
381         GtkTextTagTable    *table;
382         GtkTextTag         *tag;
383         gint                x, y;
384         GtkTextIter         iter, start, end;
385         GtkWidget          *item;
386         gchar              *str = NULL;
387
388         priv = GET_PRIV (view);
389
390         /* Clear menu item */
391         if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
392                 item = gtk_menu_item_new ();
393                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
394                 gtk_widget_show (item);
395
396                 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
397                 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
398                 gtk_widget_show (item);
399
400                 g_signal_connect (item,
401                                   "activate",
402                                   G_CALLBACK (chat_view_clear_view_cb),
403                                   view);
404         }
405
406         /* Link context menu items */
407         table = gtk_text_buffer_get_tag_table (priv->buffer);
408         tag = gtk_text_tag_table_lookup (table, "link");
409
410         gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
411
412         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
413                                                GTK_TEXT_WINDOW_WIDGET,
414                                                x, y,
415                                                &x, &y);
416
417         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
418
419         start = end = iter;
420
421         if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
422             gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
423                 str = gtk_text_buffer_get_text (priv->buffer,
424                                                 &start, &end, FALSE);
425         }
426
427         if (G_STR_EMPTY (str)) {
428                 g_free (str);
429                 return;
430         }
431
432         /* NOTE: Set data just to get the string freed when not needed. */
433         g_object_set_data_full (G_OBJECT (menu),
434                                 "url", str,
435                                 (GDestroyNotify) g_free);
436
437         item = gtk_menu_item_new ();
438         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
439         gtk_widget_show (item);
440
441         item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
442         g_signal_connect (item,
443                           "activate",
444                           G_CALLBACK (chat_view_copy_address_cb),
445                           str);
446         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
447         gtk_widget_show (item);
448
449         item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
450         g_signal_connect (item,
451                           "activate",
452                           G_CALLBACK (chat_view_open_address_cb),
453                           str);
454         gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
455         gtk_widget_show (item);
456 }
457
458 static gboolean
459 chat_view_event_cb (EmpathyChatView *view,
460                     GdkEventMotion *event,
461                     GtkTextTag     *tag)
462 {
463         static GdkCursor  *hand = NULL;
464         static GdkCursor  *beam = NULL;
465         GtkTextWindowType  type;
466         GtkTextIter        iter;
467         GdkWindow         *win;
468         gint               x, y, buf_x, buf_y;
469
470         type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
471                                               event->window);
472
473         if (type != GTK_TEXT_WINDOW_TEXT) {
474                 return FALSE;
475         }
476
477         /* Get where the pointer really is. */
478         win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
479         if (!win) {
480                 return FALSE;
481         }
482
483         gdk_window_get_pointer (win, &x, &y, NULL);
484
485         /* Get the iter where the cursor is at */
486         gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
487                                                x, y,
488                                                &buf_x, &buf_y);
489
490         gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
491                                             &iter,
492                                             buf_x, buf_y);
493
494         if (gtk_text_iter_has_tag (&iter, tag)) {
495                 if (!hand) {
496                         hand = gdk_cursor_new (GDK_HAND2);
497                         beam = gdk_cursor_new (GDK_XTERM);
498                 }
499                 gdk_window_set_cursor (win, hand);
500         } else {
501                 if (!beam) {
502                         beam = gdk_cursor_new (GDK_XTERM);
503                 }
504                 gdk_window_set_cursor (win, beam);
505         }
506
507         return FALSE;
508 }
509
510 static gboolean
511 chat_view_url_event_cb (GtkTextTag    *tag,
512                         GObject       *object,
513                         GdkEvent      *event,
514                         GtkTextIter   *iter,
515                         GtkTextBuffer *buffer)
516 {
517         GtkTextIter  start, end;
518         gchar       *str;
519
520         /* If the link is being selected, don't do anything. */
521         gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
522         if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
523                 return FALSE;
524         }
525
526         if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
527                 start = end = *iter;
528
529                 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
530                     gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
531                         str = gtk_text_buffer_get_text (buffer,
532                                                         &start,
533                                                         &end,
534                                                         FALSE);
535
536                         empathy_url_show (str);
537                         g_free (str);
538                 }
539         }
540
541         return FALSE;
542 }
543
544 static void
545 chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
546 {
547         empathy_url_show (url);
548 }
549
550 static void
551 chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
552 {
553         GtkClipboard *clipboard;
554
555         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
556         gtk_clipboard_set_text (clipboard, url, -1);
557
558         clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
559         gtk_clipboard_set_text (clipboard, url, -1);
560 }
561
562 static void
563 chat_view_clear_view_cb (GtkMenuItem *menuitem, EmpathyChatView *view)
564 {
565         empathy_chat_view_clear (view);
566 }
567
568 static gboolean
569 chat_view_is_scrolled_down (EmpathyChatView *view)
570 {
571         GtkWidget *sw;
572
573         sw = gtk_widget_get_parent (GTK_WIDGET (view));
574         if (GTK_IS_SCROLLED_WINDOW (sw)) {
575                 GtkAdjustment *vadj;
576
577                 vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
578
579                 if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
580                         return FALSE;
581                 }
582         }
583
584         return TRUE;
585 }
586
587 static void
588 chat_view_maybe_trim_buffer (EmpathyChatView *view)
589 {
590         EmpathyChatViewPriv *priv;
591         GtkTextIter         top, bottom;
592         gint                line;
593         gint                remove;
594         GtkTextTagTable    *table;
595         GtkTextTag         *tag;
596
597         priv = GET_PRIV (view);
598
599         gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
600         line = gtk_text_iter_get_line (&bottom);
601         if (line < MAX_LINES) {
602                 return;
603         }
604
605         remove = line - MAX_LINES;
606         gtk_text_buffer_get_start_iter (priv->buffer, &top);
607
608         bottom = top;
609         if (!gtk_text_iter_forward_lines (&bottom, remove)) {
610                 return;
611         }
612
613         /* Track backwords to a place where we can safely cut, we don't do it in
614          * the middle of a tag.
615          */
616         table = gtk_text_buffer_get_tag_table (priv->buffer);
617         tag = gtk_text_tag_table_lookup (table, "cut");
618         if (!tag) {
619                 return;
620         }
621
622         if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
623                 return;
624         }
625
626         if (!gtk_text_iter_equal (&top, &bottom)) {
627                 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
628         }
629 }
630
631 static void
632 chat_view_theme_changed_cb (EmpathyThemeManager *manager,
633                             EmpathyChatView     *view)
634 {
635         EmpathyChatViewPriv *priv;
636         gboolean            show_avatars = FALSE;
637         gboolean            theme_rooms = FALSE;
638
639         priv = GET_PRIV (view);
640
641         priv->last_block_type = EMPATHY_CHAT_VIEW_BLOCK_NONE;
642
643         empathy_conf_get_bool (empathy_conf_get (),
644                               EMPATHY_PREFS_CHAT_THEME_CHAT_ROOM,
645                               &theme_rooms);
646         if (!theme_rooms && priv->is_group_chat) {
647                 empathy_theme_manager_apply (manager, view, NULL);
648         } else {
649                 empathy_theme_manager_apply_saved (manager, view);
650         }
651
652         /* Needed for now to update the "rise" property of the names to get it
653          * vertically centered.
654          */
655         empathy_conf_get_bool (empathy_conf_get (),
656                                EMPATHY_PREFS_UI_SHOW_AVATARS,
657                                &show_avatars);
658         empathy_theme_set_show_avatars (priv->theme, show_avatars);
659 }
660
661 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
662  * pixbuf. Returns a new ref.
663  */
664 static GdkPixbuf *
665 chat_view_pad_to_size (GdkPixbuf *pixbuf,
666                        gint       width,
667                        gint       height,
668                        gint       extra_padding_right)
669 {
670         gint       src_width, src_height;
671         GdkPixbuf *padded;
672         gint       x_offset, y_offset;
673
674         src_width = gdk_pixbuf_get_width (pixbuf);
675         src_height = gdk_pixbuf_get_height (pixbuf);
676
677         x_offset = (width - src_width) / 2;
678         y_offset = (height - src_height) / 2;
679
680         padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
681                                  TRUE, /* alpha */
682                                  gdk_pixbuf_get_bits_per_sample (pixbuf),
683                                  width + extra_padding_right,
684                                  height);
685
686         gdk_pixbuf_fill (padded, 0);
687
688         gdk_pixbuf_copy_area (pixbuf,
689                               0, /* source coords */
690                               0,
691                               src_width,
692                               src_height,
693                               padded,
694                               x_offset, /* dest coords */
695                               y_offset);
696
697         return padded;
698 }
699
700 typedef struct {
701         GdkPixbuf *pixbuf;
702         gchar     *token;
703 } AvatarData;
704
705 static void
706 chat_view_avatar_cache_data_free (gpointer ptr)
707 {
708         AvatarData *data = ptr;
709
710         g_object_unref (data->pixbuf);
711         g_free (data->token);
712         g_slice_free (AvatarData, data);
713 }
714
715 GdkPixbuf *
716 empathy_chat_view_get_avatar_pixbuf_with_cache (EmpathyContact *contact)
717 {
718         AvatarData        *data;
719         EmpathyAvatar     *avatar;
720         GdkPixbuf         *tmp_pixbuf;
721         GdkPixbuf         *pixbuf = NULL;
722
723         /* Check if avatar is in cache and if it's up to date */
724         avatar = empathy_contact_get_avatar (contact);
725         data = g_object_get_data (G_OBJECT (contact), "chat-view-avatar-cache");
726         if (data) {
727                 if (avatar && !tp_strdiff (avatar->token, data->token)) {
728                         /* We have the avatar in cache */
729                         return data->pixbuf;
730                 }
731         }
732
733         /* Avatar not in cache, create pixbuf */
734         tmp_pixbuf = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
735         if (tmp_pixbuf) {
736                 pixbuf = chat_view_pad_to_size (tmp_pixbuf, 32, 32, 6);
737                 g_object_unref (tmp_pixbuf);
738         }
739         if (!pixbuf) {
740                 return NULL;
741         }
742
743         /* Insert new pixbuf in cache */
744         data = g_slice_new0 (AvatarData);
745         data->token = g_strdup (avatar->token);
746         data->pixbuf = pixbuf;
747
748         g_object_set_data_full (G_OBJECT (contact), "chat-view-avatar-cache",
749                                 data, chat_view_avatar_cache_data_free);
750
751         return data->pixbuf;
752 }
753
754 EmpathyChatView *
755 empathy_chat_view_new (void)
756 {
757         return g_object_new (EMPATHY_TYPE_CHAT_VIEW, NULL);
758 }
759
760 void
761 empathy_chat_view_append_message (EmpathyChatView *view,
762                                   EmpathyMessage  *msg)
763 {
764         EmpathyChatViewPriv *priv = GET_PRIV (view);
765         EmpathyContact      *sender;
766         gboolean             bottom;
767         gboolean             from_self;
768
769         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
770         g_return_if_fail (EMPATHY_IS_MESSAGE (msg));
771
772         if (!empathy_message_get_body (msg)) {
773                 return;
774         }
775
776         bottom = chat_view_is_scrolled_down (view);
777         sender = empathy_message_get_sender (msg);
778         from_self = empathy_contact_is_user (sender);
779         
780         chat_view_maybe_trim_buffer (view);
781
782         empathy_theme_append_message (priv->theme, view, msg);
783
784         if (bottom) {
785                 empathy_chat_view_scroll_down (view);
786         }
787 }
788
789 void
790 empathy_chat_view_append_event (EmpathyChatView *view,
791                                const gchar    *str)
792 {
793         EmpathyChatViewPriv *priv;
794         gboolean            bottom;
795
796         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
797         g_return_if_fail (!G_STR_EMPTY (str));
798
799         priv = GET_PRIV (view);
800
801         bottom = chat_view_is_scrolled_down (view);
802
803         chat_view_maybe_trim_buffer (view);
804
805         empathy_theme_append_event (priv->theme, view, str);
806
807         if (bottom) {
808                 empathy_chat_view_scroll_down (view);
809         }
810
811         priv->last_block_type = EMPATHY_CHAT_VIEW_BLOCK_EVENT;
812 }
813
814 void
815 empathy_chat_view_append_button (EmpathyChatView *view,
816                                 const gchar    *message,
817                                 GtkWidget      *button1,
818                                 GtkWidget      *button2)
819 {
820         EmpathyChatViewPriv   *priv;
821         GtkTextChildAnchor   *anchor;
822         GtkTextIter           iter;
823         gboolean              bottom;
824         const gchar          *tag;
825
826         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
827         g_return_if_fail (button1 != NULL);
828
829         priv = GET_PRIV (view);
830
831         tag = "invite";
832
833         bottom = chat_view_is_scrolled_down (view);
834
835         empathy_theme_append_timestamp (priv->theme, view, NULL, TRUE, TRUE);
836
837         if (message) {
838                 empathy_theme_append_text (priv->theme, view, message, tag, NULL);
839         }
840
841         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
842
843         anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
844         gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
845         gtk_widget_show (button1);
846
847         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
848                                                   &iter,
849                                                   " ",
850                                                   1,
851                                                   tag,
852                                                   NULL);
853
854         if (button2) {
855                 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
856                 
857                 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
858                 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
859                 gtk_widget_show (button2);
860                 
861                 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
862                                                           &iter,
863                                                           " ",
864                                                           1,
865                                                           tag,
866                                                           NULL);
867         }
868
869         gtk_text_buffer_get_end_iter (priv->buffer, &iter);
870         gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
871                                                   &iter,
872                                                   "\n\n",
873                                                   2,
874                                                   tag,
875                                                   NULL);
876
877         if (bottom) {
878                 empathy_chat_view_scroll_down (view);
879         }
880
881         priv->last_block_type = EMPATHY_CHAT_VIEW_BLOCK_INVITE;
882 }
883
884 void
885 empathy_chat_view_scroll (EmpathyChatView *view,
886                          gboolean        allow_scrolling)
887 {
888         EmpathyChatViewPriv *priv;
889
890         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
891
892         priv = GET_PRIV (view);
893
894         priv->allow_scrolling = allow_scrolling;
895
896         empathy_debug (DEBUG_DOMAIN, "Scrolling %s",
897                       allow_scrolling ? "enabled" : "disabled");
898 }
899
900 /* Code stolen from pidgin/gtkimhtml.c */
901 static gboolean
902 chat_view_scroll_cb (EmpathyChatView *view)
903 {
904         EmpathyChatViewPriv *priv;
905         GtkAdjustment      *adj;
906         gdouble             max_val;
907
908         priv = GET_PRIV (view);
909         adj = GTK_TEXT_VIEW (view)->vadjustment;
910         max_val = adj->upper - adj->page_size;
911
912         g_return_val_if_fail (priv->scroll_time != NULL, FALSE);
913
914         if (g_timer_elapsed (priv->scroll_time, NULL) > MAX_SCROLL_TIME) {
915                 /* time's up. jump to the end and kill the timer */
916                 gtk_adjustment_set_value (adj, max_val);
917                 g_timer_destroy (priv->scroll_time);
918                 priv->scroll_time = NULL;
919                 priv->scroll_timeout = 0;
920                 return FALSE;
921         }
922
923         /* scroll by 1/3rd the remaining distance */
924         gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + ((max_val - gtk_adjustment_get_value (adj)) / 3));
925         return TRUE;
926 }
927
928 void
929 empathy_chat_view_scroll_down (EmpathyChatView *view)
930 {
931         EmpathyChatViewPriv *priv;
932
933         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
934
935         priv = GET_PRIV (view);
936
937         if (!priv->allow_scrolling) {
938                 return;
939         }
940
941         empathy_debug (DEBUG_DOMAIN, "Scrolling down");
942
943         if (priv->scroll_time) {
944                 g_timer_reset (priv->scroll_time);
945         } else {
946                 priv->scroll_time = g_timer_new();
947         }
948         if (!priv->scroll_timeout) {
949                 priv->scroll_timeout = g_timeout_add (SCROLL_DELAY,
950                                                       (GSourceFunc) chat_view_scroll_cb,
951                                                       view);
952         }
953 }
954
955 gboolean
956 empathy_chat_view_get_selection_bounds (EmpathyChatView *view,
957                                        GtkTextIter    *start,
958                                        GtkTextIter    *end)
959 {
960         GtkTextBuffer *buffer;
961
962         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), FALSE);
963
964         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
965
966         return gtk_text_buffer_get_selection_bounds (buffer, start, end);
967 }
968
969 void
970 empathy_chat_view_clear (EmpathyChatView *view)
971 {
972         GtkTextBuffer      *buffer;
973         EmpathyChatViewPriv *priv;
974
975         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
976
977         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
978         gtk_text_buffer_set_text (buffer, "", -1);
979
980         /* We set these back to the initial values so we get
981          * timestamps when clearing the window to know when
982          * conversations start.
983          */
984         priv = GET_PRIV (view);
985
986         priv->last_block_type = EMPATHY_CHAT_VIEW_BLOCK_NONE;
987         priv->last_timestamp = 0;
988 }
989
990 gboolean
991 empathy_chat_view_find_previous (EmpathyChatView *view,
992                                 const gchar    *search_criteria,
993                                 gboolean        new_search)
994 {
995         EmpathyChatViewPriv *priv;
996         GtkTextBuffer      *buffer;
997         GtkTextIter         iter_at_mark;
998         GtkTextIter         iter_match_start;
999         GtkTextIter         iter_match_end;
1000         gboolean            found;
1001         gboolean            from_start = FALSE;
1002
1003         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), FALSE);
1004         g_return_val_if_fail (search_criteria != NULL, FALSE);
1005
1006         priv = GET_PRIV (view);
1007
1008         buffer = priv->buffer;
1009
1010         if (G_STR_EMPTY (search_criteria)) {
1011                 if (priv->find_mark_previous) {
1012                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1013
1014                         gtk_text_buffer_move_mark (buffer,
1015                                                    priv->find_mark_previous,
1016                                                    &iter_at_mark);
1017                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1018                                                       priv->find_mark_previous,
1019                                                       0.0,
1020                                                       TRUE,
1021                                                       0.0,
1022                                                       0.0);
1023                         gtk_text_buffer_select_range (buffer,
1024                                                       &iter_at_mark,
1025                                                       &iter_at_mark);
1026                 }
1027
1028                 return FALSE;
1029         }
1030
1031         if (new_search) {
1032                 from_start = TRUE;
1033         }
1034
1035         if (priv->find_mark_previous) {
1036                 gtk_text_buffer_get_iter_at_mark (buffer,
1037                                                   &iter_at_mark,
1038                                                   priv->find_mark_previous);
1039         } else {
1040                 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1041                 from_start = TRUE;
1042         }
1043
1044         priv->find_last_direction = FALSE;
1045
1046         found = empathy_text_iter_backward_search (&iter_at_mark,
1047                                                   search_criteria,
1048                                                   &iter_match_start,
1049                                                   &iter_match_end,
1050                                                   NULL);
1051
1052         if (!found) {
1053                 gboolean result = FALSE;
1054
1055                 if (from_start) {
1056                         return result;
1057                 }
1058
1059                 /* Here we wrap around. */
1060                 if (!new_search && !priv->find_wrapped) {
1061                         priv->find_wrapped = TRUE;
1062                         result = empathy_chat_view_find_previous (view, 
1063                                                                  search_criteria, 
1064                                                                  FALSE);
1065                         priv->find_wrapped = FALSE;
1066                 }
1067
1068                 return result;
1069         }
1070
1071         /* Set new mark and show on screen */
1072         if (!priv->find_mark_previous) {
1073                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1074                                                                         &iter_match_start,
1075                                                                         TRUE);
1076         } else {
1077                 gtk_text_buffer_move_mark (buffer,
1078                                            priv->find_mark_previous,
1079                                            &iter_match_start);
1080         }
1081
1082         if (!priv->find_mark_next) {
1083                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1084                                                                     &iter_match_end,
1085                                                                     TRUE);
1086         } else {
1087                 gtk_text_buffer_move_mark (buffer,
1088                                            priv->find_mark_next,
1089                                            &iter_match_end);
1090         }
1091
1092         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1093                                       priv->find_mark_previous,
1094                                       0.0,
1095                                       TRUE,
1096                                       0.5,
1097                                       0.5);
1098
1099         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1100         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1101
1102         return TRUE;
1103 }
1104
1105 gboolean
1106 empathy_chat_view_find_next (EmpathyChatView *view,
1107                             const gchar    *search_criteria,
1108                             gboolean        new_search)
1109 {
1110         EmpathyChatViewPriv *priv;
1111         GtkTextBuffer      *buffer;
1112         GtkTextIter         iter_at_mark;
1113         GtkTextIter         iter_match_start;
1114         GtkTextIter         iter_match_end;
1115         gboolean            found;
1116         gboolean            from_start = FALSE;
1117
1118         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), FALSE);
1119         g_return_val_if_fail (search_criteria != NULL, FALSE);
1120
1121         priv = GET_PRIV (view);
1122
1123         buffer = priv->buffer;
1124
1125         if (G_STR_EMPTY (search_criteria)) {
1126                 if (priv->find_mark_next) {
1127                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1128
1129                         gtk_text_buffer_move_mark (buffer,
1130                                                    priv->find_mark_next,
1131                                                    &iter_at_mark);
1132                         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1133                                                       priv->find_mark_next,
1134                                                       0.0,
1135                                                       TRUE,
1136                                                       0.0,
1137                                                       0.0);
1138                         gtk_text_buffer_select_range (buffer,
1139                                                       &iter_at_mark,
1140                                                       &iter_at_mark);
1141                 }
1142
1143                 return FALSE;
1144         }
1145
1146         if (new_search) {
1147                 from_start = TRUE;
1148         }
1149
1150         if (priv->find_mark_next) {
1151                 gtk_text_buffer_get_iter_at_mark (buffer,
1152                                                   &iter_at_mark,
1153                                                   priv->find_mark_next);
1154         } else {
1155                 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1156                 from_start = TRUE;
1157         }
1158
1159         priv->find_last_direction = TRUE;
1160
1161         found = empathy_text_iter_forward_search (&iter_at_mark,
1162                                                  search_criteria,
1163                                                  &iter_match_start,
1164                                                  &iter_match_end,
1165                                                  NULL);
1166
1167         if (!found) {
1168                 gboolean result = FALSE;
1169
1170                 if (from_start) {
1171                         return result;
1172                 }
1173
1174                 /* Here we wrap around. */
1175                 if (!new_search && !priv->find_wrapped) {
1176                         priv->find_wrapped = TRUE;
1177                         result = empathy_chat_view_find_next (view, 
1178                                                              search_criteria, 
1179                                                              FALSE);
1180                         priv->find_wrapped = FALSE;
1181                 }
1182
1183                 return result;
1184         }
1185
1186         /* Set new mark and show on screen */
1187         if (!priv->find_mark_next) {
1188                 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1189                                                                &iter_match_end,
1190                                                                TRUE);
1191         } else {
1192                 gtk_text_buffer_move_mark (buffer,
1193                                            priv->find_mark_next,
1194                                            &iter_match_end);
1195         }
1196
1197         if (!priv->find_mark_previous) {
1198                 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1199                                                                         &iter_match_start,
1200                                                                         TRUE);
1201         } else {
1202                 gtk_text_buffer_move_mark (buffer,
1203                                            priv->find_mark_previous,
1204                                            &iter_match_start);
1205         }
1206
1207         gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1208                                       priv->find_mark_next,
1209                                       0.0,
1210                                       TRUE,
1211                                       0.5,
1212                                       0.5);
1213
1214         gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1215         gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1216
1217         return TRUE;
1218 }
1219
1220
1221 void
1222 empathy_chat_view_find_abilities (EmpathyChatView *view,
1223                                  const gchar    *search_criteria,
1224                                  gboolean       *can_do_previous,
1225                                  gboolean       *can_do_next)
1226 {
1227         EmpathyChatViewPriv *priv;
1228         GtkTextBuffer      *buffer;
1229         GtkTextIter         iter_at_mark;
1230         GtkTextIter         iter_match_start;
1231         GtkTextIter         iter_match_end;
1232
1233         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1234         g_return_if_fail (search_criteria != NULL);
1235         g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1236
1237         priv = GET_PRIV (view);
1238
1239         buffer = priv->buffer;
1240
1241         if (can_do_previous) {
1242                 if (priv->find_mark_previous) {
1243                         gtk_text_buffer_get_iter_at_mark (buffer,
1244                                                           &iter_at_mark,
1245                                                           priv->find_mark_previous);
1246                 } else {
1247                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1248                 }
1249                 
1250                 *can_do_previous = empathy_text_iter_backward_search (&iter_at_mark,
1251                                                                      search_criteria,
1252                                                                      &iter_match_start,
1253                                                                      &iter_match_end,
1254                                                                      NULL);
1255         }
1256
1257         if (can_do_next) {
1258                 if (priv->find_mark_next) {
1259                         gtk_text_buffer_get_iter_at_mark (buffer,
1260                                                           &iter_at_mark,
1261                                                           priv->find_mark_next);
1262                 } else {
1263                         gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1264                 }
1265                 
1266                 *can_do_next = empathy_text_iter_forward_search (&iter_at_mark,
1267                                                                 search_criteria,
1268                                                                 &iter_match_start,
1269                                                                 &iter_match_end,
1270                                                                 NULL);
1271         }
1272 }
1273
1274 void
1275 empathy_chat_view_highlight (EmpathyChatView *view,
1276                              const gchar     *text)
1277 {
1278         GtkTextBuffer *buffer;
1279         GtkTextIter    iter;
1280         GtkTextIter    iter_start;
1281         GtkTextIter    iter_end;
1282         GtkTextIter    iter_match_start;
1283         GtkTextIter    iter_match_end;
1284         gboolean       found;
1285
1286         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1287
1288         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1289
1290         gtk_text_buffer_get_start_iter (buffer, &iter);
1291
1292         gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
1293         gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
1294                                             &iter_start,
1295                                             &iter_end);
1296
1297         if (G_STR_EMPTY (text)) {
1298                 return;
1299         }
1300
1301         while (1) {
1302                 found = empathy_text_iter_forward_search (&iter,
1303                                                          text,
1304                                                          &iter_match_start,
1305                                                          &iter_match_end,
1306                                                          NULL);
1307
1308                 if (!found) {
1309                         break;
1310                 }
1311
1312                 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
1313                                                    &iter_match_start,
1314                                                    &iter_match_end);
1315
1316                 iter = iter_match_end;
1317                 gtk_text_iter_forward_char (&iter);
1318         }
1319 }
1320
1321 void
1322 empathy_chat_view_copy_clipboard (EmpathyChatView *view)
1323 {
1324         GtkTextBuffer *buffer;
1325         GtkClipboard  *clipboard;
1326
1327         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1328
1329         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1330         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1331
1332         gtk_text_buffer_copy_clipboard (buffer, clipboard);
1333 }
1334
1335 EmpathyTheme *
1336 empathy_chat_view_get_theme (EmpathyChatView *view)
1337 {
1338         EmpathyChatViewPriv *priv;
1339
1340         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), NULL);
1341
1342         priv = GET_PRIV (view);
1343
1344         return priv->theme;
1345 }
1346
1347 static void
1348 chat_view_theme_notify_cb (EmpathyTheme    *theme,
1349                            GParamSpec      *param,
1350                            EmpathyChatView *view)
1351 {
1352         empathy_theme_update_view (theme, view);
1353 }
1354
1355 void
1356 empathy_chat_view_set_theme (EmpathyChatView *view, EmpathyTheme *theme)
1357 {
1358         EmpathyChatViewPriv *priv;
1359
1360         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1361         g_return_if_fail (EMPATHY_IS_THEME (theme));
1362
1363         priv = GET_PRIV (view);
1364
1365         if (priv->theme) {
1366                 g_signal_handlers_disconnect_by_func (priv->theme,
1367                                                       chat_view_theme_notify_cb,
1368                                                       view);
1369                 g_object_unref (priv->theme);
1370         }
1371
1372         priv->theme = g_object_ref (theme);
1373
1374         empathy_theme_update_view (theme, view);
1375         g_signal_connect (priv->theme, "notify",
1376                           G_CALLBACK (chat_view_theme_notify_cb),
1377                           view);
1378
1379         /* FIXME: Redraw all messages using the new theme */
1380 }
1381
1382 void
1383 empathy_chat_view_set_margin (EmpathyChatView *view,
1384                              gint            margin)
1385 {
1386         EmpathyChatViewPriv *priv;
1387
1388         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1389
1390         priv = GET_PRIV (view);
1391
1392         g_object_set (view,
1393                       "left-margin", margin,
1394                       "right-margin", margin,
1395                       NULL);
1396 }
1397
1398 GtkWidget *
1399 empathy_chat_view_get_smiley_menu (GCallback    callback,
1400                                    gpointer     user_data)
1401 {
1402         EmpathySmileyManager *smiley_manager;
1403         GSList               *smileys, *l;
1404         GtkWidget            *menu;
1405         gint                  x = 0;
1406         gint                  y = 0;
1407
1408         g_return_val_if_fail (callback != NULL, NULL);
1409
1410         menu = gtk_menu_new ();
1411
1412         smiley_manager = empathy_smiley_manager_new ();
1413         smileys = empathy_smiley_manager_get_all (smiley_manager);
1414         for (l = smileys; l; l = l->next) {
1415                 EmpathySmiley *smiley;
1416                 GtkWidget     *item;
1417                 GtkWidget     *image;
1418
1419                 smiley = l->data;
1420                 image = gtk_image_new_from_pixbuf (smiley->pixbuf);
1421
1422                 item = gtk_image_menu_item_new_with_label ("");
1423                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1424
1425                 gtk_menu_attach (GTK_MENU (menu), item,
1426                                  x, x + 1, y, y + 1);
1427
1428                 gtk_widget_set_tooltip_text (item, smiley->str);
1429
1430                 g_object_set_data  (G_OBJECT (item), "smiley_text", smiley->str);
1431                 g_signal_connect (item, "activate", callback, user_data);
1432
1433                 if (x > 3) {
1434                         y++;
1435                         x = 0;
1436                 } else {
1437                         x++;
1438                 }
1439         }
1440         g_object_unref (smiley_manager);
1441
1442         gtk_widget_show_all (menu);
1443
1444         return menu;
1445 }
1446
1447 /* FIXME: Do we really need this? Better to do it internally only at setup time,
1448  * we will never change it on the fly.
1449  */
1450 void
1451 empathy_chat_view_set_is_group_chat (EmpathyChatView *view,
1452                                     gboolean        is_group_chat)
1453 {
1454         EmpathyChatViewPriv *priv;
1455         gboolean            theme_rooms = FALSE;
1456
1457         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1458
1459         priv = GET_PRIV (view);
1460
1461         priv->is_group_chat = is_group_chat;
1462
1463         empathy_conf_get_bool (empathy_conf_get (),
1464                               EMPATHY_PREFS_CHAT_THEME_CHAT_ROOM,
1465                               &theme_rooms);
1466
1467         if (!theme_rooms && is_group_chat) {
1468                 empathy_theme_manager_apply (empathy_theme_manager_get (),
1469                                             view,
1470                                             NULL);
1471         } else {
1472                 empathy_theme_manager_apply_saved (empathy_theme_manager_get (),
1473                                                   view);
1474         }
1475 }
1476
1477 time_t
1478 empathy_chat_view_get_last_timestamp (EmpathyChatView *view)
1479 {
1480         EmpathyChatViewPriv *priv;
1481
1482         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), 0);
1483
1484         priv = GET_PRIV (view);
1485
1486         return priv->last_timestamp;
1487 }
1488
1489 void
1490 empathy_chat_view_set_last_timestamp (EmpathyChatView *view,
1491                                      time_t          timestamp)
1492 {
1493         EmpathyChatViewPriv *priv;
1494
1495         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1496
1497         priv = GET_PRIV (view);
1498
1499         priv->last_timestamp = timestamp;
1500 }
1501
1502 EmpathyChatViewBlock
1503 empathy_chat_view_get_last_block_type (EmpathyChatView *view)
1504 {
1505         EmpathyChatViewPriv *priv;
1506
1507         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), 0);
1508
1509         priv = GET_PRIV (view);
1510
1511         return priv->last_block_type;
1512 }
1513
1514 void
1515 empathy_chat_view_set_last_block_type (EmpathyChatView      *view, 
1516                                        EmpathyChatViewBlock  block_type)
1517 {
1518         EmpathyChatViewPriv *priv;
1519
1520         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1521
1522         priv = GET_PRIV (view);
1523
1524         priv->last_block_type = block_type;
1525 }
1526
1527 EmpathyContact *
1528 empathy_chat_view_get_last_contact (EmpathyChatView *view)
1529 {
1530         EmpathyChatViewPriv *priv;
1531
1532         g_return_val_if_fail (EMPATHY_IS_CHAT_VIEW (view), NULL);
1533
1534         priv = GET_PRIV (view);
1535
1536         return priv->last_contact;
1537 }
1538
1539 void
1540 empathy_chat_view_set_last_contact (EmpathyChatView *view, EmpathyContact *contact)
1541 {
1542         EmpathyChatViewPriv *priv;
1543
1544         g_return_if_fail (EMPATHY_IS_CHAT_VIEW (view));
1545
1546         priv = GET_PRIV (view);
1547
1548         if (priv->last_contact) {
1549                 g_object_unref (priv->last_contact);
1550                 priv->last_contact = NULL;
1551         }
1552
1553         if (contact) {
1554                 priv->last_contact = g_object_ref (contact);
1555         }
1556 }
1557