diff --git a/services/presence/activity.py b/services/presence/activity.py index e9c3c05c..c856f54a 100644 --- a/services/presence/activity.py +++ b/services/presence/activity.py @@ -29,6 +29,15 @@ class DBusGObjectMetaclass(dbus.service.InterfaceType, gobject.GObjectMeta): pas class DBusGObject(dbus.service.Object, gobject.GObject): __metaclass__ = DBusGObjectMetaclass +_PROP_ID = "id" +_PROP_NAME = "name" +_PROP_COLOR = "color" +_PROP_TYPE = "type" +_PROP_VALID = "valid" +_PROP_LOCAL = "local" +_PROP_JOINED = "joined" +_PROP_CUSTOM_PROPS = "custom-props" + class Activity(DBusGObject): """Represents a potentially shareable activity on the network. """ @@ -41,17 +50,21 @@ class Activity(DBusGObject): } __gproperties__ = { - 'id' : (str, None, None, None, - gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), - 'name' : (str, None, None, None, gobject.PARAM_READWRITE), - 'color' : (str, None, None, None, gobject.PARAM_READWRITE), - 'type' : (str, None, None, None, gobject.PARAM_READWRITE), - 'valid' : (bool, None, None, False, gobject.PARAM_READABLE), - 'local' : (bool, None, None, False, - gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), - 'joined' : (bool, None, None, False, gobject.PARAM_READABLE) + _PROP_ID : (str, None, None, None, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), + _PROP_NAME : (str, None, None, None, gobject.PARAM_READWRITE), + _PROP_COLOR : (str, None, None, None, gobject.PARAM_READWRITE), + _PROP_TYPE : (str, None, None, None, gobject.PARAM_READWRITE), + _PROP_VALID : (bool, None, None, False, gobject.PARAM_READABLE), + _PROP_LOCAL : (bool, None, None, False, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY), + _PROP_JOINED : (bool, None, None, False, gobject.PARAM_READABLE), + _PROP_CUSTOM_PROPS : (object, None, None, + gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY) } + _RESERVED_PROPNAMES = __gproperties__.keys() + def __init__(self, bus_name, object_id, tp, **kwargs): """Initializes the activity and sets its properties to default values. @@ -85,11 +98,18 @@ class Activity(DBusGObject): self._color = None self._local = False self._type = None + self._custom_props = {} - if not kwargs.get("id"): + # ensure no reserved property names are in custom properties + if kwargs.get(_PROP_CUSTOM_PROPS): + (rprops, cprops) = self._split_properties(kwargs.get(_PROP_CUSTOM_PROPS)) + if len(rprops.keys()) > 0: + raise ValueError("Cannot use reserved property names '%s'" % ", ".join(rprops.keys())) + + if not kwargs.get(_PROP_ID): raise ValueError("activity id is required") - if not util.validate_activity_id(kwargs['id']): - raise ValueError("Invalid activity id '%s'" % kwargs['id']) + if not util.validate_activity_id(kwargs[_PROP_ID]): + raise ValueError("Invalid activity id '%s'" % kwargs[_PROP_ID]) gobject.GObject.__init__(self, **kwargs) if self.props.local and not self.props.valid: @@ -107,19 +127,19 @@ class Activity(DBusGObject): returns The value of the given property. """ - if pspec.name == "id": + if pspec.name == _PROP_ID: return self._id - elif pspec.name == "name": + elif pspec.name == _PROP_NAME: return self._name - elif pspec.name == "color": + elif pspec.name == _PROP_COLOR: return self._color - elif pspec.name == "type": + elif pspec.name == _PROP_TYPE: return self._type - elif pspec.name == "valid": + elif pspec.name == _PROP_VALID: return self._valid - elif pspec.name == "joined": + elif pspec.name == _PROP_JOINED: return self._joined - elif pspec.name == "local": + elif pspec.name == _PROP_LOCAL: return self._local def do_set_property(self, pspec, value): @@ -132,20 +152,29 @@ class Activity(DBusGObject): to something different later will raise a RuntimeError. """ - if pspec.name == "id": + if pspec.name == _PROP_ID: + if self._id: + raise RuntimeError("activity ID is already set") self._id = value - elif pspec.name == "name": + elif pspec.name == _PROP_NAME: self._name = value - elif pspec.name == "color": + elif pspec.name == _PROP_COLOR: self._color = value - elif pspec.name == "type": + elif pspec.name == _PROP_TYPE: if self._type: raise RuntimeError("activity type is already set") self._type = value - elif pspec.name == "joined": + elif pspec.name == _PROP_JOINED: self._joined = value - elif pspec.name == "local": + elif pspec.name == _PROP_LOCAL: self._local = value + elif pspec.name == _PROP_CUSTOM_PROPS: + if not value: + value = {} + (rprops, cprops) = self._split_properties(value) + self._custom_props = {} + for (key, dvalue) in cprops.items(): + self._custom_props[str(key)] = str(dvalue) self._update_validity() @@ -441,6 +470,11 @@ class Activity(DBusGObject): props['name'] = self._name props['color'] = self._color props['type'] = self._type + + # Add custom properties + for (key, value) in self._custom_props.items(): + props[key] = value + self._tp.set_activity_properties(self.props.id, props) def set_properties(self, properties): @@ -450,29 +484,53 @@ class Activity(DBusGObject): Note that if any of the name, colour and/or type property values is changed from what it originally was, the update_validity method will be called, resulting in - a "validity-changed" signal being generated. (Also note that unlike with the - do_set_property method, it *is* possible to change an already-set activity type - to something else; this may be a bug.) Called by the PresenceService on the - local machine. + a "validity-changed" signal being generated. Called by the PresenceService + on the local machine. """ changed = False - if "name" in properties.keys(): - name = properties["name"] + # split reserved properties from activity-custom properties + (rprops, cprops) = self._split_properties(properties) + if _PROP_NAME in rprops.keys(): + name = rprops[_PROP_NAME] if name != self._name: self._name = name changed = True - if "color" in properties.keys(): - color = properties["color"] + if _PROP_COLOR in rprops.keys(): + color = rprops[_PROP_COLOR] if color != self._color: self._color = color changed = True - if "type" in properties.keys(): - type = properties["type"] - if type != self._type: - self._type = type - changed = True + if _PROP_TYPE in rprops.keys(): + type = rprops[_PROP_TYPE] + # Type can never be changed after first set + if self._type: + logging.debug("Activity type changed by network; this is illegal") + else: + if type != self._type: + self._type = type + changed = True + + # Set custom properties + if len(cprops.keys()) > 0: + self.props.custom_props = cprops if changed: self._update_validity() + + def _split_properties(self, properties): + """Extracts reserved properties. + + properties - Dictionary object containing properties keyed by property names + + returns a tuple of 2 dictionaries, reserved properties and custom properties + """ + rprops = {} + cprops = {} + for (key, value) in properties.items(): + if key in self._RESERVED_PROPNAMES: + rprops[key] = value + else: + cprops[key] = value + return (rprops, cprops) diff --git a/services/presence/buddy.py b/services/presence/buddy.py index 25bdb69a..ca3aff22 100644 --- a/services/presence/buddy.py +++ b/services/presence/buddy.py @@ -560,7 +560,7 @@ class TestOwner(GenericOwner): __gtype_name__ = "TestOwner" - def __init__(self, ps, bus_name, object_id, test_num): + def __init__(self, ps, bus_name, object_id, test_num, randomize): self._cp = ConfigParser() self._section = "Info" self._test_activities = [] @@ -588,7 +588,9 @@ class TestOwner(GenericOwner): GenericOwner.__init__(self, ps, bus_name, object_id, key=pubkey, nick=nick, color=color, icon=icon, registered=registered, key_hash=privkey_hash) - self._ps.connect('connection-status', self._ps_connection_status_cb) + # Only do the random stuff if randomize is true + if randomize: + self._ps.connect('connection-status', self._ps_connection_status_cb) def _share_reply_cb(self, actid, object_path): activity = self._ps.internal_get_activity(actid) diff --git a/services/presence/presenceservice.py b/services/presence/presenceservice.py index d90eb56c..46ac1a28 100644 --- a/services/presence/presenceservice.py +++ b/services/presence/presenceservice.py @@ -51,7 +51,7 @@ class PresenceService(DBusGObject): ([gobject.TYPE_BOOLEAN])) } - def __init__(self, test=0): + def __init__(self, test_num=0, randomize=False): self._next_object_id = 0 self._connected = False @@ -66,8 +66,8 @@ class PresenceService(DBusGObject): # Create the Owner object objid = self._get_next_object_id() - if test > 0: - self._owner = TestOwner(self, self._bus_name, objid, test) + if test_num > 0: + self._owner = TestOwner(self, self._bus_name, objid, test_num, randomize) else: self._owner = ShellOwner(self, self._bus_name, objid) self._buddies[self._owner.props.key] = self._owner @@ -357,9 +357,9 @@ class PresenceService(DBusGObject): return self._activities[actid] -def main(test=False): +def main(test_num=0, randomize=False): loop = gobject.MainLoop() - ps = PresenceService(test) + ps = PresenceService(test_num, randomize) try: loop.run() except KeyboardInterrupt: diff --git a/services/presence/sugar-presence-service b/services/presence/sugar-presence-service index 7eec6965..1680fea5 100755 --- a/services/presence/sugar-presence-service +++ b/services/presence/sugar-presence-service @@ -24,24 +24,36 @@ import os from sugar import logger from sugar import env +def usage(): + logging.debug("Usage: sugar-presence-service [] [randomize]") + sys.path.append(env.get_service_path('presence')) -test=0 -if len(sys.argv) > 1: +test_num = 0 +randomize = False +if len(sys.argv) in [2, 3]: try: - test = int(sys.argv[1]) + test_num = int(sys.argv[1]) except ValueError: logging.debug("Bad test user number.") - if test < 1 or test > 10: + if test_num < 1 or test_num > 10: logging.debug("Bad test user number.") -if test > 0: - logger.start('test-%d-presenceservice' % test) + if len(sys.argv) == 3 and sys.argv[2] == "randomize": + randomize = True +elif len(sys.argv) == 1: + pass +else: + usage() + os._exit(1) + +if test_num > 0: + logger.start('test-%d-presenceservice' % test_num) else: logger.start('presenceservice') import presenceservice -logging.info('Starting presence service') +logging.info('Starting presence service...') -presenceservice.main(test) +presenceservice.main(test_num, randomize) diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index fa5b8882..a859810c 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -64,6 +64,31 @@ class ActivityToolbar(gtk.Toolbar): def _activity_shared_cb(self, activity): self._share_button.set_sensitive(False) +class EditToolbar(gtk.Toolbar): + def __init__(self): + gtk.Toolbar.__init__(self) + + self.undo = ToolButton('edit-undo') + self.insert(self.undo, -1) + self.undo.show() + + self.redo = ToolButton('edit-redo') + self.insert(self.redo, -1) + self.redo.show() + + separator = gtk.SeparatorToolItem() + separator.set_draw(True) + self.insert(separator, -1) + separator.show() + + self.copy = ToolButton('edit-copy') + self.insert(self.copy, -1) + self.copy.show() + + self.paste = ToolButton('edit-paste') + self.insert(self.paste, -1) + self.paste.show() + class ActivityToolbox(Toolbox): def __init__(self, activity): Toolbox.__init__(self) diff --git a/sugar/graphics/Makefile.am b/sugar/graphics/Makefile.am index 6756aa93..eb4b3795 100644 --- a/sugar/graphics/Makefile.am +++ b/sugar/graphics/Makefile.am @@ -2,6 +2,7 @@ sugardir = $(pythondir)/sugar/graphics sugar_PYTHON = \ __init__.py \ animator.py \ + icon.py \ iconbutton.py \ canvasicon.py \ color.py \ @@ -11,6 +12,7 @@ sugar_PYTHON = \ menu.py \ menushell.py \ roundbox.py \ + panel.py \ popup.py \ popupcontext.py \ snowflakebox.py \ diff --git a/sugar/graphics/panel.py b/sugar/graphics/panel.py new file mode 100644 index 00000000..bf3ed243 --- /dev/null +++ b/sugar/graphics/panel.py @@ -0,0 +1,23 @@ +# Copyright (C) 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. + +import gtk + +class Panel(gtk.VBox): + __gtype_name__ = 'SugarPanel' + def __init__(self): + gtk.VBox.__init__(self) diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py new file mode 100644 index 00000000..09ec0efb --- /dev/null +++ b/sugar/graphics/toggletoolbutton.py @@ -0,0 +1,28 @@ +# Copyright (C) 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. + +import gtk + +from sugar.graphics.icon import Icon + +class ToggleToolButton(gtk.ToggleToolButton): + def __init__(self, icon_resource=None): + gtk.ToggleToolButton.__init__(self) + + icon = Icon(icon_resource) + self.set_icon_widget(icon) + icon.show() diff --git a/sugar/graphics/toolbox.py b/sugar/graphics/toolbox.py index 441d157b..6dd805f1 100644 --- a/sugar/graphics/toolbox.py +++ b/sugar/graphics/toolbox.py @@ -26,6 +26,7 @@ class Toolbox(gtk.VBox): gtk.VBox.__init__(self) self._notebook = gtk.Notebook() + self._notebook.set_name('sugar-toolbox-notebook') self._notebook.set_tab_pos(gtk.POS_BOTTOM) self._notebook.set_show_border(False) self.pack_start(self._notebook) diff --git a/tests/presence/test-ps-bindings.py b/tests/presence/test-ps-bindings.py index 8da94a1d..ccf6617f 100755 --- a/tests/presence/test-ps-bindings.py +++ b/tests/presence/test-ps-bindings.py @@ -24,7 +24,7 @@ from sugar.presence import presenceservice import mockps def start_ps(): - argv = ["mockps.py", "mockps.py"] + argv = ["mockps.py"] (pid, stdin, stdout, stderr) = gobject.spawn_async(argv, flags=gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN) # Wait until it shows up on the bus