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:
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user