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,
250 G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
253 gossip_chat_view_class_init (GossipChatViewClass *klass)
255 GObjectClass *object_class = G_OBJECT_CLASS (klass);
256 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
258 object_class->finalize = chat_view_finalize;
259 widget_class->size_allocate = chat_view_size_allocate;
260 widget_class->drag_motion = chat_view_drag_motion;
262 g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
266 gossip_chat_view_init (GossipChatView *view)
268 GossipChatViewPriv *priv;
269 gboolean show_avatars;
271 priv = GET_PRIV (view);
273 priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
275 priv->last_block_type = BLOCK_TYPE_NONE;
276 priv->last_timestamp = 0;
278 priv->allow_scrolling = TRUE;
280 priv->is_group_chat = FALSE;
283 "wrap-mode", GTK_WRAP_WORD_CHAR,
285 "cursor-visible", FALSE,
288 priv->notify_system_fonts_id =
289 gossip_conf_notify_add (gossip_conf_get (),
290 "/desktop/gnome/interface/document_font_name",
291 chat_view_notify_system_font_cb,
293 chat_view_system_font_update (view);
295 priv->notify_show_avatars_id =
296 gossip_conf_notify_add (gossip_conf_get (),
297 GOSSIP_PREFS_UI_SHOW_AVATARS,
298 chat_view_notify_show_avatars_cb,
301 chat_view_setup_tags (view);
303 gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
305 show_avatars = FALSE;
306 gossip_conf_get_bool (gossip_conf_get (),
307 GOSSIP_PREFS_UI_SHOW_AVATARS,
310 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
313 g_signal_connect (view,
315 G_CALLBACK (chat_view_populate_popup),
318 g_signal_connect_object (gossip_theme_manager_get (),
320 G_CALLBACK (chat_view_theme_changed_cb),
326 chat_view_finalize (GObject *object)
328 GossipChatView *view;
329 GossipChatViewPriv *priv;
331 view = GOSSIP_CHAT_VIEW (object);
332 priv = GET_PRIV (view);
334 gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
336 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
337 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
339 if (priv->last_contact) {
340 g_object_unref (priv->last_contact);
343 G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
347 chat_view_drag_motion (GtkWidget *widget,
348 GdkDragContext *context,
353 /* Don't handle drag motion, since we don't want the view to scroll as
354 * the result of dragging something across it.
361 chat_view_size_allocate (GtkWidget *widget,
362 GtkAllocation *alloc)
366 down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
368 GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
371 gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
376 chat_view_setup_tags (GossipChatView *view)
378 GossipChatViewPriv *priv;
381 priv = GET_PRIV (view);
383 gtk_text_buffer_create_tag (priv->buffer,
387 /* FIXME: Move to the theme and come up with something that looks a bit
390 gtk_text_buffer_create_tag (priv->buffer,
392 "background", "yellow",
395 tag = gtk_text_buffer_create_tag (priv->buffer,
399 g_signal_connect (tag,
401 G_CALLBACK (chat_view_url_event_cb),
404 g_signal_connect (view,
405 "motion-notify-event",
406 G_CALLBACK (chat_view_event_cb),
411 chat_view_system_font_update (GossipChatView *view)
413 PangoFontDescription *font_description = NULL;
416 if (gossip_conf_get_string (gossip_conf_get (),
417 "/desktop/gnome/interface/document_font_name",
418 &font_name) && font_name) {
419 font_description = pango_font_description_from_string (font_name);
422 font_description = NULL;
425 gtk_widget_modify_font (GTK_WIDGET (view), font_description);
427 if (font_description) {
428 pango_font_description_free (font_description);
433 chat_view_notify_system_font_cb (GossipConf *conf,
437 GossipChatView *view;
438 gboolean show_avatars = FALSE;
442 chat_view_system_font_update (view);
444 /* Ugly, again, to adjust the vertical position of the nick... Will fix
445 * this when reworking the theme manager so that view register
446 * themselves with it instead of the other way around.
448 gossip_conf_get_bool (conf,
449 GOSSIP_PREFS_UI_SHOW_AVATARS,
452 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
457 chat_view_notify_show_avatars_cb (GossipConf *conf,
461 GossipChatView *view;
462 GossipChatViewPriv *priv;
463 gboolean show_avatars = FALSE;
466 priv = GET_PRIV (view);
468 gossip_conf_get_bool (conf, key, &show_avatars);
470 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
475 chat_view_populate_popup (GossipChatView *view,
479 GossipChatViewPriv *priv;
480 GtkTextTagTable *table;
483 GtkTextIter iter, start, end;
487 priv = GET_PRIV (view);
489 /* Clear menu item */
490 if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
491 item = gtk_menu_item_new ();
492 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
493 gtk_widget_show (item);
495 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
496 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
497 gtk_widget_show (item);
499 g_signal_connect (item,
501 G_CALLBACK (chat_view_clear_view_cb),
505 /* Link context menu items */
506 table = gtk_text_buffer_get_tag_table (priv->buffer);
507 tag = gtk_text_tag_table_lookup (table, "link");
509 gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
511 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
512 GTK_TEXT_WINDOW_WIDGET,
516 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
520 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
521 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
522 str = gtk_text_buffer_get_text (priv->buffer,
523 &start, &end, FALSE);
526 if (G_STR_EMPTY (str)) {
531 /* NOTE: Set data just to get the string freed when not needed. */
532 g_object_set_data_full (G_OBJECT (menu),
534 (GDestroyNotify) g_free);
536 item = gtk_menu_item_new ();
537 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
538 gtk_widget_show (item);
540 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
541 g_signal_connect (item,
543 G_CALLBACK (chat_view_copy_address_cb),
545 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
546 gtk_widget_show (item);
548 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
549 g_signal_connect (item,
551 G_CALLBACK (chat_view_open_address_cb),
553 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
554 gtk_widget_show (item);
558 chat_view_event_cb (GossipChatView *view,
559 GdkEventMotion *event,
562 static GdkCursor *hand = NULL;
563 static GdkCursor *beam = NULL;
564 GtkTextWindowType type;
567 gint x, y, buf_x, buf_y;
569 type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
572 if (type != GTK_TEXT_WINDOW_TEXT) {
576 /* Get where the pointer really is. */
577 win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
582 gdk_window_get_pointer (win, &x, &y, NULL);
584 /* Get the iter where the cursor is at */
585 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
589 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
593 if (gtk_text_iter_has_tag (&iter, tag)) {
595 hand = gdk_cursor_new (GDK_HAND2);
596 beam = gdk_cursor_new (GDK_XTERM);
598 gdk_window_set_cursor (win, hand);
601 beam = gdk_cursor_new (GDK_XTERM);
603 gdk_window_set_cursor (win, beam);
610 chat_view_url_event_cb (GtkTextTag *tag,
614 GtkTextBuffer *buffer)
616 GtkTextIter start, end;
619 /* If the link is being selected, don't do anything. */
620 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
621 if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
625 if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
628 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
629 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
630 str = gtk_text_buffer_get_text (buffer,
635 gossip_url_show (str);
644 chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
646 gossip_url_show (url);
650 chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
652 GtkClipboard *clipboard;
654 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
655 gtk_clipboard_set_text (clipboard, url, -1);
657 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
658 gtk_clipboard_set_text (clipboard, url, -1);
662 chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
664 gossip_chat_view_clear (view);
668 chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
677 gboolean use_smileys = FALSE;
679 gossip_conf_get_bool (gossip_conf_get (),
680 GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
684 gtk_text_buffer_insert (buf, iter, str, -1);
689 gint smileys_index[G_N_ELEMENTS (smileys)];
694 memset (smileys_index, 0, sizeof (smileys_index));
702 c = g_utf8_get_char (p);
704 if (match != -1 && g_unichar_isspace (c)) {
710 if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
713 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
714 /* Only try to match if we already have
715 * a beginning match for the pattern, or
716 * if it's the first character in the
717 * pattern, if it's not in the middle of
720 if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
721 smileys_index[i] > 0) &&
722 smileys[i].pattern[smileys_index[i]] == c) {
726 if (!smileys[i].pattern[smileys_index[i]]) {
730 smileys_index[i] = 0;
736 p = g_utf8_next_char (p);
740 gtk_text_buffer_insert (buf, iter, str, -1);
744 start = p - strlen (smileys[match].pattern);
748 gtk_text_buffer_insert (buf, iter, str, len);
751 pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
752 gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
754 gtk_text_buffer_insert (buf, iter, " ", 1);
756 str = g_utf8_find_next_char (p, NULL);
761 chat_view_is_scrolled_down (GossipChatView *view)
765 sw = gtk_widget_get_parent (GTK_WIDGET (view));
766 if (GTK_IS_SCROLLED_WINDOW (sw)) {
769 vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
771 if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
780 chat_view_maybe_trim_buffer (GossipChatView *view)
782 GossipChatViewPriv *priv;
783 GtkTextIter top, bottom;
786 GtkTextTagTable *table;
789 priv = GET_PRIV (view);
791 gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
792 line = gtk_text_iter_get_line (&bottom);
793 if (line < MAX_LINES) {
797 remove = line - MAX_LINES;
798 gtk_text_buffer_get_start_iter (priv->buffer, &top);
801 if (!gtk_text_iter_forward_lines (&bottom, remove)) {
805 /* Track backwords to a place where we can safely cut, we don't do it in
806 * the middle of a tag.
808 table = gtk_text_buffer_get_tag_table (priv->buffer);
809 tag = gtk_text_tag_table_lookup (table, "cut");
814 if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
818 if (!gtk_text_iter_equal (&top, &bottom)) {
819 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
824 chat_view_maybe_append_date_and_time (GossipChatView *view,
827 GossipChatViewPriv *priv;
830 GDate *date, *last_date;
832 gboolean append_date, append_time;
835 priv = GET_PRIV (view);
837 if (priv->irc_style) {
843 if (priv->last_block_type == BLOCK_TYPE_TIME) {
847 str = g_string_new (NULL);
851 timestamp = gossip_message_get_timestamp (msg);
854 if (timestamp <= 0) {
855 timestamp = gossip_time_get_current ();
858 date = g_date_new ();
859 g_date_set_time (date, timestamp);
861 last_date = g_date_new ();
862 g_date_set_time (last_date, priv->last_timestamp);
867 if (g_date_compare (date, last_date) > 0) {
872 if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
876 if (append_time || append_date) {
877 chat_view_append_spacing (view);
879 g_string_append (str, "- ");
885 g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
886 g_string_append (str, buf);
889 g_string_append (str, ", ");
894 g_date_free (last_date);
899 tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
900 g_string_append (str, tmp);
904 if (append_time || append_date) {
905 g_string_append (str, " -\n");
907 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
908 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
914 priv->last_block_type = BLOCK_TYPE_TIME;
915 priv->last_timestamp = timestamp;
918 g_string_free (str, TRUE);
922 chat_view_append_spacing (GossipChatView *view)
924 GossipChatViewPriv *priv;
928 priv = GET_PRIV (view);
930 if (priv->irc_style) {
933 tag = "fancy-spacing";
936 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
937 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
947 chat_view_append_text (GossipChatView *view,
951 GossipChatViewPriv *priv;
952 GtkTextIter start_iter, end_iter;
957 const gchar *link_tag;
959 priv = GET_PRIV (view);
961 if (priv->irc_style) {
962 link_tag = "irc-link";
964 link_tag = "fancy-link";
967 gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
968 mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
970 start = g_array_new (FALSE, FALSE, sizeof (gint));
971 end = g_array_new (FALSE, FALSE, sizeof (gint));
973 num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
975 if (num_matches == 0) {
976 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
977 chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
983 for (i = 0; i < num_matches; i++) {
984 s = g_array_index (start, gint, i);
985 e = g_array_index (end, gint, i);
988 tmp = gossip_substring (body, last, s);
990 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
991 chat_view_insert_text_with_emoticons (priv->buffer,
997 tmp = gossip_substring (body, s, e);
999 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1000 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1013 if (e < strlen (body)) {
1014 tmp = gossip_substring (body, e, strlen (body));
1016 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1017 chat_view_insert_text_with_emoticons (priv->buffer,
1024 g_array_free (start, TRUE);
1025 g_array_free (end, TRUE);
1027 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1028 gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1030 /* Apply the style to the inserted text. */
1031 gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1032 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
1034 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1039 gtk_text_buffer_delete_mark (priv->buffer, mark);
1043 chat_view_maybe_append_fancy_header (GossipChatView *view,
1046 GossipChatViewPriv *priv;
1047 GossipContact *sender;
1048 GossipContact *my_contact;
1054 const gchar *avatar_tag;
1055 const gchar *line_top_tag;
1056 const gchar *line_bottom_tag;
1060 priv = GET_PRIV (view);
1062 sender = gossip_message_get_sender (msg);
1063 my_contact = gossip_get_own_contact_from_contact (sender);
1064 name = gossip_contact_get_name (sender);
1065 from_self = gossip_contact_equal (sender, my_contact);
1067 gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
1070 tag = "fancy-header-self";
1071 line_top_tag = "fancy-line-top-self";
1072 line_bottom_tag = "fancy-line-bottom-self";
1074 tag = "fancy-header-other";
1075 line_top_tag = "fancy-line-top-other";
1076 line_bottom_tag = "fancy-line-bottom-other";
1081 /* Only insert a header if the previously inserted block is not the same
1082 * as this one. This catches all the different cases:
1084 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1085 priv->last_block_type != BLOCK_TYPE_OTHER) {
1088 else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
1091 else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
1094 else if (!from_self &&
1095 (!priv->last_contact ||
1096 !gossip_contact_equal (sender, priv->last_contact))) {
1104 chat_view_append_spacing (view);
1106 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1107 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1114 avatar = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
1119 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1120 gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
1122 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1124 gtk_text_iter_backward_char (&start);
1127 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1128 "fancy-avatar-self",
1130 avatar_tag = "fancy-header-self-avatar";
1132 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1133 "fancy-avatar-other",
1135 avatar_tag = "fancy-header-other-avatar";
1138 g_object_unref (avatar);
1143 tmp = g_strdup_printf ("%s\n", name);
1145 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1146 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1155 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1156 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1165 chat_view_append_irc_action (GossipChatView *view,
1168 GossipChatViewPriv *priv;
1169 GossipContact *my_contact;
1170 GossipContact *sender;
1177 priv = GET_PRIV (view);
1179 gossip_debug (DEBUG_DOMAIN, "Add IRC action");
1181 sender = gossip_message_get_sender (msg);
1182 my_contact = gossip_get_own_contact_from_contact (sender);
1183 name = gossip_contact_get_name (sender);
1185 /* Skip the "/me ". */
1186 if (gossip_contact_equal (sender, my_contact)) {
1187 tag = "irc-action-self";
1189 tag = "irc-action-other";
1192 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1193 priv->last_block_type != BLOCK_TYPE_OTHER) {
1194 chat_view_append_spacing (view);
1197 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1199 tmp = g_strdup_printf (" * %s ", name);
1200 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1209 body = gossip_message_get_body (msg);
1210 chat_view_append_text (view, body, tag);
1214 chat_view_append_fancy_action (GossipChatView *view,
1217 GossipChatViewPriv *priv;
1218 GossipContact *sender;
1219 GossipContact *my_contact;
1225 const gchar *line_tag;
1227 priv = GET_PRIV (view);
1229 gossip_debug (DEBUG_DOMAIN, "Add fancy action");
1231 sender = gossip_message_get_sender (msg);
1232 my_contact = gossip_get_own_contact_from_contact (sender);
1233 name = gossip_contact_get_name (sender);
1235 if (gossip_contact_equal (sender, my_contact)) {
1236 tag = "fancy-action-self";
1237 line_tag = "fancy-line-self";
1239 tag = "fancy-action-other";
1240 line_tag = "fancy-line-other";
1243 tmp = g_strdup_printf (" * %s ", name);
1244 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1245 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1253 body = gossip_message_get_body (msg);
1254 chat_view_append_text (view, body, tag);
1258 chat_view_append_irc_message (GossipChatView *view,
1261 GossipChatViewPriv *priv;
1262 GossipContact *sender;
1263 GossipContact *my_contact;
1266 const gchar *nick_tag;
1267 const gchar *body_tag;
1271 priv = GET_PRIV (view);
1273 gossip_debug (DEBUG_DOMAIN, "Add IRC message");
1275 body = gossip_message_get_body (msg);
1276 sender = gossip_message_get_sender (msg);
1277 my_contact = gossip_get_own_contact_from_contact (sender);
1278 name = gossip_contact_get_name (sender);
1280 if (gossip_contact_equal (sender, my_contact)) {
1281 nick_tag = "irc-nick-self";
1282 body_tag = "irc-body-self";
1284 if (gossip_chat_should_highlight_nick (msg)) {
1285 nick_tag = "irc-nick-highlight";
1287 nick_tag = "irc-nick-other";
1290 body_tag = "irc-body-other";
1293 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1294 priv->last_block_type != BLOCK_TYPE_OTHER) {
1295 chat_view_append_spacing (view);
1298 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1301 tmp = g_strdup_printf ("%s: ", name);
1302 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1311 /* The text body. */
1312 chat_view_append_text (view, body, body_tag);
1316 chat_view_append_fancy_message (GossipChatView *view,
1319 GossipChatViewPriv *priv;
1320 GossipContact *sender;
1321 GossipContact *my_contact;
1325 priv = GET_PRIV (view);
1327 sender = gossip_message_get_sender (msg);
1328 my_contact = gossip_get_own_contact_from_contact (sender);
1330 if (gossip_contact_equal (sender, my_contact)) {
1331 tag = "fancy-body-self";
1333 tag = "fancy-body-other";
1335 /* FIXME: Might want to support nick highlighting here... */
1338 body = gossip_message_get_body (msg);
1339 chat_view_append_text (view, body, tag);
1343 chat_view_theme_changed_cb (GossipThemeManager *manager,
1344 GossipChatView *view)
1346 GossipChatViewPriv *priv;
1347 gboolean show_avatars = FALSE;
1348 gboolean theme_rooms = FALSE;
1350 priv = GET_PRIV (view);
1352 priv->last_block_type = BLOCK_TYPE_NONE;
1354 gossip_conf_get_bool (gossip_conf_get (),
1355 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
1357 if (!theme_rooms && priv->is_group_chat) {
1358 gossip_theme_manager_apply (manager, view, NULL);
1360 gossip_theme_manager_apply_saved (manager, view);
1363 /* Needed for now to update the "rise" property of the names to get it
1364 * vertically centered.
1366 gossip_conf_get_bool (gossip_conf_get (),
1367 GOSSIP_PREFS_UI_SHOW_AVATARS,
1369 gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
1373 gossip_chat_view_new (void)
1375 return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
1378 /* The name is optional, if NULL, the sender for msg is used. */
1380 gossip_chat_view_append_message (GossipChatView *view,
1383 GossipChatViewPriv *priv;
1384 GossipContact *sender;
1386 gboolean scroll_down;
1388 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1389 g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
1391 priv = GET_PRIV (view);
1393 body = gossip_message_get_body (msg);
1398 scroll_down = chat_view_is_scrolled_down (view);
1400 chat_view_maybe_trim_buffer (view);
1401 chat_view_maybe_append_date_and_time (view, msg);
1403 sender = gossip_message_get_sender (msg);
1405 if (!priv->irc_style) {
1406 chat_view_maybe_append_fancy_header (view, msg);
1409 if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
1410 if (priv->irc_style) {
1411 chat_view_append_irc_action (view, msg);
1413 chat_view_append_fancy_action (view, msg);
1416 if (priv->irc_style) {
1417 chat_view_append_irc_message (view, msg);
1419 chat_view_append_fancy_message (view, msg);
1423 priv->last_block_type = BLOCK_TYPE_SELF;
1425 /* Reset the last inserted contact, since it was from self. */
1426 if (priv->last_contact) {
1427 g_object_unref (priv->last_contact);
1428 priv->last_contact = NULL;
1432 gossip_chat_view_scroll_down (view);
1437 gossip_chat_view_append_event (GossipChatView *view,
1440 GossipChatViewPriv *priv;
1446 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1447 g_return_if_fail (!G_STR_EMPTY (str));
1449 priv = GET_PRIV (view);
1451 bottom = chat_view_is_scrolled_down (view);
1453 chat_view_maybe_trim_buffer (view);
1455 if (priv->irc_style) {
1457 msg = g_strdup_printf (" - %s\n", str);
1459 tag = "fancy-event";
1460 msg = g_strdup_printf (" - %s\n", str);
1463 if (priv->last_block_type != BLOCK_TYPE_EVENT) {
1464 /* Comment out for now. */
1465 /*chat_view_append_spacing (view);*/
1468 chat_view_maybe_append_date_and_time (view, NULL);
1470 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1472 gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
1479 gossip_chat_view_scroll_down (view);
1482 priv->last_block_type = BLOCK_TYPE_EVENT;
1486 gossip_chat_view_append_button (GossipChatView *view,
1487 const gchar *message,
1491 GossipChatViewPriv *priv;
1492 GtkTextChildAnchor *anchor;
1497 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1498 g_return_if_fail (button1 != NULL);
1500 priv = GET_PRIV (view);
1502 if (priv->irc_style) {
1505 tag = "fancy-invite";
1508 bottom = chat_view_is_scrolled_down (view);
1510 chat_view_maybe_append_date_and_time (view, NULL);
1513 chat_view_append_text (view, message, tag);
1516 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1518 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1519 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
1520 gtk_widget_show (button1);
1522 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1530 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1532 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1533 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
1534 gtk_widget_show (button2);
1536 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1544 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1545 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1553 gossip_chat_view_scroll_down (view);
1556 priv->last_block_type = BLOCK_TYPE_INVITE;
1560 gossip_chat_view_scroll (GossipChatView *view,
1561 gboolean allow_scrolling)
1563 GossipChatViewPriv *priv;
1565 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1567 priv = GET_PRIV (view);
1569 priv->allow_scrolling = allow_scrolling;
1571 gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
1572 allow_scrolling ? "enabled" : "disabled");
1576 gossip_chat_view_scroll_down (GossipChatView *view)
1578 GossipChatViewPriv *priv;
1579 GtkTextBuffer *buffer;
1583 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1585 priv = GET_PRIV (view);
1587 if (!priv->allow_scrolling) {
1591 gossip_debug (DEBUG_DOMAIN, "Scrolling down");
1593 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1595 gtk_text_buffer_get_end_iter (buffer, &iter);
1596 mark = gtk_text_buffer_create_mark (buffer,
1601 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1608 gtk_text_buffer_delete_mark (buffer, mark);
1612 gossip_chat_view_get_selection_bounds (GossipChatView *view,
1616 GtkTextBuffer *buffer;
1618 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1620 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1622 return gtk_text_buffer_get_selection_bounds (buffer, start, end);
1626 gossip_chat_view_clear (GossipChatView *view)
1628 GtkTextBuffer *buffer;
1629 GossipChatViewPriv *priv;
1631 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1633 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1634 gtk_text_buffer_set_text (buffer, "", -1);
1636 /* We set these back to the initial values so we get
1637 * timestamps when clearing the window to know when
1638 * conversations start.
1640 priv = GET_PRIV (view);
1642 priv->last_block_type = BLOCK_TYPE_NONE;
1643 priv->last_timestamp = 0;
1647 gossip_chat_view_find_previous (GossipChatView *view,
1648 const gchar *search_criteria,
1649 gboolean new_search)
1651 GossipChatViewPriv *priv;
1652 GtkTextBuffer *buffer;
1653 GtkTextIter iter_at_mark;
1654 GtkTextIter iter_match_start;
1655 GtkTextIter iter_match_end;
1657 gboolean from_start = FALSE;
1659 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1660 g_return_val_if_fail (search_criteria != NULL, FALSE);
1662 priv = GET_PRIV (view);
1664 buffer = priv->buffer;
1666 if (G_STR_EMPTY (search_criteria)) {
1667 if (priv->find_mark_previous) {
1668 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1670 gtk_text_buffer_move_mark (buffer,
1671 priv->find_mark_previous,
1673 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1674 priv->find_mark_previous,
1679 gtk_text_buffer_select_range (buffer,
1691 if (priv->find_mark_previous) {
1692 gtk_text_buffer_get_iter_at_mark (buffer,
1694 priv->find_mark_previous);
1696 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1700 priv->find_last_direction = FALSE;
1702 found = gossip_text_iter_backward_search (&iter_at_mark,
1709 gboolean result = FALSE;
1715 /* Here we wrap around. */
1716 if (!new_search && !priv->find_wrapped) {
1717 priv->find_wrapped = TRUE;
1718 result = gossip_chat_view_find_previous (view,
1721 priv->find_wrapped = FALSE;
1727 /* Set new mark and show on screen */
1728 if (!priv->find_mark_previous) {
1729 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1733 gtk_text_buffer_move_mark (buffer,
1734 priv->find_mark_previous,
1738 if (!priv->find_mark_next) {
1739 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1743 gtk_text_buffer_move_mark (buffer,
1744 priv->find_mark_next,
1748 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1749 priv->find_mark_previous,
1755 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1756 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1762 gossip_chat_view_find_next (GossipChatView *view,
1763 const gchar *search_criteria,
1764 gboolean new_search)
1766 GossipChatViewPriv *priv;
1767 GtkTextBuffer *buffer;
1768 GtkTextIter iter_at_mark;
1769 GtkTextIter iter_match_start;
1770 GtkTextIter iter_match_end;
1772 gboolean from_start = FALSE;
1774 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1775 g_return_val_if_fail (search_criteria != NULL, FALSE);
1777 priv = GET_PRIV (view);
1779 buffer = priv->buffer;
1781 if (G_STR_EMPTY (search_criteria)) {
1782 if (priv->find_mark_next) {
1783 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1785 gtk_text_buffer_move_mark (buffer,
1786 priv->find_mark_next,
1788 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1789 priv->find_mark_next,
1794 gtk_text_buffer_select_range (buffer,
1806 if (priv->find_mark_next) {
1807 gtk_text_buffer_get_iter_at_mark (buffer,
1809 priv->find_mark_next);
1811 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1815 priv->find_last_direction = TRUE;
1817 found = gossip_text_iter_forward_search (&iter_at_mark,
1824 gboolean result = FALSE;
1830 /* Here we wrap around. */
1831 if (!new_search && !priv->find_wrapped) {
1832 priv->find_wrapped = TRUE;
1833 result = gossip_chat_view_find_next (view,
1836 priv->find_wrapped = FALSE;
1842 /* Set new mark and show on screen */
1843 if (!priv->find_mark_next) {
1844 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1848 gtk_text_buffer_move_mark (buffer,
1849 priv->find_mark_next,
1853 if (!priv->find_mark_previous) {
1854 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1858 gtk_text_buffer_move_mark (buffer,
1859 priv->find_mark_previous,
1863 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1864 priv->find_mark_next,
1870 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1871 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1878 gossip_chat_view_find_abilities (GossipChatView *view,
1879 const gchar *search_criteria,
1880 gboolean *can_do_previous,
1881 gboolean *can_do_next)
1883 GossipChatViewPriv *priv;
1884 GtkTextBuffer *buffer;
1885 GtkTextIter iter_at_mark;
1886 GtkTextIter iter_match_start;
1887 GtkTextIter iter_match_end;
1889 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1890 g_return_if_fail (search_criteria != NULL);
1891 g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1893 priv = GET_PRIV (view);
1895 buffer = priv->buffer;
1897 if (can_do_previous) {
1898 if (priv->find_mark_previous) {
1899 gtk_text_buffer_get_iter_at_mark (buffer,
1901 priv->find_mark_previous);
1903 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1906 *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
1914 if (priv->find_mark_next) {
1915 gtk_text_buffer_get_iter_at_mark (buffer,
1917 priv->find_mark_next);
1919 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1922 *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
1931 gossip_chat_view_highlight (GossipChatView *view,
1934 GtkTextBuffer *buffer;
1936 GtkTextIter iter_start;
1937 GtkTextIter iter_end;
1938 GtkTextIter iter_match_start;
1939 GtkTextIter iter_match_end;
1942 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1944 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1946 gtk_text_buffer_get_start_iter (buffer, &iter);
1948 gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
1949 gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
1953 if (G_STR_EMPTY (text)) {
1958 found = gossip_text_iter_forward_search (&iter,
1968 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
1972 iter = iter_match_end;
1973 gtk_text_iter_forward_char (&iter);
1978 gossip_chat_view_copy_clipboard (GossipChatView *view)
1980 GtkTextBuffer *buffer;
1981 GtkClipboard *clipboard;
1983 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1985 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1986 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1988 gtk_text_buffer_copy_clipboard (buffer, clipboard);
1992 gossip_chat_view_get_irc_style (GossipChatView *view)
1994 GossipChatViewPriv *priv;
1996 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1998 priv = GET_PRIV (view);
2000 return priv->irc_style;
2004 gossip_chat_view_set_irc_style (GossipChatView *view,
2007 GossipChatViewPriv *priv;
2009 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2011 priv = GET_PRIV (view);
2013 priv->irc_style = irc_style;
2017 gossip_chat_view_set_margin (GossipChatView *view,
2020 GossipChatViewPriv *priv;
2022 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2024 priv = GET_PRIV (view);
2027 "left-margin", margin,
2028 "right-margin", margin,
2033 gossip_chat_view_get_smiley_image (GossipSmiley smiley)
2035 static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
2036 static gboolean inited = FALSE;
2041 for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2042 pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
2048 return pixbufs[smiley];
2052 gossip_chat_view_get_smiley_text (GossipSmiley smiley)
2056 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
2057 if (smileys[i].smiley != smiley) {
2061 return smileys[i].pattern;
2068 gossip_chat_view_get_smiley_menu (GCallback callback,
2070 GtkTooltips *tooltips)
2077 g_return_val_if_fail (callback != NULL, NULL);
2079 menu = gtk_menu_new ();
2081 for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2085 const gchar *smiley_text;
2087 pixbuf = gossip_chat_view_get_smiley_image (i);
2092 image = gtk_image_new_from_pixbuf (pixbuf);
2094 item = gtk_image_menu_item_new_with_label ("");
2095 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2097 gtk_menu_attach (GTK_MENU (menu), item,
2098 x, x + 1, y, y + 1);
2100 smiley_text = gossip_chat_view_get_smiley_text (i);
2102 gtk_tooltips_set_tip (tooltips,
2107 g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
2108 g_signal_connect (item, "activate", callback, user_data);
2118 gtk_widget_show_all (menu);
2123 /* FIXME: Do we really need this? Better to do it internally only at setup time,
2124 * we will never change it on the fly.
2127 gossip_chat_view_set_is_group_chat (GossipChatView *view,
2128 gboolean is_group_chat)
2130 GossipChatViewPriv *priv;
2131 gboolean theme_rooms = FALSE;
2133 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2135 priv = GET_PRIV (view);
2137 priv->is_group_chat = is_group_chat;
2139 gossip_conf_get_bool (gossip_conf_get (),
2140 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
2143 if (!theme_rooms && is_group_chat) {
2144 gossip_theme_manager_apply (gossip_theme_manager_get (),
2148 gossip_theme_manager_apply_saved (gossip_theme_manager_get (),