Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
This commit is contained in:
@@ -1,211 +0,0 @@
|
||||
# Copyright (C) 2006, 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 logging
|
||||
|
||||
import dbus
|
||||
import dbus.service
|
||||
import gtk
|
||||
import gobject
|
||||
import datetime
|
||||
|
||||
from sugar.presence import PresenceService
|
||||
from sugar.datastore import datastore
|
||||
from sugar import activity
|
||||
from sugar import env
|
||||
import sugar.util
|
||||
|
||||
ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
|
||||
ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
|
||||
ACTIVITY_INTERFACE = "org.laptop.Activity"
|
||||
|
||||
def get_service_name(xid):
|
||||
return ACTIVITY_SERVICE_NAME + '%d' % xid
|
||||
|
||||
def get_object_path(xid):
|
||||
return ACTIVITY_SERVICE_PATH + "/%s" % xid
|
||||
|
||||
def get_service(xid):
|
||||
bus = dbus.SessionBus()
|
||||
proxy_obj = bus.get_object(get_service_name(xid), get_object_path(xid))
|
||||
return dbus.Interface(proxy_obj, ACTIVITY_INTERFACE)
|
||||
|
||||
|
||||
class ActivityDbusService(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):
|
||||
xid = activity.window.xid
|
||||
bus = dbus.SessionBus()
|
||||
bus_name = dbus.service.BusName(get_service_name(xid), bus=bus)
|
||||
dbus.service.Object.__init__(self, bus_name, get_object_path(xid))
|
||||
|
||||
self._activity = activity
|
||||
self._pservice = PresenceService.get_instance()
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def start(self, activity_id):
|
||||
"""Start the activity in unshared mode."""
|
||||
self._activity.start(activity_id)
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def join(self, activity_ps_path):
|
||||
"""Join the activity specified by its presence service path."""
|
||||
activity_ps = self._pservice.get(activity_ps_path)
|
||||
return self._activity.join(activity_ps)
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def share(self):
|
||||
"""Called by the shell to request the activity to share itself on the network."""
|
||||
self._activity.share()
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def get_id(self):
|
||||
"""Get the activity identifier"""
|
||||
return self._activity.get_id()
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def get_type(self):
|
||||
"""Get the activity type"""
|
||||
return self._activity.get_type()
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE)
|
||||
def get_shared(self):
|
||||
"""Returns True if the activity is shared on the mesh."""
|
||||
return self._activity.get_shared()
|
||||
|
||||
@dbus.service.method(ACTIVITY_INTERFACE,
|
||||
in_signature="sas", out_signature="b")
|
||||
def execute(self, command, args):
|
||||
return self._activity.execute(command, args)
|
||||
|
||||
class Activity(gtk.Window):
|
||||
"""Base Activity class that all other Activities derive from."""
|
||||
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
|
||||
self.connect('destroy', self._destroy_cb)
|
||||
#self.connect('notify::title', self._title_changed_cb)
|
||||
|
||||
self._shared = False
|
||||
self._activity_id = None
|
||||
self._service = None
|
||||
#self._journal_object = None
|
||||
self._pservice = PresenceService.get_instance()
|
||||
|
||||
self.realize()
|
||||
|
||||
group = gtk.Window()
|
||||
group.realize()
|
||||
self.window.set_group(group.window)
|
||||
|
||||
self._bus = ActivityDbusService(self)
|
||||
|
||||
def start(self, activity_id):
|
||||
"""Start the activity."""
|
||||
if self._activity_id != None:
|
||||
logging.warning('The activity has been already started.')
|
||||
return
|
||||
|
||||
self._activity_id = activity_id
|
||||
|
||||
#ds = datastore.get_instance()
|
||||
#self._journal_object = ds.create('', {}, self._activity_id)
|
||||
#
|
||||
#date = datetime.datetime.now()
|
||||
#self._journal_jobject.set_properties({'date' : date,
|
||||
# 'title' : self.get_title()})
|
||||
|
||||
self.present()
|
||||
|
||||
# def get_journal_object(self):
|
||||
# """Returns the journal object associated with the activity."""
|
||||
# return self._journal_object
|
||||
|
||||
def get_type(self):
|
||||
"""Gets the activity type."""
|
||||
return env.get_bundle_service_name()
|
||||
|
||||
def get_default_type(self):
|
||||
"""Gets the type of the default activity network service"""
|
||||
return env.get_bundle_default_type()
|
||||
|
||||
def get_shared(self):
|
||||
"""Returns TRUE if the activity is shared on the mesh."""
|
||||
return self._shared
|
||||
|
||||
def get_id(self):
|
||||
"""Get the unique activity identifier."""
|
||||
return self._activity_id
|
||||
|
||||
def join(self, activity_ps):
|
||||
"""Join an activity shared on the network."""
|
||||
if self._activity_id != None:
|
||||
logging.warning('The activity has been already started.')
|
||||
return
|
||||
self._activity_id = activity_ps.get_id()
|
||||
|
||||
self._shared = True
|
||||
|
||||
# Publish the default service, it's a copy of
|
||||
# one of those we found on the network.
|
||||
default_type = self.get_default_type()
|
||||
services = activity_ps.get_services_of_type(default_type)
|
||||
if len(services) > 0:
|
||||
service = services[0]
|
||||
addr = service.get_address()
|
||||
port = service.get_port()
|
||||
properties = service.get_published_values()
|
||||
self._service = self._pservice.share_activity(
|
||||
self, default_type, properties, addr, port)
|
||||
else:
|
||||
logging.error('Cannot join the activity')
|
||||
|
||||
#ds = datastore.get_instance()
|
||||
#self._journal_object = ds.get_activity_object(self._activity_id)
|
||||
|
||||
self.present()
|
||||
|
||||
def share(self):
|
||||
"""Share the activity on the network."""
|
||||
logging.debug('Share activity %s on the network.' % self.get_id())
|
||||
|
||||
default_type = self.get_default_type()
|
||||
self._service = self._pservice.share_activity(self, default_type)
|
||||
self._shared = True
|
||||
|
||||
def execute(self, command, args):
|
||||
"""Execute the given command with args"""
|
||||
return False
|
||||
|
||||
def _destroy_cb(self, window):
|
||||
if self._bus:
|
||||
del self._bus
|
||||
self._bus = None
|
||||
if self._service:
|
||||
self._pservice.unregister_service(self._service)
|
||||
|
||||
def _title_changed_cb(self, window, spec):
|
||||
pass
|
||||
# jobject = self.get_journal_object()
|
||||
# if jobject:
|
||||
# jobject.set_properties({'title' : self.props.title})
|
||||
@@ -1,127 +0,0 @@
|
||||
# Copyright (C) 2006, 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 logging
|
||||
|
||||
import dbus
|
||||
import dbus.service
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
from sugar.presence.PresenceService import PresenceService
|
||||
from sugar.activity import Activity
|
||||
from sugar.activity.bundle import Bundle
|
||||
from sugar import logger
|
||||
|
||||
def get_path(activity_name):
|
||||
"""Returns the activity path"""
|
||||
return '/' + activity_name.replace('.', '/')
|
||||
|
||||
class ActivityFactory(dbus.service.Object):
|
||||
"""Dbus service that takes care of creating new instances of an activity"""
|
||||
|
||||
def __init__(self, activity_type, activity_class):
|
||||
self._activity_type = activity_type
|
||||
self._activities = []
|
||||
|
||||
splitted_module = activity_class.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)
|
||||
if hasattr(module, 'start'):
|
||||
module.start()
|
||||
|
||||
self._module = module
|
||||
self._constructor = getattr(module, class_name)
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
factory = activity_type
|
||||
bus_name = dbus.service.BusName(factory, bus = bus)
|
||||
dbus.service.Object.__init__(self, bus_name, get_path(factory))
|
||||
|
||||
@dbus.service.method("com.redhat.Sugar.ActivityFactory")
|
||||
def create(self):
|
||||
activity = self._constructor()
|
||||
|
||||
self._activities.append(activity)
|
||||
activity.connect('destroy', self._activity_destroy_cb)
|
||||
|
||||
return activity.window.xid
|
||||
|
||||
def _activity_destroy_cb(self, activity):
|
||||
self._activities.remove(activity)
|
||||
|
||||
if hasattr(self._module, 'stop'):
|
||||
self._module.stop()
|
||||
|
||||
if len(self._activities) == 0:
|
||||
gtk.main_quit()
|
||||
|
||||
class ActivityCreationHandler(gobject.GObject):
|
||||
|
||||
__gsignals__ = {
|
||||
'error': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
'success': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT]))
|
||||
}
|
||||
|
||||
def __init__(self, activity_name):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
factory_name = activity_name
|
||||
factory_path = get_path(factory_name)
|
||||
|
||||
proxy_obj = bus.get_object(factory_name, factory_path)
|
||||
factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory")
|
||||
|
||||
factory.create(reply_handler=self._reply_handler, error_handler=self._error_handler)
|
||||
|
||||
def _reply_handler(self, xid):
|
||||
bus = dbus.SessionBus()
|
||||
proxy_obj = bus.get_object(Activity.get_service_name(xid),
|
||||
Activity.get_object_path(xid))
|
||||
activity = dbus.Interface(proxy_obj, Activity.ACTIVITY_INTERFACE)
|
||||
self.emit('success', activity)
|
||||
|
||||
def _error_handler(self, err):
|
||||
logging.debug("Couldn't create activity: %s" % err)
|
||||
self.emit('error', err)
|
||||
|
||||
def create(activity_name):
|
||||
"""Create a new activity from its name."""
|
||||
return ActivityCreationHandler(activity_name)
|
||||
|
||||
def start_factory(activity_class, bundle_path):
|
||||
"""Start the activity factory."""
|
||||
bundle = Bundle(bundle_path)
|
||||
|
||||
logger.start(bundle.get_name())
|
||||
|
||||
os.environ['SUGAR_BUNDLE_PATH'] = bundle_path
|
||||
os.environ['SUGAR_BUNDLE_SERVICE_NAME'] = bundle.get_service_name()
|
||||
os.environ['SUGAR_BUNDLE_DEFAULT_TYPE'] = bundle.get_default_type()
|
||||
|
||||
factory = ActivityFactory(bundle.get_service_name(), activity_class)
|
||||
@@ -1,8 +1,11 @@
|
||||
sugardir = $(pythondir)/sugar/activity
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
Activity.py \
|
||||
ActivityFactory.py \
|
||||
bundle.py \
|
||||
bundlebuilder.py \
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
activity.py \
|
||||
activityfactory.py \
|
||||
activityfactoryservice.py \
|
||||
activityhandle.py \
|
||||
activityservice.py \
|
||||
bundle.py \
|
||||
bundlebuilder.py \
|
||||
bundleregistry.py
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2006, 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 logging
|
||||
import os
|
||||
|
||||
import gtk
|
||||
|
||||
from sugar.presence import PresenceService
|
||||
from sugar.activity.activityservice import ActivityService
|
||||
|
||||
class Activity(gtk.Window):
|
||||
"""Base Activity class that all other Activities derive from."""
|
||||
|
||||
def __init__(self, handle):
|
||||
gtk.Window.__init__(self)
|
||||
|
||||
self.connect('destroy', self._destroy_cb)
|
||||
|
||||
self._shared = False
|
||||
self._activity_id = handle.activity_id
|
||||
self._pservice = PresenceService.get_instance()
|
||||
|
||||
service = handle.get_presence_service()
|
||||
if service:
|
||||
self._join(service)
|
||||
|
||||
self.realize()
|
||||
|
||||
group = gtk.Window()
|
||||
group.realize()
|
||||
self.window.set_group(group.window)
|
||||
|
||||
self._bus = ActivityService(self)
|
||||
|
||||
self.present()
|
||||
|
||||
def get_service_name(self):
|
||||
"""Gets the activity service name."""
|
||||
return os.environ['SUGAR_BUNDLE_SERVICE_NAME']
|
||||
|
||||
def get_default_type(self):
|
||||
"""Gets the type of the default activity network service"""
|
||||
return os.environ['SUGAR_BUNDLE_DEFAULT_TYPE']
|
||||
|
||||
def get_shared(self):
|
||||
"""Returns TRUE if the activity is shared on the mesh."""
|
||||
return self._shared
|
||||
|
||||
def get_id(self):
|
||||
"""Get the unique activity identifier."""
|
||||
return self._activity_id
|
||||
|
||||
def _join(self, service):
|
||||
self._service = service
|
||||
self._shared = True
|
||||
|
||||
# Publish the default service, it's a copy of
|
||||
# one of those we found on the network.
|
||||
default_type = self.get_default_type()
|
||||
services = activity_ps.get_services_of_type(default_type)
|
||||
if len(services) > 0:
|
||||
service = services[0]
|
||||
addr = service.get_address()
|
||||
port = service.get_port()
|
||||
properties = service.get_published_values()
|
||||
self._service = self._pservice.share_activity(
|
||||
self, default_type, properties, addr, port)
|
||||
else:
|
||||
logging.error('Cannot join the activity')
|
||||
|
||||
self.present()
|
||||
|
||||
def share(self):
|
||||
"""Share the activity on the network."""
|
||||
logging.debug('Share activity %s on the network.' % self.get_id())
|
||||
|
||||
default_type = self.get_default_type()
|
||||
self._service = self._pservice.share_activity(self, default_type)
|
||||
self._shared = True
|
||||
|
||||
def execute(self, command, args):
|
||||
"""Execute the given command with args"""
|
||||
return False
|
||||
|
||||
def _destroy_cb(self, window):
|
||||
if self._bus:
|
||||
del self._bus
|
||||
self._bus = None
|
||||
if self._service:
|
||||
self._pservice.unregister_service(self._service)
|
||||
|
||||
def get_bundle_path():
|
||||
return os.environ['SUGAR_BUNDLE_BUNDLE_PATH']
|
||||
@@ -0,0 +1,102 @@
|
||||
# Copyright (C) 2006, 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 logging
|
||||
|
||||
import dbus
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
from sugar.presence import PresenceService
|
||||
from sugar.activity import bundleregistry
|
||||
from sugar.activity.activityhandle import ActivityHandle
|
||||
from sugar import util
|
||||
|
||||
_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
|
||||
_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
|
||||
_ACTIVITY_INTERFACE = "org.laptop.Activity"
|
||||
|
||||
def _find_activity_id():
|
||||
pservice = PresenceService.get_instance()
|
||||
|
||||
# create a new unique activity ID
|
||||
i = 0
|
||||
act_id = None
|
||||
while i < 10:
|
||||
act_id = util.unique_id()
|
||||
i += 1
|
||||
|
||||
# check through network activities
|
||||
found = False
|
||||
activities = pservice.get_activities()
|
||||
for act in activities:
|
||||
if act_id == act.get_id():
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
raise RuntimeError("Cannot generate unique activity id.")
|
||||
|
||||
return act_id
|
||||
|
||||
class ActivityCreationHandler(gobject.GObject):
|
||||
|
||||
__gsignals__ = {
|
||||
'error': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE,
|
||||
([gobject.TYPE_PYOBJECT])),
|
||||
}
|
||||
|
||||
def __init__(self, service_name, activity_handle):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
self._service_name = service_name
|
||||
self._activity_handle = activity_handle
|
||||
|
||||
registry = bundleregistry.get_registry()
|
||||
bundle = registry.get_bundle(service_name)
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
proxy_obj = bus.get_object(service_name, bundle.get_object_path())
|
||||
factory = dbus.Interface(proxy_obj, "com.redhat.Sugar.ActivityFactory")
|
||||
|
||||
factory.create(self._activity_handle.get_dict(),
|
||||
reply_handler=self._reply_handler,
|
||||
error_handler=self._error_handler)
|
||||
|
||||
def get_activity_id(self):
|
||||
return self._activity_handle.activity_id
|
||||
|
||||
def _reply_handler(self, xid):
|
||||
logging.debug("Activity created %s (%s)." %
|
||||
(self._activity_handle.activity_id, self._service_name))
|
||||
|
||||
def _error_handler(self, err):
|
||||
logging.debug("Couldn't create activity %s (%s): %s" %
|
||||
(self._activity_handle.activity_id, self._service_name, err))
|
||||
self.emit('error', err)
|
||||
|
||||
def create(service_name, activity_handle=None):
|
||||
"""Create a new activity from its name."""
|
||||
if not activity_handle:
|
||||
activity_handle = ActivityHandle(_find_activity_id())
|
||||
return ActivityCreationHandler(service_name, activity_handle)
|
||||
|
||||
def create_with_uri(service_name, uri):
|
||||
"""Create a new activity and pass the uri as handle."""
|
||||
activity_handle = ActivityHandle(_find_activity_id())
|
||||
activity_handle.uri = uri
|
||||
return ActivityCreationHandler(service_name, handle)
|
||||
@@ -0,0 +1,97 @@
|
||||
# Copyright (C) 2006, 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
|
||||
from optparse import OptionParser
|
||||
|
||||
import gobject
|
||||
import gtk
|
||||
import dbus
|
||||
import dbus.service
|
||||
import dbus.glib
|
||||
|
||||
from sugar.activity.bundle import Bundle
|
||||
from sugar.activity import activityhandle
|
||||
from sugar import logger
|
||||
|
||||
# Work around for dbus mutex locking issue
|
||||
gobject.threads_init()
|
||||
dbus.glib.threads_init()
|
||||
|
||||
class ActivityFactoryService(dbus.service.Object):
|
||||
"""D-Bus service that creates new instances of an activity"""
|
||||
|
||||
def __init__(self, service_name, activity_class):
|
||||
self._activities = []
|
||||
|
||||
splitted_module = activity_class.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)
|
||||
if hasattr(module, 'start'):
|
||||
module.start()
|
||||
|
||||
self._module = module
|
||||
self._constructor = getattr(module, class_name)
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
bus_name = dbus.service.BusName(service_name, bus = bus)
|
||||
object_path = '/' + service_name.replace('.', '/')
|
||||
dbus.service.Object.__init__(self, bus_name, object_path)
|
||||
|
||||
@dbus.service.method("com.redhat.Sugar.ActivityFactory", in_signature="a{ss}")
|
||||
def create(self, handle):
|
||||
activity_handle = activityhandle.create_from_dict(handle)
|
||||
activity = self._constructor(activity_handle)
|
||||
|
||||
self._activities.append(activity)
|
||||
activity.connect('destroy', self._activity_destroy_cb)
|
||||
|
||||
return activity.window.xid
|
||||
|
||||
def _activity_destroy_cb(self, activity):
|
||||
self._activities.remove(activity)
|
||||
|
||||
if hasattr(self._module, 'stop'):
|
||||
self._module.stop()
|
||||
|
||||
if len(self._activities) == 0:
|
||||
gtk.main_quit()
|
||||
|
||||
def run(args):
|
||||
"""Start the activity factory."""
|
||||
parser = OptionParser()
|
||||
parser.add_option("-p", "--bundle-path", dest="bundle_path",
|
||||
help="path to the activity bundle")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
sys.path.insert(0, options.bundle_path)
|
||||
|
||||
bundle = Bundle(options.bundle_path)
|
||||
|
||||
logger.start(bundle.get_name())
|
||||
|
||||
os.environ['SUGAR_BUNDLE_PATH'] = options.bundle_path
|
||||
os.environ['SUGAR_BUNDLE_SERVICE_NAME'] = bundle.get_service_name()
|
||||
os.environ['SUGAR_BUNDLE_DEFAULT_TYPE'] = bundle.get_default_type()
|
||||
|
||||
factory = ActivityFactoryService(bundle.get_service_name(), args[0])
|
||||
gtk.main()
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (C) 2006, 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.
|
||||
|
||||
from sugar.presence import PresenceService
|
||||
|
||||
class ActivityHandle(object):
|
||||
def __init__(self, activity_id):
|
||||
self.activity_id = activity_id
|
||||
self.pservice_id = None
|
||||
self.uri = None
|
||||
|
||||
def get_presence_service(self):
|
||||
if self.pservice_id:
|
||||
pservice = PresenceService.get_instance()
|
||||
return pservice.get_activity(self.pservice_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_dict(self):
|
||||
result = { 'activity_id' : self.activity_id }
|
||||
if self.pservice_id:
|
||||
result['pservice_id'] = self.pservice_id
|
||||
if self.uri:
|
||||
result['uri'] = self.uri
|
||||
|
||||
return result
|
||||
|
||||
def create_from_dict(handle_dict):
|
||||
result = ActivityHandle(handle_dict['activity_id'])
|
||||
if handle_dict.has_key('pservice_id'):
|
||||
result.pservice_id = handle_dict['pservice_id']
|
||||
if handle_dict.has_key('uri'):
|
||||
result.uri = handle_dict['uri']
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2006, 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 dbus
|
||||
import dbus.service
|
||||
|
||||
from sugar.presence import PresenceService
|
||||
|
||||
_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):
|
||||
xid = activity.window.xid
|
||||
service_name = _ACTIVITY_SERVICE_NAME + '%d' % xid
|
||||
object_path = _ACTIVITY_SERVICE_PATH + "/%s" % xid
|
||||
|
||||
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 share(self):
|
||||
"""Called by the shell to request the activity to share itself on the network."""
|
||||
self._activity.share()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE)
|
||||
def get_id(self):
|
||||
"""Get the activity identifier"""
|
||||
return self._activity.get_id()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE)
|
||||
def get_service_name(self):
|
||||
"""Get the activity service name"""
|
||||
return self._activity.get_service_name()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE)
|
||||
def get_shared(self):
|
||||
"""Returns True if the activity is shared on the mesh."""
|
||||
return self._activity.get_shared()
|
||||
|
||||
@dbus.service.method(_ACTIVITY_INTERFACE,
|
||||
in_signature="sas", out_signature="b")
|
||||
def execute(self, command, args):
|
||||
return self._activity.execute(command, args)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
from sugar import env
|
||||
|
||||
_PYTHON_FACTORY='sugar-activity-factory'
|
||||
|
||||
class Bundle:
|
||||
"""Info about an activity bundle. Wraps the activity.info file."""
|
||||
def __init__(self, path):
|
||||
@@ -38,11 +41,18 @@ class Bundle:
|
||||
self._valid = False
|
||||
logging.error('%s must specify a name' % self._path)
|
||||
|
||||
if cp.has_option(section, 'exec'):
|
||||
if cp.has_option(section, 'class'):
|
||||
self._class = cp.get(section, 'class')
|
||||
self._exec = '%s %s --bundle-path=%s' % (
|
||||
os.path.join(env.get_shell_bin_dir(), _PYTHON_FACTORY),
|
||||
self._class, self.get_path())
|
||||
elif cp.has_option(section, 'exec'):
|
||||
self._class = None
|
||||
self._exec = cp.get(section, 'exec')
|
||||
else:
|
||||
self._exec = None
|
||||
self._valid = False
|
||||
logging.error('%s must specify an exec' % self._path)
|
||||
logging.error('%s must specify exec or class' % self._path)
|
||||
|
||||
if cp.has_option(section, 'show_launcher'):
|
||||
if cp.get(section, 'show_launcher') == 'no':
|
||||
@@ -71,6 +81,10 @@ class Bundle:
|
||||
"""Get the activity service name"""
|
||||
return self._service_name
|
||||
|
||||
def get_object_path(self):
|
||||
"""Get the path to the service object"""
|
||||
return '/' + self._service_name.replace('.', '/')
|
||||
|
||||
def get_default_type(self):
|
||||
"""Get the type of the main network service which tracks presence
|
||||
and provides info about the activity, for example the title."""
|
||||
@@ -90,6 +104,10 @@ class Bundle:
|
||||
"""Get the command to execute to launch the activity factory"""
|
||||
return self._exec
|
||||
|
||||
def get_class(self):
|
||||
"""Get the main Activity class"""
|
||||
return self._exec
|
||||
|
||||
def get_show_launcher(self):
|
||||
"""Get whether there should be a visible launcher for the activity"""
|
||||
return self._show_launcher
|
||||
|
||||
@@ -11,13 +11,8 @@ class _ServiceManager(object):
|
||||
self._path = env.get_user_service_dir()
|
||||
|
||||
def add(self, bundle):
|
||||
name = bundle.get_service_name()
|
||||
|
||||
# FIXME evil hack. Probably need to fix Exec spec
|
||||
full_exec = env.get_shell_bin_dir() + '/' + bundle.get_exec()
|
||||
full_exec += ' ' + bundle.get_path()
|
||||
|
||||
util.write_service(name, full_exec, self._path)
|
||||
util.write_service(bundle.get_service_name(),
|
||||
bundle.get_exec(), self._path)
|
||||
|
||||
class BundleRegistry(gobject.GObject):
|
||||
"""Service that tracks the available activity bundles"""
|
||||
@@ -73,3 +68,14 @@ class BundleRegistry(gobject.GObject):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_registry():
|
||||
return _bundle_registry
|
||||
|
||||
_bundle_registry = BundleRegistry()
|
||||
|
||||
for path in env.get_data_dirs():
|
||||
bundles_path = os.path.join(path, 'activities')
|
||||
_bundle_registry.add_search_path(bundles_path)
|
||||
|
||||
_bundle_registry.add_search_path(env.get_user_activities_dir())
|
||||
|
||||
@@ -24,30 +24,12 @@ try:
|
||||
except ImportError:
|
||||
from sugar.__installed__ import *
|
||||
|
||||
def get_bundle_path():
|
||||
if os.environ.has_key('SUGAR_BUNDLE_PATH'):
|
||||
return os.environ['SUGAR_BUNDLE_PATH']
|
||||
else:
|
||||
return None
|
||||
|
||||
def is_emulator():
|
||||
if os.environ.has_key('SUGAR_EMULATOR'):
|
||||
if os.environ['SUGAR_EMULATOR'] == 'yes':
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_bundle_service_name():
|
||||
if os.environ.has_key('SUGAR_BUNDLE_SERVICE_NAME'):
|
||||
return os.environ['SUGAR_BUNDLE_SERVICE_NAME']
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_bundle_default_type():
|
||||
if os.environ.has_key('SUGAR_BUNDLE_DEFAULT_TYPE'):
|
||||
return os.environ['SUGAR_BUNDLE_DEFAULT_TYPE']
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_profile_path():
|
||||
if os.environ.has_key('SUGAR_PROFILE'):
|
||||
profile_id = os.environ['SUGAR_PROFILE']
|
||||
|
||||
@@ -12,7 +12,6 @@ sugar_PYTHON = \
|
||||
iconcolor.py \
|
||||
label.py \
|
||||
menu.py \
|
||||
menuicon.py \
|
||||
menushell.py \
|
||||
optionmenu.py \
|
||||
roundbox.py \
|
||||
|
||||
@@ -25,6 +25,7 @@ import cairo
|
||||
import time
|
||||
|
||||
from sugar.graphics.iconcolor import IconColor
|
||||
from sugar.graphics.timeline import Timeline
|
||||
|
||||
class _IconCacheIcon:
|
||||
def __init__(self, name, color, now):
|
||||
@@ -152,9 +153,17 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
|
||||
self._icon_name = None
|
||||
self._cache = False
|
||||
self._handle = None
|
||||
self._popup = None
|
||||
self._hover_popup = False
|
||||
|
||||
self._timeline = Timeline(self)
|
||||
self._timeline.add_tag('popup', 6, 6)
|
||||
self._timeline.add_tag('before_popdown', 7, 7)
|
||||
self._timeline.add_tag('popdown', 8, 8)
|
||||
|
||||
hippo.CanvasBox.__init__(self, **kwargs)
|
||||
|
||||
self.connect('motion-notify-event', self._motion_notify_event_cb)
|
||||
self.connect('button-press-event', self._button_press_event_cb)
|
||||
|
||||
def _clear_buffers(self):
|
||||
@@ -261,3 +270,80 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
|
||||
|
||||
def _button_press_event_cb(self, item, event):
|
||||
item.emit_activated()
|
||||
|
||||
def get_popup(self):
|
||||
return self._popup
|
||||
|
||||
def get_popup_context(self):
|
||||
return None
|
||||
|
||||
def do_popup(self, current, n_frames):
|
||||
if self._popup:
|
||||
return
|
||||
|
||||
popup = self.get_popup()
|
||||
if not popup:
|
||||
return
|
||||
|
||||
popup_context = self.get_popup_context()
|
||||
|
||||
[x, y] = [None, None]
|
||||
if popup_context:
|
||||
try:
|
||||
[x, y] = popup_context.get_position(self, popup)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
if [x, y] == [None, None]:
|
||||
context = self.get_context()
|
||||
[x, y] = context.translate_to_screen(self)
|
||||
|
||||
# TODO: Any better place to do this?
|
||||
popup.props.box_width = max(popup.props.box_width,
|
||||
self.get_width_request())
|
||||
|
||||
[width, height] = self.get_allocation()
|
||||
y += height
|
||||
position = [x, y]
|
||||
|
||||
popup.popup(x, y)
|
||||
popup.connect('motion-notify-event',
|
||||
self._popup_motion_notify_event_cb)
|
||||
popup.connect('action-completed',
|
||||
self._popup_action_completed_cb)
|
||||
|
||||
if popup_context:
|
||||
popup_context.popped_up(popup)
|
||||
|
||||
self._popup = popup
|
||||
|
||||
def do_popdown(self, current, frame):
|
||||
if self._popup:
|
||||
self._popup.popdown()
|
||||
|
||||
popup_context = self.get_popup_context()
|
||||
if popup_context:
|
||||
popup_context.popped_down(self._popup)
|
||||
|
||||
self._popup = None
|
||||
|
||||
def popdown(self):
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
|
||||
def _motion_notify_event_cb(self, button, event):
|
||||
if event.detail == hippo.MOTION_DETAIL_ENTER:
|
||||
self._timeline.play(None, 'popup')
|
||||
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
|
||||
if not self._hover_popup:
|
||||
self._timeline.play('before_popdown', 'popdown')
|
||||
|
||||
def _popup_motion_notify_event_cb(self, popup, event):
|
||||
if event.detail == hippo.MOTION_DETAIL_ENTER:
|
||||
self._hover_popup = True
|
||||
self._timeline.play('popup', 'popup')
|
||||
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
|
||||
self._hover_popup = False
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
|
||||
def _popup_action_completed_cb(self, popup):
|
||||
self.popdown()
|
||||
|
||||
@@ -23,7 +23,6 @@ import hippo
|
||||
from canvasicon import CanvasIcon
|
||||
from iconcolor import IconColor
|
||||
from sugar.graphics import units
|
||||
from sugar.graphics.timeline import Timeline
|
||||
from sugar import profile
|
||||
|
||||
STANDARD_SIZE = 0
|
||||
@@ -49,100 +48,14 @@ class IconButton(CanvasIcon):
|
||||
self._prelight_color = profile.get_color()
|
||||
self._inactive_color = IconColor('#808080,#424242')
|
||||
self._active = True
|
||||
self._popup = None
|
||||
self._hover_popup = False
|
||||
|
||||
CanvasIcon.__init__(self, icon_name=icon_name, cache=True,
|
||||
color=self._normal_color)
|
||||
|
||||
self._set_size(STANDARD_SIZE)
|
||||
|
||||
self._timeline = Timeline(self)
|
||||
self._timeline.add_tag('popup', 6, 6)
|
||||
self._timeline.add_tag('before_popdown', 7, 7)
|
||||
self._timeline.add_tag('popdown', 8, 8)
|
||||
|
||||
self.connect('motion-notify-event', self._motion_notify_event_cb)
|
||||
self.connect('button-press-event', self._button_press_event_cb)
|
||||
|
||||
def get_popup(self):
|
||||
return self._popup
|
||||
|
||||
def get_popup_context(self):
|
||||
return None
|
||||
|
||||
def do_popup(self, current, n_frames):
|
||||
if self._popup:
|
||||
return
|
||||
|
||||
popup = self.get_popup()
|
||||
if not popup:
|
||||
return
|
||||
|
||||
popup_context = self.get_popup_context()
|
||||
|
||||
[x, y] = [None, None]
|
||||
if popup_context:
|
||||
try:
|
||||
[x, y] = popup_context.get_position(self, popup)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
if [x, y] == [None, None]:
|
||||
context = self.get_context()
|
||||
#[x, y] = context.translate_to_screen(self)
|
||||
[x, y] = context.translate_to_widget(self)
|
||||
|
||||
# TODO: Any better place to do this?
|
||||
popup.props.box_width = max(popup.props.box_width,
|
||||
self.get_width_request())
|
||||
|
||||
[width, height] = self.get_allocation()
|
||||
y += height
|
||||
position = [x, y]
|
||||
|
||||
popup.popup(x, y)
|
||||
popup.connect('motion-notify-event',
|
||||
self._popup_motion_notify_event_cb)
|
||||
popup.connect('action-completed',
|
||||
self._popup_action_completed_cb)
|
||||
|
||||
if popup_context:
|
||||
popup_context.popped_up(popup)
|
||||
|
||||
self._popup = popup
|
||||
|
||||
def do_popdown(self, current, frame):
|
||||
if self._popup:
|
||||
self._popup.popdown()
|
||||
|
||||
popup_context = self.get_popup_context()
|
||||
if popup_context:
|
||||
popup_context.popped_down(self._popup)
|
||||
|
||||
self._popup = None
|
||||
|
||||
def popdown(self):
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
|
||||
def _motion_notify_event_cb(self, button, event):
|
||||
if event.detail == hippo.MOTION_DETAIL_ENTER:
|
||||
self._timeline.play(None, 'popup')
|
||||
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
|
||||
if not self._hover_popup:
|
||||
self._timeline.play('before_popdown', 'popdown')
|
||||
|
||||
def _popup_motion_notify_event_cb(self, popup, event):
|
||||
if event.detail == hippo.MOTION_DETAIL_ENTER:
|
||||
self._hover_popup = True
|
||||
self._timeline.play('popup', 'popup')
|
||||
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
|
||||
self._hover_popup = False
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
|
||||
def _popup_action_completed_cb(self, popup):
|
||||
self.popdown()
|
||||
|
||||
def _set_size(self, size):
|
||||
if size == SMALL_SIZE:
|
||||
self.props.box_width = -1
|
||||
@@ -173,8 +86,9 @@ class IconButton(CanvasIcon):
|
||||
elif pspec.name == 'active':
|
||||
return self._active
|
||||
else:
|
||||
return CanvasIcon.get_property(self, pspec)
|
||||
return CanvasIcon.do_get_property(self, pspec)
|
||||
|
||||
def _button_press_event_cb(self, widget, event):
|
||||
if self._active:
|
||||
self.emit_activated()
|
||||
return True
|
||||
|
||||
+78
-74
@@ -14,94 +14,98 @@
|
||||
# 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 sys
|
||||
|
||||
import gtk
|
||||
import hippo
|
||||
import gobject
|
||||
|
||||
from sugar.graphics.canvasicon import CanvasIcon
|
||||
from sugar.graphics.popup import Popup
|
||||
from sugar.graphics.roundbox import RoundBox
|
||||
from sugar.graphics import color
|
||||
from sugar.graphics import font
|
||||
from sugar.graphics import units
|
||||
|
||||
class Menu(gtk.Window):
|
||||
__gsignals__ = {
|
||||
'action': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE, ([int])),
|
||||
class MenuItem(hippo.CanvasBox):
|
||||
__gtype_name__ = 'SugarMenuItem'
|
||||
|
||||
__gproperties__ = {
|
||||
'action-id': (int, None, None,
|
||||
0, sys.maxint, 0,
|
||||
gobject.PARAM_READWRITE),
|
||||
'label' : (str, None, None, None,
|
||||
gobject.PARAM_READWRITE)
|
||||
}
|
||||
|
||||
def __init__(self, title=None, content_box=None):
|
||||
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
|
||||
def __init__(self, action_id, label, icon_name=None, icon_color=None):
|
||||
hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
|
||||
|
||||
self._action_id = action_id
|
||||
self.props.padding = 5
|
||||
self.props.spacing = 5
|
||||
|
||||
canvas = hippo.Canvas()
|
||||
self.add(canvas)
|
||||
canvas.show()
|
||||
if icon_name:
|
||||
icon = CanvasIcon(icon_name=icon_name,
|
||||
scale=units.SMALL_ICON_SCALE)
|
||||
if icon_color:
|
||||
icon.props.color = icon_color
|
||||
self.append(icon)
|
||||
|
||||
self._root = hippo.CanvasBox()
|
||||
canvas.set_root(self._root)
|
||||
self._canvas_text = hippo.CanvasText(text=label)
|
||||
self._canvas_text.props.color = color.LABEL_TEXT.get_int()
|
||||
self._canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
|
||||
self.append(self._canvas_text)
|
||||
|
||||
def do_set_property(self, pspec, value):
|
||||
if pspec.name == 'action-id':
|
||||
self._action_id = value
|
||||
elif pspec.name == 'label':
|
||||
self._canvas_text.props.text = value
|
||||
else:
|
||||
hippo.CanvasBox.do_set_property(self, pspec, value)
|
||||
|
||||
def do_get_property(self, pspec):
|
||||
if pspec.name == 'action-id':
|
||||
return self._action_id
|
||||
elif pspec.name == 'label':
|
||||
return self._canvas_text.props.text
|
||||
else:
|
||||
return hippo.CanvasBox.do_get_property(self, pspec)
|
||||
|
||||
class Menu(Popup):
|
||||
__gtype_name__ = 'SugarMenu'
|
||||
|
||||
__gsignals__ = {
|
||||
'action': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
|
||||
}
|
||||
|
||||
def __init__(self, title=None):
|
||||
Popup.__init__(self)
|
||||
|
||||
self.props.background_color = color.MENU_BACKGROUND.get_int()
|
||||
self.props.border_color = color.MENU_BORDER.get_int()
|
||||
self.props.border = units.points_to_pixels(1)
|
||||
|
||||
if title:
|
||||
self._title_item = hippo.CanvasText(text=title)
|
||||
self._root.append(self._title_item)
|
||||
else:
|
||||
self._title_item = None
|
||||
title_item = hippo.CanvasText(text=title)
|
||||
title_item.props.color = color.LABEL_TEXT.get_int()
|
||||
title_item.props.font_desc = font.DEFAULT.get_pango_desc()
|
||||
self.append(title_item)
|
||||
self.add_separator()
|
||||
|
||||
if content_box:
|
||||
separator = self._create_separator()
|
||||
self._root.append(separator)
|
||||
self._root.append(content_box)
|
||||
def add_item(self, item):
|
||||
item.connect('button-press-event', self._item_button_press_event_cb)
|
||||
self.append(item)
|
||||
|
||||
self._action_box = None
|
||||
self._item_box = None
|
||||
def remove_item(self, item):
|
||||
self.remove(item)
|
||||
|
||||
def _create_separator(self):
|
||||
separator = hippo.CanvasBox()
|
||||
return separator
|
||||
def add_separator(self):
|
||||
box = hippo.CanvasBox()
|
||||
box.props.background_color = color.MENU_SEPARATOR.get_int()
|
||||
box.props.box_height = units.points_to_pixels(1)
|
||||
self.append(box)
|
||||
|
||||
def _create_item_box(self):
|
||||
if self._title_item:
|
||||
separator = self._create_separator()
|
||||
self._root.append(separator)
|
||||
|
||||
self._item_box = hippo.CanvasBox(
|
||||
orientation=hippo.ORIENTATION_VERTICAL)
|
||||
self._root.append(self._item_box)
|
||||
|
||||
def _create_action_box(self):
|
||||
separator = self._create_separator()
|
||||
self._root.append(separator)
|
||||
|
||||
self._action_box = hippo.CanvasBox(
|
||||
orientation=hippo.ORIENTATION_HORIZONTAL)
|
||||
self._root.append(self._action_box)
|
||||
|
||||
def add_item(self, label, action_id=None, wrap=False):
|
||||
if not self._item_box:
|
||||
self._create_item_box()
|
||||
|
||||
text = hippo.CanvasText(text=label)
|
||||
if wrap:
|
||||
text.set_property("size-mode", "wrap-word")
|
||||
|
||||
# FIXME need a way to make hippo items activable in python
|
||||
if action_id:
|
||||
text.connect('button-press-event', self._item_clicked_cb, action_id)
|
||||
#text.connect('activated', self._action_clicked_cb, action_id)
|
||||
|
||||
self._item_box.append(text)
|
||||
|
||||
def add_action(self, icon, action_id):
|
||||
if not self._action_box:
|
||||
self._create_action_box()
|
||||
|
||||
icon.connect('activated', self._action_clicked_cb, action_id)
|
||||
self._action_box.append(icon)
|
||||
|
||||
def remove_action(self, icon):
|
||||
self._action_box.remove(icon)
|
||||
|
||||
def _item_clicked_cb(self, icon, event, action):
|
||||
self.emit('action', action)
|
||||
|
||||
def _action_clicked_cb(self, icon, action):
|
||||
self.emit('action', action)
|
||||
|
||||
def set_title(self, title):
|
||||
self._title_item.set_property('text', title)
|
||||
def _item_button_press_event_cb(self, menu_item, event):
|
||||
self.emit('action', menu_item)
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# Copyright (C) 2006, 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 hippo
|
||||
import gobject
|
||||
import logging
|
||||
|
||||
from sugar.graphics.canvasicon import CanvasIcon
|
||||
from sugar.graphics.timeline import Timeline
|
||||
|
||||
class MenuIcon(CanvasIcon):
|
||||
def __init__(self, menu_shell, **kwargs):
|
||||
CanvasIcon.__init__(self, **kwargs)
|
||||
|
||||
self._menu_shell = menu_shell
|
||||
self._menu = None
|
||||
self._hover_menu = False
|
||||
|
||||
self._timeline = Timeline(self)
|
||||
self._timeline.add_tag('popup', 6, 6)
|
||||
self._timeline.add_tag('before_popdown', 7, 7)
|
||||
self._timeline.add_tag('popdown', 8, 8)
|
||||
|
||||
self.connect('motion-notify-event', self._motion_notify_event_cb)
|
||||
|
||||
def do_popup(self, current, n_frames):
|
||||
if self._menu:
|
||||
return
|
||||
|
||||
self._menu = self.create_menu()
|
||||
|
||||
self._menu.connect('enter-notify-event',
|
||||
self._menu_enter_notify_event_cb)
|
||||
self._menu.connect('leave-notify-event',
|
||||
self._menu_leave_notify_event_cb)
|
||||
|
||||
[x, y] = self._menu_shell.get_position(self._menu, self)
|
||||
|
||||
self._menu.move(x, y)
|
||||
self._menu.show()
|
||||
|
||||
self._menu_shell.set_active(self)
|
||||
|
||||
def do_popdown(self, current, frame):
|
||||
if self._menu:
|
||||
self._menu.destroy()
|
||||
self._menu = None
|
||||
self._menu_shell.set_active(None)
|
||||
|
||||
def popdown(self):
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
|
||||
def _motion_notify_event_cb(self, item, event):
|
||||
if event.detail == hippo.MOTION_DETAIL_ENTER:
|
||||
self._timeline.play(None, 'popup')
|
||||
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
|
||||
if not self._hover_menu:
|
||||
self._timeline.play('before_popdown', 'popdown')
|
||||
|
||||
def _menu_enter_notify_event_cb(self, widget, event):
|
||||
self._hover_menu = True
|
||||
self._timeline.play('popup', 'popup')
|
||||
|
||||
def _menu_leave_notify_event_cb(self, widget, event):
|
||||
self._hover_menu = False
|
||||
self._timeline.play('popdown', 'popdown')
|
||||
@@ -24,70 +24,27 @@ import hippo
|
||||
|
||||
from sugar.graphics import units
|
||||
from sugar.graphics.roundbox import RoundBox
|
||||
from sugar.graphics.menu import Menu, MenuItem
|
||||
from sugar.graphics import iconbutton
|
||||
from sugar.graphics import color
|
||||
from sugar.graphics import font
|
||||
from sugar.graphics.canvasicon import CanvasIcon
|
||||
|
||||
class Menu(hippo.CanvasBox, hippo.CanvasItem):
|
||||
__gtype_name__ = 'SugarMenu'
|
||||
|
||||
__gsignals__ = {
|
||||
'action': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object]))
|
||||
}
|
||||
|
||||
class _Menu(Menu):
|
||||
def __init__(self):
|
||||
hippo.CanvasBox.__init__(self)
|
||||
self.props.background_color = color.MENU_BACKGROUND.get_int()
|
||||
self.props.border_color = color.MENU_BORDER.get_int()
|
||||
self.props.border = units.points_to_pixels(1)
|
||||
self._window = None
|
||||
|
||||
def add_item(self, action_id, label, icon_name=None, icon_color=None):
|
||||
box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
|
||||
box.props.padding = 5
|
||||
box.props.spacing = 5
|
||||
if icon_name:
|
||||
icon = CanvasIcon(icon_name=icon_name,
|
||||
scale=units.SMALL_ICON_SCALE)
|
||||
if icon_color:
|
||||
icon.props.color = icon_color
|
||||
box.append(icon)
|
||||
|
||||
canvas_text = hippo.CanvasText()
|
||||
canvas_text.props.text = label
|
||||
canvas_text.props.color = color.LABEL_TEXT.get_int()
|
||||
canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
|
||||
box.append(canvas_text)
|
||||
|
||||
box.connect('button-press-event', self._item_button_press_event_cb,
|
||||
[action_id, label])
|
||||
self.append(box)
|
||||
|
||||
def add_separator(self):
|
||||
box = hippo.CanvasBox()
|
||||
box.props.background_color = color.MENU_SEPARATOR.get_int()
|
||||
box.props.box_height = units.points_to_pixels(1)
|
||||
self.append(box)
|
||||
|
||||
def show(self, x, y):
|
||||
if not self._window:
|
||||
self._window = hippo.CanvasWindow(gtk.WINDOW_POPUP)
|
||||
self._window.move(x, y)
|
||||
self._window.set_root(self)
|
||||
self._window.show()
|
||||
|
||||
def hide(self):
|
||||
if self._window:
|
||||
self._window.destroy()
|
||||
self._window = None
|
||||
|
||||
def _item_button_press_event_cb(self, item, event, data):
|
||||
self.emit('action', data)
|
||||
self.hide()
|
||||
Menu.__init__(self)
|
||||
self._is_visible = False
|
||||
|
||||
def is_visible(self):
|
||||
return self._window != None
|
||||
return self._is_visible
|
||||
|
||||
def popup(self, x, y):
|
||||
Menu.popup(self, x, y)
|
||||
self._is_visible = True
|
||||
|
||||
def popdown(self):
|
||||
Menu.popdown(self)
|
||||
self._is_visible = False
|
||||
|
||||
class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
|
||||
__gtype_name__ = 'SugarOptionMenu'
|
||||
@@ -122,8 +79,9 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
|
||||
arrow.props.xalign = hippo.ALIGNMENT_START
|
||||
self._round_box.append(arrow)
|
||||
|
||||
self._menu = Menu()
|
||||
self._menu = _Menu()
|
||||
self._menu.connect('action', self._menu_action_cb)
|
||||
self._menu.connect('action-completed', self._menu_action_completed_cb)
|
||||
|
||||
self.connect('button-press-event', self._button_press_event_cb)
|
||||
|
||||
@@ -135,33 +93,37 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem):
|
||||
if pspec.name == 'value':
|
||||
return self._value
|
||||
|
||||
def add_option(self, action_id, label, icon_name=None, icon_color=None):
|
||||
def add_item(self, menu_item):
|
||||
if not self._value:
|
||||
self._value = action_id
|
||||
self._canvas_text.props.text = label
|
||||
self._value = menu_item.props.action_id
|
||||
self._canvas_text.props.text = menu_item.props.label
|
||||
|
||||
self._menu.add_item(action_id, label, icon_name, icon_color)
|
||||
self._menu.add_item(menu_item)
|
||||
|
||||
def add_separator(self):
|
||||
self._menu.add_separator()
|
||||
|
||||
def _button_press_event_cb(self, box, event):
|
||||
if self._menu.is_visible():
|
||||
self._menu.hide()
|
||||
self._menu.popdown()
|
||||
else:
|
||||
context = self._round_box.get_context()
|
||||
#[x, y] = context.translate_to_screen(self._round_box)
|
||||
[x, y] = context.translate_to_widget(self._round_box)
|
||||
[x, y] = context.translate_to_screen(self._round_box)
|
||||
|
||||
# TODO: Any better place to do this?
|
||||
self._menu.props.box_width = self.get_width_request()
|
||||
|
||||
[width, height] = self._round_box.get_allocation()
|
||||
self._menu.show(x, y + height)
|
||||
self._menu.popup(x, y + height)
|
||||
|
||||
def _menu_action_cb(self, menu, data):
|
||||
[action_id, label] = data
|
||||
def _menu_action_cb(self, menu, menu_item):
|
||||
action_id = menu_item.props.action_id
|
||||
label = menu_item.props.label
|
||||
|
||||
if action_id != self._value:
|
||||
self._value = action_id
|
||||
self._canvas_text.props.text = label
|
||||
self.emit('changed')
|
||||
|
||||
def _menu_action_completed_cb(self, menu):
|
||||
self._menu.popdown()
|
||||
|
||||
+3
-38
@@ -21,13 +21,6 @@ import gobject
|
||||
import gtk
|
||||
import hippo
|
||||
|
||||
from sugar.graphics import units
|
||||
from sugar.graphics.roundbox import RoundBox
|
||||
from sugar.graphics import button
|
||||
from sugar.graphics import color
|
||||
from sugar.graphics import font
|
||||
from sugar.graphics.canvasicon import CanvasIcon
|
||||
|
||||
class Popup(hippo.CanvasBox, hippo.CanvasItem):
|
||||
__gtype_name__ = 'SugarPopup'
|
||||
|
||||
@@ -35,38 +28,10 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
|
||||
'action-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
|
||||
}
|
||||
|
||||
def __init__(self, title):
|
||||
def __init__(self):
|
||||
hippo.CanvasBox.__init__(self)
|
||||
self.props.background_color = color.MENU_BACKGROUND.get_int()
|
||||
self.props.border_color = color.MENU_BORDER.get_int()
|
||||
self.props.border = units.points_to_pixels(1)
|
||||
self._window = None
|
||||
|
||||
def add_item(self, action_id, label, icon_name=None, icon_color=None):
|
||||
box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
|
||||
box.props.padding = 5
|
||||
box.props.spacing = 5
|
||||
if icon_name:
|
||||
icon = CanvasIcon(icon_name=icon_name,
|
||||
scale=units.SMALL_ICON_SCALE)
|
||||
if icon_color:
|
||||
icon.props.color = icon_color
|
||||
box.append(icon)
|
||||
|
||||
canvas_text = hippo.CanvasText()
|
||||
canvas_text.props.text = label
|
||||
canvas_text.props.color = color.LABEL_TEXT.get_int()
|
||||
canvas_text.props.font_desc = font.DEFAULT.get_pango_desc()
|
||||
box.append(canvas_text)
|
||||
|
||||
box.connect('button-press-event', self._item_button_press_event_cb)
|
||||
self.append(box)
|
||||
|
||||
def add_separator(self):
|
||||
box = hippo.CanvasBox()
|
||||
box.props.background_color = color.MENU_SEPARATOR.get_int()
|
||||
box.props.box_height = units.points_to_pixels(1)
|
||||
self.append(box)
|
||||
self.connect('button-press-event', self._button_press_event_cb)
|
||||
|
||||
def popup(self, x, y):
|
||||
if not self._window:
|
||||
@@ -80,5 +45,5 @@ class Popup(hippo.CanvasBox, hippo.CanvasItem):
|
||||
self._window.destroy()
|
||||
self._window = None
|
||||
|
||||
def _item_button_press_event_cb(self, item, event):
|
||||
def _button_press_event_cb(self, menu, event):
|
||||
self.emit('action-completed')
|
||||
|
||||
Reference in New Issue
Block a user