Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSimon Schampijer <simon@schampijer.de>2009-02-19 18:24:29 (GMT)
committer Simon Schampijer <simon@schampijer.de>2009-02-19 18:24:29 (GMT)
commit6500dc7403fa9c71f846e319fe87f366587d28a7 (patch)
tree3d12223b689b1207ca9b16f9d9b7f9ec7abed09d /src
parenta871c2bb1ba79cafb02ed428cd93ae4571248bfe (diff)
View Source: Move to a seperate module to work for key and UI
Diffstat (limited to 'src')
-rw-r--r--src/jarabe/view/Makefile.am3
-rw-r--r--src/jarabe/view/viewsource.py462
2 files changed, 464 insertions, 1 deletions
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('<b>%s</b>' % 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)
+