[libgepub] Added pagination to the GepubWidget



commit 2616050444094776de73fcdd124eb1f9b5053176
Author: Daniel GarcĂ­a Moreno <danigm wadobo com>
Date:   Sat May 20 20:59:16 2017 +0200

    Added pagination to the GepubWidget
    
    I've added a property to set pagination on/off so the webkitwebview will
    be shown with or without scroll.
    
    The pagination is done with the column-width css property, setting the
    "body" with the widget width and height and the column-width with the
    same widget width, so we change the scroll from vertical to horizontal.
    Using this combined with "overflow" css property and with the "scrollTo"
    javascript function we can control the page showed.
    
    The position in the book we've the chapter number and the the position
    inside this chapter, the position is a percentage that we can get and
    set with "gepub_widget_(g/s)et_pos". We store a percentage to make it
    valid if the widget size changes, the page is not the same, but the
    position is always valid.
    
    To make this work well we need to remove margin and padding from the
    body tag, because this affects the column-width.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=768002

 libgepub/gepub-widget.c |  387 ++++++++++++++++++++++++++++++++++++++++++++++-
 libgepub/gepub-widget.h |   16 ++
 tests/test-gepub.c      |   57 ++++++-
 3 files changed, 452 insertions(+), 8 deletions(-)
---
diff --git a/libgepub/gepub-widget.c b/libgepub/gepub-widget.c
index 149b3dc..b3dbb6a 100644
--- a/libgepub/gepub-widget.c
+++ b/libgepub/gepub-widget.c
@@ -19,6 +19,7 @@
 
 #include <config.h>
 #include <gtk/gtk.h>
+#include <JavaScriptCore/JSValueRef.h>
 
 #include "gepub-widget.h"
 
