[Sugar-devel] [PATCH] Add-clone-to-view-source-submenu

Walter Bender walter.bender at gmail.com
Mon Aug 22 11:06:51 EDT 2011


On Mon, Aug 22, 2011 at 10:37 AM, Simon Schampijer <simon at schampijer.de> wrote:
> On 08/19/2011 12:11 AM, 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.
>>
>> Note: Managed to regenerate from a broken version. This version of the
>> patch
>> looks for the emblem icon in 'emblems' instead of 'actions'.
>>
>> Note: This version uses a seperate method to generate an all-alpha name
>> for the
>> new activity bundle combining the nick and public_key in a manner similar
>> the the IRC activity nick generator.
>
> Hi Walter,
>
> thanks a lot for the new patch:
>
> There seem to be an issue with the patch I had to remove the following log
> message.
>
> diff --git a/src/jarabe/view/customizebundle.py
> b/src/jarabe/view/customizebundle.py
> index 8964d9d..14eef08 100644
> --- a/src/jarabe/view/customizebundle.py
> +++ b/src/jarabe/view/customizebundle.py
> @@ -145,7 +145,7 @@ def _custom_icon(home_activities, new_bundle_name,
> old_icon_name,
>
>     if icon_path is None:
>         # If we cannot find the overlay, just copy the old icon
> -        _logger.debug('%s not found', CUSTOMICON)
> +        #_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,
>
>
> Furthermore seem the duplication to replace my old activity (at least under
> jhbuild) - I can see the clone but not the old one anymore when starting
> Sugar.
>

This sounds like a bug. I wonder: is your old activity in ~/Activities
or is it in a link from ~/Activities? I am wondering if there is some
issue with copying links instead of the actual directory?

> Regards,
>   Simon
>
> PS: the code does have a few: 'then do this' or 'finally do this'. Those
> might be hints while developing but I would argue that either the code
> stands for itself or you need to regroup it with methods or similar to make
> more readable.

Happy enough to remove the comments.


regards.

-walter

>
>
>>
>>  src/jarabe/view/Makefile.am        |    1 +
>>  src/jarabe/view/customizebundle.py |  235
>> ++++++++++++++++++++++++++++++++++++
>>  src/jarabe/view/viewsource.py      |   42 ++++++-
>>  3 files changed, 272 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..8964d9d
>> --- /dev/null
>> +++ b/src/jarabe/view/customizebundle.py
>> @@ -0,0 +1,235 @@
>> +# 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 hashlib
>> +
>> +import sugar.profile
>> +from sugar.activity import bundlebuilder
>> +from sugar.datastore import datastore
>> +
>> +import logging
>> +_logger = logging.getLogger('ViewSource')
>> +
>> +CUSTOMICONSUBPATH = 'emblems/emblem-view-source.svg'
>> +BADGETRANSFORM = '<g transform="matrix(0.45,0,0,0.45,32,32)">\n'
>> +
>> +
>> +def generate_unique_id():
>> +    ''' Generate an id based on the user's nick name and their public key
>> +    (Based on schema used by IRC activity). '''
>> +
>> +    nick = sugar.profile.get_nick_name()
>> +    pubkey = sugar.profile.get_pubkey()
>> +    m = hashlib.sha1()
>> +    m.update(pubkey)
>> +    hexhash = m.hexdigest()
>> +
>> +    # Get the alphabetic bits of the nick
>> +    nick_letters = "".join([x for x in nick if x.isalpha()])
>> +
>> +    # If that came up with nothing, make it 'XO'.  Also, shorten it if
>> +    # it's more than 11 characters (as we need 5 for - and the hash).
>> +    if len(nick_letters) == 0:
>> +        nick_letters = 'XO'
>> +    elif len(nick_letters)>  11:
>> +        nick_letters = nick_letters[0:11]
>> +
>> +    # Finally, generate an id by using those letters plus the first
>> +    # four hash bits of the user's public key.
>> +    return(nick_letters + '_' + hexhash[0:4])
>> +
>> +
>> +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',
>> +                                       CUSTOMICONSUBPATH)):
>> +            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',
>> +                                CUSTOMICONSUBPATH), '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 BADGETRANSFORM
>> +    custom_svg = BADGETRANSFORM + 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..8671db1 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,8 @@ from sugar.graphics.radiotoolbutton import
>> RadioToolButton
>>  from sugar.bundle.activitybundle import ActivityBundle
>>  from sugar.datastore import datastore
>>  from sugar import mime
>> +from jarabe.view.customizebundle import customize_activity_info, \
>> +    generate_bundle, generate_unique_id
>>
>>
>>  _EXCLUDE_EXTENSIONS = ('.pyc', '.pyo', '.so', '.o', '.a', '.la', '.mo',
>> '~',
>> @@ -206,7 +209,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 +226,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 = generate_unique_id()
>> +        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 +317,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(),
>
>



-- 
Walter Bender
Sugar Labs
http://www.sugarlabs.org


More information about the Sugar-devel mailing list