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
);
102 typedef struct _GtkIMHtmlFontDetail
{
112 } GtkIMHtmlFontDetail
;
115 gtk_text_view_drag_motion (GtkWidget
*widget
,
116 GdkDragContext
*context
,
121 static void preinsert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
);
122 static void insert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
);
123 static void delete_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, GtkTextIter
*end
, GtkIMHtml
*imhtml
);
124 static void insert_ca_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextChildAnchor
*arg2
, gpointer user_data
);
125 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
);
126 void gtk_imhtml_close_tags(GtkIMHtml
*imhtml
, GtkTextIter
*iter
);
127 static void gtk_imhtml_link_drop_cb(GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer user_data
);
128 static void gtk_imhtml_link_drag_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
, GtkSelectionData
*sd
, guint info
, guint t
, GtkIMHtml
*imhtml
);
129 static void mark_set_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
, GtkIMHtml
*imhtml
);
130 static void hijack_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
);
131 static void paste_received_cb (GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, gpointer data
);
132 static void paste_plaintext_received_cb (GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
);
133 static void imhtml_paste_insert(GtkIMHtml
*imhtml
, const char *text
, gboolean plaintext
);
134 static void imhtml_toggle_bold(GtkIMHtml
*imhtml
);
135 static void imhtml_toggle_italic(GtkIMHtml
*imhtml
);
136 static void imhtml_toggle_strike(GtkIMHtml
*imhtml
);
137 static void imhtml_toggle_underline(GtkIMHtml
*imhtml
);
138 static void imhtml_font_grow(GtkIMHtml
*imhtml
);
139 static void imhtml_font_shrink(GtkIMHtml
*imhtml
);
140 static void imhtml_clear_formatting(GtkIMHtml
*imhtml
);
141 static int gtk_imhtml_is_protocol(const char *text
);
142 static void gtk_imhtml_activate_tag(GtkIMHtml
*imhtml
, GtkTextTag
*tag
);
143 static void gtk_imhtml_link_destroy(GtkIMHtmlLink
*link
);
145 /* POINT_SIZE converts from AIM font sizes to a point size scale factor. */
146 #define MAX_FONT_SIZE 7
147 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
148 static const gdouble _point_sizes
[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736};
153 TARGET_COMPOUND_TEXT
,
170 static guint signals
[LAST_SIGNAL
] = { 0 };
172 static char *html_clipboard
= NULL
;
173 static char *text_clipboard
= NULL
;
174 static GtkClipboard
*clipboard_selection
= NULL
;
176 static const GtkTargetEntry selection_targets
[] = {
178 { "text/html", 0, TARGET_HTML
},
180 { "HTML Format", 0, TARGET_HTML
},
182 { "UTF8_STRING", 0, TARGET_UTF8_STRING
},
183 { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT
},
184 { "STRING", 0, TARGET_STRING
},
185 { "TEXT", 0, TARGET_TEXT
}};
187 static const GtkTargetEntry link_drag_drop_targets
[] = {
188 GTK_IMHTML_DND_TARGETS
193 clipboard_win32_to_html(char *clipboard
) {
195 const char *begin
, *end
;
200 int clipboard_length
= 0;
202 #if 0 /* Debugging for Windows clipboard */
205 purple_debug_info("imhtml clipboard", "from clipboard: %s\n", clipboard
);
207 fd
= g_fopen("c:\\purplecb.txt", "wb");
208 fprintf(fd
, "%s", clipboard
);
212 clipboard_length
= strlen(clipboard
);
214 if (!(header
= strstr(clipboard
, "StartFragment:")) || (header
- clipboard
) >= clipboard_length
)
216 sscanf(header
, "StartFragment:%d", &start
);
218 if (!(header
= strstr(clipboard
, "EndFragment:")) || (header
- clipboard
) >= clipboard_length
)
220 sscanf(header
, "EndFragment:%d", &finish
);
222 if (finish
> clipboard_length
)
223 finish
= clipboard_length
;
228 begin
= clipboard
+ start
;
230 end
= clipboard
+ finish
;
232 html
= g_strndup(begin
, end
- begin
);
234 /* any newlines in the string will now be \r\n, so we need to strip out the \r */
235 split
= g_strsplit(html
, "\r\n", 0);
237 html
= g_strjoinv("\n", split
);
240 #if 0 /* Debugging for Windows clipboard */
241 purple_debug_info("imhtml clipboard", "HTML fragment: '%s'\n", html
);
248 clipboard_html_to_win32(char *html
) {
255 length
= strlen(html
);
256 clipboard
= g_string_new ("Version:1.0\r\n");
257 g_string_append(clipboard
, "StartHTML:0000000105\r\n");
258 g_string_append_printf(clipboard
, "EndHTML:%010d\r\n", 147 + length
);
259 g_string_append(clipboard
, "StartFragment:0000000127\r\n");
260 g_string_append_printf(clipboard
, "EndFragment:%010d\r\n", 127 + length
);
261 g_string_append(clipboard
, "<!--StartFragment-->\r\n");
262 g_string_append(clipboard
, html
);
263 g_string_append(clipboard
, "\r\n<!--EndFragment-->");
265 return g_string_free(clipboard
, FALSE
);
268 static gboolean
clipboard_paste_html_win32(GtkIMHtml
*imhtml
) {
269 gboolean pasted
= FALSE
;
271 /* Win32 clipboard format value, and functions to convert back and
272 * forth between HTML and the clipboard format.
274 static UINT win_html_fmt
= 0;
276 /* Register HTML Format as desired clipboard format */
278 win_html_fmt
= RegisterClipboardFormat("HTML Format");
280 if (gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
))
281 && IsClipboardFormatAvailable(win_html_fmt
)) {
282 gboolean error_reading_clipboard
= FALSE
;
283 HWND hwnd
= GDK_WINDOW_HWND(GTK_WIDGET(imhtml
)->window
);
285 if (OpenClipboard(hwnd
)) {
286 HGLOBAL hdata
= GetClipboardData(win_html_fmt
);
288 if (GetLastError() != ERROR_SUCCESS
)
289 error_reading_clipboard
= TRUE
;
291 char *buffer
= GlobalLock(hdata
);
292 if (buffer
== NULL
) {
293 error_reading_clipboard
= TRUE
;
295 char *text
= clipboard_win32_to_html(
297 imhtml_paste_insert(imhtml
, text
,
307 error_reading_clipboard
= TRUE
;
310 if (error_reading_clipboard
) {
311 gchar
*err_msg
= g_win32_error_message(GetLastError());
312 purple_debug_info("html clipboard",
313 "Unable to read clipboard data: %s\n",
314 err_msg
? err_msg
: "Unknown Error");
323 static GtkSmileyTree
*
324 gtk_smiley_tree_new (void)
326 return g_new0 (GtkSmileyTree
, 1);
330 gtk_smiley_tree_insert (GtkSmileyTree
*tree
,
331 GtkIMHtmlSmiley
*smiley
)
333 GtkSmileyTree
*t
= tree
;
334 const gchar
*x
= smiley
->smile
;
344 t
->values
= g_string_new ("");
346 pos
= strchr (t
->values
->str
, *x
);
348 t
->values
= g_string_append_c (t
->values
, *x
);
349 index
= t
->values
->len
- 1;
350 t
->children
= g_realloc (t
->children
, t
->values
->len
* sizeof (GtkSmileyTree
*));
351 t
->children
[index
] = g_new0 (GtkSmileyTree
, 1);
353 index
= GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
);
355 t
= t
->children
[index
];
365 gtk_smiley_tree_destroy (GtkSmileyTree
*tree
)
367 GSList
*list
= g_slist_prepend (NULL
, tree
);
370 GtkSmileyTree
*t
= list
->data
;
372 list
= g_slist_remove(list
, t
);
373 if (t
&& t
->values
) {
374 for (i
= 0; i
< t
->values
->len
; i
++)
375 list
= g_slist_prepend (list
, t
->children
[i
]);
376 g_string_free (t
->values
, TRUE
);
377 g_free (t
->children
);
384 static void (*parent_size_allocate
)(GtkWidget
*widget
, GtkAllocation
*alloc
);
386 static void gtk_imhtml_size_allocate(GtkWidget
*widget
, GtkAllocation
*alloc
)
388 GtkIMHtml
*imhtml
= GTK_IMHTML(widget
);
391 int height
= 0, y
= 0;
393 gboolean scroll
= TRUE
;
395 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
397 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &rect
);
398 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
400 if (((y
+ height
) - (rect
.y
+ rect
.height
)) > height
&&
401 gtk_text_buffer_get_char_count(imhtml
->text_buffer
)) {
405 if(imhtml
->old_rect
.width
!= rect
.width
|| imhtml
->old_rect
.height
!= rect
.height
) {
406 GList
*iter
= GTK_IMHTML(widget
)->scalables
;
408 xminus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(widget
)) +
409 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(widget
));
412 struct scalable_data
*sd
= iter
->data
;
413 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
414 scale
->scale(scale
, rect
.width
- xminus
, rect
.height
);
420 imhtml
->old_rect
= rect
;
421 parent_size_allocate(widget
, alloc
);
423 /* Don't scroll here if we're in the middle of a smooth scroll */
424 if (scroll
&& imhtml
->scroll_time
== NULL
&&
425 GTK_WIDGET_REALIZED(imhtml
))
426 gtk_imhtml_scroll_to_end(imhtml
, FALSE
);
429 #define DEFAULT_SEND_COLOR "#204a87"
430 #define DEFAULT_RECV_COLOR "#cc0000"
431 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
432 #define DEFAULT_ACTION_COLOR "#062585"
433 #define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
434 #define DEFAULT_WHISPER_COLOR "#00FF00"
436 static void (*parent_style_set
)(GtkWidget
*widget
, GtkStyle
*prev_style
);
439 gtk_imhtml_style_set(GtkWidget
*widget
, GtkStyle
*prev_style
)
447 {"send-name", "send-name-color", DEFAULT_SEND_COLOR
},
448 {"receive-name", "receive-name-color", DEFAULT_RECV_COLOR
},
449 {"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR
},
450 {"action-name", "action-name-color", DEFAULT_ACTION_COLOR
},
451 {"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR
},
452 {"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR
},
455 GtkIMHtml
*imhtml
= GTK_IMHTML(widget
);
456 GtkTextTagTable
*table
= gtk_text_buffer_get_tag_table(imhtml
->text_buffer
);
458 for (i
= 0; styles
[i
].tag
; i
++) {
459 GdkColor
*color
= NULL
;
460 GtkTextTag
*tag
= gtk_text_tag_table_lookup(table
, styles
[i
].tag
);
462 purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles
[i
].tag
);
465 gtk_widget_style_get(widget
, styles
[i
].color
, &color
, NULL
);
467 g_object_set(tag
, "foreground-gdk", color
, NULL
);
468 gdk_color_free(color
);
471 gdk_color_parse(styles
[i
].def
, &defcolor
);
472 g_object_set(tag
, "foreground-gdk", &defcolor
, NULL
);
475 parent_style_set(widget
, prev_style
);
479 imhtml_get_iter_bounds(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
482 gtk_text_buffer_get_bounds(imhtml
->text_buffer
, start
, end
);
484 } else if (imhtml
->editable
) {
485 if (!gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, start
, end
)) {
486 GtkTextMark
*mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
487 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, start
, mark
);
497 gtk_imhtml_set_link_color(GtkIMHtml
*imhtml
, GtkTextTag
*tag
)
499 GdkColor
*color
= NULL
;
500 gboolean visited
= !!g_object_get_data(G_OBJECT(tag
), "visited");
501 gtk_widget_style_get(GTK_WIDGET(imhtml
), visited
? "hyperlink-visited-color" : "hyperlink-color", &color
, NULL
);
503 g_object_set(G_OBJECT(tag
), "foreground-gdk", color
, NULL
);
504 gdk_color_free(color
);
506 g_object_set(G_OBJECT(tag
), "foreground", visited
? "#800000" : "blue", NULL
);
511 gtk_imhtml_tip_paint (GtkIMHtml
*imhtml
)
515 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
517 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
519 gtk_paint_flat_box (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
,
520 GTK_STATE_NORMAL
, GTK_SHADOW_OUT
, NULL
, imhtml
->tip_window
,
521 "tooltip", 0, 0, -1, -1);
523 gtk_paint_layout (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
, GTK_STATE_NORMAL
,
524 FALSE
, NULL
, imhtml
->tip_window
, NULL
, 4, 4, layout
);
526 g_object_unref(layout
);
531 gtk_imhtml_tip (gpointer data
)
533 GtkIMHtml
*imhtml
= data
;
534 PangoFontMetrics
*font_metrics
;
538 gint gap
, x
, y
, h
, w
, scr_w
, baseline_skip
;
540 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
542 if (!imhtml
->tip
|| !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml
))) {
543 imhtml
->tip_timer
= 0;
547 if (imhtml
->tip_window
){
548 gtk_widget_destroy (imhtml
->tip_window
);
549 imhtml
->tip_window
= NULL
;
552 imhtml
->tip_timer
= 0;
553 imhtml
->tip_window
= gtk_window_new (GTK_WINDOW_POPUP
);
554 gtk_widget_set_app_paintable (imhtml
->tip_window
, TRUE
);
555 gtk_window_set_title(GTK_WINDOW(imhtml
->tip_window
), "GtkIMHtml");
556 gtk_window_set_resizable (GTK_WINDOW (imhtml
->tip_window
), FALSE
);
557 gtk_widget_set_name (imhtml
->tip_window
, "gtk-tooltips");
558 gtk_window_set_type_hint (GTK_WINDOW (imhtml
->tip_window
),
559 GDK_WINDOW_TYPE_HINT_TOOLTIP
);
560 g_signal_connect_swapped (G_OBJECT (imhtml
->tip_window
), "expose_event",
561 G_CALLBACK (gtk_imhtml_tip_paint
), imhtml
);
563 gtk_widget_ensure_style (imhtml
->tip_window
);
564 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, imhtml
->tip
);
565 font
= pango_context_load_font(pango_layout_get_context(layout
),
566 imhtml
->tip_window
->style
->font_desc
);
569 char *tmp
= pango_font_description_to_string(
570 imhtml
->tip_window
->style
->font_desc
);
572 purple_debug(PURPLE_DEBUG_ERROR
, "gtk_imhtml_tip",
573 "pango_context_load_font() couldn't load font: '%s'\n",
577 g_object_unref(layout
);
581 font_metrics
= pango_font_get_metrics(font
, NULL
);
583 pango_layout_get_pixel_size(layout
, &scr_w
, NULL
);
584 gap
= PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics
) +
585 pango_font_metrics_get_descent(font_metrics
))/ 4);
589 baseline_skip
= PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
590 pango_font_metrics_get_descent(font_metrics
));
592 h
= 8 + baseline_skip
;
594 gdk_window_get_pointer (NULL
, &x
, &y
, NULL
);
595 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml
)))
596 y
+= GTK_WIDGET(imhtml
)->allocation
.y
;
598 scr_w
= gdk_screen_width();
603 x
-= (x
+ w
) - scr_w
;
607 y
= y
+ PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
608 pango_font_metrics_get_descent(font_metrics
));
610 gtk_widget_set_size_request (imhtml
->tip_window
, w
, h
);
611 gtk_window_move (GTK_WINDOW(imhtml
->tip_window
), x
, y
);
612 gtk_widget_show (imhtml
->tip_window
);
614 pango_font_metrics_unref(font_metrics
);
615 g_object_unref(font
);
616 g_object_unref(layout
);
622 gtk_motion_event_notify(GtkWidget
*imhtml
, GdkEventMotion
*event
, gpointer data
)
625 GdkWindow
*win
= event
->window
;
628 GSList
*tags
= NULL
, *templist
= NULL
;
629 GtkTextTag
*tag
= NULL
, *oldprelit_tag
;
630 GtkTextChildAnchor
* anchor
;
631 gboolean hand
= TRUE
;
632 GdkCursor
*cursor
= NULL
;
634 oldprelit_tag
= GTK_IMHTML(imhtml
)->prelit_tag
;
636 gdk_window_get_pointer(GTK_WIDGET(imhtml
)->window
, NULL
, NULL
, NULL
);
637 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
), GTK_TEXT_WINDOW_WIDGET
,
638 event
->x
, event
->y
, &x
, &y
);
639 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
640 tags
= gtk_text_iter_get_tags(&iter
);
644 tag
= templist
->data
;
645 tip
= g_object_get_data(G_OBJECT(tag
), "link_url");
648 templist
= templist
->next
;
651 if (tip
&& (!tag
|| !g_object_get_data(G_OBJECT(tag
), "visited"))) {
652 GTK_IMHTML(imhtml
)->prelit_tag
= tag
;
653 if (tag
!= oldprelit_tag
) {
654 GdkColor
*pre
= NULL
;
655 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-prelight-color", &pre
, NULL
);
657 g_object_set(G_OBJECT(tag
), "foreground-gdk", pre
, NULL
);
660 g_object_set(G_OBJECT(tag
), "foreground", "#70a0ff", NULL
);
663 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
666 if ((oldprelit_tag
!= NULL
) && (GTK_IMHTML(imhtml
)->prelit_tag
!= oldprelit_tag
)) {
667 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), oldprelit_tag
);
670 if (GTK_IMHTML(imhtml
)->tip
) {
671 if ((tip
== GTK_IMHTML(imhtml
)->tip
)) {
675 /* We've left the cell. Remove the timeout and create a new one below */
676 if (GTK_IMHTML(imhtml
)->tip_window
) {
677 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
678 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
680 if (GTK_IMHTML(imhtml
)->editable
)
681 cursor
= GTK_IMHTML(imhtml
)->text_cursor
;
683 cursor
= GTK_IMHTML(imhtml
)->arrow_cursor
;
684 if (GTK_IMHTML(imhtml
)->tip_timer
)
685 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
686 GTK_IMHTML(imhtml
)->tip_timer
= 0;
689 /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
690 anchor
= gtk_text_iter_get_child_anchor(&iter
);
692 tip
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_tiptext");
697 GTK_IMHTML(imhtml
)->tip_timer
= g_timeout_add (TOOLTIP_TIMEOUT
,
698 gtk_imhtml_tip
, imhtml
);
701 for (templist
= tags
; templist
; templist
= templist
->next
) {
702 tag
= templist
->data
;
703 if ((tip
= g_object_get_data(G_OBJECT(tag
), "cursor"))) {
710 if (hand
&& !(GTK_IMHTML(imhtml
)->editable
))
711 cursor
= GTK_IMHTML(imhtml
)->hand_cursor
;
714 gdk_window_set_cursor(win
, cursor
);
716 GTK_IMHTML(imhtml
)->tip
= tip
;
722 gtk_enter_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
724 if (GTK_IMHTML(imhtml
)->editable
)
725 gdk_window_set_cursor(
726 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
727 GTK_TEXT_WINDOW_TEXT
),
728 GTK_IMHTML(imhtml
)->text_cursor
);
730 gdk_window_set_cursor(
731 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
732 GTK_TEXT_WINDOW_TEXT
),
733 GTK_IMHTML(imhtml
)->arrow_cursor
);
735 /* propagate the event normally */
740 gtk_leave_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
742 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
743 if (GTK_IMHTML(imhtml
)->prelit_tag
) {
744 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), GTK_IMHTML(imhtml
)->prelit_tag
);
745 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
748 if (GTK_IMHTML(imhtml
)->tip_window
) {
749 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
750 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
752 if (GTK_IMHTML(imhtml
)->tip_timer
) {
753 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
754 GTK_IMHTML(imhtml
)->tip_timer
= 0;
756 gdk_window_set_cursor(
757 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
758 GTK_TEXT_WINDOW_TEXT
), NULL
);
760 /* propagate the event normally */
765 gtk_imhtml_expose_event (GtkWidget
*widget
,
766 GdkEventExpose
*event
)
768 GtkTextIter start
, end
, cur
;
770 GdkRectangle visible_rect
;
771 cairo_t
*cr
= gdk_cairo_create(GDK_DRAWABLE(event
->window
));
774 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &visible_rect
);
775 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
776 GTK_TEXT_WINDOW_TEXT
,
782 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget
), GTK_TEXT_WINDOW_TEXT
,
783 event
->area
.x
, event
->area
.y
, &buf_x
, &buf_y
);
785 if (GTK_IMHTML(widget
)->editable
|| GTK_IMHTML(widget
)->wbfo
) {
787 if (GTK_IMHTML(widget
)->edit
.background
) {
788 gdk_color_parse(GTK_IMHTML(widget
)->edit
.background
, &gcolor
);
789 gdk_cairo_set_source_color(cr
, &gcolor
);
791 gdk_cairo_set_source_color(cr
, &(widget
->style
->base
[GTK_WIDGET_STATE(widget
)]));
795 visible_rect
.x
, visible_rect
.y
,
796 visible_rect
.width
, visible_rect
.height
);
800 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
801 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
807 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &start
, buf_x
, buf_y
);
808 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &end
,
809 buf_x
+ event
->area
.width
, buf_y
+ event
->area
.height
);
811 gtk_text_iter_order(&start
, &end
);
815 while (gtk_text_iter_in_range(&cur
, &start
, &end
)) {
816 GSList
*tags
= gtk_text_iter_get_tags(&cur
);
819 for (l
= tags
; l
; l
= l
->next
) {
820 GtkTextTag
*tag
= l
->data
;
822 GdkRectangle tag_area
;
825 if (strncmp(tag
->name
, "BACKGROUND ", 11))
828 if (gtk_text_iter_ends_tag(&cur
, tag
))
831 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
832 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
833 GTK_TEXT_WINDOW_TEXT
,
838 rect
.x
= visible_rect
.x
;
840 rect
.width
= visible_rect
.width
;
843 gtk_text_iter_forward_to_tag_toggle(&cur
, tag
);
844 while (!gtk_text_iter_is_end(&cur
) && gtk_text_iter_begins_tag(&cur
, tag
));
846 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
847 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
848 GTK_TEXT_WINDOW_TEXT
,
855 rect
.height
= tag_area
.y
+ tag_area
.height
- rect
.y
856 + gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget
))
857 + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget
));
859 color
= tag
->name
+ 11;
861 if (!gdk_color_parse(color
, &gcolor
)) {
864 strncpy(&tmp
[1], color
, 7);
866 if (!gdk_color_parse(tmp
, &gcolor
))
867 gdk_color_parse("white", &gcolor
);
869 gdk_cairo_set_source_color(cr
, &gcolor
);
873 rect
.width
, rect
.height
);
875 gtk_text_iter_backward_char(&cur
); /* go back one, in case the end is the begining is the end
876 * note that above, we always moved cur ahead by at least
883 /* loop until another tag begins, or no tag begins */
884 while (gtk_text_iter_forward_to_tag_toggle(&cur
, NULL
) &&
885 !gtk_text_iter_is_end(&cur
) &&
886 !gtk_text_iter_begins_tag(&cur
, NULL
));
891 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
892 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
899 static void paste_unformatted_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
901 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
903 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
907 static void clear_formatting_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
909 gtk_imhtml_clear_formatting(imhtml
);
912 static void disable_smiley_selected(GtkMenuItem
*item
, GtkIMHtml
*imhtml
)
914 GtkTextIter start
, end
;
918 if (!gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
))
921 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
923 mark
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
924 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
926 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, mark
);
927 gtk_imhtml_insert_html_at_iter(imhtml
, text
, GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_SMILEY
, &start
);
932 static void hijack_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
)
935 GtkTextIter start
, end
;
937 menuitem
= gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
938 gtk_widget_show(menuitem
);
940 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
941 * mainloop, which tends to be a source of bugs. It would
942 * be good to audit this or change it to not wait.
944 gtk_widget_set_sensitive(menuitem
,
946 gtk_clipboard_wait_is_text_available(
947 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
))));
948 /* put it after "Paste" */
949 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 3);
951 g_signal_connect(G_OBJECT(menuitem
), "activate",
952 G_CALLBACK(paste_unformatted_cb
), imhtml
);
954 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
955 gtk_widget_show(menuitem
);
956 gtk_widget_set_sensitive(menuitem
, imhtml
->editable
);
957 /* put it after Delete */
958 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 5);
960 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(clear_formatting_cb
), imhtml
);
962 menuitem
= gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
963 gtk_widget_show(menuitem
);
964 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
965 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(disable_smiley_selected
), imhtml
);
967 gtk_widget_set_sensitive(menuitem
, FALSE
);
969 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 6);
973 ucs2_order(gboolean swap
)
977 be
= G_BYTE_ORDER
== G_BIG_ENDIAN
;
978 be
= swap
? be
: !be
;
987 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
989 utf16_to_utf8_with_bom_check(gchar
*data
, guint len
) {
990 char *fromcode
= NULL
;
991 GError
*error
= NULL
;
996 * Unicode Techinical Report 20
997 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
998 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
999 * what we do. If there is no indicator assume it is in the default
1003 memcpy(&c
, data
, 2);
1007 fromcode
= ucs2_order(c
== 0xfeff);
1012 fromcode
= "UTF-16";
1016 utf8_ret
= g_convert(data
, len
, "UTF-8", fromcode
, NULL
, NULL
, &error
);
1019 purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error
->message
);
1020 g_error_free(error
);
1026 static void gtk_imhtml_clipboard_get(GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, guint info
, GtkIMHtml
*imhtml
) {
1028 gboolean primary
= (clipboard
!= clipboard_selection
);
1029 GtkTextIter start
, end
;
1032 GtkTextMark
*sel
= NULL
, *ins
= NULL
;
1034 g_return_if_fail(imhtml
!= NULL
);
1036 ins
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1037 sel
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
1038 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, sel
);
1039 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &end
, ins
);
1042 if (info
== TARGET_HTML
) {
1047 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1049 text
= html_clipboard
;
1051 /* Mozilla asks that we start our text/html with the Unicode byte order mark */
1052 selection
= g_convert(text
, -1, "UTF-16", "UTF-8", NULL
, &len
, NULL
);
1053 gtk_selection_data_set(selection_data
, gdk_atom_intern("text/html", FALSE
), 16, (const guchar
*)selection
, len
);
1055 selection
= clipboard_html_to_win32(html_clipboard
);
1056 gtk_selection_data_set(selection_data
, gdk_atom_intern("HTML Format", FALSE
), 8, (const guchar
*)selection
, strlen(selection
));
1061 text
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1063 text
= text_clipboard
;
1064 gtk_selection_data_set_text(selection_data
, text
, strlen(text
));
1066 if (primary
) /* This was allocated here */
1070 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard
*clipboard
, GtkIMHtml
*imhtml
)
1073 GtkTextIter selection_bound
;
1075 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &insert
,
1076 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "insert"));
1077 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &selection_bound
,
1078 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"));
1080 if (!gtk_text_iter_equal (&insert
, &selection_bound
))
1081 gtk_text_buffer_move_mark (imhtml
->text_buffer
,
1082 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"),
1086 static void gtk_imhtml_clipboard_clear (GtkClipboard
*clipboard
, GtkSelectionData
*sel_data
,
1087 guint info
, gpointer user_data_or_owner
)
1091 static void copy_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1093 GtkTextIter start
, end
;
1094 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1095 if (!clipboard_selection
)
1096 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1097 gtk_clipboard_set_with_data(clipboard_selection
,
1098 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1099 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1100 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1102 g_free(html_clipboard
);
1103 g_free(text_clipboard
);
1105 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1106 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1109 g_signal_stop_emission_by_name(imhtml
, "copy-clipboard");
1112 static void cut_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1114 GtkTextIter start
, end
;
1115 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1116 if (!clipboard_selection
)
1117 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1118 gtk_clipboard_set_with_data(clipboard_selection
,
1119 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1120 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1121 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1123 g_free(html_clipboard
);
1124 g_free(text_clipboard
);
1126 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1127 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1129 if (imhtml
->editable
)
1130 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
1133 g_signal_stop_emission_by_name(imhtml
, "cut-clipboard");
1136 static void imhtml_paste_insert(GtkIMHtml
*imhtml
, const char *text
, gboolean plaintext
)
1139 GtkIMHtmlOptions flags
= plaintext
? GTK_IMHTML_NO_SMILEY
: (GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_COMMENTS
);
1141 /* Delete any currently selected text */
1142 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
1144 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1145 if (!imhtml
->wbfo
&& !plaintext
)
1146 gtk_imhtml_close_tags(imhtml
, &iter
);
1148 gtk_imhtml_insert_html_at_iter(imhtml
, text
, flags
, &iter
);
1149 gtk_text_buffer_move_mark_by_name(imhtml
->text_buffer
, "insert", &iter
);
1150 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml
), gtk_text_buffer_get_insert(imhtml
->text_buffer
),
1151 0, FALSE
, 0.0, 0.0);
1152 if (!imhtml
->wbfo
&& !plaintext
)
1153 gtk_imhtml_close_tags(imhtml
, &iter
);
1157 static void paste_plaintext_received_cb (GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
1161 if (text
== NULL
|| !(*text
))
1164 tmp
= g_markup_escape_text(text
, -1);
1165 imhtml_paste_insert(data
, tmp
, TRUE
);
1169 static void paste_received_cb (GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, gpointer data
)
1172 GtkIMHtml
*imhtml
= data
;
1174 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1177 if (imhtml
->wbfo
|| selection_data
->length
<= 0) {
1178 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1182 /* Here's some debug code, for figuring out what sent to us over the clipboard. */
1186 purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1187 selection_data
->format
, selection_data
->length
);
1189 for (i
= 0; i
< (/*(selection_data->format / 8) **/ selection_data
->length
); i
++) {
1192 if (selection_data
->data
[i
] == '\0')
1195 printf("%c", selection_data
->data
[i
]);
1201 text
= g_malloc(selection_data
->length
+ 1);
1202 memcpy(text
, selection_data
->data
, selection_data
->length
);
1203 /* Make sure the paste data is null-terminated. Given that
1204 * we're passed length (but assume later that it is
1205 * null-terminated), this seems sensible to me.
1207 text
[selection_data
->length
] = '\0';
1211 if (gtk_selection_data_get_data_type(selection_data
) == gdk_atom_intern("HTML Format", FALSE
)) {
1212 char *tmp
= clipboard_win32_to_html(text
);
1218 if (selection_data
->length
>= 2 &&
1219 (*(guint16
*)text
== 0xfeff || *(guint16
*)text
== 0xfffe)) {
1220 /* This is UTF-16 */
1221 char *utf8
= utf16_to_utf8_with_bom_check(text
, selection_data
->length
);
1225 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1230 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1231 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1236 imhtml_paste_insert(imhtml
, text
, FALSE
);
1241 static void smart_backspace_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1244 GtkTextChildAnchor
* anchor
;
1248 if (!imhtml
->editable
)
1251 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1253 /* Get the character before the insertion point */
1254 offset
= gtk_text_iter_get_offset(&iter
);
1258 gtk_text_iter_backward_char(&iter
);
1259 anchor
= gtk_text_iter_get_child_anchor(&iter
);
1262 return; /* No smiley here */
1264 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
1268 /* ok, then we need to insert the image buffer text before the anchor */
1269 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
1272 static void paste_clipboard_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1275 /* If we're on windows, let's see if we can get data from the HTML Format
1276 clipboard before we try to paste from the GTK buffer */
1277 if (!clipboard_paste_html_win32(imhtml
) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
))) {
1278 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1279 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1283 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1284 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1285 paste_received_cb
, imhtml
);
1287 g_signal_stop_emission_by_name(imhtml
, "paste-clipboard");
1290 static void imhtml_realized_remove_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1292 gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1293 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1297 static void imhtml_destroy_add_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1299 gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1300 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1303 static void mark_set_so_update_selection_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
, GtkIMHtml
*imhtml
)
1305 if (gtk_text_buffer_get_selection_bounds(buffer
, NULL
, NULL
)) {
1306 gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
),
1307 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1308 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1309 (GtkClipboardClearFunc
)gtk_imhtml_primary_clipboard_clear
, G_OBJECT(imhtml
));
1313 static gboolean
gtk_imhtml_button_press_event(GtkIMHtml
*imhtml
, GdkEventButton
*event
, gpointer unused
)
1315 if (event
->button
== 2) {
1318 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
);
1320 if (!imhtml
->editable
)
1323 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
),
1324 GTK_TEXT_WINDOW_TEXT
,
1329 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
1330 gtk_text_buffer_place_cursor(imhtml
->text_buffer
, &iter
);
1332 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1333 paste_received_cb
, imhtml
);
1342 gtk_imhtml_undo(GtkIMHtml
*imhtml
)
1344 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1345 if (imhtml
->editable
&&
1346 gtk_source_undo_manager_can_undo(imhtml
->undo_manager
))
1347 gtk_source_undo_manager_undo(imhtml
->undo_manager
);
1351 gtk_imhtml_redo(GtkIMHtml
*imhtml
)
1353 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1354 if (imhtml
->editable
&&
1355 gtk_source_undo_manager_can_redo(imhtml
->undo_manager
))
1356 gtk_source_undo_manager_redo(imhtml
->undo_manager
);
1360 static gboolean
imhtml_message_send(GtkIMHtml
*imhtml
)
1366 imhtml_paste_cb(GtkIMHtml
*imhtml
, const char *str
)
1368 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1371 if (!str
|| !*str
|| !strcmp(str
, "html"))
1372 g_signal_emit_by_name(imhtml
, "paste_clipboard");
1373 else if (!strcmp(str
, "text"))
1374 paste_unformatted_cb(NULL
, imhtml
);
1377 static void imhtml_toggle_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
1379 /* since this function is the handler for the formatting keystrokes,
1380 we need to check here that the formatting attempted is permitted */
1381 buttons
&= imhtml
->format_functions
;
1384 case GTK_IMHTML_BOLD
:
1385 imhtml_toggle_bold(imhtml
);
1387 case GTK_IMHTML_ITALIC
:
1388 imhtml_toggle_italic(imhtml
);
1390 case GTK_IMHTML_UNDERLINE
:
1391 imhtml_toggle_underline(imhtml
);
1393 case GTK_IMHTML_STRIKE
:
1394 imhtml_toggle_strike(imhtml
);
1396 case GTK_IMHTML_SHRINK
:
1397 imhtml_font_shrink(imhtml
);
1399 case GTK_IMHTML_GROW
:
1400 imhtml_font_grow(imhtml
);
1408 gtk_imhtml_finalize (GObject
*object
)
1410 GtkIMHtml
*imhtml
= GTK_IMHTML(object
);
1414 if (imhtml
->scroll_src
)
1415 g_source_remove(imhtml
->scroll_src
);
1416 if (imhtml
->scroll_time
)
1417 g_timer_destroy(imhtml
->scroll_time
);
1419 g_hash_table_destroy(imhtml
->smiley_data
);
1420 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
1421 gdk_cursor_unref(imhtml
->hand_cursor
);
1422 gdk_cursor_unref(imhtml
->arrow_cursor
);
1423 gdk_cursor_unref(imhtml
->text_cursor
);
1425 if(imhtml
->tip_window
){
1426 gtk_widget_destroy(imhtml
->tip_window
);
1428 if(imhtml
->tip_timer
)
1429 g_source_remove(imhtml
->tip_timer
);
1431 for(scalables
= imhtml
->scalables
; scalables
; scalables
= scalables
->next
) {
1432 struct scalable_data
*sd
= scalables
->data
;
1433 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
1438 for (l
= imhtml
->im_images
; l
; l
= l
->next
) {
1439 struct im_image_data
*img_data
= l
->data
;
1440 if (imhtml
->funcs
->image_unref
)
1441 imhtml
->funcs
->image_unref(img_data
->id
);
1445 g_list_free(imhtml
->scalables
);
1446 g_slist_free(imhtml
->im_images
);
1447 g_queue_free(imhtml
->animations
);
1448 g_free(imhtml
->protocol_name
);
1449 g_free(imhtml
->search_string
);
1450 g_object_unref(imhtml
->undo_manager
);
1451 G_OBJECT_CLASS(parent_class
)->finalize (object
);
1455 static GtkIMHtmlProtocol
*
1456 imhtml_find_protocol(const char *url
, gboolean reverse
)
1458 GtkIMHtmlClass
*klass
;
1460 GtkIMHtmlProtocol
*proto
= NULL
;
1461 int length
= reverse
? strlen(url
) : -1;
1463 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
1464 for (iter
= klass
->protocols
; iter
; iter
= iter
->next
) {
1466 if (g_ascii_strncasecmp(url
, proto
->name
, reverse
? MIN(length
, proto
->length
) : proto
->length
) == 0) {
1474 imhtml_url_clicked(GtkIMHtml
*imhtml
, const char *url
)
1476 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(url
, FALSE
);
1477 GtkIMHtmlLink
*link
;
1480 link
= g_new0(GtkIMHtmlLink
, 1);
1481 link
->imhtml
= g_object_ref(imhtml
);
1482 link
->url
= g_strdup(url
);
1483 proto
->activate(imhtml
, link
); /* XXX: Do something with the return value? */
1484 gtk_imhtml_link_destroy(link
);
1487 /* Boring GTK+ stuff */
1488 static void gtk_imhtml_class_init (GtkIMHtmlClass
*klass
)
1490 GtkWidgetClass
*widget_class
= (GtkWidgetClass
*) klass
;
1491 GtkBindingSet
*binding_set
;
1492 GObjectClass
*gobject_class
;
1493 gobject_class
= (GObjectClass
*) klass
;
1494 parent_class
= g_type_class_ref(GTK_TYPE_TEXT_VIEW
);
1495 signals
[URL_CLICKED
] = g_signal_new("url_clicked",
1496 G_TYPE_FROM_CLASS(gobject_class
),
1498 G_STRUCT_OFFSET(GtkIMHtmlClass
, url_clicked
),
1501 g_cclosure_marshal_VOID__POINTER
,
1504 signals
[BUTTONS_UPDATE
] = g_signal_new("format_buttons_update",
1505 G_TYPE_FROM_CLASS(gobject_class
),
1507 G_STRUCT_OFFSET(GtkIMHtmlClass
, buttons_update
),
1510 g_cclosure_marshal_VOID__INT
,
1513 signals
[TOGGLE_FORMAT
] = g_signal_new("format_function_toggle",
1514 G_TYPE_FROM_CLASS(gobject_class
),
1515 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1516 G_STRUCT_OFFSET(GtkIMHtmlClass
, toggle_format
),
1519 g_cclosure_marshal_VOID__INT
,
1522 signals
[CLEAR_FORMAT
] = g_signal_new("format_function_clear",
1523 G_TYPE_FROM_CLASS(gobject_class
),
1524 G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
1525 G_STRUCT_OFFSET(GtkIMHtmlClass
, clear_format
),
1528 g_cclosure_marshal_VOID__VOID
,
1530 signals
[UPDATE_FORMAT
] = g_signal_new("format_function_update",
1531 G_TYPE_FROM_CLASS(gobject_class
),
1533 G_STRUCT_OFFSET(GtkIMHtmlClass
, update_format
),
1536 g_cclosure_marshal_VOID__VOID
,
1538 signals
[MESSAGE_SEND
] = g_signal_new("message_send",
1539 G_TYPE_FROM_CLASS(gobject_class
),
1540 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1541 G_STRUCT_OFFSET(GtkIMHtmlClass
, message_send
),
1543 0, g_cclosure_marshal_VOID__VOID
,
1545 signals
[PASTE
] = g_signal_new("paste",
1546 G_TYPE_FROM_CLASS(gobject_class
),
1547 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1550 0, g_cclosure_marshal_VOID__STRING
,
1551 G_TYPE_NONE
, 1, G_TYPE_STRING
);
1552 signals
[UNDO
] = g_signal_new ("undo",
1553 G_TYPE_FROM_CLASS (klass
),
1554 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1555 G_STRUCT_OFFSET (GtkIMHtmlClass
, undo
),
1558 gtksourceview_marshal_VOID__VOID
,
1561 signals
[REDO
] = g_signal_new ("redo",
1562 G_TYPE_FROM_CLASS (klass
),
1563 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1564 G_STRUCT_OFFSET (GtkIMHtmlClass
, redo
),
1567 gtksourceview_marshal_VOID__VOID
,
1573 klass
->toggle_format
= imhtml_toggle_format
;
1574 klass
->message_send
= imhtml_message_send
;
1575 klass
->clear_format
= imhtml_clear_formatting
;
1576 klass
->url_clicked
= imhtml_url_clicked
;
1577 klass
->undo
= gtk_imhtml_undo
;
1578 klass
->redo
= gtk_imhtml_redo
;
1580 gobject_class
->finalize
= gtk_imhtml_finalize
;
1581 widget_class
->drag_motion
= gtk_text_view_drag_motion
;
1582 widget_class
->expose_event
= gtk_imhtml_expose_event
;
1583 parent_size_allocate
= widget_class
->size_allocate
;
1584 widget_class
->size_allocate
= gtk_imhtml_size_allocate
;
1585 parent_style_set
= widget_class
->style_set
;
1586 widget_class
->style_set
= gtk_imhtml_style_set
;
1588 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-color",
1589 _("Hyperlink color"),
1590 _("Color to draw hyperlinks."),
1591 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1592 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-visited-color",
1593 _("Hyperlink visited color"),
1594 _("Color to draw hyperlink after it has been visited (or activated)."),
1595 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1596 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-prelight-color",
1597 _("Hyperlink prelight color"),
1598 _("Color to draw hyperlinks when mouse is over them."),
1599 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1600 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("send-name-color",
1601 _("Sent Message Name Color"),
1602 _("Color to draw the name of a message you sent."),
1603 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1604 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("receive-name-color",
1605 _("Received Message Name Color"),
1606 _("Color to draw the name of a message you received."),
1607 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1608 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("highlight-name-color",
1609 _("\"Attention\" Name Color"),
1610 _("Color to draw the name of a message you received containing your name."),
1611 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1612 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("action-name-color",
1613 _("Action Message Name Color"),
1614 _("Color to draw the name of an action message."),
1615 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1616 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-action-name-color",
1617 _("Action Message Name Color for Whispered Message"),
1618 _("Color to draw the name of a whispered action message."),
1619 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1620 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-name-color",
1621 _("Whisper Message Name Color"),
1622 _("Color to draw the name of a whispered message."),
1623 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1625 /* Customizable typing notification ... sort of. Example:
1626 * GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1627 * GtkIMHtml::typing-notification-color = "#ff0000"
1628 * GtkIMHtml::typing-notification-enable = 1
1630 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("typing-notification-color",
1631 _("Typing notification color"),
1632 _("The color to use for the typing notification"),
1633 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1634 gtk_widget_class_install_style_property(widget_class
, g_param_spec_string("typing-notification-font",
1635 _("Typing notification font"),
1636 _("The font to use for the typing notification"),
1637 "light 8.0", G_PARAM_READABLE
));
1638 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boolean("typing-notification-enable",
1639 _("Enable typing notification"),
1640 _("Enable typing notification"),
1641 TRUE
, G_PARAM_READABLE
));
1643 binding_set
= gtk_binding_set_by_class (parent_class
);
1644 gtk_binding_entry_add_signal (binding_set
, GDK_b
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_BOLD
);
1645 gtk_binding_entry_add_signal (binding_set
, GDK_i
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_ITALIC
);
1646 gtk_binding_entry_add_signal (binding_set
, GDK_u
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_UNDERLINE
);
1647 gtk_binding_entry_add_signal (binding_set
, GDK_plus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1648 gtk_binding_entry_add_signal (binding_set
, GDK_equal
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1649 gtk_binding_entry_add_signal (binding_set
, GDK_minus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_SHRINK
);
1650 binding_set
= gtk_binding_set_by_class(klass
);
1651 gtk_binding_entry_add_signal (binding_set
, GDK_r
, GDK_CONTROL_MASK
, "format_function_clear", 0);
1652 gtk_binding_entry_add_signal (binding_set
, GDK_KP_Enter
, 0, "message_send", 0);
1653 gtk_binding_entry_add_signal (binding_set
, GDK_Return
, 0, "message_send", 0);
1654 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
, "undo", 0);
1655 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "redo", 0);
1656 gtk_binding_entry_add_signal (binding_set
, GDK_F14
, 0, "undo", 0);
1657 gtk_binding_entry_add_signal(binding_set
, GDK_v
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "paste", 1, G_TYPE_STRING
, "text");
1660 static void gtk_imhtml_init (GtkIMHtml
*imhtml
)
1662 imhtml
->text_buffer
= gtk_text_buffer_new(NULL
);
1663 imhtml
->undo_manager
= gtk_source_undo_manager_new(imhtml
->text_buffer
);
1664 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml
), imhtml
->text_buffer
);
1665 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
1666 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml
), 2);
1667 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml
), 3);
1668 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml
), 2);
1669 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml
), 2);
1670 /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1671 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1673 /* These tags will be used often and can be reused--we create them on init and then apply them by name
1674 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1675 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1676 * apply them anywhere yet. */
1677 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "BOLD", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1678 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "ITALICS", "style", PANGO_STYLE_ITALIC
, NULL
);
1679 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
1680 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "STRIKE", "strikethrough", TRUE
, NULL
);
1681 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUB", "rise", -5000, NULL
);
1682 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUP", "rise", 5000, NULL
);
1683 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "PRE", "family", "Monospace", NULL
);
1684 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "search", "background", "#22ff00", "weight", "bold", NULL
);
1685 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "comment", "weight", PANGO_WEIGHT_NORMAL
,
1686 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1691 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "send-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1692 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "receive-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1693 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "highlight-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1694 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1695 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1696 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1698 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1699 imhtml
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
1700 imhtml
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
1701 imhtml
->text_cursor
= gdk_cursor_new (GDK_XTERM
);
1703 imhtml
->show_comments
= TRUE
;
1705 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1706 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
1707 imhtml
->default_smilies
= gtk_smiley_tree_new();
1709 g_signal_connect(G_OBJECT(imhtml
), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify
), NULL
);
1710 g_signal_connect(G_OBJECT(imhtml
), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify
), NULL
);
1711 g_signal_connect(G_OBJECT(imhtml
), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify
), NULL
);
1712 g_signal_connect(G_OBJECT(imhtml
), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event
), NULL
);
1713 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(preinsert_cb
), imhtml
);
1714 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "delete_range", G_CALLBACK(delete_cb
), imhtml
);
1715 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(insert_cb
), imhtml
);
1716 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-child-anchor", G_CALLBACK(insert_ca_cb
), imhtml
);
1717 gtk_drag_dest_set(GTK_WIDGET(imhtml
), 0,
1718 link_drag_drop_targets
, sizeof(link_drag_drop_targets
) / sizeof(GtkTargetEntry
),
1720 g_signal_connect(G_OBJECT(imhtml
), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb
), imhtml
);
1721 g_signal_connect(G_OBJECT(imhtml
), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb
), imhtml
);
1723 g_signal_connect(G_OBJECT(imhtml
), "copy-clipboard", G_CALLBACK(copy_clipboard_cb
), NULL
);
1724 g_signal_connect(G_OBJECT(imhtml
), "cut-clipboard", G_CALLBACK(cut_clipboard_cb
), NULL
);
1725 g_signal_connect(G_OBJECT(imhtml
), "paste-clipboard", G_CALLBACK(paste_clipboard_cb
), NULL
);
1726 g_signal_connect_after(G_OBJECT(imhtml
), "realize", G_CALLBACK(imhtml_realized_remove_primary
), NULL
);
1727 g_signal_connect(G_OBJECT(imhtml
), "unrealize", G_CALLBACK(imhtml_destroy_add_primary
), NULL
);
1728 g_signal_connect(G_OBJECT(imhtml
), "paste", G_CALLBACK(imhtml_paste_cb
), NULL
);
1731 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
1732 G_CALLBACK(mark_set_so_update_selection_cb
), imhtml
);
1735 gtk_widget_add_events(GTK_WIDGET(imhtml
),
1736 GDK_LEAVE_NOTIFY_MASK
| GDK_ENTER_NOTIFY_MASK
);
1739 imhtml
->tip_timer
= 0;
1740 imhtml
->tip_window
= NULL
;
1742 imhtml
->edit
.bold
= FALSE
;
1743 imhtml
->edit
.italic
= FALSE
;
1744 imhtml
->edit
.underline
= FALSE
;
1745 imhtml
->edit
.forecolor
= NULL
;
1746 imhtml
->edit
.backcolor
= NULL
;
1747 imhtml
->edit
.fontface
= NULL
;
1748 imhtml
->edit
.fontsize
= 0;
1749 imhtml
->edit
.link
= NULL
;
1752 imhtml
->scalables
= NULL
;
1753 imhtml
->animations
= g_queue_new();
1754 gtk_imhtml_set_editable(imhtml
, FALSE
);
1755 g_signal_connect(G_OBJECT(imhtml
), "populate-popup",
1756 G_CALLBACK(hijack_menu_cb
), NULL
);
1759 GtkWidget
*gtk_imhtml_new(void *a
, void *b
)
1761 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL
));
1764 GType
gtk_imhtml_get_type()
1766 static GType imhtml_type
= 0;
1769 static const GTypeInfo imhtml_info
= {
1770 sizeof(GtkIMHtmlClass
),
1773 (GClassInitFunc
) gtk_imhtml_class_init
,
1778 (GInstanceInitFunc
) gtk_imhtml_init
,
1782 imhtml_type
= g_type_register_static(gtk_text_view_get_type(),
1783 "GtkIMHtml", &imhtml_info
, 0);
1789 static void gtk_imhtml_link_destroy(GtkIMHtmlLink
*link
)
1792 g_object_unref(link
->imhtml
);
1794 g_object_unref(link
->tag
);
1799 /* The callback for an event on a link tag. */
1800 static gboolean
tag_event(GtkTextTag
*tag
, GObject
*imhtml
, GdkEvent
*event
, GtkTextIter
*arg2
, gpointer unused
)
1802 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
1803 if (GTK_IMHTML(imhtml
)->editable
)
1805 if (event
->type
== GDK_BUTTON_RELEASE
) {
1806 if ((event_button
->button
== 1) || (event_button
->button
== 2)) {
1807 GtkTextIter start
, end
;
1808 /* we shouldn't open a URL if the user has selected something: */
1809 if (gtk_text_buffer_get_selection_bounds(
1810 gtk_text_iter_get_buffer(arg2
), &start
, &end
))
1812 gtk_imhtml_activate_tag(GTK_IMHTML(imhtml
), tag
);
1814 } else if(event_button
->button
== 3) {
1817 GtkIMHtmlProtocol
*proto
;
1818 GtkIMHtmlLink
*link
= g_new(GtkIMHtmlLink
, 1);
1819 link
->imhtml
= g_object_ref(imhtml
);
1820 link
->url
= g_strdup(g_object_get_data(G_OBJECT(tag
), "link_url"));
1821 link
->tag
= g_object_ref(tag
);
1823 /* Don't want the tooltip around if user right-clicked on link */
1824 if (GTK_IMHTML(imhtml
)->tip_window
) {
1825 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
1826 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
1828 if (GTK_IMHTML(imhtml
)->tip_timer
) {
1829 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
1830 GTK_IMHTML(imhtml
)->tip_timer
= 0;
1832 if (GTK_IMHTML(imhtml
)->editable
)
1833 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->text_cursor
);
1835 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->arrow_cursor
);
1836 menu
= gtk_menu_new();
1837 g_object_set_data_full(G_OBJECT(menu
), "x-imhtml-url-data", link
,
1838 (GDestroyNotify
)gtk_imhtml_link_destroy
);
1840 proto
= imhtml_find_protocol(link
->url
, FALSE
);
1842 if (proto
&& proto
->context_menu
) {
1843 proto
->context_menu(GTK_IMHTML(link
->imhtml
), link
, menu
);
1846 children
= gtk_container_get_children(GTK_CONTAINER(menu
));
1848 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
1849 gtk_widget_show(item
);
1850 gtk_widget_set_sensitive(item
, FALSE
);
1851 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1853 g_list_free(children
);
1857 gtk_widget_show_all(menu
);
1858 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1859 event_button
->button
, event_button
->time
);
1864 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
1865 return TRUE
; /* Clicking the right mouse button on a link shouldn't
1866 be caught by the regular GtkTextView menu */
1868 return FALSE
; /* Let clicks go through if we didn't catch anything */
1872 gtk_text_view_drag_motion (GtkWidget
*widget
,
1873 GdkDragContext
*context
,
1878 GdkDragAction suggested_action
= 0;
1880 if (gtk_drag_dest_find_target (widget
, context
, NULL
) == GDK_NONE
) {
1881 /* can't accept any of the offered targets */
1883 GtkWidget
*source_widget
;
1884 suggested_action
= context
->suggested_action
;
1885 source_widget
= gtk_drag_get_source_widget (context
);
1886 if (source_widget
== widget
) {
1887 /* Default to MOVE, unless the user has
1888 * pressed ctrl or alt to affect available actions
1890 if ((context
->actions
& GDK_ACTION_MOVE
) != 0)
1891 suggested_action
= GDK_ACTION_MOVE
;
1895 gdk_drag_status (context
, suggested_action
, time
);
1897 /* TRUE return means don't propagate the drag motion to parent
1898 * widgets that may also be drop sites.
1904 gtk_imhtml_link_drop_cb(GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer user_data
)
1906 GdkAtom target
= gtk_drag_dest_find_target (widget
, context
, NULL
);
1908 if (target
!= GDK_NONE
)
1909 gtk_drag_get_data (widget
, context
, target
, time
);
1911 gtk_drag_finish (context
, FALSE
, FALSE
, time
);
1917 gtk_imhtml_link_drag_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
1918 GtkSelectionData
*sd
, guint info
, guint t
, GtkIMHtml
*imhtml
)
1922 char *text
= (char *)sd
->data
;
1923 GtkTextMark
*mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1927 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
1929 if(gtk_imhtml_get_editable(imhtml
) && sd
->data
){
1931 case GTK_IMHTML_DRAG_URL
:
1932 /* TODO: Is it really ok to change sd->data...? */
1933 purple_str_strip_char((char *)sd
->data
, '\r');
1935 links
= g_strsplit((char *)sd
->data
, "\n", 0);
1936 while((link
= links
[i
]) != NULL
){
1937 if (gtk_imhtml_is_protocol(link
)) {
1945 gtk_imhtml_insert_link(imhtml
, mark
, link
, label
);
1946 } else if (*link
== '\0') {
1947 /* Ignore blank lines */
1949 /* Special reasons, aka images being put in via other tag, etc. */
1950 /* ... don't pretend we handled it if we didn't */
1951 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
1960 case GTK_IMHTML_DRAG_HTML
:
1963 /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1964 * as explained by this comment in gtkhtml:
1966 * FIXME This hack decides the charset of the selection. It seems that
1967 * mozilla/netscape alway use ucs2 for text/html
1968 * and openoffice.org seems to always use utf8 so we try to validate
1969 * the string as utf8 and if that fails we assume it is ucs2
1971 * See also the comment on text/html here:
1972 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1974 if (sd
->length
>= 2 && !g_utf8_validate(text
, sd
->length
- 1, NULL
)) {
1975 utf8
= utf16_to_utf8_with_bom_check(text
, sd
->length
);
1978 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1981 } else if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1982 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1986 gtk_imhtml_insert_html_at_iter(imhtml
, utf8
? utf8
: text
, 0, &iter
);
1990 case GTK_IMHTML_DRAG_TEXT
:
1991 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1992 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
1995 char *tmp
= g_markup_escape_text(text
, -1);
1996 gtk_imhtml_insert_html_at_iter(imhtml
, tmp
, 0, &iter
);
2001 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2004 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2006 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2010 static void gtk_smiley_tree_remove (GtkSmileyTree
*tree
,
2011 GtkIMHtmlSmiley
*smiley
)
2013 GtkSmileyTree
*t
= tree
;
2014 const gchar
*x
= smiley
->smile
;
2023 pos
= strchr (t
->values
->str
, *x
);
2025 t
= t
->children
[pos
- t
->values
->str
];
2038 gtk_smiley_tree_lookup (GtkSmileyTree
*tree
,
2041 GtkSmileyTree
*t
= tree
;
2042 const gchar
*x
= text
;
2053 if(*x
== '&' && (amp
= purple_markup_unescape_entity(x
, &alen
))) {
2054 gboolean matched
= TRUE
;
2055 /* Make sure all chars of the unescaped value match */
2056 while (*(amp
+ 1)) {
2057 pos
= strchr (t
->values
->str
, *amp
);
2059 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2069 pos
= strchr (t
->values
->str
, *amp
);
2071 else if (*x
== '<') /* Because we're all WYSIWYG now, a '<'
2072 * char should only appear as the start of a tag. Perhaps a safer (but costlier)
2073 * check would be to call gtk_imhtml_is_tag on it */
2077 pos
= strchr (t
->values
->str
, *x
);
2081 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2096 gtk_imhtml_disassociate_smiley_foreach(gpointer key
, gpointer value
,
2099 GtkSmileyTree
*tree
= (GtkSmileyTree
*) value
;
2100 GtkIMHtmlSmiley
*smiley
= (GtkIMHtmlSmiley
*) user_data
;
2101 gtk_smiley_tree_remove(tree
, smiley
);
2105 gtk_imhtml_disconnect_smiley(GtkIMHtml
*imhtml
, GtkIMHtmlSmiley
*smiley
)
2107 smiley
->imhtml
= NULL
;
2108 g_signal_handlers_disconnect_matched(imhtml
, G_SIGNAL_MATCH_DATA
, 0, 0,
2109 NULL
, NULL
, smiley
);
2113 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley
*smiley
)
2115 if (smiley
->imhtml
) {
2116 gtk_smiley_tree_remove(smiley
->imhtml
->default_smilies
, smiley
);
2117 g_hash_table_foreach(smiley
->imhtml
->smiley_data
,
2118 gtk_imhtml_disassociate_smiley_foreach
, smiley
);
2119 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2120 0, 0, NULL
, NULL
, smiley
);
2121 smiley
->imhtml
= NULL
;
2126 gtk_imhtml_associate_smiley (GtkIMHtml
*imhtml
,
2128 GtkIMHtmlSmiley
*smiley
)
2130 GtkSmileyTree
*tree
;
2131 g_return_if_fail (imhtml
!= NULL
);
2132 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2135 tree
= imhtml
->default_smilies
;
2136 else if (!(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
))) {
2137 tree
= gtk_smiley_tree_new();
2138 g_hash_table_insert(imhtml
->smiley_data
, g_strdup(sml
), tree
);
2141 /* need to disconnect old imhtml, if there is one */
2142 if (smiley
->imhtml
) {
2143 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2144 0, 0, NULL
, NULL
, smiley
);
2147 smiley
->imhtml
= imhtml
;
2149 gtk_smiley_tree_insert (tree
, smiley
);
2151 /* connect destroy signal for the imhtml */
2152 g_signal_connect(imhtml
, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley
),
2157 gtk_imhtml_is_smiley (GtkIMHtml
*imhtml
,
2162 GtkSmileyTree
*tree
;
2163 GtkIMHtmlFontDetail
*font
;
2172 sml
= imhtml
->protocol_name
;
2174 if (!sml
|| !(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
)))
2175 tree
= imhtml
->default_smilies
;
2180 *len
= gtk_smiley_tree_lookup (tree
, text
);
2184 static GtkIMHtmlSmiley
*gtk_imhtml_smiley_get_from_tree(GtkSmileyTree
*t
, const gchar
*text
)
2186 const gchar
*x
= text
;
2196 pos
= strchr(t
->values
->str
, *x
);
2200 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2208 gtk_imhtml_smiley_get(GtkIMHtml
*imhtml
, const gchar
*sml
, const gchar
*text
)
2210 GtkIMHtmlSmiley
*ret
;
2212 /* Look for custom smileys first */
2214 ret
= gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml
->smiley_data
, sml
), text
);
2219 /* Fall back to check for default smileys */
2220 return gtk_imhtml_smiley_get_from_tree(imhtml
->default_smilies
, text
);
2223 static GdkPixbufAnimation
*
2224 gtk_smiley_get_image(GtkIMHtmlSmiley
*smiley
)
2226 if (!smiley
->icon
) {
2228 smiley
->icon
= gdk_pixbuf_animation_new_from_file(smiley
->file
, NULL
);
2229 } else if (smiley
->loader
) {
2230 smiley
->icon
= gdk_pixbuf_loader_get_animation(smiley
->loader
);
2232 g_object_ref(G_OBJECT(smiley
->icon
));
2236 return smiley
->icon
;
2239 #define VALID_TAG(x) do { \
2240 if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
2241 if (tag) *tag = g_strndup (string, strlen (x)); \
2242 if (len) *len = strlen (x) + 1; \
2245 if (type) (*type)++; \
2248 #define VALID_OPT_TAG(x) do { \
2249 if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
2250 const gchar *c = string + strlen (x " "); \
2252 gboolean quote = FALSE; \
2254 if (*c == '"' || *c == '\'') { \
2255 if (quote && (*c == e)) \
2257 else if (!quote) { \
2261 } else if (!quote && (*c == '>')) \
2266 if (tag) *tag = g_strndup (string, c - string); \
2267 if (len) *len = c - string + 1; \
2271 if (type) (*type)++; \
2276 gtk_imhtml_is_tag (const gchar
*string
,
2285 if (!(close
= strchr (string
, '>')))
2291 VALID_TAG ("/BOLD");
2293 VALID_TAG ("ITALIC");
2295 VALID_TAG ("/ITALIC");
2297 VALID_TAG ("UNDERLINE");
2299 VALID_TAG ("/UNDERLINE");
2301 VALID_TAG ("STRIKE");
2303 VALID_TAG ("/STRIKE");
2310 VALID_TAG ("TITLE");
2311 VALID_TAG ("/TITLE");
2314 VALID_TAG ("/FONT");
2321 VALID_TAG ("/HTML");
2323 VALID_TAG ("/BODY");
2326 VALID_TAG ("/HEAD");
2327 VALID_TAG ("BINARY");
2328 VALID_TAG ("/BINARY");
2330 VALID_OPT_TAG ("HR");
2331 VALID_OPT_TAG ("FONT");
2332 VALID_OPT_TAG ("BODY");
2333 VALID_OPT_TAG ("A");
2334 VALID_OPT_TAG ("IMG");
2335 VALID_OPT_TAG ("P");
2336 VALID_OPT_TAG ("H3");
2337 VALID_OPT_TAG ("HTML");
2340 VALID_TAG ("/CITE");
2343 VALID_TAG ("STRONG");
2344 VALID_TAG ("/STRONG");
2346 VALID_OPT_TAG ("SPAN");
2347 VALID_TAG ("/SPAN");
2348 VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2351 VALID_OPT_TAG("BR");
2353 if (!g_ascii_strncasecmp(string
, "!--", strlen ("!--"))) {
2354 gchar
*e
= strstr (string
+ strlen("!--"), "-->");
2357 *len
= e
- string
+ strlen ("-->");
2359 *tag
= g_strndup (string
+ strlen ("!--"), *len
- strlen ("!---->"));
2367 *len
= close
- string
+ 1;
2369 *tag
= g_strndup(string
, *len
- 1);
2374 gtk_imhtml_get_html_opt (gchar
*tag
,
2384 while (g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2385 gboolean quote
= FALSE
;
2386 if (*t
== '\0') break;
2387 while (*t
&& !((*t
== ' ') && !quote
)) {
2392 while (*t
&& (*t
== ' ')) t
++;
2395 if (!g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2401 if ((*t
== '\"') || (*t
== '\'')) {
2403 while (*e
&& (*e
!= *(t
- 1))) e
++;
2407 val
= g_strndup(a
, e
- a
);
2410 while (*e
&& !isspace ((gint
) *e
)) e
++;
2411 val
= g_strndup(a
, e
- a
);
2414 ret
= g_string_new("");
2417 if((c
= purple_markup_unescape_entity(e
, &len
))) {
2418 ret
= g_string_append(ret
, c
);
2421 gunichar uni
= g_utf8_get_char(e
);
2422 ret
= g_string_append_unichar(ret
, uni
);
2423 e
= g_utf8_next_char(e
);
2429 return g_string_free(ret
, FALSE
);
2432 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2433 the caller knows how long the protocol string is. */
2434 static int gtk_imhtml_is_protocol(const char *text
)
2436 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(text
, FALSE
);
2437 return proto
? proto
->length
: 0;
2440 static gboolean
smooth_scroll_cb(gpointer data
);
2443 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2446 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2447 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2448 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2449 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2450 [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?
2451 [20:00] <KingAnt> marv: Right
2452 [20:00] <marv> alright
2454 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2455 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2456 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2457 images can be looked up like that, instead of passing a GSList of them.
2460 void gtk_imhtml_append_text_with_images (GtkIMHtml
*imhtml
,
2462 GtkIMHtmlOptions options
,
2465 GtkTextIter iter
, ins
, sel
;
2466 int ins_offset
= 0, sel_offset
= 0;
2467 gboolean fixins
= FALSE
, fixsel
= FALSE
;
2469 g_return_if_fail (imhtml
!= NULL
);
2470 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2471 g_return_if_fail (text
!= NULL
);
2474 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
2475 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &ins
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
2476 if (gtk_text_iter_equal(&iter
, &ins
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2478 ins_offset
= gtk_text_iter_get_offset(&ins
);
2481 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &sel
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
));
2482 if (gtk_text_iter_equal(&iter
, &sel
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2484 sel_offset
= gtk_text_iter_get_offset(&sel
);
2487 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2491 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2492 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
2494 if (((y
+ height
) - (rect
.y
+ rect
.height
)) > height
&&
2495 gtk_text_buffer_get_char_count(imhtml
->text_buffer
)) {
2496 /* If we are in the middle of smooth-scrolling, then take a scroll step.
2497 * If we are not in the middle of smooth-scrolling, that means we were
2498 * not looking at the end of the buffer before the new text was added,
2499 * so do not scroll. */
2500 if (imhtml
->scroll_time
)
2501 smooth_scroll_cb(imhtml
);
2503 options
|= GTK_IMHTML_NO_SCROLL
;
2507 gtk_imhtml_insert_html_at_iter(imhtml
, text
, options
, &iter
);
2510 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &ins
, ins_offset
);
2511 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_insert(imhtml
->text_buffer
), &ins
);
2515 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &sel
, sel_offset
);
2516 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
), &sel
);
2519 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2520 gtk_imhtml_scroll_to_end(imhtml
, (options
& GTK_IMHTML_USE_SMOOTHSCROLLING
));
2524 #define MAX_SCROLL_TIME 0.4 /* seconds */
2525 #define SCROLL_DELAY 33 /* milliseconds */
2528 * Smoothly scroll a GtkIMHtml.
2530 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2532 static gboolean
smooth_scroll_cb(gpointer data
)
2534 GtkIMHtml
*imhtml
= data
;
2535 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2536 gdouble max_val
= adj
->upper
- adj
->page_size
;
2537 gdouble scroll_val
= gtk_adjustment_get_value(adj
) + ((max_val
- gtk_adjustment_get_value(adj
)) / 3);
2539 g_return_val_if_fail(imhtml
->scroll_time
!= NULL
, FALSE
);
2541 if (g_timer_elapsed(imhtml
->scroll_time
, NULL
) > MAX_SCROLL_TIME
|| scroll_val
>= max_val
) {
2542 /* time's up. jump to the end and kill the timer */
2543 gtk_adjustment_set_value(adj
, max_val
);
2544 g_timer_destroy(imhtml
->scroll_time
);
2545 imhtml
->scroll_time
= NULL
;
2546 g_source_remove(imhtml
->scroll_src
);
2547 imhtml
->scroll_src
= 0;
2551 /* scroll by 1/3rd the remaining distance */
2552 gtk_adjustment_set_value(adj
, scroll_val
);
2556 static gboolean
scroll_idle_cb(gpointer data
)
2558 GtkIMHtml
*imhtml
= data
;
2559 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2561 gtk_adjustment_set_value(adj
, adj
->upper
- adj
->page_size
);
2563 imhtml
->scroll_src
= 0;
2567 void gtk_imhtml_scroll_to_end(GtkIMHtml
*imhtml
, gboolean smooth
)
2569 if (imhtml
->scroll_time
)
2570 g_timer_destroy(imhtml
->scroll_time
);
2571 if (imhtml
->scroll_src
)
2572 g_source_remove(imhtml
->scroll_src
);
2574 imhtml
->scroll_time
= g_timer_new();
2575 imhtml
->scroll_src
= g_timeout_add_full(G_PRIORITY_LOW
, SCROLL_DELAY
, smooth_scroll_cb
, imhtml
, NULL
);
2577 imhtml
->scroll_time
= NULL
;
2578 imhtml
->scroll_src
= g_idle_add_full(G_PRIORITY_LOW
, scroll_idle_cb
, imhtml
, NULL
);
2582 /* CSS colors are either rgb (x,y,z) or #hex
2583 * we need to convert to hex if it is RGB */
2585 parse_css_color(gchar
*in_color
)
2587 char *tmp
= in_color
;
2589 if (*tmp
== 'r' && *(++tmp
) == 'g' && *(++tmp
) == 'b' && *(++tmp
)) {
2590 int rgbval
[] = {0, 0, 0};
2592 const char *v_start
;
2594 while (*tmp
&& g_ascii_isspace(*tmp
))
2597 /* We don't support rgba() */
2598 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2604 /* Skip any leading spaces */
2605 while (*tmp
&& g_ascii_isspace(*tmp
))
2608 /* Find the subsequent contiguous digits */
2610 if (*v_start
== '-')
2612 while (*tmp
&& g_ascii_isdigit(*tmp
))
2615 if (tmp
!= v_start
) {
2618 rgbval
[count
] = atoi(v_start
);
2622 while (*tmp
&& g_ascii_isspace(*tmp
))
2625 rgbval
[count
] = (rgbval
[count
] / 100.0) * 255;
2629 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2633 if (rgbval
[count
] > 255) {
2634 rgbval
[count
] = 255;
2635 } else if (rgbval
[count
] < 0) {
2639 while (*tmp
&& g_ascii_isspace(*tmp
))
2648 return g_strdup_printf("#%02X%02X%02X", rgbval
[0], rgbval
[1], rgbval
[2]);
2654 void gtk_imhtml_insert_html_at_iter(GtkIMHtml
*imhtml
,
2656 GtkIMHtmlOptions options
,
2665 gint tlen
, smilelen
, wpos
=0;
2680 gboolean br
= FALSE
;
2681 gboolean align_right
= FALSE
;
2682 gboolean rtl_direction
= FALSE
;
2683 gint align_line
= 0;
2685 GSList
*fonts
= NULL
;
2687 GtkIMHtmlScalable
*scalable
= NULL
;
2689 g_return_if_fail (imhtml
!= NULL
);
2690 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2691 g_return_if_fail (text
!= NULL
);
2694 ws
= g_malloc(len
+ 1);
2697 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2699 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
2701 if (*c
== '<' && gtk_imhtml_is_tag (c
+ 1, &tag
, &tlen
, &type
)) {
2710 case 54: /* STRONG */
2711 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2712 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2714 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
))
2715 gtk_imhtml_toggle_bold(imhtml
);
2717 ws
[0] = '\0'; wpos
= 0;
2722 case 55: /* /STRONG */
2723 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2724 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2725 ws
[0] = '\0'; wpos
= 0;
2729 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
) && !imhtml
->wbfo
)
2730 gtk_imhtml_toggle_bold(imhtml
);
2735 case 6: /* ITALIC */
2737 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2738 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2739 ws
[0] = '\0'; wpos
= 0;
2740 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
))
2741 gtk_imhtml_toggle_italic(imhtml
);
2746 case 8: /* /ITALIC */
2748 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2749 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2750 ws
[0] = '\0'; wpos
= 0;
2753 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
) && !imhtml
->wbfo
)
2754 gtk_imhtml_toggle_italic(imhtml
);
2759 case 10: /* UNDERLINE */
2760 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2761 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2762 ws
[0] = '\0'; wpos
= 0;
2763 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
))
2764 gtk_imhtml_toggle_underline(imhtml
);
2769 case 12: /* /UNDERLINE */
2770 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2771 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2772 ws
[0] = '\0'; wpos
= 0;
2775 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
) && !imhtml
->wbfo
)
2776 gtk_imhtml_toggle_underline(imhtml
);
2781 case 14: /* STRIKE */
2782 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2783 ws
[0] = '\0'; wpos
= 0;
2784 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
))
2785 gtk_imhtml_toggle_strike(imhtml
);
2789 case 16: /* /STRIKE */
2790 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2791 ws
[0] = '\0'; wpos
= 0;
2794 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
) && !imhtml
->wbfo
)
2795 gtk_imhtml_toggle_strike(imhtml
);
2798 /* FIXME: reimpliment this */
2802 /* FIXME: reimpliment this */
2807 /* FIXME: reimplement this */
2811 /* FIXME: reimplement this */
2816 /* FIXME: reimplement this */
2820 /* FIXME: reimplement this */
2824 case 23: /* TITLE */
2825 /* FIXME: what was this supposed to do anyway? */
2828 case 24: /* /TITLE */
2829 /* FIXME: make this undo whatever 23 was supposed to do */
2831 if (options
& GTK_IMHTML_NO_TITLE
) {
2840 case 61: /* BR (opt) */
2846 case 42: /* HR (opt) */
2849 struct scalable_data
*sd
= g_new(struct scalable_data
, 1);
2852 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2854 sd
->scalable
= scalable
= gtk_imhtml_hr_new();
2855 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
2856 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2857 scalable
->add_to(scalable
, imhtml
, iter
);
2858 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
2859 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
2860 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
2861 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
2862 ws
[0] = '\0'; wpos
= 0;
2867 case 27: /* /FONT */
2868 if (fonts
&& !imhtml
->wbfo
) {
2869 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2870 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2871 ws
[0] = '\0'; wpos
= 0;
2872 /* NEW_BIT (NEW_TEXT_BIT); */
2874 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2875 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
2877 g_free (font
->face
);
2878 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2879 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
2881 g_free (font
->fore
);
2882 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2883 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
2885 g_free (font
->back
);
2888 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2889 gtk_imhtml_font_set_size(imhtml
, 3);
2891 fonts
= g_slist_remove (fonts
, font
);
2895 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2897 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
))
2898 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2899 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
))
2900 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2901 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
))
2902 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2903 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2904 gtk_imhtml_font_set_size(imhtml
, font
->size
);
2909 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2910 gtk_imhtml_toggle_link(imhtml
, NULL
);
2911 ws
[0] = '\0'; wpos
= 0;
2919 case 34: /* /HTML */
2922 case 36: /* /BODY */
2923 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2924 ws
[0] = '\0'; wpos
= 0;
2925 gtk_imhtml_toggle_background(imhtml
, NULL
);
2929 case 39: /* /HEAD */
2930 case 40: /* BINARY */
2931 case 41: /* /BINARY */
2933 case 43: /* FONT (opt) */
2935 gchar
*color
, *back
, *face
, *size
, *sml
;
2936 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
2937 color
= gtk_imhtml_get_html_opt (tag
, "COLOR=");
2938 back
= gtk_imhtml_get_html_opt (tag
, "BACK=");
2939 face
= gtk_imhtml_get_html_opt (tag
, "FACE=");
2940 size
= gtk_imhtml_get_html_opt (tag
, "SIZE=");
2941 sml
= gtk_imhtml_get_html_opt (tag
, "SML=");
2942 if (!(color
|| back
|| face
|| size
|| sml
))
2945 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2946 ws
[0] = '\0'; wpos
= 0;
2948 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
2950 oldfont
= fonts
->data
;
2952 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2954 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2958 if (back
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2960 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2964 if (face
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2966 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2974 if (oldfont
&& oldfont
->sml
)
2975 font
->sml
= g_strdup(oldfont
->sml
);
2978 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
))) {
2980 sscanf (size
+ 1, "%hd", &font
->size
);
2982 } else if (*size
== '-') {
2983 sscanf (size
+ 1, "%hd", &font
->size
);
2984 font
->size
= MAX (0, 3 - font
->size
);
2985 } else if (isdigit (*size
)) {
2986 sscanf (size
, "%hd", &font
->size
);
2988 if (font
->size
> 100)
2991 font
->size
= oldfont
->size
;
2994 if ((imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)) && (font
->size
!= 3 || (oldfont
&& oldfont
->size
== 3)))
2995 gtk_imhtml_font_set_size(imhtml
, font
->size
);
2997 fonts
= g_slist_prepend (fonts
, font
);
3000 case 44: /* BODY (opt) */
3001 if (!(options
& GTK_IMHTML_NO_COLOURS
)) {
3002 char *bgcolor
= gtk_imhtml_get_html_opt (tag
, "BGCOLOR=");
3003 if (bgcolor
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
3004 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3005 ws
[0] = '\0'; wpos
= 0;
3006 /* NEW_BIT(NEW_TEXT_BIT); */
3009 gtk_imhtml_toggle_background(imhtml
, bg
);
3014 case 45: /* A (opt) */
3016 gchar
*href
= gtk_imhtml_get_html_opt (tag
, "HREF=");
3017 if (href
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3018 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3019 ws
[0] = '\0'; wpos
= 0;
3020 gtk_imhtml_toggle_link(imhtml
, href
);
3025 case 46: /* IMG (opt) */
3030 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3031 ws
[0] = '\0'; wpos
= 0;
3033 if (!(imhtml
->format_functions
& GTK_IMHTML_IMAGE
))
3036 id
= gtk_imhtml_get_html_opt(tag
, "ID=");
3038 gtk_imhtml_insert_image_at_iter(imhtml
, atoi(id
), iter
);
3042 src
= gtk_imhtml_get_html_opt(tag
, "SRC=");
3043 alt
= gtk_imhtml_get_html_opt(tag
, "ALT=");
3045 gtk_imhtml_toggle_link(imhtml
, src
);
3046 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, alt
? alt
: src
, -1);
3047 gtk_imhtml_toggle_link(imhtml
, NULL
);
3054 case 47: /* P (opt) */
3055 case 48: /* H3 (opt) */
3056 case 49: /* HTML (opt) */
3058 case 51: /* /CITE */
3059 case 56: /* SPAN (opt) */
3060 /* Inline CSS Support - Douglas Thrift
3066 * text-decoration: underline
3076 gchar
*style
, *color
, *background
, *family
, *size
, *direction
, *alignment
;
3077 gchar
*textdec
, *weight
;
3078 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
3079 style
= gtk_imhtml_get_html_opt (tag
, "style=");
3083 color
= purple_markup_get_css_property (style
, "color");
3084 background
= purple_markup_get_css_property (style
, "background");
3085 family
= purple_markup_get_css_property (style
, "font-family");
3086 size
= purple_markup_get_css_property (style
, "font-size");
3087 textdec
= purple_markup_get_css_property (style
, "text-decoration");
3088 weight
= purple_markup_get_css_property (style
, "font-weight");
3089 direction
= purple_markup_get_css_property (style
, "direction");
3090 alignment
= purple_markup_get_css_property (style
, "text-align");
3093 if (!(color
|| family
|| size
|| background
|| textdec
|| weight
|| direction
|| alignment
)) {
3099 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3100 ws
[0] = '\0'; wpos
= 0;
3101 /* NEW_BIT (NEW_TEXT_BIT); */
3103 /* Bi-Directional text support */
3104 if (direction
&& (!g_ascii_strncasecmp(direction
, "RTL", 3))) {
3105 rtl_direction
= TRUE
;
3106 /* insert RLE character to set direction */
3111 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3112 ws
[0] = '\0'; wpos
= 0;
3116 if (alignment
&& (!g_ascii_strncasecmp(alignment
, "RIGHT", 5))) {
3118 align_line
= gtk_text_iter_get_line(iter
);
3122 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
3124 oldfont
= fonts
->data
;
3126 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
3127 font
->fore
= parse_css_color(color
);
3128 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
3130 if (oldfont
&& oldfont
->fore
)
3131 font
->fore
= g_strdup(oldfont
->fore
);
3135 if (background
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
3136 font
->back
= parse_css_color(background
);
3137 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
3139 if (oldfont
&& oldfont
->back
)
3140 font
->back
= g_strdup(oldfont
->back
);
3144 if (family
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
3145 font
->face
= family
;
3146 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
3148 if (oldfont
&& oldfont
->face
)
3149 font
->face
= g_strdup(oldfont
->face
);
3152 if (font
->face
&& (atoi(font
->face
) > 100)) {
3154 /* Maybe it sets a max size on the font face? I seem to
3155 * remember bad things happening if the font size was
3158 font
->face
= g_strdup("100");
3161 if (oldfont
&& oldfont
->sml
)
3162 font
->sml
= g_strdup(oldfont
->sml
);
3164 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_SHRINK
|GTK_IMHTML_GROW
))) {
3165 if (g_ascii_strcasecmp(size
, "xx-small") == 0)
3167 else if (g_ascii_strcasecmp(size
, "smaller") == 0
3168 || g_ascii_strcasecmp(size
, "x-small") == 0)
3170 else if (g_ascii_strcasecmp(size
, "medium") == 0)
3172 else if (g_ascii_strcasecmp(size
, "large") == 0
3173 || g_ascii_strcasecmp(size
, "larger") == 0)
3175 else if (g_ascii_strcasecmp(size
, "x-large") == 0)
3177 else if (g_ascii_strcasecmp(size
, "xx-large") == 0)
3181 * TODO: Handle other values, like percentages, or
3182 * lengths specified as em, ex, px, in, cm, mm, pt
3183 * or pc. Or even better, use an actual HTML
3184 * renderer like webkit.
3187 gtk_imhtml_font_set_size(imhtml
, font
->size
);
3191 font
->size
= oldfont
->size
;
3196 font
->underline
= oldfont
->underline
;
3198 if (textdec
&& font
->underline
!= 1
3199 && g_ascii_strcasecmp(textdec
, "underline") == 0
3200 && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
)
3201 && !(options
& GTK_IMHTML_NO_FORMATTING
))
3203 gtk_imhtml_toggle_underline(imhtml
);
3204 font
->underline
= 1;
3209 font
->strike
= oldfont
->strike
;
3211 if (textdec
&& font
->strike
!= 1
3212 && g_ascii_strcasecmp(textdec
, "line-through") == 0
3213 && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
)
3214 && !(options
& GTK_IMHTML_NO_FORMATTING
))
3216 gtk_imhtml_toggle_strike(imhtml
);
3223 font
->bold
= oldfont
->bold
;
3227 if(!g_ascii_strcasecmp(weight
, "normal")) {
3229 } else if(!g_ascii_strcasecmp(weight
, "bold")) {
3231 } else if(!g_ascii_strcasecmp(weight
, "bolder")) {
3233 } else if(!g_ascii_strcasecmp(weight
, "lighter")) {
3237 int num
= atoi(weight
);
3243 if (((font
->bold
&& oldfont
&& !oldfont
->bold
) || (oldfont
&& oldfont
->bold
&& !font
->bold
) || (font
->bold
&& !oldfont
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3245 gtk_imhtml_toggle_bold(imhtml
);
3252 fonts
= g_slist_prepend (fonts
, font
);
3255 case 57: /* /SPAN */
3256 /* Inline CSS Support - Douglas Thrift */
3257 if (fonts
&& !imhtml
->wbfo
) {
3258 GtkIMHtmlFontDetail
*oldfont
= NULL
;
3259 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3260 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3261 ws
[0] = '\0'; wpos
= 0;
3262 /* NEW_BIT (NEW_TEXT_BIT); */
3263 fonts
= g_slist_remove (fonts
, font
);
3265 oldfont
= fonts
->data
;
3268 gtk_imhtml_font_set_size(imhtml
, 3);
3269 if (font
->underline
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3270 gtk_imhtml_toggle_underline(imhtml
);
3271 if (font
->strike
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3272 gtk_imhtml_toggle_strike(imhtml
);
3273 if (font
->bold
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3274 gtk_imhtml_toggle_bold(imhtml
);
3275 if (!(options
& GTK_IMHTML_NO_FONTS
))
3276 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
3277 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3278 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
3279 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3280 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
3285 if ((font
->size
!= oldfont
->size
) && !(options
& GTK_IMHTML_NO_SIZES
))
3286 gtk_imhtml_font_set_size(imhtml
, oldfont
->size
);
3288 if ((font
->underline
!= oldfont
->underline
) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3289 gtk_imhtml_toggle_underline(imhtml
);
3291 if ((font
->strike
!= oldfont
->strike
) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3292 gtk_imhtml_toggle_strike(imhtml
);
3294 if (((font
->bold
&& !oldfont
->bold
) || (oldfont
->bold
&& !font
->bold
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3295 gtk_imhtml_toggle_bold(imhtml
);
3297 if (font
->face
&& (!oldfont
->face
|| strcmp(font
->face
, oldfont
->face
) != 0) && !(options
& GTK_IMHTML_NO_FONTS
))
3298 gtk_imhtml_toggle_fontface(imhtml
, oldfont
->face
);
3300 if (font
->fore
&& (!oldfont
->fore
|| strcmp(font
->fore
, oldfont
->fore
) != 0) && !(options
& GTK_IMHTML_NO_COLOURS
))
3301 gtk_imhtml_toggle_forecolor(imhtml
, oldfont
->fore
);
3303 if (font
->back
&& (!oldfont
->back
|| strcmp(font
->back
, oldfont
->back
) != 0) && !(options
& GTK_IMHTML_NO_COLOURS
))
3304 gtk_imhtml_toggle_backcolor(imhtml
, oldfont
->back
);
3307 g_free (font
->face
);
3308 g_free (font
->fore
);
3309 g_free (font
->back
);
3317 case 62: /* comment */
3318 /* NEW_BIT (NEW_TEXT_BIT); */
3321 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3323 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3324 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3325 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3327 if (imhtml
->show_comments
&& !(options
& GTK_IMHTML_NO_COMMENTS
)) {
3328 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3329 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3332 ws
[0] = '\0'; wpos
= 0;
3334 /* NEW_BIT (NEW_COMMENT_BIT); */
3341 g_free(tag
); /* This was allocated back in VALID_TAG() */
3342 } else if (imhtml
->edit
.link
== NULL
&&
3343 !(options
& GTK_IMHTML_NO_SMILEY
) &&
3344 gtk_imhtml_is_smiley(imhtml
, fonts
, c
, &smilelen
)) {
3345 GtkIMHtmlFontDetail
*fd
;
3355 sml
= imhtml
->protocol_name
;
3357 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3358 wpos
= g_snprintf (ws
, smilelen
+ 1, "%s", c
);
3360 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, ws
, iter
);
3366 } else if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3369 ws
[wpos
++] = *amp
++;
3373 } else if (*c
== '\n') {
3374 if (!(options
& GTK_IMHTML_NO_NEWLINE
)) {
3377 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3378 ws
[0] = '\0'; wpos
= 0;
3379 /* NEW_BIT (NEW_TEXT_BIT); */
3380 } else if (!br
) { /* Don't insert a space immediately after an HTML break */
3381 /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3382 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3383 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3384 * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand?
3388 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3389 ws
[0] = '\0'; wpos
= 0;
3393 } else if ((pos
== 0 || wpos
== 0 || isspace(*(c
- 1))) &&
3394 (len_protocol
= gtk_imhtml_is_protocol(c
)) > 0 &&
3395 c
[len_protocol
] && !isspace(c
[len_protocol
]) &&
3396 (c
[len_protocol
] != '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3399 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3400 ws
[0] = '\0'; wpos
= 0;
3402 while (len_protocol
--) {
3403 /* Skip the next len_protocol characters, but
3404 * make sure they're copied into the ws array.
3409 if (!imhtml
->edit
.link
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3410 while (*c
&& !isspace((int)*c
) &&
3411 (*c
!= '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3412 if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3414 ws
[wpos
++] = *amp
++;
3423 gtk_imhtml_toggle_link(imhtml
, ws
);
3424 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3425 ws
[0] = '\0'; wpos
= 0;
3426 gtk_imhtml_toggle_link(imhtml
, NULL
);
3436 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3437 ws
[0] = '\0'; wpos
= 0;
3439 /* NEW_BIT(NEW_TEXT_BIT); */
3442 /* insert RLM+LRM at beginning of the line to set alignment */
3443 GtkTextIter line_iter
;
3445 gtk_text_iter_set_line(&line_iter
, align_line
);
3446 /* insert RLM character to set alignment */
3453 /* insert LRM character to set direction */
3454 /* (alignment=right and direction=LTR) */
3461 gtk_text_buffer_insert(imhtml
->text_buffer
, &line_iter
, ws
, wpos
);
3462 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter
), iter
);
3463 ws
[0] = '\0'; wpos
= 0;
3467 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3468 fonts
= g_slist_remove (fonts
, font
);
3469 g_free (font
->face
);
3470 g_free (font
->fore
);
3471 g_free (font
->back
);
3480 gtk_imhtml_close_tags(imhtml
, iter
);
3482 object
= g_object_ref(G_OBJECT(imhtml
));
3483 g_signal_emit(object
, signals
[UPDATE_FORMAT
], 0);
3484 g_object_unref(object
);
3486 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
3489 void gtk_imhtml_remove_smileys(GtkIMHtml
*imhtml
)
3491 g_hash_table_destroy(imhtml
->smiley_data
);
3492 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
3493 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3494 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
3495 imhtml
->default_smilies
= gtk_smiley_tree_new();
3498 void gtk_imhtml_show_comments (GtkIMHtml
*imhtml
,
3501 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3503 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), "comment");
3505 g_object_set(G_OBJECT(tag
), "invisible", !show
, NULL
);
3507 imhtml
->show_comments
= show
;
3511 gtk_imhtml_get_protocol_name(GtkIMHtml
*imhtml
) {
3512 return imhtml
->protocol_name
;
3516 gtk_imhtml_set_protocol_name(GtkIMHtml
*imhtml
, const gchar
*protocol_name
) {
3517 g_free(imhtml
->protocol_name
);
3518 imhtml
->protocol_name
= g_strdup(protocol_name
);
3522 gtk_imhtml_delete(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
) {
3525 GtkTextIter i
, i_s
, i_e
;
3526 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
3528 if (start
== NULL
) {
3529 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &i_s
);
3534 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &i_e
);
3538 l
= imhtml
->scalables
;
3540 GList
*next
= l
->next
;
3541 struct scalable_data
*sd
= l
->data
;
3542 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3544 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3545 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
3548 imhtml
->scalables
= g_list_delete_link(imhtml
->scalables
, l
);
3553 sl
= imhtml
->im_images
;
3555 GSList
*next
= sl
->next
;
3556 struct im_image_data
*img_data
= sl
->data
;
3557 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3558 &i
, img_data
->mark
);
3559 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3560 if (imhtml
->funcs
->image_unref
)
3561 imhtml
->funcs
->image_unref(img_data
->id
);
3562 imhtml
->im_images
= g_slist_delete_link(imhtml
->im_images
, sl
);
3567 gtk_text_buffer_delete(imhtml
->text_buffer
, start
, end
);
3569 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3571 g_object_unref(object
);
3574 void gtk_imhtml_page_up (GtkIMHtml
*imhtml
)
3579 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3580 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3581 rect
.y
- rect
.height
);
3582 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3585 void gtk_imhtml_page_down (GtkIMHtml
*imhtml
)
3590 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3591 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3592 rect
.y
+ rect
.height
);
3593 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3596 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
3597 GtkIMHtmlScalable
*gtk_imhtml_image_new(GdkPixbuf
*img
, const gchar
*filename
, int id
)
3599 GtkIMHtmlImage
*im_image
= g_malloc(sizeof(GtkIMHtmlImage
));
3601 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3602 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3603 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_image_free
;
3605 im_image
->pixbuf
= img
;
3606 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3607 im_image
->width
= gdk_pixbuf_get_width(img
);
3608 im_image
->height
= gdk_pixbuf_get_height(img
);
3609 im_image
->mark
= NULL
;
3610 im_image
->filename
= g_strdup(filename
);
3612 im_image
->filesel
= NULL
;
3615 return GTK_IMHTML_SCALABLE(im_image
);
3619 animate_image_cb(gpointer data
)
3621 GtkIMHtmlImage
*im_image
;
3627 /* Update the pointer to this GdkPixbuf frame of the animation */
3628 if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image
)->iter
, NULL
)) {
3629 GdkPixbuf
*pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3630 g_object_unref(G_OBJECT(im_image
->pixbuf
));
3631 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3633 /* Update the displayed GtkImage */
3634 width
= gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
));
3635 height
= gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image
->image
));
3636 if (width
> 0 && height
> 0)
3638 /* Need to scale the new frame to the same size as the old frame */
3640 tmp
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, width
, height
, GDK_INTERP_BILINEAR
);
3641 gtk_image_set_from_pixbuf(im_image
->image
, tmp
);
3642 g_object_unref(G_OBJECT(tmp
));
3644 /* Display at full-size */
3645 gtk_image_set_from_pixbuf(im_image
->image
, im_image
->pixbuf
);
3649 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3650 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3655 GtkIMHtmlScalable
*gtk_imhtml_animation_new(GdkPixbufAnimation
*anim
, const gchar
*filename
, int id
)
3657 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*) g_new0(GtkIMHtmlAnimation
, 1);
3659 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3660 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3661 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_animation_free
;
3663 GTK_IMHTML_ANIMATION(im_image
)->anim
= anim
;
3664 if (gdk_pixbuf_animation_is_static_image(anim
)) {
3665 im_image
->pixbuf
= gdk_pixbuf_animation_get_static_image(anim
);
3666 g_object_ref(im_image
->pixbuf
);
3670 GTK_IMHTML_ANIMATION(im_image
)->iter
= gdk_pixbuf_animation_get_iter(anim
, NULL
);
3671 pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3672 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3673 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3674 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3676 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3677 im_image
->width
= gdk_pixbuf_animation_get_width(anim
);
3678 im_image
->height
= gdk_pixbuf_animation_get_height(anim
);
3679 im_image
->filename
= g_strdup(filename
);
3684 return GTK_IMHTML_SCALABLE(im_image
);
3687 void gtk_imhtml_image_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
3689 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*)scale
;
3691 if (im_image
->width
> width
|| im_image
->height
> height
) {
3692 double ratio_w
, ratio_h
, ratio
;
3694 GdkPixbuf
*new_image
= NULL
;
3696 ratio_w
= ((double)width
- 2) / im_image
->width
;
3697 ratio_h
= ((double)height
- 2) / im_image
->height
;
3699 ratio
= (ratio_w
< ratio_h
) ? ratio_w
: ratio_h
;
3701 new_w
= (int)(im_image
->width
* ratio
);
3702 new_h
= (int)(im_image
->height
* ratio
);
3704 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, new_w
, new_h
, GDK_INTERP_BILINEAR
);
3705 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3706 g_object_unref(G_OBJECT(new_image
));
3707 } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
)) != im_image
->width
) {
3708 /* Enough space to show the full-size of the image. */
3709 GdkPixbuf
*new_image
;
3711 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, im_image
->width
, im_image
->height
, GDK_INTERP_BILINEAR
);
3712 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3713 g_object_unref(G_OBJECT(new_image
));
3718 image_save_yes_cb(GtkIMHtmlImageSave
*save
, const char *filename
)
3720 GError
*error
= NULL
;
3721 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3723 gtk_widget_destroy(image
->filesel
);
3724 image
->filesel
= NULL
;
3726 if (save
->data
&& save
->datasize
) {
3727 g_file_set_contents(filename
, save
->data
, save
->datasize
, &error
);
3730 GSList
*formats
= gdk_pixbuf_get_formats();
3734 GdkPixbufFormat
*format
= formats
->data
;
3735 gchar
**extensions
= gdk_pixbuf_format_get_extensions(format
);
3736 gpointer p
= extensions
;
3738 while(gdk_pixbuf_format_is_writable(format
) && extensions
&& extensions
[0]){
3739 gchar
*fmt_ext
= extensions
[0];
3740 const gchar
* file_ext
= filename
+ strlen(filename
) - strlen(fmt_ext
);
3742 if(!g_ascii_strcasecmp(fmt_ext
, file_ext
)){
3743 type
= gdk_pixbuf_format_get_name(format
);
3755 formats
= formats
->next
;
3758 g_slist_free(formats
);
3760 /* If I can't find a valid type, I will just tell the user about it and then assume
3763 char *basename
, *tmp
;
3765 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3766 _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3768 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3769 gtk_widget_show(dialog
);
3771 type
= g_strdup("png");
3772 dirname
= g_path_get_dirname(filename
);
3773 basename
= g_path_get_basename(filename
);
3774 tmp
= strrchr(basename
, '.');
3777 newfilename
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s.png", dirname
, basename
);
3782 * We're able to save the file in it's original format, so we
3783 * can use the original file name.
3785 newfilename
= g_strdup(filename
);
3788 gdk_pixbuf_save(image
->pixbuf
, newfilename
, type
, &error
, NULL
);
3790 g_free(newfilename
);
3795 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3796 _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error
->message
);
3797 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3798 gtk_widget_show(dialog
);
3799 g_error_free(error
);
3804 image_save_check_if_exists_cb(GtkWidget
*widget
, gint response
, GtkIMHtmlImageSave
*save
)
3807 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3809 if (response
!= GTK_RESPONSE_ACCEPT
) {
3810 gtk_widget_destroy(widget
);
3811 image
->filesel
= NULL
;
3815 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget
));
3818 * XXX - We should probably prompt the user to determine if they really
3819 * want to overwrite the file or not. However, I don't feel like doing
3820 * that, so we're just always going to overwrite if the file exists.
3823 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3825 image_save_yes_cb(image, filename);
3828 image_save_yes_cb(save
, filename
);
3834 gtk_imhtml_image_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3836 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3838 if (image
->filesel
!= NULL
) {
3839 gtk_window_present(GTK_WINDOW(image
->filesel
));
3843 image
->filesel
= gtk_file_chooser_dialog_new(_("Save Image"),
3845 GTK_FILE_CHOOSER_ACTION_SAVE
,
3846 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
3847 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
3849 gtk_dialog_set_default_response(GTK_DIALOG(image
->filesel
), GTK_RESPONSE_ACCEPT
);
3850 if (image
->filename
!= NULL
)
3851 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image
->filesel
), image
->filename
);
3852 g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image
->filesel
)), "response",
3853 G_CALLBACK(image_save_check_if_exists_cb
), save
);
3855 gtk_widget_show(image
->filesel
);
3859 gtk_imhtml_custom_smiley_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3861 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3863 /* Create an add dialog */
3864 PidginSmiley
*editor
= pidgin_smiley_edit(NULL
, NULL
);
3865 pidgin_smiley_editor_set_shortcut(editor
, image
->filename
);
3866 pidgin_smiley_editor_set_image(editor
, image
->pixbuf
);
3867 pidgin_smiley_editor_set_data(editor
, save
->data
, save
->datasize
);
3871 * So, um, AIM Direct IM lets you send any file, not just images. You can
3872 * just insert a sound or a file or whatever in a conversation. It's
3873 * basically like file transfer, except there is an icon to open the file
3874 * embedded in the conversation. Someone should make the Purple core handle
3877 static gboolean
gtk_imhtml_image_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlImageSave
*save
)
3879 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
3880 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3882 if (event
->type
== GDK_BUTTON_RELEASE
) {
3883 if(event_button
->button
== 3) {
3884 GtkWidget
*img
, *item
, *menu
;
3885 menu
= gtk_menu_new();
3887 /* buttons and such */
3888 img
= gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
);
3889 item
= gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3890 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3891 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_image_save
), save
);
3892 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3894 /* Add menu item for adding custom smiley to local smileys */
3895 /* we only add the menu if the image is of "custom smiley size"
3897 if (image
->width
<= 96 && image
->height
<= 96) {
3898 img
= gtk_image_new_from_stock(GTK_STOCK_ADD
, GTK_ICON_SIZE_MENU
);
3899 item
= gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3900 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3901 g_signal_connect(G_OBJECT(item
), "activate",
3902 G_CALLBACK(gtk_imhtml_custom_smiley_save
), save
);
3903 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3906 gtk_widget_show_all(menu
);
3907 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
3908 event_button
->button
, event_button
->time
);
3913 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
3914 return TRUE
; /* Clicking the right mouse button on a link shouldn't
3915 be caught by the regular GtkTextView menu */
3917 return FALSE
; /* Let clicks go through if we didn't catch anything */
3921 static gboolean
gtk_imhtml_smiley_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlSmiley
*smiley
)
3923 GdkPixbufAnimation
*anim
= NULL
;
3924 GtkIMHtmlImageSave
*save
= NULL
;
3927 if (event
->type
!= GDK_BUTTON_RELEASE
|| ((GdkEventButton
*)event
)->button
!= 3)
3930 anim
= gtk_smiley_get_image(smiley
);
3934 save
= g_new0(GtkIMHtmlImageSave
, 1);
3935 save
->image
= (GtkIMHtmlScalable
*)gtk_imhtml_animation_new(anim
, smiley
->smile
, 0);
3936 save
->data
= smiley
->data
; /* Do not need to memdup here, since the smiley is not
3937 destroyed before this GtkIMHtmlImageSave */
3938 save
->datasize
= smiley
->datasize
;
3939 ret
= gtk_imhtml_image_clicked(w
, event
, save
);
3940 g_object_set_data_full(G_OBJECT(w
), "image-data", save
->image
, (GDestroyNotify
)gtk_imhtml_animation_free
);
3941 g_object_set_data_full(G_OBJECT(w
), "image-save-data", save
, (GDestroyNotify
)g_free
);
3945 void gtk_imhtml_image_free(GtkIMHtmlScalable
*scale
)
3947 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3949 g_object_unref(image
->pixbuf
);
3950 g_free(image
->filename
);
3952 gtk_widget_destroy(image
->filesel
);
3956 void gtk_imhtml_animation_free(GtkIMHtmlScalable
*scale
)
3958 GtkIMHtmlAnimation
*animation
= (GtkIMHtmlAnimation
*)scale
;
3960 if (animation
->timer
> 0)
3961 g_source_remove(animation
->timer
);
3962 if (animation
->iter
!= NULL
)
3963 g_object_unref(animation
->iter
);
3964 g_object_unref(animation
->anim
);
3966 gtk_imhtml_image_free(scale
);
3969 void gtk_imhtml_image_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
3971 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3972 GtkWidget
*box
= gtk_event_box_new();
3974 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
3975 GtkIMHtmlImageSave
*save
;
3977 gtk_container_add(GTK_CONTAINER(box
), GTK_WIDGET(image
->image
));
3979 if(!gtk_check_version(2, 4, 0))
3980 g_object_set(G_OBJECT(box
), "visible-window", FALSE
, NULL
);
3982 gtk_widget_show(GTK_WIDGET(image
->image
));
3983 gtk_widget_show(box
);
3985 tag
= g_strdup_printf("<IMG ID=\"%d\">", image
->id
);
3986 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", tag
, g_free
);
3987 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "[Image]");
3989 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), box
, anchor
);
3991 save
= g_new0(GtkIMHtmlImageSave
, 1);
3992 save
->image
= scale
;
3993 g_signal_connect(G_OBJECT(box
), "event", G_CALLBACK(gtk_imhtml_image_clicked
), save
);
3994 g_object_set_data_full(G_OBJECT(box
), "image-save-data", save
, (GDestroyNotify
)g_free
);
3997 GtkIMHtmlScalable
*gtk_imhtml_hr_new()
3999 GtkIMHtmlHr
*hr
= g_malloc(sizeof(GtkIMHtmlHr
));
4001 GTK_IMHTML_SCALABLE(hr
)->scale
= gtk_imhtml_hr_scale
;
4002 GTK_IMHTML_SCALABLE(hr
)->add_to
= gtk_imhtml_hr_add_to
;
4003 GTK_IMHTML_SCALABLE(hr
)->free
= gtk_imhtml_hr_free
;
4005 hr
->sep
= gtk_hseparator_new();
4006 gtk_widget_set_size_request(hr
->sep
, 5000, 2);
4007 gtk_widget_show(hr
->sep
);
4009 return GTK_IMHTML_SCALABLE(hr
);
4012 void gtk_imhtml_hr_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
4014 gtk_widget_set_size_request(((GtkIMHtmlHr
*)scale
)->sep
, width
- 2, 2);
4017 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
4019 GtkIMHtmlHr
*hr
= (GtkIMHtmlHr
*)scale
;
4020 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
4021 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_htmltext", "<hr>");
4022 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "\n---\n");
4023 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), hr
->sep
, anchor
);
4026 void gtk_imhtml_hr_free(GtkIMHtmlScalable
*scale
)
4031 gboolean
gtk_imhtml_search_find(GtkIMHtml
*imhtml
, const gchar
*text
)
4033 GtkTextIter iter
, start
, end
;
4034 gboolean new_search
= TRUE
;
4035 GtkTextMark
*start_mark
;
4037 g_return_val_if_fail(imhtml
!= NULL
, FALSE
);
4038 g_return_val_if_fail(text
!= NULL
, FALSE
);
4040 start_mark
= gtk_text_buffer_get_mark(imhtml
->text_buffer
, "search");
4042 if (start_mark
&& imhtml
->search_string
&& !strcmp(text
, imhtml
->search_string
))
4046 gtk_imhtml_search_clear(imhtml
);
4047 g_free(imhtml
->search_string
);
4048 imhtml
->search_string
= g_strdup(text
);
4049 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4051 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
,
4055 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4056 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4057 &start
, &end
, NULL
))
4059 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4060 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4063 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &iter
, &end
);
4065 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4066 while (gtk_source_iter_backward_search(&start
, imhtml
->search_string
,
4067 GTK_SOURCE_SEARCH_VISIBLE_ONLY
|
4068 GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4069 &start
, &end
, NULL
));
4073 else if (!new_search
)
4075 /* We hit the end, so start at the beginning again. */
4076 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4078 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4079 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4080 &start
, &end
, NULL
))
4082 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4083 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4093 void gtk_imhtml_search_clear(GtkIMHtml
*imhtml
)
4095 GtkTextIter start
, end
;
4097 g_return_if_fail(imhtml
!= NULL
);
4099 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
4100 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
4102 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4103 g_free(imhtml
->search_string
);
4104 imhtml
->search_string
= NULL
;
4107 static GtkTextTag
*find_font_forecolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4112 g_snprintf(str
, sizeof(str
), "FORECOLOR %s", color
);
4114 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4117 if (!gdk_color_parse(color
, &gcolor
)) {
4120 strncpy(&tmp
[1], color
, 7);
4122 if (!gdk_color_parse(tmp
, &gcolor
))
4123 gdk_color_parse("black", &gcolor
);
4125 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", &gcolor
, NULL
);
4131 static GtkTextTag
*find_font_backcolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4136 g_snprintf(str
, sizeof(str
), "BACKCOLOR %s", color
);
4138 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4141 if (!gdk_color_parse(color
, &gcolor
)) {
4144 strncpy(&tmp
[1], color
, 7);
4146 if (!gdk_color_parse(tmp
, &gcolor
))
4147 gdk_color_parse("white", &gcolor
);
4149 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "background-gdk", &gcolor
, NULL
);
4155 static GtkTextTag
*find_font_background_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4160 g_snprintf(str
, sizeof(str
), "BACKGROUND %s", color
);
4162 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4164 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, NULL
);
4169 static GtkTextTag
*find_font_face_tag(GtkIMHtml
*imhtml
, gchar
*face
)
4174 g_snprintf(str
, sizeof(str
), "FONT FACE %s", face
);
4177 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4179 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "family", face
, NULL
);
4184 static GtkTextTag
*find_font_size_tag(GtkIMHtml
*imhtml
, int size
)
4189 g_snprintf(str
, sizeof(str
), "FONT SIZE %d", size
);
4192 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4194 /* For reasons I don't understand, setting "scale" here scaled
4195 * based on some default size other than my theme's default
4196 * size. Our size 4 was actually smaller than our size 3 for
4197 * me. So this works around that oddity.
4199 GtkTextAttributes
*attr
= gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml
));
4200 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "size",
4201 (gint
) (pango_font_description_get_size(attr
->font
) *
4202 (double) POINT_SIZE(size
)), NULL
);
4203 gtk_text_attributes_unref(attr
);
4209 static void remove_tag_by_prefix(GtkIMHtml
*imhtml
, const GtkTextIter
*i
, const GtkTextIter
*e
,
4210 const char *prefix
, guint len
, gboolean homo
)
4215 tags
= gtk_text_iter_get_tags(i
);
4217 for (l
= tags
; l
; l
= l
->next
) {
4218 GtkTextTag
*tag
= l
->data
;
4220 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4221 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, i
, e
);
4231 while (gtk_text_iter_forward_char(&iter
) && !gtk_text_iter_equal(&iter
, e
)) {
4232 if (gtk_text_iter_begins_tag(&iter
, NULL
)) {
4233 tags
= gtk_text_iter_get_toggled_tags(&iter
, TRUE
);
4235 for (l
= tags
; l
; l
= l
->next
) {
4236 GtkTextTag
*tag
= l
->data
;
4238 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4239 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, &iter
, e
);
4247 static void remove_font_size(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4249 remove_tag_by_prefix(imhtml
, i
, e
, "FONT SIZE ", 10, homo
);
4252 static void remove_font_face(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4254 remove_tag_by_prefix(imhtml
, i
, e
, "FONT FACE ", 10, homo
);
4257 static void remove_font_forecolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4259 remove_tag_by_prefix(imhtml
, i
, e
, "FORECOLOR ", 10, homo
);
4262 static void remove_font_backcolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4264 remove_tag_by_prefix(imhtml
, i
, e
, "BACKCOLOR ", 10, homo
);
4267 static void remove_font_background(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4269 remove_tag_by_prefix(imhtml
, i
, e
, "BACKGROUND ", 10, homo
);
4272 static void remove_font_link(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4274 remove_tag_by_prefix(imhtml
, i
, e
, "LINK ", 5, homo
);
4278 imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4280 GtkTextIter start
, end
;
4282 if (!imhtml
->editable
)
4285 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4288 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4289 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4290 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4291 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4292 remove_font_size(imhtml
, &start
, &end
, FALSE
);
4293 remove_font_face(imhtml
, &start
, &end
, FALSE
);
4294 remove_font_forecolor(imhtml
, &start
, &end
, FALSE
);
4295 remove_font_backcolor(imhtml
, &start
, &end
, FALSE
);
4296 remove_font_background(imhtml
, &start
, &end
, FALSE
);
4297 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4299 imhtml
->edit
.bold
= 0;
4300 imhtml
->edit
.italic
= 0;
4301 imhtml
->edit
.underline
= 0;
4302 imhtml
->edit
.strike
= 0;
4303 imhtml
->edit
.fontsize
= 0;
4305 g_free(imhtml
->edit
.fontface
);
4306 imhtml
->edit
.fontface
= NULL
;
4308 g_free(imhtml
->edit
.forecolor
);
4309 imhtml
->edit
.forecolor
= NULL
;
4311 g_free(imhtml
->edit
.backcolor
);
4312 imhtml
->edit
.backcolor
= NULL
;
4314 g_free(imhtml
->edit
.background
);
4315 imhtml
->edit
.background
= NULL
;
4318 /* Editable stuff */
4319 static void preinsert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4321 imhtml
->insert_offset
= gtk_text_iter_get_offset(iter
);
4324 static void insert_ca_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextChildAnchor
*arg2
, gpointer user_data
)
4329 gtk_text_iter_backward_char(&start
);
4331 gtk_imhtml_apply_tags_on_insert(user_data
, &start
, arg1
);
4334 static void insert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*end
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4342 gtk_text_iter_set_offset(&start
, imhtml
->insert_offset
);
4344 gtk_imhtml_apply_tags_on_insert(imhtml
, &start
, end
);
4347 static void delete_cb(GtkTextBuffer
*buffer
, GtkTextIter
*start
, GtkTextIter
*end
, GtkIMHtml
*imhtml
)
4351 tags
= gtk_text_iter_get_tags(start
);
4352 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4353 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4355 if (tag
&& /* Remove the formatting only if */
4356 gtk_text_iter_starts_word(start
) && /* beginning of a word */
4357 gtk_text_iter_begins_tag(start
, tag
) && /* the tag starts with the selection */
4358 (!gtk_text_iter_has_tag(end
, tag
) || /* the tag ends within the selection */
4359 gtk_text_iter_ends_tag(end
, tag
))) {
4360 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, start
, end
);
4362 strncmp(tag
->name
, "LINK ", 5) == 0 && imhtml
->edit
.link
) {
4363 gtk_imhtml_toggle_link(imhtml
, NULL
);
4370 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
4372 if (imhtml
->edit
.bold
)
4373 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4375 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4377 if (imhtml
->edit
.italic
)
4378 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4380 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4382 if (imhtml
->edit
.underline
)
4383 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4385 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4387 if (imhtml
->edit
.strike
)
4388 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4390 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4392 if (imhtml
->edit
.forecolor
) {
4393 remove_font_forecolor(imhtml
, start
, end
, TRUE
);
4394 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4395 find_font_forecolor_tag(imhtml
, imhtml
->edit
.forecolor
),
4399 if (imhtml
->edit
.backcolor
) {
4400 remove_font_backcolor(imhtml
, start
, end
, TRUE
);
4401 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4402 find_font_backcolor_tag(imhtml
, imhtml
->edit
.backcolor
),
4406 if (imhtml
->edit
.background
) {
4407 remove_font_background(imhtml
, start
, end
, TRUE
);
4408 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4409 find_font_background_tag(imhtml
, imhtml
->edit
.background
),
4412 if (imhtml
->edit
.fontface
) {
4413 remove_font_face(imhtml
, start
, end
, TRUE
);
4414 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4415 find_font_face_tag(imhtml
, imhtml
->edit
.fontface
),
4419 if (imhtml
->edit
.fontsize
) {
4420 remove_font_size(imhtml
, start
, end
, TRUE
);
4421 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4422 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
),
4426 if (imhtml
->edit
.link
) {
4427 remove_font_link(imhtml
, start
, end
, TRUE
);
4428 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4434 void gtk_imhtml_set_editable(GtkIMHtml
*imhtml
, gboolean editable
)
4436 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml
), editable
);
4438 * We need a visible caret for accessibility, so mouseless
4439 * people can highlight stuff.
4441 /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4442 if (editable
&& !imhtml
->editable
) {
4443 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
4444 G_CALLBACK(mark_set_cb
), imhtml
);
4445 g_signal_connect(G_OBJECT(imhtml
), "backspace", G_CALLBACK(smart_backspace_cb
), NULL
);
4446 } else if (!editable
&& imhtml
->editable
) {
4447 g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
),
4448 mark_set_cb
, imhtml
);
4449 g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml
),
4450 smart_backspace_cb
, NULL
);
4453 imhtml
->editable
= editable
;
4454 imhtml
->format_functions
= GTK_IMHTML_ALL
;
4457 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml
*imhtml
, gboolean wbfo
)
4459 g_return_if_fail(imhtml
!= NULL
);
4461 imhtml
->wbfo
= wbfo
;
4464 void gtk_imhtml_set_format_functions(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
4466 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
4467 imhtml
->format_functions
= buttons
;
4468 g_signal_emit(object
, signals
[BUTTONS_UPDATE
], 0, buttons
);
4469 g_object_unref(object
);
4472 GtkIMHtmlButtons
gtk_imhtml_get_format_functions(GtkIMHtml
*imhtml
)
4474 return imhtml
->format_functions
;
4477 void gtk_imhtml_get_current_format(GtkIMHtml
*imhtml
, gboolean
*bold
,
4478 gboolean
*italic
, gboolean
*underline
)
4481 (*bold
) = imhtml
->edit
.bold
;
4483 (*italic
) = imhtml
->edit
.italic
;
4484 if (underline
!= NULL
)
4485 (*underline
) = imhtml
->edit
.underline
;
4489 gtk_imhtml_get_current_fontface(GtkIMHtml
*imhtml
)
4491 return g_strdup(imhtml
->edit
.fontface
);
4495 gtk_imhtml_get_current_forecolor(GtkIMHtml
*imhtml
)
4497 return g_strdup(imhtml
->edit
.forecolor
);
4501 gtk_imhtml_get_current_backcolor(GtkIMHtml
*imhtml
)
4503 return g_strdup(imhtml
->edit
.backcolor
);
4507 gtk_imhtml_get_current_background(GtkIMHtml
*imhtml
)
4509 return g_strdup(imhtml
->edit
.background
);
4513 gtk_imhtml_get_current_fontsize(GtkIMHtml
*imhtml
)
4515 return imhtml
->edit
.fontsize
;
4518 gboolean
gtk_imhtml_get_editable(GtkIMHtml
*imhtml
)
4520 return imhtml
->editable
;
4524 gtk_imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4528 object
= g_object_ref(G_OBJECT(imhtml
));
4529 g_signal_emit(object
, signals
[CLEAR_FORMAT
], 0);
4531 gtk_widget_grab_focus(GTK_WIDGET(imhtml
));
4533 g_object_unref(object
);
4537 * I had this crazy idea about changing the text cursor color to reflex the foreground color
4538 * of the text about to be entered. This is the place you'd do it, along with the place where
4539 * we actually set a new foreground color.
4540 * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4543 * Just in case I do do this, I asked about what to set the secondary text cursor to.
4545 * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4546 * (12:45:55) ?? ???: understand?
4547 * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4548 * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4551 static void mark_set_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
,
4557 if (mark
!= gtk_text_buffer_get_insert(buffer
))
4560 if (!gtk_text_buffer_get_char_count(buffer
))
4563 imhtml
->edit
.bold
= imhtml
->edit
.italic
= imhtml
->edit
.underline
= imhtml
->edit
.strike
= FALSE
;
4564 g_free(imhtml
->edit
.forecolor
);
4565 imhtml
->edit
.forecolor
= NULL
;
4567 g_free(imhtml
->edit
.backcolor
);
4568 imhtml
->edit
.backcolor
= NULL
;
4570 g_free(imhtml
->edit
.fontface
);
4571 imhtml
->edit
.fontface
= NULL
;
4573 imhtml
->edit
.fontsize
= 0;
4574 imhtml
->edit
.link
= NULL
;
4576 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4579 if (gtk_text_iter_is_end(&iter
))
4580 tags
= gtk_text_iter_get_toggled_tags(&iter
, FALSE
);
4582 tags
= gtk_text_iter_get_tags(&iter
);
4584 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4585 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4588 if (strcmp(tag
->name
, "BOLD") == 0)
4589 imhtml
->edit
.bold
= TRUE
;
4590 else if (strcmp(tag
->name
, "ITALICS") == 0)
4591 imhtml
->edit
.italic
= TRUE
;
4592 else if (strcmp(tag
->name
, "UNDERLINE") == 0)
4593 imhtml
->edit
.underline
= TRUE
;
4594 else if (strcmp(tag
->name
, "STRIKE") == 0)
4595 imhtml
->edit
.strike
= TRUE
;
4596 else if (strncmp(tag
->name
, "FORECOLOR ", 10) == 0)
4597 imhtml
->edit
.forecolor
= g_strdup(&(tag
->name
)[10]);
4598 else if (strncmp(tag
->name
, "BACKCOLOR ", 10) == 0)
4599 imhtml
->edit
.backcolor
= g_strdup(&(tag
->name
)[10]);
4600 else if (strncmp(tag
->name
, "FONT FACE ", 10) == 0)
4601 imhtml
->edit
.fontface
= g_strdup(&(tag
->name
)[10]);
4602 else if (strncmp(tag
->name
, "FONT SIZE ", 10) == 0)
4603 imhtml
->edit
.fontsize
= strtol(&(tag
->name
)[10], NULL
, 10);
4604 else if ((strncmp(tag
->name
, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter
))
4605 imhtml
->edit
.link
= tag
;
4612 static void imhtml_emit_signal_for_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons button
)
4616 g_return_if_fail(imhtml
!= NULL
);
4618 object
= g_object_ref(G_OBJECT(imhtml
));
4619 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4620 g_object_unref(object
);
4623 static void imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4625 GtkTextIter start
, end
;
4627 imhtml
->edit
.bold
= !imhtml
->edit
.bold
;
4629 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4632 if (imhtml
->edit
.bold
)
4633 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4635 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4638 void gtk_imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4640 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_BOLD
);
4643 static void imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4645 GtkTextIter start
, end
;
4647 imhtml
->edit
.italic
= !imhtml
->edit
.italic
;
4649 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4652 if (imhtml
->edit
.italic
)
4653 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4655 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4658 void gtk_imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4660 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_ITALIC
);
4663 static void imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4665 GtkTextIter start
, end
;
4667 imhtml
->edit
.underline
= !imhtml
->edit
.underline
;
4669 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4672 if (imhtml
->edit
.underline
)
4673 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4675 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4678 void gtk_imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4680 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_UNDERLINE
);
4683 static void imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4685 GtkTextIter start
, end
;
4687 imhtml
->edit
.strike
= !imhtml
->edit
.strike
;
4689 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4692 if (imhtml
->edit
.strike
)
4693 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4695 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4698 void gtk_imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4700 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_STRIKE
);
4703 void gtk_imhtml_font_set_size(GtkIMHtml
*imhtml
, gint size
)
4706 GtkTextIter start
, end
;
4708 imhtml
->edit
.fontsize
= size
;
4710 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4713 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4714 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4715 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4717 object
= g_object_ref(G_OBJECT(imhtml
));
4718 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_SHRINK
| GTK_IMHTML_GROW
);
4719 g_object_unref(object
);
4722 static void imhtml_font_shrink(GtkIMHtml
*imhtml
)
4724 GtkTextIter start
, end
;
4726 if (imhtml
->edit
.fontsize
== 1)
4729 if (!imhtml
->edit
.fontsize
)
4730 imhtml
->edit
.fontsize
= 2;
4732 imhtml
->edit
.fontsize
--;
4734 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4736 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4737 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4738 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4741 void gtk_imhtml_font_shrink(GtkIMHtml
*imhtml
)
4743 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_SHRINK
);
4746 static void imhtml_font_grow(GtkIMHtml
*imhtml
)
4748 GtkTextIter start
, end
;
4750 if (imhtml
->edit
.fontsize
== MAX_FONT_SIZE
)
4753 if (!imhtml
->edit
.fontsize
)
4754 imhtml
->edit
.fontsize
= 4;
4756 imhtml
->edit
.fontsize
++;
4758 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4760 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4761 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4762 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4765 void gtk_imhtml_font_grow(GtkIMHtml
*imhtml
)
4767 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_GROW
);
4770 static gboolean
gtk_imhtml_toggle_str_tag(GtkIMHtml
*imhtml
, const char *value
, char **edit_field
,
4771 void (*remove_func
)(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
),
4772 GtkTextTag
*(find_func
)(GtkIMHtml
*imhtml
, gchar
*color
), GtkIMHtmlButtons button
)
4778 g_free(*edit_field
);
4781 if (value
&& strcmp(value
, "") != 0)
4783 *edit_field
= g_strdup(value
);
4785 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
)) {
4786 remove_func(imhtml
, &start
, &end
, imhtml
->wbfo
);
4787 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4788 find_func(imhtml
, *edit_field
), &start
, &end
);
4793 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4794 remove_func(imhtml
, &start
, &end
, TRUE
); /* 'TRUE' or 'imhtml->wbfo'? */
4797 object
= g_object_ref(G_OBJECT(imhtml
));
4798 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4799 g_object_unref(object
);
4801 return *edit_field
!= NULL
;
4804 gboolean
gtk_imhtml_toggle_forecolor(GtkIMHtml
*imhtml
, const char *color
)
4806 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.forecolor
,
4807 remove_font_forecolor
, find_font_forecolor_tag
,
4808 GTK_IMHTML_FORECOLOR
);
4811 gboolean
gtk_imhtml_toggle_backcolor(GtkIMHtml
*imhtml
, const char *color
)
4813 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.backcolor
,
4814 remove_font_backcolor
, find_font_backcolor_tag
,
4815 GTK_IMHTML_BACKCOLOR
);
4818 gboolean
gtk_imhtml_toggle_background(GtkIMHtml
*imhtml
, const char *color
)
4820 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.background
,
4821 remove_font_background
, find_font_background_tag
,
4822 GTK_IMHTML_BACKGROUND
);
4825 gboolean
gtk_imhtml_toggle_fontface(GtkIMHtml
*imhtml
, const char *face
)
4827 return gtk_imhtml_toggle_str_tag(imhtml
, face
, &imhtml
->edit
.fontface
,
4828 remove_font_face
, find_font_face_tag
,
4832 void gtk_imhtml_toggle_link(GtkIMHtml
*imhtml
, const char *url
)
4835 GtkTextIter start
, end
;
4836 GtkTextTag
*linktag
;
4837 static guint linkno
= 0;
4839 GdkColor
*color
= NULL
;
4841 imhtml
->edit
.link
= NULL
;
4844 g_snprintf(str
, sizeof(str
), "LINK %d", linkno
++);
4847 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-color", &color
, NULL
);
4849 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", color
, "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4850 gdk_color_free(color
);
4852 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4854 g_object_set_data_full(G_OBJECT(linktag
), "link_url", g_strdup(url
), g_free
);
4855 g_signal_connect(G_OBJECT(linktag
), "event", G_CALLBACK(tag_event
), NULL
);
4857 if (imhtml
->editable
&& gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
4858 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4859 gtk_text_buffer_apply_tag(imhtml
->text_buffer
, linktag
, &start
, &end
);
4863 object
= g_object_ref(G_OBJECT(imhtml
));
4864 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_LINK
);
4865 g_object_unref(object
);
4868 void gtk_imhtml_insert_link(GtkIMHtml
*imhtml
, GtkTextMark
*mark
, const char *url
, const char *text
)
4872 /* Delete any currently selected text */
4873 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4875 gtk_imhtml_toggle_link(imhtml
, url
);
4876 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4877 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
4878 gtk_imhtml_toggle_link(imhtml
, NULL
);
4881 void gtk_imhtml_insert_smiley(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
)
4886 /* Delete any currently selected text */
4887 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4889 mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
4891 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4892 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
4893 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, smiley
, &iter
);
4894 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
4898 image_expose(GtkWidget
*widget
, GdkEventExpose
*event
, gpointer user_data
)
4900 GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget
))->expose_event(widget
, event
);
4905 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
4906 static void animated_smiley_destroy_cb(GtkObject
*widget
, GtkIMHtml
*imhtml
)
4908 GList
*l
= imhtml
->animations
->head
;
4910 GList
*next
= l
->next
;
4911 if (l
->data
== widget
) {
4912 if (l
== imhtml
->animations
->tail
)
4913 imhtml
->animations
->tail
= imhtml
->animations
->tail
->prev
;
4914 imhtml
->animations
->head
= g_list_delete_link(imhtml
->animations
->head
, l
);
4915 imhtml
->num_animations
--;
4921 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
, GtkTextIter
*iter
)
4923 GdkPixbuf
*pixbuf
= NULL
;
4924 GdkPixbufAnimation
*annipixbuf
= NULL
;
4925 GtkWidget
*icon
= NULL
;
4926 GtkTextChildAnchor
*anchor
= NULL
;
4928 GtkIMHtmlSmiley
*imhtml_smiley
;
4929 GtkWidget
*ebox
= NULL
;
4930 int numsmileys_thismsg
, numsmileys_total
;
4933 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4934 * add any more. We do this for performance reasons, because smileys
4935 * are apparently pretty inefficient. Hopefully we can remove this
4936 * restriction when we're using a better HTML widget.
4938 unescaped
= purple_unescape_html(smiley
);
4939 numsmileys_thismsg
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg"));
4940 if (numsmileys_thismsg
>= 30) {
4941 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
4945 numsmileys_total
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total"));
4946 if (numsmileys_total
>= 300) {
4947 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
4952 imhtml_smiley
= gtk_imhtml_smiley_get(imhtml
, sml
, unescaped
);
4954 if (imhtml
->format_functions
& GTK_IMHTML_SMILEY
) {
4955 annipixbuf
= imhtml_smiley
? gtk_smiley_get_image(imhtml_smiley
) : NULL
;
4957 if (gdk_pixbuf_animation_is_static_image(annipixbuf
)) {
4958 pixbuf
= gdk_pixbuf_animation_get_static_image(annipixbuf
);
4960 icon
= gtk_image_new_from_pixbuf(pixbuf
);
4962 icon
= gtk_image_new_from_animation(annipixbuf
);
4963 if (imhtml
->num_animations
== 20) {
4964 GtkImage
*image
= GTK_IMAGE(g_queue_pop_head(imhtml
->animations
));
4965 GdkPixbufAnimation
*anim
= gtk_image_get_animation(image
);
4966 g_signal_handlers_disconnect_matched(G_OBJECT(image
), G_SIGNAL_MATCH_FUNC
,
4967 0, 0, NULL
, G_CALLBACK(animated_smiley_destroy_cb
), NULL
);
4969 GdkPixbuf
*pb
= gdk_pixbuf_animation_get_static_image(anim
);
4971 GdkPixbuf
*copy
= gdk_pixbuf_copy(pb
);
4972 gtk_image_set_from_pixbuf(image
, copy
);
4973 g_object_unref(G_OBJECT(copy
));
4977 imhtml
->num_animations
++;
4979 g_signal_connect(G_OBJECT(icon
), "destroy", G_CALLBACK(animated_smiley_destroy_cb
), imhtml
);
4980 g_queue_push_tail(imhtml
->animations
, icon
);
4985 if (imhtml_smiley
&& imhtml_smiley
->flags
& GTK_IMHTML_SMILEY_CUSTOM
) {
4986 ebox
= gtk_event_box_new();
4987 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
4988 gtk_widget_show(ebox
);
4992 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
4993 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
4994 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
4995 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
4997 /* This catches the expose events generated by animated
4998 * images, and ensures that they are handled by the image
4999 * itself, without propagating to the textview and causing
5000 * a complete refresh */
5001 g_signal_connect(G_OBJECT(icon
), "expose-event", G_CALLBACK(image_expose
), NULL
);
5003 gtk_widget_show(icon
);
5005 gtk_container_add(GTK_CONTAINER(ebox
), icon
);
5006 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
? ebox
: icon
, anchor
);
5008 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg
+ 1));
5009 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total
+ 1));
5010 } else if (imhtml_smiley
!= NULL
&& (imhtml
->format_functions
& GTK_IMHTML_SMILEY
)) {
5011 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
5012 imhtml_smiley
->anchors
= g_slist_append(imhtml_smiley
->anchors
, g_object_ref(anchor
));
5014 GtkWidget
*img
= gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE
, GTK_ICON_SIZE_MENU
);
5015 gtk_container_add(GTK_CONTAINER(ebox
), img
);
5016 gtk_widget_show(img
);
5017 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
5018 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
5019 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
5020 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
, anchor
);
5023 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg
+ 1));
5024 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total
+ 1));
5026 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
5030 g_signal_connect(G_OBJECT(ebox
), "event", G_CALLBACK(gtk_imhtml_smiley_clicked
), imhtml_smiley
);
5036 void gtk_imhtml_insert_image_at_iter(GtkIMHtml
*imhtml
, int id
, GtkTextIter
*iter
)
5038 GdkPixbufAnimation
*anim
= NULL
;
5039 const char *filename
= NULL
;
5042 GtkIMHtmlScalable
*scalable
= NULL
;
5043 struct scalable_data
*sd
;
5046 if (!imhtml
->funcs
|| !imhtml
->funcs
->image_get
||
5047 !imhtml
->funcs
->image_get_size
|| !imhtml
->funcs
->image_get_data
||
5048 !imhtml
->funcs
->image_get_filename
|| !imhtml
->funcs
->image_ref
||
5049 !imhtml
->funcs
->image_unref
)
5052 image
= imhtml
->funcs
->image_get(id
);
5058 data
= imhtml
->funcs
->image_get_data(image
);
5059 len
= imhtml
->funcs
->image_get_size(image
);
5061 anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5066 struct im_image_data
*t
= g_new(struct im_image_data
, 1);
5067 filename
= imhtml
->funcs
->image_get_filename(image
);
5068 imhtml
->funcs
->image_ref(id
);
5070 t
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5071 imhtml
->im_images
= g_slist_prepend(imhtml
->im_images
, t
);
5072 scalable
= gtk_imhtml_animation_new(anim
, filename
, id
);
5073 g_object_unref(G_OBJECT(anim
));
5076 pixbuf
= gtk_widget_render_icon(GTK_WIDGET(imhtml
), GTK_STOCK_MISSING_IMAGE
,
5077 GTK_ICON_SIZE_BUTTON
, "gtkimhtml-missing-image");
5078 scalable
= gtk_imhtml_image_new(pixbuf
, filename
, id
);
5079 g_object_unref(G_OBJECT(pixbuf
));
5082 sd
= g_new(struct scalable_data
, 1);
5083 sd
->scalable
= scalable
;
5084 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5085 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
5086 scalable
->add_to(scalable
, imhtml
, iter
);
5087 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
5088 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
5089 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
5090 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
5093 static const gchar
*tag_to_html_start(GtkTextTag
*tag
)
5096 static gchar buf
[1024];
5099 g_return_val_if_fail(name
!= NULL
, "");
5101 if (strcmp(name
, "BOLD") == 0) {
5103 } else if (strcmp(name
, "ITALICS") == 0) {
5105 } else if (strcmp(name
, "UNDERLINE") == 0) {
5107 } else if (strcmp(name
, "STRIKE") == 0) {
5109 } else if (strncmp(name
, "LINK ", 5) == 0) {
5110 char *tmp
= g_object_get_data(G_OBJECT(tag
), "link_url");
5112 g_snprintf(buf
, sizeof(buf
), "<a href=\"%s\">", tmp
);
5113 buf
[sizeof(buf
)-1] = '\0';
5118 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5119 g_snprintf(buf
, sizeof(buf
), "<font color=\"%s\">", &name
[10]);
5121 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5122 g_snprintf(buf
, sizeof(buf
), "<font back=\"%s\">", &name
[10]);
5124 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5125 g_snprintf(buf
, sizeof(buf
), "<body bgcolor=\"%s\">", &name
[11]);
5127 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5128 g_snprintf(buf
, sizeof(buf
), "<font face=\"%s\">", &name
[10]);
5130 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5131 g_snprintf(buf
, sizeof(buf
), "<font size=\"%s\">", &name
[10]);
5137 GdkColor
*color
= NULL
;
5138 GObject
*obj
= G_OBJECT(tag
);
5139 gboolean empty
= TRUE
;
5141 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "<span style='");
5144 g_object_get(obj
, "weight-set", &isset
, "weight", &ivalue
, NULL
);
5146 const char *weight
= "";
5147 if (ivalue
>= PANGO_WEIGHT_ULTRABOLD
)
5149 else if (ivalue
>= PANGO_WEIGHT_BOLD
)
5151 else if (ivalue
>= PANGO_WEIGHT_NORMAL
)
5156 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "font-weight: %s;", weight
);
5160 /* Foreground color */
5161 g_object_get(obj
, "foreground-set", &isset
, "foreground-gdk", &color
, NULL
);
5162 if (isset
&& color
) {
5163 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5164 "color: #%02x%02x%02x;",
5165 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5168 gdk_color_free(color
);
5170 /* Background color */
5171 g_object_get(obj
, "background-set", &isset
, "background-gdk", &color
, NULL
);
5172 if (isset
&& color
) {
5173 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5174 "background: #%02x%02x%02x;",
5175 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5178 gdk_color_free(color
);
5181 g_object_get(obj
, "underline-set", &isset
, "underline", &ivalue
, NULL
);
5184 case PANGO_UNDERLINE_NONE
:
5185 case PANGO_UNDERLINE_ERROR
:
5188 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "text-decoration: underline;");
5193 g_snprintf(str
, sizeof(buf
) - (str
- buf
), "'>");
5195 return (empty
? "" : buf
);
5199 static const gchar
*tag_to_html_end(GtkTextTag
*tag
)
5204 g_return_val_if_fail(name
!= NULL
, "");
5206 if (strcmp(name
, "BOLD") == 0) {
5208 } else if (strcmp(name
, "ITALICS") == 0) {
5210 } else if (strcmp(name
, "UNDERLINE") == 0) {
5212 } else if (strcmp(name
, "STRIKE") == 0) {
5214 } else if (strncmp(name
, "LINK ", 5) == 0) {
5216 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5218 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5220 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5222 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5224 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5227 const char *props
[] = {"weight-set", "foreground-set", "background-set",
5228 "size-set", "underline-set", NULL
};
5230 for (i
= 0; props
[i
]; i
++) {
5231 gboolean set
= FALSE
;
5232 g_object_get(G_OBJECT(tag
), props
[i
], &set
, NULL
);
5245 } PidginTextTagData
;
5247 static PidginTextTagData
*text_tag_data_new(GtkTextTag
*tag
)
5249 const char *start
, *end
;
5250 PidginTextTagData
*ret
= NULL
;
5252 start
= tag_to_html_start(tag
);
5253 if (!start
|| !*start
)
5255 end
= tag_to_html_end(tag
);
5259 ret
= g_new0(PidginTextTagData
, 1);
5260 ret
->start
= g_strdup(start
);
5261 ret
->end
= g_strdup(end
);
5266 static void text_tag_data_destroy(PidginTextTagData
*data
)
5268 g_free(data
->start
);
5273 static gboolean
tag_ends_here(GtkTextTag
*tag
, GtkTextIter
*iter
, GtkTextIter
*niter
)
5275 return ((gtk_text_iter_has_tag(iter
, GTK_TEXT_TAG(tag
)) &&
5276 !gtk_text_iter_has_tag(niter
, GTK_TEXT_TAG(tag
))) ||
5277 gtk_text_iter_is_end(niter
));
5280 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5281 * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else
5282 * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert
5283 * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5284 * Finally, replace <, >, &, and " with their HTML equivalent.
5286 char *gtk_imhtml_get_markup_range(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
5289 GtkTextIter iter
, next_iter
, non_neutral_iter
;
5290 gboolean is_rtl_message
= FALSE
;
5291 GString
*str
= g_string_new("");
5295 PidginTextTagData
*tagdata
;
5299 gtk_text_iter_order(start
, end
);
5300 non_neutral_iter
= next_iter
= iter
= *start
;
5301 gtk_text_iter_forward_char(&next_iter
);
5303 /* Bi-directional text support */
5304 /* Get to the first non-neutral character */
5306 while ((PANGO_DIRECTION_NEUTRAL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
)))
5307 && gtk_text_iter_forward_char(&non_neutral_iter
));
5308 if (PANGO_DIRECTION_RTL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
))) {
5309 is_rtl_message
= TRUE
;
5310 g_string_append(str
, "<SPAN style=\"direction:rtl;text-align:right;\">");
5314 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
5315 tags
= gtk_text_iter_get_tags(start
);
5317 for (sl
= tags
; sl
; sl
= sl
->next
) {
5319 if (!gtk_text_iter_toggles_tag(start
, GTK_TEXT_TAG(tag
))) {
5320 PidginTextTagData
*data
= text_tag_data_new(tag
);
5322 g_string_append(str
, data
->start
);
5323 g_queue_push_tail(q
, data
);
5329 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, end
)) {
5331 tags
= gtk_text_iter_get_tags(&iter
);
5333 for (sl
= tags
; sl
; sl
= sl
->next
) {
5335 if (gtk_text_iter_begins_tag(&iter
, GTK_TEXT_TAG(tag
))) {
5336 PidginTextTagData
*data
= text_tag_data_new(tag
);
5338 g_string_append(str
, data
->start
);
5339 g_queue_push_tail(q
, data
);
5345 GtkTextChildAnchor
* anchor
= gtk_text_iter_get_child_anchor(&iter
);
5347 char *text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_htmltext");
5349 str
= g_string_append(str
, text
);
5351 } else if (c
== '<') {
5352 str
= g_string_append(str
, "<");
5353 } else if (c
== '>') {
5354 str
= g_string_append(str
, ">");
5355 } else if (c
== '&') {
5356 str
= g_string_append(str
, "&");
5357 } else if (c
== '"') {
5358 str
= g_string_append(str
, """);
5359 } else if (c
== '\n') {
5360 str
= g_string_append(str
, "<br>");
5362 str
= g_string_append_unichar(str
, c
);
5365 tags
= g_slist_reverse(tags
);
5366 for (sl
= tags
; sl
; sl
= sl
->next
) {
5368 /** don't worry about non-printing tags ending */
5369 if (tag_ends_here(tag
, &iter
, &next_iter
) &&
5370 strlen(tag_to_html_end(tag
)) > 0 &&
5371 strlen(tag_to_html_start(tag
)) > 0) {
5373 PidginTextTagData
*tmp
;
5374 GQueue
*r
= g_queue_new();
5376 while ((tmp
= g_queue_pop_tail(q
)) && tmp
->tag
!= tag
) {
5377 g_string_append(str
, tmp
->end
);
5378 if (!tag_ends_here(tmp
->tag
, &iter
, &next_iter
))
5379 g_queue_push_tail(r
, tmp
);
5381 text_tag_data_destroy(tmp
);
5385 g_string_append(str
, tmp
->end
);
5386 text_tag_data_destroy(tmp
);
5388 #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 */
5390 purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5393 while ((tmp
= g_queue_pop_head(r
))) {
5394 g_string_append(str
, tmp
->start
);
5395 g_queue_push_tail(q
, tmp
);
5402 gtk_text_iter_forward_char(&iter
);
5403 gtk_text_iter_forward_char(&next_iter
);
5406 while ((tagdata
= g_queue_pop_tail(q
))) {
5407 g_string_append(str
, tagdata
->end
);
5408 text_tag_data_destroy(tagdata
);
5411 /* Bi-directional text support - close tags */
5413 g_string_append(str
, "</SPAN>");
5416 return g_string_free(str
, FALSE
);
5419 void gtk_imhtml_close_tags(GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
5421 if (imhtml
->edit
.bold
)
5422 gtk_imhtml_toggle_bold(imhtml
);
5424 if (imhtml
->edit
.italic
)
5425 gtk_imhtml_toggle_italic(imhtml
);
5427 if (imhtml
->edit
.underline
)
5428 gtk_imhtml_toggle_underline(imhtml
);
5430 if (imhtml
->edit
.strike
)
5431 gtk_imhtml_toggle_strike(imhtml
);
5433 if (imhtml
->edit
.forecolor
)
5434 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
5436 if (imhtml
->edit
.backcolor
)
5437 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
5439 if (imhtml
->edit
.fontface
)
5440 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
5442 imhtml
->edit
.fontsize
= 0;
5444 if (imhtml
->edit
.link
)
5445 gtk_imhtml_toggle_link(imhtml
, NULL
);
5448 char *gtk_imhtml_get_markup(GtkIMHtml
*imhtml
)
5450 GtkTextIter start
, end
;
5452 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5453 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5454 return gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5457 char **gtk_imhtml_get_markup_lines(GtkIMHtml
*imhtml
)
5460 GtkTextIter start
, end
;
5463 lines
= gtk_text_buffer_get_line_count(imhtml
->text_buffer
);
5464 ret
= g_new0(char *, lines
+ 1);
5465 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5467 gtk_text_iter_forward_to_line_end(&end
);
5469 for (i
= 0, j
= 0; i
< lines
; i
++) {
5470 if (gtk_text_iter_get_char(&start
) != '\n') {
5471 ret
[j
] = gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5476 gtk_text_iter_forward_line(&start
);
5478 gtk_text_iter_forward_to_line_end(&end
);
5484 char *gtk_imhtml_get_text(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*stop
)
5486 GString
*str
= g_string_new("");
5487 GtkTextIter iter
, end
;
5491 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &iter
);
5496 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5500 gtk_text_iter_order(&iter
, &end
);
5502 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, &end
)) {
5504 GtkTextChildAnchor
* anchor
;
5507 anchor
= gtk_text_iter_get_child_anchor(&iter
);
5509 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
5511 str
= g_string_append(str
, text
);
5513 g_string_append_unichar(str
, c
);
5515 gtk_text_iter_forward_char(&iter
);
5518 return g_string_free(str
, FALSE
);
5521 void gtk_imhtml_set_funcs(GtkIMHtml
*imhtml
, GtkIMHtmlFuncs
*f
)
5523 g_return_if_fail(imhtml
!= NULL
);
5527 void gtk_imhtml_setup_entry(GtkIMHtml
*imhtml
, PurpleConnectionFlags flags
)
5529 GtkIMHtmlButtons buttons
;
5531 if (flags
& PURPLE_CONNECTION_HTML
) {
5533 GdkColor fg_color
, bg_color
;
5535 buttons
= GTK_IMHTML_ALL
;
5537 if (flags
& PURPLE_CONNECTION_NO_BGCOLOR
)
5538 buttons
&= ~GTK_IMHTML_BACKCOLOR
;
5539 if (flags
& PURPLE_CONNECTION_NO_FONTSIZE
)
5541 buttons
&= ~GTK_IMHTML_GROW
;
5542 buttons
&= ~GTK_IMHTML_SHRINK
;
5544 if (flags
& PURPLE_CONNECTION_NO_URLDESC
)
5545 buttons
&= ~GTK_IMHTML_LINKDESC
;
5547 gtk_imhtml_set_format_functions(imhtml
, GTK_IMHTML_ALL
);
5548 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold") != imhtml
->edit
.bold
)
5549 gtk_imhtml_toggle_bold(imhtml
);
5551 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic") != imhtml
->edit
.italic
)
5552 gtk_imhtml_toggle_italic(imhtml
);
5554 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline") != imhtml
->edit
.underline
)
5555 gtk_imhtml_toggle_underline(imhtml
);
5557 gtk_imhtml_toggle_fontface(imhtml
,
5558 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/font_face"));
5560 if (!(flags
& PURPLE_CONNECTION_NO_FONTSIZE
))
5562 int size
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/font_size");
5564 /* 3 is the default. */
5566 gtk_imhtml_font_set_size(imhtml
, size
);
5569 if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"), "") != 0)
5571 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"),
5573 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5575 fg_color
.green
/ 256,
5576 fg_color
.blue
/ 256);
5580 gtk_imhtml_toggle_forecolor(imhtml
, color
);
5582 if(!(flags
& PURPLE_CONNECTION_NO_BGCOLOR
) &&
5583 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"), "") != 0)
5585 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"),
5587 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5589 bg_color
.green
/ 256,
5590 bg_color
.blue
/ 256);
5594 gtk_imhtml_toggle_background(imhtml
, color
);
5596 if (flags
& PURPLE_CONNECTION_FORMATTING_WBFO
)
5597 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, TRUE
);
5599 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, FALSE
);
5601 buttons
= GTK_IMHTML_SMILEY
| GTK_IMHTML_IMAGE
;
5602 imhtml_clear_formatting(imhtml
);
5605 if (flags
& PURPLE_CONNECTION_NO_IMAGES
)
5606 buttons
&= ~GTK_IMHTML_IMAGE
;
5608 if (flags
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
5609 buttons
|= GTK_IMHTML_CUSTOM_SMILEY
;
5611 buttons
&= ~GTK_IMHTML_CUSTOM_SMILEY
;
5613 gtk_imhtml_set_format_functions(imhtml
, buttons
);
5617 * GtkIMHtmlSmiley functions
5619 static void gtk_custom_smiley_allocated(GdkPixbufLoader
*loader
, gpointer user_data
)
5621 GtkIMHtmlSmiley
*smiley
;
5623 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5624 smiley
->icon
= gdk_pixbuf_loader_get_animation(loader
);
5627 g_object_ref(G_OBJECT(smiley
->icon
));
5628 #ifdef DEBUG_CUSTOM_SMILEY
5629 purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley
->icon
, smiley
->smile
);
5633 static void gtk_custom_smiley_closed(GdkPixbufLoader
*loader
, gpointer user_data
)
5635 GtkIMHtmlSmiley
*smiley
;
5636 GtkWidget
*icon
= NULL
;
5637 GtkTextChildAnchor
*anchor
= NULL
;
5638 GSList
*current
= NULL
;
5640 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5641 if (!smiley
->imhtml
) {
5642 #ifdef DEBUG_CUSTOM_SMILEY
5643 purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley
);
5645 g_object_unref(G_OBJECT(loader
));
5646 smiley
->loader
= NULL
;
5650 for (current
= smiley
->anchors
; current
; current
= g_slist_next(current
)) {
5651 anchor
= GTK_TEXT_CHILD_ANCHOR(current
->data
);
5652 if (gtk_text_child_anchor_get_deleted(anchor
))
5655 icon
= gtk_image_new_from_animation(smiley
->icon
);
5657 #ifdef DEBUG_CUSTOM_SMILEY
5658 purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5659 icon
, smiley
->icon
, smiley
->smile
);
5663 gtk_widget_show(icon
);
5665 wids
= gtk_text_child_anchor_get_widgets(anchor
);
5667 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", purple_unescape_html(smiley
->smile
), g_free
);
5668 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
->smile
), g_free
);
5670 if (smiley
->imhtml
) {
5672 GList
*children
= gtk_container_get_children(GTK_CONTAINER(wids
->data
));
5673 g_list_foreach(children
, (GFunc
)gtk_widget_destroy
, NULL
);
5674 g_list_free(children
);
5675 gtk_container_add(GTK_CONTAINER(wids
->data
), icon
);
5677 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley
->imhtml
), icon
, anchor
);
5681 g_object_unref(anchor
);
5684 g_slist_free(smiley
->anchors
);
5685 smiley
->anchors
= NULL
;
5687 g_object_unref(G_OBJECT(loader
));
5688 smiley
->loader
= NULL
;
5692 gtk_custom_smiley_size_prepared(GdkPixbufLoader
*loader
, gint width
, gint height
, gpointer data
)
5694 #define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */
5695 if (width
<= CUSTOM_SMILEY_SIZE
&& height
<= CUSTOM_SMILEY_SIZE
)
5698 if (width
>= height
) {
5699 height
= height
* CUSTOM_SMILEY_SIZE
/ width
;
5700 width
= CUSTOM_SMILEY_SIZE
;
5702 width
= width
* CUSTOM_SMILEY_SIZE
/ height
;
5703 height
= CUSTOM_SMILEY_SIZE
;
5706 gdk_pixbuf_loader_set_size(loader
, width
, height
);
5710 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley
*smiley
)
5713 g_object_unref(smiley
->icon
);
5715 g_object_unref(smiley
->loader
); /* XXX: does this crash? */
5717 smiley
->icon
= NULL
;
5718 smiley
->loader
= NULL
;
5721 /* We do not use the pixbuf loader for a smiley that can be loaded
5722 * from a file. (e.g., local custom smileys)
5727 smiley
->loader
= gdk_pixbuf_loader_new();
5729 g_signal_connect(smiley
->loader
, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated
), smiley
);
5730 g_signal_connect(smiley
->loader
, "closed", G_CALLBACK(gtk_custom_smiley_closed
), smiley
);
5731 g_signal_connect(smiley
->loader
, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared
), smiley
);
5734 GtkIMHtmlSmiley
*gtk_imhtml_smiley_create(const char *file
, const char *shortcut
, gboolean hide
,
5735 GtkIMHtmlSmileyFlags flags
)
5737 GtkIMHtmlSmiley
*smiley
= g_new0(GtkIMHtmlSmiley
, 1);
5738 smiley
->file
= g_strdup(file
);
5739 smiley
->smile
= g_strdup(shortcut
);
5740 smiley
->hidden
= hide
;
5741 smiley
->flags
= flags
;
5742 smiley
->imhtml
= NULL
;
5743 gtk_imhtml_smiley_reload(smiley
);
5747 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley
*smiley
)
5749 gtk_imhtml_disassociate_smiley(smiley
);
5750 g_free(smiley
->smile
);
5751 g_free(smiley
->file
);
5753 g_object_unref(smiley
->icon
);
5755 g_object_unref(smiley
->loader
);
5756 g_free(smiley
->data
);
5760 gboolean
gtk_imhtml_class_register_protocol(const char *name
,
5761 gboolean (*activate
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
),
5762 gboolean (*context_menu
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
))
5764 GtkIMHtmlClass
*klass
;
5765 GtkIMHtmlProtocol
*proto
;
5767 g_return_val_if_fail(name
, FALSE
);
5769 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
5770 g_return_val_if_fail(klass
, FALSE
);
5772 if ((proto
= imhtml_find_protocol(name
, TRUE
))) {
5776 klass
->protocols
= g_list_remove(klass
->protocols
, proto
);
5777 g_free(proto
->name
);
5780 } else if (!activate
) {
5784 proto
= g_new0(GtkIMHtmlProtocol
, 1);
5785 proto
->name
= g_strdup(name
);
5786 proto
->length
= strlen(name
);
5787 proto
->activate
= activate
;
5788 proto
->context_menu
= context_menu
;
5789 klass
->protocols
= g_list_prepend(klass
->protocols
, proto
);
5795 gtk_imhtml_activate_tag(GtkIMHtml
*imhtml
, GtkTextTag
*tag
)
5797 /* A link was clicked--we emit the "url_clicked" signal
5798 * with the URL as the argument */
5799 g_object_ref(G_OBJECT(tag
));
5800 g_signal_emit(imhtml
, signals
[URL_CLICKED
], 0, g_object_get_data(G_OBJECT(tag
), "link_url"));
5801 g_object_unref(G_OBJECT(tag
));
5802 g_object_set_data(G_OBJECT(tag
), "visited", GINT_TO_POINTER(TRUE
));
5803 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), tag
);
5806 gboolean
gtk_imhtml_link_activate(GtkIMHtmlLink
*link
)
5808 g_return_val_if_fail(link
, FALSE
);
5811 gtk_imhtml_activate_tag(link
->imhtml
, link
->tag
);
5812 } else if (link
->url
) {
5813 g_signal_emit(link
->imhtml
, signals
[URL_CLICKED
], 0, link
->url
);
5819 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink
*link
)
5824 const GtkTextTag
* gtk_imhtml_link_get_text_tag(GtkIMHtmlLink
*link
)
5829 static gboolean
return_add_newline_cb(GtkWidget
*widget
, gpointer data
)
5831 GtkTextBuffer
*buffer
;
5835 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget
));
5837 /* Delete any currently selected text */
5838 gtk_text_buffer_delete_selection(buffer
, TRUE
, TRUE
);
5840 /* Insert a newline at the current cursor position */
5841 mark
= gtk_text_buffer_get_insert(buffer
);
5842 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
5843 gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget
), "\n", 0, &iter
);
5846 * If we just newlined ourselves past the end of the visible area
5847 * then scroll down so the cursor is in view.
5849 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget
),
5850 gtk_text_buffer_get_insert(buffer
),
5851 0, FALSE
, 0.0, 0.0);
5857 * It's kind of a pain that we need this function and the above just
5858 * to reinstate the default GtkTextView behavior. It might be better
5859 * if GtkIMHtml didn't intercept the enter key and just required the
5860 * application to deal with it--it's really not much more work than it
5861 * is to connect to the current "message_send" signal.
5863 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml
*imhtml
)
5865 g_signal_connect(G_OBJECT(imhtml
), "message_send",
5866 G_CALLBACK(return_add_newline_cb
), NULL
);
5869 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml
*imhtml
, gboolean populate
)
5872 signal_id
= g_signal_handler_find(imhtml
->text_buffer
,
5873 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_UNBLOCKED
, 0, 0, NULL
,
5874 mark_set_so_update_selection_cb
, NULL
);
5877 /* We didn't find an unblocked signal handler, which means there
5878 is a blocked handler. Now unblock it.
5879 This is necessary to avoid a mutex-lock when the debug message
5880 saying 'no handler is blocked' is printed in the debug window.
5883 g_signal_handlers_unblock_matched(imhtml
->text_buffer
,
5884 G_SIGNAL_MATCH_FUNC
, 0, 0, NULL
,
5885 mark_set_so_update_selection_cb
, NULL
);
5888 /* Block only if we found an unblocked handler */
5890 g_signal_handler_block(imhtml
->text_buffer
, signal_id
);