Convert UI ops to PurpleBuddyListClass methods.
[pidgin-git.git] / pidgin / gtkwebview.c
blob11848420a41be0ae85f03edb50eeaad21c91827b
1 /* pidgin
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
5 * source distribution.
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
23 #include "internal.h"
24 #include "debug.h"
25 #include "glibcompat.h"
26 #include "image-store.h"
27 #include "pidgin.h"
28 #include "pidginstock.h"
30 #include <gdk/gdkkeysyms.h>
31 #ifdef USE_ENCHANT
32 #include <enchant.h>
33 #endif
35 #include "gtkutils.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 */
48 enum {
49 LOAD_HTML,
50 LOAD_JS
53 enum {
54 BUTTONS_UPDATE,
55 TOGGLE_FORMAT,
56 CLEAR_FORMAT,
57 UPDATE_FORMAT,
58 CHANGED,
59 HTML_APPENDED,
60 INSERT_IMAGE,
61 LAST_SIGNAL
63 static guint signals[LAST_SIGNAL] = { 0 };
65 /******************************************************************************
66 * Structs
67 *****************************************************************************/
69 typedef struct {
70 WebKitWebInspector *inspector;
71 WebKitDOMNode *node;
72 } PidginWebViewInspectData;
74 typedef struct {
75 WebKitWebView *webview;
76 gunichar ch;
77 } PidginWebViewInsertData;
79 typedef struct {
80 const char *label;
81 gunichar ch;
82 } GtkUnicodeMenuEntry;
84 typedef struct {
85 char *name;
86 int length;
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 */
94 gboolean is_loading;
95 GQueue *load_queue;
96 guint loader;
98 /* Scroll adjustments */
99 GtkAdjustment *vadj;
100 gboolean autoscroll;
101 guint scroll_src;
102 GTimer *scroll_time;
104 /* Format options */
105 PidginWebViewButtons format_functions;
106 PidginWebViewToolbar *toolbar;
107 struct {
108 gboolean wbfo:1; /* Whole buffer formatting only. */
109 gboolean block_changed:1;
110 } edit;
112 /* WebKit inspector */
113 WebKitWebView *inspector_view;
114 GtkWindow *inspector_win;
116 /* helper scripts */
117 gboolean refresh_spell_installed;
118 } PidginWebViewPrivate;
120 /******************************************************************************
121 * Globals
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;
130 /* Resources cache.
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 /******************************************************************************
141 * Helpers
142 *****************************************************************************/
144 static void
145 webview_resource_loading(WebKitWebView *webview,
146 WebKitWebFrame *frame,
147 WebKitWebResource *resource,
148 WebKitNetworkRequest *request,
149 WebKitNetworkResponse *response,
150 gpointer user_data)
152 const gchar *uri;
153 PurpleImage *img = NULL;
154 const gchar *path;
156 uri = webkit_network_request_get_uri(request);
157 if ((img = purple_image_store_get_from_uri(uri)) != NULL) {
158 /* noop */
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, '/');
167 if (!found) {
168 purple_debug_warning("webview", "Invalid purple stock "
169 "image uri: %s", uri);
170 g_free(p_uri);
171 return;
174 found[0] = '\0';
175 domain = p_uri;
176 stock_name = found + 1;
178 if (g_strcmp0(domain, "e2ee") == 0) {
179 img = _pidgin_e2ee_stock_icon_get(stock_name);
180 if (!img) {
181 g_free(p_uri);
182 return;
184 } else {
185 purple_debug_warning("webview", "Invalid purple stock "
186 "image domain: %s", domain);
188 g_free(p_uri);
189 return;
191 } else
192 return;
194 if (img != NULL) {
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.
201 gchar *uri = NULL;
203 path = purple_image_get_path(img);
205 if (path) {
206 uri = g_filename_to_uri(path, NULL, NULL);
209 if (uri != NULL) {
210 webkit_network_request_set_uri(request, uri);
211 g_free(uri);
212 } else {
213 gchar *b64, *src;
214 const gchar *type;
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);
220 g_free(b64);
221 webkit_network_request_set_uri(request, src);
222 g_free(src);
227 static void
228 webview_resource_loaded(WebKitWebView *web_view, WebKitWebFrame *web_frame,
229 WebKitWebResource *web_resource, gpointer user_data)
231 const gchar *uri;
232 GString *data;
233 PurpleImage *image = NULL;
235 if (!purple_str_has_caseprefix(
236 webkit_web_resource_get_mime_type(web_resource), "image/"))
238 return;
241 uri = webkit_web_resource_get_uri(web_resource);
242 if (g_hash_table_lookup(globally_loaded_images, uri))
243 return;
245 data = webkit_web_resource_get_data(web_resource);
246 if (data->len == 0)
247 return;
249 image = purple_image_store_get_from_uri(uri);
250 if (image) {
251 g_object_ref(image);
252 } else {
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);
263 static PurpleImage *
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);
274 static void
275 process_load_queue_element(PidginWebView *webview)
277 PidginWebViewPrivate *priv =
278 pidgin_webview_get_instance_private(webview);
279 int type;
280 char *str;
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);
290 switch (type) {
291 case LOAD_HTML:
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",
303 str, NULL);
305 range = webkit_dom_document_create_range(doc);
306 if (start) {
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),
310 NULL);
311 webkit_dom_range_set_end_after(range,
312 WEBKIT_DOM_NODE(end),
313 NULL);
314 } else {
315 webkit_dom_range_select_node_contents(range,
316 WEBKIT_DOM_NODE(body),
317 NULL);
320 if (require_scroll) {
321 if (start)
322 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(start),
323 TRUE);
324 else
325 webkit_dom_element_scroll_into_view(WEBKIT_DOM_ELEMENT(body),
326 TRUE);
329 g_signal_emit(webview, signals[HTML_APPENDED], 0, range);
331 break;
333 case LOAD_JS:
334 webkit_web_view_execute_script(WEBKIT_WEB_VIEW(webview), str);
335 break;
337 default:
338 purple_debug_error("webview",
339 "Got unknown loading queue type: %d\n", type);
340 break;
343 g_free(str);
346 static gboolean
347 process_load_queue(PidginWebView *webview)
349 PidginWebViewPrivate *priv =
350 pidgin_webview_get_instance_private(webview);
351 gint64 start_time;
353 if (priv->is_loading) {
354 priv->loader = 0;
355 return FALSE;
357 if (!priv->load_queue || g_queue_is_empty(priv->load_queue)) {
358 priv->loader = 0;
359 return FALSE;
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)
367 break;
370 if (g_queue_is_empty(priv->load_queue)) {
371 priv->loader = 0;
372 return FALSE;
374 return TRUE;
377 static void
378 webview_load_started(WebKitWebView *webview, WebKitWebFrame *frame,
379 gpointer userdata)
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;
388 static void
389 webview_load_finished(WebKitWebView *webview, WebKitWebFrame *frame,
390 gpointer userdata)
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);
400 static void
401 webview_inspector_inspect_element(GtkWidget *item, PidginWebViewInspectData *data)
403 webkit_web_inspector_inspect_node(data->inspector, data->node);
406 static void
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;
439 static gboolean
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));
447 return TRUE;
450 static PidginWebViewProtocol *
451 webview_find_protocol(const char *url, gboolean reverse)
453 PidginWebViewClass *klass;
454 GList *iter;
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) {
460 proto = iter->data;
461 if (g_ascii_strncasecmp(url, proto->name, reverse ? MIN(length, proto->length) : proto->length) == 0) {
462 g_type_class_unref(klass);
463 return proto;
467 g_type_class_unref(klass);
468 return NULL;
471 static gboolean
472 webview_navigation_decision(WebKitWebView *webview,
473 WebKitWebFrame *frame,
474 WebKitNetworkRequest *request,
475 WebKitWebNavigationAction *navigation_action,
476 WebKitWebPolicyDecision *policy_decision,
477 gpointer userdata)
479 const gchar *uri;
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);
487 if (proto) {
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);
494 else
495 webkit_web_policy_decision_ignore(policy_decision);
497 return TRUE;
500 static GtkWidget *
501 get_input_methods_menu(WebKitWebView *webview)
503 GtkSettings *settings;
504 gboolean show = TRUE;
505 GtkWidget *item;
506 GtkWidget *menu;
507 GtkIMContext *im;
509 settings = webview ? gtk_widget_get_settings(GTK_WIDGET(webview)) : gtk_settings_get_default();
511 if (settings)
512 g_object_get(settings, "gtk-show-input-method-menu", &show, NULL);
513 if (!show)
514 return 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);
524 return item;
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 }
541 static void
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;
548 char buf[6];
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;
559 static GtkWidget *
560 get_unicode_menu(WebKitWebView *webview)
562 GtkSettings *settings;
563 gboolean show = TRUE;
564 GtkWidget *menuitem;
565 GtkWidget *menu;
566 gsize i;
568 settings = webview ? gtk_widget_get_settings(GTK_WIDGET(webview)) : gtk_settings_get_default();
570 if (settings)
571 g_object_get(settings, "gtk-show-unicode-menu", &show, NULL);
572 if (!show)
573 return 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;
580 GtkWidget *item;
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);
596 return menuitem;
599 #ifdef USE_ENCHANT
601 static void
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);"
616 "};";
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()");
626 static void
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);
641 static GtkWidget *
642 get_spelldict_menu(WebKitWebView *webview)
644 GtkWidget *menuitem;
645 GtkWidget *menu;
646 GList *it;
648 if (spellcheck_languages == NULL)
649 return 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)) {
655 GtkWidget *item;
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);
667 return menuitem;
670 #else
671 static GtkWidget *
672 get_spelldict_menu(WebKitWebView *webview)
674 return NULL;
676 #endif
678 static void
679 webview_image_saved(GtkWidget *dialog, gint response, gpointer _unused)
681 PurpleImage *image;
682 gchar *filename;
684 if (response != GTK_RESPONSE_ACCEPT) {
685 gtk_widget_destroy(dialog);
686 return;
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 */
701 g_free(filename);
702 gtk_widget_destroy(dialog);
705 static void
706 webview_image_save(GtkWidget *item, WebKitDOMHTMLImageElement *image_node)
708 const gchar *src;
709 WebKitWebView *webview;
710 PurpleImage *image;
711 GtkFileChooserDialog *dialog;
712 const gchar *filename;
713 GtkWidget *parent;
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(
724 _("Save Image"),
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,
729 NULL));
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);
740 g_object_ref(image);
741 g_object_set_data_full(G_OBJECT(dialog), "pidgin-gtkwebview-image",
742 image, g_object_unref);
744 gtk_widget_show(GTK_WIDGET(dialog));
747 static void
748 webview_image_add_smiley(GtkWidget *item, WebKitDOMHTMLImageElement *image_node)
750 const gchar *src;
751 WebKitWebView *webview;
752 PurpleImage *image;
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));
765 static void
766 do_popup_menu(WebKitWebView *webview, GdkEvent *event, int context,
767 WebKitDOMNode *node, const char *uri)
769 GtkWidget *menu;
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;
780 GList *children;
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));
796 if (!children) {
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);
801 } else {
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);
815 if (_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;
821 int width, height;
823 width = webkit_dom_html_image_element_get_width(image_node);
824 height = webkit_dom_html_image_element_get_height(image_node);
826 /* XXX */
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),
848 image_node, 0);
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,
859 NULL, NULL);
860 g_signal_connect_swapped(G_OBJECT(cut), "activate",
861 G_CALLBACK(webkit_web_view_cut_clipboard),
862 webview);
864 copy = pidgin_new_menu_item(menu, _("_Copy"), GTK_STOCK_COPY,
865 NULL, NULL);
866 g_signal_connect_swapped(G_OBJECT(copy), "activate",
867 G_CALLBACK(webkit_web_view_copy_clipboard),
868 webview);
870 paste = pidgin_new_menu_item(menu, _("_Paste"), GTK_STOCK_PASTE,
871 NULL, NULL);
872 g_signal_connect_swapped(G_OBJECT(paste), "activate",
873 G_CALLBACK(webkit_web_view_paste_clipboard),
874 webview);
876 delete = pidgin_new_menu_item(menu, _("_Delete"), GTK_STOCK_DELETE,
877 NULL, NULL);
878 g_signal_connect_swapped(G_OBJECT(delete), "activate",
879 G_CALLBACK(webkit_web_view_delete_selection),
880 webview);
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),
888 webview);
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;
904 GtkWidget *inspect;
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);
912 data->node = node;
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);
931 if (im) {
932 gtk_menu_shell_append(GTK_MENU_SHELL(menu), im);
933 gtk_widget_show(im);
936 if (unicode) {
937 gtk_menu_shell_append(GTK_MENU_SHELL(menu), unicode);
938 gtk_widget_show(unicode);
941 if (spelldict) {
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);
953 static gboolean
954 webview_popup_menu(WebKitWebView *webview)
956 WebKitDOMDocument *doc;
957 WebKitDOMNode *node = NULL;
958 int context = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT;
959 char *uri = NULL;
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));
970 link = active;
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);
981 g_free(uri);
983 return TRUE;
986 static gboolean
987 webview_button_pressed(WebKitWebView *webview, GdkEventButton *event)
989 if (gdk_event_triggers_context_menu((GdkEvent *)event)) {
990 WebKitHitTestResult *hit;
991 int context;
992 WebKitDOMNode *node;
993 char *uri;
995 hit = webkit_web_view_get_hit_test_result(webview, event);
996 g_object_get(G_OBJECT(hit),
997 "context", &context,
998 "inner-node", &node,
999 "link-uri", &uri,
1000 NULL);
1002 do_popup_menu(webview, (GdkEvent *)event, context,
1003 node, uri);
1005 g_free(uri);
1006 g_object_unref(hit);
1008 return TRUE;
1011 return FALSE;
1015 * Smoothly scroll a WebView.
1017 * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
1019 static gboolean
1020 smooth_scroll_cb(gpointer data)
1022 PidginWebViewPrivate *priv = data;
1023 GtkAdjustment *adj;
1024 gdouble max_val;
1025 gdouble scroll_val;
1027 g_return_val_if_fail(priv->scroll_time != NULL, FALSE);
1029 adj = priv->vadj;
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;
1041 return FALSE;
1044 /* scroll by 1/3rd the remaining distance */
1045 gtk_adjustment_set_value(adj, scroll_val);
1046 return TRUE;
1049 static gboolean
1050 scroll_idle_cb(gpointer data)
1052 PidginWebViewPrivate *priv = data;
1053 GtkAdjustment *adj = priv->vadj;
1054 gdouble max_val;
1056 if (adj) {
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;
1062 return FALSE;
1065 static void
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);
1073 static void
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) {
1098 if (range) {
1099 webkit_dom_dom_selection_remove_all_ranges(sel);
1100 webkit_dom_dom_selection_add_range(sel, range);
1101 } else {
1102 webkit_dom_dom_selection_collapse_to_end(sel, NULL);
1107 static void
1108 webview_font_shrink(PidginWebView *webview)
1110 gint fontsize;
1111 char *tmp;
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);
1118 g_free(tmp);
1121 static void
1122 webview_font_grow(PidginWebView *webview)
1124 gint fontsize;
1125 char *tmp;
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);
1132 g_free(tmp);
1135 static void
1136 webview_clear_formatting(PidginWebView *webview)
1138 if (!webkit_web_view_get_editable(WEBKIT_WEB_VIEW(webview)))
1139 return;
1141 do_formatting(webview, "removeFormat", "");
1142 do_formatting(webview, "unlink", "");
1143 do_formatting(webview, "backColor", "inherit");
1146 static void
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);
1153 switch (buttons) {
1154 case PIDGIN_WEBVIEW_BOLD:
1155 do_formatting(webview, "bold", "");
1156 break;
1157 case PIDGIN_WEBVIEW_ITALIC:
1158 do_formatting(webview, "italic", "");
1159 break;
1160 case PIDGIN_WEBVIEW_UNDERLINE:
1161 do_formatting(webview, "underline", "");
1162 break;
1163 case PIDGIN_WEBVIEW_STRIKE:
1164 do_formatting(webview, "strikethrough", "");
1165 break;
1166 case PIDGIN_WEBVIEW_SHRINK:
1167 webview_font_shrink(webview);
1168 break;
1169 case PIDGIN_WEBVIEW_GROW:
1170 webview_font_grow(webview);
1171 break;
1172 default:
1173 break;
1177 static void
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 /******************************************************************************
1187 * GObject Stuff
1188 *****************************************************************************/
1190 GtkWidget *
1191 pidgin_webview_new(gboolean editable)
1193 GtkWidget *result;
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);
1202 #ifdef _WIN32
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
1206 * font family).
1208 g_object_set(G_OBJECT(settings), "default-font-family", "Verdana", NULL);
1209 #endif
1210 webkit_web_view_set_settings(webview, settings);
1212 if (editable) {
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);
1219 return result;
1222 static void
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));
1231 if (priv->loader)
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);
1249 enum {
1250 PROP_0,
1251 PROP_EXPAND
1254 static void
1255 pidgin_webview_set_property(GObject *object, guint prop_id, const GValue *value,
1256 GParamSpec *pspec)
1258 g_return_if_fail(PIDGIN_IS_WEBVIEW(object));
1260 switch (prop_id) {
1261 case PROP_EXPAND:
1262 purple_debug_misc("webview",
1263 "Ignored expand property (set to %d)",
1264 g_value_get_boolean(value));
1265 break;
1266 default:
1267 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id,
1268 pspec);
1272 static void
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);
1280 #ifdef USE_ENCHANT
1282 static void
1283 fill_spellcheck_dicts_cb(const gchar *lang_tag, const gchar *provider_name,
1284 const gchar *provider_desc, const gchar *provider_file,
1285 void *_unused)
1287 gboolean is_dialect;
1288 GList *it;
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);
1295 if (is_dialect) {
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))
1300 return;
1302 } else {
1303 GList *next;
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))
1309 continue;
1311 g_free(it_lang);
1312 spellcheck_languages =
1313 g_list_delete_link(spellcheck_languages, it);
1317 spellcheck_languages = g_list_prepend(spellcheck_languages,
1318 g_strdup(lang_tag));
1321 static void
1322 fill_spellcheck_dicts(void)
1324 EnchantBroker *eb;
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);
1333 #endif
1335 static gboolean
1336 pidgin_webview_insert_image_accu(GSignalInvocationHint *ihint,
1337 GValue *return_accu, const GValue *handler_return, gpointer _unused)
1339 gboolean cancel;
1341 cancel = g_value_get_boolean(handler_return);
1342 if (!cancel)
1343 return FALSE;
1345 g_value_set_boolean(return_accu, TRUE);
1346 return TRUE;
1349 static void
1350 pidgin_webview_class_init(PidginWebViewClass *klass)
1352 GObjectClass *gobject_class;
1353 GtkBindingSet *binding_set;
1355 gobject_class = G_OBJECT_CLASS(klass);
1357 /* Signals */
1359 signals[BUTTONS_UPDATE] = g_signal_new("allowed-formats-updated",
1360 G_TYPE_FROM_CLASS(gobject_class),
1361 G_SIGNAL_RUN_FIRST,
1362 G_STRUCT_OFFSET(PidginWebViewClass, buttons_update),
1363 NULL, 0, NULL,
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),
1369 NULL, 0, NULL,
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),
1375 NULL, 0, NULL,
1376 G_TYPE_NONE, 0);
1377 signals[UPDATE_FORMAT] = g_signal_new("format-updated",
1378 G_TYPE_FROM_CLASS(gobject_class),
1379 G_SIGNAL_RUN_FIRST,
1380 G_STRUCT_OFFSET(PidginWebViewClass, update_format),
1381 NULL, 0, NULL,
1382 G_TYPE_NONE, 0);
1383 signals[CHANGED] = g_signal_new("changed",
1384 G_TYPE_FROM_CLASS(gobject_class),
1385 G_SIGNAL_RUN_FIRST,
1386 G_STRUCT_OFFSET(PidginWebViewClass, changed),
1387 NULL, NULL, NULL,
1388 G_TYPE_NONE, 0);
1389 signals[HTML_APPENDED] = g_signal_new("html-appended",
1390 G_TYPE_FROM_CLASS(gobject_class),
1391 G_SIGNAL_RUN_FIRST,
1392 G_STRUCT_OFFSET(PidginWebViewClass, html_appended),
1393 NULL, NULL, NULL,
1394 G_TYPE_NONE, 1, WEBKIT_TYPE_DOM_RANGE,
1395 NULL);
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,
1400 G_TYPE_BOOLEAN, 1,
1401 PURPLE_TYPE_IMAGE);
1403 /* Class Methods */
1405 klass->toggle_format = webview_toggle_format;
1406 klass->clear_format = webview_clear_formatting;
1408 gobject_class->finalize = pidgin_webview_finalize;
1410 /* Key Bindings */
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);
1436 /* properties */
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);
1460 #ifdef USE_ENCHANT
1461 fill_spellcheck_dicts();
1462 #endif
1465 static void
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 *****************************************************************************/
1512 char *
1513 pidgin_webview_quote_js_string(const char *text)
1515 GString *str = g_string_new("\"");
1516 const char *cur = text;
1518 while (cur && *cur) {
1519 switch (*cur) {
1520 case '\\':
1521 g_string_append(str, "\\\\");
1522 break;
1523 case '\"':
1524 g_string_append(str, "\\\"");
1525 break;
1526 case '\r':
1527 g_string_append(str, "<br/>");
1528 break;
1529 case '\n':
1530 break;
1531 default:
1532 g_string_append_c(str, *cur);
1534 cur++;
1537 g_string_append_c(str, '"');
1539 return g_string_free(str, FALSE);
1542 void
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);
1556 void
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,
1562 "file:///");
1565 void
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();"
1577 "r.selectNode(n);"
1578 "r.insertNode(f);"
1579 "n.parentNode.removeChild(n);"
1580 "s.removeAllRanges();"
1581 "s.addRange(r);");
1584 void
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);
1598 void
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);
1606 priv->vadj = vadj;
1609 void
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);
1621 if (smooth) {
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);
1624 } else {
1625 priv->scroll_time = NULL;
1626 priv->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, priv, NULL);
1630 void
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;
1641 gboolean
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;
1652 void
1653 pidgin_webview_page_up(PidginWebView *webview)
1655 PidginWebViewPrivate *priv;
1656 GtkAdjustment *vadj;
1657 gdouble scroll_val;
1659 g_return_if_fail(webview != NULL);
1661 priv = pidgin_webview_get_instance_private(webview);
1662 vadj = priv->vadj;
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);
1669 void
1670 pidgin_webview_page_down(PidginWebView *webview)
1672 PidginWebViewPrivate *priv;
1673 GtkAdjustment *vadj;
1674 gdouble scroll_val;
1675 gdouble page_size;
1677 g_return_if_fail(webview != NULL);
1679 priv = pidgin_webview_get_instance_private(webview);
1680 vadj = priv->vadj;
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);
1688 void
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. */
1733 if (size != 3)
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"));
1743 } else {
1744 pidgin_webview_toggle_backcolor(webview, "");
1747 if (flags & PURPLE_CONNECTION_FLAG_FORMATTING_WBFO)
1748 pidgin_webview_set_whole_buffer_formatting_only(webview, TRUE);
1749 else
1750 pidgin_webview_set_whole_buffer_formatting_only(webview, FALSE);
1751 } else {
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;
1761 else
1762 buttons &= ~PIDGIN_WEBVIEW_CUSTOM_SMILEY;
1764 pidgin_webview_set_format_functions(webview, buttons);
1767 void
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);
1779 void
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;
1790 void
1791 pidgin_webview_set_format_functions(PidginWebView *webview, PidginWebViewButtons buttons)
1793 PidginWebViewPrivate *priv;
1794 GObject *object;
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);
1805 void
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);
1817 gboolean
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))) {
1831 if (activate) {
1832 return FALSE;
1834 klass->protocols = g_list_remove(klass->protocols, proto);
1835 g_free(proto->name);
1836 g_free(proto);
1837 return TRUE;
1838 } else if (!activate) {
1839 return FALSE;
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);
1849 return TRUE;
1852 gchar *
1853 pidgin_webview_get_head_html(PidginWebView *webview)
1855 WebKitDOMDocument *doc;
1856 WebKitDOMHTMLHeadElement *head;
1857 gchar *html;
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));
1865 return html;
1868 static gchar *
1869 pidgin_webview_strip_smileys(const gchar *text)
1871 return g_regex_replace(smileys_re, text, -1, 0, "\\1", 0, NULL);
1874 gchar *
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);
1887 g_free(html);
1889 return stripped;
1892 gchar *
1893 pidgin_webview_get_body_text(PidginWebView *webview)
1895 WebKitDOMDocument *doc;
1896 WebKitDOMHTMLElement *body;
1897 gchar *text;
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);
1905 return text;
1908 gchar *
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);
1924 if (range)
1925 return webkit_dom_range_get_text(range);
1926 else
1927 return NULL;
1930 static gchar *
1931 pidgin_webview_strip_empty_html(const gchar *text)
1933 return g_regex_replace(empty_html_re, text, -1, 0, "", 0, NULL);
1936 gboolean
1937 pidgin_webview_is_empty(PidginWebView *webview)
1939 gchar *html, *tmp;
1940 gboolean is_empty;
1942 g_return_val_if_fail(webview != NULL, TRUE);
1944 html = pidgin_webview_get_body_html(webview);
1945 tmp = purple_strreplace(html, "&nbsp;", " ");
1946 g_free(html);
1947 html = tmp;
1949 tmp = pidgin_webview_strip_empty_html(html);
1950 g_free(html);
1951 html = tmp;
1953 g_strstrip(html);
1954 is_empty = (html[0] == '\0');
1955 g_free(html);
1957 return is_empty;
1960 void
1961 pidgin_webview_get_caret(PidginWebView *webview, WebKitDOMNode **container_ret,
1962 glong *pos_ret)
1964 WebKitDOMDocument *dom;
1965 WebKitDOMDOMWindow *win;
1966 WebKitDOMDOMSelection *sel;
1967 WebKitDOMRange *range = NULL;
1968 WebKitDOMNode *start_container, *end_container;
1969 glong start, end;
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);
1979 if (range) {
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);
1985 if (start == end &&
1986 webkit_dom_node_is_same_node(start_container, end_container)) {
1988 *container_ret = start_container;
1989 *pos_ret = start;
1990 return;
1994 *container_ret = NULL;
1995 *pos_ret = -1;
1998 void
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;
2025 void
2026 pidgin_webview_get_current_format(PidginWebView *webview, gboolean *bold,
2027 gboolean *italic, gboolean *underline,
2028 gboolean *strike)
2030 WebKitDOMDocument *dom;
2032 g_return_if_fail(webview != NULL);
2034 dom = webkit_web_view_get_dom_document(WEBKIT_WEB_VIEW(webview));
2036 if (bold)
2037 *bold = webkit_dom_document_query_command_state(dom, "bold");
2038 if (italic)
2039 *italic = webkit_dom_document_query_command_state(dom, "italic");
2040 if (underline)
2041 *underline = webkit_dom_document_query_command_state(dom, "underline");
2042 if (strike)
2043 *strike = webkit_dom_document_query_command_state(dom, "strikethrough");
2046 char *
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");
2057 char *
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");
2068 char *
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");
2079 gint
2080 pidgin_webview_get_current_fontsize(PidginWebView *webview)
2082 WebKitDOMDocument *dom;
2083 gchar *text;
2084 gint size;
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");
2090 size = atoi(text);
2091 g_free(text);
2093 return size;
2096 void
2097 pidgin_webview_clear_formatting(PidginWebView *webview)
2099 GObject *object;
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);
2108 void
2109 pidgin_webview_toggle_bold(PidginWebView *webview)
2111 g_return_if_fail(webview != NULL);
2112 emit_format_signal(webview, PIDGIN_WEBVIEW_BOLD);
2115 void
2116 pidgin_webview_toggle_italic(PidginWebView *webview)
2118 g_return_if_fail(webview != NULL);
2119 emit_format_signal(webview, PIDGIN_WEBVIEW_ITALIC);
2122 void
2123 pidgin_webview_toggle_underline(PidginWebView *webview)
2125 g_return_if_fail(webview != NULL);
2126 emit_format_signal(webview, PIDGIN_WEBVIEW_UNDERLINE);
2129 void
2130 pidgin_webview_toggle_strike(PidginWebView *webview)
2132 g_return_if_fail(webview != NULL);
2133 emit_format_signal(webview, PIDGIN_WEBVIEW_STRIKE);
2136 gboolean
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);
2144 return FALSE;
2147 gboolean
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);
2155 return FALSE;
2158 gboolean
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);
2166 return FALSE;
2169 void
2170 pidgin_webview_font_set_size(PidginWebView *webview, gint size)
2172 char *tmp;
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);
2179 g_free(tmp);
2182 void
2183 pidgin_webview_font_shrink(PidginWebView *webview)
2185 g_return_if_fail(webview != NULL);
2186 emit_format_signal(webview, PIDGIN_WEBVIEW_SHRINK);
2189 void
2190 pidgin_webview_font_grow(PidginWebView *webview)
2192 g_return_if_fail(webview != NULL);
2193 emit_format_signal(webview, PIDGIN_WEBVIEW_GROW);
2196 void
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;
2212 void
2213 pidgin_webview_insert_link(PidginWebView *webview, const char *url, const char *desc)
2215 PidginWebViewPrivate *priv;
2216 WebKitDOMDocument *dom;
2217 char *link;
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;
2228 g_free(link);
2231 void
2232 pidgin_webview_insert_image(PidginWebView *webview, PurpleImage *image)
2234 PidginWebViewPrivate *priv;
2235 WebKitDOMDocument *dom;
2236 char *img;
2237 guint id;
2238 gboolean cancel;
2240 g_return_if_fail(webview != NULL);
2242 g_signal_emit(webview, signals[INSERT_IMAGE], 0, image, &cancel);
2243 if (cancel)
2244 return;
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
2250 "%u'/>", id);
2252 priv->edit.block_changed = TRUE;
2253 webkit_dom_document_exec_command(dom, "insertHTML", FALSE, img);
2254 priv->edit.block_changed = FALSE;
2255 g_free(img);
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);
2272 gint
2273 pidgin_webview_get_DOM_height(PidginWebView *webview)
2275 gchar *value;
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);
2284 gint
2285 pidgin_webview_get_font_size(PidginWebView *webview)
2287 gchar *value;
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);
2296 void
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);
2307 GtkWidget *
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);
2318 void
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));
2331 void
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));
2344 void
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);
2357 void
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)
2366 return;
2368 pidgin_webviewtoolbar_switch_active_conversation(priv->toolbar, conv);