[balsa/gtk3] Refactor HTML code to work with async search



commit 359a302736753b2642a0f70dd2f0fa4934240729
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Sat Mar 30 15:16:10 2013 -0400

    Refactor HTML code to work with async search
    
        * libbalsa/html.c (lbh_web_process_crashed_cb): spew message
        only when debugging;
        (lbh_search_failed_to_find_text_cb), (lbh_search_found_text_cb),
        (lbh_search_init), (lbh_search_continue),
        (libbalsa_html_search): work with asynchronous search;
        (libbalsa_html_get_selection_bounds): is now gboolean, return
        FALSE in Webkit2 version.
        * libbalsa/html.h: new protoypes.
        * src/balsa-message.c (bm_find_cb), (bm_find_entry_changed_cb),
        (bm_find_again), (balsa_message_destroy): work with asynchronous
        search.
        * src/balsa-message.h: add gpointer member to store HTML search
        info.

 ChangeLog           |   19 ++++
 libbalsa/html.c     |  240 ++++++++++++++++++++++++++++++++-------------------
 libbalsa/html.h     |   16 +++-
 src/balsa-message.c |   89 +++++++++++++++-----
 src/balsa-message.h |    3 +
 5 files changed, 253 insertions(+), 114 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 43eb98c..d6e53db 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2013-03-30  Peter Bloomfield
+
+       Refactor HTML search code to work with asynchronous search in
+       Webkit2.
+
+       * libbalsa/html.c (lbh_web_process_crashed_cb): spew message
+       only when debugging;
+       (lbh_search_failed_to_find_text_cb), (lbh_search_found_text_cb),
+       (lbh_search_init), (lbh_search_continue),
+       (libbalsa_html_search): work with asynchronous search;
+       (libbalsa_html_get_selection_bounds): is now gboolean, return
+       FALSE in Webkit2 version.
+       * libbalsa/html.h: new protoypes.
+       * src/balsa-message.c (bm_find_cb), (bm_find_entry_changed_cb),
+       (bm_find_again), (balsa_message_destroy): work with asynchronous
+       search.
+       * src/balsa-message.h: add gpointer member to store HTML search
+       info.
+
 2013-03-28  Peter Bloomfield
 
        * libbalsa/html.c (lbh_navigation_policy_decision),
diff --git a/libbalsa/html.c b/libbalsa/html.c
index f4086d6..87e1cb9 100644
--- a/libbalsa/html.c
+++ b/libbalsa/html.c
@@ -417,7 +417,7 @@ static gboolean
 lbh_web_process_crashed_cb(WebKitWebView * web_view,
                            gpointer        data)
 {
-    g_print("%s\n", __func__);
+    d(g_print("%s\n", __func__));
     return FALSE;
 }
 
@@ -599,114 +599,161 @@ libbalsa_html_can_search(GtkWidget * widget)
 }
 
 /*
- * JavaScript-based helpers for text search
+ * Search for the text; if text is empty, call the callback with TRUE
+ * (for consistency with GtkTextIter methods).
  */
