1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2002-2007 Imendio AB
4 * Copyright (C) 2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Richard Hult <richard@imendio.com>
23 * Martyn Russell <martyn@imendio.com>
24 * Xavier Claessens <xclaesse@gmail.com>
29 #include <sys/types.h>
33 #include <glib/gi18n-lib.h>
36 #include <telepathy-glib/util.h>
38 #include <libempathy/empathy-utils.h>
40 #include "empathy-chat-text-view.h"
41 #include "empathy-chat.h"
42 #include "empathy-conf.h"
43 #include "empathy-ui-utils.h"
44 #include "empathy-smiley-manager.h"
46 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
47 #include <libempathy/empathy-debug.h>
49 /* Number of seconds between timestamps when using normal mode, 5 minutes. */
50 #define TIMESTAMP_INTERVAL 300
53 #define MAX_SCROLL_TIME 0.4 /* seconds */
54 #define SCROLL_DELAY 33 /* milliseconds */
56 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatTextView)
59 GtkTextBuffer
*buffer
;
62 GtkTextMark
*find_mark_previous
;
63 GtkTextMark
*find_mark_next
;
64 gboolean find_wrapped
;
65 gboolean find_last_direction
;
66 EmpathyContact
*last_contact
;
67 time_t last_timestamp
;
68 gboolean allow_scrolling
;
69 guint notify_system_fonts_id
;
70 EmpathySmileyManager
*smiley_manager
;
71 gboolean only_if_date
;
72 } EmpathyChatTextViewPriv
;
74 static void chat_text_view_iface_init (EmpathyChatViewIface
*iface
);
76 static void chat_text_view_copy_clipboard (EmpathyChatView
*view
);
78 G_DEFINE_TYPE_WITH_CODE (EmpathyChatTextView
, empathy_chat_text_view
,
80 G_IMPLEMENT_INTERFACE (EMPATHY_TYPE_CHAT_VIEW
,
81 chat_text_view_iface_init
));
90 chat_text_view_url_event_cb (GtkTextTag
*tag
,
94 EmpathyChatTextView
*view
)
96 EmpathyChatTextViewPriv
*priv
;
97 GtkTextIter start
, end
;
100 priv
= GET_PRIV (view
);
102 /* If the link is being selected, don't do anything. */
103 gtk_text_buffer_get_selection_bounds (priv
->buffer
, &start
, &end
);
104 if (gtk_text_iter_get_offset (&start
) != gtk_text_iter_get_offset (&end
)) {
108 if (event
->type
== GDK_BUTTON_RELEASE
&& event
->button
.button
== 1) {
111 if (gtk_text_iter_backward_to_tag_toggle (&start
, tag
) &&
112 gtk_text_iter_forward_to_tag_toggle (&end
, tag
)) {
113 str
= gtk_text_buffer_get_text (priv
->buffer
,
118 empathy_url_show (GTK_WIDGET (view
), str
);
127 chat_text_view_event_cb (EmpathyChatTextView
*view
,
128 GdkEventMotion
*event
,
131 static GdkCursor
*hand
= NULL
;
132 static GdkCursor
*beam
= NULL
;
133 GtkTextWindowType type
;
136 gint x
, y
, buf_x
, buf_y
;
138 type
= gtk_text_view_get_window_type (GTK_TEXT_VIEW (view
),
141 if (type
!= GTK_TEXT_WINDOW_TEXT
) {
145 /* Get where the pointer really is. */
146 win
= gtk_text_view_get_window (GTK_TEXT_VIEW (view
), type
);
151 gdk_window_get_pointer (win
, &x
, &y
, NULL
);
153 /* Get the iter where the cursor is at */
154 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view
), type
,
158 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view
),
162 if (gtk_text_iter_has_tag (&iter
, tag
)) {
164 hand
= gdk_cursor_new (GDK_HAND2
);
165 beam
= gdk_cursor_new (GDK_XTERM
);
167 gdk_window_set_cursor (win
, hand
);
170 beam
= gdk_cursor_new (GDK_XTERM
);
172 gdk_window_set_cursor (win
, beam
);
179 chat_text_view_create_tags (EmpathyChatTextView
*view
)
181 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
184 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT
, NULL
);
185 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT
, NULL
);
186 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING
, NULL
);
187 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_TIME
, NULL
);
188 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_ACTION
, NULL
);
189 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_BODY
, NULL
);
190 gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_EVENT
, NULL
);
192 tag
= gtk_text_buffer_create_tag (priv
->buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK
, NULL
);
193 g_signal_connect (tag
, "event",
194 G_CALLBACK (chat_text_view_url_event_cb
),
197 g_signal_connect (view
, "motion-notify-event",
198 G_CALLBACK (chat_text_view_event_cb
),
203 chat_text_view_system_font_update (EmpathyChatTextView
*view
)
205 PangoFontDescription
*font_description
= NULL
;
208 if (empathy_conf_get_string (empathy_conf_get (),
209 "/desktop/gnome/interface/document_font_name",
210 &font_name
) && font_name
) {
211 font_description
= pango_font_description_from_string (font_name
);
214 font_description
= NULL
;
217 gtk_widget_modify_font (GTK_WIDGET (view
), font_description
);
219 if (font_description
) {
220 pango_font_description_free (font_description
);
225 chat_text_view_notify_system_font_cb (EmpathyConf
*conf
,
229 EmpathyChatTextView
*view
= user_data
;
231 chat_text_view_system_font_update (view
);
235 chat_text_view_open_address_cb (GtkMenuItem
*menuitem
, const gchar
*url
)
237 empathy_url_show (GTK_WIDGET (menuitem
), url
);
241 chat_text_view_copy_address_cb (GtkMenuItem
*menuitem
, const gchar
*url
)
243 GtkClipboard
*clipboard
;
245 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
246 gtk_clipboard_set_text (clipboard
, url
, -1);
248 clipboard
= gtk_clipboard_get (GDK_SELECTION_PRIMARY
);
249 gtk_clipboard_set_text (clipboard
, url
, -1);
253 chat_text_view_populate_popup (EmpathyChatTextView
*view
,
257 EmpathyChatTextViewPriv
*priv
;
258 GtkTextTagTable
*table
;
261 GtkTextIter iter
, start
, end
;
265 priv
= GET_PRIV (view
);
267 /* Clear menu item */
268 if (gtk_text_buffer_get_char_count (priv
->buffer
) > 0) {
269 item
= gtk_separator_menu_item_new ();
270 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
271 gtk_widget_show (item
);
273 item
= gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR
, NULL
);
274 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
275 gtk_widget_show (item
);
277 g_signal_connect_swapped (item
, "activate",
278 G_CALLBACK (empathy_chat_view_clear
),
282 /* Link context menu items */
283 table
= gtk_text_buffer_get_tag_table (priv
->buffer
);
284 tag
= gtk_text_tag_table_lookup (table
, EMPATHY_CHAT_TEXT_VIEW_TAG_LINK
);
286 gtk_widget_get_pointer (GTK_WIDGET (view
), &x
, &y
);
288 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view
),
289 GTK_TEXT_WINDOW_WIDGET
,
293 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view
), &iter
, x
, y
);
297 if (gtk_text_iter_backward_to_tag_toggle (&start
, tag
) &&
298 gtk_text_iter_forward_to_tag_toggle (&end
, tag
)) {
299 str
= gtk_text_buffer_get_text (priv
->buffer
,
300 &start
, &end
, FALSE
);
303 if (EMP_STR_EMPTY (str
)) {
308 /* NOTE: Set data just to get the string freed when not needed. */
309 g_object_set_data_full (G_OBJECT (menu
),
311 (GDestroyNotify
) g_free
);
313 item
= gtk_separator_menu_item_new ();
314 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
315 gtk_widget_show (item
);
317 item
= gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
318 g_signal_connect (item
, "activate",
319 G_CALLBACK (chat_text_view_copy_address_cb
),
321 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
322 gtk_widget_show (item
);
324 item
= gtk_menu_item_new_with_mnemonic (_("_Open Link"));
325 g_signal_connect (item
, "activate",
326 G_CALLBACK (chat_text_view_open_address_cb
),
328 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu
), item
);
329 gtk_widget_show (item
);
333 chat_text_view_is_scrolled_down (EmpathyChatTextView
*view
)
337 sw
= gtk_widget_get_parent (GTK_WIDGET (view
));
338 if (GTK_IS_SCROLLED_WINDOW (sw
)) {
344 vadj
= gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw
));
345 value
= gtk_adjustment_get_value (vadj
);
346 upper
= gtk_adjustment_get_upper (vadj
);
347 page_size
= gtk_adjustment_get_page_size (vadj
);
349 if (value
< upper
- page_size
) {
358 chat_text_view_maybe_trim_buffer (EmpathyChatTextView
*view
)
360 EmpathyChatTextViewPriv
*priv
;
361 GtkTextIter top
, bottom
;
364 GtkTextTagTable
*table
;
367 priv
= GET_PRIV (view
);
369 gtk_text_buffer_get_end_iter (priv
->buffer
, &bottom
);
370 line
= gtk_text_iter_get_line (&bottom
);
371 if (line
< MAX_LINES
) {
375 remove_
= line
- MAX_LINES
;
376 gtk_text_buffer_get_start_iter (priv
->buffer
, &top
);
379 if (!gtk_text_iter_forward_lines (&bottom
, remove_
)) {
383 /* Track backwords to a place where we can safely cut, we don't do it in
384 * the middle of a tag.
386 table
= gtk_text_buffer_get_tag_table (priv
->buffer
);
387 tag
= gtk_text_tag_table_lookup (table
, EMPATHY_CHAT_TEXT_VIEW_TAG_CUT
);
392 if (!gtk_text_iter_forward_to_tag_toggle (&bottom
, tag
)) {
396 if (!gtk_text_iter_equal (&top
, &bottom
)) {
397 gtk_text_buffer_delete (priv
->buffer
, &top
, &bottom
);
402 chat_text_view_append_timestamp (EmpathyChatTextView
*view
,
406 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
411 str
= g_string_new ("- ");
413 /* Append date if needed */
418 date
= g_date_new ();
419 g_date_set_time_t (date
, timestamp
);
420 /* Translators: timestamp displayed between conversations in
421 * chat windows (strftime format string) */
422 g_date_strftime (buf
, 256, _("%A %B %d %Y"), date
);
423 g_string_append (str
, buf
);
424 g_string_append (str
, ", ");
429 tmp
= empathy_time_to_string_local (timestamp
, EMPATHY_TIME_FORMAT_DISPLAY_SHORT
);
430 g_string_append (str
, tmp
);
433 g_string_append (str
, " -\n");
435 /* Insert the string in the buffer */
436 empathy_chat_text_view_append_spacing (view
);
437 gtk_text_buffer_get_end_iter (priv
->buffer
, &iter
);
438 gtk_text_buffer_insert_with_tags_by_name (priv
->buffer
,
441 EMPATHY_CHAT_TEXT_VIEW_TAG_TIME
,
444 priv
->last_timestamp
= timestamp
;
446 g_string_free (str
, TRUE
);
450 chat_text_maybe_append_date_and_time (EmpathyChatTextView
*view
,
453 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
454 GDate
*date
, *last_date
;
455 gboolean append_date
= FALSE
;
456 gboolean append_time
= FALSE
;
458 /* Get the date from last message */
459 last_date
= g_date_new ();
460 g_date_set_time_t (last_date
, priv
->last_timestamp
);
462 /* Get the date of the message we are appending */
463 date
= g_date_new ();
464 g_date_set_time_t (date
, timestamp
);
466 /* If last message was from another day we append date and time */
467 if (g_date_compare (date
, last_date
) > 0) {
472 g_date_free (last_date
);
475 /* If last message is 'old' append the time */
476 if (timestamp
- priv
->last_timestamp
>= TIMESTAMP_INTERVAL
) {
480 if (append_date
|| (!priv
->only_if_date
&& append_time
)) {
481 chat_text_view_append_timestamp (view
, timestamp
, append_date
);
486 chat_text_view_size_allocate (GtkWidget
*widget
,
487 GtkAllocation
*alloc
)
491 down
= chat_text_view_is_scrolled_down (EMPATHY_CHAT_TEXT_VIEW (widget
));
493 GTK_WIDGET_CLASS (empathy_chat_text_view_parent_class
)->size_allocate (widget
, alloc
);
498 adj
= GTK_TEXT_VIEW (widget
)->vadjustment
;
499 gtk_adjustment_set_value (adj
,
500 gtk_adjustment_get_upper (adj
) -
501 gtk_adjustment_get_page_size (adj
));
506 chat_text_view_drag_motion (GtkWidget
*widget
,
507 GdkDragContext
*context
,
512 /* Don't handle drag motion, since we don't want the view to scroll as
513 * the result of dragging something across it. */
519 chat_text_view_get_property (GObject
*object
,
524 EmpathyChatTextViewPriv
*priv
= GET_PRIV (object
);
527 case PROP_LAST_CONTACT
:
528 g_value_set_object (value
, priv
->last_contact
);
530 case PROP_ONLY_IF_DATE
:
531 g_value_set_boolean (value
, priv
->only_if_date
);
534 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
540 chat_text_view_set_property (GObject
*object
,
545 EmpathyChatTextViewPriv
*priv
= GET_PRIV (object
);
548 case PROP_ONLY_IF_DATE
:
549 priv
->only_if_date
= g_value_get_boolean (value
);
552 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
558 chat_text_view_finalize (GObject
*object
)
560 EmpathyChatTextView
*view
;
561 EmpathyChatTextViewPriv
*priv
;
563 view
= EMPATHY_CHAT_TEXT_VIEW (object
);
564 priv
= GET_PRIV (view
);
566 DEBUG ("%p", object
);
568 empathy_conf_notify_remove (empathy_conf_get (), priv
->notify_system_fonts_id
);
570 if (priv
->last_contact
) {
571 g_object_unref (priv
->last_contact
);
573 if (priv
->scroll_time
) {
574 g_timer_destroy (priv
->scroll_time
);
576 if (priv
->scroll_timeout
) {
577 g_source_remove (priv
->scroll_timeout
);
579 g_object_unref (priv
->smiley_manager
);
581 G_OBJECT_CLASS (empathy_chat_text_view_parent_class
)->finalize (object
);
585 text_view_copy_clipboard (GtkTextView
*text_view
)
587 chat_text_view_copy_clipboard (EMPATHY_CHAT_VIEW (text_view
));
591 empathy_chat_text_view_class_init (EmpathyChatTextViewClass
*klass
)
593 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
594 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (klass
);
595 GtkTextViewClass
*text_view_class
= GTK_TEXT_VIEW_CLASS (klass
);
597 object_class
->finalize
= chat_text_view_finalize
;
598 object_class
->get_property
= chat_text_view_get_property
;
599 object_class
->set_property
= chat_text_view_set_property
;
601 widget_class
->size_allocate
= chat_text_view_size_allocate
;
602 widget_class
->drag_motion
= chat_text_view_drag_motion
;
604 text_view_class
->copy_clipboard
= text_view_copy_clipboard
;
606 g_object_class_install_property (object_class
,
608 g_param_spec_object ("last-contact",
610 "The sender of the last received message",
611 EMPATHY_TYPE_CONTACT
,
613 g_object_class_install_property (object_class
,
615 g_param_spec_boolean ("only-if-date",
617 "Display timestamp only if the date changes",
622 g_type_class_add_private (object_class
, sizeof (EmpathyChatTextViewPriv
));
626 empathy_chat_text_view_init (EmpathyChatTextView
*view
)
628 EmpathyChatTextViewPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (view
,
629 EMPATHY_TYPE_CHAT_TEXT_VIEW
, EmpathyChatTextViewPriv
);
632 priv
->buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (view
));
633 priv
->last_timestamp
= 0;
634 priv
->allow_scrolling
= TRUE
;
635 priv
->smiley_manager
= empathy_smiley_manager_dup_singleton ();
638 "wrap-mode", GTK_WRAP_WORD_CHAR
,
640 "cursor-visible", FALSE
,
643 priv
->notify_system_fonts_id
=
644 empathy_conf_notify_add (empathy_conf_get (),
645 "/desktop/gnome/interface/document_font_name",
646 chat_text_view_notify_system_font_cb
,
648 chat_text_view_system_font_update (view
);
649 chat_text_view_create_tags (view
);
651 g_signal_connect (view
,
653 G_CALLBACK (chat_text_view_populate_popup
),
657 /* Code stolen from pidgin/gtkimhtml.c */
659 chat_text_view_scroll_cb (EmpathyChatTextView
*view
)
661 EmpathyChatTextViewPriv
*priv
;
665 priv
= GET_PRIV (view
);
666 adj
= GTK_TEXT_VIEW (view
)->vadjustment
;
667 max_val
= gtk_adjustment_get_upper (adj
) - gtk_adjustment_get_page_size (adj
);
669 g_return_val_if_fail (priv
->scroll_time
!= NULL
, FALSE
);
671 if (g_timer_elapsed (priv
->scroll_time
, NULL
) > MAX_SCROLL_TIME
) {
672 /* time's up. jump to the end and kill the timer */
673 gtk_adjustment_set_value (adj
, max_val
);
674 g_timer_destroy (priv
->scroll_time
);
675 priv
->scroll_time
= NULL
;
676 priv
->scroll_timeout
= 0;
680 /* scroll by 1/3rd the remaining distance */
681 gtk_adjustment_set_value (adj
, gtk_adjustment_get_value (adj
) + ((max_val
- gtk_adjustment_get_value (adj
)) / 3));
686 chat_text_view_scroll_down (EmpathyChatView
*view
)
688 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
690 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
692 if (!priv
->allow_scrolling
) {
696 DEBUG ("Scrolling down");
698 if (priv
->scroll_time
) {
699 g_timer_reset (priv
->scroll_time
);
701 priv
->scroll_time
= g_timer_new ();
703 if (!priv
->scroll_timeout
) {
704 priv
->scroll_timeout
= g_timeout_add (SCROLL_DELAY
,
705 (GSourceFunc
) chat_text_view_scroll_cb
,
711 chat_text_view_append_message (EmpathyChatView
*view
,
714 EmpathyChatTextView
*text_view
= EMPATHY_CHAT_TEXT_VIEW (view
);
715 EmpathyChatTextViewPriv
*priv
= GET_PRIV (text_view
);
719 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
720 g_return_if_fail (EMPATHY_IS_MESSAGE (msg
));
722 if (!empathy_message_get_body (msg
)) {
726 bottom
= chat_text_view_is_scrolled_down (text_view
);
728 chat_text_view_maybe_trim_buffer (EMPATHY_CHAT_TEXT_VIEW (view
));
730 timestamp
= empathy_message_get_timestamp (msg
);
731 chat_text_maybe_append_date_and_time (text_view
, timestamp
);
732 if (EMPATHY_CHAT_TEXT_VIEW_GET_CLASS (view
)->append_message
) {
733 EMPATHY_CHAT_TEXT_VIEW_GET_CLASS (view
)->append_message (text_view
,
738 chat_text_view_scroll_down (view
);
741 if (priv
->last_contact
) {
742 g_object_unref (priv
->last_contact
);
744 priv
->last_contact
= g_object_ref (empathy_message_get_sender (msg
));
745 g_object_notify (G_OBJECT (view
), "last-contact");
749 chat_text_view_append_event (EmpathyChatView
*view
,
752 EmpathyChatTextView
*text_view
= EMPATHY_CHAT_TEXT_VIEW (view
);
753 EmpathyChatTextViewPriv
*priv
= GET_PRIV (text_view
);
759 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
760 g_return_if_fail (!EMP_STR_EMPTY (str
));
762 bottom
= chat_text_view_is_scrolled_down (text_view
);
763 chat_text_view_maybe_trim_buffer (EMPATHY_CHAT_TEXT_VIEW (view
));
764 chat_text_maybe_append_date_and_time (text_view
,
765 empathy_time_get_current ());
767 gtk_text_buffer_get_end_iter (priv
->buffer
, &iter
);
768 msg
= g_strdup_printf (" - %s\n", str
);
769 gtk_text_buffer_insert_with_tags_by_name (priv
->buffer
, &iter
,
771 EMPATHY_CHAT_TEXT_VIEW_TAG_EVENT
,
776 chat_text_view_scroll_down (view
);
779 if (priv
->last_contact
) {
780 g_object_unref (priv
->last_contact
);
781 priv
->last_contact
= NULL
;
782 g_object_notify (G_OBJECT (view
), "last-contact");
787 chat_text_view_scroll (EmpathyChatView
*view
,
788 gboolean allow_scrolling
)
790 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
792 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
794 DEBUG ("Scrolling %s", allow_scrolling
? "enabled" : "disabled");
796 priv
->allow_scrolling
= allow_scrolling
;
797 if (allow_scrolling
) {
798 empathy_chat_view_scroll_down (view
);
803 chat_text_view_get_has_selection (EmpathyChatView
*view
)
805 GtkTextBuffer
*buffer
;
807 g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
), FALSE
);
809 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (view
));
811 return gtk_text_buffer_get_has_selection (buffer
);
815 chat_text_view_clear (EmpathyChatView
*view
)
817 GtkTextBuffer
*buffer
;
818 EmpathyChatTextViewPriv
*priv
;
820 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
822 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (view
));
823 gtk_text_buffer_set_text (buffer
, "", -1);
825 /* We set these back to the initial values so we get
826 * timestamps when clearing the window to know when
827 * conversations start.
829 priv
= GET_PRIV (view
);
831 priv
->last_timestamp
= 0;
832 if (priv
->last_contact
) {
833 g_object_unref (priv
->last_contact
);
834 priv
->last_contact
= NULL
;
839 chat_text_view_find_previous (EmpathyChatView
*view
,
840 const gchar
*search_criteria
,
843 EmpathyChatTextViewPriv
*priv
;
844 GtkTextBuffer
*buffer
;
845 GtkTextIter iter_at_mark
;
846 GtkTextIter iter_match_start
;
847 GtkTextIter iter_match_end
;
849 gboolean from_start
= FALSE
;
851 g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
), FALSE
);
852 g_return_val_if_fail (search_criteria
!= NULL
, FALSE
);
854 priv
= GET_PRIV (view
);
856 buffer
= priv
->buffer
;
858 if (EMP_STR_EMPTY (search_criteria
)) {
859 if (priv
->find_mark_previous
) {
860 gtk_text_buffer_get_start_iter (buffer
, &iter_at_mark
);
862 gtk_text_buffer_move_mark (buffer
,
863 priv
->find_mark_previous
,
865 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view
),
866 priv
->find_mark_previous
,
871 gtk_text_buffer_select_range (buffer
,
883 if (!new_search
&& priv
->find_mark_previous
) {
884 gtk_text_buffer_get_iter_at_mark (buffer
,
886 priv
->find_mark_previous
);
888 gtk_text_buffer_get_end_iter (buffer
, &iter_at_mark
);
892 priv
->find_last_direction
= FALSE
;
894 found
= empathy_text_iter_backward_search (&iter_at_mark
,
901 gboolean result
= FALSE
;
907 /* Here we wrap around. */
908 if (!new_search
&& !priv
->find_wrapped
) {
909 priv
->find_wrapped
= TRUE
;
910 result
= chat_text_view_find_previous (view
,
913 priv
->find_wrapped
= FALSE
;
919 /* Set new mark and show on screen */
920 if (!priv
->find_mark_previous
) {
921 priv
->find_mark_previous
= gtk_text_buffer_create_mark (buffer
, NULL
,
925 gtk_text_buffer_move_mark (buffer
,
926 priv
->find_mark_previous
,
930 if (!priv
->find_mark_next
) {
931 priv
->find_mark_next
= gtk_text_buffer_create_mark (buffer
, NULL
,
935 gtk_text_buffer_move_mark (buffer
,
936 priv
->find_mark_next
,
940 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view
),
941 priv
->find_mark_previous
,
947 gtk_text_buffer_move_mark_by_name (buffer
, "selection_bound", &iter_match_start
);
948 gtk_text_buffer_move_mark_by_name (buffer
, "insert", &iter_match_end
);
954 chat_text_view_find_next (EmpathyChatView
*view
,
955 const gchar
*search_criteria
,
958 EmpathyChatTextViewPriv
*priv
;
959 GtkTextBuffer
*buffer
;
960 GtkTextIter iter_at_mark
;
961 GtkTextIter iter_match_start
;
962 GtkTextIter iter_match_end
;
964 gboolean from_start
= FALSE
;
966 g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
), FALSE
);
967 g_return_val_if_fail (search_criteria
!= NULL
, FALSE
);
969 priv
= GET_PRIV (view
);
971 buffer
= priv
->buffer
;
973 if (EMP_STR_EMPTY (search_criteria
)) {
974 if (priv
->find_mark_next
) {
975 gtk_text_buffer_get_start_iter (buffer
, &iter_at_mark
);
977 gtk_text_buffer_move_mark (buffer
,
978 priv
->find_mark_next
,
980 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view
),
981 priv
->find_mark_next
,
986 gtk_text_buffer_select_range (buffer
,
998 if (!new_search
&& priv
->find_mark_next
) {
999 gtk_text_buffer_get_iter_at_mark (buffer
,
1001 priv
->find_mark_next
);
1003 gtk_text_buffer_get_start_iter (buffer
, &iter_at_mark
);
1007 priv
->find_last_direction
= TRUE
;
1009 found
= empathy_text_iter_forward_search (&iter_at_mark
,
1016 gboolean result
= FALSE
;
1022 /* Here we wrap around. */
1023 if (!new_search
&& !priv
->find_wrapped
) {
1024 priv
->find_wrapped
= TRUE
;
1025 result
= chat_text_view_find_next (view
,
1028 priv
->find_wrapped
= FALSE
;
1034 /* Set new mark and show on screen */
1035 if (!priv
->find_mark_next
) {
1036 priv
->find_mark_next
= gtk_text_buffer_create_mark (buffer
, NULL
,
1040 gtk_text_buffer_move_mark (buffer
,
1041 priv
->find_mark_next
,
1045 if (!priv
->find_mark_previous
) {
1046 priv
->find_mark_previous
= gtk_text_buffer_create_mark (buffer
, NULL
,
1050 gtk_text_buffer_move_mark (buffer
,
1051 priv
->find_mark_previous
,
1055 gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view
),
1056 priv
->find_mark_next
,
1062 gtk_text_buffer_move_mark_by_name (buffer
, "selection_bound", &iter_match_start
);
1063 gtk_text_buffer_move_mark_by_name (buffer
, "insert", &iter_match_end
);
1069 chat_text_view_find_abilities (EmpathyChatView
*view
,
1070 const gchar
*search_criteria
,
1071 gboolean
*can_do_previous
,
1072 gboolean
*can_do_next
)
1074 EmpathyChatTextViewPriv
*priv
;
1075 GtkTextBuffer
*buffer
;
1076 GtkTextIter iter_at_mark
;
1077 GtkTextIter iter_match_start
;
1078 GtkTextIter iter_match_end
;
1080 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
1081 g_return_if_fail (search_criteria
!= NULL
);
1082 g_return_if_fail (can_do_previous
!= NULL
&& can_do_next
!= NULL
);
1084 priv
= GET_PRIV (view
);
1086 buffer
= priv
->buffer
;
1088 if (can_do_previous
) {
1089 if (priv
->find_mark_previous
) {
1090 gtk_text_buffer_get_iter_at_mark (buffer
,
1092 priv
->find_mark_previous
);
1094 gtk_text_buffer_get_start_iter (buffer
, &iter_at_mark
);
1097 *can_do_previous
= empathy_text_iter_backward_search (&iter_at_mark
,
1105 if (priv
->find_mark_next
) {
1106 gtk_text_buffer_get_iter_at_mark (buffer
,
1108 priv
->find_mark_next
);
1110 gtk_text_buffer_get_start_iter (buffer
, &iter_at_mark
);
1113 *can_do_next
= empathy_text_iter_forward_search (&iter_at_mark
,
1122 chat_text_view_highlight (EmpathyChatView
*view
,
1125 GtkTextBuffer
*buffer
;
1127 GtkTextIter iter_start
;
1128 GtkTextIter iter_end
;
1129 GtkTextIter iter_match_start
;
1130 GtkTextIter iter_match_end
;
1133 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
1135 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (view
));
1137 gtk_text_buffer_get_start_iter (buffer
, &iter
);
1139 gtk_text_buffer_get_bounds (buffer
, &iter_start
, &iter_end
);
1140 gtk_text_buffer_remove_tag_by_name (buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT
,
1144 if (EMP_STR_EMPTY (text
)) {
1149 found
= empathy_text_iter_forward_search (&iter
,
1159 gtk_text_buffer_apply_tag_by_name (buffer
, EMPATHY_CHAT_TEXT_VIEW_TAG_HIGHLIGHT
,
1163 iter
= iter_match_end
;
1168 chat_text_view_copy_clipboard (EmpathyChatView
*view
)
1170 GtkTextBuffer
*buffer
;
1171 GtkTextIter start
, iter
, end
;
1172 GtkClipboard
*clipboard
;
1175 GtkTextChildAnchor
*anchor
= NULL
;
1178 gboolean ignore_newlines
= FALSE
;
1180 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
1182 buffer
= gtk_text_view_get_buffer (GTK_TEXT_VIEW (view
));
1183 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
1185 if (!gtk_text_buffer_get_selection_bounds (buffer
, &start
, &end
))
1188 str
= g_string_new ("");
1190 for (iter
= start
; !gtk_text_iter_equal (&iter
, &end
); gtk_text_iter_forward_char (&iter
)) {
1191 c
= gtk_text_iter_get_char (&iter
);
1192 /* 0xFFFC is the 'object replacement' unicode character,
1193 * it indicates the presence of a pixbuf or a widget. */
1195 ignore_newlines
= FALSE
;
1196 if ((pixbuf
= gtk_text_iter_get_pixbuf (&iter
))) {
1198 text
= g_object_get_data (G_OBJECT(pixbuf
),
1201 str
= g_string_append (str
, text
);
1202 } else if ((anchor
= gtk_text_iter_get_child_anchor (&iter
))) {
1204 list
= gtk_text_child_anchor_get_widgets (anchor
);
1206 text
= g_object_get_data (G_OBJECT(list
->data
),
1209 str
= g_string_append (str
, text
);
1213 } else if (c
== '\n') {
1214 if (!ignore_newlines
) {
1215 ignore_newlines
= TRUE
;
1216 str
= g_string_append_unichar (str
, c
);
1219 ignore_newlines
= FALSE
;
1220 str
= g_string_append_unichar (str
, c
);
1224 gtk_clipboard_set_text (clipboard
, str
->str
, str
->len
);
1225 g_string_free (str
, TRUE
);
1229 chat_text_view_iface_init (EmpathyChatViewIface
*iface
)
1231 iface
->append_message
= chat_text_view_append_message
;
1232 iface
->append_event
= chat_text_view_append_event
;
1233 iface
->scroll
= chat_text_view_scroll
;
1234 iface
->scroll_down
= chat_text_view_scroll_down
;
1235 iface
->get_has_selection
= chat_text_view_get_has_selection
;
1236 iface
->clear
= chat_text_view_clear
;
1237 iface
->find_previous
= chat_text_view_find_previous
;
1238 iface
->find_next
= chat_text_view_find_next
;
1239 iface
->find_abilities
= chat_text_view_find_abilities
;
1240 iface
->highlight
= chat_text_view_highlight
;
1241 iface
->copy_clipboard
= chat_text_view_copy_clipboard
;
1245 empathy_chat_text_view_get_last_contact (EmpathyChatTextView
*view
)
1247 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
1249 g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
), NULL
);
1251 return priv
->last_contact
;
1255 empathy_chat_text_view_set_only_if_date (EmpathyChatTextView
*view
,
1256 gboolean only_if_date
)
1258 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
1260 g_return_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
));
1262 if (only_if_date
!= priv
->only_if_date
) {
1263 priv
->only_if_date
= only_if_date
;
1264 g_object_notify (G_OBJECT (view
), "only-if-date");
1269 chat_text_view_replace_link (const gchar
*text
,
1271 gpointer match_data
,
1274 GtkTextBuffer
*buffer
= GTK_TEXT_BUFFER (user_data
);
1277 gtk_text_buffer_get_end_iter (buffer
, &iter
);
1278 gtk_text_buffer_insert_with_tags_by_name (buffer
, &iter
,
1280 EMPATHY_CHAT_TEXT_VIEW_TAG_LINK
,
1285 chat_text_view_replace_smiley (const gchar
*text
,
1287 gpointer match_data
,
1290 EmpathySmileyHit
*hit
= match_data
;
1291 GtkTextBuffer
*buffer
= GTK_TEXT_BUFFER (user_data
);
1294 gtk_text_buffer_get_end_iter (buffer
, &iter
);
1295 gtk_text_buffer_insert_pixbuf (buffer
, &iter
, hit
->pixbuf
);
1299 chat_text_view_replace_verbatim (const gchar
*text
,
1301 gpointer match_data
,
1304 GtkTextBuffer
*buffer
= GTK_TEXT_BUFFER (user_data
);
1307 gtk_text_buffer_get_end_iter (buffer
, &iter
);
1308 gtk_text_buffer_insert (buffer
, &iter
, text
, len
);
1311 static EmpathyStringParser string_parsers
[] = {
1312 {empathy_string_match_link
, chat_text_view_replace_link
},
1313 {empathy_string_match_all
, chat_text_view_replace_verbatim
},
1317 static EmpathyStringParser string_parsers_with_smiley
[] = {
1318 {empathy_string_match_link
, chat_text_view_replace_link
},
1319 {empathy_string_match_smiley
, chat_text_view_replace_smiley
},
1320 {empathy_string_match_all
, chat_text_view_replace_verbatim
},
1325 empathy_chat_text_view_append_body (EmpathyChatTextView
*view
,
1329 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
1330 EmpathyStringParser
*parsers
;
1331 gboolean use_smileys
;
1332 GtkTextIter start_iter
;
1336 /* Check if we have to parse smileys */
1337 empathy_conf_get_bool (empathy_conf_get (),
1338 EMPATHY_PREFS_CHAT_SHOW_SMILEYS
,
1341 parsers
= string_parsers_with_smiley
;
1343 parsers
= string_parsers
;
1345 /* Create a mark at the place we'll start inserting */
1346 gtk_text_buffer_get_end_iter (priv
->buffer
, &start_iter
);
1347 mark
= gtk_text_buffer_create_mark (priv
->buffer
, NULL
, &start_iter
, TRUE
);
1349 /* Parse text for links/smileys and insert in the buffer */
1350 empathy_string_parser_substr (body
, -1, parsers
, priv
->buffer
);
1352 /* Insert a newline after the text inserted */
1353 gtk_text_buffer_get_end_iter (priv
->buffer
, &iter
);
1354 gtk_text_buffer_insert (priv
->buffer
, &iter
, "\n", 1);
1356 /* Apply the style to the inserted text. */
1357 gtk_text_buffer_get_iter_at_mark (priv
->buffer
, &start_iter
, mark
);
1358 gtk_text_buffer_get_end_iter (priv
->buffer
, &iter
);
1359 gtk_text_buffer_apply_tag_by_name (priv
->buffer
, tag
,
1363 gtk_text_buffer_delete_mark (priv
->buffer
, mark
);
1367 empathy_chat_text_view_append_spacing (EmpathyChatTextView
*view
)
1369 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
1372 gtk_text_buffer_get_end_iter (priv
->buffer
, &iter
);
1373 gtk_text_buffer_insert_with_tags_by_name (priv
->buffer
,
1377 EMPATHY_CHAT_TEXT_VIEW_TAG_CUT
,
1378 EMPATHY_CHAT_TEXT_VIEW_TAG_SPACING
,
1383 empathy_chat_text_view_tag_set (EmpathyChatTextView
*view
,
1384 const gchar
*tag_name
,
1385 const gchar
*first_property_name
,
1388 EmpathyChatTextViewPriv
*priv
= GET_PRIV (view
);
1390 GtkTextTagTable
*table
;
1393 g_return_val_if_fail (EMPATHY_IS_CHAT_TEXT_VIEW (view
), NULL
);
1394 g_return_val_if_fail (tag_name
!= NULL
, NULL
);
1396 table
= gtk_text_buffer_get_tag_table (priv
->buffer
);
1397 tag
= gtk_text_tag_table_lookup (table
, tag_name
);
1399 if (tag
&& first_property_name
) {
1400 va_start (list
, first_property_name
);
1401 g_object_set_valist (G_OBJECT (tag
), first_property_name
, list
);