1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2002-2007 Imendio AB
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.
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.
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.
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Richard Hult <richard@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
27 #include <sys/types.h>
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>
42 #include <libmissioncontrol/mc-account.h>
44 #include <libempathy/gossip-utils.h>
45 #include <libempathy/gossip-debug.h>
46 #include <libempathy/gossip-conf.h>
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"
54 #define DEBUG_DOMAIN "ChatView"
56 /* Number of seconds between timestamps when using normal mode, 5 minutes. */
57 #define TIMESTAMP_INTERVAL 300
61 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
72 struct _GossipChatViewPriv {
73 GtkTextBuffer *buffer;
76 time_t last_timestamp;
77 BlockType last_block_type;
79 gboolean allow_scrolling;
80 gboolean is_group_chat;
82 GtkTextMark *find_mark_previous;
83 GtkTextMark *find_mark_next;
84 gboolean find_wrapped;
85 gboolean find_last_direction;
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.
90 GossipContact *last_contact;
92 guint notify_system_fonts_id;
93 guint notify_show_avatars_id;
99 } GossipSmileyPattern;
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(" },
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+" }
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,
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,
207 static void chat_view_notify_show_avatars_cb (GossipConf *conf,
210 static void chat_view_populate_popup (GossipChatView *view,
213 static gboolean chat_view_event_cb (GossipChatView *view,
214 GdkEventMotion *event,
216 static gboolean chat_view_url_event_cb (GtkTextTag *tag,
220 GtkTextBuffer *buffer);
221 static void chat_view_open_address_cb (GtkMenuItem *menuitem,
223 static void chat_view_copy_address_cb (GtkMenuItem *menuitem,
225 static void chat_view_clear_view_cb (GtkMenuItem *menuitem,
226 GossipChatView *view);
227 static void chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
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,
235 static void chat_view_append_spacing (GossipChatView *view);
236 static void chat_view_append_text (GossipChatView *view,
239 static void chat_view_maybe_append_fancy_header (GossipChatView *view,
241 static void chat_view_append_irc_action (GossipChatView *view,
243 static void chat_view_append_fancy_action (GossipChatView *view,
245 static void chat_view_append_irc_message (GossipChatView *view,
247 static void chat_view_append_fancy_message (GossipChatView *view,
249 static GdkPixbuf *chat_view_pad_to_size (GdkPixbuf *pixbuf,
252 gint extra_padding_right);
254 G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
257 gossip_chat_view_class_init (GossipChatViewClass *klass)
259 GObjectClass *object_class = G_OBJECT_CLASS (klass);
260 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
262 object_class->finalize = chat_view_finalize;
263 widget_class->size_allocate = chat_view_size_allocate;
264 widget_class->drag_motion = chat_view_drag_motion;
266 g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
270 gossip_chat_view_init (GossipChatView *view)
272 GossipChatViewPriv *priv;
273 gboolean show_avatars;
275 priv = GET_PRIV (view);
277 priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
279 priv->last_block_type = BLOCK_TYPE_NONE;
280 priv->last_timestamp = 0;
282 priv->allow_scrolling = TRUE;
284 priv->is_group_chat = FALSE;
287 "wrap-mode", GTK_WRAP_WORD_CHAR,
289 "cursor-visible", FALSE,
292 priv->notify_system_fonts_id =
293 gossip_conf_notify_add (gossip_conf_get (),
294 "/desktop/gnome/interface/document_font_name",
295 chat_view_notify_system_font_cb,
297 chat_view_system_font_update (view);
299 priv->notify_show_avatars_id =
300 gossip_conf_notify_add (gossip_conf_get (),
301 GOSSIP_PREFS_UI_SHOW_AVATARS,
302 chat_view_notify_show_avatars_cb,
305 chat_view_setup_tags (view);
307 gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
309 show_avatars = FALSE;
310 gossip_conf_get_bool (gossip_conf_get (),
311 GOSSIP_PREFS_UI_SHOW_AVATARS,
314 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
317 g_signal_connect (view,
319 G_CALLBACK (chat_view_populate_popup),
322 g_signal_connect_object (gossip_theme_manager_get (),
324 G_CALLBACK (chat_view_theme_changed_cb),
330 chat_view_finalize (GObject *object)
332 GossipChatView *view;
333 GossipChatViewPriv *priv;
335 view = GOSSIP_CHAT_VIEW (object);
336 priv = GET_PRIV (view);
338 gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
340 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
341 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
343 if (priv->last_contact) {
344 g_object_unref (priv->last_contact);
347 G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
351 chat_view_drag_motion (GtkWidget *widget,
352 GdkDragContext *context,
357 /* Don't handle drag motion, since we don't want the view to scroll as
358 * the result of dragging something across it.
365 chat_view_size_allocate (GtkWidget *widget,
366 GtkAllocation *alloc)
370 down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
372 GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
375 gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
380 chat_view_setup_tags (GossipChatView *view)
382 GossipChatViewPriv *priv;
385 priv = GET_PRIV (view);
387 gtk_text_buffer_create_tag (priv->buffer,
391 /* FIXME: Move to the theme and come up with something that looks a bit
394 gtk_text_buffer_create_tag (priv->buffer,
396 "background", "yellow",
399 tag = gtk_text_buffer_create_tag (priv->buffer,
403 g_signal_connect (tag,
405 G_CALLBACK (chat_view_url_event_cb),
408 g_signal_connect (view,
409 "motion-notify-event",
410 G_CALLBACK (chat_view_event_cb),
415 chat_view_system_font_update (GossipChatView *view)
417 PangoFontDescription *font_description = NULL;
420 if (gossip_conf_get_string (gossip_conf_get (),
421 "/desktop/gnome/interface/document_font_name",
422 &font_name) && font_name) {
423 font_description = pango_font_description_from_string (font_name);
426 font_description = NULL;
429 gtk_widget_modify_font (GTK_WIDGET (view), font_description);
431 if (font_description) {
432 pango_font_description_free (font_description);
437 chat_view_notify_system_font_cb (GossipConf *conf,
441 GossipChatView *view;
442 gboolean show_avatars = FALSE;
446 chat_view_system_font_update (view);
448 /* Ugly, again, to adjust the vertical position of the nick... Will fix
449 * this when reworking the theme manager so that view register
450 * themselves with it instead of the other way around.
452 gossip_conf_get_bool (conf,
453 GOSSIP_PREFS_UI_SHOW_AVATARS,
456 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
461 chat_view_notify_show_avatars_cb (GossipConf *conf,
465 GossipChatView *view;
466 GossipChatViewPriv *priv;
467 gboolean show_avatars = FALSE;
470 priv = GET_PRIV (view);
472 gossip_conf_get_bool (conf, key, &show_avatars);
474 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
479 chat_view_populate_popup (GossipChatView *view,
483 GossipChatViewPriv *priv;
484 GtkTextTagTable *table;
487 GtkTextIter iter, start, end;
491 priv = GET_PRIV (view);
493 /* Clear menu item */
494 if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
495 item = gtk_menu_item_new ();
496 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
497 gtk_widget_show (item);
499 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
500 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
501 gtk_widget_show (item);
503 g_signal_connect (item,
505 G_CALLBACK (chat_view_clear_view_cb),
509 /* Link context menu items */
510 table = gtk_text_buffer_get_tag_table (priv->buffer);
511 tag = gtk_text_tag_table_lookup (table, "link");
513 gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
515 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
516 GTK_TEXT_WINDOW_WIDGET,
520 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
524 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
525 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
526 str = gtk_text_buffer_get_text (priv->buffer,
527 &start, &end, FALSE);
530 if (G_STR_EMPTY (str)) {
535 /* NOTE: Set data just to get the string freed when not needed. */
536 g_object_set_data_full (G_OBJECT (menu),
538 (GDestroyNotify) g_free);
540 item = gtk_menu_item_new ();
541 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
542 gtk_widget_show (item);
544 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
545 g_signal_connect (item,
547 G_CALLBACK (chat_view_copy_address_cb),
549 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
550 gtk_widget_show (item);
552 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
553 g_signal_connect (item,
555 G_CALLBACK (chat_view_open_address_cb),
557 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
558 gtk_widget_show (item);
562 chat_view_event_cb (GossipChatView *view,
563 GdkEventMotion *event,
566 static GdkCursor *hand = NULL;
567 static GdkCursor *beam = NULL;
568 GtkTextWindowType type;
571 gint x, y, buf_x, buf_y;
573 type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
576 if (type != GTK_TEXT_WINDOW_TEXT) {
580 /* Get where the pointer really is. */
581 win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
586 gdk_window_get_pointer (win, &x, &y, NULL);
588 /* Get the iter where the cursor is at */
589 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
593 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
597 if (gtk_text_iter_has_tag (&iter, tag)) {
599 hand = gdk_cursor_new (GDK_HAND2);
600 beam = gdk_cursor_new (GDK_XTERM);
602 gdk_window_set_cursor (win, hand);
605 beam = gdk_cursor_new (GDK_XTERM);
607 gdk_window_set_cursor (win, beam);
614 chat_view_url_event_cb (GtkTextTag *tag,
618 GtkTextBuffer *buffer)
620 GtkTextIter start, end;
623 /* If the link is being selected, don't do anything. */
624 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
625 if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
629 if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
632 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
633 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
634 str = gtk_text_buffer_get_text (buffer,
639 gossip_url_show (str);
648 chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
650 gossip_url_show (url);
654 chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
656 GtkClipboard *clipboard;
658 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
659 gtk_clipboard_set_text (clipboard, url, -1);
661 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
662 gtk_clipboard_set_text (clipboard, url, -1);
666 chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
668 gossip_chat_view_clear (view);
672 chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
681 gboolean use_smileys = FALSE;
683 gossip_conf_get_bool (gossip_conf_get (),
684 GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
688 gtk_text_buffer_insert (buf, iter, str, -1);
693 gint smileys_index[G_N_ELEMENTS (smileys)];
698 memset (smileys_index, 0, sizeof (smileys_index));
706 c = g_utf8_get_char (p);
708 if (match != -1 && g_unichar_isspace (c)) {
714 if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
717 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
718 /* Only try to match if we already have
719 * a beginning match for the pattern, or
720 * if it's the first character in the
721 * pattern, if it's not in the middle of
724 if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
725 smileys_index[i] > 0) &&
726 smileys[i].pattern[smileys_index[i]] == c) {
730 if (!smileys[i].pattern[smileys_index[i]]) {
734 smileys_index[i] = 0;
740 p = g_utf8_next_char (p);
744 gtk_text_buffer_insert (buf, iter, str, -1);
748 start = p - strlen (smileys[match].pattern);
752 gtk_text_buffer_insert (buf, iter, str, len);
755 pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
756 gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
758 gtk_text_buffer_insert (buf, iter, " ", 1);
760 str = g_utf8_find_next_char (p, NULL);
765 chat_view_is_scrolled_down (GossipChatView *view)
769 sw = gtk_widget_get_parent (GTK_WIDGET (view));
770 if (GTK_IS_SCROLLED_WINDOW (sw)) {
773 vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
775 if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
784 chat_view_maybe_trim_buffer (GossipChatView *view)
786 GossipChatViewPriv *priv;
787 GtkTextIter top, bottom;
790 GtkTextTagTable *table;
793 priv = GET_PRIV (view);
795 gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
796 line = gtk_text_iter_get_line (&bottom);
797 if (line < MAX_LINES) {
801 remove = line - MAX_LINES;
802 gtk_text_buffer_get_start_iter (priv->buffer, &top);
805 if (!gtk_text_iter_forward_lines (&bottom, remove)) {
809 /* Track backwords to a place where we can safely cut, we don't do it in
810 * the middle of a tag.
812 table = gtk_text_buffer_get_tag_table (priv->buffer);
813 tag = gtk_text_tag_table_lookup (table, "cut");
818 if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
822 if (!gtk_text_iter_equal (&top, &bottom)) {
823 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
828 chat_view_maybe_append_date_and_time (GossipChatView *view,
831 GossipChatViewPriv *priv;
834 GDate *date, *last_date;
836 gboolean append_date, append_time;
839 priv = GET_PRIV (view);
841 if (priv->irc_style) {
847 if (priv->last_block_type == BLOCK_TYPE_TIME) {
851 str = g_string_new (NULL);
855 timestamp = gossip_message_get_timestamp (msg);
858 if (timestamp <= 0) {
859 timestamp = gossip_time_get_current ();
862 date = g_date_new ();
863 g_date_set_time (date, timestamp);
865 last_date = g_date_new ();
866 g_date_set_time (last_date, priv->last_timestamp);
871 if (g_date_compare (date, last_date) > 0) {
876 if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
880 if (append_time || append_date) {
881 chat_view_append_spacing (view);
883 g_string_append (str, "- ");
889 g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
890 g_string_append (str, buf);
893 g_string_append (str, ", ");
898 g_date_free (last_date);
903 tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
904 g_string_append (str, tmp);
908 if (append_time || append_date) {
909 g_string_append (str, " -\n");
911 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
912 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
918 priv->last_block_type = BLOCK_TYPE_TIME;
919 priv->last_timestamp = timestamp;
922 g_string_free (str, TRUE);
926 chat_view_append_spacing (GossipChatView *view)
928 GossipChatViewPriv *priv;
932 priv = GET_PRIV (view);
934 if (priv->irc_style) {
937 tag = "fancy-spacing";
940 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
941 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
951 chat_view_append_text (GossipChatView *view,
955 GossipChatViewPriv *priv;
956 GtkTextIter start_iter, end_iter;
961 const gchar *link_tag;
963 priv = GET_PRIV (view);
965 if (priv->irc_style) {
966 link_tag = "irc-link";
968 link_tag = "fancy-link";
971 gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
972 mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
974 start = g_array_new (FALSE, FALSE, sizeof (gint));
975 end = g_array_new (FALSE, FALSE, sizeof (gint));
977 num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
979 if (num_matches == 0) {
980 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
981 chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
987 for (i = 0; i < num_matches; i++) {
988 s = g_array_index (start, gint, i);
989 e = g_array_index (end, gint, i);
992 tmp = gossip_substring (body, last, s);
994 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
995 chat_view_insert_text_with_emoticons (priv->buffer,
1001 tmp = gossip_substring (body, s, e);
1003 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1004 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1017 if (e < strlen (body)) {
1018 tmp = gossip_substring (body, e, strlen (body));
1020 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1021 chat_view_insert_text_with_emoticons (priv->buffer,
1028 g_array_free (start, TRUE);
1029 g_array_free (end, TRUE);
1031 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1032 gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1034 /* Apply the style to the inserted text. */
1035 gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1036 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
1038 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1043 gtk_text_buffer_delete_mark (priv->buffer, mark);
1047 chat_view_maybe_append_fancy_header (GossipChatView *view,
1050 GossipChatViewPriv *priv;
1051 GossipContact *sender;
1052 GossipContact *my_contact;
1058 const gchar *avatar_tag;
1059 const gchar *line_top_tag;
1060 const gchar *line_bottom_tag;
1062 GdkPixbuf *pixbuf = NULL;
1063 GdkPixbuf *avatar = NULL;
1065 priv = GET_PRIV (view);
1067 sender = gossip_message_get_sender (msg);
1068 my_contact = gossip_contact_get_user (sender);
1069 name = gossip_contact_get_name (sender);
1070 from_self = gossip_contact_equal (sender, my_contact);
1072 gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
1075 tag = "fancy-header-self";
1076 line_top_tag = "fancy-line-top-self";
1077 line_bottom_tag = "fancy-line-bottom-self";
1079 tag = "fancy-header-other";
1080 line_top_tag = "fancy-line-top-other";
1081 line_bottom_tag = "fancy-line-bottom-other";
1086 /* Only insert a header if the previously inserted block is not the same
1087 * as this one. This catches all the different cases:
1089 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1090 priv->last_block_type != BLOCK_TYPE_OTHER) {
1093 else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
1096 else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
1099 else if (!from_self &&
1100 (!priv->last_contact ||
1101 !gossip_contact_equal (sender, priv->last_contact))) {
1109 chat_view_append_spacing (view);
1111 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1112 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1119 /* FIXME: we should have a cash of avatar pixbufs */
1120 pixbuf = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
1122 avatar = chat_view_pad_to_size (pixbuf, 32, 32, 6);
1123 g_object_unref (pixbuf);
1129 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1130 gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
1132 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1134 gtk_text_iter_backward_char (&start);
1137 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1138 "fancy-avatar-self",
1140 avatar_tag = "fancy-header-self-avatar";
1142 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1143 "fancy-avatar-other",
1145 avatar_tag = "fancy-header-other-avatar";
1148 g_object_unref (avatar);
1153 tmp = g_strdup_printf ("%s\n", name);
1155 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1156 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1165 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1166 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1175 chat_view_append_irc_action (GossipChatView *view,
1178 GossipChatViewPriv *priv;
1179 GossipContact *my_contact;
1180 GossipContact *sender;
1187 priv = GET_PRIV (view);
1189 gossip_debug (DEBUG_DOMAIN, "Add IRC action");
1191 sender = gossip_message_get_sender (msg);
1192 my_contact = gossip_contact_get_user (sender);
1193 name = gossip_contact_get_name (sender);
1195 /* Skip the "/me ". */
1196 if (gossip_contact_equal (sender, my_contact)) {
1197 tag = "irc-action-self";
1199 tag = "irc-action-other";
1202 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1203 priv->last_block_type != BLOCK_TYPE_OTHER) {
1204 chat_view_append_spacing (view);
1207 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1209 tmp = g_strdup_printf (" * %s ", name);
1210 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1219 body = gossip_message_get_body (msg);
1220 chat_view_append_text (view, body, tag);
1224 chat_view_append_fancy_action (GossipChatView *view,
1227 GossipChatViewPriv *priv;
1228 GossipContact *sender;
1229 GossipContact *my_contact;
1235 const gchar *line_tag;
1237 priv = GET_PRIV (view);
1239 gossip_debug (DEBUG_DOMAIN, "Add fancy action");
1241 sender = gossip_message_get_sender (msg);
1242 my_contact = gossip_contact_get_user (sender);
1243 name = gossip_contact_get_name (sender);
1245 if (gossip_contact_equal (sender, my_contact)) {
1246 tag = "fancy-action-self";
1247 line_tag = "fancy-line-self";
1249 tag = "fancy-action-other";
1250 line_tag = "fancy-line-other";
1253 tmp = g_strdup_printf (" * %s ", name);
1254 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1255 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1263 body = gossip_message_get_body (msg);
1264 chat_view_append_text (view, body, tag);
1268 chat_view_append_irc_message (GossipChatView *view,
1271 GossipChatViewPriv *priv;
1272 GossipContact *sender;
1273 GossipContact *my_contact;
1276 const gchar *nick_tag;
1277 const gchar *body_tag;
1281 priv = GET_PRIV (view);
1283 gossip_debug (DEBUG_DOMAIN, "Add IRC message");
1285 body = gossip_message_get_body (msg);
1286 sender = gossip_message_get_sender (msg);
1287 my_contact = gossip_contact_get_user (sender);
1288 name = gossip_contact_get_name (sender);
1290 if (gossip_contact_equal (sender, my_contact)) {
1291 nick_tag = "irc-nick-self";
1292 body_tag = "irc-body-self";
1294 if (gossip_chat_should_highlight_nick (msg)) {
1295 nick_tag = "irc-nick-highlight";
1297 nick_tag = "irc-nick-other";
1300 body_tag = "irc-body-other";
1303 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1304 priv->last_block_type != BLOCK_TYPE_OTHER) {
1305 chat_view_append_spacing (view);
1308 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1311 tmp = g_strdup_printf ("%s: ", name);
1312 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1321 /* The text body. */
1322 chat_view_append_text (view, body, body_tag);
1326 chat_view_append_fancy_message (GossipChatView *view,
1329 GossipChatViewPriv *priv;
1330 GossipContact *sender;
1331 GossipContact *my_contact;
1335 priv = GET_PRIV (view);
1337 sender = gossip_message_get_sender (msg);
1338 my_contact = gossip_contact_get_user (sender);
1340 if (gossip_contact_equal (sender, my_contact)) {
1341 tag = "fancy-body-self";
1343 tag = "fancy-body-other";
1345 /* FIXME: Might want to support nick highlighting here... */
1348 body = gossip_message_get_body (msg);
1349 chat_view_append_text (view, body, tag);
1353 chat_view_theme_changed_cb (GossipThemeManager *manager,
1354 GossipChatView *view)
1356 GossipChatViewPriv *priv;
1357 gboolean show_avatars = FALSE;
1358 gboolean theme_rooms = FALSE;
1360 priv = GET_PRIV (view);
1362 priv->last_block_type = BLOCK_TYPE_NONE;
1364 gossip_conf_get_bool (gossip_conf_get (),
1365 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
1367 if (!theme_rooms && priv->is_group_chat) {
1368 gossip_theme_manager_apply (manager, view, NULL);
1370 gossip_theme_manager_apply_saved (manager, view);
1373 /* Needed for now to update the "rise" property of the names to get it
1374 * vertically centered.
1376 gossip_conf_get_bool (gossip_conf_get (),
1377 GOSSIP_PREFS_UI_SHOW_AVATARS,
1379 gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
1382 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
1383 * pixbuf. Returns a new ref.
1386 chat_view_pad_to_size (GdkPixbuf *pixbuf,
1389 gint extra_padding_right)
1391 gint src_width, src_height;
1393 gint x_offset, y_offset;
1395 src_width = gdk_pixbuf_get_width (pixbuf);
1396 src_height = gdk_pixbuf_get_height (pixbuf);
1398 x_offset = (width - src_width) / 2;
1399 y_offset = (height - src_height) / 2;
1401 padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
1403 gdk_pixbuf_get_bits_per_sample (pixbuf),
1404 width + extra_padding_right,
1407 gdk_pixbuf_fill (padded, 0);
1409 gdk_pixbuf_copy_area (pixbuf,
1410 0, /* source coords */
1415 x_offset, /* dest coords */
1422 gossip_chat_view_new (void)
1424 return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
1427 /* The name is optional, if NULL, the sender for msg is used. */
1429 gossip_chat_view_append_message (GossipChatView *view,
1432 GossipChatViewPriv *priv;
1433 GossipContact *sender;
1434 GossipContact *my_contact;
1436 gboolean scroll_down;
1438 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1439 g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
1441 priv = GET_PRIV (view);
1443 body = gossip_message_get_body (msg);
1448 scroll_down = chat_view_is_scrolled_down (view);
1450 chat_view_maybe_trim_buffer (view);
1451 chat_view_maybe_append_date_and_time (view, msg);
1453 sender = gossip_message_get_sender (msg);
1455 if (!priv->irc_style) {
1456 chat_view_maybe_append_fancy_header (view, msg);
1459 if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
1460 if (priv->irc_style) {
1461 chat_view_append_irc_action (view, msg);
1463 chat_view_append_fancy_action (view, msg);
1466 if (priv->irc_style) {
1467 chat_view_append_irc_message (view, msg);
1469 chat_view_append_fancy_message (view, msg);
1473 my_contact = gossip_contact_get_user (sender);
1475 /* Reset the last inserted contact. */
1476 if (priv->last_contact) {
1477 g_object_unref (priv->last_contact);
1480 if (gossip_contact_equal (my_contact, sender)) {
1481 priv->last_block_type = BLOCK_TYPE_SELF;
1482 priv->last_contact = NULL;
1484 priv->last_block_type = BLOCK_TYPE_OTHER;
1485 priv->last_contact = g_object_ref (sender);
1489 gossip_chat_view_scroll_down (view);
1494 gossip_chat_view_append_event (GossipChatView *view,
1497 GossipChatViewPriv *priv;
1503 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1504 g_return_if_fail (!G_STR_EMPTY (str));
1506 priv = GET_PRIV (view);
1508 bottom = chat_view_is_scrolled_down (view);
1510 chat_view_maybe_trim_buffer (view);
1512 if (priv->irc_style) {
1514 msg = g_strdup_printf (" - %s\n", str);
1516 tag = "fancy-event";
1517 msg = g_strdup_printf (" - %s\n", str);
1520 if (priv->last_block_type != BLOCK_TYPE_EVENT) {
1521 /* Comment out for now. */
1522 /*chat_view_append_spacing (view);*/
1525 chat_view_maybe_append_date_and_time (view, NULL);
1527 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1529 gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
1536 gossip_chat_view_scroll_down (view);
1539 priv->last_block_type = BLOCK_TYPE_EVENT;
1543 gossip_chat_view_append_button (GossipChatView *view,
1544 const gchar *message,
1548 GossipChatViewPriv *priv;
1549 GtkTextChildAnchor *anchor;
1554 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1555 g_return_if_fail (button1 != NULL);
1557 priv = GET_PRIV (view);
1559 if (priv->irc_style) {
1562 tag = "fancy-invite";
1565 bottom = chat_view_is_scrolled_down (view);
1567 chat_view_maybe_append_date_and_time (view, NULL);
1570 chat_view_append_text (view, message, tag);
1573 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1575 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1576 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
1577 gtk_widget_show (button1);
1579 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1587 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1589 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1590 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
1591 gtk_widget_show (button2);
1593 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1601 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1602 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1610 gossip_chat_view_scroll_down (view);
1613 priv->last_block_type = BLOCK_TYPE_INVITE;
1617 gossip_chat_view_scroll (GossipChatView *view,
1618 gboolean allow_scrolling)
1620 GossipChatViewPriv *priv;
1622 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1624 priv = GET_PRIV (view);
1626 priv->allow_scrolling = allow_scrolling;
1628 gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
1629 allow_scrolling ? "enabled" : "disabled");
1633 gossip_chat_view_scroll_down (GossipChatView *view)
1635 GossipChatViewPriv *priv;
1636 GtkTextBuffer *buffer;
1640 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1642 priv = GET_PRIV (view);
1644 if (!priv->allow_scrolling) {
1648 gossip_debug (DEBUG_DOMAIN, "Scrolling down");
1650 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1652 gtk_text_buffer_get_end_iter (buffer, &iter);
1653 mark = gtk_text_buffer_create_mark (buffer,
1658 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1665 gtk_text_buffer_delete_mark (buffer, mark);
1669 gossip_chat_view_get_selection_bounds (GossipChatView *view,
1673 GtkTextBuffer *buffer;
1675 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1677 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1679 return gtk_text_buffer_get_selection_bounds (buffer, start, end);
1683 gossip_chat_view_clear (GossipChatView *view)
1685 GtkTextBuffer *buffer;
1686 GossipChatViewPriv *priv;
1688 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1690 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1691 gtk_text_buffer_set_text (buffer, "", -1);
1693 /* We set these back to the initial values so we get
1694 * timestamps when clearing the window to know when
1695 * conversations start.
1697 priv = GET_PRIV (view);
1699 priv->last_block_type = BLOCK_TYPE_NONE;
1700 priv->last_timestamp = 0;
1704 gossip_chat_view_find_previous (GossipChatView *view,
1705 const gchar *search_criteria,
1706 gboolean new_search)
1708 GossipChatViewPriv *priv;
1709 GtkTextBuffer *buffer;
1710 GtkTextIter iter_at_mark;
1711 GtkTextIter iter_match_start;
1712 GtkTextIter iter_match_end;
1714 gboolean from_start = FALSE;
1716 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1717 g_return_val_if_fail (search_criteria != NULL, FALSE);
1719 priv = GET_PRIV (view);
1721 buffer = priv->buffer;
1723 if (G_STR_EMPTY (search_criteria)) {
1724 if (priv->find_mark_previous) {
1725 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1727 gtk_text_buffer_move_mark (buffer,
1728 priv->find_mark_previous,
1730 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1731 priv->find_mark_previous,
1736 gtk_text_buffer_select_range (buffer,
1748 if (priv->find_mark_previous) {
1749 gtk_text_buffer_get_iter_at_mark (buffer,
1751 priv->find_mark_previous);
1753 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1757 priv->find_last_direction = FALSE;
1759 found = gossip_text_iter_backward_search (&iter_at_mark,
1766 gboolean result = FALSE;
1772 /* Here we wrap around. */
1773 if (!new_search && !priv->find_wrapped) {
1774 priv->find_wrapped = TRUE;
1775 result = gossip_chat_view_find_previous (view,
1778 priv->find_wrapped = FALSE;
1784 /* Set new mark and show on screen */
1785 if (!priv->find_mark_previous) {
1786 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1790 gtk_text_buffer_move_mark (buffer,
1791 priv->find_mark_previous,
1795 if (!priv->find_mark_next) {
1796 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1800 gtk_text_buffer_move_mark (buffer,
1801 priv->find_mark_next,
1805 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1806 priv->find_mark_previous,
1812 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1813 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1819 gossip_chat_view_find_next (GossipChatView *view,
1820 const gchar *search_criteria,
1821 gboolean new_search)
1823 GossipChatViewPriv *priv;
1824 GtkTextBuffer *buffer;
1825 GtkTextIter iter_at_mark;
1826 GtkTextIter iter_match_start;
1827 GtkTextIter iter_match_end;
1829 gboolean from_start = FALSE;
1831 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1832 g_return_val_if_fail (search_criteria != NULL, FALSE);
1834 priv = GET_PRIV (view);
1836 buffer = priv->buffer;
1838 if (G_STR_EMPTY (search_criteria)) {
1839 if (priv->find_mark_next) {
1840 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1842 gtk_text_buffer_move_mark (buffer,
1843 priv->find_mark_next,
1845 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1846 priv->find_mark_next,
1851 gtk_text_buffer_select_range (buffer,
1863 if (priv->find_mark_next) {
1864 gtk_text_buffer_get_iter_at_mark (buffer,
1866 priv->find_mark_next);
1868 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1872 priv->find_last_direction = TRUE;
1874 found = gossip_text_iter_forward_search (&iter_at_mark,
1881 gboolean result = FALSE;
1887 /* Here we wrap around. */
1888 if (!new_search && !priv->find_wrapped) {
1889 priv->find_wrapped = TRUE;
1890 result = gossip_chat_view_find_next (view,
1893 priv->find_wrapped = FALSE;
1899 /* Set new mark and show on screen */
1900 if (!priv->find_mark_next) {
1901 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1905 gtk_text_buffer_move_mark (buffer,
1906 priv->find_mark_next,
1910 if (!priv->find_mark_previous) {
1911 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1915 gtk_text_buffer_move_mark (buffer,
1916 priv->find_mark_previous,
1920 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1921 priv->find_mark_next,
1927 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1928 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1935 gossip_chat_view_find_abilities (GossipChatView *view,
1936 const gchar *search_criteria,
1937 gboolean *can_do_previous,
1938 gboolean *can_do_next)
1940 GossipChatViewPriv *priv;
1941 GtkTextBuffer *buffer;
1942 GtkTextIter iter_at_mark;
1943 GtkTextIter iter_match_start;
1944 GtkTextIter iter_match_end;
1946 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1947 g_return_if_fail (search_criteria != NULL);
1948 g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1950 priv = GET_PRIV (view);
1952 buffer = priv->buffer;
1954 if (can_do_previous) {
1955 if (priv->find_mark_previous) {
1956 gtk_text_buffer_get_iter_at_mark (buffer,
1958 priv->find_mark_previous);
1960 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1963 *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
1971 if (priv->find_mark_next) {
1972 gtk_text_buffer_get_iter_at_mark (buffer,
1974 priv->find_mark_next);
1976 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1979 *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
1988 gossip_chat_view_highlight (GossipChatView *view,
1991 GtkTextBuffer *buffer;
1993 GtkTextIter iter_start;
1994 GtkTextIter iter_end;
1995 GtkTextIter iter_match_start;
1996 GtkTextIter iter_match_end;
1999 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2001 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2003 gtk_text_buffer_get_start_iter (buffer, &iter);
2005 gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
2006 gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
2010 if (G_STR_EMPTY (text)) {
2015 found = gossip_text_iter_forward_search (&iter,
2025 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
2029 iter = iter_match_end;
2030 gtk_text_iter_forward_char (&iter);
2035 gossip_chat_view_copy_clipboard (GossipChatView *view)
2037 GtkTextBuffer *buffer;
2038 GtkClipboard *clipboard;
2040 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2042 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2043 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2045 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2049 gossip_chat_view_get_irc_style (GossipChatView *view)
2051 GossipChatViewPriv *priv;
2053 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
2055 priv = GET_PRIV (view);
2057 return priv->irc_style;
2061 gossip_chat_view_set_irc_style (GossipChatView *view,
2064 GossipChatViewPriv *priv;
2066 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2068 priv = GET_PRIV (view);
2070 priv->irc_style = irc_style;
2074 gossip_chat_view_set_margin (GossipChatView *view,
2077 GossipChatViewPriv *priv;
2079 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2081 priv = GET_PRIV (view);
2084 "left-margin", margin,
2085 "right-margin", margin,
2090 gossip_chat_view_get_smiley_image (GossipSmiley smiley)
2092 static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
2093 static gboolean inited = FALSE;
2098 for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2099 pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
2105 return pixbufs[smiley];
2109 gossip_chat_view_get_smiley_text (GossipSmiley smiley)
2113 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
2114 if (smileys[i].smiley != smiley) {
2118 return smileys[i].pattern;
2125 gossip_chat_view_get_smiley_menu (GCallback callback,
2127 GtkTooltips *tooltips)
2134 g_return_val_if_fail (callback != NULL, NULL);
2136 menu = gtk_menu_new ();
2138 for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2142 const gchar *smiley_text;
2144 pixbuf = gossip_chat_view_get_smiley_image (i);
2149 image = gtk_image_new_from_pixbuf (pixbuf);
2151 item = gtk_image_menu_item_new_with_label ("");
2152 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2154 gtk_menu_attach (GTK_MENU (menu), item,
2155 x, x + 1, y, y + 1);
2157 smiley_text = gossip_chat_view_get_smiley_text (i);
2159 gtk_tooltips_set_tip (tooltips,
2164 g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
2165 g_signal_connect (item, "activate", callback, user_data);
2175 gtk_widget_show_all (menu);
2180 /* FIXME: Do we really need this? Better to do it internally only at setup time,
2181 * we will never change it on the fly.
2184 gossip_chat_view_set_is_group_chat (GossipChatView *view,
2185 gboolean is_group_chat)
2187 GossipChatViewPriv *priv;
2188 gboolean theme_rooms = FALSE;
2190 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2192 priv = GET_PRIV (view);
2194 priv->is_group_chat = is_group_chat;
2196 gossip_conf_get_bool (gossip_conf_get (),
2197 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
2200 if (!theme_rooms && is_group_chat) {
2201 gossip_theme_manager_apply (gossip_theme_manager_get (),
2205 gossip_theme_manager_apply_saved (gossip_theme_manager_get (),