From 010be3f15bb3e1d351606324ea2d038a22e714f3 Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Wed, 07 Dec 2011 18:53:54 +0000 Subject: added sharing (still needs testing) --- diff --git a/ReflectionActivity.py b/ReflectionActivity.py index ac59374..4ee68f3 100644 --- a/ReflectionActivity.py +++ b/ReflectionActivity.py @@ -25,6 +25,13 @@ if _have_toolbox: from sugar.activity.widgets import StopButton from toolbar_utils import button_factory, label_factory, separator_factory +from utils import json_load, json_dump + +import telepathy +from dbus.service import signal +from dbus.gobject_service import ExportedGObject +from sugar.presence import presenceservice +from sugar.presence.tubeconn import TubeConnection from gettext import gettext as _ @@ -71,7 +78,7 @@ class ReflectionActivity(activity.Activity): def _setup_toolbars(self, have_toolbox): """ Setup the toolbars. """ - self.max_participants = 1 + self.max_participants = 4 if have_toolbox: toolbox = ToolbarBox() @@ -128,7 +135,7 @@ class ReflectionActivity(activity.Activity): def write_file(self, file_path): """ Write the grid status to the Journal """ - (orientation, dot_list) = self._game.save_game() + (dot_list, orientation) = self._game.save_game() self.metadata['orientation'] = orientation self.metadata['dotlist'] = '' for dot in dot_list: @@ -148,3 +155,142 @@ class ReflectionActivity(activity.Activity): for dot in dots: dot_list.append(int(dot)) self._game.restore_game(dot_list, orientation) + + # Collaboration-related methods + + def _setup_presence_service(self): + """ Setup the Presence Service. """ + self.pservice = presenceservice.get_instance() + self.initiating = None # sharing (True) or joining (False) + + owner = self.pservice.get_owner() + self.owner = owner + self._share = "" + self.connect('shared', self._shared_cb) + self.connect('joined', self._joined_cb) + + def _shared_cb(self, activity): + """ Either set up initial share...""" + self._new_tube_common(True) + + def _joined_cb(self, activity): + """ ...or join an exisiting share. """ + self._new_tube_common(False) + + def _new_tube_common(self, sharer): + """ Joining and sharing are mostly the same... """ + if self._shared_activity is None: + print("Error: Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()") + return + + self.initiating = sharer + self.waiting_for_hand = not sharer + + self.conn = self._shared_activity.telepathy_conn + self.tubes_chan = self._shared_activity.telepathy_tubes_chan + self.text_chan = self._shared_activity.telepathy_text_chan + + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( + 'NewTube', self._new_tube_cb) + + if sharer: + print('This is my activity: making a tube...') + id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + SERVICE, {}) + else: + print('I am joining an activity: waiting for a tube...') + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( + reply_handler=self._list_tubes_reply_cb, + error_handler=self._list_tubes_error_cb) + + def _list_tubes_reply_cb(self, tubes): + """ Reply to a list request. """ + for tube_info in tubes: + self._new_tube_cb(*tube_info) + + def _list_tubes_error_cb(self, e): + """ Log errors. """ + print('Error: ListTubes() failed: %s', e) + + def _new_tube_cb(self, id, initiator, type, service, params, state): + """ Create a new tube. """ + print('New tube: ID=%d initator=%d type=%d service=%s params=%r \ +state=%d' % (id, initiator, type, service, params, state)) + + if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): + if state == telepathy.TUBE_STATE_LOCAL_PENDING: + self.tubes_chan[ \ + telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) + + tube_conn = TubeConnection(self.conn, + self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, \ + group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) + + self.chattube = ChatTube(tube_conn, self.initiating, \ + self.event_received_cb) + + def _setup_dispatch_table(self): + ''' Associate tokens with commands. ''' + self._processing_methods = { + 'n': [self._receive_new_game, 'get a new game grid'], + 'p': [self._receive_dot_click, 'get a dot click'], + } + + def event_received_cb(self, event_message): + ''' Data from a tube has arrived. ''' + if len(event_message) == 0: + return + try: + command, payload = event_message.split('|', 2) + except ValueError: + print('Could not split event message %s' % (event_message)) + return + self._processing_methods[command][0](payload) + + def start_new_game(self): + ''' Send a new orientation, grid to all players ''' + self.send_event('n|%s' % (json_dump(self._game.save_game()))) + + def _receive_new_game(self, payload): + ''' Sharer can start a new game. ''' + (dot_list, orientation) = json_load(payload) + self._game.restore_game(dot_list, orientation) + + def send_button_press(self, dot, color): + ''' Send a dot click to all the players ''' + self.send_event('p|%s' % (json_dump([dot, color]))) + + def _receive_dot_click(self, payload): + ''' When a dot is clicked, everyone should change its color. ''' + (dot, color) = json_load(payload) + self._game.remote_button_press(dot, color) + + def send_event(self, entry): + """ Send event through the tube. """ + if hasattr(self, 'chattube') and self.chattube is not None: + self.chattube.SendText(entry) + + +class ChatTube(ExportedGObject): + """ Class for setting up tube for sharing """ + + def __init__(self, tube, is_initiator, stack_received_cb): + super(ChatTube, self).__init__(tube, PATH) + self.tube = tube + self.is_initiator = is_initiator # Are we sharing or joining activity? + self.stack_received_cb = stack_received_cb + self.stack = '' + + self.tube.add_signal_receiver(self.send_stack_cb, 'SendText', IFACE, + path=PATH, sender_keyword='sender') + + def send_stack_cb(self, text, sender=None): + if sender == self.tube.get_unique_name(): + return + self.stack = text + self.stack_received_cb(text) + + @signal(dbus_interface=IFACE, signature='s') + def SendText(self, text): + self.stack = text diff --git a/game.py b/game.py index 8b8672d..16894b6 100644 --- a/game.py +++ b/game.py @@ -45,7 +45,9 @@ class Game(): self._colors.append('#000000') self._canvas = canvas - parent.show_all() + if parent is not None: + parent.show_all() + self._patent = parent self._canvas.set_flags(gtk.CAN_FOCUS) self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK) @@ -58,6 +60,7 @@ class Game(): self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._orientation = 'horizontal' + self.we_are_sharing = False # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) @@ -137,6 +140,9 @@ class Game(): self._dots[n].set_shape(self._new_dot( self._colors[self._dots[n].type])) + if self.we_are_sharing: + self._parent.start_new_game() + def restore_game(self, dot_list, orientation): ''' Restore a game from the Journal or share ''' for i, dot in enumerate(dot_list): @@ -145,13 +151,16 @@ class Game(): self._colors[self._dots[i].type])) self._set_orientation() + if self.we_are_sharing: + self._parent.start_new_game() + def save_game(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) - return (self._orientation, dot_list) + return (dot_list, self._orientation) def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' @@ -170,8 +179,20 @@ class Game(): spr.type %= 4 spr.set_shape(self._new_dot(self._colors[spr.type])) self._test_game_over() + + if self.we_are_sharing: + self._parent.send_button_press(self._dots.index(spr), + spr.type) return True + def remote_button_press(self, dot, color): + ''' Receive a button press from a sharer ''' + self._dots[dot].type = color + self._dots.set_shape(self._new_dot(self._colors[color])) + + def set_sharing(self, share=True): + self.we_are_sharing = share + def _test_game_over(self): ''' Check to see if game is over ''' if self._orientation == 'horizontal': @@ -237,15 +258,18 @@ class Game(): def _new_dot(self, color): ''' generate a dot of a color color ''' - self._stroke = color - self._fill = color - self._svg_width = self._dot_size - self._svg_height = self._dot_size - return svg_str_to_pixbuf( - self._header() + \ - self._circle(self._dot_size / 2., self._dot_size / 2., - self._dot_size / 2.) + \ - self._footer()) + self._dot_cache = {} + if not color in self._dot_cache: + self._stroke = color + self._fill = color + self._svg_width = self._dot_size + self._svg_height = self._dot_size + self._dot_cache[color] = svg_str_to_pixbuf( + self._header() + \ + self._circle(self._dot_size / 2., self._dot_size / 2., + self._dot_size / 2.) + \ + self._footer()) + return self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..ffab831 --- /dev/null +++ b/utils.py @@ -0,0 +1,53 @@ +#Copyright (c) 2011 Walter Bender + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# You should have received a copy of the GNU General Public License +# along with this library; if not, write to the Free Software +# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + + +from StringIO import StringIO +try: + OLD_SUGAR_SYSTEM = False + import json + json.dumps + from json import load as jload + from json import dump as jdump +except (ImportError, AttributeError): + try: + import simplejson as json + from simplejson import load as jload + from simplejson import dump as jdump + except: + OLD_SUGAR_SYSTEM = True + + +def json_load(text): + """ Load JSON data using what ever resources are available. """ + if OLD_SUGAR_SYSTEM is True: + listdata = json.read(text) + else: + # strip out leading and trailing whitespace, nulls, and newlines + io = StringIO(text) + try: + listdata = jload(io) + except ValueError: + # assume that text is ascii list + listdata = text.split() + for i, value in enumerate(listdata): + listdata[i] = int(value) + return listdata + + +def json_dump(data): + """ Save data using available JSON tools. """ + if OLD_SUGAR_SYSTEM is True: + return json.write(data) + else: + _io = StringIO() + jdump(data, _io) + return _io.getvalue() -- cgit v0.9.1