[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