[Sugar-devel] [PATCH] Add clone to view-source submenu
Simon Schampijer
simon at schampijer.de
Tue Aug 16 07:05:59 EDT 2011
Hi Walter,
thanks for the patch! First a few comments about the part that touches
viewsource.py, I created a patch on top of yours to hopefully make
things clearer:
- use shutil.copytree for recursively copying a directory tree (maybe we
should do the copying async to not freeze the UI)
- imports on several lines and sugar.* and jarabe.* imports separated by
one line
- I would put the ActivityButton in one class, makes things clearer imho
I will look next at customizebundle.py...
Regards,
Simon
On 08/15/2011 10:44 PM, Walter Bender wrote:
> From: Walter Bender<walter.bender at gmail.com>
>
>
> This patch adds a Clone submenu to the view source toolbar, enabling the
> cloning of an activity for end-user modification.
>
> As per the design team meeting, this patch includes the generation of an
> .xo bundle to be copied to the Journal of the cloned activity.
>
> One known bug not addressed here is that if the bundle_id is missing, the
> cloning will be successful, but the resultant activity will not launch.
>
> ---
>
> Note: This is a modification of an earlier version of the patch. In this
> an emblem is overlaid onto the cloned activity icon. Also a logic error has
> been fixed where cloning would fail if the overlay icon was not found.
>
> Note: This version of the patch uses 'duplicate' rather than 'clone' as
> per the discussion in the 2011-08-15 Design Team meeting and has a dependency
> on the newly committed edit-duplicate icon.
>
> Note: Regeneration of patch would not apply without applying the sugar-toolkit
> view-source patch first. This version applies stand-alone.
>
> src/jarabe/view/Makefile.am | 1 +
> src/jarabe/view/customizebundle.py | 206 ++++++++++++++++++++++++++++++++++++
> src/jarabe/view/viewsource.py | 43 +++++++-
> 3 files changed, 244 insertions(+), 6 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..57434e9
> --- /dev/null
> +++ b/src/jarabe/view/customizebundle.py
> @@ -0,0 +1,206 @@
> +# 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 = 'emblem-view-source.svg'
> +TRANSFORM = '<g transform="matrix(0.4,0,0,0.4,32,32)">\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 = ''
> +
> + info_old = open(os.path.join(home_activities, new_bundle_name,
> + 'activity', 'activity.info'), 'r')
> + info_new = open(os.path.join(home_activities, new_bundle_name,
> + 'activity', 'new_activity.info'), 'w')
> + for line in info_old:
> + tokens = line.split('=')
> + if tokens[0].rstrip() == 'bundle_id':
> + new_bundle_id = '%s_%s_clone' % (tokens[1].strip(), nick)
> + info_new.write('%s = %s\n' % (tokens[0].rstrip(), new_bundle_id))
> + elif tokens[0].rstrip() == 'activity_version':
> + info_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)
> + info_new.write('%s = %s\n' % (tokens[0].rstrip(), new_icon_name))
> + elif tokens[0].rstrip() == 'name':
> + info_new.write('%s = %s_%s\n' % (tokens[0].rstrip(), nick,
> + tokens[1].strip()))
> + activity_name = tokens[1].strip()
> + else:
> + info_new.write(line)
> + info_old.close
> + info_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 CUSTOMICON. '''
> +
> + # First, find CUSTOMICON, which will be used as an overlay.
> + icon_path = None
> + for path in gtk.icon_theme_get_default().get_search_path():
> + if os.path.exists(os.path.join(path, 'sugar', 'scalable', 'emblems',
> + CUSTOMICON)):
> + icon_path = path
> + break
> +
> + if icon_path is None:
> + # If we cannot find the overlay, just copy the old icon
> + _logger.debug('%s not found', CUSTOMICON)
> + command_line = ['cp', 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 CUSTOMICON.
> + fd_custom = open(os.path.join(icon_path, 'sugar', 'scalable', 'actions',
> + CUSTOMICON), 'r')
> +
> + temp_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:
> + temp_custom_svg += temp_string
> + found_end_svg_tag, temp_string = _find_and_split(
> + line, '</svg>', '', None)
> + else:
> + temp_custom_svg += line
> + fd_custom.close
> +
> + # Next, modify CUSTOMICON by applying TRANSFORM
> + custom_svg = TRANSFORM + temp_custom_svg + '\n</g>\n'
> +
> + # Finally, modify the old icon by applying the overlay.
> + icon_old = open(os.path.join(home_activities, new_bundle_name, 'activity',
> + old_icon_name + '.svg'), 'r')
> + icon_new = open(os.path.join(home_activities, new_bundle_name, 'activity',
> + new_icon_name + '.svg'), 'w')
> +
> + found_end_svg_tag = False
> + for line in icon_old:
> + if not found_end_svg_tag:
> + found_end_svg_tag = _find_and_split(line, '</svg>', custom_svg,
> + icon_new, insert_before=True)
> + else:
> + icon_new.write(line)
> + icon_old.close
> + icon_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 648e740..ff98dcb 100644
> --- a/src/jarabe/view/viewsource.py
> +++ b/src/jarabe/view/viewsource.py
> @@ -17,6 +17,7 @@
> # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
>
> import os
> +import subprocess
> import logging
> from gettext import gettext as _
>
> @@ -36,6 +37,9 @@ 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
>
>
> _EXCLUDE_EXTENSIONS = ('.pyc', '.pyo', '.so', '.o', '.a', '.la', '.mo', '~',
> @@ -206,7 +210,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
> @@ -223,15 +227,41 @@ 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(_('Duplicate'))
> + icon = Icon(icon_name='edit-duplicate',
> + 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 [NICK]- '''
> +
> + # 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':
> @@ -288,7 +318,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(),
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: viewsource.patch
URL: <http://lists.sugarlabs.org/archive/sugar-devel/attachments/20110816/650e389e/attachment-0001.ksh>
More information about the Sugar-devel
mailing list