Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tests/sketch/sketchactivity.py
blob: 007d96e54549a70ecb8bb83704c6de195a3209b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# Copyright (C) 2006, Red Hat, Inc.
#
# 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

import gtk
import gobject
import os
import logging

from sugar.p2p import MostlyReliablePipe
from sugar.p2p.Stream import Stream

from sugar.presence import PresenceService
from sugar.activity.Activity import Activity
from sugar.chat.sketchpad import SketchPad
from sugar.chat.sketchpad import Sketch
from sugar.graphics.iconcolor import IconColor
from sugar import profile

class NetworkController(gobject.GObject):
	__gsignals__ = {
		'new-path':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
				   ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
	}

	def __init__(self, parent, ps_owner):
		gobject.GObject.__init__(self)
		self._parent = parent
		self._parent.connect('buddy-joined', self._buddy_joined)
		self._parent.connect('buddy-left', self._buddy_left)
		self._stream = None
		self._stream_writer = None
		self._joined_buddies = {}  # IP address -> buddy
		self._ps_owner = ps_owner

	def init_stream(self, service):
		self._stream = Stream.new_from_service(service)
		self._stream.set_data_listener(self._recv_message)
		self._stream_writer = self._stream.new_writer()

	def _recv_message(self, address, msg):
		# Ignore multicast messages from ourself
		if self._ps_owner and address == self._ps_owner.get_ip4_address():
			return

		# Ensure the message comes from somebody in this activity
		if not self._joined_buddies.has_key(address):
			logging.debug("Message from unjoined buddy.")
			return

		# Convert the points to an array and send to the sketchpad
		points = []
		msg = msg.strip()
		split_coords = msg.split(" ")
		for item in split_coords:
			x = 0
			y = 0
			try:
				(x, y) = item.split(",")
				x = float(x)
				y = float(y)
			except ValueError:
				continue
			if x < 0 or y < 0:
				continue
			points.append((x, y))

		buddy = self._joined_buddies[address]
		self.emit("new-path", buddy, points)

	def _buddy_joined(self, widget, activity, buddy, activity_type):
		activity_service = buddy.get_service_of_type(activity_type, activity)
		if not activity_service:
			logging.debug("Buddy Joined, but could not get activity service " \
					"of %s" % activity_type)
			return

		address = activity_service.get_source_address()
		port = activity_service.get_port()
		if not address or not port:
			logging.debug("Buddy Joined, but could not get address/port from" \
						" activity service %s" % activity_type)
			return
		if not self._joined_buddies.has_key(address):
			logging.debug("Buddy joined: %s (%s)" % (address, port))
			self._joined_buddies[address] = buddy

	def _buddy_left(self, widget, activity, buddy, activity_type):
		buddy_key = None
		for (key, value) in self._joined_buddies.items():
			if value == buddy:
				buddy_key = key
				break
		if buddy_key:
			del self._joined_buddies[buddy_key]

	def new_local_sketch(self, path):
		""" Receive an array of point tuples the local user created """
		cmd = ""
		# Convert points into the wire format
		for point in path:
			cmd = cmd + "%d,%d " % (point[0], point[1])

		# If there were no points, or we aren't in a shared activity yet,
		# don't send anything
		if not len(cmd) or not self._stream_writer:
			return

		# Send the points to other buddies
		self._stream_writer.write(cmd)

def _html_to_rgb_color(colorstring):
	""" converts #RRGGBB to cairo-suitable floats"""
	colorstring = colorstring.strip()
	while colorstring[0] == '#':
		colorstring = colorstring[1:]
	r = int(colorstring[:2], 16)
	g = int(colorstring[2:4], 16)
	b = int(colorstring[4:6], 16)
	color = ((float(r) / 255.0), (float(g) / 255.0), (float(b) / 255.0))
	return color


class SharedSketchPad(SketchPad.SketchPad):
	def __init__(self, net_controller, color):
		SketchPad.SketchPad.__init__(self, bgcolor=(1.0, 0.984313725, 0.560784314))
		self._net_controller = net_controller
		self._user_color = _html_to_rgb_color(color)
		self.set_color(self._user_color)

		# Receive notifications when our buddies send us new sketches
		self._net_controller.connect('new-path', self._new_buddy_path)

		self.connect('new-user-sketch', self._new_local_sketch_cb)

	def _new_buddy_path(self, net_controller, buddy, path):
		""" Called whenever a buddy on the mesh sends us a new sketch path """
		str_color = buddy.get_color()
		if not str_color:
			str_color = "#348798"  # FIXME
		color = IconColor(str_color)
		stroke_color = _html_to_rgb_color(color.get_stroke_color())
		sketch = Sketch.Sketch(stroke_color)
		for item in path:
			sketch.add_point(item[0], item[1])
		self.add_sketch(sketch)

	def _new_local_sketch_cb(self, widget, sketch):
		""" Send the sketch the user just made to the network """
		self._net_controller.new_local_sketch(sketch.get_points())


class SketchActivity(Activity):
	__gsignals__ = {
		'buddy-joined':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
					     ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
		'buddy-left':  (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
					     ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
	}

	def __init__(self):
		Activity.__init__(self)
		self.connect('destroy', self._cleanup_cb)
	
		self.set_title("Sketch")

		self._ps = PresenceService.get_instance()
		self._ps_activity = None
		self._owner = self._ps.get_owner()
		
		self._net_controller = NetworkController(self, self._owner)
		self._sketchpad = SharedSketchPad(self._net_controller,
								profile.get_color().get_stroke_color())
		self.add(self._sketchpad)
		self.show_all()

	def get_ps(self):
		return self._ps

	def _cleanup_cb(self):
		del self._net_controller

	def share(self):
		Activity.share(self)
		self._net_controller.init_stream(self._service)
		self._ps.connect('activity-appeared', self._activity_appeared_cb)

	def join(self, activity_ps):
		Activity.join(self, activity_ps)
		self._net_controller.init_stream(self._service)
		self._ps.connect('activity-appeared', self._activity_appeared_cb)
		self._activity_appeared_cb(self._ps, activity_ps)

	def _activity_appeared_cb(self, ps, activity):
		# Only care about our own activity
		if activity.get_id() != self.get_id():
			return

		# If we already have found our shared activity, do nothing
		if self._ps_activity:
			return

		self._ps_activity = activity

		# Connect signals to the shared activity so we are notified when
		# buddies join and leave
		self._ps_activity.connect('buddy-joined', self._add_buddy)
		self._ps_activity.connect('buddy-left', self._remove_buddy)

		# Get the list of buddies already in this shared activity so we can
		# connect to them
		buddies = self._ps_activity.get_joined_buddies()
		for buddy in buddies:
			self._add_buddy(self._ps_activity, buddy)

	def _add_buddy(self, ps_activity, buddy):
		service_type = self._ps_activity
		self.emit('buddy-joined', ps_activity, buddy, self.get_default_type())

	def _remove_buddy(self, ps_activity, buddy):
		self.emit('buddy-left', ps_activity, buddy, self.get_default_type())