On 08/19/2011 12:11 AM, 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.

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.

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.



  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(),

_______________________________________________
Sugar-devel mailing list
Sugar-devel@lists.sugarlabs.org
http://lists.sugarlabs.org/listinfo/sugar-devel

Reply via email to