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_get_own_contact_from_contact (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_get_own_contact_from_contact (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_get_own_contact_from_contact (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_get_own_contact_from_contact (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_get_own_contact_from_contact (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;
1435 gboolean scroll_down;
1437 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1438 g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
1440 priv = GET_PRIV (view);
1442 body = gossip_message_get_body (msg);
1447 scroll_down = chat_view_is_scrolled_down (view);
1449 chat_view_maybe_trim_buffer (view);
1450 chat_view_maybe_append_date_and_time (view, msg);
1452 sender = gossip_message_get_sender (msg);
1454 if (!priv->irc_style) {
1455 chat_view_maybe_append_fancy_header (view, msg);
1458 if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
1459 if (priv->irc_style) {
1460 chat_view_append_irc_action (view, msg);
1462 chat_view_append_fancy_action (view, msg);
1465 if (priv->irc_style) {
1466 chat_view_append_irc_message (view, msg);
1468 chat_view_append_fancy_message (view, msg);
1472 priv->last_block_type = BLOCK_TYPE_SELF;
1474 /* Reset the last inserted contact, since it was from self. */
1475 if (priv->last_contact) {
1476 g_object_unref (priv->last_contact);
1477 priv->last_contact = NULL;
1481 gossip_chat_view_scroll_down (view);
1486 gossip_chat_view_append_event (GossipChatView *view,
1489 GossipChatViewPriv *priv;
1495 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1496 g_return_if_fail (!G_STR_EMPTY (str));
1498 priv = GET_PRIV (view);
1500 bottom = chat_view_is_scrolled_down (view);
1502 chat_view_maybe_trim_buffer (view);
1504 if (priv->irc_style) {
1506 msg = g_strdup_printf (" - %s\n", str);
1508 tag = "fancy-event";
1509 msg = g_strdup_printf (" - %s\n", str);
1512 if (priv->last_block_type != BLOCK_TYPE_EVENT) {
1513 /* Comment out for now. */
1514 /*chat_view_append_spacing (view);*/
1517 chat_view_maybe_append_date_and_time (view, NULL);
1519 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1521 gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
1528 gossip_chat_view_scroll_down (view);
1531 priv->last_block_type = BLOCK_TYPE_EVENT;
1535 gossip_chat_view_append_button (GossipChatView *view,
1536 const gchar *message,
1540 GossipChatViewPriv *priv;
1541 GtkTextChildAnchor *anchor;
1546 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1547 g_return_if_fail (button1 != NULL);
1549 priv = GET_PRIV (view);
1551 if (priv->irc_style) {
1554 tag = "fancy-invite";
1557 bottom = chat_view_is_scrolled_down (view);
1559 chat_view_maybe_append_date_and_time (view, NULL);
1562 chat_view_append_text (view, message, tag);
1565 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1567 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1568 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
1569 gtk_widget_show (button1);
1571 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1579 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1581 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1582 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
1583 gtk_widget_show (button2);
1585 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1593 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1594 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1602 gossip_chat_view_scroll_down (view);
1605 priv->last_block_type = BLOCK_TYPE_INVITE;
1609 gossip_chat_view_scroll (GossipChatView *view,
1610 gboolean allow_scrolling)
1612 GossipChatViewPriv *priv;
1614 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1616 priv = GET_PRIV (view);
1618 priv->allow_scrolling = allow_scrolling;
1620 gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
1621 allow_scrolling ? "enabled" : "disabled");
1625 gossip_chat_view_scroll_down (GossipChatView *view)
1627 GossipChatViewPriv *priv;
1628 GtkTextBuffer *buffer;
1632 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1634 priv = GET_PRIV (view);
1636 if (!priv->allow_scrolling) {
1640 gossip_debug (DEBUG_DOMAIN, "Scrolling down");
1642 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1644 gtk_text_buffer_get_end_iter (buffer, &iter);
1645 mark = gtk_text_buffer_create_mark (buffer,
1650 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1657 gtk_text_buffer_delete_mark (buffer, mark);
1661 gossip_chat_view_get_selection_bounds (GossipChatView *view,
1665 GtkTextBuffer *buffer;
1667 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1669 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1671 return gtk_text_buffer_get_selection_bounds (buffer, start, end);
1675 gossip_chat_view_clear (GossipChatView *view)
1677 GtkTextBuffer *buffer;
1678 GossipChatViewPriv *priv;
1680 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1682 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1683 gtk_text_buffer_set_text (buffer, "", -1);
1685 /* We set these back to the initial values so we get
1686 * timestamps when clearing the window to know when
1687 * conversations start.
1689 priv = GET_PRIV (view);
1691 priv->last_block_type = BLOCK_TYPE_NONE;
1692 priv->last_timestamp = 0;
1696 gossip_chat_view_find_previous (GossipChatView *view,
1697 const gchar *search_criteria,
1698 gboolean new_search)
1700 GossipChatViewPriv *priv;
1701 GtkTextBuffer *buffer;
1702 GtkTextIter iter_at_mark;
1703 GtkTextIter iter_match_start;
1704 GtkTextIter iter_match_end;
1706 gboolean from_start = FALSE;
1708 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1709 g_return_val_if_fail (search_criteria != NULL, FALSE);
1711 priv = GET_PRIV (view);
1713 buffer = priv->buffer;
1715 if (G_STR_EMPTY (search_criteria)) {
1716 if (priv->find_mark_previous) {
1717 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1719 gtk_text_buffer_move_mark (buffer,
1720 priv->find_mark_previous,
1722 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1723 priv->find_mark_previous,
1728 gtk_text_buffer_select_range (buffer,
1740 if (priv->find_mark_previous) {
1741 gtk_text_buffer_get_iter_at_mark (buffer,
1743 priv->find_mark_previous);
1745 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1749 priv->find_last_direction = FALSE;
1751 found = gossip_text_iter_backward_search (&iter_at_mark,
1758 gboolean result = FALSE;
1764 /* Here we wrap around. */
1765 if (!new_search && !priv->find_wrapped) {
1766 priv->find_wrapped = TRUE;
1767 result = gossip_chat_view_find_previous (view,
1770 priv->find_wrapped = FALSE;
1776 /* Set new mark and show on screen */
1777 if (!priv->find_mark_previous) {
1778 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1782 gtk_text_buffer_move_mark (buffer,
1783 priv->find_mark_previous,
1787 if (!priv->find_mark_next) {
1788 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1792 gtk_text_buffer_move_mark (buffer,
1793 priv->find_mark_next,
1797 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1798 priv->find_mark_previous,
1804 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1805 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1811 gossip_chat_view_find_next (GossipChatView *view,
1812 const gchar *search_criteria,
1813 gboolean new_search)
1815 GossipChatViewPriv *priv;
1816 GtkTextBuffer *buffer;
1817 GtkTextIter iter_at_mark;
1818 GtkTextIter iter_match_start;
1819 GtkTextIter iter_match_end;
1821 gboolean from_start = FALSE;
1823 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1824 g_return_val_if_fail (search_criteria != NULL, FALSE);
1826 priv = GET_PRIV (view);
1828 buffer = priv->buffer;
1830 if (G_STR_EMPTY (search_criteria)) {
1831 if (priv->find_mark_next) {
1832 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1834 gtk_text_buffer_move_mark (buffer,
1835 priv->find_mark_next,
1837 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1838 priv->find_mark_next,
1843 gtk_text_buffer_select_range (buffer,
1855 if (priv->find_mark_next) {
1856 gtk_text_buffer_get_iter_at_mark (buffer,
1858 priv->find_mark_next);
1860 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1864 priv->find_last_direction = TRUE;
1866 found = gossip_text_iter_forward_search (&iter_at_mark,
1873 gboolean result = FALSE;
1879 /* Here we wrap around. */
1880 if (!new_search && !priv->find_wrapped) {
1881 priv->find_wrapped = TRUE;
1882 result = gossip_chat_view_find_next (view,
1885 priv->find_wrapped = FALSE;
1891 /* Set new mark and show on screen */
1892 if (!priv->find_mark_next) {
1893 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1897 gtk_text_buffer_move_mark (buffer,
1898 priv->find_mark_next,
1902 if (!priv->find_mark_previous) {
1903 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1907 gtk_text_buffer_move_mark (buffer,
1908 priv->find_mark_previous,
1912 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1913 priv->find_mark_next,
1919 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1920 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1927 gossip_chat_view_find_abilities (GossipChatView *view,
1928 const gchar *search_criteria,
1929 gboolean *can_do_previous,
1930 gboolean *can_do_next)
1932 GossipChatViewPriv *priv;
1933 GtkTextBuffer *buffer;
1934 GtkTextIter iter_at_mark;
1935 GtkTextIter iter_match_start;
1936 GtkTextIter iter_match_end;
1938 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1939 g_return_if_fail (search_criteria != NULL);
1940 g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1942 priv = GET_PRIV (view);
1944 buffer = priv->buffer;
1946 if (can_do_previous) {
1947 if (priv->find_mark_previous) {
1948 gtk_text_buffer_get_iter_at_mark (buffer,
1950 priv->find_mark_previous);
1952 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1955 *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
1963 if (priv->find_mark_next) {
1964 gtk_text_buffer_get_iter_at_mark (buffer,
1966 priv->find_mark_next);
1968 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1971 *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
1980 gossip_chat_view_highlight (GossipChatView *view,
1983 GtkTextBuffer *buffer;
1985 GtkTextIter iter_start;
1986 GtkTextIter iter_end;
1987 GtkTextIter iter_match_start;
1988 GtkTextIter iter_match_end;
1991 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1993 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1995 gtk_text_buffer_get_start_iter (buffer, &iter);
1997 gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
1998 gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
2002 if (G_STR_EMPTY (text)) {
2007 found = gossip_text_iter_forward_search (&iter,
2017 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
2021 iter = iter_match_end;
2022 gtk_text_iter_forward_char (&iter);
2027 gossip_chat_view_copy_clipboard (GossipChatView *view)
2029 GtkTextBuffer *buffer;
2030 GtkClipboard *clipboard;
2032 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2034 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2035 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2037 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2041 gossip_chat_view_get_irc_style (GossipChatView *view)
2043 GossipChatViewPriv *priv;
2045 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
2047 priv = GET_PRIV (view);
2049 return priv->irc_style;
2053 gossip_chat_view_set_irc_style (GossipChatView *view,
2056 GossipChatViewPriv *priv;
2058 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2060 priv = GET_PRIV (view);
2062 priv->irc_style = irc_style;
2066 gossip_chat_view_set_margin (GossipChatView *view,
2069 GossipChatViewPriv *priv;
2071 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2073 priv = GET_PRIV (view);
2076 "left-margin", margin,
2077 "right-margin", margin,
2082 gossip_chat_view_get_smiley_image (GossipSmiley smiley)
2084 static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
2085 static gboolean inited = FALSE;
2090 for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2091 pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
2097 return pixbufs[smiley];
2101 gossip_chat_view_get_smiley_text (GossipSmiley smiley)
2105 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
2106 if (smileys[i].smiley != smiley) {
2110 return smileys[i].pattern;
2117 gossip_chat_view_get_smiley_menu (GCallback callback,
2119 GtkTooltips *tooltips)
2126 g_return_val_if_fail (callback != NULL, NULL);
2128 menu = gtk_menu_new ();
2130 for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2134 const gchar *smiley_text;
2136 pixbuf = gossip_chat_view_get_smiley_image (i);
2141 image = gtk_image_new_from_pixbuf (pixbuf);
2143 item = gtk_image_menu_item_new_with_label ("");
2144 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2146 gtk_menu_attach (GTK_MENU (menu), item,
2147 x, x + 1, y, y + 1);
2149 smiley_text = gossip_chat_view_get_smiley_text (i);
2151 gtk_tooltips_set_tip (tooltips,
2156 g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
2157 g_signal_connect (item, "activate", callback, user_data);
2167 gtk_widget_show_all (menu);
2172 /* FIXME: Do we really need this? Better to do it internally only at setup time,
2173 * we will never change it on the fly.
2176 gossip_chat_view_set_is_group_chat (GossipChatView *view,
2177 gboolean is_group_chat)
2179 GossipChatViewPriv *priv;
2180 gboolean theme_rooms = FALSE;
2182 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2184 priv = GET_PRIV (view);
2186 priv->is_group_chat = is_group_chat;
2188 gossip_conf_get_bool (gossip_conf_get (),
2189 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
2192 if (!theme_rooms && is_group_chat) {
2193 gossip_theme_manager_apply (gossip_theme_manager_get (),
2197 gossip_theme_manager_apply_saved (gossip_theme_manager_get (),