diff options
author | Andi_G <andigros72@googlemail.com> | 2011-07-24 00:42:35 (GMT) |
---|---|---|
committer | Andi_G <andigros72@googlemail.com> | 2011-07-24 00:42:35 (GMT) |
commit | 06ae426ec0a44b7daecdfab54710e9d6181f2566 (patch) | |
tree | d0ab46ea53a251480b49418298427211e76dfdff /epubview | |
parent | b0b980e500f5707757d5b29233deb93004e117d8 (diff) |
now based on the latest read release
Diffstat (limited to 'epubview')
-rw-r--r-- | epubview/__init__.py | 2 | ||||
-rw-r--r-- | epubview/epub.py | 90 | ||||
-rw-r--r-- | epubview/epubinfo.py | 49 | ||||
-rw-r--r-- | epubview/epubview.py | 422 | ||||
-rw-r--r-- | epubview/jobs.py | 143 | ||||
-rw-r--r-- | epubview/navmap.py | 69 | ||||
-rw-r--r-- | epubview/widgets.py | 33 |
7 files changed, 477 insertions, 331 deletions
diff --git a/epubview/__init__.py b/epubview/__init__.py index 81a3175..5051bdf 100644 --- a/epubview/__init__.py +++ b/epubview/__init__.py @@ -21,4 +21,4 @@ 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 +from jobs import _JobFind as JobFind diff --git a/epubview/epub.py b/epubview/epub.py index 71267aa..063a982 100644 --- a/epubview/epub.py +++ b/epubview/epub.py @@ -17,11 +17,12 @@ import zipfile import tempfile -import os, os.path +import os from lxml import etree import shutil -import navmap, epubinfo +import navmap +import epubinfo class _Epub(object): @@ -32,28 +33,31 @@ class _Epub(object): self._ncxpath = 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_opf() self._get_ncx() - + ncxfile = self._zobject.open(self._ncxpath) - opffile = self._zobject.open(self._opfpath) + opffile = self._zobject.open(self._opfpath) self._navmap = navmap.NavMap(opffile, ncxfile, self._basepath) - + opffile = self._zobject.open(self._opfpath) - self._info = epubinfo.EpubInfo(opffile) - + self._info = epubinfo.EpubInfo(opffile) + self._unzip() - + def _unzip(self): - #self._zobject.extractall(path = self._tempdir) # This is broken upto python 2.7 + # This is broken upto python 2.7 + #self._zobject.extractall(path = self._tempdir) 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 + # Some weird zip file entries start with a slash, + # and we don't want to write to the root directory + if name.startswith(os.path.sep): name = name[1:] if name.endswith(os.path.sep) or name.endswith('\\'): os.makedirs(name) @@ -61,28 +65,27 @@ class _Epub(object): self._zobject.extract(name) os.chdir(orig_cwd) - def _get_opf(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'): + + for element in root.iterfind( + './/{urn:oasis:names:tc:opendocument:xmlns:container}rootfile'): if element.get('media-type') == 'application/oebps-package+xml': self._opfpath = element.get('full-path') - - if self._opfpath.rpartition('/')[0]: + + if self._opfpath.rpartition('/')[0]: self._basepath = self._opfpath.rpartition('/')[0] + '/' else: self._basepath = '' - - containerfile.close() + containerfile.close() def _get_ncx(self): opffile = self._zobject.open(self._opfpath) - + tree = etree.parse(opffile) root = tree.getroot() @@ -92,61 +95,64 @@ class _Epub(object): for element in root.iterfind('.//{http://www.idpf.org/2007/opf}item'): if element.get('id') == tocid: self._ncxpath = self._basepath + element.get('href') - + opffile.close() def _verify(self): ''' - Method to crudely check to verify that what we + 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 not mimetype.startswith('application/epub+zip'): # Some files seem to have trailing characters + + # Some files seem to have trailing characters + if not mimetype.startswith('application/epub+zip'): return False - + return True - + def get_toc_model(self): ''' Returns a GtkTreeModel representation of the Epub table of contents - ''' + ''' return self._navmap.get_gtktreestore() - + def get_flattoc(self): ''' Returns a flat (linear) list of files to be rendered. - ''' + ''' return self._navmap.get_flattoc() - + def get_basedir(self): ''' Returns the base directory where the contents of the epub has been unzipped ''' return self._tempdir - + def get_info(self): ''' - Returns a EpubInfo object for the open Epub file - ''' - return self._info - + Returns a EpubInfo object title + ''' + return self._info.title + def close(self): ''' - Cleans up (closes open zip files and deletes uncompressed content of Epub. - Please call this when a file is being closed or during application exit. - ''' + Cleans up (closes open zip files and deletes + uncompressed content of Epub. + Please call this when a file is being closed or during + application exit. + ''' self._zobject.close() shutil.rmtree(self._tempdir) diff --git a/epubview/epubinfo.py b/epubview/epubinfo.py index d25dfc2..b4e5c22 100644 --- a/epubview/epubinfo.py +++ b/epubview/epubinfo.py @@ -2,12 +2,16 @@ import os from lxml import etree -class EpubInfo(): #TODO: Cover the entire DC range +class EpubInfo(): + + #TODO: Cover the entire DC range + def __init__(self, opffile): self._tree = etree.parse(opffile) self._root = self._tree.getroot() - self._e_metadata = self._root.find('{http://www.idpf.org/2007/opf}metadata') - + 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() @@ -16,34 +20,34 @@ class EpubInfo(): #TODO: Cover the entire DC range 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') + ret = self._get_data( + './/{http://purl.org/dc/elements/1.1/}creator') except AttributeError: - return None + 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): @@ -51,7 +55,7 @@ class EpubInfo(): #TODO: Cover the entire DC range ret = self._get_data('.//{http://purl.org/dc/elements/1.1/}source') except AttributeError: return None - + return ret def _get_rights(self): @@ -59,32 +63,35 @@ class EpubInfo(): #TODO: Cover the entire DC range 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') + 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} + 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') + 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'): + 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 + + return subjectlist diff --git a/epubview/epubview.py b/epubview/epubview.py index 595eb20..c4b3df0 100644 --- a/epubview/epubview.py +++ b/epubview/epubview.py @@ -15,42 +15,43 @@ # 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 gtk import gobject import widgets import os.path import math import shutil +import BeautifulSoup 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) - } + 'scale': (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, - ([])) - } + 'page-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([int, int])), + 'selection-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 @@ -62,13 +63,13 @@ class _View(gtk.HBox): self.__going_fwd = True self.__going_back = False self.__page_changed = False - self.has_selection = False - self.zoom = 1.0 + self._has_selection = False + self.scale = 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', '/') @@ -79,69 +80,86 @@ class _View(gtk.HBox): 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._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._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) - + 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): ''' Sets document (should be a Epub instance) - ''' + ''' 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 + return self._has_selection + elif property.name == 'scale': + return self.scale else: - raise AttributeError, 'unknown property %s' % property.name + raise AttributeError('unknown property %s' % property.name) def do_set_property(self, property, value): - if property.name == 'zoom': + if property.name == 'scale': self.__set_zoom(value) else: - raise AttributeError, 'unknown property %s' % property.name - + raise AttributeError('unknown property %s' % property.name) + def get_has_selection(self): ''' Returns True if any part of the content is selected - ''' - return self.get_property('has-selection') - + ''' + return self._has_selection + def get_zoom(self): ''' Returns the current zoom level - ''' - return self.get_property('zoom') - + ''' + return self.get_property('scale') * 100.0 + def set_zoom(self, value): ''' Sets the current zoom level - ''' - self.set_property('zoom', value) - + ''' + self._view.set_zoom_level(value / 100.0) + + def _get_scale(self): + ''' + Returns the current zoom level + ''' + return self.get_property('scale') + + def _set_scale(self, value): + ''' + Sets the current zoom level + ''' + self.set_property('scale', value) + def zoom_in(self): ''' Zooms in (increases zoom level by 0.1) - ''' + ''' if self.can_zoom_in(): - self.set_zoom(self.get_zoom() + 0.1) + self._set_scale(self._get_scale() + 0.1) return True else: return False @@ -149,18 +167,18 @@ class _View(gtk.HBox): def zoom_out(self): ''' Zooms out (decreases zoom level by 0.1) - ''' + ''' if self.can_zoom_out(): - self.set_zoom(self.get_zoom() - 0.1) + self._set_scale(self._get_scale() - 0.1) return True else: return False - + def can_zoom_in(self): ''' Returns True if it is possible to zoom in further - ''' - if self.zoom < 4: + ''' + if self.scale < 4: return True else: return False @@ -168,58 +186,58 @@ class _View(gtk.HBox): def can_zoom_out(self): ''' Returns True if it is possible to zoom out further - ''' - if self.zoom > 0.5: + ''' + if self.scale > 0.5: return True else: return False - + def get_current_page(self): ''' Returns the currently loaded page - ''' + ''' return self._loaded_page - + def get_current_file(self): ''' Returns the currently loaded XML file - ''' + ''' #return self._loaded_filename - if self._paginator: + if self._paginator: return self._paginator.get_file_for_pageno(self._loaded_page) else: return None - + def get_pagecount(self): ''' Returns the pagecount of the loaded file - ''' + ''' return self._pagecount - + def set_current_page(self, n): ''' Loads page number n - ''' + ''' if n < 1 or n > self._pagecount: return False self._load_page(n) return True - + def next_page(self): ''' Loads next page if possible Returns True if transition to next page is possible and done - ''' + ''' if self._loaded_page == self._pagecount: return False self._load_next_page() return True - + def previous_page(self): ''' Loads previous page if possible Returns True if transition to previous page is possible and done - ''' + ''' if self._loaded_page == 1: return False self._load_prev_page() @@ -229,8 +247,11 @@ class _View(gtk.HBox): ''' Scrolls through the pages. Scrolling is horizontal if horizontal is set to True - Valid scrolltypes are: gtk.SCROLL_PAGE_BACKWARD and gtk.SCROLL_PAGE_FORWARD - ''' + Valid scrolltypes are: + gtk.SCROLL_PAGE_BACKWARD, gtk.SCROLL_PAGE_FORWARD, + gtk.SCROLL_STEP_BACKWARD, gtk.SCROLL_STEP_FORWARD + gtk.SCROLL_STEP_START and gtk.SCROLL_STEP_STOP + ''' if scrolltype == gtk.SCROLL_PAGE_BACKWARD: self.__going_back = True self.__going_fwd = False @@ -241,43 +262,67 @@ class _View(gtk.HBox): self.__going_fwd = True if not self._do_page_transition(): self._view.move_cursor(gtk.MOVEMENT_PAGES, 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_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_START: + self.__going_back = True + self.__going_fwd = False + if not self._do_page_transition(): + self.set_current_page(1) + elif scrolltype == gtk.SCROLL_END: + self.__going_back = False + self.__going_fwd = True + if not self._do_page_transition(): + self.set_current_page(self._pagecount - 1) else: print ('Got unsupported scrolltype %s' % str(scrolltype)) - + def copy(self): ''' Copies the current selection to clipboard. - ''' + ''' self._view.copy_clipboard() - + def find_next(self): ''' Highlights the next matching item for current search - ''' + ''' 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): + 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()) + 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): ''' Highlights the previous matching item for current search - ''' + ''' 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): + 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()) + 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) @@ -286,42 +331,43 @@ class _View(gtk.HBox): 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 - + self.scale = value + def __set_has_selection(self, value): - if value != self.has_selection: - self.has_selection = value - self.notify('has-selection') - + if value != self._has_selection: + self._has_selection = value + self.emit('selection-changed') + def _view_populate_popup_cb(self, view, menu): - menu.destroy() #HACK + menu.destroy() # HACK return - + def _view_selection_changed_cb(self, view): - # FIXME: This does not seem to be implemented in + # FIXME: This does not seem to be implemented in # webkitgtk yet - print view.has_selection() - + print "epubview _view_selection_changed_cb", view.has_selection() + self.__set_has_selection(view.has_selection()) + def _view_buttonrelease_event_cb(self, view, event): # Ugly hack + print "epubview _view_buttonrelease_event_cb", view.has_selection(), \ + view.can_copy_clipboard(), view.can_cut_clipboard() 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': + 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): @@ -332,8 +378,8 @@ class _View(gtk.HBox): self.__going_back = True self.__going_fwd = False - self._do_page_transition() - + self._do_page_transition() + def _do_page_transition(self): if self.__going_fwd: if self._v_vscrollbar.get_value() >= \ @@ -342,35 +388,37 @@ class _View(gtk.HBox): self._load_next_file() return True elif self.__going_back: - if self._v_vscrollbar.get_value() == self._v_vscrollbar.props.adjustment.props.lower: + 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): - - # Normally the line below would not be required - ugly workaround for + # Normally the line below would not be required - ugly workaround for # possible Webkit bug. See : https://bugs.launchpad.net/bugs/483231 self._sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER) - + 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 - - filename = filename.split('#')[0] # Get rid of anchors - + # Hack for making javascript work + filename = filename.replace('xhtml', 'xml') + + filename = filename.split('#')[0] # Get rid of anchors + 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))) + 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(), \ @@ -382,49 +430,81 @@ class _View(gtk.HBox): self._scroll_page_end() else: self._scroll_page() - + base_pageno = self._paginator.get_base_pageno_for_file(filename) scrollval = self._v_vscrollbar.get_value() scroll_upper = self._v_vscrollbar.props.adjustment.props.upper - if scroll_upper == 0: # This is a one page file + if scroll_upper == 0: # This is a one page file pageno = base_pageno else: - offset = (scrollval/scroll_upper) * self._paginator.get_pagecount_for_file(filename) + offset = (scrollval / scroll_upper) * \ + self._paginator.get_pagecount_for_file(filename) pageno = math.floor(base_pageno + offset) - + if pageno != self._loaded_page: - self._on_page_changed(int(pageno)) - + self._on_page_changed(0, int(pageno)) + + # prepare text to speech + html_file = open(self._loaded_filename) + soup = BeautifulSoup.BeautifulSoup(html_file) + body = soup.find('body') + tags = body.findAll(text=True) + self._all_text = ''.join([tag for tag in tags]) + self._prepare_text_to_speech(self._all_text) + + def _prepare_text_to_speech(self, page_text): + i = 0 + j = 0 + word_begin = 0 + word_end = 0 + ignore_chars = [' ', '\n', u'\r', '_', '[', '{', ']', '}', '|', + '<', '>', '*', '+', '/', '\\'] + ignore_set = set(ignore_chars) + self.word_tuples = [] + len_page_text = len(page_text) + while i < len_page_text: + if page_text[i] not in ignore_set: + word_begin = i + j = i + while j < len_page_text and page_text[j] not in ignore_set: + j = j + 1 + word_end = j + i = j + word_tuple = (word_begin, word_end, + page_text[word_begin: word_end]) + if word_tuple[2] != u'\r': + self.word_tuples.append(word_tuple) + i = i + 1 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)) - + 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 @@ -432,58 +512,87 @@ class _View(gtk.HBox): 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.__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 + # We don't need this if the next page is in another file + return - scrollfactor_next = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page + 1) + scrollfactor_next = \ + self._paginator.get_scrollfactor_pos_for_pageno( + self._loaded_page + 1) if scrollval > 0: - scrollfactor = scrollval/(scroll_upper - scroll_page_size) + scrollfactor = scrollval / (scroll_upper - scroll_page_size) else: scrollfactor = 0 if scrollfactor >= scrollfactor_next: - self._on_page_changed(self._loaded_page + 1) + self._on_page_changed(self._loaded_page, 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): + self._paginator.get_file_for_pageno( + self._loaded_page - 1): return - scrollfactor_cur = self._paginator.get_scrollfactor_pos_for_pageno(self._loaded_page) + scrollfactor_cur = \ + self._paginator.get_scrollfactor_pos_for_pageno( + self._loaded_page) if scrollval > 0: - scrollfactor = scrollval/(scroll_upper - scroll_page_size) + 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._on_page_changed(self._loaded_page, self._loaded_page - 1) + + def _on_page_changed(self, oldpage, 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') - + self.emit('page-changed', oldpage, pageno) + def _load_page(self, pageno): if pageno > self._pagecount or pageno < 1: #TODO: Cause an exception return - self._on_page_changed(pageno) + self._on_page_changed(self._loaded_page, pageno) filename = self._paginator.get_file_for_pageno(pageno) if filename != self._loaded_filename: #self._loaded_filename = filename + + """ + TODO: disabled because javascript can't be executed + with the velocity needed + # Copy javascript to highligth text to speech + destpath, destname = os.path.split(filename.replace('file://', '')) + shutil.copy('./epubview/highlight_words.js', destpath) + self._insert_js_reference(filename.replace('file://', ''), + destpath) + """ + if filename.endswith('xml'): dest = filename.replace('xml', 'xhtml') - shutil.copy(filename.replace('file://', ''), dest.replace('file://', '')) + shutil.copy(filename.replace('file://', ''), + dest.replace('file://', '')) self._view.open(dest) else: self._view.open(filename) else: self._scroll_page() - + + def _insert_js_reference(self, file_name, path): + js_reference = '<script type="text/javascript" ' + \ + 'src="./highlight_words.js"></script>' + o = open(file_name + '.tmp', 'a') + for line in open(file_name): + line = line.replace('</head>', js_reference + '</head>') + o.write(line + "\n") + o.close() + shutil.copy(file_name + '.tmp', file_name) + def _load_next_file(self): if self._loaded_page == self._pagecount: return @@ -493,7 +602,7 @@ class _View(gtk.HBox): pageno += 1 if self._paginator.get_file_for_pageno(pageno) != cur_file: break - + self._load_page(pageno) def _load_file(self, path): @@ -513,7 +622,7 @@ class _View(gtk.HBox): 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): @@ -526,7 +635,7 @@ class _View(gtk.HBox): self.__going_fwd = False self.__going_back = True if not self._do_page_transition(): - self._view.move_cursor(gtk.MOVEMENT_DISPLAY_LINES, -1) + 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: @@ -535,19 +644,21 @@ class _View(gtk.HBox): 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 - + print 'Warning: unknown scrolltype %s with value %f' \ + % (str(scrolltype), value) + + #FIXME: This should not be needed here + self._scrollbar.set_value(self._loaded_page) + 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) @@ -555,9 +666,6 @@ class _View(gtk.HBox): 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 index 19c8434..a0b6771 100644 --- a/epubview/jobs.py +++ b/epubview/jobs.py @@ -32,19 +32,21 @@ import threading PAGE_WIDTH = 135 PAGE_HEIGHT = 216 + def _pixel_to_mm(pixel, dpi): - inches = pixel/dpi - return int(inches/0.03937) + 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) + threading.Thread.__init__(self) self.obj = obj self.stopthread = threading.Event() @@ -57,50 +59,49 @@ class SearchThread(threading.Thread): if self._searchfile(f): self.obj._matchfilelist.append(entry) f.close() - + gtk.gdk.threads_enter() - self.obj._finished = True + 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 not tag.string is None: if tag.string.find(self.obj._text) > -1: return True - + return False - def run (self): + 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, - ([])) - } + '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() @@ -109,7 +110,7 @@ class _JobPaginator(gobject.GObject): 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() @@ -119,93 +120,97 @@ class _JobPaginator(gobject.GObject): 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 + #FIXME: This does not seem to work + #settings.props.auto_shrink_images = False 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.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.unmap() - + 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)) + 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 + 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) + pagelen = 1 / pages + self._pagemap[float(self._pagecount + i)] = \ + (f.props.uri, (i - 1) / math.ceil(pages), pagelen) + + self._pagecount += int(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): + + 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): ''' Returns the file in which pageno occurs - ''' + ''' return self._pagemap[pageno][0] - + def get_scrollfactor_pos_for_pageno(self, pageno): ''' Returns the position scrollfactor (fraction) for pageno - ''' + ''' return self._pagemap[pageno][1] def get_scrollfactor_len_for_pageno(self, pageno): ''' Returns the length scrollfactor (fraction) for pageno - ''' + ''' return self._pagemap[pageno][2] - + def get_pagecount_for_file(self, filename): ''' Returns the number of pages in file - ''' + ''' return self._filedict[filename][0] def get_base_pageno_for_file(self, filename): ''' Returns the pageno which begins in filename - ''' + ''' for key in self._pagemap.keys(): if self._pagemap[key][0].replace('file://', '') == filename: return key - + return None def get_remfactor_for_file(self, filename): ''' - Returns the remainder factor (1 - fraction length of last page in file) - ''' + Returns the remainder + factor (1 - fraction length of last page in file) + ''' return self._filedict[filename][1] - + def get_total_pagecount(self): ''' Returns the total pagecount for the Epub file @@ -215,20 +220,20 @@ class _JobPaginator(gobject.GObject): def get_total_height(self): ''' Returns the total height of the Epub in pixels - ''' + ''' return self._bookheight class _JobFind(gobject.GObject): __gsignals__ = { - 'updated': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])) + 'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } - def __init__(self, document, start_page, n_pages, text, case_sensitive=False): + + def __init__(self, document, start_page, n_pages, text, + case_sensitive=False): gobject.GObject.__init__(self) gtk.gdk.threads_init() - + self._finished = False self._document = document self._start_page = start_page @@ -239,58 +244,58 @@ class _JobFind(gobject.GObject): self._matchfilelist = [] self._current_file_index = 0 self.threads = [] - + s_thread = SearchThread(self) self.threads.append(s_thread) s_thread.start() - + def cancel(self): ''' Cancels the search job - ''' + ''' for s_thread in self.threads: s_thread.stop() - + def is_finished(self): ''' Returns True if the entire search job has been finished - ''' + ''' return self._finished - + def get_next_file(self): ''' Returns the next file which has the search pattern - ''' + ''' 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): ''' Returns the previous file which has the search pattern - ''' + ''' 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): ''' Returns the search text - ''' + ''' return self._text - + def get_case_sensitive(self): ''' Returns True if the search is case-sensitive - ''' + ''' return self._case_sensitive diff --git a/epubview/navmap.py b/epubview/navmap.py index a0a1799..612f2d1 100644 --- a/epubview/navmap.py +++ b/epubview/navmap.py @@ -1,18 +1,20 @@ from lxml import etree import gtk + class NavPoint(object): - def __init__(self, label, contentsrc, children = []): + + def __init__(self, label, contentsrc, children=[]): self._label = label self._contentsrc = contentsrc - self._children = children - + self._children = children + def get_label(self): return self._label - + def get_contentsrc(self): return self._contentsrc - + def get_children(self): return self._children @@ -25,69 +27,74 @@ class NavMap(object): self._root = self._tree.getroot() self._gtktreestore = gtk.TreeStore(str, str) self._flattoc = [] - + self._populate_flattoc() self._populate_toc() - + def _populate_flattoc(self): tree = etree.parse(self._opffile) root = tree.getroot() - + itemmap = {} manifest = root.find('.//{http://www.idpf.org/2007/opf}manifest') for element in manifest.iterfind('{http://www.idpf.org/2007/opf}item'): itemmap[element.get('id')] = element - - spine = root.find('.//{http://www.idpf.org/2007/opf}spine') + + spine = root.find('.//{http://www.idpf.org/2007/opf}spine') for element in spine.iterfind('{http://www.idpf.org/2007/opf}itemref'): idref = element.get('idref') href = itemmap[idref].get('href') self._flattoc.append(self._basepath + href) - + self._opffile.close() - + 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'): + 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') + 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/') + text = navpoint.find( + './{http://www.daisy.org/z3986/2005/ncx/}content/') return self._basepath + text.get('src') - def _process_navpoint(self, navpoint, parent = None): + def _process_navpoint(self, navpoint, parent=None): title = self._gettitle(navpoint) content = self._getcontent(navpoint) - + #print title, content - + iter = self._gtktreestore.append(parent, [title, content]) #self._flattoc.append((title, content)) - - childnavpointlist = list(navpoint.iterfind('./{http://www.daisy.org/z3986/2005/ncx/}navPoint')) - + + 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: + self._process_navpoint(childnavpoint, parent=iter) + else: return - + def get_gtktreestore(self): ''' Returns a GtkTreeModel representation of the Epub table of contents - ''' + ''' return self._gtktreestore - + def get_flattoc(self): ''' Returns a flat (linear) list of files to be rendered. - ''' + ''' return self._flattoc - -#t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx')
\ No newline at end of file + +#t = TocParser('/home/sayamindu/Desktop/Test/OPS/fb.ncx') diff --git a/epubview/widgets.py b/epubview/widgets.py index d80c45a..a603c34 100644 --- a/epubview/widgets.py +++ b/epubview/widgets.py @@ -5,27 +5,40 @@ import gtk class _WebView(webkit.WebView): def __init__(self): webkit.WebView.__init__(self) - + def get_page_height(self): ''' Gets height (in pixels) of loaded (X)HTML page. This is done via javascript at the moment - ''' - #TODO: Need to check status of page load - js = 'oldtitle=document.title;document.title=Math.max(document.body.scrollHeight, document.body.offsetHeight,document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);' + ''' + js = 'oldtitle=document.title;' + \ + 'if (document.body == null) {' + \ + 'document.title = 0} else {' + \ + 'document.title=Math.max(document.body.scrollHeight, ' + \ + 'document.body.offsetHeight,document.documentElement.clientHeight,' + \ + 'document.documentElement.scrollHeight, ' + \ + 'document.documentElement.offsetHeight)};' self.execute_script(js) ret = self.get_main_frame().get_title() js = 'document.title=oldtitle;' self.execute_script(js) - if ret is None: + try: + return int(ret) + except ValueError: return 0 - return int(ret) - + def add_bottom_padding(self, incr): ''' Adds incr pixels of padding to the end of the loaded (X)HTML page. This is done via javascript at the moment - ''' - js = ('var newdiv = document.createElement("div");newdiv.style.height = "%dpx";document.body.appendChild(newdiv);' % incr) + ''' + js = ('var newdiv = document.createElement("div");' + \ + 'newdiv.style.height = "%dpx";document.body.appendChild(newdiv);' \ + % incr) self.execute_script(js) - + + def highlight_next_word(self): + ''' + Highlight next word (for text to speech) + ''' + self.execute_script('highLightNextWord();') |