diff options
author | Your 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) |
commit | 83338521e86f15f88c57f29d9d81f0d9ee6a76f5 (patch) | |
tree | 506dfbc6d1be366f32308c2ce4bf8fbe3238c2bd | |
parent | 3274873efe3e2ff5bc9d0c7d6710797018092e20 (diff) |
well first commit
-rw-r--r-- | printactivity.py | 589 | ||||
-rw-r--r-- | printtoolbar.py | 473 |
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') |