[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