3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
25 #include "glibcompat.h"
26 #include "image-store.h"
28 #include "pidginstock.h"
30 #include <gdk/gdkkeysyms.h>
36 #include "gtksmiley-manager.h"
37 #include "gtkwebview.h"
38 #include "gtkwebviewtoolbar.h"
40 #include "gtkinternal.h"
41 #include "gtk3compat.h"
43 #define MAX_FONT_SIZE 7
44 #define MAX_SCROLL_TIME 0.4 /* seconds */
45 #define SCROLL_DELAY 33 /* milliseconds */
46 #define PIDGIN_WEBVIEW_MAX_PROCESS_TIME 100000 /* microseconds */
63 static guint signals
[LAST_SIGNAL
] = { 0 };
65 /******************************************************************************
67 *****************************************************************************/
70 WebKitWebInspector
*inspector
;
72 } PidginWebViewInspectData
;
75 WebKitWebView
*webview
;
77 } PidginWebViewInsertData
;
82 } GtkUnicodeMenuEntry
;
88 gboolean (*activate
)(PidginWebView
*webview
, const char *uri
);
89 gboolean (*context_menu
)(PidginWebView
*webview
, WebKitDOMHTMLAnchorElement
*link
, GtkWidget
*menu
);
90 } PidginWebViewProtocol
;
92 typedef struct _PidginWebViewPrivate
{
93 /* Processing queues */
98 /* Scroll adjustments */
105 PidginWebViewButtons format_functions
;
106 PidginWebViewToolbar
*toolbar
;
108 gboolean wbfo
:1; /* Whole buffer formatting only. */
109 gboolean block_changed
:1;
112 /* WebKit inspector */
113 WebKitWebView
*inspector_view
;
114 GtkWindow
*inspector_win
;
117 gboolean refresh_spell_installed
;
118 } PidginWebViewPrivate
;
120 /******************************************************************************
122 *****************************************************************************/
124 G_DEFINE_TYPE_WITH_PRIVATE(PidginWebView
, pidgin_webview
,
125 webkit_web_view_get_type());
127 static GRegex
*smileys_re
= NULL
;
128 static GRegex
*empty_html_re
= NULL
;
132 * It's global, because gtkwebkit calls "resource-load-finished" only once
133 * for each static resource.
135 static GHashTable
*globally_loaded_images
= NULL
;
136 guint globally_loaded_images_refcnt
= 0;
138 static GList
*spellcheck_languages
= NULL
;
140 /******************************************************************************
142 *****************************************************************************/
145 webview_resource_loading(WebKitWebView
*webview
,
146 WebKitWebFrame
*frame
,
147 WebKitWebResource
*resource
,
148 WebKitNetworkRequest
*request
,
149 WebKitNetworkResponse
*response
,
153 PurpleImage
*img
= NULL
;
156 uri
= webkit_network_request_get_uri(request
);
157 if ((img
= purple_image_store_get_from_uri(uri
)) != NULL
) {
159 } else if (purple_str_has_prefix(uri
, PURPLE_IMAGE_STORE_STOCK_PROTOCOL
)) {
160 gchar
*p_uri
, *found
;
161 const gchar
*domain
, *stock_name
;
163 uri
+= sizeof(PURPLE_IMAGE_STORE_STOCK_PROTOCOL
) - 1;
165 p_uri
= g_strdup(uri
);
166 found
= strchr(p_uri
, '/');
168 purple_debug_warning("webview", "Invalid purple stock "
169 "image uri: %s", uri
);
176 stock_name
= found
+ 1;
178 if (g_strcmp0(domain
, "e2ee") == 0) {
179 img
= _pidgin_e2ee_stock_icon_get(stock_name
);
185 purple_debug_warning("webview", "Invalid purple stock "
186 "image domain: %s", domain
);
195 /* At the time of this comment, purple_image_get_path()
196 * always returns something, whether it's the actual
197 * path or a unique identifier, such as derived from a
198 * hash. That API will probably be reviewed after which
199 * this code can probably be simplified.
203 path
= purple_image_get_path(img
);
206 uri
= g_filename_to_uri(path
, NULL
, NULL
);
210 webkit_network_request_set_uri(request
, uri
);
216 b64
= g_base64_encode(purple_image_get_data(img
),
217 purple_image_get_data_size(img
));
218 type
= purple_image_get_mimetype(img
);
219 src
= g_strdup_printf("data:%s;base64,%s", type
, b64
);
221 webkit_network_request_set_uri(request
, src
);
228 webview_resource_loaded(WebKitWebView
*web_view
, WebKitWebFrame
*web_frame
,
229 WebKitWebResource
*web_resource
, gpointer user_data
)
233 PurpleImage
*image
= NULL
;
235 if (!purple_str_has_caseprefix(
236 webkit_web_resource_get_mime_type(web_resource
), "image/"))
241 uri
= webkit_web_resource_get_uri(web_resource
);
242 if (g_hash_table_lookup(globally_loaded_images
, uri
))
245 data
= webkit_web_resource_get_data(web_resource
);
249 image
= purple_image_store_get_from_uri(uri
);
253 image
= purple_image_new_from_data(
254 g_memdup(data
->str
, data
->len
), data
->len
);
255 if (purple_str_has_prefix(uri
, "file:"))
256 purple_image_set_friendly_filename(image
, uri
);
257 g_return_if_fail(image
!= NULL
);
260 g_hash_table_insert(globally_loaded_images
, g_strdup(uri
), image
);
264 webview_resource_get_loaded(WebKitWebView
*web_view
, const gchar
*uri
)
266 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
267 PIDGIN_WEBVIEW(web_view
));
269 g_return_val_if_fail(priv
!= NULL
, NULL
);
271 return g_hash_table_lookup(globally_loaded_images
, uri
);
275 process_load_queue_element(PidginWebView
*webview
)
277 PidginWebViewPrivate
*priv
=
278 pidgin_webview_get_instance_private(webview
);
281 WebKitDOMDocument
*doc
;
282 WebKitDOMHTMLElement
*body
;
283 WebKitDOMNode
*start
, *end
;
284 WebKitDOMRange
*range
;
285 gboolean require_scroll
= FALSE
;
287 type
= GPOINTER_TO_INT(g_queue_pop_head(priv
->load_queue
));
288 str
= g_queue_pop_head(priv
->load_queue
);
292 doc
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
293 body
= webkit_dom_document_get_body(doc
);
294 start
= webkit_dom_node_get_last_child(WEBKIT_DOM_NODE(body
));
296 if (priv
->autoscroll
) {
297 require_scroll
= (gtk_adjustment_get_value(priv
->vadj
)
298 >= (gtk_adjustment_get_upper(priv
->vadj
) -
299 1.5*gtk_adjustment_get_page_size(priv
->vadj
)));
302 webkit_dom_html_element_insert_adjacent_html(body
, "beforeend",
305 range
= webkit_dom_document_create_range(doc
);
307 end
= webkit_dom_node_get_last_child(WEBKIT_DOM_NODE(body
));
308 webkit_dom_range_set_start_after(range
,
309 WEBKIT_DOM_NODE(start
),
311 webkit_dom_range_set_end_after(range
,
312 WEBKIT_DOM_NODE(end
),
315 webkit_dom_range_select_node_contents(range
,
316 WEBKIT_DOM_NODE(body
),
320 if (require_scroll
) {
322 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(start
),
325 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(body
),
329 g_signal_emit(webview
, signals
[HTML_APPENDED
], 0, range
);
334 webkit_web_view_execute_script(WEBKIT_WEB_VIEW(webview
), str
);
338 purple_debug_error("webview",
339 "Got unknown loading queue type: %d\n", type
);
347 process_load_queue(PidginWebView
*webview
)
349 PidginWebViewPrivate
*priv
=
350 pidgin_webview_get_instance_private(webview
);
353 if (priv
->is_loading
) {
357 if (!priv
->load_queue
|| g_queue_is_empty(priv
->load_queue
)) {
362 start_time
= g_get_monotonic_time();
363 while (!g_queue_is_empty(priv
->load_queue
)) {
364 process_load_queue_element(webview
);
365 if (g_get_monotonic_time() - start_time
>
366 PIDGIN_WEBVIEW_MAX_PROCESS_TIME
)
370 if (g_queue_is_empty(priv
->load_queue
)) {
378 webview_load_started(WebKitWebView
*webview
, WebKitWebFrame
*frame
,
381 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
382 PIDGIN_WEBVIEW(webview
));
384 /* is there a better way to test for is_loading? */
385 priv
->is_loading
= TRUE
;
389 webview_load_finished(WebKitWebView
*webview
, WebKitWebFrame
*frame
,
392 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
393 PIDGIN_WEBVIEW(webview
));
395 priv
->is_loading
= FALSE
;
396 if (priv
->loader
== 0)
397 priv
->loader
= g_idle_add((GSourceFunc
)process_load_queue
, webview
);
401 webview_inspector_inspect_element(GtkWidget
*item
, PidginWebViewInspectData
*data
)
403 webkit_web_inspector_inspect_node(data
->inspector
, data
->node
);
407 webview_inspector_destroy(GtkWindow
*window
, PidginWebViewPrivate
*priv
)
409 g_return_if_fail(priv
->inspector_win
== window
);
411 priv
->inspector_win
= NULL
;
412 priv
->inspector_view
= NULL
;
415 static WebKitWebView
*
416 webview_inspector_create(WebKitWebInspector
*inspector
,
417 WebKitWebView
*webview
, gpointer _unused
)
419 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
420 PIDGIN_WEBVIEW(webview
));
422 if (priv
->inspector_view
!= NULL
)
423 return priv
->inspector_view
;
425 priv
->inspector_win
= GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL
));
426 gtk_window_set_title(priv
->inspector_win
, _("WebKit inspector"));
427 gtk_window_set_default_size(priv
->inspector_win
, 600, 400);
429 priv
->inspector_view
= WEBKIT_WEB_VIEW(webkit_web_view_new());
430 gtk_container_add(GTK_CONTAINER(priv
->inspector_win
),
431 GTK_WIDGET(priv
->inspector_view
));
433 g_signal_connect(priv
->inspector_win
, "destroy",
434 G_CALLBACK(webview_inspector_destroy
), priv
);
436 return priv
->inspector_view
;
440 webview_inspector_show(WebKitWebInspector
*inspector
, GtkWidget
*webview
)
442 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
443 PIDGIN_WEBVIEW(webview
));
445 gtk_widget_show_all(GTK_WIDGET(priv
->inspector_win
));
450 static PidginWebViewProtocol
*
451 webview_find_protocol(const char *url
, gboolean reverse
)
453 PidginWebViewClass
*klass
;
455 PidginWebViewProtocol
*proto
= NULL
;
456 gssize length
= reverse
? (gssize
)strlen(url
) : -1;
458 klass
= g_type_class_ref(PIDGIN_TYPE_WEBVIEW
);
459 for (iter
= klass
->protocols
; iter
; iter
= iter
->next
) {
461 if (g_ascii_strncasecmp(url
, proto
->name
, reverse
? MIN(length
, proto
->length
) : proto
->length
) == 0) {
462 g_type_class_unref(klass
);
467 g_type_class_unref(klass
);
472 webview_navigation_decision(WebKitWebView
*webview
,
473 WebKitWebFrame
*frame
,
474 WebKitNetworkRequest
*request
,
475 WebKitWebNavigationAction
*navigation_action
,
476 WebKitWebPolicyDecision
*policy_decision
,
480 WebKitWebNavigationReason reason
;
482 uri
= webkit_network_request_get_uri(request
);
483 reason
= webkit_web_navigation_action_get_reason(navigation_action
);
485 if (reason
== WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
) {
486 PidginWebViewProtocol
*proto
= webview_find_protocol(uri
, FALSE
);
488 /* XXX: Do something with the return value? */
489 proto
->activate(PIDGIN_WEBVIEW(webview
), uri
);
491 webkit_web_policy_decision_ignore(policy_decision
);
492 } else if (reason
== WEBKIT_WEB_NAVIGATION_REASON_OTHER
)
493 webkit_web_policy_decision_use(policy_decision
);
495 webkit_web_policy_decision_ignore(policy_decision
);
501 get_input_methods_menu(WebKitWebView
*webview
)
503 GtkSettings
*settings
;
504 gboolean show
= TRUE
;
509 settings
= webview
? gtk_widget_get_settings(GTK_WIDGET(webview
)) : gtk_settings_get_default();
512 g_object_get(settings
, "gtk-show-input-method-menu", &show
, NULL
);
516 item
= gtk_image_menu_item_new_with_mnemonic(_("Input _Methods"));
518 g_object_get(webview
, "im-context", &im
, NULL
);
519 menu
= gtk_menu_new();
520 gtk_im_multicontext_append_menuitems(GTK_IM_MULTICONTEXT(im
),
521 GTK_MENU_SHELL(menu
));
522 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), menu
);
527 /* Values taken from gtktextutil.c */
528 static const GtkUnicodeMenuEntry bidi_menu_entries
[] = {
529 { N_("LRM _Left-to-right mark"), 0x200E },
530 { N_("RLM _Right-to-left mark"), 0x200F },
531 { N_("LRE Left-to-right _embedding"), 0x202A },
532 { N_("RLE Right-to-left e_mbedding"), 0x202B },
533 { N_("LRO Left-to-right _override"), 0x202D },
534 { N_("RLO Right-to-left o_verride"), 0x202E },
535 { N_("PDF _Pop directional formatting"), 0x202C },
536 { N_("ZWS _Zero width space"), 0x200B },
537 { N_("ZWJ Zero width _joiner"), 0x200D },
538 { N_("ZWNJ Zero width _non-joiner"), 0x200C }
542 insert_control_character_cb(GtkMenuItem
*item
, PidginWebViewInsertData
*data
)
544 WebKitWebView
*webview
= data
->webview
;
545 gunichar ch
= data
->ch
;
546 PidginWebViewPrivate
*priv
;
547 WebKitDOMDocument
*dom
;
550 priv
= pidgin_webview_get_instance_private(PIDGIN_WEBVIEW(webview
));
551 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
553 g_unichar_to_utf8(ch
, buf
);
554 priv
->edit
.block_changed
= TRUE
;
555 webkit_dom_document_exec_command(dom
, "insertHTML", FALSE
, buf
);
556 priv
->edit
.block_changed
= FALSE
;
560 get_unicode_menu(WebKitWebView
*webview
)
562 GtkSettings
*settings
;
563 gboolean show
= TRUE
;
568 settings
= webview
? gtk_widget_get_settings(GTK_WIDGET(webview
)) : gtk_settings_get_default();
571 g_object_get(settings
, "gtk-show-unicode-menu", &show
, NULL
);
575 menuitem
= gtk_image_menu_item_new_with_mnemonic(_("_Insert Unicode Control Character"));
577 menu
= gtk_menu_new();
578 for (i
= 0; i
< G_N_ELEMENTS(bidi_menu_entries
); i
++) {
579 PidginWebViewInsertData
*data
;
582 data
= g_new0(PidginWebViewInsertData
, 1);
583 data
->webview
= webview
;
584 data
->ch
= bidi_menu_entries
[i
].ch
;
586 item
= gtk_menu_item_new_with_mnemonic(_(bidi_menu_entries
[i
].label
));
587 g_signal_connect_data(item
, "activate",
588 G_CALLBACK(insert_control_character_cb
), data
,
589 (GClosureNotify
)g_free
, 0);
590 gtk_widget_show(item
);
591 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
594 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), menu
);
602 webview_refresh_spellcheck(WebKitWebView
*webview
)
604 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
605 PIDGIN_WEBVIEW(webview
));
606 static const gchar jsfunc
[] =
607 "var pidgin_refresh_spellcheck = function() {"
608 "var selection = window.getSelection();"
609 "var originalSelection = selection.getRangeAt(0);"
610 "for (var i = 0; i < 5; i++)"
611 "selection.modify('move', 'backward', 'line');"
612 "for (i = 0; i < 100; i++)"
613 "selection.modify('move', 'forward', 'word');"
614 "selection.removeAllRanges();"
615 "selection.addRange(originalSelection);"
618 if (!priv
->refresh_spell_installed
) {
619 priv
->refresh_spell_installed
= TRUE
;
620 webkit_web_view_execute_script(webview
, jsfunc
);
623 webkit_web_view_execute_script(webview
, "pidgin_refresh_spellcheck()");
627 webview_lang_select(GtkMenuItem
*item
, const gchar
*lang
)
629 WebKitWebView
*webview
= g_object_get_data(G_OBJECT(item
), "gtkwebview");
630 WebKitWebSettings
*settings
;
632 g_return_if_fail(lang
!= NULL
);
633 g_return_if_fail(webview
!= NULL
);
635 settings
= webkit_web_view_get_settings(webview
);
636 g_object_set(G_OBJECT(settings
),
637 "spell-checking-languages", lang
, NULL
);
638 webview_refresh_spellcheck(webview
);
642 get_spelldict_menu(WebKitWebView
*webview
)
648 if (spellcheck_languages
== NULL
)
651 menuitem
= gtk_image_menu_item_new_with_mnemonic(_("_Language"));
652 menu
= gtk_menu_new();
653 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem
), menu
);
654 for (it
= spellcheck_languages
; it
; it
= g_list_next(it
)) {
656 const gchar
*lang
= it
->data
;
658 /* we could convert lang id to name here */
659 item
= gtk_menu_item_new_with_label(lang
);
660 g_object_set_data(G_OBJECT(item
), "gtkwebview", webview
);
661 g_signal_connect(item
, "activate",
662 G_CALLBACK(webview_lang_select
), (gpointer
)lang
);
663 gtk_widget_show(item
);
664 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
672 get_spelldict_menu(WebKitWebView
*webview
)
679 webview_image_saved(GtkWidget
*dialog
, gint response
, gpointer _unused
)
684 if (response
!= GTK_RESPONSE_ACCEPT
) {
685 gtk_widget_destroy(dialog
);
689 image
= g_object_get_data(G_OBJECT(dialog
), "pidgin-gtkwebview-image");
690 g_return_if_fail(image
!= NULL
);
692 filename
= gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog
));
693 g_return_if_fail(filename
!= NULL
);
694 g_return_if_fail(filename
[0] != '\0');
696 if (!purple_image_save(image
, filename
)) {
697 purple_debug_error("gtkwebview", "Failed saving image");
698 /* TODO: we should display a notification here */
702 gtk_widget_destroy(dialog
);
706 webview_image_save(GtkWidget
*item
, WebKitDOMHTMLImageElement
*image_node
)
709 WebKitWebView
*webview
;
711 GtkFileChooserDialog
*dialog
;
712 const gchar
*filename
;
715 webview
= g_object_get_data(G_OBJECT(image_node
), "pidgin-gtkwebview");
716 g_return_if_fail(webview
!= NULL
);
718 src
= webkit_dom_html_image_element_get_src(image_node
); /* XXX: a leak or not? */
719 image
= webview_resource_get_loaded(webview
, src
);
720 g_return_if_fail(image
!= NULL
);
722 parent
= gtk_widget_get_ancestor(item
, GTK_TYPE_WINDOW
);
723 dialog
= GTK_FILE_CHOOSER_DIALOG(gtk_file_chooser_dialog_new(
725 parent
? GTK_WINDOW(parent
) : NULL
,
726 GTK_FILE_CHOOSER_ACTION_SAVE
,
727 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
728 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
730 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog
), TRUE
);
731 gtk_dialog_set_default_response(GTK_DIALOG(dialog
), GTK_RESPONSE_ACCEPT
);
733 filename
= purple_image_get_friendly_filename(image
);
734 g_warn_if_fail(filename
!= NULL
);
735 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog
), filename
);
737 g_signal_connect(G_OBJECT(dialog
), "response",
738 G_CALLBACK(webview_image_saved
), NULL
);
741 g_object_set_data_full(G_OBJECT(dialog
), "pidgin-gtkwebview-image",
742 image
, g_object_unref
);
744 gtk_widget_show(GTK_WIDGET(dialog
));
748 webview_image_add_smiley(GtkWidget
*item
, WebKitDOMHTMLImageElement
*image_node
)
751 WebKitWebView
*webview
;
754 src
= webkit_dom_html_image_element_get_src(image_node
);
755 webview
= g_object_get_data(G_OBJECT(image_node
), "pidgin-gtkwebview");
756 g_return_if_fail(webview
!= NULL
);
758 image
= webview_resource_get_loaded(webview
, src
);
759 g_return_if_fail(image
!= NULL
);
761 pidgin_smiley_manager_add(image
,
762 webkit_dom_html_image_element_get_alt(image_node
));
766 do_popup_menu(WebKitWebView
*webview
, GdkEvent
*event
, int context
,
767 WebKitDOMNode
*node
, const char *uri
)
770 GtkWidget
*cut
, *copy
, *paste
, *delete, *select
;
771 gboolean show_clipboard
= TRUE
;
772 WebKitDOMHTMLImageElement
*image_node
= NULL
;
774 menu
= gtk_menu_new();
775 g_signal_connect(menu
, "selection-done",
776 G_CALLBACK(gtk_widget_destroy
), NULL
);
778 if (context
& WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
) {
779 PidginWebViewProtocol
*proto
= NULL
;
781 WebKitDOMNode
*link_node
= node
;
783 while (link_node
&& !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT(link_node
)) {
784 link_node
= webkit_dom_node_get_parent_node(link_node
);
787 if (uri
&& link_node
)
788 proto
= webview_find_protocol(uri
, FALSE
);
790 if (proto
&& proto
->context_menu
) {
791 proto
->context_menu(PIDGIN_WEBVIEW(webview
),
792 WEBKIT_DOM_HTML_ANCHOR_ELEMENT(link_node
), menu
);
795 children
= gtk_container_get_children(GTK_CONTAINER(menu
));
797 GtkWidget
*item
= gtk_menu_item_new_with_label(_("No actions available"));
798 gtk_widget_show(item
);
799 gtk_widget_set_sensitive(item
, FALSE
);
800 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
802 g_list_free(children
);
804 gtk_widget_show_all(menu
);
806 show_clipboard
= FALSE
;
809 if (context
& WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE
) {
810 WebKitDOMNode
*_image_node
= node
;
812 while (_image_node
&& !WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT(_image_node
)) {
813 _image_node
= webkit_dom_node_get_parent_node(_image_node
);
816 image_node
= WEBKIT_DOM_HTML_IMAGE_ELEMENT(_image_node
);
817 /* don't do it on our theme smileys */
819 if (image_node
&& webkit_dom_html_image_element_get_complete(image_node
)) {
820 GtkWidget
*menu_item
;
823 width
= webkit_dom_html_image_element_get_width(image_node
);
824 height
= webkit_dom_html_image_element_get_height(image_node
);
827 g_object_set_data(G_OBJECT(image_node
), "pidgin-gtkwebview", webview
);
829 menu_item
= gtk_image_menu_item_new_with_mnemonic(
830 _("_Save Image..."));
831 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item
),
832 gtk_image_new_from_stock(GTK_STOCK_SAVE
, GTK_ICON_SIZE_MENU
));
833 g_signal_connect_object(G_OBJECT(menu_item
), "activate",
834 G_CALLBACK(webview_image_save
), image_node
, 0);
835 gtk_widget_show(menu_item
);
836 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
838 /* TODO: check, if it's not *our* custom smiley (use css) */
839 if (width
<= 96 && height
<= 96) {
840 menu_item
= gtk_image_menu_item_new_with_mnemonic(
841 _("_Add Custom Smiley..."));
842 gtk_image_menu_item_set_image(
843 GTK_IMAGE_MENU_ITEM(menu_item
),
844 gtk_image_new_from_stock(GTK_STOCK_ADD
,
845 GTK_ICON_SIZE_MENU
));
846 g_signal_connect_object(G_OBJECT(menu_item
), "activate",
847 G_CALLBACK(webview_image_add_smiley
),
849 gtk_widget_show(menu_item
);
850 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), menu_item
);
853 show_clipboard
= FALSE
;
856 if (show_clipboard
) {
857 /* Using connect_swapped means we don't need any wrapper functions */
858 cut
= pidgin_new_menu_item(menu
, _("Cu_t"), GTK_STOCK_CUT
,
860 g_signal_connect_swapped(G_OBJECT(cut
), "activate",
861 G_CALLBACK(webkit_web_view_cut_clipboard
),
864 copy
= pidgin_new_menu_item(menu
, _("_Copy"), GTK_STOCK_COPY
,
866 g_signal_connect_swapped(G_OBJECT(copy
), "activate",
867 G_CALLBACK(webkit_web_view_copy_clipboard
),
870 paste
= pidgin_new_menu_item(menu
, _("_Paste"), GTK_STOCK_PASTE
,
872 g_signal_connect_swapped(G_OBJECT(paste
), "activate",
873 G_CALLBACK(webkit_web_view_paste_clipboard
),
876 delete = pidgin_new_menu_item(menu
, _("_Delete"), GTK_STOCK_DELETE
,
878 g_signal_connect_swapped(G_OBJECT(delete), "activate",
879 G_CALLBACK(webkit_web_view_delete_selection
),
882 pidgin_separator(menu
);
884 select
= pidgin_new_menu_item(menu
, _("Select _All"),
885 GTK_STOCK_SELECT_ALL
, NULL
, NULL
);
886 g_signal_connect_swapped(G_OBJECT(select
), "activate",
887 G_CALLBACK(webkit_web_view_select_all
),
890 gtk_widget_set_sensitive(cut
,
891 webkit_web_view_can_cut_clipboard(webview
));
892 gtk_widget_set_sensitive(copy
,
893 webkit_web_view_can_copy_clipboard(webview
));
894 gtk_widget_set_sensitive(paste
,
895 webkit_web_view_can_paste_clipboard(webview
));
896 gtk_widget_set_sensitive(delete,
897 webkit_web_view_can_cut_clipboard(webview
));
900 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
901 "/webview/inspector_enabled"))
903 WebKitWebSettings
*settings
;
905 PidginWebViewInspectData
*data
;
907 settings
= webkit_web_view_get_settings(webview
);
908 g_object_set(G_OBJECT(settings
), "enable-developer-extras", TRUE
, NULL
);
910 data
= g_new0(PidginWebViewInspectData
, 1);
911 data
->inspector
= webkit_web_view_get_inspector(webview
);
914 pidgin_separator(menu
);
916 inspect
= pidgin_new_menu_item(menu
, _("Inspect _Element"),
917 PIDGIN_STOCK_DEBUG
, NULL
, NULL
);
918 g_signal_connect_data(G_OBJECT(inspect
), "activate",
919 G_CALLBACK(webview_inspector_inspect_element
),
920 data
, (GClosureNotify
)g_free
, 0);
923 if (webkit_web_view_get_editable(webview
)) {
924 GtkWidget
*im
= get_input_methods_menu(webview
);
925 GtkWidget
*unicode
= get_unicode_menu(webview
);
926 GtkWidget
*spelldict
= get_spelldict_menu(webview
);
928 if (im
|| unicode
|| spelldict
)
929 pidgin_separator(menu
);
932 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), im
);
937 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), unicode
);
938 gtk_widget_show(unicode
);
942 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), spelldict
);
943 gtk_widget_show(spelldict
);
947 g_signal_emit_by_name(G_OBJECT(webview
), "populate-popup", menu
);
949 gtk_menu_attach_to_widget(GTK_MENU(menu
), GTK_WIDGET(webview
), NULL
);
950 gtk_menu_popup_at_pointer(GTK_MENU(menu
), event
);
954 webview_popup_menu(WebKitWebView
*webview
)
956 WebKitDOMDocument
*doc
;
957 WebKitDOMNode
*node
= NULL
;
958 int context
= WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT
;
961 doc
= webkit_web_view_get_dom_document(webview
);
963 /* it's unlikely, at least for webkit 1.x */
964 if (WEBKIT_DOM_IS_HTML_DOCUMENT(doc
)) {
965 WebKitDOMElement
*active
;
966 WebKitDOMElement
*link
;
967 active
= webkit_dom_html_document_get_active_element(
968 WEBKIT_DOM_HTML_DOCUMENT(doc
));
971 while (link
&& !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT(link
))
972 link
= webkit_dom_node_get_parent_element(WEBKIT_DOM_NODE(link
));
973 if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT(link
)) {
974 context
|= WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
;
975 uri
= webkit_dom_html_anchor_element_get_href(WEBKIT_DOM_HTML_ANCHOR_ELEMENT(link
));
979 do_popup_menu(webview
, NULL
, context
, node
, uri
);
987 webview_button_pressed(WebKitWebView
*webview
, GdkEventButton
*event
)
989 if (gdk_event_triggers_context_menu((GdkEvent
*)event
)) {
990 WebKitHitTestResult
*hit
;
995 hit
= webkit_web_view_get_hit_test_result(webview
, event
);
996 g_object_get(G_OBJECT(hit
),
1002 do_popup_menu(webview
, (GdkEvent
*)event
, context
,
1006 g_object_unref(hit
);
1015 * Smoothly scroll a WebView.
1017 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
1020 smooth_scroll_cb(gpointer data
)
1022 PidginWebViewPrivate
*priv
= data
;
1027 g_return_val_if_fail(priv
->scroll_time
!= NULL
, FALSE
);
1030 max_val
= gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
);
1031 scroll_val
= gtk_adjustment_get_value(adj
) +
1032 ((max_val
- gtk_adjustment_get_value(adj
)) / 3);
1034 if (g_timer_elapsed(priv
->scroll_time
, NULL
) > MAX_SCROLL_TIME
1035 || scroll_val
>= max_val
) {
1036 /* time's up. jump to the end and kill the timer */
1037 gtk_adjustment_set_value(adj
, max_val
);
1038 g_timer_destroy(priv
->scroll_time
);
1039 priv
->scroll_time
= NULL
;
1040 priv
->scroll_src
= 0;
1044 /* scroll by 1/3rd the remaining distance */
1045 gtk_adjustment_set_value(adj
, scroll_val
);
1050 scroll_idle_cb(gpointer data
)
1052 PidginWebViewPrivate
*priv
= data
;
1053 GtkAdjustment
*adj
= priv
->vadj
;
1057 max_val
= gtk_adjustment_get_upper(adj
) - gtk_adjustment_get_page_size(adj
);
1058 gtk_adjustment_set_value(adj
, max_val
);
1061 priv
->scroll_src
= 0;
1066 emit_format_signal(PidginWebView
*webview
, PidginWebViewButtons buttons
)
1068 g_object_ref(webview
);
1069 g_signal_emit(webview
, signals
[TOGGLE_FORMAT
], 0, buttons
);
1070 g_object_unref(webview
);
1074 do_formatting(PidginWebView
*webview
, const char *name
, const char *value
)
1076 PidginWebViewPrivate
*priv
=
1077 pidgin_webview_get_instance_private(webview
);
1078 WebKitDOMDocument
*dom
;
1079 WebKitDOMDOMWindow
*win
;
1080 WebKitDOMDOMSelection
*sel
= NULL
;
1081 WebKitDOMRange
*range
= NULL
;
1083 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1085 if (priv
->edit
.wbfo
) {
1086 win
= webkit_dom_document_get_default_view(dom
);
1087 sel
= webkit_dom_dom_window_get_selection(win
);
1088 if (webkit_dom_dom_selection_get_range_count(sel
) > 0)
1089 range
= webkit_dom_dom_selection_get_range_at(sel
, 0, NULL
);
1090 webkit_web_view_select_all(WEBKIT_WEB_VIEW(webview
));
1093 priv
->edit
.block_changed
= TRUE
;
1094 webkit_dom_document_exec_command(dom
, (gchar
*)name
, FALSE
, (gchar
*)value
);
1095 priv
->edit
.block_changed
= FALSE
;
1097 if (priv
->edit
.wbfo
) {
1099 webkit_dom_dom_selection_remove_all_ranges(sel
);
1100 webkit_dom_dom_selection_add_range(sel
, range
);
1102 webkit_dom_dom_selection_collapse_to_end(sel
, NULL
);
1108 webview_font_shrink(PidginWebView
*webview
)
1113 fontsize
= pidgin_webview_get_current_fontsize(webview
);
1114 fontsize
= MAX(fontsize
- 1, 1);
1116 tmp
= g_strdup_printf("%d", fontsize
);
1117 do_formatting(webview
, "fontSize", tmp
);
1122 webview_font_grow(PidginWebView
*webview
)
1127 fontsize
= pidgin_webview_get_current_fontsize(webview
);
1128 fontsize
= MIN(fontsize
+ 1, MAX_FONT_SIZE
);
1130 tmp
= g_strdup_printf("%d", fontsize
);
1131 do_formatting(webview
, "fontSize", tmp
);
1136 webview_clear_formatting(PidginWebView
*webview
)
1138 if (!webkit_web_view_get_editable(WEBKIT_WEB_VIEW(webview
)))
1141 do_formatting(webview
, "removeFormat", "");
1142 do_formatting(webview
, "unlink", "");
1143 do_formatting(webview
, "backColor", "inherit");
1147 webview_toggle_format(PidginWebView
*webview
, PidginWebViewButtons buttons
)
1149 /* since this function is the handler for the formatting keystrokes,
1150 we need to check here that the formatting attempted is permitted */
1151 buttons
&= pidgin_webview_get_format_functions(webview
);
1154 case PIDGIN_WEBVIEW_BOLD
:
1155 do_formatting(webview
, "bold", "");
1157 case PIDGIN_WEBVIEW_ITALIC
:
1158 do_formatting(webview
, "italic", "");
1160 case PIDGIN_WEBVIEW_UNDERLINE
:
1161 do_formatting(webview
, "underline", "");
1163 case PIDGIN_WEBVIEW_STRIKE
:
1164 do_formatting(webview
, "strikethrough", "");
1166 case PIDGIN_WEBVIEW_SHRINK
:
1167 webview_font_shrink(webview
);
1169 case PIDGIN_WEBVIEW_GROW
:
1170 webview_font_grow(webview
);
1178 editable_input_cb(PidginWebView
*webview
, gpointer data
)
1180 PidginWebViewPrivate
*priv
=
1181 pidgin_webview_get_instance_private(webview
);
1182 if (!priv
->edit
.block_changed
&& gtk_widget_is_sensitive(GTK_WIDGET(webview
)))
1183 g_signal_emit(webview
, signals
[CHANGED
], 0);
1186 /******************************************************************************
1188 *****************************************************************************/
1191 pidgin_webview_new(gboolean editable
)
1194 WebKitWebView
*webview
;
1195 WebKitWebSettings
*settings
;
1197 result
= g_object_new(pidgin_webview_get_type(), NULL
);
1198 webview
= WEBKIT_WEB_VIEW(result
);
1199 settings
= webkit_web_view_get_settings(webview
);
1201 g_object_set(G_OBJECT(settings
), "default-encoding", "utf-8", NULL
);
1203 /* XXX: win32 WebKitGTK replaces backslash with yen sign for
1204 * "sans-serif" font. We should figure out, how to disable this
1205 * behavior, but for now I will just apply this simple hack (using other
1208 g_object_set(G_OBJECT(settings
), "default-font-family", "Verdana", NULL
);
1210 webkit_web_view_set_settings(webview
, settings
);
1213 webkit_web_view_set_editable(WEBKIT_WEB_VIEW(webview
), editable
);
1215 g_signal_connect(G_OBJECT(webview
), "user-changed-contents",
1216 G_CALLBACK(editable_input_cb
), NULL
);
1223 pidgin_webview_finalize(GObject
*webview
)
1225 PidginWebViewPrivate
*priv
= pidgin_webview_get_instance_private(
1226 PIDGIN_WEBVIEW(webview
));
1228 if (priv
->inspector_win
!= NULL
)
1229 gtk_widget_destroy(GTK_WIDGET(priv
->inspector_win
));
1232 g_source_remove(priv
->loader
);
1234 while (!g_queue_is_empty(priv
->load_queue
)) {
1235 g_queue_pop_head(priv
->load_queue
);
1236 g_free(g_queue_pop_head(priv
->load_queue
));
1238 g_queue_free(priv
->load_queue
);
1240 if (--globally_loaded_images_refcnt
== 0) {
1241 g_assert(globally_loaded_images
!= NULL
);
1242 g_hash_table_destroy(globally_loaded_images
);
1243 globally_loaded_images
= NULL
;
1246 G_OBJECT_CLASS(pidgin_webview_parent_class
)->finalize(webview
);
1255 pidgin_webview_set_property(GObject
*object
, guint prop_id
, const GValue
*value
,
1258 g_return_if_fail(PIDGIN_IS_WEBVIEW(object
));
1262 purple_debug_misc("webview",
1263 "Ignored expand property (set to %d)",
1264 g_value_get_boolean(value
));
1267 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
,
1273 pidgin_webview_get_property(GObject
*object
, guint prop_id
, GValue
*value
, GParamSpec
*pspec
)
1275 g_return_if_fail(PIDGIN_IS_WEBVIEW(object
));
1277 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
1283 fill_spellcheck_dicts_cb(const gchar
*lang_tag
, const gchar
*provider_name
,
1284 const gchar
*provider_desc
, const gchar
*provider_file
,
1287 gboolean is_dialect
;
1290 /* It's not super efficient, but even with large number of installed
1291 * dictionaries (100?) it won't hurt us. */
1293 is_dialect
= (strchr(lang_tag
, '_') != NULL
);
1296 for (it
= spellcheck_languages
; it
; it
= g_list_next(it
)) {
1297 gchar
*it_lang
= it
->data
;
1299 if (purple_str_has_prefix(lang_tag
, it_lang
))
1304 for (it
= spellcheck_languages
; it
; it
= next
) {
1305 gchar
*it_lang
= it
->data
;
1306 next
= g_list_next(it
);
1308 if (!purple_str_has_prefix(it_lang
, lang_tag
))
1312 spellcheck_languages
=
1313 g_list_delete_link(spellcheck_languages
, it
);
1317 spellcheck_languages
= g_list_prepend(spellcheck_languages
,
1318 g_strdup(lang_tag
));
1322 fill_spellcheck_dicts(void)
1326 eb
= enchant_broker_init();
1327 enchant_broker_list_dicts(eb
, fill_spellcheck_dicts_cb
, NULL
);
1328 enchant_broker_free(eb
);
1329 spellcheck_languages
= g_list_sort(spellcheck_languages
,
1330 (GCompareFunc
)strcmp
);
1336 pidgin_webview_insert_image_accu(GSignalInvocationHint
*ihint
,
1337 GValue
*return_accu
, const GValue
*handler_return
, gpointer _unused
)
1341 cancel
= g_value_get_boolean(handler_return
);
1345 g_value_set_boolean(return_accu
, TRUE
);
1350 pidgin_webview_class_init(PidginWebViewClass
*klass
)
1352 GObjectClass
*gobject_class
;
1353 GtkBindingSet
*binding_set
;
1355 gobject_class
= G_OBJECT_CLASS(klass
);
1359 signals
[BUTTONS_UPDATE
] = g_signal_new("allowed-formats-updated",
1360 G_TYPE_FROM_CLASS(gobject_class
),
1362 G_STRUCT_OFFSET(PidginWebViewClass
, buttons_update
),
1364 G_TYPE_NONE
, 1, G_TYPE_INT
);
1365 signals
[TOGGLE_FORMAT
] = g_signal_new("format-toggled",
1366 G_TYPE_FROM_CLASS(gobject_class
),
1367 G_SIGNAL_RUN_LAST
| G_SIGNAL_ACTION
,
1368 G_STRUCT_OFFSET(PidginWebViewClass
, toggle_format
),
1370 G_TYPE_NONE
, 1, G_TYPE_INT
);
1371 signals
[CLEAR_FORMAT
] = g_signal_new("format-cleared",
1372 G_TYPE_FROM_CLASS(gobject_class
),
1373 G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
1374 G_STRUCT_OFFSET(PidginWebViewClass
, clear_format
),
1377 signals
[UPDATE_FORMAT
] = g_signal_new("format-updated",
1378 G_TYPE_FROM_CLASS(gobject_class
),
1380 G_STRUCT_OFFSET(PidginWebViewClass
, update_format
),
1383 signals
[CHANGED
] = g_signal_new("changed",
1384 G_TYPE_FROM_CLASS(gobject_class
),
1386 G_STRUCT_OFFSET(PidginWebViewClass
, changed
),
1389 signals
[HTML_APPENDED
] = g_signal_new("html-appended",
1390 G_TYPE_FROM_CLASS(gobject_class
),
1392 G_STRUCT_OFFSET(PidginWebViewClass
, html_appended
),
1394 G_TYPE_NONE
, 1, WEBKIT_TYPE_DOM_RANGE
,
1396 signals
[INSERT_IMAGE
] = g_signal_new("insert-image",
1397 G_TYPE_FROM_CLASS(gobject_class
), G_SIGNAL_RUN_LAST
,
1398 G_STRUCT_OFFSET(PidginWebViewClass
, insert_image
),
1399 pidgin_webview_insert_image_accu
, NULL
, NULL
,
1405 klass
->toggle_format
= webview_toggle_format
;
1406 klass
->clear_format
= webview_clear_formatting
;
1408 gobject_class
->finalize
= pidgin_webview_finalize
;
1412 binding_set
= gtk_binding_set_by_class(pidgin_webview_parent_class
);
1413 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_b
, GDK_CONTROL_MASK
,
1414 "format-toggled", 1, G_TYPE_INT
,
1415 PIDGIN_WEBVIEW_BOLD
);
1416 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_i
, GDK_CONTROL_MASK
,
1417 "format-toggled", 1, G_TYPE_INT
,
1418 PIDGIN_WEBVIEW_ITALIC
);
1419 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_u
, GDK_CONTROL_MASK
,
1420 "format-toggled", 1, G_TYPE_INT
,
1421 PIDGIN_WEBVIEW_UNDERLINE
);
1422 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_plus
, GDK_CONTROL_MASK
,
1423 "format-toggled", 1, G_TYPE_INT
,
1424 PIDGIN_WEBVIEW_GROW
);
1425 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_equal
, GDK_CONTROL_MASK
,
1426 "format-toggled", 1, G_TYPE_INT
,
1427 PIDGIN_WEBVIEW_GROW
);
1428 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_minus
, GDK_CONTROL_MASK
,
1429 "format-toggled", 1, G_TYPE_INT
,
1430 PIDGIN_WEBVIEW_SHRINK
);
1432 binding_set
= gtk_binding_set_by_class(klass
);
1433 gtk_binding_entry_add_signal(binding_set
, GDK_KEY_r
, GDK_CONTROL_MASK
,
1434 "format-cleared", 0);
1438 G_OBJECT_CLASS(klass
)->set_property
= pidgin_webview_set_property
;
1439 G_OBJECT_CLASS(klass
)->get_property
= pidgin_webview_get_property
;
1441 if (!g_object_class_find_property(G_OBJECT_CLASS(klass
), "expand")) {
1442 /* webkitgtk for gtk2 doesn't seems to have this */
1443 g_object_class_install_property(G_OBJECT_CLASS(klass
),
1444 PROP_EXPAND
, g_param_spec_boolean("expand", "Expand Both",
1445 "Whether widget wants to expand in both directions",
1446 FALSE
, G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS
));
1449 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/webview");
1450 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/webview/inspector_enabled", FALSE
);
1452 g_return_if_fail(smileys_re
== NULL
);
1453 g_return_if_fail(empty_html_re
== NULL
);
1454 smileys_re
= g_regex_new("<img[^>]* class=\"emoticon "
1455 "[^\"^>]*\"[^>]*alt=\"([^\"^>]+)\"[^>]*>",
1456 G_REGEX_DOTALL
| G_REGEX_OPTIMIZE
, 0, NULL
);
1457 empty_html_re
= g_regex_new("<(?!img)[^>]*>",
1458 G_REGEX_DOTALL
| G_REGEX_OPTIMIZE
, 0, NULL
);
1461 fill_spellcheck_dicts();
1466 pidgin_webview_init(PidginWebView
*webview
)
1468 PidginWebViewPrivate
*priv
=
1469 pidgin_webview_get_instance_private(webview
);
1470 WebKitWebInspector
*inspector
;
1472 priv
->load_queue
= g_queue_new();
1474 g_signal_connect(G_OBJECT(webview
), "button-press-event",
1475 G_CALLBACK(webview_button_pressed
), NULL
);
1477 g_signal_connect(G_OBJECT(webview
), "popup-menu",
1478 G_CALLBACK(webview_popup_menu
), NULL
);
1480 g_signal_connect(G_OBJECT(webview
), "navigation-policy-decision-requested",
1481 G_CALLBACK(webview_navigation_decision
), NULL
);
1483 g_signal_connect(G_OBJECT(webview
), "load-started",
1484 G_CALLBACK(webview_load_started
), NULL
);
1486 g_signal_connect(G_OBJECT(webview
), "load-finished",
1487 G_CALLBACK(webview_load_finished
), NULL
);
1489 g_signal_connect(G_OBJECT(webview
), "resource-request-starting",
1490 G_CALLBACK(webview_resource_loading
), NULL
);
1492 g_signal_connect(G_OBJECT(webview
), "resource-load-finished",
1493 G_CALLBACK(webview_resource_loaded
), NULL
);
1495 inspector
= webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview
));
1496 g_signal_connect(G_OBJECT(inspector
), "inspect-web-view",
1497 G_CALLBACK(webview_inspector_create
), NULL
);
1498 g_signal_connect(G_OBJECT(inspector
), "show-window",
1499 G_CALLBACK(webview_inspector_show
), webview
);
1501 if (globally_loaded_images_refcnt
++ == 0) {
1502 g_assert(globally_loaded_images
== NULL
);
1503 globally_loaded_images
= g_hash_table_new_full(g_str_hash
,
1504 g_str_equal
, g_free
, g_object_unref
);
1508 /*****************************************************************************
1509 * Public API functions
1510 *****************************************************************************/
1513 pidgin_webview_quote_js_string(const char *text
)
1515 GString
*str
= g_string_new("\"");
1516 const char *cur
= text
;
1518 while (cur
&& *cur
) {
1521 g_string_append(str
, "\\\\");
1524 g_string_append(str
, "\\\"");
1527 g_string_append(str
, "<br/>");
1532 g_string_append_c(str
, *cur
);
1537 g_string_append_c(str
, '"');
1539 return g_string_free(str
, FALSE
);
1543 pidgin_webview_safe_execute_script(PidginWebView
*webview
, const char *script
)
1545 PidginWebViewPrivate
*priv
;
1547 g_return_if_fail(webview
!= NULL
);
1549 priv
= pidgin_webview_get_instance_private(webview
);
1550 g_queue_push_tail(priv
->load_queue
, GINT_TO_POINTER(LOAD_JS
));
1551 g_queue_push_tail(priv
->load_queue
, g_strdup(script
));
1552 if (!priv
->is_loading
&& priv
->loader
== 0)
1553 priv
->loader
= g_idle_add((GSourceFunc
)process_load_queue
, webview
);
1557 pidgin_webview_load_html_string(PidginWebView
*webview
, const char *html
)
1559 g_return_if_fail(webview
!= NULL
);
1561 webkit_web_view_load_string(WEBKIT_WEB_VIEW(webview
), html
, NULL
, NULL
,
1566 pidgin_webview_load_html_string_with_selection(PidginWebView
*webview
, const char *html
)
1568 g_return_if_fail(webview
!= NULL
);
1570 pidgin_webview_load_html_string(webview
, html
);
1571 pidgin_webview_safe_execute_script(webview
,
1572 "var s = window.getSelection();"
1573 "var r = document.createRange();"
1574 "var n = document.getElementById('caret');"
1575 "r.selectNodeContents(n);"
1576 "var f = r.extractContents();"
1579 "n.parentNode.removeChild(n);"
1580 "s.removeAllRanges();"
1585 pidgin_webview_append_html(PidginWebView
*webview
, const char *html
)
1587 PidginWebViewPrivate
*priv
;
1589 g_return_if_fail(webview
!= NULL
);
1591 priv
= pidgin_webview_get_instance_private(webview
);
1592 g_queue_push_tail(priv
->load_queue
, GINT_TO_POINTER(LOAD_HTML
));
1593 g_queue_push_tail(priv
->load_queue
, g_strdup(html
));
1594 if (!priv
->is_loading
&& priv
->loader
== 0)
1595 priv
->loader
= g_idle_add((GSourceFunc
)process_load_queue
, webview
);
1599 pidgin_webview_set_vadjustment(PidginWebView
*webview
, GtkAdjustment
*vadj
)
1601 PidginWebViewPrivate
*priv
;
1603 g_return_if_fail(webview
!= NULL
);
1605 priv
= pidgin_webview_get_instance_private(webview
);
1610 pidgin_webview_scroll_to_end(PidginWebView
*webview
, gboolean smooth
)
1612 PidginWebViewPrivate
*priv
;
1614 g_return_if_fail(webview
!= NULL
);
1616 priv
= pidgin_webview_get_instance_private(webview
);
1617 if (priv
->scroll_time
)
1618 g_timer_destroy(priv
->scroll_time
);
1619 if (priv
->scroll_src
)
1620 g_source_remove(priv
->scroll_src
);
1622 priv
->scroll_time
= g_timer_new();
1623 priv
->scroll_src
= g_timeout_add_full(G_PRIORITY_LOW
, SCROLL_DELAY
, smooth_scroll_cb
, priv
, NULL
);
1625 priv
->scroll_time
= NULL
;
1626 priv
->scroll_src
= g_idle_add_full(G_PRIORITY_LOW
, scroll_idle_cb
, priv
, NULL
);
1631 pidgin_webview_set_autoscroll(PidginWebView
*webview
, gboolean scroll
)
1633 PidginWebViewPrivate
*priv
;
1635 g_return_if_fail(webview
!= NULL
);
1637 priv
= pidgin_webview_get_instance_private(webview
);
1638 priv
->autoscroll
= scroll
;
1642 pidgin_webview_get_autoscroll(PidginWebView
*webview
)
1644 PidginWebViewPrivate
*priv
;
1646 g_return_val_if_fail(webview
!= NULL
, FALSE
);
1648 priv
= pidgin_webview_get_instance_private(webview
);
1649 return priv
->autoscroll
;
1653 pidgin_webview_page_up(PidginWebView
*webview
)
1655 PidginWebViewPrivate
*priv
;
1656 GtkAdjustment
*vadj
;
1659 g_return_if_fail(webview
!= NULL
);
1661 priv
= pidgin_webview_get_instance_private(webview
);
1663 scroll_val
= gtk_adjustment_get_value(vadj
) - gtk_adjustment_get_page_size(vadj
);
1664 scroll_val
= MAX(scroll_val
, gtk_adjustment_get_lower(vadj
));
1666 gtk_adjustment_set_value(vadj
, scroll_val
);
1670 pidgin_webview_page_down(PidginWebView
*webview
)
1672 PidginWebViewPrivate
*priv
;
1673 GtkAdjustment
*vadj
;
1677 g_return_if_fail(webview
!= NULL
);
1679 priv
= pidgin_webview_get_instance_private(webview
);
1681 page_size
= gtk_adjustment_get_page_size(vadj
);
1682 scroll_val
= gtk_adjustment_get_value(vadj
) + page_size
;
1683 scroll_val
= MIN(scroll_val
, gtk_adjustment_get_upper(vadj
) - page_size
);
1685 gtk_adjustment_set_value(vadj
, scroll_val
);
1689 pidgin_webview_setup_entry(PidginWebView
*webview
, PurpleConnectionFlags flags
)
1691 PidginWebViewButtons buttons
;
1693 g_return_if_fail(webview
!= NULL
);
1695 if (flags
& PURPLE_CONNECTION_FLAG_HTML
) {
1696 gboolean bold
, italic
, underline
, strike
;
1698 buttons
= PIDGIN_WEBVIEW_ALL
;
1700 if (flags
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
1701 buttons
&= ~PIDGIN_WEBVIEW_BACKCOLOR
;
1702 if (flags
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
1704 buttons
&= ~PIDGIN_WEBVIEW_GROW
;
1705 buttons
&= ~PIDGIN_WEBVIEW_SHRINK
;
1707 if (flags
& PURPLE_CONNECTION_FLAG_NO_URLDESC
)
1708 buttons
&= ~PIDGIN_WEBVIEW_LINKDESC
;
1710 pidgin_webview_get_current_format(webview
, &bold
, &italic
, &underline
, &strike
);
1712 pidgin_webview_set_format_functions(webview
, PIDGIN_WEBVIEW_ALL
);
1713 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_bold") != bold
)
1714 pidgin_webview_toggle_bold(webview
);
1716 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_italic") != italic
)
1717 pidgin_webview_toggle_italic(webview
);
1719 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_underline") != underline
)
1720 pidgin_webview_toggle_underline(webview
);
1722 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/conversations/send_strike") != strike
)
1723 pidgin_webview_toggle_strike(webview
);
1725 pidgin_webview_toggle_fontface(webview
,
1726 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/font_face"));
1728 if (!(flags
& PURPLE_CONNECTION_FLAG_NO_FONTSIZE
))
1730 int size
= purple_prefs_get_int(PIDGIN_PREFS_ROOT
"/conversations/font_size");
1732 /* 3 is the default. */
1734 pidgin_webview_font_set_size(webview
, size
);
1737 pidgin_webview_toggle_forecolor(webview
,
1738 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/fgcolor"));
1740 if (!(flags
& PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)) {
1741 pidgin_webview_toggle_backcolor(webview
,
1742 purple_prefs_get_string(PIDGIN_PREFS_ROOT
"/conversations/bgcolor"));
1744 pidgin_webview_toggle_backcolor(webview
, "");
1747 if (flags
& PURPLE_CONNECTION_FLAG_FORMATTING_WBFO
)
1748 pidgin_webview_set_whole_buffer_formatting_only(webview
, TRUE
);
1750 pidgin_webview_set_whole_buffer_formatting_only(webview
, FALSE
);
1752 buttons
= PIDGIN_WEBVIEW_SMILEY
| PIDGIN_WEBVIEW_IMAGE
;
1753 webview_clear_formatting(webview
);
1756 if (flags
& PURPLE_CONNECTION_FLAG_NO_IMAGES
)
1757 buttons
&= ~PIDGIN_WEBVIEW_IMAGE
;
1759 if (flags
& PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
1760 buttons
|= PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
1762 buttons
&= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
1764 pidgin_webview_set_format_functions(webview
, buttons
);
1768 pidgin_webview_set_spellcheck(PidginWebView
*webview
, gboolean enable
)
1770 WebKitWebSettings
*settings
;
1772 g_return_if_fail(webview
!= NULL
);
1774 settings
= webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview
));
1775 g_object_set(G_OBJECT(settings
), "enable-spell-checking", enable
, NULL
);
1776 webkit_web_view_set_settings(WEBKIT_WEB_VIEW(webview
), settings
);
1780 pidgin_webview_set_whole_buffer_formatting_only(PidginWebView
*webview
, gboolean wbfo
)
1782 PidginWebViewPrivate
*priv
;
1784 g_return_if_fail(webview
!= NULL
);
1786 priv
= pidgin_webview_get_instance_private(webview
);
1787 priv
->edit
.wbfo
= wbfo
;
1791 pidgin_webview_set_format_functions(PidginWebView
*webview
, PidginWebViewButtons buttons
)
1793 PidginWebViewPrivate
*priv
;
1796 g_return_if_fail(webview
!= NULL
);
1798 priv
= pidgin_webview_get_instance_private(webview
);
1799 object
= g_object_ref(G_OBJECT(webview
));
1800 priv
->format_functions
= buttons
;
1801 g_signal_emit(object
, signals
[BUTTONS_UPDATE
], 0, buttons
);
1802 g_object_unref(object
);
1806 pidgin_webview_activate_anchor(WebKitDOMHTMLAnchorElement
*link
)
1808 WebKitDOMDocument
*doc
;
1809 WebKitDOMEvent
*event
;
1811 doc
= webkit_dom_node_get_owner_document(WEBKIT_DOM_NODE(link
));
1812 event
= webkit_dom_document_create_event(doc
, "MouseEvent", NULL
);
1813 webkit_dom_event_init_event(event
, "click", TRUE
, TRUE
);
1814 webkit_dom_node_dispatch_event(WEBKIT_DOM_NODE(link
), event
, NULL
);
1818 pidgin_webview_class_register_protocol(const char *name
,
1819 gboolean (*activate
)(PidginWebView
*webview
, const char *uri
),
1820 gboolean (*context_menu
)(PidginWebView
*webview
, WebKitDOMHTMLAnchorElement
*link
, GtkWidget
*menu
))
1822 PidginWebViewClass
*klass
;
1823 PidginWebViewProtocol
*proto
;
1825 g_return_val_if_fail(name
, FALSE
);
1827 klass
= g_type_class_ref(PIDGIN_TYPE_WEBVIEW
);
1828 g_return_val_if_fail(klass
, FALSE
);
1830 if ((proto
= webview_find_protocol(name
, TRUE
))) {
1834 klass
->protocols
= g_list_remove(klass
->protocols
, proto
);
1835 g_free(proto
->name
);
1838 } else if (!activate
) {
1842 proto
= g_new0(PidginWebViewProtocol
, 1);
1843 proto
->name
= g_strdup(name
);
1844 proto
->length
= strlen(name
);
1845 proto
->activate
= activate
;
1846 proto
->context_menu
= context_menu
;
1847 klass
->protocols
= g_list_prepend(klass
->protocols
, proto
);
1853 pidgin_webview_get_head_html(PidginWebView
*webview
)
1855 WebKitDOMDocument
*doc
;
1856 WebKitDOMHTMLHeadElement
*head
;
1859 g_return_val_if_fail(webview
!= NULL
, NULL
);
1861 doc
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1862 head
= webkit_dom_document_get_head(doc
);
1863 html
= webkit_dom_html_element_get_inner_html(WEBKIT_DOM_HTML_ELEMENT(head
));
1869 pidgin_webview_strip_smileys(const gchar
*text
)
1871 return g_regex_replace(smileys_re
, text
, -1, 0, "\\1", 0, NULL
);
1875 pidgin_webview_get_body_html(PidginWebView
*webview
)
1877 WebKitDOMDocument
*doc
;
1878 WebKitDOMHTMLElement
*body
;
1879 gchar
*html
, *stripped
;
1881 g_return_val_if_fail(webview
!= NULL
, NULL
);
1883 doc
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1884 body
= webkit_dom_document_get_body(doc
);
1885 html
= webkit_dom_html_element_get_inner_html(body
);
1886 stripped
= pidgin_webview_strip_smileys(html
);
1893 pidgin_webview_get_body_text(PidginWebView
*webview
)
1895 WebKitDOMDocument
*doc
;
1896 WebKitDOMHTMLElement
*body
;
1899 g_return_val_if_fail(webview
!= NULL
, NULL
);
1901 doc
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1902 body
= webkit_dom_document_get_body(doc
);
1903 text
= webkit_dom_html_element_get_inner_text(body
);
1909 pidgin_webview_get_selected_text(PidginWebView
*webview
)
1911 WebKitDOMDocument
*dom
;
1912 WebKitDOMDOMWindow
*win
;
1913 WebKitDOMDOMSelection
*sel
;
1914 WebKitDOMRange
*range
= NULL
;
1916 g_return_val_if_fail(webview
!= NULL
, NULL
);
1918 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1919 win
= webkit_dom_document_get_default_view(dom
);
1920 sel
= webkit_dom_dom_window_get_selection(win
);
1921 if (webkit_dom_dom_selection_get_range_count(sel
))
1922 range
= webkit_dom_dom_selection_get_range_at(sel
, 0, NULL
);
1925 return webkit_dom_range_get_text(range
);
1931 pidgin_webview_strip_empty_html(const gchar
*text
)
1933 return g_regex_replace(empty_html_re
, text
, -1, 0, "", 0, NULL
);
1937 pidgin_webview_is_empty(PidginWebView
*webview
)
1942 g_return_val_if_fail(webview
!= NULL
, TRUE
);
1944 html
= pidgin_webview_get_body_html(webview
);
1945 tmp
= purple_strreplace(html
, " ", " ");
1949 tmp
= pidgin_webview_strip_empty_html(html
);
1954 is_empty
= (html
[0] == '\0');
1961 pidgin_webview_get_caret(PidginWebView
*webview
, WebKitDOMNode
**container_ret
,
1964 WebKitDOMDocument
*dom
;
1965 WebKitDOMDOMWindow
*win
;
1966 WebKitDOMDOMSelection
*sel
;
1967 WebKitDOMRange
*range
= NULL
;
1968 WebKitDOMNode
*start_container
, *end_container
;
1971 g_return_if_fail(webview
&& container_ret
&& pos_ret
);
1973 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
1974 win
= webkit_dom_document_get_default_view(dom
);
1975 sel
= webkit_dom_dom_window_get_selection(win
);
1976 if (webkit_dom_dom_selection_get_range_count(sel
))
1977 range
= webkit_dom_dom_selection_get_range_at(sel
, 0, NULL
);
1980 start_container
= webkit_dom_range_get_start_container(range
, NULL
);
1981 start
= webkit_dom_range_get_start_offset(range
, NULL
);
1982 end_container
= webkit_dom_range_get_end_container(range
, NULL
);
1983 end
= webkit_dom_range_get_end_offset(range
, NULL
);
1986 webkit_dom_node_is_same_node(start_container
, end_container
)) {
1988 *container_ret
= start_container
;
1994 *container_ret
= NULL
;
1999 pidgin_webview_set_caret(PidginWebView
*webview
, WebKitDOMNode
*container
, glong pos
)
2001 WebKitDOMDocument
*dom
;
2002 WebKitDOMDOMWindow
*win
;
2003 WebKitDOMDOMSelection
*sel
;
2005 g_return_if_fail(webview
&& container
&& pos
>= 0);
2007 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2008 win
= webkit_dom_document_get_default_view(dom
);
2009 sel
= webkit_dom_dom_window_get_selection(win
);
2011 webkit_dom_dom_selection_set_position(sel
, container
, pos
, NULL
);
2014 PidginWebViewButtons
2015 pidgin_webview_get_format_functions(PidginWebView
*webview
)
2017 PidginWebViewPrivate
*priv
;
2019 g_return_val_if_fail(webview
!= NULL
, 0);
2021 priv
= pidgin_webview_get_instance_private(webview
);
2022 return priv
->format_functions
;
2026 pidgin_webview_get_current_format(PidginWebView
*webview
, gboolean
*bold
,
2027 gboolean
*italic
, gboolean
*underline
,
2030 WebKitDOMDocument
*dom
;
2032 g_return_if_fail(webview
!= NULL
);
2034 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2037 *bold
= webkit_dom_document_query_command_state(dom
, "bold");
2039 *italic
= webkit_dom_document_query_command_state(dom
, "italic");
2041 *underline
= webkit_dom_document_query_command_state(dom
, "underline");
2043 *strike
= webkit_dom_document_query_command_state(dom
, "strikethrough");
2047 pidgin_webview_get_current_fontface(PidginWebView
*webview
)
2049 WebKitDOMDocument
*dom
;
2051 g_return_val_if_fail(webview
!= NULL
, NULL
);
2053 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2054 return webkit_dom_document_query_command_value(dom
, "fontName");
2058 pidgin_webview_get_current_forecolor(PidginWebView
*webview
)
2060 WebKitDOMDocument
*dom
;
2062 g_return_val_if_fail(webview
!= NULL
, NULL
);
2064 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2065 return webkit_dom_document_query_command_value(dom
, "foreColor");
2069 pidgin_webview_get_current_backcolor(PidginWebView
*webview
)
2071 WebKitDOMDocument
*dom
;
2073 g_return_val_if_fail(webview
!= NULL
, NULL
);
2075 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2076 return webkit_dom_document_query_command_value(dom
, "backColor");
2080 pidgin_webview_get_current_fontsize(PidginWebView
*webview
)
2082 WebKitDOMDocument
*dom
;
2086 g_return_val_if_fail(webview
!= NULL
, 0);
2088 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2089 text
= webkit_dom_document_query_command_value(dom
, "fontSize");
2097 pidgin_webview_clear_formatting(PidginWebView
*webview
)
2101 g_return_if_fail(webview
!= NULL
);
2103 object
= g_object_ref(G_OBJECT(webview
));
2104 g_signal_emit(object
, signals
[CLEAR_FORMAT
], 0);
2105 g_object_unref(object
);
2109 pidgin_webview_toggle_bold(PidginWebView
*webview
)
2111 g_return_if_fail(webview
!= NULL
);
2112 emit_format_signal(webview
, PIDGIN_WEBVIEW_BOLD
);
2116 pidgin_webview_toggle_italic(PidginWebView
*webview
)
2118 g_return_if_fail(webview
!= NULL
);
2119 emit_format_signal(webview
, PIDGIN_WEBVIEW_ITALIC
);
2123 pidgin_webview_toggle_underline(PidginWebView
*webview
)
2125 g_return_if_fail(webview
!= NULL
);
2126 emit_format_signal(webview
, PIDGIN_WEBVIEW_UNDERLINE
);
2130 pidgin_webview_toggle_strike(PidginWebView
*webview
)
2132 g_return_if_fail(webview
!= NULL
);
2133 emit_format_signal(webview
, PIDGIN_WEBVIEW_STRIKE
);
2137 pidgin_webview_toggle_forecolor(PidginWebView
*webview
, const char *color
)
2139 g_return_val_if_fail(webview
!= NULL
, FALSE
);
2141 do_formatting(webview
, "foreColor", color
);
2142 emit_format_signal(webview
, PIDGIN_WEBVIEW_FORECOLOR
);
2148 pidgin_webview_toggle_backcolor(PidginWebView
*webview
, const char *color
)
2150 g_return_val_if_fail(webview
!= NULL
, FALSE
);
2152 do_formatting(webview
, "backColor", color
);
2153 emit_format_signal(webview
, PIDGIN_WEBVIEW_BACKCOLOR
);
2159 pidgin_webview_toggle_fontface(PidginWebView
*webview
, const char *face
)
2161 g_return_val_if_fail(webview
!= NULL
, FALSE
);
2163 do_formatting(webview
, "fontName", face
);
2164 emit_format_signal(webview
, PIDGIN_WEBVIEW_FACE
);
2170 pidgin_webview_font_set_size(PidginWebView
*webview
, gint size
)
2174 g_return_if_fail(webview
!= NULL
);
2176 tmp
= g_strdup_printf("%d", size
);
2177 do_formatting(webview
, "fontSize", tmp
);
2178 emit_format_signal(webview
, PIDGIN_WEBVIEW_SHRINK
|PIDGIN_WEBVIEW_GROW
);
2183 pidgin_webview_font_shrink(PidginWebView
*webview
)
2185 g_return_if_fail(webview
!= NULL
);
2186 emit_format_signal(webview
, PIDGIN_WEBVIEW_SHRINK
);
2190 pidgin_webview_font_grow(PidginWebView
*webview
)
2192 g_return_if_fail(webview
!= NULL
);
2193 emit_format_signal(webview
, PIDGIN_WEBVIEW_GROW
);
2197 pidgin_webview_insert_hr(PidginWebView
*webview
)
2199 PidginWebViewPrivate
*priv
;
2200 WebKitDOMDocument
*dom
;
2202 g_return_if_fail(webview
!= NULL
);
2204 priv
= pidgin_webview_get_instance_private(webview
);
2205 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2207 priv
->edit
.block_changed
= TRUE
;
2208 webkit_dom_document_exec_command(dom
, "insertHorizontalRule", FALSE
, "");
2209 priv
->edit
.block_changed
= FALSE
;
2213 pidgin_webview_insert_link(PidginWebView
*webview
, const char *url
, const char *desc
)
2215 PidginWebViewPrivate
*priv
;
2216 WebKitDOMDocument
*dom
;
2219 g_return_if_fail(webview
!= NULL
);
2221 priv
= pidgin_webview_get_instance_private(webview
);
2222 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2223 link
= g_strdup_printf("<a href='%s'>%s</a>", url
, desc
? desc
: url
);
2225 priv
->edit
.block_changed
= TRUE
;
2226 webkit_dom_document_exec_command(dom
, "insertHTML", FALSE
, link
);
2227 priv
->edit
.block_changed
= FALSE
;
2232 pidgin_webview_insert_image(PidginWebView
*webview
, PurpleImage
*image
)
2234 PidginWebViewPrivate
*priv
;
2235 WebKitDOMDocument
*dom
;
2240 g_return_if_fail(webview
!= NULL
);
2242 g_signal_emit(webview
, signals
[INSERT_IMAGE
], 0, image
, &cancel
);
2246 id
= purple_image_store_add(image
);
2247 priv
= pidgin_webview_get_instance_private(webview
);
2248 dom
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2249 img
= g_strdup_printf("<img src='" PURPLE_IMAGE_STORE_PROTOCOL
2252 priv
->edit
.block_changed
= TRUE
;
2253 webkit_dom_document_exec_command(dom
, "insertHTML", FALSE
, img
);
2254 priv
->edit
.block_changed
= FALSE
;
2258 static WebKitDOMCSSStyleDeclaration
*
2259 pidgin_webview_get_DOM_CSS_style(PidginWebView
*webview
)
2261 WebKitDOMDocument
*document
;
2262 WebKitDOMElement
*dom_element
;
2263 WebKitDOMDOMWindow
*dom_window
;
2265 document
= webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview
));
2266 dom_window
= webkit_dom_document_get_default_view(document
);
2268 dom_element
= webkit_dom_document_get_document_element(document
);
2269 return webkit_dom_dom_window_get_computed_style(dom_window
, dom_element
, 0);
2273 pidgin_webview_get_DOM_height(PidginWebView
*webview
)
2276 WebKitDOMCSSStyleDeclaration
*style
;
2278 style
= pidgin_webview_get_DOM_CSS_style(webview
);
2279 value
= webkit_dom_css_style_declaration_get_property_value(style
, "height");
2281 return g_ascii_strtoll(value
, NULL
, 0);
2285 pidgin_webview_get_font_size(PidginWebView
*webview
)
2288 WebKitDOMCSSStyleDeclaration
*style
;
2290 style
= pidgin_webview_get_DOM_CSS_style(webview
);
2291 value
= webkit_dom_css_style_declaration_get_property_value(style
, "font-size");
2293 return g_ascii_strtoll(value
, NULL
, 0);
2297 pidgin_webview_set_toolbar(PidginWebView
*webview
, GtkWidget
*toolbar
)
2299 PidginWebViewPrivate
*priv
;
2301 g_return_if_fail(webview
!= NULL
);
2303 priv
= pidgin_webview_get_instance_private(webview
);
2304 priv
->toolbar
= PIDGIN_WEBVIEWTOOLBAR(toolbar
);
2308 pidgin_webview_get_toolbar(PidginWebView
*webview
)
2310 PidginWebViewPrivate
*priv
;
2312 g_return_val_if_fail(webview
!= NULL
, NULL
);
2314 priv
= pidgin_webview_get_instance_private(webview
);
2315 return GTK_WIDGET(priv
->toolbar
);
2319 pidgin_webview_show_toolbar(PidginWebView
*webview
)
2321 PidginWebViewPrivate
*priv
;
2323 g_return_if_fail(webview
!= NULL
);
2325 priv
= pidgin_webview_get_instance_private(webview
);
2326 g_return_if_fail(priv
->toolbar
!= NULL
);
2328 gtk_widget_show(GTK_WIDGET(priv
->toolbar
));
2332 pidgin_webview_hide_toolbar(PidginWebView
*webview
)
2334 PidginWebViewPrivate
*priv
;
2336 g_return_if_fail(webview
!= NULL
);
2338 priv
= pidgin_webview_get_instance_private(webview
);
2339 g_return_if_fail(priv
->toolbar
!= NULL
);
2341 gtk_widget_hide(GTK_WIDGET(priv
->toolbar
));
2345 pidgin_webview_activate_toolbar(PidginWebView
*webview
, PidginWebViewAction action
)
2347 PidginWebViewPrivate
*priv
;
2349 g_return_if_fail(webview
!= NULL
);
2351 priv
= pidgin_webview_get_instance_private(webview
);
2352 g_return_if_fail(priv
->toolbar
!= NULL
);
2354 pidgin_webviewtoolbar_activate(priv
->toolbar
, action
);
2358 pidgin_webview_switch_active_conversation(PidginWebView
*webview
,
2359 PurpleConversation
*conv
)
2361 PidginWebViewPrivate
*priv
=
2362 pidgin_webview_get_instance_private(webview
);
2364 g_return_if_fail(priv
!= NULL
);
2365 if (priv
->toolbar
== NULL
)
2368 pidgin_webviewtoolbar_switch_active_conversation(priv
->toolbar
, conv
);