Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYour Name Comes Here <ss1vamsi@yahoo.co.in>2009-05-17 12:06:39 (GMT)
committer Your Name Comes Here <ss1vamsi@yahoo.co.in>2009-05-17 12:06:39 (GMT)
commit83338521e86f15f88c57f29d9d81f0d9ee6a76f5 (patch)
tree506dfbc6d1be366f32308c2ce4bf8fbe3238c2bd
parent3274873efe3e2ff5bc9d0c7d6710797018092e20 (diff)
well first commit
-rw-r--r--printactivity.py589
-rw-r--r--printtoolbar.py473
2 files changed, 1062 insertions, 0 deletions
diff --git a/printactivity.py b/printactivity.py
new file mode 100644
index 0000000..e5b23e6
--- /dev/null
+++ b/printactivity.py
@@ -0,0 +1,589 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+# Copyright 2008 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 logging
+import os
+import time
+from gettext import gettext as _
+
+import dbus
+import evince
+import gobject
+import gtk
+import telepathy
+
+from sugar.activity import activity
+from sugar import network
+from sugar import mime
+
+from sugar.datastore import datastore
+from sugar.graphics.objectchooser import ObjectChooser
+
+from printtoolbar import EditToolbar, ReadToolbar, ViewToolbar
+
+_HARDWARE_MANAGER_INTERFACE = 'org.laptop.HardwareManager'
+_HARDWARE_MANAGER_SERVICE = 'org.laptop.HardwareManager'
+_HARDWARE_MANAGER_OBJECT_PATH = '/org/laptop/HardwareManager'
+
+_TOOLBAR_READ = 2
+
+_logger = logging.getLogger('read-activity')
+
+def _get_screen_dpi():
+ xft_dpi = gtk.settings_get_default().get_property('gtk-xft-dpi')
+ _logger.debug('Setting dpi to %f', float(xft_dpi / 1024))
+ return float(xft_dpi / 1024)
+
+
+class ReadHTTPRequestHandler(network.ChunkedGlibHTTPRequestHandler):
+ """HTTP Request Handler for transferring document while collaborating.
+
+ RequestHandler class that integrates with Glib mainloop. It writes
+ the specified file to the client in chunks, returning control to the
+ mainloop between chunks.
+
+ """
+ def translate_path(self, path):
+ """Return the filepath to the shared document."""
+ return self.server.filepath
+
+
+class ReadHTTPServer(network.GlibTCPServer):
+ """HTTP Server for transferring document while collaborating."""
+ def __init__(self, server_address, filepath):
+ """Set up the GlibTCPServer with the ReadHTTPRequestHandler.
+
+ filepath -- path to shared document to be served.
+ """
+ self.filepath = filepath
+ network.GlibTCPServer.__init__(self, server_address,
+ ReadHTTPRequestHandler)
+
+
+class ReadURLDownloader(network.GlibURLDownloader):
+ """URLDownloader that provides content-length and content-type."""
+
+ def get_content_length(self):
+ """Return the content-length of the download."""
+ if self._info is not None:
+ return int(self._info.headers.get('Content-Length'))
+
+ def get_content_type(self):
+ """Return the content-type of the download."""
+ if self._info is not None:
+ return self._info.headers.get('Content-type')
+ return None
+
+
+READ_STREAM_SERVICE = 'read-activity-http'
+
+class ReadActivity(activity.Activity):
+ """The Read sugar 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._document = None
+ self._fileserver = None
+ self._object_id = handle.object_id
+
+ self.connect('key-press-event', self._key_press_event_cb)
+ self.connect('key-release-event', self._key_release_event_cb)
+
+ _logger.debug('Starting Read...')
+
+ self._view = evince.View()
+ self._view.set_screen_dpi(_get_screen_dpi())
+ self._view.connect('notify::has-selection',
+ self._view_notify_has_selection_cb)
+
+ toolbox = activity.ActivityToolbox(self)
+
+ self._edit_toolbar = EditToolbar(self._view)
+ self._edit_toolbar.undo.props.visible = False
+ self._edit_toolbar.redo.props.visible = False
+ self._edit_toolbar.separator.props.visible = False
+ self._edit_toolbar.copy.set_sensitive(False)
+ self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
+ self._edit_toolbar.paste.props.visible = False
+ toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
+ self._edit_toolbar.show()
+
+ self._read_toolbar = ReadToolbar(self._view)
+ toolbox.add_toolbar(_('Read'), self._read_toolbar)
+ self._read_toolbar.show()
+
+ self._view_toolbar = ViewToolbar(self._view)
+ 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)
+ toolbox.add_toolbar(_('View'), self._view_toolbar)
+ self._view_toolbar.show()
+
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.props.shadow_type = gtk.SHADOW_NONE
+
+ scrolled.add(self._view)
+ self._view.show()
+
+ self.set_canvas(scrolled)
+ scrolled.show()
+
+ # Set up for idle suspend
+ self._idle_timer = 0
+ self._service = None
+
+ # start with sleep off
+ self._sleep_inhibit = True
+
+ self.unused_download_tubes = set()
+ self._want_document = True
+ self._download_content_length = 0
+ self._download_content_type = None
+ # Status of temp file used for write_file:
+ self._tempfile = None
+ self._close_requested = False
+
+ fname = os.path.join('/etc', 'inhibit-ebook-sleep')
+ if not os.path.exists(fname):
+ try:
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(_HARDWARE_MANAGER_SERVICE,
+ _HARDWARE_MANAGER_OBJECT_PATH)
+ self._service = dbus.Interface(proxy,
+ _HARDWARE_MANAGER_INTERFACE)
+ scrolled.props.vadjustment.connect("value-changed",
+ self._user_action_cb)
+ scrolled.props.hadjustment.connect("value-changed",
+ self._user_action_cb)
+ self.connect("focus-in-event", self._focus_in_event_cb)
+ self.connect("focus-out-event", self._focus_out_event_cb)
+ self.connect("notify::active", self._now_active_cb)
+
+ _logger.debug('Suspend on idle enabled')
+ except dbus.DBusException, e:
+ _logger.info(
+ 'Hardware manager service not found, no idle suspend.')
+ else:
+ _logger.debug('Suspend on idle disabled')
+
+ self.connect("shared", self._shared_cb)
+
+ h = hash(self._activity_id)
+ self.port = 1024 + (h % 64511)
+
+ if handle.uri:
+ self._load_document(handle.uri)
+
+ # start on the read toolbar
+ self.toolbox.set_current_toolbar(_TOOLBAR_READ)
+
+ if self.shared_activity:
+ # We're joining
+ if self.get_shared():
+ # Already joined for some reason, just get the document
+ self._joined_cb(self)
+ else:
+ # Wait for a successful join before trying to get the document
+ self.connect("joined", self._joined_cb)
+ elif self._object_id is None:
+ # Not joining, not resuming
+ self._show_journal_object_picker()
+ # uncomment this and adjust the path for easier testing
+ #else:
+ # self._load_document('file:///home/smcv/tmp/test.pdf')
+
+ def _show_journal_object_picker(self):
+ """Show the journal object picker to load a document.
+
+ This is for if Read is launched without a document.
+ """
+ if not self._want_document:
+ return
+ chooser = ObjectChooser(_('Choose document'), self,
+ gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ what_filter=mime.GENERIC_TYPE_TEXT)
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ logging.debug('ObjectChooser: %r' %
+ chooser.get_selected_object())
+ jobject = chooser.get_selected_object()
+ if jobject and jobject.file_path:
+ self.read_file(jobject.file_path)
+ finally:
+ chooser.destroy()
+ del chooser
+
+ def _now_active_cb(self, widget, pspec):
+ if self.props.active:
+ # Now active, start initial suspend timeout
+ if self._idle_timer > 0:
+ gobject.source_remove(self._idle_timer)
+ self._idle_timer = gobject.timeout_add(15000, self._suspend_cb)
+ self._sleep_inhibit = False
+ else:
+ # Now inactive
+ self._sleep_inhibit = True
+
+ def _focus_in_event_cb(self, widget, event):
+ """Enable ebook mode idle sleep since Read has focus."""
+ self._sleep_inhibit = False
+ self._user_action_cb(self)
+
+ def _focus_out_event_cb(self, widget, event):
+ """Disable ebook mode idle sleep since Read lost focus."""
+ self._sleep_inhibit = True
+
+ def _user_action_cb(self, widget):
+ """Set a timer for going back to ebook mode idle sleep."""
+ if self._idle_timer > 0:
+ gobject.source_remove(self._idle_timer)
+ self._idle_timer = gobject.timeout_add(5000, self._suspend_cb)
+
+ def _suspend_cb(self):
+ """Go into ebook mode idle sleep."""
+ # If the machine has been idle for 5 seconds, suspend
+ self._idle_timer = 0
+ if not self._sleep_inhibit and not self.get_shared():
+ self._service.set_kernel_suspend()
+ return False
+
+ def read_file(self, file_path):
+ """Load a file from the datastore on activity start."""
+ _logger.debug('ReadActivity.read_file: %s', file_path)
+ tempfile = os.path.join(self.get_activity_root(), 'instance',
+ 'tmp%i' % time.time())
+ os.link(file_path, tempfile)
+ self._tempfile = tempfile
+ self._load_document('file://' + self._tempfile)
+
+ # FIXME: This should obviously be fixed properly
+ gobject.timeout_add(1000, self.__view_toolbar_needs_update_size_cb,
+ None)
+
+ def write_file(self, file_path):
+ """Write into datastore for Keep.
+
+ The document is saved by hardlinking from the temporary file we
+ keep around instead of "saving".
+
+ The metadata is updated, including current page, view settings,
+ search text.
+
+ """
+ if self._tempfile is None:
+ # Workaround for closing Read with no document loaded
+ raise NotImplementedError
+
+ try:
+ self.metadata['Read_current_page'] = \
+ str(self._document.get_page_cache().get_current_page())
+
+ self.metadata['Read_zoom'] = str(self._view.props.zoom)
+
+ if self._view.props.sizing_mode == evince.SIZING_BEST_FIT:
+ self.metadata['Read_sizing_mode'] = "best-fit"
+ elif self._view.props.sizing_mode == evince.SIZING_FREE:
+ self.metadata['Read_sizing_mode'] = "free"
+ elif self._view.props.sizing_mode == evince.SIZING_FIT_WIDTH:
+ self.metadata['Read_sizing_mode'] = "fit-width"
+ else:
+ _logger.error("Don't know how to save sizing_mode state '%s'" %
+ self._view.props.sizing_mode)
+ self.metadata['Read_sizing_mode'] = "fit-width"
+
+ self.metadata['Read_search'] = \
+ self._edit_toolbar._search_entry.props.text
+
+ except Exception, e:
+ _logger.error('write_file(): %s', e)
+
+ self.metadata['Read_search'] = \
+ self._edit_toolbar._search_entry.props.text
+
+ os.link(self._tempfile, file_path)
+
+ if self._close_requested:
+ _logger.debug("Removing temp file %s because we will close",
+ self._tempfile)
+ os.unlink(self._tempfile)
+ self._tempfile = None
+
+ def can_close(self):
+ """Prepare to cleanup on closing.
+
+ Called from self.close()
+ """
+ self._close_requested = True
+ return True
+
+ def _download_result_cb(self, getter, tempfile, suggested_name, tube_id):
+ if self._download_content_type == 'text/html':
+ # got an error page instead
+ self._download_error_cb(getter, 'HTTP Error', tube_id)
+ return
+
+ del self.unused_download_tubes
+
+ self._tempfile = tempfile
+ file_path = os.path.join(self.get_activity_root(), 'instance',
+ '%i' % time.time())
+ _logger.debug("Saving file %s to datastore...", file_path)
+ os.link(tempfile, file_path)
+ self._jobject.file_path = file_path
+ datastore.write(self._jobject, transfer_ownership=True)
+
+ _logger.debug("Got document %s (%s) from tube %u",
+ tempfile, suggested_name, tube_id)
+ self._load_document("file://%s" % tempfile)
+ self.save()
+
+ def _download_progress_cb(self, getter, bytes_downloaded, tube_id):
+ # FIXME: Draw a progress bar
+ if self._download_content_length > 0:
+ _logger.debug("Downloaded %u of %u bytes from tube %u...",
+ bytes_downloaded, self._download_content_length,
+ tube_id)
+ else:
+ _logger.debug("Downloaded %u bytes from tube %u...",
+ bytes_downloaded, tube_id)
+
+ def _download_error_cb(self, getter, err, tube_id):
+ _logger.debug("Error getting document from tube %u: %s",
+ tube_id, err)
+ self._want_document = True
+ self._download_content_length = 0
+ self._download_content_type = None
+ gobject.idle_add(self._get_document)
+
+ def _download_document(self, tube_id, path):
+ # FIXME: should ideally have the CM listen on a Unix socket
+ # instead of IPv4 (might be more compatible with Rainbow)
+ chan = self.shared_activity.telepathy_tubes_chan
+ iface = chan[telepathy.CHANNEL_TYPE_TUBES]
+ addr = iface.AcceptStreamTube(tube_id,
+ telepathy.SOCKET_ADDRESS_TYPE_IPV4,
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0,
+ utf8_strings=True)
+ _logger.debug('Accepted stream tube: listening address is %r', addr)
+ # SOCKET_ADDRESS_TYPE_IPV4 is defined to have addresses of type '(sq)'
+ assert isinstance(addr, dbus.Struct)
+ assert len(addr) == 2
+ assert isinstance(addr[0], str)
+ assert isinstance(addr[1], (int, long))
+ assert addr[1] > 0 and addr[1] < 65536
+ port = int(addr[1])
+
+ getter = ReadURLDownloader("http://%s:%d/document"
+ % (addr[0], port))
+ getter.connect("finished", self._download_result_cb, tube_id)
+ getter.connect("progress", self._download_progress_cb, tube_id)
+ getter.connect("error", self._download_error_cb, tube_id)
+ _logger.debug("Starting download to %s...", path)
+ getter.start(path)
+ self._download_content_length = getter.get_content_length()
+ self._download_content_type = getter.get_content_type()
+ return False
+
+ def _get_document(self):
+ if not self._want_document:
+ return False
+
+ # Assign a file path to download if one doesn't exist yet
+ if not self._jobject.file_path:
+ path = os.path.join(self.get_activity_root(), 'instance',
+ 'tmp%i' % time.time())
+ else:
+ path = self._jobject.file_path
+
+ # Pick an arbitrary tube we can try to download the document from
+ try:
+ tube_id = self.unused_download_tubes.pop()
+ except (ValueError, KeyError), e:
+ _logger.debug('No tubes to get the document from right now: %s',
+ e)
+ return False
+
+ # Avoid trying to download the document multiple times at once
+ self._want_document = False
+ gobject.idle_add(self._download_document, tube_id, path)
+ return False
+
+ def _joined_cb(self, also_self):
+ """Callback for when a shared activity is joined.
+
+ Get the shared document from another participant.
+ """
+ self.watch_for_tubes()
+ gobject.idle_add(self._get_document)
+
+ def _load_document(self, filepath):
+ """Load the specified document and set up the UI.
+
+ filepath -- string starting with file://
+
+ """
+ self._document = evince.factory_get_document(filepath)
+ self._want_document = False
+ self._view.set_document(self._document)
+ self._edit_toolbar.set_document(self._document)
+ self._read_toolbar.set_document(self._document)
+
+ if not self.metadata['title_set_by_user'] == '1':
+ info = self._document.get_info()
+ if info and info.title:
+ self.metadata['title'] = info.title
+
+ current_page = int(self.metadata.get('Read_current_page', '0'))
+ self._document.get_page_cache().set_current_page(current_page)
+
+ sizing_mode = self.metadata.get('Read_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
+ self._view.update_view_size(self.canvas)
+ elif sizing_mode == "free":
+ self._view.props.sizing_mode = evince.SIZING_FREE
+ self._view.props.zoom = float(self.metadata.get('Read_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
+ self._view.update_view_size(self.canvas)
+ 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._view.props.zoom = float(self.metadata['Read_zoom'])
+
+ self._view_toolbar._update_zoom_buttons()
+
+ self._edit_toolbar._search_entry.props.text = \
+ self.metadata.get('Read_search', '')
+
+ # We've got the document, so if we're a shared activity, offer it
+ try:
+ if self.get_shared():
+ self.watch_for_tubes()
+ self._share_document()
+ except Exception, e:
+ _logger.debug('Sharing failed: %s', e)
+
+ def _share_document(self):
+ """Share the document."""
+ # FIXME: should ideally have the fileserver listen on a Unix socket
+ # instead of IPv4 (might be more compatible with Rainbow)
+
+ _logger.debug('Starting HTTP server on port %d', self.port)
+ self._fileserver = ReadHTTPServer(("", self.port),
+ self._tempfile)
+
+ # Make a tube for it
+ chan = self.shared_activity.telepathy_tubes_chan
+ iface = chan[telepathy.CHANNEL_TYPE_TUBES]
+ self._fileserver_tube_id = iface.OfferStreamTube(READ_STREAM_SERVICE,
+ {},
+ telepathy.SOCKET_ADDRESS_TYPE_IPV4,
+ ('127.0.0.1', dbus.UInt16(self.port)),
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0)
+
+ def watch_for_tubes(self):
+ """Watch for new tubes."""
+ tubes_chan = self.shared_activity.telepathy_tubes_chan
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ self._new_tube_cb)
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _new_tube_cb(self, tube_id, initiator, tube_type, service, params,
+ state):
+ """Callback when a new tube becomes available."""
+ _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
+ 'params=%r state=%d', tube_id, initiator, tube_type,
+ service, params, state)
+ if self._document is None and service == READ_STREAM_SERVICE:
+ _logger.debug('I could download from that tube')
+ self.unused_download_tubes.add(tube_id)
+ # if no download is in progress, let's fetch the document
+ if self._want_document:
+ gobject.idle_add(self._get_document)
+
+ def _list_tubes_reply_cb(self, tubes):
+ """Callback when new tubes are available."""
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ """Handle ListTubes error by logging."""
+ _logger.error('ListTubes() failed: %s', e)
+
+ def _shared_cb(self, activityid):
+ """Callback when activity shared.
+
+ Set up to share the document.
+
+ """
+ # We initiated this activity and have now shared it, so by
+ # definition we have the file.
+ _logger.debug('Activity became shared')
+ 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 _edit_toolbar_copy_cb(self, button):
+ self._view.copy()
+
+ def _key_press_event_cb(self, widget, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ _logger.debug("Keyname Press: %s, time: %s", keyname, event.time)
+ if keyname == 'c' and event.state & gtk.gdk.CONTROL_MASK:
+ self._view.copy()
+ return True
+ elif keyname == 'KP_Home':
+ # FIXME: refactor later to self.zoom_in()
+ self._view_toolbar.zoom_in()
+ return True
+ elif keyname == 'KP_End':
+ self._view_toolbar.zoom_out()
+ return True
+ else:
+ return False
+
+ def _key_release_event_cb(self, widget, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ _logger.debug("Keyname Release: %s, time: %s", keyname, event.time)
+
+ def __view_toolbar_needs_update_size_cb(self, view_toolbar):
+ self._view.update_view_size(self.canvas)
+
+ def __view_toolbar_go_fullscreen_cb(self, view_toolbar):
+ self.fullscreen()
diff --git a/printtoolbar.py b/printtoolbar.py
new file mode 100644
index 0000000..5c812c9
--- /dev/null
+++ b/printtoolbar.py
@@ -0,0 +1,473 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# 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 logging
+from gettext import gettext as _
+import re
+
+import pango
+import gobject
+import gtk
+import evince
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics import iconentry
+from sugar.activity import activity
+
+class EditToolbar(activity.EditToolbar):
+ __gtype_name__ = 'EditToolbar'
+
+ def __init__(self, evince_view):
+ activity.EditToolbar.__init__(self)
+
+ self._evince_view = evince_view
+ self._evince_view.find_set_highlight_search(True)
+
+ self._document = None
+ self._find_job = None
+
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ search_item = gtk.ToolItem()
+
+ self._search_entry = iconentry.IconEntry()
+ self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ self._search_entry.add_clear_button()
+ self._search_entry.connect('activate', self._search_entry_activate_cb)
+ self._search_entry.connect('changed', self._search_entry_changed_cb)
+ self._search_entry_changed = True
+
+ width = int(gtk.gdk.screen_width() / 3)
+ self._search_entry.set_size_request(width, -1)
+
+ search_item.add(self._search_entry)
+ self._search_entry.show()
+
+ self.insert(search_item, -1)
+ search_item.show()
+
+ self._prev = ToolButton('go-previous-paired')
+ self._prev.set_tooltip(_('Previous'))
+ self._prev.props.sensitive = False
+ self._prev.connect('clicked', self._find_prev_cb)
+ self.insert(self._prev, -1)
+ self._prev.show()
+
+ self._next = ToolButton('go-next-paired')
+ self._next.set_tooltip(_('Next'))
+ self._next.props.sensitive = False
+ self._next.connect('clicked', self._find_next_cb)
+ self.insert(self._next, -1)
+ self._next.show()
+
+ def set_document(self, document):
+ self._document = document
+
+ def _clear_find_job(self):
+ if self._find_job is None:
+ return
+ if not self._find_job.is_finished():
+ self._find_job.cancel()
+ self._find_job.disconnect(self._find_updated_handler)
+ self._find_job = None
+
+ def _search_find_first(self):
+ self._clear_find_job()
+ text = self._search_entry.props.text
+ if text != "":
+ 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)
+ else:
+ # FIXME: highlight nothing
+ pass
+
+ self._search_entry_changed = False
+ self._update_find_buttons()
+
+ def _search_find_next(self):
+ self._evince_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()
+
+ def _search_entry_activate_cb(self, entry):
+ if self._search_entry_changed:
+ self._search_find_first()
+ else:
+ self._search_find_next()
+
+ def _search_entry_changed_cb(self, entry):
+ self._search_entry_changed = True
+ self._update_find_buttons()
+
+# Automatically start search, maybe after timeout?
+# self._search_find_first()
+
+ def _find_changed_cb(self, page, spec):
+ self._update_find_buttons()
+
+ def _find_updated_cb(self, job, page):
+ self._evince_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()
+ else:
+ self._search_find_next()
+
+ def _update_find_buttons(self):
+ if self._search_entry_changed:
+ if self._search_entry.props.text != "":
+ self._prev.props.sensitive = False
+# self._prev.set_tooltip(_('Find last'))
+ self._next.props.sensitive = True
+ self._next.set_tooltip(_('Find first'))
+ else:
+ self._prev.props.sensitive = False
+ self._next.props.sensitive = False
+ else:
+ self._prev.props.sensitive = True
+ self._prev.set_tooltip(_('Find previous'))
+ self._next.props.sensitive = True
+ self._next.set_tooltip(_('Find next'))
+
+class ReadToolbar(gtk.Toolbar):
+ __gtype_name__ = 'ReadToolbar'
+
+ def __init__(self, evince_view):
+ gtk.Toolbar.__init__(self)
+
+ self._evince_view = evince_view
+ self._document = None
+
+ self._back = ToolButton('go-previous')
+ self._back.set_tooltip(_('Back'))
+ self._back.props.sensitive = False
+ self._back.connect('clicked', self._go_back_cb)
+ self.insert(self._back, -1)
+ self._back.show()
+
+ self._forward = ToolButton('go-next')
+ self._forward.set_tooltip(_('Forward'))
+ self._forward.props.sensitive = False
+ self._forward.connect('clicked', self._go_forward_cb)
+ self.insert(self._forward, -1)
+ self._forward.show()
+
+ num_page_item = gtk.ToolItem()
+
+ self._num_page_entry = gtk.Entry()
+ self._num_page_entry.set_text('0')
+ self._num_page_entry.set_alignment(1)
+ self._num_page_entry.connect('insert-text',
+ self._num_page_entry_insert_text_cb)
+ self._num_page_entry.connect('activate',
+ self._num_page_entry_activate_cb)
+
+ self._num_page_entry.set_width_chars(4)
+
+ num_page_item.add(self._num_page_entry)
+ self._num_page_entry.show()
+
+ self.insert(num_page_item, -1)
+ num_page_item.show()
+
+ total_page_item = gtk.ToolItem()
+
+ self._total_page_label = gtk.Label()
+
+ label_attributes = pango.AttrList()
+ label_attributes.insert(pango.AttrSize(14000, 0, -1))
+ label_attributes.insert(pango.AttrForeground(65535, 65535, 65535, 0, -1))
+ self._total_page_label.set_attributes(label_attributes)
+
+ self._total_page_label.set_text(' / 0')
+ total_page_item.add(self._total_page_label)
+ self._total_page_label.show()
+
+ self.insert(total_page_item, -1)
+ total_page_item.show()
+
+ spacer = gtk.SeparatorToolItem()
+ spacer.props.draw = False
+ self.insert(spacer, -1)
+ spacer.show()
+
+ navitem = gtk.ToolItem()
+
+ self._navigator = gtk.ComboBox()
+ cell = gtk.CellRendererText()
+ self._navigator.pack_start(cell, True)
+ self._navigator.add_attribute(cell, 'text', 0)
+ self._navigator.props.sensitive = False
+
+ navitem.add(self._navigator)
+
+ self.insert(navitem, -1)
+ navitem.show_all()
+
+
+ def set_document(self, document):
+ self._document = document
+ page_cache = self._document.get_page_cache()
+ page_cache.connect('page-changed', self._page_changed_cb)
+ self._update_nav_buttons()
+ self._update_toc()
+
+ def _num_page_entry_insert_text_cb(self, entry, text, length, position):
+ if not re.match('[0-9]', text):
+ entry.emit_stop_by_name('insert-text')
+ return True
+ return False
+
+ def _num_page_entry_activate_cb(self, entry):
+ if entry.props.text:
+ page = int(entry.props.text) - 1
+ else:
+ page = 0
+
+ if page >= self._document.get_n_pages():
+ page = self._document.get_n_pages() - 1
+ elif page < 0:
+ page = 0
+
+ self._document.get_page_cache().set_current_page(page)
+ entry.props.text = str(page + 1)
+
+ def _go_back_cb(self, button):
+ self._evince_view.previous_page()
+
+ def _go_forward_cb(self, button):
+ self._evince_view.next_page()
+
+ def _page_changed_cb(self, page, proxy):
+ self._update_nav_buttons()
+ if hasattr(self._document, 'has_document_links'):
+ if self._document.has_document_links():
+ self._toc_select_active_page()
+
+ def _update_nav_buttons(self):
+ current_page = self._document.get_page_cache().get_current_page()
+ self._back.props.sensitive = current_page > 0
+ self._forward.props.sensitive = \
+ current_page < self._document.get_n_pages() - 1
+
+ self._num_page_entry.props.text = str(current_page + 1)
+ self._total_page_label.props.label = \
+ ' / ' + str(self._document.get_n_pages())
+
+ def _update_toc(self):
+ if hasattr(self._document, 'has_document_links'):
+ if self._document.has_document_links():
+ self._navigator.props.sensitive = True
+
+ 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()
+
+ def _navigator_changed_cb(self, combobox):
+ iter = self._navigator.get_active_iter()
+
+ link = self._toc_model.get(iter, 1)[0]
+ self._evince_view.handle_link(link)
+
+ def _toc_select_active_page_foreach(self, model, path, iter, current_page):
+ link = self._toc_model.get(iter, 1)[0]
+
+ if current_page == link.get_page():
+ self._navigator.set_active_iter(iter)
+ return True
+ else:
+ return False
+
+ def _toc_select_active_page(self):
+ 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()
+
+ if current_link.get_page() == current_page:
+ # Nothing to do
+ return
+
+ self._navigator.handler_block(self.__navigator_changed_handler_id)
+ self._toc_model.foreach(self._toc_select_active_page_foreach, current_page)
+ self._navigator.handler_unblock(self.__navigator_changed_handler_id)
+
+
+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,
+ ([]))
+ }
+
+ def __init__(self, evince_view):
+ gtk.Toolbar.__init__(self)
+
+ self._evince_view = evince_view
+ self._document = None
+
+ self._zoom_out = ToolButton('zoom-out')
+ self._zoom_out.set_tooltip(_('Zoom out'))
+ self._zoom_out.connect('clicked', self._zoom_out_cb)
+ self.insert(self._zoom_out, -1)
+ self._zoom_out.show()
+
+ self._zoom_in = ToolButton('zoom-in')
+ self._zoom_in.set_tooltip(_('Zoom in'))
+ 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)
+ self.insert(self._zoom_to_width, -1)
+ self._zoom_to_width.show()
+
+ palette = self._zoom_to_width.get_palette()
+ menu_item = MenuItem(_('Zoom to fit'))
+ menu_item.connect('activate', self._zoom_to_fit_menu_item_activate_cb)
+ palette.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Actual size'))
+ menu_item.connect('activate', self._actual_size_menu_item_activate_cb)
+ palette.menu.append(menu_item)
+ menu_item.show()
+
+ tool_item = gtk.ToolItem()
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ self._zoom_spin = gtk.SpinButton()
+ self._zoom_spin.set_range(5.409, 400)
+ self._zoom_spin.set_increments(1, 10)
+ self._zoom_spin.props.value = self._evince_view.props.zoom * 100
+ self._zoom_spin_notify_value_handler = self._zoom_spin.connect(
+ 'notify::value', self._zoom_spin_notify_value_cb)
+ tool_item.add(self._zoom_spin)
+ self._zoom_spin.show()
+
+ zoom_perc_label = gtk.Label(_("%"))
+ zoom_perc_label.show()
+ tool_item_zoom_perc_label = gtk.ToolItem()
+ tool_item_zoom_perc_label.add(zoom_perc_label)
+ self.insert(tool_item_zoom_perc_label, -1)
+ tool_item_zoom_perc_label.show()
+
+ self._view_notify_zoom_handler = self._evince_view.connect(
+ 'notify::zoom', self._view_notify_zoom_cb)
+
+ self._update_zoom_buttons()
+
+ spacer = gtk.SeparatorToolItem()
+ spacer.props.draw = False
+ self.insert(spacer, -1)
+ spacer.show()
+
+ self._fullscreen = ToolButton('view-fullscreen')
+ self._fullscreen.set_tooltip(_('Fullscreen'))
+ self._fullscreen.connect('clicked', self._fullscreen_cb)
+ self.insert(self._fullscreen, -1)
+ self._fullscreen.show()
+
+ def _zoom_spin_notify_value_cb(self, zoom_spin, pspec):
+ self._evince_view.disconnect(self._view_notify_zoom_handler)
+ try:
+ 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)
+
+ def _view_notify_zoom_cb(self, evince_view, pspec):
+ self._zoom_spin.disconnect(self._zoom_spin_notify_value_handler)
+ try:
+ self._zoom_spin.props.value = round(evince_view.props.zoom * 100.0)
+ finally:
+ self._zoom_spin_notify_value_handler = self._zoom_spin.connect(
+ 'notify::value', self._zoom_spin_notify_value_cb)
+
+ def zoom_in(self):
+ self._evince_view.props.sizing_mode = evince.SIZING_FREE
+ self._evince_view.zoom_in()
+ self._update_zoom_buttons()
+
+ def _zoom_in_cb(self, button):
+ self.zoom_in()
+
+ def zoom_out(self):
+ self._evince_view.props.sizing_mode = evince.SIZING_FREE
+ self._evince_view.zoom_out()
+ self._update_zoom_buttons()
+
+ def _zoom_out_cb(self, button):
+ self.zoom_out()
+
+ def zoom_to_width(self):
+ self._evince_view.props.sizing_mode = evince.SIZING_FIT_WIDTH
+ self.emit('needs-update-size')
+ 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()
+
+ def _zoom_to_fit_menu_item_activate_cb(self, menu_item):
+ self._evince_view.props.sizing_mode = evince.SIZING_BEST_FIT
+ self.emit('needs-update-size')
+ self._update_zoom_buttons()
+
+ def _actual_size_menu_item_activate_cb(self, menu_item):
+ self._evince_view.props.sizing_mode = evince.SIZING_FREE
+ self._evince_view.props.zoom = 1.0
+ self._update_zoom_buttons()
+
+ def _fullscreen_cb(self, button):
+ self.emit('go-fullscreen')