-static JSValueRef
-lbh_js_run_script(JSContextRef  ctx,
-                  const gchar * script)
-{
-    JSStringRef str;
-    JSValueRef value;
 
-    str = JSStringCreateWithUTF8CString(script);
-    value = JSEvaluateScript(ctx, str, NULL, NULL, 0, NULL);
-    JSStringRelease(str);
+typedef struct {
+    LibBalsaHtmlSearchCallback search_cb;
+    gpointer                   cb_data;
+    gchar                    * text;
+} LibBalsaHtmlSearchInfo;
 
-    return value;
+#define LIBBALSA_HTML_SEARCH_INFO "LibBalsaHtmlSearchInfo"
+
+static void
+lbh_search_failed_to_find_text_cb(WebKitFindController * controller,
+                                  gpointer               data)
+{
+    LibBalsaHtmlSearchInfo *info = data;
+
+    (*info->search_cb)(info->text, FALSE, info->cb_data);
 }
 
-static gint
-lbh_js_object_get_property(JSContextRef  ctx,
-                           JSObjectRef   object,
-                           const gchar * property_name)
+static void
+lbh_search_found_text_cb(WebKitFindController *controller,
+                         guint                 match_count,
+                         gpointer              data)
 {
-    JSStringRef str  = JSStringCreateWithUTF8CString(property_name);
-    JSValueRef value = JSObjectGetProperty(ctx, object, str, NULL);
-    JSStringRelease(str);
+    LibBalsaHtmlSearchInfo *info = data;
 
-    return (gint) JSValueToNumber(ctx, value, NULL);
+    (*info->search_cb)(info->text, TRUE, info->cb_data);
 }
 
-/*
- * Search for the text; if text is empty, return TRUE (for consistency
- * with GtkTextIter methods).
- */
-gboolean
-libbalsa_html_search_text(GtkWidget   * widget,
-                          const gchar * text,
-                          gboolean      find_forward,
-                          gboolean      wrap)
+static void
+lbh_search_init(WebKitFindController     * controller,
+                const gchar              * text,
+                gboolean                   find_forward,
+                gboolean                   wrap,
+                LibBalsaHtmlSearchCallback search_cb,
+                gpointer                   cb_data)
 {
-    WebKitWebView *web_view;
-    WebKitFindController *find_controller;
-    guint find_options;
+    LibBalsaHtmlSearchInfo *info;
+    guint32 find_options;
 
-    if (!lbh_get_web_view(widget, &web_view))
-        return FALSE;
+    info = g_object_get_data(G_OBJECT(controller),
+                             LIBBALSA_HTML_SEARCH_INFO);
 
     if (!*text) {
-        JSGlobalContextRef ctx;
-        gchar script[] = "window.getSelection().removeAllRanges()";
-
-        ctx = webkit_web_view_get_javascript_global_context(web_view);
-        lbh_js_run_script(ctx, script);
+        webkit_find_controller_search_finish(controller);
+        (*search_cb)(text, TRUE, cb_data);
+        if (info) {
+            g_free(info->text);
+            info->text = NULL;
+        }
+        return;
+    }
 
-        return TRUE;
+    if (!info) {
+        info = g_new(LibBalsaHtmlSearchInfo, 1);
+        info->text = NULL;
+        g_object_set_data_full(G_OBJECT(controller),
+                               LIBBALSA_HTML_SEARCH_INFO, info, g_free);
+        g_signal_connect(controller, "failed-to-find-text",
+                         G_CALLBACK(lbh_search_failed_to_find_text_cb),
+                         info);
+        g_signal_connect(controller, "found-text",
+                         G_CALLBACK(lbh_search_found_text_cb),
+                         info);
     }
 
+    g_free(info->text);
+    info->text = g_strdup(text);
+    info->search_cb = search_cb;
+    info->cb_data = cb_data;
+
     find_options = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE;
     if (!find_forward)
         find_options |= WEBKIT_FIND_OPTIONS_BACKWARDS;
     if (wrap)
         find_options |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
 
-    find_controller = webkit_web_view_get_find_controller(web_view);
-    webkit_find_controller_search(find_controller, text, find_options,
+    webkit_find_controller_search(controller, text, find_options,
                                   G_MAXUINT);
+}
 
-    return FALSE;
+static void
+lbh_search_continue(WebKitFindController * controller,
+                    const gchar          * text,
+                    gboolean               find_forward,
+                    gboolean               wrap)
+{
+    guint32 find_options;
+
+    find_options = webkit_find_controller_get_options(controller);
+
+    if (!find_forward)
+        find_options |= WEBKIT_FIND_OPTIONS_BACKWARDS;
+    else
+        find_options &= ~WEBKIT_FIND_OPTIONS_BACKWARDS;
+
+    if (wrap)
+        find_options |= WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+    else
+        find_options &= ~WEBKIT_FIND_OPTIONS_WRAP_AROUND;
+
+    if (find_options != webkit_find_controller_get_options(controller)) {
+        /* No setter for find-options, so we start a new search */
+        webkit_find_controller_search(controller, text, find_options,
+                                      G_MAXUINT);
+    } else {
+        /* OK to use next/previous methods */
+        if (find_forward)
+            webkit_find_controller_search_next(controller);
+        else
+            webkit_find_controller_search_previous(controller);
+    }
 }
 
-/*
- * Get the rectangle containing the currently selected text, for
- * scrolling.
- */
 void
-libbalsa_html_get_selection_bounds(GtkWidget    * widget,
-                                   GdkRectangle * selection_bounds)
+libbalsa_html_search(GtkWidget                * widget,
+                     const gchar              * text,
+                     gboolean                   find_forward,
+                     gboolean                   wrap,
+                     LibBalsaHtmlSearchCallback search_cb,
+                     gpointer                   cb_data)
 {
     WebKitWebView *web_view;
-    JSGlobalContextRef ctx;
-    gchar script[] =
-        "window.getSelection().getRangeAt(0).getBoundingClientRect()";
-    JSValueRef value;
+    WebKitFindController *controller;
+    LibBalsaHtmlSearchInfo *info;
 
-    if (!lbh_get_web_view(widget, &web_view))
+    if (!lbh_get_web_view(widget, &web_view)) {
         return;
+    }
 
-    ctx = webkit_web_view_get_javascript_global_context(web_view);
-    value = lbh_js_run_script(ctx, script);
-
-    if (value && JSValueIsObject(ctx, value)) {
-        JSObjectRef object = JSValueToObject(ctx, value, NULL);
-        gint x, y;
-
-        x = lbh_js_object_get_property(ctx, object, "left");
-        y = lbh_js_object_get_property(ctx, object, "top");
-
-        gtk_widget_translate_coordinates(GTK_WIDGET(web_view), widget,
-                                         x, y,
-                                         &selection_bounds->x,
-                                         &selection_bounds->y);
+    controller = webkit_web_view_get_find_controller(web_view);
 
-        selection_bounds->width =
-            lbh_js_object_get_property(ctx, object, "width");
-        selection_bounds->height =
-            lbh_js_object_get_property(ctx, object, "height");
+    if (!(info = g_object_get_data(G_OBJECT(controller),
+                                   LIBBALSA_HTML_SEARCH_INFO)) ||
+        (!info->text || strcmp(text, info->text))) {
+        lbh_search_init(controller, text, find_forward, wrap,
+                        search_cb, cb_data);
+    } else {
+        lbh_search_continue(controller, text, find_forward, wrap);
     }
 }
 
 /*
+ * We do not need selection bounds.
+ */
+gboolean
+libbalsa_html_get_selection_bounds(GtkWidget    * widget,
+                                   GdkRectangle * selection_bounds)
+{
+    return FALSE;
+}
+
+/*
  * Get the WebKitWebView widget from the container; we need to connect
  * to its "populate-popup" signal.
  */
@@ -1231,35 +1278,42 @@ lbh_js_object_get_property(JSContextRef  ctx,
  * Search for the text; if text is empty, return TRUE (for consistency
  * with GtkTextIter methods).
  */
-gboolean
-libbalsa_html_search_text(GtkWidget   * widget,
-                          const gchar * text,
-                          gboolean      find_forward,
-                          gboolean      wrap)
+void
+libbalsa_html_search(GtkWidget                * widget,
+                     const gchar              * text,
+                     gboolean                   find_forward,
+                     gboolean                   wrap,
+                     LibBalsaHtmlSearchCallback search_cb,
+                     gpointer                   cb_data)
 {
     WebKitWebView *web_view;
+    gboolean retval;
 
     if (!lbh_get_web_view(widget, &web_view))
-        return FALSE;
+        return;
 
     if (!*text) {
         gchar script[] = "window.getSelection().removeAllRanges()";
 
         lbh_js_run_script(lbh_js_get_global_context(web_view), script);
 
-        return TRUE;
+        (*search_cb)(text, TRUE, cb_data);
+        return;
     }
 
-    return webkit_web_view_search_text(web_view, text,
-                                       FALSE,    /* case-insensitive */
-                                       find_forward, wrap);
+    retval = webkit_web_view_search_text(web_view, text,
+                                         FALSE,    /* case-insensitive */
+                                         find_forward, wrap);
+    (*search_cb)(text, retval, cb_data);
+
+    return;
 }
 
 /*
  * Get the rectangle containing the currently selected text, for
  * scrolling.
  */
-void
+gboolean
 libbalsa_html_get_selection_bounds(GtkWidget    * widget,
                                    GdkRectangle * selection_bounds)
 {
@@ -1270,7 +1324,7 @@ libbalsa_html_get_selection_bounds(GtkWidget    * widget,
     JSValueRef value;
 
     if (!lbh_get_web_view(widget, &web_view))
-        return;
+        return FALSE;
 
     ctx = lbh_js_get_global_context(web_view);
     value = lbh_js_run_script(ctx, script);
@@ -1291,7 +1345,11 @@ libbalsa_html_get_selection_bounds(GtkWidget    * widget,
             lbh_js_object_get_property(ctx, object, "width");
         selection_bounds->height =
             lbh_js_object_get_property(ctx, object, "height");
+
+        return TRUE;
     }
+
+    return FALSE;
 }
 
 /*
@@ -1820,17 +1878,21 @@ libbalsa_html_can_search(GtkWidget * widget)
     return FALSE;
 }
 
-gboolean
-libbalsa_html_search_text(GtkWidget * widget, const gchar * text,
-                          gboolean find_forward, gboolean wrap)
+void
+libbalsa_html_search(GtkWidget                * widget,
+                     const gchar              * text,
+                     gboolean                   find_forward,
+                     gboolean                   wrap,
+                     LibBalsaHtmlSearchCallback search_cb,
+                     gpointer                   cb_data)
 {
-    return FALSE;
 }
 
-void
+gboolean
 libbalsa_html_get_selection_bounds(GtkWidget    * widget,
                                    GdkRectangle * selection_bounds)
 {
+    return FALSE;
 }
 
 /*
diff --git a/libbalsa/html.h b/libbalsa/html.h
index 0f578b7..03be566 100644
--- a/libbalsa/html.h
+++ b/libbalsa/html.h
@@ -59,11 +59,19 @@ void libbalsa_html_copy(GtkWidget * widget);
 guint libbalsa_html_filter(LibBalsaHTMLType html_type, gchar ** text,
                           guint len);
 
+typedef void (*LibBalsaHtmlSearchCallback)(const gchar * text,
+                                           gboolean      found,
+                                           gpointer      data);
 gboolean libbalsa_html_can_search(GtkWidget * widget);
-gboolean libbalsa_html_search_text(GtkWidget * widget, const gchar * text,
-                                   gboolean find_forward, gboolean wrap);
-void libbalsa_html_get_selection_bounds(GtkWidget * widget,
-                                        GdkRectangle * selection_bounds);
+void libbalsa_html_search(GtkWidget                * widget,
+                          const gchar              * text,
+                          gboolean                   find_forward,
+                          gboolean                   wrap,
+                          LibBalsaHtmlSearchCallback search_cb,
+                          gpointer                   cb_data);
+gboolean libbalsa_html_get_selection_bounds(GtkWidget * widget,
+                                            GdkRectangle *
+                                            selection_bounds);
 
 GtkWidget *libbalsa_html_popup_menu_widget(GtkWidget * widget);
 GtkWidget *libbalsa_html_get_view_widget(GtkWidget * widget);
diff --git a/src/balsa-message.c b/src/balsa-message.c
index df2ccd4..0392487 100644
--- a/src/balsa-message.c
+++ b/src/balsa-message.c
@@ -441,6 +441,44 @@ bm_find_scroll_to_selection(BalsaMessage * bm,
     bm_find_scroll_to_rectangle(bm, GTK_WIDGET(text_view), &begin_location);
 }
 
+#ifdef HAVE_HTML_WIDGET
+typedef struct {
+    BalsaMessage *bm;
+    GtkWidget    *widget;
+    gboolean      continuing;
+    gboolean      wrapping;
+} BalsaMessageFindInfo;
+#define BALSA_MESSAGE_FIND_INFO "BalsaMessageFindInfo"
+
+static void
+bm_find_cb(const gchar * text, gboolean found, gpointer data)
+{
+    BalsaMessageFindInfo *info = data;
+
+    if (!found && info->continuing) {
+        info->wrapping = TRUE;
+        libbalsa_html_search(info->widget, text, info->bm->find_forward,
+                             TRUE, bm_find_cb, info);
+        return;
+    }
+
+    if (found && *text) {
+        GdkRectangle selection_bounds;
+        if (libbalsa_html_get_selection_bounds(info->widget,
+                                               &selection_bounds))
+            bm_find_scroll_to_rectangle(info->bm, info->widget,
+                                        &selection_bounds);
+    }
+
+    if (info->wrapping) {
+        info->wrapping = FALSE;
+        bm_find_set_status(info->bm, BM_FIND_STATUS_WRAPPED);
+    } else
+        bm_find_set_status(info->bm, found ? BM_FIND_STATUS_FOUND :
+                                             BM_FIND_STATUS_NOT_FOUND);
+}
+#endif                          /* HAVE_HTML_WIDGET */
+
 static void
 bm_find_entry_changed_cb(GtkEditable * editable, gpointer data)
 {
@@ -480,23 +518,26 @@ bm_find_entry_changed_cb(GtkEditable * editable, gpointer data)
                                         &match_begin, &match_end);
             bm->find_iter = match_begin;
         }
+
+        bm_find_set_status(bm, found ? BM_FIND_STATUS_FOUND :
+                                       BM_FIND_STATUS_NOT_FOUND);
 #ifdef HAVE_HTML_WIDGET
     } else if (libbalsa_html_can_search(widget)) {
-        found = libbalsa_html_search_text(widget, text,
-                                          bm->find_forward, TRUE);
-        if (found && *text) {
-            GdkRectangle selection_bounds;
-
-            libbalsa_html_get_selection_bounds(widget,
-                                               &selection_bounds);
-            bm_find_scroll_to_rectangle(bm, widget, &selection_bounds);
+        BalsaMessageFindInfo *info;
+
+        if (!(info = bm->html_find_info)) {
+            bm->html_find_info = info = g_new(BalsaMessageFindInfo, 1);
+            info->bm = bm;
         }
+        info->widget = widget;
+        info->continuing = FALSE;
+        info->wrapping = FALSE;
+
+        libbalsa_html_search(widget, text, bm->find_forward, TRUE,
+                             bm_find_cb, info);
 #endif                          /* HAVE_HTML_WIDGET */
     } else
         g_assert_not_reached();
-
-    bm_find_set_status(bm, found ?
-                       BM_FIND_STATUS_FOUND : BM_FIND_STATUS_NOT_FOUND);
 }
 
 static void
@@ -506,6 +547,8 @@ bm_find_again(BalsaMessage * bm, gboolean find_forward)
     GtkWidget *widget = bm->current_part->mime_widget->widget;
     gboolean found;
 
+    bm->find_forward = find_forward;
+
     if (GTK_IS_TEXT_VIEW(widget)) {
         GtkTextView *text_view = GTK_TEXT_VIEW(widget);
         GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
@@ -535,22 +578,19 @@ bm_find_again(BalsaMessage * bm, gboolean find_forward)
         bm_find_scroll_to_selection(bm, text_view,
                                     &match_begin, &match_end);
         bm->find_iter = match_begin;
+
+        bm_find_set_status(bm, found ?
+                           BM_FIND_STATUS_FOUND : BM_FIND_STATUS_WRAPPED);
 #ifdef HAVE_HTML_WIDGET
     } else if (libbalsa_html_can_search(widget)) {
-        GdkRectangle selection_bounds;
+        BalsaMessageFindInfo *info = bm->html_find_info;
 
-        found = libbalsa_html_search_text(widget, text, find_forward, FALSE);
-        if (!found)
-            libbalsa_html_search_text(widget, text, find_forward, TRUE);
-        libbalsa_html_get_selection_bounds(widget, &selection_bounds);
-        bm_find_scroll_to_rectangle(bm, widget, &selection_bounds);
+        info->continuing = TRUE;
+        libbalsa_html_search(widget, text, find_forward, FALSE,
+                             bm_find_cb, info);
 #endif                          /* HAVE_HTML_WIDGET */
     } else
         g_assert_not_reached();
-
-    bm_find_set_status(bm, found ?
-                       BM_FIND_STATUS_FOUND : BM_FIND_STATUS_WRAPPED);
-    bm->find_forward = find_forward;
 }
 
 static void
@@ -804,6 +844,13 @@ balsa_message_destroy(GObject * object)
        bm->parts_popup = NULL;
     }
 
+#ifdef HAVE_HTML_WIDGET
+    if (bm->html_find_info) {
+        g_free(bm->html_find_info);
+        bm->html_find_info = NULL;
+    }
+#endif                          /* HAVE_HTML_WIDGET */
+
     if (G_OBJECT_CLASS(parent_class)->dispose)
         (*G_OBJECT_CLASS(parent_class)->dispose) (object);
 }
diff --git a/src/balsa-message.h b/src/balsa-message.h
index 1596629..8d530c2 100644
--- a/src/balsa-message.h
+++ b/src/balsa-message.h
@@ -96,6 +96,9 @@ struct _BalsaMessage {
 
         /* Tab position for headers */
         gint tab_position;
+#ifdef HAVE_HTML_WIDGET
+        gpointer html_find_info;
+#endif                         /* HAVE_HTML_WIDGET */
 };
 
 struct _BalsaMessageClass {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]