diff options
-rw-r--r-- | src/jarabe/view/Makefile.am | 1 | ||||
-rw-r--r-- | src/jarabe/view/customizebundle.py | 209 | ||||
-rw-r--r-- | src/jarabe/view/viewsource.py | 46 |
3 files changed, 250 insertions, 6 deletions
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..b6fd440 --- /dev/null +++ b/src/jarabe/view/customizebundle.py @@ -0,0 +1,209 @@ +# 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 glob +import shutil + +import gtk + +import hashlib + +import sugar.profile +from sugar.activity import bundlebuilder +from sugar.datastore import datastore + +import logging +_logger = logging.getLogger('ViewSource') + + +BADGE_SUBPATH = 'emblems/emblem-view-source.svg' +BADGE_TRANSFORM = ' <g transform="matrix(0.45,0,0,0.45,32,32)">\n' +ICON_TRANSFORM = ' <g transform="matrix(1.0,0,0,1.0,0,0)">\n' +XML_HEADER = '<?xml version="1.0" ?> \ +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \ +"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\n\ +<!ENTITY stroke_color "#010101">\n\ +<!ENTITY fill_color "#FFFFFF">\n]>\n' +SVG_START = '<svg enable-background="new 0 0 55 55" height="55px" \ +version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" \ +xmlns="http://www.w3.org/2000/svg" \ +xmlns:xlink="http://www.w3.org/1999/xlink" y="0px">\n' +SVG_END = '</svg>\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() + + nick_letters = "".join([x for x in nick if x.isalpha()]) + + if not nick_letters: + nick_letters = 'XO' + + return nick_letters + '_' + hexhash[:4] + + +def generate_bundle(new_activity_name, user_activities_path, new_basename): + """ Generate a new .xo bundle for the activity and copy it into the + Journal. """ + + if os.path.exists(os.path.join(user_activities_path, new_basename, + 'dist')): + for path in glob.glob(os.path.join(user_activities_path, new_basename, + 'dist', '*')): + os.remove(path) + + config = bundlebuilder.Config(source_dir=os.path.join( + user_activities_path, new_basename), + dist_name='%s-1.xo' % (new_activity_name)) + bundlebuilder.cmd_fix_manifest(config, None) + bundlebuilder.cmd_dist_xo(config, None) + + dsobject = datastore.create() + dsobject.metadata['title'] = '%s-1.xo' % (new_activity_name) + dsobject.metadata['mime_type'] = 'application/vnd.olpc-sugar' + dsobject.set_file_path(os.path.join( + user_activities_path, new_basename, 'dist', + '%s-1.xo' % (new_activity_name))) + datastore.write(dsobject) + dsobject.destroy() + + +def customize_activity_info(nick, user_activities_path, new_basename): + """ 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; + (4) modify the activity icon by applying a customize overlay. + """ + new_activity_name = '' + + info_old = open(os.path.join(user_activities_path, new_basename, + 'activity', 'activity.info'), 'r') + info_new = open(os.path.join(user_activities_path, new_basename, + 'activity', 'new_activity.info'), 'w') + + for line in info_old: + if line.find('=') < 0: + info_new.write(line) + continue + name, value = [token.strip() for token in line.split('=', 1)] + if name == 'bundle_id': + new_value = '%s_%s' % (value, nick) + elif name == 'activity_version': + new_value = '1' + elif name == 'icon': + new_value = value + icon_name = value + elif name == 'name': + new_value = '%s_copy_of_%s' % (nick, value) + new_activity_name = new_value + else: + info_new.write(line) + continue + + info_new.write('%s = %s\n' % (name, new_value)) + + info_old.close() + info_new.close() + + os.rename(os.path.join(user_activities_path, new_basename, + 'activity', 'new_activity.info'), + os.path.join(user_activities_path, new_basename, + 'activity', 'activity.info')) + + _create_custom_icon(user_activities_path, new_basename, icon_name) + + return new_activity_name + + +def _create_custom_icon(user_activities_path, new_basename, icon_name): + """ Modify activity icon by overlaying a badge: + (1) Extract the payload from the badge icon; + (2) Add a transform to resize it and position it; + (3) Insert it into the activity icon. """ + + badge_path = None + for path in gtk.icon_theme_get_default().get_search_path(): + if os.path.exists(os.path.join(path, 'sugar', 'scalable', + BADGE_SUBPATH)): + badge_path = path + break + + if badge_path is None: + _logger.debug('%s not found', BADGE_SUBPATH) + return + + badge_fd = open(os.path.join(badge_path, 'sugar', 'scalable', + BADGE_SUBPATH), 'r') + badge_payload = _extract_svg_payload(badge_fd) + badge_fd.close() + + badge_svg = BADGE_TRANSFORM + badge_payload + '\n</g>' + + icon_path = os.path.join(user_activities_path, new_basename, 'activity', + icon_name + '.svg') + icon_fd = open(icon_path, 'r') + icon_payload = _extract_svg_payload(icon_fd) + icon_fd.close() + + icon_svg = ICON_TRANSFORM + icon_payload + '\n</g>' + + tmp_path = os.path.join(user_activities_path, new_basename, 'activity', + 'tmp.svg') + tmp_icon_fd = open(tmp_path, 'w') + tmp_icon_fd.write(XML_HEADER) + tmp_icon_fd.write(SVG_START) + tmp_icon_fd.write(icon_svg) + tmp_icon_fd.write(badge_svg) + tmp_icon_fd.write(SVG_END) + tmp_icon_fd.close() + + os.remove(icon_path) + os.rename(tmp_path, icon_path) + + +def _extract_svg_payload(fd): + """ Returns everything between <svg ...> and </svg> """ + payload = '' + looking_for_start_svg_token = True + looking_for_close_token = True + looking_for_end_svg_token = True + for line in fd: + if looking_for_start_svg_token: + if line.find('<svg') < 0: + continue + looking_for_start_svg_token = False + line = line.split('<svg', 1)[1] + if looking_for_close_token: + if line.find('>') < 0: + continue + looking_for_close_token = False + line = line.split('>', 1)[1] + if looking_for_end_svg_token: + if line.find('</svg>') < 0: + payload += line + continue + payload += line.split('</svg>')[0] + break + return payload diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py index 1edf061..2c6337c 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 shutil import sys import logging from gettext import gettext as _ @@ -36,7 +37,10 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.bundle.activitybundle import ActivityBundle from sugar.datastore import datastore +from sugar.env import get_user_activities_path 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', '~', @@ -250,7 +254,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 @@ -267,15 +271,44 @@ 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 + user_activities_path """ + + user_activities_path = get_user_activities_path() + nick = generate_unique_id() + new_basename = '%s_copy_of_%s' % ( + nick, os.path.basename(self._document_path)) + if not os.path.exists(os.path.join(user_activities_path, + new_basename)): + shutil.copytree(self._document_path, + os.path.join(user_activities_path, new_basename), + symlinks=True) + new_activity_name = customize_activity_info( + nick, user_activities_path, new_basename) + generate_bundle(new_activity_name, user_activities_path, + new_basename) + else: + _logger.debug('%s already exists', new_basename) + def __keep_in_journal_cb(self, menu_item): mime_type = mime.get_from_file_name(self._document_path) if mime_type == 'application/octet-stream': @@ -334,7 +367,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(), |