/pidgin/main: f9503bfa54a4: Add protocol registration on a GtkWe...

Elliott Sales de Andrade qulogic at pidgin.im
Mon Aug 6 03:42:21 EDT 2012


Changeset: f9503bfa54a4ab0b1ff7be139c0d68a3e574710f
Author:	 Elliott Sales de Andrade <qulogic at pidgin.im>
Date:	 2012-08-05 04:10 -0400
Branch:	 default
URL: http://hg.pidgin.im/pidgin/main/rev/f9503bfa54a4

Description:

Add protocol registration on a GtkWebView.

diffstat:

 pidgin/gtkwebview.c |  121 ++++++++++++++++++++++++++++++++++++++++++++++++---
 pidgin/gtkwebview.h |   23 +++++++++
 2 files changed, 135 insertions(+), 9 deletions(-)

diffs (224 lines):

diff --git a/pidgin/gtkwebview.c b/pidgin/gtkwebview.c
--- a/pidgin/gtkwebview.c
+++ b/pidgin/gtkwebview.c
@@ -61,6 +61,14 @@ static guint signals[LAST_SIGNAL] = { 0 
  * Structs
  *****************************************************************************/
 
+typedef struct {
+	char *name;
+	int length;
+
+	gboolean (*activate)(GtkWebView *webview, const char *uri);
+	gboolean (*context_menu)(GtkWebView *webview, WebKitDOMHTMLAnchorElement *link, GtkWidget *menu);
+} GtkWebViewProtocol;
+
 typedef struct _GtkWebViewPriv {
 	/* Processing queues */
 	gboolean is_loading;
@@ -196,6 +204,27 @@ webview_load_finished(WebKitWebView *web
 		priv->loader = g_idle_add((GSourceFunc)process_load_queue, webview);
 }
 
+static GtkWebViewProtocol *
+webview_find_protocol(const char *url, gboolean reverse)
+{
+	GtkWebViewClass *klass;
+	GList *iter;
+	GtkWebViewProtocol *proto = NULL;
+	int length = reverse ? strlen(url) : -1;
+
+	klass = g_type_class_ref(GTK_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,
@@ -211,9 +240,11 @@ webview_navigation_decision(WebKitWebVie
 	reason = webkit_web_navigation_action_get_reason(navigation_action);
 
 	if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
-		/* the gtk imhtml way was to create an idle cb, not sure
-		 * why, so right now just using purple_notify_uri directly */
-		purple_notify_uri(NULL, uri);
+		GtkWebViewProtocol *proto = webview_find_protocol(uri, FALSE);
+		if (proto) {
+			/* XXX: Do something with the return value? */
+			proto->activate(GTK_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);
@@ -224,17 +255,37 @@ webview_navigation_decision(WebKitWebVie
 }
 
 static void
-do_popup_menu(WebKitWebView *webview, int button, int time, int context)
+do_popup_menu(WebKitWebView *webview, int button, int time, int context,
+              WebKitDOMHTMLAnchorElement *link, const char *uri)
 {
 	GtkWidget *menu;
-	GtkWidget *cut, *copy, *paste, *delete, *link, *select;
+	GtkWidget *cut, *copy, *paste, *delete, *select;
 
 	menu = gtk_menu_new();
 	g_signal_connect(menu, "selection-done",
 	                 G_CALLBACK(gtk_widget_destroy), NULL);
 
 	if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
-		/* Do something special... */
+		GtkWebViewProtocol *proto = NULL;
+		GList *children;
+
+		if (uri && link)
+			proto = webview_find_protocol(uri, FALSE);
+
+		if (proto && proto->context_menu) {
+			proto->context_menu(GTK_WEBVIEW(webview), link, 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);
+		}
+
 	} else {
 		/* Using connect_swapped means we don't need any wrapper functions */
 		cut = pidgin_new_item_from_stock(menu, _("Cu_t"), GTK_STOCK_CUT,
@@ -290,7 +341,8 @@ static gboolean
 webview_popup_menu(WebKitWebView *webview)
 {
 	do_popup_menu(webview, 0, gtk_get_current_event_time(),
-	              WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT);
+	              WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
+	              NULL, NULL);
 	return TRUE;
 }
 
@@ -300,11 +352,27 @@ webview_button_pressed(WebKitWebView *we
 	if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
 		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, NULL);
+		g_object_get(G_OBJECT(hit),
+		             "context", &context,
+		             "inner-node", &node,
+		             "link-uri", &uri,
+		             NULL);
 
-		do_popup_menu(webview, event->button, event->time, context);
+		if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) {
+			while (node && !WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT(node)) {
+				node = webkit_dom_node_get_parent_node(node);
+			}
+		}
+
+		do_popup_menu(webview, event->button, event->time, context,
+		              WEBKIT_DOM_HTML_ANCHOR_ELEMENT(node), uri);
+
+		g_free(uri);
+
 		return TRUE;
 	}
 
@@ -927,6 +995,41 @@ gtk_webview_set_format_functions(GtkWebV
 	g_object_unref(object);
 }
 
+gboolean
+gtk_webview_class_register_protocol(const char *name,
+	gboolean (*activate)(GtkWebView *webview, const char *uri),
+	gboolean (*context_menu)(GtkWebView *webview, WebKitDOMHTMLAnchorElement *link, GtkWidget *menu))
+{
+	GtkWebViewClass *klass;
+	GtkWebViewProtocol *proto;
+
+	g_return_val_if_fail(name, FALSE);
+
+	klass = g_type_class_ref(GTK_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(GtkWebViewProtocol, 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 *
 gtk_webview_get_head_html(GtkWebView *webview)
 {
diff --git a/pidgin/gtkwebview.h b/pidgin/gtkwebview.h
--- a/pidgin/gtkwebview.h
+++ b/pidgin/gtkwebview.h
@@ -70,6 +70,8 @@ struct _GtkWebViewClass
 {
 	WebKitWebViewClass parent;
 
+	GList *protocols;
+
 	void (*buttons_update)(GtkWebView *, GtkWebViewButtons);
 	void (*toggle_format)(GtkWebView *, GtkWebViewButtons);
 	void (*clear_format)(GtkWebView *);
@@ -218,6 +220,27 @@ void gtk_webview_set_format_functions(Gt
                                       GtkWebViewButtons buttons);
 
 /**
+ * Register a protocol with the GtkWebView widget. Registering a protocol would
+ * allow certain text to be clickable.
+ *
+ * @param name      The name of the protocol (e.g. http://)
+ * @param activate  The callback to trigger when the protocol text is clicked.
+ *                  Removes any current protocol definition if @c NULL. The
+ *                  callback should return @c TRUE if the link was activated
+ *                  properly, @c FALSE otherwise.
+ * @param context_menu  The callback to trigger when the context menu is popped
+ *                      up on the protocol text. The callback should return
+ *                      @c TRUE if the request for context menu was processed
+ *                      successfully, @c FALSE otherwise.
+ *
+ * @return  @c TRUE if the protocol was successfully registered
+ *          (or unregistered, when \a activate is @c NULL)
+ */
+gboolean gtk_webview_class_register_protocol(const char *name,
+		gboolean (*activate)(GtkWebView *webview, const char *uri),
+		gboolean (*context_menu)(GtkWebView *webview, WebKitDOMHTMLAnchorElement *link, GtkWidget *menu));
+
+/**
  * Returns which formatting functions are enabled in a GtkWebView.
  *
  * @param webview The GtkWebView



More information about the Commits mailing list