[gnome-documents/wip/gepub: 1/4] epub: Initial epub support
- From: Cosimo Cecchi <cosimoc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-documents/wip/gepub: 1/4] epub: Initial epub support
- Date: Sat, 11 Jun 2016 22:59:55 +0000 (UTC)
commit 98d3d16242589dfe2af0f2689e2c22bf22c08bc2
Author: Daniel Garcia Moreno <danigm wadobo com>
Date: Sat Jun 11 12:33:39 2016 +0200
epub: Initial epub support
This change adds the epub document preview using libgepub and webkit to
show the content.
https://bugzilla.gnome.org/show_bug.cgi?id=740971
configure.ac | 1 +
src/documents.js | 14 +-
src/embed.js | 35 +++
src/epubview.js | 459 +++++++++++++++++++++++++++++
src/org.gnome.Books.src.gresource.xml | 1 +
src/org.gnome.Documents.src.gresource.xml | 1 +
src/windowMode.js | 7 +-
7 files changed, 512 insertions(+), 6 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 33b7088..214b5f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -123,6 +123,7 @@ AX_CHECK_GIRS_GJS([Tracker], [1.0])
AX_CHECK_GIRS_GJS([TrackerControl], [1.0])
AX_CHECK_GIRS_GJS([WebKit2], [4.0])
AX_CHECK_GIRS_GJS([Zpj], [0.0])
+AX_CHECK_GIRS_GJS([Gepub], [0.3])
GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0`
AC_SUBST(GLIB_COMPILE_RESOURCES)
diff --git a/src/documents.js b/src/documents.js
index 53f7b4a..3c08302 100644
--- a/src/documents.js
+++ b/src/documents.js
@@ -22,6 +22,7 @@
const EvDocument = imports.gi.EvinceDocument;
const EvView = imports.gi.EvinceView;
const LOKView = imports.lokview;
+const EPUBView = imports.epubview;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio;
const Gd = imports.gi.Gd;
@@ -49,7 +50,8 @@ const Utils = imports.utils;
const ViewType = {
NONE: 0,
EV: 1,
- LOK: 2
+ LOK: 2,
+ EPUB: 3
};
const DeleteItemJob = new Lang.Class({
@@ -601,8 +603,7 @@ const DocCommon = new Lang.Class({
},
loadLocal: function(passwd, cancellable, callback) {
- if (this.mimeType == 'application/epub+zip' ||
- this.mimeType == 'application/x-mobipocket-ebook' ||
+ if (this.mimeType == 'application/x-mobipocket-ebook' ||
this.mimeType == 'application/x-fictionbook+xml' ||
this.mimeType == 'application/x-zip-compressed-fb2') {
let exception = new GLib.Error(Gio.IOErrorEnum,
@@ -623,6 +624,11 @@ const DocCommon = new Lang.Class({
return;
}
+ if (EPUBView.isEpub(this.mimeType) && Application.application.isBooks) {
+ callback(this, null, null);
+ return;
+ }
+
GdPrivate.pdf_loader_load_uri_async(this.uri, passwd, cancellable, Lang.bind(this,
function(source, res) {
try {
@@ -704,6 +710,8 @@ const DocCommon = new Lang.Class({
updateViewType: function() {
if (LOKView.isOpenDocumentFormat(this.mimeType) && !Application.application.isBooks) {
this.viewType = ViewType.LOK;
+ } else if (EPUBView.isEpub(this.mimeType) && Application.application.isBooks) {
+ this.viewType = ViewType.EPUB;
} else {
this.viewType = ViewType.EV;
}
diff --git a/src/embed.js b/src/embed.js
index cc7cdc6..9703c18 100644
--- a/src/embed.js
+++ b/src/embed.js
@@ -35,6 +35,7 @@ const Documents = imports.documents;
const EvView = imports.gi.EvinceView;
const EvinceView = imports.evinceview;
const LOKView = imports.lokview;
+const EPUBView = imports.epubview;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const _ = imports.gettext.gettext;
@@ -86,6 +87,9 @@ const Embed = new Lang.Class({
this._previewEv = new EvinceView.EvinceView(this._stackOverlay);
this._stack.add_named(this._previewEv, 'preview-ev');
+ this._previewEPUB = new EPUBView.EPUBView(this._stackOverlay);
+ this._stack.add_named(this._previewEPUB, 'preview-epub');
+
this._previewLok = new LOKView.LOKView(this._stackOverlay);
this._stack.add_named(this._previewLok, 'preview-lok');
@@ -152,6 +156,9 @@ const Embed = new Lang.Class({
case WindowMode.WindowMode.PREVIEW_LOK:
view = this._previewLok;
break;
+ case WindowMode.WindowMode.PREVIEW_EPUB:
+ view = this._previewEPUB;
+ break;
case WindowMode.WindowMode.SEARCH:
view = this._search;
break;
@@ -192,6 +199,9 @@ const Embed = new Lang.Class({
case WindowMode.WindowMode.PREVIEW_LOK:
page = 'preview-lok';
break;
+ case WindowMode.WindowMode.PREVIEW_EPUB:
+ page = 'preview-epub';
+ break;
default:
throw(new Error('Not handled'));
break;
@@ -280,6 +290,9 @@ const Embed = new Lang.Class({
Application.documentManager.reloadActiveItem();
this._prepareForLOKView();
break;
+ case WindowMode.WindowMode.PREVIEW_EPUB:
+ this._prepareForEPUBView();
+ break;
case WindowMode.WindowMode.EDIT:
this._prepareForEdit();
break;
@@ -339,6 +352,8 @@ const Embed = new Lang.Class({
_onLoadStarted: function(manager, doc) {
if (LOKView.isOpenDocumentFormat(doc.mimeType))
Application.modeController.setWindowMode(WindowMode.WindowMode.PREVIEW_LOK);
+ else if (EPUBView.isEpub(doc.mimeType))
+ Application.modeController.setWindowMode(WindowMode.WindowMode.PREVIEW_EPUB);
else
Application.modeController.setWindowMode(WindowMode.WindowMode.PREVIEW_EV);
@@ -374,6 +389,9 @@ const Embed = new Lang.Class({
case Documents.ViewType.LOK:
this._stack.set_visible_child_name('preview-lok');
break;
+ case Documents.ViewType.EPUB:
+ this._stack.set_visible_child_name('preview-epub');
+ break;
case Documents.ViewType.NONE:
default:
log('Something bad happened and the document type is unset');
@@ -482,6 +500,23 @@ const Embed = new Lang.Class({
this._stack.set_visible_child_name('preview-lok');
},
+ _prepareForEPUBView: function() {
+ if (this._previewEv)
+ this._previewEv.setModel(null);
+ if (this._edit)
+ this._edit.setUri(null);
+ if (this._toolbar)
+ this._toolbar.destroy();
+
+ this._previewEPUB.reset();
+
+ // pack the toolbar
+ this._toolbar = new EPUBView.EPUBViewToolbar(this._previewEPUB);
+ this._titlebar.add(this._toolbar);
+
+ this._stack.set_visible_child_name('preview-epub');
+ },
+
getMainToolbar: function() {
let windowMode = Application.modeController.getWindowMode();
let fullscreen = Application.modeController.getFullscreen();
diff --git a/src/epubview.js b/src/epubview.js
new file mode 100644
index 0000000..7697615
--- /dev/null
+++ b/src/epubview.js
@@ -0,0 +1,459 @@
+/*
+ * Copyright (c) 2016 Daniel Garcia <danigm wadobo com>
+ *
+ * Gnome Documents 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.
+ *
+ * Gnome Documents 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 Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Daniel Garcia <danigm wadobo com>
+ *
+ */
+
+const GLib = imports.gi.GLib;
+const Gdk = imports.gi.Gdk;
+const Gepub = imports.gi.Gepub;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const WebKit2 = imports.gi.WebKit2;
+
+const _ = imports.gettext.gettext;
+
+const Lang = imports.lang;
+
+const Application = imports.application;
+const Documents = imports.documents;
+const ErrorBox = imports.errorBox;
+const MainToolbar = imports.mainToolbar;
+const Searchbar = imports.searchbar;
+const WindowMode = imports.windowMode;
+
+const Mainloop = imports.mainloop;
+const Signals = imports.signals;
+const Tweener = imports.tweener.tweener;
+
+function isEpub(mimeType) {
+ return (mimeType == 'application/epub+zip');
+}
+
+const EPUBView = new Lang.Class({
+ Name: 'EPUBView',
+ Extends: Gtk.Stack,
+
+ _init: function(overlay) {
+ this.parent({ homogeneous: true,
+ transition_type: Gtk.StackTransitionType.CROSSFADE });
+
+ this._uri = null;
+ this._overlay = overlay;
+ this.page = 1;
+
+ this._errorBox = new ErrorBox.ErrorBox();
+ this.add_named(this._errorBox, 'error');
+
+ this._sw = new Gtk.ScrolledWindow({ hexpand: true,
+ vexpand: true });
+
+ this.add_named(this._sw, 'view');
+ this._createView();
+
+ this.show_all();
+
+ Application.documentManager.connect('load-started',
+ Lang.bind(this, this._onLoadStarted));
+ Application.documentManager.connect('load-error',
+ Lang.bind(this, this._onLoadError));
+ Application.modeController.connect('window-mode-changed', Lang.bind(this,
+ this._onWindowModeChanged));
+
+ let findPrev = Application.application.lookup_action('find-prev');
+ let findPrevId = findPrev.connect('activate', Lang.bind(this, this._findPrev));
+ let findNext = Application.application.lookup_action('find-next');
+ let findNextId = findNext.connect('activate', Lang.bind(this, this._findNext));
+ },
+
+ _findNext: function() {
+ let fc = this.view.get_find_controller();
+ fc.search_next();
+ },
+
+ _findPrev: function() {
+ let fc = this.view.get_find_controller();
+ fc.search_previous();
+ },
+
+ _onWindowModeChanged: function() {
+ let windowMode = Application.modeController.getWindowMode();
+ if (windowMode != WindowMode.WindowMode.PREVIEW_EPUB) {
+ this._navControls.hide();
+ }
+ },
+
+ _onLoadStarted: function(manager, doc) {
+ if (doc.viewType != Documents.ViewType.EPUB)
+ return;
+
+ let f = Gio.File.new_for_uri(doc.uri);
+ this._doc = doc;
+ this._epubdoc = new Gepub.Doc({ path: f.get_path() });
+ this._epubdoc.init(null);
+ this._epubSpine = this._epubdoc.get_spine();
+ this._load_current();
+ this.set_visible_child_name('view');
+ },
+
+ _onLoadError: function(manager, doc, message, exception) {
+ if (doc.viewType != Documents.ViewType.EPUB)
+ return;
+ this._setError(message, exception.message);
+ },
+
+ _getResource: function(req) {
+ var uri = req.get_uri();
+ // removing "epub://"
+ var path = uri.slice(7);
+ var stream = new Gio.MemoryInputStream();
+ var data = this._epubdoc.get_resource_v(path);
+ var mime = this._epubdoc.get_resource_mime(path);
+ stream.add_data(data);
+ req.finish(stream, data.length, mime);
+ },
+
+ reset: function () {
+ if (!this.view)
+ return;
+
+ this.set_visible_child_full('view', Gtk.StackTransitionType.NONE);
+ this.page = 1;
+ this._navControls.show();
+ },
+
+ _createView: function() {
+ this.view = new WebKit2.WebView();
+ var ctx = this.view.get_context();
+ ctx.register_uri_scheme("epub", Lang.bind(this, this._getResource));
+
+ this._sw.add(this.view);
+ this.view.show();
+
+ this._navControls = new EPUBViewNavControls(this, this._overlay);
+ this.set_visible_child_full('view', Gtk.StackTransitionType.NONE);
+ },
+
+ _setError: function(primary, secondary) {
+ this._errorBox.update(primary, secondary);
+ this.set_visible_child_name('error');
+ },
+
+ goNext: function() {
+ if (this._epubdoc.go_next()) {
+ this.page++;
+ this._load_current();
+ }
+ },
+
+ goPrev: function() {
+ if (this._epubdoc.go_prev()) {
+ this.page--;
+ this._load_current();
+ }
+ },
+
+ _load_current: function() {
+ var mime = this._epubdoc.get_current_mime();
+ var current = this._epubdoc.get_current_with_epub_uris ();
+ this.view.load_bytes(new GLib.Bytes(current), mime, "UTF-8", null);
+ }
+});
+
+const EPUBSearchbar = new Lang.Class({
+ Name: 'EPUBSearchbar',
+ Extends: Searchbar.Searchbar,
+
+ _init: function(previewView) {
+ this._previewView = previewView;
+ this.parent();
+ },
+
+ createSearchWidgets: function() {
+ this._searchContainer = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
+ halign: Gtk.Align.CENTER});
+ this._searchContainer.get_style_context().add_class('linked');
+
+ this._searchEntry = new Gtk.SearchEntry({ width_request: 500 });
+ this._searchEntry.connect('activate', Lang.bind(this, function() {
+ Application.application.activate_action('find-next', null);
+ }));
+ this._searchContainer.add(this._searchEntry);
+
+ this._prev = new Gtk.Button({ action_name: 'app.find-prev' });
+ this._prev.set_image(new Gtk.Image({ icon_name: 'go-up-symbolic',
+ icon_size: Gtk.IconSize.MENU }));
+ this._prev.set_tooltip_text(_("Find Previous"));
+ this._searchContainer.add(this._prev);
+
+ this._next = new Gtk.Button({ action_name: 'app.find-next' });
+ this._next.set_image(new Gtk.Image({ icon_name: 'go-down-symbolic',
+ icon_size: Gtk.IconSize.MENU }));
+ this._next.set_tooltip_text(_("Find Next"));
+ this._searchContainer.add(this._next);
+
+ let fc = this._previewView.view.get_find_controller();
+ fc.connect('found-text', Lang.bind(this, function(w, match_count, data) {
+ this._onSearchChanged(this._previewView, match_count > 0);
+ }));
+
+ this._onSearchChanged(this._previewView, false);
+ },
+
+ _onSearchChanged: function(view, results) {
+ let findPrev = Application.application.lookup_action('find-prev');
+ let findNext = Application.application.lookup_action('find-next');
+ findPrev.enabled = results;
+ findNext.enabled = results;
+ },
+
+ _search: function(str) {
+ let fc = this._previewView.view.get_find_controller();
+ fc.search(str, WebKit2.FindOptions.CASE_INSENSITIVE, 0);
+ },
+
+ entryChanged: function() {
+ this._search(this._searchEntry.get_text());
+ },
+
+ reveal: function() {
+ this.parent();
+ this._search(this._searchEntry.get_text());
+ },
+
+ conceal: function() {
+ this._search("");
+ let fc = this._previewView.view.get_find_controller();
+ fc.search_finish();
+
+ this.searchChangeBlocked = true;
+ this.parent();
+ this.searchChangeBlocked = false;
+ }
+});
+
+const EPUBViewToolbar = new Lang.Class({
+ Name: 'EPUBViewToolbar',
+ Extends: MainToolbar.MainToolbar,
+
+ _init: function(previewView) {
+ this._previewView = previewView;
+
+ this.parent();
+ this.toolbar.set_show_close_button(true);
+
+ this._handleEvent = false;
+ this._model = null;
+
+ this._searchAction = Application.application.lookup_action('search');
+ this._searchAction.enabled = true;
+
+ this._gearMenu = Application.application.lookup_action('gear-menu');
+ this._gearMenu.enabled = true;
+
+ // back button, on the left of the toolbar
+ let backButton = this.addBackButton();
+ backButton.connect('clicked', Lang.bind(this, function() {
+ Application.documentManager.setActiveItem(null);
+ Application.modeController.goBack();
+ }));
+
+ // search button, on the right of the toolbar
+ this.addSearchButton();
+
+ this._setToolbarTitle();
+ this.toolbar.show_all();
+ },
+
+ createSearchbar: function() {
+ return new EPUBSearchbar(this._previewView);
+ },
+
+ _setToolbarTitle: function() {
+ let primary = null;
+ let doc = Application.documentManager.getActiveItem();
+
+ if (doc)
+ primary = doc.name;
+
+ this.toolbar.set_title(primary);
+ },
+});
+
+const _PREVIEW_NAVBAR_MARGIN = 30;
+const _AUTO_HIDE_TIMEOUT = 2;
+
+const EPUBViewNavControls = new Lang.Class({
+ Name: 'EPUBViewNavControls',
+
+ _init: function(epubView, overlay) {
+ this._epubView = epubView;
+ this._overlay = overlay;
+
+ this._visible = false;
+ this._visibleInternal = false;
+ this._pageChangedId = 0;
+ this._autoHideId = 0;
+ this._motionId = 0;
+
+ this.prev_widget = new Gtk.Button({ image: new Gtk.Image ({ icon_name: 'go-previous-symbolic',
+ pixel_size: 16 }),
+ margin: _PREVIEW_NAVBAR_MARGIN,
+ halign: Gtk.Align.START,
+ valign: Gtk.Align.CENTER });
+ this.prev_widget.get_style_context().add_class('osd');
+ this._overlay.add_overlay(this.prev_widget);
+ this.prev_widget.connect('clicked', Lang.bind(this, this._onPrevClicked));
+ this.prev_widget.connect('enter-notify-event', Lang.bind(this, this._onEnterNotify));
+ this.prev_widget.connect('leave-notify-event', Lang.bind(this, this._onLeaveNotify));
+
+ this.next_widget = new Gtk.Button({ image: new Gtk.Image ({ icon_name: 'go-next-symbolic',
+ pixel_size: 16 }),
+ margin: _PREVIEW_NAVBAR_MARGIN,
+ halign: Gtk.Align.END,
+ valign: Gtk.Align.CENTER });
+ this.next_widget.get_style_context().add_class('osd');
+ this._overlay.add_overlay(this.next_widget);
+ this.next_widget.connect('clicked', Lang.bind(this, this._onNextClicked));
+ this.next_widget.connect('enter-notify-event', Lang.bind(this, this._onEnterNotify));
+ this.next_widget.connect('leave-notify-event', Lang.bind(this, this._onLeaveNotify));
+ this._overlay.connect('motion-notify-event', Lang.bind(this, this._onMotion));
+ this._visible = true;
+
+ },
+
+ _onEnterNotify: function() {
+ this._unqueueAutoHide();
+ return false;
+ },
+
+ _onLeaveNotify: function() {
+ this._queueAutoHide();
+ return false;
+ },
+
+ _motionTimeout: function() {
+ this._motionId = 0;
+ this._visibleInternal = true;
+ this._updateVisibility();
+ this._queueAutoHide();
+ return false;
+ },
+
+ _onMotion: function(widget, event) {
+ if (this._motionId != 0) {
+ return false;
+ }
+
+ let device = event.get_source_device();
+ if (device.input_source == Gdk.InputSource.TOUCHSCREEN) {
+ return false;
+ }
+
+ this._motionId = Mainloop.idle_add(Lang.bind(this, this._motionTimeout));
+ return false;
+ },
+
+ _onPrevClicked: function() {
+ this._epubView.goPrev();
+ },
+
+ _onNextClicked: function() {
+ this._epubView.goNext();
+ },
+
+ _autoHide: function() {
+ this._autoHideId = 0;
+ this._visibleInternal = false;
+ this._updateVisibility();
+ return false;
+ },
+
+ _unqueueAutoHide: function() {
+ if (this._autoHideId == 0)
+ return;
+
+ Mainloop.source_remove(this._autoHideId);
+ this._autoHideId = 0;
+ },
+
+ _queueAutoHide: function() {
+ this._unqueueAutoHide();
+ this._autoHideId = Mainloop.timeout_add_seconds(_AUTO_HIDE_TIMEOUT, Lang.bind(this, this._autoHide));
+ },
+
+ _updateVisibility: function() {
+ if (!this._epubView) {
+ return;
+ }
+
+ if (!this._visible || !this._visibleInternal) {
+ this._fadeOutButton(this.prev_widget);
+ this._fadeOutButton(this.next_widget);
+ return;
+ }
+
+ if (this._epubView.page == 1) {
+ this._fadeOutButton(this.prev_widget);
+ } else {
+ this._fadeInButton(this.prev_widget);
+ }
+
+ var l = this._epubView._epubSpine.length;
+ if (this._epubView.page >= l) {
+ this._fadeOutButton(this.next_widget);
+ } else {
+ this._fadeInButton(this.next_widget);
+ }
+ },
+
+ _fadeInButton: function(widget) {
+ widget.show_all();
+ Tweener.addTween(widget, { opacity: 1,
+ time: 0.30,
+ transition: 'easeOutQuad' });
+ },
+
+ _fadeOutButton: function(widget) {
+ Tweener.addTween(widget, { opacity: 0,
+ time: 0.30,
+ transition: 'easeOutQuad',
+ onComplete: function() {
+ widget.hide();
+ },
+ onCompleteScope: this });
+ },
+
+ show: function() {
+ this._visible = true;
+ this._visibleInternal = true;
+ this._updateVisibility();
+ this._queueAutoHide();
+ },
+
+ hide: function() {
+ this._visible = false;
+ this._visibleInternal = false;
+ this._updateVisibility();
+ },
+
+ destroy: function() {
+ this.prev_widget.destroy();
+ this.next_widget.destroy();
+ }
+});
diff --git a/src/org.gnome.Books.src.gresource.xml b/src/org.gnome.Books.src.gresource.xml
index 1448541..ec04c5f 100644
--- a/src/org.gnome.Books.src.gresource.xml
+++ b/src/org.gnome.Books.src.gresource.xml
@@ -6,6 +6,7 @@
<file>documents.js</file>
<file>edit.js</file>
<file>lokview.js</file>
+ <file>epubview.js</file>
<file>embed.js</file>
<file>errorBox.js</file>
<file>main.js</file>
diff --git a/src/org.gnome.Documents.src.gresource.xml b/src/org.gnome.Documents.src.gresource.xml
index 3ad150b..ea5ff97 100644
--- a/src/org.gnome.Documents.src.gresource.xml
+++ b/src/org.gnome.Documents.src.gresource.xml
@@ -6,6 +6,7 @@
<file>documents.js</file>
<file>edit.js</file>
<file>lokview.js</file>
+ <file>epubview.js</file>
<file>embed.js</file>
<file>errorBox.js</file>
<file>main.js</file>
diff --git a/src/windowMode.js b/src/windowMode.js
index fd2daa2..d8527c5 100644
--- a/src/windowMode.js
+++ b/src/windowMode.js
@@ -27,9 +27,10 @@ const WindowMode = {
DOCUMENTS: 1,
PREVIEW_EV: 2,
PREVIEW_LOK: 3,
- EDIT: 4,
- COLLECTIONS: 5,
- SEARCH: 6
+ PREVIEW_EPUB: 4,
+ EDIT: 5,
+ COLLECTIONS: 6,
+ SEARCH: 7,
};
const ModeController = new Lang.Class({
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]