Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2008-06-09 10:22:07 (GMT)
committer Jonas Smedegaard <dr@jones.dk>2008-06-09 10:22:07 (GMT)
commit2e0d07c476794b1eb0f30556d0a3e01553085287 (patch)
tree279de434713ec2bf1d46e769536f1b0059994bca
parentb58049a0470ebb9f8dcd195053de269bf49feb0a (diff)
Imported Upstream version 0.81.4upstream/0.81.4
-rwxr-xr-xconfigure20
-rw-r--r--configure.ac2
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/sugar/activity/activityfactory.py2
-rw-r--r--src/sugar/activity/bundlebuilder.py414
-rw-r--r--src/sugar/activity/registry.py8
-rw-r--r--src/sugar/bundle/activitybundle.py5
-rw-r--r--src/sugar/datastore/datastore.py4
-rw-r--r--src/sugar/graphics/icon.py6
-rw-r--r--src/sugar/graphics/palette.py81
-rw-r--r--src/sugar/profile.py18
-rw-r--r--src/sugar/util.py70
12 files changed, 363 insertions, 268 deletions
diff --git a/configure b/configure
index e0a5bba..c50737f 100755
--- a/configure
+++ b/configure
@@ -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
+