[epiphany] Alternative HTML-based implementation for the overview



commit 65a1e4d1d4a46dd5ff781a3d1fa78879e2efbc4c
Author: Lorenzo Tilve <ltilve igalia com>
Date:   Mon Dec 9 11:58:52 2013 +0100

    Alternative HTML-based implementation for the overview
    
    Provides a basic alternative HTML implementation for the
    overview, that can be accessed at 'about:html-overview'.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=723950

 embed/ephy-about-handler.c               |   87 +++++++++++++++++
 embed/ephy-embed-shell.c                 |   37 +++++++
 embed/web-extension/ephy-web-extension.c |  102 ++++++++++++++++++++-
 src/Makefile.am                          |    1 +
 src/epiphany.gresource.xml               |    1 +
 src/resources/overview.html              |  151 ++++++++++++++++++++++++++++++
 6 files changed, 378 insertions(+), 1 deletions(-)
---
diff --git a/embed/ephy-about-handler.c b/embed/ephy-about-handler.c
index fcadd07..13aeb92 100644
--- a/embed/ephy-about-handler.c
+++ b/embed/ephy-about-handler.c
@@ -26,6 +26,7 @@
 #include "ephy-file-helpers.h"
 #include "ephy-smaps.h"
 #include "ephy-web-app-utils.h"
+#include "ephy-embed-private.h"
 
 #include <gio/gio.h>
 #include <gtk/gtk.h>
@@ -36,6 +37,8 @@ struct _EphyAboutHandlerPrivate {
   EphySMaps *smaps;
 };
 
+#define EPHY_PAGE_TEMPLATE_OVERVIEW         "/org/gnome/epiphany/page-templates/overview.html"
+
 G_DEFINE_TYPE (EphyAboutHandler, ephy_about_handler, G_TYPE_OBJECT)
 
 static void
@@ -426,6 +429,88 @@ ephy_about_handler_handle_applications (EphyAboutHandler *handler,
   return TRUE;
 }
 
+static GString *
+ephy_about_handler_generate_overview_html (EphyOverviewStore *store)
+{
+  GtkTreeIter iter;
+  GString *data_str;
+  char *row_url, *row_title, *row_snapshot_base64;
+  GdkPixbuf *row_snapshot;
+  guchar *buffer;
+  gsize buffersize;
+  gboolean valid;
+
+  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
+  data_str = g_string_new (NULL);
+
+  while (valid) {
+    gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+      EPHY_OVERVIEW_STORE_URI, &row_url,
+      EPHY_OVERVIEW_STORE_TITLE, &row_title,
+      EPHY_OVERVIEW_STORE_SNAPSHOT, &row_snapshot,
+      -1);
+
+    /* FIXME: use a more efficient way to get the base64 snapshot */
+    gdk_pixbuf_save_to_buffer (row_snapshot, (gchar **) &buffer, &buffersize, "png", NULL, NULL);
+    row_snapshot_base64 = g_base64_encode (buffer, buffersize);
+    g_free (buffer);
+
+    g_string_append_printf (data_str,
+      "<li>" \
+      "  <a class=\"overview-item\" title=\"%s\" href=\"%s\">" \
+      "    <div class=\"close-button\" onclick=\"removeFromOverview(this.parentNode,event)\" 
title=\"%s\">&#10006;</div>" \
+      "    <span class=\"thumbnail\" style=\"background-image: url(data:image/png;base64,%s);\"></span>" \
+      "    <span class=\"title\">%s</span>" \
+      "  </a>" \
+      "</li>",
+      g_markup_escape_text (row_title,-1), row_url, _("Remove from overview"),row_snapshot_base64, 
row_title);
+
+    g_free (row_title);
+    g_free (row_url);
+    g_free (row_snapshot_base64);
+
+    valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
+  }
+  return data_str;
+}
+
+
+static gboolean
+ephy_about_handler_handle_html_overview (EphyAboutHandler *handler,
+                                        WebKitURISchemeRequest *request)
+{
+  GString *data_str;
+  GBytes *html_file;
+  GString *html = g_string_new ("");
+  gsize data_length;
+  EphyOverviewStore *store;
+  char *lang;
+
+  store = EPHY_OVERVIEW_STORE (ephy_embed_shell_get_frecent_store (ephy_embed_shell_get_default ()));
+
+  data_str = ephy_about_handler_generate_overview_html(store);
+  html_file = g_resources_lookup_data (EPHY_PAGE_TEMPLATE_OVERVIEW, 0, NULL);
+
+  lang = g_strdup (pango_language_to_string (gtk_get_default_language ()));
+  g_strdelimit (lang, "_-@", '\0');
+
+  g_string_printf (html,
+                   g_bytes_get_data (html_file, NULL),
+                   lang, lang,
+                   ((gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) ? "rtl" : "ltr"),
+                   _("Most visited"),
+                   data_str->str);
+
+  data_length = html->len;
+  ephy_about_handler_finish_request (request, g_string_free (html, FALSE), data_length);
+
+  g_string_free (data_str,TRUE);
+  g_bytes_unref (html_file);
+  g_free (lang);
+
+  return TRUE;
+}
+
 static gboolean
 ephy_about_handler_handle_incognito (EphyAboutHandler *handler,
                                      WebKitURISchemeRequest *request)
