From 6500dc7403fa9c71f846e319fe87f366587d28a7 Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Thu, 19 Feb 2009 18:24:29 +0000 Subject: View Source: Move to a seperate module to work for key and UI --- (limited to 'src/jarabe') diff --git a/src/jarabe/view/Makefile.am b/src/jarabe/view/Makefile.am index 21e7a91..1abea6d 100644 --- a/src/jarabe/view/Makefile.am +++ b/src/jarabe/view/Makefile.am @@ -8,4 +8,5 @@ sugar_PYTHON = \ palettes.py \ pulsingicon.py \ service.py \ - tabbinghandler.py + tabbinghandler.py \ + viewsource.py diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py new file mode 100644 index 0000000..aac1eb2 --- /dev/null +++ b/src/jarabe/view/viewsource.py @@ -0,0 +1,462 @@ +# Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer +# +# 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 traceback +from gettext import gettext as _ + +import gobject +import pango +import gtk +import gtksourceview2 +import dbus +import gconf + +from sugar.graphics import style +from sugar.graphics.icon import Icon +from sugar.graphics.xocolor import XoColor +from sugar.graphics.menuitem import MenuItem +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.bundle.activitybundle import ActivityBundle +from sugar.datastore import datastore +from sugar import mime + +_SOURCE_FONT = pango.FontDescription('Monospace %d' % style.zoom(6)) + +_logger = logging.getLogger('ViewSource') +map_activity_to_window = {} + +def setup_view_source(activity): + service = activity.get_service() + if service is not None: + try: + service.HandleViewSource() + return + except dbus.DBusException, e: + expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', + 'org.freedesktop.DBus.Python.NotImplementedError'] + if e.get_dbus_name() not in expected_exceptions: + logging.error(traceback.format_exc()) + except Exception: + logging.error(traceback.format_exc()) + + window_xid = activity.get_xid() + if window_xid is None: + _logger.error('Activity without a window xid') + return + + bundle_path = activity.get_bundle_path() + + if window_xid in map_activity_to_window: + _logger.debug('Viewsource window already open for %s %s' % \ + (window_xid, bundle_path)) + return + + document_path = None + if service is not None: + try: + document_path = service.GetDocumentPath() + except dbus.DBusException, e: + expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', + 'org.freedesktop.DBus.Python.NotImplementedError'] + if e.get_dbus_name() not in expected_exceptions: + logging.error(traceback.format_exc()) + except Exception: + logging.error(traceback.format_exc()) + + if bundle_path is None and document_path is None: + _logger.debug('Activity without bundle_path nor document_path') + return + + view_source = ViewSource(window_xid, bundle_path, document_path, + activity.get_title()) + map_activity_to_window[window_xid] = view_source + view_source.show() + +class ViewSource(gtk.Window): + __gtype_name__ = 'SugarViewSource' + + def __init__(self, window_xid, bundle_path, document_path, title): + gtk.Window.__init__(self) + + logging.debug('ViewSource paths: %r %r' % (bundle_path, document_path)) + + self.set_decorated(False) + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) + self.set_border_width(style.LINE_WIDTH) + + width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2 + height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2 + self.set_size_request(width, height) + + self._parent_window_xid = window_xid + + self.connect('realize', self.__realize_cb) + self.connect('destroy', self.__destroy_cb, document_path) + self.connect('key-press-event', self.__key_press_event_cb) + + vbox = gtk.VBox() + self.add(vbox) + vbox.show() + + toolbar = Toolbar(title, bundle_path, document_path) + vbox.pack_start(toolbar, expand=False) + toolbar.connect('stop-clicked', self.__stop_clicked_cb) + toolbar.connect('source-selected', self.__source_selected_cb) + toolbar.show() + + pane = gtk.HPaned() + vbox.pack_start(pane) + pane.show() + + self._selected_file = None + + activity_bundle = ActivityBundle(bundle_path) + command = activity_bundle.get_command() + if len(command.split(' ')) > 1: + name = command.split(' ')[1].split('.')[0] + file_name = name + '.py' + path = os.path.join(activity_bundle.get_path(), file_name) + self._selected_file = path + + self._file_viewer = FileViewer(bundle_path, file_name) + self._file_viewer.connect('file-selected', self.__file_selected_cb) + pane.add1(self._file_viewer) + self._file_viewer.show() + + self._source_display = SourceDisplay() + pane.add2(self._source_display) + self._source_display.show() + self._source_display.file_path = self._selected_file + + if document_path is not None: + self._select_source(document_path) + + def _calculate_char_width(self, char_count): + widget = gtk.Label('') + context = widget.get_pango_context() + pango_font = context.load_font(_SOURCE_FONT) + metrics = pango_font.get_metrics() + return pango.PIXELS(metrics.get_approximate_char_width()) * char_count + + def __realize_cb(self, widget): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.window.set_accept_focus(True) + + parent = gtk.gdk.window_foreign_new(self._parent_window_xid) + self.window.set_transient_for(parent) + + def __stop_clicked_cb(self, widget): + self.destroy() + + def __source_selected_cb(self, widget, path): + self._select_source(path) + + def _select_source(self, path): + if os.path.isfile(path): + self._source_display.file_path = path + self._file_viewer.hide() + else: + self._file_viewer.set_path(path) + self._source_display.file_path = self._selected_file + self._file_viewer.show() + + def __destroy_cb(self, window, document_path): + del map_activity_to_window[self._parent_window_xid] + if document_path is not None and os.path.exists(document_path): + os.unlink(document_path) + + def __key_press_event_cb(self, window, event): + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == 'Escape': + self.destroy() + + def __file_selected_cb(self, file_viewer, file_path): + if file_path is not None and os.path.isfile(file_path): + self._source_display.file_path = file_path + self._selected_file = file_path + else: + self._source_display.file_path = None + +class DocumentButton(RadioToolButton): + __gtype_name__ = 'SugarDocumentButton' + + def __init__(self, file_name, document_path, title): + RadioToolButton.__init__(self) + + self._document_path = document_path + self._title = title + self._jobject = None + + self.props.tooltip = _('Instance Source') + + client = gconf.client_get_default() + self._color = client.get_string('/desktop/sugar/user/color') + icon = Icon(file=file_name, + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + xo_color=XoColor(self._color)) + self.set_icon_widget(icon) + icon.show() + + menu_item = MenuItem(_('Keep')) + icon = Icon(icon_name='document-save', icon_size=gtk.ICON_SIZE_MENU, + xo_color=XoColor(self._color)) + menu_item.set_image(icon) + + menu_item.connect('activate', self.__keep_in_journal_cb) + self.props.palette.menu.append(menu_item) + menu_item.show() + + def __keep_in_journal_cb(self, menu_item): + mime_type = mime.get_from_file_name(self._document_path) + if mime_type == 'application/octet-stream': + mime_type = mime.get_for_file(self._document_path) + + self._jobject = datastore.create() + title = _('Source') + ': ' + self._title + self._jobject.metadata['title'] = title + self._jobject.metadata['keep'] = '0' + self._jobject.metadata['buddies'] = '' + self._jobject.metadata['preview'] = '' + self._jobject.metadata['icon-color'] = self._color + self._jobject.metadata['mime_type'] = mime_type + self._jobject.metadata['source'] = '1' + self._jobject.file_path = self._document_path + datastore.write(self._jobject, transfer_ownership=True, + reply_handler=self.__internal_save_cb, + error_handler=self.__internal_save_error_cb) + + def __internal_save_cb(self): + logging.debug("Saved Source object to datastore.") + self._jobject.destroy() + + def __internal_save_error_cb(self, err): + logging.debug("Error saving Source object to datastore: %s" % err) + self._jobject.destroy() + +class Toolbar(gtk.Toolbar): + __gtype_name__ = 'SugarViewSourceToolbar' + + __gsignals__ = { + 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'source-selected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self, title, bundle_path, document_path): + gtk.Toolbar.__init__(self) + + self._add_separator() + + activity_bundle = ActivityBundle(bundle_path) + file_name = activity_bundle.get_icon() + + if document_path is not None and os.path.exists(document_path): + document_button = DocumentButton(file_name, document_path, title) + document_button.connect('toggled', self.__button_toggled_cb, + document_path) + self.insert(document_button, -1) + document_button.show() + self._add_separator() + + if bundle_path is not None and os.path.exists(bundle_path): + activity_button = RadioToolButton() + icon = Icon(file=file_name, + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR, + fill_color=style.COLOR_TRANSPARENT.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + activity_button.set_icon_widget(icon) + icon.show() + if document_path is not None: + activity_button.props.group = document_button + activity_button.props.tooltip = _('Activity Bundle Source') + activity_button.connect('toggled', self.__button_toggled_cb, + bundle_path) + self.insert(activity_button, -1) + activity_button.show() + self._add_separator() + + text = _('View source: %r') % title + label = gtk.Label() + label.set_markup('%s' % text) + label.set_alignment(0, 0.5) + self._add_widget(label) + + self._add_separator(True) + + stop = ToolButton(icon_name='dialog-cancel') + stop.set_tooltip(_('Close')) + stop.connect('clicked', self.__stop_clicked_cb) + stop.show() + self.insert(stop, -1) + stop.show() + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show() + + def _add_widget(self, widget, expand=False): + tool_item = gtk.ToolItem() + tool_item.set_expand(expand) + + tool_item.add(widget) + widget.show() + + self.insert(tool_item, -1) + tool_item.show() + + def __stop_clicked_cb(self, button): + self.emit('stop-clicked') + + def __button_toggled_cb(self, button, path): + if button.props.active: + self.emit('source-selected', path) + +class FileViewer(gtk.ScrolledWindow): + __gtype_name__ = 'SugarFileViewer' + + __gsignals__ = { + 'file-selected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self, path, initial_filename): + gtk.ScrolledWindow.__init__(self) + + self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC + self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC + self.set_size_request(style.GRID_CELL_SIZE * 3, -1) + + self._path = None + self._initial_filename = initial_filename + + self._tree_view = gtk.TreeView() + self.add(self._tree_view) + self._tree_view.show() + + self._tree_view.props.headers_visible = False + selection = self._tree_view.get_selection() + selection.connect('changed', self.__selection_changed_cb) + + cell = gtk.CellRendererText() + column = gtk.TreeViewColumn() + column.pack_start(cell, True) + column.add_attribute(cell, 'text', 0) + self._tree_view.append_column(column) + self._tree_view.set_search_column(0) + + self.set_path(path) + + def set_path(self, path): + self.emit('file-selected', None) + if self._path == path: + return + self._path = path + self._tree_view.set_model(gtk.TreeStore(str, str)) + self._add_dir_to_model(path) + + def _add_dir_to_model(self, dir_path, parent=None): + model = self._tree_view.get_model() + for f in os.listdir(dir_path): + if not f.endswith('.pyc'): + full_path = os.path.join(dir_path, f) + if os.path.isdir(full_path): + new_iter = model.append(parent, [f, full_path]) + self._add_dir_to_model(full_path, new_iter) + else: + current_iter = model.append(parent, [f, full_path]) + if f == self._initial_filename: + selection = self._tree_view.get_selection() + selection.select_iter(current_iter) + + def __selection_changed_cb(self, selection): + model, tree_iter = selection.get_selected() + if tree_iter is None: + file_path = None + else: + file_path = model.get_value(tree_iter, 1) + self.emit('file-selected', file_path) + +class SourceDisplay(gtk.ScrolledWindow): + __gtype_name__ = 'SugarSourceDisplay' + + def __init__(self): + gtk.ScrolledWindow.__init__(self) + + self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC + self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC + + self._buffer = gtksourceview2.Buffer() + self._buffer.set_highlight_syntax(True) + + self._source_view = gtksourceview2.View(self._buffer) + self._source_view.set_editable(False) + self._source_view.set_cursor_visible(True) + self._source_view.set_show_line_numbers(True) + self._source_view.set_show_right_margin(True) + self._source_view.set_right_margin_position(80) + #self._source_view.set_highlight_current_line(True) #FIXME: Ugly color + self._source_view.modify_font(_SOURCE_FONT) + self.add(self._source_view) + self._source_view.show() + + self._file_path = None + + def _set_file_path(self, file_path): + if file_path == self._file_path: + return + self._file_path = file_path + + if self._file_path is None: + self._buffer.set_text('') + return + + mime_type = mime.get_for_file(self._file_path) + logging.debug('Detected mime type: %r' % mime_type) + + language_manager = gtksourceview2.language_manager_get_default() + detected_language = None + for language_id in language_manager.get_language_ids(): + language = language_manager.get_language(language_id) + if mime_type in language.get_mime_types(): + detected_language = language + break + + if detected_language is not None: + logging.debug('Detected language: %r' % \ + detected_language.get_name()) + + self._buffer.set_language(detected_language) + self._buffer.set_text(open(self._file_path, 'r').read()) + + def _get_file_path(self): + return self._file_path + + file_path = property(_get_file_path, _set_file_path) + -- cgit v0.9.1