@@ -26,6 +27,11 @@ struct _GepubWidget {
     WebKitWebView parent;
 
     GepubDoc *doc;
+    gboolean paginate;
+    gint chapter_length; // real chapter length
+    gint chapter_pos; // position in the chapter, a percentage based on chapter_length
+    gint length;
+    gint init_chapter_pos;
 };
 
 struct _GepubWidgetClass {
@@ -35,6 +41,10 @@ struct _GepubWidgetClass {
 enum {
     PROP_0,
     PROP_DOC,
+    PROP_PAGINATE,
+    PROP_CHAPTER,
+    PROP_N_CHAPTERS,
+    PROP_CHAPTER_POS,
     NUM_PROPS
 };
 
@@ -43,6 +53,147 @@ static GParamSpec *properties[NUM_PROPS] = { NULL, };
 G_DEFINE_TYPE (GepubWidget, gepub_widget, WEBKIT_TYPE_WEB_VIEW)
 
 static void
+scroll_to_chapter_pos (GepubWidget *widget) {
+    gchar *script = g_strdup_printf("document.querySelector('body').scrollTo(%d, 0)", widget->chapter_pos);
+    webkit_web_view_run_javascript (WEBKIT_WEB_VIEW (widget), script, NULL, NULL, NULL);
+    g_free(script);
+}
+
+static void
+adjust_chapter_pos (GepubWidget *widget)
+{
+    // integer division to make a page start
+    gint page = widget->chapter_pos / widget->length;
+    gint next = page + 1;
+    gint d1 = widget->chapter_pos - (widget->length * page);
+    gint d2 = (widget->length * next) - widget->chapter_pos;
+
+    if (d1 < d2) {
+        widget->chapter_pos = widget->length * page;
+    } else {
+        widget->chapter_pos = widget->length * next;
+    }
+    scroll_to_chapter_pos (widget);
+}
+
+static void
+pagination_initialize_finished (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer     user_data)
+{
+    WebKitJavascriptResult *js_result;
+    JSValueRef              value;
+    JSGlobalContextRef      context;
+    GError                 *error = NULL;
+    GepubWidget            *widget = GEPUB_WIDGET (user_data);
+
+    js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
+    if (!js_result) {
+        g_warning ("Error running javascript: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    context = webkit_javascript_result_get_global_context (js_result);
+    value = webkit_javascript_result_get_value (js_result);
+    if (JSValueIsNumber (context, value)) {
+        double n;
+
+        n = JSValueToNumber (context, value, NULL);
+        widget->chapter_length = (int)n;
+
+        if (widget->init_chapter_pos) {
+            widget->chapter_pos = widget->init_chapter_pos * widget->chapter_length / 100;
+            if (widget->chapter_pos > (widget->chapter_length - widget->length)) {
+                widget->chapter_pos = (widget->chapter_length - widget->length);
+            }
+            widget->init_chapter_pos = 0;
+        }
+
+        if (widget->chapter_pos) {
+            adjust_chapter_pos (widget);
+        }
+
+    } else {
+        g_warning ("Error running javascript: unexpected return value");
+    }
+    webkit_javascript_result_unref (js_result);
+}
+
+static void
+get_length_finished (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer     user_data)
+{
+    WebKitJavascriptResult *js_result;
+    JSValueRef              value;
+    JSGlobalContextRef      context;
+    GError                 *error = NULL;
+    GepubWidget            *widget = GEPUB_WIDGET (user_data);
+
+    js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
+    if (!js_result) {
+        g_warning ("Error running javascript: %s", error->message);
+        g_error_free (error);
+        return;
+    }
+
+    context = webkit_javascript_result_get_global_context (js_result);
+    value = webkit_javascript_result_get_value (js_result);
+    if (JSValueIsNumber (context, value)) {
+        double n;
+
+        n = JSValueToNumber (context, value, NULL);
+        widget->length = (int)n;
+    } else {
+        g_warning ("Error running javascript: unexpected return value");
+    }
+    webkit_javascript_result_unref (js_result);
+}
+
+static void
+reload_length_cb (GtkWidget *widget,
+                  GdkRectangle *allocation,
+                  gpointer      user_data)
+{
+    GepubWidget *gwidget = GEPUB_WIDGET (widget);
+    WebKitWebView *web_view = WEBKIT_WEB_VIEW (widget);
+
+    webkit_web_view_run_javascript (web_view,
+        "window.innerWidth",
+        NULL, get_length_finished, (gpointer)widget);
+
+    if (gwidget->paginate) {
+        webkit_web_view_run_javascript (web_view,
+                // TODO: Adjusts to show a little margin at least
+                "document.querySelector('body').setAttribute('style', '"
+                    "overflow: hidden;"
+                    "column-gap: 0px;"
+                    "margin-left: 0px;"
+                    "margin-right: 0px;"
+                    "padding-left: 0px;"
+                    "padding-right: 0px;"
+                    "');"
+                "document.querySelector('body').style.columnWidth = window.innerWidth+'px';"
+                "document.querySelector('body').style.height = window.innerHeight+'px';"
+                "document.querySelector('body').scrollWidth",
+                NULL, pagination_initialize_finished, (gpointer)widget);
+    }
+}
+
+static void
+docready_cb (WebKitWebView  *web_view,
+             WebKitLoadEvent load_event,
+             gpointer        user_data)
+{
+    GepubWidget *widget = GEPUB_WIDGET (web_view);
+
+    if (load_event == WEBKIT_LOAD_FINISHED) {
+        reload_length_cb (GTK_WIDGET (widget), NULL, NULL);
+    }
+}
+
+static void
 resource_callback (WebKitURISchemeRequest *request, gpointer user_data)
 {
     GInputStream *stream;
@@ -87,6 +238,15 @@ gepub_widget_set_property (GObject      *object,
     case PROP_DOC:
         gepub_widget_set_doc (widget, g_value_get_object (value));
         break;
+    case PROP_PAGINATE:
+        gepub_widget_set_pagination (widget, g_value_get_boolean (value));
+        break;
+    case PROP_CHAPTER:
+        gepub_doc_set_page (widget->doc, g_value_get_int (value));
+        break;
+    case PROP_CHAPTER_POS:
+        gepub_widget_set_pos (widget, g_value_get_float (value));
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -105,6 +265,18 @@ gepub_widget_get_property (GObject    *object,
     case PROP_DOC:
         g_value_set_object (value, gepub_widget_get_doc (widget));
         break;
+    case PROP_PAGINATE:
+        g_value_set_boolean (value, widget->paginate);
+        break;
+    case PROP_CHAPTER:
+        g_value_set_int (value, gepub_doc_get_page (widget->doc));
+        break;
+    case PROP_N_CHAPTERS:
+        g_value_set_int (value, gepub_doc_get_n_pages (widget->doc));
+        break;
+    case PROP_CHAPTER_POS:
+        g_value_set_float (value, gepub_widget_get_pos (widget));
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
         break;
@@ -124,6 +296,11 @@ gepub_widget_finalize (GObject *object)
 static void
 gepub_widget_init (GepubWidget *widget)
 {
+    widget->chapter_length = 0;
+    widget->paginate = FALSE;
+    widget->chapter_pos = 0;
+    widget->length = 0;
+    widget->init_chapter_pos = 0;
 }
 
 static void
@@ -136,6 +313,8 @@ gepub_widget_constructed (GObject *object)
 
     ctx = webkit_web_view_get_context (WEBKIT_WEB_VIEW (widget));
     webkit_web_context_register_uri_scheme (ctx, "epub", resource_callback, widget, NULL);
+    g_signal_connect (widget, "load-changed", G_CALLBACK (docready_cb), NULL);
+    g_signal_connect (widget, "size-allocate", G_CALLBACK (reload_length_cb), NULL);
 }
 
 static void
@@ -156,6 +335,34 @@ gepub_widget_class_init (GepubWidgetClass *klass)
                              G_PARAM_READWRITE |
                              G_PARAM_STATIC_STRINGS);
 
+    properties[PROP_PAGINATE] =
+        g_param_spec_boolean ("paginate",
+                              "paginate",
+                              "If the widget should paginate",
+                              FALSE,
+                              G_PARAM_READWRITE);
+
+    properties[PROP_CHAPTER] =
+        g_param_spec_int ("chapter",
+                          "Current chapter",
+                          "Current chapter in the doc",
+                          -1, G_MAXINT, 0,
+                          G_PARAM_READWRITE);
+
+    properties[PROP_N_CHAPTERS] =
+        g_param_spec_int ("nchapters",
+                          "Number of chapters in the doc",
+                          "Number of chapters in the doc",
+                          -1, G_MAXINT, 0,
+                          G_PARAM_READABLE);
+
+    properties[PROP_CHAPTER_POS] =
+        g_param_spec_float ("chapter_pos",
+                            "Current position in chapter",
+                            "Current position in chapter",
+                            0, 100, 0,
+                            G_PARAM_READWRITE);
+
     g_object_class_install_properties (object_class, NUM_PROPS, properties);
 }
 
@@ -190,12 +397,15 @@ reload_current_chapter (GepubWidget *widget)
 {
     GBytes *current;
 
+    widget->chapter_length = 0;
+    widget->chapter_pos = 0;
+    widget->length = 0;
+
     current = gepub_doc_get_current_with_epub_uris (widget->doc);
     webkit_web_view_load_bytes (WEBKIT_WEB_VIEW (widget),
                                 current,
                                 gepub_doc_get_current_mime (widget->doc),
                                 "UTF-8", NULL);
-
     g_bytes_unref (current);
 }
 
@@ -232,3 +442,178 @@ gepub_widget_set_doc (GepubWidget *widget,
 
     g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_DOC]);
 }
+
+/**
+ * gepub_widget_set_pagination:
+ * @widget: a #GepubWidget
+ * @p: true if the widget should paginate
+ *
+ * Enable or disable pagination
+ */
+void
+gepub_widget_set_pagination (GepubWidget *widget,
+                             gboolean p)
+{
+    widget->paginate = p;
+    reload_current_chapter (widget);
+}
+
+/**
+ * gepub_widget_get_n_chapters:
+ * @widget: a #GepubWidget
+ *
+ * Returns: the number of chapters in the document
+ */
+gint
+gepub_widget_get_n_chapters (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), 0);
+    return gepub_doc_get_n_pages (widget->doc);
+}
+
+/**
+ * gepub_widget_get_chapter:
+ * @widget: a #GepubWidget
+ *
+ * Returns: the current chapter in the document
+ */
+gint
+gepub_widget_get_chapter (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), 0);
+    return gepub_doc_get_page (widget->doc);
+}
+
+/**
+ * gepub_widget_get_chapter_length:
+ * @widget: a #GepubWidget
+ *
+ * Returns: the current chapter length
+ */
+gint
+gepub_widget_get_chapter_length (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), 0);
+    return widget->chapter_length;
+}
+
+/**
+ * gepub_widget_set_chapter:
+ * @widget: a #GepubWidget
+ *
+ * Sets the current chapter in the doc
+ */
+void
+gepub_widget_set_chapter (GepubWidget *widget,
+                          gint         index)
+{
+    g_return_if_fail (GEPUB_IS_DOC (widget->doc));
+    return gepub_doc_set_page (widget->doc, index);
+}
+
+/**
+ * gepub_widget_chapter_next:
+ * @widget: a #GepubWidget
+ *
+ * Returns: TRUE on success, FALSE if there's no next chapter
+ */
+gboolean
+gepub_widget_chapter_next (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), FALSE);
+    return gepub_doc_go_next (widget->doc);
+}
+
+/**
+ * gepub_widget_chapter_prev:
+ * @widget: a #GepubWidget
+ *
+ * Returns: TRUE on success, FALSE if there's no prev chapter
+ */
+gboolean
+gepub_widget_chapter_prev (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), FALSE);
+    return gepub_doc_go_prev (widget->doc);
+}
+
+/**
+ * gepub_widget_page_next:
+ * @widget: a #GepubWidget
+ *
+ * Returns: TRUE on success, FALSE if there's no next page
+ */
+gboolean
+gepub_widget_page_next (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), FALSE);
+    widget->chapter_pos = widget->chapter_pos + widget->length;
+
+    if (widget->chapter_pos > (widget->chapter_length - widget->length)) {
+        widget->chapter_pos = (widget->chapter_length - widget->length);
+        return gepub_doc_go_next (widget->doc);
+    }
+
+    scroll_to_chapter_pos (widget);
+
+    g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_CHAPTER_POS]);
+    return TRUE;
+}
+
+/**
+ * gepub_widget_page_prev:
+ * @widget: a #GepubWidget
+ *
+ * Returns: TRUE on success, FALSE if there's no next page
+ */
+gboolean
+gepub_widget_page_prev (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), FALSE);
+    widget->chapter_pos = widget->chapter_pos - widget->length;
+
+    if (widget->chapter_pos < 0) {
+        widget->init_chapter_pos = 100;
+        return gepub_doc_go_prev (widget->doc);
+    }
+
+    scroll_to_chapter_pos (widget);
+
+    g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_CHAPTER_POS]);
+    return TRUE;
+}
+
+/**
+ * gepub_widget_get_pos:
+ * @widget: a #GepubWidget
+ *
+ * Returns: the current position in the chapter
+ */
+gfloat
+gepub_widget_get_pos (GepubWidget *widget)
+{
+    g_return_val_if_fail (GEPUB_IS_DOC (widget->doc), 0);
+
+    if (!widget->chapter_length) {
+        return 0;
+    }
+
+    return widget->chapter_pos * 100 / (float)(widget->chapter_length);
+}
+
+/**
+ * gepub_widget_set_pos:
+ * @widget: a #GepubWidget
+ *
+ * Sets the current position in the chapter
+ */
+void
+gepub_widget_set_pos (GepubWidget *widget,
+                      gfloat       index)
+{
+    g_return_if_fail (GEPUB_IS_DOC (widget->doc));
+    widget->chapter_pos = index * widget->chapter_length / 100;
+    adjust_chapter_pos (widget);
+
+    g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_CHAPTER_POS]);
+}
diff --git a/libgepub/gepub-widget.h b/libgepub/gepub-widget.h
index b77cc6a..67ed53a 100644
--- a/libgepub/gepub-widget.h
+++ b/libgepub/gepub-widget.h
@@ -46,6 +46,22 @@ GepubDoc         *gepub_widget_get_doc                         (GepubWidget *wid
 void              gepub_widget_set_doc                         (GepubWidget *widget,
                                                                 GepubDoc    *doc);
 
+void              gepub_widget_set_pagination                  (GepubWidget *widget, gboolean p);
+
+gint              gepub_widget_get_n_chapters                  (GepubWidget *widget);
+gint              gepub_widget_get_chapter                     (GepubWidget *widget);
+gint              gepub_widget_get_chapter_length              (GepubWidget *widget);
+void              gepub_widget_set_chapter                     (GepubWidget *widget,
+                                                                gint         index);
+gboolean          gepub_widget_chapter_next                    (GepubWidget *widget);
+gboolean          gepub_widget_chapter_prev                    (GepubWidget *widget);
+
+gfloat            gepub_widget_get_pos                         (GepubWidget *widget);
+void              gepub_widget_set_pos                         (GepubWidget *widget,
+                                                                gfloat       index);
+gboolean          gepub_widget_page_next                       (GepubWidget *widget);
+gboolean          gepub_widget_page_prev                       (GepubWidget *widget);
+
 G_END_DECLS
 
 #endif /* __GEPUB_WIDGET_H__ */
diff --git a/tests/test-gepub.c b/tests/test-gepub.c
index 20048d4..8f6864b 100644
--- a/tests/test-gepub.c
+++ b/tests/test-gepub.c
@@ -8,6 +8,7 @@ gchar *buf2 = NULL;
 gchar *tmpbuf;
 
 GtkTextBuffer *page_buffer;
+GtkWidget *PAGE_LABEL;
 
 #define PTEST1(...) printf (__VA_ARGS__)
 #define PTEST2(...) buf = g_strdup_printf (__VA_ARGS__);\
@@ -19,6 +20,14 @@ GtkTextBuffer *page_buffer;
 
 #define TEST(f,arg...) PTEST ("\n### TESTING " #f " ###\n\n"); f (arg); PTEST ("\n\n");
 
+static void
+reload_current_chapter (GepubWidget *widget)
+{
+    gchar *txt = g_strdup_printf ("%02.2f", gepub_widget_get_pos (widget));
+    gtk_label_set_text (GTK_LABEL (PAGE_LABEL), txt);
+    g_free (txt);
+}
+
 void
 update_text (GepubDoc *doc)
 {
@@ -69,14 +78,22 @@ void
 button_pressed (GtkButton *button, GepubWidget *widget)
 {
     GepubDoc *doc = gepub_widget_get_doc (widget);
+    printf("CLICKED %s\n",  gtk_button_get_label (button));
 
-    if (!strcmp (gtk_button_get_label (button), "prev")) {
+    if (!strcmp (gtk_button_get_label (button), "< chapter")) {
         gepub_doc_go_prev (doc);
-    } else {
+    } else if (!strcmp (gtk_button_get_label (button), "chapter >")) {
         gepub_doc_go_next (doc);
+    } else if (!strcmp (gtk_button_get_label (button), "paginated")) {
+        gboolean b = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+        gepub_widget_set_pagination (widget, b);
+    } else if (!strcmp (gtk_button_get_label (button), "< page")) {
+        gepub_widget_page_prev (widget);
+    } else if (!strcmp (gtk_button_get_label (button), "page >")) {
+        gepub_widget_page_next (widget);
     }
     update_text (doc);
-    print_replaced_text (doc);
+    //print_replaced_text (doc);
 }
 
 void
@@ -245,6 +262,11 @@ main (int argc, char **argv)
     GtkWidget *b_next;
     GtkWidget *b_prev;
 
+    GtkWidget *p_next;
+    GtkWidget *p_prev;
+
+    GtkWidget *paginate;
+
     GtkTextBuffer *buffer;
 
     GepubDoc *doc;
@@ -258,7 +280,7 @@ main (int argc, char **argv)
 
     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
     g_signal_connect (window, "destroy", (GCallback)gtk_main_quit, NULL);
-    gtk_widget_set_size_request (GTK_WIDGET (window), 800, 500);
+    gtk_widget_set_size_request (GTK_WIDGET (window), 1200, 800);
     vpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
     gtk_container_add (GTK_CONTAINER (window), vpaned);
 
@@ -285,12 +307,33 @@ main (int argc, char **argv)
 
     vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
     hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
-    b_prev = gtk_button_new_with_label ("prev");
+    b_prev = gtk_button_new_with_label ("< chapter");
     g_signal_connect (b_prev, "clicked", (GCallback)button_pressed, GEPUB_WIDGET (widget));
-    b_next = gtk_button_new_with_label ("next");
+    b_next = gtk_button_new_with_label ("chapter >");
     g_signal_connect (b_next, "clicked", (GCallback)button_pressed, GEPUB_WIDGET (widget));
+
+    p_prev = gtk_button_new_with_label ("< page");
+    g_signal_connect (p_prev, "clicked", (GCallback)button_pressed, GEPUB_WIDGET (widget));
+    p_next = gtk_button_new_with_label ("page >");
+    g_signal_connect (p_next, "clicked", (GCallback)button_pressed, GEPUB_WIDGET (widget));
+
+    PAGE_LABEL = gtk_label_new ("0");
+
+    g_signal_connect_swapped (widget, "notify",
+                              G_CALLBACK (reload_current_chapter), widget);
+
+    paginate = gtk_check_button_new_with_label ("paginated");
+    g_signal_connect (paginate, "clicked", (GCallback)button_pressed, GEPUB_WIDGET (widget));
+
     gtk_container_add (GTK_CONTAINER (hbox), b_prev);
     gtk_container_add (GTK_CONTAINER (hbox), b_next);
+
+    gtk_container_add (GTK_CONTAINER (hbox), p_prev);
+    gtk_container_add (GTK_CONTAINER (hbox), p_next);
+
+    gtk_container_add (GTK_CONTAINER (hbox), PAGE_LABEL);
+    gtk_container_add (GTK_CONTAINER (hbox), paginate);
+
     gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 5);
     gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 5);
 
@@ -300,7 +343,7 @@ main (int argc, char **argv)
     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_AUTOMATIC, 
GTK_POLICY_AUTOMATIC);
     gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 5);
 
-    gtk_widget_set_size_request (GTK_WIDGET (vbox), 400, 500);
+    gtk_widget_set_size_request (GTK_WIDGET (vbox), 600, 500);
     gtk_paned_add1 (GTK_PANED (vpaned), vbox);
     gtk_paned_add2 (GTK_PANED (vpaned), widget);
 


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