Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/epubview/epubview.py
diff options
context:
space:
mode:
Diffstat (limited to 'epubview/epubview.py')
-rw-r--r--epubview/epubview.py480
1 files changed, 480 insertions, 0 deletions
diff --git a/epubview/epubview.py b/epubview/epubview.py
new file mode 100644
index 0000000..f59fb9c
--- /dev/null
+++ b/epubview/epubview.py
@@ -0,0 +1,480 @@
+# Copyright 2009 One Laptop Per Child
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# 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 of the License, 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk, gtk.gdk
+import gobject
+import widgets
+
+import os.path
+import math
+import shutil
+
+from epub import _Epub
+from jobs import _JobPaginator as _Paginator
+
+
+LOADING_HTML = '''
+<div style="width:100%;height:100%;text-align:center;padding-top:50%;">
+ <h1>Loading...</h1>
+</div>
+'''
+
+class _View(gtk.HBox):
+ __gproperties__ = {
+ 'has-selection' : (gobject.TYPE_BOOLEAN, 'whether has selection',
+ 'whether the widget has selection or not',
+ 0, gobject.PARAM_READABLE),
+ 'zoom' : (gobject.TYPE_FLOAT, 'the zoom level',
+ 'the zoom level of the widget',
+ 0.5, 4.0, 1.0, gobject.PARAM_READWRITE)
+ }
+ __gsignals__ = {
+ 'page-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([]))
+ }
+ def __init__(self):
+ gobject.threads_init()
+ gtk.HBox.__init__(self)
+
+ self.connect("destroy", self._destroy_cb)
+
+ self._ready = False
+ self._paginator = None
+ self._loaded_page = -1
+ #self._old_scrollval = -1
+ self._loaded_filename = None
+ self._pagecount = -1
+ self.__going_fwd = True
+ self.__going_back = False
+ self.__page_changed = False
+ self.has_selection = False
+ self.zoom = 1.0
+ self._epub = None
+ self._findjob = None
+ self.__in_search = False
+ self.__search_fwd = True
+
+ self._sw = gtk.ScrolledWindow()
+ self._view = widgets._WebView()
+ self._view.load_string(LOADING_HTML, 'text/html', 'utf-8', '/')
+ settings = self._view.get_settings()
+ settings.props.default_font_family = 'DejaVu LGC Serif'
+ settings.props.enable_plugins = False
+ settings.props.default_encoding = 'utf-8'
+ self._view.connect('load-finished', self._view_load_finished_cb)
+ self._view.connect('scroll-event', self._view_scroll_event_cb)
+ self._view.connect('key-press-event', self._view_keypress_event_cb)
+ self._view.connect('button-release-event', self._view_buttonrelease_event_cb)
+ self._view.connect('selection-changed', self._view_selection_changed_cb)
+ self._view.connect_after('populate-popup', self._view_populate_popup_cb)
+
+ self._sw.add(self._view)
+ self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
+ self._v_vscrollbar = self._sw.get_vscrollbar()
+ self._v_scrollbar_value_changed_cb_id = self._v_vscrollbar.connect('value-changed', \
+ self._v_scrollbar_value_changed_cb)
+ self._scrollbar = gtk.VScrollbar()
+ self._scrollbar.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
+ self._scrollbar_change_value_cb_id = self._scrollbar.connect('change-value', \
+ self._scrollbar_change_value_cb)
+ self.pack_start(self._sw, expand = True, fill = True)
+ self.pack_start(self._scrollbar, expand = False, fill = False)
+
+ self._view.set_flags(gtk.CAN_DEFAULT|gtk.CAN_FOCUS)
+
+ def set_document(self, epubdocumentinstance):
+ self._epub = epubdocumentinstance
+ gobject.idle_add(self._paginate)
+
+ def do_get_property(self, property):
+ if property.name == 'has-selection':
+ return self.has_selection
+ elif property.name == 'zoom':
+ return self.zoom
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+ def do_set_property(self, property, value):
+ if property.name == 'zoom':
+ self.__set_zoom(value)
+ else:
+ raise AttributeError, 'unknown property %s' % property.name
+
+ def get_has_selection(self):
+ return self.get_property('has-selection')
+
+ def get_zoom(self):
+ return self.get_property('zoom')
+
+ def set_zoom(self, value):
+ self.set_property('zoom', value)
+
+ def zoom_in(self):
+ if self.can_zoom_in():
+ self.set_zoom(self.get_zoom() + 0.1)
+ return True
+ else:
+ return False
+
+ def zoom_out(self):
+ if self.can_zoom_out():
+ self.set_zoom(self.get_zoom() - 0.1)
+ return True
+ else:
+ return False
+
+ def can_zoom_in(self):
+ if self.zoom < 4:
+ return True
+ else:
+ return False
+
+ def can_zoom_out(self):
+ if self.zoom > 0.5:
+ return True
+ else:
+ return False
+
+ def get_current_page(self):
+ return self._loaded_page
+
+ def get_current_file(self):
+ #return self._loaded_filename
+ if self._paginator:
+ return self._paginator.get_file_for_pageno(self._loaded_page)
+ else:
+ return None
+
+ def get_pagecount(self):
+ return self._pagecount
+
+ def set_current_page(self, n):
+ if n < 1 or n > self._pagecount:
+ return False
+ self._load_page(n)
+ return True
+
+ def next_page(self):
+ if self._loaded_page == self._pagecount:
+ return False
+ self._load_next_page()
+ return True
+
+ def previous_page(self):
+ if self._loaded_page == 1:
+ return False
+ self._load_prev_page()
+ return True
+
+ def scroll(self, scrolltype, horizontal):
+ if scrolltype == gtk.SCROLL_PAGE_BACKWARD:
+ self.__going_back = True
+ self.__going_fwd = False
+ if not self._do_page_transition():
+ self._view.move_cursor(gtk.MOVEMENT_PAGES, -1)
+ elif scrolltype == gtk.SCROLL_PAGE_FORWARD:
+ self.__going_back = False
+ self.__going_fwd = True
+ if not self._do_page_transition():
+ self._view.move_cursor(gtk.MOVEMENT_PAGES, 1)
+ else:
+ print ('Got unsupported scrolltype %s' % str(scrolltype))
+
+ def copy(self):
+ self._view.copy_clipboard()
+
+ def find_next(self):
+ self._view.grab_focus()
+ self._view.grab_default()
+
+ if self._view.search_text(self._findjob.get_search_text(), \
+ self._findjob.get_case_sensitive(), True, False):
+ return
+ else:
+ path = os.path.join(self._epub.get_basedir(), self._findjob.get_next_file())
+ self.__in_search = True
+ self.__search_fwd = True
+ self._load_file(path)
+
+ def find_previous(self):
+ self._view.grab_focus()
+ self._view.grab_default()
+
+ if self._view.search_text(self._findjob.get_search_text(), \
+ self._findjob.get_case_sensitive(), False, False):
+ return
+ else:
+ path = os.path.join(self._epub.get_basedir(), self._findjob.get_prev_file())
+ self.__in_search = True
+ self.__search_fwd = False
+ self._load_file(path)
+
+ def _find_changed(self, job):
+ self._view.grab_focus()
+ self._view.grab_default()
+ self._findjob = job
+ #self._view.search_text(self._findjob.get_search_text(), \
+ # self._findjob.get_case_sensitive(), True, False)
+ self.find_next()
+
+ def __set_zoom(self, value):
+ self._view.set_zoom_level(value)
+ self.zoom = value
+
+ def __set_has_selection(self, value):
+ if value != self.has_selection:
+ self.has_selection = value
+ self.notify('has-selection')
+
+ def _view_populate_popup_cb(self, view, menu):
+ menu.destroy() #HACK
+ return
+
+ def _view_selection_changed_cb(self, view):
+ # FIXME: This does not seem to be implemented in
+ # webkitgtk yet
+ print view.has_selection()
+
+ def _view_buttonrelease_event_cb(self, view, event):
+ # Ugly hack
+ self.__set_has_selection(view.can_copy_clipboard() \
+ | view.can_cut_clipboard())
+
+ def _view_keypress_event_cb(self, view, event):
+ name = gtk.gdk.keyval_name(event.keyval)
+ if name == 'Page_Down' or name == 'Down':
+ self.__going_back = False
+ self.__going_fwd = True
+ elif name == 'Page_Up' or name == 'Up':
+ self.__going_back = True
+ self.__going_fwd = False
+
+ self._do_page_transition()
+
+ def _view_scroll_event_cb(self, view, event):
+ if event.direction == gtk.gdk.SCROLL_DOWN:
+ self.__going_back = False
+ self.__going_fwd = True
+ elif event.direction == gtk.gdk.SCROLL_UP:
+ self.__going_back = True
+ self.__going_fwd = False
+
+ self._do_page_transition()
+
+ def _do_page_transition(self):
+ if self.__going_fwd:
+ if self._v_vscrollbar.get_value() >= \
+ self._v_vscrollbar.props.adjustment.props.upper - \
+ self._v_vscrollbar.props.adjustment.props.page_size:
+ self._load_next_file()
+ return True
+ elif self.__going_back:
+ if self._v_vscrollbar.get_value() == self._v_vscrollbar.props.adjustment.props.lower:
+ self._load_prev_file()
+ return True
+
+ return False
+
+ def _view_load_finished_cb(self, v, frame):
+ filename = self._view.props.uri.replace('file://', '')
+ if os.path.exists(filename.replace('xhtml', 'xml')):
+ filename = filename.replace('xhtml', 'xml') # Hack for making javascript work
+
+ if self._loaded_page < 1 or filename == None:
+ return False
+
+ self._loaded_filename = filename
+
+ remfactor = self._paginator.get_remfactor_for_file(filename)
+ pages = self._paginator.get_pagecount_for_file(filename)
+ extra = int(math.ceil(remfactor * self._view.get_page_height()/(pages-remfactor)))
+ if extra > 0:
+ self._view.add_bottom_padding(extra)
+
+ if self.__in_search:
+ self._view.search_text(self._findjob.get_search_text(), \
+ self._findjob.get_case_sensitive(), \
+ self.__search_fwd, False)
+ self.__in_search = False
+ else:
+ if self.__going_back:
+ # We need to scroll to the last page
+ self._scroll_page_end()
+ else:
+ self._scroll_page()
+
+ def _scroll_page_end(self):
+ v_upper = self._v_vscrollbar.props.adjustment.props.upper
+ v_page_size = self._v_vscrollbar.props.adjustment.props.page_size
+ self._v_vscrollbar.set_value(v_upper)
+
+ def _scroll_page(self):
+ pageno = self._loaded_page
+
+ v_upper = self._v_vscrollbar.props.adjustment.props.upper
+ v_page_size = self._v_vscrollbar.props.adjustment.props.page_size
+
+ scrollfactor = self._paginator.get_scrollfactor_pos_for_pageno(pageno)
+ self._v_vscrollbar.set_value((v_upper - v_page_size) * scrollfactor)
+
+ def _paginate(self):
+ filelist = []
+ for i in self._epub._navmap.get_flattoc():
+ filelist.append(os.path.join(self._epub._tempdir, i[1]))
+
+ self._paginator = _Paginator(filelist)
+ self._paginator.connect('paginated', self._paginated_cb)
+
+ def _load_next_page(self):
+ self._load_page(self._loaded_page + 1)
+
+ def _load_prev_page(self):
+ self._load_page(self._loaded_page - 1)
+
+ def _v_scrollbar_value_changed_cb(self, scrollbar):
+ if self._loaded_page < 1:
+ return
+ scrollval = scrollbar.get_value()
+ scroll_upper = self._v_vscrollbar.props.adjustment.props.upper
+ scroll_page_size = self._v_vscrollbar.props.adjustment.props.page_size
+
+ if self.__going_fwd == True and not self._loaded_page == self._pagecount:
+ if self._paginator.get_file_for_pageno(self._loaded_page) != \
+ self._paginator.get_file_for_pageno(self._loaded_page + 1):
+ return # We don't need this if the next page is in another file
+
+ scrollfactor_next = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page + 1)
+ if scrollval > 0:
+ scrollfactor = scrollval/(scroll_upper - scroll_page_size)
+ else:
+ scrollfactor = 0
+ if scrollfactor >= scrollfactor_next:
+ self._on_page_changed(self._loaded_page + 1)
+ elif self.__going_back == True and self._loaded_page > 1:
+ if self._paginator.get_file_for_pageno(self._loaded_page) != \
+ self._paginator.get_file_for_pageno(self._loaded_page - 1):
+ return
+
+ scrollfactor_cur = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page)
+ if scrollval > 0:
+ scrollfactor = scrollval/(scroll_upper - scroll_page_size)
+ else:
+ scrollfactor = 0
+
+ if scrollfactor <= scrollfactor_cur:
+ self._on_page_changed(self._loaded_page - 1)
+
+ def _on_page_changed(self, pageno):
+ self.__page_changed = True
+ self._loaded_page = pageno
+ self._scrollbar.handler_block(self._scrollbar_change_value_cb_id)
+ self._scrollbar.set_value(pageno)
+ self._scrollbar.handler_unblock(self._scrollbar_change_value_cb_id)
+ self.emit('page-changed')
+
+ def _load_page(self, pageno):
+ if pageno > self._pagecount or pageno < 1:
+ #TODO: Cause an exception
+ return
+
+ self._on_page_changed(pageno)
+ filename = self._paginator.get_file_for_pageno(pageno)
+ if filename != self._loaded_filename:
+ #self._loaded_filename = filename
+ if filename.endswith('xml'):
+ dest = filename.replace('xml', 'xhtml')
+ shutil.copy(filename.replace('file://', ''), dest.replace('file://', ''))
+ self._view.open(dest)
+ else:
+ self._view.open(filename)
+ else:
+ self._scroll_page()
+
+ def _load_next_file(self):
+ cur_file = self._paginator.get_file_for_pageno(self._loaded_page)
+ pageno = self._loaded_page
+ while pageno < self._paginator.get_total_pagecount():
+ pageno += 1
+ if self._paginator.get_file_for_pageno(pageno) != cur_file:
+ break
+
+ self._load_page(pageno)
+
+ def _load_file(self, path):
+ #TODO: This is a bit suboptimal - fix it
+ for pageno in range(1, self.get_pagecount()):
+ filepath = self._paginator.get_file_for_pageno(pageno)
+ if filepath.endswith(path):
+ self._load_page(pageno)
+ break
+
+ def _load_prev_file(self):
+ cur_file = self._paginator.get_file_for_pageno(self._loaded_page)
+ pageno = self._loaded_page
+ while pageno > 1:
+ pageno -= 1
+ if self._paginator.get_file_for_pageno(pageno) != cur_file:
+ break
+
+ self._load_page(pageno)
+
+ def _scrollbar_change_value_cb(self, range, scrolltype, value):
+ if scrolltype == gtk.SCROLL_STEP_FORWARD:
+ self.__going_fwd = True
+ self.__going_back = False
+ if not self._do_page_transition():
+ self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, 1)
+ elif scrolltype == gtk.SCROLL_STEP_BACKWARD:
+ self.__going_fwd = False
+ self.__going_back = True
+ if not self._do_page_transition():
+ self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, -1)
+ elif scrolltype == gtk.SCROLL_JUMP or \
+ scrolltype == gtk.SCROLL_PAGE_FORWARD or \
+ scrolltype == gtk.SCROLL_PAGE_BACKWARD:
+ if value > self._scrollbar.props.adjustment.props.upper:
+ self._load_page(self._pagecount)
+ else:
+ self._load_page(round(value))
+ else:
+ print 'Warning: unknown scrolltype %s with value %f' % (str(scrolltype), value)
+
+ self._scrollbar.set_value(self._loaded_page) #FIXME: This should not be needed here
+
+ if self.__page_changed == True:
+ self.__page_changed = False
+ return False
+ else:
+ return True
+
+ def _paginated_cb(self, object):
+ self._ready = True
+
+ self._pagecount = self._paginator.get_total_pagecount()
+ self._scrollbar.set_range(1.0, self._pagecount - 1.0)
+ self._scrollbar.set_increments(1.0, 1.0)
+ self._view.grab_focus()
+ self._view.grab_default()
+ if self._loaded_page < 1:
+ self._load_page(1)
+
+
+
+ def _destroy_cb(self, widget):
+ self._epub.close()
+