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
60 #define MAX_SCROLL_TIME 0.4 /* seconds */
61 #define SCROLL_DELAY 33 /* milliseconds */
63 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
74 struct _GossipChatViewPriv {
75 GtkTextBuffer *buffer;
78 time_t last_timestamp;
79 BlockType last_block_type;
81 gboolean allow_scrolling;
84 gboolean is_group_chat;
86 GtkTextMark *find_mark_previous;
87 GtkTextMark *find_mark_next;
88 gboolean find_wrapped;
89 gboolean find_last_direction;
91 /* This is for the group chat so we know if the "other" last contact
92 * changed, so we know whether to insert a header or not.
94 GossipContact *last_contact;
96 guint notify_system_fonts_id;
97 guint notify_show_avatars_id;
102 const gchar *pattern;
103 } GossipSmileyPattern;
105 static const GossipSmileyPattern smileys[] = {
106 /* Forward smileys. */
107 { GOSSIP_SMILEY_NORMAL, ":)" },
108 { GOSSIP_SMILEY_WINK, ";)" },
109 { GOSSIP_SMILEY_WINK, ";-)" },
110 { GOSSIP_SMILEY_BIGEYE, "=)" },
111 { GOSSIP_SMILEY_NOSE, ":-)" },
112 { GOSSIP_SMILEY_CRY, ":'(" },
113 { GOSSIP_SMILEY_SAD, ":(" },
114 { GOSSIP_SMILEY_SAD, ":-(" },
115 { GOSSIP_SMILEY_SCEPTICAL, ":/" },
116 { GOSSIP_SMILEY_SCEPTICAL, ":\\" },
117 { GOSSIP_SMILEY_BIGSMILE, ":D" },
118 { GOSSIP_SMILEY_BIGSMILE, ":-D" },
119 { GOSSIP_SMILEY_INDIFFERENT, ":|" },
120 { GOSSIP_SMILEY_TOUNGE, ":p" },
121 { GOSSIP_SMILEY_TOUNGE, ":-p" },
122 { GOSSIP_SMILEY_TOUNGE, ":P" },
123 { GOSSIP_SMILEY_TOUNGE, ":-P" },
124 { GOSSIP_SMILEY_TOUNGE, ";p" },
125 { GOSSIP_SMILEY_TOUNGE, ";-p" },
126 { GOSSIP_SMILEY_TOUNGE, ";P" },
127 { GOSSIP_SMILEY_TOUNGE, ";-P" },
128 { GOSSIP_SMILEY_SHOCKED, ":o" },
129 { GOSSIP_SMILEY_SHOCKED, ":-o" },
130 { GOSSIP_SMILEY_SHOCKED, ":O" },
131 { GOSSIP_SMILEY_SHOCKED, ":-O" },
132 { GOSSIP_SMILEY_COOL, "8)" },
133 { GOSSIP_SMILEY_COOL, "B)" },
134 { GOSSIP_SMILEY_SORRY, "*|" },
135 { GOSSIP_SMILEY_KISS, ":*" },
136 { GOSSIP_SMILEY_SHUTUP, ":#" },
137 { GOSSIP_SMILEY_SHUTUP, ":-#" },
138 { GOSSIP_SMILEY_YAWN, "|O" },
139 { GOSSIP_SMILEY_CONFUSED, ":S" },
140 { GOSSIP_SMILEY_CONFUSED, ":s" },
141 { GOSSIP_SMILEY_ANGEL, "<)" },
142 { GOSSIP_SMILEY_OOOH, ":x" },
143 { GOSSIP_SMILEY_LOOKAWAY, "*)" },
144 { GOSSIP_SMILEY_LOOKAWAY, "*-)" },
145 { GOSSIP_SMILEY_BLUSH, "*S" },
146 { GOSSIP_SMILEY_BLUSH, "*s" },
147 { GOSSIP_SMILEY_BLUSH, "*$" },
148 { GOSSIP_SMILEY_COOLBIGSMILE, "8D" },
149 { GOSSIP_SMILEY_ANGRY, ":@" },
150 { GOSSIP_SMILEY_BOSS, "@)" },
151 { GOSSIP_SMILEY_MONKEY, "#)" },
152 { GOSSIP_SMILEY_SILLY, "O)" },
153 { GOSSIP_SMILEY_SICK, "+o(" },
155 /* Backward smileys. */
156 { GOSSIP_SMILEY_NORMAL, "(:" },
157 { GOSSIP_SMILEY_WINK, "(;" },
158 { GOSSIP_SMILEY_WINK, "(-;" },
159 { GOSSIP_SMILEY_BIGEYE, "(=" },
160 { GOSSIP_SMILEY_NOSE, "(-:" },
161 { GOSSIP_SMILEY_CRY, ")':" },
162 { GOSSIP_SMILEY_SAD, "):" },
163 { GOSSIP_SMILEY_SAD, ")-:" },
164 { GOSSIP_SMILEY_SCEPTICAL, "/:" },
165 { GOSSIP_SMILEY_SCEPTICAL, "//:" },
166 { GOSSIP_SMILEY_INDIFFERENT, "|:" },
167 { GOSSIP_SMILEY_TOUNGE, "d:" },
168 { GOSSIP_SMILEY_TOUNGE, "d-:" },
169 { GOSSIP_SMILEY_TOUNGE, "d;" },
170 { GOSSIP_SMILEY_TOUNGE, "d-;" },
171 { GOSSIP_SMILEY_SHOCKED, "o:" },
172 { GOSSIP_SMILEY_SHOCKED, "O:" },
173 { GOSSIP_SMILEY_COOL, "(8" },
174 { GOSSIP_SMILEY_COOL, "(B" },
175 { GOSSIP_SMILEY_SORRY, "|*" },
176 { GOSSIP_SMILEY_KISS, "*:" },
177 { GOSSIP_SMILEY_SHUTUP, "#:" },
178 { GOSSIP_SMILEY_SHUTUP, "#-:" },
179 { GOSSIP_SMILEY_YAWN, "O|" },
180 { GOSSIP_SMILEY_CONFUSED, "S:" },
181 { GOSSIP_SMILEY_CONFUSED, "s:" },
182 { GOSSIP_SMILEY_ANGEL, "(>" },
183 { GOSSIP_SMILEY_OOOH, "x:" },
184 { GOSSIP_SMILEY_LOOKAWAY, "(*" },
185 { GOSSIP_SMILEY_LOOKAWAY, "(-*" },
186 { GOSSIP_SMILEY_BLUSH, "S*" },
187 { GOSSIP_SMILEY_BLUSH, "s*" },
188 { GOSSIP_SMILEY_BLUSH, "$*" },
189 { GOSSIP_SMILEY_ANGRY, "@:" },
190 { GOSSIP_SMILEY_BOSS, "(@" },
191 { GOSSIP_SMILEY_MONKEY, "#)" },
192 { GOSSIP_SMILEY_SILLY, "(O" },
193 { GOSSIP_SMILEY_SICK, ")o+" }
196 static void gossip_chat_view_class_init (GossipChatViewClass *klass);
197 static void gossip_chat_view_init (GossipChatView *view);
198 static void chat_view_finalize (GObject *object);
199 static gboolean chat_view_drag_motion (GtkWidget *widget,
200 GdkDragContext *context,
204 static void chat_view_size_allocate (GtkWidget *widget,
205 GtkAllocation *alloc);
206 static void chat_view_setup_tags (GossipChatView *view);
207 static void chat_view_system_font_update (GossipChatView *view);
208 static void chat_view_notify_system_font_cb (GossipConf *conf,
211 static void chat_view_notify_show_avatars_cb (GossipConf *conf,
214 static void chat_view_populate_popup (GossipChatView *view,
217 static gboolean chat_view_event_cb (GossipChatView *view,
218 GdkEventMotion *event,
220 static gboolean chat_view_url_event_cb (GtkTextTag *tag,
224 GtkTextBuffer *buffer);
225 static void chat_view_open_address_cb (GtkMenuItem *menuitem,
227 static void chat_view_copy_address_cb (GtkMenuItem *menuitem,
229 static void chat_view_clear_view_cb (GtkMenuItem *menuitem,
230 GossipChatView *view);
231 static void chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
234 static gboolean chat_view_is_scrolled_down (GossipChatView *view);
235 static void chat_view_theme_changed_cb (GossipThemeManager *manager,
236 GossipChatView *view);
237 static void chat_view_maybe_append_date_and_time (GossipChatView *view,
239 static void chat_view_append_spacing (GossipChatView *view);
240 static void chat_view_append_text (GossipChatView *view,
243 static void chat_view_maybe_append_fancy_header (GossipChatView *view,
245 static void chat_view_append_irc_action (GossipChatView *view,
247 static void chat_view_append_fancy_action (GossipChatView *view,
249 static void chat_view_append_irc_message (GossipChatView *view,
251 static void chat_view_append_fancy_message (GossipChatView *view,
253 static GdkPixbuf *chat_view_pad_to_size (GdkPixbuf *pixbuf,
256 gint extra_padding_right);
258 G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
261 gossip_chat_view_class_init (GossipChatViewClass *klass)
263 GObjectClass *object_class = G_OBJECT_CLASS (klass);
264 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
266 object_class->finalize = chat_view_finalize;
267 widget_class->size_allocate = chat_view_size_allocate;
268 widget_class->drag_motion = chat_view_drag_motion;
270 g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
274 gossip_chat_view_init (GossipChatView *view)
276 GossipChatViewPriv *priv;
277 gboolean show_avatars;
279 priv = GET_PRIV (view);
281 priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
283 priv->last_block_type = BLOCK_TYPE_NONE;
284 priv->last_timestamp = 0;
286 priv->allow_scrolling = TRUE;
288 priv->is_group_chat = FALSE;
291 "wrap-mode", GTK_WRAP_WORD_CHAR,
293 "cursor-visible", FALSE,
296 priv->notify_system_fonts_id =
297 gossip_conf_notify_add (gossip_conf_get (),
298 "/desktop/gnome/interface/document_font_name",
299 chat_view_notify_system_font_cb,
301 chat_view_system_font_update (view);
303 priv->notify_show_avatars_id =
304 gossip_conf_notify_add (gossip_conf_get (),
305 GOSSIP_PREFS_UI_SHOW_AVATARS,
306 chat_view_notify_show_avatars_cb,
309 chat_view_setup_tags (view);
311 gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
313 show_avatars = FALSE;
314 gossip_conf_get_bool (gossip_conf_get (),
315 GOSSIP_PREFS_UI_SHOW_AVATARS,
318 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
321 g_signal_connect (view,
323 G_CALLBACK (chat_view_populate_popup),
326 g_signal_connect_object (gossip_theme_manager_get (),
328 G_CALLBACK (chat_view_theme_changed_cb),
334 chat_view_finalize (GObject *object)
336 GossipChatView *view;
337 GossipChatViewPriv *priv;
339 view = GOSSIP_CHAT_VIEW (object);
340 priv = GET_PRIV (view);
342 gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
344 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
345 gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
347 if (priv->last_contact) {
348 g_object_unref (priv->last_contact);
350 if (priv->scroll_time) {
351 g_timer_destroy (priv->scroll_time);
353 if (priv->scroll_src) {
354 g_source_remove (priv->scroll_src);
357 G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
361 chat_view_drag_motion (GtkWidget *widget,
362 GdkDragContext *context,
367 /* Don't handle drag motion, since we don't want the view to scroll as
368 * the result of dragging something across it.
375 chat_view_size_allocate (GtkWidget *widget,
376 GtkAllocation *alloc)
380 down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
382 GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
385 gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
390 chat_view_setup_tags (GossipChatView *view)
392 GossipChatViewPriv *priv;
395 priv = GET_PRIV (view);
397 gtk_text_buffer_create_tag (priv->buffer,
401 /* FIXME: Move to the theme and come up with something that looks a bit
404 gtk_text_buffer_create_tag (priv->buffer,
406 "background", "yellow",
409 tag = gtk_text_buffer_create_tag (priv->buffer,
413 g_signal_connect (tag,
415 G_CALLBACK (chat_view_url_event_cb),
418 g_signal_connect (view,
419 "motion-notify-event",
420 G_CALLBACK (chat_view_event_cb),
425 chat_view_system_font_update (GossipChatView *view)
427 PangoFontDescription *font_description = NULL;
430 if (gossip_conf_get_string (gossip_conf_get (),
431 "/desktop/gnome/interface/document_font_name",
432 &font_name) && font_name) {
433 font_description = pango_font_description_from_string (font_name);
436 font_description = NULL;
439 gtk_widget_modify_font (GTK_WIDGET (view), font_description);
441 if (font_description) {
442 pango_font_description_free (font_description);
447 chat_view_notify_system_font_cb (GossipConf *conf,
451 GossipChatView *view;
452 gboolean show_avatars = FALSE;
456 chat_view_system_font_update (view);
458 /* Ugly, again, to adjust the vertical position of the nick... Will fix
459 * this when reworking the theme manager so that view register
460 * themselves with it instead of the other way around.
462 gossip_conf_get_bool (conf,
463 GOSSIP_PREFS_UI_SHOW_AVATARS,
466 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
471 chat_view_notify_show_avatars_cb (GossipConf *conf,
475 GossipChatView *view;
476 GossipChatViewPriv *priv;
477 gboolean show_avatars = FALSE;
480 priv = GET_PRIV (view);
482 gossip_conf_get_bool (conf, key, &show_avatars);
484 gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
489 chat_view_populate_popup (GossipChatView *view,
493 GossipChatViewPriv *priv;
494 GtkTextTagTable *table;
497 GtkTextIter iter, start, end;
501 priv = GET_PRIV (view);
503 /* Clear menu item */
504 if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
505 item = gtk_menu_item_new ();
506 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
507 gtk_widget_show (item);
509 item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
510 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
511 gtk_widget_show (item);
513 g_signal_connect (item,
515 G_CALLBACK (chat_view_clear_view_cb),
519 /* Link context menu items */
520 table = gtk_text_buffer_get_tag_table (priv->buffer);
521 tag = gtk_text_tag_table_lookup (table, "link");
523 gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
525 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
526 GTK_TEXT_WINDOW_WIDGET,
530 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
534 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
535 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
536 str = gtk_text_buffer_get_text (priv->buffer,
537 &start, &end, FALSE);
540 if (G_STR_EMPTY (str)) {
545 /* NOTE: Set data just to get the string freed when not needed. */
546 g_object_set_data_full (G_OBJECT (menu),
548 (GDestroyNotify) g_free);
550 item = gtk_menu_item_new ();
551 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
552 gtk_widget_show (item);
554 item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
555 g_signal_connect (item,
557 G_CALLBACK (chat_view_copy_address_cb),
559 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
560 gtk_widget_show (item);
562 item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
563 g_signal_connect (item,
565 G_CALLBACK (chat_view_open_address_cb),
567 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
568 gtk_widget_show (item);
572 chat_view_event_cb (GossipChatView *view,
573 GdkEventMotion *event,
576 static GdkCursor *hand = NULL;
577 static GdkCursor *beam = NULL;
578 GtkTextWindowType type;
581 gint x, y, buf_x, buf_y;
583 type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
586 if (type != GTK_TEXT_WINDOW_TEXT) {
590 /* Get where the pointer really is. */
591 win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
596 gdk_window_get_pointer (win, &x, &y, NULL);
598 /* Get the iter where the cursor is at */
599 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
603 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
607 if (gtk_text_iter_has_tag (&iter, tag)) {
609 hand = gdk_cursor_new (GDK_HAND2);
610 beam = gdk_cursor_new (GDK_XTERM);
612 gdk_window_set_cursor (win, hand);
615 beam = gdk_cursor_new (GDK_XTERM);
617 gdk_window_set_cursor (win, beam);
624 chat_view_url_event_cb (GtkTextTag *tag,
628 GtkTextBuffer *buffer)
630 GtkTextIter start, end;
633 /* If the link is being selected, don't do anything. */
634 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
635 if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
639 if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
642 if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
643 gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
644 str = gtk_text_buffer_get_text (buffer,
649 gossip_url_show (str);
658 chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
660 gossip_url_show (url);
664 chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
666 GtkClipboard *clipboard;
668 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
669 gtk_clipboard_set_text (clipboard, url, -1);
671 clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
672 gtk_clipboard_set_text (clipboard, url, -1);
676 chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
678 gossip_chat_view_clear (view);
682 chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
691 gboolean use_smileys = FALSE;
693 gossip_conf_get_bool (gossip_conf_get (),
694 GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
698 gtk_text_buffer_insert (buf, iter, str, -1);
703 gint smileys_index[G_N_ELEMENTS (smileys)];
708 memset (smileys_index, 0, sizeof (smileys_index));
716 c = g_utf8_get_char (p);
718 if (match != -1 && g_unichar_isspace (c)) {
724 if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
727 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
728 /* Only try to match if we already have
729 * a beginning match for the pattern, or
730 * if it's the first character in the
731 * pattern, if it's not in the middle of
734 if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
735 smileys_index[i] > 0) &&
736 smileys[i].pattern[smileys_index[i]] == c) {
740 if (!smileys[i].pattern[smileys_index[i]]) {
744 smileys_index[i] = 0;
750 p = g_utf8_next_char (p);
754 gtk_text_buffer_insert (buf, iter, str, -1);
758 start = p - strlen (smileys[match].pattern);
762 gtk_text_buffer_insert (buf, iter, str, len);
765 pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
766 gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
768 gtk_text_buffer_insert (buf, iter, " ", 1);
770 str = g_utf8_find_next_char (p, NULL);
775 chat_view_is_scrolled_down (GossipChatView *view)
779 sw = gtk_widget_get_parent (GTK_WIDGET (view));
780 if (GTK_IS_SCROLLED_WINDOW (sw)) {
783 vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
785 if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
794 chat_view_maybe_trim_buffer (GossipChatView *view)
796 GossipChatViewPriv *priv;
797 GtkTextIter top, bottom;
800 GtkTextTagTable *table;
803 priv = GET_PRIV (view);
805 gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
806 line = gtk_text_iter_get_line (&bottom);
807 if (line < MAX_LINES) {
811 remove = line - MAX_LINES;
812 gtk_text_buffer_get_start_iter (priv->buffer, &top);
815 if (!gtk_text_iter_forward_lines (&bottom, remove)) {
819 /* Track backwords to a place where we can safely cut, we don't do it in
820 * the middle of a tag.
822 table = gtk_text_buffer_get_tag_table (priv->buffer);
823 tag = gtk_text_tag_table_lookup (table, "cut");
828 if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
832 if (!gtk_text_iter_equal (&top, &bottom)) {
833 gtk_text_buffer_delete (priv->buffer, &top, &bottom);
838 chat_view_maybe_append_date_and_time (GossipChatView *view,
841 GossipChatViewPriv *priv;
844 GDate *date, *last_date;
846 gboolean append_date, append_time;
849 priv = GET_PRIV (view);
851 if (priv->irc_style) {
857 if (priv->last_block_type == BLOCK_TYPE_TIME) {
861 str = g_string_new (NULL);
865 timestamp = gossip_message_get_timestamp (msg);
868 if (timestamp <= 0) {
869 timestamp = gossip_time_get_current ();
872 date = g_date_new ();
873 g_date_set_time (date, timestamp);
875 last_date = g_date_new ();
876 g_date_set_time (last_date, priv->last_timestamp);
881 if (g_date_compare (date, last_date) > 0) {
886 if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
890 if (append_time || append_date) {
891 chat_view_append_spacing (view);
893 g_string_append (str, "- ");
899 g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
900 g_string_append (str, buf);
903 g_string_append (str, ", ");
908 g_date_free (last_date);
913 tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
914 g_string_append (str, tmp);
918 if (append_time || append_date) {
919 g_string_append (str, " -\n");
921 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
922 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
928 priv->last_block_type = BLOCK_TYPE_TIME;
929 priv->last_timestamp = timestamp;
932 g_string_free (str, TRUE);
936 chat_view_append_spacing (GossipChatView *view)
938 GossipChatViewPriv *priv;
942 priv = GET_PRIV (view);
944 if (priv->irc_style) {
947 tag = "fancy-spacing";
950 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
951 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
961 chat_view_append_text (GossipChatView *view,
965 GossipChatViewPriv *priv;
966 GtkTextIter start_iter, end_iter;
971 const gchar *link_tag;
973 priv = GET_PRIV (view);
975 if (priv->irc_style) {
976 link_tag = "irc-link";
978 link_tag = "fancy-link";
981 gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
982 mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
984 start = g_array_new (FALSE, FALSE, sizeof (gint));
985 end = g_array_new (FALSE, FALSE, sizeof (gint));
987 num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
989 if (num_matches == 0) {
990 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
991 chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
997 for (i = 0; i < num_matches; i++) {
998 s = g_array_index (start, gint, i);
999 e = g_array_index (end, gint, i);
1002 tmp = gossip_substring (body, last, s);
1004 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1005 chat_view_insert_text_with_emoticons (priv->buffer,
1011 tmp = gossip_substring (body, s, e);
1013 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1014 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1027 if (e < strlen (body)) {
1028 tmp = gossip_substring (body, e, strlen (body));
1030 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1031 chat_view_insert_text_with_emoticons (priv->buffer,
1038 g_array_free (start, TRUE);
1039 g_array_free (end, TRUE);
1041 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1042 gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
1044 /* Apply the style to the inserted text. */
1045 gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
1046 gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
1048 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1053 gtk_text_buffer_delete_mark (priv->buffer, mark);
1057 chat_view_maybe_append_fancy_header (GossipChatView *view,
1060 GossipChatViewPriv *priv;
1061 GossipContact *sender;
1062 GossipContact *my_contact;
1068 const gchar *avatar_tag;
1069 const gchar *line_top_tag;
1070 const gchar *line_bottom_tag;
1072 GdkPixbuf *pixbuf = NULL;
1073 GdkPixbuf *avatar = NULL;
1075 priv = GET_PRIV (view);
1077 sender = gossip_message_get_sender (msg);
1078 my_contact = gossip_contact_get_user (sender);
1079 name = gossip_contact_get_name (sender);
1080 from_self = gossip_contact_equal (sender, my_contact);
1082 gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
1085 tag = "fancy-header-self";
1086 line_top_tag = "fancy-line-top-self";
1087 line_bottom_tag = "fancy-line-bottom-self";
1089 tag = "fancy-header-other";
1090 line_top_tag = "fancy-line-top-other";
1091 line_bottom_tag = "fancy-line-bottom-other";
1096 /* Only insert a header if the previously inserted block is not the same
1097 * as this one. This catches all the different cases:
1099 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1100 priv->last_block_type != BLOCK_TYPE_OTHER) {
1103 else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
1106 else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
1109 else if (!from_self &&
1110 (!priv->last_contact ||
1111 !gossip_contact_equal (sender, priv->last_contact))) {
1119 chat_view_append_spacing (view);
1121 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1122 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1129 /* FIXME: we should have a cash of avatar pixbufs */
1130 pixbuf = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
1132 avatar = chat_view_pad_to_size (pixbuf, 32, 32, 6);
1133 g_object_unref (pixbuf);
1139 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1140 gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
1142 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1144 gtk_text_iter_backward_char (&start);
1147 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1148 "fancy-avatar-self",
1150 avatar_tag = "fancy-header-self-avatar";
1152 gtk_text_buffer_apply_tag_by_name (priv->buffer,
1153 "fancy-avatar-other",
1155 avatar_tag = "fancy-header-other-avatar";
1158 g_object_unref (avatar);
1163 tmp = g_strdup_printf ("%s\n", name);
1165 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1166 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1175 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1176 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1185 chat_view_append_irc_action (GossipChatView *view,
1188 GossipChatViewPriv *priv;
1189 GossipContact *my_contact;
1190 GossipContact *sender;
1197 priv = GET_PRIV (view);
1199 gossip_debug (DEBUG_DOMAIN, "Add IRC action");
1201 sender = gossip_message_get_sender (msg);
1202 my_contact = gossip_contact_get_user (sender);
1203 name = gossip_contact_get_name (sender);
1205 /* Skip the "/me ". */
1206 if (gossip_contact_equal (sender, my_contact)) {
1207 tag = "irc-action-self";
1209 tag = "irc-action-other";
1212 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1213 priv->last_block_type != BLOCK_TYPE_OTHER) {
1214 chat_view_append_spacing (view);
1217 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1219 tmp = g_strdup_printf (" * %s ", name);
1220 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1229 body = gossip_message_get_body (msg);
1230 chat_view_append_text (view, body, tag);
1234 chat_view_append_fancy_action (GossipChatView *view,
1237 GossipChatViewPriv *priv;
1238 GossipContact *sender;
1239 GossipContact *my_contact;
1245 const gchar *line_tag;
1247 priv = GET_PRIV (view);
1249 gossip_debug (DEBUG_DOMAIN, "Add fancy action");
1251 sender = gossip_message_get_sender (msg);
1252 my_contact = gossip_contact_get_user (sender);
1253 name = gossip_contact_get_name (sender);
1255 if (gossip_contact_equal (sender, my_contact)) {
1256 tag = "fancy-action-self";
1257 line_tag = "fancy-line-self";
1259 tag = "fancy-action-other";
1260 line_tag = "fancy-line-other";
1263 tmp = g_strdup_printf (" * %s ", name);
1264 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1265 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1273 body = gossip_message_get_body (msg);
1274 chat_view_append_text (view, body, tag);
1278 chat_view_append_irc_message (GossipChatView *view,
1281 GossipChatViewPriv *priv;
1282 GossipContact *sender;
1283 GossipContact *my_contact;
1286 const gchar *nick_tag;
1287 const gchar *body_tag;
1291 priv = GET_PRIV (view);
1293 gossip_debug (DEBUG_DOMAIN, "Add IRC message");
1295 body = gossip_message_get_body (msg);
1296 sender = gossip_message_get_sender (msg);
1297 my_contact = gossip_contact_get_user (sender);
1298 name = gossip_contact_get_name (sender);
1300 if (gossip_contact_equal (sender, my_contact)) {
1301 nick_tag = "irc-nick-self";
1302 body_tag = "irc-body-self";
1304 if (gossip_chat_should_highlight_nick (msg)) {
1305 nick_tag = "irc-nick-highlight";
1307 nick_tag = "irc-nick-other";
1310 body_tag = "irc-body-other";
1313 if (priv->last_block_type != BLOCK_TYPE_SELF &&
1314 priv->last_block_type != BLOCK_TYPE_OTHER) {
1315 chat_view_append_spacing (view);
1318 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1321 tmp = g_strdup_printf ("%s: ", name);
1322 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1331 /* The text body. */
1332 chat_view_append_text (view, body, body_tag);
1336 chat_view_append_fancy_message (GossipChatView *view,
1339 GossipChatViewPriv *priv;
1340 GossipContact *sender;
1341 GossipContact *my_contact;
1345 priv = GET_PRIV (view);
1347 sender = gossip_message_get_sender (msg);
1348 my_contact = gossip_contact_get_user (sender);
1350 if (gossip_contact_equal (sender, my_contact)) {
1351 tag = "fancy-body-self";
1353 tag = "fancy-body-other";
1355 /* FIXME: Might want to support nick highlighting here... */
1358 body = gossip_message_get_body (msg);
1359 chat_view_append_text (view, body, tag);
1363 chat_view_theme_changed_cb (GossipThemeManager *manager,
1364 GossipChatView *view)
1366 GossipChatViewPriv *priv;
1367 gboolean show_avatars = FALSE;
1368 gboolean theme_rooms = FALSE;
1370 priv = GET_PRIV (view);
1372 priv->last_block_type = BLOCK_TYPE_NONE;
1374 gossip_conf_get_bool (gossip_conf_get (),
1375 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
1377 if (!theme_rooms && priv->is_group_chat) {
1378 gossip_theme_manager_apply (manager, view, NULL);
1380 gossip_theme_manager_apply_saved (manager, view);
1383 /* Needed for now to update the "rise" property of the names to get it
1384 * vertically centered.
1386 gossip_conf_get_bool (gossip_conf_get (),
1387 GOSSIP_PREFS_UI_SHOW_AVATARS,
1389 gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
1392 /* Pads a pixbuf to the specified size, by centering it in a larger transparent
1393 * pixbuf. Returns a new ref.
1396 chat_view_pad_to_size (GdkPixbuf *pixbuf,
1399 gint extra_padding_right)
1401 gint src_width, src_height;
1403 gint x_offset, y_offset;
1405 src_width = gdk_pixbuf_get_width (pixbuf);
1406 src_height = gdk_pixbuf_get_height (pixbuf);
1408 x_offset = (width - src_width) / 2;
1409 y_offset = (height - src_height) / 2;
1411 padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
1413 gdk_pixbuf_get_bits_per_sample (pixbuf),
1414 width + extra_padding_right,
1417 gdk_pixbuf_fill (padded, 0);
1419 gdk_pixbuf_copy_area (pixbuf,
1420 0, /* source coords */
1425 x_offset, /* dest coords */
1432 gossip_chat_view_new (void)
1434 return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
1437 /* The name is optional, if NULL, the sender for msg is used. */
1439 gossip_chat_view_append_message (GossipChatView *view,
1442 GossipChatViewPriv *priv;
1443 GossipContact *sender;
1444 GossipContact *my_contact;
1446 gboolean scroll_down;
1448 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1449 g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
1451 priv = GET_PRIV (view);
1453 body = gossip_message_get_body (msg);
1458 scroll_down = chat_view_is_scrolled_down (view);
1460 chat_view_maybe_trim_buffer (view);
1461 chat_view_maybe_append_date_and_time (view, msg);
1463 sender = gossip_message_get_sender (msg);
1465 if (!priv->irc_style) {
1466 chat_view_maybe_append_fancy_header (view, msg);
1469 if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
1470 if (priv->irc_style) {
1471 chat_view_append_irc_action (view, msg);
1473 chat_view_append_fancy_action (view, msg);
1476 if (priv->irc_style) {
1477 chat_view_append_irc_message (view, msg);
1479 chat_view_append_fancy_message (view, msg);
1483 my_contact = gossip_contact_get_user (sender);
1485 /* Reset the last inserted contact. */
1486 if (priv->last_contact) {
1487 g_object_unref (priv->last_contact);
1490 if (gossip_contact_equal (my_contact, sender)) {
1491 priv->last_block_type = BLOCK_TYPE_SELF;
1492 priv->last_contact = NULL;
1494 priv->last_block_type = BLOCK_TYPE_OTHER;
1495 priv->last_contact = g_object_ref (sender);
1499 gossip_chat_view_scroll_down (view);
1504 gossip_chat_view_append_event (GossipChatView *view,
1507 GossipChatViewPriv *priv;
1513 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1514 g_return_if_fail (!G_STR_EMPTY (str));
1516 priv = GET_PRIV (view);
1518 bottom = chat_view_is_scrolled_down (view);
1520 chat_view_maybe_trim_buffer (view);
1522 if (priv->irc_style) {
1524 msg = g_strdup_printf (" - %s\n", str);
1526 tag = "fancy-event";
1527 msg = g_strdup_printf (" - %s\n", str);
1530 if (priv->last_block_type != BLOCK_TYPE_EVENT) {
1531 /* Comment out for now. */
1532 /*chat_view_append_spacing (view);*/
1535 chat_view_maybe_append_date_and_time (view, NULL);
1537 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1539 gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
1546 gossip_chat_view_scroll_down (view);
1549 priv->last_block_type = BLOCK_TYPE_EVENT;
1553 gossip_chat_view_append_button (GossipChatView *view,
1554 const gchar *message,
1558 GossipChatViewPriv *priv;
1559 GtkTextChildAnchor *anchor;
1564 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1565 g_return_if_fail (button1 != NULL);
1567 priv = GET_PRIV (view);
1569 if (priv->irc_style) {
1572 tag = "fancy-invite";
1575 bottom = chat_view_is_scrolled_down (view);
1577 chat_view_maybe_append_date_and_time (view, NULL);
1580 chat_view_append_text (view, message, tag);
1583 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1585 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1586 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
1587 gtk_widget_show (button1);
1589 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1597 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1599 anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
1600 gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
1601 gtk_widget_show (button2);
1603 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1611 gtk_text_buffer_get_end_iter (priv->buffer, &iter);
1612 gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
1620 gossip_chat_view_scroll_down (view);
1623 priv->last_block_type = BLOCK_TYPE_INVITE;
1627 gossip_chat_view_scroll (GossipChatView *view,
1628 gboolean allow_scrolling)
1630 GossipChatViewPriv *priv;
1632 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1634 priv = GET_PRIV (view);
1636 priv->allow_scrolling = allow_scrolling;
1638 gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
1639 allow_scrolling ? "enabled" : "disabled");
1642 /* Code stolen from pidgin/gtkimhtml.c */
1644 chat_view_scroll_cb (GossipChatView *view)
1646 GossipChatViewPriv *priv;
1650 priv = GET_PRIV (view);
1651 adj = GTK_TEXT_VIEW (view)->vadjustment;
1652 max_val = adj->upper - adj->page_size;
1654 g_return_val_if_fail (priv->scroll_time != NULL, FALSE);
1656 if (g_timer_elapsed (priv->scroll_time, NULL) > MAX_SCROLL_TIME) {
1657 /* time's up. jump to the end and kill the timer */
1658 gtk_adjustment_set_value (adj, max_val);
1659 g_timer_destroy (priv->scroll_time);
1660 priv->scroll_time = NULL;
1661 priv->scroll_src = 0;
1665 /* scroll by 1/3rd the remaining distance */
1666 gtk_adjustment_set_value (adj, gtk_adjustment_get_value (adj) + ((max_val - gtk_adjustment_get_value (adj)) / 3));
1671 gossip_chat_view_scroll_down (GossipChatView *view)
1673 GossipChatViewPriv *priv;
1675 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1677 priv = GET_PRIV (view);
1679 if (!priv->allow_scrolling) {
1683 gossip_debug (DEBUG_DOMAIN, "Scrolling down");
1685 if (priv->scroll_time) {
1686 g_timer_destroy (priv->scroll_time);
1688 if (priv->scroll_src) {
1689 g_source_remove (priv->scroll_src);
1692 priv->scroll_time = g_timer_new();
1693 priv->scroll_src = g_timeout_add (SCROLL_DELAY,
1694 (GSourceFunc) chat_view_scroll_cb,
1699 gossip_chat_view_get_selection_bounds (GossipChatView *view,
1703 GtkTextBuffer *buffer;
1705 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1707 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1709 return gtk_text_buffer_get_selection_bounds (buffer, start, end);
1713 gossip_chat_view_clear (GossipChatView *view)
1715 GtkTextBuffer *buffer;
1716 GossipChatViewPriv *priv;
1718 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1720 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
1721 gtk_text_buffer_set_text (buffer, "", -1);
1723 /* We set these back to the initial values so we get
1724 * timestamps when clearing the window to know when
1725 * conversations start.
1727 priv = GET_PRIV (view);
1729 priv->last_block_type = BLOCK_TYPE_NONE;
1730 priv->last_timestamp = 0;
1734 gossip_chat_view_find_previous (GossipChatView *view,
1735 const gchar *search_criteria,
1736 gboolean new_search)
1738 GossipChatViewPriv *priv;
1739 GtkTextBuffer *buffer;
1740 GtkTextIter iter_at_mark;
1741 GtkTextIter iter_match_start;
1742 GtkTextIter iter_match_end;
1744 gboolean from_start = FALSE;
1746 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1747 g_return_val_if_fail (search_criteria != NULL, FALSE);
1749 priv = GET_PRIV (view);
1751 buffer = priv->buffer;
1753 if (G_STR_EMPTY (search_criteria)) {
1754 if (priv->find_mark_previous) {
1755 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1757 gtk_text_buffer_move_mark (buffer,
1758 priv->find_mark_previous,
1760 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1761 priv->find_mark_previous,
1766 gtk_text_buffer_select_range (buffer,
1778 if (priv->find_mark_previous) {
1779 gtk_text_buffer_get_iter_at_mark (buffer,
1781 priv->find_mark_previous);
1783 gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
1787 priv->find_last_direction = FALSE;
1789 found = gossip_text_iter_backward_search (&iter_at_mark,
1796 gboolean result = FALSE;
1802 /* Here we wrap around. */
1803 if (!new_search && !priv->find_wrapped) {
1804 priv->find_wrapped = TRUE;
1805 result = gossip_chat_view_find_previous (view,
1808 priv->find_wrapped = FALSE;
1814 /* Set new mark and show on screen */
1815 if (!priv->find_mark_previous) {
1816 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1820 gtk_text_buffer_move_mark (buffer,
1821 priv->find_mark_previous,
1825 if (!priv->find_mark_next) {
1826 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1830 gtk_text_buffer_move_mark (buffer,
1831 priv->find_mark_next,
1835 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1836 priv->find_mark_previous,
1842 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1843 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1849 gossip_chat_view_find_next (GossipChatView *view,
1850 const gchar *search_criteria,
1851 gboolean new_search)
1853 GossipChatViewPriv *priv;
1854 GtkTextBuffer *buffer;
1855 GtkTextIter iter_at_mark;
1856 GtkTextIter iter_match_start;
1857 GtkTextIter iter_match_end;
1859 gboolean from_start = FALSE;
1861 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
1862 g_return_val_if_fail (search_criteria != NULL, FALSE);
1864 priv = GET_PRIV (view);
1866 buffer = priv->buffer;
1868 if (G_STR_EMPTY (search_criteria)) {
1869 if (priv->find_mark_next) {
1870 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1872 gtk_text_buffer_move_mark (buffer,
1873 priv->find_mark_next,
1875 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1876 priv->find_mark_next,
1881 gtk_text_buffer_select_range (buffer,
1893 if (priv->find_mark_next) {
1894 gtk_text_buffer_get_iter_at_mark (buffer,
1896 priv->find_mark_next);
1898 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1902 priv->find_last_direction = TRUE;
1904 found = gossip_text_iter_forward_search (&iter_at_mark,
1911 gboolean result = FALSE;
1917 /* Here we wrap around. */
1918 if (!new_search && !priv->find_wrapped) {
1919 priv->find_wrapped = TRUE;
1920 result = gossip_chat_view_find_next (view,
1923 priv->find_wrapped = FALSE;
1929 /* Set new mark and show on screen */
1930 if (!priv->find_mark_next) {
1931 priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
1935 gtk_text_buffer_move_mark (buffer,
1936 priv->find_mark_next,
1940 if (!priv->find_mark_previous) {
1941 priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
1945 gtk_text_buffer_move_mark (buffer,
1946 priv->find_mark_previous,
1950 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
1951 priv->find_mark_next,
1957 gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
1958 gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
1965 gossip_chat_view_find_abilities (GossipChatView *view,
1966 const gchar *search_criteria,
1967 gboolean *can_do_previous,
1968 gboolean *can_do_next)
1970 GossipChatViewPriv *priv;
1971 GtkTextBuffer *buffer;
1972 GtkTextIter iter_at_mark;
1973 GtkTextIter iter_match_start;
1974 GtkTextIter iter_match_end;
1976 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
1977 g_return_if_fail (search_criteria != NULL);
1978 g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
1980 priv = GET_PRIV (view);
1982 buffer = priv->buffer;
1984 if (can_do_previous) {
1985 if (priv->find_mark_previous) {
1986 gtk_text_buffer_get_iter_at_mark (buffer,
1988 priv->find_mark_previous);
1990 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
1993 *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
2001 if (priv->find_mark_next) {
2002 gtk_text_buffer_get_iter_at_mark (buffer,
2004 priv->find_mark_next);
2006 gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
2009 *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
2018 gossip_chat_view_highlight (GossipChatView *view,
2021 GtkTextBuffer *buffer;
2023 GtkTextIter iter_start;
2024 GtkTextIter iter_end;
2025 GtkTextIter iter_match_start;
2026 GtkTextIter iter_match_end;
2029 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2031 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2033 gtk_text_buffer_get_start_iter (buffer, &iter);
2035 gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
2036 gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
2040 if (G_STR_EMPTY (text)) {
2045 found = gossip_text_iter_forward_search (&iter,
2055 gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
2059 iter = iter_match_end;
2060 gtk_text_iter_forward_char (&iter);
2065 gossip_chat_view_copy_clipboard (GossipChatView *view)
2067 GtkTextBuffer *buffer;
2068 GtkClipboard *clipboard;
2070 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2072 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
2073 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2075 gtk_text_buffer_copy_clipboard (buffer, clipboard);
2079 gossip_chat_view_get_irc_style (GossipChatView *view)
2081 GossipChatViewPriv *priv;
2083 g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
2085 priv = GET_PRIV (view);
2087 return priv->irc_style;
2091 gossip_chat_view_set_irc_style (GossipChatView *view,
2094 GossipChatViewPriv *priv;
2096 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2098 priv = GET_PRIV (view);
2100 priv->irc_style = irc_style;
2104 gossip_chat_view_set_margin (GossipChatView *view,
2107 GossipChatViewPriv *priv;
2109 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2111 priv = GET_PRIV (view);
2114 "left-margin", margin,
2115 "right-margin", margin,
2120 gossip_chat_view_get_smiley_image (GossipSmiley smiley)
2122 static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
2123 static gboolean inited = FALSE;
2128 for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2129 pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
2135 return pixbufs[smiley];
2139 gossip_chat_view_get_smiley_text (GossipSmiley smiley)
2143 for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
2144 if (smileys[i].smiley != smiley) {
2148 return smileys[i].pattern;
2155 gossip_chat_view_get_smiley_menu (GCallback callback,
2157 GtkTooltips *tooltips)
2164 g_return_val_if_fail (callback != NULL, NULL);
2166 menu = gtk_menu_new ();
2168 for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
2172 const gchar *smiley_text;
2174 pixbuf = gossip_chat_view_get_smiley_image (i);
2179 image = gtk_image_new_from_pixbuf (pixbuf);
2181 item = gtk_image_menu_item_new_with_label ("");
2182 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2184 gtk_menu_attach (GTK_MENU (menu), item,
2185 x, x + 1, y, y + 1);
2187 smiley_text = gossip_chat_view_get_smiley_text (i);
2189 gtk_tooltips_set_tip (tooltips,
2194 g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
2195 g_signal_connect (item, "activate", callback, user_data);
2205 gtk_widget_show_all (menu);
2210 /* FIXME: Do we really need this? Better to do it internally only at setup time,
2211 * we will never change it on the fly.
2214 gossip_chat_view_set_is_group_chat (GossipChatView *view,
2215 gboolean is_group_chat)
2217 GossipChatViewPriv *priv;
2218 gboolean theme_rooms = FALSE;
2220 g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
2222 priv = GET_PRIV (view);
2224 priv->is_group_chat = is_group_chat;
2226 gossip_conf_get_bool (gossip_conf_get (),
2227 GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
2230 if (!theme_rooms && is_group_chat) {
2231 gossip_theme_manager_apply (gossip_theme_manager_get (),
2235 gossip_theme_manager_apply_saved (gossip_theme_manager_get (),