Codebase list sugar-read-activity / 1cee358f-d61f-44c1-801b-fdc1693b3993/main epubadapter.py
1cee358f-d61f-44c1-801b-fdc1693b3993/main

Tree @1cee358f-d61f-44c1-801b-fdc1693b3993/main (Download .tar.gz)

epubadapter.py @1cee358f-d61f-44c1-801b-fdc1693b3993/mainraw · history · blame

from gi.repository import GObject
import logging

import epubview

# import speech

from io import StringIO

_logger = logging.getLogger('read-activity')


class EpubViewer(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, True, True, 0)
        self.show_all()
        self._modified_files = []

        # 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.reset_cb = self.reset_text_to_speech
        # 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
        if 'Read_zoom' in self.metadata:
            try:
                logging.error('Loading zoom %s', self.metadata['Read_zoom'])
                self.set_zoom(float(self.metadata['Read_zoom']))
            except:
                pass

    def update_metadata(self, activity):
        self.metadata = activity.metadata
        self.metadata['Read_zoom'] = str(self.get_zoom())

    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 True

    def show_highlights(self, page):
        # we save the highlights in the page as html
        pass

    def toggle_highlight(self, highlight):
        self._view.set_editable(True)

        if highlight:
            js = 'document.execCommand("backColor", false, "yellow");'
        else:
            # need remove the highlight nodes
            js = '''
                (function(){
                    var selObj = window.getSelection();
                    if (selObj.rangeCount < 1)
                        return;
                    var range  = selObj.getRangeAt(0);
                    var node = range.startContainer;
                    while (node.parentNode != null) {
                      if (node.localName == "span") {
                        if (node.hasAttributes()) {
                          var attrs = node.attributes;
                          for (var i = attrs.length - 1; i >= 0; i--) {
                            if (attrs[i].name == "style" &&
                                attrs[i].value == "background-color: yellow;") {
                              node.removeAttribute("style");
                              break;
                            };
                          };
                        };
                      };
                      node = node.parentNode;
                    };
                }())
            '''

        self._view.run_javascript(js)

        self._view.set_editable(False)
        # mark the file as modified
        current_file = self.get_current_file()
        logging.error('file %s was modified', current_file)
        if current_file not in self._modified_files:
            self._modified_files.append(current_file)
        GObject.idle_add(self._save_page)

    def _save_page(self):
        html = self._view._execute_script_sync("document.documentElement.innerHTML")
        file_path = self.get_current_file().replace('file:///', '/')
        logging.error(html)
        with open(file_path, 'w') as fd:
            header = """<?xml version="1.0" encoding="utf-8" standalone="no"?>
                <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
                 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
                <html xmlns="http://www.w3.org/1999/xhtml">"""
            fd.write(header)
            fd.write(html)
            fd.write('</html>')

    def save(self, file_path):
        if self._modified_files:
            self._epub.write(file_path)
            return True

        return False

    def in_highlight(self):
        # Verify if the selection already exist or the cursor
        # is in a highlighted area
        return self._view._execute_script_sync("""
            (function(){
                var selObj = window.getSelection();
                if (selObj.rangeCount < 1)
                    return false;
                var range  = selObj.getRangeAt(0);
                var node = range.startContainer;
                while (node.parentNode != null) {
                  if (node.localName == "span") {
                    if (node.hasAttributes()) {
                      var attrs = node.attributes;
                      for(var i = attrs.length - 1; i >= 0; i--) {
                        if (attrs[i].name == "style" &&
                            attrs[i].value == "background-color: yellow;") {
                          return true;
                        };
                      };
                    };
                  };
                  node = node.parentNode;
                };
                return false;
            })()""") == "true", None;

    def can_do_text_to_speech(self):
        return False

    def can_rotate(self):
        return False

    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].encode('utf-8'))
            i = i + 1
        self.current_word = i
        file_str.write('</speak>')
        return file_str.getvalue()

    def get_more_text(self):
        pass
        """
        if self.current_word < len(self.word_tuples):
            speech.stop()
            more_text = self.get_marked_words()
            speech.play(more_text)
        else:
            if speech.reset_buttons_cb is not None:
                speech.reset_buttons_cb()
        """

    def reset_text_to_speech(self):
        self.current_word = 0

    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)
            return False
        else:
            return True

    def set_screen_dpi(self, dpi):
        return

    def find_set_highlight_search(self, set_highlight_search):
        pass

    def set_current_page(self, n):
        # When the book is being loaded, calling this does not help
        # In such a situation, we go into a loop and try to load the
        # supplied page when the book has loaded completely
        n += 1
        if self._ready:
            self._load_page(n)
        else:
            GObject.timeout_add(200, self._try_load_page, n)

    def get_current_page(self):
        return int(self._loaded_page) - 1

    def get_current_link(self):
        # the _loaded_filename include all the path,
        # need only the part included in the link
        return self._loaded_filename[len(self._epub._tempdir) + 1:]

    def update_toc(self, activity):
        if self._epub.has_document_links():
            activity.show_navigator_button()
            activity.set_navigator_model(self._epub.get_links_model())
            return True
        else:
            return False

    def get_link_iter(self, current_link):
        """
        Returns the iter related to a link
        """
        link_iter = self._epub.get_links_model().get_iter_first()

        while link_iter is not None and \
                self._epub.get_links_model().get_value(link_iter, 1) \
                != current_link:
            link_iter = self._epub.get_links_model().iter_next(link_iter)
        return link_iter

    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_n_pages(self):
        return int(self._page_cache.get_pagecount())

    def has_document_links(self):
        return True

    def get_links_model(self):
        return self.get_toc_model()


class JobFind(epubview.JobFind):

    def __init__(self, document, start_page, n_pages, text,
                 case_sensitive=False):
        epubview.JobFind.__init__(self, document, start_page, n_pages, text,
                                  case_sensitive=False)