@@ -511,6 +596,8 @@ ephy_about_handler_handle_request (EphyAboutHandler *handler,
     handled =  ephy_about_handler_handle_epiphany (handler, request);
   else if (!g_strcmp0 (path, "applications"))
     handled = ephy_about_handler_handle_applications (handler, request);
+  else if (!g_strcmp0 (path, "html-overview"))
+    handled = ephy_about_handler_handle_html_overview (handler, request);
   else if (!g_strcmp0 (path, "incognito"))
     handled = ephy_about_handler_handle_incognito (handler, request);
   else if (path == NULL || path[0] == '\0' || !g_strcmp0 (path, "Web") || !g_strcmp0 (path, "web"))
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index c902db2..0d744c3 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -58,6 +58,7 @@ struct _EphyEmbedShellPrivate
   GList *web_extensions;
   guint web_extensions_page_created_signal_id;
   guint web_extensions_form_auth_save_signal_id;
+  guint web_extensions_remove_from_overview_signal_id;
 };
 
 enum
@@ -161,6 +162,26 @@ web_extension_page_created (GDBusConnection *connection,
 }
 
 static void
+web_extension_remove_from_overview (GDBusConnection *connection,
+                                    const char *sender_name,
+                                    const char *object_path,
+                                    const char *interface_name,
+                                    const char *signal_name,
+                                    GVariant *parameters,
+                                    EphyEmbedShell *shell)
+{
+  GtkTreeIter iter;
+  EphyFrecentStore *store;
+  const char *url_to_remove;
+
+  g_variant_get (parameters, "(&s)", &url_to_remove);
+  store = ephy_embed_shell_get_frecent_store (ephy_embed_shell_get_default ());
+  if (ephy_overview_store_find_url (EPHY_OVERVIEW_STORE(store), url_to_remove, &iter)) {
+         ephy_frecent_store_set_hidden(store, &iter);
+  }
+}
+
+static void
 web_extension_destroyed (EphyEmbedShell *shell,
                          GObject *web_extension)
 {
@@ -356,6 +377,17 @@ ephy_embed_shell_setup_web_extensions_connection (EphyEmbedShell *shell)
                                         (GDBusSignalCallback)web_extension_form_auth_save_requested,
                                         shell,
                                         NULL);
+  shell->priv->web_extensions_remove_from_overview_signal_id =
+    g_dbus_connection_signal_subscribe (shell->priv->bus,
+                                        NULL,
+                                        EPHY_WEB_EXTENSION_INTERFACE,
+                                        "RemoveItemFromOverview",
+                                        EPHY_WEB_EXTENSION_OBJECT_PATH,
+                                        NULL,
+                                        G_DBUS_SIGNAL_FLAGS_NONE,
+                                        (GDBusSignalCallback)web_extension_remove_from_overview,
+                                        shell,
+                                        NULL);
 }
 
 static void
@@ -445,6 +477,11 @@ ephy_embed_shell_shutdown (GApplication* application)
     priv->web_extensions_form_auth_save_signal_id = 0;
   }
 
+  if (priv->web_extensions_remove_from_overview_signal_id > 0) {
+    g_dbus_connection_signal_unsubscribe (priv->bus, priv->web_extensions_remove_from_overview_signal_id);
+    priv->web_extensions_remove_from_overview_signal_id = 0;
+  }
+
   g_list_foreach (priv->web_extensions, (GFunc)ephy_embed_shell_unwatch_web_extension, application);
 
   ephy_embed_prefs_shutdown ();
diff --git a/embed/web-extension/ephy-web-extension.c b/embed/web-extension/ephy-web-extension.c
index d6c051f..d5197bd 100644
--- a/embed/web-extension/ephy-web-extension.c
+++ b/embed/web-extension/ephy-web-extension.c
@@ -34,7 +34,7 @@
 #include <gtk/gtk.h>
 #include <libsoup/soup.h>
 #include <webkit2/webkit-web-extension.h>
