Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/pdfviewer.py
diff options
context:
space:
mode:
Diffstat (limited to 'pdfviewer.py')
-rw-r--r--pdfviewer.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/pdfviewer.py b/pdfviewer.py
new file mode 100644
index 0000000..c7e2452
--- /dev/null
+++ b/pdfviewer.py
@@ -0,0 +1,420 @@
+# Copyright (C) 2012, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import tempfile
+import threading
+from gettext import gettext as _
+
+from gi.repository import GObject
+from gi.repository import Gtk
+from gi.repository import GLib
+from gi.repository import EvinceDocument
+from gi.repository import EvinceView
+from gi.repository import WebKit
+
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.graphics.toolbutton import ToolButton
+from sugar3.datastore import datastore
+from sugar3.activity import activity
+
+
+class EvinceViewer(Gtk.Overlay):
+ """PDF viewer with a toolbar overlay for basic navigation and an
+ option to save to Journal.
+
+ """
+ __gsignals__ = {
+ 'save-to-journal': (GObject.SignalFlags.RUN_FIRST,
+ None,
+ ([])),
+ 'open-link': (GObject.SignalFlags.RUN_FIRST,
+ None,
+ ([str])),
+ }
+
+ def __init__(self, uri):
+ GObject.GObject.__init__(self)
+
+ self._uri = uri
+
+ # Create Evince objects to handle the PDF in the URI:
+ EvinceDocument.init()
+ self._doc = EvinceDocument.Document.factory_get_document(uri)
+ self._view = EvinceView.View()
+ self._model = EvinceView.DocumentModel()
+ self._model.set_document(self._doc)
+ self._view.set_model(self._model)
+
+ self._view.connect('external-link', self.__handle_link_cb)
+ self._model.connect('page-changed', self.__page_changed_cb)
+
+ self._back_page_button = None
+ self._forward_page_button = None
+ self._toolbar_box = self._create_toolbar()
+ self._update_nav_buttons()
+
+ self._toolbar_box.set_halign(Gtk.Align.FILL)
+ self._toolbar_box.set_valign(Gtk.Align.END)
+ self.add_overlay(self._toolbar_box)
+ self._toolbar_box.show()
+
+ scrolled_window = Gtk.ScrolledWindow()
+ self.add(scrolled_window)
+ scrolled_window.show()
+
+ scrolled_window.add(self._view)
+ self._view.show()
+
+ def _create_toolbar(self):
+ toolbar_box = ToolbarBox()
+
+ zoom_out_button = ToolButton('zoom-out')
+ zoom_out_button.set_tooltip(_('Zoom out'))
+ zoom_out_button.connect('clicked', self.__zoom_out_cb)
+ toolbar_box.toolbar.insert(zoom_out_button, -1)
+ zoom_out_button.show()
+
+ zoom_in_button = ToolButton('zoom-in')
+ zoom_in_button.set_tooltip(_('Zoom in'))
+ zoom_in_button.connect('clicked', self.__zoom_in_cb)
+ toolbar_box.toolbar.insert(zoom_in_button, -1)
+ zoom_in_button.show()
+
+ separator = Gtk.SeparatorToolItem()
+ separator.props.draw = True
+ toolbar_box.toolbar.insert(separator, -1)
+ separator.show()
+
+ self._back_page_button = ToolButton('go-previous-paired')
+ self._back_page_button.set_tooltip(_('Previous page'))
+ self._back_page_button.props.sensitive = False
+ self._back_page_button.connect('clicked', self.__go_back_page_cb)
+ toolbar_box.toolbar.insert(self._back_page_button, -1)
+ self._back_page_button.show()
+
+ self._forward_page_button = ToolButton('go-next-paired')
+ self._forward_page_button.set_tooltip(_('Next page'))
+ self._forward_page_button.props.sensitive = False
+ self._forward_page_button.connect('clicked', self.__go_forward_page_cb)
+ toolbar_box.toolbar.insert(self._forward_page_button, -1)
+ self._forward_page_button.show()
+
+ separator = Gtk.SeparatorToolItem()
+ separator.props.draw = True
+ toolbar_box.toolbar.insert(separator, -1)
+ separator.show()
+
+ self._save_to_journal_button = ToolButton('save-to-journal')
+ self._save_to_journal_button.set_tooltip(_('Save PDF to Journal'))
+ self._save_to_journal_button.connect('clicked',
+ self.__save_to_journal_button_cb)
+ toolbar_box.toolbar.insert(self._save_to_journal_button, -1)
+ self._save_to_journal_button.show()
+
+ return toolbar_box
+
+ def disable_journal_button(self):
+ self._save_to_journal_button.props.sensitive = False
+
+ def __handle_link_cb(self, widget, url):
+ self.emit('open-link', url.get_uri())
+
+ def __page_changed_cb(self, model, page_from, page_to):
+ self._update_nav_buttons()
+
+ def __zoom_out_cb(self, widget):
+ self.zoom_out()
+
+ def __zoom_in_cb(self, widget):
+ self.zoom_in()
+
+ def __go_back_page_cb(self, widget):
+ self._view.previous_page()
+
+ def __go_forward_page_cb(self, widget):
+ self._view.next_page()
+
+ def __save_to_journal_button_cb(self, widget):
+ self.emit('save-to-journal')
+ self._save_to_journal_button.props.sensitive = False
+
+ def _update_nav_buttons(self):
+ current_page = self._model.props.page
+ self._back_page_button.props.sensitive = current_page > 0
+ self._forward_page_button.props.sensitive = \
+ current_page < self._doc.get_n_pages() - 1
+
+ def zoom_in(self):
+ self._model.props.sizing_mode = EvinceView.SizingMode.FREE
+ self._view.zoom_in()
+
+ def zoom_out(self):
+ self._model.props.sizing_mode = EvinceView.SizingMode.FREE
+ self._view.zoom_out()
+
+ def get_pdf_title(self):
+ return self._doc.get_title()
+
+
+class DummyBrowser(GObject.GObject):
+ """Has the same interface as browser.Browser ."""
+ __gsignals__ = {
+ 'new-tab': (GObject.SignalFlags.RUN_FIRST, None, ([str])),
+ 'tab-close': (GObject.SignalFlags.RUN_FIRST, None, ([object])),
+ 'selection-changed': (GObject.SignalFlags.RUN_FIRST, None, ([])),
+ }
+
+ __gproperties__ = {
+ "title": (object, "title", "Title", GObject.PARAM_READWRITE),
+ "uri": (object, "uri", "URI", GObject.PARAM_READWRITE),
+ "progress": (object, "progress", "Progress", GObject.PARAM_READWRITE),
+ "load-status": (object, "load status", "a WebKit LoadStatus",
+ GObject.PARAM_READWRITE),
+ }
+
+ def __init__(self, tab):
+ GObject.GObject.__init__(self)
+ self._tab = tab
+ self._title = ""
+ self._uri = ""
+ self._progress = 0.0
+ self._load_status = WebKit.LoadStatus.PROVISIONAL
+
+ def do_get_property(self, prop):
+ if prop.name == 'title':
+ return self._title
+ elif prop.name == 'uri':
+ return self._uri
+ elif prop.name == 'progress':
+ return self._progress
+ elif prop.name == 'load-status':
+ return self._load_status
+ else:
+ raise AttributeError, 'Unknown property %s' % prop.name
+
+ def do_set_property(self, prop, value):
+ if prop.name == 'title':
+ self._title = value
+ elif prop.name == 'uri':
+ self._uri = value
+ elif prop.name == 'progress':
+ self._progress = value
+ elif prop.name == 'load-status':
+ self._load_status = value
+ else:
+ raise AttributeError, 'Unknown property %s' % prop.name
+
+ def get_title(self):
+ return self._title
+
+ def get_uri(self):
+ return self._uri
+
+ def get_progress(self):
+ return self._progress
+
+ def get_load_status(self):
+ return self._load_status
+
+ def emit_new_tab(self, uri):
+ self.emit('new-tab', uri)
+
+ def emit_close_tab(self):
+ self.emit('tab-close', self._tab)
+
+ def get_history(self):
+ return [{'url': self.props.uri, 'title': self.props.title}]
+
+ def can_undo(self):
+ return False
+
+ def can_redo(self):
+ return False
+
+ def can_go_back(self):
+ return False
+
+ def can_go_forward(self):
+ return False
+
+ def can_copy_clipboard(self):
+ return False
+
+ def can_paste_clipboard(self):
+ return False
+
+ def set_history_index(self, index):
+ pass
+
+ def get_history_index(self):
+ return 0
+
+ def stop_loading(self):
+ self._tab.cancel_download()
+
+ def reload(self):
+ pass
+
+
+class PDFTabPage(Gtk.HBox):
+ """Shows a basic PDF viewer, download the file first if the PDF is
+ in a remote location.
+
+ """
+ def __init__(self):
+ GObject.GObject.__init__(self)
+ self._browser = DummyBrowser(self)
+ self._evince_viewer = None
+ self._pdf_uri = None
+ self._requested_uri = None
+
+ def setup(self, requested_uri, title=None):
+ self._requested_uri = requested_uri
+
+ # The title may be given from the Journal:
+ if title is not None:
+ self._browser.props.title = title
+ else:
+ self._browser.props.title = os.path.basename(requested_uri)
+
+ self._browser.props.uri = requested_uri
+ self._browser.props.load_status = WebKit.LoadStatus.PROVISIONAL
+
+ # show PDF directly if the file is local (from the system tree
+ # or from the journal)
+
+ if requested_uri.startswith('file://'):
+ self._pdf_uri = requested_uri
+ self._show_pdf()
+
+ elif requested_uri.startswith('journal://'):
+ self._pdf_uri = self._get_path_from_journal(requested_uri)
+ self._show_pdf(from_journal=True)
+
+ # download first if file is remote
+
+ elif requested_uri.startswith('http://'):
+ self._download_from_http(requested_uri)
+
+ def _get_browser(self):
+ return self._browser
+
+ browser = GObject.property(type=object, getter=_get_browser)
+
+ def _show_pdf(self, from_journal=False):
+ self._evince_viewer = EvinceViewer(self._pdf_uri)
+ self._evince_viewer.connect('save-to-journal',
+ self.__save_to_journal_cb)
+ self._evince_viewer.connect('open-link',
+ self.__open_link_cb)
+
+ # disable save to journal if the PDF is already loaded from
+ # the journal:
+ if from_journal:
+ self._evince_viewer.disable_journal_button()
+
+ self._evince_viewer.show()
+ self.pack_start(self._evince_viewer, True, True, 0)
+
+ # if the PDF has a title, show it instead of the URI:
+ pdf_title = self._evince_viewer.get_pdf_title()
+ if pdf_title is not None:
+ self._browser.props.title = pdf_title
+
+ def _get_path_from_journal(self, journal_uri):
+ """Get the system tree URI of the file for the Journal object."""
+ journal_id = self.__journal_id_from_uri(journal_uri)
+ jobject = datastore.get(journal_id)
+ return 'file://' + jobject.file_path
+
+ def _download_from_http(self, remote_uri):
+ """Download the PDF from a remote location to a temporal file."""
+
+ # Figure out download URI
+ temp_path = os.path.join(activity.get_activity_root(), 'instance')
+ if not os.path.exists(temp_path):
+ os.makedirs(temp_path)
+
+ fd, dest_path = tempfile.mkstemp(dir=temp_path)
+
+ self._pdf_uri = 'file://' + dest_path
+
+ network_request = WebKit.NetworkRequest.new(remote_uri)
+ self._download = WebKit.Download.new(network_request)
+ self._download.set_destination_uri('file://' + dest_path)
+
+ self._download.connect('notify::progress', self.__download_progress_cb)
+ self._download.connect('notify::status', self.__download_status_cb)
+ self._download.connect('error', self.__download_error_cb)
+
+ self._download.start()
+
+ def __download_progress_cb(self, download, data):
+ progress = download.get_progress()
+ self._browser.props.load_status = WebKit.LoadStatus.PROVISIONAL
+ self._browser.props.progress = progress
+
+ def __download_status_cb(self, download, data):
+ status = download.get_status()
+ if status == WebKit.DownloadStatus.STARTED:
+ self._browser.props.load_status = WebKit.LoadStatus.PROVISIONAL
+ elif status == WebKit.DownloadStatus.FINISHED:
+ self._browser.props.load_status = WebKit.LoadStatus.FINISHED
+ self._show_pdf()
+ elif status == WebKit.DownloadStatus.CANCELLED:
+ logging.debug('Download PDF canceled')
+
+ def __download_error_cb(self, download, err_code, err_detail, reason):
+ logging.debug('Download error! code %s, detail %s: %s' % \
+ (err_code, err_detail, reason))
+
+ def cancel_download(self):
+ self._download.cancel()
+ self._browser.emit_close_tab()
+
+ def __journal_id_to_uri(self, journal_id):
+ """Return an URI for a Journal object ID."""
+ return "journal://" + journal_id + ".pdf"
+
+ def __journal_id_from_uri(self, journal_uri):
+ """Return a Journal object ID from an URI."""
+ return journal_uri[len("journal://"):-len(".pdf")]
+
+ def __save_to_journal_cb(self, widget):
+ """Save the PDF in the Journal.
+
+ Put the PDF title as the title, or if the PDF doesn't have
+ one, use the filename instead. Put the requested uri as the
+ description.
+
+ """
+ jobject = datastore.create()
+
+ jobject.metadata['title'] = self._browser.props.title
+ jobject.metadata['description'] = _('From: %s') % self._requested_uri
+
+ jobject.metadata['mime_type'] = "application/pdf"
+ jobject.file_path = self._pdf_uri[len("file://"):]
+ datastore.write(jobject)
+
+ # display the new URI:
+ self._browser.props.uri = self.__journal_id_to_uri(jobject.object_id)
+
+ def __open_link_cb(self, widget, uri):
+ """Open the external link of a PDF in a new tab."""
+ self._browser.emit_new_tab(uri)