diff options
author | Jonas Smedegaard <dr@jones.dk> | 2008-06-09 10:22:07 (GMT) |
---|---|---|
committer | Jonas Smedegaard <dr@jones.dk> | 2008-06-09 10:22:07 (GMT) |
commit | 2e0d07c476794b1eb0f30556d0a3e01553085287 (patch) | |
tree | 279de434713ec2bf1d46e769536f1b0059994bca | |
parent | b58049a0470ebb9f8dcd195053de269bf49feb0a (diff) |
Imported Upstream version 0.81.4upstream/0.81.4
-rwxr-xr-x | configure | 20 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | po/POTFILES.in | 1 | ||||
-rw-r--r-- | src/sugar/activity/activityfactory.py | 2 | ||||
-rw-r--r-- | src/sugar/activity/bundlebuilder.py | 414 | ||||
-rw-r--r-- | src/sugar/activity/registry.py | 8 | ||||
-rw-r--r-- | src/sugar/bundle/activitybundle.py | 5 | ||||
-rw-r--r-- | src/sugar/datastore/datastore.py | 4 | ||||
-rw-r--r-- | src/sugar/graphics/icon.py | 6 | ||||
-rw-r--r-- | src/sugar/graphics/palette.py | 81 | ||||
-rw-r--r-- | src/sugar/profile.py | 18 | ||||
-rw-r--r-- | src/sugar/util.py | 70 |
12 files changed, 363 insertions, 268 deletions
@@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.61 for sugar-toolkit 0.81.3. +# Generated by GNU Autoconf 2.61 for sugar-toolkit 0.81.4. # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. @@ -726,8 +726,8 @@ SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='sugar-toolkit' PACKAGE_TARNAME='sugar-toolkit' -PACKAGE_VERSION='0.81.3' -PACKAGE_STRING='sugar-toolkit 0.81.3' +PACKAGE_VERSION='0.81.4' +PACKAGE_STRING='sugar-toolkit 0.81.4' PACKAGE_BUGREPORT='' ac_unique_file="configure.ac" @@ -1450,7 +1450,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures sugar-toolkit 0.81.3 to adapt to many kinds of systems. +\`configure' configures sugar-toolkit 0.81.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1520,7 +1520,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of sugar-toolkit 0.81.3:";; + short | recursive ) echo "Configuration of sugar-toolkit 0.81.4:";; esac cat <<\_ACEOF @@ -1624,7 +1624,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -sugar-toolkit configure 0.81.3 +sugar-toolkit configure 0.81.4 generated by GNU Autoconf 2.61 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, @@ -1638,7 +1638,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by sugar-toolkit $as_me 0.81.3, which was +It was created by sugar-toolkit $as_me 0.81.4, which was generated by GNU Autoconf 2.61. Invocation command line was $ $0 $@ @@ -2334,7 +2334,7 @@ fi # Define the identity of the package. PACKAGE='sugar-toolkit' - VERSION='0.81.3' + VERSION='0.81.4' cat >>confdefs.h <<_ACEOF @@ -22300,7 +22300,7 @@ exec 6>&1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by sugar-toolkit $as_me 0.81.3, which was +This file was extended by sugar-toolkit $as_me 0.81.4, which was generated by GNU Autoconf 2.61. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -22347,7 +22347,7 @@ Report bugs to <bug-autoconf@gnu.org>." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_cs_version="\\ -sugar-toolkit config.status 0.81.3 +sugar-toolkit config.status 0.81.4 configured by $0, generated by GNU Autoconf 2.61, with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" diff --git a/configure.ac b/configure.ac index 8be66b2..295d547 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT([sugar-toolkit],[0.81.3],[],[sugar-toolkit]) +AC_INIT([sugar-toolkit],[0.81.4],[],[sugar-toolkit]) AC_PREREQ([2.59]) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5eea4af..56bc892 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,3 +1,4 @@ src/sugar/activity/activity.py src/sugar/graphics/alert.py src/sugar/graphics/objectchooser.py +src/sugar/util.py diff --git a/src/sugar/activity/activityfactory.py b/src/sugar/activity/activityfactory.py index 42b8c40..4d6871f 100644 --- a/src/sugar/activity/activityfactory.py +++ b/src/sugar/activity/activityfactory.py @@ -195,7 +195,7 @@ class ActivityCreationHandler(gobject.GObject): self._use_rainbow = os.path.exists('/etc/olpc-security') if service_name in [ 'org.laptop.JournalActivity', 'org.laptop.Terminal', - 'org.laptop.LogViewer', + 'org.laptop.Log', 'org.laptop.Analyze' ]: self._use_rainbow = False diff --git a/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py index 5306de6..bf21085 100644 --- a/src/sugar/activity/bundlebuilder.py +++ b/src/sugar/activity/bundlebuilder.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2008 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -15,218 +15,222 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. -import sys import os import zipfile +import tarfile import shutil import subprocess import re import gettext +from optparse import OptionParser from sugar import env from sugar.bundle.activitybundle import ActivityBundle -class _SvnFileList(list): - def __init__(self): - f = os.popen('svn list -R') - for line in f.readlines(): - filename = line.strip() - if os.path.isfile(filename): - self.append(filename) - f.close() - -class _GitFileList(list): - def __init__(self): - f = os.popen('git-ls-files') - for line in f.readlines(): - filename = line.strip() - if not filename.startswith('.'): - self.append(filename) +def list_files(base_dir, ignore_dirs=None, ignore_files=None): + result = [] + + for root, dirs, files in os.walk(base_dir): + for f in files: + if ignore_files and f not in ignore_files: + rel_path = root[len(base_dir) + 1:] + result.append(os.path.join(rel_path, f)) + if ignore_dirs and root == base_dir: + for ignore in ignore_dirs: + if ignore in dirs: + dirs.remove(ignore) + + return result + +class Config(object): + def __init__(self, bundle_name): + self.source_dir = os.getcwd() + + bundle = ActivityBundle(self.source_dir) + version = bundle.get_activity_version() + + self.bundle_name = bundle_name + self.xo_name = '%s-%d.xo' % (self.bundle_name, version) + self.tarball_name = '%s-%d.tar.bz2' % (self.bundle_name, version) + self.bundle_id = bundle.get_bundle_id() + self.bundle_root_dir = self.bundle_name + '.activity' + self.tarball_root_dir = '%s-%d' % (self.bundle_name, version) + + info_path = os.path.join(self.source_dir, 'activity', 'activity.info') + f = open(info_path,'r') + info = f.read() f.close() + match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE) + self.activity_name = match.group(1) + +class Builder(object): + def __init__(self, config): + self.config = config + + def build(self): + self.build_locale() + + def build_locale(self): + po_dir = os.path.join(self.config.source_dir, 'po') + + for f in os.listdir(po_dir): + if not f.endswith('.po'): + continue + + file_name = os.path.join(po_dir, f) + lang = f[:-3] + + localedir = os.path.join(self.config.source_dir, 'locale', lang) + mo_path = os.path.join(localedir, 'LC_MESSAGES') + if not os.path.isdir(mo_path): + os.makedirs(mo_path) + + mo_file = os.path.join(mo_path, "%s.mo" % self.config.bundle_id) + args = ["msgfmt", "--output-file=%s" % mo_file, file_name] + retcode = subprocess.call(args) + if retcode: + print 'ERROR - msgfmt failed with return code %i.' % retcode + + cat = gettext.GNUTranslations(open(mo_file, 'r')) + translated_name = cat.gettext(self.config.activity_name) + linfo_file = os.path.join(localedir, 'activity.linfo') + f = open(linfo_file, 'w') + f.write('[Activity]\nname = %s\n' % translated_name) + f.close() -class _DefaultFileList(list): - def __init__(self): - for name in os.listdir('activity'): - if name.endswith('.svg'): - self.append(os.path.join('activity', name)) - - self.append('activity/activity.info') - - if os.path.isfile(_get_source_path('NEWS')): - self.append('NEWS') - -class _ManifestFileList(_DefaultFileList): - def __init__(self, manifest): - _DefaultFileList.__init__(self) - self.append(manifest) - - f = open(manifest,'r') - for line in f.readlines(): - stripped_line = line.strip() - if stripped_line and not stripped_line in self: - self.append(stripped_line) - f.close() - -class _AllFileList(list): - def __init__(self): - for root, dirs, files in os.walk('.'): - if not root.startswith('./locale'): - for f in files: - if not f.endswith('.xo') and \ - f != '.gitignore': - self.append(os.path.join(root, f)) - -def _extract_bundle(source_file, dest_dir): - if not os.path.exists(dest_dir): - os.mkdir(dest_dir) - - zf = zipfile.ZipFile(source_file) - - for name in zf.namelist(): - path = os.path.join(dest_dir, name) - if not os.path.exists(os.path.dirname(path)): - os.makedirs(os.path.dirname(path)) - - outfile = open(path, 'wb') - outfile.write(zf.read(name)) - outfile.flush() - outfile.close() - -def _get_source_path(path=None): - if path: - return os.path.join(os.getcwd(), path) - else: - return os.getcwd() - -def _get_bundle_dir(): - bundle_name = os.path.basename(_get_source_path()) - return bundle_name + '.activity' - -def _get_package_name(bundle_name): - bundle = ActivityBundle(_get_source_path()) - zipname = '%s-%d.xo' % (bundle_name, bundle.get_activity_version()) - return zipname - -def _delete_backups(arg, dirname, names): - for name in names: - if name.endswith('~') or name.endswith('pyc'): - os.remove(os.path.join(dirname, name)) - -def _get_bundle_id(): - bundle = ActivityBundle(_get_source_path()) - return bundle.get_bundle_id() - -def cmd_help(): +class Packager(object): + def __init__(self, config): + self.config = config + self.dist_dir = os.path.join(self.config.source_dir, 'dist') + self.package_path = None + + if not os.path.exists(self.dist_dir): + os.mkdir(self.dist_dir) + + +class BuildPackager(Packager): + def __init__(self, config): + Packager.__init__(self, config) + self.build_dir = self.config.source_dir + + def get_files(self): + return list_files(self.build_dir, + ignore_dirs=['po', 'dist', '.git'], + ignore_files=['.gitignore']) + +class XOPackager(BuildPackager): + def __init__(self, config): + BuildPackager.__init__(self, config) + self.package_path = os.path.join(self.dist_dir, self.config.xo_name) + + def package(self): + bundle_zip = zipfile.ZipFile(self.package_path, 'w', + zipfile.ZIP_DEFLATED) + + for f in self.get_files(): + bundle_zip.write(os.path.join(self.build_dir, f), + os.path.join(self.config.bundle_root_dir, f)) + + bundle_zip.close() + +class SourcePackager(Packager): + def __init__(self, config): + Packager.__init__(self, config) + self.package_path = os.path.join(self.dist_dir, + self.config.tarball_name) + + def get_files(self): + return list_files(self.config.source_dir, + ignore_dirs=['locale', 'dist', '.git'], + ignore_files=['.gitignore']) + + def package(self): + tar = tarfile.open(self.package_path, "w:bz2") + for f in self.get_files(): + tar.add(os.path.join(self.config.source_dir, f), + os.path.join(self.config.tarball_root_dir, f)) + tar.close() + +def cmd_help(config, options, args): print 'Usage: \n\ +setup.py build - build generated files \n\ setup.py dev - setup for development \n\ -setup.py dist - create a bundle package \n\ +setup.py dist_xo - create a xo bundle package \n\ +setup.py dist_source - create a tar source package \n\ setup.py install [dirname] - install the bundle \n\ setup.py uninstall [dirname] - uninstall the bundle \n\ setup.py genpot - generate the gettext pot file \n\ -setup.py genl10n - generate localization files \n\ -setup.py clean - clean the directory \n\ setup.py release - do a new release of the bundle \n\ setup.py help - print this message \n\ ' -def cmd_dev(): +def cmd_dev(config, options, args): bundle_path = env.get_user_activities_path() if not os.path.isdir(bundle_path): os.mkdir(bundle_path) - bundle_path = os.path.join(bundle_path, _get_bundle_dir()) + bundle_path = os.path.join(bundle_path, config.bundle_root_dir) try: - os.symlink(_get_source_path(), bundle_path) + os.symlink(config.source_dir, bundle_path) except OSError: if os.path.islink(bundle_path): print 'ERROR - The bundle has been already setup for development.' else: print 'ERROR - A bundle with the same name is already installed.' -def _get_file_list(manifest): - if os.path.isfile(manifest): - return _ManifestFileList(manifest) - elif os.path.isdir('.git'): - return _GitFileList() - elif os.path.isdir('.svn'): - return _SvnFileList() - else: - return _AllFileList() - -def _get_po_list(manifest): - file_list = {} - - po_regex = re.compile("po/(.*)\.po$") - for file_name in _get_file_list(manifest): - match = po_regex.match(file_name) - if match: - file_list[match.group(1)] = file_name - - return file_list +def cmd_dist_xo(config, options, args): + builder = Builder(config) + builder.build() -def _get_l10n_list(manifest): - l10n_list = [] + packager = XOPackager(config) + packager.package() - for lang in _get_po_list(manifest).keys(): - filename = _get_bundle_id() + '.mo' - l10n_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename)) - l10n_list.append(os.path.join('locale', lang, 'activity.linfo')) +def cmd_dist_source(config, options, args): + packager = SourcePackager(config) + packager.package() - return l10n_list +def cmd_install(config, options, args): + path = args[0] -def _get_activity_name(): - info_path = os.path.join(_get_source_path(), 'activity', 'activity.info') - f = open(info_path,'r') - info = f.read() - f.close() - match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE) - return match.group(1) + packager = XOPackager(config) + packager.package() -def cmd_dist(bundle_name, manifest): - cmd_genl10n(bundle_name, manifest) - file_list = _get_file_list(manifest) + root_path = os.path.join(args[0], config.bundle_root_dir) + if os.path.isdir(root_path): + shutil.rmtree(root_path) - zipname = _get_package_name(bundle_name) - bundle_zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED) - base_dir = bundle_name + '.activity' - - for filename in file_list: - bundle_zip.write(filename, os.path.join(base_dir, filename)) + if not os.path.exists(path): + os.mkdir(path) - for filename in _get_l10n_list(manifest): - bundle_zip.write(filename, os.path.join(base_dir, filename)) + zf = zipfile.ZipFile(packager.package_path) - bundle_zip.close() - -def cmd_install(bundle_name, manifest, path): - cmd_dist(bundle_name, manifest) - cmd_uninstall(path) - - _extract_bundle(_get_package_name(bundle_name), path) + for name in zf.namelist(): + full_path = os.path.join(path, name) + if not os.path.exists(os.path.dirname(full_path)): + os.makedirs(os.path.dirname(full_path)) -def cmd_uninstall(path): - path = os.path.join(path, _get_bundle_dir()) - if os.path.isdir(path): - shutil.rmtree(path) + outfile = open(full_path, 'wb') + outfile.write(zf.read(name)) + outfile.flush() + outfile.close() -def cmd_genpot(bundle_name, manifest): - po_path = os.path.join(_get_source_path(), 'po') +def cmd_genpot(config, options, args): + po_path = os.path.join(config.source_dir, 'po') if not os.path.isdir(po_path): os.mkdir(po_path) python_files = [] - file_list = _get_file_list(manifest) - for file_name in file_list: - if file_name.endswith('.py'): - python_files.append(file_name) + for root_dummy, dirs_dummy, files in os.walk(config.source_dir): + for file_name in files: + if file_name.endswith('.py'): + python_files.append(file_name) # First write out a stub .pot file containing just the translated # activity name, then have xgettext merge the rest of the # translations into that. (We can't just append the activity name # to the end of the .pot file afterwards, because that might # create a duplicate msgid.) - pot_file = os.path.join('po', '%s.pot' % bundle_name) - activity_name = _get_activity_name() - escaped_name = re.sub('([\\\\"])', '\\\\\\1', activity_name) + pot_file = os.path.join('po', '%s.pot' % config.bundle_name) + escaped_name = re.sub('([\\\\"])', '\\\\\\1', config.activity_name) f = open(pot_file, 'w') f.write('#: activity/activity.info:2\n') f.write('msgid "%s"\n' % escaped_name) @@ -241,34 +245,7 @@ def cmd_genpot(bundle_name, manifest): if retcode: print 'ERROR - xgettext failed with return code %i.' % retcode - -def cmd_genl10n(bundle_name, manifest): - source_path = _get_source_path() - activity_name = _get_activity_name() - - po_list = _get_po_list(manifest) - for lang in po_list.keys(): - file_name = po_list[lang] - - localedir = os.path.join(source_path, 'locale', lang) - mo_path = os.path.join(localedir, 'LC_MESSAGES') - if not os.path.isdir(mo_path): - os.makedirs(mo_path) - - mo_file = os.path.join(mo_path, "%s.mo" % _get_bundle_id()) - args = ["msgfmt", "--output-file=%s" % mo_file, file_name] - retcode = subprocess.call(args) - if retcode: - print 'ERROR - msgfmt failed with return code %i.' % retcode - - cat = gettext.GNUTranslations(open(mo_file, 'r')) - translated_name = cat.gettext(activity_name) - linfo_file = os.path.join(localedir, 'activity.linfo') - f = open(linfo_file, 'w') - f.write('[Activity]\nname = %s\n' % translated_name) - f.close() - -def cmd_release(bundle_name, manifest): +def cmd_release(config, options, args): if not os.path.isdir('.git'): print 'ERROR - this command works only for git repositories' @@ -278,7 +255,7 @@ def cmd_release(bundle_name, manifest): print 'Bumping activity version...' - info_path = os.path.join(_get_source_path(), 'activity', 'activity.info') + info_path = os.path.join(config.source_dir, 'activity', 'activity.info') f = open(info_path,'r') info = f.read() f.close() @@ -292,7 +269,7 @@ def cmd_release(bundle_name, manifest): f.write(info) f.close() - news_path = os.path.join(_get_source_path(), 'NEWS') + news_path = os.path.join(config.source_dir, 'NEWS') if os.environ.has_key('SUGAR_NEWS'): print 'Update NEWS.sugar...' @@ -305,7 +282,7 @@ def cmd_release(bundle_name, manifest): else: sugar_news = '' - sugar_news += '%s - %d\n\n' % (bundle_name, version) + sugar_news += '%s - %d\n\n' % (config.bundle_name, version) f = open(news_path,'r') for line in f.readlines(): @@ -345,42 +322,25 @@ def cmd_release(bundle_name, manifest): print 'ERROR - cannot push to git' print 'Creating the bundle...' - cmd_dist(bundle_name, manifest) + packager = XOPackager(config) + packager.package() print 'Done.' -def cmd_clean(): - os.path.walk('.', _delete_backups, None) - -def sanity_check(): - if not os.path.isfile(_get_source_path('NEWS')): - print 'WARNING: NEWS file is missing.' - -def start(bundle_name, manifest='MANIFEST'): - sanity_check() - - if len(sys.argv) < 2: - cmd_help() - elif sys.argv[1] == 'build': - pass - elif sys.argv[1] == 'dev': - cmd_dev() - elif sys.argv[1] == 'dist': - cmd_dist(bundle_name, manifest) - elif sys.argv[1] == 'install' and len(sys.argv) == 3: - cmd_install(bundle_name, manifest, sys.argv[2]) - elif sys.argv[1] == 'uninstall' and len(sys.argv) == 3: - cmd_uninstall(sys.argv[2]) - elif sys.argv[1] == 'genpot': - cmd_genpot(bundle_name, manifest) - elif sys.argv[1] == 'genl10n': - cmd_genl10n(bundle_name, manifest) - elif sys.argv[1] == 'clean': - cmd_clean() - elif sys.argv[1] == 'release': - cmd_release(bundle_name, manifest) - else: - cmd_help() - +def cmd_build(config, options, args): + builder = Builder(config) + builder.build() + +def start(bundle_name): + parser = OptionParser() + (options, args) = parser.parse_args() + + config = Config(bundle_name) + + try: + globals()['cmd_' + args[0]](config, options, args[1:]) + except (KeyError, IndexError): + cmd_help(config, options, args) + if __name__ == '__main__': start() diff --git a/src/sugar/activity/registry.py b/src/sugar/activity/registry.py index 5f5aefc..d5d0529 100644 --- a/src/sugar/activity/registry.py +++ b/src/sugar/activity/registry.py @@ -31,11 +31,12 @@ def _activity_info_from_dict(info_dict): return ActivityInfo(info_dict['name'], info_dict['icon'], info_dict['bundle_id'], info_dict['version'], info_dict['path'], info_dict['show_launcher'], - info_dict['command'], info_dict['favorite']) + info_dict['command'], info_dict['favorite'], + info_dict['installation_time']) class ActivityInfo(object): - def __init__(self, name, icon, bundle_id, version, - path, show_launcher, command, favorite): + def __init__(self, name, icon, bundle_id, version, path, show_launcher, + command, favorite, installation_time): self.name = name self.icon = icon self.bundle_id = bundle_id @@ -44,6 +45,7 @@ class ActivityInfo(object): self.command = command self.show_launcher = show_launcher self.favorite = favorite + self.installation_time = installation_time class ActivityRegistry(gobject.GObject): __gsignals__ = { diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py index 5f1fb7b..db30555 100644 --- a/src/sugar/bundle/activitybundle.py +++ b/src/sugar/bundle/activitybundle.py @@ -163,6 +163,11 @@ class ActivityBundle(Bundle): """Get the activity user visible name.""" return self._name + def get_installation_time(self): + """Get a timestamp representing the time at which this activity was + installed.""" + return os.stat(self._path).st_mtime + def get_bundle_id(self): """Get the activity bundle id""" return self._bundle_id diff --git a/src/sugar/datastore/datastore.py b/src/sugar/datastore/datastore.py index fda29b0..b7792f4 100644 --- a/src/sugar/datastore/datastore.py +++ b/src/sugar/datastore/datastore.py @@ -155,9 +155,7 @@ class DSObject(object): def resume(self, bundle_id=None): from sugar.activity import activityfactory - if self.is_activity_bundle(): - if bundle_id is not None: - raise ValueError('Bundle cannot be resumed as an activity.') + if self.is_activity_bundle() and not bundle_id: logging.debug('Creating activity bundle') bundle = ActivityBundle(self.file_path) diff --git a/src/sugar/graphics/icon.py b/src/sugar/graphics/icon.py index eeb36a1..d22b412 100644 --- a/src/sugar/graphics/icon.py +++ b/src/sugar/graphics/icon.py @@ -414,8 +414,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): self.connect('destroy', self.__destroy_cb) def __destroy_cb(self, icon): - if self._palette is not None: - self._palette.destroy() + if self._palette_invoker is not None: + self._palette_invoker.detach() def set_file_name(self, value): if self._buffer.file_name != value: @@ -508,7 +508,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): self._buffer.badge_name = value self.emit_paint_needed(0, 0, -1, -1) - def get_badge_name(self, value): + def get_badge_name(self): return self._buffer.badge_name badge_name = gobject.property( diff --git a/src/sugar/graphics/palette.py b/src/sugar/graphics/palette.py index 592a6df..79f5e86 100644 --- a/src/sugar/graphics/palette.py +++ b/src/sugar/graphics/palette.py @@ -209,10 +209,10 @@ class Palette(gtk.Window): self._menu_content_separator = gtk.HSeparator() - self._popup_anim = animator.Animator(0.3, 10) + self._popup_anim = animator.Animator(.5, 10) self._popup_anim.add(_PopupAnimation(self)) - self._secondary_anim = animator.Animator(1.0, 10) + self._secondary_anim = animator.Animator(2.0, 10) self._secondary_anim.add(_SecondaryAnimation(self)) self._popdown_anim = animator.Animator(0.6, 10) @@ -246,6 +246,7 @@ class Palette(gtk.Window): self._palette_popup_sid = None self._enter_invoker_hid = None self._leave_invoker_hid = None + self._right_click_invoker_hid = None # we set these for backward compatibility if label is not None: @@ -265,10 +266,8 @@ class Palette(gtk.Window): # The menu is not shown here until an item is added self.menu = _Menu(self) - self.connect('enter-notify-event', - self._enter_notify_event_cb) - self.connect('leave-notify-event', - self._leave_notify_event_cb) + self.connect('enter-notify-event', self.__enter_notify_event_cb) + self.connect('leave-notify-event', self.__leave_notify_event_cb) self._mouse_detector = MouseSpeedDetector(self, 200, 5) self._mouse_detector.connect('motion-slow', self._mouse_slow_cb) @@ -314,6 +313,7 @@ class Palette(gtk.Window): if self._invoker is not None: self._invoker.disconnect(self._enter_invoker_hid) self._invoker.disconnect(self._leave_invoker_hid) + self._invoker.disconnect(self._right_click_invoker_hid) self._invoker = invoker if invoker is not None: @@ -321,6 +321,8 @@ class Palette(gtk.Window): 'mouse-enter', self._invoker_mouse_enter_cb) self._leave_invoker_hid = self._invoker.connect( 'mouse-leave', self._invoker_mouse_leave_cb) + self._right_click_invoker_hid = self._invoker.connect( + 'right-click', self._invoker_right_click_cb) if hasattr(invoker.props, 'widget'): self._label.props.accel_widget = invoker.props.widget @@ -586,9 +588,6 @@ class Palette(gtk.Window): self.palette_state = state - def _invoker_mouse_enter_cb(self, invoker): - self._mouse_detector.start() - def _mouse_slow_cb(self, widget): self._mouse_detector.stop() self._palette_do_popup() @@ -610,16 +609,26 @@ class Palette(gtk.Window): self.popup(immediate=immediate) + def _invoker_mouse_enter_cb(self, invoker): + self._mouse_detector.start() + def _invoker_mouse_leave_cb(self, invoker): self._mouse_detector.stop() self.popdown() - def _enter_notify_event_cb(self, widget, event): + def _invoker_right_click_cb(self, invoker): + self._popup_anim.stop() + self._secondary_anim.stop() + self._popdown_anim.stop() + self._set_state(self.SECONDARY) + self._show() + + def __enter_notify_event_cb(self, widget, event): if event.detail != gtk.gdk.NOTIFY_INFERIOR: self._popdown_anim.stop() self._secondary_anim.start() - def _leave_notify_event_cb(self, widget, event): + def __leave_notify_event_cb(self, widget, event): if event.detail != gtk.gdk.NOTIFY_INFERIOR: self.popdown() @@ -706,6 +715,7 @@ class Invoker(gobject.GObject): __gsignals__ = { 'mouse-enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), 'mouse-leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'right-click': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), 'focus-out': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) } @@ -877,17 +887,23 @@ class Invoker(gobject.GObject): self._cursor_x = -1 self._cursor_y = -1 - def notify_mouse_enter(self): + def _ensure_palette_exists(self): if self.parent and self.palette is None: palette = self.parent.create_palette() if palette: self.palette = palette + def notify_mouse_enter(self): + self._ensure_palette_exists() self.emit('mouse-enter') def notify_mouse_leave(self): self.emit('mouse-leave') + def notify_right_click(self): + self._ensure_palette_exists() + self.emit('right-click') + def get_palette(self): return self._palette @@ -908,6 +924,9 @@ class WidgetInvoker(Invoker): Invoker.__init__(self) self._widget = None + self._enter_hid = None + self._leave_hid = None + self._release_hid = None if parent or widget: self.attach_widget(parent, widget) @@ -918,10 +937,12 @@ class WidgetInvoker(Invoker): else: self._widget = parent - self._enter_hid = widget.connect('enter-notify-event', - self._enter_notify_event_cb) - self._leave_hid = widget.connect('leave-notify-event', - self._leave_notify_event_cb) + self._enter_hid = self._widget.connect('enter-notify-event', + self.__enter_notify_event_cb) + self._leave_hid = self._widget.connect('leave-notify-event', + self.__leave_notify_event_cb) + self._release_hid = self._widget.connect('button-release-event', + self.__button_release_event_cb) self.attach(parent) @@ -929,6 +950,7 @@ class WidgetInvoker(Invoker): Invoker.detach(self) self._widget.disconnect(self._enter_hid) self._widget.disconnect(self._leave_hid) + self._widget.disconnect(self._release_hid) def get_rect(self): allocation = self._widget.get_allocation() @@ -973,12 +995,19 @@ class WidgetInvoker(Invoker): self._widget.allocation.width, self._widget.allocation.height) - def _enter_notify_event_cb(self, widget, event): + def __enter_notify_event_cb(self, widget, event): self.notify_mouse_enter() - def _leave_notify_event_cb(self, widget, event): + def __leave_notify_event_cb(self, widget, event): self.notify_mouse_leave() + def __button_release_event_cb(self, widget, event): + if event.button == 3: + self.notify_right_click() + return True + else: + return False + def get_toplevel(self): return self._widget.get_toplevel() @@ -999,6 +1028,8 @@ class CanvasInvoker(Invoker): Invoker.__init__(self) self._position_hint = self.AT_CURSOR + self._motion_hid = None + self._release_hid = None if parent: self.attach(parent) @@ -1008,11 +1039,14 @@ class CanvasInvoker(Invoker): self._item = parent self._motion_hid = self._item.connect('motion-notify-event', - self._motion_notify_event_cb) + self.__motion_notify_event_cb) + self._release_hid = self._item.connect('button-release-event', + self.__button_release_event_cb) def detach(self): Invoker.detach(self) self._item.disconnect(self._motion_hid) + self._item.disconnect(self._release_hid) def get_default_position(self): return self.AT_CURSOR @@ -1026,7 +1060,7 @@ class CanvasInvoker(Invoker): else: return gtk.gdk.Rectangle() - def _motion_notify_event_cb(self, button, event): + def __motion_notify_event_cb(self, button, event): if event.detail == hippo.MOTION_DETAIL_ENTER: self.notify_mouse_enter() elif event.detail == hippo.MOTION_DETAIL_LEAVE: @@ -1034,6 +1068,13 @@ class CanvasInvoker(Invoker): return False + def __button_release_event_cb(self, button, event): + if event.button == 3: + self.notify_right_click() + return True + else: + return False + def get_toplevel(self): return hippo.get_canvas_for_item(self._item).get_toplevel() diff --git a/src/sugar/profile.py b/src/sugar/profile.py index 11991dd..dc9e1d4 100644 --- a/src/sugar/profile.py +++ b/src/sugar/profile.py @@ -26,6 +26,9 @@ from sugar.graphics.xocolor import XoColor DEFAULT_JABBER_SERVER = 'olpc.collabora.co.uk' DEFAULT_VOLUME = 81 +DEFAULT_TIMEZONE = 'UTC' +DEFAULT_HOT_CORNERS_DELAY = 0.0 +DEFAULT_WARM_EDGES_DELAY = 1000.0 _profile = None @@ -63,8 +66,11 @@ class Profile(object): self.color = None self.jabber_server = DEFAULT_JABBER_SERVER self.jabber_registered = False + self.timezone = DEFAULT_TIMEZONE self.backup1 = None self.sound_volume = DEFAULT_VOLUME + self.hot_corners_delay = DEFAULT_HOT_CORNERS_DELAY + self.warm_edges_delay = DEFAULT_WARM_EDGES_DELAY self._pubkey = None self._privkey_hash = None @@ -94,6 +100,12 @@ class Profile(object): if self.jabber_server: _set_key(cp, 'Jabber', 'Server', self.jabber_server) + _set_key(cp, 'Date', 'Timezone', self.timezone) + + _set_key(cp, 'Frame', 'HotCorners', self.hot_corners_delay) + + _set_key(cp, 'Frame', 'WarmEdges', self.warm_edges_delay) + _set_key(cp, 'Jabber', 'Registered', self.jabber_registered) _set_key(cp, 'Sound', 'Volume', self.sound_volume) @@ -118,6 +130,12 @@ class Profile(object): registered = cp.get('Jabber', 'Registered') if registered.lower() == "true": self.jabber_registered = True + if cp.has_option('Date', 'Timezone'): + self.timezone = cp.get('Date', 'Timezone') + if cp.has_option('Frame', 'HotCorners'): + self.hot_corners_delay = float(cp.get('Frame', 'HotCorners')) + if cp.has_option('Frame', 'WarmEdges'): + self.warm_edges_delay = float(cp.get('Frame', 'WarmEdges')) if cp.has_option('Server', 'Backup1'): self.backup1 = cp.get('Server', 'Backup1') if cp.has_option('Sound', 'Volume'): diff --git a/src/sugar/util.py b/src/sugar/util.py index 12f6824..8f81210 100644 --- a/src/sugar/util.py +++ b/src/sugar/util.py @@ -21,6 +21,8 @@ import sha import random import binascii import string +from gettext import gettext as _ +import gettext def printable_hash(in_hash): """Convert binary hash data into printable characters.""" @@ -168,3 +170,71 @@ class LRU: yield j def keys(self): return self.d.keys() + +units = [['%d year', '%d years', 356 * 24 * 60 * 60], + ['%d month', '%d months', 30 * 24 * 60 * 60], + ['%d week', '%d weeks', 7 * 24 * 60 * 60], + ['%d day', '%d days', 24 * 60 * 60], + ['%d hour', '%d hours', 60 * 60], + ['%d minute', '%d minutes', 60]] + +AND = _(' and ') +COMMA = _(', ') + +# TRANS: Indicating something that just happened, eg. "just now", "moments ago" +NOW = _('Seconds ago') + +# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago", +# "[2 minutes] in the past", or "[3 years, 1 month] earlier" +ELAPSED = _('%s ago') + +# Explanation of the following hack: +# The xgettext utility extracts plural forms by reading the strings included as +# parameters of ngettext(). As our plurals are not passed to ngettext() +# straight away because there needs to be a calculation before we know which +# strings need to be used, then we need to call ngettext() in a fake way so +# xgettext will pick them up as plurals. + +def ngettext(singular, plural, n): + pass + +# TRANS: Relative dates (eg. 1 month and 5 days). +ngettext('%d year', '%d years', 1) +ngettext('%d month', '%d months', 1) +ngettext('%d week', '%d weeks', 1) +ngettext('%d day', '%d days', 1) +ngettext('%d hour', '%d hours', 1) +ngettext('%d minute', '%d minutes', 1) + +del ngettext + +# End of plurals hack + +def timestamp_to_elapsed_string(timestamp, max_levels=2): + levels = 0 + time_period = '' + elapsed_seconds = int(time.time() - timestamp) + + for name_singular, name_plural, factor in units: + elapsed_units = elapsed_seconds / factor + if elapsed_units > 0: + + if levels > 0: + time_period += COMMA + + time_period += gettext.ngettext(name_singular, name_plural, + elapsed_units) % elapsed_units + + elapsed_seconds -= elapsed_units * factor + + if time_period != '': + levels += 1 + + if levels == max_levels: + break + + if levels == 0: + return NOW + + return ELAPSED % time_period + |