-
+#include <JavaScriptCore/JavaScript.h>
 
 /* FIXME: These global variables should be freed somehow. */
 static UriTester *uri_tester;
@@ -69,6 +69,9 @@ static const char introspection_xml[] =
   "   <arg type='s' name='hostname' direction='out'/>"
   "   <arg type='s' name='username' direction='out'/>"
   "  </signal>"
+  "  <signal name='RemoveItemFromOverview'>"
+  "   <arg type='s' name='url' direction='out'/>"
+  "  </signal>"
   "  <method name='FormAuthDataSaveConfirmationResponse'>"
   "   <arg type='u' name='request_id' direction='in'/>"
   "   <arg type='b' name='should_store' direction='in'/>"
@@ -238,6 +241,24 @@ request_decision_on_storing (EphyEmbedFormAuth *form_auth)
 }
 
 static void
+remove_from_overview_emit_signal (char *url)
+{
+  GError *error = NULL;
+
+  g_dbus_connection_emit_signal (dbus_connection,
+                                 NULL,
+                                 EPHY_WEB_EXTENSION_OBJECT_PATH,
+                                 EPHY_WEB_EXTENSION_INTERFACE,
+                                 "RemoveItemFromOverview",
+                                 g_variant_new ("(s)", url),
+                                 &error);
+  if (error) {
+    g_debug ("Error emitting signal RemoveItemFromOverview: %s\n", error->message);
+    g_error_free (error);
+  }
+}
+
+static void
 should_store_cb (const char *username,
                  const char *password,
                  gpointer user_data)
@@ -1151,6 +1172,80 @@ bus_acquired_cb (GDBusConnection *connection,
   }
 }
 
+
+static JSValueRef
+remove_from_overview_cb (JSContextRef context,
+                         JSObjectRef function,
+                         JSObjectRef this_object,
+                         size_t argument_count,
+                         const JSValueRef arguments[],
+                         JSValueRef *exception)
+{
+  JSStringRef result_string_js;
+  size_t max_size;
+  char *result_string;
+
+  result_string_js = JSValueToStringCopy (context, arguments[0], NULL);
+  max_size = JSStringGetMaximumUTF8CStringSize (result_string_js);
+
+  result_string = g_malloc (max_size);
+  JSStringGetUTF8CString (result_string_js, result_string, max_size);
+  remove_from_overview_emit_signal (result_string);
+
+  JSStringRelease (result_string_js);
+  g_free (result_string);
+
+  return JSValueMakeUndefined (context);
+}
+
+static const JSStaticFunction overview_staticfuncs[] =
+{
+  { "removeItemFromOverview", remove_from_overview_cb, kJSPropertyAttributeReadOnly | 
kJSPropertyAttributeDontDelete },
+  { NULL, NULL, 0 }
+};
+
+static const JSClassDefinition overview_notification_def =
+{
+  0,                     /* version */
+  kJSClassAttributeNone, /* attributes */
+  "Overview",            /* className */
+  NULL,                  /* parentClass */
+  NULL,                  /* staticValues */
+  overview_staticfuncs,  /* staticFunctions */
+  NULL,                  /* initialize */
+  NULL,                  /* finalize */
+  NULL,                  /* hasProperty */
+  NULL,                  /* getProperty */
+  NULL,                  /* setProperty */
+  NULL,                  /* deleteProperty */
+  NULL,                  /* getPropertyNames */
+  NULL,                  /* callAsFunction */
+  NULL,                  /* callAsConstructor */
+  NULL,                  /* hasInstance */
+  NULL                   /* convertToType */
+};
+
+static void
+window_object_cleared_cb (WebKitScriptWorld *world,
+                          WebKitWebPage     *web_page,
+                          WebKitFrame       *frame,
+                          gpointer           user_data)
+{
+  JSGlobalContextRef context;
+  JSObjectRef global_object;
+  JSClassRef class_def;
+  JSObjectRef class_object;
+  JSStringRef str;
+
+  context = webkit_frame_get_javascript_context_for_script_world (frame, world);
+  global_object = JSContextGetGlobalObject (context);
+
+  class_def = JSClassCreate (&overview_notification_def);
+  class_object = JSObjectMake (context, class_def, context);
+  str = JSStringCreateWithUTF8CString ("Overview");
+  JSObjectSetProperty (context, global_object, str, class_object, kJSPropertyAttributeNone, NULL);
+}
+
 G_MODULE_EXPORT void
 webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension,
                                                 GVariant *user_data)
