[Sugar-devel] [PATCH] view source enhancements: browse Sugar source and copy bundle source
Walter Bender
walter at sugarlabs.org
Mon Jun 13 15:20:57 EDT 2011
From: Walter Bender <walter.bender at gmail.com>
---
src/jarabe/view/Makefile.am | 1 +
src/jarabe/view/customizebundle.py | 216 ++++++++++++++++++++++++++++++++++++
src/jarabe/view/viewsource.py | 168 ++++++++++++++++++++++------
3 files changed, 350 insertions(+), 35 deletions(-)
create mode 100644 src/jarabe/view/customizebundle.py
diff --git a/src/jarabe/view/Makefile.am b/src/jarabe/view/Makefile.am
index 1abea6d..630f184 100644
--- a/src/jarabe/view/Makefile.am
+++ b/src/jarabe/view/Makefile.am
@@ -3,6 +3,7 @@ sugar_PYTHON = \
__init__.py \
buddyicon.py \
buddymenu.py \
+ customizebundle.py \
keyhandler.py \
launcher.py \
palettes.py \
diff --git a/src/jarabe/view/customizebundle.py b/src/jarabe/view/customizebundle.py
new file mode 100644
index 0000000..bc0cf9c
--- /dev/null
+++ b/src/jarabe/view/customizebundle.py
@@ -0,0 +1,216 @@
+# Copyright (C) 2011 Walter Bender
+#
+# 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 subprocess
+import gtk
+
+import logging
+_logger = logging.getLogger('ViewSource')
+
+from sugar.activity import bundlebuilder
+from sugar.datastore import datastore
+
+CUSTOMICON = 'customize.svg'
+RESCALE = ' <g transform="matrix(0.73,0,0,0.73,7.5,7.5)">\n'
+
+
+def generate_bundle(nick, activity_name, home_activities, new_bundle_name):
+ ''' Generate a new .xo bundle for the activity and copy it into the
+ Journal. '''
+
+ # First remove any existing bundles in dist/
+ if os.path.exists(os.path.join(home_activities, new_bundle_name, 'dist')):
+ command_line = ['rm', os.path.join(home_activities, new_bundle_name,
+ 'dist', '*.xo')]
+ _logger.debug(subprocess.call(command_line))
+ command_line = ['rm', os.path.join(home_activities, new_bundle_name,
+ 'dist', '*.bz2')]
+ _logger.debug(subprocess.call(command_line))
+
+ # Then create a new bundle.
+ config = bundlebuilder.Config(source_dir=os.path.join(
+ home_activities, new_bundle_name),
+ dist_name='%s_%s-1.xo' % (nick, activity_name))
+ bundlebuilder.cmd_fix_manifest(config, None)
+ bundlebuilder.cmd_dist_xo(config, None)
+
+ # Finally copy the new bundle to the Journal.
+ dsobject = datastore.create()
+ dsobject.metadata['title'] = '%s_%s-1.xo' % (nick, activity_name)
+ dsobject.metadata['mime_type'] = 'application/vnd.olpc-sugar'
+ dsobject.set_file_path(os.path.join(
+ home_activities, new_bundle_name, 'dist',
+ '%s_%s-1.xo' % (nick, activity_name)))
+ datastore.write(dsobject)
+ dsobject.destroy()
+
+
+def customize_activity_info(nick, home_activities, new_bundle_name):
+ ''' Modify bundle_id in new activity.info file: (1) change the
+ bundle_id to bundle_id_[NICKNAME]; (2) change the activity_icon
+ [NICKNAME]-activity-icon.svg; (3) set activity_version to 1.
+ Also, modify the activity icon by applying a customize overlay.
+ '''
+ activity_name = ''
+
+ fd_old = open(os.path.join(home_activities, new_bundle_name,
+ 'activity', 'activity.info'), 'r')
+ fd_new = open(os.path.join(home_activities, new_bundle_name,
+ 'activity', 'new_activity.info'), 'w')
+ for line in fd_old:
+ tokens = line.split('=')
+ if tokens[0].rstrip() == 'bundle_id':
+ new_bundle_id = '%s_%s_clone' % (tokens[1].strip(), nick)
+ fd_new.write('%s = %s\n' % (tokens[0].rstrip(), new_bundle_id))
+ elif tokens[0].rstrip() == 'activity_version':
+ fd_new.write('%s = 1\n' % (tokens[0].rstrip()))
+ elif tokens[0].rstrip() == 'icon':
+ old_icon_name = tokens[1].strip()
+ new_icon_name = '%s-%s' % (nick, old_icon_name)
+ fd_new.write('%s = %s\n' % (tokens[0].rstrip(), new_icon_name))
+ elif tokens[0].rstrip() == 'name':
+ fd_new.write('%s = %s_%s\n' % (tokens[0].rstrip(), nick,
+ tokens[1].strip()))
+ activity_name = tokens[1].strip()
+ else:
+ fd_new.write(line)
+ fd_old.close
+ fd_new.close
+ command_line = ['mv', os.path.join(home_activities, new_bundle_name,
+ 'activity', 'new_activity.info'),
+ os.path.join(home_activities, new_bundle_name,
+ 'activity', 'activity.info')]
+ _logger.debug(subprocess.call(command_line))
+
+ _custom_icon(home_activities, new_bundle_name, old_icon_name,
+ new_icon_name)
+
+ return activity_name
+
+
+def _custom_icon(home_activities, new_bundle_name, old_icon_name,
+ new_icon_name):
+ ''' Modify new activity icon by overlaying custom icon. '''
+
+ # First, find customize.svg, which will be used as an overlay.
+ path = None
+ for path in gtk.icon_theme_get_default().get_search_path():
+ if os.path.exists(os.path.join(path, 'sugar', 'scalable', 'actions',
+ CUSTOMICON)):
+ break
+
+ if path is None:
+ _logger.debug('customize.svg not found')
+ command_line = ['mv', os.path.join(home_activities, new_bundle_name,
+ 'activity', old_icon_name + '.svg'),
+ os.path.join(home_activities, new_bundle_name,
+ 'activity', new_icon_name + '.svg')]
+ _logger.debug(subprocess.call(command_line))
+ return
+
+ # Extract the 'payload' from customize.svg.
+ fd_custom = open(os.path.join(path, 'sugar', 'scalable', 'actions',
+ CUSTOMICON), 'r')
+
+ custom_svg = ''
+ found_begin_svg_tag = False
+ found_close_tag = False
+ found_end_svg_tag = False
+
+ for line in fd_custom:
+ if not found_begin_svg_tag:
+ if line.count('<svg') > 0:
+ found_begin_svg_tag = True
+ partials = line.split('<svg')
+ found_close_tag, temp_string = _find_and_split(
+ partials[1], '>', '', None)
+ else:
+ pass
+ elif not found_close_tag:
+ found_close_tag, temp_string = _find_and_split(
+ line, '>', '', None)
+ temp_string = ''
+ elif not found_end_svg_tag:
+ custom_svg += temp_string
+ found_end_svg_tag, temp_string = _find_and_split(
+ line, '</svg>', '', None)
+ else:
+ custom_svg += line
+ fd_custom.close
+
+ # Next, modify the old icon by shrinking it and applying the overlay.
+ fd_old = open(os.path.join(home_activities, new_bundle_name, 'activity',
+ old_icon_name + '.svg'), 'r')
+ fd_new = open(os.path.join(home_activities, new_bundle_name, 'activity',
+ new_icon_name + '.svg'), 'w')
+
+ found_begin_svg_tag = False
+ found_close_tag = False
+ found_end_svg_tag = False
+
+ for line in fd_old:
+ if not found_begin_svg_tag:
+ if line.count('<svg') > 0:
+ found_begin_svg_tag = True
+ partials = line.split('<svg')
+ fd_new.write(partials[0] + '<svg\n')
+ found_close_tag = _find_and_split(partials[1], '>',
+ RESCALE, fd_new)
+ else:
+ fd_new.write(line)
+ elif not found_close_tag:
+ found_close_tag = _find_and_split(line, '>', RESCALE, fd_new)
+ elif not found_end_svg_tag:
+ found_end_svg_tag = _find_and_split(
+ line, '</svg>', ' </g>\n' + custom_svg, fd_new,
+ insert_before=True)
+ else:
+ fd_new.write(line)
+ fd_old.close
+ fd_new.close
+
+
+def _find_and_split(line, token, insert, fd, insert_before=False):
+ ''' If token is found in line, split line, add insert, and write;
+ else just write. '''
+
+ tmp_string = ''
+
+ if line.count(token) > 0:
+ partials = line.split(token)
+ if insert_before:
+ tmp_string += insert
+ tmp_string += partials[0] + token + '\n'
+ else:
+ tmp_string += partials[0] + token + '\n'
+ tmp_string += insert
+ tmp_string += partials[1]
+ if len(partials) > 2:
+ for i, part in enumerate(partials):
+ if i > 1:
+ tmp_string += part + token
+ if fd is None:
+ return True, tmp_string
+ else:
+ fd.write(tmp_string)
+ return True
+ else:
+ if fd is None:
+ return False, line
+ else:
+ fd.write(line)
+ return False
diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py
index a1c0be3..f624d78 100644
--- a/src/jarabe/view/viewsource.py
+++ b/src/jarabe/view/viewsource.py
@@ -1,5 +1,6 @@
# Copyright (C) 2008 One Laptop Per Child
# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2011 Walter Bender
#
# 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
@@ -16,6 +17,8 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
+import sys
+import subprocess
import logging
from gettext import gettext as _
@@ -35,7 +38,10 @@ from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.bundle.activitybundle import ActivityBundle
from sugar.datastore import datastore
from sugar import mime
+from sugar import profile
+from jarabe.view.customizebundle import customize_activity_info, \
+ generate_bundle
_SOURCE_FONT = pango.FontDescription('Monospace %d' % style.FONT_SIZE)
@@ -48,7 +54,6 @@ def setup_view_source(activity):
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']
@@ -82,11 +87,17 @@ def setup_view_source(activity):
logging.exception('Exception occured in GetDocumentPath():')
if bundle_path is None and document_path is None:
- _logger.debug('Activity without bundle_path nor document_path')
+ _logger.debug('Activity has neither a bundle_path nor a document_path')
return
+ sugar_source_paths = [None]
+ for path in sys.path:
+ if path.endswith('site-packages'):
+ sugar_source_paths = [os.path.join(path, 'sugar')]
+ break
+
view_source = ViewSource(window_xid, bundle_path, document_path,
- activity.get_title())
+ sugar_source_paths, activity.get_title())
map_activity_to_window[window_xid] = view_source
view_source.show()
@@ -94,10 +105,11 @@ def setup_view_source(activity):
class ViewSource(gtk.Window):
__gtype_name__ = 'SugarViewSource'
- def __init__(self, window_xid, bundle_path, document_path, title):
+ def __init__(self, window_xid, bundle_path, document_path,
+ sugar_source_paths, title):
gtk.Window.__init__(self)
- logging.debug('ViewSource paths: %r %r', bundle_path, document_path)
+ _logger.debug('ViewSource paths: %r %r', bundle_path, document_path)
self.set_decorated(False)
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
@@ -117,7 +129,8 @@ class ViewSource(gtk.Window):
self.add(vbox)
vbox.show()
- toolbar = Toolbar(title, bundle_path, document_path)
+ toolbar = Toolbar(title, bundle_path, document_path,
+ sugar_source_paths)
vbox.pack_start(toolbar, expand=False)
toolbar.connect('stop-clicked', self.__stop_clicked_cb)
toolbar.connect('source-selected', self.__source_selected_cb)
@@ -127,26 +140,42 @@ class ViewSource(gtk.Window):
vbox.pack_start(pane)
pane.show()
- self._selected_file = None
+ self._selected_bundle_file = None
+ self._selected_sugar_file = None
file_name = ''
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'
+ name = command.split(' ')[1].split('.')[-1]
+ tmppath = command.split(' ')[1].replace('.', '/')
+ file_name = tmppath[0:-(len(name) + 1)] + '.py'
path = os.path.join(activity_bundle.get_path(), file_name)
- self._selected_file = path
+ self._selected_bundle_file = path
+
+ # Split the tree pane into two vertical panes, one of which
+ # will be hidden
+ tree_panes = gtk.VPaned()
+ tree_panes.show()
+
+ self._bundle_source_viewer = FileViewer(bundle_path, file_name)
+ self._bundle_source_viewer.connect('file-selected',
+ self.__file_selected_cb)
+ tree_panes.add1(self._bundle_source_viewer)
+ self._bundle_source_viewer.show()
- 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._sugar_source_viewer = FileViewer(sugar_source_paths, None)
+ self._sugar_source_viewer.connect('file-selected',
+ self.__file_selected_cb)
+ tree_panes.add2(self._sugar_source_viewer)
+ self._sugar_source_viewer.hide()
+
+ pane.add1(tree_panes)
self._source_display = SourceDisplay()
pane.add2(self._source_display)
self._source_display.show()
- self._source_display.file_path = self._selected_file
+ self._source_display.file_path = self._selected_bundle_file
if document_path is not None:
self._select_source(document_path)
@@ -173,12 +202,21 @@ class ViewSource(gtk.Window):
def _select_source(self, path):
if os.path.isfile(path):
+ _logger.debug('_select_source called with file: %r', 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()
+ self._bundle_source_viewer.hide()
+ self._sugar_source_viewer.hide()
+ elif os.path.isdir(path):
+ _logger.debug('_select_source called with path: %r', path)
+ self._bundle_source_viewer.set_path(path)
+ self._source_display.file_path = self._selected_bundle_file
+ self._bundle_source_viewer.show()
+ self._sugar_source_viewer.hide()
+ else: # Sugar source paths
+ _logger.debug('_select_source called with sugar source paths')
+ self._source_display.file_path = self._selected_sugar_file
+ self._bundle_source_viewer.hide()
+ self._sugar_source_viewer.show()
def __destroy_cb(self, window, document_path):
del map_activity_to_window[self._parent_window_xid]
@@ -193,7 +231,10 @@ class ViewSource(gtk.Window):
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
+ if file_viewer == self._bundle_source_viewer:
+ self._selected_bundle_file = file_path
+ else:
+ self._selected_sugar_file = file_path
else:
self._source_display.file_path = None
@@ -201,7 +242,7 @@ class ViewSource(gtk.Window):
class DocumentButton(RadioToolButton):
__gtype_name__ = 'SugarDocumentButton'
- def __init__(self, file_name, document_path, title):
+ def __init__(self, file_name, document_path, title, bundle=False):
RadioToolButton.__init__(self)
self._document_path = document_path
@@ -218,15 +259,40 @@ class DocumentButton(RadioToolButton):
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))
+ if bundle:
+ menu_item = MenuItem(_('Clone'))
+ icon = Icon(icon_name='edit-copy', icon_size=gtk.ICON_SIZE_MENU,
+ xo_color=XoColor(self._color))
+ menu_item.connect('activate', self.__copy_to_home_cb)
+ else:
+ menu_item = MenuItem(_('Keep'))
+ icon = Icon(icon_name='document-save',
+ icon_size=gtk.ICON_SIZE_MENU,
+ xo_color=XoColor(self._color))
+ menu_item.connect('activate', self.__keep_in_journal_cb)
+
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 __copy_to_home_cb(self, menu_item):
+ ''' Make a local copy of the activity bundle in
+ $HOME/Activities as MyActivity '''
+
+ # TODO: Check to see if a copy of activity already exisits
+ # If so, alert the user before overwriting.
+ home_activities = os.path.join(os.environ['HOME'], 'Activities')
+ nick = profile.get_nick_name().replace(' ', '_')
+ new_bundle_name = '%s_%s' % (nick, self._document_path.split('/')[-1])
+ command_line = ['cp', '-r', self._document_path,
+ os.path.join(home_activities, new_bundle_name)]
+ _logger.debug(subprocess.call(command_line))
+
+ activity_name = customize_activity_info(nick, home_activities,
+ new_bundle_name)
+ generate_bundle(nick, activity_name, home_activities, new_bundle_name)
+
def __keep_in_journal_cb(self, menu_item):
mime_type = mime.get_from_file_name(self._document_path)
if mime_type == 'application/octet-stream':
@@ -264,7 +330,7 @@ class Toolbar(gtk.Toolbar):
([str])),
}
- def __init__(self, title, bundle_path, document_path):
+ def __init__(self, title, bundle_path, document_path, sugar_source_paths):
gtk.Toolbar.__init__(self)
document_button = None
@@ -283,7 +349,8 @@ class Toolbar(gtk.Toolbar):
self._add_separator()
if bundle_path is not None and os.path.exists(bundle_path):
- activity_button = RadioToolButton()
+ activity_button = DocumentButton(file_name, bundle_path, title,
+ bundle=True)
icon = Icon(file=file_name,
icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
fill_color=style.COLOR_TRANSPARENT.get_svg(),
@@ -299,6 +366,26 @@ class Toolbar(gtk.Toolbar):
activity_button.show()
self._add_separator()
+ if sugar_source_paths[0] is not None and \
+ os.path.exists(sugar_source_paths[0]):
+ sugar_button = RadioToolButton()
+ icon = Icon(icon_name='computer-xo',
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ fill_color=style.COLOR_TRANSPARENT.get_svg(),
+ stroke_color=style.COLOR_WHITE.get_svg())
+ sugar_button.set_icon_widget(icon)
+ icon.show()
+ if document_button is not None:
+ sugar_button.props.group = document_button
+ else:
+ sugar_button.props.group = activity_button
+ sugar_button.props.tooltip = _('Sugar Source')
+ sugar_button.connect('toggled', self.__button_toggled_cb,
+ sugar_source_paths)
+ self.insert(sugar_button, -1)
+ sugar_button.show()
+ self._add_separator()
+
text = _('View source: %r') % title
label = gtk.Label()
label.set_markup('<b>%s</b>' % text)
@@ -381,20 +468,31 @@ class FileViewer(gtk.ScrolledWindow):
self.emit('file-selected', None)
if self._path == path:
return
- self._path = path
+
self._tree_view.set_model(gtk.TreeStore(str, str))
+
+ if type(path) == list:
+ self._path = path[0]
+ else:
+ self._path = path
+
+ self._model = self._tree_view.get_model()
self._add_dir_to_model(path)
def _add_dir_to_model(self, dir_path, parent=None):
- model = self._tree_view.get_model()
+ if type(dir_path) == list:
+ for path in dir_path:
+ self._add_dir_to_model(path)
+ return
+
for f in os.listdir(dir_path):
- if not f.endswith('.pyc'):
+ if not f.endswith(('.pyc', '.pyo', '.so', '.mo', '~')):
full_path = os.path.join(dir_path, f)
if os.path.isdir(full_path):
- new_iter = model.append(parent, [f, full_path])
+ new_iter = self._model.append(parent, [f, full_path])
self._add_dir_to_model(full_path, new_iter)
else:
- current_iter = model.append(parent, [f, full_path])
+ current_iter = self._model.append(parent, [f, full_path])
if f == self._initial_filename:
selection = self._tree_view.get_selection()
selection.select_iter(current_iter)
@@ -434,8 +532,8 @@ class SourceDisplay(gtk.ScrolledWindow):
self._file_path = None
def _set_file_path(self, file_path):
- if file_path == self._file_path:
- return
+ # if file_path == self._file_path:
+ # return
self._file_path = file_path
if self._file_path is None:
--
1.7.4.4
More information about the Sugar-devel
mailing list