Rename the module to sugar3

The old gtk-2 based module will be present in
the 0.94 branch in the sugar-toolkit.

Signed-off-by: Simon Schampijer <simon@laptop.org>
Acked-by: Sascha Silbe <silbe@activitycentral.com>
This commit is contained in:
Simon Schampijer
2011-10-29 10:19:34 +02:00
parent 516d7fc700
commit 000ed75cbe
103 changed files with 25 additions and 25 deletions
+12
View File
@@ -0,0 +1,12 @@
sugardir = $(pythondir)/sugar3/activity
sugar_PYTHON = \
__init__.py \
activity.py \
activityfactory.py \
activityhandle.py \
activityservice.py \
bundlebuilder.py \
i18n.py \
main.py \
namingalert.py \
widgets.py
+55
View File
@@ -0,0 +1,55 @@
# Copyright (C) 2006-2007, 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""Activity implementation code for Sugar-based activities
Each activity within the OLPC environment must provide two
dbus services. The first, patterned after the
sugar.activity.activityfactory.ActivityFactory
class is responsible for providing a "create" method which
takes a small dictionary with values corresponding to a
sugar.activity.activityhandle.ActivityHandle
describing an individual instance of the activity.
Each activity so registered is described by a
sugar.activity.bundle.Bundle
instance, which parses a specially formatted activity.info
file (stored in the activity directory's ./activity
subdirectory). The
sugar.activity.bundlebuilder
module provides facilities for the standard setup.py module
which produces and registers bundles from activity source
directories.
Once instantiated by the ActivityFactory's create method,
each activity must provide an introspection API patterned
after the
sugar.activity.activityservice.ActivityService
class. This class allows for querying the ID of the root
window, requesting sharing across the network, and basic
"what type of application are you" queries.
"""
File diff suppressed because it is too large Load Diff
+374
View File
@@ -0,0 +1,374 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""Shell side object which manages request to start activity
UNSTABLE. Activities are currently not allowed to run other activities so at
the moment there is no reason to stabilize this API.
"""
import logging
import uuid
import dbus
import gobject
from sugar.activity.activityhandle import ActivityHandle
from sugar import util
from sugar import env
from sugar.datastore import datastore
from errno import EEXIST, ENOSPC
import os
import tempfile
import subprocess
import pwd
_SHELL_SERVICE = 'org.laptop.Shell'
_SHELL_PATH = '/org/laptop/Shell'
_SHELL_IFACE = 'org.laptop.Shell'
_ACTIVITY_FACTORY_INTERFACE = 'org.laptop.ActivityFactory'
# helper method to close all filedescriptors
# borrowed from subprocess.py
try:
MAXFD = os.sysconf('SC_OPEN_MAX')
except ValueError:
MAXFD = 256
def _close_fds():
for i in xrange(3, MAXFD):
try:
os.close(i)
# pylint: disable=W0704
except Exception:
pass
def create_activity_id():
"""Generate a new, unique ID for this activity"""
return util.unique_id(uuid.getnode())
def get_environment(activity):
environ = os.environ.copy()
bin_path = os.path.join(activity.get_path(), 'bin')
activity_root = env.get_profile_path(activity.get_bundle_id())
if not os.path.exists(activity_root):
os.mkdir(activity_root)
data_dir = os.path.join(activity_root, 'instance')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
data_dir = os.path.join(activity_root, 'data')
if not os.path.exists(data_dir):
os.mkdir(data_dir)
tmp_dir = os.path.join(activity_root, 'tmp')
if not os.path.exists(tmp_dir):
os.mkdir(tmp_dir)
environ['SUGAR_BUNDLE_PATH'] = activity.get_path()
environ['SUGAR_BUNDLE_ID'] = activity.get_bundle_id()
environ['SUGAR_ACTIVITY_ROOT'] = activity_root
environ['PATH'] = bin_path + ':' + environ['PATH']
if activity.get_path().startswith(env.get_user_activities_path()):
environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(),
'locale')
return environ
def get_command(activity, activity_id=None, object_id=None, uri=None,
activity_invite=False):
if not activity_id:
activity_id = create_activity_id()
command = activity.get_command().split(' ')
command.extend(['-b', activity.get_bundle_id()])
command.extend(['-a', activity_id])
if object_id is not None:
command.extend(['-o', object_id])
if uri is not None:
command.extend(['-u', uri])
if activity_invite:
command.append('-i')
# if the command is in $BUNDLE_ROOT/bin, execute the absolute path so there
# is no need to mangle with the shell's PATH
if '/' not in command[0]:
bin_path = os.path.join(activity.get_path(), 'bin')
absolute_path = os.path.join(bin_path, command[0])
if os.path.exists(absolute_path):
command[0] = absolute_path
logging.debug('launching: %r', command)
return command
def open_log_file(activity):
i = 1
while True:
path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i))
try:
fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0644)
f = os.fdopen(fd, 'w', 0)
return (path, f)
except OSError, e:
if e.errno == EEXIST:
i += 1
elif e.errno == ENOSPC:
# not the end of the world; let's try to keep going.
return ('/dev/null', open('/dev/null', 'w'))
else:
raise e
class ActivityCreationHandler(gobject.GObject):
"""Sugar-side activity creation interface
This object uses a dbus method on the ActivityFactory
service to create the new activity. It generates
GObject events in response to the success/failure of
activity startup using callbacks to the service's
create call.
"""
def __init__(self, bundle, handle):
"""Initialise the handler
bundle -- the ActivityBundle to launch
activity_handle -- stores the values which are to
be passed to the service to uniquely identify
the activity to be created and the sharing
service that may or may not be connected with it
sugar.activity.activityhandle.ActivityHandle instance
calls the "create" method on the service for this
particular activity type and registers the
_reply_handler and _error_handler methods on that
call's results.
The specific service which creates new instances of this
particular type of activity is created during the activity
registration process in shell bundle registry which creates
service definition files for each registered bundle type.
If the file '/etc/olpc-security' exists, then activity launching
will be delegated to the prototype 'Rainbow' security service.
"""
gobject.GObject.__init__(self)
self._bundle = bundle
self._service_name = bundle.get_bundle_id()
self._handle = handle
bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
self._shell = dbus.Interface(bus_object, _SHELL_IFACE)
if handle.activity_id is not None and handle.object_id is None:
datastore.find({'activity_id': self._handle.activity_id},
reply_handler=self._find_object_reply_handler,
error_handler=self._find_object_error_handler)
else:
self._launch_activity()
def _launch_activity(self):
if self._handle.activity_id != None:
self._shell.ActivateActivity(self._handle.activity_id,
reply_handler=self._activate_reply_handler,
error_handler=self._activate_error_handler)
else:
self._create_activity()
def _create_activity(self):
if self._handle.activity_id is None:
self._handle.activity_id = create_activity_id()
self._shell.NotifyLaunch(
self._service_name, self._handle.activity_id,
reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_error_handler)
environ = get_environment(self._bundle)
(log_path, log_file) = open_log_file(self._bundle)
command = get_command(self._bundle, self._handle.activity_id,
self._handle.object_id, self._handle.uri,
self._handle.invited)
dev_null = file('/dev/null', 'w')
environment_dir = None
rainbow_found = subprocess.call(['which', 'rainbow-run'],
stdout=dev_null, stderr=dev_null) == 0
use_rainbow = rainbow_found and os.path.exists('/etc/olpc-security')
if use_rainbow:
environment_dir = tempfile.mkdtemp()
command = ['sudo', '-E', '--',
'rainbow-run',
'-v', '-v',
'-a', 'rainbow-sugarize',
'-s', '/var/spool/rainbow/2',
'-f', '1',
'-f', '2',
'-c', self._bundle.get_path(),
'-u', pwd.getpwuid(os.getuid()).pw_name,
'-i', environ['SUGAR_BUNDLE_ID'],
'-e', environment_dir,
'--',
] + command
for key, value in environ.items():
file_path = os.path.join(environment_dir, str(key))
open(file_path, 'w').write(str(value))
log_file.write(' '.join(command) + '\n\n')
dev_null = file('/dev/null', 'r')
child = subprocess.Popen([str(s) for s in command],
env=environ,
cwd=str(self._bundle.get_path()),
close_fds=True,
stdin=dev_null.fileno(),
stdout=log_file.fileno(),
stderr=log_file.fileno())
gobject.child_watch_add(child.pid,
_child_watch_cb,
(environment_dir, log_file,
self._handle.activity_id))
def _no_reply_handler(self, *args):
pass
def _notify_launch_failure_error_handler(self, err):
logging.error('Notify launch failure failed %s', err)
def _notify_launch_error_handler(self, err):
logging.debug('Notify launch failed %s', err)
def _activate_reply_handler(self, activated):
if not activated:
self._create_activity()
def _activate_error_handler(self, err):
logging.error('Activity activation request failed %s', err)
def _create_reply_handler(self):
logging.debug('Activity created %s (%s).',
self._handle.activity_id, self._service_name)
def _create_error_handler(self, err):
logging.error("Couldn't create activity %s (%s): %s",
self._handle.activity_id, self._service_name, err)
self._shell.NotifyLaunchFailure(
self._handle.activity_id, reply_handler=self._no_reply_handler,
error_handler=self._notify_launch_failure_error_handler)
def _find_object_reply_handler(self, jobjects, count):
if count > 0:
if count > 1:
logging.debug('Multiple objects has the same activity_id.')
self._handle.object_id = jobjects[0]['uid']
self._launch_activity()
def _find_object_error_handler(self, err):
logging.error('Datastore find failed %s', err)
self._launch_activity()
def create(bundle, activity_handle=None):
"""Create a new activity from its name."""
if not activity_handle:
activity_handle = ActivityHandle()
return ActivityCreationHandler(bundle, activity_handle)
def create_with_uri(bundle, uri):
"""Create a new activity and pass the uri as handle."""
activity_handle = ActivityHandle(uri=uri)
return ActivityCreationHandler(bundle, activity_handle)
def create_with_object_id(bundle, object_id):
"""Create a new activity and pass the object id as handle."""
activity_handle = ActivityHandle(object_id=object_id)
return ActivityCreationHandler(bundle, activity_handle)
def _child_watch_cb(pid, condition, user_data):
# FIXME we use standalone method here instead of ActivityCreationHandler's
# member to have workaround code, see #1123
environment_dir, log_file, activity_id = user_data
if environment_dir is not None:
subprocess.call(['/bin/rm', '-rf', environment_dir])
if os.WIFEXITED(condition):
status = os.WEXITSTATUS(condition)
signum = None
message = 'Exited with status %s' % status
elif os.WIFSIGNALED(condition):
status = None
signum = os.WTERMSIG(condition)
message = 'Terminated by signal %s' % signum
else:
status = None
signum = os.WTERMSIG(condition)
message = 'Undefined status with signal %s' % signum
try:
log_file.write('%s, pid %s data %s\n' % (message, pid, user_data))
finally:
log_file.close()
# try to reap zombies in case SIGCHLD has not been set to SIG_IGN
try:
os.waitpid(pid, 0)
except OSError:
# SIGCHLD = SIG_IGN, no zombies
pass
if status or signum:
# XXX have to recreate dbus object since we can't reuse
# ActivityCreationHandler's one, see
# https://bugs.freedesktop.org/show_bug.cgi?id=23507
bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
shell = dbus.Interface(bus_object, _SHELL_IFACE)
def reply_handler_cb(*args):
pass
def error_handler_cb(error):
logging.error('Cannot send NotifyLaunchFailure to the shell')
# TODO send launching failure but activity could already show
# main window, see http://bugs.sugarlabs.org/ticket/1447#comment:19
shell.NotifyLaunchFailure(activity_id,
reply_handler=reply_handler_cb,
error_handler=error_handler_cb)
+75
View File
@@ -0,0 +1,75 @@
# Copyright (C) 2006-2007 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
STABLE.
"""
class ActivityHandle(object):
"""Data structure storing simple activity metadata"""
def __init__(self, activity_id=None, object_id=None, uri=None,
invited=False):
"""Initialise the handle from activity_id
activity_id -- unique id for the activity to be
created
object_id -- identity of the journal object
associated with the activity. It was used by
the journal prototype implementation, might
change when we do the real one.
When you resume an activity from the journal
the object_id will be passed in. It's optional
since new activities does not have an
associated object (yet).
XXX Not clear how this relates to the activity
id yet, i.e. not sure we really need both. TBF
uri -- URI associated with the activity. Used when
opening an external file or resource in the
activity, rather than a journal object
(downloads stored on the file system for
example or web pages)
invited -- the activity is being launched for handling an invite
from the network
"""
self.activity_id = activity_id
self.object_id = object_id
self.uri = uri
self.invited = invited
def get_dict(self):
"""Retrieve our settings as a dictionary"""
result = {'activity_id': self.activity_id,
'invited': self.invited}
if self.object_id:
result['object_id'] = self.object_id
if self.uri:
result['uri'] = self.uri
return result
def create_from_dict(handle_dict):
"""Create a handle from a dictionary of parameters"""
result = ActivityHandle(handle_dict['activity_id'],
object_id=handle_dict.get('object_id'),
uri=handle_dict.get('uri'),
invited=handle_dict.get('invited'))
return result
+83
View File
@@ -0,0 +1,83 @@
# Copyright (C) 2006-2007 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
UNSTABLE. It should really be internal to the Activity class.
"""
import logging
import dbus
import dbus.service
_ACTIVITY_SERVICE_NAME = 'org.laptop.Activity'
_ACTIVITY_SERVICE_PATH = '/org/laptop/Activity'
_ACTIVITY_INTERFACE = 'org.laptop.Activity'
class ActivityService(dbus.service.Object):
"""Base dbus service object that each Activity uses to export dbus methods.
The dbus service is separate from the actual Activity object so that we can
tightly control what stuff passes through the dbus python bindings."""
def __init__(self, activity):
"""Initialise the service for the given activity
activity -- sugar.activity.activity.Activity instance
Creates dbus services that use the instance's activity_id
as discriminants among all active services
of this type. That is, the services are all available
as names/paths derived from the instance's activity_id.
The various methods exposed on dbus are just forwarded
to the client Activity object's equally-named methods.
"""
activity.realize()
activity_id = activity.get_id()
service_name = _ACTIVITY_SERVICE_NAME + activity_id
object_path = _ACTIVITY_SERVICE_PATH + '/' + activity_id
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(service_name, bus=bus)
dbus.service.Object.__init__(self, bus_name, object_path)
self._activity = activity
@dbus.service.method(_ACTIVITY_INTERFACE)
def SetActive(self, active):
logging.debug('ActivityService.set_active: %s.', active)
self._activity.props.active = active
@dbus.service.method(_ACTIVITY_INTERFACE)
def InviteContact(self, account_path, contact_id):
self._activity.invite(account_path, contact_id)
@dbus.service.method(_ACTIVITY_INTERFACE)
def HandleViewSource(self):
self._activity.handle_view_source()
@dbus.service.method(_ACTIVITY_INTERFACE,
async_callbacks=('async_cb', 'async_err_cb'))
def GetDocumentPath(self, async_cb, async_err_cb):
try:
self._activity.get_document_path(async_cb, async_err_cb)
except Exception, e:
async_err_cb(e)
+399
View File
@@ -0,0 +1,399 @@
# 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
STABLE.
"""
import operator
import os
import sys
import zipfile
import tarfile
import shutil
import subprocess
import re
import gettext
from optparse import OptionParser
import logging
from fnmatch import fnmatch
from sugar import env
from sugar.bundle.activitybundle import ActivityBundle
IGNORE_DIRS = ['dist', '.git']
IGNORE_FILES = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak', 'pseudo.po']
def list_files(base_dir, ignore_dirs=None, ignore_files=None):
result = []
base_dir = os.path.abspath(base_dir)
for root, dirs, files in os.walk(base_dir):
if ignore_files:
for pattern in ignore_files:
files = [f for f in files if not fnmatch(f, pattern)]
rel_path = root[len(base_dir) + 1:]
for f in files:
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, source_dir=None, dist_dir=None, dist_name=None):
self.source_dir = source_dir or os.getcwd()
self.dist_dir = dist_dir or os.path.join(self.source_dir, 'dist')
self.dist_name = dist_name
self.bundle = None
self.version = None
self.activity_name = None
self.bundle_id = None
self.bundle_name = None
self.bundle_root_dir = None
self.tar_root_dir = None
self.xo_name = None
self.tar_name = None
self.update()
def update(self):
self.bundle = bundle = ActivityBundle(self.source_dir)
self.version = bundle.get_activity_version()
self.activity_name = bundle.get_bundle_name()
self.bundle_id = bundle.get_bundle_id()
self.bundle_name = reduce(operator.add, self.activity_name.split())
self.bundle_root_dir = self.bundle_name + '.activity'
self.tar_root_dir = '%s-%s' % (self.bundle_name, self.version)
if self.dist_name:
self.xo_name = self.tar_name = self.dist_name
else:
self.xo_name = '%s-%s.xo' % (self.bundle_name, self.version)
self.tar_name = '%s-%s.tar.bz2' % (self.bundle_name, self.version)
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')
if not self.config.bundle.is_dir(po_dir):
logging.warn('Missing po/ dir, cannot build_locale')
return
locale_dir = os.path.join(self.config.source_dir, 'locale')
if os.path.exists(locale_dir):
shutil.rmtree(locale_dir)
for f in os.listdir(po_dir):
if not f.endswith('.po') or f == 'pseudo.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()
def get_files(self):
allfiles = list_files(self.config.source_dir,
IGNORE_DIRS, IGNORE_FILES)
return allfiles
class Packager(object):
def __init__(self, config):
self.config = config
self.package_path = None
if not os.path.exists(self.config.dist_dir):
os.mkdir(self.config.dist_dir)
def get_files_in_git(self):
git_ls = subprocess.Popen(['git', 'ls-files'], stdout=subprocess.PIPE,
cwd=self.config.source_dir)
stdout, _ = git_ls.communicate()
if git_ls.returncode:
# Fall back to filtered list
return list_files(self.config.source_dir,
IGNORE_DIRS, IGNORE_FILES)
# pylint: disable=E1103
return [path.strip() for path in stdout.strip('\n').split('\n')]
class XOPackager(Packager):
def __init__(self, builder):
Packager.__init__(self, builder.config)
self.builder = builder
self.builder.build_locale()
self.package_path = os.path.join(self.config.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_in_git():
bundle_zip.write(os.path.join(self.config.source_dir, f),
os.path.join(self.config.bundle_root_dir, f))
locale_dir = os.path.join(self.config.source_dir, 'locale')
locale_files = list_files(locale_dir, IGNORE_DIRS, IGNORE_FILES)
for f in locale_files:
bundle_zip.write(os.path.join(locale_dir, f),
os.path.join(self.config.bundle_root_dir,
'locale', f))
bundle_zip.close()
class SourcePackager(Packager):
def __init__(self, config):
Packager.__init__(self, config)
self.package_path = os.path.join(self.config.dist_dir,
self.config.tar_name)
def package(self):
tar = tarfile.open(self.package_path, 'w:bz2')
for f in self.get_files_in_git():
tar.add(os.path.join(self.config.source_dir, f),
os.path.join(self.config.tar_root_dir, f))
tar.close()
class Installer(object):
IGNORES = ['po/*', 'MANIFEST', 'AUTHORS']
def __init__(self, builder):
self.config = builder.config
self.builder = builder
def should_ignore(self, f):
for pattern in self.IGNORES:
if fnmatch(f, pattern):
return True
return False
def install(self, prefix):
self.builder.build()
activity_path = os.path.join(prefix, 'share', 'sugar', 'activities',
self.config.bundle_root_dir)
source_to_dest = {}
for f in self.builder.get_files():
if self.should_ignore(f):
pass
elif f.startswith('locale/') and f.endswith('.mo'):
source_to_dest[f] = os.path.join(prefix, 'share', f)
else:
source_to_dest[f] = os.path.join(activity_path, f)
for source, dest in source_to_dest.items():
print 'Install %s to %s.' % (source, dest)
path = os.path.dirname(dest)
if not os.path.exists(path):
os.makedirs(path)
shutil.copy(source, dest)
self.config.bundle.install_mime_type(self.config.source_dir)
def cmd_dev(config, args):
"""Setup for development"""
if args:
print 'Usage: %prog dev'
return
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, config.bundle_root_dir)
try:
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 cmd_dist_xo(config, args):
"""Create a xo bundle package"""
if args:
print 'Usage: %prog dist_xo'
return
packager = XOPackager(Builder(config))
packager.package()
def cmd_fix_manifest(config, args):
'''Add missing files to the manifest (OBSOLETE)'''
print 'WARNING: The fix_manifest command is obsolete.'
print ' The MANIFEST file is no longer used in bundles,'
print ' please remove it.'
def cmd_dist_source(config, args):
"""Create a tar source package"""
if args:
print 'Usage: %prog dist_source'
return
packager = SourcePackager(config)
packager.package()
def cmd_install(config, args):
"""Install the activity in the system"""
parser = OptionParser(usage='usage: %prog install [options]')
parser.add_option('--prefix', dest='prefix', default=sys.prefix,
help='Prefix to install files to')
(suboptions, subargs) = parser.parse_args(args)
if subargs:
parser.print_help()
return
installer = Installer(Builder(config))
installer.install(suboptions.prefix)
def cmd_genpot(config, args):
"""Generate the gettext pot file"""
if args:
print 'Usage: %prog genpot'
return
po_path = os.path.join(config.source_dir, 'po')
if not os.path.isdir(po_path):
os.mkdir(po_path)
python_files = []
for root, dirs_dummy, files in os.walk(config.source_dir):
for file_name in files:
if file_name.endswith('.py'):
file_path = os.path.relpath(os.path.join(root, file_name),
config.source_dir)
python_files.append(file_path)
# 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' % 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)
f.write('msgstr ""\n')
f.close()
args = ['xgettext', '--join-existing', '--language=Python',
'--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file]
args += python_files
retcode = subprocess.call(args)
if retcode:
print 'ERROR - xgettext failed with return code %i.' % retcode
def cmd_build(config, args):
"""Build generated files"""
if args:
print 'Usage: %prog build'
return
builder = Builder(config)
builder.build()
def print_commands():
print 'Available commands:\n'
for name, func in globals().items():
if name.startswith('cmd_'):
print '%-20s %s' % (name.replace('cmd_', ''), func.__doc__)
print '\n(Type "./setup.py <command> --help" for help about a ' \
'particular command\'s options.'
def start(bundle_name=None):
if bundle_name:
logging.warn('bundle_name deprecated, now comes from activity.info')
parser = OptionParser(usage='[action] [options]')
parser.disable_interspersed_args()
(options_, args) = parser.parse_args()
config = Config()
try:
globals()['cmd_' + args[0]](config, args[1:])
except (KeyError, IndexError):
print_commands()
if __name__ == '__main__':
start()
+162
View File
@@ -0,0 +1,162 @@
# Copyright (C) 2010 One Laptop Per Child
#
# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gconf
from gettext import gettext
import locale
import os
import struct
import sys
import dateutil.parser
import time
_MO_BIG_ENDIAN = 0xde120495
_MO_LITTLE_ENDIAN = 0x950412de
def _read_bin(handle, format_string, byte_count):
read_bytes = handle.read(byte_count)
return_value = struct.unpack(format_string, read_bytes)
if len(return_value) == 1:
return return_value[0]
else:
return return_value
def _extract_header(file_path):
header = ''
handle = open(file_path, 'rb')
magic_number = _read_bin(handle, '<I', 4)
if magic_number == _MO_BIG_ENDIAN:
format_string = '>II'
elif magic_number == _MO_LITTLE_ENDIAN:
format_string = '<II'
else:
raise IOError('File does not seem to be a valid MO file')
version_, num_of_strings = _read_bin(handle, format_string, 8)
msgids_hash_offset, msgstrs_hash_offset = _read_bin(handle, \
format_string, 8)
handle.seek(msgids_hash_offset)
msgids_index = []
for i in range(num_of_strings):
msgids_index.append(_read_bin(handle, format_string, 8))
handle.seek(msgstrs_hash_offset)
msgstrs_index = []
for i in range(num_of_strings):
msgstrs_index.append(_read_bin(handle, format_string, 8))
for i in range(num_of_strings):
handle.seek(msgids_index[i][1])
msgid = handle.read(msgids_index[i][0])
if msgid == '':
handle.seek(msgstrs_index[i][1])
msgstr = handle.read(msgstrs_index[i][0])
header = msgstr
break
else:
continue
handle.close()
return header
def _extract_modification_time(file_path):
header = _extract_header(file_path)
items = header.split('\n')
for item in items:
if item.startswith('PO-Revision-Date:'):
time_str = item.split(': ')[1]
parsed_time = dateutil.parser.parse(time_str)
return time.mktime(parsed_time.timetuple())
raise ValueError('Could not find a revision date')
# We ship our own version of pgettext() because Python 2.x will never contain
# it: http://bugs.python.org/issue2504#msg122482
def pgettext(context, message):
"""
Return the localized translation of message, based on context and
the current global domain, language, and locale directory.
Similar to gettext(). Context is a string used to disambiguate
messages that are the same in the source language (usually english),
but might be different in one or more of the target languages.
"""
translation = gettext('\x04'.join([context, message]))
if '\x04' in translation:
return message
return translation
def get_locale_path(bundle_id):
""" Returns the locale path, which is the directory where the preferred
MO file is located.
The preferred MO file is the one with the latest translation.
@type bundle_id: string
@param bundle_id: The bundle id of the activity in question
@rtype: string
@return: the preferred locale path
"""
# Note: We pre-assign weights to the directories so that if no translations
# exist, the appropriate fallbacks (eg: bn for bn_BD) can be loaded
# The directory with the highest weight is returned, and if a MO file is
# found, the weight of the directory is set to the MO's modification time
# (as described in the MO header, and _not_ the filesystem mtime)
candidate_dirs = {}
if 'SUGAR_LOCALEDIR' in os.environ:
candidate_dirs[os.environ['SUGAR_LOCALEDIR']] = 2
gconf_client = gconf.client_get_default()
package_dir = gconf_client.get_string('/desktop/sugar/i18n/langpackdir')
if package_dir is not None and package_dir is not '':
candidate_dirs[package_dir] = 1
candidate_dirs[os.path.join(sys.prefix, 'share', 'locale')] = 0
for candidate_dir in candidate_dirs.keys():
if os.path.exists(candidate_dir):
full_path = os.path.join(candidate_dir, \
locale.getdefaultlocale()[0], 'LC_MESSAGES', \
bundle_id + '.mo')
if os.path.exists(full_path):
try:
candidate_dirs[candidate_dir] = \
_extract_modification_time(full_path)
except (IOError, ValueError):
# The mo file is damaged or has not been initialized
# Set lowest priority
candidate_dirs[candidate_dir] = -1
available_paths = sorted(candidate_dirs.iteritems(), key=lambda (k, v): \
(v, k), reverse=True)
preferred_path = available_paths[0][0]
return preferred_path
+160
View File
@@ -0,0 +1,160 @@
# 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
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import os
import sys
import gettext
from optparse import OptionParser
import gtk
import dbus
import dbus.service
import dbus.glib
import sugar
from sugar.activity import activityhandle
from sugar.activity import i18n
from sugar.bundle.activitybundle import ActivityBundle
from sugar.graphics import style
from sugar import logger
def create_activity_instance(constructor, handle):
activity = constructor(handle)
activity.show()
def get_single_process_name(bundle_id):
return bundle_id
def get_single_process_path(bundle_id):
return '/' + bundle_id.replace('.', '/')
class SingleProcess(dbus.service.Object):
def __init__(self, name_service, constructor):
self.constructor = constructor
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(name_service, bus=bus)
object_path = get_single_process_path(name_service)
dbus.service.Object.__init__(self, bus_name, object_path)
@dbus.service.method('org.laptop.SingleProcess', in_signature='a{sv}')
def create(self, handle_dict):
handle = activityhandle.create_from_dict(handle_dict)
create_activity_instance(self.constructor, handle)
def main():
parser = OptionParser()
parser.add_option('-b', '--bundle-id', dest='bundle_id',
help='identifier of the activity bundle')
parser.add_option('-a', '--activity-id', dest='activity_id',
help='identifier of the activity instance')
parser.add_option('-o', '--object-id', dest='object_id',
help='identifier of the associated datastore object')
parser.add_option('-u', '--uri', dest='uri',
help='URI to load')
parser.add_option('-s', '--single-process', dest='single_process',
action='store_true',
help='start all the instances in the same process')
parser.add_option('-i', '--invited', dest='invited',
action='store_true', default=False,
help='the activity is being launched for handling an '
'invite from the network')
(options, args) = parser.parse_args()
logger.start()
if 'SUGAR_BUNDLE_PATH' not in os.environ:
print 'SUGAR_BUNDLE_PATH is not defined in the environment.'
sys.exit(1)
if len(args) == 0:
print 'A python class must be specified as first argument.'
sys.exit(1)
bundle_path = os.environ['SUGAR_BUNDLE_PATH']
sys.path.append(bundle_path)
bundle = ActivityBundle(bundle_path)
os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id()
os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name()
os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version())
gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path())
# This code can be removed when we grow an xsettings daemon (the GTK+
# init routines will then automatically figure out the font settings)
settings = gtk.settings_get_default()
settings.set_property('gtk-font-name',
'%s %f' % (style.FONT_FACE, style.FONT_SIZE))
locale_path = i18n.get_locale_path(bundle.get_bundle_id())
gettext.bindtextdomain(bundle.get_bundle_id(), locale_path)
gettext.bindtextdomain('sugar-toolkit', sugar.locale_path)
gettext.textdomain(bundle.get_bundle_id())
splitted_module = args[0].rsplit('.', 1)
module_name = splitted_module[0]
class_name = splitted_module[1]
module = __import__(module_name)
for comp in module_name.split('.')[1:]:
module = getattr(module, comp)
activity_constructor = getattr(module, class_name)
activity_handle = activityhandle.ActivityHandle(
activity_id=options.activity_id,
object_id=options.object_id, uri=options.uri,
invited=options.invited)
if options.single_process is True:
sessionbus = dbus.SessionBus()
service_name = get_single_process_name(options.bundle_id)
service_path = get_single_process_path(options.bundle_id)
bus_object = sessionbus.get_object(
'org.freedesktop.DBus', '/org/freedesktop/DBus')
try:
name = bus_object.GetNameOwner(
service_name, dbus_interface='org.freedesktop.DBus')
except dbus.DBusException:
name = None
if not name:
SingleProcess(service_name, activity_constructor)
else:
single_process = sessionbus.get_object(service_name, service_path)
single_process.create(activity_handle.get_dict(),
dbus_interface='org.laptop.SingleProcess')
print 'Created %s in a single process.' % service_name
sys.exit(0)
if hasattr(module, 'start'):
module.start()
create_activity_instance(activity_constructor, activity_handle)
gtk.main()
+306
View File
@@ -0,0 +1,306 @@
# Copyright (C) 2009 One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gettext
import os
import gio
import gtk
import gobject
import gconf
from sugar.graphics import style
from sugar.graphics.icon import Icon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.icon import get_icon_file_name
from sugar.graphics.toolbutton import ToolButton
from sugar.bundle.activitybundle import ActivityBundle
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def _get_icon_name(metadata):
file_name = None
mime_type = metadata.get('mime_type', '')
if not file_name and mime_type:
icons = gio.content_type_get_icon(mime_type)
for icon_name in icons.props.names:
file_name = get_icon_file_name(icon_name)
if file_name is not None:
break
if file_name is None or not os.path.exists(file_name):
file_name = get_icon_file_name('application-octet-stream')
return file_name
class NamingToolbar(gtk.Toolbar):
""" Toolbar of the naming alert
"""
__gtype_name__ = 'SugarNamingToolbar'
__gsignals__ = {
'keep-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
}
def __init__(self):
gtk.Toolbar.__init__(self)
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
icon = Icon()
icon.set_from_icon_name('activity-journal',
gtk.ICON_SIZE_LARGE_TOOLBAR)
icon.props.xo_color = color
self._add_widget(icon)
self._add_separator()
self._title = gtk.Label(_('Name this entry'))
self._add_widget(self._title)
self._add_separator(True)
self._keep_button = ToolButton('dialog-ok', tooltip=_('Keep'))
self._keep_button.props.accelerator = 'Return'
self._keep_button.connect('clicked', self.__keep_button_clicked_cb)
self.insert(self._keep_button, -1)
self._keep_button.show()
def _add_separator(self, expand=False):
separator = gtk.SeparatorToolItem()
separator.props.draw = False
if expand:
separator.set_expand(True)
else:
separator.set_size_request(style.DEFAULT_SPACING, -1)
self.insert(separator, -1)
separator.show()
def _add_widget(self, widget, expand=False):
tool_item = gtk.ToolItem()
tool_item.set_expand(expand)
tool_item.add(widget)
widget.show()
self.insert(tool_item, -1)
tool_item.show()
def __keep_button_clicked_cb(self, widget, data=None):
self.emit('keep-clicked')
class FavoriteIcon(gtk.ToggleButton):
def __init__(self):
gtk.ToggleButton.__init__(self)
self.set_relief(gtk.RELIEF_NONE)
self.set_focus_on_click(False)
self._icon = Icon(icon_name='emblem-favorite',
pixel_size=style.SMALL_ICON_SIZE)
self.set_image(self._icon)
self.connect('toggled', self.__toggled_cb)
self.connect('leave-notify-event', self.__leave_notify_event_cb)
self.connect('enter-notify-event', self.__enter_notify_event_cb)
def __toggled_cb(self, widget):
if self.get_active():
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
self._icon.props.xo_color = color
else:
self._icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self._icon.props.fill_color = style.COLOR_WHITE.get_svg()
def __enter_notify_event_cb(self, icon, event):
if not self.get_active():
self._icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
def __leave_notify_event_cb(self, icon, event):
if not self.get_active():
self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
class NamingAlert(gtk.Window):
__gtype_name__ = 'SugarNamingAlert'
def __init__(self, activity, bundle_path):
gtk.Window.__init__(self)
self._bundle_path = bundle_path
self._favorite_icon = None
self._title = None
self._description = None
self._tags = None
accel_group = gtk.AccelGroup()
self.set_data('sugar-accel-group', accel_group)
self.add_accel_group(accel_group)
self.set_border_width(style.LINE_WIDTH)
offset = style.GRID_CELL_SIZE
width = gtk.gdk.screen_width() - offset * 2
height = gtk.gdk.screen_height() - offset * 2
self.set_size_request(width, height)
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
self.set_decorated(False)
self.set_resizable(False)
self.set_modal(True)
self.connect('realize', self.__realize_cb)
self._activity = activity
vbox = gtk.VBox()
self.add(vbox)
vbox.show()
toolbar = NamingToolbar()
toolbar.connect('keep-clicked', self.__keep_cb)
vbox.pack_start(toolbar, False)
toolbar.show()
body = self._create_body()
vbox.pack_start(body, expand=True, fill=True)
body.show()
self._title.grab_focus()
def _create_body(self):
body = gtk.VBox(spacing=style.DEFAULT_SPACING)
body.set_border_width(style.DEFAULT_SPACING * 3)
header = self._create_header()
body.pack_start(header, expand=False, padding=style.DEFAULT_PADDING)
body.pack_start(self._create_separator(style.DEFAULT_SPACING),
expand=False)
body.pack_start(self._create_label(_('Description:')), expand=False)
description = self._activity.metadata.get('description', '')
description_box, self._description = self._create_text_view(description)
body.pack_start(description_box, expand=True, fill=True)
body.pack_start(self._create_separator(style.DEFAULT_PADDING),
expand=False)
body.pack_start(self._create_label(_('Tags:')), expand=False)
tags = self._activity.metadata.get('tags', '')
tags_box, self._tags = self._create_text_view(tags)
body.pack_start(tags_box, expand=True, fill=True)
body.show_all()
return body
def _create_label(self, text):
text = gtk.Label(text)
text.set_alignment(0, 0.5)
text.modify_fg(gtk.STATE_NORMAL,
style.COLOR_BUTTON_GREY.get_gdk_color())
return text
def _create_separator(self, height):
separator = gtk.HSeparator()
separator.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
separator.set_size_request(-1, height)
return separator
def _create_header(self):
header = gtk.HBox(spacing=style.DEFAULT_SPACING)
self._favorite_icon = FavoriteIcon()
header.pack_start(self._favorite_icon, expand=False)
entry_icon = self._create_entry_icon()
header.pack_start(entry_icon, expand=False)
self._title = self._create_title()
header.pack_start(self._title, expand=True)
return header
def _create_entry_icon(self):
bundle_id = self._activity.metadata.get('activity', '')
if not bundle_id:
bundle_id = self._activity.metadata.get('bundle_id', '')
if bundle_id == '':
file_name = _get_icon_name(self._activity.metadata)
else:
activity_bundle = ActivityBundle(self._bundle_path)
file_name = activity_bundle.get_icon()
entry_icon = Icon(file=file_name, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
if self._activity.metadata.get('icon-color'):
entry_icon.props.xo_color = XoColor( \
self._activity.metadata['icon-color'])
return entry_icon
def _create_title(self):
title = gtk.Entry()
title.set_text(self._activity.metadata.get('title', _('Untitled')))
return title
def _create_text_view(self, text):
scrolled_window = gtk.ScrolledWindow()
scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scrolled_window.set_border_width(style.LINE_WIDTH)
scrolled_window.set_shadow_type(gtk.SHADOW_IN)
text_view = gtk.TextView()
text_view.set_left_margin(style.DEFAULT_PADDING)
text_view.set_wrap_mode(gtk.WRAP_WORD_CHAR)
text_view.set_accepts_tab(False)
text_view.get_buffer().set_text(text)
scrolled_window.add(text_view)
return scrolled_window, text_view
def __realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self.window.set_accept_focus(True)
def __keep_cb(self, widget):
if self._favorite_icon.get_active():
self._activity.metadata['keep'] = 1
else:
self._activity.metadata['keep'] = 0
self._activity.metadata['title'] = self._title.get_text()
text_buffer = self._tags.get_buffer()
start, end = text_buffer.get_bounds()
new_tags = text_buffer.get_text(start, end)
self._activity.metadata['tags'] = new_tags
text_buffer = self._description.get_buffer()
start, end = text_buffer.get_bounds()
new_description = text_buffer.get_text(start, end)
self._activity.metadata['description'] = new_description
self._activity.metadata['title_set_by_user'] = '1'
self._activity.close()
self.destroy()
+362
View File
@@ -0,0 +1,362 @@
# Copyright (C) 2009, Aleksey Lim, Simon Schampijer
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
import gtk
import gettext
import gconf
import logging
from sugar.graphics.toolbutton import ToolButton
from sugar.graphics.toolbarbox import ToolbarButton
from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton
from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.toolbox import Toolbox
from sugar.graphics.xocolor import XoColor
from sugar.graphics.icon import Icon
from sugar.bundle.activitybundle import ActivityBundle
_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
def _create_activity_icon(metadata):
if metadata is not None and metadata.get('icon-color'):
color = XoColor(metadata['icon-color'])
else:
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
from sugar.activity.activity import get_bundle_path
bundle = ActivityBundle(get_bundle_path())
icon = Icon(file=bundle.get_icon(), xo_color=color)
return icon
class ActivityButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, **kwargs)
icon = _create_activity_icon(activity.metadata)
self.set_icon_widget(icon)
icon.show()
self.props.tooltip = activity.metadata['title']
activity.metadata.connect('updated', self.__jobject_updated_cb)
def __jobject_updated_cb(self, jobject):
self.props.tooltip = jobject['title']
class ActivityToolbarButton(ToolbarButton):
def __init__(self, activity, **kwargs):
toolbar = ActivityToolbar(activity, orientation_left=True)
toolbar.stop.hide()
ToolbarButton.__init__(self, page=toolbar, **kwargs)
icon = _create_activity_icon(activity.metadata)
self.set_icon_widget(icon)
icon.show()
class StopButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, 'activity-stop', **kwargs)
self.props.tooltip = _('Stop')
self.props.accelerator = '<Ctrl>Q'
self.connect('clicked', self.__stop_button_clicked_cb, activity)
def __stop_button_clicked_cb(self, button, activity):
activity.close()
class UndoButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-undo', **kwargs)
self.props.tooltip = _('Undo')
self.props.accelerator = '<Ctrl>Z'
class RedoButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-redo', **kwargs)
self.props.tooltip = _('Redo')
class CopyButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-copy', **kwargs)
self.props.tooltip = _('Copy')
self.props.accelerator = '<Ctrl>C'
class PasteButton(ToolButton):
def __init__(self, **kwargs):
ToolButton.__init__(self, 'edit-paste', **kwargs)
self.props.tooltip = _('Paste')
self.props.accelerator = '<Ctrl>V'
class ShareButton(RadioMenuButton):
def __init__(self, activity, **kwargs):
palette = RadioPalette()
self.private = RadioToolButton(
icon_name='zoom-home')
palette.append(self.private, _('Private'))
self.neighborhood = RadioToolButton(
icon_name='zoom-neighborhood',
group=self.private)
self._neighborhood_handle = self.neighborhood.connect(
'clicked', self.__neighborhood_clicked_cb, activity)
palette.append(self.neighborhood, _('My Neighborhood'))
activity.connect('shared', self.__update_share_cb)
activity.connect('joined', self.__update_share_cb)
RadioMenuButton.__init__(self, **kwargs)
self.props.palette = palette
if activity.max_participants == 1:
self.props.sensitive = False
def __neighborhood_clicked_cb(self, button, activity):
activity.share()
def __update_share_cb(self, activity):
self.neighborhood.handler_block(self._neighborhood_handle)
try:
if activity.shared_activity is not None and \
not activity.shared_activity.props.private:
self.private.props.sensitive = False
self.neighborhood.props.sensitive = False
self.neighborhood.props.active = True
else:
self.private.props.sensitive = True
self.neighborhood.props.sensitive = True
self.private.props.active = True
finally:
self.neighborhood.handler_unblock(self._neighborhood_handle)
# DEPRECATED
class KeepButton(ToolButton):
def __init__(self, activity, **kwargs):
ToolButton.__init__(self, **kwargs)
logging.warning('KeepButton has been deprecated since Sugar 0.94'
' and should not be used in newly written code.')
self.props.tooltip = _('Keep')
self.props.accelerator = '<Ctrl>S'
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
keep_icon = Icon(icon_name='document-save', xo_color=color)
keep_icon.show()
self.set_icon_widget(keep_icon)
self.connect('clicked', self.__keep_button_clicked_cb, activity)
def __keep_button_clicked_cb(self, button, activity):
activity.copy()
class TitleEntry(gtk.ToolItem):
def __init__(self, activity, **kwargs):
gtk.ToolItem.__init__(self)
self.set_expand(False)
self.entry = gtk.Entry(**kwargs)
self.entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
self.entry.set_text(activity.metadata['title'])
self.entry.connect('focus-out-event', self.__title_changed_cb, activity)
self.entry.show()
self.add(self.entry)
activity.metadata.connect('updated', self.__jobject_updated_cb)
activity.connect('_closing', self.__closing_cb)
def modify_bg(self, state, color):
gtk.ToolItem.modify_bg(self, state, color)
self.entry.modify_bg(state, color)
def __jobject_updated_cb(self, jobject):
if self.entry.flags() & gtk.HAS_FOCUS:
return
if self.entry.get_text() == jobject['title']:
return
self.entry.set_text(jobject['title'])
def __closing_cb(self, activity):
self.save_title(activity)
return False
def __title_changed_cb(self, editable, event, activity):
self.save_title(activity)
return False
def save_title(self, activity):
title = self.entry.get_text()
if title == activity.metadata['title']:
return
activity.metadata['title'] = title
activity.metadata['title_set_by_user'] = '1'
activity.save()
activity.set_title(title)
shared_activity = activity.get_shared_activity()
if shared_activity is not None:
shared_activity.props.name = title
class ActivityToolbar(gtk.Toolbar):
"""The Activity toolbar with the Journal entry title, sharing
and Stop buttons
All activities should have this toolbar. It is easiest to add it to your
Activity by using the ActivityToolbox.
"""
def __init__(self, activity, orientation_left=False):
gtk.Toolbar.__init__(self)
self._activity = activity
if activity.metadata:
title_button = TitleEntry(activity)
title_button.show()
self.insert(title_button, -1)
self.title = title_button.entry
if orientation_left == False:
separator = gtk.SeparatorToolItem()
separator.props.draw = False
separator.set_expand(True)
self.insert(separator, -1)
separator.show()
self.share = ShareButton(activity)
self.share.show()
self.insert(self.share, -1)
# DEPRECATED
self.keep = KeepButton(activity)
self.stop = StopButton(activity)
self.insert(self.stop, -1)
self.stop.show()
class EditToolbar(gtk.Toolbar):
"""Provides the standard edit toolbar for Activities.
Members:
undo -- the undo button
redo -- the redo button
copy -- the copy button
paste -- the paste button
separator -- A separator between undo/redo and copy/paste
This class only provides the 'edit' buttons in a standard layout,
your activity will need to either hide buttons which make no sense for your
Activity, or you need to connect the button events to your own callbacks:
## Example from Read.activity:
# Create the edit toolbar:
self._edit_toolbar = EditToolbar(self._view)
# Hide undo and redo, they're not needed
self._edit_toolbar.undo.props.visible = False
self._edit_toolbar.redo.props.visible = False
# Hide the separator too:
self._edit_toolbar.separator.props.visible = False
# As long as nothing is selected, copy needs to be insensitive:
self._edit_toolbar.copy.set_sensitive(False)
# When the user clicks the button, call _edit_toolbar_copy_cb()
self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
# Add the edit toolbar:
toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
# And make it visible:
self._edit_toolbar.show()
"""
def __init__(self):
gtk.Toolbar.__init__(self)
self.undo = UndoButton()
self.insert(self.undo, -1)
self.undo.show()
self.redo = RedoButton()
self.insert(self.redo, -1)
self.redo.show()
self.separator = gtk.SeparatorToolItem()
self.separator.set_draw(True)
self.insert(self.separator, -1)
self.separator.show()
self.copy = CopyButton()
self.insert(self.copy, -1)
self.copy.show()
self.paste = PasteButton()
self.insert(self.paste, -1)
self.paste.show()
class ActivityToolbox(Toolbox):
"""Creates the Toolbox for the Activity
By default, the toolbox contains only the ActivityToolbar. After creating
the toolbox, you can add your activity specific toolbars, for example the
EditToolbar.
To add the ActivityToolbox to your Activity in MyActivity.__init__() do:
# Create the Toolbar with the ActivityToolbar:
toolbox = activity.ActivityToolbox(self)
... your code, inserting all other toolbars you need, like EditToolbar
# Add the toolbox to the activity frame:
self.set_toolbar_box(toolbox)
# And make it visible:
toolbox.show()
"""
def __init__(self, activity):
Toolbox.__init__(self)
self._activity_toolbar = ActivityToolbar(activity)
self.add_toolbar(_('Activity'), self._activity_toolbar)
self._activity_toolbar.show()
def get_activity_toolbar(self):
return self._activity_toolbar