Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter.bender@gmail.com>2011-12-07 18:53:54 (GMT)
committer Walter Bender <walter.bender@gmail.com>2011-12-07 18:53:54 (GMT)
commit010be3f15bb3e1d351606324ea2d038a22e714f3 (patch)
treea902064e36963e33a0cf04c1d57a8be9f5e68a3d
parentc9bdaca34179fe1a301227142a183fa261cd6305 (diff)
added sharing (still needs testing)
-rw-r--r--ReflectionActivity.py150
-rw-r--r--game.py46
-rw-r--r--utils.py53
3 files changed, 236 insertions, 13 deletions
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()