From 86e2111461fab98d8c188d50ec8bd0c72c527f73 Mon Sep 17 00:00:00 2001 From: Pootle daemon Date: Mon, 13 Jun 2011 14:23:40 +0000 Subject: Merge branch 'master' of git.sugarlabs.org:turtleart/mainline --- (limited to 'TurtleArt/tacollaboration.py') diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py index 52164e0..e1534a4 100644 --- a/TurtleArt/tacollaboration.py +++ b/TurtleArt/tacollaboration.py @@ -1,9 +1,34 @@ +#Copyright (c) 2011, Walter Bender +#Copyright (c) 2011 Collabora Ltd. + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. from dbus.service import signal from dbus.gobject_service import ExportedGObject -import logging import telepathy -from TurtleArt.tautils import data_to_string, data_from_string + +import gtk +import base64 + +from TurtleArt.tautils import data_to_string, data_from_string, get_path, \ + base64_to_image, debug_output, error_output +from TurtleArt.taconstants import DEFAULT_TURTLE_COLORS try: from sugar import profile @@ -17,43 +42,63 @@ except: SERVICE = 'org.laptop.TurtleArtActivity' IFACE = SERVICE PATH = '/org/laptop/TurtleArtActivity' -_logger = logging.getLogger('turtleart-activity') + class Collaboration(): def __init__(self, tw, activity): """ A simplistic sharing model: the sharer is the master """ self._tw = tw self._tw.send_event = self.send_event + self._tw.remote_turtle_dictionary = {} self._activity = activity + self._setup_dispatch_table() def setup(self): # TODO: hand off role of master is sharer leaves self.pservice = presenceservice.get_instance() - self.initiating = None # sharing (True) or joining (False) + self.initiating = None # sharing (True) or joining (False) # Add my buddy object to the list owner = self.pservice.get_owner() self.owner = owner self._tw.buddies.append(self.owner) - self._share = "" - + self._share = '' self._activity.connect('shared', self._shared_cb) self._activity.connect('joined', self._joined_cb) + def _setup_dispatch_table(self): + self._processing_methods = { + 't': self._turtle_request, + 'T': self._receive_turtle_dict, + 'f': self._move_forward, + 'a': self._move_in_arc, + 'r': self._rotate_turtle, + 'x': self._setxy, + 'W': self._draw_text, + 'c': self._set_pen_color, + 'g': self._set_pen_gray_level, + 's': self._set_pen_shade, + 'w': self._set_pen_width, + 'p': self._set_pen_state, + 'F': self._fill_polygon, + 'P': self._draw_pixbuf + } + def _shared_cb(self, activity): self._shared_activity = self._activity._shared_activity if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ - _shared_activity is null in _shared_cb()") + debug_output('Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()', + self._tw.running_sugar) return self._tw.set_sharing(True) self.initiating = True self.waiting_for_turtles = False - self.turtle_dictionary = self._get_dictionary() - - _logger.debug('I am sharing...') + self._tw.remote_turtle_dictionary = self._get_dictionary() + + debug_output('I am sharing...', self._tw.running_sugar) self.conn = self._shared_activity.telepathy_conn self.tubes_chan = self._shared_activity.telepathy_tubes_chan @@ -62,7 +107,8 @@ class Collaboration(): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) - _logger.debug('This is my activity: making a tube...') + debug_output('This is my activity: making a tube...', + self._tw.running_sugar) id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) @@ -70,8 +116,9 @@ class Collaboration(): def _joined_cb(self, activity): self._shared_activity = self._activity._shared_activity if self._shared_activity is None: - _logger.error("Failed to share or join activity ... \ - _shared_activity is null in _shared_cb()") + debug_output('Failed to share or join activity ... \ + _shared_activity is null in _shared_cb()', + self._tw.running_sugar) return self._tw.set_sharing(True) @@ -85,7 +132,8 @@ class Collaboration(): self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) - _logger.debug('I am joining an activity: waiting for a tube...') + debug_output('I am joining an activity: waiting for a tube...', + self._tw.running_sugar) self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) @@ -98,13 +146,13 @@ class Collaboration(): self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): - _logger.error('ListTubes() failed: %s', e) + error_output('ListTubes() failed: %s' % (e), self._tw.running_sugar) def _new_tube_cb(self, id, initiator, type, service, params, state): """ Create a new tube. """ - _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' - 'params=%r state=%d', id, initiator, type, service, - params, state) + debug_output('New tube: ID=%d initator=%d type=%d service=%s \ + params=%r state=%d' % (id, initiator, type, service, + params, state), self._tw.running_sugar) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: @@ -120,106 +168,35 @@ class Collaboration(): self.event_received_cb) # Now that we have the tube, we can ask for the turtle dictionary. - if self.waiting_for_turtles: - _logger.debug("Sending a request for the turtle dictionary") - # we need to send our own nick and colors + if self.waiting_for_turtles: # A joiner must wait for turtles. + debug_output('Sending a request for the turtle dictionary', + self._tw.running_sugar) + # We need to send our own nick, colors, and turtle position colors = self._get_colors() - event = "t|" + data_to_string([self._get_nick(), colors]) - _logger.debug(event) + event = 't|' + data_to_string([self._get_nick(), colors]) + debug_output(event, self._tw.running_sugar) self.send_event(event) - def event_received_cb(self, text): + def event_received_cb(self, event_message): """ Events are sent as a tuple, nick|cmd, where nick is a turle name and cmd is a turtle event. Everyone gets the turtle dictionary from the sharer and watches for 't' events, which indicate that a new turtle has joined. """ - if len(text) == 0: + if len(event_message) == 0: return - # Save active Turtle + + # Save active Turtle save_active_turtle = self._tw.active_turtle - e = text.split("|", 2) - text = e[1] - if e[0] == 't': # request for turtle dictionary - if text > 0: - [nick, colors] = data_from_string(text) - if nick != self._tw.nick: - # There may not be a turtle dictionary. - if hasattr(self, "turtle_dictionary"): - self.turtle_dictionary[nick] = colors - else: - self.turtle_dictionary = {nick: colors} - # Add new turtle for the joiner. - self._tw.canvas.set_turtle(nick, colors) - # Sharer should send turtle dictionary. - if self.initiating: - text = data_to_string(self.turtle_dictionary) - self.send_event("T|" + text) - elif e[0] == 'T': # Receiving the turtle dictionary. - if self.waiting_for_turtles: - if len(text) > 0: - self.turtle_dictionary = data_from_string(text) - for nick in self.turtle_dictionary: - if nick != self._tw.nick: - colors = self.turtle_dictionary[nick] - # add new turtle for the joiner - self._tw.canvas.set_turtle(nick, colors) - self.waiting_for_turtles = False - elif e[0] == 'f': # move a turtle forward - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.forward(x, False) - elif e[0] == 'a': # move a turtle in an arc - if len(text) > 0: - [nick, [a, r]] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.arc(a, r, False) - elif e[0] == 'r': # rotate turtle - if len(text) > 0: - [nick, h] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.seth(h, False) - elif e[0] == 'x': # set turtle xy position - if len(text) > 0: - [nick, [x, y]] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setxy(x, y, False) - elif e[0] == 'c': # set turtle pen color - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setcolor(x, False) - elif e[0] == 'g': # set turtle pen gray level - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setgray(x, False) - elif e[0] == 's': # set turtle pen shade - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setshade(x, False) - elif e[0] == 'w': # set turtle pen width - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setpensize(x, False) - elif e[0] == 'p': # set turtle pen state - if len(text) > 0: - [nick, x] = data_from_string(text) - if nick != self._tw.nick: - self._tw.canvas.set_turtle(nick) - self._tw.canvas.setpen(x, False) + + try: + command, payload = event_message.split('|', 2) + self._processing_methods[command](payload) + except ValueError: + debug_output('Could not split event message.', + self._tw.running_sugar) + # Restore active Turtle self._tw.canvas.set_turtle(self._tw.turtles.get_turtle_key( save_active_turtle)) @@ -229,19 +206,193 @@ class Collaboration(): if hasattr(self, 'chattube') and self.chattube is not None: self.chattube.SendText(entry) + def _turtle_request(self, payload): + ''' incoming turtle from a joiner ''' + if payload > 0: + [nick, colors] = data_from_string(payload) + if nick != self._tw.nick: # It is not me. + # There may not be a turtle dictionary. + if hasattr(self._tw, 'remote_turtle_dictionary'): + # Make sure it is not a "rejoin". + if not nick in self._tw.remote_turtle_dictionary: + # Add new turtle for the joiner. + self._tw.canvas.set_turtle(nick, colors) + self._tw.label_remote_turtle(nick, colors) + self._tw.remote_turtle_dictionary[nick] = colors + else: + self._tw.remote_turtle_dictionary = self._get_dictionary() + # Add new turtle for the joiner. + self._tw.canvas.set_turtle(nick, colors) + self._tw.label_remote_turtle(nick, colors) + + # Sharer should send the updated remote turtle dictionary to everyone. + if self.initiating: + if not self._tw.nick in self._tw.remote_turtle_dictionary: + self._tw.remote_turtle_dictionary[self._tw.nick] = \ + self._get_colors() + event_payload = data_to_string(self._tw.remote_turtle_dictionary) + self.send_event('T|' + event_payload) + self._send_my_xy() # And the sender should report her xy position. + + def _receive_turtle_dict(self, payload): + ''' Any time there is a new joiner, an updated turtle dictionary is + circulated. Everyone must report their turtle positions so that we + are in sync. ''' + if self.waiting_for_turtles: + if len(payload) > 0: + # Grab the new remote turtles dictionary. + remote_turtle_dictionary = data_from_string(payload) + # Add see what is new. + for nick in remote_turtle_dictionary: + if nick == self._tw.nick: + debug_output('skipping my nick %s' \ + % (nick), self._tw.running_sugar) + elif nick != self._tw.remote_turtle_dictionary: + # Add new the turtle. + colors = remote_turtle_dictionary[nick] + self._tw.remote_turtle_dictionary[nick] = colors + self._tw.canvas.set_turtle(nick, colors) + # Label the remote turtle. + self._tw.label_remote_turtle(nick, colors) + debug_output('adding %s to remote turtle dictionary' \ + % (nick), self._tw.running_sugar) + else: + debug_output('%s already in remote turtle dictionary' \ + % (nick), self._tw.running_sugar) + self.waiting_for_turtles = False + self._send_my_xy() + + def _send_my_xy(self): + ''' Set xy location so joiner can sync turtle positions. ''' + self._tw.canvas.set_turtle(self._get_nick()) + if self._tw.canvas.pendown: + self.send_event('p|%s' % (data_to_string([self._get_nick(), + False]))) + put_pen_back_down = True + else: + put_pen_back_down = False + self.send_event('x|%s' % (data_to_string([self._get_nick(), + [int(self._tw.canvas.xcor), int(self._tw.canvas.ycor)]]))) + if put_pen_back_down: + self.send_event('p|%s' % (data_to_string([self._get_nick(), + True]))) + self.send_event('r|%s' % (data_to_string([self._get_nick(), + int(self._tw.canvas.heading)]))) + + def _draw_pixbuf(self, payload): + if len(payload) > 0: + [nick, [a, b, x, y, w, h, width, height, data]] =\ + data_from_string(payload) + if nick != self._tw.nick: + if self._tw.running_sugar: + tmp_path = get_path(self._tw.activity, 'instance') + else: + tmp_path = '/tmp' + file_name = base64_to_image(data, tmp_path) + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(file_name, + width, height) + x, y = self._tw.canvas.turtle_to_screen_coordinates(x, y) + self._tw.canvas.draw_pixbuf(pixbuf, a, b, x, y, w, h, + file_name, False) + + def _move_forward(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.forward(x, False) + + def _move_in_arc(self, payload): + if len(payload) > 0: + [nick, [a, r]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.arc(a, r, False) + + def _rotate_turtle(self, payload): + if len(payload) > 0: + [nick, h] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.seth(h, False) + + def _setxy(self, payload): + if len(payload) > 0: + [nick, [x, y]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setxy(x, y, False) + + def _draw_text(self, payload): + if len(payload) > 0: + [nick, [label, x, y, size, w]] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.draw_text(label, x, y, size, w, False) + + def _set_pen_color(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setcolor(x, False) + + def _set_pen_gray_level(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setgray(x, False) + + def _set_pen_shade(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setshade(x, False) + + def _set_pen_width(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setpensize(x, False) + + def _set_pen_state(self, payload): + if len(payload) > 0: + [nick, x] = data_from_string(payload) + if nick != self._tw.nick: + self._tw.canvas.set_turtle(nick) + self._tw.canvas.setpen(x, False) + + def _fill_polygon(self, payload): + # Check to make sure that the poly_point array is passed properly + if len(payload) > 0: + [nick, poly_points] = data_from_string(payload) + shared_poly_points = [] + for i in range(len(poly_points)): + shared_poly_points.append(( + self._tw.canvas.turtle_to_screen_coordinates( + poly_points[i][0], poly_points[i][1]))) + self._tw.canvas.fill_polygon(shared_poly_points) + def _get_dictionary(self): - d = { self._get_nick(): self._get_colors()} - return d + return {self._get_nick(): self._get_colors()} def _get_nick(self): return self._tw.nick def _get_colors(self): - if profile: - colors = profile.get_color().to_string() + colors = None + if self._tw.running_sugar: + if profile.get_color() is not None: + colors = profile.get_color().to_string() else: colors = self._activity.get_colors() - return colors + if colors is None: + colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0], + DEFAULT_TURTLE_COLORS[1]) + return colors.split(',') + class ChatTube(ExportedGObject): @@ -249,7 +400,7 @@ class ChatTube(ExportedGObject): """Class for setting up tube for sharing.""" super(ChatTube, self).__init__(tube, PATH) self.tube = tube - self.is_initiator = is_initiator # Are we sharing or joining activity? + self.is_initiator = is_initiator # Are we sharing or joining activity? self.stack_received_cb = stack_received_cb self.stack = '' -- cgit v0.9.1