pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Remove a test that doesn't make sense
2019-10-08, Gary Kramlich
ad06872023fb
Remove a test that doesn't make sense
/* pidgin
*
* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include
"internal.h"
#include
"debug.h"
#include
"glibcompat.h"
#include
"image-store.h"
#include
"pidgin.h"
#include
"pidginstock.h"
#include
<gdk/gdkkeysyms.h>
#ifdef USE_ENCHANT
#include
<enchant.h>
#endif
#include
"gtkutils.h"
#include
"gtksmiley-manager.h"
#include
"gtkwebview.h"
#include
"gtkwebviewtoolbar.h"
#include
"gtkinternal.h"
#include
"gtk3compat.h"
#define MAX_FONT_SIZE 7
#define MAX_SCROLL_TIME 0.4
/* seconds */
#define SCROLL_DELAY 33
/* milliseconds */
#define PIDGIN_WEBVIEW_MAX_PROCESS_TIME 100000
/* microseconds */
enum
{
LOAD_HTML
,
LOAD_JS
};
enum
{
BUTTONS_UPDATE
,
TOGGLE_FORMAT
,
CLEAR_FORMAT
,
UPDATE_FORMAT
,
CHANGED
,
HTML_APPENDED
,
INSERT_IMAGE
,
LAST_SIGNAL
};
static
guint
signals
[
LAST_SIGNAL
]
=
{
0
};
/******************************************************************************
* Structs
*****************************************************************************/
typedef
struct
{
WebKitWebInspector
*
inspector
;
WebKitDOMNode
*
node
;
}
PidginWebViewInspectData
;
typedef
struct
{
WebKitWebView
*
webview
;
gunichar
ch
;
}
PidginWebViewInsertData
;
typedef
struct
{
const
char
*
label
;
gunichar
ch
;
}
GtkUnicodeMenuEntry
;
typedef
struct
{
char
*
name
;
int
length
;
gboolean
(
*
activate
)(
PidginWebView
*
webview
,
const
char
*
uri
);
gboolean
(
*
context_menu
)(
PidginWebView
*
webview
,
WebKitDOMHTMLAnchorElement
*
link
,
GtkWidget
*
menu
);
}
PidginWebViewProtocol
;
typedef
struct
_PidginWebViewPrivate
{
/* Processing queues */
gboolean
is_loading
;
GQueue
*
load_queue
;
guint
loader
;
/* Scroll adjustments */
GtkAdjustment
*
vadj
;
gboolean
autoscroll
;
guint
scroll_src
;
GTimer
*
scroll_time
;
/* Format options */
PidginWebViewButtons
format_functions
;
PidginWebViewToolbar
*
toolbar
;
struct
{
gboolean
wbfo
:
1
;
/* Whole buffer formatting only. */
gboolean
block_changed
:
1
;
}
edit
;
/* WebKit inspector */
WebKitWebView
*
inspector_view
;
GtkWindow
*
inspector_win
;
/* helper scripts */
gboolean
refresh_spell_installed
;
}
PidginWebViewPrivate
;
/******************************************************************************
* Globals
*****************************************************************************/
G_DEFINE_TYPE_WITH_PRIVATE
(
PidginWebView
,
pidgin_webview
,
webkit_web_view_get_type
());
static
GRegex
*
smileys_re
=
NULL
;
static
GRegex
*
empty_html_re
=
NULL
;
/* Resources cache.
*
* It's global, because gtkwebkit calls "resource-load-finished" only once
* for each static resource.
*/
static
GHashTable
*
globally_loaded_images
=
NULL
;
guint
globally_loaded_images_refcnt
=
0
;
static
GList
*
spellcheck_languages
=
NULL
;
/******************************************************************************
* Helpers
*****************************************************************************/
static
void
webview_resource_loading
(
WebKitWebView
*
webview
,
WebKitWebFrame
*
frame
,
WebKitWebResource
*
resource
,
WebKitNetworkRequest
*
request
,
WebKitNetworkResponse
*
response
,
gpointer
user_data
)
{
const
gchar
*
uri
;
PurpleImage
*
img
=
NULL
;
const
gchar
*
path
;
uri
=
webkit_network_request_get_uri
(
request
);
if
((
img
=
purple_image_store_get_from_uri
(
uri
))
!=
NULL
)
{
/* noop */
}
else
if
(
purple_str_has_prefix
(
uri
,
PURPLE_IMAGE_STORE_STOCK_PROTOCOL
))
{
gchar
*
p_uri
,
*
found
;
const
gchar
*
domain
,
*
stock_name
;
uri
+=
sizeof
(
PURPLE_IMAGE_STORE_STOCK_PROTOCOL
)
-
1
;
p_uri
=
g_strdup
(
uri
);
found
=
strchr
(
p_uri
,
'/'
);
if
(
!
found
)
{
purple_debug_warning
(
"webview"
,
"Invalid purple stock "
"image uri: %s"
,
uri
);
g_free
(
p_uri
);
return
;
}
found
[
0
]
=
'\0'
;
domain
=
p_uri
;
stock_name
=
found
+
1
;
if
(
g_strcmp0
(
domain
,
"e2ee"
)
==
0
)
{
img
=
_pidgin_e2ee_stock_icon_get
(
stock_name
);
if
(
!
img
)
{
g_free
(
p_uri
);
return
;
}
}
else
{
purple_debug_warning
(
"webview"
,
"Invalid purple stock "
"image domain: %s"
,
domain
);
g_free
(
p_uri
);
return
;
}
}
else
return
;
if
(
img
!=
NULL
)
{
/* At the time of this comment, purple_image_get_path()
* always returns something, whether it's the actual
* path or a unique identifier, such as derived from a
* hash. That API will probably be reviewed after which
* this code can probably be simplified.
*/
gchar
*
uri
=
NULL
;
path
=
purple_image_get_path
(
img
);
if
(
path
)
{
uri
=
g_filename_to_uri
(
path
,
NULL
,
NULL
);
}
if
(
uri
!=
NULL
)
{
webkit_network_request_set_uri
(
request
,
uri
);
g_free
(
uri
);
}
else
{
gchar
*
b64
,
*
src
;
const
gchar
*
type
;
b64
=
g_base64_encode
(
purple_image_get_data
(
img
),
purple_image_get_data_size
(
img
));
type
=
purple_image_get_mimetype
(
img
);
src
=
g_strdup_printf
(
"data:%s;base64,%s"
,
type
,
b64
);
g_free
(
b64
);
webkit_network_request_set_uri
(
request
,
src
);
g_free
(
src
);
}
}
}
static
void
webview_resource_loaded
(
WebKitWebView
*
web_view
,
WebKitWebFrame
*
web_frame
,
WebKitWebResource
*
web_resource
,
gpointer
user_data
)
{
const
gchar
*
uri
;
GString
*
data
;
PurpleImage
*
image
=
NULL
;
if
(
!
purple_str_has_caseprefix
(
webkit_web_resource_get_mime_type
(
web_resource
),
"image/"
))
{
return
;
}
uri
=
webkit_web_resource_get_uri
(
web_resource
);
if
(
g_hash_table_lookup
(
globally_loaded_images
,
uri
))
return
;
data
=
webkit_web_resource_get_data
(
web_resource
);
if
(
data
->
len
==
0
)
return
;
image
=
purple_image_store_get_from_uri
(
uri
);
if
(
image
)
{
g_object_ref
(
image
);
}
else
{
image
=
purple_image_new_from_data
(
g_memdup
(
data
->
str
,
data
->
len
),
data
->
len
);
if
(
purple_str_has_prefix
(
uri
,
"file:"
))
purple_image_set_friendly_filename
(
image
,
uri
);
g_return_if_fail
(
image
!=
NULL
);
}
g_hash_table_insert
(
globally_loaded_images
,
g_strdup
(
uri
),
image
);
}
static
PurpleImage
*
webview_resource_get_loaded
(
WebKitWebView
*
web_view
,
const
gchar
*
uri
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
web_view
));
g_return_val_if_fail
(
priv
!=
NULL
,
NULL
);
return
g_hash_table_lookup
(
globally_loaded_images
,
uri
);
}
static
void
process_load_queue_element
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
int
type
;
char
*
str
;
WebKitDOMDocument
*
doc
;
WebKitDOMHTMLElement
*
body
;
WebKitDOMNode
*
start
,
*
end
;
WebKitDOMRange
*
range
;
gboolean
require_scroll
=
FALSE
;
type
=
GPOINTER_TO_INT
(
g_queue_pop_head
(
priv
->
load_queue
));
str
=
g_queue_pop_head
(
priv
->
load_queue
);
switch
(
type
)
{
case
LOAD_HTML
:
doc
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
body
=
webkit_dom_document_get_body
(
doc
);
start
=
webkit_dom_node_get_last_child
(
WEBKIT_DOM_NODE
(
body
));
if
(
priv
->
autoscroll
)
{
require_scroll
=
(
gtk_adjustment_get_value
(
priv
->
vadj
)
>=
(
gtk_adjustment_get_upper
(
priv
->
vadj
)
-
1.5
*
gtk_adjustment_get_page_size
(
priv
->
vadj
)));
}
webkit_dom_html_element_insert_adjacent_html
(
body
,
"beforeend"
,
str
,
NULL
);
range
=
webkit_dom_document_create_range
(
doc
);
if
(
start
)
{
end
=
webkit_dom_node_get_last_child
(
WEBKIT_DOM_NODE
(
body
));
webkit_dom_range_set_start_after
(
range
,
WEBKIT_DOM_NODE
(
start
),
NULL
);
webkit_dom_range_set_end_after
(
range
,
WEBKIT_DOM_NODE
(
end
),
NULL
);
}
else
{
webkit_dom_range_select_node_contents
(
range
,
WEBKIT_DOM_NODE
(
body
),
NULL
);
}
if
(
require_scroll
)
{
if
(
start
)
webkit_dom_element_scroll_into_view
(
WEBKIT_DOM_ELEMENT
(
start
),
TRUE
);
else
webkit_dom_element_scroll_into_view
(
WEBKIT_DOM_ELEMENT
(
body
),
TRUE
);
}
g_signal_emit
(
webview
,
signals
[
HTML_APPENDED
],
0
,
range
);
break
;
case
LOAD_JS
:
webkit_web_view_execute_script
(
WEBKIT_WEB_VIEW
(
webview
),
str
);
break
;
default
:
purple_debug_error
(
"webview"
,
"Got unknown loading queue type: %d
\n
"
,
type
);
break
;
}
g_free
(
str
);
}
static
gboolean
process_load_queue
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
gint64
start_time
;
if
(
priv
->
is_loading
)
{
priv
->
loader
=
0
;
return
FALSE
;
}
if
(
!
priv
->
load_queue
||
g_queue_is_empty
(
priv
->
load_queue
))
{
priv
->
loader
=
0
;
return
FALSE
;
}
start_time
=
g_get_monotonic_time
();
while
(
!
g_queue_is_empty
(
priv
->
load_queue
))
{
process_load_queue_element
(
webview
);
if
(
g_get_monotonic_time
()
-
start_time
>
PIDGIN_WEBVIEW_MAX_PROCESS_TIME
)
break
;
}
if
(
g_queue_is_empty
(
priv
->
load_queue
))
{
priv
->
loader
=
0
;
return
FALSE
;
}
return
TRUE
;
}
static
void
webview_load_started
(
WebKitWebView
*
webview
,
WebKitWebFrame
*
frame
,
gpointer
userdata
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
/* is there a better way to test for is_loading? */
priv
->
is_loading
=
TRUE
;
}
static
void
webview_load_finished
(
WebKitWebView
*
webview
,
WebKitWebFrame
*
frame
,
gpointer
userdata
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
priv
->
is_loading
=
FALSE
;
if
(
priv
->
loader
==
0
)
priv
->
loader
=
g_idle_add
((
GSourceFunc
)
process_load_queue
,
webview
);
}
static
void
webview_inspector_inspect_element
(
GtkWidget
*
item
,
PidginWebViewInspectData
*
data
)
{
webkit_web_inspector_inspect_node
(
data
->
inspector
,
data
->
node
);
}
static
void
webview_inspector_destroy
(
GtkWindow
*
window
,
PidginWebViewPrivate
*
priv
)
{
g_return_if_fail
(
priv
->
inspector_win
==
window
);
priv
->
inspector_win
=
NULL
;
priv
->
inspector_view
=
NULL
;
}
static
WebKitWebView
*
webview_inspector_create
(
WebKitWebInspector
*
inspector
,
WebKitWebView
*
webview
,
gpointer
_unused
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
if
(
priv
->
inspector_view
!=
NULL
)
return
priv
->
inspector_view
;
priv
->
inspector_win
=
GTK_WINDOW
(
gtk_window_new
(
GTK_WINDOW_TOPLEVEL
));
gtk_window_set_title
(
priv
->
inspector_win
,
_
(
"WebKit inspector"
));
gtk_window_set_default_size
(
priv
->
inspector_win
,
600
,
400
);
priv
->
inspector_view
=
WEBKIT_WEB_VIEW
(
webkit_web_view_new
());
gtk_container_add
(
GTK_CONTAINER
(
priv
->
inspector_win
),
GTK_WIDGET
(
priv
->
inspector_view
));
g_signal_connect
(
priv
->
inspector_win
,
"destroy"
,
G_CALLBACK
(
webview_inspector_destroy
),
priv
);
return
priv
->
inspector_view
;
}
static
gboolean
webview_inspector_show
(
WebKitWebInspector
*
inspector
,
GtkWidget
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
gtk_widget_show_all
(
GTK_WIDGET
(
priv
->
inspector_win
));
return
TRUE
;
}
static
PidginWebViewProtocol
*
webview_find_protocol
(
const
char
*
url
,
gboolean
reverse
)
{
PidginWebViewClass
*
klass
;
GList
*
iter
;
PidginWebViewProtocol
*
proto
=
NULL
;
gssize
length
=
reverse
?
(
gssize
)
strlen
(
url
)
:
-1
;
klass
=
g_type_class_ref
(
PIDGIN_TYPE_WEBVIEW
);
for
(
iter
=
klass
->
protocols
;
iter
;
iter
=
iter
->
next
)
{
proto
=
iter
->
data
;
if
(
g_ascii_strncasecmp
(
url
,
proto
->
name
,
reverse
?
MIN
(
length
,
proto
->
length
)
:
proto
->
length
)
==
0
)
{
g_type_class_unref
(
klass
);
return
proto
;
}
}
g_type_class_unref
(
klass
);
return
NULL
;
}
static
gboolean
webview_navigation_decision
(
WebKitWebView
*
webview
,
WebKitWebFrame
*
frame
,
WebKitNetworkRequest
*
request
,
WebKitWebNavigationAction
*
navigation_action
,
WebKitWebPolicyDecision
*
policy_decision
,
gpointer
userdata
)
{
const
gchar
*
uri
;
WebKitWebNavigationReason
reason
;
uri
=
webkit_network_request_get_uri
(
request
);
reason
=
webkit_web_navigation_action_get_reason
(
navigation_action
);
if
(
reason
==
WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED
)
{
PidginWebViewProtocol
*
proto
=
webview_find_protocol
(
uri
,
FALSE
);
if
(
proto
)
{
/* XXX: Do something with the return value? */
proto
->
activate
(
PIDGIN_WEBVIEW
(
webview
),
uri
);
}
webkit_web_policy_decision_ignore
(
policy_decision
);
}
else
if
(
reason
==
WEBKIT_WEB_NAVIGATION_REASON_OTHER
)
webkit_web_policy_decision_use
(
policy_decision
);
else
webkit_web_policy_decision_ignore
(
policy_decision
);
return
TRUE
;
}
static
GtkWidget
*
get_input_methods_menu
(
WebKitWebView
*
webview
)
{
GtkSettings
*
settings
;
gboolean
show
=
TRUE
;
GtkWidget
*
item
;
GtkWidget
*
menu
;
GtkIMContext
*
im
;
settings
=
webview
?
gtk_widget_get_settings
(
GTK_WIDGET
(
webview
))
:
gtk_settings_get_default
();
if
(
settings
)
g_object_get
(
settings
,
"gtk-show-input-method-menu"
,
&
show
,
NULL
);
if
(
!
show
)
return
NULL
;
item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"Input _Methods"
));
g_object_get
(
webview
,
"im-context"
,
&
im
,
NULL
);
menu
=
gtk_menu_new
();
gtk_im_multicontext_append_menuitems
(
GTK_IM_MULTICONTEXT
(
im
),
GTK_MENU_SHELL
(
menu
));
gtk_menu_item_set_submenu
(
GTK_MENU_ITEM
(
item
),
menu
);
return
item
;
}
/* Values taken from gtktextutil.c */
static
const
GtkUnicodeMenuEntry
bidi_menu_entries
[]
=
{
{
N_
(
"LRM _Left-to-right mark"
),
0x200E
},
{
N_
(
"RLM _Right-to-left mark"
),
0x200F
},
{
N_
(
"LRE Left-to-right _embedding"
),
0x202A
},
{
N_
(
"RLE Right-to-left e_mbedding"
),
0x202B
},
{
N_
(
"LRO Left-to-right _override"
),
0x202D
},
{
N_
(
"RLO Right-to-left o_verride"
),
0x202E
},
{
N_
(
"PDF _Pop directional formatting"
),
0x202C
},
{
N_
(
"ZWS _Zero width space"
),
0x200B
},
{
N_
(
"ZWJ Zero width _joiner"
),
0x200D
},
{
N_
(
"ZWNJ Zero width _non-joiner"
),
0x200C
}
};
static
void
insert_control_character_cb
(
GtkMenuItem
*
item
,
PidginWebViewInsertData
*
data
)
{
WebKitWebView
*
webview
=
data
->
webview
;
gunichar
ch
=
data
->
ch
;
PidginWebViewPrivate
*
priv
;
WebKitDOMDocument
*
dom
;
char
buf
[
6
];
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
g_unichar_to_utf8
(
ch
,
buf
);
priv
->
edit
.
block_changed
=
TRUE
;
webkit_dom_document_exec_command
(
dom
,
"insertHTML"
,
FALSE
,
buf
);
priv
->
edit
.
block_changed
=
FALSE
;
}
static
GtkWidget
*
get_unicode_menu
(
WebKitWebView
*
webview
)
{
GtkSettings
*
settings
;
gboolean
show
=
TRUE
;
GtkWidget
*
menuitem
;
GtkWidget
*
menu
;
gsize
i
;
settings
=
webview
?
gtk_widget_get_settings
(
GTK_WIDGET
(
webview
))
:
gtk_settings_get_default
();
if
(
settings
)
g_object_get
(
settings
,
"gtk-show-unicode-menu"
,
&
show
,
NULL
);
if
(
!
show
)
return
NULL
;
menuitem
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Insert Unicode Control Character"
));
menu
=
gtk_menu_new
();
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
bidi_menu_entries
);
i
++
)
{
PidginWebViewInsertData
*
data
;
GtkWidget
*
item
;
data
=
g_new0
(
PidginWebViewInsertData
,
1
);
data
->
webview
=
webview
;
data
->
ch
=
bidi_menu_entries
[
i
].
ch
;
item
=
gtk_menu_item_new_with_mnemonic
(
_
(
bidi_menu_entries
[
i
].
label
));
g_signal_connect_data
(
item
,
"activate"
,
G_CALLBACK
(
insert_control_character_cb
),
data
,
(
GClosureNotify
)
g_free
,
0
);
gtk_widget_show
(
item
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
}
gtk_menu_item_set_submenu
(
GTK_MENU_ITEM
(
menuitem
),
menu
);
return
menuitem
;
}
#ifdef USE_ENCHANT
static
void
webview_refresh_spellcheck
(
WebKitWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
static
const
gchar
jsfunc
[]
=
"var pidgin_refresh_spellcheck = function() {"
"var selection = window.getSelection();"
"var originalSelection = selection.getRangeAt(0);"
"for (var i = 0; i < 5; i++)"
"selection.modify('move', 'backward', 'line');"
"for (i = 0; i < 100; i++)"
"selection.modify('move', 'forward', 'word');"
"selection.removeAllRanges();"
"selection.addRange(originalSelection);"
"};"
;
if
(
!
priv
->
refresh_spell_installed
)
{
priv
->
refresh_spell_installed
=
TRUE
;
webkit_web_view_execute_script
(
webview
,
jsfunc
);
}
webkit_web_view_execute_script
(
webview
,
"pidgin_refresh_spellcheck()"
);
}
static
void
webview_lang_select
(
GtkMenuItem
*
item
,
const
gchar
*
lang
)
{
WebKitWebView
*
webview
=
g_object_get_data
(
G_OBJECT
(
item
),
"gtkwebview"
);
WebKitWebSettings
*
settings
;
g_return_if_fail
(
lang
!=
NULL
);
g_return_if_fail
(
webview
!=
NULL
);
settings
=
webkit_web_view_get_settings
(
webview
);
g_object_set
(
G_OBJECT
(
settings
),
"spell-checking-languages"
,
lang
,
NULL
);
webview_refresh_spellcheck
(
webview
);
}
static
GtkWidget
*
get_spelldict_menu
(
WebKitWebView
*
webview
)
{
GtkWidget
*
menuitem
;
GtkWidget
*
menu
;
GList
*
it
;
if
(
spellcheck_languages
==
NULL
)
return
NULL
;
menuitem
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Language"
));
menu
=
gtk_menu_new
();
gtk_menu_item_set_submenu
(
GTK_MENU_ITEM
(
menuitem
),
menu
);
for
(
it
=
spellcheck_languages
;
it
;
it
=
g_list_next
(
it
))
{
GtkWidget
*
item
;
const
gchar
*
lang
=
it
->
data
;
/* we could convert lang id to name here */
item
=
gtk_menu_item_new_with_label
(
lang
);
g_object_set_data
(
G_OBJECT
(
item
),
"gtkwebview"
,
webview
);
g_signal_connect
(
item
,
"activate"
,
G_CALLBACK
(
webview_lang_select
),
(
gpointer
)
lang
);
gtk_widget_show
(
item
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
}
return
menuitem
;
}
#else
static
GtkWidget
*
get_spelldict_menu
(
WebKitWebView
*
webview
)
{
return
NULL
;
}
#endif
static
void
webview_image_saved
(
GtkWidget
*
dialog
,
gint
response
,
gpointer
_unused
)
{
PurpleImage
*
image
;
gchar
*
filename
;
if
(
response
!=
GTK_RESPONSE_ACCEPT
)
{
gtk_widget_destroy
(
dialog
);
return
;
}
image
=
g_object_get_data
(
G_OBJECT
(
dialog
),
"pidgin-gtkwebview-image"
);
g_return_if_fail
(
image
!=
NULL
);
filename
=
gtk_file_chooser_get_filename
(
GTK_FILE_CHOOSER
(
dialog
));
g_return_if_fail
(
filename
!=
NULL
);
g_return_if_fail
(
filename
[
0
]
!=
'\0'
);
if
(
!
purple_image_save
(
image
,
filename
))
{
purple_debug_error
(
"gtkwebview"
,
"Failed saving image"
);
/* TODO: we should display a notification here */
}
g_free
(
filename
);
gtk_widget_destroy
(
dialog
);
}
static
void
webview_image_save
(
GtkWidget
*
item
,
WebKitDOMHTMLImageElement
*
image_node
)
{
const
gchar
*
src
;
WebKitWebView
*
webview
;
PurpleImage
*
image
;
GtkFileChooserDialog
*
dialog
;
const
gchar
*
filename
;
GtkWidget
*
parent
;
webview
=
g_object_get_data
(
G_OBJECT
(
image_node
),
"pidgin-gtkwebview"
);
g_return_if_fail
(
webview
!=
NULL
);
src
=
webkit_dom_html_image_element_get_src
(
image_node
);
/* XXX: a leak or not? */
image
=
webview_resource_get_loaded
(
webview
,
src
);
g_return_if_fail
(
image
!=
NULL
);
parent
=
gtk_widget_get_ancestor
(
item
,
GTK_TYPE_WINDOW
);
dialog
=
GTK_FILE_CHOOSER_DIALOG
(
gtk_file_chooser_dialog_new
(
_
(
"Save Image"
),
parent
?
GTK_WINDOW
(
parent
)
:
NULL
,
GTK_FILE_CHOOSER_ACTION_SAVE
,
GTK_STOCK_CANCEL
,
GTK_RESPONSE_CANCEL
,
GTK_STOCK_SAVE
,
GTK_RESPONSE_ACCEPT
,
NULL
));
gtk_file_chooser_set_do_overwrite_confirmation
(
GTK_FILE_CHOOSER
(
dialog
),
TRUE
);
gtk_dialog_set_default_response
(
GTK_DIALOG
(
dialog
),
GTK_RESPONSE_ACCEPT
);
filename
=
purple_image_get_friendly_filename
(
image
);
g_warn_if_fail
(
filename
!=
NULL
);
gtk_file_chooser_set_current_name
(
GTK_FILE_CHOOSER
(
dialog
),
filename
);
g_signal_connect
(
G_OBJECT
(
dialog
),
"response"
,
G_CALLBACK
(
webview_image_saved
),
NULL
);
g_object_ref
(
image
);
g_object_set_data_full
(
G_OBJECT
(
dialog
),
"pidgin-gtkwebview-image"
,
image
,
g_object_unref
);
gtk_widget_show
(
GTK_WIDGET
(
dialog
));
}
static
void
webview_image_add_smiley
(
GtkWidget
*
item
,
WebKitDOMHTMLImageElement
*
image_node
)
{
const
gchar
*
src
;
WebKitWebView
*
webview
;
PurpleImage
*
image
;
src
=
webkit_dom_html_image_element_get_src
(
image_node
);
webview
=
g_object_get_data
(
G_OBJECT
(
image_node
),
"pidgin-gtkwebview"
);
g_return_if_fail
(
webview
!=
NULL
);
image
=
webview_resource_get_loaded
(
webview
,
src
);
g_return_if_fail
(
image
!=
NULL
);
pidgin_smiley_manager_add
(
image
,
webkit_dom_html_image_element_get_alt
(
image_node
));
}
static
void
do_popup_menu
(
WebKitWebView
*
webview
,
GdkEvent
*
event
,
int
context
,
WebKitDOMNode
*
node
,
const
char
*
uri
)
{
GtkWidget
*
menu
;
GtkWidget
*
cut
,
*
copy
,
*
paste
,
*
delete
,
*
select
;
gboolean
show_clipboard
=
TRUE
;
WebKitDOMHTMLImageElement
*
image_node
=
NULL
;
menu
=
gtk_menu_new
();
g_signal_connect
(
menu
,
"selection-done"
,
G_CALLBACK
(
gtk_widget_destroy
),
NULL
);
if
(
context
&
WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
)
{
PidginWebViewProtocol
*
proto
=
NULL
;
GList
*
children
;
WebKitDOMNode
*
link_node
=
node
;
while
(
link_node
&&
!
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT
(
link_node
))
{
link_node
=
webkit_dom_node_get_parent_node
(
link_node
);
}
if
(
uri
&&
link_node
)
proto
=
webview_find_protocol
(
uri
,
FALSE
);
if
(
proto
&&
proto
->
context_menu
)
{
proto
->
context_menu
(
PIDGIN_WEBVIEW
(
webview
),
WEBKIT_DOM_HTML_ANCHOR_ELEMENT
(
link_node
),
menu
);
}
children
=
gtk_container_get_children
(
GTK_CONTAINER
(
menu
));
if
(
!
children
)
{
GtkWidget
*
item
=
gtk_menu_item_new_with_label
(
_
(
"No actions available"
));
gtk_widget_show
(
item
);
gtk_widget_set_sensitive
(
item
,
FALSE
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
item
);
}
else
{
g_list_free
(
children
);
}
gtk_widget_show_all
(
menu
);
show_clipboard
=
FALSE
;
}
if
(
context
&
WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE
)
{
WebKitDOMNode
*
_image_node
=
node
;
while
(
_image_node
&&
!
WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT
(
_image_node
))
{
_image_node
=
webkit_dom_node_get_parent_node
(
_image_node
);
}
if
(
_image_node
)
image_node
=
WEBKIT_DOM_HTML_IMAGE_ELEMENT
(
_image_node
);
/* don't do it on our theme smileys */
}
if
(
image_node
&&
webkit_dom_html_image_element_get_complete
(
image_node
))
{
GtkWidget
*
menu_item
;
int
width
,
height
;
width
=
webkit_dom_html_image_element_get_width
(
image_node
);
height
=
webkit_dom_html_image_element_get_height
(
image_node
);
/* XXX */
g_object_set_data
(
G_OBJECT
(
image_node
),
"pidgin-gtkwebview"
,
webview
);
menu_item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Save Image..."
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
menu_item
),
gtk_image_new_from_stock
(
GTK_STOCK_SAVE
,
GTK_ICON_SIZE_MENU
));
g_signal_connect_object
(
G_OBJECT
(
menu_item
),
"activate"
,
G_CALLBACK
(
webview_image_save
),
image_node
,
0
);
gtk_widget_show
(
menu_item
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menu_item
);
/* TODO: check, if it's not *our* custom smiley (use css) */
if
(
width
<=
96
&&
height
<=
96
)
{
menu_item
=
gtk_image_menu_item_new_with_mnemonic
(
_
(
"_Add Custom Smiley..."
));
gtk_image_menu_item_set_image
(
GTK_IMAGE_MENU_ITEM
(
menu_item
),
gtk_image_new_from_stock
(
GTK_STOCK_ADD
,
GTK_ICON_SIZE_MENU
));
g_signal_connect_object
(
G_OBJECT
(
menu_item
),
"activate"
,
G_CALLBACK
(
webview_image_add_smiley
),
image_node
,
0
);
gtk_widget_show
(
menu_item
);
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
menu_item
);
}
show_clipboard
=
FALSE
;
}
if
(
show_clipboard
)
{
/* Using connect_swapped means we don't need any wrapper functions */
cut
=
pidgin_new_menu_item
(
menu
,
_
(
"Cu_t"
),
GTK_STOCK_CUT
,
NULL
,
NULL
);
g_signal_connect_swapped
(
G_OBJECT
(
cut
),
"activate"
,
G_CALLBACK
(
webkit_web_view_cut_clipboard
),
webview
);
copy
=
pidgin_new_menu_item
(
menu
,
_
(
"_Copy"
),
GTK_STOCK_COPY
,
NULL
,
NULL
);
g_signal_connect_swapped
(
G_OBJECT
(
copy
),
"activate"
,
G_CALLBACK
(
webkit_web_view_copy_clipboard
),
webview
);
paste
=
pidgin_new_menu_item
(
menu
,
_
(
"_Paste"
),
GTK_STOCK_PASTE
,
NULL
,
NULL
);
g_signal_connect_swapped
(
G_OBJECT
(
paste
),
"activate"
,
G_CALLBACK
(
webkit_web_view_paste_clipboard
),
webview
);
delete
=
pidgin_new_menu_item
(
menu
,
_
(
"_Delete"
),
GTK_STOCK_DELETE
,
NULL
,
NULL
);
g_signal_connect_swapped
(
G_OBJECT
(
delete
),
"activate"
,
G_CALLBACK
(
webkit_web_view_delete_selection
),
webview
);
pidgin_separator
(
menu
);
select
=
pidgin_new_menu_item
(
menu
,
_
(
"Select _All"
),
GTK_STOCK_SELECT_ALL
,
NULL
,
NULL
);
g_signal_connect_swapped
(
G_OBJECT
(
select
),
"activate"
,
G_CALLBACK
(
webkit_web_view_select_all
),
webview
);
gtk_widget_set_sensitive
(
cut
,
webkit_web_view_can_cut_clipboard
(
webview
));
gtk_widget_set_sensitive
(
copy
,
webkit_web_view_can_copy_clipboard
(
webview
));
gtk_widget_set_sensitive
(
paste
,
webkit_web_view_can_paste_clipboard
(
webview
));
gtk_widget_set_sensitive
(
delete
,
webkit_web_view_can_cut_clipboard
(
webview
));
}
if
(
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/webview/inspector_enabled"
))
{
WebKitWebSettings
*
settings
;
GtkWidget
*
inspect
;
PidginWebViewInspectData
*
data
;
settings
=
webkit_web_view_get_settings
(
webview
);
g_object_set
(
G_OBJECT
(
settings
),
"enable-developer-extras"
,
TRUE
,
NULL
);
data
=
g_new0
(
PidginWebViewInspectData
,
1
);
data
->
inspector
=
webkit_web_view_get_inspector
(
webview
);
data
->
node
=
node
;
pidgin_separator
(
menu
);
inspect
=
pidgin_new_menu_item
(
menu
,
_
(
"Inspect _Element"
),
PIDGIN_STOCK_DEBUG
,
NULL
,
NULL
);
g_signal_connect_data
(
G_OBJECT
(
inspect
),
"activate"
,
G_CALLBACK
(
webview_inspector_inspect_element
),
data
,
(
GClosureNotify
)
g_free
,
0
);
}
if
(
webkit_web_view_get_editable
(
webview
))
{
GtkWidget
*
im
=
get_input_methods_menu
(
webview
);
GtkWidget
*
unicode
=
get_unicode_menu
(
webview
);
GtkWidget
*
spelldict
=
get_spelldict_menu
(
webview
);
if
(
im
||
unicode
||
spelldict
)
pidgin_separator
(
menu
);
if
(
im
)
{
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
im
);
gtk_widget_show
(
im
);
}
if
(
unicode
)
{
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
unicode
);
gtk_widget_show
(
unicode
);
}
if
(
spelldict
)
{
gtk_menu_shell_append
(
GTK_MENU_SHELL
(
menu
),
spelldict
);
gtk_widget_show
(
spelldict
);
}
}
g_signal_emit_by_name
(
G_OBJECT
(
webview
),
"populate-popup"
,
menu
);
gtk_menu_attach_to_widget
(
GTK_MENU
(
menu
),
GTK_WIDGET
(
webview
),
NULL
);
gtk_menu_popup_at_pointer
(
GTK_MENU
(
menu
),
event
);
}
static
gboolean
webview_popup_menu
(
WebKitWebView
*
webview
)
{
WebKitDOMDocument
*
doc
;
WebKitDOMNode
*
node
=
NULL
;
int
context
=
WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT
;
char
*
uri
=
NULL
;
doc
=
webkit_web_view_get_dom_document
(
webview
);
/* it's unlikely, at least for webkit 1.x */
if
(
WEBKIT_DOM_IS_HTML_DOCUMENT
(
doc
))
{
WebKitDOMElement
*
active
;
WebKitDOMElement
*
link
;
active
=
webkit_dom_html_document_get_active_element
(
WEBKIT_DOM_HTML_DOCUMENT
(
doc
));
link
=
active
;
while
(
link
&&
!
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT
(
link
))
link
=
webkit_dom_node_get_parent_element
(
WEBKIT_DOM_NODE
(
link
));
if
(
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT
(
link
))
{
context
|=
WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK
;
uri
=
webkit_dom_html_anchor_element_get_href
(
WEBKIT_DOM_HTML_ANCHOR_ELEMENT
(
link
));
}
}
do_popup_menu
(
webview
,
NULL
,
context
,
node
,
uri
);
g_free
(
uri
);
return
TRUE
;
}
static
gboolean
webview_button_pressed
(
WebKitWebView
*
webview
,
GdkEventButton
*
event
)
{
if
(
gdk_event_triggers_context_menu
((
GdkEvent
*
)
event
))
{
WebKitHitTestResult
*
hit
;
int
context
;
WebKitDOMNode
*
node
;
char
*
uri
;
hit
=
webkit_web_view_get_hit_test_result
(
webview
,
event
);
g_object_get
(
G_OBJECT
(
hit
),
"context"
,
&
context
,
"inner-node"
,
&
node
,
"link-uri"
,
&
uri
,
NULL
);
do_popup_menu
(
webview
,
(
GdkEvent
*
)
event
,
context
,
node
,
uri
);
g_free
(
uri
);
g_object_unref
(
hit
);
return
TRUE
;
}
return
FALSE
;
}
/*
* Smoothly scroll a WebView.
*
* @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
*/
static
gboolean
smooth_scroll_cb
(
gpointer
data
)
{
PidginWebViewPrivate
*
priv
=
data
;
GtkAdjustment
*
adj
;
gdouble
max_val
;
gdouble
scroll_val
;
g_return_val_if_fail
(
priv
->
scroll_time
!=
NULL
,
FALSE
);
adj
=
priv
->
vadj
;
max_val
=
gtk_adjustment_get_upper
(
adj
)
-
gtk_adjustment_get_page_size
(
adj
);
scroll_val
=
gtk_adjustment_get_value
(
adj
)
+
((
max_val
-
gtk_adjustment_get_value
(
adj
))
/
3
);
if
(
g_timer_elapsed
(
priv
->
scroll_time
,
NULL
)
>
MAX_SCROLL_TIME
||
scroll_val
>=
max_val
)
{
/* time's up. jump to the end and kill the timer */
gtk_adjustment_set_value
(
adj
,
max_val
);
g_timer_destroy
(
priv
->
scroll_time
);
priv
->
scroll_time
=
NULL
;
priv
->
scroll_src
=
0
;
return
FALSE
;
}
/* scroll by 1/3rd the remaining distance */
gtk_adjustment_set_value
(
adj
,
scroll_val
);
return
TRUE
;
}
static
gboolean
scroll_idle_cb
(
gpointer
data
)
{
PidginWebViewPrivate
*
priv
=
data
;
GtkAdjustment
*
adj
=
priv
->
vadj
;
gdouble
max_val
;
if
(
adj
)
{
max_val
=
gtk_adjustment_get_upper
(
adj
)
-
gtk_adjustment_get_page_size
(
adj
);
gtk_adjustment_set_value
(
adj
,
max_val
);
}
priv
->
scroll_src
=
0
;
return
FALSE
;
}
static
void
emit_format_signal
(
PidginWebView
*
webview
,
PidginWebViewButtons
buttons
)
{
g_object_ref
(
webview
);
g_signal_emit
(
webview
,
signals
[
TOGGLE_FORMAT
],
0
,
buttons
);
g_object_unref
(
webview
);
}
static
void
do_formatting
(
PidginWebView
*
webview
,
const
char
*
name
,
const
char
*
value
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
WebKitDOMDocument
*
dom
;
WebKitDOMDOMWindow
*
win
;
WebKitDOMDOMSelection
*
sel
=
NULL
;
WebKitDOMRange
*
range
=
NULL
;
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
if
(
priv
->
edit
.
wbfo
)
{
win
=
webkit_dom_document_get_default_view
(
dom
);
sel
=
webkit_dom_dom_window_get_selection
(
win
);
if
(
webkit_dom_dom_selection_get_range_count
(
sel
)
>
0
)
range
=
webkit_dom_dom_selection_get_range_at
(
sel
,
0
,
NULL
);
webkit_web_view_select_all
(
WEBKIT_WEB_VIEW
(
webview
));
}
priv
->
edit
.
block_changed
=
TRUE
;
webkit_dom_document_exec_command
(
dom
,
(
gchar
*
)
name
,
FALSE
,
(
gchar
*
)
value
);
priv
->
edit
.
block_changed
=
FALSE
;
if
(
priv
->
edit
.
wbfo
)
{
if
(
range
)
{
webkit_dom_dom_selection_remove_all_ranges
(
sel
);
webkit_dom_dom_selection_add_range
(
sel
,
range
);
}
else
{
webkit_dom_dom_selection_collapse_to_end
(
sel
,
NULL
);
}
}
}
static
void
webview_font_shrink
(
PidginWebView
*
webview
)
{
gint
fontsize
;
char
*
tmp
;
fontsize
=
pidgin_webview_get_current_fontsize
(
webview
);
fontsize
=
MAX
(
fontsize
-
1
,
1
);
tmp
=
g_strdup_printf
(
"%d"
,
fontsize
);
do_formatting
(
webview
,
"fontSize"
,
tmp
);
g_free
(
tmp
);
}
static
void
webview_font_grow
(
PidginWebView
*
webview
)
{
gint
fontsize
;
char
*
tmp
;
fontsize
=
pidgin_webview_get_current_fontsize
(
webview
);
fontsize
=
MIN
(
fontsize
+
1
,
MAX_FONT_SIZE
);
tmp
=
g_strdup_printf
(
"%d"
,
fontsize
);
do_formatting
(
webview
,
"fontSize"
,
tmp
);
g_free
(
tmp
);
}
static
void
webview_clear_formatting
(
PidginWebView
*
webview
)
{
if
(
!
webkit_web_view_get_editable
(
WEBKIT_WEB_VIEW
(
webview
)))
return
;
do_formatting
(
webview
,
"removeFormat"
,
""
);
do_formatting
(
webview
,
"unlink"
,
""
);
do_formatting
(
webview
,
"backColor"
,
"inherit"
);
}
static
void
webview_toggle_format
(
PidginWebView
*
webview
,
PidginWebViewButtons
buttons
)
{
/* since this function is the handler for the formatting keystrokes,
we need to check here that the formatting attempted is permitted */
buttons
&=
pidgin_webview_get_format_functions
(
webview
);
switch
(
buttons
)
{
case
PIDGIN_WEBVIEW_BOLD
:
do_formatting
(
webview
,
"bold"
,
""
);
break
;
case
PIDGIN_WEBVIEW_ITALIC
:
do_formatting
(
webview
,
"italic"
,
""
);
break
;
case
PIDGIN_WEBVIEW_UNDERLINE
:
do_formatting
(
webview
,
"underline"
,
""
);
break
;
case
PIDGIN_WEBVIEW_STRIKE
:
do_formatting
(
webview
,
"strikethrough"
,
""
);
break
;
case
PIDGIN_WEBVIEW_SHRINK
:
webview_font_shrink
(
webview
);
break
;
case
PIDGIN_WEBVIEW_GROW
:
webview_font_grow
(
webview
);
break
;
default
:
break
;
}
}
static
void
editable_input_cb
(
PidginWebView
*
webview
,
gpointer
data
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
if
(
!
priv
->
edit
.
block_changed
&&
gtk_widget_is_sensitive
(
GTK_WIDGET
(
webview
)))
g_signal_emit
(
webview
,
signals
[
CHANGED
],
0
);
}
/******************************************************************************
* GObject Stuff
*****************************************************************************/
GtkWidget
*
pidgin_webview_new
(
gboolean
editable
)
{
GtkWidget
*
result
;
WebKitWebView
*
webview
;
WebKitWebSettings
*
settings
;
result
=
g_object_new
(
pidgin_webview_get_type
(),
NULL
);
webview
=
WEBKIT_WEB_VIEW
(
result
);
settings
=
webkit_web_view_get_settings
(
webview
);
g_object_set
(
G_OBJECT
(
settings
),
"default-encoding"
,
"utf-8"
,
NULL
);
#ifdef _WIN32
/* XXX: win32 WebKitGTK replaces backslash with yen sign for
* "sans-serif" font. We should figure out, how to disable this
* behavior, but for now I will just apply this simple hack (using other
* font family).
*/
g_object_set
(
G_OBJECT
(
settings
),
"default-font-family"
,
"Verdana"
,
NULL
);
#endif
webkit_web_view_set_settings
(
webview
,
settings
);
if
(
editable
)
{
webkit_web_view_set_editable
(
WEBKIT_WEB_VIEW
(
webview
),
editable
);
g_signal_connect
(
G_OBJECT
(
webview
),
"user-changed-contents"
,
G_CALLBACK
(
editable_input_cb
),
NULL
);
}
return
result
;
}
static
void
pidgin_webview_finalize
(
GObject
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
PIDGIN_WEBVIEW
(
webview
));
if
(
priv
->
inspector_win
!=
NULL
)
gtk_widget_destroy
(
GTK_WIDGET
(
priv
->
inspector_win
));
if
(
priv
->
loader
)
g_source_remove
(
priv
->
loader
);
while
(
!
g_queue_is_empty
(
priv
->
load_queue
))
{
g_queue_pop_head
(
priv
->
load_queue
);
g_free
(
g_queue_pop_head
(
priv
->
load_queue
));
}
g_queue_free
(
priv
->
load_queue
);
if
(
--
globally_loaded_images_refcnt
==
0
)
{
g_assert
(
globally_loaded_images
!=
NULL
);
g_hash_table_destroy
(
globally_loaded_images
);
globally_loaded_images
=
NULL
;
}
G_OBJECT_CLASS
(
pidgin_webview_parent_class
)
->
finalize
(
webview
);
}
enum
{
PROP_0
,
PROP_EXPAND
};
static
void
pidgin_webview_set_property
(
GObject
*
object
,
guint
prop_id
,
const
GValue
*
value
,
GParamSpec
*
pspec
)
{
g_return_if_fail
(
PIDGIN_IS_WEBVIEW
(
object
));
switch
(
prop_id
)
{
case
PROP_EXPAND
:
purple_debug_misc
(
"webview"
,
"Ignored expand property (set to %d)"
,
g_value_get_boolean
(
value
));
break
;
default
:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
object
,
prop_id
,
pspec
);
}
}
static
void
pidgin_webview_get_property
(
GObject
*
object
,
guint
prop_id
,
GValue
*
value
,
GParamSpec
*
pspec
)
{
g_return_if_fail
(
PIDGIN_IS_WEBVIEW
(
object
));
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
object
,
prop_id
,
pspec
);
}
#ifdef USE_ENCHANT
static
void
fill_spellcheck_dicts_cb
(
const
gchar
*
lang_tag
,
const
gchar
*
provider_name
,
const
gchar
*
provider_desc
,
const
gchar
*
provider_file
,
void
*
_unused
)
{
gboolean
is_dialect
;
GList
*
it
;
/* It's not super efficient, but even with large number of installed
* dictionaries (100?) it won't hurt us. */
is_dialect
=
(
strchr
(
lang_tag
,
'_'
)
!=
NULL
);
if
(
is_dialect
)
{
for
(
it
=
spellcheck_languages
;
it
;
it
=
g_list_next
(
it
))
{
gchar
*
it_lang
=
it
->
data
;
if
(
purple_str_has_prefix
(
lang_tag
,
it_lang
))
return
;
}
}
else
{
GList
*
next
;
for
(
it
=
spellcheck_languages
;
it
;
it
=
next
)
{
gchar
*
it_lang
=
it
->
data
;
next
=
g_list_next
(
it
);
if
(
!
purple_str_has_prefix
(
it_lang
,
lang_tag
))
continue
;
g_free
(
it_lang
);
spellcheck_languages
=
g_list_delete_link
(
spellcheck_languages
,
it
);
}
}
spellcheck_languages
=
g_list_prepend
(
spellcheck_languages
,
g_strdup
(
lang_tag
));
}
static
void
fill_spellcheck_dicts
(
void
)
{
EnchantBroker
*
eb
;
eb
=
enchant_broker_init
();
enchant_broker_list_dicts
(
eb
,
fill_spellcheck_dicts_cb
,
NULL
);
enchant_broker_free
(
eb
);
spellcheck_languages
=
g_list_sort
(
spellcheck_languages
,
(
GCompareFunc
)
strcmp
);
}
#endif
static
gboolean
pidgin_webview_insert_image_accu
(
GSignalInvocationHint
*
ihint
,
GValue
*
return_accu
,
const
GValue
*
handler_return
,
gpointer
_unused
)
{
gboolean
cancel
;
cancel
=
g_value_get_boolean
(
handler_return
);
if
(
!
cancel
)
return
FALSE
;
g_value_set_boolean
(
return_accu
,
TRUE
);
return
TRUE
;
}
static
void
pidgin_webview_class_init
(
PidginWebViewClass
*
klass
)
{
GObjectClass
*
gobject_class
;
GtkBindingSet
*
binding_set
;
gobject_class
=
G_OBJECT_CLASS
(
klass
);
/* Signals */
signals
[
BUTTONS_UPDATE
]
=
g_signal_new
(
"allowed-formats-updated"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_FIRST
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
buttons_update
),
NULL
,
0
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_INT
);
signals
[
TOGGLE_FORMAT
]
=
g_signal_new
(
"format-toggled"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_LAST
|
G_SIGNAL_ACTION
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
toggle_format
),
NULL
,
0
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_INT
);
signals
[
CLEAR_FORMAT
]
=
g_signal_new
(
"format-cleared"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_FIRST
|
G_SIGNAL_ACTION
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
clear_format
),
NULL
,
0
,
NULL
,
G_TYPE_NONE
,
0
);
signals
[
UPDATE_FORMAT
]
=
g_signal_new
(
"format-updated"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_FIRST
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
update_format
),
NULL
,
0
,
NULL
,
G_TYPE_NONE
,
0
);
signals
[
CHANGED
]
=
g_signal_new
(
"changed"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_FIRST
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
changed
),
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
0
);
signals
[
HTML_APPENDED
]
=
g_signal_new
(
"html-appended"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_FIRST
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
html_appended
),
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
WEBKIT_TYPE_DOM_RANGE
,
NULL
);
signals
[
INSERT_IMAGE
]
=
g_signal_new
(
"insert-image"
,
G_TYPE_FROM_CLASS
(
gobject_class
),
G_SIGNAL_RUN_LAST
,
G_STRUCT_OFFSET
(
PidginWebViewClass
,
insert_image
),
pidgin_webview_insert_image_accu
,
NULL
,
NULL
,
G_TYPE_BOOLEAN
,
1
,
PURPLE_TYPE_IMAGE
);
/* Class Methods */
klass
->
toggle_format
=
webview_toggle_format
;
klass
->
clear_format
=
webview_clear_formatting
;
gobject_class
->
finalize
=
pidgin_webview_finalize
;
/* Key Bindings */
binding_set
=
gtk_binding_set_by_class
(
pidgin_webview_parent_class
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_b
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_BOLD
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_i
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_ITALIC
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_u
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_UNDERLINE
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_plus
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_GROW
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_equal
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_GROW
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_minus
,
GDK_CONTROL_MASK
,
"format-toggled"
,
1
,
G_TYPE_INT
,
PIDGIN_WEBVIEW_SHRINK
);
binding_set
=
gtk_binding_set_by_class
(
klass
);
gtk_binding_entry_add_signal
(
binding_set
,
GDK_KEY_r
,
GDK_CONTROL_MASK
,
"format-cleared"
,
0
);
/* properties */
G_OBJECT_CLASS
(
klass
)
->
set_property
=
pidgin_webview_set_property
;
G_OBJECT_CLASS
(
klass
)
->
get_property
=
pidgin_webview_get_property
;
if
(
!
g_object_class_find_property
(
G_OBJECT_CLASS
(
klass
),
"expand"
))
{
/* webkitgtk for gtk2 doesn't seems to have this */
g_object_class_install_property
(
G_OBJECT_CLASS
(
klass
),
PROP_EXPAND
,
g_param_spec_boolean
(
"expand"
,
"Expand Both"
,
"Whether widget wants to expand in both directions"
,
FALSE
,
G_PARAM_READWRITE
|
G_PARAM_STATIC_STRINGS
));
}
purple_prefs_add_none
(
PIDGIN_PREFS_ROOT
"/webview"
);
purple_prefs_add_bool
(
PIDGIN_PREFS_ROOT
"/webview/inspector_enabled"
,
FALSE
);
g_return_if_fail
(
smileys_re
==
NULL
);
g_return_if_fail
(
empty_html_re
==
NULL
);
smileys_re
=
g_regex_new
(
"<img[^>]* class=
\"
emoticon "
"[^
\"
^>]*
\"
[^>]*alt=
\"
([^
\"
^>]+)
\"
[^>]*>"
,
G_REGEX_DOTALL
|
G_REGEX_OPTIMIZE
,
0
,
NULL
);
empty_html_re
=
g_regex_new
(
"<(?!img)[^>]*>"
,
G_REGEX_DOTALL
|
G_REGEX_OPTIMIZE
,
0
,
NULL
);
#ifdef USE_ENCHANT
fill_spellcheck_dicts
();
#endif
}
static
void
pidgin_webview_init
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
WebKitWebInspector
*
inspector
;
priv
->
load_queue
=
g_queue_new
();
g_signal_connect
(
G_OBJECT
(
webview
),
"button-press-event"
,
G_CALLBACK
(
webview_button_pressed
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"popup-menu"
,
G_CALLBACK
(
webview_popup_menu
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"navigation-policy-decision-requested"
,
G_CALLBACK
(
webview_navigation_decision
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"load-started"
,
G_CALLBACK
(
webview_load_started
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"load-finished"
,
G_CALLBACK
(
webview_load_finished
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"resource-request-starting"
,
G_CALLBACK
(
webview_resource_loading
),
NULL
);
g_signal_connect
(
G_OBJECT
(
webview
),
"resource-load-finished"
,
G_CALLBACK
(
webview_resource_loaded
),
NULL
);
inspector
=
webkit_web_view_get_inspector
(
WEBKIT_WEB_VIEW
(
webview
));
g_signal_connect
(
G_OBJECT
(
inspector
),
"inspect-web-view"
,
G_CALLBACK
(
webview_inspector_create
),
NULL
);
g_signal_connect
(
G_OBJECT
(
inspector
),
"show-window"
,
G_CALLBACK
(
webview_inspector_show
),
webview
);
if
(
globally_loaded_images_refcnt
++
==
0
)
{
g_assert
(
globally_loaded_images
==
NULL
);
globally_loaded_images
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
g_object_unref
);
}
}
/*****************************************************************************
* Public API functions
*****************************************************************************/
char
*
pidgin_webview_quote_js_string
(
const
char
*
text
)
{
GString
*
str
=
g_string_new
(
"
\"
"
);
const
char
*
cur
=
text
;
while
(
cur
&&
*
cur
)
{
switch
(
*
cur
)
{
case
'\\'
:
g_string_append
(
str
,
"
\\\\
"
);
break
;
case
'\"'
:
g_string_append
(
str
,
"
\\\"
"
);
break
;
case
'\r'
:
g_string_append
(
str
,
"<br/>"
);
break
;
case
'\n'
:
break
;
default
:
g_string_append_c
(
str
,
*
cur
);
}
cur
++
;
}
g_string_append_c
(
str
,
'"'
);
return
g_string_free
(
str
,
FALSE
);
}
void
pidgin_webview_safe_execute_script
(
PidginWebView
*
webview
,
const
char
*
script
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_queue_push_tail
(
priv
->
load_queue
,
GINT_TO_POINTER
(
LOAD_JS
));
g_queue_push_tail
(
priv
->
load_queue
,
g_strdup
(
script
));
if
(
!
priv
->
is_loading
&&
priv
->
loader
==
0
)
priv
->
loader
=
g_idle_add
((
GSourceFunc
)
process_load_queue
,
webview
);
}
void
pidgin_webview_load_html_string
(
PidginWebView
*
webview
,
const
char
*
html
)
{
g_return_if_fail
(
webview
!=
NULL
);
webkit_web_view_load_string
(
WEBKIT_WEB_VIEW
(
webview
),
html
,
NULL
,
NULL
,
"file:///"
);
}
void
pidgin_webview_load_html_string_with_selection
(
PidginWebView
*
webview
,
const
char
*
html
)
{
g_return_if_fail
(
webview
!=
NULL
);
pidgin_webview_load_html_string
(
webview
,
html
);
pidgin_webview_safe_execute_script
(
webview
,
"var s = window.getSelection();"
"var r = document.createRange();"
"var n = document.getElementById('caret');"
"r.selectNodeContents(n);"
"var f = r.extractContents();"
"r.selectNode(n);"
"r.insertNode(f);"
"n.parentNode.removeChild(n);"
"s.removeAllRanges();"
"s.addRange(r);"
);
}
void
pidgin_webview_append_html
(
PidginWebView
*
webview
,
const
char
*
html
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_queue_push_tail
(
priv
->
load_queue
,
GINT_TO_POINTER
(
LOAD_HTML
));
g_queue_push_tail
(
priv
->
load_queue
,
g_strdup
(
html
));
if
(
!
priv
->
is_loading
&&
priv
->
loader
==
0
)
priv
->
loader
=
g_idle_add
((
GSourceFunc
)
process_load_queue
,
webview
);
}
void
pidgin_webview_set_vadjustment
(
PidginWebView
*
webview
,
GtkAdjustment
*
vadj
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
priv
->
vadj
=
vadj
;
}
void
pidgin_webview_scroll_to_end
(
PidginWebView
*
webview
,
gboolean
smooth
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
if
(
priv
->
scroll_time
)
g_timer_destroy
(
priv
->
scroll_time
);
if
(
priv
->
scroll_src
)
g_source_remove
(
priv
->
scroll_src
);
if
(
smooth
)
{
priv
->
scroll_time
=
g_timer_new
();
priv
->
scroll_src
=
g_timeout_add_full
(
G_PRIORITY_LOW
,
SCROLL_DELAY
,
smooth_scroll_cb
,
priv
,
NULL
);
}
else
{
priv
->
scroll_time
=
NULL
;
priv
->
scroll_src
=
g_idle_add_full
(
G_PRIORITY_LOW
,
scroll_idle_cb
,
priv
,
NULL
);
}
}
void
pidgin_webview_set_autoscroll
(
PidginWebView
*
webview
,
gboolean
scroll
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
priv
->
autoscroll
=
scroll
;
}
gboolean
pidgin_webview_get_autoscroll
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
g_return_val_if_fail
(
webview
!=
NULL
,
FALSE
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
return
priv
->
autoscroll
;
}
void
pidgin_webview_page_up
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
GtkAdjustment
*
vadj
;
gdouble
scroll_val
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
vadj
=
priv
->
vadj
;
scroll_val
=
gtk_adjustment_get_value
(
vadj
)
-
gtk_adjustment_get_page_size
(
vadj
);
scroll_val
=
MAX
(
scroll_val
,
gtk_adjustment_get_lower
(
vadj
));
gtk_adjustment_set_value
(
vadj
,
scroll_val
);
}
void
pidgin_webview_page_down
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
GtkAdjustment
*
vadj
;
gdouble
scroll_val
;
gdouble
page_size
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
vadj
=
priv
->
vadj
;
page_size
=
gtk_adjustment_get_page_size
(
vadj
);
scroll_val
=
gtk_adjustment_get_value
(
vadj
)
+
page_size
;
scroll_val
=
MIN
(
scroll_val
,
gtk_adjustment_get_upper
(
vadj
)
-
page_size
);
gtk_adjustment_set_value
(
vadj
,
scroll_val
);
}
void
pidgin_webview_setup_entry
(
PidginWebView
*
webview
,
PurpleConnectionFlags
flags
)
{
PidginWebViewButtons
buttons
;
g_return_if_fail
(
webview
!=
NULL
);
if
(
flags
&
PURPLE_CONNECTION_FLAG_HTML
)
{
gboolean
bold
,
italic
,
underline
,
strike
;
buttons
=
PIDGIN_WEBVIEW_ALL
;
if
(
flags
&
PURPLE_CONNECTION_FLAG_NO_BGCOLOR
)
buttons
&=
~
PIDGIN_WEBVIEW_BACKCOLOR
;
if
(
flags
&
PURPLE_CONNECTION_FLAG_NO_FONTSIZE
)
{
buttons
&=
~
PIDGIN_WEBVIEW_GROW
;
buttons
&=
~
PIDGIN_WEBVIEW_SHRINK
;
}
if
(
flags
&
PURPLE_CONNECTION_FLAG_NO_URLDESC
)
buttons
&=
~
PIDGIN_WEBVIEW_LINKDESC
;
pidgin_webview_get_current_format
(
webview
,
&
bold
,
&
italic
,
&
underline
,
&
strike
);
pidgin_webview_set_format_functions
(
webview
,
PIDGIN_WEBVIEW_ALL
);
if
(
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/send_bold"
)
!=
bold
)
pidgin_webview_toggle_bold
(
webview
);
if
(
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/send_italic"
)
!=
italic
)
pidgin_webview_toggle_italic
(
webview
);
if
(
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/send_underline"
)
!=
underline
)
pidgin_webview_toggle_underline
(
webview
);
if
(
purple_prefs_get_bool
(
PIDGIN_PREFS_ROOT
"/conversations/send_strike"
)
!=
strike
)
pidgin_webview_toggle_strike
(
webview
);
pidgin_webview_toggle_fontface
(
webview
,
purple_prefs_get_string
(
PIDGIN_PREFS_ROOT
"/conversations/font_face"
));
if
(
!
(
flags
&
PURPLE_CONNECTION_FLAG_NO_FONTSIZE
))
{
int
size
=
purple_prefs_get_int
(
PIDGIN_PREFS_ROOT
"/conversations/font_size"
);
/* 3 is the default. */
if
(
size
!=
3
)
pidgin_webview_font_set_size
(
webview
,
size
);
}
pidgin_webview_toggle_forecolor
(
webview
,
purple_prefs_get_string
(
PIDGIN_PREFS_ROOT
"/conversations/fgcolor"
));
if
(
!
(
flags
&
PURPLE_CONNECTION_FLAG_NO_BGCOLOR
))
{
pidgin_webview_toggle_backcolor
(
webview
,
purple_prefs_get_string
(
PIDGIN_PREFS_ROOT
"/conversations/bgcolor"
));
}
else
{
pidgin_webview_toggle_backcolor
(
webview
,
""
);
}
if
(
flags
&
PURPLE_CONNECTION_FLAG_FORMATTING_WBFO
)
pidgin_webview_set_whole_buffer_formatting_only
(
webview
,
TRUE
);
else
pidgin_webview_set_whole_buffer_formatting_only
(
webview
,
FALSE
);
}
else
{
buttons
=
PIDGIN_WEBVIEW_SMILEY
|
PIDGIN_WEBVIEW_IMAGE
;
webview_clear_formatting
(
webview
);
}
if
(
flags
&
PURPLE_CONNECTION_FLAG_NO_IMAGES
)
buttons
&=
~
PIDGIN_WEBVIEW_IMAGE
;
if
(
flags
&
PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY
)
buttons
|=
PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
else
buttons
&=
~
PIDGIN_WEBVIEW_CUSTOM_SMILEY
;
pidgin_webview_set_format_functions
(
webview
,
buttons
);
}
void
pidgin_webview_set_spellcheck
(
PidginWebView
*
webview
,
gboolean
enable
)
{
WebKitWebSettings
*
settings
;
g_return_if_fail
(
webview
!=
NULL
);
settings
=
webkit_web_view_get_settings
(
WEBKIT_WEB_VIEW
(
webview
));
g_object_set
(
G_OBJECT
(
settings
),
"enable-spell-checking"
,
enable
,
NULL
);
webkit_web_view_set_settings
(
WEBKIT_WEB_VIEW
(
webview
),
settings
);
}
void
pidgin_webview_set_whole_buffer_formatting_only
(
PidginWebView
*
webview
,
gboolean
wbfo
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
priv
->
edit
.
wbfo
=
wbfo
;
}
void
pidgin_webview_set_format_functions
(
PidginWebView
*
webview
,
PidginWebViewButtons
buttons
)
{
PidginWebViewPrivate
*
priv
;
GObject
*
object
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
object
=
g_object_ref
(
G_OBJECT
(
webview
));
priv
->
format_functions
=
buttons
;
g_signal_emit
(
object
,
signals
[
BUTTONS_UPDATE
],
0
,
buttons
);
g_object_unref
(
object
);
}
void
pidgin_webview_activate_anchor
(
WebKitDOMHTMLAnchorElement
*
link
)
{
WebKitDOMDocument
*
doc
;
WebKitDOMEvent
*
event
;
doc
=
webkit_dom_node_get_owner_document
(
WEBKIT_DOM_NODE
(
link
));
event
=
webkit_dom_document_create_event
(
doc
,
"MouseEvent"
,
NULL
);
webkit_dom_event_init_event
(
event
,
"click"
,
TRUE
,
TRUE
);
webkit_dom_node_dispatch_event
(
WEBKIT_DOM_NODE
(
link
),
event
,
NULL
);
}
gboolean
pidgin_webview_class_register_protocol
(
const
char
*
name
,
gboolean
(
*
activate
)(
PidginWebView
*
webview
,
const
char
*
uri
),
gboolean
(
*
context_menu
)(
PidginWebView
*
webview
,
WebKitDOMHTMLAnchorElement
*
link
,
GtkWidget
*
menu
))
{
PidginWebViewClass
*
klass
;
PidginWebViewProtocol
*
proto
;
g_return_val_if_fail
(
name
,
FALSE
);
klass
=
g_type_class_ref
(
PIDGIN_TYPE_WEBVIEW
);
g_return_val_if_fail
(
klass
,
FALSE
);
if
((
proto
=
webview_find_protocol
(
name
,
TRUE
)))
{
if
(
activate
)
{
return
FALSE
;
}
klass
->
protocols
=
g_list_remove
(
klass
->
protocols
,
proto
);
g_free
(
proto
->
name
);
g_free
(
proto
);
return
TRUE
;
}
else
if
(
!
activate
)
{
return
FALSE
;
}
proto
=
g_new0
(
PidginWebViewProtocol
,
1
);
proto
->
name
=
g_strdup
(
name
);
proto
->
length
=
strlen
(
name
);
proto
->
activate
=
activate
;
proto
->
context_menu
=
context_menu
;
klass
->
protocols
=
g_list_prepend
(
klass
->
protocols
,
proto
);
return
TRUE
;
}
gchar
*
pidgin_webview_get_head_html
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
doc
;
WebKitDOMHTMLHeadElement
*
head
;
gchar
*
html
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
doc
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
head
=
webkit_dom_document_get_head
(
doc
);
html
=
webkit_dom_html_element_get_inner_html
(
WEBKIT_DOM_HTML_ELEMENT
(
head
));
return
html
;
}
static
gchar
*
pidgin_webview_strip_smileys
(
const
gchar
*
text
)
{
return
g_regex_replace
(
smileys_re
,
text
,
-1
,
0
,
"
\\
1"
,
0
,
NULL
);
}
gchar
*
pidgin_webview_get_body_html
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
doc
;
WebKitDOMHTMLElement
*
body
;
gchar
*
html
,
*
stripped
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
doc
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
body
=
webkit_dom_document_get_body
(
doc
);
html
=
webkit_dom_html_element_get_inner_html
(
body
);
stripped
=
pidgin_webview_strip_smileys
(
html
);
g_free
(
html
);
return
stripped
;
}
gchar
*
pidgin_webview_get_body_text
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
doc
;
WebKitDOMHTMLElement
*
body
;
gchar
*
text
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
doc
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
body
=
webkit_dom_document_get_body
(
doc
);
text
=
webkit_dom_html_element_get_inner_text
(
body
);
return
text
;
}
gchar
*
pidgin_webview_get_selected_text
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
dom
;
WebKitDOMDOMWindow
*
win
;
WebKitDOMDOMSelection
*
sel
;
WebKitDOMRange
*
range
=
NULL
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
win
=
webkit_dom_document_get_default_view
(
dom
);
sel
=
webkit_dom_dom_window_get_selection
(
win
);
if
(
webkit_dom_dom_selection_get_range_count
(
sel
))
range
=
webkit_dom_dom_selection_get_range_at
(
sel
,
0
,
NULL
);
if
(
range
)
return
webkit_dom_range_get_text
(
range
);
else
return
NULL
;
}
static
gchar
*
pidgin_webview_strip_empty_html
(
const
gchar
*
text
)
{
return
g_regex_replace
(
empty_html_re
,
text
,
-1
,
0
,
""
,
0
,
NULL
);
}
gboolean
pidgin_webview_is_empty
(
PidginWebView
*
webview
)
{
gchar
*
html
,
*
tmp
;
gboolean
is_empty
;
g_return_val_if_fail
(
webview
!=
NULL
,
TRUE
);
html
=
pidgin_webview_get_body_html
(
webview
);
tmp
=
purple_strreplace
(
html
,
" "
,
" "
);
g_free
(
html
);
html
=
tmp
;
tmp
=
pidgin_webview_strip_empty_html
(
html
);
g_free
(
html
);
html
=
tmp
;
g_strstrip
(
html
);
is_empty
=
(
html
[
0
]
==
'\0'
);
g_free
(
html
);
return
is_empty
;
}
void
pidgin_webview_get_caret
(
PidginWebView
*
webview
,
WebKitDOMNode
**
container_ret
,
glong
*
pos_ret
)
{
WebKitDOMDocument
*
dom
;
WebKitDOMDOMWindow
*
win
;
WebKitDOMDOMSelection
*
sel
;
WebKitDOMRange
*
range
=
NULL
;
WebKitDOMNode
*
start_container
,
*
end_container
;
glong
start
,
end
;
g_return_if_fail
(
webview
&&
container_ret
&&
pos_ret
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
win
=
webkit_dom_document_get_default_view
(
dom
);
sel
=
webkit_dom_dom_window_get_selection
(
win
);
if
(
webkit_dom_dom_selection_get_range_count
(
sel
))
range
=
webkit_dom_dom_selection_get_range_at
(
sel
,
0
,
NULL
);
if
(
range
)
{
start_container
=
webkit_dom_range_get_start_container
(
range
,
NULL
);
start
=
webkit_dom_range_get_start_offset
(
range
,
NULL
);
end_container
=
webkit_dom_range_get_end_container
(
range
,
NULL
);
end
=
webkit_dom_range_get_end_offset
(
range
,
NULL
);
if
(
start
==
end
&&
webkit_dom_node_is_same_node
(
start_container
,
end_container
))
{
*
container_ret
=
start_container
;
*
pos_ret
=
start
;
return
;
}
}
*
container_ret
=
NULL
;
*
pos_ret
=
-1
;
}
void
pidgin_webview_set_caret
(
PidginWebView
*
webview
,
WebKitDOMNode
*
container
,
glong
pos
)
{
WebKitDOMDocument
*
dom
;
WebKitDOMDOMWindow
*
win
;
WebKitDOMDOMSelection
*
sel
;
g_return_if_fail
(
webview
&&
container
&&
pos
>=
0
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
win
=
webkit_dom_document_get_default_view
(
dom
);
sel
=
webkit_dom_dom_window_get_selection
(
win
);
webkit_dom_dom_selection_set_position
(
sel
,
container
,
pos
,
NULL
);
}
PidginWebViewButtons
pidgin_webview_get_format_functions
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
g_return_val_if_fail
(
webview
!=
NULL
,
0
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
return
priv
->
format_functions
;
}
void
pidgin_webview_get_current_format
(
PidginWebView
*
webview
,
gboolean
*
bold
,
gboolean
*
italic
,
gboolean
*
underline
,
gboolean
*
strike
)
{
WebKitDOMDocument
*
dom
;
g_return_if_fail
(
webview
!=
NULL
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
if
(
bold
)
*
bold
=
webkit_dom_document_query_command_state
(
dom
,
"bold"
);
if
(
italic
)
*
italic
=
webkit_dom_document_query_command_state
(
dom
,
"italic"
);
if
(
underline
)
*
underline
=
webkit_dom_document_query_command_state
(
dom
,
"underline"
);
if
(
strike
)
*
strike
=
webkit_dom_document_query_command_state
(
dom
,
"strikethrough"
);
}
char
*
pidgin_webview_get_current_fontface
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
dom
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
return
webkit_dom_document_query_command_value
(
dom
,
"fontName"
);
}
char
*
pidgin_webview_get_current_forecolor
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
dom
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
return
webkit_dom_document_query_command_value
(
dom
,
"foreColor"
);
}
char
*
pidgin_webview_get_current_backcolor
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
dom
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
return
webkit_dom_document_query_command_value
(
dom
,
"backColor"
);
}
gint
pidgin_webview_get_current_fontsize
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
dom
;
gchar
*
text
;
gint
size
;
g_return_val_if_fail
(
webview
!=
NULL
,
0
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
text
=
webkit_dom_document_query_command_value
(
dom
,
"fontSize"
);
size
=
atoi
(
text
);
g_free
(
text
);
return
size
;
}
void
pidgin_webview_clear_formatting
(
PidginWebView
*
webview
)
{
GObject
*
object
;
g_return_if_fail
(
webview
!=
NULL
);
object
=
g_object_ref
(
G_OBJECT
(
webview
));
g_signal_emit
(
object
,
signals
[
CLEAR_FORMAT
],
0
);
g_object_unref
(
object
);
}
void
pidgin_webview_toggle_bold
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_BOLD
);
}
void
pidgin_webview_toggle_italic
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_ITALIC
);
}
void
pidgin_webview_toggle_underline
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_UNDERLINE
);
}
void
pidgin_webview_toggle_strike
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_STRIKE
);
}
gboolean
pidgin_webview_toggle_forecolor
(
PidginWebView
*
webview
,
const
char
*
color
)
{
g_return_val_if_fail
(
webview
!=
NULL
,
FALSE
);
do_formatting
(
webview
,
"foreColor"
,
color
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_FORECOLOR
);
return
FALSE
;
}
gboolean
pidgin_webview_toggle_backcolor
(
PidginWebView
*
webview
,
const
char
*
color
)
{
g_return_val_if_fail
(
webview
!=
NULL
,
FALSE
);
do_formatting
(
webview
,
"backColor"
,
color
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_BACKCOLOR
);
return
FALSE
;
}
gboolean
pidgin_webview_toggle_fontface
(
PidginWebView
*
webview
,
const
char
*
face
)
{
g_return_val_if_fail
(
webview
!=
NULL
,
FALSE
);
do_formatting
(
webview
,
"fontName"
,
face
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_FACE
);
return
FALSE
;
}
void
pidgin_webview_font_set_size
(
PidginWebView
*
webview
,
gint
size
)
{
char
*
tmp
;
g_return_if_fail
(
webview
!=
NULL
);
tmp
=
g_strdup_printf
(
"%d"
,
size
);
do_formatting
(
webview
,
"fontSize"
,
tmp
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_SHRINK
|
PIDGIN_WEBVIEW_GROW
);
g_free
(
tmp
);
}
void
pidgin_webview_font_shrink
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_SHRINK
);
}
void
pidgin_webview_font_grow
(
PidginWebView
*
webview
)
{
g_return_if_fail
(
webview
!=
NULL
);
emit_format_signal
(
webview
,
PIDGIN_WEBVIEW_GROW
);
}
void
pidgin_webview_insert_hr
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
WebKitDOMDocument
*
dom
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
priv
->
edit
.
block_changed
=
TRUE
;
webkit_dom_document_exec_command
(
dom
,
"insertHorizontalRule"
,
FALSE
,
""
);
priv
->
edit
.
block_changed
=
FALSE
;
}
void
pidgin_webview_insert_link
(
PidginWebView
*
webview
,
const
char
*
url
,
const
char
*
desc
)
{
PidginWebViewPrivate
*
priv
;
WebKitDOMDocument
*
dom
;
char
*
link
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
link
=
g_strdup_printf
(
"<a href='%s'>%s</a>"
,
url
,
desc
?
desc
:
url
);
priv
->
edit
.
block_changed
=
TRUE
;
webkit_dom_document_exec_command
(
dom
,
"insertHTML"
,
FALSE
,
link
);
priv
->
edit
.
block_changed
=
FALSE
;
g_free
(
link
);
}
void
pidgin_webview_insert_image
(
PidginWebView
*
webview
,
PurpleImage
*
image
)
{
PidginWebViewPrivate
*
priv
;
WebKitDOMDocument
*
dom
;
char
*
img
;
guint
id
;
gboolean
cancel
;
g_return_if_fail
(
webview
!=
NULL
);
g_signal_emit
(
webview
,
signals
[
INSERT_IMAGE
],
0
,
image
,
&
cancel
);
if
(
cancel
)
return
;
id
=
purple_image_store_add
(
image
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
dom
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
img
=
g_strdup_printf
(
"<img src='"
PURPLE_IMAGE_STORE_PROTOCOL
"%u'/>"
,
id
);
priv
->
edit
.
block_changed
=
TRUE
;
webkit_dom_document_exec_command
(
dom
,
"insertHTML"
,
FALSE
,
img
);
priv
->
edit
.
block_changed
=
FALSE
;
g_free
(
img
);
}
static
WebKitDOMCSSStyleDeclaration
*
pidgin_webview_get_DOM_CSS_style
(
PidginWebView
*
webview
)
{
WebKitDOMDocument
*
document
;
WebKitDOMElement
*
dom_element
;
WebKitDOMDOMWindow
*
dom_window
;
document
=
webkit_web_view_get_dom_document
(
WEBKIT_WEB_VIEW
(
webview
));
dom_window
=
webkit_dom_document_get_default_view
(
document
);
dom_element
=
webkit_dom_document_get_document_element
(
document
);
return
webkit_dom_dom_window_get_computed_style
(
dom_window
,
dom_element
,
0
);
}
gint
pidgin_webview_get_DOM_height
(
PidginWebView
*
webview
)
{
gchar
*
value
;
WebKitDOMCSSStyleDeclaration
*
style
;
style
=
pidgin_webview_get_DOM_CSS_style
(
webview
);
value
=
webkit_dom_css_style_declaration_get_property_value
(
style
,
"height"
);
return
g_ascii_strtoll
(
value
,
NULL
,
0
);
}
gint
pidgin_webview_get_font_size
(
PidginWebView
*
webview
)
{
gchar
*
value
;
WebKitDOMCSSStyleDeclaration
*
style
;
style
=
pidgin_webview_get_DOM_CSS_style
(
webview
);
value
=
webkit_dom_css_style_declaration_get_property_value
(
style
,
"font-size"
);
return
g_ascii_strtoll
(
value
,
NULL
,
0
);
}
void
pidgin_webview_set_toolbar
(
PidginWebView
*
webview
,
GtkWidget
*
toolbar
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
priv
->
toolbar
=
PIDGIN_WEBVIEWTOOLBAR
(
toolbar
);
}
GtkWidget
*
pidgin_webview_get_toolbar
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
g_return_val_if_fail
(
webview
!=
NULL
,
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
return
GTK_WIDGET
(
priv
->
toolbar
);
}
void
pidgin_webview_show_toolbar
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_return_if_fail
(
priv
->
toolbar
!=
NULL
);
gtk_widget_show
(
GTK_WIDGET
(
priv
->
toolbar
));
}
void
pidgin_webview_hide_toolbar
(
PidginWebView
*
webview
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_return_if_fail
(
priv
->
toolbar
!=
NULL
);
gtk_widget_hide
(
GTK_WIDGET
(
priv
->
toolbar
));
}
void
pidgin_webview_activate_toolbar
(
PidginWebView
*
webview
,
PidginWebViewAction
action
)
{
PidginWebViewPrivate
*
priv
;
g_return_if_fail
(
webview
!=
NULL
);
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_return_if_fail
(
priv
->
toolbar
!=
NULL
);
pidgin_webviewtoolbar_activate
(
priv
->
toolbar
,
action
);
}
void
pidgin_webview_switch_active_conversation
(
PidginWebView
*
webview
,
PurpleConversation
*
conv
)
{
PidginWebViewPrivate
*
priv
=
pidgin_webview_get_instance_private
(
webview
);
g_return_if_fail
(
priv
!=
NULL
);
if
(
priv
->
toolbar
==
NULL
)
return
;
pidgin_webviewtoolbar_switch_active_conversation
(
priv
->
toolbar
,
conv
);
}