On Tue, Aug 16, 2011 at 7:05 AM, Simon Schampijer <si...@schampijer.de> wrote: > 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.ben...@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(), > > > _______________________________________________ > Sugar-devel mailing list > Sugar-devel@lists.sugarlabs.org > http://lists.sugarlabs.org/listinfo/sugar-devel > >
I created a separate patch for Sascha yesterday to apply the clone patch without having applied the add sugar-toolkit patch. But this morning I also adding the badges as we discussed in the design meeting and reworking the title code as well. Maybe we can merge all of our efforts over the course of the day. -walter -- Walter Bender Sugar Labs http://www.sugarlabs.org _______________________________________________ Sugar-devel mailing list Sugar-devel@lists.sugarlabs.org http://lists.sugarlabs.org/listinfo/sugar-devel