diff options
author | Sayamindu Dasgupta <sayamindu@gmail.com> | 2009-07-20 20:36:51 (GMT) |
---|---|---|
committer | Sayamindu Dasgupta <sayamindu@gmail.com> | 2009-07-20 20:36:51 (GMT) |
commit | 0957516a88ef7216fc1e23f9ab2224562af3d91c (patch) | |
tree | 039e7d17ce05a25fde1f3b20db406363dfb53a0b | |
parent | aee4aefc8c9536a18156a4db8d59d8c767f78729 (diff) | |
parent | 9760c4c9acdf0ea1cf4c57ded8c65eb4390ca171 (diff) |
Merge branch 'master' of git://git.sugarlabs.org/read/epub-support
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | activity/activity.info | 2 | ||||
-rw-r--r-- | activity/mimetypes.xml | 7 | ||||
-rw-r--r-- | epubadapter.py | 67 | ||||
-rw-r--r-- | epubview/__init__.py | 24 | ||||
-rw-r--r-- | epubview/epub.py | 133 | ||||
-rw-r--r-- | epubview/epubinfo.py | 90 | ||||
-rw-r--r-- | epubview/epubview.py | 480 | ||||
-rw-r--r-- | epubview/jobs.py | 248 | ||||
-rwxr-xr-x | epubview/modules/webkit.so | bin | 0 -> 132372 bytes | |||
-rw-r--r-- | epubview/navmap.py | 64 | ||||
-rw-r--r-- | epubview/widgets.py | 27 | ||||
-rw-r--r-- | readactivity.py | 149 | ||||
-rw-r--r-- | readtoolbar.py | 116 |
14 files changed, 1319 insertions, 89 deletions
@@ -7,3 +7,4 @@ Simon Schampijer <simon@schampijer.de> Reinier Heeres <reinier@heeres.eu> Guillaume Desmottes <guillaume.desmottes@collabora.co.uk> Morgan Collett <morgan.collett@gmail.com> +Sayamindu Dasgupta <sayamindu@laptop.org> (Current maintainer) diff --git a/activity/activity.info b/activity/activity.info index 92f0684..15729b4 100644 --- a/activity/activity.info +++ b/activity/activity.info @@ -5,5 +5,5 @@ icon = activity-read exec = sugar-activity readactivity.ReadActivity show_launcher = no activity_version = 67 -mime_types = application/pdf;image/vnd.djvu;image/x.djvu;image/tiff;application/x-cbz;application/x-cbr +mime_types = application/pdf;image/vnd.djvu;image/x.djvu;image/tiff;application/x-cbz;application/x-cbr;application/epub+zip license = GPLv2+ diff --git a/activity/mimetypes.xml b/activity/mimetypes.xml new file mode 100644 index 0000000..5c27572 --- /dev/null +++ b/activity/mimetypes.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> + <mime-type type="application/epub+zip"> + <comment xml:lang="en">Epub document</comment> + <glob pattern="*.epub"/> + </mime-type> +</mime-info> diff --git a/epubadapter.py b/epubadapter.py new file mode 100644 index 0000000..3fa4576 --- /dev/null +++ b/epubadapter.py @@ -0,0 +1,67 @@ +import gobject +import logging + +import epubview + +_logger = logging.getLogger('read-activity') + +class View(epubview.EpubView): + def __init__(self): + epubview.EpubView.__init__(self) + + def _try_load_page(self, n): + if self._ready: + self._load_page(n) + return False + else: + return True + + def set_screen_dpi(self, dpi): + return + + def find_set_highlight_search(self, set_highlight_search): + return + + def set_current_page(self, n): + # When the book is being loaded, calling this does not help + # In such a situation, we go into a loop and try to load the + # supplied page when the book has loaded completely + n += 1 + if self._ready: + self._load_page(n) + else: + gobject.timeout_add(200, self._try_load_page, n) + + def get_current_page(self): + return int(self._loaded_page - 1) + + def find_changed(self, job, page = None): + self._find_changed(job) + + def update_view_size(self, widget): + return + + def handle_link(self, link): + self._load_file(link) + + +class EpubDocument(epubview.Epub): + def __init__(self, view, docpath): + epubview.Epub.__init__(self, docpath) + self._page_cache = view + + def get_page_cache(self): + return self._page_cache + + def get_n_pages(self): + return int(self._page_cache.get_pagecount()) + + def has_document_links(self): + return True + + def get_links_model(self): + return self.get_toc_model() + +class JobFind(epubview.JobFind): + def __init__(self, document, start_page, n_pages, text, case_sensitive=False): + epubview.JobFind.__init__(self, document, start_page, n_pages, text, case_sensitive=False) diff --git a/epubview/__init__.py b/epubview/__init__.py new file mode 100644 index 0000000..81a3175 --- /dev/null +++ b/epubview/__init__.py @@ -0,0 +1,24 @@ +# 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 gobject + +gobject.threads_init() + +from epub import _Epub as Epub +from epubview import _View as EpubView +from jobs import _JobFind as JobFind
\ No newline at end of file diff --git a/epubview/epub.py b/epubview/epub.py new file mode 100644 index 0000000..81de9d7 --- /dev/null +++ b/epubview/epub.py @@ -0,0 +1,133 @@ +# 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 zipfile +import tempfile +import os, os.path +from lxml import etree +import shutil + +import navmap, epubinfo + + +class _Epub(object): + def __init__(self, filepath): + self._filepath = filepath + self._zobject = None + self._obffile = None + self._obfpath = None + self._ncxpath = None + self._ncxfile = None + self._basepath = None + self._tempdir = tempfile.mkdtemp() + + if not self._verify(): + print 'Warning: This does not seem to be a valid epub file' + + self._get_obf() + self._get_ncx() + + self._ncxfile = self._zobject.open(self._ncxpath) + self._navmap = navmap.NavMap(self._ncxfile, self._basepath) + + self._obffile = self._zobject.open(self._obfpath) + self._info = epubinfo.EpubInfo(self._obffile) + + self._unzip() + + def _unzip(self): + #self._zobject.extractall(path = self._tempdir) # This is broken upto python 2.7 + orig_cwd = os.getcwd() + os.chdir(self._tempdir) + for name in self._zobject.namelist(): + if name.startswith(os.path.sep): # Some weird zip file entries start with a slash, and we don't want to write to the root directory + name = name[1:] + if name.endswith(os.path.sep) or name.endswith('\\'): + os.makedirs(name) + else: + self._zobject.extract(name) + os.chdir(orig_cwd) + + + def _get_obf(self): + containerfile = self._zobject.open('META-INF/container.xml') + + tree = etree.parse(containerfile) + root = tree.getroot() + + for element in root.iterfind('.//{urn:oasis:names:tc:opendocument:xmlns:container}rootfile'): + if element.get('media-type') == 'application/oebps-package+xml': + self._obfpath = element.get('full-path') + + if self._obfpath.rpartition('/')[0]: + self._basepath = self._obfpath.rpartition('/')[0] + '/' + else: + self._basepath = '' + + containerfile.close() + + + def _get_ncx(self): + obffile = self._zobject.open(self._obfpath) + + tree = etree.parse(obffile) + root = tree.getroot() + + for element in root.iterfind('.//{http://www.idpf.org/2007/opf}item'): + if element.get('media-type') == 'application/x-dtbncx+xml' or \ + element.get('id') == 'ncx': + self._ncxpath = self._basepath + element.get('href') + + obffile.close() + + + def _verify(self): + ''' + Method to crudely check to verify that what we + are dealing with is a epub file or not + ''' + if not os.path.exists(self._filepath): + return False + + self._zobject = zipfile.ZipFile(self._filepath) + + if not 'mimetype' in self._zobject.namelist(): + return False + + mtypefile = self._zobject.open('mimetype') + mimetype = mtypefile.readline() + + if mimetype != 'application/epub+zip': + return False + + return True + + def get_toc_model(self): + return self._navmap.get_gtktreestore() + + def get_flattoc(self): + return self._navmap.get_flattoc() + + def get_basedir(self): + return self._tempdir + + def get_info(self): + return self._info + + def close(self): + self._zobject.close() + shutil.rmtree(self._tempdir) diff --git a/epubview/epubinfo.py b/epubview/epubinfo.py new file mode 100644 index 0000000..ddf9ad5 --- /dev/null +++ b/epubview/epubinfo.py @@ -0,0 +1,90 @@ +import os +from lxml import etree + + +class EpubInfo(): #TODO: Cover the entire DC range + def __init__(self, file): + self._tree = etree.parse(file) + self._root = self._tree.getroot() + self._e_metadata = self._root.find('{http://www.idpf.org/2007/opf}metadata') + + self.title = self._get_title() + self.creator = self._get_creator() + self.date = self._get_date() + self.subject = self._get_subject() + self.source = self._get_source() + self.rights = self._get_rights() + self.identifier = self._get_identifier() + self.language = self._get_language() + + + def _get_data(self, tagname): + element = self._e_metadata.find(tagname) + return element.text + + def _get_title(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}title') + except AttributeError: + return None + + return ret + + def _get_creator(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}creator') + except AttributeError: + return None + return ret + + def _get_date(self): + #TODO: iter + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}date') + except AttributeError: + return None + + return ret + + def _get_source(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}source') + except AttributeError: + return None + + return ret + + def _get_rights(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}rights') + except AttributeError: + return None + + return ret + + def _get_identifier(self): + #TODO: iter + element = self._e_metadata.find('.//{http://purl.org/dc/elements/1.1/}identifier') + + if element is not None: + return {'id':element.get('id'), 'value':element.text} + else: + return None + + def _get_language(self): + try: + ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}language') + except AttributeError: + return None + + return ret + + def _get_subject(self): + try: + subjectlist = [] + for element in self._e_metadata.iterfind('.//{http://purl.org/dc/elements/1.1/}subject'): + subjectlist.append(element.text) + except AttributeError: + return None + + return subjectlist
\ No newline at end of file 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() + diff --git a/epubview/jobs.py b/epubview/jobs.py new file mode 100644 index 0000000..de276b3 --- /dev/null +++ b/epubview/jobs.py @@ -0,0 +1,248 @@ +# 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 gobject +import gtk +import widgets +import cairo + +import math +import os.path +import BeautifulSoup + +import epub + +import threading + +PAGE_WIDTH = 135 +PAGE_HEIGHT = 216 + +def _pixel_to_mm(pixel, dpi): + inches = pixel/dpi + return int(inches/0.03937) + +def _mm_to_pixel(mm, dpi): + inches = mm * 0.03937 + return int(inches * dpi) + + + +class SearchThread(threading.Thread): + def __init__(self, obj): + threading.Thread.__init__ (self) + self.obj = obj + self.stopthread = threading.Event() + + def _start_search(self): + for entry in self.obj.flattoc: + if self.stopthread.isSet(): + break + name, file = entry + filepath = os.path.join(self.obj._document.get_basedir(), file) + f = open(filepath) + if self._searchfile(f): + self.obj._matchfilelist.append(file) + f.close() + + gtk.gdk.threads_enter() + self.obj._finished = True + self.obj.emit('updated') + gtk.gdk.threads_leave() + + return False + + def _searchfile(self, fileobj): + soup = BeautifulSoup.BeautifulSoup(fileobj) + body = soup.find('body') + tags = body.findChildren(True) + for tag in tags: + if not tag.string is None: + if tag.string.find(self.obj._text) > -1: + return True + + return False + + def run (self): + self._start_search() + + def stop(self): + self.stopthread.set() + + + +class _JobPaginator(gobject.GObject): + __gsignals__ = { + 'paginated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self, filelist): + gobject.GObject.__init__(self) + + self._filelist = filelist + self._filedict = {} + self._pagemap = {} + + self._bookheight = 0 + self._count = 0 + self._pagecount = 0 + + self._screen = gtk.gdk.screen_get_default() + self._old_fontoptions = self._screen.get_font_options() + options = cairo.FontOptions() + options.set_hint_style(cairo.HINT_STYLE_MEDIUM) + options.set_antialias(cairo.ANTIALIAS_GRAY) + options.set_subpixel_order(cairo.SUBPIXEL_ORDER_DEFAULT) + options.set_hint_metrics(cairo.HINT_METRICS_DEFAULT) + self._screen.set_font_options(options) + + self._temp_win = gtk.Window() + self._temp_view = widgets._WebView() + + settings = self._temp_view.get_settings() + settings.props.default_font_family = 'DejaVu LGC Serif' + settings.props.sans_serif_font_family = 'DejaVu LGC Sans' + settings.props.serif_font_family = 'DejaVu LGC Serif' + settings.props.monospace_font_family = 'DejaVu LGC Sans Mono' + settings.props.enforce_96_dpi = True + #settings.props.auto_shrink_images = False #FIXME: This does not seem to work + settings.props.enable_plugins = False + settings.props.default_font_size = 12 + settings.props.default_monospace_font_size = 10 + settings.props.default_encoding = 'utf-8' + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) + self._dpi = 96 + sw.set_size_request(_mm_to_pixel(PAGE_WIDTH, self._dpi), _mm_to_pixel(PAGE_HEIGHT, self._dpi)) + sw.add(self._temp_view) + self._temp_win.add(sw) + self._temp_view.connect('load-finished', self._page_load_finished_cb) + + self._temp_win.show_all() + self._temp_win.unrealize() + + self._temp_view.open(self._filelist[self._count]) + + def _page_load_finished_cb(self, v, frame): + f = v.get_main_frame() + pageheight = v.get_page_height() + + if pageheight <= _mm_to_pixel(PAGE_HEIGHT, self._dpi): + pages = 1 + else: + pages = pageheight/float(_mm_to_pixel(PAGE_HEIGHT, self._dpi)) + for i in range(1, int(math.ceil(pages) + 1)): + if pages - i < 0: + pagelen = (pages - math.floor(pages))/pages + else: + pagelen = 1/pages + self._pagemap[float(self._pagecount + i)] = (f.props.uri, (i-1)/math.ceil(pages), pagelen) + + self._pagecount += math.ceil(pages) + self._filedict[f.props.uri.replace('file://', '')] = (math.ceil(pages), math.ceil(pages) - pages) + self._bookheight += pageheight + + if self._count+1 >= len(self._filelist): + self._temp_win.destroy() + self._screen.set_font_options(self._old_fontoptions) + self.emit('paginated') + else: + self._count += 1 + self._temp_view.open(self._filelist[self._count]) + + + def get_file_for_pageno(self, pageno): + return self._pagemap[pageno][0] + + def get_scrollfactor_pos_for_pageno(self, pageno): + return self._pagemap[pageno][1] + + def get_scrollfactor_len_for_pageno(self, pageno): + return self._pagemap[pageno][2] + + def get_pagecount_for_file(self, file): + return self._filedict[file][0] + + def get_remfactor_for_file(self, file): + return self._filedict[file][1] + + def get_total_pagecount(self): + return self._pagecount + + def get_total_height(self): + return self._bookheight + + +class _JobFind(gobject.GObject): + __gsignals__ = { + 'updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self, document, start_page, n_pages, text, case_sensitive=False): + gobject.GObject.__init__(self) + gtk.threads_init() + + self._finished = False + self._document = document + self._start_page = start_page + self._n_pages = n_pages + self._text = text + self._case_sensitive = case_sensitive + self.flattoc = self._document.get_flattoc() + self._matchfilelist = [] + self._current_file_index = 0 + self.threads = [] + + s_thread = SearchThread(self) + self.threads.append(s_thread) + s_thread.start() + + def cancel(self): + for s_thread in self.threads: + s_thread.stop() + + def is_finished(self): + return self._finished + + def get_next_file(self): + self._current_file_index += 1 + try: + path = self._matchfilelist[self._current_file_index] + except IndexError: + self._current_file_index = 0 + path = self._matchfilelist[self._current_file_index] + + return path + + def get_prev_file(self): + self._current_file_index -= 1 + try: + path = self._matchfilelist[self._current_file_index] + except IndexError: + self._current_file_index = -1 + path = self._matchfilelist[self._current_file_index] + + return path + + def get_search_text(self): + return self._text + + def get_case_sensitive(self): + return self._case_sensitive diff --git a/epubview/modules/webkit.so b/epubview/modules/webkit.so Binary files differnew file mode 100755 index 0000000..f8dce2a --- /dev/null +++ b/epubview/modules/webkit.so diff --git a/epubview/navmap.py b/epubview/navmap.py new file mode 100644 index 0000000..0ba0b8f --- /dev/null +++ b/epubview/navmap.py @@ -0,0 +1,64 @@ +from lxml import etree +import gtk + +class NavPoint(object): + def __init__(self, label, contentsrc, children = []): + self._label = label + self._contentsrc = contentsrc + self._children = children + + def get_label(self): + return self._label + + def get_contentsrc(self): + return self._contentsrc + + def get_children(self): + return self._children + + +class NavMap(object): + def __init__(self, file, basepath): + self._basepath = basepath + self._tree = etree.parse(file) + self._root = self._tree.getroot() + self._gtktreestore = gtk.TreeStore(str, str) + self._flattoc = [] + + self._populate_toc() + + def _populate_toc(self): + navmap = self._root.find('{http://www.daisy.org/z3986/2005/ncx/}navMap') + for navpoint in navmap.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint'): + self._process_navpoint(navpoint) + + def _gettitle(self, navpoint): + text = navpoint.find('./{http://www.daisy.org/z3986/2005/ncx/}navLabel/{http://www.daisy.org/z3986/2005/ncx/}text') + return text.text + + def _getcontent(self, navpoint): + text = navpoint.find('./{http://www.daisy.org/z3986/2005/ncx/}content/') + return self._basepath + text.get('src') + + def _process_navpoint(self, navpoint, parent = None): + title = self._gettitle(navpoint) + content = self._getcontent(navpoint) + + iter = self._gtktreestore.append(parent, [title, content]) + self._flattoc.append((title, content)) + + childnavpointlist = list(navpoint.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint')) + + if len(childnavpointlist): + for childnavpoint in childnavpointlist: + self._process_navpoint(childnavpoint, parent = iter) + else: + return + + def get_gtktreestore(self): + return self._gtktreestore + + def get_flattoc(self): + return self._flattoc + +#t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx')
\ No newline at end of file diff --git a/epubview/widgets.py b/epubview/widgets.py new file mode 100644 index 0000000..19d2dc9 --- /dev/null +++ b/epubview/widgets.py @@ -0,0 +1,27 @@ +try: + import webkit +except ImportError: + import os, sys + sys.path.append(os.path.join(os.environ['SUGAR_BUNDLE_PATH'], 'epubview', 'modules')) + import webkit + +import gtk + + +class _WebView(webkit.WebView): + def __init__(self): + webkit.WebView.__init__(self) + + def get_page_height(self): + #TODO: Need to check status of page load + js = 'oldtitle=document.title;document.title=document.body.clientHeight;' + self.execute_script(js) + ret = self.get_main_frame().get_title() + js = 'document.title=oldtitle;' + self.execute_script(js) + return int(ret) + + def add_bottom_padding(self, incr): + js = ('var newdiv = document.createElement("div");newdiv.style.height = "%dpx";document.body.appendChild(newdiv);' % incr) + self.execute_script(js) + diff --git a/readactivity.py b/readactivity.py index c876e22..ca1fcbd 100644 --- a/readactivity.py +++ b/readactivity.py @@ -36,6 +36,8 @@ from sugar.graphics.objectchooser import ObjectChooser from readtoolbar import EditToolbar, ReadToolbar, ViewToolbar from readsidebar import Sidebar +import epubadapter + _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager' _HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager' @@ -102,6 +104,7 @@ class ReadActivity(activity.Activity): # if we use evince-2.24 evince.evince_embed_init() + self._epub = False self._document = None self._fileserver = None self._object_id = handle.object_id @@ -111,17 +114,14 @@ class ReadActivity(activity.Activity): _logger.debug('Starting Read...') - self._view = evince.View() - self._view.set_screen_dpi(_get_screen_dpi()) - self._view.connect('notify::has-selection', - self._view_notify_has_selection_cb) + self._view = None self._sidebar = Sidebar() self._sidebar.show() toolbox = activity.ActivityToolbox(self) - self._edit_toolbar = EditToolbar(self._view) + self._edit_toolbar = EditToolbar() self._edit_toolbar.undo.props.visible = False self._edit_toolbar.redo.props.visible = False self._edit_toolbar.separator.props.visible = False @@ -131,11 +131,11 @@ class ReadActivity(activity.Activity): toolbox.add_toolbar(_('Edit'), self._edit_toolbar) self._edit_toolbar.show() - self._read_toolbar = ReadToolbar(self._view, self._sidebar) + self._read_toolbar = ReadToolbar(self._sidebar) toolbox.add_toolbar(_('Read'), self._read_toolbar) self._read_toolbar.show() - self._view_toolbar = ViewToolbar(self._view) + self._view_toolbar = ViewToolbar() self._view_toolbar.connect('needs-update-size', self.__view_toolbar_needs_update_size_cb) self._view_toolbar.connect('go-fullscreen', @@ -146,20 +146,11 @@ class ReadActivity(activity.Activity): self.set_toolbox(toolbox) toolbox.show() - self._scrolled = gtk.ScrolledWindow() - self._scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - self._scrolled.props.shadow_type = gtk.SHADOW_NONE - - self._scrolled.add(self._view) - self._view.show() + self._hbox = gtk.HBox() + self._hbox.pack_start(self._sidebar, expand=False, fill=False) - hbox = gtk.HBox() - hbox.pack_start(self._sidebar, expand=False, fill=False) - hbox.pack_start(self._scrolled, expand=True, fill=True) - - self.set_canvas(hbox) - self._scrolled.show() - hbox.show() + self.set_canvas(self._hbox) + self._hbox.show() # Set up for idle suspend self._idle_timer = 0 @@ -316,15 +307,16 @@ class ReadActivity(activity.Activity): self.metadata['Read_zoom'] = str(self._view.props.zoom) - if self._view.props.sizing_mode == evince.SIZING_BEST_FIT: - self.metadata['Read_sizing_mode'] = "best-fit" - elif self._view.props.sizing_mode == evince.SIZING_FREE: - self.metadata['Read_sizing_mode'] = "free" - elif self._view.props.sizing_mode == evince.SIZING_FIT_WIDTH: - self.metadata['Read_sizing_mode'] = "fit-width" - else: - _logger.error("Don't know how to save sizing_mode state '%s'" % - self._view.props.sizing_mode) + if not self._epub: + if self._view.props.sizing_mode == evince.SIZING_BEST_FIT: + self.metadata['Read_sizing_mode'] = "best-fit" + elif self._view.props.sizing_mode == evince.SIZING_FREE: + self.metadata['Read_sizing_mode'] = "free" + elif self._view.props.sizing_mode == evince.SIZING_FIT_WIDTH: + self.metadata['Read_sizing_mode'] = "fit-width" + else: + _logger.error("Don't know how to save sizing_mode state '%s'" % + self._view.props.sizing_mode) self.metadata['Read_sizing_mode'] = "fit-width" self.metadata['Read_search'] = \ @@ -452,13 +444,63 @@ class ReadActivity(activity.Activity): self.watch_for_tubes() gobject.idle_add(self._get_document) + def _setup_evince_viewer(self): + self._view = evince.View() + self._scrolled = gtk.ScrolledWindow() + self._scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self._scrolled.props.shadow_type = gtk.SHADOW_NONE + + self._scrolled.add(self._view) + self._view.show() + + self._hbox.pack_start(self._scrolled, expand=True, fill=True) + + + def _setup_epub_viewer(self): + self._view = epubadapter.View() + self._view.set_screen_dpi(_get_screen_dpi()) + self._view.connect('notify::has-selection', + self._view_notify_has_selection_cb) + + self._hbox.pack_start(self._view, expand=True, fill=True) + self._view.show_all() + + def _setup_evince_viewer(self): + self._view = evince.View() + self._view.set_screen_dpi(_get_screen_dpi()) + self._view.connect('notify::has-selection', + self._view_notify_has_selection_cb) + + self._scrolled = gtk.ScrolledWindow() + self._scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self._scrolled.props.shadow_type = gtk.SHADOW_NONE + + self._scrolled.add(self._view) + self._view.show() + + self._hbox.pack_start(self._scrolled, expand=True, fill=True) + self._scrolled.show() + + def _load_document(self, filepath): """Load the specified document and set up the UI. filepath -- string starting with file:// """ - self._document = evince.factory_get_document(filepath) + mimetype = mime.get_for_file(filepath) + if mimetype == 'application/epub+zip': + self._epub = True + self._setup_epub_viewer() + self._document = epubadapter.EpubDocument(self._view, filepath.replace('file://', '')) + else: + self._setup_evince_viewer() + self._document = evince.factory_get_document(filepath) + + self._view_toolbar.set_view(self._view) + self._edit_toolbar.set_view(self._view) + self._read_toolbar.set_view(self._view) + self._want_document = False self._view.set_document(self._document) self._edit_toolbar.set_document(self._document) @@ -469,33 +511,35 @@ class ReadActivity(activity.Activity): if info and info.title: self.metadata['title'] = info.title - current_page = int(self.metadata.get('Read_current_page', '0')) - self._document.get_page_cache().set_current_page(current_page) - - sizing_mode = self.metadata.get('Read_sizing_mode', 'fit-width') - _logger.debug('Found sizing mode: %s', sizing_mode) - if sizing_mode == "best-fit": - self._view.props.sizing_mode = evince.SIZING_BEST_FIT - self._view.update_view_size(self._scrolled) - elif sizing_mode == "free": - self._view.props.sizing_mode = evince.SIZING_FREE - self._view.props.zoom = float(self.metadata.get('Read_zoom', '1.0')) - _logger.debug('Set zoom to %f', self._view.props.zoom) - elif sizing_mode == "fit-width": - self._view.props.sizing_mode = evince.SIZING_FIT_WIDTH - self._view.update_view_size(self._scrolled) - else: - # this may happen when we get a document from a buddy with a later - # version of Read, for example. - _logger.warning("Unknown sizing_mode state '%s'", sizing_mode) - if self.metadata.get('Read_zoom', None) is not None: - self._view.props.zoom = float(self.metadata['Read_zoom']) + if not self._epub: + sizing_mode = self.metadata.get('Read_sizing_mode', 'fit-width') + _logger.debug('Found sizing mode: %s', sizing_mode) + if sizing_mode == "best-fit": + self._view.props.sizing_mode = evince.SIZING_BEST_FIT + self._view.update_view_size(self._scrolled) + elif sizing_mode == "free": + self._view.props.sizing_mode = evince.SIZING_FREE + self._view.props.zoom = float(self.metadata.get('Read_zoom', '1.0')) + _logger.debug('Set zoom to %f', self._view.props.zoom) + elif sizing_mode == "fit-width": + self._view.props.sizing_mode = evince.SIZING_FIT_WIDTH + self._view.update_view_size(self._scrolled) + else: + # this may happen when we get a document from a buddy with a later + # version of Read, for example. + _logger.warning("Unknown sizing_mode state '%s'", sizing_mode) + if self.metadata.get('Read_zoom', None) is not None: + self._view.props.zoom = float(self.metadata['Read_zoom']) self._view_toolbar._update_zoom_buttons() self._edit_toolbar._search_entry.props.text = \ self.metadata.get('Read_search', '') + current_page = int(self.metadata.get('Read_current_page', '0')) + _logger.debug('Setting page to: %d', current_page) + self._document.get_page_cache().set_current_page(current_page) + # We've got the document, so if we're a shared activity, offer it try: if self.get_shared(): @@ -593,7 +637,8 @@ class ReadActivity(activity.Activity): _logger.debug("Keyname Release: %s, time: %s", keyname, event.time) def __view_toolbar_needs_update_size_cb(self, view_toolbar): - self._view.update_view_size(self._scrolled) + if not self._epub: + self._view.update_view_size(self._scrolled) def __view_toolbar_go_fullscreen_cb(self, view_toolbar): self.fullscreen() diff --git a/readtoolbar.py b/readtoolbar.py index d79c1ed..9af7ac4 100644 --- a/readtoolbar.py +++ b/readtoolbar.py @@ -23,6 +23,8 @@ import gobject import gtk import evince +import epubadapter + import md5 from sugar.graphics.toolbutton import ToolButton @@ -49,11 +51,10 @@ def get_md5(filename): #FIXME: Should be moved somewhere else class EditToolbar(activity.EditToolbar): __gtype_name__ = 'EditToolbar' - def __init__(self, evince_view): + def __init__(self): activity.EditToolbar.__init__(self) - self._evince_view = evince_view - self._evince_view.find_set_highlight_search(True) + self._evince_view = None self._document = None self._find_job = None @@ -97,6 +98,10 @@ class EditToolbar(activity.EditToolbar): self.insert(self._next, -1) self._next.show() + def set_view(self, view): + self._evince_view = view + self._evince_view.find_set_highlight_search(True) + def set_document(self, document): self._document = document @@ -112,9 +117,13 @@ class EditToolbar(activity.EditToolbar): self._clear_find_job() text = self._search_entry.props.text if text != "": - self._find_job = evince.JobFind(document=self._document, start_page=0, n_pages=self._document.get_n_pages(), text=text, case_sensitive=False) - self._find_updated_handler = self._find_job.connect('updated', self._find_updated_cb) - evince.job_scheduler_push_job(self._find_job, evince.JOB_PRIORITY_NONE) + try: + self._find_job = evince.JobFind(document=self._document, start_page=0, n_pages=self._document.get_n_pages(), text=text, case_sensitive=False) + self._find_updated_handler = self._find_job.connect('updated', self._find_updated_cb) + evince.job_scheduler_push_job(self._find_job, evince.JOB_PRIORITY_NONE) + except TypeError: + self._find_job = epubadapter.JobFind(document=self._document, start_page=0, n_pages=self._document.get_n_pages(), text=text, case_sensitive=False) + self._find_updated_handler = self._find_job.connect('updated', self._find_updated_cb) else: # FIXME: highlight nothing pass @@ -148,7 +157,7 @@ class EditToolbar(activity.EditToolbar): def _find_changed_cb(self, page, spec): self._update_find_buttons() - def _find_updated_cb(self, job, page): + def _find_updated_cb(self, job, page = None): self._evince_view.find_changed(job, page) def _find_prev_cb(self, button): @@ -182,10 +191,10 @@ class EditToolbar(activity.EditToolbar): class ReadToolbar(gtk.Toolbar): __gtype_name__ = 'ReadToolbar' - def __init__(self, evince_view, sidebar): + def __init__(self, sidebar): gtk.Toolbar.__init__(self) - self._evince_view = evince_view + self._evince_view = None self._sidebar = sidebar self._document = None @@ -200,7 +209,7 @@ class ReadToolbar(gtk.Toolbar): palette.menu.append(self._prev_bookmark) self._prev_bookmark.show_all() self._back.connect('clicked', self._go_back_cb) - self._prev_page.connect('activate', self._go_back_cb) + self._prev_page.connect('activate', self._go_back_page_cb) self._prev_bookmark.connect('activate', self._prev_bookmark_activate_cb) self.insert(self._back, -1) self._back.show() @@ -216,7 +225,7 @@ class ReadToolbar(gtk.Toolbar): palette.menu.append(self._next_bookmark) self._next_bookmark.show_all() self._forward.connect('clicked', self._go_forward_cb) - self._next_page.connect('activate', self._go_forward_cb) + self._next_page.connect('activate', self._go_forward_page_cb) self._next_bookmark.connect('activate', self._next_bookmark_activate_cb) self.insert(self._forward, -1) self._forward.show() @@ -286,7 +295,10 @@ class ReadToolbar(gtk.Toolbar): self.insert(bookmarkitem, -1) bookmarkitem.show_all() - + + def set_view(self, view): + self._evince_view = view + def set_document(self, document, filepath): filehash = get_md5(filepath) self._document = document @@ -315,11 +327,17 @@ class ReadToolbar(gtk.Toolbar): self._document.get_page_cache().set_current_page(page) entry.props.text = str(page + 1) - + def _go_back_cb(self, button): + self._evince_view.scroll(gtk.SCROLL_PAGE_BACKWARD, False) + + def _go_forward_cb(self, button): + self._evince_view.scroll(gtk.SCROLL_PAGE_FORWARD, False) + + def _go_back_page_cb(self, button): self._evince_view.previous_page() - def _go_forward_cb(self, button): + def _go_forward_page_cb(self, button): self._evince_view.next_page() def _prev_bookmark_activate_cb(self, menuitem): @@ -345,7 +363,7 @@ class ReadToolbar(gtk.Toolbar): else: self._sidebar.del_bookmark(page) - def _page_changed_cb(self, page, proxy): + def _page_changed_cb(self, page, proxy = None): self._update_nav_buttons() if hasattr(self._document, 'has_document_links'): if self._document.has_document_links(): @@ -391,11 +409,19 @@ class ReadToolbar(gtk.Toolbar): def _toc_select_active_page_foreach(self, model, path, iter, current_page): link = self._toc_model.get(iter, 1)[0] - if current_page == link.get_page(): - self._navigator.set_active_iter(iter) - return True + if not hasattr(link, 'get_page'): + #FIXME: This needs to be implemented in epubadapter, not here + filepath = self._evince_view.get_current_file() + if filepath.endswith(link): + self._navigator.set_active_iter(iter) + return True else: - return False + if current_page == link.get_page(): + self._navigator.set_active_iter(iter) + return True + + return False + def _toc_select_active_page(self): iter = self._navigator.get_active_iter() @@ -403,9 +429,14 @@ class ReadToolbar(gtk.Toolbar): current_link = self._toc_model.get(iter, 1)[0] current_page = self._document.get_page_cache().get_current_page() - if current_link.get_page() == current_page: - # Nothing to do - return + + if not hasattr(current_link, 'get_page'): + filepath = self._evince_view.get_current_file() + if filepath is None or filepath.endswith(current_link): + return + else: + if current_link.get_page() == current_page: + return self._navigator.handler_block(self.__navigator_changed_handler_id) self._toc_model.foreach(self._toc_select_active_page_foreach, current_page) @@ -424,10 +455,10 @@ class ViewToolbar(gtk.Toolbar): ([])) } - def __init__(self, evince_view): + def __init__(self): gtk.Toolbar.__init__(self) - self._evince_view = evince_view + self._evince_view = None self._document = None self._zoom_out = ToolButton('zoom-out') @@ -466,7 +497,6 @@ class ViewToolbar(gtk.Toolbar): self._zoom_spin = gtk.SpinButton() self._zoom_spin.set_range(5.409, 400) self._zoom_spin.set_increments(1, 10) - self._zoom_spin.props.value = self._evince_view.props.zoom * 100 self._zoom_spin_notify_value_handler = self._zoom_spin.connect( 'notify::value', self._zoom_spin_notify_value_cb) tool_item.add(self._zoom_spin) @@ -479,11 +509,6 @@ class ViewToolbar(gtk.Toolbar): self.insert(tool_item_zoom_perc_label, -1) tool_item_zoom_perc_label.show() - self._view_notify_zoom_handler = self._evince_view.connect( - 'notify::zoom', self._view_notify_zoom_cb) - - self._update_zoom_buttons() - spacer = gtk.SeparatorToolItem() spacer.props.draw = False self.insert(spacer, -1) @@ -495,10 +520,24 @@ class ViewToolbar(gtk.Toolbar): self.insert(self._fullscreen, -1) self._fullscreen.show() + self._view_notify_zoom_handler = None + + def set_view(self, view): + self._evince_view = view + self._zoom_spin.props.value = self._evince_view.props.zoom * 100 + self._view_notify_zoom_handler = self._evince_view.connect( + 'notify::zoom', self._view_notify_zoom_cb) + + self._update_zoom_buttons() + + def _zoom_spin_notify_value_cb(self, zoom_spin, pspec): + if not self._view_notify_zoom_handler: + return self._evince_view.disconnect(self._view_notify_zoom_handler) try: - self._evince_view.props.sizing_mode = evince.SIZING_FREE + if hasattr(self._evince_view.props, 'sizing_mode'): + self._evince_view.props.sizing_mode = evince.SIZING_FREE self._evince_view.props.zoom = zoom_spin.props.value / 100.0 finally: self._view_notify_zoom_handler = self._evince_view.connect( @@ -513,7 +552,8 @@ class ViewToolbar(gtk.Toolbar): 'notify::value', self._zoom_spin_notify_value_cb) def zoom_in(self): - self._evince_view.props.sizing_mode = evince.SIZING_FREE + if hasattr(self._evince_view.props, 'sizing_mode'): + self._evince_view.props.sizing_mode = evince.SIZING_FREE self._evince_view.zoom_in() self._update_zoom_buttons() @@ -521,7 +561,8 @@ class ViewToolbar(gtk.Toolbar): self.zoom_in() def zoom_out(self): - self._evince_view.props.sizing_mode = evince.SIZING_FREE + if hasattr(self._evince_view.props, 'sizing_mode'): + self._evince_view.props.sizing_mode = evince.SIZING_FREE self._evince_view.zoom_out() self._update_zoom_buttons() @@ -529,7 +570,8 @@ class ViewToolbar(gtk.Toolbar): self.zoom_out() def zoom_to_width(self): - self._evince_view.props.sizing_mode = evince.SIZING_FIT_WIDTH + if hasattr(self._evince_view.props, 'sizing_mode'): + self._evince_view.props.sizing_mode = evince.SIZING_FIT_WIDTH self.emit('needs-update-size') self._update_zoom_buttons() @@ -541,12 +583,14 @@ class ViewToolbar(gtk.Toolbar): self._zoom_out.props.sensitive = self._evince_view.can_zoom_out() def _zoom_to_fit_menu_item_activate_cb(self, menu_item): - self._evince_view.props.sizing_mode = evince.SIZING_BEST_FIT + if hasattr(self._evince_view.props, 'sizing_mode'): #XXX + self._evince_view.props.sizing_mode = evince.SIZING_BEST_FIT self.emit('needs-update-size') self._update_zoom_buttons() def _actual_size_menu_item_activate_cb(self, menu_item): - self._evince_view.props.sizing_mode = evince.SIZING_FREE + if hasattr(self._evince_view.props, 'sizing_mode'): + self._evince_view.props.sizing_mode = evince.SIZING_FREE self._evince_view.props.zoom = 1.0 self._update_zoom_buttons() |