from gettext import gettext as _
import os
import logging
import time
import gi
gi.require_version('EvinceDocument', '3.0')
gi.require_version('EvinceView', '3.0')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import EvinceDocument
from gi.repository import EvinceView
from sugar3 import profile
from sugar3.activity.activity import get_activity_root, show_object_in_journal
from sugar3.datastore import datastore
_logger = logging.getLogger('read-activity')
class EvinceViewer():
def __init__(self):
self._view_notify_zoom_handler = None
EvinceDocument.init()
self._view = EvinceView.View()
def setup(self, activity):
self._activity = activity
self._view.connect('selection-changed',
activity._view_selection_changed_cb)
self._view.connect('external-link', self.__handle_link_cb)
activity._scrolled = Gtk.ScrolledWindow()
activity._scrolled.set_policy(Gtk.PolicyType.AUTOMATIC,
Gtk.PolicyType.AUTOMATIC)
activity._scrolled.props.shadow_type = Gtk.ShadowType.NONE
activity._scrolled.add(self._view)
self._view.show()
self._view.set_events(self._view.get_events() |
Gdk.EventMask.TOUCH_MASK)
self._view.connect('event', self.__view_touch_event_cb)
activity._hbox.pack_start(activity._scrolled, True, True, 0)
activity._scrolled.show()
self.dpi = activity.dpi
def load_document(self, file_path):
try:
self._document = \
EvinceDocument.Document.factory_get_document(file_path)
except GObject.GError, e:
_logger.error('Can not load document: %s', e)
return
else:
self._model = EvinceView.DocumentModel()
self._model.set_document(self._document)
self._view.set_model(self._model)
# set dpi
# TODO why we need set this?
"""
min_scale = self._model.get_min_scale()
max_scale = self._model.get_max_scale()
logging.error("min scale %s max_scale %s", min_scale, max_scale)
logging.error("setting min scale %s", min_scale * self.dpi / 72.0)
logging.error("setting max scale %s", max_scale * self.dpi / 72.0)
self._model.set_min_scale(min_scale * self.dpi / 72.0)
self._model.set_max_scale(max_scale * self.dpi / 72.0)
"""
def __view_touch_event_cb(self, widget, event):
if event.type == Gdk.EventType.TOUCH_BEGIN:
x = event.touch.x
view_width = widget.get_allocation().width
if x > view_width * 3 / 4:
self._view.scroll(Gtk.ScrollType.PAGE_FORWARD, False)
elif x < view_width * 1 / 4:
self._view.scroll(Gtk.ScrollType.PAGE_BACKWARD, False)
def __handle_link_cb(self, widget, url_object):
url = url_object.get_uri()
logging.debug('Create journal entry for URL: %s', url)
jobject = datastore.create()
metadata = {
'title': "%s: %s" % (_('URL from Read'), url),
'title_set_by_user': '1',
'icon-color': profile.get_color().to_string(),
'mime_type': 'text/uri-list', }
for k, v in metadata.items():
jobject.metadata[k] = v
file_path = os.path.join(get_activity_root(),
'instance', '%i_' % time.time())
open(file_path, 'w').write(url + '\r\n')
os.chmod(file_path, 0755)
jobject.set_file_path(file_path)
datastore.write(jobject)
show_object_in_journal(jobject.object_id)
jobject.destroy()
os.unlink(file_path)
def get_current_page(self):
return self._model.props.page
def set_current_page(self, page):
if page >= self._document.get_n_pages():
page = self._document.get_n_pages() - 1
elif page < 0:
page = 0
self._model.props.page = page
def next_page(self):
self._view.next_page()
def previous_page(self):
self._view.previous_page()
def rotate_left(self):
rotation = self._model.get_rotation()
self._model.set_rotation(rotation - 90)
def rotate_right(self):
rotation = self._model.get_rotation()
self._model.set_rotation(rotation + 90)
def can_rotate(self):
return True
def get_pagecount(self):
'''
Returns the pagecount of the loaded file
'''
return self._document.get_n_pages()
def load_metadata(self, activity):
self.metadata = activity.metadata
if not self.metadata['title_set_by_user'] == '1':
title = self._document.get_title()
if title:
self.metadata['title'] = title
sizing_mode = self.metadata.get('Read_sizing_mode', 'fit-width')
_logger.debug('Found sizing mode: %s', sizing_mode)
if sizing_mode == "best-fit":
self._model.set_sizing_mode(EvinceView.SizingMode.BEST_FIT)
if hasattr(self._view, 'update_view_size'):
self._view.update_view_size(self._scrolled)
elif sizing_mode == "free":
self._model.set_sizing_mode(EvinceView.SizingMode.FREE)
self._model.set_scale(float(self.metadata.get('Read_zoom', '1.0')))
_logger.debug('Set zoom to %f', self._model.props.scale)
elif sizing_mode == "fit-width":
self._model.set_sizing_mode(EvinceView.SizingMode.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 Read, for example.
_logger.warning("Unknown sizing_mode state '%s'", sizing_mode)
if self.metadata.get('Read_zoom', None) is not None:
self._model.set_scale(float(self.metadata['Read_zoom']))
def update_metadata(self, activity):
self.metadata = activity.metadata
self.metadata['Read_zoom'] = str(self._model.props.scale)
if self._model.get_sizing_mode() == EvinceView.SizingMode.BEST_FIT:
self.metadata['Read_sizing_mode'] = "best-fit"
elif self._model.get_sizing_mode() == EvinceView.SizingMode.FREE:
self.metadata['Read_sizing_mode'] = "free"
elif self._model.get_sizing_mode() == EvinceView.SizingMode.FIT_WIDTH:
self.metadata['Read_sizing_mode'] = "fit-width"
else:
_logger.error("Don't know how to save sizing_mode state '%s'" %
self._model.get_sizing_mode())
def can_highlight(self):
return False
def can_do_text_to_speech(self):
return False
def get_zoom(self):
'''
Returns the current zoom level
'''
return self._model.props.scale * 100
def set_zoom(self, value):
'''
Sets the current zoom level
'''
self._model.props.sizing_mode = EvinceView.SizingMode.FREE
if not self._view_notify_zoom_handler:
return
self._model.disconnect(self._view_notify_zoom_handler)
try:
self._model.props.scale = value / 100.0
finally:
self._view_notify_zoom_handler = self._model.connect(
'notify::scale', self._zoom_handler)
def zoom_in(self):
'''
Zooms in (increases zoom level by 0.1)
'''
self._model.props.sizing_mode = EvinceView.SizingMode.FREE
self._view.zoom_in()
def zoom_out(self):
'''
Zooms out (decreases zoom level by 0.1)
'''
self._model.props.sizing_mode = EvinceView.SizingMode.FREE
self._view.zoom_out()
def zoom_to_width(self):
self._model.props.sizing_mode = EvinceView.SizingMode.FIT_WIDTH
def can_zoom_in(self):
'''
Returns True if it is possible to zoom in further
'''
return self._view.can_zoom_in()
def can_zoom_out(self):
'''
Returns True if it is possible to zoom out further
'''
return self._view.can_zoom_out()
def can_zoom_to_width(self):
return True
def zoom_to_best_fit(self):
self._model.props.sizing_mode = EvinceView.SizingMode.BEST_FIT
def zoom_to_actual_size(self):
self._model.props.sizing_mode = EvinceView.SizingMode.FREE
self._model.props.scale = 1.0
def connect_zoom_handler(self, handler):
self._zoom_handler = handler
self._view_notify_zoom_handler = \
self._model.connect('notify::scale', handler)
return self._view_notify_zoom_handler
def setup_find_job(self, text, updated_cb):
self._find_job = EvinceView.JobFind.new(
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',
updated_cb)
self._view.find_started(self._find_job)
EvinceView.Job.scheduler_push_job(
self._find_job, EvinceView.JobPriority.PRIORITY_NONE)
return self._find_job, self._find_updated_handler
def connect_page_changed_handler(self, handler):
self._model.connect('page-changed', handler)
def update_toc(self, activity):
if self._validate_min_version(3, 5, 92):
# check version because does not work and crash with older evince
doc = self._model.get_document()
if not doc.has_document_links():
logging.error('The pdf file does not have a index')
return False
else:
self._job_links = EvinceView.JobLinks.new(document=doc)
self._job_links.connect('finished', self.__index_loaded_cb,
activity)
EvinceView.Job.scheduler_push_job(
self._job_links,
EvinceView.JobPriority.PRIORITY_NONE)
return True
else:
return False
def handle_link(self, link):
self._view.handle_link(link)
def _validate_min_version(self, major, minor, micro):
"""
Check if Evince version is at major or equal than the requested
"""
evince_version = [EvinceDocument.MAJOR_VERSION,
EvinceDocument.MINOR_VERSION,
EvinceDocument.MICRO_VERSION]
return evince_version >= [major, minor, micro]
def __index_loaded_cb(self, job, activity):
self._index_model = job.get_model()
if job.get_model() is None:
return False
activity.show_navigator_button()
activity.set_navigator_model(self._index_model)
return True
def get_current_link(self):
_iter = self._index_model.get_iter_first()
link_found = ""
current_page = self._model.props.page
while True:
link = self._index_model.get_value(_iter, 1)
if self._document.get_link_page(link) > current_page:
break
else:
link_found = link
_iter = self._index_model.iter_next(_iter)
if _iter is None:
break
return link_found
def get_link_iter(self, link):
_iter = self._index_model.get_iter_first()
while True:
value = self._index_model.get_value(_iter, 1)
if value == link:
break
else:
_iter = self._index_model.iter_next(_iter)
if _iter is None:
break
return _iter
def find_set_highlight_search(self, set_highlight_search):
self._view.find_set_highlight_search(set_highlight_search)
def find_next(self):
'''
Highlights the next matching item for current search
'''
self._view.find_next()
def find_previous(self):
'''
Highlights the previous matching item for current search
'''
self._view.find_previous()
def find_changed(self, job, page=None):
pass
def scroll(self, scrolltype, horizontal):
'''
Scrolls through the pages.
Scrolling is horizontal if horizontal is set to True
Valid scrolltypes are:
Gtk.ScrollType.PAGE_BACKWARD, Gtk.ScrollType.PAGE_FORWARD,
Gtk.ScrollType.STEP_BACKWARD, Gtk.ScrollType.STEP_FORWARD,
Gtk.ScrollType.START and Gtk.ScrollType.END
'''
_logger.error('scroll: %s', scrolltype)
if scrolltype == Gtk.ScrollType.PAGE_BACKWARD:
self._view.scroll(Gtk.ScrollType.PAGE_BACKWARD, horizontal)
elif scrolltype == Gtk.ScrollType.PAGE_FORWARD:
self._view.scroll(Gtk.ScrollType.PAGE_FORWARD, horizontal)
elif scrolltype == Gtk.ScrollType.STEP_BACKWARD:
self._scroll_step(False, horizontal)
elif scrolltype == Gtk.ScrollType.STEP_FORWARD:
self._scroll_step(True, horizontal)
elif scrolltype == Gtk.ScrollType.START:
self.set_current_page(0)
elif scrolltype == Gtk.ScrollType.END:
self.set_current_page(self._document.get_n_pages())
else:
print ('Got unsupported scrolltype %s' % str(scrolltype))
def _scroll_step(self, forward, horizontal):
if horizontal:
adj = self._activity._scrolled.get_hadjustment()
else:
adj = self._activity._scrolled.get_vadjustment()
value = adj.get_value()
step = adj.get_step_increment()
if forward:
adj.set_value(value + step)
else:
adj.set_value(value - step)
def copy(self):
self._view.copy()