Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@member.fsf.org>2009-02-11 21:43:37 (GMT)
committer Aleksey Lim <alsroot@member.fsf.org>2009-02-11 21:43:37 (GMT)
commitc71d67bc8af32cf791a8e6c90102dfd831181644 (patch)
tree915ac82efd02185c3ebc49bd853f0b3b49f7edbf
parenta9dbf044231cd168246df34a23dc28d1dc57a430 (diff)
Implement collabcollab
-rw-r--r--activity.py9
-rw-r--r--messenger.py159
-rw-r--r--model.py132
-rw-r--r--montage.py48
-rw-r--r--screen.py50
-rw-r--r--shared.py2
-rw-r--r--theme.py41
7 files changed, 343 insertions, 98 deletions
diff --git a/activity.py b/activity.py
index 78af8d8..e209e1a 100644
--- a/activity.py
+++ b/activity.py
@@ -22,7 +22,7 @@ from sugar.graphics.toolbutton import ToolButton
import model
import montage
import lessons
-import messenger
+from messenger import Messenger, SERVICE
from shared import SharedActivity
from theme import *
from utils import *
@@ -30,7 +30,9 @@ from utils import *
class flipsticksActivity(SharedActivity):
def __init__(self, handle):
self.notebook = gtk.Notebook()
- SharedActivity.__init__(self, self.notebook, messenger.SERVICE, handle)
+ SharedActivity.__init__(self, self.notebook, SERVICE, handle)
+
+ self.connect('tube', self._tube_cb)
self.notebook.show()
self.notebook.props.show_border = False
@@ -70,6 +72,9 @@ class flipsticksActivity(SharedActivity):
else:
self.notebook.set_current_page(0)
+ def _tube_cb(self, activity, tube_conn, initiating):
+ self.messenger = Messenger(tube_conn, initiating, self.montage)
+
class MontageToolbar(gtk.Toolbar):
def __init__(self, montage):
gtk.Toolbar.__init__(self)
diff --git a/messenger.py b/messenger.py
index 1bad33f..10701c5 100644
--- a/messenger.py
+++ b/messenger.py
@@ -12,6 +12,165 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+import dbus
+import pickle
+import cjson
+import logging
+from dbus.gobject_service import ExportedGObject
+from dbus.service import method, signal
+
+from sugar.presence import presenceservice
+
+import model
+
+logger = logging.getLogger('flipsticks')
+
SERVICE = 'org.freedesktop.Telepathy.Tube.Connect'
IFACE = SERVICE
PATH = '/org/freedesktop/Telepathy/Tube/Connect'
+
+class Slot:
+ def __init__(self, seqno=-1, owner=None):
+ self.seqno = seqno
+ self.owner = owner
+
+class Messenger(ExportedGObject):
+ def __init__(self, tube, initiator, view):
+ ExportedGObject.__init__(self, tube, PATH)
+
+ self.initiator = initiator
+ self._tube = tube
+ self._entered = False
+ self._slots = []
+ self._view = view
+
+ self._view.connect('frame-changed', self._frame_changed_cb)
+ self._tube.watch_participants(self._participant_change_cb)
+
+ def _participant_change_cb(self, added, removed):
+ if not self._entered and added:
+ self.me = self._tube.get_unique_name()
+
+ for i in range(len(model.keys)):
+ self._slots.append(Slot())
+
+ if self.initiator:
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+ for i in self._slots:
+ i.seqno = 0
+ i.owner = self.me
+ else:
+ self._pong_handle = self._tube.add_signal_receiver(
+ self._pong_cb, '_pong', IFACE, path=PATH,
+ sender_keyword='sender')
+ self._ping()
+
+ self._tube.add_signal_receiver(self._notify_cb, '_notify', IFACE,
+ path=PATH, sender_keyword='sender')
+ self._entered = True
+
+ # incomers' signal to retrieve initial snapshot
+ @signal(IFACE, signature='')
+ def _ping(self):
+ logger.debug('send ping')
+ pass
+
+ # object is ready to post snapshot to incomer
+ @signal(IFACE, signature='')
+ def _pong(self):
+ logger.debug('send pong')
+ pass
+
+ # slot was changed
+ @signal(IFACE, signature='iiss')
+ def _notify(self, slot, seqno, sender, raw):
+ pass
+
+ # the whole list of slots for incomers
+ @method(dbus_interface=IFACE, in_signature='', out_signature='a(iss)',
+ sender_keyword='sender')
+ def _snapshot(self, sender=None):
+ logger.debug('_snapshot requested from %s' % sender)
+ out = []
+
+ for i, slot in enumerate(self._slots):
+ out.append((slot.seqno, slot.owner,
+ cjson.encode(model.keys[i].collect())))
+
+ return out
+
+ def _ping_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_ping received from %s' % sender)
+ self._pong()
+
+ def _pong_cb(self, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_pong sent from %s' % sender)
+
+ # we've got source for _snapshot and don't need _pong anymore
+ self._tube.remove_signal_receiver(self._pong_handle)
+ self._pong_handle = None
+
+ remote = self._tube.get_object(sender, PATH)
+ rawlist = remote._snapshot()
+
+ logger.debug('snapshot received len=%d' % len(rawlist))
+
+ for i, (seqno, owner, raw) in enumerate(rawlist):
+ self._receive(i, seqno, owner, raw, sender)
+
+ # we are ready to receive _snapshot requests
+ self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE,
+ path=PATH, sender_keyword='sender')
+
+ def _notify_cb(self, slot, seqno, owner, raw, sender=None):
+ if sender == self.me:
+ return
+ logger.debug('_notify requested from %s' % sender)
+ self._receive(slot, seqno, owner, raw, sender)
+
+ def _receive(self, slot, seqno, owner, raw, sender):
+ cur = self._slots[slot]
+ new = Slot(seqno, owner)
+
+ logger.debug('object received slot=%s seqno=%d owner=%s from %s'
+ % (slot, new.seqno, new.owner, sender))
+
+ if cur.seqno > new.seqno:
+ logger.debug('trying to rewrite newer value by older one')
+ return
+ elif cur.seqno == new.seqno:
+ # arrived value was sent at the same time as current one
+ if cur.owner > new.owner:
+ logger.debug('current value is higher ranked then arrived')
+ return
+ if cur.owner == self.me:
+ # we sent current and arrived value rewrites it
+ logger.debug('resend current with higher seqno')
+ self._send(slot)
+ return
+ else:
+ logger.debug('just discard low rank')
+ return
+ else:
+ logger.debug('accept higher seqno')
+
+ self._view.props.keyframe = (slot, model.StoredFrame(cjson.decode(raw)))
+ self._slots[slot] = new
+
+ def _send(self, slot_num):
+ slot = self._slots[slot_num]
+ slot.seqno += 1
+ slot.owner = self.me
+
+ self._notify(slot_num, slot.seqno, slot.owner,
+ cjson.encode(model.keys[slot_num].collect()))
+
+ logger.debug('_send slot=%s seqno=%d' % (slot_num, slot.seqno))
+
+ def _frame_changed_cb(self, sender, slot):
+ self._send(slot)
diff --git a/model.py b/model.py
index 479af62..6d34dfb 100644
--- a/model.py
+++ b/model.py
@@ -13,28 +13,81 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import os
-import pickle
+import cjson
+import math
-import theme
-import kinematic
+from theme import *
keys = []
class KeyFrame:
- def __init__(self):
- self.clear()
-
def empty(self):
return self.joints == None
- def assign(self, x):
- self.middle = x.middle
- self.parts = x.parts.copy()
- self.sticks = x.sticks.copy()
- self.joints = x.joints.copy()
- self.scaled_sticks = x.get_scaled_sticks()
- self.scaled_joints = x.get_scaled_joints(self.x,
- int(theme.KEYFRAMEHEIGHT/2.0))
+ def _setjoints(self, joints, sticks, middle):
+ if self.empty():
+ return
+
+ # have to traverse in order because
+ # parent joints must be set right
+ for stickname in STICKLIST:
+ (angle,len) = sticks[stickname]
+ jname = JOINTS[stickname]
+ (x,y) = getparentjoint(jname, joints, middle)
+ parents = getparentsticks(stickname)
+ panglesum = 0
+ for parentname in parents:
+ (pangle,plen) = sticks[parentname]
+ panglesum += pangle
+ (nx,ny) = self._getpoints(x,y,angle+panglesum,len)
+ joints[jname] = (nx,ny)
+
+ def _getpoints(self, x, y, angle, len):
+ nx = int(round(x + (len * math.cos(math.radians(angle)))))
+ ny = int(round(y - (len * math.sin(math.radians(angle)))))
+ return (nx,ny)
+
+ def _initjoints(self):
+ joints = {}
+ for stickname in JOINTS:
+ jname = JOINTS[stickname]
+ joints[jname] = (0,0)
+ return joints
+
+class StoredFrame(KeyFrame):
+ def __init__(self, data=None):
+ if not data:
+ self.clear()
+ else:
+ def array2tuple(a):
+ return a and (a[0], a[1])
+
+ def hash2tuple(h):
+ if not h:
+ return None
+ out = {}
+ for i, j in h.items():
+ out[i] = array2tuple(j)
+ return out
+
+ self.x = scale_keyframe(data['x'])
+ self.middle = scale_middle(data['middle'])
+ self.parts = data['parts']
+ self.sticks = hash2tuple(data['sticks'])
+ self.joints = hash2tuple(data['joints'])
+ self._make_thumbs()
+ self.setjoints()
+
+ def collect(self):
+ return { 'x' : unscale_keyframe(self.x),
+ 'middle' : unscale_middle(self.middle),
+ 'parts' : self.parts,
+ 'sticks' : self.sticks,
+ 'joints' : self.joints }
+
+ def setjoints(self):
+ if not self.empty():
+ self._setjoints(self.joints, self.sticks, self.middle)
def clear(self):
self.middle = None
@@ -51,34 +104,43 @@ class KeyFrame:
self.scaled_joints[jname] = (jx+dx, jy)
self.x += dx
+ def assign(self, x):
+ self.middle = x.middle
+ self.parts = x.parts.copy()
+ self.sticks = x.sticks.copy()
+ self.joints = x.joints.copy()
+ self._make_thumbs()
+
+ def _make_thumbs(self):
+ if self.empty():
+ self.scaled_sticks = None
+ self.scaled_joints = None
+ return
+
+ self.scaled_sticks = self.sticks.copy()
+ self.scaled_joints = self._initjoints()
+
+ for key in self.scaled_sticks:
+ (angle,len) = self.scaled_sticks[key]
+ newlen = int(len * .2)
+ self.scaled_sticks[key] = (angle,newlen)
+
+ self._setjoints(self.scaled_joints, self.scaled_sticks,
+ (self.x, KEYFRAMEHEIGHT/2))
+
def save(filename):
out = []
for i in keys:
- out.append({
- 'x' : i.x,
- 'middle' : i.middle,
- 'parts' : i.parts,
- 'sticks' : i.sticks,
- 'joints' : i.joints,
- 'scaled_sticks' : i.scaled_sticks,
- 'scaled_joints' : i.scaled_joints })
+ out.append(i.collect())
- file(filename, 'w').write(pickle.dumps(out))
+ file(filename, 'w').write(cjson.encode(out))
def load(filename):
- inc = pickle.loads(file(filename, 'r').read())
+ inc = cjson.decode(file(filename, 'r').read())
for i, data in enumerate(inc):
- key = keys[i]
-
- key.x = data['x']
- key.middle = data['middle']
- key.parts = data['parts']
- key.sticks = data['sticks']
- key.joints = data['joints']
- key.scaled_sticks = data['scaled_sticks']
- key.scaled_joints = data['scaled_joints']
+ keys[i] = StoredFrame(data)
def getparentsticks(stickname):
if stickname in ['RIGHT SHOULDER','LEFT SHOULDER','NECK','TORSO']:
@@ -158,7 +220,7 @@ def _get_base64_pixbuf_data(pixbuf):
return base64.b64encode(str(data[0]))
for i in range(5):
- key = KeyFrame()
- keyframe_width = theme.KEYFRAMEWIDTH/5
+ key = StoredFrame()
+ keyframe_width = KEYFRAMEWIDTH/5
key.x = keyframe_width/2 + i*keyframe_width
keys.append(key)
diff --git a/montage.py b/montage.py
index b2d8030..db02a97 100644
--- a/montage.py
+++ b/montage.py
@@ -22,6 +22,7 @@ import gtk
import math
import gobject
import logging
+from gobject import SIGNAL_RUN_FIRST, TYPE_PYOBJECT
from gettext import gettext as _
from sugar.activity.activity import get_bundle_path
@@ -35,6 +36,20 @@ from theme import *
logger = logging.getLogger('flipsticks')
class View(gtk.EventBox):
+ __gsignals__ = {
+ 'frame-changed' : (SIGNAL_RUN_FIRST, None, [TYPE_PYOBJECT]) }
+
+ def set_keyframe(self, value):
+ i, key = value
+ logger.debug('set_keyframe[%d]=%s' % (i, key and key.collect()))
+ if not key:
+ model.keys[i].clear()
+ else:
+ model.keys[i] = key
+ self.restore()
+
+ keyframe = gobject.property(type=object, getter=None, setter=set_keyframe)
+
def reset(self):
self.key.reset()
self.selectstickebox()
@@ -43,10 +58,12 @@ class View(gtk.EventBox):
def setframe(self):
model.keys[self.kfselected].assign(self.key)
self.drawkeyframe()
+ self.emit('frame-changed', self.kfselected)
def clearframe(self):
model.keys[self.kfselected].clear()
self.drawkeyframe()
+ self.emit('frame-changed', self.kfselected)
def setplayspeed(self, value):
self.waittime = int((100-value)*5)
@@ -113,17 +130,6 @@ class View(gtk.EventBox):
model.screen_shot(pixbuf)
def restore(self):
- # keep keyframes on screen after decreasing screen resolution
- for i in model.keys:
- if i.x >= KEYFRAMEWIDTH-KEYFRAME_RADIUS:
- i.move(KEYFRAMEWIDTH-KEYFRAME_RADIUS - i.x)
-
- if i.middle[0] >= DRAWWIDTH or i.middle[1] >= DRAWHEIGHT:
- tmp = screen.ScreenFrame()
- i.middle = (DRAWWIDTH/2, DRAWHEIGHT/3)
- tmp.assign(i)
- i.assign(tmp)
-
self.drawkeyframe()
self.syncmaintokf()
self.updateentrybox()
@@ -245,6 +251,17 @@ class View(gtk.EventBox):
if frame.x + xdiff > KEYFRAME_RADIUS \
and frame.x + xdiff < KEYFRAMEWIDTH-KEYFRAME_RADIUS:
frame.move(xdiff)
+
+ if self._emit_move_handle:
+ gobject.source_remove(self._emit_move_handle)
+ if self._emit_move_key != self.kfpressed:
+ self._emit_move(self._emit_move_key)
+
+ self._emit_move_key = self.kfpressed
+ self._emit_move_handle = gobject.timeout_add(
+ MOVEMIT_TIMEOUT, self._emit_move,
+ self.kfpressed)
+
self.drawkeyframe()
self.kf_mouse_pos = x
return True
@@ -571,6 +588,7 @@ class View(gtk.EventBox):
self.toplevel = activity
self.stickselected = 'RIGHT SHOULDER'
self.pixmap = None
+ self._emit_move_handle = None
self.setplayspeed(50)
@@ -602,6 +620,7 @@ class View(gtk.EventBox):
| gtk.gdk.BUTTON_RELEASE_MASK
| gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.POINTER_MOTION_HINT_MASK)
+ self.mfdraw.set_size_request(DRAWWIDTH, DRAWHEIGHT)
screen_box = gtk.EventBox()
screen_box.modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse(BACKGROUND))
@@ -732,7 +751,7 @@ class View(gtk.EventBox):
hdesktop = gtk.HBox()
hdesktop.pack_start(leftbox, False)
- hdesktop.pack_start(screen_pink)
+ hdesktop.pack_start(screen_pink, False, False)
desktop = gtk.VBox()
desktop.pack_start(hdesktop)
@@ -793,6 +812,11 @@ class View(gtk.EventBox):
self.sizeentry.set_text(str(int(adj.value)))
self.enterlen_callback(None, self.sizeentry)
+ def _emit_move(self, key):
+ self._emit_move_handle = None
+ self.emit('frame-changed', key)
+ return False
+
def _inarea(widget, x, y):
x_, y_, width, height = widget.get_allocation()
if x < 0 or y < 0 or x >= width or y >= height:
diff --git a/screen.py b/screen.py
index 31ba47f..bd31199 100644
--- a/screen.py
+++ b/screen.py
@@ -12,22 +12,20 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import math
-
import theme
import model
-class ScreenFrame:
+class ScreenFrame(model.KeyFrame):
def __init__(self):
self.reset()
def setjoints(self):
- _setjoints(self.joints, self.sticks, self.middle)
+ self._setjoints(self.joints, self.sticks, self.middle)
def reset(self):
self.parts = theme.PARTS.copy()
self.sticks = theme.STICKS.copy()
- self.joints = _initjoints()
+ self.joints = self._initjoints()
self.middle = (theme.DRAWWIDTH/2, theme.DRAWHEIGHT/3)
self.setjoints()
@@ -38,23 +36,10 @@ class ScreenFrame:
self.joints = x.joints.copy()
self.setjoints()
- def get_scaled_sticks(self):
- out = self.sticks.copy()
- for key in out:
- (angle,len) = out[key]
- newlen = int(len * .2)
- out[key] = (angle,newlen)
- return out
-
- def get_scaled_joints(self, x, y):
- out = _initjoints()
- _setjoints(out, self.get_scaled_sticks(), (x, y))
- return out
-
def getrotatepoint(self):
(angle,len) = self.sticks['TORSO']
x,y = self.middle
- (rx,ry) = _getpoints(x,y,angle,int(len/2.0))
+ (rx,ry) = self._getpoints(x,y,angle,int(len/2.0))
return (rx,ry)
def inrotate(self, x, y):
@@ -81,30 +66,3 @@ class ScreenFrame:
for jname in self.joints:
(jx, jy) = self.joints[jname]
self.joints[jname] = (jx+dx, jy+dy)
-
-def _initjoints():
- joints = {}
- for stickname in theme.JOINTS:
- jname = theme.JOINTS[stickname]
- joints[jname] = (0,0)
- return joints
-
-def _setjoints(joints, sticks, middle):
- # have to traverse in order because
- # parent joints must be set right
- for stickname in theme.STICKLIST:
- (angle,len) = sticks[stickname]
- jname = theme.JOINTS[stickname]
- (x,y) = model.getparentjoint(jname, joints, middle)
- parents = model.getparentsticks(stickname)
- panglesum = 0
- for parentname in parents:
- (pangle,plen) = sticks[parentname]
- panglesum += pangle
- (nx,ny) = _getpoints(x,y,angle+panglesum,len)
- joints[jname] = (nx,ny)
-
-def _getpoints(x,y,angle,len):
- nx = int(round(x + (len * math.cos(math.radians(angle)))))
- ny = int(round(y - (len * math.sin(math.radians(angle)))))
- return (nx,ny)
diff --git a/shared.py b/shared.py
index 3ca51ca..6725cf0 100644
--- a/shared.py
+++ b/shared.py
@@ -19,7 +19,7 @@ from gobject import property, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
from sugar.activity.activity import Activity
from sugar.presence.sugartubeconn import SugarTubeConnection
-logger = logging.getLogger('cartoon-builder')
+logger = logging.getLogger('flipsticks')
class CanvasActivity(Activity):
__gsignals__ = {
diff --git a/theme.py b/theme.py
index 5bf4b07..3d3b404 100644
--- a/theme.py
+++ b/theme.py
@@ -14,6 +14,8 @@
import gtk
+from sugar.graphics import style
+
GRAY = "#B7B7B7" # gray
PINK = "#FF0099" # pink
YELLOW = "#FFFF00" # yellow
@@ -96,12 +98,13 @@ LANG = {'English':{'size':'Size',
PAD = 10
LOGO_WIDTH = 276
+TOLLBAR_HEIGHT = style.LARGE_ICON_SIZE
KEYFRAMEWIDTH = gtk.gdk.screen_width() - PAD*3
KEYFRAMEHEIGHT = 80
DRAWWIDTH = gtk.gdk.screen_width() - LOGO_WIDTH - PAD*4
-DRAWHEIGHT = gtk.gdk.screen_height() - KEYFRAMEHEIGHT - PAD*5
+DRAWHEIGHT = gtk.gdk.screen_height() - KEYFRAMEHEIGHT - PAD*6 - TOLLBAR_HEIGHT
KEYFRAMES = []
KEYFRAMES_NUMBER = 5
@@ -109,10 +112,45 @@ TOTALFRAMES = 30
KEYFRAME_RADIUS = 40
+MOVEMIT_TIMEOUT = 1000
+
for i in range(KEYFRAMES_NUMBER):
keyframe_width = KEYFRAMEWIDTH/KEYFRAMES_NUMBER
KEYFRAMES.append(keyframe_width/2 + i*keyframe_width)
+# scale coordinates between native resolution and transportable
+
+TRANSFER_DRAWWIDTH = 350
+TRANSFER_DRAWHEIGHT = 350
+TRANSFER_KEYFRAMEWIDTH = 600
+
+def scale_keyframe(x):
+ factor = float(TRANSFER_KEYFRAMEWIDTH) / (KEYFRAMEWIDTH)
+ x = max(KEYFRAME_RADIUS, int(x/factor))
+ x = min(KEYFRAMEWIDTH-KEYFRAME_RADIUS-1, x)
+ return x
+
+def scale_middle(middle):
+ if not middle:
+ return None
+ x_factor = float(TRANSFER_DRAWWIDTH) / DRAWWIDTH
+ y_factor = float(TRANSFER_DRAWHEIGHT) / DRAWHEIGHT
+ return (min(DRAWWIDTH-1, int(middle[0]/x_factor)),
+ min(DRAWHEIGHT-1, int(middle[1]/y_factor)))
+
+def unscale_keyframe(x):
+ factor = float(TRANSFER_KEYFRAMEWIDTH) / (KEYFRAMEWIDTH)
+ return int(x*factor)
+
+def unscale_middle(middle):
+ if not middle:
+ return None
+ x_factor = float(TRANSFER_DRAWWIDTH) / DRAWWIDTH
+ y_factor = float(TRANSFER_DRAWHEIGHT) / DRAWHEIGHT
+ return (int(middle[0]*x_factor), int(middle[1]*y_factor))
+
+# defaults
+
STICKS = {'HEAD':(0,15),
'NECK':(90,15),
'RIGHT SHOULDER':(185,25),
@@ -134,7 +172,6 @@ STICKS = {'HEAD':(0,15),
PARTS = {'RIGHT HAND':14,
'LEFT HAND':14}
-
STICKLIST = ['NECK','HEAD','RIGHT SHOULDER','UPPER RIGHT ARM','LOWER RIGHT ARM',
'LEFT SHOULDER','UPPER LEFT ARM','LOWER LEFT ARM','TORSO',
'RIGHT HIP','UPPER RIGHT LEG','LOWER RIGHT LEG','RIGHT FOOT',