2 * @file gtkimhtml.c GTK+ IMHtml
8 * Pidgin is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #define _PIDGIN_GTKIMHTML_C_
35 #include "pidginstock.h"
42 #include "gtkimhtml.h"
43 #include "gtksmiley.h"
44 #include "gtksourceiter.h"
45 #include "gtksourceundomanager.h"
46 #include "gtksourceview-marshal.h"
49 #include <gdk/gdkkeysyms.h>
55 #ifdef HAVE_LANGINFO_CODESET
60 #include <gdk/gdkwin32.h>
64 #include <pango/pango-font.h>
66 #define TOOLTIP_TIMEOUT 500
68 static GtkTextViewClass
*parent_class
= NULL
;
70 struct scalable_data
{
71 GtkIMHtmlScalable
*scalable
;
76 GtkIMHtmlScalable
*image
;
81 struct im_image_data
{
93 typedef struct _GtkIMHtmlProtocol
98 gboolean (*activate
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
);
99 gboolean (*context_menu
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
);
103 gtk_text_view_drag_motion (GtkWidget
*widget
,
104 GdkDragContext
*context
,
109 static void preinsert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
);
110 static void insert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
);
111 static void delete_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, GtkTextIter
*end
, GtkIMHtml
*imhtml
);
112 static void insert_ca_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextChildAnchor
*arg2
, gpointer user_data
);
113 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
);
114 void gtk_imhtml_close_tags(GtkIMHtml
*imhtml
, GtkTextIter
*iter
);
115 static void gtk_imhtml_link_drop_cb(GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer user_data
);
116 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
, GtkSelectionData
*sd
, guint info
, guint t
, GtkIMHtml
*imhtml
);
117 static void mark_set_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
, GtkIMHtml
*imhtml
);
118 static void hijack_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
);
119 static void paste_received_cb (GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, gpointer data
);
120 static void paste_plaintext_received_cb (GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
);
121 static void imhtml_paste_insert(GtkIMHtml
*imhtml
, const char *text
, gboolean plaintext
);
122 static void imhtml_toggle_bold(GtkIMHtml
*imhtml
);
123 static void imhtml_toggle_italic(GtkIMHtml
*imhtml
);
124 static void imhtml_toggle_strike(GtkIMHtml
*imhtml
);
125 static void imhtml_toggle_underline(GtkIMHtml
*imhtml
);
126 static void imhtml_font_grow(GtkIMHtml
*imhtml
);
127 static void imhtml_font_shrink(GtkIMHtml
*imhtml
);
128 static void imhtml_clear_formatting(GtkIMHtml
*imhtml
);
129 static int gtk_imhtml_is_protocol(const char *text
);
130 static void gtk_imhtml_activate_tag(GtkIMHtml
*imhtml
, GtkTextTag
*tag
);
131 static void gtk_imhtml_link_destroy(GtkIMHtmlLink
*link
);
133 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
134 #define MAX_FONT_SIZE 7
135 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
136 static const gdouble _point_sizes
[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
141 TARGET_COMPOUND_TEXT
,
158 static guint signals
[LAST_SIGNAL
] = { 0 };
160 static char *html_clipboard
= NULL
;
161 static char *text_clipboard
= NULL
;
162 static GtkClipboard
*clipboard_selection
= NULL
;
164 static const GtkTargetEntry selection_targets
[] = {
166 { "text/html", 0, TARGET_HTML
},
168 { "HTML Format", 0, TARGET_HTML
},
170 { "UTF8_STRING", 0, TARGET_UTF8_STRING
},
171 { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT
},
172 { "STRING", 0, TARGET_STRING
},
173 { "TEXT", 0, TARGET_TEXT
}};
175 static const GtkTargetEntry link_drag_drop_targets
[] = {
176 GTK_IMHTML_DND_TARGETS
181 clipboard_win32_to_html(char *clipboard
) {
183 const char *begin
, *end
;
188 int clipboard_length
= 0;
190 #if 0 /* Debugging for Windows clipboard */
193 purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard
);
195 fd
= g_fopen("c:\\purplecb.txt", "wb");
196 fprintf(fd
, "%s", clipboard
);
200 clipboard_length
= strlen(clipboard
);
202 if (!(header
= strstr(clipboard
, "StartFragment:")) || (header
- clipboard
) >= clipboard_length
)
204 sscanf(header
, "StartFragment:%d", &start
);
206 if (!(header
= strstr(clipboard
, "EndFragment:")) || (header
- clipboard
) >= clipboard_length
)
208 sscanf(header
, "EndFragment:%d", &finish
);
210 if (finish
> clipboard_length
)
211 finish
= clipboard_length
;
216 begin
= clipboard
+ start
;
218 end
= clipboard
+ finish
;
220 html
= g_strndup(begin
, end
- begin
);
222 /* any newlines in the string will now be \r\n, so we need to strip out the \r */
223 split
= g_strsplit(html
, "\r\n", 0);
225 html
= g_strjoinv("\n", split
);
228 #if 0 /* Debugging for Windows clipboard */
229 purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html
);
236 clipboard_html_to_win32(char *html
) {
243 length
= strlen(html
);
244 clipboard
= g_string_new ("Version:1.0\r\n");
245 g_string_append(clipboard
, "StartHTML:0000000105\r\n");
246 g_string_append_printf(clipboard
, "EndHTML:%010d\r\n", 147 + length
);
247 g_string_append(clipboard
, "StartFragment:0000000127\r\n");
248 g_string_append_printf(clipboard
, "EndFragment:%010d\r\n", 127 + length
);
249 g_string_append(clipboard
, "<!--StartFragment-->\r\n");
250 g_string_append(clipboard
, html
);
251 g_string_append(clipboard
, "\r\n<!--EndFragment-->");
253 return g_string_free(clipboard
, FALSE
);
256 static gboolean
clipboard_paste_html_win32(GtkIMHtml
*imhtml
) {
257 gboolean pasted
= FALSE
;
259 /* Win32 clipboard format value, and functions to convert back and
260 * forth between HTML and the clipboard format.
262 static UINT win_html_fmt
= 0;
264 /* Register HTML Format as desired clipboard format */
266 win_html_fmt
= RegisterClipboardFormat("HTML Format");
268 if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
))
269 && IsClipboardFormatAvailable(win_html_fmt
)) {
270 gboolean error_reading_clipboard
= FALSE
;
271 HWND hwnd
= GDK_WINDOW_HWND(GTK_WIDGET(imhtml
)->window
);
273 if (OpenClipboard(hwnd
)) {
274 HGLOBAL hdata
= GetClipboardData(win_html_fmt
);
276 if (GetLastError() != ERROR_SUCCESS
)
277 error_reading_clipboard
= TRUE
;
279 char *buffer
= GlobalLock(hdata
);
280 if (buffer
== NULL
) {
281 error_reading_clipboard
= TRUE
;
283 char *text
= clipboard_win32_to_html(
285 imhtml_paste_insert(imhtml
, text
,
295 error_reading_clipboard
= TRUE
;
298 if (error_reading_clipboard
) {
299 gchar
*err_msg
= g_win32_error_message(GetLastError());
300 purple_debug_info("html clipboard",
301 "Unable to read clipboard data: %s\n",
302 err_msg
? err_msg
: "Unknown Error");
311 static GtkSmileyTree
*
312 gtk_smiley_tree_new (void)
314 return g_new0 (GtkSmileyTree
, 1);
318 gtk_smiley_tree_insert (GtkSmileyTree
*tree
,
319 GtkIMHtmlSmiley
*smiley
)
321 GtkSmileyTree
*t
= tree
;
322 const gchar
*x
= smiley
->smile
;
332 t
->values
= g_string_new ("");
334 pos
= strchr (t
->values
->str
, *x
);
336 t
->values
= g_string_append_c (t
->values
, *x
);
337 index
= t
->values
->len
- 1;
338 t
->children
= g_realloc (t
->children
, t
->values
->len
* sizeof (GtkSmileyTree
*));
339 t
->children
[index
] = g_new0 (GtkSmileyTree
, 1);
341 index
= GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
);
343 t
= t
->children
[index
];
353 gtk_smiley_tree_destroy (GtkSmileyTree
*tree
)
355 GSList
*list
= g_slist_prepend (NULL
, tree
);
358 GtkSmileyTree
*t
= list
->data
;
360 list
= g_slist_remove(list
, t
);
361 if (t
&& t
->values
) {
362 for (i
= 0; i
< t
->values
->len
; i
++)
363 list
= g_slist_prepend (list
, t
->children
[i
]);
364 g_string_free (t
->values
, TRUE
);
365 g_free (t
->children
);
372 static void (*parent_size_allocate
)(GtkWidget
*widget
, GtkAllocation
*alloc
);
374 static void gtk_imhtml_size_allocate(GtkWidget
*widget
, GtkAllocation
*alloc
)
376 GtkIMHtml
*imhtml
= GTK_IMHTML(widget
);
379 int height
= 0, y
= 0;
381 gboolean scroll
= TRUE
;
383 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
385 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &rect
);
386 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
388 if (((y
+ height
) - (rect
.y
+ rect
.height
)) > height
&&
389 gtk_text_buffer_get_char_count(imhtml
->text_buffer
)) {
393 if(imhtml
->old_rect
.width
!= rect
.width
|| imhtml
->old_rect
.height
!= rect
.height
) {
394 GList
*iter
= GTK_IMHTML(widget
)->scalables
;
396 xminus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget
)) +
397 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget
));
400 struct scalable_data
*sd
= iter
->data
;
401 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
402 scale
->scale(scale
, rect
.width
- xminus
, rect
.height
);
408 imhtml
->old_rect
= rect
;
409 parent_size_allocate(widget
, alloc
);
411 /* Don't scroll here if we're in the middle of a smooth scroll */
412 if (scroll
&& imhtml
->scroll_time
== NULL
&&
413 GTK_WIDGET_REALIZED(imhtml
))
414 gtk_imhtml_scroll_to_end(imhtml
, FALSE
);
417 #define DEFAULT_SEND_COLOR "#204a87"
418 #define DEFAULT_RECV_COLOR "#cc0000"
419 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
420 #define DEFAULT_ACTION_COLOR "#062585"
421 #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
422 #define DEFAULT_WHISPER_COLOR "#00FF00"
424 static void (*parent_style_set
)(GtkWidget
*widget
, GtkStyle
*prev_style
);
427 gtk_imhtml_style_set(GtkWidget
*widget
, GtkStyle
*prev_style
)
435 {"send-name", "send-name-color", DEFAULT_SEND_COLOR
},
436 {"receive-name", "receive-name-color", DEFAULT_RECV_COLOR
},
437 {"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR
},
438 {"action-name", "action-name-color", DEFAULT_ACTION_COLOR
},
439 {"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR
},
440 {"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR
},
443 GtkIMHtml
*imhtml
= GTK_IMHTML(widget
);
444 GtkTextTagTable
*table
= gtk_text_buffer_get_tag_table(imhtml
->text_buffer
);
446 for (i
= 0; styles
[i
].tag
; i
++) {
447 GdkColor
*color
= NULL
;
448 GtkTextTag
*tag
= gtk_text_tag_table_lookup(table
, styles
[i
].tag
);
450 purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles
[i
].tag
);
453 gtk_widget_style_get(widget
, styles
[i
].color
, &color
, NULL
);
455 g_object_set(tag
, "foreground-gdk", color
, NULL
);
456 gdk_color_free(color
);
459 gdk_color_parse(styles
[i
].def
, &defcolor
);
460 g_object_set(tag
, "foreground-gdk", &defcolor
, NULL
);
463 parent_style_set(widget
, prev_style
);
467 imhtml_get_iter_bounds(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
470 gtk_text_buffer_get_bounds(imhtml
->text_buffer
, start
, end
);
472 } else if (imhtml
->editable
) {
473 if (!gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, start
, end
)) {
474 GtkTextMark
*mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
475 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, start
, mark
);
485 gtk_imhtml_set_link_color(GtkIMHtml
*imhtml
, GtkTextTag
*tag
)
487 GdkColor
*color
= NULL
;
488 gboolean visited
= !!g_object_get_data(G_OBJECT(tag
), "visited");
489 gtk_widget_style_get(GTK_WIDGET(imhtml
), visited
? "hyperlink-visited-color" : "hyperlink-color", &color
, NULL
);
491 g_object_set(G_OBJECT(tag
), "foreground-gdk", color
, NULL
);
492 gdk_color_free(color
);
494 g_object_set(G_OBJECT(tag
), "foreground", visited
? "#800000" : "blue", NULL
);
499 gtk_imhtml_tip_paint (GtkIMHtml
*imhtml
)
503 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
505 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
507 gtk_paint_flat_box (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
,
508 GTK_STATE_NORMAL
, GTK_SHADOW_OUT
, NULL
, imhtml
->tip_window
,
509 "tooltip", 0, 0, -1, -1);
511 gtk_paint_layout (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
, GTK_STATE_NORMAL
,
512 FALSE
, NULL
, imhtml
->tip_window
, NULL
, 4, 4, layout
);
514 g_object_unref(layout
);
519 gtk_imhtml_tip (gpointer data
)
521 GtkIMHtml
*imhtml
= data
;
522 PangoFontMetrics
*font_metrics
;
526 gint gap
, x
, y
, h
, w
, scr_w
, baseline_skip
;
528 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
530 if (!imhtml
->tip
|| !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml
))) {
531 imhtml
->tip_timer
= 0;
535 if (imhtml
->tip_window
){
536 gtk_widget_destroy (imhtml
->tip_window
);
537 imhtml
->tip_window
= NULL
;
540 imhtml
->tip_timer
= 0;
541 imhtml
->tip_window
= gtk_window_new (GTK_WINDOW_POPUP
);
542 gtk_widget_set_app_paintable (imhtml
->tip_window
, TRUE
);
543 gtk_window_set_title(GTK_WINDOW(imhtml
->tip_window
), "GtkIMHtml");
544 gtk_window_set_resizable (GTK_WINDOW (imhtml
->tip_window
), FALSE
);
545 gtk_widget_set_name (imhtml
->tip_window
, "gtk-tooltips");
546 gtk_window_set_type_hint (GTK_WINDOW (imhtml
->tip_window
),
547 GDK_WINDOW_TYPE_HINT_TOOLTIP
);
548 g_signal_connect_swapped (G_OBJECT (imhtml
->tip_window
), "expose_event",
549 G_CALLBACK (gtk_imhtml_tip_paint
), imhtml
);
551 gtk_widget_ensure_style (imhtml
->tip_window
);
552 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
553 font
= pango_context_load_font(pango_layout_get_context(layout
),
554 imhtml
->tip_window
->style
->font_desc
);
557 char *tmp
= pango_font_description_to_string(
558 imhtml
->tip_window
->style
->font_desc
);
560 purple_debug(PURPLE_DEBUG_ERROR
, "gtk_imhtml_tip",
561 "pango_context_load_font() couldn't load font: '%s'\n",
565 g_object_unref(layout
);
569 font_metrics
= pango_font_get_metrics(font
, NULL
);
571 pango_layout_get_pixel_size(layout
, &scr_w
, NULL
);
572 gap
= PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics
) +
573 pango_font_metrics_get_descent(font_metrics
))/ 4);
577 baseline_skip
= PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
578 pango_font_metrics_get_descent(font_metrics
));
580 h
= 8 + baseline_skip
;
582 gdk_window_get_pointer (NULL
, &x
, &y
, NULL
);
583 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml
)))
584 y
+= GTK_WIDGET(imhtml
)->allocation
.y
;
586 scr_w
= gdk_screen_width();
591 x
-= (x
+ w
) - scr_w
;
595 y
= y
+ PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
596 pango_font_metrics_get_descent(font_metrics
));
598 gtk_widget_set_size_request (imhtml
->tip_window
, w
, h
);
599 gtk_window_move (GTK_WINDOW(imhtml
->tip_window
), x
, y
);
600 gtk_widget_show (imhtml
->tip_window
);
602 pango_font_metrics_unref(font_metrics
);
603 g_object_unref(font
);
604 g_object_unref(layout
);
610 gtk_motion_event_notify(GtkWidget
*imhtml
, GdkEventMotion
*event
, gpointer data
)
613 GdkWindow
*win
= event
->window
;
616 GSList
*tags
= NULL
, *templist
= NULL
;
617 GtkTextTag
*tag
= NULL
, *oldprelit_tag
;
618 GtkTextChildAnchor
* anchor
;
619 gboolean hand
= TRUE
;
620 GdkCursor
*cursor
= NULL
;
622 oldprelit_tag
= GTK_IMHTML(imhtml
)->prelit_tag
;
624 gdk_window_get_pointer(GTK_WIDGET(imhtml
)->window
, NULL
, NULL
, NULL
);
625 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
), GTK_TEXT_WINDOW_WIDGET
,
626 event
->x
, event
->y
, &x
, &y
);
627 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
628 tags
= gtk_text_iter_get_tags(&iter
);
632 tag
= templist
->data
;
633 tip
= g_object_get_data(G_OBJECT(tag
), "link_url");
636 templist
= templist
->next
;
639 if (tip
&& (!tag
|| !g_object_get_data(G_OBJECT(tag
), "visited"))) {
640 GTK_IMHTML(imhtml
)->prelit_tag
= tag
;
641 if (tag
!= oldprelit_tag
) {
642 GdkColor
*pre
= NULL
;
643 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-prelight-color", &pre
, NULL
);
645 g_object_set(G_OBJECT(tag
), "foreground-gdk", pre
, NULL
);
648 g_object_set(G_OBJECT(tag
), "foreground", "#70a0ff", NULL
);
651 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
654 if ((oldprelit_tag
!= NULL
) && (GTK_IMHTML(imhtml
)->prelit_tag
!= oldprelit_tag
)) {
655 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), oldprelit_tag
);
658 if (GTK_IMHTML(imhtml
)->tip
) {
659 if ((tip
== GTK_IMHTML(imhtml
)->tip
)) {
663 /* We've left the cell. Remove the timeout and create a new one below */
664 if (GTK_IMHTML(imhtml
)->tip_window
) {
665 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
666 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
668 if (GTK_IMHTML(imhtml
)->editable
)
669 cursor
= GTK_IMHTML(imhtml
)->text_cursor
;
671 cursor
= GTK_IMHTML(imhtml
)->arrow_cursor
;
672 if (GTK_IMHTML(imhtml
)->tip_timer
)
673 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
674 GTK_IMHTML(imhtml
)->tip_timer
= 0;
677 /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
678 anchor
= gtk_text_iter_get_child_anchor(&iter
);
680 tip
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_tiptext");
685 GTK_IMHTML(imhtml
)->tip_timer
= g_timeout_add (TOOLTIP_TIMEOUT
,
686 gtk_imhtml_tip
, imhtml
);
689 for (templist
= tags
; templist
; templist
= templist
->next
) {
690 tag
= templist
->data
;
691 if ((tip
= g_object_get_data(G_OBJECT(tag
), "cursor"))) {
698 if (hand
&& !(GTK_IMHTML(imhtml
)->editable
))
699 cursor
= GTK_IMHTML(imhtml
)->hand_cursor
;
702 gdk_window_set_cursor(win
, cursor
);
704 GTK_IMHTML(imhtml
)->tip
= tip
;
710 gtk_enter_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
712 if (GTK_IMHTML(imhtml
)->editable
)
713 gdk_window_set_cursor(
714 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
715 GTK_TEXT_WINDOW_TEXT
),
716 GTK_IMHTML(imhtml
)->text_cursor
);
718 gdk_window_set_cursor(
719 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
720 GTK_TEXT_WINDOW_TEXT
),
721 GTK_IMHTML(imhtml
)->arrow_cursor
);
723 /* propagate the event normally */
728 gtk_leave_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
730 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
731 if (GTK_IMHTML(imhtml
)->prelit_tag
) {
732 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), GTK_IMHTML(imhtml
)->prelit_tag
);
733 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
736 if (GTK_IMHTML(imhtml
)->tip_window
) {
737 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
738 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
740 if (GTK_IMHTML(imhtml
)->tip_timer
) {
741 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
742 GTK_IMHTML(imhtml
)->tip_timer
= 0;
744 gdk_window_set_cursor(
745 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
746 GTK_TEXT_WINDOW_TEXT
), NULL
);
748 /* propagate the event normally */
753 gtk_imhtml_expose_event (GtkWidget
*widget
,
754 GdkEventExpose
*event
)
756 GtkTextIter start
, end
, cur
;
758 GdkRectangle visible_rect
;
759 GdkGC
*gc
= gdk_gc_new(GDK_DRAWABLE(event
->window
));
762 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &visible_rect
);
763 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
764 GTK_TEXT_WINDOW_TEXT
,
770 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget
), GTK_TEXT_WINDOW_TEXT
,
771 event
->area
.x
, event
->area
.y
, &buf_x
, &buf_y
);
773 if (GTK_IMHTML(widget
)->editable
|| GTK_IMHTML(widget
)->wbfo
) {
775 if (GTK_IMHTML(widget
)->edit
.background
) {
776 gdk_color_parse(GTK_IMHTML(widget
)->edit
.background
, &gcolor
);
777 gdk_gc_set_rgb_fg_color(gc
, &gcolor
);
779 gdk_gc_set_rgb_fg_color(gc
, &(widget
->style
->base
[GTK_WIDGET_STATE(widget
)]));
782 gdk_draw_rectangle(event
->window
,
785 visible_rect
.x
, visible_rect
.y
, visible_rect
.width
, visible_rect
.height
);
786 g_object_unref(G_OBJECT(gc
));
788 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
789 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
795 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &start
, buf_x
, buf_y
);
796 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &end
,
797 buf_x
+ event
->area
.width
, buf_y
+ event
->area
.height
);
799 gtk_text_iter_order(&start
, &end
);
803 while (gtk_text_iter_in_range(&cur
, &start
, &end
)) {
804 GSList
*tags
= gtk_text_iter_get_tags(&cur
);
807 for (l
= tags
; l
; l
= l
->next
) {
808 GtkTextTag
*tag
= l
->data
;
810 GdkRectangle tag_area
;
813 if (strncmp(tag
->name
, "BACKGROUND ", 11))
816 if (gtk_text_iter_ends_tag(&cur
, tag
))
819 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
820 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
821 GTK_TEXT_WINDOW_TEXT
,
826 rect
.x
= visible_rect
.x
;
828 rect
.width
= visible_rect
.width
;
831 gtk_text_iter_forward_to_tag_toggle(&cur
, tag
);
832 while (!gtk_text_iter_is_end(&cur
) && gtk_text_iter_begins_tag(&cur
, tag
));
834 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
835 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
836 GTK_TEXT_WINDOW_TEXT
,
843 rect
.height
= tag_area
.y
+ tag_area
.height
- rect
.y
844 + gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget
))
845 + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget
));
847 color
= tag
->name
+ 11;
849 if (!gdk_color_parse(color
, &gcolor
)) {
852 strncpy(&tmp
[1], color
, 7);
854 if (!gdk_color_parse(tmp
, &gcolor
))
855 gdk_color_parse("white", &gcolor
);
857 gdk_gc_set_rgb_fg_color(gc
, &gcolor
);
859 gdk_draw_rectangle(event
->window
,
862 rect
.x
, rect
.y
, rect
.width
, rect
.height
);
863 gtk_text_iter_backward_char(&cur
); /* go back one, in case the end is the begining is the end
864 * note that above, we always moved cur ahead by at least
871 /* loop until another tag begins, or no tag begins */
872 while (gtk_text_iter_forward_to_tag_toggle(&cur
, NULL
) &&
873 !gtk_text_iter_is_end(&cur
) &&
874 !gtk_text_iter_begins_tag(&cur
, NULL
));
877 g_object_unref(G_OBJECT(gc
));
879 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
880 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
887 static void paste_unformatted_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
889 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
891 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
895 static void clear_formatting_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
897 gtk_imhtml_clear_formatting(imhtml
);
900 static void disable_smiley_selected(GtkMenuItem
*item
, GtkIMHtml
*imhtml
)
902 GtkTextIter start
, end
;
906 if (!gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
))
909 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
911 mark
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
912 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
914 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, mark
);
915 gtk_imhtml_insert_html_at_iter(imhtml
, text
, GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_SMILEY
, &start
);
920 static void hijack_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
)
923 GtkTextIter start
, end
;
925 menuitem
= gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
926 gtk_widget_show(menuitem
);
928 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
929 * mainloop, which tends to be a source of bugs. It would
930 * be good to audit this or change it to not wait.
932 gtk_widget_set_sensitive(menuitem
,
934 gtk_clipboard_wait_is_text_available(
935 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
))));
936 /* put it after "Paste" */
937 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 3);
939 g_signal_connect(G_OBJECT(menuitem
), "activate",
940 G_CALLBACK(paste_unformatted_cb
), imhtml
);
942 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
943 gtk_widget_show(menuitem
);
944 gtk_widget_set_sensitive(menuitem
, imhtml
->editable
);
945 /* put it after Delete */
946 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 5);
948 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(clear_formatting_cb
), imhtml
);
950 menuitem
= gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
951 gtk_widget_show(menuitem
);
952 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
953 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(disable_smiley_selected
), imhtml
);
955 gtk_widget_set_sensitive(menuitem
, FALSE
);
957 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 6);
961 ucs2_order(gboolean swap
)
965 be
= G_BYTE_ORDER
== G_BIG_ENDIAN
;
966 be
= swap
? be
: !be
;
975 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
977 utf16_to_utf8_with_bom_check(gchar
*data
, guint len
) {
978 char *fromcode
= NULL
;
979 GError
*error
= NULL
;
984 * Unicode Techinical Report 20
985 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
986 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
987 * what we do. If there is no indicator assume it is in the default
995 fromcode
= ucs2_order(c
== 0xfeff);
1000 fromcode
= "UTF-16";
1004 utf8_ret
= g_convert(data
, len
, "UTF-8", fromcode
, NULL
, NULL
, &error
);
1007 purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error
->message
);
1008 g_error_free(error
);
1014 static void gtk_imhtml_clipboard_get(GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, guint info
, GtkIMHtml
*imhtml
) {
1016 gboolean primary
= (clipboard
!= clipboard_selection
);
1017 GtkTextIter start
, end
;
1020 GtkTextMark
*sel
= NULL
, *ins
= NULL
;
1022 g_return_if_fail(imhtml
!= NULL
);
1024 ins
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1025 sel
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
1026 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, sel
);
1027 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &end
, ins
);
1030 if (info
== TARGET_HTML
) {
1035 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1037 text
= html_clipboard
;
1039 /* Mozilla asks that we start our text/html with the Unicode byte order mark */
1040 selection
= g_convert(text
, -1, "UTF-16", "UTF-8", NULL
, &len
, NULL
);
1041 gtk_selection_data_set(selection_data
, gdk_atom_intern("text/html", FALSE
), 16, (const guchar
*)selection
, len
);
1043 selection
= clipboard_html_to_win32(html_clipboard
);
1044 gtk_selection_data_set(selection_data
, gdk_atom_intern("HTML Format", FALSE
), 8, (const guchar
*)selection
, strlen(selection
));
1049 text
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1051 text
= text_clipboard
;
1052 gtk_selection_data_set_text(selection_data
, text
, strlen(text
));
1054 if (primary
) /* This was allocated here */
1058 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard
*clipboard
, GtkIMHtml
*imhtml
)
1061 GtkTextIter selection_bound
;
1063 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &insert
,
1064 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "insert"));
1065 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &selection_bound
,
1066 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"));
1068 if (!gtk_text_iter_equal (&insert
, &selection_bound
))
1069 gtk_text_buffer_move_mark (imhtml
->text_buffer
,
1070 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"),
1074 static void gtk_imhtml_clipboard_clear (GtkClipboard
*clipboard
, GtkSelectionData
*sel_data
,
1075 guint info
, gpointer user_data_or_owner
)
1079 static void copy_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1081 GtkTextIter start
, end
;
1082 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1083 if (!clipboard_selection
)
1084 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1085 gtk_clipboard_set_with_data(clipboard_selection
,
1086 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1087 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1088 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1090 g_free(html_clipboard
);
1091 g_free(text_clipboard
);
1093 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1094 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1097 g_signal_stop_emission_by_name(imhtml
, "copy-clipboard");
1100 static void cut_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1102 GtkTextIter start
, end
;
1103 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1104 if (!clipboard_selection
)
1105 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1106 gtk_clipboard_set_with_data(clipboard_selection
,
1107 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1108 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1109 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1111 g_free(html_clipboard
);
1112 g_free(text_clipboard
);
1114 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1115 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1117 if (imhtml
->editable
)
1118 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
1121 g_signal_stop_emission_by_name(imhtml
, "cut-clipboard");
1124 static void imhtml_paste_insert(GtkIMHtml
*imhtml
, const char *text
, gboolean plaintext
)
1127 GtkIMHtmlOptions flags
= plaintext
? GTK_IMHTML_NO_SMILEY
: (GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_COMMENTS
);
1129 /* Delete any currently selected text */
1130 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
1132 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1133 if (!imhtml
->wbfo
&& !plaintext
)
1134 gtk_imhtml_close_tags(imhtml
, &iter
);
1136 gtk_imhtml_insert_html_at_iter(imhtml
, text
, flags
, &iter
);
1137 gtk_text_buffer_move_mark_by_name(imhtml
->text_buffer
, "insert", &iter
);
1138 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml
), gtk_text_buffer_get_insert(imhtml
->text_buffer
),
1139 0, FALSE
, 0.0, 0.0);
1140 if (!imhtml
->wbfo
&& !plaintext
)
1141 gtk_imhtml_close_tags(imhtml
, &iter
);
1145 static void paste_plaintext_received_cb (GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
1149 if (text
== NULL
|| !(*text
))
1152 tmp
= g_markup_escape_text(text
, -1);
1153 imhtml_paste_insert(data
, tmp
, TRUE
);
1157 static void paste_received_cb (GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, gpointer data
)
1160 GtkIMHtml
*imhtml
= data
;
1162 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1165 if (imhtml
->wbfo
|| selection_data
->length
<= 0) {
1166 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1170 /* Here's some debug code, for figuring out what sent to us over the clipboard. */
1174 purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1175 selection_data
->format
, selection_data
->length
);
1177 for (i
= 0; i
< (/*(selection_data->format / 8) **/ selection_data
->length
); i
++) {
1180 if (selection_data
->data
[i
] == '\0')
1183 printf("%c", selection_data
->data
[i
]);
1189 text
= g_malloc(selection_data
->length
+ 1);
1190 memcpy(text
, selection_data
->data
, selection_data
->length
);
1191 /* Make sure the paste data is null-terminated. Given that
1192 * we're passed length (but assume later that it is
1193 * null-terminated), this seems sensible to me.
1195 text
[selection_data
->length
] = '\0';
1199 if (gtk_selection_data_get_data_type(selection_data
) == gdk_atom_intern("HTML Format", FALSE
)) {
1200 char *tmp
= clipboard_win32_to_html(text
);
1206 if (selection_data
->length
>= 2 &&
1207 (*(guint16
*)text
== 0xfeff || *(guint16
*)text
== 0xfffe)) {
1208 /* This is UTF-16 */
1209 char *utf8
= utf16_to_utf8_with_bom_check(text
, selection_data
->length
);
1213 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1218 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1219 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1224 imhtml_paste_insert(imhtml
, text
, FALSE
);
1229 static void smart_backspace_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1232 GtkTextChildAnchor
* anchor
;
1236 if (!imhtml
->editable
)
1239 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1241 /* Get the character before the insertion point */
1242 offset
= gtk_text_iter_get_offset(&iter
);
1246 gtk_text_iter_backward_char(&iter
);
1247 anchor
= gtk_text_iter_get_child_anchor(&iter
);
1250 return; /* No smiley here */
1252 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
1256 /* ok, then we need to insert the image buffer text before the anchor */
1257 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
1260 static void paste_clipboard_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1263 /* If we're on windows, let's see if we can get data from the HTML Format
1264 clipboard before we try to paste from the GTK buffer */
1265 if (!clipboard_paste_html_win32(imhtml
) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
))) {
1266 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1267 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1271 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1272 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1273 paste_received_cb
, imhtml
);
1275 g_signal_stop_emission_by_name(imhtml
, "paste-clipboard");
1278 static void imhtml_realized_remove_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1280 gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1281 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1285 static void imhtml_destroy_add_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1287 gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1288 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1291 static void mark_set_so_update_selection_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
, GtkIMHtml
*imhtml
)
1293 if (gtk_text_buffer_get_selection_bounds(buffer
, NULL
, NULL
)) {
1294 gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
),
1295 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1296 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1297 (GtkClipboardClearFunc
)gtk_imhtml_primary_clipboard_clear
, G_OBJECT(imhtml
));
1301 static gboolean
gtk_imhtml_button_press_event(GtkIMHtml
*imhtml
, GdkEventButton
*event
, gpointer unused
)
1303 if (event
->button
== 2) {
1306 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
);
1308 if (!imhtml
->editable
)
1311 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
),
1312 GTK_TEXT_WINDOW_TEXT
,
1317 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
1318 gtk_text_buffer_place_cursor(imhtml
->text_buffer
, &iter
);
1320 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1321 paste_received_cb
, imhtml
);
1330 gtk_imhtml_undo(GtkIMHtml
*imhtml
)
1332 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1333 if (imhtml
->editable
&&
1334 gtk_source_undo_manager_can_undo(imhtml
->undo_manager
))
1335 gtk_source_undo_manager_undo(imhtml
->undo_manager
);
1339 gtk_imhtml_redo(GtkIMHtml
*imhtml
)
1341 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1342 if (imhtml
->editable
&&
1343 gtk_source_undo_manager_can_redo(imhtml
->undo_manager
))
1344 gtk_source_undo_manager_redo(imhtml
->undo_manager
);
1348 static gboolean
imhtml_message_send(GtkIMHtml
*imhtml
)
1354 imhtml_paste_cb(GtkIMHtml
*imhtml
, const char *str
)
1356 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1359 if (!str
|| !*str
|| !strcmp(str
, "html"))
1360 g_signal_emit_by_name(imhtml
, "paste_clipboard");
1361 else if (!strcmp(str
, "text"))
1362 paste_unformatted_cb(NULL
, imhtml
);
1365 static void imhtml_toggle_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
1367 /* since this function is the handler for the formatting keystrokes,
1368 we need to check here that the formatting attempted is permitted */
1369 buttons
&= imhtml
->format_functions
;
1372 case GTK_IMHTML_BOLD
:
1373 imhtml_toggle_bold(imhtml
);
1375 case GTK_IMHTML_ITALIC
:
1376 imhtml_toggle_italic(imhtml
);
1378 case GTK_IMHTML_UNDERLINE
:
1379 imhtml_toggle_underline(imhtml
);
1381 case GTK_IMHTML_STRIKE
:
1382 imhtml_toggle_strike(imhtml
);
1384 case GTK_IMHTML_SHRINK
:
1385 imhtml_font_shrink(imhtml
);
1387 case GTK_IMHTML_GROW
:
1388 imhtml_font_grow(imhtml
);
1396 gtk_imhtml_finalize (GObject
*object
)
1398 GtkIMHtml
*imhtml
= GTK_IMHTML(object
);
1402 if (imhtml
->scroll_src
)
1403 g_source_remove(imhtml
->scroll_src
);
1404 if (imhtml
->scroll_time
)
1405 g_timer_destroy(imhtml
->scroll_time
);
1407 g_hash_table_destroy(imhtml
->smiley_data
);
1408 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
1409 gdk_cursor_unref(imhtml
->hand_cursor
);
1410 gdk_cursor_unref(imhtml
->arrow_cursor
);
1411 gdk_cursor_unref(imhtml
->text_cursor
);
1413 if(imhtml
->tip_window
){
1414 gtk_widget_destroy(imhtml
->tip_window
);
1416 if(imhtml
->tip_timer
)
1417 g_source_remove(imhtml
->tip_timer
);
1419 for(scalables
= imhtml
->scalables
; scalables
; scalables
= scalables
->next
) {
1420 struct scalable_data
*sd
= scalables
->data
;
1421 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
1426 for (l
= imhtml
->im_images
; l
; l
= l
->next
) {
1427 struct im_image_data
*img_data
= l
->data
;
1428 if (imhtml
->funcs
->image_unref
)
1429 imhtml
->funcs
->image_unref(img_data
->id
);
1433 g_list_free(imhtml
->scalables
);
1434 g_slist_free(imhtml
->im_images
);
1435 g_queue_free(imhtml
->animations
);
1436 g_free(imhtml
->protocol_name
);
1437 g_free(imhtml
->search_string
);
1438 g_object_unref(imhtml
->undo_manager
);
1439 G_OBJECT_CLASS(parent_class
)->finalize (object
);
1443 static GtkIMHtmlProtocol
*
1444 imhtml_find_protocol(const char *url
, gboolean reverse
)
1446 GtkIMHtmlClass
*klass
;
1448 GtkIMHtmlProtocol
*proto
= NULL
;
1449 int length
= reverse
? strlen(url
) : -1;
1451 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
1452 for (iter
= klass
->protocols
; iter
; iter
= iter
->next
) {
1454 if (g_ascii_strncasecmp(url
, proto
->name
, reverse
? MIN(length
, proto
->length
) : proto
->length
) == 0) {
1462 imhtml_url_clicked(GtkIMHtml
*imhtml
, const char *url
)
1464 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(url
, FALSE
);
1465 GtkIMHtmlLink
*link
;
1468 link
= g_new0(GtkIMHtmlLink
, 1);
1469 link
->imhtml
= g_object_ref(imhtml
);
1470 link
->url
= g_strdup(url
);
1471 proto
->activate(imhtml
, link
); /* XXX: Do something with the return value? */
1472 gtk_imhtml_link_destroy(link
);
1475 /* Boring GTK+ stuff */
1476 static void gtk_imhtml_class_init (GtkIMHtmlClass
*klass
)
1478 GtkWidgetClass
*widget_class
= (GtkWidgetClass
*) klass
;
1479 GtkBindingSet
*binding_set
;
1480 GObjectClass
*gobject_class
;
1481 gobject_class
= (GObjectClass
*) klass
;
1482 parent_class
= g_type_class_ref(GTK_TYPE_TEXT_VIEW
);
1483 signals
[URL_CLICKED
] = g_signal_new("url_clicked",
1484 G_TYPE_FROM_CLASS(gobject_class
),
1486 G_STRUCT_OFFSET(GtkIMHtmlClass
, url_clicked
),
1489 g_cclosure_marshal_VOID__POINTER
,
1492 signals
[BUTTONS_UPDATE
] = g_signal_new("format_buttons_update",
1493 G_TYPE_FROM_CLASS(gobject_class
),
1495 G_STRUCT_OFFSET(GtkIMHtmlClass
, buttons_update
),
1498 g_cclosure_marshal_VOID__INT
,
1501 signals
[TOGGLE_FORMAT
] = g_signal_new("format_function_toggle",
1502 G_TYPE_FROM_CLASS(gobject_class
),
1503 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1504 G_STRUCT_OFFSET(GtkIMHtmlClass
, toggle_format
),
1507 g_cclosure_marshal_VOID__INT
,
1510 signals
[CLEAR_FORMAT
] = g_signal_new("format_function_clear",
1511 G_TYPE_FROM_CLASS(gobject_class
),
1512 G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
1513 G_STRUCT_OFFSET(GtkIMHtmlClass
, clear_format
),
1516 g_cclosure_marshal_VOID__VOID
,
1518 signals
[UPDATE_FORMAT
] = g_signal_new("format_function_update",
1519 G_TYPE_FROM_CLASS(gobject_class
),
1521 G_STRUCT_OFFSET(GtkIMHtmlClass
, update_format
),
1524 g_cclosure_marshal_VOID__VOID
,
1526 signals
[MESSAGE_SEND
] = g_signal_new("message_send",
1527 G_TYPE_FROM_CLASS(gobject_class
),
1528 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1529 G_STRUCT_OFFSET(GtkIMHtmlClass
, message_send
),
1531 0, g_cclosure_marshal_VOID__VOID
,
1533 signals
[PASTE
] = g_signal_new("paste",
1534 G_TYPE_FROM_CLASS(gobject_class
),
1535 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1538 0, g_cclosure_marshal_VOID__STRING
,
1539 G_TYPE_NONE
, 1, G_TYPE_STRING
);
1540 signals
[UNDO
] = g_signal_new ("undo",
1541 G_TYPE_FROM_CLASS (klass
),
1542 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1543 G_STRUCT_OFFSET (GtkIMHtmlClass
, undo
),
1546 gtksourceview_marshal_VOID__VOID
,
1549 signals
[REDO
] = g_signal_new ("redo",
1550 G_TYPE_FROM_CLASS (klass
),
1551 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1552 G_STRUCT_OFFSET (GtkIMHtmlClass
, redo
),
1555 gtksourceview_marshal_VOID__VOID
,
1561 klass
->toggle_format
= imhtml_toggle_format
;
1562 klass
->message_send
= imhtml_message_send
;
1563 klass
->clear_format
= imhtml_clear_formatting
;
1564 klass
->url_clicked
= imhtml_url_clicked
;
1565 klass
->undo
= gtk_imhtml_undo
;
1566 klass
->redo
= gtk_imhtml_redo
;
1568 gobject_class
->finalize
= gtk_imhtml_finalize
;
1569 widget_class
->drag_motion
= gtk_text_view_drag_motion
;
1570 widget_class
->expose_event
= gtk_imhtml_expose_event
;
1571 parent_size_allocate
= widget_class
->size_allocate
;
1572 widget_class
->size_allocate
= gtk_imhtml_size_allocate
;
1573 parent_style_set
= widget_class
->style_set
;
1574 widget_class
->style_set
= gtk_imhtml_style_set
;
1576 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-color",
1577 _("Hyperlink color"),
1578 _("Color to draw hyperlinks."),
1579 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1580 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-visited-color",
1581 _("Hyperlink visited color"),
1582 _("Color to draw hyperlink after it has been visited (or activated)."),
1583 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1584 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-prelight-color",
1585 _("Hyperlink prelight color"),
1586 _("Color to draw hyperlinks when mouse is over them."),
1587 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1588 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("send-name-color",
1589 _("Sent Message Name Color"),
1590 _("Color to draw the name of a message you sent."),
1591 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1592 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("receive-name-color",
1593 _("Received Message Name Color"),
1594 _("Color to draw the name of a message you received."),
1595 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1596 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("highlight-name-color",
1597 _("\"Attention\" Name Color"),
1598 _("Color to draw the name of a message you received containing your name."),
1599 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1600 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("action-name-color",
1601 _("Action Message Name Color"),
1602 _("Color to draw the name of an action message."),
1603 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1604 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-action-name-color",
1605 _("Action Message Name Color for Whispered Message"),
1606 _("Color to draw the name of a whispered action message."),
1607 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1608 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-name-color",
1609 _("Whisper Message Name Color"),
1610 _("Color to draw the name of a whispered message."),
1611 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1613 /* Customizable typing notification ... sort of. Example:
1614 * GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1615 * GtkIMHtml::typing-notification-color = "#ff0000"
1616 * GtkIMHtml::typing-notification-enable = 1
1618 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("typing-notification-color",
1619 _("Typing notification color"),
1620 _("The color to use for the typing notification"),
1621 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1622 gtk_widget_class_install_style_property(widget_class
, g_param_spec_string("typing-notification-font",
1623 _("Typing notification font"),
1624 _("The font to use for the typing notification"),
1625 "light 8.0", G_PARAM_READABLE
));
1626 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boolean("typing-notification-enable",
1627 _("Enable typing notification"),
1628 _("Enable typing notification"),
1629 TRUE
, G_PARAM_READABLE
));
1631 binding_set
= gtk_binding_set_by_class (parent_class
);
1632 gtk_binding_entry_add_signal (binding_set
, GDK_b
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_BOLD
);
1633 gtk_binding_entry_add_signal (binding_set
, GDK_i
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_ITALIC
);
1634 gtk_binding_entry_add_signal (binding_set
, GDK_u
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_UNDERLINE
);
1635 gtk_binding_entry_add_signal (binding_set
, GDK_plus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1636 gtk_binding_entry_add_signal (binding_set
, GDK_equal
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1637 gtk_binding_entry_add_signal (binding_set
, GDK_minus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_SHRINK
);
1638 binding_set
= gtk_binding_set_by_class(klass
);
1639 gtk_binding_entry_add_signal (binding_set
, GDK_r
, GDK_CONTROL_MASK
, "format_function_clear", 0);
1640 gtk_binding_entry_add_signal (binding_set
, GDK_KP_Enter
, 0, "message_send", 0);
1641 gtk_binding_entry_add_signal (binding_set
, GDK_Return
, 0, "message_send", 0);
1642 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
, "undo", 0);
1643 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "redo", 0);
1644 gtk_binding_entry_add_signal (binding_set
, GDK_F14
, 0, "undo", 0);
1645 gtk_binding_entry_add_signal(binding_set
, GDK_v
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "paste", 1, G_TYPE_STRING
, "text");
1648 static void gtk_imhtml_init (GtkIMHtml
*imhtml
)
1650 imhtml
->text_buffer
= gtk_text_buffer_new(NULL
);
1651 imhtml
->undo_manager
= gtk_source_undo_manager_new(imhtml
->text_buffer
);
1652 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml
), imhtml
->text_buffer
);
1653 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
1654 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml
), 2);
1655 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml
), 3);
1656 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml
), 2);
1657 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml
), 2);
1658 /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1659 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1661 /* These tags will be used often and can be reused--we create them on init and then apply them by name
1662 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1663 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1664 * apply them anywhere yet. */
1665 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "BOLD", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1666 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "ITALICS", "style", PANGO_STYLE_ITALIC
, NULL
);
1667 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
1668 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "STRIKE", "strikethrough", TRUE
, NULL
);
1669 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUB", "rise", -5000, NULL
);
1670 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUP", "rise", 5000, NULL
);
1671 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "PRE", "family", "Monospace", NULL
);
1672 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "search", "background", "#22ff00", "weight", "bold", NULL
);
1673 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "comment", "weight", PANGO_WEIGHT_NORMAL
,
1674 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1679 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "send-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1680 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "receive-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1681 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "highlight-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1682 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1683 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1684 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1686 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1687 imhtml
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
1688 imhtml
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
1689 imhtml
->text_cursor
= gdk_cursor_new (GDK_XTERM
);
1691 imhtml
->show_comments
= TRUE
;
1693 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1694 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
1695 imhtml
->default_smilies
= gtk_smiley_tree_new();
1697 g_signal_connect(G_OBJECT(imhtml
), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify
), NULL
);
1698 g_signal_connect(G_OBJECT(imhtml
), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify
), NULL
);
1699 g_signal_connect(G_OBJECT(imhtml
), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify
), NULL
);
1700 g_signal_connect(G_OBJECT(imhtml
), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event
), NULL
);
1701 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(preinsert_cb
), imhtml
);
1702 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "delete_range", G_CALLBACK(delete_cb
), imhtml
);
1703 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(insert_cb
), imhtml
);
1704 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-child-anchor", G_CALLBACK(insert_ca_cb
), imhtml
);
1705 gtk_drag_dest_set(GTK_WIDGET(imhtml
), 0,
1706 link_drag_drop_targets
, sizeof(link_drag_drop_targets
) / sizeof(GtkTargetEntry
),
1708 g_signal_connect(G_OBJECT(imhtml
), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb
), imhtml
);
1709 g_signal_connect(G_OBJECT(imhtml
), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb
), imhtml
);
1711 g_signal_connect(G_OBJECT(imhtml
), "copy-clipboard", G_CALLBACK(copy_clipboard_cb
), NULL
);
1712 g_signal_connect(G_OBJECT(imhtml
), "cut-clipboard", G_CALLBACK(cut_clipboard_cb
), NULL
);
1713 g_signal_connect(G_OBJECT(imhtml
), "paste-clipboard", G_CALLBACK(paste_clipboard_cb
), NULL
);
1714 g_signal_connect_after(G_OBJECT(imhtml
), "realize", G_CALLBACK(imhtml_realized_remove_primary
), NULL
);
1715 g_signal_connect(G_OBJECT(imhtml
), "unrealize", G_CALLBACK(imhtml_destroy_add_primary
), NULL
);
1716 g_signal_connect(G_OBJECT(imhtml
), "paste", G_CALLBACK(imhtml_paste_cb
), NULL
);
1719 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
1720 G_CALLBACK(mark_set_so_update_selection_cb
), imhtml
);
1723 gtk_widget_add_events(GTK_WIDGET(imhtml
),
1724 GDK_LEAVE_NOTIFY_MASK
| GDK_ENTER_NOTIFY_MASK
);
1727 imhtml
->tip_timer
= 0;
1728 imhtml
->tip_window
= NULL
;
1730 imhtml
->edit
.bold
= FALSE
;
1731 imhtml
->edit
.italic
= FALSE
;
1732 imhtml
->edit
.underline
= FALSE
;
1733 imhtml
->edit
.forecolor
= NULL
;
1734 imhtml
->edit
.backcolor
= NULL
;
1735 imhtml
->edit
.fontface
= NULL
;
1736 imhtml
->edit
.fontsize
= 0;
1737 imhtml
->edit
.link
= NULL
;
1740 imhtml
->scalables
= NULL
;
1741 imhtml
->animations
= g_queue_new();
1742 gtk_imhtml_set_editable(imhtml
, FALSE
);
1743 g_signal_connect(G_OBJECT(imhtml
), "populate-popup",
1744 G_CALLBACK(hijack_menu_cb
), NULL
);
1747 GtkWidget
*gtk_imhtml_new(void *a
, void *b
)
1749 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL
));
1752 GType
gtk_imhtml_get_type()
1754 static GType imhtml_type
= 0;
1757 static const GTypeInfo imhtml_info
= {
1758 sizeof(GtkIMHtmlClass
),
1761 (GClassInitFunc
) gtk_imhtml_class_init
,
1766 (GInstanceInitFunc
) gtk_imhtml_init
,
1770 imhtml_type
= g_type_register_static(gtk_text_view_get_type(),
1771 "GtkIMHtml", &imhtml_info
, 0);
1777 static void gtk_imhtml_link_destroy(GtkIMHtmlLink
*link
)
1780 g_object_unref(link
->imhtml
);
1782 g_object_unref(link
->tag
);
1787 /* The callback for an event on a link tag. */
1788 static gboolean
tag_event(GtkTextTag
*tag
, GObject
*imhtml
, GdkEvent
*event
, GtkTextIter
*arg2
, gpointer unused
)
1790 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
1791 if (GTK_IMHTML(imhtml
)->editable
)
1793 if (event
->type
== GDK_BUTTON_RELEASE
) {
1794 if ((event_button
->button
== 1) || (event_button
->button
== 2)) {
1795 GtkTextIter start
, end
;
1796 /* we shouldn't open a URL if the user has selected something: */
1797 if (gtk_text_buffer_get_selection_bounds(
1798 gtk_text_iter_get_buffer(arg2
), &start
, &end
))
1800 gtk_imhtml_activate_tag(GTK_IMHTML(imhtml
), tag
);
1802 } else if(event_button
->button
== 3) {
1805 GtkIMHtmlProtocol
*proto
;
1806 GtkIMHtmlLink
*link
= g_new(GtkIMHtmlLink
, 1);
1807 link
->imhtml
= g_object_ref(imhtml
);
1808 link
->url
= g_strdup(g_object_get_data(G_OBJECT(tag
), "link_url"));
1809 link
->tag
= g_object_ref(tag
);
1811 /* Don't want the tooltip around if user right-clicked on link */
1812 if (GTK_IMHTML(imhtml
)->tip_window
) {
1813 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
1814 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
1816 if (GTK_IMHTML(imhtml
)->tip_timer
) {
1817 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
1818 GTK_IMHTML(imhtml
)->tip_timer
= 0;
1820 if (GTK_IMHTML(imhtml
)->editable
)
1821 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->text_cursor
);
1823 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->arrow_cursor
);
1824 menu
= gtk_menu_new();
1825 g_object_set_data_full(G_OBJECT(menu
), "x-imhtml-url-data", link
,
1826 (GDestroyNotify
)gtk_imhtml_link_destroy
);
1828 proto
= imhtml_find_protocol(link
->url
, FALSE
);
1830 if (proto
&& proto
->context_menu
) {
1831 proto
->context_menu(GTK_IMHTML(link
->imhtml
), link
, menu
);
1834 children
= gtk_container_get_children(GTK_CONTAINER(menu
));
1836 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
1837 gtk_widget_show(item
);
1838 gtk_widget_set_sensitive(item
, FALSE
);
1839 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1841 g_list_free(children
);
1845 gtk_widget_show_all(menu
);
1846 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1847 event_button
->button
, event_button
->time
);
1852 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
1853 return TRUE
; /* Clicking the right mouse button on a link shouldn't
1854 be caught by the regular GtkTextView menu */
1856 return FALSE
; /* Let clicks go through if we didn't catch anything */
1860 gtk_text_view_drag_motion (GtkWidget
*widget
,
1861 GdkDragContext
*context
,
1866 GdkDragAction suggested_action
= 0;
1868 if (gtk_drag_dest_find_target (widget
, context
, NULL
) == GDK_NONE
) {
1869 /* can't accept any of the offered targets */
1871 GtkWidget
*source_widget
;
1872 suggested_action
= context
->suggested_action
;
1873 source_widget
= gtk_drag_get_source_widget (context
);
1874 if (source_widget
== widget
) {
1875 /* Default to MOVE, unless the user has
1876 * pressed ctrl or alt to affect available actions
1878 if ((context
->actions
& GDK_ACTION_MOVE
) != 0)
1879 suggested_action
= GDK_ACTION_MOVE
;
1883 gdk_drag_status (context
, suggested_action
, time
);
1885 /* TRUE return means don't propagate the drag motion to parent
1886 * widgets that may also be drop sites.
1892 gtk_imhtml_link_drop_cb(GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer user_data
)
1894 GdkAtom target
= gtk_drag_dest_find_target (widget
, context
, NULL
);
1896 if (target
!= GDK_NONE
)
1897 gtk_drag_get_data (widget
, context
, target
, time
);
1899 gtk_drag_finish (context
, FALSE
, FALSE
, time
);
1905 gtk_imhtml_link_drag_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
1906 GtkSelectionData
*sd
, guint info
, guint t
, GtkIMHtml
*imhtml
)
1910 char *text
= (char *)sd
->data
;
1911 GtkTextMark
*mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1915 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
1917 if(gtk_imhtml_get_editable(imhtml
) && sd
->data
){
1919 case GTK_IMHTML_DRAG_URL
:
1920 /* TODO: Is it really ok to change sd->data...? */
1921 purple_str_strip_char((char *)sd
->data
, '\r');
1923 links
= g_strsplit((char *)sd
->data
, "\n", 0);
1924 while((link
= links
[i
]) != NULL
){
1925 if (gtk_imhtml_is_protocol(link
)) {
1933 gtk_imhtml_insert_link(imhtml
, mark
, link
, label
);
1934 } else if (*link
== '\0') {
1935 /* Ignore blank lines */
1937 /* Special reasons, aka images being put in via other tag, etc. */
1938 /* ... don't pretend we handled it if we didn't */
1939 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
1948 case GTK_IMHTML_DRAG_HTML
:
1951 /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1952 * as explained by this comment in gtkhtml:
1954 * FIXME This hack decides the charset of the selection. It seems that
1955 * mozilla/netscape alway use ucs2 for text/html
1956 * and openoffice.org seems to always use utf8 so we try to validate
1957 * the string as utf8 and if that fails we assume it is ucs2
1959 * See also the comment on text/html here:
1960 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1962 if (sd
->length
>= 2 && !g_utf8_validate(text
, sd
->length
- 1, NULL
)) {
1963 utf8
= utf16_to_utf8_with_bom_check(text
, sd
->length
);
1966 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1969 } else if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1970 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1974 gtk_imhtml_insert_html_at_iter(imhtml
, utf8
? utf8
: text
, 0, &iter
);
1978 case GTK_IMHTML_DRAG_TEXT
:
1979 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1980 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1983 char *tmp
= g_markup_escape_text(text
, -1);
1984 gtk_imhtml_insert_html_at_iter(imhtml
, tmp
, 0, &iter
);
1989 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
1992 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
1994 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
1998 static void gtk_smiley_tree_remove (GtkSmileyTree
*tree
,
1999 GtkIMHtmlSmiley
*smiley
)
2001 GtkSmileyTree
*t
= tree
;
2002 const gchar
*x
= smiley
->smile
;
2011 pos
= strchr (t
->values
->str
, *x
);
2013 t
= t
->children
[pos
- t
->values
->str
];
2026 gtk_smiley_tree_lookup (GtkSmileyTree
*tree
,
2029 GtkSmileyTree
*t
= tree
;
2030 const gchar
*x
= text
;
2041 if(*x
== '&' && (amp
= purple_markup_unescape_entity(x
, &alen
))) {
2042 gboolean matched
= TRUE
;
2043 /* Make sure all chars of the unescaped value match */
2044 while (*(amp
+ 1)) {
2045 pos
= strchr (t
->values
->str
, *amp
);
2047 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2057 pos
= strchr (t
->values
->str
, *amp
);
2059 else if (*x
== '<') /* Because we're all WYSIWYG now, a '<'
2060 * char should only appear as the start of a tag. Perhaps a safer (but costlier)
2061 * check would be to call gtk_imhtml_is_tag on it */
2065 pos
= strchr (t
->values
->str
, *x
);
2069 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2084 gtk_imhtml_disassociate_smiley_foreach(gpointer key
, gpointer value
,
2087 GtkSmileyTree
*tree
= (GtkSmileyTree
*) value
;
2088 GtkIMHtmlSmiley
*smiley
= (GtkIMHtmlSmiley
*) user_data
;
2089 gtk_smiley_tree_remove(tree
, smiley
);
2093 gtk_imhtml_disconnect_smiley(GtkIMHtml
*imhtml
, GtkIMHtmlSmiley
*smiley
)
2095 smiley
->imhtml
= NULL
;
2096 g_signal_handlers_disconnect_matched(imhtml
, G_SIGNAL_MATCH_DATA
, 0, 0,
2097 NULL
, NULL
, smiley
);
2101 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley
*smiley
)
2103 if (smiley
->imhtml
) {
2104 gtk_smiley_tree_remove(smiley
->imhtml
->default_smilies
, smiley
);
2105 g_hash_table_foreach(smiley
->imhtml
->smiley_data
,
2106 gtk_imhtml_disassociate_smiley_foreach
, smiley
);
2107 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2108 0, 0, NULL
, NULL
, smiley
);
2109 smiley
->imhtml
= NULL
;
2114 gtk_imhtml_associate_smiley (GtkIMHtml
*imhtml
,
2116 GtkIMHtmlSmiley
*smiley
)
2118 GtkSmileyTree
*tree
;
2119 g_return_if_fail (imhtml
!= NULL
);
2120 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2123 tree
= imhtml
->default_smilies
;
2124 else if (!(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
))) {
2125 tree
= gtk_smiley_tree_new();
2126 g_hash_table_insert(imhtml
->smiley_data
, g_strdup(sml
), tree
);
2129 /* need to disconnect old imhtml, if there is one */
2130 if (smiley
->imhtml
) {
2131 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2132 0, 0, NULL
, NULL
, smiley
);
2135 smiley
->imhtml
= imhtml
;
2137 gtk_smiley_tree_insert (tree
, smiley
);
2139 /* connect destroy signal for the imhtml */
2140 g_signal_connect(imhtml
, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley
),
2145 gtk_imhtml_is_smiley (GtkIMHtml
*imhtml
,
2150 GtkSmileyTree
*tree
;
2151 GtkIMHtmlFontDetail
*font
;
2160 sml
= imhtml
->protocol_name
;
2162 if (!sml
|| !(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
)))
2163 tree
= imhtml
->default_smilies
;
2168 *len
= gtk_smiley_tree_lookup (tree
, text
);
2172 static GtkIMHtmlSmiley
*gtk_imhtml_smiley_get_from_tree(GtkSmileyTree
*t
, const gchar
*text
)
2174 const gchar
*x
= text
;
2184 pos
= strchr(t
->values
->str
, *x
);
2188 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2196 gtk_imhtml_smiley_get(GtkIMHtml
*imhtml
, const gchar
*sml
, const gchar
*text
)
2198 GtkIMHtmlSmiley
*ret
;
2200 /* Look for custom smileys first */
2202 ret
= gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml
->smiley_data
, sml
), text
);
2207 /* Fall back to check for default smileys */
2208 return gtk_imhtml_smiley_get_from_tree(imhtml
->default_smilies
, text
);
2211 static GdkPixbufAnimation
*
2212 gtk_smiley_get_image(GtkIMHtmlSmiley
*smiley
)
2214 if (!smiley
->icon
) {
2216 smiley
->icon
= gdk_pixbuf_animation_new_from_file(smiley
->file
, NULL
);
2217 } else if (smiley
->loader
) {
2218 smiley
->icon
= gdk_pixbuf_loader_get_animation(smiley
->loader
);
2220 g_object_ref(G_OBJECT(smiley
->icon
));
2224 return smiley
->icon
;
2227 #define VALID_TAG(x) do { \
2228 if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
2229 if (tag) *tag = g_strndup (string, strlen (x)); \
2230 if (len) *len = strlen (x) + 1; \
2233 if (type) (*type)++; \
2236 #define VALID_OPT_TAG(x) do { \
2237 if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
2238 const gchar *c = string + strlen (x " "); \
2240 gboolean quote = FALSE; \
2242 if (*c == '"' || *c == '\'') { \
2243 if (quote && (*c == e)) \
2245 else if (!quote) { \
2249 } else if (!quote && (*c == '>')) \
2254 if (tag) *tag = g_strndup (string, c - string); \
2255 if (len) *len = c - string + 1; \
2259 if (type) (*type)++; \
2264 gtk_imhtml_is_tag (const gchar
*string
,
2273 if (!(close
= strchr (string
, '>')))
2279 VALID_TAG ("/BOLD");
2281 VALID_TAG ("ITALIC");
2283 VALID_TAG ("/ITALIC");
2285 VALID_TAG ("UNDERLINE");
2287 VALID_TAG ("/UNDERLINE");
2289 VALID_TAG ("STRIKE");
2291 VALID_TAG ("/STRIKE");
2298 VALID_TAG ("TITLE");
2299 VALID_TAG ("/TITLE");
2302 VALID_TAG ("/FONT");
2309 VALID_TAG ("/HTML");
2311 VALID_TAG ("/BODY");
2314 VALID_TAG ("/HEAD");
2315 VALID_TAG ("BINARY");
2316 VALID_TAG ("/BINARY");
2318 VALID_OPT_TAG ("HR");
2319 VALID_OPT_TAG ("FONT");
2320 VALID_OPT_TAG ("BODY");
2321 VALID_OPT_TAG ("A");
2322 VALID_OPT_TAG ("IMG");
2323 VALID_OPT_TAG ("P");
2324 VALID_OPT_TAG ("H3");
2325 VALID_OPT_TAG ("HTML");
2328 VALID_TAG ("/CITE");
2331 VALID_TAG ("STRONG");
2332 VALID_TAG ("/STRONG");
2334 VALID_OPT_TAG ("SPAN");
2335 VALID_TAG ("/SPAN");
2336 VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2339 VALID_OPT_TAG("BR");
2341 if (!g_ascii_strncasecmp(string
, "!--", strlen ("!--"))) {
2342 gchar
*e
= strstr (string
+ strlen("!--"), "-->");
2345 *len
= e
- string
+ strlen ("-->");
2347 *tag
= g_strndup (string
+ strlen ("!--"), *len
- strlen ("!---->"));
2355 *len
= close
- string
+ 1;
2357 *tag
= g_strndup(string
, *len
- 1);
2362 gtk_imhtml_get_html_opt (gchar
*tag
,
2372 while (g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2373 gboolean quote
= FALSE
;
2374 if (*t
== '\0') break;
2375 while (*t
&& !((*t
== ' ') && !quote
)) {
2380 while (*t
&& (*t
== ' ')) t
++;
2383 if (!g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2389 if ((*t
== '\"') || (*t
== '\'')) {
2391 while (*e
&& (*e
!= *(t
- 1))) e
++;
2395 val
= g_strndup(a
, e
- a
);
2398 while (*e
&& !isspace ((gint
) *e
)) e
++;
2399 val
= g_strndup(a
, e
- a
);
2402 ret
= g_string_new("");
2405 if((c
= purple_markup_unescape_entity(e
, &len
))) {
2406 ret
= g_string_append(ret
, c
);
2409 gunichar uni
= g_utf8_get_char(e
);
2410 ret
= g_string_append_unichar(ret
, uni
);
2411 e
= g_utf8_next_char(e
);
2417 return g_string_free(ret
, FALSE
);
2420 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2421 the caller knows how long the protocol string is. */
2422 static int gtk_imhtml_is_protocol(const char *text
)
2424 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(text
, FALSE
);
2425 return proto
? proto
->length
: 0;
2428 static gboolean
smooth_scroll_cb(gpointer data
);
2431 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2434 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2435 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2436 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2437 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2438 [20:00] <marv> Robot101: so how does the image get passed to serv_got_im() and serv_send_im()? just as the <img id="#" and then the prpl looks it up from the store?
2439 [20:00] <KingAnt> marv: Right
2440 [20:00] <marv> alright
2442 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2443 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2444 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2445 images can be looked up like that, instead of passing a GSList of them.
2448 void gtk_imhtml_append_text_with_images (GtkIMHtml
*imhtml
,
2450 GtkIMHtmlOptions options
,
2453 GtkTextIter iter
, ins
, sel
;
2454 int ins_offset
= 0, sel_offset
= 0;
2455 gboolean fixins
= FALSE
, fixsel
= FALSE
;
2457 g_return_if_fail (imhtml
!= NULL
);
2458 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2459 g_return_if_fail (text
!= NULL
);
2462 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
2463 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &ins
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
2464 if (gtk_text_iter_equal(&iter
, &ins
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2466 ins_offset
= gtk_text_iter_get_offset(&ins
);
2469 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &sel
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
));
2470 if (gtk_text_iter_equal(&iter
, &sel
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2472 sel_offset
= gtk_text_iter_get_offset(&sel
);
2475 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2479 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2480 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
2482 if (((y
+ height
) - (rect
.y
+ rect
.height
)) > height
&&
2483 gtk_text_buffer_get_char_count(imhtml
->text_buffer
)) {
2484 /* If we are in the middle of smooth-scrolling, then take a scroll step.
2485 * If we are not in the middle of smooth-scrolling, that means we were
2486 * not looking at the end of the buffer before the new text was added,
2487 * so do not scroll. */
2488 if (imhtml
->scroll_time
)
2489 smooth_scroll_cb(imhtml
);
2491 options
|= GTK_IMHTML_NO_SCROLL
;
2495 gtk_imhtml_insert_html_at_iter(imhtml
, text
, options
, &iter
);
2498 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &ins
, ins_offset
);
2499 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_insert(imhtml
->text_buffer
), &ins
);
2503 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &sel
, sel_offset
);
2504 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
), &sel
);
2507 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2508 gtk_imhtml_scroll_to_end(imhtml
, (options
& GTK_IMHTML_USE_SMOOTHSCROLLING
));
2512 #define MAX_SCROLL_TIME 0.4 /* seconds */
2513 #define SCROLL_DELAY 33 /* milliseconds */
2516 * Smoothly scroll a GtkIMHtml.
2518 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2520 static gboolean
smooth_scroll_cb(gpointer data
)
2522 GtkIMHtml
*imhtml
= data
;
2523 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2524 gdouble max_val
= adj
->upper
- adj
->page_size
;
2525 gdouble scroll_val
= gtk_adjustment_get_value(adj
) + ((max_val
- gtk_adjustment_get_value(adj
)) / 3);
2527 g_return_val_if_fail(imhtml
->scroll_time
!= NULL
, FALSE
);
2529 if (g_timer_elapsed(imhtml
->scroll_time
, NULL
) > MAX_SCROLL_TIME
|| scroll_val
>= max_val
) {
2530 /* time's up. jump to the end and kill the timer */
2531 gtk_adjustment_set_value(adj
, max_val
);
2532 g_timer_destroy(imhtml
->scroll_time
);
2533 imhtml
->scroll_time
= NULL
;
2534 g_source_remove(imhtml
->scroll_src
);
2535 imhtml
->scroll_src
= 0;
2539 /* scroll by 1/3rd the remaining distance */
2540 gtk_adjustment_set_value(adj
, scroll_val
);
2544 static gboolean
scroll_idle_cb(gpointer data
)
2546 GtkIMHtml
*imhtml
= data
;
2547 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2549 gtk_adjustment_set_value(adj
, adj
->upper
- adj
->page_size
);
2551 imhtml
->scroll_src
= 0;
2555 void gtk_imhtml_scroll_to_end(GtkIMHtml
*imhtml
, gboolean smooth
)
2557 if (imhtml
->scroll_time
)
2558 g_timer_destroy(imhtml
->scroll_time
);
2559 if (imhtml
->scroll_src
)
2560 g_source_remove(imhtml
->scroll_src
);
2562 imhtml
->scroll_time
= g_timer_new();
2563 imhtml
->scroll_src
= g_timeout_add_full(G_PRIORITY_LOW
, SCROLL_DELAY
, smooth_scroll_cb
, imhtml
, NULL
);
2565 imhtml
->scroll_time
= NULL
;
2566 imhtml
->scroll_src
= g_idle_add_full(G_PRIORITY_LOW
, scroll_idle_cb
, imhtml
, NULL
);
2570 /* CSS colors are either rgb (x,y,z) or #hex
2571 * we need to convert to hex if it is RGB */
2573 parse_css_color(gchar
*in_color
)
2575 char *tmp
= in_color
;
2577 if (*tmp
== 'r' && *(++tmp
) == 'g' && *(++tmp
) == 'b' && *(++tmp
)) {
2578 int rgbval
[] = {0, 0, 0};
2580 const char *v_start
;
2582 while (*tmp
&& g_ascii_isspace(*tmp
))
2585 /* We don't support rgba() */
2586 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2592 /* Skip any leading spaces */
2593 while (*tmp
&& g_ascii_isspace(*tmp
))
2596 /* Find the subsequent contiguous digits */
2598 if (*v_start
== '-')
2600 while (*tmp
&& g_ascii_isdigit(*tmp
))
2603 if (tmp
!= v_start
) {
2606 rgbval
[count
] = atoi(v_start
);
2610 while (*tmp
&& g_ascii_isspace(*tmp
))
2613 rgbval
[count
] = (rgbval
[count
] / 100.0) * 255;
2617 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2621 if (rgbval
[count
] > 255) {
2622 rgbval
[count
] = 255;
2623 } else if (rgbval
[count
] < 0) {
2627 while (*tmp
&& g_ascii_isspace(*tmp
))
2636 return g_strdup_printf("#%02X%02X%02X", rgbval
[0], rgbval
[1], rgbval
[2]);
2642 void gtk_imhtml_insert_html_at_iter(GtkIMHtml
*imhtml
,
2644 GtkIMHtmlOptions options
,
2653 gint tlen
, smilelen
, wpos
=0;
2668 gboolean br
= FALSE
;
2669 gboolean align_right
= FALSE
;
2670 gboolean rtl_direction
= FALSE
;
2671 gint align_line
= 0;
2673 GSList
*fonts
= NULL
;
2675 GtkIMHtmlScalable
*scalable
= NULL
;
2677 g_return_if_fail (imhtml
!= NULL
);
2678 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2679 g_return_if_fail (text
!= NULL
);
2682 ws
= g_malloc(len
+ 1);
2685 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2687 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
2689 if (*c
== '<' && gtk_imhtml_is_tag (c
+ 1, &tag
, &tlen
, &type
)) {
2698 case 54: /* STRONG */
2699 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2700 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2702 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
))
2703 gtk_imhtml_toggle_bold(imhtml
);
2705 ws
[0] = '\0'; wpos
= 0;
2710 case 55: /* /STRONG */
2711 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2712 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2713 ws
[0] = '\0'; wpos
= 0;
2717 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
) && !imhtml
->wbfo
)
2718 gtk_imhtml_toggle_bold(imhtml
);
2723 case 6: /* ITALIC */
2725 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2726 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2727 ws
[0] = '\0'; wpos
= 0;
2728 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
))
2729 gtk_imhtml_toggle_italic(imhtml
);
2734 case 8: /* /ITALIC */
2736 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2737 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2738 ws
[0] = '\0'; wpos
= 0;
2741 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
) && !imhtml
->wbfo
)
2742 gtk_imhtml_toggle_italic(imhtml
);
2747 case 10: /* UNDERLINE */
2748 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2749 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2750 ws
[0] = '\0'; wpos
= 0;
2751 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
))
2752 gtk_imhtml_toggle_underline(imhtml
);
2757 case 12: /* /UNDERLINE */
2758 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2759 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2760 ws
[0] = '\0'; wpos
= 0;
2763 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
) && !imhtml
->wbfo
)
2764 gtk_imhtml_toggle_underline(imhtml
);
2769 case 14: /* STRIKE */
2770 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2771 ws
[0] = '\0'; wpos
= 0;
2772 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
))
2773 gtk_imhtml_toggle_strike(imhtml
);
2777 case 16: /* /STRIKE */
2778 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2779 ws
[0] = '\0'; wpos
= 0;
2782 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
) && !imhtml
->wbfo
)
2783 gtk_imhtml_toggle_strike(imhtml
);
2786 /* FIXME: reimpliment this */
2790 /* FIXME: reimpliment this */
2795 /* FIXME: reimplement this */
2799 /* FIXME: reimplement this */
2804 /* FIXME: reimplement this */
2808 /* FIXME: reimplement this */
2812 case 23: /* TITLE */
2813 /* FIXME: what was this supposed to do anyway? */
2816 case 24: /* /TITLE */
2817 /* FIXME: make this undo whatever 23 was supposed to do */
2819 if (options
& GTK_IMHTML_NO_TITLE
) {
2828 case 61: /* BR (opt) */
2834 case 42: /* HR (opt) */
2837 struct scalable_data
*sd
= g_new(struct scalable_data
, 1);
2840 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2842 sd
->scalable
= scalable
= gtk_imhtml_hr_new();
2843 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
2844 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2845 scalable
->add_to(scalable
, imhtml
, iter
);
2846 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
2847 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
2848 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
2849 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
2850 ws
[0] = '\0'; wpos
= 0;
2855 case 27: /* /FONT */
2856 if (fonts
&& !imhtml
->wbfo
) {
2857 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2858 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2859 ws
[0] = '\0'; wpos
= 0;
2860 /* NEW_BIT (NEW_TEXT_BIT); */
2862 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2863 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
2865 g_free (font
->face
);
2866 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2867 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
2869 g_free (font
->fore
);
2870 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2871 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
2873 g_free (font
->back
);
2876 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2877 gtk_imhtml_font_set_size(imhtml
, 3);
2879 fonts
= g_slist_remove (fonts
, font
);
2883 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2885 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
))
2886 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2887 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
))
2888 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2889 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
))
2890 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2891 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2892 gtk_imhtml_font_set_size(imhtml
, font
->size
);
2897 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2898 gtk_imhtml_toggle_link(imhtml
, NULL
);
2899 ws
[0] = '\0'; wpos
= 0;
2907 case 34: /* /HTML */
2910 case 36: /* /BODY */
2911 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2912 ws
[0] = '\0'; wpos
= 0;
2913 gtk_imhtml_toggle_background(imhtml
, NULL
);
2917 case 39: /* /HEAD */
2918 case 40: /* BINARY */
2919 case 41: /* /BINARY */
2921 case 43: /* FONT (opt) */
2923 gchar
*color
, *back
, *face
, *size
, *sml
;
2924 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
2925 color
= gtk_imhtml_get_html_opt (tag
, "COLOR=");
2926 back
= gtk_imhtml_get_html_opt (tag
, "BACK=");
2927 face
= gtk_imhtml_get_html_opt (tag
, "FACE=");
2928 size
= gtk_imhtml_get_html_opt (tag
, "SIZE=");
2929 sml
= gtk_imhtml_get_html_opt (tag
, "SML=");
2930 if (!(color
|| back
|| face
|| size
|| sml
))
2933 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2934 ws
[0] = '\0'; wpos
= 0;
2936 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
2938 oldfont
= fonts
->data
;
2940 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2942 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2946 if (back
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2948 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2952 if (face
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2954 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2962 if (oldfont
&& oldfont
->sml
)
2963 font
->sml
= g_strdup(oldfont
->sml
);
2966 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
))) {
2968 sscanf (size
+ 1, "%hd", &font
->size
);
2970 } else if (*size
== '-') {
2971 sscanf (size
+ 1, "%hd", &font
->size
);
2972 font
->size
= MAX (0, 3 - font
->size
);
2973 } else if (isdigit (*size
)) {
2974 sscanf (size
, "%hd", &font
->size
);
2976 if (font
->size
> 100)
2979 font
->size
= oldfont
->size
;
2982 if ((imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)) && (font
->size
!= 3 || (oldfont
&& oldfont
->size
== 3)))
2983 gtk_imhtml_font_set_size(imhtml
, font
->size
);
2985 fonts
= g_slist_prepend (fonts
, font
);
2988 case 44: /* BODY (opt) */
2989 if (!(options
& GTK_IMHTML_NO_COLOURS
)) {
2990 char *bgcolor
= gtk_imhtml_get_html_opt (tag
, "BGCOLOR=");
2991 if (bgcolor
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2992 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2993 ws
[0] = '\0'; wpos
= 0;
2994 /* NEW_BIT(NEW_TEXT_BIT); */
2997 gtk_imhtml_toggle_background(imhtml
, bg
);
3002 case 45: /* A (opt) */
3004 gchar
*href
= gtk_imhtml_get_html_opt (tag
, "HREF=");
3005 if (href
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3006 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3007 ws
[0] = '\0'; wpos
= 0;
3008 gtk_imhtml_toggle_link(imhtml
, href
);
3013 case 46: /* IMG (opt) */
3018 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3019 ws
[0] = '\0'; wpos
= 0;
3021 if (!(imhtml
->format_functions
& GTK_IMHTML_IMAGE
))
3024 id
= gtk_imhtml_get_html_opt(tag
, "ID=");
3026 gtk_imhtml_insert_image_at_iter(imhtml
, atoi(id
), iter
);
3030 src
= gtk_imhtml_get_html_opt(tag
, "SRC=");
3031 alt
= gtk_imhtml_get_html_opt(tag
, "ALT=");
3033 gtk_imhtml_toggle_link(imhtml
, src
);
3034 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, alt
? alt
: src
, -1);
3035 gtk_imhtml_toggle_link(imhtml
, NULL
);
3042 case 47: /* P (opt) */
3043 case 48: /* H3 (opt) */
3044 case 49: /* HTML (opt) */
3046 case 51: /* /CITE */
3047 case 56: /* SPAN (opt) */
3048 /* Inline CSS Support - Douglas Thrift
3054 * text-decoration: underline
3064 gchar
*style
, *color
, *background
, *family
, *size
, *direction
, *alignment
;
3065 gchar
*textdec
, *weight
;
3066 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
3067 style
= gtk_imhtml_get_html_opt (tag
, "style=");
3071 color
= purple_markup_get_css_property (style
, "color");
3072 background
= purple_markup_get_css_property (style
, "background");
3073 family
= purple_markup_get_css_property (style
, "font-family");
3074 size
= purple_markup_get_css_property (style
, "font-size");
3075 textdec
= purple_markup_get_css_property (style
, "text-decoration");
3076 weight
= purple_markup_get_css_property (style
, "font-weight");
3077 direction
= purple_markup_get_css_property (style
, "direction");
3078 alignment
= purple_markup_get_css_property (style
, "text-align");
3081 if (!(color
|| family
|| size
|| background
|| textdec
|| weight
|| direction
|| alignment
)) {
3087 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3088 ws
[0] = '\0'; wpos
= 0;
3089 /* NEW_BIT (NEW_TEXT_BIT); */
3091 /* Bi-Directional text support */
3092 if (direction
&& (!g_ascii_strncasecmp(direction
, "RTL", 3))) {
3093 rtl_direction
= TRUE
;
3094 /* insert RLE character to set direction */
3099 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3100 ws
[0] = '\0'; wpos
= 0;
3104 if (alignment
&& (!g_ascii_strncasecmp(alignment
, "RIGHT", 5))) {
3106 align_line
= gtk_text_iter_get_line(iter
);
3110 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
3112 oldfont
= fonts
->data
;
3114 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
3115 font
->fore
= parse_css_color(color
);
3116 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
3118 if (oldfont
&& oldfont
->fore
)
3119 font
->fore
= g_strdup(oldfont
->fore
);
3123 if (background
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
3124 font
->back
= parse_css_color(background
);
3125 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
3127 if (oldfont
&& oldfont
->back
)
3128 font
->back
= g_strdup(oldfont
->back
);
3132 if (family
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
3133 font
->face
= family
;
3134 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
3136 if (oldfont
&& oldfont
->face
)
3137 font
->face
= g_strdup(oldfont
->face
);
3140 if (font
->face
&& (atoi(font
->face
) > 100)) {
3142 /* Maybe it sets a max size on the font face? I seem to
3143 * remember bad things happening if the font size was
3146 font
->face
= g_strdup("100");
3149 if (oldfont
&& oldfont
->sml
)
3150 font
->sml
= g_strdup(oldfont
->sml
);
3152 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_SHRINK
|GTK_IMHTML_GROW
))) {
3153 if (g_ascii_strcasecmp(size
, "xx-small") == 0)
3155 else if (g_ascii_strcasecmp(size
, "smaller") == 0
3156 || g_ascii_strcasecmp(size
, "x-small") == 0)
3158 else if (g_ascii_strcasecmp(size
, "medium") == 0)
3160 else if (g_ascii_strcasecmp(size
, "large") == 0
3161 || g_ascii_strcasecmp(size
, "larger") == 0)
3163 else if (g_ascii_strcasecmp(size
, "x-large") == 0)
3165 else if (g_ascii_strcasecmp(size
, "xx-large") == 0)
3169 * TODO: Handle other values, like percentages, or
3170 * lengths specified as em, ex, px, in, cm, mm, pt
3171 * or pc. Or even better, use an actual HTML
3172 * renderer like webkit.
3175 gtk_imhtml_font_set_size(imhtml
, font
->size
);
3179 font
->size
= oldfont
->size
;
3184 font
->underline
= oldfont
->underline
;
3186 if (textdec
&& font
->underline
!= 1
3187 && g_ascii_strcasecmp(textdec
, "underline") == 0
3188 && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
)
3189 && !(options
& GTK_IMHTML_NO_FORMATTING
))
3191 gtk_imhtml_toggle_underline(imhtml
);
3192 font
->underline
= 1;
3198 font
->bold
= oldfont
->bold
;
3202 if(!g_ascii_strcasecmp(weight
, "normal")) {
3204 } else if(!g_ascii_strcasecmp(weight
, "bold")) {
3206 } else if(!g_ascii_strcasecmp(weight
, "bolder")) {
3208 } else if(!g_ascii_strcasecmp(weight
, "lighter")) {
3212 int num
= atoi(weight
);
3218 if (((font
->bold
&& oldfont
&& !oldfont
->bold
) || (oldfont
&& oldfont
->bold
&& !font
->bold
) || (font
->bold
&& !oldfont
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3220 gtk_imhtml_toggle_bold(imhtml
);
3227 fonts
= g_slist_prepend (fonts
, font
);
3230 case 57: /* /SPAN */
3231 /* Inline CSS Support - Douglas Thrift */
3232 if (fonts
&& !imhtml
->wbfo
) {
3233 GtkIMHtmlFontDetail
*oldfont
= NULL
;
3234 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3235 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3236 ws
[0] = '\0'; wpos
= 0;
3237 /* NEW_BIT (NEW_TEXT_BIT); */
3238 fonts
= g_slist_remove (fonts
, font
);
3240 oldfont
= fonts
->data
;
3243 gtk_imhtml_font_set_size(imhtml
, 3);
3244 if (font
->underline
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3245 gtk_imhtml_toggle_underline(imhtml
);
3246 if (font
->bold
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3247 gtk_imhtml_toggle_bold(imhtml
);
3248 if (!(options
& GTK_IMHTML_NO_FONTS
))
3249 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
3250 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3251 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
3252 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3253 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
3258 if ((font
->size
!= oldfont
->size
) && !(options
& GTK_IMHTML_NO_SIZES
))
3259 gtk_imhtml_font_set_size(imhtml
, oldfont
->size
);
3261 if ((font
->underline
!= oldfont
->underline
) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3262 gtk_imhtml_toggle_underline(imhtml
);
3264 if (((font
->bold
&& !oldfont
->bold
) || (oldfont
->bold
&& !font
->bold
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3265 gtk_imhtml_toggle_bold(imhtml
);
3267 if (font
->face
&& (!oldfont
->face
|| strcmp(font
->face
, oldfont
->face
) != 0) && !(options
& GTK_IMHTML_NO_FONTS
))
3268 gtk_imhtml_toggle_fontface(imhtml
, oldfont
->face
);
3270 if (font
->fore
&& (!oldfont
->fore
|| strcmp(font
->fore
, oldfont
->fore
) != 0) && !(options
& GTK_IMHTML_NO_COLOURS
))
3271 gtk_imhtml_toggle_forecolor(imhtml
, oldfont
->fore
);
3273 if (font
->back
&& (!oldfont
->back
|| strcmp(font
->back
, oldfont
->back
) != 0) && !(options
& GTK_IMHTML_NO_COLOURS
))
3274 gtk_imhtml_toggle_backcolor(imhtml
, oldfont
->back
);
3277 g_free (font
->face
);
3278 g_free (font
->fore
);
3279 g_free (font
->back
);
3287 case 62: /* comment */
3288 /* NEW_BIT (NEW_TEXT_BIT); */
3291 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3293 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3294 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3295 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3297 if (imhtml
->show_comments
&& !(options
& GTK_IMHTML_NO_COMMENTS
)) {
3298 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3299 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3302 ws
[0] = '\0'; wpos
= 0;
3304 /* NEW_BIT (NEW_COMMENT_BIT); */
3311 g_free(tag
); /* This was allocated back in VALID_TAG() */
3312 } else if (imhtml
->edit
.link
== NULL
&&
3313 !(options
& GTK_IMHTML_NO_SMILEY
) &&
3314 gtk_imhtml_is_smiley(imhtml
, fonts
, c
, &smilelen
)) {
3315 GtkIMHtmlFontDetail
*fd
;
3325 sml
= imhtml
->protocol_name
;
3327 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3328 wpos
= g_snprintf (ws
, smilelen
+ 1, "%s", c
);
3330 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, ws
, iter
);
3336 } else if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3339 ws
[wpos
++] = *amp
++;
3343 } else if (*c
== '\n') {
3344 if (!(options
& GTK_IMHTML_NO_NEWLINE
)) {
3347 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3348 ws
[0] = '\0'; wpos
= 0;
3349 /* NEW_BIT (NEW_TEXT_BIT); */
3350 } else if (!br
) { /* Don't insert a space immediately after an HTML break */
3351 /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3352 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3353 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3354 * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand?
3358 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3359 ws
[0] = '\0'; wpos
= 0;
3363 } else if ((pos
== 0 || wpos
== 0 || isspace(*(c
- 1))) &&
3364 (len_protocol
= gtk_imhtml_is_protocol(c
)) > 0 &&
3365 c
[len_protocol
] && !isspace(c
[len_protocol
]) &&
3366 (c
[len_protocol
] != '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3369 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3370 ws
[0] = '\0'; wpos
= 0;
3372 while (len_protocol
--) {
3373 /* Skip the next len_protocol characters, but
3374 * make sure they're copied into the ws array.
3379 if (!imhtml
->edit
.link
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3380 while (*c
&& !isspace((int)*c
) &&
3381 (*c
!= '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3382 if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3384 ws
[wpos
++] = *amp
++;
3393 gtk_imhtml_toggle_link(imhtml
, ws
);
3394 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3395 ws
[0] = '\0'; wpos
= 0;
3396 gtk_imhtml_toggle_link(imhtml
, NULL
);
3406 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3407 ws
[0] = '\0'; wpos
= 0;
3409 /* NEW_BIT(NEW_TEXT_BIT); */
3412 /* insert RLM+LRM at beginning of the line to set alignment */
3413 GtkTextIter line_iter
;
3415 gtk_text_iter_set_line(&line_iter
, align_line
);
3416 /* insert RLM character to set alignment */
3423 /* insert LRM character to set direction */
3424 /* (alignment=right and direction=LTR) */
3431 gtk_text_buffer_insert(imhtml
->text_buffer
, &line_iter
, ws
, wpos
);
3432 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter
), iter
);
3433 ws
[0] = '\0'; wpos
= 0;
3437 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3438 fonts
= g_slist_remove (fonts
, font
);
3439 g_free (font
->face
);
3440 g_free (font
->fore
);
3441 g_free (font
->back
);
3450 gtk_imhtml_close_tags(imhtml
, iter
);
3452 object
= g_object_ref(G_OBJECT(imhtml
));
3453 g_signal_emit(object
, signals
[UPDATE_FORMAT
], 0);
3454 g_object_unref(object
);
3456 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
3459 void gtk_imhtml_remove_smileys(GtkIMHtml
*imhtml
)
3461 g_hash_table_destroy(imhtml
->smiley_data
);
3462 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
3463 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3464 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
3465 imhtml
->default_smilies
= gtk_smiley_tree_new();
3468 void gtk_imhtml_show_comments (GtkIMHtml
*imhtml
,
3471 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3473 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), "comment");
3475 g_object_set(G_OBJECT(tag
), "invisible", !show
, NULL
);
3477 imhtml
->show_comments
= show
;
3481 gtk_imhtml_get_protocol_name(GtkIMHtml
*imhtml
) {
3482 return imhtml
->protocol_name
;
3486 gtk_imhtml_set_protocol_name(GtkIMHtml
*imhtml
, const gchar
*protocol_name
) {
3487 g_free(imhtml
->protocol_name
);
3488 imhtml
->protocol_name
= g_strdup(protocol_name
);
3492 gtk_imhtml_delete(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
) {
3495 GtkTextIter i
, i_s
, i_e
;
3496 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
3498 if (start
== NULL
) {
3499 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &i_s
);
3504 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &i_e
);
3508 l
= imhtml
->scalables
;
3510 GList
*next
= l
->next
;
3511 struct scalable_data
*sd
= l
->data
;
3512 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3514 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3515 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
3518 imhtml
->scalables
= g_list_delete_link(imhtml
->scalables
, l
);
3523 sl
= imhtml
->im_images
;
3525 GSList
*next
= sl
->next
;
3526 struct im_image_data
*img_data
= sl
->data
;
3527 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3528 &i
, img_data
->mark
);
3529 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3530 if (imhtml
->funcs
->image_unref
)
3531 imhtml
->funcs
->image_unref(img_data
->id
);
3532 imhtml
->im_images
= g_slist_delete_link(imhtml
->im_images
, sl
);
3537 gtk_text_buffer_delete(imhtml
->text_buffer
, start
, end
);
3539 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3541 g_object_unref(object
);
3544 void gtk_imhtml_page_up (GtkIMHtml
*imhtml
)
3549 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3550 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3551 rect
.y
- rect
.height
);
3552 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3555 void gtk_imhtml_page_down (GtkIMHtml
*imhtml
)
3560 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3561 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3562 rect
.y
+ rect
.height
);
3563 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3566 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
3567 GtkIMHtmlScalable
*gtk_imhtml_image_new(GdkPixbuf
*img
, const gchar
*filename
, int id
)
3569 GtkIMHtmlImage
*im_image
= g_malloc(sizeof(GtkIMHtmlImage
));
3571 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3572 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3573 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_image_free
;
3575 im_image
->pixbuf
= img
;
3576 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3577 im_image
->width
= gdk_pixbuf_get_width(img
);
3578 im_image
->height
= gdk_pixbuf_get_height(img
);
3579 im_image
->mark
= NULL
;
3580 im_image
->filename
= g_strdup(filename
);
3582 im_image
->filesel
= NULL
;
3585 return GTK_IMHTML_SCALABLE(im_image
);
3589 animate_image_cb(gpointer data
)
3591 GtkIMHtmlImage
*im_image
;
3597 /* Update the pointer to this GdkPixbuf frame of the animation */
3598 if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image
)->iter
, NULL
)) {
3599 GdkPixbuf
*pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3600 g_object_unref(G_OBJECT(im_image
->pixbuf
));
3601 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3603 /* Update the displayed GtkImage */
3604 width
= gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
));
3605 height
= gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image
->image
));
3606 if (width
> 0 && height
> 0)
3608 /* Need to scale the new frame to the same size as the old frame */
3610 tmp
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, width
, height
, GDK_INTERP_BILINEAR
);
3611 gtk_image_set_from_pixbuf(im_image
->image
, tmp
);
3612 g_object_unref(G_OBJECT(tmp
));
3614 /* Display at full-size */
3615 gtk_image_set_from_pixbuf(im_image
->image
, im_image
->pixbuf
);
3619 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3620 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3625 GtkIMHtmlScalable
*gtk_imhtml_animation_new(GdkPixbufAnimation
*anim
, const gchar
*filename
, int id
)
3627 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*) g_new0(GtkIMHtmlAnimation
, 1);
3629 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3630 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3631 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_animation_free
;
3633 GTK_IMHTML_ANIMATION(im_image
)->anim
= anim
;
3634 if (gdk_pixbuf_animation_is_static_image(anim
)) {
3635 im_image
->pixbuf
= gdk_pixbuf_animation_get_static_image(anim
);
3636 g_object_ref(im_image
->pixbuf
);
3640 GTK_IMHTML_ANIMATION(im_image
)->iter
= gdk_pixbuf_animation_get_iter(anim
, NULL
);
3641 pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3642 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3643 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3644 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3646 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3647 im_image
->width
= gdk_pixbuf_animation_get_width(anim
);
3648 im_image
->height
= gdk_pixbuf_animation_get_height(anim
);
3649 im_image
->filename
= g_strdup(filename
);
3654 return GTK_IMHTML_SCALABLE(im_image
);
3657 void gtk_imhtml_image_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
3659 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*)scale
;
3661 if (im_image
->width
> width
|| im_image
->height
> height
) {
3662 double ratio_w
, ratio_h
, ratio
;
3664 GdkPixbuf
*new_image
= NULL
;
3666 ratio_w
= ((double)width
- 2) / im_image
->width
;
3667 ratio_h
= ((double)height
- 2) / im_image
->height
;
3669 ratio
= (ratio_w
< ratio_h
) ? ratio_w
: ratio_h
;
3671 new_w
= (int)(im_image
->width
* ratio
);
3672 new_h
= (int)(im_image
->height
* ratio
);
3674 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, new_w
, new_h
, GDK_INTERP_BILINEAR
);
3675 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3676 g_object_unref(G_OBJECT(new_image
));
3677 } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
)) != im_image
->width
) {
3678 /* Enough space to show the full-size of the image. */
3679 GdkPixbuf
*new_image
;
3681 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, im_image
->width
, im_image
->height
, GDK_INTERP_BILINEAR
);
3682 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3683 g_object_unref(G_OBJECT(new_image
));
3688 image_save_yes_cb(GtkIMHtmlImageSave
*save
, const char *filename
)
3690 GError
*error
= NULL
;
3691 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3693 gtk_widget_destroy(image
->filesel
);
3694 image
->filesel
= NULL
;
3696 if (save
->data
&& save
->datasize
) {
3697 g_file_set_contents(filename
, save
->data
, save
->datasize
, &error
);
3700 GSList
*formats
= gdk_pixbuf_get_formats();
3704 GdkPixbufFormat
*format
= formats
->data
;
3705 gchar
**extensions
= gdk_pixbuf_format_get_extensions(format
);
3706 gpointer p
= extensions
;
3708 while(gdk_pixbuf_format_is_writable(format
) && extensions
&& extensions
[0]){
3709 gchar
*fmt_ext
= extensions
[0];
3710 const gchar
* file_ext
= filename
+ strlen(filename
) - strlen(fmt_ext
);
3712 if(!g_ascii_strcasecmp(fmt_ext
, file_ext
)){
3713 type
= gdk_pixbuf_format_get_name(format
);
3725 formats
= formats
->next
;
3728 g_slist_free(formats
);
3730 /* If I can't find a valid type, I will just tell the user about it and then assume
3733 char *basename
, *tmp
;
3735 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3736 _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3738 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3739 gtk_widget_show(dialog
);
3741 type
= g_strdup("png");
3742 dirname
= g_path_get_dirname(filename
);
3743 basename
= g_path_get_basename(filename
);
3744 tmp
= strrchr(basename
, '.');
3747 newfilename
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s.png", dirname
, basename
);
3752 * We're able to save the file in it's original format, so we
3753 * can use the original file name.
3755 newfilename
= g_strdup(filename
);
3758 gdk_pixbuf_save(image
->pixbuf
, newfilename
, type
, &error
, NULL
);
3760 g_free(newfilename
);
3765 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3766 _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error
->message
);
3767 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3768 gtk_widget_show(dialog
);
3769 g_error_free(error
);
3774 image_save_check_if_exists_cb(GtkWidget
*widget
, gint response
, GtkIMHtmlImageSave
*save
)
3777 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3779 if (response
!= GTK_RESPONSE_ACCEPT
) {
3780 gtk_widget_destroy(widget
);
3781 image
->filesel
= NULL
;
3785 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget
));
3788 * XXX - We should probably prompt the user to determine if they really
3789 * want to overwrite the file or not. However, I don't feel like doing
3790 * that, so we're just always going to overwrite if the file exists.
3793 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3795 image_save_yes_cb(image, filename);
3798 image_save_yes_cb(save
, filename
);
3804 gtk_imhtml_image_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3806 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3808 if (image
->filesel
!= NULL
) {
3809 gtk_window_present(GTK_WINDOW(image
->filesel
));
3813 image
->filesel
= gtk_file_chooser_dialog_new(_("Save Image"),
3815 GTK_FILE_CHOOSER_ACTION_SAVE
,
3816 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
3817 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
3819 gtk_dialog_set_default_response(GTK_DIALOG(image
->filesel
), GTK_RESPONSE_ACCEPT
);
3820 if (image
->filename
!= NULL
)
3821 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image
->filesel
), image
->filename
);
3822 g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image
->filesel
)), "response",
3823 G_CALLBACK(image_save_check_if_exists_cb
), save
);
3825 gtk_widget_show(image
->filesel
);
3829 gtk_imhtml_custom_smiley_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3831 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3833 /* Create an add dialog */
3834 PidginSmiley
*editor
= pidgin_smiley_edit(NULL
, NULL
);
3835 pidgin_smiley_editor_set_shortcut(editor
, image
->filename
);
3836 pidgin_smiley_editor_set_image(editor
, image
->pixbuf
);
3837 pidgin_smiley_editor_set_data(editor
, save
->data
, save
->datasize
);
3841 * So, um, AIM Direct IM lets you send any file, not just images. You can
3842 * just insert a sound or a file or whatever in a conversation. It's
3843 * basically like file transfer, except there is an icon to open the file
3844 * embedded in the conversation. Someone should make the Purple core handle
3847 static gboolean
gtk_imhtml_image_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlImageSave
*save
)
3849 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
3850 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3852 if (event
->type
== GDK_BUTTON_RELEASE
) {
3853 if(event_button
->button
== 3) {
3854 GtkWidget
*img
, *item
, *menu
;
3855 menu
= gtk_menu_new();
3857 /* buttons and such */
3858 img
= gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
);
3859 item
= gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3860 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3861 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_image_save
), save
);
3862 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3864 /* Add menu item for adding custom smiley to local smileys */
3865 /* we only add the menu if the image is of "custom smiley size"
3867 if (image
->width
<= 96 && image
->height
<= 96) {
3868 img
= gtk_image_new_from_stock(GTK_STOCK_ADD
, GTK_ICON_SIZE_MENU
);
3869 item
= gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3870 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3871 g_signal_connect(G_OBJECT(item
), "activate",
3872 G_CALLBACK(gtk_imhtml_custom_smiley_save
), save
);
3873 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3876 gtk_widget_show_all(menu
);
3877 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
3878 event_button
->button
, event_button
->time
);
3883 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
3884 return TRUE
; /* Clicking the right mouse button on a link shouldn't
3885 be caught by the regular GtkTextView menu */
3887 return FALSE
; /* Let clicks go through if we didn't catch anything */
3891 static gboolean
gtk_imhtml_smiley_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlSmiley
*smiley
)
3893 GdkPixbufAnimation
*anim
= NULL
;
3894 GtkIMHtmlImageSave
*save
= NULL
;
3897 if (event
->type
!= GDK_BUTTON_RELEASE
|| ((GdkEventButton
*)event
)->button
!= 3)
3900 anim
= gtk_smiley_get_image(smiley
);
3904 save
= g_new0(GtkIMHtmlImageSave
, 1);
3905 save
->image
= (GtkIMHtmlScalable
*)gtk_imhtml_animation_new(anim
, smiley
->smile
, 0);
3906 save
->data
= smiley
->data
; /* Do not need to memdup here, since the smiley is not
3907 destroyed before this GtkIMHtmlImageSave */
3908 save
->datasize
= smiley
->datasize
;
3909 ret
= gtk_imhtml_image_clicked(w
, event
, save
);
3910 g_object_set_data_full(G_OBJECT(w
), "image-data", save
->image
, (GDestroyNotify
)gtk_imhtml_animation_free
);
3911 g_object_set_data_full(G_OBJECT(w
), "image-save-data", save
, (GDestroyNotify
)g_free
);
3915 void gtk_imhtml_image_free(GtkIMHtmlScalable
*scale
)
3917 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3919 g_object_unref(image
->pixbuf
);
3920 g_free(image
->filename
);
3922 gtk_widget_destroy(image
->filesel
);
3926 void gtk_imhtml_animation_free(GtkIMHtmlScalable
*scale
)
3928 GtkIMHtmlAnimation
*animation
= (GtkIMHtmlAnimation
*)scale
;
3930 if (animation
->timer
> 0)
3931 g_source_remove(animation
->timer
);
3932 if (animation
->iter
!= NULL
)
3933 g_object_unref(animation
->iter
);
3934 g_object_unref(animation
->anim
);
3936 gtk_imhtml_image_free(scale
);
3939 void gtk_imhtml_image_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
3941 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3942 GtkWidget
*box
= gtk_event_box_new();
3944 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
3945 GtkIMHtmlImageSave
*save
;
3947 gtk_container_add(GTK_CONTAINER(box
), GTK_WIDGET(image
->image
));
3949 if(!gtk_check_version(2, 4, 0))
3950 g_object_set(G_OBJECT(box
), "visible-window", FALSE
, NULL
);
3952 gtk_widget_show(GTK_WIDGET(image
->image
));
3953 gtk_widget_show(box
);
3955 tag
= g_strdup_printf("<IMG ID=\"%d\">", image
->id
);
3956 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", tag
, g_free
);
3957 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "[Image]");
3959 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), box
, anchor
);
3961 save
= g_new0(GtkIMHtmlImageSave
, 1);
3962 save
->image
= scale
;
3963 g_signal_connect(G_OBJECT(box
), "event", G_CALLBACK(gtk_imhtml_image_clicked
), save
);
3964 g_object_set_data_full(G_OBJECT(box
), "image-save-data", save
, (GDestroyNotify
)g_free
);
3967 GtkIMHtmlScalable
*gtk_imhtml_hr_new()
3969 GtkIMHtmlHr
*hr
= g_malloc(sizeof(GtkIMHtmlHr
));
3971 GTK_IMHTML_SCALABLE(hr
)->scale
= gtk_imhtml_hr_scale
;
3972 GTK_IMHTML_SCALABLE(hr
)->add_to
= gtk_imhtml_hr_add_to
;
3973 GTK_IMHTML_SCALABLE(hr
)->free
= gtk_imhtml_hr_free
;
3975 hr
->sep
= gtk_hseparator_new();
3976 gtk_widget_set_size_request(hr
->sep
, 5000, 2);
3977 gtk_widget_show(hr
->sep
);
3979 return GTK_IMHTML_SCALABLE(hr
);
3982 void gtk_imhtml_hr_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
3984 gtk_widget_set_size_request(((GtkIMHtmlHr
*)scale
)->sep
, width
- 2, 2);
3987 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
3989 GtkIMHtmlHr
*hr
= (GtkIMHtmlHr
*)scale
;
3990 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
3991 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_htmltext", "<hr>");
3992 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "\n---\n");
3993 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), hr
->sep
, anchor
);
3996 void gtk_imhtml_hr_free(GtkIMHtmlScalable
*scale
)
4001 gboolean
gtk_imhtml_search_find(GtkIMHtml
*imhtml
, const gchar
*text
)
4003 GtkTextIter iter
, start
, end
;
4004 gboolean new_search
= TRUE
;
4005 GtkTextMark
*start_mark
;
4007 g_return_val_if_fail(imhtml
!= NULL
, FALSE
);
4008 g_return_val_if_fail(text
!= NULL
, FALSE
);
4010 start_mark
= gtk_text_buffer_get_mark(imhtml
->text_buffer
, "search");
4012 if (start_mark
&& imhtml
->search_string
&& !strcmp(text
, imhtml
->search_string
))
4016 gtk_imhtml_search_clear(imhtml
);
4017 g_free(imhtml
->search_string
);
4018 imhtml
->search_string
= g_strdup(text
);
4019 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4021 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
,
4025 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4026 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4027 &start
, &end
, NULL
))
4029 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4030 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4033 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &iter
, &end
);
4035 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4036 while (gtk_source_iter_backward_search(&start
, imhtml
->search_string
,
4037 GTK_SOURCE_SEARCH_VISIBLE_ONLY
|
4038 GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4039 &start
, &end
, NULL
));
4043 else if (!new_search
)
4045 /* We hit the end, so start at the beginning again. */
4046 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4048 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4049 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4050 &start
, &end
, NULL
))
4052 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4053 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4063 void gtk_imhtml_search_clear(GtkIMHtml
*imhtml
)
4065 GtkTextIter start
, end
;
4067 g_return_if_fail(imhtml
!= NULL
);
4069 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
4070 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
4072 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4073 g_free(imhtml
->search_string
);
4074 imhtml
->search_string
= NULL
;
4077 static GtkTextTag
*find_font_forecolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4082 g_snprintf(str
, sizeof(str
), "FORECOLOR %s", color
);
4084 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4087 if (!gdk_color_parse(color
, &gcolor
)) {
4090 strncpy(&tmp
[1], color
, 7);
4092 if (!gdk_color_parse(tmp
, &gcolor
))
4093 gdk_color_parse("black", &gcolor
);
4095 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", &gcolor
, NULL
);
4101 static GtkTextTag
*find_font_backcolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4106 g_snprintf(str
, sizeof(str
), "BACKCOLOR %s", color
);
4108 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4111 if (!gdk_color_parse(color
, &gcolor
)) {
4114 strncpy(&tmp
[1], color
, 7);
4116 if (!gdk_color_parse(tmp
, &gcolor
))
4117 gdk_color_parse("white", &gcolor
);
4119 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "background-gdk", &gcolor
, NULL
);
4125 static GtkTextTag
*find_font_background_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4130 g_snprintf(str
, sizeof(str
), "BACKGROUND %s", color
);
4132 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4134 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, NULL
);
4139 static GtkTextTag
*find_font_face_tag(GtkIMHtml
*imhtml
, gchar
*face
)
4144 g_snprintf(str
, sizeof(str
), "FONT FACE %s", face
);
4147 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4149 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "family", face
, NULL
);
4154 static GtkTextTag
*find_font_size_tag(GtkIMHtml
*imhtml
, int size
)
4159 g_snprintf(str
, sizeof(str
), "FONT SIZE %d", size
);
4162 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4164 /* For reasons I don't understand, setting "scale" here scaled
4165 * based on some default size other than my theme's default
4166 * size. Our size 4 was actually smaller than our size 3 for
4167 * me. So this works around that oddity.
4169 GtkTextAttributes
*attr
= gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml
));
4170 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "size",
4171 (gint
) (pango_font_description_get_size(attr
->font
) *
4172 (double) POINT_SIZE(size
)), NULL
);
4173 gtk_text_attributes_unref(attr
);
4179 static void remove_tag_by_prefix(GtkIMHtml
*imhtml
, const GtkTextIter
*i
, const GtkTextIter
*e
,
4180 const char *prefix
, guint len
, gboolean homo
)
4185 tags
= gtk_text_iter_get_tags(i
);
4187 for (l
= tags
; l
; l
= l
->next
) {
4188 GtkTextTag
*tag
= l
->data
;
4190 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4191 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, i
, e
);
4201 while (gtk_text_iter_forward_char(&iter
) && !gtk_text_iter_equal(&iter
, e
)) {
4202 if (gtk_text_iter_begins_tag(&iter
, NULL
)) {
4203 tags
= gtk_text_iter_get_toggled_tags(&iter
, TRUE
);
4205 for (l
= tags
; l
; l
= l
->next
) {
4206 GtkTextTag
*tag
= l
->data
;
4208 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4209 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, &iter
, e
);
4217 static void remove_font_size(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4219 remove_tag_by_prefix(imhtml
, i
, e
, "FONT SIZE ", 10, homo
);
4222 static void remove_font_face(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4224 remove_tag_by_prefix(imhtml
, i
, e
, "FONT FACE ", 10, homo
);
4227 static void remove_font_forecolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4229 remove_tag_by_prefix(imhtml
, i
, e
, "FORECOLOR ", 10, homo
);
4232 static void remove_font_backcolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4234 remove_tag_by_prefix(imhtml
, i
, e
, "BACKCOLOR ", 10, homo
);
4237 static void remove_font_background(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4239 remove_tag_by_prefix(imhtml
, i
, e
, "BACKGROUND ", 10, homo
);
4242 static void remove_font_link(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4244 remove_tag_by_prefix(imhtml
, i
, e
, "LINK ", 5, homo
);
4248 imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4250 GtkTextIter start
, end
;
4252 if (!imhtml
->editable
)
4255 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4258 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4259 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4260 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4261 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4262 remove_font_size(imhtml
, &start
, &end
, FALSE
);
4263 remove_font_face(imhtml
, &start
, &end
, FALSE
);
4264 remove_font_forecolor(imhtml
, &start
, &end
, FALSE
);
4265 remove_font_backcolor(imhtml
, &start
, &end
, FALSE
);
4266 remove_font_background(imhtml
, &start
, &end
, FALSE
);
4267 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4269 imhtml
->edit
.bold
= 0;
4270 imhtml
->edit
.italic
= 0;
4271 imhtml
->edit
.underline
= 0;
4272 imhtml
->edit
.strike
= 0;
4273 imhtml
->edit
.fontsize
= 0;
4275 g_free(imhtml
->edit
.fontface
);
4276 imhtml
->edit
.fontface
= NULL
;
4278 g_free(imhtml
->edit
.forecolor
);
4279 imhtml
->edit
.forecolor
= NULL
;
4281 g_free(imhtml
->edit
.backcolor
);
4282 imhtml
->edit
.backcolor
= NULL
;
4284 g_free(imhtml
->edit
.background
);
4285 imhtml
->edit
.background
= NULL
;
4288 /* Editable stuff */
4289 static void preinsert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4291 imhtml
->insert_offset
= gtk_text_iter_get_offset(iter
);
4294 static void insert_ca_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextChildAnchor
*arg2
, gpointer user_data
)
4299 gtk_text_iter_backward_char(&start
);
4301 gtk_imhtml_apply_tags_on_insert(user_data
, &start
, arg1
);
4304 static void insert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*end
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4312 gtk_text_iter_set_offset(&start
, imhtml
->insert_offset
);
4314 gtk_imhtml_apply_tags_on_insert(imhtml
, &start
, end
);
4317 static void delete_cb(GtkTextBuffer
*buffer
, GtkTextIter
*start
, GtkTextIter
*end
, GtkIMHtml
*imhtml
)
4321 tags
= gtk_text_iter_get_tags(start
);
4322 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4323 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4325 if (tag
&& /* Remove the formatting only if */
4326 gtk_text_iter_starts_word(start
) && /* beginning of a word */
4327 gtk_text_iter_begins_tag(start
, tag
) && /* the tag starts with the selection */
4328 (!gtk_text_iter_has_tag(end
, tag
) || /* the tag ends within the selection */
4329 gtk_text_iter_ends_tag(end
, tag
))) {
4330 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, start
, end
);
4332 strncmp(tag
->name
, "LINK ", 5) == 0 && imhtml
->edit
.link
) {
4333 gtk_imhtml_toggle_link(imhtml
, NULL
);
4340 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
4342 if (imhtml
->edit
.bold
)
4343 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4345 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4347 if (imhtml
->edit
.italic
)
4348 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4350 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4352 if (imhtml
->edit
.underline
)
4353 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4355 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4357 if (imhtml
->edit
.strike
)
4358 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4360 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4362 if (imhtml
->edit
.forecolor
) {
4363 remove_font_forecolor(imhtml
, start
, end
, TRUE
);
4364 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4365 find_font_forecolor_tag(imhtml
, imhtml
->edit
.forecolor
),
4369 if (imhtml
->edit
.backcolor
) {
4370 remove_font_backcolor(imhtml
, start
, end
, TRUE
);
4371 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4372 find_font_backcolor_tag(imhtml
, imhtml
->edit
.backcolor
),
4376 if (imhtml
->edit
.background
) {
4377 remove_font_background(imhtml
, start
, end
, TRUE
);
4378 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4379 find_font_background_tag(imhtml
, imhtml
->edit
.background
),
4382 if (imhtml
->edit
.fontface
) {
4383 remove_font_face(imhtml
, start
, end
, TRUE
);
4384 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4385 find_font_face_tag(imhtml
, imhtml
->edit
.fontface
),
4389 if (imhtml
->edit
.fontsize
) {
4390 remove_font_size(imhtml
, start
, end
, TRUE
);
4391 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4392 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
),
4396 if (imhtml
->edit
.link
) {
4397 remove_font_link(imhtml
, start
, end
, TRUE
);
4398 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4404 void gtk_imhtml_set_editable(GtkIMHtml
*imhtml
, gboolean editable
)
4406 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml
), editable
);
4408 * We need a visible caret for accessibility, so mouseless
4409 * people can highlight stuff.
4411 /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4412 if (editable
&& !imhtml
->editable
) {
4413 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
4414 G_CALLBACK(mark_set_cb
), imhtml
);
4415 g_signal_connect(G_OBJECT(imhtml
), "backspace", G_CALLBACK(smart_backspace_cb
), NULL
);
4416 } else if (!editable
&& imhtml
->editable
) {
4417 g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
),
4418 mark_set_cb
, imhtml
);
4419 g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml
),
4420 smart_backspace_cb
, NULL
);
4423 imhtml
->editable
= editable
;
4424 imhtml
->format_functions
= GTK_IMHTML_ALL
;
4427 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml
*imhtml
, gboolean wbfo
)
4429 g_return_if_fail(imhtml
!= NULL
);
4431 imhtml
->wbfo
= wbfo
;
4434 void gtk_imhtml_set_format_functions(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
4436 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
4437 imhtml
->format_functions
= buttons
;
4438 g_signal_emit(object
, signals
[BUTTONS_UPDATE
], 0, buttons
);
4439 g_object_unref(object
);
4442 GtkIMHtmlButtons
gtk_imhtml_get_format_functions(GtkIMHtml
*imhtml
)
4444 return imhtml
->format_functions
;
4447 void gtk_imhtml_get_current_format(GtkIMHtml
*imhtml
, gboolean
*bold
,
4448 gboolean
*italic
, gboolean
*underline
)
4451 (*bold
) = imhtml
->edit
.bold
;
4453 (*italic
) = imhtml
->edit
.italic
;
4454 if (underline
!= NULL
)
4455 (*underline
) = imhtml
->edit
.underline
;
4459 gtk_imhtml_get_current_fontface(GtkIMHtml
*imhtml
)
4461 return g_strdup(imhtml
->edit
.fontface
);
4465 gtk_imhtml_get_current_forecolor(GtkIMHtml
*imhtml
)
4467 return g_strdup(imhtml
->edit
.forecolor
);
4471 gtk_imhtml_get_current_backcolor(GtkIMHtml
*imhtml
)
4473 return g_strdup(imhtml
->edit
.backcolor
);
4477 gtk_imhtml_get_current_background(GtkIMHtml
*imhtml
)
4479 return g_strdup(imhtml
->edit
.background
);
4483 gtk_imhtml_get_current_fontsize(GtkIMHtml
*imhtml
)
4485 return imhtml
->edit
.fontsize
;
4488 gboolean
gtk_imhtml_get_editable(GtkIMHtml
*imhtml
)
4490 return imhtml
->editable
;
4494 gtk_imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4498 object
= g_object_ref(G_OBJECT(imhtml
));
4499 g_signal_emit(object
, signals
[CLEAR_FORMAT
], 0);
4501 gtk_widget_grab_focus(GTK_WIDGET(imhtml
));
4503 g_object_unref(object
);
4507 * I had this crazy idea about changing the text cursor color to reflex the foreground color
4508 * of the text about to be entered. This is the place you'd do it, along with the place where
4509 * we actually set a new foreground color.
4510 * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4513 * Just in case I do do this, I asked about what to set the secondary text cursor to.
4515 * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4516 * (12:45:55) ?? ???: understand?
4517 * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4518 * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4521 static void mark_set_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
,
4527 if (mark
!= gtk_text_buffer_get_insert(buffer
))
4530 if (!gtk_text_buffer_get_char_count(buffer
))
4533 imhtml
->edit
.bold
= imhtml
->edit
.italic
= imhtml
->edit
.underline
= imhtml
->edit
.strike
= FALSE
;
4534 g_free(imhtml
->edit
.forecolor
);
4535 imhtml
->edit
.forecolor
= NULL
;
4537 g_free(imhtml
->edit
.backcolor
);
4538 imhtml
->edit
.backcolor
= NULL
;
4540 g_free(imhtml
->edit
.fontface
);
4541 imhtml
->edit
.fontface
= NULL
;
4543 imhtml
->edit
.fontsize
= 0;
4544 imhtml
->edit
.link
= NULL
;
4546 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4549 if (gtk_text_iter_is_end(&iter
))
4550 tags
= gtk_text_iter_get_toggled_tags(&iter
, FALSE
);
4552 tags
= gtk_text_iter_get_tags(&iter
);
4554 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4555 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4558 if (strcmp(tag
->name
, "BOLD") == 0)
4559 imhtml
->edit
.bold
= TRUE
;
4560 else if (strcmp(tag
->name
, "ITALICS") == 0)
4561 imhtml
->edit
.italic
= TRUE
;
4562 else if (strcmp(tag
->name
, "UNDERLINE") == 0)
4563 imhtml
->edit
.underline
= TRUE
;
4564 else if (strcmp(tag
->name
, "STRIKE") == 0)
4565 imhtml
->edit
.strike
= TRUE
;
4566 else if (strncmp(tag
->name
, "FORECOLOR ", 10) == 0)
4567 imhtml
->edit
.forecolor
= g_strdup(&(tag
->name
)[10]);
4568 else if (strncmp(tag
->name
, "BACKCOLOR ", 10) == 0)
4569 imhtml
->edit
.backcolor
= g_strdup(&(tag
->name
)[10]);
4570 else if (strncmp(tag
->name
, "FONT FACE ", 10) == 0)
4571 imhtml
->edit
.fontface
= g_strdup(&(tag
->name
)[10]);
4572 else if (strncmp(tag
->name
, "FONT SIZE ", 10) == 0)
4573 imhtml
->edit
.fontsize
= strtol(&(tag
->name
)[10], NULL
, 10);
4574 else if ((strncmp(tag
->name
, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter
))
4575 imhtml
->edit
.link
= tag
;
4582 static void imhtml_emit_signal_for_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons button
)
4586 g_return_if_fail(imhtml
!= NULL
);
4588 object
= g_object_ref(G_OBJECT(imhtml
));
4589 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4590 g_object_unref(object
);
4593 static void imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4595 GtkTextIter start
, end
;
4597 imhtml
->edit
.bold
= !imhtml
->edit
.bold
;
4599 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4602 if (imhtml
->edit
.bold
)
4603 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4605 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4608 void gtk_imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4610 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_BOLD
);
4613 static void imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4615 GtkTextIter start
, end
;
4617 imhtml
->edit
.italic
= !imhtml
->edit
.italic
;
4619 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4622 if (imhtml
->edit
.italic
)
4623 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4625 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4628 void gtk_imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4630 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_ITALIC
);
4633 static void imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4635 GtkTextIter start
, end
;
4637 imhtml
->edit
.underline
= !imhtml
->edit
.underline
;
4639 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4642 if (imhtml
->edit
.underline
)
4643 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4645 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4648 void gtk_imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4650 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_UNDERLINE
);
4653 static void imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4655 GtkTextIter start
, end
;
4657 imhtml
->edit
.strike
= !imhtml
->edit
.strike
;
4659 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4662 if (imhtml
->edit
.strike
)
4663 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4665 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4668 void gtk_imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4670 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_STRIKE
);
4673 void gtk_imhtml_font_set_size(GtkIMHtml
*imhtml
, gint size
)
4676 GtkTextIter start
, end
;
4678 imhtml
->edit
.fontsize
= size
;
4680 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4683 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4684 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4685 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4687 object
= g_object_ref(G_OBJECT(imhtml
));
4688 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_SHRINK
| GTK_IMHTML_GROW
);
4689 g_object_unref(object
);
4692 static void imhtml_font_shrink(GtkIMHtml
*imhtml
)
4694 GtkTextIter start
, end
;
4696 if (imhtml
->edit
.fontsize
== 1)
4699 if (!imhtml
->edit
.fontsize
)
4700 imhtml
->edit
.fontsize
= 2;
4702 imhtml
->edit
.fontsize
--;
4704 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4706 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4707 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4708 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4711 void gtk_imhtml_font_shrink(GtkIMHtml
*imhtml
)
4713 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_SHRINK
);
4716 static void imhtml_font_grow(GtkIMHtml
*imhtml
)
4718 GtkTextIter start
, end
;
4720 if (imhtml
->edit
.fontsize
== MAX_FONT_SIZE
)
4723 if (!imhtml
->edit
.fontsize
)
4724 imhtml
->edit
.fontsize
= 4;
4726 imhtml
->edit
.fontsize
++;
4728 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4730 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4731 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4732 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4735 void gtk_imhtml_font_grow(GtkIMHtml
*imhtml
)
4737 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_GROW
);
4740 static gboolean
gtk_imhtml_toggle_str_tag(GtkIMHtml
*imhtml
, const char *value
, char **edit_field
,
4741 void (*remove_func
)(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
),
4742 GtkTextTag
*(find_func
)(GtkIMHtml
*imhtml
, gchar
*color
), GtkIMHtmlButtons button
)
4748 g_free(*edit_field
);
4751 if (value
&& strcmp(value
, "") != 0)
4753 *edit_field
= g_strdup(value
);
4755 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
)) {
4756 remove_func(imhtml
, &start
, &end
, imhtml
->wbfo
);
4757 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4758 find_func(imhtml
, *edit_field
), &start
, &end
);
4763 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4764 remove_func(imhtml
, &start
, &end
, TRUE
); /* 'TRUE' or 'imhtml->wbfo'? */
4767 object
= g_object_ref(G_OBJECT(imhtml
));
4768 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4769 g_object_unref(object
);
4771 return *edit_field
!= NULL
;
4774 gboolean
gtk_imhtml_toggle_forecolor(GtkIMHtml
*imhtml
, const char *color
)
4776 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.forecolor
,
4777 remove_font_forecolor
, find_font_forecolor_tag
,
4778 GTK_IMHTML_FORECOLOR
);
4781 gboolean
gtk_imhtml_toggle_backcolor(GtkIMHtml
*imhtml
, const char *color
)
4783 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.backcolor
,
4784 remove_font_backcolor
, find_font_backcolor_tag
,
4785 GTK_IMHTML_BACKCOLOR
);
4788 gboolean
gtk_imhtml_toggle_background(GtkIMHtml
*imhtml
, const char *color
)
4790 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.background
,
4791 remove_font_background
, find_font_background_tag
,
4792 GTK_IMHTML_BACKGROUND
);
4795 gboolean
gtk_imhtml_toggle_fontface(GtkIMHtml
*imhtml
, const char *face
)
4797 return gtk_imhtml_toggle_str_tag(imhtml
, face
, &imhtml
->edit
.fontface
,
4798 remove_font_face
, find_font_face_tag
,
4802 void gtk_imhtml_toggle_link(GtkIMHtml
*imhtml
, const char *url
)
4805 GtkTextIter start
, end
;
4806 GtkTextTag
*linktag
;
4807 static guint linkno
= 0;
4809 GdkColor
*color
= NULL
;
4811 imhtml
->edit
.link
= NULL
;
4814 g_snprintf(str
, sizeof(str
), "LINK %d", linkno
++);
4817 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-color", &color
, NULL
);
4819 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", color
, "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4820 gdk_color_free(color
);
4822 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4824 g_object_set_data_full(G_OBJECT(linktag
), "link_url", g_strdup(url
), g_free
);
4825 g_signal_connect(G_OBJECT(linktag
), "event", G_CALLBACK(tag_event
), NULL
);
4827 if (imhtml
->editable
&& gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
4828 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4829 gtk_text_buffer_apply_tag(imhtml
->text_buffer
, linktag
, &start
, &end
);
4833 object
= g_object_ref(G_OBJECT(imhtml
));
4834 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_LINK
);
4835 g_object_unref(object
);
4838 void gtk_imhtml_insert_link(GtkIMHtml
*imhtml
, GtkTextMark
*mark
, const char *url
, const char *text
)
4842 /* Delete any currently selected text */
4843 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4845 gtk_imhtml_toggle_link(imhtml
, url
);
4846 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4847 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
4848 gtk_imhtml_toggle_link(imhtml
, NULL
);
4851 void gtk_imhtml_insert_smiley(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
)
4856 /* Delete any currently selected text */
4857 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4859 mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
4861 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4862 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
4863 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, smiley
, &iter
);
4864 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
4868 image_expose(GtkWidget
*widget
, GdkEventExpose
*event
, gpointer user_data
)
4870 GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget
))->expose_event(widget
, event
);
4875 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
4876 static void animated_smiley_destroy_cb(GtkObject
*widget
, GtkIMHtml
*imhtml
)
4878 GList
*l
= imhtml
->animations
->head
;
4880 GList
*next
= l
->next
;
4881 if (l
->data
== widget
) {
4882 if (l
== imhtml
->animations
->tail
)
4883 imhtml
->animations
->tail
= imhtml
->animations
->tail
->prev
;
4884 imhtml
->animations
->head
= g_list_delete_link(imhtml
->animations
->head
, l
);
4885 imhtml
->num_animations
--;
4891 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
, GtkTextIter
*iter
)
4893 GdkPixbuf
*pixbuf
= NULL
;
4894 GdkPixbufAnimation
*annipixbuf
= NULL
;
4895 GtkWidget
*icon
= NULL
;
4896 GtkTextChildAnchor
*anchor
= NULL
;
4898 GtkIMHtmlSmiley
*imhtml_smiley
;
4899 GtkWidget
*ebox
= NULL
;
4900 int numsmileys_thismsg
, numsmileys_total
;
4903 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4904 * add any more. We do this for performance reasons, because smileys
4905 * are apparently pretty inefficient. Hopefully we can remove this
4906 * restriction when we're using a better HTML widget.
4908 numsmileys_thismsg
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg"));
4909 if (numsmileys_thismsg
>= 30) {
4910 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, smiley
, -1);
4913 numsmileys_total
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total"));
4914 if (numsmileys_total
>= 300) {
4915 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, smiley
, -1);
4919 unescaped
= purple_unescape_html(smiley
);
4920 imhtml_smiley
= gtk_imhtml_smiley_get(imhtml
, sml
, unescaped
);
4922 if (imhtml
->format_functions
& GTK_IMHTML_SMILEY
) {
4923 annipixbuf
= imhtml_smiley
? gtk_smiley_get_image(imhtml_smiley
) : NULL
;
4925 if (gdk_pixbuf_animation_is_static_image(annipixbuf
)) {
4926 pixbuf
= gdk_pixbuf_animation_get_static_image(annipixbuf
);
4928 icon
= gtk_image_new_from_pixbuf(pixbuf
);
4930 icon
= gtk_image_new_from_animation(annipixbuf
);
4931 if (imhtml
->num_animations
== 20) {
4932 GtkImage
*image
= GTK_IMAGE(g_queue_pop_head(imhtml
->animations
));
4933 GdkPixbufAnimation
*anim
= gtk_image_get_animation(image
);
4934 g_signal_handlers_disconnect_matched(G_OBJECT(image
), G_SIGNAL_MATCH_FUNC
,
4935 0, 0, NULL
, G_CALLBACK(animated_smiley_destroy_cb
), NULL
);
4937 GdkPixbuf
*pb
= gdk_pixbuf_animation_get_static_image(anim
);
4939 GdkPixbuf
*copy
= gdk_pixbuf_copy(pb
);
4940 gtk_image_set_from_pixbuf(image
, copy
);
4941 g_object_unref(G_OBJECT(copy
));
4945 imhtml
->num_animations
++;
4947 g_signal_connect(G_OBJECT(icon
), "destroy", G_CALLBACK(animated_smiley_destroy_cb
), imhtml
);
4948 g_queue_push_tail(imhtml
->animations
, icon
);
4953 if (imhtml_smiley
&& imhtml_smiley
->flags
& GTK_IMHTML_SMILEY_CUSTOM
) {
4954 ebox
= gtk_event_box_new();
4955 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
4956 gtk_widget_show(ebox
);
4960 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
4961 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
4962 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
4963 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
4965 /* This catches the expose events generated by animated
4966 * images, and ensures that they are handled by the image
4967 * itself, without propagating to the textview and causing
4968 * a complete refresh */
4969 g_signal_connect(G_OBJECT(icon
), "expose-event", G_CALLBACK(image_expose
), NULL
);
4971 gtk_widget_show(icon
);
4973 gtk_container_add(GTK_CONTAINER(ebox
), icon
);
4974 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
? ebox
: icon
, anchor
);
4976 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg
+ 1));
4977 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total
+ 1));
4978 } else if (imhtml_smiley
!= NULL
&& (imhtml
->format_functions
& GTK_IMHTML_SMILEY
)) {
4979 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
4980 imhtml_smiley
->anchors
= g_slist_append(imhtml_smiley
->anchors
, g_object_ref(anchor
));
4982 GtkWidget
*img
= gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE
, GTK_ICON_SIZE_MENU
);
4983 gtk_container_add(GTK_CONTAINER(ebox
), img
);
4984 gtk_widget_show(img
);
4985 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
4986 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
4987 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
4988 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
, anchor
);
4991 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg
+ 1));
4992 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total
+ 1));
4994 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, smiley
, -1);
4998 g_signal_connect(G_OBJECT(ebox
), "event", G_CALLBACK(gtk_imhtml_smiley_clicked
), imhtml_smiley
);
5004 void gtk_imhtml_insert_image_at_iter(GtkIMHtml
*imhtml
, int id
, GtkTextIter
*iter
)
5006 GdkPixbufAnimation
*anim
= NULL
;
5007 const char *filename
= NULL
;
5010 GtkIMHtmlScalable
*scalable
= NULL
;
5011 struct scalable_data
*sd
;
5014 if (!imhtml
->funcs
|| !imhtml
->funcs
->image_get
||
5015 !imhtml
->funcs
->image_get_size
|| !imhtml
->funcs
->image_get_data
||
5016 !imhtml
->funcs
->image_get_filename
|| !imhtml
->funcs
->image_ref
||
5017 !imhtml
->funcs
->image_unref
)
5020 image
= imhtml
->funcs
->image_get(id
);
5026 data
= imhtml
->funcs
->image_get_data(image
);
5027 len
= imhtml
->funcs
->image_get_size(image
);
5030 GdkPixbufLoader
*loader
= gdk_pixbuf_loader_new();
5031 gdk_pixbuf_loader_write(loader
, data
, len
, NULL
);
5032 gdk_pixbuf_loader_close(loader
, NULL
);
5033 anim
= gdk_pixbuf_loader_get_animation(loader
);
5035 g_object_ref(G_OBJECT(anim
));
5036 g_object_unref(G_OBJECT(loader
));
5042 struct im_image_data
*t
= g_new(struct im_image_data
, 1);
5043 filename
= imhtml
->funcs
->image_get_filename(image
);
5044 imhtml
->funcs
->image_ref(id
);
5046 t
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5047 imhtml
->im_images
= g_slist_prepend(imhtml
->im_images
, t
);
5048 scalable
= gtk_imhtml_animation_new(anim
, filename
, id
);
5049 g_object_unref(G_OBJECT(anim
));
5052 pixbuf
= gtk_widget_render_icon(GTK_WIDGET(imhtml
), GTK_STOCK_MISSING_IMAGE
,
5053 GTK_ICON_SIZE_BUTTON
, "gtkimhtml-missing-image");
5054 scalable
= gtk_imhtml_image_new(pixbuf
, filename
, id
);
5055 g_object_unref(G_OBJECT(pixbuf
));
5058 sd
= g_new(struct scalable_data
, 1);
5059 sd
->scalable
= scalable
;
5060 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5061 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
5062 scalable
->add_to(scalable
, imhtml
, iter
);
5063 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
5064 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
5065 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
5066 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
5069 static const gchar
*tag_to_html_start(GtkTextTag
*tag
)
5072 static gchar buf
[1024];
5075 g_return_val_if_fail(name
!= NULL
, "");
5077 if (strcmp(name
, "BOLD") == 0) {
5079 } else if (strcmp(name
, "ITALICS") == 0) {
5081 } else if (strcmp(name
, "UNDERLINE") == 0) {
5083 } else if (strcmp(name
, "STRIKE") == 0) {
5085 } else if (strncmp(name
, "LINK ", 5) == 0) {
5086 char *tmp
= g_object_get_data(G_OBJECT(tag
), "link_url");
5088 g_snprintf(buf
, sizeof(buf
), "<a href=\"%s\">", tmp
);
5089 buf
[sizeof(buf
)-1] = '\0';
5094 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5095 g_snprintf(buf
, sizeof(buf
), "<font color=\"%s\">", &name
[10]);
5097 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5098 g_snprintf(buf
, sizeof(buf
), "<font back=\"%s\">", &name
[10]);
5100 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5101 g_snprintf(buf
, sizeof(buf
), "<body bgcolor=\"%s\">", &name
[11]);
5103 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5104 g_snprintf(buf
, sizeof(buf
), "<font face=\"%s\">", &name
[10]);
5106 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5107 g_snprintf(buf
, sizeof(buf
), "<font size=\"%s\">", &name
[10]);
5113 GdkColor
*color
= NULL
;
5114 GObject
*obj
= G_OBJECT(tag
);
5115 gboolean empty
= TRUE
;
5117 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "<span style='");
5120 g_object_get(obj
, "weight-set", &isset
, "weight", &ivalue
, NULL
);
5122 const char *weight
= "";
5123 if (ivalue
>= PANGO_WEIGHT_ULTRABOLD
)
5125 else if (ivalue
>= PANGO_WEIGHT_BOLD
)
5127 else if (ivalue
>= PANGO_WEIGHT_NORMAL
)
5132 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "font-weight: %s;", weight
);
5136 /* Foreground color */
5137 g_object_get(obj
, "foreground-set", &isset
, "foreground-gdk", &color
, NULL
);
5138 if (isset
&& color
) {
5139 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5140 "color: #%02x%02x%02x;",
5141 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5144 gdk_color_free(color
);
5146 /* Background color */
5147 g_object_get(obj
, "background-set", &isset
, "background-gdk", &color
, NULL
);
5148 if (isset
&& color
) {
5149 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5150 "background: #%02x%02x%02x;",
5151 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5154 gdk_color_free(color
);
5157 g_object_get(obj
, "underline-set", &isset
, "underline", &ivalue
, NULL
);
5160 case PANGO_UNDERLINE_NONE
:
5161 case PANGO_UNDERLINE_ERROR
:
5164 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "text-decoration: underline;");
5169 g_snprintf(str
, sizeof(buf
) - (str
- buf
), "'>");
5171 return (empty
? "" : buf
);
5175 static const gchar
*tag_to_html_end(GtkTextTag
*tag
)
5180 g_return_val_if_fail(name
!= NULL
, "");
5182 if (strcmp(name
, "BOLD") == 0) {
5184 } else if (strcmp(name
, "ITALICS") == 0) {
5186 } else if (strcmp(name
, "UNDERLINE") == 0) {
5188 } else if (strcmp(name
, "STRIKE") == 0) {
5190 } else if (strncmp(name
, "LINK ", 5) == 0) {
5192 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5194 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5196 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5198 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5200 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5203 const char *props
[] = {"weight-set", "foreground-set", "background-set",
5204 "size-set", "underline-set", NULL
};
5206 for (i
= 0; props
[i
]; i
++) {
5207 gboolean set
= FALSE
;
5208 g_object_get(G_OBJECT(tag
), props
[i
], &set
, NULL
);
5221 } PidginTextTagData
;
5223 static PidginTextTagData
*text_tag_data_new(GtkTextTag
*tag
)
5225 const char *start
, *end
;
5226 PidginTextTagData
*ret
= NULL
;
5228 start
= tag_to_html_start(tag
);
5229 if (!start
|| !*start
)
5231 end
= tag_to_html_end(tag
);
5235 ret
= g_new0(PidginTextTagData
, 1);
5236 ret
->start
= g_strdup(start
);
5237 ret
->end
= g_strdup(end
);
5242 static void text_tag_data_destroy(PidginTextTagData
*data
)
5244 g_free(data
->start
);
5249 static gboolean
tag_ends_here(GtkTextTag
*tag
, GtkTextIter
*iter
, GtkTextIter
*niter
)
5251 return ((gtk_text_iter_has_tag(iter
, GTK_TEXT_TAG(tag
)) &&
5252 !gtk_text_iter_has_tag(niter
, GTK_TEXT_TAG(tag
))) ||
5253 gtk_text_iter_is_end(niter
));
5256 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5257 * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else
5258 * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert
5259 * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5260 * Finally, replace <, >, &, and " with their HTML equivalent.
5262 char *gtk_imhtml_get_markup_range(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
5265 GtkTextIter iter
, next_iter
, non_neutral_iter
;
5266 gboolean is_rtl_message
= FALSE
;
5267 GString
*str
= g_string_new("");
5271 PidginTextTagData
*tagdata
;
5275 gtk_text_iter_order(start
, end
);
5276 non_neutral_iter
= next_iter
= iter
= *start
;
5277 gtk_text_iter_forward_char(&next_iter
);
5279 /* Bi-directional text support */
5280 /* Get to the first non-neutral character */
5282 while ((PANGO_DIRECTION_NEUTRAL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
)))
5283 && gtk_text_iter_forward_char(&non_neutral_iter
));
5284 if (PANGO_DIRECTION_RTL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
))) {
5285 is_rtl_message
= TRUE
;
5286 g_string_append(str
, "<SPAN style=\"direction:rtl;text-align:right;\">");
5290 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
5291 tags
= gtk_text_iter_get_tags(start
);
5293 for (sl
= tags
; sl
; sl
= sl
->next
) {
5295 if (!gtk_text_iter_toggles_tag(start
, GTK_TEXT_TAG(tag
))) {
5296 PidginTextTagData
*data
= text_tag_data_new(tag
);
5298 g_string_append(str
, data
->start
);
5299 g_queue_push_tail(q
, data
);
5305 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, end
)) {
5307 tags
= gtk_text_iter_get_tags(&iter
);
5309 for (sl
= tags
; sl
; sl
= sl
->next
) {
5311 if (gtk_text_iter_begins_tag(&iter
, GTK_TEXT_TAG(tag
))) {
5312 PidginTextTagData
*data
= text_tag_data_new(tag
);
5314 g_string_append(str
, data
->start
);
5315 g_queue_push_tail(q
, data
);
5321 GtkTextChildAnchor
* anchor
= gtk_text_iter_get_child_anchor(&iter
);
5323 char *text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_htmltext");
5325 str
= g_string_append(str
, text
);
5327 } else if (c
== '<') {
5328 str
= g_string_append(str
, "<");
5329 } else if (c
== '>') {
5330 str
= g_string_append(str
, ">");
5331 } else if (c
== '&') {
5332 str
= g_string_append(str
, "&");
5333 } else if (c
== '"') {
5334 str
= g_string_append(str
, """);
5335 } else if (c
== '\n') {
5336 str
= g_string_append(str
, "<br>");
5338 str
= g_string_append_unichar(str
, c
);
5341 tags
= g_slist_reverse(tags
);
5342 for (sl
= tags
; sl
; sl
= sl
->next
) {
5344 /** don't worry about non-printing tags ending */
5345 if (tag_ends_here(tag
, &iter
, &next_iter
) &&
5346 strlen(tag_to_html_end(tag
)) > 0 &&
5347 strlen(tag_to_html_start(tag
)) > 0) {
5349 PidginTextTagData
*tmp
;
5350 GQueue
*r
= g_queue_new();
5352 while ((tmp
= g_queue_pop_tail(q
)) && tmp
->tag
!= tag
) {
5353 g_string_append(str
, tmp
->end
);
5354 if (!tag_ends_here(tmp
->tag
, &iter
, &next_iter
))
5355 g_queue_push_tail(r
, tmp
);
5357 text_tag_data_destroy(tmp
);
5361 g_string_append(str
, tmp
->end
);
5362 text_tag_data_destroy(tmp
);
5364 #if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */
5366 purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5369 while ((tmp
= g_queue_pop_head(r
))) {
5370 g_string_append(str
, tmp
->start
);
5371 g_queue_push_tail(q
, tmp
);
5378 gtk_text_iter_forward_char(&iter
);
5379 gtk_text_iter_forward_char(&next_iter
);
5382 while ((tagdata
= g_queue_pop_tail(q
))) {
5383 g_string_append(str
, tagdata
->end
);
5384 text_tag_data_destroy(tagdata
);
5387 /* Bi-directional text support - close tags */
5389 g_string_append(str
, "</SPAN>");
5392 return g_string_free(str
, FALSE
);
5395 void gtk_imhtml_close_tags(GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
5397 if (imhtml
->edit
.bold
)
5398 gtk_imhtml_toggle_bold(imhtml
);
5400 if (imhtml
->edit
.italic
)
5401 gtk_imhtml_toggle_italic(imhtml
);
5403 if (imhtml
->edit
.underline
)
5404 gtk_imhtml_toggle_underline(imhtml
);
5406 if (imhtml
->edit
.strike
)
5407 gtk_imhtml_toggle_strike(imhtml
);
5409 if (imhtml
->edit
.forecolor
)
5410 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
5412 if (imhtml
->edit
.backcolor
)
5413 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
5415 if (imhtml
->edit
.fontface
)
5416 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
5418 imhtml
->edit
.fontsize
= 0;
5420 if (imhtml
->edit
.link
)
5421 gtk_imhtml_toggle_link(imhtml
, NULL
);
5424 char *gtk_imhtml_get_markup(GtkIMHtml
*imhtml
)
5426 GtkTextIter start
, end
;
5428 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5429 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5430 return gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5433 char **gtk_imhtml_get_markup_lines(GtkIMHtml
*imhtml
)
5436 GtkTextIter start
, end
;
5439 lines
= gtk_text_buffer_get_line_count(imhtml
->text_buffer
);
5440 ret
= g_new0(char *, lines
+ 1);
5441 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5443 gtk_text_iter_forward_to_line_end(&end
);
5445 for (i
= 0, j
= 0; i
< lines
; i
++) {
5446 if (gtk_text_iter_get_char(&start
) != '\n') {
5447 ret
[j
] = gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5452 gtk_text_iter_forward_line(&start
);
5454 gtk_text_iter_forward_to_line_end(&end
);
5460 char *gtk_imhtml_get_text(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*stop
)
5462 GString
*str
= g_string_new("");
5463 GtkTextIter iter
, end
;
5467 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &iter
);
5472 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5476 gtk_text_iter_order(&iter
, &end
);
5478 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, &end
)) {
5480 GtkTextChildAnchor
* anchor
;
5483 anchor
= gtk_text_iter_get_child_anchor(&iter
);
5485 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
5487 str
= g_string_append(str
, text
);
5489 g_string_append_unichar(str
, c
);
5491 gtk_text_iter_forward_char(&iter
);
5494 return g_string_free(str
, FALSE
);
5497 void gtk_imhtml_set_funcs(GtkIMHtml
*imhtml
, GtkIMHtmlFuncs
*f
)
5499 g_return_if_fail(imhtml
!= NULL
);
5503 void gtk_imhtml_setup_entry(GtkIMHtml
*imhtml
, PurpleConnectionFlags flags
)
5505 GtkIMHtmlButtons buttons
;
5507 if (flags
& PURPLE_CONNECTION_HTML
) {
5509 GdkColor fg_color
, bg_color
;
5511 buttons
= GTK_IMHTML_ALL
;
5513 if (flags
& PURPLE_CONNECTION_NO_BGCOLOR
)
5514 buttons
&= ~GTK_IMHTML_BACKCOLOR
;
5515 if (flags
& PURPLE_CONNECTION_NO_FONTSIZE
)
5517 buttons
&= ~GTK_IMHTML_GROW
;
5518 buttons
&= ~GTK_IMHTML_SHRINK
;
5520 if (flags
& PURPLE_CONNECTION_NO_URLDESC
)
5521 buttons
&= ~GTK_IMHTML_LINKDESC
;
5523 gtk_imhtml_set_format_functions(imhtml
, GTK_IMHTML_ALL
);
5524 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold") != imhtml
->edit
.bold
)
5525 gtk_imhtml_toggle_bold(imhtml
);
5527 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic") != imhtml
->edit
.italic
)
5528 gtk_imhtml_toggle_italic(imhtml
);
5530 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline") != imhtml
->edit
.underline
)
5531 gtk_imhtml_toggle_underline(imhtml
);
5533 gtk_imhtml_toggle_fontface(imhtml
,
5534 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/font_face"));
5536 if (!(flags
& PURPLE_CONNECTION_NO_FONTSIZE
))
5538 int size
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/font_size");
5540 /* 3 is the default. */
5542 gtk_imhtml_font_set_size(imhtml
, size
);
5545 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"), "") != 0)
5547 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"),
5549 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5551 fg_color
.green
/ 256,
5552 fg_color
.blue
/ 256);
5556 gtk_imhtml_toggle_forecolor(imhtml
, color
);
5558 if(!(flags
& PURPLE_CONNECTION_NO_BGCOLOR
) &&
5559 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"), "") != 0)
5561 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"),
5563 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5565 bg_color
.green
/ 256,
5566 bg_color
.blue
/ 256);
5570 gtk_imhtml_toggle_background(imhtml
, color
);
5572 if (flags
& PURPLE_CONNECTION_FORMATTING_WBFO
)
5573 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, TRUE
);
5575 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, FALSE
);
5577 buttons
= GTK_IMHTML_SMILEY
| GTK_IMHTML_IMAGE
;
5578 imhtml_clear_formatting(imhtml
);
5581 if (flags
& PURPLE_CONNECTION_NO_IMAGES
)
5582 buttons
&= ~GTK_IMHTML_IMAGE
;
5584 if (flags
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
5585 buttons
|= GTK_IMHTML_CUSTOM_SMILEY
;
5587 buttons
&= ~GTK_IMHTML_CUSTOM_SMILEY
;
5589 gtk_imhtml_set_format_functions(imhtml
, buttons
);
5593 * GtkIMHtmlSmiley functions
5595 static void gtk_custom_smiley_allocated(GdkPixbufLoader
*loader
, gpointer user_data
)
5597 GtkIMHtmlSmiley
*smiley
;
5599 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5600 smiley
->icon
= gdk_pixbuf_loader_get_animation(loader
);
5603 g_object_ref(G_OBJECT(smiley
->icon
));
5604 #ifdef DEBUG_CUSTOM_SMILEY
5605 purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley
->icon
, smiley
->smile
);
5609 static void gtk_custom_smiley_closed(GdkPixbufLoader
*loader
, gpointer user_data
)
5611 GtkIMHtmlSmiley
*smiley
;
5612 GtkWidget
*icon
= NULL
;
5613 GtkTextChildAnchor
*anchor
= NULL
;
5614 GSList
*current
= NULL
;
5616 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5617 if (!smiley
->imhtml
) {
5618 #ifdef DEBUG_CUSTOM_SMILEY
5619 purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley
);
5621 g_object_unref(G_OBJECT(loader
));
5622 smiley
->loader
= NULL
;
5626 for (current
= smiley
->anchors
; current
; current
= g_slist_next(current
)) {
5627 anchor
= GTK_TEXT_CHILD_ANCHOR(current
->data
);
5628 if (gtk_text_child_anchor_get_deleted(anchor
))
5631 icon
= gtk_image_new_from_animation(smiley
->icon
);
5633 #ifdef DEBUG_CUSTOM_SMILEY
5634 purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5635 icon
, smiley
->icon
, smiley
->smile
);
5639 gtk_widget_show(icon
);
5641 wids
= gtk_text_child_anchor_get_widgets(anchor
);
5643 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", purple_unescape_html(smiley
->smile
), g_free
);
5644 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
->smile
), g_free
);
5646 if (smiley
->imhtml
) {
5648 GList
*children
= gtk_container_get_children(GTK_CONTAINER(wids
->data
));
5649 g_list_foreach(children
, (GFunc
)gtk_widget_destroy
, NULL
);
5650 g_list_free(children
);
5651 gtk_container_add(GTK_CONTAINER(wids
->data
), icon
);
5653 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley
->imhtml
), icon
, anchor
);
5657 g_object_unref(anchor
);
5660 g_slist_free(smiley
->anchors
);
5661 smiley
->anchors
= NULL
;
5663 g_object_unref(G_OBJECT(loader
));
5664 smiley
->loader
= NULL
;
5668 gtk_custom_smiley_size_prepared(GdkPixbufLoader
*loader
, gint width
, gint height
, gpointer data
)
5670 #define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */
5671 if (width
<= CUSTOM_SMILEY_SIZE
&& height
<= CUSTOM_SMILEY_SIZE
)
5674 if (width
>= height
) {
5675 height
= height
* CUSTOM_SMILEY_SIZE
/ width
;
5676 width
= CUSTOM_SMILEY_SIZE
;
5678 width
= width
* CUSTOM_SMILEY_SIZE
/ height
;
5679 height
= CUSTOM_SMILEY_SIZE
;
5682 gdk_pixbuf_loader_set_size(loader
, width
, height
);
5686 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley
*smiley
)
5689 g_object_unref(smiley
->icon
);
5691 g_object_unref(smiley
->loader
); /* XXX: does this crash? */
5693 smiley
->icon
= NULL
;
5694 smiley
->loader
= NULL
;
5697 /* We do not use the pixbuf loader for a smiley that can be loaded
5698 * from a file. (e.g., local custom smileys)
5703 smiley
->loader
= gdk_pixbuf_loader_new();
5705 g_signal_connect(smiley
->loader
, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated
), smiley
);
5706 g_signal_connect(smiley
->loader
, "closed", G_CALLBACK(gtk_custom_smiley_closed
), smiley
);
5707 g_signal_connect(smiley
->loader
, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared
), smiley
);
5710 GtkIMHtmlSmiley
*gtk_imhtml_smiley_create(const char *file
, const char *shortcut
, gboolean hide
,
5711 GtkIMHtmlSmileyFlags flags
)
5713 GtkIMHtmlSmiley
*smiley
= g_new0(GtkIMHtmlSmiley
, 1);
5714 smiley
->file
= g_strdup(file
);
5715 smiley
->smile
= g_strdup(shortcut
);
5716 smiley
->hidden
= hide
;
5717 smiley
->flags
= flags
;
5718 smiley
->imhtml
= NULL
;
5719 gtk_imhtml_smiley_reload(smiley
);
5723 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley
*smiley
)
5725 gtk_imhtml_disassociate_smiley(smiley
);
5726 g_free(smiley
->smile
);
5727 g_free(smiley
->file
);
5729 g_object_unref(smiley
->icon
);
5731 g_object_unref(smiley
->loader
);
5732 g_free(smiley
->data
);
5736 gboolean
gtk_imhtml_class_register_protocol(const char *name
,
5737 gboolean (*activate
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
),
5738 gboolean (*context_menu
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
))
5740 GtkIMHtmlClass
*klass
;
5741 GtkIMHtmlProtocol
*proto
;
5743 g_return_val_if_fail(name
, FALSE
);
5745 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
5746 g_return_val_if_fail(klass
, FALSE
);
5748 if ((proto
= imhtml_find_protocol(name
, TRUE
))) {
5752 klass
->protocols
= g_list_remove(klass
->protocols
, proto
);
5753 g_free(proto
->name
);
5756 } else if (!activate
) {
5760 proto
= g_new0(GtkIMHtmlProtocol
, 1);
5761 proto
->name
= g_strdup(name
);
5762 proto
->length
= strlen(name
);
5763 proto
->activate
= activate
;
5764 proto
->context_menu
= context_menu
;
5765 klass
->protocols
= g_list_prepend(klass
->protocols
, proto
);
5771 gtk_imhtml_activate_tag(GtkIMHtml
*imhtml
, GtkTextTag
*tag
)
5773 /* A link was clicked--we emit the "url_clicked" signal
5774 * with the URL as the argument */
5775 g_object_ref(G_OBJECT(tag
));
5776 g_signal_emit(imhtml
, signals
[URL_CLICKED
], 0, g_object_get_data(G_OBJECT(tag
), "link_url"));
5777 g_object_unref(G_OBJECT(tag
));
5778 g_object_set_data(G_OBJECT(tag
), "visited", GINT_TO_POINTER(TRUE
));
5779 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), tag
);
5782 gboolean
gtk_imhtml_link_activate(GtkIMHtmlLink
*link
)
5784 g_return_val_if_fail(link
, FALSE
);
5787 gtk_imhtml_activate_tag(link
->imhtml
, link
->tag
);
5788 } else if (link
->url
) {
5789 g_signal_emit(link
->imhtml
, signals
[URL_CLICKED
], 0, link
->url
);
5795 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink
*link
)
5800 const GtkTextTag
* gtk_imhtml_link_get_text_tag(GtkIMHtmlLink
*link
)
5805 static gboolean
return_add_newline_cb(GtkWidget
*widget
, gpointer data
)
5807 GtkTextBuffer
*buffer
;
5811 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget
));
5813 /* Delete any currently selected text */
5814 gtk_text_buffer_delete_selection(buffer
, TRUE
, TRUE
);
5816 /* Insert a newline at the current cursor position */
5817 mark
= gtk_text_buffer_get_insert(buffer
);
5818 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
5819 gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget
), "\n", 0, &iter
);
5822 * If we just newlined ourselves past the end of the visible area
5823 * then scroll down so the cursor is in view.
5825 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget
),
5826 gtk_text_buffer_get_insert(buffer
),
5827 0, FALSE
, 0.0, 0.0);
5833 * It's kind of a pain that we need this function and the above just
5834 * to reinstate the default GtkTextView behavior. It might be better
5835 * if GtkIMHtml didn't intercept the enter key and just required the
5836 * application to deal with it--it's really not much more work than it
5837 * is to connect to the current "message_send" signal.
5839 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml
*imhtml
)
5841 g_signal_connect(G_OBJECT(imhtml
), "message_send",
5842 G_CALLBACK(return_add_newline_cb
), NULL
);
5845 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml
*imhtml
, gboolean populate
)
5848 signal_id
= g_signal_handler_find(imhtml
->text_buffer
,
5849 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_UNBLOCKED
, 0, 0, NULL
,
5850 mark_set_so_update_selection_cb
, NULL
);
5853 /* We didn't find an unblocked signal handler, which means there
5854 is a blocked handler. Now unblock it.
5855 This is necessary to avoid a mutex-lock when the debug message
5856 saying 'no handler is blocked' is printed in the debug window.
5859 g_signal_handlers_unblock_matched(imhtml
->text_buffer
,
5860 G_SIGNAL_MATCH_FUNC
, 0, 0, NULL
,
5861 mark_set_so_update_selection_cb
, NULL
);
5864 /* Block only if we found an unblocked handler */
5866 g_signal_handler_block(imhtml
->text_buffer
, signal_id
);