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 | |
parent | b0b980e500f5707757d5b29233deb93004e117d8 (diff) |
now based on the latest read release
-rw-r--r-- | MANIFEST | 1 | ||||
-rw-r--r-- | annoactivity.py | 390 | ||||
-rw-r--r-- | annobookmark.py | 90 | ||||
-rw-r--r-- | epubadapter.py | 132 | ||||
-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 | ||||
-rw-r--r-- | readdb.py | 524 | ||||
-rw-r--r-- | readsidebar.py | 149 | ||||
-rw-r--r-- | readtoolbar.py | 243 | ||||
-rw-r--r-- | readtopbar.py | 203 |
15 files changed, 1349 insertions, 1191 deletions
@@ -6,6 +6,7 @@ annobookmark.py readtopbar.py readsidebar.py annoactivity.py +annoicon.py anno_v1.db readtoolbar.py setup.py diff --git a/annoactivity.py b/annoactivity.py index e5a3bbc..ec52f49 100644 --- a/annoactivity.py +++ b/annoactivity.py @@ -25,17 +25,16 @@ import re import md5 import dbus -import evince import gobject import gtk import pango -from glib import GError import telepathy from sugar.activity import activity from sugar.graphics.toolbutton import ToolButton from sugar.graphics.toolbarbox import ToolbarBox from sugar.graphics.toolbarbox import ToolbarButton +from sugar.graphics.toolcombobox import ToolComboBox from sugar.graphics.toggletoolbutton import ToggleToolButton from sugar.graphics.menuitem import MenuItem from sugar.activity.widgets import ActivityToolbarButton @@ -43,21 +42,29 @@ from sugar.activity.widgets import StopButton from sugar import network from sugar import mime -from jarabe.journal import journalactivity +#from jarabe.journal import journalactivity from sugar.datastore import datastore from sugar.graphics.objectchooser import ObjectChooser -from readtoolbar import EditToolbar, ViewToolbar +from readtoolbar import EditToolbar +from readtoolbar import ViewToolbar +from readtoolbar import SpeechToolbar from readsidebar import Sidebar from readtopbar import TopBar +from readdb import AnnotationManager +import epubadapter +import evinceadapter +import textadapter +import speech -_EPUB_SUPPORT = True -try: - import epubadapter -except: - _EPUB_SUPPORT = False - +# +#_EPUB_SUPPORT = True +#try: +# import epubadapter +#except ImportError, e: +# _EPUB_SUPPORT = False +# logging.warning('Epub support disabled because: %s' % e) _HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager' _HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager' @@ -131,24 +138,25 @@ class AnnoActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) - if hasattr(evince, 'evince_embed_init'): - # if we use evince-2.24 - evince.evince_embed_init() - - self._epub = False + #self._epub = False self._document = None self._fileserver = None self._object_id = handle.object_id - self._url = '' + #self._url = '' + self._toc_model = None + + self._mimetype = None self.connect('key-press-event', self._key_press_event_cb) self.connect('key-release-event', self._key_release_event_cb) - self.connect('window-state-event', self._window_state_event_cb) + #self.connect('window-state-event', self._window_state_event_cb) _logger.debug('Starting Anno...') self._view = None - + self.dpi = _get_screen_dpi() + #self._model = None + self._sidebar = Sidebar() self._sidebar.show() @@ -174,8 +182,6 @@ class AnnoActivity(activity.Activity): edit_toolbar_button.show() self._view_toolbar = ViewToolbar() - self._view_toolbar.connect('needs-update-size', - self.__view_toolbar_needs_update_size_cb) self._view_toolbar.connect('go-fullscreen', self.__view_toolbar_go_fullscreen_cb) view_toolbar_button = ToolbarButton( @@ -213,12 +219,14 @@ class AnnoActivity(activity.Activity): spacer.show() navigator_toolbar = gtk.Toolbar() - navigator_item = gtk.ToolItem() + #navigator_item = gtk.ToolItem() self._navigator = self._create_navigator() - navigator_item.add(self._navigator) + #navigator_item.add(self._navigator) + combotool = ToolComboBox(self._navigator) + navigator_toolbar.insert(combotool, -1) + self._navigator.show() - navigator_toolbar.insert(navigator_item, -1) - navigator_item.show() + combotool.show() self._navigator_toolbar_button = ToolbarButton(page=navigator_toolbar, icon_name='view-list') navigator_toolbar.show() @@ -258,8 +266,26 @@ class AnnoActivity(activity.Activity): toolbar_box.toolbar.insert(annotator_downloader_item, -1) annotator_downloader_item.show() + + + self._highlight_item = gtk.ToolItem() + self._highlight = ToggleToolButton('format-text-underline') + self._highlight.set_tooltip(_('Highlight')) + self._highlight.props.sensitive = False + self._highlight_id = self._highlight.connect('clicked', \ + self.__highlight_cb) + self._highlight_item.add(self._highlight) + toolbar_box.toolbar.insert(self._highlight_item, -1) + self._highlight_item.show_all() + + self.speech_toolbar = SpeechToolbar(self) + self.speech_toolbar_button = ToolbarButton(page=self.speech_toolbar, + icon_name='speak') + toolbar_box.toolbar.insert(self.speech_toolbar_button, -1) + + stop_button = StopButton(self) - stop_button.props.accelerator = '<Ctrl><Shift>Q' + #stop_button.props.accelerator = '<Ctrl><Shift>Q' toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() @@ -294,7 +320,7 @@ class AnnoActivity(activity.Activity): self._tempfile = None self._close_requested = False - self._journal = journalactivity.get_journal() + #self._journal = journalactivity.get_journal() fname = os.path.join('/etc', 'inhibit-ebook-sleep') @@ -344,6 +370,21 @@ class AnnoActivity(activity.Activity): #else: # self._load_document('file:///home/smcv/tmp/test.pdf') + + def fullscreen(self): + self._topbar.show_all() + activity.Activity.fullscreen(self) + + + def unfullscreen(self): + self._topbar.hide() + activity.Activity.unfullscreen(self) + + + + + + def _create_back_button(self): back = ToolButton('go-previous') back.set_tooltip(_('Back')) @@ -436,12 +477,13 @@ class AnnoActivity(activity.Activity): else: page = 0 - if page >= self._document.get_n_pages(): - page = self._document.get_n_pages() - 1 - elif page < 0: - page = 0 + #if page >= self._view.get_pagecount(): + # page = self._view.get_pagecount() - 1 + #elif page < 0: + # page = 0 - self._document.get_page_cache().set_current_page(page) + #self._model.props.page = page + self._view.set_current_page(page) entry.props.text = str(page + 1) def __go_back_cb(self, button): @@ -456,38 +498,66 @@ class AnnoActivity(activity.Activity): def __go_forward_page_cb(self, button): self._view.next_page() + def __highlight_cb(self, button): + tuples_list = self._annotationmanager.get_highlights( + self._view.get_current_page()) + selection_tuple = self._view.get_selection_bounds() + cursor_position = self._view.get_cursor_position() + + old_highlight_found = None + for compare_tuple in tuples_list: + if selection_tuple: + if selection_tuple[0] >= compare_tuple[0] and \ + selection_tuple[1] <= compare_tuple[1]: + old_highlight_found = compare_tuple + break + if cursor_position >= compare_tuple[0] and \ + cursor_position <= compare_tuple[1]: + old_highlight_found = compare_tuple + break + + if old_highlight_found == None: + self._annotationmanager.add_highlight( + self._view.get_current_page(), selection_tuple) + else: + self._annotationmanager.del_highlight( + self._view.get_current_page(), old_highlight_found) + + self._view.show_highlights(self._annotationmanager.get_highlights( + self._view.get_current_page())) + + + + def __prev_annotation_activate_cb(self, menuitem): - page = self._document.get_page_cache().get_current_page() + page = self._view.get_current_page() annomanager = self._sidebar.get_annotationmanager() prev_anno = annomanager.get_prev_annotation(page) if prev_anno is not None: _logger.debug('prev annotation page is %d' % prev_anno.page) - self._document.get_page_cache().set_current_page(prev_anno.page + 1) - + self._view.set_current_page(prev_anno.page) def __next_annotation_activate_cb(self, menuitem): - page = self._document.get_page_cache().get_current_page() + page = self._view.get_current_page() annomanager = self._sidebar.get_annotationmanager() next_anno= annomanager.get_next_annotation(page) if next_anno is not None: _logger.debug('next annotation page is %d' % next_anno.page) - self._document.get_page_cache().set_current_page(next_anno.page + 1) - - + self._view.set_current_page(next_anno.page) def __bookmarker_toggled_cb(self, button): - page = self._document.get_page_cache().get_current_page() + page = self._view.get_current_page() if self._bookmarker.props.active: self._sidebar.add_bookmark(page) else: self._sidebar.del_bookmark(page) def __annotator_toggled_cb(self, button): - page = self._document.get_page_cache().get_current_page() + page = self._view.get_current_page() if self._annotator.props.active: self._sidebar.add_annotation(page) else: @@ -500,45 +570,39 @@ class AnnoActivity(activity.Activity): def __annotator_downloader_toggled_cb(self, button): self._sidebar.download_annotations() - - def __page_changed_cb(self, page, proxy = None): + def __page_changed_cb(self, model, page_from, page_to): self._update_nav_buttons() - if hasattr(self._document, 'has_document_links'): - if self._document.has_document_links(): - self._toc_select_active_page() - - self._sidebar.update_for_page(self._document.get_page_cache().get_current_page()) + if self._toc_model != None: + self._toc_select_active_page() + self._sidebar.update_for_page(self._view.get_current_page()) self._annotator.handler_block(self._annotator_toggle_handler_id) - self._annotator.props.active = self._sidebar.is_showing_local_bookmark() + self._annotator.props.active = self._sidebar.is_showing_local_annotation() self._annotator.handler_unblock(self._annotator_toggle_handler_id) + tuples_list = self._annotationmanager.get_highlights( + self._view.get_current_page()) + if self._view.can_highlight(): + self._view.show_highlights(tuples_list) + + + def _update_nav_buttons(self): - current_page = self._document.get_page_cache().get_current_page() + current_page = self._view.get_current_page() self._back_button.props.sensitive = current_page > 0 self._forward_button.props.sensitive = \ - current_page < self._document.get_n_pages() - 1 + current_page < self._view.get_pagecount() - 1 self._num_page_entry.props.text = str(current_page + 1) self._total_page_label.props.label = \ - ' / ' + str(self._document.get_n_pages()) + ' / ' + str(self._view.get_pagecount()) def _update_toc(self): - if hasattr(self._document, 'has_document_links'): - if self._document.has_document_links(): - self._navigator_toolbar_button.show() - self._navigator.show_all() - - self._toc_model = self._document.get_links_model() - self._navigator.set_model(self._toc_model) - self._navigator.set_active(0) - - self._navigator_changed_handler_id = \ - self._navigator.connect('changed', - self.__navigator_changed_cb) - - self._toc_select_active_page() + if self._view.update_toc(self): + self._navigator_changed_handler_id = \ + self._navigator.connect('changed', self.__navigator_changed_cb) + def __navigator_changed_cb(self, combobox): iter = self._navigator.get_active_iter() @@ -565,8 +629,7 @@ class AnnoActivity(activity.Activity): iter = self._navigator.get_active_iter() current_link = self._toc_model.get(iter, 1)[0] - current_page = self._document.get_page_cache().get_current_page() - + current_page = self._view.get_current_page() if not hasattr(current_link, 'get_page'): filepath = self._view.get_current_file() @@ -600,6 +663,7 @@ class AnnoActivity(activity.Activity): if jobject and jobject.file_path: self.read_file(jobject.file_path) properties = jobject.metadata.get_dictionary().copy() + _logger.debug('\n\n\nthe metadata properties: %s' % str(properties)) self._url = properties['url'] finally: chooser.destroy() @@ -669,23 +733,13 @@ class AnnoActivity(activity.Activity): try: self.metadata['Anno_current_page'] = \ - str(self._document.get_page_cache().get_current_page()) - - self.metadata['Anno_zoom'] = str(self._view.props.zoom) + str(self._model.props.page) + self.metadata['Anno_zoom'] = str(self._view.get_current_page()) + self.metadata['url'] = self._url - if not self._epub: - if self._view.props.sizing_mode == evince.SIZING_BEST_FIT: - self.metadata['Anno_sizing_mode'] = "best-fit" - elif self._view.props.sizing_mode == evince.SIZING_FREE: - self.metadata['Anno_sizing_mode'] = "free" - elif self._view.props.sizing_mode == evince.SIZING_FIT_WIDTH: - self.metadata['Anno_sizing_mode'] = "fit-width" - else: - _logger.error("Don't know how to save sizing_mode state '%s'" % - self._view.props.sizing_mode) - self.metadata['Anno_sizing_mode'] = "fit-width" - + self._view.update_metadata(self) + self.metadata['Anno_search'] = \ self._edit_toolbar._search_entry.props.text @@ -694,6 +748,7 @@ class AnnoActivity(activity.Activity): self.metadata['Anno_search'] = \ self._edit_toolbar._search_entry.props.text + self.metadata['activity'] = self.get_bundle_id() self.metadata['url'] = self._url _logger.debug(str('set url to %s' % self._url)) @@ -814,109 +869,50 @@ class AnnoActivity(activity.Activity): self.watch_for_tubes() gobject.idle_add(self._get_document) - 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:// - + """ - _logger.debug('_load_document filepath: %s ' % filepath ) + filename = filepath.replace('file://', '') + if not os.path.exists(filename) or os.path.getsize(filename) == 0: + return mimetype = mime.get_for_file(filepath) + self._mimetype = mimetype if mimetype == 'application/epub+zip': - if not _EPUB_SUPPORT: - self.close() - self._epub = True - self._setup_epub_viewer() - self._document = epubadapter.EpubDocument(self._view, filepath.replace('file://', '')) + self._view = epubadapter.EpubViewer() + elif mimetype == 'text/plain' or mimetype == 'application/zip': + self._view = textadapter.TextViewer() else: - self._setup_evince_viewer() - #get the url from the object in the journal that matches this file path + self._view = evinceadapter.EvinceViewer() - try: - self._document = evince.factory_get_document(filepath) - except GError, e: - _logger.error('Can not load document: %s', e) - return + self._view.setup(self) + self._view.load_document(filepath) + + self._want_document = False self._view_toolbar.set_view(self._view) self._edit_toolbar.set_view(self._view) - self._want_document = False - self._view.set_document(self._document) - self._edit_toolbar.set_document(self._document) - self._topbar.set_document(self._document) + self._topbar.set_view(self._view) filehash = get_md5(filepath) - self._sidebar.set_bookmarkmanager(filehash) - self._sidebar.set_annotationmanager(filehash, mimetype) - + self._annotationmanager = AnnotationManager(filehash, self._mimetype, self._sidebar) + self._sidebar.set_annotationmanager(self._annotationmanager) self._update_nav_buttons() self._update_toc() - - page_cache = self._document.get_page_cache() - page_cache.connect('page-changed', self.__page_changed_cb) - - if not self.metadata['title_set_by_user'] == '1': - info = self._document.get_info() - if info and info.title: - self.metadata['title'] = info.title - - if not self._epub: - sizing_mode = self.metadata.get('Anno_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 - if hasattr(self._view, 'update_view_size'): - 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('Anno_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 - if hasattr(self._view, 'update_view_size'): - self._view.update_view_size(self._scrolled) - else: - # this may happen when we get a document from a buddy with a later - # version of Anno, for example. - _logger.warning("Unknown sizing_mode state '%s'", sizing_mode) - if self.metadata.get('Anno_zoom', None) is not None: - self._view.props.zoom = float(self.metadata['Anno_zoom']) - - self._view_toolbar._update_zoom_buttons() + self._view.connect_page_changed_handler(self.__page_changed_cb) + self._view.load_metadata(self) + self._update_toolbars() self._edit_toolbar._search_entry.props.text = \ self.metadata.get('Anno_search', '') current_page = int(self.metadata.get('Anno_current_page', '0')) _logger.debug('Setting page to: %d', current_page) - self._document.get_page_cache().set_current_page(current_page) + self._view.set_current_page(current_page) # We've got the document, so if we're a shared activity, offer it try: @@ -926,6 +922,14 @@ class AnnoActivity(activity.Activity): except Exception, e: _logger.debug('Sharing failed: %s', e) + def _update_toolbars(self): + self._view_toolbar._update_zoom_buttons() + if not self._view.can_highlight(): + self._highlight_item.hide() + if speech.supported and self._view.can_do_text_to_speech(): + self.speech_toolbar_button.show() + self.speech_toolbar_button.show() + def _share_document(self): """Share the document.""" # FIXME: should ideally have the fileserver listen on a Unix socket @@ -988,8 +992,35 @@ class AnnoActivity(activity.Activity): self.watch_for_tubes() self._share_document() - def _view_notify_has_selection_cb(self, view, pspec): - self._edit_toolbar.copy.set_sensitive(self._view.props.has_selection) + def _view_selection_changed_cb(self, view): + self._edit_toolbar.copy.props.sensitive = view.get_has_selection() + if self._view.can_highlight(): + # Verify if the selection already exist or the cursor + # is in a highlighted area + cursor_position = self._view.get_cursor_position() + logging.debug('cursor position %d' % cursor_position) + selection_tuple = self._view.get_selection_bounds() + tuples_list = self._annotationmanager.get_highlights( \ + self._view.get_current_page()) + in_bounds = False + for highlight_tuple in tuples_list: + logging.debug('control tuple %s' % str(highlight_tuple)) + if selection_tuple: + if selection_tuple[0] >= highlight_tuple[0] and \ + selection_tuple[1] <= highlight_tuple[1]: + in_bounds = True + break + if cursor_position >= highlight_tuple[0] and \ + cursor_position <= highlight_tuple[1]: + in_bounds = True + break + + self._highlight.props.sensitive = \ + view.get_has_selection() or in_bounds + + self._highlight.handler_block(self._highlight_id) + self._highlight.set_active(in_bounds) + self._highlight.handler_unblock(self._highlight_id) def _edit_toolbar_copy_cb(self, button): self._view.copy() @@ -1006,6 +1037,30 @@ class AnnoActivity(activity.Activity): elif keyname == 'KP_End': self._view_toolbar.zoom_out() return True + elif keyname == 'Home': + self._view.scroll(gtk.SCROLL_START, False) + return True + elif keyname == 'End': + self._view.scroll(gtk.SCROLL_END, False) + return True + elif keyname == 'Page_Up' or keyname == 'KP_Page_Up': + self._view.scroll(gtk.SCROLL_PAGE_BACKWARD, False) + return True + elif keyname == 'Page_Down' or keyname == 'KP_Page_Down': + self._view.scroll(gtk.SCROLL_PAGE_FORWARD, False) + return True + elif keyname == 'Up' or keyname == 'KP_Up': + self._view.scroll(gtk.SCROLL_STEP_BACKWARD, False) + return True + elif keyname == 'Down' or keyname == 'KP_Down': + self._view.scroll(gtk.SCROLL_STEP_FORWARD, False) + return True + elif keyname == 'Left' or keyname == 'KP_Left': + self._view.scroll(gtk.SCROLL_STEP_BACKWARD, True) + return True + elif keyname == 'Right' or keyname == 'KP_Right': + self._view.scroll(gtk.SCROLL_STEP_FORWARD, True) + return True else: return False @@ -1014,15 +1069,6 @@ class AnnoActivity(activity.Activity): #_logger.debug("Keyname Release: %s, time: %s", keyname, event.time) return False - def _window_state_event_cb(self, window, event): - if not (event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN): - return False - - if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: - self._topbar.show_all() - else: - self._topbar.hide() - def __view_toolbar_needs_update_size_cb(self, view_toolbar): if hasattr(self._view, 'update_view_size'): self._view.update_view_size(self._scrolled) diff --git a/annobookmark.py b/annobookmark.py index b1d7468..b5ac9fe 100644 --- a/annobookmark.py +++ b/annobookmark.py @@ -15,8 +15,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import cjson +import simplejson +#import cjson import logging +import gconf +from sugar.graphics.xocolor import XoColor _logger = logging.getLogger('anno-activity') @@ -41,14 +44,17 @@ class Bookmark: if self.content == '' or self.content is None: return '' - note = cjson.decode(self.content) + note = simplejson.loads(self.content) + #note = cjson.decode(self.content) + return note['title'] def get_note_body(self): if self.content == '' or self.content is None: return '' - note = cjson.decode(self.content) + note = simplejson.loads(self.content) + #note = cjson.decode(self.content) return note['body'] @@ -62,22 +68,31 @@ class AnnoBookmark: self.title = data[3] self.content = data[4] self.bodyurl = data[5] - self.context = data[6] - self.created = data[7] - self.modified = data[8] - self.creator = data[9] - self.annotates = data[10] - self.rdf = data[11] - self.color = data[12] + self.texttitle = data[6] + self.textcreator = data[7] + self.created = data[8] + self.modified = data[9] + self.creator = data[10] + self.annotates = data[11] + + if isinstance(data[12], str) or isinstance(data[12], unicode): + self.color = XoColor(data[12]) + elif isinstance(data[12], XoColor): + self.color = data[12] + else: + self.color = XoColor(" ") + self.local = data[13] self.mimetype = data[14] self.uuid = data[15] self.annotationurl = data[16] - self.bodysvg = data[17] if ( ( self.uuid == None ) or ( len( self.uuid ) == 0 ) ) and ( self.md5 != None ) and ( self.id != None ): self.uuid = "urn:sugaruuid:" + self.creator + "-" + self.md5 + "-" + str(self.id) _logger.debug('annobookmark annotates is %s' % self.annotates) + if ( self.annotationurl == None ): + self.annotationurl = '' + def __str__(self): r = str( "A bookmark: id: %s \nuuid: %s" % ( str( self.id ), self.uuid ) ) @@ -85,12 +100,10 @@ class AnnoBookmark: r += str( "\npage: %d\ntitle: %s" % ( self.page, self.title ) ) r += str( "\ncontent: %s" % self.content ) r += str( "\nbodyurl: %s \nannotationurl: %s" % ( self.bodyurl, self.annotationurl ) ) - r += str( "\ncontext: %s \ncreated: %s" % ( self.context, str( self.created ) ) ) - r += str( "\nmodified: %s \ncreator: %s" % ( str( self.modified ), self.creator ) ) + r += str( "\ncreated: %s \nmodified: %s" % ( self.created, str( self.modified ) ) ) + r += str( "\ncreator: %s" % ( self.creator ) ) r += str( "\nannotates: %s \nmimetype: %s" % ( self.annotates, self.mimetype ) ) - r += str( "\nrdf: %s" % self.rdf ) r += str( "\ncolor: %s \nlocal: %s" % ( str( self.color ), str( self.local ) ) ) - r += str( "\nbodysvg: %s" % self.bodysvg ) return r @@ -116,13 +129,6 @@ class AnnoBookmark: return '' return self.content - def set_bodysvg(self, bodysvg): - self.bodysvg = bodysvg - - - def get_bodysvg(self): - return self.bodysvg - def get_id(self): return self.id @@ -147,9 +153,6 @@ class AnnoBookmark: def set_annotationurl(self, url): self.annotationurl = url - def get_context(self): - return self.context - def get_created(self): return self.created @@ -162,12 +165,18 @@ class AnnoBookmark: def get_annotates(self): return self.annotates - def get_rdf(self): - return self.rdf + def get_texttitle(self): + return self.texttitle - def set_rdf(self, rdf): - self.rdf = rdf + def set_texttitle(self, title): + self.texttitle = title + + def get_textcreator(self): + return self.texttitle + def set_textcreator(self, creator): + self.textcreator = creator + def get_color(self): return self.color @@ -179,3 +188,26 @@ class AnnoBookmark: def get_uuid(self): return self.uuid + + def get_json(self): + arr = { + 'id' : self.id, + 'md5' : self.md5, + 'page' : self.page, + 'title' : self.title, + 'content' : self.content, + 'bodyurl' : self.bodyurl, + 'texttitle' : self.texttitle, + 'textcreator' : self.textcreator, + 'created' : self.created, + 'modified' : self.modified, + 'creator' : self.creator, + 'annotates' : self.annotates, + 'color' : self.color.to_string(), + 'local' : self.local, + 'mimetype' : self.mimetype, + 'uuid' : self.uuid, + 'annotationurl' : self.annotationurl + } + return simplejson.dumps(arr) + #return cjson.encode(arr) diff --git a/epubadapter.py b/epubadapter.py index cfb50d2..8def465 100644 --- a/epubadapter.py +++ b/epubadapter.py @@ -2,13 +2,104 @@ import gobject import logging import epubview +import speech -_logger = logging.getLogger('anno-activity') +from cStringIO import StringIO + +_logger = logging.getLogger('read-activity') + + +class EpubViewer(epubview.EpubView): -class View(epubview.EpubView): def __init__(self): epubview.EpubView.__init__(self) + def setup(self, activity): + self.set_screen_dpi(activity.dpi) + self.connect('selection-changed', + activity._view_selection_changed_cb) + + activity._hbox.pack_start(self, expand=True, fill=True) + self.show_all() + # text to speech initialization + self.current_word = 0 + self.word_tuples = [] + + def load_document(self, file_path): + self.set_document(EpubDocument(self, file_path.replace('file://', ''))) + speech.highlight_cb = self.highlight_next_word + speech.end_text_cb = self.get_more_text + + def load_metadata(self, activity): + + self.metadata = activity.metadata + + if not self.metadata['title_set_by_user'] == '1': + title = self._epub._info._get_title() + if title: + self.metadata['title'] = title + + def update_metadata(self, activity): + pass + + def zoom_to_width(self): + pass + + def zoom_to_best_fit(self): + pass + + def zoom_to_actual_size(self): + pass + + def can_zoom_to_width(self): + return False + + def can_highlight(self): + return False + + def can_do_text_to_speech(self): + return True + + def get_marked_words(self): + "Adds a mark between each word of text." + i = self.current_word + file_str = StringIO() + file_str.write('<speak> ') + end_range = i + 40 + if end_range > len(self.word_tuples): + end_range = len(self.word_tuples) + for word_tuple in self.word_tuples[self.current_word:end_range]: + file_str.write('<mark name="' + str(i) + '"/>' + word_tuple[2]) + i = i + 1 + self.current_word = i + file_str.write('</speak>') + return file_str.getvalue() + + def get_more_text(self): + if self.current_word < len(self.word_tuples): + speech.stop() + more_text = self.get_marked_words() + speech.play(more_text) + + def highlight_next_word(self, word_count): + pass + """ + TODO: disabled because javascript can't be executed + with the velocity needed + self.current_word = word_count + self._view.highlight_next_word() + return True + """ + + def connect_zoom_handler(self, handler): + self._zoom_handler = handler + self._view_notify_zoom_handler = \ + self.connect('notify::scale', handler) + return self._view_notify_zoom_handler + + def connect_page_changed_handler(self, handler): + self.connect('page-changed', handler) + def _try_load_page(self, n): if self._ready: self._load_page(n) @@ -20,11 +111,12 @@ class View(epubview.EpubView): return def find_set_highlight_search(self, set_highlight_search): + #TODO : what is this? 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 + # 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: @@ -35,21 +127,39 @@ class View(epubview.EpubView): def get_current_page(self): return int(self._loaded_page - 1) - def find_changed(self, job, page = None): + def update_toc(self, activity): + if self._epub.has_document_links(): + activity._navigator_toolbar_button.show() + activity._navigator.show_all() + + activity._toc_model = self._epub.get_links_model() + activity._navigator.set_model(activity._toc_model) + activity._navigator.set_active(0) + return True + else: + return False + + def find_changed(self, job, page=None): self._find_changed(job) def handle_link(self, link): self._load_file(link) + def setup_find_job(self, text, updated_cb): + self._find_job = JobFind(document=self._epub, + start_page=0, n_pages=self.get_pagecount(), + text=text, case_sensitive=False) + self._find_updated_handler = self._find_job.connect('updated', + updated_cb) + return self._find_job, self._find_updated_handler + 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()) @@ -59,6 +169,10 @@ class EpubDocument(epubview.Epub): 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) + + 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 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();') @@ -20,15 +20,19 @@ import logging import os, os.path import shutil import sqlite3 +import random +import hashlib import time import gconf -import cjson +import simplejson +#import cjson import urllib, urllib2 import re from xml.dom import minidom from sugar.datastore import datastore from sugar import mime from annobookmark import AnnoBookmark, Bookmark +from sugar.graphics.xocolor import XoColor _logger = logging.getLogger('anno-activity') @@ -36,7 +40,6 @@ _logger = logging.getLogger('anno-activity') def _init_db(): dbdir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data') dbpath = os.path.join(dbdir, 'anno_v1.db') - olddbpath = os.path.join(dbdir, 'anno.db') srcpath = os.path.join(os.environ['SUGAR_BUNDLE_PATH'], 'anno_v1.db') @@ -45,35 +48,42 @@ def _init_db(): return dbpath #Situation 1: DB is non-existent at all - if not os.path.exists(dbpath) and not os.path.exists(olddbpath): + if not os.path.exists(dbpath): try: os.makedirs(dbdir) except: pass shutil.copy(srcpath, dbpath) return dbpath - + + #Situation 2: DB is outdated - if not os.path.exists(dbpath) and os.path.exists(olddbpath): - shutil.copy(olddbpath, dbpath) - + """ + if not os.path.exists(dbpath): conn = sqlite3.connect(dbpath) - conn.execute("CREATE TABLE temp_bookmarks AS SELECT md5, page, 'content', timestamp, user, color, local FROM bookmarks") - conn.execute("ALTER TABLE bookmarks RENAME TO bookmarks_old") - conn.execute("ALTER TABLE temp_bookmarks RENAME TO bookmarks") - conn.execute("DROP TABLE bookmarks_old") #conn.execute("DROP TABLE annotations") - conn.execute("CREATE TABLE annotations (id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, context, created TIMESTAMP, modified TIMESTAMP, creator, annotates, rdf, color, local, mimetype, uuid, annotationurl, bodysvg)") + conn.execute("CREATE TABLE annotations (id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl)") + conn.execute("CREATE TABLE annuserid (username, userid)") conn.commit() conn.close() return dbpath - + """ # Should not reach this point return None +def _init_db_highlights(conn): + conn.execute('CREATE TABLE IF NOT EXISTS HIGHLIGHTS ' + + '(md5 TEXT, page INTEGER, ' + + 'init_pos INTEGER, end_pos INTEGER)') + conn.commit() + + + + + class BookmarkManager: def __init__(self, filehash): self._filehash = filehash @@ -171,21 +181,6 @@ class BookmarkManager: #working on: upon update of an annotation, all annotations are deleted and new one's created => needs to be fixed class AnnotationManager: - #Namespaces - #cnt http://www.w3.org/2008/content# Content in RDF [Content] - #dc http://purl.org/dc/elements/1.1/ Dublin Core elements [DC Elements] - #dcterms http://purl.org/dc/terms/ Dublin Core terms [DC Terms] - #foaf http://xmlns.com/foaf/0.1/ Friend of a Friend vocabulary terms [FOAF] - #oac http://www.openannotation.org/ns/ OAC vocabulary terms [OAC] - #ore http://www.openarchives.org/ore/terms/ ORE vocabulary terms - #rdf http://www.w3.org/1999/02/22-rdf-syntax-ns# RDF vocabulary terms [RDF Vocabulary] - #rdfs http://www.w3.org/2001/01/rdf-schema# RDF Schema vocabulary [RDF Vocabulary] - #bibo http://purl.org/ontology/bibo/ Bibliographic vocabulary - #oac:Annotation http://www.openannotation.org/ns/Annotation - #oac:hasTarget http://www.openannotation.org/ns/hasTarget - #oac:hasBody http://www.openannotation.org/ns/hasBody - #oac:Target http://www.openannotation.org/ns/Target - #oac:Body http://www.openannotation.org/ns/Body def __init__(self, filehash, mimetype, sidebar): @@ -207,56 +202,112 @@ class AnnotationManager: self._annotationurl = '' self._bodyurl = '' self._annotates = '' - self._context = '' self._modified = '' self._bodysvg = '' self._id = '' self._rdf = '' self.modifiedtolerance = 10 - #self._annotationserver='http://localhost/wordpress/wp-content/plugins/annotation/annotation.php' - self._annotationserver='http://www.andreasgros.net/wp-content/plugins/annotation/annotation.php' + #self._annotationserver='http://localhost/anno/index.php' + self._annotationserver='http://anno.treehouse.su/anno/index.php' + #self._annotationserver='http://www.andreasgros.net/wp-content/plugins/annotation/annotation.php' self.get_etext_url() self._to_delete = [] + + self._texttitle = '' + self._textcreator = '' + self._annojson = '' + self.remotecreators = [] + self.remotecolors = {} + self._highlights = {} dbpath = _init_db() assert dbpath != None self._conn = sqlite3.connect(dbpath) + + _init_db_highlights(self._conn) + self._conn.text_factory = lambda x: unicode(x, 'utf-8', 'ignore') self._annotations = [] self._populate_annotations() - - def parse_userid(self, rdf): - userid = '' - dom = minidom.parseString(rdf) - userid = self.get_node_data(dom.getElementsByTagName("dcterms:identifier")[0]) - _logger.debug('got this userid from the server: %s' % userid) - return userid + + def get_highlights(self, page): + try: + return self._highlights[page] + except KeyError: + self._highlights[page] = [] + return self._highlights[page] + + + + def add_highlight(self, page, highlight_tuple): + logging.error('Adding hg page %d %s' % (page, highlight_tuple)) + self.get_highlights(page).append(highlight_tuple) + + t = (self._filehash, page, highlight_tuple[0], + highlight_tuple[1]) + self._conn.execute('insert into highlights values ' + \ + '(?, ?, ?, ?)', t) + self._conn.commit() + + def del_highlight(self, page, highlight_tuple): + self._highlights[page].remove(highlight_tuple) + t = (self._filehash, page, highlight_tuple[0], \ + highlight_tuple[1]) + self._conn.execute('delete from highlights ' + \ + 'where md5=? and page=? and init_pos=? and end_pos=?', \ + t) + self._conn.commit() + + def _populate_highlights(self): + rows = self._conn.execute('select * from highlights ' + \ + 'where md5=? order by page', (self._filehash, )) + for row in rows: + page = row[1] + init_pos = row[2] + end_pos = row[3] + highlight_tuple = [init_pos, end_pos] + self.get_highlights(page).append(highlight_tuple) def get_userid_for_username(self, user): + needsupdate = False + needsinsert = False userid = '' rows = self._conn.execute('select userid from annuserid where username=?', (user, )) if ( rows != None ): r = rows.fetchone() - if r != None: - userid = r[0] - else: - url = self._annotationserver - values = {'getidforuser' : user} - try: - data = urllib.urlencode(values) - req = urllib2.Request(url, data) - response = urllib2.urlopen(req) - rdf = response.read() - _logger.debug("\n\ngot this userid rdf %s\n\n" % rdf) - userid = self.parse_userid(rdf) - _logger.debug("\nuserid is %s\n\n" % userid) - except Exception, detail: - _logger.debug("userid fetching failed; detail: %s ", detail) - self._conn.execute('insert into annuserid values (?, ?)', (user, userid)) - self._conn.commit() + if ( r != None ): + if ( len(r[0]) > 0 ): + userid = r[0] + else: + needsupdate = True + else: + needsinsert = True + if ( rows == None ) or needsupdate or needsinsert: + url = self._annotationserver + values = {'getidforuser' : user} + try: + data = urllib.urlencode(values) + req = urllib2.Request(url, data) + response = urllib2.urlopen(req) + jsonstr = response.read() + _logger.debug("\n\ngot this userid json %s\n\n" % jsonstr ) + json_arr = simplejson.loads( jsonstr ) + _logger.debug("userid - json_arr %s" % json_arr ) + userid = json_arr['userid'] + _logger.debug("\nuserid is %s\n\n" % userid) + except Exception, detail: + _logger.debug("userid fetching failed; detail: %s ", detail) + if not needsupdate or needsinsert: + _logger.debug('insert user, userid %s', str((user, userid))) + self._conn.execute( 'insert into annuserid values (?, ?)', (user, userid) ) + else: + _logger.debug('updating userid %s', userid) + self._conn.execute( 'update annuserid set userid=? where username=?', ( userid, user ) ) + self._conn.commit() + _logger.debug('userid: found %s', userid) return userid @@ -268,11 +319,14 @@ class AnnotationManager: client = gconf.client_get_default() user = client.get_string("/desktop/sugar/user/nick") color = client.get_string("/desktop/sugar/user/color") - + self._color = color + _logger.debug('got this color: %s' % color) if self._userid == '': - self._userid = self.get_userid_for_username(user) + self._userid = self.get_userid_for_username( self.get_user_string( user ) ) + _logger.debug('got this userid: %s' % self._userid) - note = cjson.decode(content) + note = simplejson.loads(content) + #note = cjson.decode(content) self._annotitle = note['title'] self._content = note['body'] self._creator = self._userid @@ -284,7 +338,6 @@ class AnnotationManager: #send annotation to server -> get url back, store url in body-url #update annotation body url in local db - self._bodysvg = self.assembleSVG(self._annotitle, self._content) #check the last id from the database store row = self._conn.execute('select id from annotations order by id desc limit 1') @@ -293,13 +346,14 @@ class AnnotationManager: if r != None: aid = int(r[0]) + 1 - t = (aid, self._filehash, page, self._annotitle, self._content, self._bodyurl, self._context, self._created, self._modified, self._userid, self._annotates, '', self._color, self._local, self._mimetype, None, None, self._bodysvg) + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + t = (aid, self._filehash, page, self._annotitle, self._content, self._bodyurl, self._texttitle, self._textcreator, self._created, self._modified, self._userid, self._annotates, self._color, self._local, self._mimetype, None, None) annotation = AnnoBookmark(t) - self._rdf = self.create_rdf_representation(annotation) - - t = (aid, self._filehash, page, self._annotitle, self._content, self._bodyurl, self._context, self._created, self._modified, self._creator, self._annotates, self._rdf, self._color, self._local, self._mimetype, annotation.get_uuid(), None, self._bodysvg) - self._conn.execute('insert into annotations values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', t) + self._annojson = annotation.get_json() + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + t = (aid, self._filehash, page, self._annotitle, self._content, self._bodyurl, self._texttitle, self._textcreator, self._created, self._modified, self._creator, self._annotates, self._color, self._local, self._mimetype, annotation.get_uuid(), None) + self._conn.execute('insert into annotations values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', t) self._conn.commit() self._resync_annotation_cache() self.current_annotation = self._annotations[-1] @@ -311,7 +365,8 @@ class AnnotationManager: # local = 0 means that this is a bookmark originally # created by the person who originally shared the file print 'edit annotation called' - note = cjson.decode(content) + note = simplejson.loads(content) + #note = cjson.decode(content) annotitle = note['title'] annocontent = note['body'] @@ -320,8 +375,6 @@ class AnnotationManager: a.set_modified(time.time()) a.set_note_title(annotitle) a.set_note_body(annocontent) - a.set_bodysvg(self.assembleSVG(annotitle, annocontent)) - a.set_rdf(self.create_rdf_representation(a)) self.update_annotation_db_record(a) break @@ -335,11 +388,13 @@ class AnnotationManager: rows = self._conn.execute('select uuid from annotations where md5=? and id=?', [self._filehash, annotation_id]) row = rows.fetchone() self._to_delete.append(row[0]) - _logger.debug(str('schedule %s for deletion' % row[0])) + _logger.debug(str('schedule annotation %s for deletion' % str(row))) # We delete only the locally made annotation t = (self._filehash, annotation_id, self._userid) + _logger.debug(str('t for deletion is %s' % str(t))) self._conn.execute('delete from annotations where md5=? and id=? and creator=?', t) - self._conn.commit() + check = self._conn.commit() + _logger.debug('deletion check %s ', str(check)) self._resync_annotation_cache() @@ -347,7 +402,8 @@ class AnnotationManager: def _populate_annotations(self): # TODO: Figure out if caching the entire set of annotations is a good idea or not - rows = self._conn.execute('select id, md5, page, title, content, bodyurl, context, created, modified, creator, annotates, rdf, color, local, mimetype, uuid, annotationurl, bodysvg from annotations where md5=? order by page', [self._filehash]) + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + rows = self._conn.execute('select id, md5, page, title, content, bodyurl, texttitle, textcreator, created, modified, creator, annotates, color, local, mimetype, uuid, annotationurl from annotations where md5=? order by page', [self._filehash]) for row in rows: self._annotations.append(AnnoBookmark(row)) _logger.debug('picked %s', self._annotations[-1].get_note_body()) @@ -399,15 +455,18 @@ class AnnotationManager: if len(prevpages) > 0: page = prevpages[-1] else: - page = pages_with_annotations[-1] - self.current_annotation = id_ann_map[pages[page][0]] - t = pages[self.current_annotation.page] - tind = t.index(self.current_annotation.id) - if tind > 0: #prev annotation on the same page - self.current_annotation = id_ann_map[t[tind - 1]] - else: - self.current_annotation = id_ann_map[pages[pages_with_annotations[( pages_with_annotations.index(self.current_annotation.page) - 1 ) % len(pages_with_annotations)]][-1]] - return self.current_annotation + if len(pages_with_annotations) > 0: + page = pages_with_annotations[-1] + if page in pages.keys(): + self.current_annotation = id_ann_map[pages[page][0]] + if not self.current_annotation == None: + t = pages[self.current_annotation.page] + tind = t.index(self.current_annotation.id) + if tind > 0: #prev annotation on the same page + self.current_annotation = id_ann_map[t[tind - 1]] + else: + self.current_annotation = id_ann_map[pages[pages_with_annotations[( pages_with_annotations.index(self.current_annotation.page) - 1 ) % len(pages_with_annotations)]][-1]] + return self.current_annotation return None @@ -424,16 +483,19 @@ class AnnotationManager: if len(nextpages) > 0: page = nextpages[0] else: - page = pages_with_annotations[0] - self.current_annotation = id_ann_map[pages[page][0]] - t = pages[self.current_annotation.page] - _logger.debug('ids for current page %s' % str(t)) - tind = t.index(self.current_annotation.id) - if tind < len(t) - 1: #next annotation on the same page - self.current_annotation = id_ann_map[t[tind + 1]] - else: - self.current_annotation = id_ann_map[pages[pages_with_annotations[( pages_with_annotations.index(self.current_annotation.page) + 1 ) % len(pages_with_annotations)]][0]] - return self.current_annotation + if len(pages_with_annotations) > 0: + page = pages_with_annotations[0] + if page in pages.keys(): + self.current_annotation = id_ann_map[pages[page][0]] + if not self.current_annotation == None: + t = pages[self.current_annotation.page] + _logger.debug('ids for current page %s' % str(t)) + tind = t.index(self.current_annotation.id) + if tind < len(t) - 1: #next annotation on the same page + self.current_annotation = id_ann_map[t[tind + 1]] + else: + self.current_annotation = id_ann_map[pages[pages_with_annotations[( pages_with_annotations.index(self.current_annotation.page) + 1 ) % len(pages_with_annotations)]][0]] + return self.current_annotation return None @@ -488,118 +550,16 @@ class AnnotationManager: - def parse_annotations(self, rdf): + def parse_annotations(self, json): + annoarr = simplejson.loads(json) + #annoarr = cjson.decode(json) remote_annotations = [] - _logger.debug("parse_annotations: The RDF: %s", rdf) - dom = minidom.parseString(rdf) - annotations = dom.getElementsByTagName("oac:Annotation") - t1 = [] - t2 = [] - clientuuid = None - title = None - creator = None - page = None - created = None - annotates = None - contextabout = None - mimetype = None - contentnode = None - contentuuid = None - annourl = None - bodyurl = None - modified = None - - for annotationdomelement in annotations: - _logger.debug(str('\n\nparsing annotation dom element xml %s\n\n' % annotationdomelement.toxml() )) - clientuuid = annotationdomelement.attributes[u'rdf:about'].value - title = self.get_node_data(annotationdomelement.getElementsByTagName("dc:title")[0]) - creator = self.get_node_data(annotationdomelement.getElementsByTagName("dcterms:creator")[0]) - page = self.get_node_data(annotationdomelement.getElementsByTagName("bibo:pageStart")[0]) - if len(page) > 0: - page = int(page) - created = self.get_node_data(annotationdomelement.getElementsByTagName("oac:when")[0]) - created = self.makeTimeStampFromDateTimeString(created) - annotates = self.get_node_attribute(annotationdomelement.getElementsByTagName("oac:hasTarget")[0], "rdf:resource") - contextabout = self.get_node_attribute(annotationdomelement.getElementsByTagName("oac:contextAbout")[0], "rdf:resource") - mimetype = self.get_node_data(annotationdomelement.getElementsByTagName("dc:type")[0]) - context = self.get_node_attribute(annotationdomelement.getElementsByTagName("oac:Context")[0], "rdf:resource") - contentnodes = annotationdomelement.getElementsByTagName("oac:hasContent") - if ( contentnodes != None ) and ( len(contentnodes) > 0 ): - contentuuid = self.get_node_attribute(contentnodes[0], "rdf:about") - annourl = self.get_node_attribute(contentnodes[0].getElementsByTagName("rdf:type")[0], "rdf:resource") - bodyurl = self.get_node_data(contentnodes[0].getElementsByTagName("oac:body")[0]) - modified = self.get_node_data(annotationdomelement.getElementsByTagName("dcterms:modified")[0]) - modified = self.makeTimeStampFromDateTimeString(modified) - t1.append({'page' : page, 'bodyurl' : bodyurl, 'contextabout' : contextabout, 'created' : created, 'modified' : modified, 'creator' : creator, 'annotates' : annotates, 'mimetype' : mimetype, 'clientuuid' : clientuuid, 'annourl' : annourl }) - - descriptions = dom.getElementsByTagName("rdf:Description") - for desc in descriptions: - contentsvgnode = desc.getElementsByTagName("cnt:chars")[0] - contentsvg = contentsvgnode.toxml() - tspans = contentsvgnode.getElementsByTagName("svg:tspan") - notetitle = '' - notecontent = '' - for ts in tspans: - if ts.attributes["id"].value == 'notetitle': - notetitle = ts.firstChild.data - if ts.attributes["id"].value == 'notecontent': - notecontent = ts.firstChild.data - t2.append({'contentsvg' : contentsvg, 'notetitle' : notetitle, 'notecontent' : notecontent}) - if (len(t1) > 0) and (len(t1) == len(t2)): - for i in range(len(t1)): - remote_annotations.append(AnnoBookmark((None, self._filehash, t1[i]['page'], t2[i]['notetitle'], t2[i]['notecontent'], t1[i]['bodyurl'], t1[i]['contextabout'], t1[i]['created'], t1[i]['modified'], t1[i]['creator'], t1[i]['annotates'], '', '', '', t1[i]['mimetype'], t1[i]['clientuuid'], t1[i]['annourl'], t2[i]['contentsvg']))) - else: - _logger.debug('Parse annotation error: RDF parsing went wrong: %s' % rdf) - return remote_annotations - - - - def assemble_annotation(self, annotationdomelement): - _logger.debug("parse annotationdomelements %s ", annotationdomelement.toxml()) - #clientuuid = annotationdomelement.firstChild.attributes["rdf:about"] - #_logger.debug("parse annotationdomelements: uuid: %s", clientuuid) - #title = annotationdomelement.getElementsByTagName("dc:title")[0].firstChild.data - #_logger.debug("parse annotationdomelements: title: %s", title) - #creator = annotationdomelement.getElementsByTagName("dcterms:creator")[0].firstChild.data - #_logger.debug("parse annotationdomelements: creator: %s", creator) - #created = annotationdomelement.getElementsByTagName("oac:when")[0].firstChild.data - #_logger.debug("parse annotationdomelements: created: %s", created) - - - - - def get_annotationurl_from_server_response(self, rdf): - annotationurl = '' - dom = minidom.parseString(rdf) - urlnodes = dom.getElementsByTagName("rdf:Description") - if ( urlnodes != None ): - node = urlnodes[0] - if "rdf:about" in node.attributes.keys(): - annotationurl = node.attributes["rdf:about"].value - return annotationurl + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + for a in annoarr: + t = (a['id'], a['md5'], a['page'], a['title'], a['content'], a['bodyurl'], a['texttitle'], a['textcreator'], a['created'], a['modified'], a['creator'], a['annotates'], a['color'], a['local'], a['mimetype'], a['uuid'], a['annotationurl']) + remote_annotations.append(AnnoBookmark(t)) - - - #check for the uuid and pick the right one - def get_bodyurl_from_server_response(self, rdf): - bodyurl = '' - dom = minidom.parseString(rdf) - urlnodes = dom.getElementsByTagName("oac:body") - if ( urlnodes != None ): - node = urlnodes[0] - if "rdf:resource" in node.attributes.keys(): - bodyurl = node.attributes["rdf:resource"].value - return bodyurl - - - #check for the uuid and pick the right one - def get_modified_from_server_response(self, rdf): - modified = '' - dom = minidom.parseString(rdf) - modified = self.get_node_data(dom.getElementsByTagName("dcterms:modified")[0]) - if modified != None: - modified = self.makeTimeStampFromDateTimeString(modified) - return modified + return remote_annotations @@ -626,19 +586,22 @@ class AnnotationManager: def insert_annotation_db_record(self, annotation): - t = (None, annotation.get_filehash(), annotation.get_page(), annotation.get_note_title(), annotation.get_note_body(), annotation.get_bodyurl(), annotation.get_context(), annotation.get_created(), annotation.get_modified(), annotation.get_creator(), annotation.get_annotates(), annotation.get_rdf(), annotation.get_color(), annotation.is_local(), annotation.get_mimetype(), annotation.get_uuid(), annotation.get_annotationurl(), annotation.get_bodysvg()) - self._conn.execute('insert into annotations values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', t) + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + t = (None, annotation.get_filehash(), annotation.get_page(), annotation.get_note_title(), annotation.get_note_body(), annotation.get_bodyurl(), annotation.get_texttitle(), annotation.get_textcreator(), annotation.get_created(), annotation.get_modified(), annotation.get_creator(), annotation.get_annotates(), annotation.get_color().to_string(), annotation.is_local(), annotation.get_mimetype(), annotation.get_uuid(), annotation.get_annotationurl()) + self._conn.execute('insert into annotations values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', t) self._conn.commit() self.current_annotation = annotation def update_annotation_db_record(self, annotation): - t = (annotation.get_filehash(), annotation.get_page(), annotation.get_note_title(), annotation.get_note_body(), annotation.get_bodyurl(), annotation.get_context(), annotation.get_created(), annotation.get_modified(), annotation.get_creator(), annotation.get_annotates(), annotation.get_rdf(), annotation.get_color(), annotation.is_local(), annotation.get_mimetype(), annotation.get_uuid(), annotation.get_annotationurl(), annotation.get_bodysvg(), annotation.get_id()) - self._conn.execute('update annotations set md5=?, page=?, title=?, content=?, bodyurl=?, context=?, created=?, modified=?, creator=?, annotates=?, rdf=?, color=?, local=?, mimetype=?, uuid=?, annotationurl=?, bodysvg=? where id=?', t) + #(id INTEGER PRIMARY KEY, md5, page, title, content, bodyurl, texttitle, textcreator, created TIMESTAMP, modified TIMESTAMP, creator, annotates, color, local, mimetype, uuid, annotationurl) + t = (annotation.get_filehash(), annotation.get_page(), annotation.get_note_title(), annotation.get_note_body(), annotation.get_bodyurl(), annotation.get_texttitle(), annotation.get_textcreator(), annotation.get_created(), annotation.get_modified(), annotation.get_creator(), annotation.get_annotates(), annotation.get_color().to_string(), annotation.is_local(), annotation.get_mimetype(), annotation.get_uuid(), annotation.get_annotationurl(), annotation.get_id()) + self._conn.execute('update annotations set md5=?, page=?, title=?, content=?, bodyurl=?, texttitle=?, textcreator=?, created=?, modified=?, creator=?, annotates=?, color=?, local=?, mimetype=?, uuid=?, annotationurl=? where id=?', t) self._conn.commit() self.current_annotation = annotation + def makeDateTimeFromTimeStamp(self, tstamp): return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(tstamp)) @@ -647,99 +610,10 @@ class AnnotationManager: return time.mktime(time.strptime(datetimestr, "%Y-%m-%d %H:%M:%S")) - def create_rdf_representation(self, annotation): - rstr = "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" " - rstr += "xmlns:rdfs=\"http://www.w3.org/2001/01/rdf-schema#\" " - rstr += "xmlns:cnt=\"http://www.w3.org/2008/content\" " - rstr += "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " - rstr += "xmlns:dcterms=\"http://purl.org/dc/terms/\" " - rstr += "xmlns:foaf=\"http://xmlns.com/foaf/0.1/\" " - rstr += "xmlns:oac=\"http://www.openannotation.org/ns/\" " - rstr += "xmlns:ore=\"http://www.openarchives.org/ore/terms/\" " - rstr += "xmlns:bibo=\"http://purl.org/ontology/bibo/\">" - - rstr += "<oac:Annotation rdf:about=\"" + annotation.get_uuid() + "\">" - rstr += "<bibo:pageStart>" + str(annotation.get_page()) + "</bibo:pageStart>" - rstr += "<dc:title>" + annotation.get_note_title() + "</dc:title>" - rstr += "<oac:hasTarget rdf:resource=\"" + annotation.get_annotates() + "\" dcterms:isPartOf=\"" + annotation.get_annotates() + "\"/>" - rstr += "<oac:hasTargetContext>" - rstr += "<oac:TargetContext>" - rstr += "<oac:contextAbout rdf:resource=\"" + annotation.get_annotates() + "\"/>" - rstr += "<dc:type>" + annotation.get_mimetype() + "</dc:type>" - rstr += "</oac:TargetContext>" - rstr += "</oac:hasTargetContext>" - - rstr += "<oac:hasContentContext>" - rstr += "<oac:Context rdf:resource=\"" + annotation.get_context() + "\">" - rstr += "<oac:contextAbout>" - rstr += annotation.get_annotates() - rstr += "</oac:contextAbout>" - rstr += '<oac:when>%s</oac:when>' % self.makeDateTimeFromTimeStamp(annotation.get_created()) - rstr += "</oac:Context>" - rstr += "</oac:hasContentContext>" - - rstr += "<rdf:type rdf:resource=\"http://www.w3.org/2000/10/annotation-ns#Annotation\"/>" - rstr += "<oac:predicate rdf:resource=\"http://www.openannotation.org/ns/annotates\"/>" - rstr += "<dcterms:creator>" + annotation.get_creator() + "</dcterms:creator>" - - if (annotation.get_annotationurl() != None) and (len(annotation.get_annotationurl()) > 0) and (annotation.get_bodyurl() != None) and (len(annotation.get_bodyurl()) > 0): - rstr += "<oac:hasContent rdf:about=\"" + annotation.get_uuid() + "\">" - rstr += "<rdf:type rdf:resource=\"" + annotation.get_annotationurl() + "\"/>" - rstr += "<dc:title>" + annotation.get_note_title() + "</dc:title>" - rstr += "<oac:hasBody>" - rstr += "<rdf:type rdf:resource=\"http://www.openannotation.org/ns/Note\"/>" - rstr += "<oac:body>" - rstr += annotation.get_bodyurl() - rstr += "</oac:body>" - rstr += "</oac:hasBody>" - rstr += '<dcterms:modified> %s </dcterms:modified>' % self.makeDateTimeFromTimeStamp(annotation.get_modified()) - rstr += "</oac:hasContent>" - - rstr += "</oac:Annotation>" - rstr += "<rdf:Description rdf:about=\"" + annotation.get_annotates() + "\">" - rstr += "<ore:Proxy>" - rstr += "<ore:proxyIn rdf:nodeID=\"anno\" />" - rstr += "<ore:proxyFor rdf:resource=\"" + annotation.get_annotates() + "\">" - rstr += "<oac:hasSegmentDescription>" - rstr += "<oac:SegmentDescription dc:format=\"image/svg+xml\">" - rstr += "<cnt:ContentAsText>" - rstr += "<cnt:chars>" - rstr += annotation.get_bodysvg() - rstr += "</cnt:chars>" - rstr += "<cnt:characterEncoding>utf-8</cnt:characterEncoding>" - rstr += "</cnt:ContentAsText>" - rstr += "</oac:SegmentDescription>" - rstr += "</oac:hasSegmentDescription>" - rstr += "</ore:proxyFor>" - rstr += "</ore:Proxy>" - rstr += "</rdf:Description>" - rstr += "</rdf:RDF>" - return rstr - - - - def assembleSVG(self, annotitle, annocontent): - #svg = "<?xml version=\"1.0\"?>" - svg = "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"500\" height=\"500\" id=\"svg2\">" - svg += "<g id=\"layer1\">" - svg += "<rect width=\"500\" height=\"500\" x=\"0\" y=\"0\" id=\"rect2816\" style=\"fill:none;stroke:#000000;stroke-opacity:1\"/>" - svg += "<text x=\"2\" y=\"2\" id=\"text2818\" xml:space=\"preserve\" style=\"font-size:16px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans\">" - svg += "<tspan x=\"10\" y=\"50\" id=\"notetitle\">" + annotitle + "</tspan>" - svg += "</text>" - - svg += "<text x=\"2\" y=\"150\" id=\"text2830\" xml:space=\"preserve\" style=\"font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans\">" - svg += "<tspan x=\"10\" y=\"150\" id=\"notecontent\">" + annocontent + "</tspan>" - svg += "</text>" - svg += "</g>" - svg += "</svg>" - return svg - - - def download_annotations(self): url = self._annotationserver annotations = [] - annordf = "" + annojson = "" _logger.debug("contacting annotationserver %s", url) if self._annotates == "": self._annotates = "_" @@ -749,12 +623,12 @@ class AnnotationManager: data = urllib.urlencode(values) req = urllib2.Request(url, data) response = urllib2.urlopen(req) - annordf = response.read() + annojson = response.read() except Exception, detail: _logger.debug("readdb: failure at initial sync request f. annotations; detail: %s ", detail) - if (annordf != None) and (len(annordf) > 0): - anno_arr = self.parse_annotations(annordf) + if (annojson != None) and (len(annojson) > 0): + anno_arr = self.parse_annotations(annojson) _logger.debug('length anno_arr %d', len(anno_arr)) remote_uuids = [] if len(anno_arr) > 0: @@ -762,6 +636,8 @@ class AnnotationManager: _logger.debug('remote_uuids %s', remote_uuids) #check the modified timestamps localuuids = [la.get_uuid() for la in self._annotations] + self.remotecreators = [] + self.remotecolors = {} for a in anno_arr: uuid = a.get_uuid() if uuid in localuuids: @@ -782,20 +658,31 @@ class AnnotationManager: _logger.debug(str('remote annotation is outdated, sending %s' % self._annotations[ind])) self.send_annotation_to_server(self._annotations[ind]) else: + remotecreator = a.get_creator() + if not remotecreator in self.remotecreators: + self.remotecreators.append(remotecreator) + self.remotecolors[remotecreator] = XoColor() + a.color = self.remotecolors[remotecreator] self._annotations.append(a) self.insert_annotation_db_record(a) self._sidebar.update_for_page(a.page) + def get_user_string( self, user ): + m = hashlib.md5() + #m.update( str( "%s%d" % ( user, random.randint( 0, 100000000 ) ) ) ) + m.update( str( "%s" % ( user ) ) ) + return m.hexdigest() + + def sync_annotations(self): url = self._annotationserver annotations = [] - annordf = "" _logger.debug("contacting annotationserver %s", url) if self._annotates == "": - self._annotates = "_" + self._annotates = self._texttitle #check if there are annotations to be deleted: if len(self._to_delete) > 0: for delete_anid in self._to_delete: @@ -804,18 +691,17 @@ class AnnotationManager: data = urllib.urlencode(values) req = urllib2.Request(url, data) response = urllib2.urlopen(req) - annordf = response.read() + annojson = response.read() self._to_delete.remove(delete_anid) - _logger.debug("\nafter delete, rdf is: %s\n\n" % annordf) + _logger.debug("\nafter delete, json is: %s\n\n" % annojson) except Exception, detail: _logger.debug("readdb: failure at request f. deleting annotations; detail: %s ", detail) else: #get annotations from server client = gconf.client_get_default() user = client.get_string("/desktop/sugar/user/nick") - if self._userid == '': - self._userid = self.get_userid_for_username(user) + self._userid = self.get_userid_for_username( self.get_user_string( user ) ) self._creator = self._userid values = {'w3c_hasTarget' : self._annotates, 'creator' : self._creator } @@ -824,11 +710,13 @@ class AnnotationManager: data = urllib.urlencode(values) req = urllib2.Request(url, data) response = urllib2.urlopen(req) - annordf = response.read() + annojson = response.read() + _logger.debug('downloaded annotations -- annojson is: %s ' % annojson) + except Exception, detail: _logger.debug("readdb: failure at initial sync request f. annotations; detail: %s ", detail) - if (annordf != None) and (len(annordf) > 0): - anno_arr = self.parse_annotations(annordf) + if (annojson != None) and (len(annojson) > 0): + anno_arr = self.parse_annotations(annojson) _logger.debug('length anno_arr %d', len(anno_arr)) remote_uuids = [] if len(anno_arr) > 0: @@ -865,23 +753,27 @@ class AnnotationManager: def send_annotation_to_server(self, annotation): url = self._annotationserver - annordf = self.create_rdf_representation(annotation) - _logger.debug('it is a new or updated annotation, trying to send it to server, rdf: %s', annotation) + annojson = annotation.get_json() + _logger.debug('it is a new or updated annotation, trying to send it to server, json: %s', annojson) try: - req = urllib2.Request(url, annordf) - req.add_header('Content-Type', 'text/xml') + req = urllib2.Request(url, annojson, {'Content-Type': 'application/json', "Accept": "application/json"} ) + #req = urllib2.Request(url, annojson, {"Content-type": "application/x-www-form-urlencoded"y} ) response = urllib2.urlopen(req) - re_rdf = response.read() - _logger.debug('rdf response from the server: %s', re_rdf) - annourl = self.get_annotationurl_from_server_response(re_rdf) - _logger.debug('got this annourl back from server: %s', annourl) + re_json = response.read() + json_arr = simplejson.loads(re_json) + _logger.debug('json response from the server: %s', re_json ) + + annourl = json_arr['annotationurl'] + _logger.debug('got this annourl back from server: %s', annourl ) if annourl != None: - annotation.set_annotationurl(annourl) - bodyurl = self.get_bodyurl_from_server_response(re_rdf) + annotation.set_annotationurl( annourl ) + + bodyurl = json_arr['bodyurl'] _logger.debug('got this bodyurl back from server: %s', bodyurl) if bodyurl != None: annotation.set_bodyurl(bodyurl) - self.update_annotation_db_record(annotation) + + self.update_annotation_db_record( annotation ) except Exception, detail: _logger.debug("readdb: sending annotation failed: %s ", detail) diff --git a/readsidebar.py b/readsidebar.py index 40c8922..abb1740 100644 --- a/readsidebar.py +++ b/readsidebar.py @@ -20,14 +20,17 @@ import time import gtk +from sugar.graphics import style from sugar.graphics.icon import Icon from sugar.graphics.xocolor import XoColor from sugar import profile from sugar.util import timestamp_to_elapsed_string from annobookmark import AnnoBookmark, Bookmark -from readdb import BookmarkManager, AnnotationManager +from readdb import AnnotationManager from readdialog import BookmarkAddDialog, BookmarkEditDialog, AnnotationAddDialog, AnnotationEditDialog +from annoicon import AnnoIcon + from gettext import gettext as _ @@ -38,7 +41,7 @@ _logger = logging.getLogger('anno-activity') class Sidebar(gtk.EventBox): def __init__(self): gtk.EventBox.__init__(self) - self.set_size_request(20, -1) + self.set_size_request(22, -1) # Take care of the background first white = gtk.gdk.color_parse("white") self.modify_bg(gtk.STATE_NORMAL, white) @@ -57,72 +60,15 @@ class Sidebar(gtk.EventBox): self._annotation_icon_query_tooltip_cb_ids = [] self._bookmark_manager = None self._annotation_manager = None - self._is_showing_local_bookmark = False self._is_showing_local_annotation = False - self.add_events(gtk.gdk.BUTTON_PRESS_MASK) - - - - - def _add_bookmark_icon(self, bookmark): - xocolor = XoColor(bookmark.color) - self._bookmark_icon = Icon(icon_name = 'emblem-favorite', \ - pixel_size = 18, xo_color = xocolor) - - self._bookmark_icon.props.has_tooltip = True - self.__bookmark_icon_query_tooltip_cb_id = \ - self._bookmark_icon.connect('query_tooltip', self.__bookmark_icon_query_tooltip_cb, \ - bookmark) - - self.__event_cb_id = \ - self.connect('event', self.__event_cb, bookmark) - - self._box.pack_start(self._bookmark_icon ,expand=False,fill=False) - self._bookmark_icon.show_all() - - if bookmark.is_local(): - self._is_showing_local_bookmark = True - - def __bookmark_icon_query_tooltip_cb(self, widget, x, y, keyboard_mode, tip, bookmark): - tooltip_header = bookmark.get_note_title() - tooltip_body = bookmark.get_note_body() - #TRANS: This goes like Bookmark added by User 5 days ago (the elapsed string gets translated - #TRANS: automatically) - tooltip_footer = (_('Bookmark added by %(user)s %(time)s') \ - % {'user': bookmark.nick, 'time': timestamp_to_elapsed_string(bookmark.timestamp)}) - - vbox = gtk.VBox() - - l = gtk.Label('<big>%s</big>' % tooltip_header) - l.set_use_markup(True) - l.set_width_chars(40) - l.set_line_wrap(True) - vbox.pack_start(l, expand = False, fill = False) - l.show() - - l = gtk.Label('%s' % tooltip_body) - l.set_use_markup(True) - l.set_alignment(0, 0) - l.set_padding(2, 6) - l.set_width_chars(40) - l.set_line_wrap(True) - l.set_justify(gtk.JUSTIFY_FILL) - vbox.pack_start(l, expand = True, fill = True) - l.show() - - l = gtk.Label('<small><i>%s</i></small>' % tooltip_footer) - l.set_use_markup(True) - l.set_width_chars(40) - l.set_line_wrap(True) - vbox.pack_start(l, expand = False, fill = False) - l.show() + #self.add_events(gtk.gdk.BUTTON_PRESS_MASK) - tip.set_custom(vbox) - return True - + def is_showing_local_annotation(self): + return self._is_showing_local_annotation + @@ -145,42 +91,16 @@ class Sidebar(gtk.EventBox): return False - - - def _clear_bookmarks(self): - if self._bookmark_icon is not None: - self._bookmark_icon.disconnect(self.__bookmark_icon_query_tooltip_cb_id) - self.disconnect(self.__event_cb_id) - - self._bookmark_icon.hide() #XXX: Is this needed?? - self._bookmark_icon.destroy() - - self._bookmark_icon = None - - self._is_showing_local_bookmark = False - - - - - def set_bookmarkmanager(self, filehash): - self._bookmark_manager = BookmarkManager(filehash) - - def get_bookmarkmanager(self): - return (self._bookmark_manager) - def _clear_annotations(self): if len(self._annotation_icons) > 0: for i in range(len(self._annotation_icons)): annotation_icon = self._annotation_icons[i] - annotation_icon.disconnect(self._annotation_icon_query_tooltip_cb_ids[i]) + annotation_icon._disconnect() annotation_icon.hide() annotation_icon.destroy() - _logger.debug('disconnecting icon: %d' % self._annotation_event_ids[i] ) - self.disconnect(self._annotation_event_ids[i]) self._annotation_icons[i] = None - #self._annotation_icon = None self._annotation_icons = [] self._annotation_icon_query_tooltip_cb_ids = [] self._annotation_event_ids = [] @@ -188,8 +108,8 @@ class Sidebar(gtk.EventBox): - def set_annotationmanager(self, filehash, mimetype): - self._annotation_manager = AnnotationManager(filehash, mimetype, self) + def set_annotationmanager(self, annotation_manager): + self._annotation_manager = annotation_manager def get_annotationmanager(self): @@ -203,31 +123,6 @@ class Sidebar(gtk.EventBox): self._add_annotation_icon(annotation) - def add_bookmark(self, page): - bookmark_title = (_("%s's bookmark") % profile.get_nick_name()) - bookmark_content = (_("Bookmark for page %d") % page) - dialog = BookmarkAddDialog(parent_xid = self.get_toplevel().window.xid, \ - dialog_title = _("Add notes for bookmark: "), \ - bookmark_title = bookmark_title, \ - bookmark_content = bookmark_content, page = page, \ - sidebarinstance = self) - dialog.show_all() - - - def _real_add_bookmark(self, page, content): - self._bookmark_manager.add_bookmark(page, unicode(content)) - self.update_for_page(page) - - - def del_bookmark(self, page): - self._bookmark_manager.del_bookmark(page) - self.update_for_page(page) - - - def is_showing_local_bookmark(self): - return self._is_showing_local_bookmark - - def sync_annotations(self): self._annotation_manager.sync_annotations() @@ -263,22 +158,19 @@ class Sidebar(gtk.EventBox): def del_annotation(self, page, annotation_id): + _logger.debug('annotation %d scheduled for deletion' % annotation_id) self._annotation_manager.del_annotation(annotation_id) self.update_for_page(page) def _add_annotation_icon(self, annotation): - xocolor = XoColor(annotation.color) - annotation_icon = Icon(icon_name = 'emblem-favorite', \ - pixel_size = 18, xo_color = xocolor) - - annotation_icon.props.has_tooltip = True - self._annotation_icon_query_tooltip_cb_ids.append(annotation_icon.connect('query_tooltip', self.__annotation_icon_query_tooltip_cb, annotation)) - - self._annotation_event_ids.append(self.connect('event', self.__event_cb, annotation)) + #xocolor = XoColor(annotation.color) + xocolor = annotation.color + annotation_icon = AnnoIcon(icon_name = 'emblem-favorite', \ + xo_color = xocolor, annotation=annotation, \ + event_cb=self.__event_cb) - _logger.debug('connecting event %d' % self._annotation_event_ids[-1]) self._box.pack_start(annotation_icon ,expand=False,fill=False) annotation_icon.show_all() self._annotation_icons.append(annotation_icon) @@ -286,8 +178,7 @@ class Sidebar(gtk.EventBox): if annotation.is_local(): self._is_showing_local_annotation = True - - + """ def __annotation_icon_query_tooltip_cb(self, widget, x, y, keyboard_mode, tip, annotation): tooltip_header = annotation.get_note_title() tooltip_body = annotation.get_note_body() @@ -325,5 +216,5 @@ class Sidebar(gtk.EventBox): tip.set_custom(vbox) return True - + """ diff --git a/readtoolbar.py b/readtoolbar.py index 53a756f..043ac05 100644 --- a/readtoolbar.py +++ b/readtoolbar.py @@ -15,30 +15,33 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA from gettext import gettext as _ +import logging import gobject import gtk -import evince - -try: - import epubadapter -except: - pass +import os +import simplejson +from sugar.graphics.combobox import ComboBox from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toggletoolbutton import ToggleToolButton +from sugar.graphics.toolcombobox import ToolComboBox from sugar.graphics.menuitem import MenuItem from sugar.graphics import iconentry from sugar.activity import activity +import speech + + class EditToolbar(activity.EditToolbar): + __gtype_name__ = 'EditToolbar' def __init__(self): activity.EditToolbar.__init__(self) - self._evince_view = None + self._view = None - self._document = None self._find_job = None search_item = gtk.ToolItem() @@ -75,11 +78,8 @@ class EditToolbar(activity.EditToolbar): 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 + self._view = view + self._view.find_set_highlight_search(True) def _clear_find_job(self): if self._find_job is None: @@ -93,13 +93,8 @@ class EditToolbar(activity.EditToolbar): self._clear_find_job() text = self._search_entry.props.text if text != "": - 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) + self._find_job, self._find_updated_handler = \ + self._view.setup_find_job(text, self._find_updated_cb) else: # FIXME: highlight nothing pass @@ -108,14 +103,14 @@ class EditToolbar(activity.EditToolbar): self._update_find_buttons() def _search_find_next(self): - self._evince_view.find_next() + self._view.find_next() def _search_find_last(self): # FIXME: does Evince support find last? return def _search_find_prev(self): - self._evince_view.find_previous() + self._view.find_previous() def _search_entry_activate_cb(self, entry): if self._search_entry_changed: @@ -124,24 +119,29 @@ class EditToolbar(activity.EditToolbar): self._search_find_next() def _search_entry_changed_cb(self, entry): + logging.debug('Search entry: %s' % (entry.props.text)) self._search_entry_changed = True self._update_find_buttons() -# Automatically start search, maybe after timeout? -# self._search_find_first() + # gobject.timeout_add(500, self._search_entry_timeout_cb) + # + #def _search_entry_timeout_cb(self): + # self._clear_find_job() + # self._search_find_first() + # return False def _find_changed_cb(self, page, spec): self._update_find_buttons() - def _find_updated_cb(self, job, page = None): - self._evince_view.find_changed(job, page) + def _find_updated_cb(self, job, page=None): + self._view.find_changed(job, page) def _find_prev_cb(self, button): if self._search_entry_changed: self._search_find_last() else: self._search_find_prev() - + def _find_next_cb(self, button): if self._search_entry_changed: self._search_find_first() @@ -169,20 +169,14 @@ class ViewToolbar(gtk.Toolbar): __gtype_name__ = 'ViewToolbar' __gsignals__ = { - 'needs-update-size': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])) + 'go-fullscreen': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): gtk.Toolbar.__init__(self) - self._evince_view = None - self._document = None - + self._view = None + self._zoom_out = ToolButton('zoom-out') self._zoom_out.set_tooltip(_('Zoom out')) self._zoom_out.connect('clicked', self._zoom_out_cb) @@ -194,7 +188,7 @@ class ViewToolbar(gtk.Toolbar): self._zoom_in.connect('clicked', self._zoom_in_cb) self.insert(self._zoom_in, -1) self._zoom_in.show() - + self._zoom_to_width = ToolButton('zoom-best-fit') self._zoom_to_width.set_tooltip(_('Zoom to width')) self._zoom_to_width.connect('clicked', self._zoom_to_width_cb) @@ -245,76 +239,181 @@ class ViewToolbar(gtk.Toolbar): 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() + self._view = view + + self._zoom_spin.props.value = self._view.get_zoom() + self._view_notify_zoom_handler = \ + self._view.connect_zoom_handler(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: - 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( - 'notify::zoom', self._view_notify_zoom_cb) + self._view.set_zoom(zoom_spin.props.value) - def _view_notify_zoom_cb(self, evince_view, pspec): + def _view_notify_zoom_cb(self, model, pspec): self._zoom_spin.disconnect(self._zoom_spin_notify_value_handler) try: - self._zoom_spin.props.value = round(evince_view.props.zoom * 100.0) + self._zoom_spin.props.value = round(self._view.get_zoom()) finally: self._zoom_spin_notify_value_handler = self._zoom_spin.connect( 'notify::value', self._zoom_spin_notify_value_cb) def zoom_in(self): - if hasattr(self._evince_view.props, 'sizing_mode'): - self._evince_view.props.sizing_mode = evince.SIZING_FREE - self._evince_view.zoom_in() + self._view.zoom_in() self._update_zoom_buttons() def _zoom_in_cb(self, button): self.zoom_in() def zoom_out(self): - if hasattr(self._evince_view.props, 'sizing_mode'): - self._evince_view.props.sizing_mode = evince.SIZING_FREE - self._evince_view.zoom_out() + self._view.zoom_out() self._update_zoom_buttons() - + def _zoom_out_cb(self, button): self.zoom_out() def zoom_to_width(self): - if hasattr(self._evince_view.props, 'sizing_mode'): - self._evince_view.props.sizing_mode = evince.SIZING_FIT_WIDTH - self.emit('needs-update-size') + self._view.zoom_to_width() self._update_zoom_buttons() def _zoom_to_width_cb(self, button): self.zoom_to_width() def _update_zoom_buttons(self): - self._zoom_in.props.sensitive = self._evince_view.can_zoom_in() - self._zoom_out.props.sensitive = self._evince_view.can_zoom_out() + self._zoom_in.props.sensitive = self._view.can_zoom_in() + self._zoom_out.props.sensitive = self._view.can_zoom_out() + self._zoom_to_width.props.sensitive = self._view.can_zoom_to_width() def _zoom_to_fit_menu_item_activate_cb(self, menu_item): - 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._view.zoom_to_best_fit() self._update_zoom_buttons() def _actual_size_menu_item_activate_cb(self, menu_item): - 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._view.zoom_to_actual_size() self._update_zoom_buttons() def _fullscreen_cb(self, button): self.emit('go-fullscreen') + + +class SpeechToolbar(gtk.Toolbar): + + def __init__(self, activity): + gtk.Toolbar.__init__(self) + voicebar = gtk.Toolbar() + self._activity = activity + if not speech.supported: + return + + self.load_speech_parameters() + + self.sorted_voices = [i for i in speech.voices()] + self.sorted_voices.sort(self.compare_voices) + default = 0 + for voice in self.sorted_voices: + if voice[0] == speech.voice[0]: + break + default = default + 1 + + # Play button + self.play_btn = ToggleToolButton('media-playback-start') + self.play_btn.show() + self.play_btn.connect('toggled', self.play_cb) + self.insert(self.play_btn, -1) + self.play_btn.set_tooltip(_('Play / Pause')) + + self.voice_combo = ComboBox() + for voice in self.sorted_voices: + self.voice_combo.append_item(voice, voice[0]) + self.voice_combo.set_active(default) + + self.voice_combo.connect('changed', self.voice_changed_cb) + combotool = ToolComboBox(self.voice_combo) + self.insert(combotool, -1) + combotool.show() + + self.pitchadj = gtk.Adjustment(0, -100, 100, 1, 10, 0) + pitchbar = gtk.HScale(self.pitchadj) + pitchbar.set_draw_value(False) + pitchbar.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + pitchbar.set_size_request(150, 15) + self.pitchadj.set_value(speech.pitch) + pitchtool = gtk.ToolItem() + pitchtool.add(pitchbar) + pitchtool.show() + self.insert(pitchtool, -1) + pitchbar.show() + + self.rateadj = gtk.Adjustment(0, -100, 100, 1, 10, 0) + ratebar = gtk.HScale(self.rateadj) + ratebar.set_draw_value(False) + ratebar.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + ratebar.set_size_request(150, 15) + self.rateadj.set_value(speech.rate) + ratetool = gtk.ToolItem() + ratetool.add(ratebar) + ratetool.show() + self.insert(ratetool, -1) + ratebar.show() + self.pitchadj.connect("value_changed", self.pitch_adjusted_cb) + self.rateadj.connect("value_changed", self.rate_adjusted_cb) + + def compare_voices(self, a, b): + if a[0].lower() == b[0].lower(): + return 0 + if a[0] .lower() < b[0].lower(): + return -1 + if a[0] .lower() > b[0].lower(): + return 1 + + def voice_changed_cb(self, combo): + speech.voice = combo.props.value + speech.say(speech.voice[0]) + self.save_speech_parameters() + + def pitch_adjusted_cb(self, get): + speech.pitch = int(get.value) + speech.say(_("pitch adjusted")) + self.save_speech_parameters() + + def rate_adjusted_cb(self, get): + speech.rate = int(get.value) + speech.say(_("rate adjusted")) + self.save_speech_parameters() + + def load_speech_parameters(self): + speech_parameters = {} + data_path = os.path.join(self._activity.get_activity_root(), 'data') + data_file_name = os.path.join(data_path, 'speech_params.json') + if os.path.exists(data_file_name): + f = open(data_file_name, 'r') + try: + speech_parameters = simplejson.load(f) + speech.pitch = speech_parameters['pitch'] + speech.rate = speech_parameters['rate'] + speech.voice = speech_parameters['voice'] + finally: + f.close() + + def save_speech_parameters(self): + speech_parameters = {} + speech_parameters['pitch'] = speech.pitch + speech_parameters['rate'] = speech.rate + speech_parameters['voice'] = speech.voice + data_path = os.path.join(self._activity.get_activity_root(), 'data') + data_file_name = os.path.join(data_path, 'speech_params.json') + f = open(data_file_name, 'w') + try: + simplejson.dump(speech_parameters, f) + finally: + f.close() + + def play_cb(self, widget): + if widget.get_active(): + self.play_btn.set_named_icon('media-playback-pause') + if speech.is_stopped(): + speech.play(self._activity._view.get_marked_words()) + else: + self.play_btn.set_named_icon('media-playback-start') + speech.stop() diff --git a/readtopbar.py b/readtopbar.py index 3f338f5..93b15b9 100644 --- a/readtopbar.py +++ b/readtopbar.py @@ -16,137 +16,64 @@ # 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, gobject +import gtk +import gobject import dbus +import logging from sugar.graphics import style from sugar.graphics.icon import Icon, get_icon_state from gettext import gettext as _ -_LEVEL_PROP = 'battery.charge_level.percentage' -_CHARGING_PROP = 'battery.rechargeable.is_charging' -_DISCHARGING_PROP = 'battery.rechargeable.is_discharging' -_PRESENT_PROP = 'battery.present' _ICON_NAME = 'battery' -# Taken from sugar/extensions/deviceicon/battery.py -class BattMan(gobject.GObject): - __gproperties__ = { - 'level' : (int, None, None, 0, 100, 0, - gobject.PARAM_READABLE), - 'charging' : (bool, None, None, False, - gobject.PARAM_READABLE), - 'discharging' : (bool, None, None, False, - gobject.PARAM_READABLE), - 'present' : (bool, None, None, False, - gobject.PARAM_READABLE) - } - - def __init__(self, udi): - gobject.GObject.__init__(self) - - bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) - proxy = bus.get_object('org.freedesktop.Hal', udi, - follow_name_owner_changes=True) - self._battery = dbus.Interface(proxy, 'org.freedesktop.Hal.Device') - bus.add_signal_receiver(self._battery_changed, - 'PropertyModified', - 'org.freedesktop.Hal.Device', - 'org.freedesktop.Hal', - udi) - - self._level = self._get_level() - self._charging = self._get_charging() - self._discharging = self._get_discharging() - self._present = self._get_present() - - def _get_level(self): - try: - return self._battery.GetProperty(_LEVEL_PROP) - except dbus.DBusException: - logging.error('Cannot access %s' % _LEVEL_PROP) - return 0 - - def _get_charging(self): - try: - return self._battery.GetProperty(_CHARGING_PROP) - except dbus.DBusException: - logging.error('Cannot access %s' % _CHARGING_PROP) - return False - - def _get_discharging(self): - try: - return self._battery.GetProperty(_DISCHARGING_PROP) - except dbus.DBusException: - logging.error('Cannot access %s' % _DISCHARGING_PROP) - return False - - def _get_present(self): - try: - return self._battery.GetProperty(_PRESENT_PROP) - except dbus.DBusException: - logging.error('Cannot access %s' % _PRESENT_PROP) - return False - - def do_get_property(self, pspec): - if pspec.name == 'level': - return self._level - if pspec.name == 'charging': - return self._charging - if pspec.name == 'discharging': - return self._discharging - if pspec.name == 'present': - return self._present - - def get_type(self): - return 'battery' - - def _battery_changed(self, num_changes, changes_list): - for change in changes_list: - if change[0] == _LEVEL_PROP: - self._level = self._get_level() - self.notify('level') - elif change[0] == _CHARGING_PROP: - self._charging = self._get_charging() - self.notify('charging') - elif change[0] == _DISCHARGING_PROP: - self._discharging = self._get_discharging() - self.notify('discharging') - elif change[0] == _PRESENT_PROP: - self._present = self._get_present() - self.notify('present') +_UP_DEVICE_IFACE = 'org.freedesktop.UPower.Device' +_UP_TYPE_BATTERY = 2 class _TopBar(gtk.HBox): __gproperties__ = { - 'completion-level' : (float, None, None, 0.0, 100.0, 0.0, - gobject.PARAM_READWRITE), + 'completion-level': (float, None, None, 0.0, 100.0, 0.0, + gobject.PARAM_READWRITE), } def __init__(self): gtk.HBox.__init__(self) - self.set_border_width(int(style.DEFAULT_SPACING/2.0)) + self.set_border_width(int(style.DEFAULT_SPACING / 2.0)) self.set_spacing(style.DEFAULT_SPACING * 4) self._completion_level = 0 self._progressbar = None + self._icon = None + self._battery_props = None - bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) - proxy = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/Manager') - hal_manager = dbus.Interface(proxy, 'org.freedesktop.Hal.Manager') - udis = hal_manager.FindDeviceByCapability('battery') - if len(udis) > 0: - self._battery = BattMan(udis[0]) # TODO: Support more than one battery - self._battery.connect('notify::level', \ - self._battery_level_changed_cb) - else: - self._battery = None + try: + bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) + up_proxy = bus.get_object('org.freedesktop.UPower', + '/org/freedesktop/UPower') + upower = dbus.Interface(up_proxy, 'org.freedesktop.UPower') + + for device_path in upower.EnumerateDevices(): + device = bus.get_object('org.freedesktop.UPower', device_path) + device_prop_iface = dbus.Interface(device, + dbus.PROPERTIES_IFACE) + device_type = device_prop_iface.Get(_UP_DEVICE_IFACE, 'Type') + if device_type != _UP_TYPE_BATTERY: + continue + + device.connect_to_signal('Changed', + self.__battery_properties_changed_cb, + dbus_interface=_UP_DEVICE_IFACE) + self._battery_props = dbus.Interface(device, + dbus.PROPERTIES_IFACE) + break - self._icon = None + except dbus.DBusException: + logging.warning("Could not connect to UPower, won't show battery" + "level.") self._setup() @@ -154,51 +81,61 @@ class _TopBar(gtk.HBox): if property.name == 'completion-level': return self._completion_level 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 == 'completion-level': self.set_completion_level(value) else: - raise AttributeError, 'unknown property %s' % property.name + raise AttributeError('unknown property %s' % property.name) def set_completion_level(self, value): self._completion_level = value - self._progressbar.set_fraction(self._completion_level/100.0) + self._progressbar.set_fraction(self._completion_level / 100.0) + + def _get_battery_level(self): + try: + return self._battery_props.Get(_UP_DEVICE_IFACE, + 'Percentage') + except dbus.DBusException: + logging.exception('Error determining battery level:') + return 0 def _setup(self): self._progressbar = gtk.ProgressBar() self._progressbar.props.discrete_blocks = 10 - self._progressbar.set_fraction(self._completion_level/100.0) - self.pack_start(self._progressbar, expand = True, fill = True) - if self._battery is not None: - icon_name = get_icon_state(_ICON_NAME, self._battery.props.level, step=-5) - self._icon = Icon(icon_name=icon_name) - self.pack_start(self._icon, expand = False, fill = False) - - def _battery_level_changed_cb(self, pspec, param): - icon_name = get_icon_state(_ICON_NAME, self._battery.props.level, step=-5) + self._progressbar.set_fraction(self._completion_level / 100.0) + self.pack_start(self._progressbar, expand=True, fill=True) + if self._battery_props is None: + return + + level = self._get_battery_level() + icon_name = get_icon_state(_ICON_NAME, level, step=-5) + self._icon = Icon(icon_name=icon_name) + self.pack_start(self._icon, expand=False, fill=False) + + def __battery_properties_changed_cb(self): + level = self._get_battery_level() + icon_name = get_icon_state(_ICON_NAME, level, step=-5) self._icon.props.icon_name = icon_name + class TopBar(_TopBar): + def __init__(self): _TopBar.__init__(self) - self._document = None + self._view = None - def set_document(self, document): - self._document = document + def set_view(self, view): + self._view = view + self._view.connect_page_changed_handler(self._page_changed_cb) - page_cache = self._document.get_page_cache() - page_cache.connect('page-changed', self._page_changed_cb) - - def _page_changed_cb(self, page, proxy = None): - current_page = self._document.get_page_cache().get_current_page() - n_pages = self._document.get_n_pages() - - self.set_completion_level(current_page * 100/n_pages) + def _page_changed_cb(self, model, page_from, page_to): + current_page = self._view.get_current_page() + n_pages = self._view.get_pagecount() + completion_level = int(float(current_page) * 100 / float(n_pages)) + self.set_completion_level(completion_level) #TRANS: Translate this as Page i of m (eg: Page 4 of 334) - self._progressbar.set_text(_("Page %i of %i") % (current_page, n_pages)) - - - + self._progressbar.set_text( + _("Page %i of %i") % (current_page, n_pages)) |