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 /* We set the text in a separate function call so we can specify a
518 max length. This is important so the tooltip isn't too wide for
519 the screen, and also because some X library function exits the
520 process when it can't allocate enough memory for a super wide
522 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, NULL
);
523 pango_layout_set_text(layout
, imhtml
->tip
, 200);
525 gtk_paint_flat_box (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
,
526 GTK_STATE_NORMAL
, GTK_SHADOW_OUT
, NULL
, imhtml
->tip_window
,
527 "tooltip", 0, 0, -1, -1);
529 gtk_paint_layout (imhtml
->tip_window
->style
, imhtml
->tip_window
->window
, GTK_STATE_NORMAL
,
530 FALSE
, NULL
, imhtml
->tip_window
, NULL
, 4, 4, layout
);
532 g_object_unref(layout
);
537 gtk_imhtml_tip (gpointer data
)
539 GtkIMHtml
*imhtml
= data
;
540 PangoFontMetrics
*font_metrics
;
544 gint gap
, x
, y
, h
, w
, scr_w
, baseline_skip
;
546 g_return_val_if_fail(GTK_IS_IMHTML(imhtml
), FALSE
);
548 if (!imhtml
->tip
|| !GTK_WIDGET_DRAWABLE (GTK_WIDGET(imhtml
))) {
549 imhtml
->tip_timer
= 0;
553 if (imhtml
->tip_window
){
554 gtk_widget_destroy (imhtml
->tip_window
);
555 imhtml
->tip_window
= NULL
;
558 imhtml
->tip_timer
= 0;
559 imhtml
->tip_window
= gtk_window_new (GTK_WINDOW_POPUP
);
560 gtk_widget_set_app_paintable (imhtml
->tip_window
, TRUE
);
561 gtk_window_set_title(GTK_WINDOW(imhtml
->tip_window
), "GtkIMHtml");
562 gtk_window_set_resizable (GTK_WINDOW (imhtml
->tip_window
), FALSE
);
563 gtk_widget_set_name (imhtml
->tip_window
, "gtk-tooltips");
564 gtk_window_set_type_hint (GTK_WINDOW (imhtml
->tip_window
),
565 GDK_WINDOW_TYPE_HINT_TOOLTIP
);
566 g_signal_connect_swapped (G_OBJECT (imhtml
->tip_window
), "expose_event",
567 G_CALLBACK (gtk_imhtml_tip_paint
), imhtml
);
569 gtk_widget_ensure_style (imhtml
->tip_window
);
571 /* We set the text in a separate function call so we can specify a
572 max length. This is important so the tooltip isn't too wide for
573 the screen, and also because some X library function exits the
574 process when it can't allocate enough memory for a super wide
576 layout
= gtk_widget_create_pango_layout(imhtml
->tip_window
, NULL
);
577 pango_layout_set_text(layout
, imhtml
->tip
, 200);
579 font
= pango_context_load_font(pango_layout_get_context(layout
),
580 imhtml
->tip_window
->style
->font_desc
);
583 char *tmp
= pango_font_description_to_string(
584 imhtml
->tip_window
->style
->font_desc
);
586 purple_debug(PURPLE_DEBUG_ERROR
, "gtk_imhtml_tip",
587 "pango_context_load_font() couldn't load font: '%s'\n",
591 g_object_unref(layout
);
595 font_metrics
= pango_font_get_metrics(font
, NULL
);
597 pango_layout_get_pixel_size(layout
, &scr_w
, NULL
);
598 gap
= PANGO_PIXELS((pango_font_metrics_get_ascent(font_metrics
) +
599 pango_font_metrics_get_descent(font_metrics
))/ 4);
603 baseline_skip
= PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
604 pango_font_metrics_get_descent(font_metrics
));
606 h
= 8 + baseline_skip
;
608 gdk_window_get_pointer (NULL
, &x
, &y
, NULL
);
609 if (GTK_WIDGET_NO_WINDOW (GTK_WIDGET(imhtml
)))
610 y
+= GTK_WIDGET(imhtml
)->allocation
.y
;
612 scr_w
= gdk_screen_width();
617 x
-= (x
+ w
) - scr_w
;
621 y
= y
+ PANGO_PIXELS(pango_font_metrics_get_ascent(font_metrics
) +
622 pango_font_metrics_get_descent(font_metrics
));
624 gtk_widget_set_size_request (imhtml
->tip_window
, w
, h
);
625 gtk_window_move (GTK_WINDOW(imhtml
->tip_window
), x
, y
);
626 gtk_widget_show (imhtml
->tip_window
);
628 pango_font_metrics_unref(font_metrics
);
629 g_object_unref(font
);
630 g_object_unref(layout
);
636 gtk_motion_event_notify(GtkWidget
*imhtml
, GdkEventMotion
*event
, gpointer data
)
639 GdkWindow
*win
= event
->window
;
642 GSList
*tags
= NULL
, *templist
= NULL
;
643 GtkTextTag
*tag
= NULL
, *oldprelit_tag
;
644 GtkTextChildAnchor
* anchor
;
645 gboolean hand
= TRUE
;
646 GdkCursor
*cursor
= NULL
;
648 oldprelit_tag
= GTK_IMHTML(imhtml
)->prelit_tag
;
650 gdk_window_get_pointer(GTK_WIDGET(imhtml
)->window
, NULL
, NULL
, NULL
);
651 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
), GTK_TEXT_WINDOW_WIDGET
,
652 event
->x
, event
->y
, &x
, &y
);
653 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
654 tags
= gtk_text_iter_get_tags(&iter
);
658 tag
= templist
->data
;
659 tip
= g_object_get_data(G_OBJECT(tag
), "link_url");
662 templist
= templist
->next
;
665 if (tip
&& (!tag
|| !g_object_get_data(G_OBJECT(tag
), "visited"))) {
666 GTK_IMHTML(imhtml
)->prelit_tag
= tag
;
667 if (tag
!= oldprelit_tag
) {
668 GdkColor
*pre
= NULL
;
669 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-prelight-color", &pre
, NULL
);
671 g_object_set(G_OBJECT(tag
), "foreground-gdk", pre
, NULL
);
674 g_object_set(G_OBJECT(tag
), "foreground", "#70a0ff", NULL
);
677 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
680 if ((oldprelit_tag
!= NULL
) && (GTK_IMHTML(imhtml
)->prelit_tag
!= oldprelit_tag
)) {
681 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), oldprelit_tag
);
684 if (GTK_IMHTML(imhtml
)->tip
) {
685 if (tip
== GTK_IMHTML(imhtml
)->tip
) {
689 /* We've left the cell. Remove the timeout and create a new one below */
690 if (GTK_IMHTML(imhtml
)->tip_window
) {
691 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
692 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
694 if (GTK_IMHTML(imhtml
)->editable
)
695 cursor
= GTK_IMHTML(imhtml
)->text_cursor
;
697 cursor
= GTK_IMHTML(imhtml
)->arrow_cursor
;
698 if (GTK_IMHTML(imhtml
)->tip_timer
)
699 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
700 GTK_IMHTML(imhtml
)->tip_timer
= 0;
703 /* If we don't have a tip from a URL, let's see if we have a tip from a smiley */
704 anchor
= gtk_text_iter_get_child_anchor(&iter
);
706 tip
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_tiptext");
711 GTK_IMHTML(imhtml
)->tip_timer
= g_timeout_add (TOOLTIP_TIMEOUT
,
712 gtk_imhtml_tip
, imhtml
);
715 for (templist
= tags
; templist
; templist
= templist
->next
) {
716 tag
= templist
->data
;
717 if ((tip
= g_object_get_data(G_OBJECT(tag
), "cursor"))) {
724 if (hand
&& !(GTK_IMHTML(imhtml
)->editable
))
725 cursor
= GTK_IMHTML(imhtml
)->hand_cursor
;
728 gdk_window_set_cursor(win
, cursor
);
730 GTK_IMHTML(imhtml
)->tip
= tip
;
736 gtk_enter_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
738 if (GTK_IMHTML(imhtml
)->editable
)
739 gdk_window_set_cursor(
740 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
741 GTK_TEXT_WINDOW_TEXT
),
742 GTK_IMHTML(imhtml
)->text_cursor
);
744 gdk_window_set_cursor(
745 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
746 GTK_TEXT_WINDOW_TEXT
),
747 GTK_IMHTML(imhtml
)->arrow_cursor
);
749 /* propagate the event normally */
754 gtk_leave_event_notify(GtkWidget
*imhtml
, GdkEventCrossing
*event
, gpointer data
)
756 /* when leaving the widget, clear any current & pending tooltips and restore the cursor */
757 if (GTK_IMHTML(imhtml
)->prelit_tag
) {
758 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), GTK_IMHTML(imhtml
)->prelit_tag
);
759 GTK_IMHTML(imhtml
)->prelit_tag
= NULL
;
762 if (GTK_IMHTML(imhtml
)->tip_window
) {
763 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
764 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
766 if (GTK_IMHTML(imhtml
)->tip_timer
) {
767 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
768 GTK_IMHTML(imhtml
)->tip_timer
= 0;
770 gdk_window_set_cursor(
771 gtk_text_view_get_window(GTK_TEXT_VIEW(imhtml
),
772 GTK_TEXT_WINDOW_TEXT
), NULL
);
774 /* propagate the event normally */
779 gtk_imhtml_expose_event (GtkWidget
*widget
,
780 GdkEventExpose
*event
)
782 GtkTextIter start
, end
, cur
;
784 GdkRectangle visible_rect
;
785 cairo_t
*cr
= gdk_cairo_create(GDK_DRAWABLE(event
->window
));
788 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget
), &visible_rect
);
789 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
790 GTK_TEXT_WINDOW_TEXT
,
796 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget
), GTK_TEXT_WINDOW_TEXT
,
797 event
->area
.x
, event
->area
.y
, &buf_x
, &buf_y
);
799 if (GTK_IMHTML(widget
)->editable
|| GTK_IMHTML(widget
)->wbfo
) {
801 if (GTK_IMHTML(widget
)->edit
.background
) {
802 gdk_color_parse(GTK_IMHTML(widget
)->edit
.background
, &gcolor
);
803 gdk_cairo_set_source_color(cr
, &gcolor
);
805 gdk_cairo_set_source_color(cr
, &(widget
->style
->base
[GTK_WIDGET_STATE(widget
)]));
809 visible_rect
.x
, visible_rect
.y
,
810 visible_rect
.width
, visible_rect
.height
);
814 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
815 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
821 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &start
, buf_x
, buf_y
);
822 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget
), &end
,
823 buf_x
+ event
->area
.width
, buf_y
+ event
->area
.height
);
825 gtk_text_iter_order(&start
, &end
);
829 while (gtk_text_iter_in_range(&cur
, &start
, &end
)) {
830 GSList
*tags
= gtk_text_iter_get_tags(&cur
);
833 for (l
= tags
; l
; l
= l
->next
) {
834 GtkTextTag
*tag
= l
->data
;
836 GdkRectangle tag_area
;
839 if (strncmp(tag
->name
, "BACKGROUND ", 11))
842 if (gtk_text_iter_ends_tag(&cur
, tag
))
845 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
846 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
847 GTK_TEXT_WINDOW_TEXT
,
852 rect
.x
= visible_rect
.x
;
854 rect
.width
= visible_rect
.width
;
857 gtk_text_iter_forward_to_tag_toggle(&cur
, tag
);
858 while (!gtk_text_iter_is_end(&cur
) && gtk_text_iter_begins_tag(&cur
, tag
));
860 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(widget
), &cur
, &tag_area
);
861 gtk_text_view_buffer_to_window_coords(GTK_TEXT_VIEW(widget
),
862 GTK_TEXT_WINDOW_TEXT
,
869 rect
.height
= tag_area
.y
+ tag_area
.height
- rect
.y
870 + gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(widget
))
871 + gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(widget
));
873 color
= tag
->name
+ 11;
875 if (!gdk_color_parse(color
, &gcolor
)) {
878 strncpy(&tmp
[1], color
, 7);
880 if (!gdk_color_parse(tmp
, &gcolor
))
881 gdk_color_parse("white", &gcolor
);
883 gdk_cairo_set_source_color(cr
, &gcolor
);
887 rect
.width
, rect
.height
);
889 gtk_text_iter_backward_char(&cur
); /* go back one, in case the end is the begining is the end
890 * note that above, we always moved cur ahead by at least
897 /* loop until another tag begins, or no tag begins */
898 while (gtk_text_iter_forward_to_tag_toggle(&cur
, NULL
) &&
899 !gtk_text_iter_is_end(&cur
) &&
900 !gtk_text_iter_begins_tag(&cur
, NULL
));
905 if (GTK_WIDGET_CLASS (parent_class
)->expose_event
)
906 return (* GTK_WIDGET_CLASS (parent_class
)->expose_event
)
913 static void paste_unformatted_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
915 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
917 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
921 static void clear_formatting_cb(GtkMenuItem
*menu
, GtkIMHtml
*imhtml
)
923 gtk_imhtml_clear_formatting(imhtml
);
926 static void disable_smiley_selected(GtkMenuItem
*item
, GtkIMHtml
*imhtml
)
928 GtkTextIter start
, end
;
932 if (!gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
))
935 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
937 mark
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
938 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
940 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, mark
);
941 gtk_imhtml_insert_html_at_iter(imhtml
, text
, GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_SMILEY
, &start
);
946 static void hijack_menu_cb(GtkIMHtml
*imhtml
, GtkMenu
*menu
, gpointer data
)
949 GtkTextIter start
, end
;
951 menuitem
= gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text"));
952 gtk_widget_show(menuitem
);
954 * TODO: gtk_clipboard_wait_is_text_available() iterates the glib
955 * mainloop, which tends to be a source of bugs. It would
956 * be good to audit this or change it to not wait.
958 gtk_widget_set_sensitive(menuitem
,
960 gtk_clipboard_wait_is_text_available(
961 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
))));
962 /* put it after "Paste" */
963 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 3);
965 g_signal_connect(G_OBJECT(menuitem
), "activate",
966 G_CALLBACK(paste_unformatted_cb
), imhtml
);
968 menuitem
= gtk_menu_item_new_with_mnemonic(_("_Reset formatting"));
969 gtk_widget_show(menuitem
);
970 gtk_widget_set_sensitive(menuitem
, imhtml
->editable
);
971 /* put it after Delete */
972 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 5);
974 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(clear_formatting_cb
), imhtml
);
976 menuitem
= gtk_menu_item_new_with_mnemonic(_("Disable _smileys in selected text"));
977 gtk_widget_show(menuitem
);
978 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
979 g_signal_connect(G_OBJECT(menuitem
), "activate", G_CALLBACK(disable_smiley_selected
), imhtml
);
981 gtk_widget_set_sensitive(menuitem
, FALSE
);
983 gtk_menu_shell_insert(GTK_MENU_SHELL(menu
), menuitem
, 6);
987 ucs2_order(gboolean swap
)
991 be
= G_BYTE_ORDER
== G_BIG_ENDIAN
;
992 be
= swap
? be
: !be
;
1001 /* Convert from UTF-16LE to UTF-8, stripping the BOM if one is present.*/
1003 utf16_to_utf8_with_bom_check(gchar
*data
, guint len
) {
1004 char *fromcode
= NULL
;
1005 GError
*error
= NULL
;
1010 * Unicode Techinical Report 20
1011 * ( http://www.unicode.org/unicode/reports/tr20/ ) says to treat an
1012 * initial 0xfeff (ZWNBSP) as a byte order indicator so that is
1013 * what we do. If there is no indicator assume it is in the default
1017 memcpy(&c
, data
, 2);
1021 fromcode
= ucs2_order(c
== 0xfeff);
1026 fromcode
= "UTF-16";
1030 utf8_ret
= g_convert(data
, len
, "UTF-8", fromcode
, NULL
, NULL
, &error
);
1033 purple_debug_warning("gtkimhtml", "g_convert error: %s\n", error
->message
);
1034 g_error_free(error
);
1040 static void gtk_imhtml_clipboard_get(GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, guint info
, GtkIMHtml
*imhtml
) {
1042 gboolean primary
= (clipboard
!= clipboard_selection
);
1043 GtkTextIter start
, end
;
1046 GtkTextMark
*sel
= NULL
, *ins
= NULL
;
1048 g_return_if_fail(imhtml
!= NULL
);
1050 ins
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1051 sel
= gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
);
1052 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &start
, sel
);
1053 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &end
, ins
);
1056 if (info
== TARGET_HTML
) {
1061 text
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1063 text
= html_clipboard
;
1065 /* Mozilla asks that we start our text/html with the Unicode byte order mark */
1066 selection
= g_convert(text
, -1, "UTF-16", "UTF-8", NULL
, &len
, NULL
);
1067 gtk_selection_data_set(selection_data
, gdk_atom_intern("text/html", FALSE
), 16, (const guchar
*)selection
, len
);
1069 selection
= clipboard_html_to_win32(html_clipboard
);
1070 gtk_selection_data_set(selection_data
, gdk_atom_intern("HTML Format", FALSE
), 8, (const guchar
*)selection
, strlen(selection
));
1075 text
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1077 text
= text_clipboard
;
1078 gtk_selection_data_set_text(selection_data
, text
, strlen(text
));
1080 if (primary
) /* This was allocated here */
1084 static void gtk_imhtml_primary_clipboard_clear(GtkClipboard
*clipboard
, GtkIMHtml
*imhtml
)
1087 GtkTextIter selection_bound
;
1089 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &insert
,
1090 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "insert"));
1091 gtk_text_buffer_get_iter_at_mark (imhtml
->text_buffer
, &selection_bound
,
1092 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"));
1094 if (!gtk_text_iter_equal (&insert
, &selection_bound
))
1095 gtk_text_buffer_move_mark (imhtml
->text_buffer
,
1096 gtk_text_buffer_get_mark (imhtml
->text_buffer
, "selection_bound"),
1100 static void gtk_imhtml_clipboard_clear (GtkClipboard
*clipboard
, GtkSelectionData
*sel_data
,
1101 guint info
, gpointer user_data_or_owner
)
1105 static void copy_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1107 GtkTextIter start
, end
;
1108 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1109 if (!clipboard_selection
)
1110 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1111 gtk_clipboard_set_with_data(clipboard_selection
,
1112 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1113 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1114 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1116 g_free(html_clipboard
);
1117 g_free(text_clipboard
);
1119 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1120 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1123 g_signal_stop_emission_by_name(imhtml
, "copy-clipboard");
1126 static void cut_clipboard_cb(GtkIMHtml
*imhtml
, gpointer unused
)
1128 GtkTextIter start
, end
;
1129 if (gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
1130 if (!clipboard_selection
)
1131 clipboard_selection
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1132 gtk_clipboard_set_with_data(clipboard_selection
,
1133 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1134 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1135 (GtkClipboardClearFunc
)gtk_imhtml_clipboard_clear
, NULL
);
1137 g_free(html_clipboard
);
1138 g_free(text_clipboard
);
1140 html_clipboard
= gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
1141 text_clipboard
= gtk_imhtml_get_text(imhtml
, &start
, &end
);
1143 if (imhtml
->editable
)
1144 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, FALSE
, FALSE
);
1147 g_signal_stop_emission_by_name(imhtml
, "cut-clipboard");
1150 static void imhtml_paste_insert(GtkIMHtml
*imhtml
, const char *text
, gboolean plaintext
)
1153 GtkIMHtmlOptions flags
= plaintext
? GTK_IMHTML_NO_SMILEY
: (GTK_IMHTML_NO_NEWLINE
| GTK_IMHTML_NO_COMMENTS
);
1155 /* Delete any currently selected text */
1156 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
1158 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1159 if (!imhtml
->wbfo
&& !plaintext
)
1160 gtk_imhtml_close_tags(imhtml
, &iter
);
1162 gtk_imhtml_insert_html_at_iter(imhtml
, text
, flags
, &iter
);
1163 gtk_text_buffer_move_mark_by_name(imhtml
->text_buffer
, "insert", &iter
);
1164 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(imhtml
), gtk_text_buffer_get_insert(imhtml
->text_buffer
),
1165 0, FALSE
, 0.0, 0.0);
1166 if (!imhtml
->wbfo
&& !plaintext
)
1167 gtk_imhtml_close_tags(imhtml
, &iter
);
1171 static void paste_plaintext_received_cb (GtkClipboard
*clipboard
, const gchar
*text
, gpointer data
)
1175 if (text
== NULL
|| !(*text
))
1178 tmp
= g_markup_escape_text(text
, -1);
1179 imhtml_paste_insert(data
, tmp
, TRUE
);
1183 static void paste_received_cb (GtkClipboard
*clipboard
, GtkSelectionData
*selection_data
, gpointer data
)
1186 GtkIMHtml
*imhtml
= data
;
1188 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1191 if (imhtml
->wbfo
|| selection_data
->length
<= 0) {
1192 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1196 /* Here's some debug code, for figuring out what sent to us over the clipboard. */
1200 purple_debug_misc("gtkimhtml", "In paste_received_cb():\n\tformat = %d, length = %d\n\t",
1201 selection_data
->format
, selection_data
->length
);
1203 for (i
= 0; i
< (/*(selection_data->format / 8) **/ selection_data
->length
); i
++) {
1206 if (selection_data
->data
[i
] == '\0')
1209 printf("%c", selection_data
->data
[i
]);
1215 text
= g_malloc(selection_data
->length
+ 1);
1216 memcpy(text
, selection_data
->data
, selection_data
->length
);
1217 /* Make sure the paste data is null-terminated. Given that
1218 * we're passed length (but assume later that it is
1219 * null-terminated), this seems sensible to me.
1221 text
[selection_data
->length
] = '\0';
1225 if (gtk_selection_data_get_data_type(selection_data
) == gdk_atom_intern("HTML Format", FALSE
)) {
1226 char *tmp
= clipboard_win32_to_html(text
);
1232 if (selection_data
->length
>= 2 &&
1233 (*(guint16
*)text
== 0xfeff || *(guint16
*)text
== 0xfffe)) {
1234 /* This is UTF-16 */
1235 char *utf8
= utf16_to_utf8_with_bom_check(text
, selection_data
->length
);
1239 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in paste_received_cb\n");
1244 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1245 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in paste_received_cb\n");
1250 imhtml_paste_insert(imhtml
, text
, FALSE
);
1255 static void smart_backspace_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1258 GtkTextChildAnchor
* anchor
;
1262 if (!imhtml
->editable
)
1265 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
1267 /* Get the character before the insertion point */
1268 offset
= gtk_text_iter_get_offset(&iter
);
1272 gtk_text_iter_backward_char(&iter
);
1273 anchor
= gtk_text_iter_get_child_anchor(&iter
);
1276 return; /* No smiley here */
1278 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
1282 /* ok, then we need to insert the image buffer text before the anchor */
1283 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
1286 static void paste_clipboard_cb(GtkIMHtml
*imhtml
, gpointer blah
)
1289 /* If we're on windows, let's see if we can get data from the HTML Format
1290 clipboard before we try to paste from the GTK buffer */
1291 if (!clipboard_paste_html_win32(imhtml
) && gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
))) {
1292 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1293 gtk_clipboard_request_text(clipboard
, paste_plaintext_received_cb
, imhtml
);
1297 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_CLIPBOARD
);
1298 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1299 paste_received_cb
, imhtml
);
1301 g_signal_stop_emission_by_name(imhtml
, "paste-clipboard");
1304 static void imhtml_realized_remove_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1306 gtk_text_buffer_remove_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1307 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1311 static void imhtml_destroy_add_primary(GtkIMHtml
*imhtml
, gpointer unused
)
1313 gtk_text_buffer_add_selection_clipboard(GTK_IMHTML(imhtml
)->text_buffer
,
1314 gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
));
1317 static void mark_set_so_update_selection_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
, GtkIMHtml
*imhtml
)
1319 if (gtk_text_buffer_get_selection_bounds(buffer
, NULL
, NULL
)) {
1320 gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
),
1321 selection_targets
, sizeof(selection_targets
) / sizeof(GtkTargetEntry
),
1322 (GtkClipboardGetFunc
)gtk_imhtml_clipboard_get
,
1323 (GtkClipboardClearFunc
)gtk_imhtml_primary_clipboard_clear
, G_OBJECT(imhtml
));
1327 static gboolean
gtk_imhtml_button_press_event(GtkIMHtml
*imhtml
, GdkEventButton
*event
, gpointer unused
)
1329 if (event
->button
== 2) {
1332 GtkClipboard
*clipboard
= gtk_widget_get_clipboard(GTK_WIDGET(imhtml
), GDK_SELECTION_PRIMARY
);
1334 if (!imhtml
->editable
)
1337 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(imhtml
),
1338 GTK_TEXT_WINDOW_TEXT
,
1343 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, x
, y
);
1344 gtk_text_buffer_place_cursor(imhtml
->text_buffer
, &iter
);
1346 gtk_clipboard_request_contents(clipboard
, gdk_atom_intern("text/html", FALSE
),
1347 paste_received_cb
, imhtml
);
1356 gtk_imhtml_undo(GtkIMHtml
*imhtml
)
1358 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1359 if (imhtml
->editable
&&
1360 gtk_source_undo_manager_can_undo(imhtml
->undo_manager
))
1361 gtk_source_undo_manager_undo(imhtml
->undo_manager
);
1365 gtk_imhtml_redo(GtkIMHtml
*imhtml
)
1367 g_return_if_fail(GTK_IS_IMHTML(imhtml
));
1368 if (imhtml
->editable
&&
1369 gtk_source_undo_manager_can_redo(imhtml
->undo_manager
))
1370 gtk_source_undo_manager_redo(imhtml
->undo_manager
);
1374 static gboolean
imhtml_message_send(GtkIMHtml
*imhtml
)
1380 imhtml_paste_cb(GtkIMHtml
*imhtml
, const char *str
)
1382 if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml
)))
1385 if (!str
|| !*str
|| purple_strequal(str
, "html"))
1386 g_signal_emit_by_name(imhtml
, "paste_clipboard");
1387 else if (purple_strequal(str
, "text"))
1388 paste_unformatted_cb(NULL
, imhtml
);
1391 static void imhtml_toggle_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
1393 /* since this function is the handler for the formatting keystrokes,
1394 we need to check here that the formatting attempted is permitted */
1395 buttons
&= imhtml
->format_functions
;
1398 case GTK_IMHTML_BOLD
:
1399 imhtml_toggle_bold(imhtml
);
1401 case GTK_IMHTML_ITALIC
:
1402 imhtml_toggle_italic(imhtml
);
1404 case GTK_IMHTML_UNDERLINE
:
1405 imhtml_toggle_underline(imhtml
);
1407 case GTK_IMHTML_STRIKE
:
1408 imhtml_toggle_strike(imhtml
);
1410 case GTK_IMHTML_SHRINK
:
1411 imhtml_font_shrink(imhtml
);
1413 case GTK_IMHTML_GROW
:
1414 imhtml_font_grow(imhtml
);
1422 gtk_imhtml_finalize (GObject
*object
)
1424 GtkIMHtml
*imhtml
= GTK_IMHTML(object
);
1428 if (imhtml
->scroll_src
)
1429 g_source_remove(imhtml
->scroll_src
);
1430 if (imhtml
->scroll_time
)
1431 g_timer_destroy(imhtml
->scroll_time
);
1433 g_hash_table_destroy(imhtml
->smiley_data
);
1434 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
1435 gdk_cursor_unref(imhtml
->hand_cursor
);
1436 gdk_cursor_unref(imhtml
->arrow_cursor
);
1437 gdk_cursor_unref(imhtml
->text_cursor
);
1439 if(imhtml
->tip_window
){
1440 gtk_widget_destroy(imhtml
->tip_window
);
1442 if(imhtml
->tip_timer
)
1443 g_source_remove(imhtml
->tip_timer
);
1445 for(scalables
= imhtml
->scalables
; scalables
; scalables
= scalables
->next
) {
1446 struct scalable_data
*sd
= scalables
->data
;
1447 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
1452 for (l
= imhtml
->im_images
; l
; l
= l
->next
) {
1453 struct im_image_data
*img_data
= l
->data
;
1454 if (imhtml
->funcs
->image_unref
)
1455 imhtml
->funcs
->image_unref(img_data
->id
);
1459 g_list_free(imhtml
->scalables
);
1460 g_slist_free(imhtml
->im_images
);
1461 g_queue_free(imhtml
->animations
);
1462 g_free(imhtml
->protocol_name
);
1463 g_free(imhtml
->search_string
);
1464 g_object_unref(imhtml
->undo_manager
);
1465 G_OBJECT_CLASS(parent_class
)->finalize (object
);
1469 static GtkIMHtmlProtocol
*
1470 imhtml_find_protocol(const char *url
, gboolean reverse
)
1472 GtkIMHtmlClass
*klass
;
1474 GtkIMHtmlProtocol
*proto
= NULL
;
1475 int length
= reverse
? strlen(url
) : 0;
1477 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
1478 for (iter
= klass
->protocols
; iter
; iter
= iter
->next
) {
1480 if (g_ascii_strncasecmp(url
, proto
->name
, reverse
? MIN(length
, proto
->length
) : proto
->length
) == 0) {
1488 imhtml_url_clicked(GtkIMHtml
*imhtml
, const char *url
)
1490 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(url
, FALSE
);
1491 GtkIMHtmlLink
*link
;
1494 link
= g_new0(GtkIMHtmlLink
, 1);
1495 link
->imhtml
= g_object_ref(imhtml
);
1496 link
->url
= g_strdup(url
);
1497 proto
->activate(imhtml
, link
); /* XXX: Do something with the return value? */
1498 gtk_imhtml_link_destroy(link
);
1501 /* Boring GTK+ stuff */
1502 static void gtk_imhtml_class_init (GtkIMHtmlClass
*klass
)
1504 GtkWidgetClass
*widget_class
= (GtkWidgetClass
*) klass
;
1505 GtkBindingSet
*binding_set
;
1506 GObjectClass
*gobject_class
;
1507 gobject_class
= (GObjectClass
*) klass
;
1508 parent_class
= g_type_class_ref(GTK_TYPE_TEXT_VIEW
);
1509 signals
[URL_CLICKED
] = g_signal_new("url_clicked",
1510 G_TYPE_FROM_CLASS(gobject_class
),
1512 G_STRUCT_OFFSET(GtkIMHtmlClass
, url_clicked
),
1515 g_cclosure_marshal_VOID__POINTER
,
1518 signals
[BUTTONS_UPDATE
] = g_signal_new("format_buttons_update",
1519 G_TYPE_FROM_CLASS(gobject_class
),
1521 G_STRUCT_OFFSET(GtkIMHtmlClass
, buttons_update
),
1524 g_cclosure_marshal_VOID__INT
,
1527 signals
[TOGGLE_FORMAT
] = g_signal_new("format_function_toggle",
1528 G_TYPE_FROM_CLASS(gobject_class
),
1529 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1530 G_STRUCT_OFFSET(GtkIMHtmlClass
, toggle_format
),
1533 g_cclosure_marshal_VOID__INT
,
1536 signals
[CLEAR_FORMAT
] = g_signal_new("format_function_clear",
1537 G_TYPE_FROM_CLASS(gobject_class
),
1538 G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
1539 G_STRUCT_OFFSET(GtkIMHtmlClass
, clear_format
),
1542 g_cclosure_marshal_VOID__VOID
,
1544 signals
[UPDATE_FORMAT
] = g_signal_new("format_function_update",
1545 G_TYPE_FROM_CLASS(gobject_class
),
1547 G_STRUCT_OFFSET(GtkIMHtmlClass
, update_format
),
1550 g_cclosure_marshal_VOID__VOID
,
1552 signals
[MESSAGE_SEND
] = g_signal_new("message_send",
1553 G_TYPE_FROM_CLASS(gobject_class
),
1554 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1555 G_STRUCT_OFFSET(GtkIMHtmlClass
, message_send
),
1557 0, g_cclosure_marshal_VOID__VOID
,
1559 signals
[PASTE
] = g_signal_new("paste",
1560 G_TYPE_FROM_CLASS(gobject_class
),
1561 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1564 0, g_cclosure_marshal_VOID__STRING
,
1565 G_TYPE_NONE
, 1, G_TYPE_STRING
);
1566 signals
[UNDO
] = g_signal_new ("undo",
1567 G_TYPE_FROM_CLASS (klass
),
1568 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1569 G_STRUCT_OFFSET (GtkIMHtmlClass
, undo
),
1572 gtksourceview_marshal_VOID__VOID
,
1575 signals
[REDO
] = g_signal_new ("redo",
1576 G_TYPE_FROM_CLASS (klass
),
1577 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1578 G_STRUCT_OFFSET (GtkIMHtmlClass
, redo
),
1581 gtksourceview_marshal_VOID__VOID
,
1587 klass
->toggle_format
= imhtml_toggle_format
;
1588 klass
->message_send
= imhtml_message_send
;
1589 klass
->clear_format
= imhtml_clear_formatting
;
1590 klass
->url_clicked
= imhtml_url_clicked
;
1591 klass
->undo
= gtk_imhtml_undo
;
1592 klass
->redo
= gtk_imhtml_redo
;
1594 gobject_class
->finalize
= gtk_imhtml_finalize
;
1595 widget_class
->drag_motion
= gtk_text_view_drag_motion
;
1596 widget_class
->expose_event
= gtk_imhtml_expose_event
;
1597 parent_size_allocate
= widget_class
->size_allocate
;
1598 widget_class
->size_allocate
= gtk_imhtml_size_allocate
;
1599 parent_style_set
= widget_class
->style_set
;
1600 widget_class
->style_set
= gtk_imhtml_style_set
;
1602 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-color",
1603 _("Hyperlink color"),
1604 _("Color to draw hyperlinks."),
1605 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1606 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-visited-color",
1607 _("Hyperlink visited color"),
1608 _("Color to draw hyperlink after it has been visited (or activated)."),
1609 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1610 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("hyperlink-prelight-color",
1611 _("Hyperlink prelight color"),
1612 _("Color to draw hyperlinks when mouse is over them."),
1613 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1614 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("send-name-color",
1615 _("Sent Message Name Color"),
1616 _("Color to draw the name of a message you sent."),
1617 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1618 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("receive-name-color",
1619 _("Received Message Name Color"),
1620 _("Color to draw the name of a message you received."),
1621 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1622 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("highlight-name-color",
1623 _("\"Attention\" Name Color"),
1624 _("Color to draw the name of a message you received containing your name."),
1625 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1626 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("action-name-color",
1627 _("Action Message Name Color"),
1628 _("Color to draw the name of an action message."),
1629 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1630 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-action-name-color",
1631 _("Action Message Name Color for Whispered Message"),
1632 _("Color to draw the name of a whispered action message."),
1633 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1634 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("whisper-name-color",
1635 _("Whisper Message Name Color"),
1636 _("Color to draw the name of a whispered message."),
1637 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1639 /* Customizable typing notification ... sort of. Example:
1640 * GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
1641 * GtkIMHtml::typing-notification-color = "#ff0000"
1642 * GtkIMHtml::typing-notification-enable = 1
1644 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boxed("typing-notification-color",
1645 _("Typing notification color"),
1646 _("The color to use for the typing notification"),
1647 GDK_TYPE_COLOR
, G_PARAM_READABLE
));
1648 gtk_widget_class_install_style_property(widget_class
, g_param_spec_string("typing-notification-font",
1649 _("Typing notification font"),
1650 _("The font to use for the typing notification"),
1651 "light 8.0", G_PARAM_READABLE
));
1652 gtk_widget_class_install_style_property(widget_class
, g_param_spec_boolean("typing-notification-enable",
1653 _("Enable typing notification"),
1654 _("Enable typing notification"),
1655 TRUE
, G_PARAM_READABLE
));
1657 binding_set
= gtk_binding_set_by_class (parent_class
);
1658 gtk_binding_entry_add_signal (binding_set
, GDK_b
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_BOLD
);
1659 gtk_binding_entry_add_signal (binding_set
, GDK_i
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_ITALIC
);
1660 gtk_binding_entry_add_signal (binding_set
, GDK_u
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_UNDERLINE
);
1661 gtk_binding_entry_add_signal (binding_set
, GDK_plus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1662 gtk_binding_entry_add_signal (binding_set
, GDK_equal
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_GROW
);
1663 gtk_binding_entry_add_signal (binding_set
, GDK_minus
, GDK_CONTROL_MASK
, "format_function_toggle", 1, G_TYPE_INT
, GTK_IMHTML_SHRINK
);
1664 binding_set
= gtk_binding_set_by_class(klass
);
1665 gtk_binding_entry_add_signal (binding_set
, GDK_r
, GDK_CONTROL_MASK
, "format_function_clear", 0);
1666 gtk_binding_entry_add_signal (binding_set
, GDK_KP_Enter
, 0, "message_send", 0);
1667 gtk_binding_entry_add_signal (binding_set
, GDK_Return
, 0, "message_send", 0);
1668 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
, "undo", 0);
1669 gtk_binding_entry_add_signal (binding_set
, GDK_z
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "redo", 0);
1670 gtk_binding_entry_add_signal (binding_set
, GDK_F14
, 0, "undo", 0);
1671 gtk_binding_entry_add_signal(binding_set
, GDK_v
, GDK_CONTROL_MASK
| GDK_SHIFT_MASK
, "paste", 1, G_TYPE_STRING
, "text");
1674 static void gtk_imhtml_init (GtkIMHtml
*imhtml
)
1676 imhtml
->text_buffer
= gtk_text_buffer_new(NULL
);
1677 imhtml
->undo_manager
= gtk_source_undo_manager_new(imhtml
->text_buffer
);
1678 gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml
), imhtml
->text_buffer
);
1679 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml
), GTK_WRAP_WORD_CHAR
);
1680 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(imhtml
), 2);
1681 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(imhtml
), 3);
1682 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(imhtml
), 2);
1683 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(imhtml
), 2);
1684 /*gtk_text_view_set_indent(GTK_TEXT_VIEW(imhtml), -15);*/
1685 /*gtk_text_view_set_justification(GTK_TEXT_VIEW(imhtml), GTK_JUSTIFY_FILL);*/
1687 /* These tags will be used often and can be reused--we create them on init and then apply them by name
1688 * other tags (color, size, face, etc.) will have to be created and applied dynamically
1689 * Note that even though we created SUB, SUP, and PRE tags here, we don't really
1690 * apply them anywhere yet. */
1691 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "BOLD", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1692 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "ITALICS", "style", PANGO_STYLE_ITALIC
, NULL
);
1693 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "UNDERLINE", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
1694 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "STRIKE", "strikethrough", TRUE
, NULL
);
1695 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUB", "rise", -5000, NULL
);
1696 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "SUP", "rise", 5000, NULL
);
1697 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "PRE", "family", "Monospace", NULL
);
1698 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "search", "background", "#22ff00", "weight", "bold", NULL
);
1699 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "comment", "weight", PANGO_WEIGHT_NORMAL
,
1700 #if FALSE && GTK_CHECK_VERSION(2,10,10)
1705 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "send-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1706 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "receive-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1707 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "highlight-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1708 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1709 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1710 gtk_text_buffer_create_tag(imhtml
->text_buffer
, "whisper-name", "weight", PANGO_WEIGHT_BOLD
, NULL
);
1712 /* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
1713 imhtml
->hand_cursor
= gdk_cursor_new (GDK_HAND2
);
1714 imhtml
->arrow_cursor
= gdk_cursor_new (GDK_LEFT_PTR
);
1715 imhtml
->text_cursor
= gdk_cursor_new (GDK_XTERM
);
1717 imhtml
->show_comments
= TRUE
;
1719 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1720 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
1721 imhtml
->default_smilies
= gtk_smiley_tree_new();
1723 g_signal_connect(G_OBJECT(imhtml
), "motion-notify-event", G_CALLBACK(gtk_motion_event_notify
), NULL
);
1724 g_signal_connect(G_OBJECT(imhtml
), "leave-notify-event", G_CALLBACK(gtk_leave_event_notify
), NULL
);
1725 g_signal_connect(G_OBJECT(imhtml
), "enter-notify-event", G_CALLBACK(gtk_enter_event_notify
), NULL
);
1726 g_signal_connect(G_OBJECT(imhtml
), "button_press_event", G_CALLBACK(gtk_imhtml_button_press_event
), NULL
);
1727 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(preinsert_cb
), imhtml
);
1728 g_signal_connect(G_OBJECT(imhtml
->text_buffer
), "delete_range", G_CALLBACK(delete_cb
), imhtml
);
1729 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-text", G_CALLBACK(insert_cb
), imhtml
);
1730 g_signal_connect_after(G_OBJECT(imhtml
->text_buffer
), "insert-child-anchor", G_CALLBACK(insert_ca_cb
), imhtml
);
1731 gtk_drag_dest_set(GTK_WIDGET(imhtml
), 0,
1732 link_drag_drop_targets
, sizeof(link_drag_drop_targets
) / sizeof(GtkTargetEntry
),
1734 g_signal_connect(G_OBJECT(imhtml
), "drag_data_received", G_CALLBACK(gtk_imhtml_link_drag_rcv_cb
), imhtml
);
1735 g_signal_connect(G_OBJECT(imhtml
), "drag_drop", G_CALLBACK(gtk_imhtml_link_drop_cb
), imhtml
);
1737 g_signal_connect(G_OBJECT(imhtml
), "copy-clipboard", G_CALLBACK(copy_clipboard_cb
), NULL
);
1738 g_signal_connect(G_OBJECT(imhtml
), "cut-clipboard", G_CALLBACK(cut_clipboard_cb
), NULL
);
1739 g_signal_connect(G_OBJECT(imhtml
), "paste-clipboard", G_CALLBACK(paste_clipboard_cb
), NULL
);
1740 g_signal_connect_after(G_OBJECT(imhtml
), "realize", G_CALLBACK(imhtml_realized_remove_primary
), NULL
);
1741 g_signal_connect(G_OBJECT(imhtml
), "unrealize", G_CALLBACK(imhtml_destroy_add_primary
), NULL
);
1742 g_signal_connect(G_OBJECT(imhtml
), "paste", G_CALLBACK(imhtml_paste_cb
), NULL
);
1745 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
1746 G_CALLBACK(mark_set_so_update_selection_cb
), imhtml
);
1749 gtk_widget_add_events(GTK_WIDGET(imhtml
),
1750 GDK_LEAVE_NOTIFY_MASK
| GDK_ENTER_NOTIFY_MASK
);
1753 imhtml
->tip_timer
= 0;
1754 imhtml
->tip_window
= NULL
;
1756 imhtml
->edit
.bold
= FALSE
;
1757 imhtml
->edit
.italic
= FALSE
;
1758 imhtml
->edit
.underline
= FALSE
;
1759 imhtml
->edit
.forecolor
= NULL
;
1760 imhtml
->edit
.backcolor
= NULL
;
1761 imhtml
->edit
.fontface
= NULL
;
1762 imhtml
->edit
.fontsize
= 0;
1763 imhtml
->edit
.link
= NULL
;
1766 imhtml
->scalables
= NULL
;
1767 imhtml
->animations
= g_queue_new();
1768 gtk_imhtml_set_editable(imhtml
, FALSE
);
1769 g_signal_connect(G_OBJECT(imhtml
), "populate-popup",
1770 G_CALLBACK(hijack_menu_cb
), NULL
);
1773 GtkWidget
*gtk_imhtml_new(void *a
, void *b
)
1775 return GTK_WIDGET(g_object_new(gtk_imhtml_get_type(), NULL
));
1778 GType
gtk_imhtml_get_type()
1780 static GType imhtml_type
= 0;
1783 static const GTypeInfo imhtml_info
= {
1784 sizeof(GtkIMHtmlClass
),
1787 (GClassInitFunc
) gtk_imhtml_class_init
,
1792 (GInstanceInitFunc
) gtk_imhtml_init
,
1796 imhtml_type
= g_type_register_static(gtk_text_view_get_type(),
1797 "GtkIMHtml", &imhtml_info
, 0);
1803 static void gtk_imhtml_link_destroy(GtkIMHtmlLink
*link
)
1806 g_object_unref(link
->imhtml
);
1808 g_object_unref(link
->tag
);
1813 /* The callback for an event on a link tag. */
1814 static gboolean
tag_event(GtkTextTag
*tag
, GObject
*imhtml
, GdkEvent
*event
, GtkTextIter
*arg2
, gpointer unused
)
1816 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
1817 if (GTK_IMHTML(imhtml
)->editable
)
1819 if (event
->type
== GDK_BUTTON_RELEASE
) {
1820 if ((event_button
->button
== 1) || (event_button
->button
== 2)) {
1821 GtkTextIter start
, end
;
1822 /* we shouldn't open a URL if the user has selected something: */
1823 if (gtk_text_buffer_get_selection_bounds(
1824 gtk_text_iter_get_buffer(arg2
), &start
, &end
))
1826 gtk_imhtml_activate_tag(GTK_IMHTML(imhtml
), tag
);
1828 } else if(event_button
->button
== 3) {
1831 GtkIMHtmlProtocol
*proto
;
1832 GtkIMHtmlLink
*link
= g_new(GtkIMHtmlLink
, 1);
1833 link
->imhtml
= g_object_ref(imhtml
);
1834 link
->url
= g_strdup(g_object_get_data(G_OBJECT(tag
), "link_url"));
1835 link
->tag
= g_object_ref(tag
);
1837 /* Don't want the tooltip around if user right-clicked on link */
1838 if (GTK_IMHTML(imhtml
)->tip_window
) {
1839 gtk_widget_destroy(GTK_IMHTML(imhtml
)->tip_window
);
1840 GTK_IMHTML(imhtml
)->tip_window
= NULL
;
1842 if (GTK_IMHTML(imhtml
)->tip_timer
) {
1843 g_source_remove(GTK_IMHTML(imhtml
)->tip_timer
);
1844 GTK_IMHTML(imhtml
)->tip_timer
= 0;
1846 if (GTK_IMHTML(imhtml
)->editable
)
1847 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->text_cursor
);
1849 gdk_window_set_cursor(event_button
->window
, GTK_IMHTML(imhtml
)->arrow_cursor
);
1850 menu
= gtk_menu_new();
1851 g_object_set_data_full(G_OBJECT(menu
), "x-imhtml-url-data", link
,
1852 (GDestroyNotify
)gtk_imhtml_link_destroy
);
1854 proto
= imhtml_find_protocol(link
->url
, FALSE
);
1856 if (proto
&& proto
->context_menu
) {
1857 proto
->context_menu(GTK_IMHTML(link
->imhtml
), link
, menu
);
1860 children
= gtk_container_get_children(GTK_CONTAINER(menu
));
1862 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
1863 gtk_widget_show(item
);
1864 gtk_widget_set_sensitive(item
, FALSE
);
1865 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
1867 g_list_free(children
);
1871 gtk_widget_show_all(menu
);
1872 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
1873 event_button
->button
, event_button
->time
);
1878 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
1879 return TRUE
; /* Clicking the right mouse button on a link shouldn't
1880 be caught by the regular GtkTextView menu */
1882 return FALSE
; /* Let clicks go through if we didn't catch anything */
1886 gtk_text_view_drag_motion (GtkWidget
*widget
,
1887 GdkDragContext
*context
,
1892 GdkDragAction suggested_action
= 0;
1894 if (gtk_drag_dest_find_target (widget
, context
, NULL
) == GDK_NONE
) {
1895 /* can't accept any of the offered targets */
1897 GtkWidget
*source_widget
;
1898 suggested_action
= context
->suggested_action
;
1899 source_widget
= gtk_drag_get_source_widget (context
);
1900 if (source_widget
== widget
) {
1901 /* Default to MOVE, unless the user has
1902 * pressed ctrl or alt to affect available actions
1904 if ((context
->actions
& GDK_ACTION_MOVE
) != 0)
1905 suggested_action
= GDK_ACTION_MOVE
;
1909 gdk_drag_status (context
, suggested_action
, time
);
1911 /* TRUE return means don't propagate the drag motion to parent
1912 * widgets that may also be drop sites.
1918 gtk_imhtml_link_drop_cb(GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, gpointer user_data
)
1920 GdkAtom target
= gtk_drag_dest_find_target (widget
, context
, NULL
);
1922 if (target
!= GDK_NONE
)
1923 gtk_drag_get_data (widget
, context
, target
, time
);
1925 gtk_drag_finish (context
, FALSE
, FALSE
, time
);
1931 gtk_imhtml_link_drag_rcv_cb(GtkWidget
*widget
, GdkDragContext
*dc
, guint x
, guint y
,
1932 GtkSelectionData
*sd
, guint info
, guint t
, GtkIMHtml
*imhtml
)
1936 char *text
= (char *)sd
->data
;
1937 GtkTextMark
*mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
1941 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
1943 if(gtk_imhtml_get_editable(imhtml
) && sd
->data
){
1945 case GTK_IMHTML_DRAG_URL
:
1946 /* TODO: Is it really ok to change sd->data...? */
1947 purple_str_strip_char((char *)sd
->data
, '\r');
1949 links
= g_strsplit((char *)sd
->data
, "\n", 0);
1950 while((link
= links
[i
]) != NULL
){
1951 if (gtk_imhtml_is_protocol(link
)) {
1959 gtk_imhtml_insert_link(imhtml
, mark
, link
, label
);
1960 } else if (*link
== '\0') {
1961 /* Ignore blank lines */
1963 /* Special reasons, aka images being put in via other tag, etc. */
1964 /* ... don't pretend we handled it if we didn't */
1965 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
1974 case GTK_IMHTML_DRAG_HTML
:
1977 /* Ewww. This is all because mozilla thinks that text/html is 'for internal use only.'
1978 * as explained by this comment in gtkhtml:
1980 * FIXME This hack decides the charset of the selection. It seems that
1981 * mozilla/netscape alway use ucs2 for text/html
1982 * and openoffice.org seems to always use utf8 so we try to validate
1983 * the string as utf8 and if that fails we assume it is ucs2
1985 * See also the comment on text/html here:
1986 * http://mail.gnome.org/archives/gtk-devel-list/2001-September/msg00114.html
1988 if (sd
->length
>= 2 && !g_utf8_validate(text
, sd
->length
- 1, NULL
)) {
1989 utf8
= utf16_to_utf8_with_bom_check(text
, sd
->length
);
1992 purple_debug_warning("gtkimhtml", "g_convert from UTF-16 failed in drag_rcv_cb\n");
1995 } else if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
1996 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
2000 gtk_imhtml_insert_html_at_iter(imhtml
, utf8
? utf8
: text
, 0, &iter
);
2004 case GTK_IMHTML_DRAG_TEXT
:
2005 if (!(*text
) || !g_utf8_validate(text
, -1, NULL
)) {
2006 purple_debug_warning("gtkimhtml", "empty string or invalid UTF-8 in drag_rcv_cb\n");
2009 char *tmp
= g_markup_escape_text(text
, -1);
2010 gtk_imhtml_insert_html_at_iter(imhtml
, tmp
, 0, &iter
);
2015 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2018 gtk_drag_finish(dc
, TRUE
, (dc
->action
== GDK_ACTION_MOVE
), t
);
2020 gtk_drag_finish(dc
, FALSE
, FALSE
, t
);
2024 static void gtk_smiley_tree_remove (GtkSmileyTree
*tree
,
2025 GtkIMHtmlSmiley
*smiley
)
2027 GtkSmileyTree
*t
= tree
;
2028 const gchar
*x
= smiley
->smile
;
2037 pos
= strchr (t
->values
->str
, *x
);
2039 t
= t
->children
[pos
- t
->values
->str
];
2052 gtk_smiley_tree_lookup (GtkSmileyTree
*tree
,
2055 GtkSmileyTree
*t
= tree
;
2056 const gchar
*x
= text
;
2067 if(*x
== '&' && (amp
= purple_markup_unescape_entity(x
, &alen
))) {
2068 gboolean matched
= TRUE
;
2069 /* Make sure all chars of the unescaped value match */
2070 while (*(amp
+ 1)) {
2071 pos
= strchr (t
->values
->str
, *amp
);
2073 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2083 pos
= strchr (t
->values
->str
, *amp
);
2085 else if (*x
== '<') /* Because we're all WYSIWYG now, a '<'
2086 * char should only appear as the start of a tag. Perhaps a safer (but costlier)
2087 * check would be to call gtk_imhtml_is_tag on it */
2091 pos
= strchr (t
->values
->str
, *x
);
2095 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2110 gtk_imhtml_disassociate_smiley_foreach(gpointer key
, gpointer value
,
2113 GtkSmileyTree
*tree
= (GtkSmileyTree
*) value
;
2114 GtkIMHtmlSmiley
*smiley
= (GtkIMHtmlSmiley
*) user_data
;
2115 gtk_smiley_tree_remove(tree
, smiley
);
2119 gtk_imhtml_disconnect_smiley(GtkIMHtml
*imhtml
, GtkIMHtmlSmiley
*smiley
)
2121 smiley
->imhtml
= NULL
;
2122 g_signal_handlers_disconnect_matched(imhtml
, G_SIGNAL_MATCH_DATA
, 0, 0,
2123 NULL
, NULL
, smiley
);
2127 gtk_imhtml_disassociate_smiley(GtkIMHtmlSmiley
*smiley
)
2129 if (smiley
->imhtml
) {
2130 gtk_smiley_tree_remove(smiley
->imhtml
->default_smilies
, smiley
);
2131 g_hash_table_foreach(smiley
->imhtml
->smiley_data
,
2132 gtk_imhtml_disassociate_smiley_foreach
, smiley
);
2133 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2134 0, 0, NULL
, NULL
, smiley
);
2135 smiley
->imhtml
= NULL
;
2140 gtk_imhtml_associate_smiley (GtkIMHtml
*imhtml
,
2142 GtkIMHtmlSmiley
*smiley
)
2144 GtkSmileyTree
*tree
;
2145 g_return_if_fail (imhtml
!= NULL
);
2146 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2149 tree
= imhtml
->default_smilies
;
2150 else if (!(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
))) {
2151 tree
= gtk_smiley_tree_new();
2152 g_hash_table_insert(imhtml
->smiley_data
, g_strdup(sml
), tree
);
2155 /* need to disconnect old imhtml, if there is one */
2156 if (smiley
->imhtml
) {
2157 g_signal_handlers_disconnect_matched(smiley
->imhtml
, G_SIGNAL_MATCH_DATA
,
2158 0, 0, NULL
, NULL
, smiley
);
2161 smiley
->imhtml
= imhtml
;
2163 gtk_smiley_tree_insert (tree
, smiley
);
2165 /* connect destroy signal for the imhtml */
2166 g_signal_connect(imhtml
, "destroy", G_CALLBACK(gtk_imhtml_disconnect_smiley
),
2171 gtk_imhtml_is_smiley (GtkIMHtml
*imhtml
,
2176 GtkSmileyTree
*tree
;
2177 GtkIMHtmlFontDetail
*font
;
2186 sml
= imhtml
->protocol_name
;
2188 if (!sml
|| !(tree
= g_hash_table_lookup(imhtml
->smiley_data
, sml
)))
2189 tree
= imhtml
->default_smilies
;
2194 *len
= gtk_smiley_tree_lookup (tree
, text
);
2198 static GtkIMHtmlSmiley
*gtk_imhtml_smiley_get_from_tree(GtkSmileyTree
*t
, const gchar
*text
)
2200 const gchar
*x
= text
;
2210 pos
= strchr(t
->values
->str
, *x
);
2214 t
= t
->children
[GPOINTER_TO_INT(pos
) - GPOINTER_TO_INT(t
->values
->str
)];
2222 gtk_imhtml_smiley_get(GtkIMHtml
*imhtml
, const gchar
*sml
, const gchar
*text
)
2224 GtkIMHtmlSmiley
*ret
;
2226 /* Look for custom smileys first */
2228 ret
= gtk_imhtml_smiley_get_from_tree(g_hash_table_lookup(imhtml
->smiley_data
, sml
), text
);
2233 /* Fall back to check for default smileys */
2234 return gtk_imhtml_smiley_get_from_tree(imhtml
->default_smilies
, text
);
2237 static GdkPixbufAnimation
*
2238 gtk_smiley_get_image(GtkIMHtmlSmiley
*smiley
)
2240 if (!smiley
->icon
) {
2242 smiley
->icon
= gdk_pixbuf_animation_new_from_file(smiley
->file
, NULL
);
2243 } else if (smiley
->loader
) {
2244 smiley
->icon
= gdk_pixbuf_loader_get_animation(smiley
->loader
);
2246 g_object_ref(G_OBJECT(smiley
->icon
));
2250 return smiley
->icon
;
2253 #define VALID_TAG(x) do { \
2254 if (!g_ascii_strncasecmp (string, x ">", strlen (x ">"))) { \
2255 if (tag) *tag = g_strndup (string, strlen (x)); \
2256 if (len) *len = strlen (x) + 1; \
2259 if (type) (*type)++; \
2262 #define VALID_OPT_TAG(x) do { \
2263 if (!g_ascii_strncasecmp (string, x " ", strlen (x " "))) { \
2264 const gchar *c = string + strlen (x " "); \
2266 gboolean quote = FALSE; \
2268 if (*c == '"' || *c == '\'') { \
2269 if (quote && (*c == e)) \
2271 else if (!quote) { \
2275 } else if (!quote && (*c == '>')) \
2280 if (tag) *tag = g_strndup (string, c - string); \
2281 if (len) *len = c - string + 1; \
2285 if (type) (*type)++; \
2290 gtk_imhtml_is_tag (const gchar
*string
,
2299 if (!(close
= strchr (string
, '>')))
2305 VALID_TAG ("/BOLD");
2307 VALID_TAG ("ITALIC");
2309 VALID_TAG ("/ITALIC");
2311 VALID_TAG ("UNDERLINE");
2313 VALID_TAG ("/UNDERLINE");
2315 VALID_TAG ("STRIKE");
2317 VALID_TAG ("/STRIKE");
2324 VALID_TAG ("TITLE");
2325 VALID_TAG ("/TITLE");
2328 VALID_TAG ("/FONT");
2335 VALID_TAG ("/HTML");
2337 VALID_TAG ("/BODY");
2340 VALID_TAG ("/HEAD");
2341 VALID_TAG ("BINARY");
2342 VALID_TAG ("/BINARY");
2344 VALID_OPT_TAG ("HR");
2345 VALID_OPT_TAG ("FONT");
2346 VALID_OPT_TAG ("BODY");
2347 VALID_OPT_TAG ("A");
2348 VALID_OPT_TAG ("IMG");
2349 VALID_OPT_TAG ("P");
2350 VALID_OPT_TAG ("H3");
2351 VALID_OPT_TAG ("HTML");
2354 VALID_TAG ("/CITE");
2357 VALID_TAG ("STRONG");
2358 VALID_TAG ("/STRONG");
2360 VALID_OPT_TAG ("SPAN");
2361 VALID_TAG ("/SPAN");
2362 VALID_TAG ("BR/"); /* hack until gtkimhtml handles things better */
2365 VALID_OPT_TAG("BR");
2367 if (!g_ascii_strncasecmp(string
, "!--", strlen ("!--"))) {
2368 gchar
*e
= strstr (string
+ strlen("!--"), "-->");
2371 *len
= e
- string
+ strlen ("-->");
2373 *tag
= g_strndup (string
+ strlen ("!--"), *len
- strlen ("!---->"));
2382 *len
= close
- string
+ 1;
2384 *tag
= g_strndup(string
, close
- string
);
2389 gtk_imhtml_get_html_opt (gchar
*tag
,
2399 while (g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2400 gboolean quote
= FALSE
;
2401 if (*t
== '\0') break;
2402 while (*t
&& !((*t
== ' ') && !quote
)) {
2407 while (*t
&& (*t
== ' ')) t
++;
2410 if (!g_ascii_strncasecmp (t
, opt
, strlen (opt
))) {
2416 if ((*t
== '\"') || (*t
== '\'')) {
2418 while (*e
&& (*e
!= *(t
- 1))) e
++;
2422 val
= g_strndup(a
, e
- a
);
2425 while (*e
&& !isspace ((gint
) *e
)) e
++;
2426 val
= g_strndup(a
, e
- a
);
2429 ret
= g_string_new("");
2432 if((c
= purple_markup_unescape_entity(e
, &len
))) {
2433 ret
= g_string_append(ret
, c
);
2436 gunichar uni
= g_utf8_get_char(e
);
2437 ret
= g_string_append_unichar(ret
, uni
);
2438 e
= g_utf8_next_char(e
);
2444 return g_string_free(ret
, FALSE
);
2447 /* returns if the beginning of the text is a protocol. If it is the protocol, returns the length so
2448 the caller knows how long the protocol string is. */
2449 static int gtk_imhtml_is_protocol(const char *text
)
2451 GtkIMHtmlProtocol
*proto
= imhtml_find_protocol(text
, FALSE
);
2452 return proto
? proto
->length
: 0;
2455 static gboolean
smooth_scroll_cb(gpointer data
);
2458 <KingAnt> marv: The two IM image functions in oscar are purple_odc_send_im and purple_odc_incoming
2461 [19:58] <Robot101> marv: images go into the imgstore, a refcounted... well.. hash. :)
2462 [19:59] <KingAnt> marv: I think the image tag used by the core is something like <img id="#"/>
2463 [19:59] Ro0tSiEgE robert42 RobFlynn Robot101 ross22 roz
2464 [20:00] <KingAnt> marv: Where the ID is the what is returned when you add the image to the imgstore using purple_imgstore_add
2465 [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?
2466 [20:00] <KingAnt> marv: Right
2467 [20:00] <marv> alright
2469 Here's my plan with IMImages. make gtk_imhtml_[append|insert]_text_with_images instead just
2470 gtkimhtml_[append|insert]_text (hrm maybe it should be called html instead of text), add a
2471 function for purple to register for look up images, i.e. gtk_imhtml_set_get_img_fnc, so that
2472 images can be looked up like that, instead of passing a GSList of them.
2475 void gtk_imhtml_append_text_with_images (GtkIMHtml
*imhtml
,
2477 GtkIMHtmlOptions options
,
2480 GtkTextIter iter
, ins
, sel
;
2481 int ins_offset
= 0, sel_offset
= 0;
2482 gboolean fixins
= FALSE
, fixsel
= FALSE
;
2484 g_return_if_fail (imhtml
!= NULL
);
2485 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2486 g_return_if_fail (text
!= NULL
);
2489 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
2490 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &ins
, gtk_text_buffer_get_insert(imhtml
->text_buffer
));
2491 if (gtk_text_iter_equal(&iter
, &ins
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2493 ins_offset
= gtk_text_iter_get_offset(&ins
);
2496 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &sel
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
));
2497 if (gtk_text_iter_equal(&iter
, &sel
) && gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, NULL
, NULL
)) {
2499 sel_offset
= gtk_text_iter_get_offset(&sel
);
2502 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2506 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2507 gtk_text_view_get_line_yrange(GTK_TEXT_VIEW(imhtml
), &iter
, &y
, &height
);
2509 if (((y
+ height
) - (rect
.y
+ rect
.height
)) > height
&&
2510 gtk_text_buffer_get_char_count(imhtml
->text_buffer
)) {
2511 /* If we are in the middle of smooth-scrolling, then take a scroll step.
2512 * If we are not in the middle of smooth-scrolling, that means we were
2513 * not looking at the end of the buffer before the new text was added,
2514 * so do not scroll. */
2515 if (imhtml
->scroll_time
)
2516 smooth_scroll_cb(imhtml
);
2518 options
|= GTK_IMHTML_NO_SCROLL
;
2522 gtk_imhtml_insert_html_at_iter(imhtml
, text
, options
, &iter
);
2525 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &ins
, ins_offset
);
2526 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_insert(imhtml
->text_buffer
), &ins
);
2530 gtk_text_buffer_get_iter_at_offset(imhtml
->text_buffer
, &sel
, sel_offset
);
2531 gtk_text_buffer_move_mark(imhtml
->text_buffer
, gtk_text_buffer_get_selection_bound(imhtml
->text_buffer
), &sel
);
2534 if (!(options
& GTK_IMHTML_NO_SCROLL
)) {
2535 gtk_imhtml_scroll_to_end(imhtml
, (options
& GTK_IMHTML_USE_SMOOTHSCROLLING
));
2539 #define MAX_SCROLL_TIME 0.4 /* seconds */
2540 #define SCROLL_DELAY 33 /* milliseconds */
2543 * Smoothly scroll a GtkIMHtml.
2545 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
2547 static gboolean
smooth_scroll_cb(gpointer data
)
2549 GtkIMHtml
*imhtml
= data
;
2550 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2551 gdouble max_val
= adj
->upper
- adj
->page_size
;
2552 gdouble scroll_val
= gtk_adjustment_get_value(adj
) + ((max_val
- gtk_adjustment_get_value(adj
)) / 3);
2554 g_return_val_if_fail(imhtml
->scroll_time
!= NULL
, FALSE
);
2556 if (g_timer_elapsed(imhtml
->scroll_time
, NULL
) > MAX_SCROLL_TIME
|| scroll_val
>= max_val
) {
2557 /* time's up. jump to the end and kill the timer */
2558 gtk_adjustment_set_value(adj
, max_val
);
2559 g_timer_destroy(imhtml
->scroll_time
);
2560 imhtml
->scroll_time
= NULL
;
2561 g_source_remove(imhtml
->scroll_src
);
2562 imhtml
->scroll_src
= 0;
2566 /* scroll by 1/3rd the remaining distance */
2567 gtk_adjustment_set_value(adj
, scroll_val
);
2571 static gboolean
scroll_idle_cb(gpointer data
)
2573 GtkIMHtml
*imhtml
= data
;
2574 GtkAdjustment
*adj
= GTK_TEXT_VIEW(imhtml
)->vadjustment
;
2576 gtk_adjustment_set_value(adj
, adj
->upper
- adj
->page_size
);
2578 imhtml
->scroll_src
= 0;
2582 void gtk_imhtml_scroll_to_end(GtkIMHtml
*imhtml
, gboolean smooth
)
2584 if (imhtml
->scroll_time
)
2585 g_timer_destroy(imhtml
->scroll_time
);
2586 if (imhtml
->scroll_src
)
2587 g_source_remove(imhtml
->scroll_src
);
2589 imhtml
->scroll_time
= g_timer_new();
2590 imhtml
->scroll_src
= g_timeout_add_full(G_PRIORITY_LOW
, SCROLL_DELAY
, smooth_scroll_cb
, imhtml
, NULL
);
2592 imhtml
->scroll_time
= NULL
;
2593 imhtml
->scroll_src
= g_idle_add_full(G_PRIORITY_LOW
, scroll_idle_cb
, imhtml
, NULL
);
2597 /* CSS colors are either rgb (x,y,z) or #hex
2598 * we need to convert to hex if it is RGB */
2600 parse_css_color(gchar
*in_color
)
2602 char *tmp
= in_color
;
2604 if (*tmp
== 'r' && *(++tmp
) == 'g' && *(++tmp
) == 'b' && *(++tmp
)) {
2605 int rgbval
[] = {0, 0, 0};
2607 const char *v_start
;
2609 while (*tmp
&& g_ascii_isspace(*tmp
))
2612 /* We don't support rgba() */
2613 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2619 /* Skip any leading spaces */
2620 while (*tmp
&& g_ascii_isspace(*tmp
))
2623 /* Find the subsequent contiguous digits */
2625 if (*v_start
== '-')
2627 while (*tmp
&& g_ascii_isdigit(*tmp
))
2630 if (tmp
!= v_start
) {
2633 rgbval
[count
] = atoi(v_start
);
2637 while (*tmp
&& g_ascii_isspace(*tmp
))
2640 rgbval
[count
] = (rgbval
[count
] / 100.0) * 255;
2644 purple_debug_warning("gtkimhtml", "Invalid rgb CSS color in '%s'!\n", in_color
);
2648 if (rgbval
[count
] > 255) {
2649 rgbval
[count
] = 255;
2650 } else if (rgbval
[count
] < 0) {
2654 while (*tmp
&& g_ascii_isspace(*tmp
))
2663 return g_strdup_printf("#%02X%02X%02X", rgbval
[0], rgbval
[1], rgbval
[2]);
2669 void gtk_imhtml_insert_html_at_iter(GtkIMHtml
*imhtml
,
2671 GtkIMHtmlOptions options
,
2680 gint tlen
, smilelen
, wpos
=0;
2695 gboolean br
= FALSE
;
2696 gboolean align_right
= FALSE
;
2697 gboolean rtl_direction
= FALSE
;
2698 gint align_line
= 0;
2700 GSList
*fonts
= NULL
;
2702 GtkIMHtmlScalable
*scalable
= NULL
;
2704 g_return_if_fail (imhtml
!= NULL
);
2705 g_return_if_fail (GTK_IS_IMHTML (imhtml
));
2706 g_return_if_fail (text
!= NULL
);
2709 ws
= g_malloc(len
+ 1);
2712 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(0));
2714 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
2716 if (*c
== '<' && gtk_imhtml_is_tag (c
+ 1, &tag
, &tlen
, &type
)) {
2725 case 54: /* STRONG */
2726 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2727 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2729 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
))
2730 gtk_imhtml_toggle_bold(imhtml
);
2732 ws
[0] = '\0'; wpos
= 0;
2737 case 55: /* /STRONG */
2738 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2739 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2740 ws
[0] = '\0'; wpos
= 0;
2744 if ((bold
== 0) && (imhtml
->format_functions
& GTK_IMHTML_BOLD
) && !imhtml
->wbfo
)
2745 gtk_imhtml_toggle_bold(imhtml
);
2750 case 6: /* ITALIC */
2752 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2753 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2754 ws
[0] = '\0'; wpos
= 0;
2755 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
))
2756 gtk_imhtml_toggle_italic(imhtml
);
2761 case 8: /* /ITALIC */
2763 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2764 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2765 ws
[0] = '\0'; wpos
= 0;
2768 if ((italics
== 0) && (imhtml
->format_functions
& GTK_IMHTML_ITALIC
) && !imhtml
->wbfo
)
2769 gtk_imhtml_toggle_italic(imhtml
);
2774 case 10: /* UNDERLINE */
2775 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2776 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2777 ws
[0] = '\0'; wpos
= 0;
2778 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
))
2779 gtk_imhtml_toggle_underline(imhtml
);
2784 case 12: /* /UNDERLINE */
2785 if (!(options
& GTK_IMHTML_NO_FORMATTING
)) {
2786 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2787 ws
[0] = '\0'; wpos
= 0;
2790 if ((underline
== 0) && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
) && !imhtml
->wbfo
)
2791 gtk_imhtml_toggle_underline(imhtml
);
2796 case 14: /* STRIKE */
2797 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2798 ws
[0] = '\0'; wpos
= 0;
2799 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
))
2800 gtk_imhtml_toggle_strike(imhtml
);
2804 case 16: /* /STRIKE */
2805 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2806 ws
[0] = '\0'; wpos
= 0;
2809 if ((strike
== 0) && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
) && !imhtml
->wbfo
)
2810 gtk_imhtml_toggle_strike(imhtml
);
2813 /* FIXME: reimpliment this */
2817 /* FIXME: reimpliment this */
2822 /* FIXME: reimplement this */
2826 /* FIXME: reimplement this */
2831 /* FIXME: reimplement this */
2835 /* FIXME: reimplement this */
2839 case 23: /* TITLE */
2840 /* FIXME: what was this supposed to do anyway? */
2843 case 24: /* /TITLE */
2844 /* FIXME: make this undo whatever 23 was supposed to do */
2846 if (options
& GTK_IMHTML_NO_TITLE
) {
2855 case 61: /* BR (opt) */
2861 case 42: /* HR (opt) */
2864 struct scalable_data
*sd
= g_new(struct scalable_data
, 1);
2867 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2869 sd
->scalable
= scalable
= gtk_imhtml_hr_new();
2870 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
2871 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
2872 scalable
->add_to(scalable
, imhtml
, iter
);
2873 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
2874 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
2875 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
2876 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
2877 ws
[0] = '\0'; wpos
= 0;
2882 case 27: /* /FONT */
2883 if (fonts
&& !imhtml
->wbfo
) {
2884 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2885 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2886 ws
[0] = '\0'; wpos
= 0;
2887 /* NEW_BIT (NEW_TEXT_BIT); */
2889 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2890 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
2892 g_free (font
->face
);
2893 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2894 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
2896 g_free (font
->fore
);
2897 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2898 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
2900 g_free (font
->back
);
2903 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2904 gtk_imhtml_font_set_size(imhtml
, 3);
2906 fonts
= g_slist_remove (fonts
, font
);
2910 GtkIMHtmlFontDetail
*font
= fonts
->data
;
2912 if (font
->face
&& (imhtml
->format_functions
& GTK_IMHTML_FACE
))
2913 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2914 if (font
->fore
&& (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
))
2915 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2916 if (font
->back
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
))
2917 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2918 if ((font
->size
!= 3) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)))
2919 gtk_imhtml_font_set_size(imhtml
, font
->size
);
2924 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2925 gtk_imhtml_toggle_link(imhtml
, NULL
);
2926 ws
[0] = '\0'; wpos
= 0;
2934 case 34: /* /HTML */
2937 case 36: /* /BODY */
2938 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2939 ws
[0] = '\0'; wpos
= 0;
2940 gtk_imhtml_toggle_background(imhtml
, NULL
);
2944 case 39: /* /HEAD */
2945 case 40: /* BINARY */
2946 case 41: /* /BINARY */
2948 case 43: /* FONT (opt) */
2950 gchar
*color
, *back
, *face
, *size
, *sml
;
2951 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
2952 color
= gtk_imhtml_get_html_opt (tag
, "COLOR=");
2953 back
= gtk_imhtml_get_html_opt (tag
, "BACK=");
2954 face
= gtk_imhtml_get_html_opt (tag
, "FACE=");
2955 size
= gtk_imhtml_get_html_opt (tag
, "SIZE=");
2956 sml
= gtk_imhtml_get_html_opt (tag
, "SML=");
2957 if (!(color
|| back
|| face
|| size
|| sml
))
2960 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
2961 ws
[0] = '\0'; wpos
= 0;
2963 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
2965 oldfont
= fonts
->data
;
2967 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
2969 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
2973 if (back
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
2975 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
2979 if (face
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
2981 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
2989 if (oldfont
&& oldfont
->sml
)
2990 font
->sml
= g_strdup(oldfont
->sml
);
2993 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
))) {
2995 sscanf (size
+ 1, "%hd", &font
->size
);
2997 } else if (*size
== '-') {
2998 sscanf (size
+ 1, "%hd", &font
->size
);
2999 font
->size
= MAX (0, 3 - font
->size
);
3000 } else if (isdigit (*size
)) {
3001 sscanf (size
, "%hd", &font
->size
);
3003 if (font
->size
> 100)
3006 font
->size
= oldfont
->size
;
3009 if ((imhtml
->format_functions
& (GTK_IMHTML_GROW
|GTK_IMHTML_SHRINK
)) && (font
->size
!= 3 || (oldfont
&& oldfont
->size
== 3)))
3010 gtk_imhtml_font_set_size(imhtml
, font
->size
);
3012 fonts
= g_slist_prepend (fonts
, font
);
3015 case 44: /* BODY (opt) */
3016 if (!(options
& GTK_IMHTML_NO_COLOURS
)) {
3017 char *bgcolor
= gtk_imhtml_get_html_opt (tag
, "BGCOLOR=");
3018 if (bgcolor
&& (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
3019 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3020 ws
[0] = '\0'; wpos
= 0;
3021 /* NEW_BIT(NEW_TEXT_BIT); */
3024 gtk_imhtml_toggle_background(imhtml
, bg
);
3029 case 45: /* A (opt) */
3031 gchar
*href
= gtk_imhtml_get_html_opt (tag
, "HREF=");
3032 if (href
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3033 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3034 ws
[0] = '\0'; wpos
= 0;
3035 gtk_imhtml_toggle_link(imhtml
, href
);
3040 case 46: /* IMG (opt) */
3045 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3046 ws
[0] = '\0'; wpos
= 0;
3048 if (!(imhtml
->format_functions
& GTK_IMHTML_IMAGE
))
3051 id
= gtk_imhtml_get_html_opt(tag
, "ID=");
3053 gtk_imhtml_insert_image_at_iter(imhtml
, atoi(id
), iter
);
3057 src
= gtk_imhtml_get_html_opt(tag
, "SRC=");
3058 alt
= gtk_imhtml_get_html_opt(tag
, "ALT=");
3060 gtk_imhtml_toggle_link(imhtml
, src
);
3061 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, alt
? alt
: src
, -1);
3062 gtk_imhtml_toggle_link(imhtml
, NULL
);
3069 case 47: /* P (opt) */
3070 case 48: /* H3 (opt) */
3071 case 49: /* HTML (opt) */
3073 case 51: /* /CITE */
3074 case 56: /* SPAN (opt) */
3075 /* Inline CSS Support - Douglas Thrift
3081 * text-decoration: underline
3091 gchar
*style
, *color
, *background
, *family
, *size
, *direction
, *alignment
;
3092 gchar
*textdec
, *weight
;
3093 GtkIMHtmlFontDetail
*font
, *oldfont
= NULL
;
3094 style
= gtk_imhtml_get_html_opt (tag
, "style=");
3098 color
= purple_markup_get_css_property (style
, "color");
3099 background
= purple_markup_get_css_property (style
, "background");
3100 family
= purple_markup_get_css_property (style
, "font-family");
3101 size
= purple_markup_get_css_property (style
, "font-size");
3102 textdec
= purple_markup_get_css_property (style
, "text-decoration");
3103 weight
= purple_markup_get_css_property (style
, "font-weight");
3104 direction
= purple_markup_get_css_property (style
, "direction");
3105 alignment
= purple_markup_get_css_property (style
, "text-align");
3108 if (!(color
|| family
|| size
|| background
|| textdec
|| weight
|| direction
|| alignment
)) {
3114 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3115 ws
[0] = '\0'; wpos
= 0;
3116 /* NEW_BIT (NEW_TEXT_BIT); */
3118 /* Bi-Directional text support */
3119 if (direction
&& (!g_ascii_strncasecmp(direction
, "RTL", 3))) {
3120 rtl_direction
= TRUE
;
3121 /* insert RLE character to set direction */
3126 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3127 ws
[0] = '\0'; wpos
= 0;
3131 if (alignment
&& (!g_ascii_strncasecmp(alignment
, "RIGHT", 5))) {
3133 align_line
= gtk_text_iter_get_line(iter
);
3137 font
= g_new0 (GtkIMHtmlFontDetail
, 1);
3139 oldfont
= fonts
->data
;
3141 if (color
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_FORECOLOR
)) {
3142 font
->fore
= parse_css_color(color
);
3143 gtk_imhtml_toggle_forecolor(imhtml
, font
->fore
);
3145 if (oldfont
&& oldfont
->fore
)
3146 font
->fore
= g_strdup(oldfont
->fore
);
3150 if (background
&& !(options
& GTK_IMHTML_NO_COLOURS
) && (imhtml
->format_functions
& GTK_IMHTML_BACKCOLOR
)) {
3151 font
->back
= parse_css_color(background
);
3152 gtk_imhtml_toggle_backcolor(imhtml
, font
->back
);
3154 if (oldfont
&& oldfont
->back
)
3155 font
->back
= g_strdup(oldfont
->back
);
3159 if (family
&& !(options
& GTK_IMHTML_NO_FONTS
) && (imhtml
->format_functions
& GTK_IMHTML_FACE
)) {
3160 font
->face
= family
;
3161 gtk_imhtml_toggle_fontface(imhtml
, font
->face
);
3163 if (oldfont
&& oldfont
->face
)
3164 font
->face
= g_strdup(oldfont
->face
);
3167 if (font
->face
&& (atoi(font
->face
) > 100)) {
3169 /* Maybe it sets a max size on the font face? I seem to
3170 * remember bad things happening if the font size was
3173 font
->face
= g_strdup("100");
3176 if (oldfont
&& oldfont
->sml
)
3177 font
->sml
= g_strdup(oldfont
->sml
);
3179 if (size
&& !(options
& GTK_IMHTML_NO_SIZES
) && (imhtml
->format_functions
& (GTK_IMHTML_SHRINK
|GTK_IMHTML_GROW
))) {
3180 if (g_ascii_strcasecmp(size
, "xx-small") == 0)
3182 else if (g_ascii_strcasecmp(size
, "smaller") == 0
3183 || g_ascii_strcasecmp(size
, "x-small") == 0)
3185 else if (g_ascii_strcasecmp(size
, "medium") == 0)
3187 else if (g_ascii_strcasecmp(size
, "large") == 0
3188 || g_ascii_strcasecmp(size
, "larger") == 0)
3190 else if (g_ascii_strcasecmp(size
, "x-large") == 0)
3192 else if (g_ascii_strcasecmp(size
, "xx-large") == 0)
3196 * TODO: Handle other values, like percentages, or
3197 * lengths specified as em, ex, px, in, cm, mm, pt
3198 * or pc. Or even better, use an actual HTML
3199 * renderer like webkit.
3202 gtk_imhtml_font_set_size(imhtml
, font
->size
);
3206 font
->size
= oldfont
->size
;
3211 font
->underline
= oldfont
->underline
;
3213 if (textdec
&& font
->underline
!= 1
3214 && g_ascii_strcasecmp(textdec
, "underline") == 0
3215 && (imhtml
->format_functions
& GTK_IMHTML_UNDERLINE
)
3216 && !(options
& GTK_IMHTML_NO_FORMATTING
))
3218 gtk_imhtml_toggle_underline(imhtml
);
3219 font
->underline
= 1;
3224 font
->strike
= oldfont
->strike
;
3226 if (textdec
&& font
->strike
!= 1
3227 && g_ascii_strcasecmp(textdec
, "line-through") == 0
3228 && (imhtml
->format_functions
& GTK_IMHTML_STRIKE
)
3229 && !(options
& GTK_IMHTML_NO_FORMATTING
))
3231 gtk_imhtml_toggle_strike(imhtml
);
3238 font
->bold
= oldfont
->bold
;
3242 if(!g_ascii_strcasecmp(weight
, "normal")) {
3244 } else if(!g_ascii_strcasecmp(weight
, "bold")) {
3246 } else if(!g_ascii_strcasecmp(weight
, "bolder")) {
3248 } else if(!g_ascii_strcasecmp(weight
, "lighter")) {
3252 int num
= atoi(weight
);
3258 if (((font
->bold
&& oldfont
&& !oldfont
->bold
) || (oldfont
&& oldfont
->bold
&& !font
->bold
) || (font
->bold
&& !oldfont
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3260 gtk_imhtml_toggle_bold(imhtml
);
3267 fonts
= g_slist_prepend (fonts
, font
);
3270 case 57: /* /SPAN */
3271 /* Inline CSS Support - Douglas Thrift */
3272 if (fonts
&& !imhtml
->wbfo
) {
3273 GtkIMHtmlFontDetail
*oldfont
= NULL
;
3274 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3275 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3276 ws
[0] = '\0'; wpos
= 0;
3277 /* NEW_BIT (NEW_TEXT_BIT); */
3278 fonts
= g_slist_remove (fonts
, font
);
3280 oldfont
= fonts
->data
;
3283 gtk_imhtml_font_set_size(imhtml
, 3);
3284 if (font
->underline
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3285 gtk_imhtml_toggle_underline(imhtml
);
3286 if (font
->strike
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3287 gtk_imhtml_toggle_strike(imhtml
);
3288 if (font
->bold
&& !(options
& GTK_IMHTML_NO_FORMATTING
))
3289 gtk_imhtml_toggle_bold(imhtml
);
3290 if (!(options
& GTK_IMHTML_NO_FONTS
))
3291 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
3292 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3293 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
3294 if (!(options
& GTK_IMHTML_NO_COLOURS
))
3295 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
3300 if ((font
->size
!= oldfont
->size
) && !(options
& GTK_IMHTML_NO_SIZES
))
3301 gtk_imhtml_font_set_size(imhtml
, oldfont
->size
);
3303 if ((font
->underline
!= oldfont
->underline
) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3304 gtk_imhtml_toggle_underline(imhtml
);
3306 if ((font
->strike
!= oldfont
->strike
) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3307 gtk_imhtml_toggle_strike(imhtml
);
3309 if (((font
->bold
&& !oldfont
->bold
) || (oldfont
->bold
&& !font
->bold
)) && !(options
& GTK_IMHTML_NO_FORMATTING
))
3310 gtk_imhtml_toggle_bold(imhtml
);
3312 if (font
->face
&& !purple_strequal(font
->face
, oldfont
->face
) && !(options
& GTK_IMHTML_NO_FONTS
))
3313 gtk_imhtml_toggle_fontface(imhtml
, oldfont
->face
);
3315 if (font
->fore
&& !purple_strequal(font
->fore
, oldfont
->fore
) && !(options
& GTK_IMHTML_NO_COLOURS
))
3316 gtk_imhtml_toggle_forecolor(imhtml
, oldfont
->fore
);
3318 if (font
->back
&& !purple_strequal(font
->back
, oldfont
->back
) && !(options
& GTK_IMHTML_NO_COLOURS
))
3319 gtk_imhtml_toggle_backcolor(imhtml
, oldfont
->back
);
3322 g_free (font
->face
);
3323 g_free (font
->fore
);
3324 g_free (font
->back
);
3332 case 62: /* comment */
3333 /* NEW_BIT (NEW_TEXT_BIT); */
3336 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3338 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3339 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3340 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3342 if (imhtml
->show_comments
&& !(options
& GTK_IMHTML_NO_COMMENTS
)) {
3343 wpos
= g_snprintf (ws
, len
, "%s", tag
);
3344 gtk_text_buffer_insert_with_tags_by_name(imhtml
->text_buffer
, iter
, ws
, wpos
, "comment", NULL
);
3347 ws
[0] = '\0'; wpos
= 0;
3349 /* NEW_BIT (NEW_COMMENT_BIT); */
3356 g_free(tag
); /* This was allocated back in VALID_TAG() */
3357 } else if (imhtml
->edit
.link
== NULL
&&
3358 !(options
& GTK_IMHTML_NO_SMILEY
) &&
3359 gtk_imhtml_is_smiley(imhtml
, fonts
, c
, &smilelen
)) {
3360 GtkIMHtmlFontDetail
*fd
;
3370 sml
= imhtml
->protocol_name
;
3372 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3373 wpos
= g_snprintf (ws
, smilelen
+ 1, "%s", c
);
3375 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, ws
, iter
);
3381 } else if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3384 ws
[wpos
++] = *amp
++;
3388 } else if (*c
== '\n') {
3389 if (!(options
& GTK_IMHTML_NO_NEWLINE
)) {
3392 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3393 ws
[0] = '\0'; wpos
= 0;
3394 /* NEW_BIT (NEW_TEXT_BIT); */
3395 } else if (!br
) { /* Don't insert a space immediately after an HTML break */
3396 /* A newline is defined by HTML as whitespace, which means we have to replace it with a word boundary.
3397 * word breaks vary depending on the language used, so the correct thing to do is to use Pango to determine
3398 * what language this is, determine the proper word boundary to use, and insert that. I'm just going to insert
3399 * a space instead. What are the non-English speakers going to do? Complain in a language I'll understand?
3403 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3404 ws
[0] = '\0'; wpos
= 0;
3408 } else if ((pos
== 0 || wpos
== 0 || isspace(*(c
- 1))) &&
3409 (len_protocol
= gtk_imhtml_is_protocol(c
)) > 0 &&
3410 c
[len_protocol
] && !isspace(c
[len_protocol
]) &&
3411 (c
[len_protocol
] != '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3414 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3415 ws
[0] = '\0'; wpos
= 0;
3417 while (len_protocol
--) {
3418 /* Skip the next len_protocol characters, but
3419 * make sure they're copied into the ws array.
3424 if (!imhtml
->edit
.link
&& (imhtml
->format_functions
& GTK_IMHTML_LINK
)) {
3425 while (*c
&& !isspace((int)*c
) &&
3426 (*c
!= '<' || !gtk_imhtml_is_tag(c
+ 1, NULL
, NULL
, NULL
))) {
3427 if (*c
== '&' && (amp
= purple_markup_unescape_entity(c
, &tlen
))) {
3429 ws
[wpos
++] = *amp
++;
3438 gtk_imhtml_toggle_link(imhtml
, ws
);
3439 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3440 ws
[0] = '\0'; wpos
= 0;
3441 gtk_imhtml_toggle_link(imhtml
, NULL
);
3451 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, ws
, wpos
);
3452 ws
[0] = '\0'; wpos
= 0;
3454 /* NEW_BIT(NEW_TEXT_BIT); */
3457 /* insert RLM+LRM at beginning of the line to set alignment */
3458 GtkTextIter line_iter
;
3460 gtk_text_iter_set_line(&line_iter
, align_line
);
3461 /* insert RLM character to set alignment */
3468 /* insert LRM character to set direction */
3469 /* (alignment=right and direction=LTR) */
3476 gtk_text_buffer_insert(imhtml
->text_buffer
, &line_iter
, ws
, wpos
);
3477 gtk_text_buffer_get_end_iter(gtk_text_iter_get_buffer(&line_iter
), iter
);
3478 ws
[0] = '\0'; wpos
= 0;
3482 GtkIMHtmlFontDetail
*font
= fonts
->data
;
3483 fonts
= g_slist_remove (fonts
, font
);
3484 g_free (font
->face
);
3485 g_free (font
->fore
);
3486 g_free (font
->back
);
3495 gtk_imhtml_close_tags(imhtml
, iter
);
3497 object
= g_object_ref(G_OBJECT(imhtml
));
3498 g_signal_emit(object
, signals
[UPDATE_FORMAT
], 0);
3499 g_object_unref(object
);
3501 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
3504 void gtk_imhtml_remove_smileys(GtkIMHtml
*imhtml
)
3506 g_hash_table_destroy(imhtml
->smiley_data
);
3507 gtk_smiley_tree_destroy(imhtml
->default_smilies
);
3508 imhtml
->smiley_data
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3509 g_free
, (GDestroyNotify
)gtk_smiley_tree_destroy
);
3510 imhtml
->default_smilies
= gtk_smiley_tree_new();
3513 void gtk_imhtml_show_comments (GtkIMHtml
*imhtml
,
3516 #if FALSE && GTK_CHECK_VERSION(2,10,10)
3518 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), "comment");
3520 g_object_set(G_OBJECT(tag
), "invisible", !show
, NULL
);
3522 imhtml
->show_comments
= show
;
3526 gtk_imhtml_get_protocol_name(GtkIMHtml
*imhtml
) {
3527 return imhtml
->protocol_name
;
3531 gtk_imhtml_set_protocol_name(GtkIMHtml
*imhtml
, const gchar
*protocol_name
) {
3532 g_free(imhtml
->protocol_name
);
3533 imhtml
->protocol_name
= g_strdup(protocol_name
);
3537 gtk_imhtml_delete(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
) {
3540 GtkTextIter i
, i_s
, i_e
;
3541 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
3543 if (start
== NULL
) {
3544 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &i_s
);
3549 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &i_e
);
3553 l
= imhtml
->scalables
;
3555 GList
*next
= l
->next
;
3556 struct scalable_data
*sd
= l
->data
;
3557 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3559 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3560 GtkIMHtmlScalable
*scale
= GTK_IMHTML_SCALABLE(sd
->scalable
);
3563 imhtml
->scalables
= g_list_delete_link(imhtml
->scalables
, l
);
3568 sl
= imhtml
->im_images
;
3570 GSList
*next
= sl
->next
;
3571 struct im_image_data
*img_data
= sl
->data
;
3572 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
,
3573 &i
, img_data
->mark
);
3574 if (gtk_text_iter_in_range(&i
, start
, end
)) {
3575 if (imhtml
->funcs
->image_unref
)
3576 imhtml
->funcs
->image_unref(img_data
->id
);
3577 imhtml
->im_images
= g_slist_delete_link(imhtml
->im_images
, sl
);
3582 gtk_text_buffer_delete(imhtml
->text_buffer
, start
, end
);
3584 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(0));
3586 g_object_unref(object
);
3589 void gtk_imhtml_page_up (GtkIMHtml
*imhtml
)
3594 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3595 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3596 rect
.y
- rect
.height
);
3597 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3600 void gtk_imhtml_page_down (GtkIMHtml
*imhtml
)
3605 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
3606 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(imhtml
), &iter
, rect
.x
,
3607 rect
.y
+ rect
.height
);
3608 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &iter
, 0, TRUE
, 0, 0);
3611 /* GtkIMHtmlScalable, gtk_imhtml_image, gtk_imhtml_hr */
3612 GtkIMHtmlScalable
*gtk_imhtml_image_new(GdkPixbuf
*img
, const gchar
*filename
, int id
)
3614 GtkIMHtmlImage
*im_image
= g_malloc(sizeof(GtkIMHtmlImage
));
3616 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3617 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3618 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_image_free
;
3620 im_image
->pixbuf
= img
;
3621 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3622 im_image
->width
= gdk_pixbuf_get_width(img
);
3623 im_image
->height
= gdk_pixbuf_get_height(img
);
3624 im_image
->mark
= NULL
;
3625 im_image
->filename
= g_strdup(filename
);
3627 im_image
->filesel
= NULL
;
3630 return GTK_IMHTML_SCALABLE(im_image
);
3634 animate_image_cb(gpointer data
)
3636 GtkIMHtmlImage
*im_image
;
3642 /* Update the pointer to this GdkPixbuf frame of the animation */
3643 if (gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image
)->iter
, NULL
)) {
3644 GdkPixbuf
*pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3645 g_object_unref(G_OBJECT(im_image
->pixbuf
));
3646 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3648 /* Update the displayed GtkImage */
3649 width
= gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
));
3650 height
= gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image
->image
));
3651 if (width
> 0 && height
> 0)
3653 /* Need to scale the new frame to the same size as the old frame */
3655 tmp
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, width
, height
, GDK_INTERP_BILINEAR
);
3656 gtk_image_set_from_pixbuf(im_image
->image
, tmp
);
3657 g_object_unref(G_OBJECT(tmp
));
3659 /* Display at full-size */
3660 gtk_image_set_from_pixbuf(im_image
->image
, im_image
->pixbuf
);
3664 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3665 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3670 GtkIMHtmlScalable
*gtk_imhtml_animation_new(GdkPixbufAnimation
*anim
, const gchar
*filename
, int id
)
3672 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*) g_new0(GtkIMHtmlAnimation
, 1);
3674 GTK_IMHTML_SCALABLE(im_image
)->scale
= gtk_imhtml_image_scale
;
3675 GTK_IMHTML_SCALABLE(im_image
)->add_to
= gtk_imhtml_image_add_to
;
3676 GTK_IMHTML_SCALABLE(im_image
)->free
= gtk_imhtml_animation_free
;
3678 GTK_IMHTML_ANIMATION(im_image
)->anim
= anim
;
3679 if (gdk_pixbuf_animation_is_static_image(anim
)) {
3680 im_image
->pixbuf
= gdk_pixbuf_animation_get_static_image(anim
);
3681 g_object_ref(im_image
->pixbuf
);
3685 GTK_IMHTML_ANIMATION(im_image
)->iter
= gdk_pixbuf_animation_get_iter(anim
, NULL
);
3686 pb
= gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image
)->iter
);
3687 im_image
->pixbuf
= gdk_pixbuf_copy(pb
);
3688 delay
= MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image
)->iter
), 100);
3689 GTK_IMHTML_ANIMATION(im_image
)->timer
= g_timeout_add(delay
, animate_image_cb
, im_image
);
3691 im_image
->image
= GTK_IMAGE(gtk_image_new_from_pixbuf(im_image
->pixbuf
));
3692 im_image
->width
= gdk_pixbuf_animation_get_width(anim
);
3693 im_image
->height
= gdk_pixbuf_animation_get_height(anim
);
3694 im_image
->filename
= g_strdup(filename
);
3699 return GTK_IMHTML_SCALABLE(im_image
);
3702 void gtk_imhtml_image_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
3704 GtkIMHtmlImage
*im_image
= (GtkIMHtmlImage
*)scale
;
3706 if (im_image
->width
> width
|| im_image
->height
> height
) {
3707 double ratio_w
, ratio_h
, ratio
;
3709 GdkPixbuf
*new_image
= NULL
;
3711 ratio_w
= ((double)width
- 2) / im_image
->width
;
3712 ratio_h
= ((double)height
- 2) / im_image
->height
;
3714 ratio
= (ratio_w
< ratio_h
) ? ratio_w
: ratio_h
;
3716 new_w
= (int)(im_image
->width
* ratio
);
3717 new_h
= (int)(im_image
->height
* ratio
);
3719 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, new_w
, new_h
, GDK_INTERP_BILINEAR
);
3720 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3721 g_object_unref(G_OBJECT(new_image
));
3722 } else if (gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image
->image
)) != im_image
->width
) {
3723 /* Enough space to show the full-size of the image. */
3724 GdkPixbuf
*new_image
;
3726 new_image
= gdk_pixbuf_scale_simple(im_image
->pixbuf
, im_image
->width
, im_image
->height
, GDK_INTERP_BILINEAR
);
3727 gtk_image_set_from_pixbuf(im_image
->image
, new_image
);
3728 g_object_unref(G_OBJECT(new_image
));
3733 image_save_yes_cb(GtkIMHtmlImageSave
*save
, const char *filename
)
3735 GError
*error
= NULL
;
3736 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3738 gtk_widget_destroy(image
->filesel
);
3739 image
->filesel
= NULL
;
3741 if (save
->data
&& save
->datasize
) {
3742 g_file_set_contents(filename
, save
->data
, save
->datasize
, &error
);
3745 GSList
*formats
= gdk_pixbuf_get_formats();
3749 GdkPixbufFormat
*format
= formats
->data
;
3750 gchar
**extensions
= gdk_pixbuf_format_get_extensions(format
);
3751 gpointer p
= extensions
;
3753 while(gdk_pixbuf_format_is_writable(format
) && extensions
&& extensions
[0]){
3754 gchar
*fmt_ext
= extensions
[0];
3755 const gchar
* file_ext
= filename
+ strlen(filename
) - strlen(fmt_ext
);
3757 if(!g_ascii_strcasecmp(fmt_ext
, file_ext
)){
3758 type
= gdk_pixbuf_format_get_name(format
);
3770 formats
= formats
->next
;
3773 g_slist_free(formats
);
3775 /* If I can't find a valid type, I will just tell the user about it and then assume
3778 char *basename
, *tmp
;
3780 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3781 _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
3783 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3784 gtk_widget_show(dialog
);
3786 type
= g_strdup("png");
3787 dirname
= g_path_get_dirname(filename
);
3788 basename
= g_path_get_basename(filename
);
3789 tmp
= strrchr(basename
, '.');
3792 newfilename
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s.png", dirname
, basename
);
3797 * We're able to save the file in it's original format, so we
3798 * can use the original file name.
3800 newfilename
= g_strdup(filename
);
3803 gdk_pixbuf_save(image
->pixbuf
, newfilename
, type
, &error
, NULL
);
3805 g_free(newfilename
);
3810 GtkWidget
*dialog
= gtk_message_dialog_new_with_markup(NULL
, 0, GTK_MESSAGE_ERROR
, GTK_BUTTONS_OK
,
3811 _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"), error
->message
);
3812 g_signal_connect_swapped(dialog
, "response", G_CALLBACK (gtk_widget_destroy
), dialog
);
3813 gtk_widget_show(dialog
);
3814 g_error_free(error
);
3819 image_save_check_if_exists_cb(GtkWidget
*widget
, gint response
, GtkIMHtmlImageSave
*save
)
3822 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3824 if (response
!= GTK_RESPONSE_ACCEPT
) {
3825 gtk_widget_destroy(widget
);
3826 image
->filesel
= NULL
;
3830 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget
));
3833 * XXX - We should probably prompt the user to determine if they really
3834 * want to overwrite the file or not. However, I don't feel like doing
3835 * that, so we're just always going to overwrite if the file exists.
3838 if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
3840 image_save_yes_cb(image, filename);
3843 image_save_yes_cb(save
, filename
);
3849 gtk_imhtml_image_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3851 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3853 if (image
->filesel
!= NULL
) {
3854 gtk_window_present(GTK_WINDOW(image
->filesel
));
3858 image
->filesel
= gtk_file_chooser_dialog_new(_("Save Image"),
3860 GTK_FILE_CHOOSER_ACTION_SAVE
,
3861 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
3862 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
3864 gtk_dialog_set_default_response(GTK_DIALOG(image
->filesel
), GTK_RESPONSE_ACCEPT
);
3865 if (image
->filename
!= NULL
)
3866 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(image
->filesel
), image
->filename
);
3867 g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(image
->filesel
)), "response",
3868 G_CALLBACK(image_save_check_if_exists_cb
), save
);
3870 gtk_widget_show(image
->filesel
);
3874 gtk_imhtml_custom_smiley_save(GtkWidget
*w
, GtkIMHtmlImageSave
*save
)
3876 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3878 /* Create an add dialog */
3879 PidginSmiley
*editor
= pidgin_smiley_edit(NULL
, NULL
);
3880 pidgin_smiley_editor_set_shortcut(editor
, image
->filename
);
3881 pidgin_smiley_editor_set_image(editor
, image
->pixbuf
);
3882 pidgin_smiley_editor_set_data(editor
, save
->data
, save
->datasize
);
3886 * So, um, AIM Direct IM lets you send any file, not just images. You can
3887 * just insert a sound or a file or whatever in a conversation. It's
3888 * basically like file transfer, except there is an icon to open the file
3889 * embedded in the conversation. Someone should make the Purple core handle
3892 static gboolean
gtk_imhtml_image_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlImageSave
*save
)
3894 GdkEventButton
*event_button
= (GdkEventButton
*) event
;
3895 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)save
->image
;
3897 if (event
->type
== GDK_BUTTON_RELEASE
) {
3898 if(event_button
->button
== 3) {
3899 GtkWidget
*img
, *item
, *menu
;
3900 menu
= gtk_menu_new();
3902 /* buttons and such */
3903 img
= gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
);
3904 item
= gtk_image_menu_item_new_with_mnemonic(_("_Save Image..."));
3905 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3906 g_signal_connect(G_OBJECT(item
), "activate", G_CALLBACK(gtk_imhtml_image_save
), save
);
3907 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3909 /* Add menu item for adding custom smiley to local smileys */
3910 /* we only add the menu if the image is of "custom smiley size"
3912 if (image
->width
<= 96 && image
->height
<= 96) {
3913 img
= gtk_image_new_from_stock(GTK_STOCK_ADD
, GTK_ICON_SIZE_MENU
);
3914 item
= gtk_image_menu_item_new_with_mnemonic(_("_Add Custom Smiley..."));
3915 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item
), img
);
3916 g_signal_connect(G_OBJECT(item
), "activate",
3917 G_CALLBACK(gtk_imhtml_custom_smiley_save
), save
);
3918 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
3921 gtk_widget_show_all(menu
);
3922 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
,
3923 event_button
->button
, event_button
->time
);
3928 if(event
->type
== GDK_BUTTON_PRESS
&& event_button
->button
== 3)
3929 return TRUE
; /* Clicking the right mouse button on a link shouldn't
3930 be caught by the regular GtkTextView menu */
3932 return FALSE
; /* Let clicks go through if we didn't catch anything */
3936 static gboolean
gtk_imhtml_smiley_clicked(GtkWidget
*w
, GdkEvent
*event
, GtkIMHtmlSmiley
*smiley
)
3938 GdkPixbufAnimation
*anim
= NULL
;
3939 GtkIMHtmlImageSave
*save
= NULL
;
3942 if (event
->type
!= GDK_BUTTON_RELEASE
|| ((GdkEventButton
*)event
)->button
!= 3)
3945 anim
= gtk_smiley_get_image(smiley
);
3949 save
= g_new0(GtkIMHtmlImageSave
, 1);
3950 save
->image
= (GtkIMHtmlScalable
*)gtk_imhtml_animation_new(anim
, smiley
->smile
, 0);
3951 save
->data
= smiley
->data
; /* Do not need to memdup here, since the smiley is not
3952 destroyed before this GtkIMHtmlImageSave */
3953 save
->datasize
= smiley
->datasize
;
3954 ret
= gtk_imhtml_image_clicked(w
, event
, save
);
3955 g_object_set_data_full(G_OBJECT(w
), "image-data", save
->image
, (GDestroyNotify
)gtk_imhtml_animation_free
);
3956 g_object_set_data_full(G_OBJECT(w
), "image-save-data", save
, (GDestroyNotify
)g_free
);
3960 void gtk_imhtml_image_free(GtkIMHtmlScalable
*scale
)
3962 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3964 g_object_unref(image
->pixbuf
);
3965 g_free(image
->filename
);
3967 gtk_widget_destroy(image
->filesel
);
3971 void gtk_imhtml_animation_free(GtkIMHtmlScalable
*scale
)
3973 GtkIMHtmlAnimation
*animation
= (GtkIMHtmlAnimation
*)scale
;
3975 if (animation
->timer
> 0)
3976 g_source_remove(animation
->timer
);
3977 if (animation
->iter
!= NULL
)
3978 g_object_unref(animation
->iter
);
3979 g_object_unref(animation
->anim
);
3981 gtk_imhtml_image_free(scale
);
3984 void gtk_imhtml_image_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
3986 GtkIMHtmlImage
*image
= (GtkIMHtmlImage
*)scale
;
3987 GtkWidget
*box
= gtk_event_box_new();
3989 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
3990 GtkIMHtmlImageSave
*save
;
3992 gtk_container_add(GTK_CONTAINER(box
), GTK_WIDGET(image
->image
));
3994 if(!gtk_check_version(2, 4, 0))
3995 g_object_set(G_OBJECT(box
), "visible-window", FALSE
, NULL
);
3997 gtk_widget_show(GTK_WIDGET(image
->image
));
3998 gtk_widget_show(box
);
4000 tag
= g_strdup_printf("<IMG ID=\"%d\">", image
->id
);
4001 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", tag
, g_free
);
4002 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "[Image]");
4004 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), box
, anchor
);
4006 save
= g_new0(GtkIMHtmlImageSave
, 1);
4007 save
->image
= scale
;
4008 g_signal_connect(G_OBJECT(box
), "event", G_CALLBACK(gtk_imhtml_image_clicked
), save
);
4009 g_object_set_data_full(G_OBJECT(box
), "image-save-data", save
, (GDestroyNotify
)g_free
);
4012 GtkIMHtmlScalable
*gtk_imhtml_hr_new()
4014 GtkIMHtmlHr
*hr
= g_malloc(sizeof(GtkIMHtmlHr
));
4016 GTK_IMHTML_SCALABLE(hr
)->scale
= gtk_imhtml_hr_scale
;
4017 GTK_IMHTML_SCALABLE(hr
)->add_to
= gtk_imhtml_hr_add_to
;
4018 GTK_IMHTML_SCALABLE(hr
)->free
= gtk_imhtml_hr_free
;
4020 hr
->sep
= gtk_hseparator_new();
4021 gtk_widget_set_size_request(hr
->sep
, 5000, 2);
4022 gtk_widget_show(hr
->sep
);
4024 return GTK_IMHTML_SCALABLE(hr
);
4027 void gtk_imhtml_hr_scale(GtkIMHtmlScalable
*scale
, int width
, int height
)
4029 gtk_widget_set_size_request(((GtkIMHtmlHr
*)scale
)->sep
, width
- 2, 2);
4032 void gtk_imhtml_hr_add_to(GtkIMHtmlScalable
*scale
, GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
4034 GtkIMHtmlHr
*hr
= (GtkIMHtmlHr
*)scale
;
4035 GtkTextChildAnchor
*anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
4036 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_htmltext", "<hr>");
4037 g_object_set_data(G_OBJECT(anchor
), "gtkimhtml_plaintext", "\n---\n");
4038 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), hr
->sep
, anchor
);
4041 void gtk_imhtml_hr_free(GtkIMHtmlScalable
*scale
)
4046 gboolean
gtk_imhtml_search_find(GtkIMHtml
*imhtml
, const gchar
*text
)
4048 GtkTextIter iter
, start
, end
;
4049 gboolean new_search
= TRUE
;
4050 GtkTextMark
*start_mark
;
4052 g_return_val_if_fail(imhtml
!= NULL
, FALSE
);
4053 g_return_val_if_fail(text
!= NULL
, FALSE
);
4055 start_mark
= gtk_text_buffer_get_mark(imhtml
->text_buffer
, "search");
4057 if (start_mark
&& imhtml
->search_string
&& purple_strequal(text
, imhtml
->search_string
))
4061 gtk_imhtml_search_clear(imhtml
);
4062 g_free(imhtml
->search_string
);
4063 imhtml
->search_string
= g_strdup(text
);
4064 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4066 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
,
4070 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4071 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4072 &start
, &end
, NULL
))
4074 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4075 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4078 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &iter
, &end
);
4080 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4081 while (gtk_source_iter_backward_search(&start
, imhtml
->search_string
,
4082 GTK_SOURCE_SEARCH_VISIBLE_ONLY
|
4083 GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4084 &start
, &end
, NULL
));
4088 else if (!new_search
)
4090 /* We hit the end, so start at the beginning again. */
4091 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &iter
);
4093 if (gtk_source_iter_backward_search(&iter
, imhtml
->search_string
,
4094 GTK_SOURCE_SEARCH_VISIBLE_ONLY
| GTK_SOURCE_SEARCH_CASE_INSENSITIVE
,
4095 &start
, &end
, NULL
))
4097 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(imhtml
), &start
, 0, TRUE
, 0, 0);
4098 gtk_text_buffer_create_mark(imhtml
->text_buffer
, "search", &start
, FALSE
);
4108 void gtk_imhtml_search_clear(GtkIMHtml
*imhtml
)
4110 GtkTextIter start
, end
;
4112 g_return_if_fail(imhtml
!= NULL
);
4114 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
4115 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
4117 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "search", &start
, &end
);
4118 g_free(imhtml
->search_string
);
4119 imhtml
->search_string
= NULL
;
4122 static GtkTextTag
*find_font_forecolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4127 g_snprintf(str
, sizeof(str
), "FORECOLOR %s", color
);
4129 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4132 if (!gdk_color_parse(color
, &gcolor
)) {
4135 strncpy(&tmp
[1], color
, 7);
4137 if (!gdk_color_parse(tmp
, &gcolor
))
4138 gdk_color_parse("black", &gcolor
);
4140 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", &gcolor
, NULL
);
4146 static GtkTextTag
*find_font_backcolor_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4151 g_snprintf(str
, sizeof(str
), "BACKCOLOR %s", color
);
4153 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4156 if (!gdk_color_parse(color
, &gcolor
)) {
4159 strncpy(&tmp
[1], color
, 7);
4161 if (!gdk_color_parse(tmp
, &gcolor
))
4162 gdk_color_parse("white", &gcolor
);
4164 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "background-gdk", &gcolor
, NULL
);
4170 static GtkTextTag
*find_font_background_tag(GtkIMHtml
*imhtml
, gchar
*color
)
4175 g_snprintf(str
, sizeof(str
), "BACKGROUND %s", color
);
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
, NULL
);
4184 static GtkTextTag
*find_font_face_tag(GtkIMHtml
*imhtml
, gchar
*face
)
4189 g_snprintf(str
, sizeof(str
), "FONT FACE %s", face
);
4192 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4194 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "family", face
, NULL
);
4199 static GtkTextTag
*find_font_size_tag(GtkIMHtml
*imhtml
, int size
)
4204 g_snprintf(str
, sizeof(str
), "FONT SIZE %d", size
);
4207 tag
= gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml
->text_buffer
), str
);
4209 /* For reasons I don't understand, setting "scale" here scaled
4210 * based on some default size other than my theme's default
4211 * size. Our size 4 was actually smaller than our size 3 for
4212 * me. So this works around that oddity.
4214 GtkTextAttributes
*attr
= gtk_text_view_get_default_attributes(GTK_TEXT_VIEW(imhtml
));
4215 tag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "size",
4216 (gint
) (pango_font_description_get_size(attr
->font
) *
4217 (double) POINT_SIZE(size
)), NULL
);
4218 gtk_text_attributes_unref(attr
);
4224 static void remove_tag_by_prefix(GtkIMHtml
*imhtml
, const GtkTextIter
*i
, const GtkTextIter
*e
,
4225 const char *prefix
, guint len
, gboolean homo
)
4230 tags
= gtk_text_iter_get_tags(i
);
4232 for (l
= tags
; l
; l
= l
->next
) {
4233 GtkTextTag
*tag
= l
->data
;
4235 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4236 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, i
, e
);
4246 while (gtk_text_iter_forward_char(&iter
) && !gtk_text_iter_equal(&iter
, e
)) {
4247 if (gtk_text_iter_begins_tag(&iter
, NULL
)) {
4248 tags
= gtk_text_iter_get_toggled_tags(&iter
, TRUE
);
4250 for (l
= tags
; l
; l
= l
->next
) {
4251 GtkTextTag
*tag
= l
->data
;
4253 if (tag
->name
&& !strncmp(tag
->name
, prefix
, len
))
4254 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, &iter
, e
);
4262 static void remove_font_size(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4264 remove_tag_by_prefix(imhtml
, i
, e
, "FONT SIZE ", 10, homo
);
4267 static void remove_font_face(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4269 remove_tag_by_prefix(imhtml
, i
, e
, "FONT FACE ", 10, homo
);
4272 static void remove_font_forecolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4274 remove_tag_by_prefix(imhtml
, i
, e
, "FORECOLOR ", 10, homo
);
4277 static void remove_font_backcolor(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4279 remove_tag_by_prefix(imhtml
, i
, e
, "BACKCOLOR ", 10, homo
);
4282 static void remove_font_background(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4284 remove_tag_by_prefix(imhtml
, i
, e
, "BACKGROUND ", 10, homo
);
4287 static void remove_font_link(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
)
4289 remove_tag_by_prefix(imhtml
, i
, e
, "LINK ", 5, homo
);
4293 imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4295 GtkTextIter start
, end
;
4297 if (!imhtml
->editable
)
4300 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4303 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4304 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4305 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4306 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4307 remove_font_size(imhtml
, &start
, &end
, FALSE
);
4308 remove_font_face(imhtml
, &start
, &end
, FALSE
);
4309 remove_font_forecolor(imhtml
, &start
, &end
, FALSE
);
4310 remove_font_backcolor(imhtml
, &start
, &end
, FALSE
);
4311 remove_font_background(imhtml
, &start
, &end
, FALSE
);
4312 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4314 imhtml
->edit
.bold
= 0;
4315 imhtml
->edit
.italic
= 0;
4316 imhtml
->edit
.underline
= 0;
4317 imhtml
->edit
.strike
= 0;
4318 imhtml
->edit
.fontsize
= 0;
4320 g_free(imhtml
->edit
.fontface
);
4321 imhtml
->edit
.fontface
= NULL
;
4323 g_free(imhtml
->edit
.forecolor
);
4324 imhtml
->edit
.forecolor
= NULL
;
4326 g_free(imhtml
->edit
.backcolor
);
4327 imhtml
->edit
.backcolor
= NULL
;
4329 g_free(imhtml
->edit
.background
);
4330 imhtml
->edit
.background
= NULL
;
4333 /* Editable stuff */
4334 static void preinsert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*iter
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4336 imhtml
->insert_offset
= gtk_text_iter_get_offset(iter
);
4339 static void insert_ca_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextChildAnchor
*arg2
, gpointer user_data
)
4344 gtk_text_iter_backward_char(&start
);
4346 gtk_imhtml_apply_tags_on_insert(user_data
, &start
, arg1
);
4349 static void insert_cb(GtkTextBuffer
*buffer
, GtkTextIter
*end
, gchar
*text
, gint len
, GtkIMHtml
*imhtml
)
4357 gtk_text_iter_set_offset(&start
, imhtml
->insert_offset
);
4359 gtk_imhtml_apply_tags_on_insert(imhtml
, &start
, end
);
4362 static void delete_cb(GtkTextBuffer
*buffer
, GtkTextIter
*start
, GtkTextIter
*end
, GtkIMHtml
*imhtml
)
4366 tags
= gtk_text_iter_get_tags(start
);
4367 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4368 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4370 if (tag
&& /* Remove the formatting only if */
4371 gtk_text_iter_starts_word(start
) && /* beginning of a word */
4372 gtk_text_iter_begins_tag(start
, tag
) && /* the tag starts with the selection */
4373 (!gtk_text_iter_has_tag(end
, tag
) || /* the tag ends within the selection */
4374 gtk_text_iter_ends_tag(end
, tag
))) {
4375 gtk_text_buffer_remove_tag(imhtml
->text_buffer
, tag
, start
, end
);
4377 strncmp(tag
->name
, "LINK ", 5) == 0 && imhtml
->edit
.link
) {
4378 gtk_imhtml_toggle_link(imhtml
, NULL
);
4385 static void gtk_imhtml_apply_tags_on_insert(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
4387 if (imhtml
->edit
.bold
)
4388 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4390 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", start
, end
);
4392 if (imhtml
->edit
.italic
)
4393 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4395 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", start
, end
);
4397 if (imhtml
->edit
.underline
)
4398 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4400 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", start
, end
);
4402 if (imhtml
->edit
.strike
)
4403 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4405 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", start
, end
);
4407 if (imhtml
->edit
.forecolor
) {
4408 remove_font_forecolor(imhtml
, start
, end
, TRUE
);
4409 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4410 find_font_forecolor_tag(imhtml
, imhtml
->edit
.forecolor
),
4414 if (imhtml
->edit
.backcolor
) {
4415 remove_font_backcolor(imhtml
, start
, end
, TRUE
);
4416 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4417 find_font_backcolor_tag(imhtml
, imhtml
->edit
.backcolor
),
4421 if (imhtml
->edit
.background
) {
4422 remove_font_background(imhtml
, start
, end
, TRUE
);
4423 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4424 find_font_background_tag(imhtml
, imhtml
->edit
.background
),
4427 if (imhtml
->edit
.fontface
) {
4428 remove_font_face(imhtml
, start
, end
, TRUE
);
4429 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4430 find_font_face_tag(imhtml
, imhtml
->edit
.fontface
),
4434 if (imhtml
->edit
.fontsize
) {
4435 remove_font_size(imhtml
, start
, end
, TRUE
);
4436 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4437 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
),
4441 if (imhtml
->edit
.link
) {
4442 remove_font_link(imhtml
, start
, end
, TRUE
);
4443 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4449 void gtk_imhtml_set_editable(GtkIMHtml
*imhtml
, gboolean editable
)
4451 gtk_text_view_set_editable(GTK_TEXT_VIEW(imhtml
), editable
);
4453 * We need a visible caret for accessibility, so mouseless
4454 * people can highlight stuff.
4456 /* gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(imhtml), editable); */
4457 if (editable
&& !imhtml
->editable
) {
4458 g_signal_connect_after(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
), "mark-set",
4459 G_CALLBACK(mark_set_cb
), imhtml
);
4460 g_signal_connect(G_OBJECT(imhtml
), "backspace", G_CALLBACK(smart_backspace_cb
), NULL
);
4461 } else if (!editable
&& imhtml
->editable
) {
4462 g_signal_handlers_disconnect_by_func(G_OBJECT(GTK_IMHTML(imhtml
)->text_buffer
),
4463 mark_set_cb
, imhtml
);
4464 g_signal_handlers_disconnect_by_func(G_OBJECT(imhtml
),
4465 smart_backspace_cb
, NULL
);
4468 imhtml
->editable
= editable
;
4469 imhtml
->format_functions
= GTK_IMHTML_ALL
;
4472 void gtk_imhtml_set_whole_buffer_formatting_only(GtkIMHtml
*imhtml
, gboolean wbfo
)
4474 g_return_if_fail(imhtml
!= NULL
);
4476 imhtml
->wbfo
= wbfo
;
4479 void gtk_imhtml_set_format_functions(GtkIMHtml
*imhtml
, GtkIMHtmlButtons buttons
)
4481 GObject
*object
= g_object_ref(G_OBJECT(imhtml
));
4482 imhtml
->format_functions
= buttons
;
4483 g_signal_emit(object
, signals
[BUTTONS_UPDATE
], 0, buttons
);
4484 g_object_unref(object
);
4487 GtkIMHtmlButtons
gtk_imhtml_get_format_functions(GtkIMHtml
*imhtml
)
4489 return imhtml
->format_functions
;
4492 void gtk_imhtml_get_current_format(GtkIMHtml
*imhtml
, gboolean
*bold
,
4493 gboolean
*italic
, gboolean
*underline
)
4496 (*bold
) = imhtml
->edit
.bold
;
4498 (*italic
) = imhtml
->edit
.italic
;
4499 if (underline
!= NULL
)
4500 (*underline
) = imhtml
->edit
.underline
;
4504 gtk_imhtml_get_current_fontface(GtkIMHtml
*imhtml
)
4506 return g_strdup(imhtml
->edit
.fontface
);
4510 gtk_imhtml_get_current_forecolor(GtkIMHtml
*imhtml
)
4512 return g_strdup(imhtml
->edit
.forecolor
);
4516 gtk_imhtml_get_current_backcolor(GtkIMHtml
*imhtml
)
4518 return g_strdup(imhtml
->edit
.backcolor
);
4522 gtk_imhtml_get_current_background(GtkIMHtml
*imhtml
)
4524 return g_strdup(imhtml
->edit
.background
);
4528 gtk_imhtml_get_current_fontsize(GtkIMHtml
*imhtml
)
4530 return imhtml
->edit
.fontsize
;
4533 gboolean
gtk_imhtml_get_editable(GtkIMHtml
*imhtml
)
4535 return imhtml
->editable
;
4539 gtk_imhtml_clear_formatting(GtkIMHtml
*imhtml
)
4543 object
= g_object_ref(G_OBJECT(imhtml
));
4544 g_signal_emit(object
, signals
[CLEAR_FORMAT
], 0);
4546 gtk_widget_grab_focus(GTK_WIDGET(imhtml
));
4548 g_object_unref(object
);
4552 * I had this crazy idea about changing the text cursor color to reflex the foreground color
4553 * of the text about to be entered. This is the place you'd do it, along with the place where
4554 * we actually set a new foreground color.
4555 * I may not do this, because people will bitch about Purple overriding their gtk theme's cursor
4558 * Just in case I do do this, I asked about what to set the secondary text cursor to.
4560 * (12:45:27) ?? ???: secondary_cursor_color = (rgb(background) + rgb(primary_cursor_color) ) / 2
4561 * (12:45:55) ?? ???: understand?
4562 * (12:46:14) Tim: yeah. i didn't know there was an exact formula
4563 * (12:46:56) ?? ???: u might need to extract separate each color from RGB
4566 static void mark_set_cb(GtkTextBuffer
*buffer
, GtkTextIter
*arg1
, GtkTextMark
*mark
,
4572 if (mark
!= gtk_text_buffer_get_insert(buffer
))
4575 if (!gtk_text_buffer_get_char_count(buffer
))
4578 imhtml
->edit
.bold
= imhtml
->edit
.italic
= imhtml
->edit
.underline
= imhtml
->edit
.strike
= FALSE
;
4579 g_free(imhtml
->edit
.forecolor
);
4580 imhtml
->edit
.forecolor
= NULL
;
4582 g_free(imhtml
->edit
.backcolor
);
4583 imhtml
->edit
.backcolor
= NULL
;
4585 g_free(imhtml
->edit
.fontface
);
4586 imhtml
->edit
.fontface
= NULL
;
4588 imhtml
->edit
.fontsize
= 0;
4589 imhtml
->edit
.link
= NULL
;
4591 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4594 if (gtk_text_iter_is_end(&iter
))
4595 tags
= gtk_text_iter_get_toggled_tags(&iter
, FALSE
);
4597 tags
= gtk_text_iter_get_tags(&iter
);
4599 for (l
= tags
; l
!= NULL
; l
= l
->next
) {
4600 GtkTextTag
*tag
= GTK_TEXT_TAG(l
->data
);
4603 if (purple_strequal(tag
->name
, "BOLD"))
4604 imhtml
->edit
.bold
= TRUE
;
4605 else if (purple_strequal(tag
->name
, "ITALICS"))
4606 imhtml
->edit
.italic
= TRUE
;
4607 else if (purple_strequal(tag
->name
, "UNDERLINE"))
4608 imhtml
->edit
.underline
= TRUE
;
4609 else if (purple_strequal(tag
->name
, "STRIKE"))
4610 imhtml
->edit
.strike
= TRUE
;
4611 else if (strncmp(tag
->name
, "FORECOLOR ", 10) == 0)
4612 imhtml
->edit
.forecolor
= g_strdup(&(tag
->name
)[10]);
4613 else if (strncmp(tag
->name
, "BACKCOLOR ", 10) == 0)
4614 imhtml
->edit
.backcolor
= g_strdup(&(tag
->name
)[10]);
4615 else if (strncmp(tag
->name
, "FONT FACE ", 10) == 0)
4616 imhtml
->edit
.fontface
= g_strdup(&(tag
->name
)[10]);
4617 else if (strncmp(tag
->name
, "FONT SIZE ", 10) == 0)
4618 imhtml
->edit
.fontsize
= strtol(&(tag
->name
)[10], NULL
, 10);
4619 else if ((strncmp(tag
->name
, "LINK ", 5) == 0) && !gtk_text_iter_is_end(&iter
))
4620 imhtml
->edit
.link
= tag
;
4627 static void imhtml_emit_signal_for_format(GtkIMHtml
*imhtml
, GtkIMHtmlButtons button
)
4631 g_return_if_fail(imhtml
!= NULL
);
4633 object
= g_object_ref(G_OBJECT(imhtml
));
4634 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4635 g_object_unref(object
);
4638 static void imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4640 GtkTextIter start
, end
;
4642 imhtml
->edit
.bold
= !imhtml
->edit
.bold
;
4644 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4647 if (imhtml
->edit
.bold
)
4648 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4650 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "BOLD", &start
, &end
);
4653 void gtk_imhtml_toggle_bold(GtkIMHtml
*imhtml
)
4655 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_BOLD
);
4658 static void imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4660 GtkTextIter start
, end
;
4662 imhtml
->edit
.italic
= !imhtml
->edit
.italic
;
4664 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4667 if (imhtml
->edit
.italic
)
4668 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4670 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "ITALICS", &start
, &end
);
4673 void gtk_imhtml_toggle_italic(GtkIMHtml
*imhtml
)
4675 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_ITALIC
);
4678 static void imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4680 GtkTextIter start
, end
;
4682 imhtml
->edit
.underline
= !imhtml
->edit
.underline
;
4684 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4687 if (imhtml
->edit
.underline
)
4688 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4690 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "UNDERLINE", &start
, &end
);
4693 void gtk_imhtml_toggle_underline(GtkIMHtml
*imhtml
)
4695 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_UNDERLINE
);
4698 static void imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4700 GtkTextIter start
, end
;
4702 imhtml
->edit
.strike
= !imhtml
->edit
.strike
;
4704 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4707 if (imhtml
->edit
.strike
)
4708 gtk_text_buffer_apply_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4710 gtk_text_buffer_remove_tag_by_name(imhtml
->text_buffer
, "STRIKE", &start
, &end
);
4713 void gtk_imhtml_toggle_strike(GtkIMHtml
*imhtml
)
4715 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_STRIKE
);
4718 void gtk_imhtml_font_set_size(GtkIMHtml
*imhtml
, gint size
)
4721 GtkTextIter start
, end
;
4723 imhtml
->edit
.fontsize
= size
;
4725 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4728 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4729 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4730 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4732 object
= g_object_ref(G_OBJECT(imhtml
));
4733 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_SHRINK
| GTK_IMHTML_GROW
);
4734 g_object_unref(object
);
4737 static void imhtml_font_shrink(GtkIMHtml
*imhtml
)
4739 GtkTextIter start
, end
;
4741 if (imhtml
->edit
.fontsize
== 1)
4744 if (!imhtml
->edit
.fontsize
)
4745 imhtml
->edit
.fontsize
= 2;
4747 imhtml
->edit
.fontsize
--;
4749 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4751 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4752 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4753 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4756 void gtk_imhtml_font_shrink(GtkIMHtml
*imhtml
)
4758 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_SHRINK
);
4761 static void imhtml_font_grow(GtkIMHtml
*imhtml
)
4763 GtkTextIter start
, end
;
4765 if (imhtml
->edit
.fontsize
== MAX_FONT_SIZE
)
4768 if (!imhtml
->edit
.fontsize
)
4769 imhtml
->edit
.fontsize
= 4;
4771 imhtml
->edit
.fontsize
++;
4773 if (!imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4775 remove_font_size(imhtml
, &start
, &end
, imhtml
->wbfo
);
4776 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4777 find_font_size_tag(imhtml
, imhtml
->edit
.fontsize
), &start
, &end
);
4780 void gtk_imhtml_font_grow(GtkIMHtml
*imhtml
)
4782 imhtml_emit_signal_for_format(imhtml
, GTK_IMHTML_GROW
);
4785 static gboolean
gtk_imhtml_toggle_str_tag(GtkIMHtml
*imhtml
, const char *value
, char **edit_field
,
4786 void (*remove_func
)(GtkIMHtml
*imhtml
, GtkTextIter
*i
, GtkTextIter
*e
, gboolean homo
),
4787 GtkTextTag
*(find_func
)(GtkIMHtml
*imhtml
, gchar
*color
), GtkIMHtmlButtons button
)
4793 g_free(*edit_field
);
4796 if (value
&& *value
)
4798 *edit_field
= g_strdup(value
);
4800 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
)) {
4801 remove_func(imhtml
, &start
, &end
, imhtml
->wbfo
);
4802 gtk_text_buffer_apply_tag(imhtml
->text_buffer
,
4803 find_func(imhtml
, *edit_field
), &start
, &end
);
4808 if (imhtml_get_iter_bounds(imhtml
, &start
, &end
))
4809 remove_func(imhtml
, &start
, &end
, TRUE
); /* 'TRUE' or 'imhtml->wbfo'? */
4812 object
= g_object_ref(G_OBJECT(imhtml
));
4813 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, button
);
4814 g_object_unref(object
);
4816 return *edit_field
!= NULL
;
4819 gboolean
gtk_imhtml_toggle_forecolor(GtkIMHtml
*imhtml
, const char *color
)
4821 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.forecolor
,
4822 remove_font_forecolor
, find_font_forecolor_tag
,
4823 GTK_IMHTML_FORECOLOR
);
4826 gboolean
gtk_imhtml_toggle_backcolor(GtkIMHtml
*imhtml
, const char *color
)
4828 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.backcolor
,
4829 remove_font_backcolor
, find_font_backcolor_tag
,
4830 GTK_IMHTML_BACKCOLOR
);
4833 gboolean
gtk_imhtml_toggle_background(GtkIMHtml
*imhtml
, const char *color
)
4835 return gtk_imhtml_toggle_str_tag(imhtml
, color
, &imhtml
->edit
.background
,
4836 remove_font_background
, find_font_background_tag
,
4837 GTK_IMHTML_BACKGROUND
);
4840 gboolean
gtk_imhtml_toggle_fontface(GtkIMHtml
*imhtml
, const char *face
)
4842 return gtk_imhtml_toggle_str_tag(imhtml
, face
, &imhtml
->edit
.fontface
,
4843 remove_font_face
, find_font_face_tag
,
4847 void gtk_imhtml_toggle_link(GtkIMHtml
*imhtml
, const char *url
)
4850 GtkTextIter start
, end
;
4851 GtkTextTag
*linktag
;
4852 static guint linkno
= 0;
4854 GdkColor
*color
= NULL
;
4856 imhtml
->edit
.link
= NULL
;
4859 g_snprintf(str
, sizeof(str
), "LINK %d", linkno
++);
4862 gtk_widget_style_get(GTK_WIDGET(imhtml
), "hyperlink-color", &color
, NULL
);
4864 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground-gdk", color
, "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4865 gdk_color_free(color
);
4867 imhtml
->edit
.link
= linktag
= gtk_text_buffer_create_tag(imhtml
->text_buffer
, str
, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE
, NULL
);
4869 g_object_set_data_full(G_OBJECT(linktag
), "link_url", g_strdup(url
), g_free
);
4870 g_signal_connect(G_OBJECT(linktag
), "event", G_CALLBACK(tag_event
), NULL
);
4872 if (imhtml
->editable
&& gtk_text_buffer_get_selection_bounds(imhtml
->text_buffer
, &start
, &end
)) {
4873 remove_font_link(imhtml
, &start
, &end
, FALSE
);
4874 gtk_text_buffer_apply_tag(imhtml
->text_buffer
, linktag
, &start
, &end
);
4878 object
= g_object_ref(G_OBJECT(imhtml
));
4879 g_signal_emit(object
, signals
[TOGGLE_FORMAT
], 0, GTK_IMHTML_LINK
);
4880 g_object_unref(object
);
4883 void gtk_imhtml_insert_link(GtkIMHtml
*imhtml
, GtkTextMark
*mark
, const char *url
, const char *text
)
4887 /* Delete any currently selected text */
4888 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4890 gtk_imhtml_toggle_link(imhtml
, url
);
4891 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4892 gtk_text_buffer_insert(imhtml
->text_buffer
, &iter
, text
, -1);
4893 gtk_imhtml_toggle_link(imhtml
, NULL
);
4896 void gtk_imhtml_insert_smiley(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
)
4901 /* Delete any currently selected text */
4902 gtk_text_buffer_delete_selection(imhtml
->text_buffer
, TRUE
, TRUE
);
4904 mark
= gtk_text_buffer_get_insert(imhtml
->text_buffer
);
4906 gtk_text_buffer_get_iter_at_mark(imhtml
->text_buffer
, &iter
, mark
);
4907 gtk_text_buffer_begin_user_action(imhtml
->text_buffer
);
4908 gtk_imhtml_insert_smiley_at_iter(imhtml
, sml
, smiley
, &iter
);
4909 gtk_text_buffer_end_user_action(imhtml
->text_buffer
);
4913 image_expose(GtkWidget
*widget
, GdkEventExpose
*event
, gpointer user_data
)
4915 GTK_WIDGET_CLASS(GTK_WIDGET_GET_CLASS(widget
))->expose_event(widget
, event
);
4920 /* In case the smiley gets removed from the imhtml before it gets removed from the queue */
4921 static void animated_smiley_destroy_cb(GtkObject
*widget
, GtkIMHtml
*imhtml
)
4923 GList
*l
= imhtml
->animations
->head
;
4925 GList
*next
= l
->next
;
4926 if (l
->data
== widget
) {
4927 if (l
== imhtml
->animations
->tail
)
4928 imhtml
->animations
->tail
= imhtml
->animations
->tail
->prev
;
4929 imhtml
->animations
->head
= g_list_delete_link(imhtml
->animations
->head
, l
);
4930 imhtml
->num_animations
--;
4936 void gtk_imhtml_insert_smiley_at_iter(GtkIMHtml
*imhtml
, const char *sml
, char *smiley
, GtkTextIter
*iter
)
4938 GdkPixbuf
*pixbuf
= NULL
;
4939 GdkPixbufAnimation
*annipixbuf
= NULL
;
4940 GtkWidget
*icon
= NULL
;
4941 GtkTextChildAnchor
*anchor
= NULL
;
4943 GtkIMHtmlSmiley
*imhtml_smiley
;
4944 GtkWidget
*ebox
= NULL
;
4945 int numsmileys_thismsg
, numsmileys_total
;
4948 * This GtkIMHtml has the maximum number of smileys allowed, so don't
4949 * add any more. We do this for performance reasons, because smileys
4950 * are apparently pretty inefficient. Hopefully we can remove this
4951 * restriction when we're using a better HTML widget.
4953 unescaped
= purple_unescape_html(smiley
);
4954 numsmileys_thismsg
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg"));
4955 if (numsmileys_thismsg
>= 30) {
4956 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
4960 numsmileys_total
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total"));
4961 if (numsmileys_total
>= 300) {
4962 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
4967 imhtml_smiley
= gtk_imhtml_smiley_get(imhtml
, sml
, unescaped
);
4969 if (imhtml
->format_functions
& GTK_IMHTML_SMILEY
) {
4970 annipixbuf
= imhtml_smiley
? gtk_smiley_get_image(imhtml_smiley
) : NULL
;
4972 if (gdk_pixbuf_animation_is_static_image(annipixbuf
)) {
4973 pixbuf
= gdk_pixbuf_animation_get_static_image(annipixbuf
);
4975 icon
= gtk_image_new_from_pixbuf(pixbuf
);
4977 icon
= gtk_image_new_from_animation(annipixbuf
);
4978 if (imhtml
->num_animations
== 20) {
4979 GtkImage
*image
= GTK_IMAGE(g_queue_pop_head(imhtml
->animations
));
4980 GdkPixbufAnimation
*anim
= gtk_image_get_animation(image
);
4981 g_signal_handlers_disconnect_matched(G_OBJECT(image
), G_SIGNAL_MATCH_FUNC
,
4982 0, 0, NULL
, G_CALLBACK(animated_smiley_destroy_cb
), NULL
);
4984 GdkPixbuf
*pb
= gdk_pixbuf_animation_get_static_image(anim
);
4986 GdkPixbuf
*copy
= gdk_pixbuf_copy(pb
);
4987 gtk_image_set_from_pixbuf(image
, copy
);
4988 g_object_unref(G_OBJECT(copy
));
4992 imhtml
->num_animations
++;
4994 g_signal_connect(G_OBJECT(icon
), "destroy", G_CALLBACK(animated_smiley_destroy_cb
), imhtml
);
4995 g_queue_push_tail(imhtml
->animations
, icon
);
5000 if (imhtml_smiley
&& imhtml_smiley
->flags
& GTK_IMHTML_SMILEY_CUSTOM
) {
5001 ebox
= gtk_event_box_new();
5002 gtk_event_box_set_visible_window(GTK_EVENT_BOX(ebox
), FALSE
);
5003 gtk_widget_show(ebox
);
5007 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
5008 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
5009 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
5010 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
5012 /* This catches the expose events generated by animated
5013 * images, and ensures that they are handled by the image
5014 * itself, without propagating to the textview and causing
5015 * a complete refresh */
5016 g_signal_connect(G_OBJECT(icon
), "expose-event", G_CALLBACK(image_expose
), NULL
);
5018 gtk_widget_show(icon
);
5020 gtk_container_add(GTK_CONTAINER(ebox
), icon
);
5021 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
? ebox
: icon
, 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));
5025 } else if (imhtml_smiley
!= NULL
&& (imhtml
->format_functions
& GTK_IMHTML_SMILEY
)) {
5026 anchor
= gtk_text_buffer_create_child_anchor(imhtml
->text_buffer
, iter
);
5027 imhtml_smiley
->anchors
= g_slist_append(imhtml_smiley
->anchors
, g_object_ref(anchor
));
5029 GtkWidget
*img
= gtk_image_new_from_stock(GTK_STOCK_MISSING_IMAGE
, GTK_ICON_SIZE_MENU
);
5030 gtk_container_add(GTK_CONTAINER(ebox
), img
);
5031 gtk_widget_show(img
);
5032 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", g_strdup(unescaped
), g_free
);
5033 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_tiptext", g_strdup(unescaped
), g_free
);
5034 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
), g_free
);
5035 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(imhtml
), ebox
, anchor
);
5038 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_thismsg", GINT_TO_POINTER(numsmileys_thismsg
+ 1));
5039 g_object_set_data(G_OBJECT(imhtml
), "gtkimhtml_numsmileys_total", GINT_TO_POINTER(numsmileys_total
+ 1));
5041 gtk_text_buffer_insert(imhtml
->text_buffer
, iter
, unescaped
, -1);
5045 g_signal_connect(G_OBJECT(ebox
), "event", G_CALLBACK(gtk_imhtml_smiley_clicked
), imhtml_smiley
);
5051 void gtk_imhtml_insert_image_at_iter(GtkIMHtml
*imhtml
, int id
, GtkTextIter
*iter
)
5053 GdkPixbufAnimation
*anim
= NULL
;
5054 const char *filename
= NULL
;
5057 GtkIMHtmlScalable
*scalable
= NULL
;
5058 struct scalable_data
*sd
;
5061 if (!imhtml
->funcs
|| !imhtml
->funcs
->image_get
||
5062 !imhtml
->funcs
->image_get_size
|| !imhtml
->funcs
->image_get_data
||
5063 !imhtml
->funcs
->image_get_filename
|| !imhtml
->funcs
->image_ref
||
5064 !imhtml
->funcs
->image_unref
)
5067 image
= imhtml
->funcs
->image_get(id
);
5073 data
= imhtml
->funcs
->image_get_data(image
);
5074 len
= imhtml
->funcs
->image_get_size(image
);
5076 anim
= pidgin_pixbuf_anim_from_data(data
, len
);
5081 struct im_image_data
*t
= g_new(struct im_image_data
, 1);
5082 filename
= imhtml
->funcs
->image_get_filename(image
);
5083 imhtml
->funcs
->image_ref(id
);
5085 t
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5086 imhtml
->im_images
= g_slist_prepend(imhtml
->im_images
, t
);
5087 scalable
= gtk_imhtml_animation_new(anim
, filename
, id
);
5088 g_object_unref(G_OBJECT(anim
));
5091 pixbuf
= gtk_widget_render_icon(GTK_WIDGET(imhtml
), GTK_STOCK_MISSING_IMAGE
,
5092 GTK_ICON_SIZE_BUTTON
, "gtkimhtml-missing-image");
5093 scalable
= gtk_imhtml_image_new(pixbuf
, filename
, id
);
5094 g_object_unref(G_OBJECT(pixbuf
));
5097 sd
= g_new(struct scalable_data
, 1);
5098 sd
->scalable
= scalable
;
5099 sd
->mark
= gtk_text_buffer_create_mark(imhtml
->text_buffer
, NULL
, iter
, TRUE
);
5100 gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml
), &rect
);
5101 scalable
->add_to(scalable
, imhtml
, iter
);
5102 minus
= gtk_text_view_get_left_margin(GTK_TEXT_VIEW(imhtml
)) +
5103 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml
));
5104 scalable
->scale(scalable
, rect
.width
- minus
, rect
.height
);
5105 imhtml
->scalables
= g_list_append(imhtml
->scalables
, sd
);
5108 static const gchar
*tag_to_html_start(GtkTextTag
*tag
)
5111 static gchar buf
[16384];
5114 g_return_val_if_fail(name
!= NULL
, "");
5116 if (purple_strequal(name
, "BOLD")) {
5118 } else if (purple_strequal(name
, "ITALICS")) {
5120 } else if (purple_strequal(name
, "UNDERLINE")) {
5122 } else if (purple_strequal(name
, "STRIKE")) {
5124 } else if (strncmp(name
, "LINK ", 5) == 0) {
5125 char *tmp
= g_object_get_data(G_OBJECT(tag
), "link_url");
5127 gchar
*escaped
= purple_markup_escape_text(tmp
, -1);
5128 g_snprintf(buf
, sizeof(buf
), "<a href=\"%s\">", escaped
);
5129 buf
[sizeof(buf
)-1] = '\0';
5135 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5136 g_snprintf(buf
, sizeof(buf
), "<font color=\"%s\">", &name
[10]);
5138 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5139 g_snprintf(buf
, sizeof(buf
), "<font back=\"%s\">", &name
[10]);
5141 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5142 g_snprintf(buf
, sizeof(buf
), "<body bgcolor=\"%s\">", &name
[11]);
5144 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5145 g_snprintf(buf
, sizeof(buf
), "<font face=\"%s\">", &name
[10]);
5147 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5148 g_snprintf(buf
, sizeof(buf
), "<font size=\"%s\">", &name
[10]);
5154 GdkColor
*color
= NULL
;
5155 GObject
*obj
= G_OBJECT(tag
);
5156 gboolean empty
= TRUE
;
5158 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "<span style='");
5161 g_object_get(obj
, "weight-set", &isset
, "weight", &ivalue
, NULL
);
5163 const char *weight
= "";
5164 if (ivalue
>= PANGO_WEIGHT_ULTRABOLD
)
5166 else if (ivalue
>= PANGO_WEIGHT_BOLD
)
5168 else if (ivalue
>= PANGO_WEIGHT_NORMAL
)
5173 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "font-weight: %s;", weight
);
5177 /* Foreground color */
5178 g_object_get(obj
, "foreground-set", &isset
, "foreground-gdk", &color
, NULL
);
5179 if (isset
&& color
) {
5180 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5181 "color: #%02x%02x%02x;",
5182 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5185 gdk_color_free(color
);
5187 /* Background color */
5188 g_object_get(obj
, "background-set", &isset
, "background-gdk", &color
, NULL
);
5189 if (isset
&& color
) {
5190 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
),
5191 "background: #%02x%02x%02x;",
5192 color
->red
>> 8, color
->green
>> 8, color
->blue
>> 8);
5195 gdk_color_free(color
);
5198 g_object_get(obj
, "underline-set", &isset
, "underline", &ivalue
, NULL
);
5201 case PANGO_UNDERLINE_NONE
:
5202 case PANGO_UNDERLINE_ERROR
:
5205 str
+= g_snprintf(str
, sizeof(buf
) - (str
- buf
), "text-decoration: underline;");
5210 g_snprintf(str
, sizeof(buf
) - (str
- buf
), "'>");
5212 return (empty
? "" : buf
);
5216 static const gchar
*tag_to_html_end(GtkTextTag
*tag
)
5221 g_return_val_if_fail(name
!= NULL
, "");
5223 if (purple_strequal(name
, "BOLD")) {
5225 } else if (purple_strequal(name
, "ITALICS")) {
5227 } else if (purple_strequal(name
, "UNDERLINE")) {
5229 } else if (purple_strequal(name
, "STRIKE")) {
5231 } else if (strncmp(name
, "LINK ", 5) == 0) {
5233 } else if (strncmp(name
, "FORECOLOR ", 10) == 0) {
5235 } else if (strncmp(name
, "BACKCOLOR ", 10) == 0) {
5237 } else if (strncmp(name
, "BACKGROUND ", 10) == 0) {
5239 } else if (strncmp(name
, "FONT FACE ", 10) == 0) {
5241 } else if (strncmp(name
, "FONT SIZE ", 10) == 0) {
5244 const char *props
[] = {"weight-set", "foreground-set", "background-set",
5245 "size-set", "underline-set", NULL
};
5247 for (i
= 0; props
[i
]; i
++) {
5248 gboolean set
= FALSE
;
5249 g_object_get(G_OBJECT(tag
), props
[i
], &set
, NULL
);
5262 } PidginTextTagData
;
5264 static PidginTextTagData
*text_tag_data_new(GtkTextTag
*tag
)
5266 const char *start
, *end
;
5267 PidginTextTagData
*ret
= NULL
;
5269 start
= tag_to_html_start(tag
);
5270 if (!start
|| !*start
)
5272 end
= tag_to_html_end(tag
);
5276 ret
= g_new0(PidginTextTagData
, 1);
5277 ret
->start
= g_strdup(start
);
5278 ret
->end
= g_strdup(end
);
5283 static void text_tag_data_destroy(PidginTextTagData
*data
)
5285 g_free(data
->start
);
5290 static gboolean
tag_ends_here(GtkTextTag
*tag
, GtkTextIter
*iter
, GtkTextIter
*niter
)
5292 return ((gtk_text_iter_has_tag(iter
, GTK_TEXT_TAG(tag
)) &&
5293 !gtk_text_iter_has_tag(niter
, GTK_TEXT_TAG(tag
))) ||
5294 gtk_text_iter_is_end(niter
));
5297 /* Basic notion here: traverse through the text buffer one-by-one, non-character elements, such
5298 * as smileys and IM images are represented by the Unicode "unknown" character. Handle them. Else
5299 * check for tags that are toggled on, insert their html form, and push them on the queue. Then insert
5300 * the actual text. Then check for tags that are toggled off and insert them, after checking the queue.
5301 * Finally, replace <, >, &, and " with their HTML equivalent.
5303 char *gtk_imhtml_get_markup_range(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*end
)
5306 GtkTextIter iter
, next_iter
, non_neutral_iter
;
5307 gboolean is_rtl_message
= FALSE
;
5308 GString
*str
= g_string_new("");
5312 PidginTextTagData
*tagdata
;
5316 gtk_text_iter_order(start
, end
);
5317 non_neutral_iter
= next_iter
= iter
= *start
;
5318 gtk_text_iter_forward_char(&next_iter
);
5320 /* Bi-directional text support */
5321 /* Get to the first non-neutral character */
5323 while ((PANGO_DIRECTION_NEUTRAL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
)))
5324 && gtk_text_iter_forward_char(&non_neutral_iter
));
5325 if (PANGO_DIRECTION_RTL
== pango_unichar_direction(gtk_text_iter_get_char(&non_neutral_iter
))) {
5326 is_rtl_message
= TRUE
;
5327 g_string_append(str
, "<SPAN style=\"direction:rtl;text-align:right;\">");
5331 /* First add the tags that are already in progress (we don't care about non-printing tags)*/
5332 tags
= gtk_text_iter_get_tags(start
);
5334 for (sl
= tags
; sl
; sl
= sl
->next
) {
5336 if (!gtk_text_iter_toggles_tag(start
, GTK_TEXT_TAG(tag
))) {
5337 PidginTextTagData
*data
= text_tag_data_new(tag
);
5339 g_string_append(str
, data
->start
);
5340 g_queue_push_tail(q
, data
);
5346 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, end
)) {
5348 tags
= gtk_text_iter_get_tags(&iter
);
5350 for (sl
= tags
; sl
; sl
= sl
->next
) {
5352 if (gtk_text_iter_begins_tag(&iter
, GTK_TEXT_TAG(tag
))) {
5353 PidginTextTagData
*data
= text_tag_data_new(tag
);
5355 g_string_append(str
, data
->start
);
5356 g_queue_push_tail(q
, data
);
5362 GtkTextChildAnchor
* anchor
= gtk_text_iter_get_child_anchor(&iter
);
5364 char *text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_htmltext");
5366 str
= g_string_append(str
, text
);
5368 } else if (c
== '<') {
5369 str
= g_string_append(str
, "<");
5370 } else if (c
== '>') {
5371 str
= g_string_append(str
, ">");
5372 } else if (c
== '&') {
5373 str
= g_string_append(str
, "&");
5374 } else if (c
== '"') {
5375 str
= g_string_append(str
, """);
5376 } else if (c
== '\n') {
5377 str
= g_string_append(str
, "<br>");
5379 str
= g_string_append_unichar(str
, c
);
5382 tags
= g_slist_reverse(tags
);
5383 for (sl
= tags
; sl
; sl
= sl
->next
) {
5385 /** don't worry about non-printing tags ending */
5386 if (tag_ends_here(tag
, &iter
, &next_iter
) &&
5387 strlen(tag_to_html_end(tag
)) > 0 &&
5388 strlen(tag_to_html_start(tag
)) > 0) {
5390 PidginTextTagData
*tmp
;
5391 GQueue
*r
= g_queue_new();
5393 while ((tmp
= g_queue_pop_tail(q
)) && tmp
->tag
!= tag
) {
5394 g_string_append(str
, tmp
->end
);
5395 if (!tag_ends_here(tmp
->tag
, &iter
, &next_iter
))
5396 g_queue_push_tail(r
, tmp
);
5398 text_tag_data_destroy(tmp
);
5402 g_string_append(str
, tmp
->end
);
5403 text_tag_data_destroy(tmp
);
5405 #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 */
5407 purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
5410 while ((tmp
= g_queue_pop_head(r
))) {
5411 g_string_append(str
, tmp
->start
);
5412 g_queue_push_tail(q
, tmp
);
5419 gtk_text_iter_forward_char(&iter
);
5420 gtk_text_iter_forward_char(&next_iter
);
5423 while ((tagdata
= g_queue_pop_tail(q
))) {
5424 g_string_append(str
, tagdata
->end
);
5425 text_tag_data_destroy(tagdata
);
5428 /* Bi-directional text support - close tags */
5430 g_string_append(str
, "</SPAN>");
5433 return g_string_free(str
, FALSE
);
5436 void gtk_imhtml_close_tags(GtkIMHtml
*imhtml
, GtkTextIter
*iter
)
5438 if (imhtml
->edit
.bold
)
5439 gtk_imhtml_toggle_bold(imhtml
);
5441 if (imhtml
->edit
.italic
)
5442 gtk_imhtml_toggle_italic(imhtml
);
5444 if (imhtml
->edit
.underline
)
5445 gtk_imhtml_toggle_underline(imhtml
);
5447 if (imhtml
->edit
.strike
)
5448 gtk_imhtml_toggle_strike(imhtml
);
5450 if (imhtml
->edit
.forecolor
)
5451 gtk_imhtml_toggle_forecolor(imhtml
, NULL
);
5453 if (imhtml
->edit
.backcolor
)
5454 gtk_imhtml_toggle_backcolor(imhtml
, NULL
);
5456 if (imhtml
->edit
.fontface
)
5457 gtk_imhtml_toggle_fontface(imhtml
, NULL
);
5459 imhtml
->edit
.fontsize
= 0;
5461 if (imhtml
->edit
.link
)
5462 gtk_imhtml_toggle_link(imhtml
, NULL
);
5465 char *gtk_imhtml_get_markup(GtkIMHtml
*imhtml
)
5467 GtkTextIter start
, end
;
5469 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5470 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5471 return gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5474 char **gtk_imhtml_get_markup_lines(GtkIMHtml
*imhtml
)
5477 GtkTextIter start
, end
;
5480 lines
= gtk_text_buffer_get_line_count(imhtml
->text_buffer
);
5481 ret
= g_new0(char *, lines
+ 1);
5482 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &start
);
5484 gtk_text_iter_forward_to_line_end(&end
);
5486 for (i
= 0, j
= 0; i
< lines
; i
++) {
5487 if (gtk_text_iter_get_char(&start
) != '\n') {
5488 ret
[j
] = gtk_imhtml_get_markup_range(imhtml
, &start
, &end
);
5493 gtk_text_iter_forward_line(&start
);
5495 gtk_text_iter_forward_to_line_end(&end
);
5501 char *gtk_imhtml_get_text(GtkIMHtml
*imhtml
, GtkTextIter
*start
, GtkTextIter
*stop
)
5503 GString
*str
= g_string_new("");
5504 GtkTextIter iter
, end
;
5508 gtk_text_buffer_get_start_iter(imhtml
->text_buffer
, &iter
);
5513 gtk_text_buffer_get_end_iter(imhtml
->text_buffer
, &end
);
5517 gtk_text_iter_order(&iter
, &end
);
5519 while ((c
= gtk_text_iter_get_char(&iter
)) != 0 && !gtk_text_iter_equal(&iter
, &end
)) {
5521 GtkTextChildAnchor
* anchor
;
5524 anchor
= gtk_text_iter_get_child_anchor(&iter
);
5526 text
= g_object_get_data(G_OBJECT(anchor
), "gtkimhtml_plaintext");
5528 str
= g_string_append(str
, text
);
5530 g_string_append_unichar(str
, c
);
5532 gtk_text_iter_forward_char(&iter
);
5535 return g_string_free(str
, FALSE
);
5538 void gtk_imhtml_set_funcs(GtkIMHtml
*imhtml
, GtkIMHtmlFuncs
*f
)
5540 g_return_if_fail(imhtml
!= NULL
);
5544 void gtk_imhtml_setup_entry(GtkIMHtml
*imhtml
, PurpleConnectionFlags flags
)
5546 GtkIMHtmlButtons buttons
;
5548 if (flags
& PURPLE_CONNECTION_HTML
) {
5550 GdkColor fg_color
, bg_color
;
5552 buttons
= GTK_IMHTML_ALL
;
5554 if (flags
& PURPLE_CONNECTION_NO_BGCOLOR
)
5555 buttons
&= ~GTK_IMHTML_BACKCOLOR
;
5556 if (flags
& PURPLE_CONNECTION_NO_FONTSIZE
)
5558 buttons
&= ~GTK_IMHTML_GROW
;
5559 buttons
&= ~GTK_IMHTML_SHRINK
;
5561 if (flags
& PURPLE_CONNECTION_NO_URLDESC
)
5562 buttons
&= ~GTK_IMHTML_LINKDESC
;
5564 gtk_imhtml_set_format_functions(imhtml
, GTK_IMHTML_ALL
);
5565 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold") != imhtml
->edit
.bold
)
5566 gtk_imhtml_toggle_bold(imhtml
);
5568 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic") != imhtml
->edit
.italic
)
5569 gtk_imhtml_toggle_italic(imhtml
);
5571 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline") != imhtml
->edit
.underline
)
5572 gtk_imhtml_toggle_underline(imhtml
);
5574 gtk_imhtml_toggle_fontface(imhtml
,
5575 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/font_face"));
5577 if (!(flags
& PURPLE_CONNECTION_NO_FONTSIZE
))
5579 int size
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/font_size");
5581 /* 3 is the default. */
5583 gtk_imhtml_font_set_size(imhtml
, size
);
5586 if(!purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"), ""))
5588 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"),
5590 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5592 fg_color
.green
/ 256,
5593 fg_color
.blue
/ 256);
5597 gtk_imhtml_toggle_forecolor(imhtml
, color
);
5599 if(!(flags
& PURPLE_CONNECTION_NO_BGCOLOR
) &&
5600 !purple_strequal(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"), ""))
5602 gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"),
5604 g_snprintf(color
, sizeof(color
), "#%02x%02x%02x",
5606 bg_color
.green
/ 256,
5607 bg_color
.blue
/ 256);
5611 gtk_imhtml_toggle_background(imhtml
, color
);
5613 if (flags
& PURPLE_CONNECTION_FORMATTING_WBFO
)
5614 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, TRUE
);
5616 gtk_imhtml_set_whole_buffer_formatting_only(imhtml
, FALSE
);
5618 buttons
= GTK_IMHTML_SMILEY
| GTK_IMHTML_IMAGE
;
5619 imhtml_clear_formatting(imhtml
);
5622 if (flags
& PURPLE_CONNECTION_NO_IMAGES
)
5623 buttons
&= ~GTK_IMHTML_IMAGE
;
5625 if (flags
& PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
)
5626 buttons
|= GTK_IMHTML_CUSTOM_SMILEY
;
5628 buttons
&= ~GTK_IMHTML_CUSTOM_SMILEY
;
5630 gtk_imhtml_set_format_functions(imhtml
, buttons
);
5634 * GtkIMHtmlSmiley functions
5636 static void gtk_custom_smiley_allocated(GdkPixbufLoader
*loader
, gpointer user_data
)
5638 GtkIMHtmlSmiley
*smiley
;
5640 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5641 smiley
->icon
= gdk_pixbuf_loader_get_animation(loader
);
5644 g_object_ref(G_OBJECT(smiley
->icon
));
5645 #ifdef DEBUG_CUSTOM_SMILEY
5646 purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley
->icon
, smiley
->smile
);
5650 static void gtk_custom_smiley_closed(GdkPixbufLoader
*loader
, gpointer user_data
)
5652 GtkIMHtmlSmiley
*smiley
;
5653 GtkWidget
*icon
= NULL
;
5654 GtkTextChildAnchor
*anchor
= NULL
;
5655 GSList
*current
= NULL
;
5657 smiley
= (GtkIMHtmlSmiley
*)user_data
;
5658 if (!smiley
->imhtml
) {
5659 #ifdef DEBUG_CUSTOM_SMILEY
5660 purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley
);
5662 g_object_unref(G_OBJECT(loader
));
5663 smiley
->loader
= NULL
;
5667 for (current
= smiley
->anchors
; current
; current
= g_slist_next(current
)) {
5668 anchor
= GTK_TEXT_CHILD_ANCHOR(current
->data
);
5669 if (gtk_text_child_anchor_get_deleted(anchor
))
5672 icon
= gtk_image_new_from_animation(smiley
->icon
);
5674 #ifdef DEBUG_CUSTOM_SMILEY
5675 purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
5676 icon
, smiley
->icon
, smiley
->smile
);
5680 gtk_widget_show(icon
);
5682 wids
= gtk_text_child_anchor_get_widgets(anchor
);
5684 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_plaintext", purple_unescape_html(smiley
->smile
), g_free
);
5685 g_object_set_data_full(G_OBJECT(anchor
), "gtkimhtml_htmltext", g_strdup(smiley
->smile
), g_free
);
5687 if (smiley
->imhtml
) {
5689 GList
*children
= gtk_container_get_children(GTK_CONTAINER(wids
->data
));
5690 g_list_foreach(children
, (GFunc
)gtk_widget_destroy
, NULL
);
5691 g_list_free(children
);
5692 gtk_container_add(GTK_CONTAINER(wids
->data
), icon
);
5694 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley
->imhtml
), icon
, anchor
);
5698 g_object_unref(anchor
);
5701 g_slist_free(smiley
->anchors
);
5702 smiley
->anchors
= NULL
;
5704 g_object_unref(G_OBJECT(loader
));
5705 smiley
->loader
= NULL
;
5709 gtk_custom_smiley_size_prepared(GdkPixbufLoader
*loader
, gint width
, gint height
, gpointer data
)
5711 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/resize_custom_smileys")) {
5712 int custom_smileys_size
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/custom_smileys_size");
5713 if (width
<= custom_smileys_size
&& height
<= custom_smileys_size
)
5716 if (width
>= height
) {
5717 height
= height
* custom_smileys_size
/ width
;
5718 width
= custom_smileys_size
;
5720 width
= width
* custom_smileys_size
/ height
;
5721 height
= custom_smileys_size
;
5724 gdk_pixbuf_loader_set_size(loader
, width
, height
);
5728 gtk_imhtml_smiley_reload(GtkIMHtmlSmiley
*smiley
)
5731 g_object_unref(smiley
->icon
);
5733 g_object_unref(smiley
->loader
); /* XXX: does this crash? */
5735 smiley
->icon
= NULL
;
5736 smiley
->loader
= NULL
;
5739 /* We do not use the pixbuf loader for a smiley that can be loaded
5740 * from a file. (e.g., local custom smileys)
5745 smiley
->loader
= gdk_pixbuf_loader_new();
5747 g_signal_connect(smiley
->loader
, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated
), smiley
);
5748 g_signal_connect(smiley
->loader
, "closed", G_CALLBACK(gtk_custom_smiley_closed
), smiley
);
5749 g_signal_connect(smiley
->loader
, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared
), smiley
);
5752 GtkIMHtmlSmiley
*gtk_imhtml_smiley_create(const char *file
, const char *shortcut
, gboolean hide
,
5753 GtkIMHtmlSmileyFlags flags
)
5755 GtkIMHtmlSmiley
*smiley
= g_new0(GtkIMHtmlSmiley
, 1);
5756 smiley
->file
= g_strdup(file
);
5757 smiley
->smile
= g_strdup(shortcut
);
5758 smiley
->hidden
= hide
;
5759 smiley
->flags
= flags
;
5760 smiley
->imhtml
= NULL
;
5761 gtk_imhtml_smiley_reload(smiley
);
5765 void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley
*smiley
)
5767 gtk_imhtml_disassociate_smiley(smiley
);
5768 g_free(smiley
->smile
);
5769 g_free(smiley
->file
);
5771 g_object_unref(smiley
->icon
);
5773 g_object_unref(smiley
->loader
);
5774 g_free(smiley
->data
);
5778 gboolean
gtk_imhtml_class_register_protocol(const char *name
,
5779 gboolean (*activate
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
),
5780 gboolean (*context_menu
)(GtkIMHtml
*imhtml
, GtkIMHtmlLink
*link
, GtkWidget
*menu
))
5782 GtkIMHtmlClass
*klass
;
5783 GtkIMHtmlProtocol
*proto
;
5785 g_return_val_if_fail(name
, FALSE
);
5787 klass
= g_type_class_ref(GTK_TYPE_IMHTML
);
5788 g_return_val_if_fail(klass
, FALSE
);
5790 if ((proto
= imhtml_find_protocol(name
, TRUE
))) {
5794 klass
->protocols
= g_list_remove(klass
->protocols
, proto
);
5795 g_free(proto
->name
);
5798 } else if (!activate
) {
5802 proto
= g_new0(GtkIMHtmlProtocol
, 1);
5803 proto
->name
= g_strdup(name
);
5804 proto
->length
= strlen(name
);
5805 proto
->activate
= activate
;
5806 proto
->context_menu
= context_menu
;
5807 klass
->protocols
= g_list_prepend(klass
->protocols
, proto
);
5813 gtk_imhtml_activate_tag(GtkIMHtml
*imhtml
, GtkTextTag
*tag
)
5815 /* A link was clicked--we emit the "url_clicked" signal
5816 * with the URL as the argument */
5817 g_object_ref(G_OBJECT(tag
));
5818 g_signal_emit(imhtml
, signals
[URL_CLICKED
], 0, g_object_get_data(G_OBJECT(tag
), "link_url"));
5819 g_object_unref(G_OBJECT(tag
));
5820 g_object_set_data(G_OBJECT(tag
), "visited", GINT_TO_POINTER(TRUE
));
5821 gtk_imhtml_set_link_color(GTK_IMHTML(imhtml
), tag
);
5824 gboolean
gtk_imhtml_link_activate(GtkIMHtmlLink
*link
)
5826 g_return_val_if_fail(link
, FALSE
);
5829 gtk_imhtml_activate_tag(link
->imhtml
, link
->tag
);
5830 } else if (link
->url
) {
5831 g_signal_emit(link
->imhtml
, signals
[URL_CLICKED
], 0, link
->url
);
5837 const char *gtk_imhtml_link_get_url(GtkIMHtmlLink
*link
)
5842 const GtkTextTag
* gtk_imhtml_link_get_text_tag(GtkIMHtmlLink
*link
)
5847 static gboolean
return_add_newline_cb(GtkWidget
*widget
, gpointer data
)
5849 GtkTextBuffer
*buffer
;
5853 buffer
= gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget
));
5855 /* Delete any currently selected text */
5856 gtk_text_buffer_delete_selection(buffer
, TRUE
, TRUE
);
5858 /* Insert a newline at the current cursor position */
5859 mark
= gtk_text_buffer_get_insert(buffer
);
5860 gtk_text_buffer_get_iter_at_mark(buffer
, &iter
, mark
);
5861 gtk_imhtml_insert_html_at_iter(GTK_IMHTML(widget
), "\n", 0, &iter
);
5864 * If we just newlined ourselves past the end of the visible area
5865 * then scroll down so the cursor is in view.
5867 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(widget
),
5868 gtk_text_buffer_get_insert(buffer
),
5869 0, FALSE
, 0.0, 0.0);
5875 * It's kind of a pain that we need this function and the above just
5876 * to reinstate the default GtkTextView behavior. It might be better
5877 * if GtkIMHtml didn't intercept the enter key and just required the
5878 * application to deal with it--it's really not much more work than it
5879 * is to connect to the current "message_send" signal.
5881 void gtk_imhtml_set_return_inserts_newline(GtkIMHtml
*imhtml
)
5883 g_signal_connect(G_OBJECT(imhtml
), "message_send",
5884 G_CALLBACK(return_add_newline_cb
), NULL
);
5887 void gtk_imhtml_set_populate_primary_clipboard(GtkIMHtml
*imhtml
, gboolean populate
)
5890 signal_id
= g_signal_handler_find(imhtml
->text_buffer
,
5891 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_UNBLOCKED
, 0, 0, NULL
,
5892 mark_set_so_update_selection_cb
, NULL
);
5895 /* We didn't find an unblocked signal handler, which means there
5896 is a blocked handler. Now unblock it.
5897 This is necessary to avoid a mutex-lock when the debug message
5898 saying 'no handler is blocked' is printed in the debug window.
5901 g_signal_handlers_unblock_matched(imhtml
->text_buffer
,
5902 G_SIGNAL_MATCH_FUNC
, 0, 0, NULL
,
5903 mark_set_so_update_selection_cb
, NULL
);
5906 /* Block only if we found an unblocked handler */
5908 g_signal_handler_block(imhtml
->text_buffer
, signal_id
);