@@ -1180,4 +1275,9 @@ webkit_web_extension_initialize_with_user_data (WebKitWebExtension *extension,
                   g_object_ref (extension),
                   (GDestroyNotify)g_object_unref);
   g_free (service_name);
+
+  g_signal_connect (webkit_script_world_get_default (),
+                    "window-object-cleared",
+                    G_CALLBACK (window_object_cleared_cb),
+                    NULL);
 }
diff --git a/src/Makefile.am b/src/Makefile.am
index 29b60e3..9439f9f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -116,6 +116,7 @@ RESOURCE_FILES = \
        resources/incognito.png                   \
        resources/incognito-tinted.png            \
        resources/error.html                      \
+       resources/overview.html                   \
        $(NULL)
 
 epiphany-resources.c: epiphany.gresource.xml $(RESOURCE_FILES)
diff --git a/src/epiphany.gresource.xml b/src/epiphany.gresource.xml
index 946428c..6908b99 100644
--- a/src/epiphany.gresource.xml
+++ b/src/epiphany.gresource.xml
@@ -15,5 +15,6 @@
     <file preprocess="xml-stripblanks">epiphany-bookmark-editor-ui.xml</file>
     <file>epiphany.css</file>
     <file alias="page-templates/error.html" compressed="true">error.html</file>
+    <file alias="page-templates/overview.html" compressed="true">overview.html</file>
   </gresource>
 </gresources>
diff --git a/src/resources/overview.html b/src/resources/overview.html
new file mode 100755
index 0000000..1a88869
--- /dev/null
+++ b/src/resources/overview.html
@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<!--
+  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, 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 02110-1301, USA.
+-->
+<html xml:lang="%s" lang="%s" dir="%s">
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+  <title>%s</title>
+  <style type="text/css">
+  body {
+      background-color: #f6f6f4;
+      background-image: -webkit-gradient(
+          linear,
+          left top,
+          left bottom,
+          color-stop(0, #eeeeec),
+          color-stop(1, #f6f6f4)
+      );
+      background-size: 100% 5em;
+      background-repeat: no-repeat;
+  }
+
+  #overview {
+      padding: 60px 5px;
+      max-width: 1200px;
+      margin-left: auto;
+      margin-right: auto;
+  }
+
+  #grid {
+      padding: 0;
+  }
+
+  #grid li {
+      list-style-type: none;
+  }
+
+  .overview-item {
+      width: 200px;
+      height: 200px;
+      float: left;
+      margin: 15px;
+      outline: 0;
+      transition: all 0.5s ease-in-out;
+  }
+
+  .title {
+      width: 100%;
+      height: 50px;
+      display: inline-block;
+      padding: 5px 0;
+      overflow: hidden;
+      font-family: Cantarell, sans-serif;
+      font-size: 11pt;
+      color: #2e3436;
+      text-overflow: ellipsis;
+      text-align: center;
+  }
+
+  .thumbnail {
+      width: 200px;
+      height: 150px;
+      display: block;
+      opacity: 0.6;
+      transition: opacity 0.2s ease-in-out;
+      background-repeat: no-repeat;
+      animation: fadeOut 0.5s;
+  }
+
+  .thumbnail:hover,
+  :focus .thumbnail {
+      opacity: 1.0;
+  }
+
+  .close-button {
+      -webkit-transition: opacity 250ms;
+      position: relative;
+      float: right;
+      top: -10px;
+      right: -5px;
+      opacity: 0;
+      z-index: 5;
+      color: #888;
+      font-size: 1.2em;
+  }
+
+  .overview-item:hover .close-button {
+      opacity: 1;
+  }
+
+  .removed .overview-item {
+      height: 0;
+      width: 0;
+      margin: 15px 0;
+      opacity: 0;
+  }
+
+  .removed .close-button {
+      display: none;
+  }
+  </style>
+
+  <script>
+
+  document.onkeypress =  listenKeypress;
+
+  function listenKeypress(event) {
+    // Remove from overview when Del is pressed
+    if (event.which == 127) {
+      var focused = document.activeElement;
+      if (focused.className == "overview-item") {
+        removeFromOverview(focused, event);
+      }
+    }
+  }
+
+  function removeFromOverview(elem, event) {
+    var listItemNode = elem.parentElement;
+    event.preventDefault();
+    listItemNode.className +=" removed ";
+    Overview.removeItemFromOverview(elem.href);
+  }
+  </script>
+
+</head>
+
+<body>
+
+  <div id="overview">
+    <div id="grid">
+      <ul>
+        %s
+      </ul>
+     </div>
+   </div>
+
+</body>
+
+</html>


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