From aa2d1d42f01f87b5a0a7e533bb6a93d3720b9d1a Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Mon, 21 Feb 2011 23:00:50 +0000 Subject: Merge git.sugarlabs.org:~walter/turtleart/collaboration-refactoring Conflicts: NEWS TurtleArt/talogo.py --- diff --git a/NEWS b/NEWS index e317257..5c5c498 100644 --- a/NEWS +++ b/NEWS @@ -2,19 +2,28 @@ BUG FIXES +* Added sharing to draw_text, fill_polygon, draw_pixbuf (#2461) * Exposed see as an external method (#2542) * Media type tests on file suffix use lower() -* Uninitialized variables in Show block for numeric arguments (#2543) -* Catch potential zero-divide in set_gray method (#2545) -* Fixed regression with show Journal object thumbnails +* Added support for localization to GNOME version (rgs) +* Work around for situations where gst is not available +* Fixed problem with displaying Journal preview images in portfolio +* Restore overlay grids on clear +* Fixed problem with help-string wrap width ENHANCEMENTS -* Code clean up by RGS -* Added 'brightness' block for reading camera luminance level +* Added a Media Palette for all media-related blocks +* Added sharing between Gnome and Sugar versions (with Raul Gutierrez) +* Added 'time' block for measuring elapsed time (in seconds) * Added 'camera sees' block for reading average camera RGB value + (with help from Tony Forster and Guzman Trinidad) * Added camera media block for grabbing images from the camera * New psuedo-color.ta example (Tony Forster) +* New love-speaks-volumes.ta example +* New spiralaterals.ta example inspired by Spiralaterals activity +* More complete translations in Spanish (es) and Italian (it) +* Added plugin support for non-standard devices (camera, sensors, RFID) 105 diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index d4395a2..79bdd04 100644 --- a/TurtleArt/tacanvas.py +++ b/TurtleArt/tacanvas.py @@ -1,5 +1,5 @@ #Copyright (c) 2007-8, Playful Invention Company. -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, Walter Bender #Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,10 +24,11 @@ import gtk from math import sin, cos, pi import pango import cairo +import base64 from sprites import Sprite from tasprite_factory import SVG -from tautils import image_to_base64, data_to_string, round_int +from tautils import image_to_base64, get_path, data_to_string, round_int from taconstants import CANVAS_LAYER, BLACK, WHITE import logging @@ -43,6 +44,23 @@ def wrap100(n): return n +def calc_poly_bounds(poly_points): + """ Calculate the minx, miny, width, height of polygon """ + minx = poly_points[0][0] + miny = poly_points[0][1] + maxx, maxy = minx, miny + for p in poly_points: + if p[0] < minx: + minx = p[0] + elif p[0] > maxx: + maxx = p[0] + if p[1] < miny: + miny = p[1] + elif p[1] > maxy: + maxy = p[1] + return(minx, miny, maxx - minx, maxy - miny) + + def calc_shade(c, s, invert=False): """ Convert a color to the current shade (lightness/darkness). """ # Assumes 16 bit input values @@ -148,27 +166,24 @@ class TurtleGraphics: self.fill = False if len(self.poly_points) == 0: return - minx = self.poly_points[0][0] - miny = self.poly_points[0][1] - maxx = minx - maxy = miny - for p in self.poly_points: - if p[0] < minx: - minx = p[0] - elif p[0] > maxx: - maxx = p[0] - if p[1] < miny: - miny = p[1] - elif p[1] > maxy: - maxy = p[1] - w = maxx - minx - h = maxy - miny - self.canvas.images[0].draw_polygon(self.gc, True, self.poly_points) + self.fill_polygon(self.poly_points) + if self.tw.sharing(): + shared_poly_points = [] + for p in self.poly_points: + shared_poly_points.append((self.screen_to_turtle_coordinates( + p[0], p[1]))) + event = "F|%s" % (data_to_string([self._get_my_nick(), + shared_poly_points])) + self.tw.send_event(event) + self.poly_points = [] + + def fill_polygon(self, poly_points): + minx, miny, w, h = calc_poly_bounds(poly_points) + self.canvas.images[0].draw_polygon(self.gc, True, poly_points) self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3, miny - self.pensize * self.tw.coord_scale / 2 - 3, w + self.pensize * self.tw.coord_scale + 6, h + self.pensize * self.tw.coord_scale + 6) - self.poly_points = [] def clearscreen(self, share=True): """Clear the canvas and reset most graphics attributes to defaults.""" @@ -215,13 +230,14 @@ class TurtleGraphics: self.move_turtle() if self.tw.saving_svg and self.pendown: self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) + self.invert_y_coordinate(oldy)) self.tw.svg_string += self.svg.line_to(self.xcor, - self.height / 2 - self.ycor) + self.invert_y_coordinate(self.ycor)) self.tw.svg_string += "\"\n" self.tw.svg_string += self.svg.style() - event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "f|%s" % (data_to_string([self._get_my_nick(), int(n)])) + self.tw.send_event(event) def seth(self, n, share=True): """ Set the turtle heading. """ @@ -232,8 +248,10 @@ class TurtleGraphics: return self.heading %= 360 self.turn_turtle() - event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "r|%s" % (data_to_string([self._get_my_nick(), + round_int(self.heading)])) + self.tw.send_event(event) def right(self, n, share=True): """ Rotate turtle clockwise """ @@ -244,8 +262,10 @@ class TurtleGraphics: return self.heading %= 360 self.turn_turtle() - event = "r|%s" % (data_to_string([self._get_my_nick(), round_int(self.heading)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "r|%s" % (data_to_string([self._get_my_nick(), + round_int(self.heading)])) + self.tw.send_event(event) def arc(self, a, r, share=True): """ Draw an arc """ @@ -260,8 +280,10 @@ class TurtleGraphics: _logger.debug("bad value sent to %s" % (__name__)) return self.move_turtle() - event = "a|%s" % (data_to_string([self._get_my_nick(), [round_int(a), round_int(r)]])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "a|%s" % (data_to_string([self._get_my_nick(), + [round_int(a), round_int(r)]])) + self.tw.send_event(event) def rarc(self, a, r): """ draw a clockwise arc """ @@ -274,8 +296,7 @@ class TurtleGraphics: oldx, oldy = self.xcor, self.ycor cx = self.xcor + r * cos(self.heading * DEGTOR) cy = self.ycor - r * sin(self.heading * DEGTOR) - x = self.width / 2 + int(cx - r) - y = self.height / 2 - int(cy + r) + x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r)) w = int(2 * r) h = w if self.pendown: @@ -290,9 +311,10 @@ class TurtleGraphics: self.ycor = cy + r * sin(self.heading * DEGTOR) if self.tw.saving_svg and self.pendown: self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) + self.invert_y_coordinate(oldx)) self.tw.svg_string += self.svg.arc_to(self.xcor, - self.height / 2 - self.ycor, r, a, 0, s) + self.invert_y_coordinate(self.ycor), + r, a, 0, s) self.tw.svg_string += "\"\n" self.tw.svg_string += self.svg.style() @@ -307,8 +329,7 @@ class TurtleGraphics: oldx, oldy = self.xcor, self.ycor cx = self.xcor - r * cos(self.heading * DEGTOR) cy = self.ycor + r * sin(self.heading * DEGTOR) - x = self.width / 2 + int(cx - r) - y = self.height / 2 - int(cy + r) + x, y = self.turtle_to_screen_coordinates(int(cx - r), int(cy + r)) w = int(2 * r) h = w if self.pendown: @@ -324,9 +345,9 @@ class TurtleGraphics: self.ycor = cy - r * sin(self.heading * DEGTOR) if self.tw.saving_svg and self.pendown: self.tw.svg_string += self.svg.new_path(oldx, - self.height / 2 - oldy) + self.invert_y_coordinate(oldy)) self.tw.svg_string += self.svg.arc_to(self.xcor, - self.height / 2 - self.ycor, + self.invert_y_coordinate(self.ycor), r, a, 0, s) self.tw.svg_string += "\"\n" self.tw.svg_string += self.svg.style() @@ -347,8 +368,10 @@ class TurtleGraphics: self.draw_line(oldx, oldy, self.xcor, self.ycor) self.move_turtle() - event = "x|%s" % (data_to_string([self._get_my_nick(), [round_int(x), round_int(y)]])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "x|%s" % (data_to_string([self._get_my_nick(), + [round_int(x), round_int(y)]])) + self.tw.send_event(event) def setpensize(self, ps, share=True): """ Set the pen size """ @@ -361,10 +384,12 @@ class TurtleGraphics: return self.tw.active_turtle.set_pen_size(ps) self.gc.set_line_attributes(int(self.pensize * self.tw.coord_scale), - gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER) + gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER) self.svg.set_stroke_width(self.pensize) - event = "w|%s" % (data_to_string([self._get_my_nick(), round_int(ps)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "w|%s" % (data_to_string([self._get_my_nick(), + round_int(ps)])) + self.tw.send_event(event) def setcolor(self, c, share=True): """ Set the pen color """ @@ -377,8 +402,10 @@ class TurtleGraphics: self.tw.active_turtle.set_color(c) self.set_fgcolor() self.set_textcolor() - event = "c|%s" % (data_to_string([self._get_my_nick(), round_int(c)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "c|%s" % (data_to_string([self._get_my_nick(), + round_int(c)])) + self.tw.send_event(event) def setgray(self, g, share=True): """ Set the gray level """ @@ -394,10 +421,12 @@ class TurtleGraphics: self.set_fgcolor() self.set_textcolor() self.tw.active_turtle.set_gray(self.gray) - event = "g|%s" % (data_to_string([self._get_my_nick(), round_int(self.gray)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "g|%s" % (data_to_string([self._get_my_nick(), + round_int(self.gray)])) + self.tw.send_event(event) - def settextcolor(self, c): + def settextcolor(self, c): # depreciated """ Set the text color """ try: self.tcolor = c @@ -423,8 +452,10 @@ class TurtleGraphics: self.tw.active_turtle.set_shade(s) self.set_fgcolor() self.set_textcolor() - event = "s|%s" % (data_to_string([self._get_my_nick(), round_int(s)])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "s|%s" % (data_to_string([self._get_my_nick(), + round_int(s)])) + self.tw.send_event(event) def fillscreen(self, c, s): """ Fill screen with color/shade and reset to defaults """ @@ -479,10 +510,11 @@ class TurtleGraphics: """ Lower or raise the pen """ self.pendown = bool self.tw.active_turtle.set_pen_state(bool) - event = "p|%s" % (data_to_string([self._get_my_nick(), bool])) - self._send_event(event, share) + if self.tw.sharing() and share: + event = "p|%s" % (data_to_string([self._get_my_nick(), bool])) + self.tw.send_event(event) - def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path): + def draw_pixbuf(self, pixbuf, a, b, x, y, w, h, path, share=True): """ Draw a pixbuf """ w *= self.tw.coord_scale h *= self.tw.coord_scale @@ -492,12 +524,31 @@ class TurtleGraphics: if self.tw.running_sugar: # In Sugar, we need to embed the images inside the SVG self.tw.svg_string += self.svg.image(x - self.width / 2, - y, w, h, path, image_to_base64(pixbuf, self.tw.activity)) + y, w, h, path, image_to_base64(pixbuf, + get_path(self.tw.activity, 'instance'))) else: + # Outside of Sugar, we save a path self.tw.svg_string += self.svg.image(x - self.width / 2, y, w, h, path) - - def draw_text(self, label, x, y, size, w): + if self.tw.sharing() and share: + if self.tw.running_sugar: + tmp_path = get_path(self.tw.activity, 'instance') + else: + tmp_path = '/tmp' + data = image_to_base64(pixbuf, tmp_path) + height = pixbuf.get_height() + width = pixbuf.get_width() + x, y = self.screen_to_turtle_coordinates(x, y) + event = "P|%s" % (data_to_string([self._get_my_nick(), + [round_int(a), round_int(b), + round_int(x), round_int(y), + round_int(w), round_int(h), + round_int(width), + round_int(height), + data]])) + self.tw.send_event(event) + + def draw_text(self, label, x, y, size, w, share=True): """ Draw text """ w *= self.tw.coord_scale self.gc.set_foreground(self.tw.textcolor) @@ -532,27 +583,47 @@ class TurtleGraphics: if self.tw.saving_svg and self.pendown: self.tw.svg_string += self.svg.text(x - self.width / 2, y + size, size, w, label) + if self.tw.sharing() and share: + event = "W|%s" % (data_to_string([self._get_my_nick(), + [label, round_int(x), + round_int(y), round_int(size), + round_int(w)]])) + self.tw.send_event(event) + + def turtle_to_screen_coordinates(self, x, y): + """ The origin of turtle coordinates is the center of the screen """ + return self.width / 2 + x, self.invert_y_coordinate(y) + + def screen_to_turtle_coordinates(self, x, y): + """ The origin of the screen coordinates is the upper left corner """ + return x - self.width / 2, self.invert_y_coordinate(y) + + def invert_y_coordinate(self, y): + """ Positive y goes up in turtle coordinates, down in sceeen + coordinates """ + return self.height / 2 - y def draw_line(self, x1, y1, x2, y2): """ Draw a line """ - x1, y1 = self.width / 2 + int(x1), self.height / 2 - int(y1) - x2, y2 = self.width / 2 + int(x2), self.height / 2 - int(y2) + x1, y1 = self.turtle_to_screen_coordinates(x1, y1) + x2, y2 = self.turtle_to_screen_coordinates(x2, y2) if x1 < x2: - minx, maxx = x1, x2 + minx, maxx = int(x1), int(x2) else: - minx, maxx = x2, x1 + minx, maxx = int(x2), int(x1) if y1 < y2: - miny, maxy = y1, y2 + miny, maxy = int(y1), int(y2) else: - miny, maxy = y2, y1 + miny, maxy = int(y2), int(y1) w, h = maxx - minx, maxy - miny - self.canvas.images[0].draw_line(self.gc, x1, y1, x2, y2) + self.canvas.images[0].draw_line(self.gc, int(x1), int(y1), int(x2), + int(y2)) if self.fill and self.poly_points == []: - self.poly_points.append((x1, y1)) + self.poly_points.append((int(x1), int(y1))) if self.fill: - self.poly_points.append((x2, y2)) - self.invalt(minx - self.pensize * self.tw.coord_scale / 2 - 3, - miny - self.pensize * self.tw.coord_scale / 2 - 3, + self.poly_points.append((int(x2), int(y2))) + self.invalt(minx - int(self.pensize * self.tw.coord_scale / 2) - 3, + miny - int(self.pensize * self.tw.coord_scale / 2) - 3, w + self.pensize * self.tw.coord_scale + 6, h + self.pensize * self.tw.coord_scale + 6) @@ -562,8 +633,7 @@ class TurtleGraphics: def move_turtle(self): """ Move the turtle """ - x, y = self.width / 2 + int(self.xcor), \ - self.height / 2 - int(self.ycor) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) self.tw.active_turtle.move( (int(self.cx + x - self.tw.active_turtle.spr.rect.width / 2), int(self.cy + y - self.tw.active_turtle.spr.rect.height / 2))) @@ -611,9 +681,9 @@ class TurtleGraphics: def get_pixel(self): """ Read the pixel at x, y """ if self.tw.interactive_mode: - return self.canvas.get_pixel( - (self.width / 2 + int(self.xcor), - self.height / 2 - int(self.ycor)), 0, self.tw.color_mode) + x, y = self.turtle_to_screen_coordinates(self.xcor, self.ycor) + return self.canvas.get_pixel((int(x), int(y)), 0, + self.tw.color_mode) else: return(-1, -1, -1, -1) @@ -628,10 +698,9 @@ class TurtleGraphics: self.tw.active_turtle = self.tw.turtles.get_turtle(k, False) self.tw.active_turtle.show() tx, ty = self.tw.active_turtle.get_xy() - self.xcor = -self.width / 2 + tx + \ - self.tw.active_turtle.spr.rect.width / 2 - self.ycor = self.height / 2 - ty - \ - self.tw.active_turtle.spr.rect.height / 2 + self.xcor, self.ycor = self.screen_to_turtle_coordinates(tx, ty) + self.xcor += self.tw.active_turtle.spr.rect.width / 2 + self.ycor -= self.tw.active_turtle.spr.rect.height / 2 self.heading = self.tw.active_turtle.get_heading() self.setcolor(self.tw.active_turtle.get_color(), False) self.setgray(self.tw.active_turtle.get_gray(), False) @@ -651,11 +720,3 @@ class TurtleGraphics: def _get_my_nick(self): return self.tw.nick - - def _send_event(self, entry, share): - if not share: - return - - if self.tw.sharing(): - print "Sending: %s" % entry - self.tw.send_event(entry) diff --git a/TurtleArt/tacollaboration.py b/TurtleArt/tacollaboration.py index 52164e0..d4b7529 100644 --- a/TurtleArt/tacollaboration.py +++ b/TurtleArt/tacollaboration.py @@ -1,9 +1,35 @@ +#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 +from TurtleArt.taconstants import DEFAULT_TURTLE_COLORS try: from sugar import profile @@ -19,17 +45,19 @@ 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._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() @@ -40,6 +68,24 @@ class Collaboration(): 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: @@ -128,98 +174,25 @@ class Collaboration(): _logger.debug(event) 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: + _logger.debug("could not split event message") + # Restore active Turtle self._tw.canvas.set_turtle(self._tw.turtles.get_turtle_key( save_active_turtle)) @@ -229,27 +202,156 @@ class Collaboration(): if hasattr(self, 'chattube') and self.chattube is not None: self.chattube.SendText(entry) + def _turtle_request(self, payload): + if payload > 0: + [nick, colors] = data_from_string(payload) + 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: + event_payload = data_to_string(self.turtle_dictionary) + self.send_event("T|" + event_payload) + + def _receive_turtle_dict(self, payload): + if self.waiting_for_turtles: + if len(payload) > 0: + self.turtle_dictionary = data_from_string(payload) + 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 + + 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()} + d = {self._get_nick(): self._get_colors()} return d 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() + if colors is None: + colors = '%s,%s' % (DEFAULT_TURTLE_COLORS[0], + DEFAULT_TURTLE_COLORS[1]) return colors + class ChatTube(ExportedGObject): def __init__(self, tube, is_initiator, stack_received_cb): """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 = '' diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index 77ebefb..f5d97ad 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -113,35 +113,33 @@ TOP_LAYER = 1000 # PALETTE_NAMES = ['turtle', 'pen', 'colors', 'numbers', 'flow', 'blocks', - 'extras', 'sensor', 'portfolio', 'trash'] + 'extras', 'sensor', 'media', 'portfolio', 'trash'] -PALETTES = [['clean', 'forward', 'back', 'show', 'left', 'right', - 'seth', 'setxy2', 'heading', 'xcor', 'ycor', 'setscale', - 'arc', 'scale', 'leftpos', 'toppos', 'rightpos', - 'bottompos'], +PALETTES = [['forward', 'back', 'clean', 'left', 'right', + 'arc', 'setxy2', 'seth', 'xcor', 'ycor', 'heading'], ['penup', 'pendown', 'setpensize', 'fillscreen', 'pensize', - 'setcolor', 'setshade', 'setgray', 'color', 'shade', - 'gray', 'startfill', 'stopfill'], - ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', + 'startfill', 'stopfill'], + ['setcolor', 'setshade', 'setgray', 'color', 'shade', 'gray', + 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white', 'black'], ['plus2', 'minus2', 'product2', 'division2', 'identity2', 'remainder2', 'sqrt', 'random', 'number', 'greater2', 'less2', 'equal2', 'not', 'and2', 'or2'], ['wait', 'forever', 'repeat', 'if', 'ifelse', 'while', 'until', 'hspace', 'vspace', 'stopstack'], - ['start', 'hat1', 'stack1', 'hat', 'hat2', 'stack2', 'stack', - 'storeinbox1', 'storeinbox2', 'string', 'box1', 'box2', 'box', - 'storein'], + ['start', 'storeinbox1', 'storeinbox2', 'string', 'box1', 'box2', + 'box', 'storein', 'hat', 'hat1', 'hat2', 'stack', 'stack1', + 'stack2'], ['push', 'printheap', 'clearheap', 'pop', 'comment', 'print', - 'myfunc1arg', 'userdefined', - 'cartesian', 'width', 'height', 'polar', 'addturtle', 'reskin', - 'sandwichtop_no_label', 'sandwichbottom'], - ['kbinput', 'keyboard', 'readpixel', 'see', - 'sound', 'volume', 'pitch'], - ['journal', 'audio', 'video', 'description', 'hideblocks', - 'showblocks', 'fullscreen', 'savepix', 'savesvg', 'mediawait', - 'picturelist', 'picture1x1a', 'picture1x1', 'picture2x2', - 'picture2x1', 'picture1x2'], + 'myfunc1arg', 'userdefined', 'cartesian', 'polar', 'addturtle', + 'reskin', 'sandwichtop_no_label', 'sandwichbottom'], + ['kbinput', 'keyboard', 'readpixel', 'see', 'time'], + ['journal', 'audio', 'video', 'description', 'string', + 'show', 'setscale', 'savepix', 'savesvg', 'scale', 'mediawait'], + ['hideblocks', 'showblocks', 'fullscreen', 'picturelist', + 'picture1x1a', 'picture1x1', 'picture2x2', 'picture2x1', + 'picture1x2', 'leftpos', 'bottompos', 'width', 'rightpos', + 'toppos', 'height'], ['empty', 'restoreall']] # @@ -152,7 +150,8 @@ COLORS = [["#00FF00", "#00A000"], ["#00FFFF", "#00A0A0"], ["#00FFFF", "#00A0A0"], ["#FF00FF", "#A000A0"], ["#FFC000", "#A08000"], ["#FFFF00", "#A0A000"], ["#FF0000", "#A00000"], ["#FF0000", "#A00000"], - ["#0000FF", "#0000A0"], ["#FFFF00", "#A0A000"]] + ["#A0FF00", "#A0A000"], ["#0000FF", "#0000A0"], + ["#FFFF00", "#A0A000"]] BOX_COLORS = {'red': ["#FF0000", "#A00000"], 'orange': ["#FFD000", "#AA8000"], @@ -176,6 +175,7 @@ STANDARD_STROKE_WIDTH = 1.0 BLOCK_SCALE = 2.0 PALETTE_SCALE = 1.5 DEFAULT_TURTLE = 'Yertle' +DEFAULT_TURTLE_COLORS = ['#008000', '#00A000'] HORIZONTAL_PALETTE = 0 VERTICAL_PALETTE = 1 BLACK = -9999 @@ -190,10 +190,6 @@ DEFAULT_SCALE = 33 XO1 = 'xo1' XO15 = 'xo1.5' UNKNOWN = 'unknown' -SENSOR_AC_NO_BIAS = 'external' -SENSOR_AC_BIAS = 'sound' -SENSOR_DC_NO_BIAS = 'voltage' -SENSOR_DC_BIAS = 'resistance' # # Block-style definitions @@ -205,15 +201,15 @@ BASIC_STYLE = [] BASIC_STYLE_EXTENDED_VERTICAL = ['clean', 'penup', 'pendown', 'stack1', 'stack2', 'hideblocks', 'showblocks', 'clearheap', 'printheap', 'kbinput', 'fullscreen', 'cartesian', 'polar', 'startfill', 'mediawait', - 'stopfill', 'readpixel', 'readcamera', 'vspace'] + 'stopfill', 'readpixel', 'vspace'] INVISIBLE = ['sandwichcollapsed'] BASIC_STYLE_EXTENDED = ['picturelist', 'picture1x1', 'picture2x2', 'picture2x1', 'picture1x2', 'picture1x1a'] -BASIC_STYLE_1ARG = ['forward', 'back', 'left', 'right', 'seth', 'show', 'image', +BASIC_STYLE_1ARG = ['forward', 'back', 'left', 'right', 'seth', 'show', 'setscale', 'setpensize', 'setcolor', 'setshade', 'print', 'showaligned', 'settextsize', 'settextcolor', 'print', 'wait', 'storeinbox1', 'savepix', 'storeinbox2', 'wait', 'stack', 'push', 'nop', 'addturtle', 'comment', - 'savesvg', 'setgray', 'skin', 'reskin'] + 'image', 'savesvg', 'setgray', 'skin', 'reskin'] BASIC_STYLE_VAR_ARG = ['userdefined', 'userdefined2args', 'userdefined3args'] BULLET_STYLE = ['templatelist', 'list'] BASIC_STYLE_2ARG = ['arc', 'setxy', 'setxy2', 'fillscreen', 'storein', 'write'] @@ -222,9 +218,8 @@ BOX_STYLE = ['number', 'xcor', 'ycor', 'heading', 'pensize', 'color', 'shade', 'toppos', 'rightpos', 'bottompos', 'width', 'height', 'pop', 'keyboard', 'red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'white', 'black', 'titlex', 'titley', 'leftx', 'topy', 'rightx', 'bottomy', - 'sound', 'volume', 'pitch', 'voltage', 'resistance', 'gray', 'see', 'rfid', - 'luminance'] -BOX_STYLE_MEDIA = ['description', 'audio', 'journal', 'video', 'camera'] + 'gray', 'see', 'time'] +BOX_STYLE_MEDIA = ['description', 'audio', 'journal', 'video'] NUMBER_STYLE = ['plus2', 'product2', 'myfunc'] NUMBER_STYLE_VAR_ARG = ['myfunc1arg', 'myfunc2arg', 'myfunc3arg'] NUMBER_STYLE_BLOCK = ['random'] @@ -278,7 +273,7 @@ OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder'] # Blocks that contain media # CONTENT_BLOCKS = ['number', 'string', 'description', 'audio', 'video', - 'journal', 'camera'] + 'journal'] # # These blocks get a special skin @@ -291,11 +286,11 @@ PYTHON_SKIN = ['nop', 'userdefined', 'userdefined2args', 'userdefined3args'] # # These blocks hold constants # -CONSTANTS = {'leftpos':None, 'toppos':None, 'rightpos':None, 'bottompos':None, - 'width':None, 'height':None, 'red':0, 'orange':10, 'yellow':20, - 'green':40, 'cyan':50, 'blue':70, 'purple':90, 'titlex':None, - 'titley':None, 'leftx':None, 'topy':None, 'rightx':None, - 'bottomy':None} +CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None, + 'bottompos': None, 'width': None, 'height': None, 'red': 0, + 'orange': 10, 'yellow': 20, 'green': 40, 'cyan': 50, 'blue': 70, + 'purple': 90, 'titlex': None, 'titley': None, 'leftx': None, + 'topy': None, 'rightx': None, 'bottomy': None} # # Block-name dictionary used for labels @@ -314,7 +309,6 @@ BLOCK_NAMES = { 'box': [_('box')], 'box1': [_('box 1')], 'box2': [_('box 2')], - 'camera': [' '], 'cartesian': [_('Cartesian')], 'clean': [_(' clean ')], 'clearheap': [_('empty heap')], @@ -354,7 +348,6 @@ BLOCK_NAMES = { 'leftx': [_('picture left')], 'less2': ['<'], 'list': ['list'], - 'luminance': [_('brightness')], 'mediawait': [_('media wait')], 'minus2': ['–'], 'myfunc': [_('Python'), 'f(x)', 'x'], @@ -388,7 +381,6 @@ BLOCK_NAMES = { 'purple': [_('purple')], 'push': [_('push')], 'random': [_('random'), _('min'), _('max')], - 'readcamera': [_('read camera')], 'readpixel': [_('read pixel')], 'red': [_('red')], 'remainder2': [_('mod')], @@ -397,7 +389,6 @@ BLOCK_NAMES = { 'resistance': [_('resistance')], 'restore': [_('restore last')], 'restoreall': [_('restore all')], - 'rfid': [_('RFID')], 'right': [_('right')], 'rightpos': [_('right')], 'rightx': [_('picture right')], @@ -447,6 +438,7 @@ BLOCK_NAMES = { 'template2x2': [' '], 'templatelist': [' '], 'textsize': [_('text size')], + 'time': [_('time')], 'titlex': [_('title x')], 'titley': [_('title y')], 'toppos': [_('top')], @@ -520,7 +512,6 @@ PRIMITIVES = { 'leftx': 'leftx', 'less2': 'less?', 'list': 'bulletlist', - 'luminance': 'luminance', 'mediawait': 'mediawait', 'minus2': 'minus', 'myfunc': 'myfunction', @@ -534,7 +525,6 @@ PRIMITIVES = { 'pendown': 'pendown', 'pensize': 'pensize', 'penup': 'penup', - 'pitch': 'pitch', 'plus2': 'plus', 'polar': 'polar', 'pop': 'pop', @@ -545,12 +535,9 @@ PRIMITIVES = { 'push': 'push', 'random': 'random', 'red': 'red', - 'readcamera': 'readcamera', 'readpixel': 'readpixel', 'remainder2': 'mod', 'repeat': 'repeat', - 'resistance': 'resistance', - 'rfid': 'rfid', 'right': 'right', 'rightpos': 'rpos', 'rightx': 'rightx', @@ -579,7 +566,6 @@ PRIMITIVES = { 'showblocks': 'showblocks', 'showaligned': 'showaligned', 'skin': 'skin', - 'sound': 'sound', 'sqrt': 'sqrt', 'stack': 'stack', 'stack1': 'stack1', @@ -598,6 +584,7 @@ PRIMITIVES = { 'template2x2': 't2x2', 'templatelist': 'bullet', 'textsize': 'textsize', + 'time': 'time', 'titlex': 'titlex', 'titley': 'titley', 'toppos': 'tpos', @@ -605,8 +592,6 @@ PRIMITIVES = { 'userdefined': 'userdefined', 'userdefined2args': 'userdefined2', 'userdefined3args': 'userdefined3', - 'voltage': 'voltage', - 'volume': 'volume', 'vspace': 'nop', 'wait': 'wait', 'while2': 'while', @@ -627,7 +612,6 @@ DEFAULTS = { 'audio': [None], 'back': [100], 'box': [_('my box')], - 'camera': ['CAMERA'], 'comment': [_('comment')], 'description': [None], 'fillscreen': [60, 80], @@ -697,6 +681,9 @@ STRING_OR_NUMBER_ARGS = ['plus2', 'equal2', 'less2', 'greater2', 'box', CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1', 'storeinbox2'] +PREFIX_DICTIONARY = {'journal': '#smedia_', 'description': '#sdescr_', + 'audio': '#saudio_', 'video': '#svideo_'} + # # Status blocks # @@ -758,8 +745,9 @@ TEMPLATES = {'t1x1': (0.5, 0.5, 0.0625, 0.125, 1.05, 0), # Names for blocks without names for popup help # SPECIAL_NAMES = { + 'and2': _('and'), 'audio': _('audio'), - 'camera': _('camera'), + 'description': _('description'), 'division2': _('divide'), 'equal2': _('equal'), 'greater2': _('greater than'), @@ -769,11 +757,16 @@ SPECIAL_NAMES = { 'ifelse': _('if then else'), 'journal': _('journal'), 'less2': _('less than'), + 'or2': _('or'), 'minus2': _('minus'), 'nop': _('Python code'), 'number': _('number'), 'plus2': _('plus'), 'product2': _('multiply'), + 'repeat': _('repeat'), + 'sandwichtop_no_label': _('top of a collapsible stack'), + 'sandwichbottom': _('bottom of a collapsible stack'), + 'sensors': _('sensors'), 'sqrt': _('square root'), 'template1x1': _('presentation 1x1'), 'template1x1a': _('presentation 1x1'), @@ -782,6 +775,9 @@ SPECIAL_NAMES = { 'template2x2': _('presentation 2x2'), 'templatelist': _('presentation bulleted list'), 'textsize': _('text size'), + 'userdefined': _('Python block'), + 'userdefined2args': _('Python block'), + 'userdefined3args': _('Python block'), 'video': _('video'), 'vspace': _('vertical space')} @@ -799,11 +795,11 @@ HELP_STRINGS = { 'box1': _("Variable 1 (numeric value)"), 'box2': _("Variable 2 (numeric value)"), 'box': _("named variable (numeric value)"), - 'camera': _('camera output'), 'cartesian': _("displays Cartesian coordinates"), 'clean': _("clears the screen and reset the turtle"), 'clearheap': _("emptys FILO (first-in-last-out heap)"), - 'color': _("holds current pen color (can be used in place of a number block)"), + 'color': _( + "holds current pen color (can be used in place of a number block)"), 'colors': _("Palette of pen colors"), 'comment': _("places a comment in your code"), 'debugoff': _("Debug"), @@ -818,7 +814,8 @@ HELP_STRINGS = { 'forever': _("loops forever"), 'forward': _("moves turtle forward"), 'fullscreen': _("hides the Sugar toolbars"), - 'gray': _("holds current gray level (can be used in place of a number block)"), + 'gray': _( + "holds current gray level (can be used in place of a number block)"), 'greater2': _("logical greater-than operator"), 'hat1': _("top of Action 1 stack"), 'hat2': _("top of Action 2 stack"), @@ -830,14 +827,15 @@ HELP_STRINGS = { 'hspace': _("jogs stack right"), 'identity2': _("identity operator used for extending blocks"), 'ifelse': _("if-then-else operator that uses boolean operators from Numbers palette"), - 'if': _("if-then operator that uses boolean operators from Numbers palette"), + 'if': _( + "if-then operator that uses boolean operators from Numbers palette"), 'journal': _("Sugar Journal media object"), 'kbinput': _("query for keyboard input (results stored in keyboard block)"), 'keyboard': _("holds results of query-keyboard block"), 'leftpos': _("xcor of left of screen"), 'left': _("turns turtle counterclockwise (angle in degrees)"), 'less2': _("logical less-than operator"), - 'luminance': _("light level detected by camera"), + 'media': _("Palette of media objects"), 'mediawait': _("wait for current video or audio to complete"), 'minus2': _("subtracts bottom numeric input from top numeric input"), 'myfunc': _("a programmable block: used to add advanced math equations, e.g., sin(x)"), @@ -849,19 +847,21 @@ HELP_STRINGS = { 'not': _("logical NOT operator"), 'numbers': _("Palette of numeric operators"), 'number': _("used as numeric input in mathematic operators"), - 'or': _("logical OR operator"), + 'or2': _("logical OR operator"), 'orientation': _("changes the orientation of the palette of blocks"), 'pendown': _("Turtle will draw when moved."), 'pen': _("Palette of pen commands"), - 'pensize': _("holds current pen size (can be used in place of a number block)"), + 'pensize': _( + "holds current pen size (can be used in place of a number block)"), 'penup': _("Turtle will not draw when moved."), - 'picture1x1': _("presentation template: select Journal object (with description)"), - 'picture1x1a': _("presentation template: select Journal object (no description)"), + 'picture1x1': _( + "presentation template: select Journal object (with description)"), + 'picture1x1a': _( + "presentation template: select Journal object (no description)"), 'picture1x2': _("presentation template: select two Journal objects"), 'picture2x1': _("presentation template: select two Journal objects"), 'picture2x2': _("presentation template: select four Journal objects"), 'picturelist': _("presentation template: list of bullets"), - 'pitch': _('microphone input pitch'), 'plus2': _("adds two alphanumeric inputs"), 'polar': _("displays polar coordinates"), 'pop': _("pops value off FILO (first-in last-out heap)"), @@ -871,7 +871,6 @@ HELP_STRINGS = { 'product2': _("multiplies two numeric inputs"), 'push': _("pushes value onto FILO (first-in last-out heap)"), 'random': _("returns random number between minimum (top) and maximum (bottom) values"), - 'readcamera': _("Average RGB color from camera is pushed to the stack"), 'readpixel': _("RGB color under the turtle is pushed to the stack"), 'remainder2': _("modular (remainder) operator"), 'repeat': _("loops specified number of times"), @@ -879,12 +878,12 @@ HELP_STRINGS = { 'reskin': _("put a custom 'shell' on the turtle"), 'restore': _("restores most recent blocks from trash"), 'restoreall': _("restore all blocks from trash"), - 'rfid': _("RFID"), 'rightpos': _("xcor of right of screen"), 'right': _("turns turtle clockwise (angle in degrees)"), 'run-fastoff': _("Run"), 'run-slowoff': _("Step"), - 'sandwichbottom': _("bottom block in a collapsibe stack: click to collapse"), + 'sandwichbottom': _( + "bottom block in a collapsibe stack: click to collapse"), 'sandwichcollapsed': _("bottom block in a collapsed stack: click to open"), 'sandwichtop': _("top of a collapsible stack"), 'sandwichtop_no_label': _("top of a collapsed stack"), @@ -897,7 +896,8 @@ HELP_STRINGS = { 'sensor': _("Palette of sensor blocks"), 'setcolor': _("sets color of the line drawn by the turtle"), 'setgray': _("sets gray level of the line drawn by the turtle"), - 'seth': _("sets the heading of the turtle (0 is towards the top of the screen.)"), + 'seth': _( + "sets the heading of the turtle (0 is towards the top of the screen.)"), 'setpensize': _("sets size of the line drawn by the turtle"), 'setscale': _("sets the scale of media"), 'setshade': _("sets shade of the line drawn by the turtle"), @@ -909,7 +909,6 @@ HELP_STRINGS = { 'show': _("draws text or show media from the Journal"), 'showblocks': _("restores hidden blocks"), 'skin': _("put a custom 'shell' on the turtle"), - 'sound': _("raw microphone input signal"), 'sqrt': _("calculates square root"), 'stack1': _("invokes Action 1 stack"), 'stack2': _("invokes Action 2 stack"), @@ -923,24 +922,31 @@ HELP_STRINGS = { 'storeinbox2': _("stores numeric value in Variable 2"), 'storein': _("stores numeric value in named variable"), 'string': _("string value"), - 'template1x1': _("presentation template: select Journal object (with description)"), - 'template1x1a': _("presentation template: select Journal object (no description)"), + 'template1x1': _( + "presentation template: select Journal object (with description)"), + 'template1x1a': _( + "presentation template: select Journal object (no description)"), 'template1x2': _("presentation template: select two Journal objects"), 'template2x1': _("presentation template: select two Journal objects"), 'template2x2': _("presentation template: select four Journal objects"), 'templatelist': _("presentation template: list of bullets"), - 'textcolor': _("holds current text color (can be used in place of a number block)"), - 'textsize': _("holds current text size (can be used in place of a number block)"), + 'textcolor': _( + "holds current text color (can be used in place of a number block)"), + 'textsize': _( + "holds current text size (can be used in place of a number block)"), + 'time': _("elapsed time (in seconds) since program started"), 'toppos': _("ycor of top of screen"), 'trash': _("Trashcan"), 'turtle': _("Palette of turtle commands"), 'until': _("do-until-True operator that uses boolean operators from Numbers palette"), - 'userdefined': _("runs code found in the tamyblock.py module found in the Journal"), - 'userdefined2args': _("runs code found in the tamyblock.py module found in the Journal"), - 'userdefined3args': _("runs code found in the tamyblock.py module found in the Journal"), + 'userdefined': _( + "runs code found in the tamyblock.py module found in the Journal"), + 'userdefined2args': _( + "runs code found in the tamyblock.py module found in the Journal"), + 'userdefined3args': _( + "runs code found in the tamyblock.py module found in the Journal"), 'video': _("Sugar Journal video object"), 'voltage': _("sensor voltage"), - 'volume': _("microphone input volume"), 'vspace': _("jogs stack down"), 'wait': _("pauses program execution a specified number of seconds"), 'while': _("do-while-True operator that uses boolean operators from Numbers palette"), @@ -953,17 +959,17 @@ HELP_STRINGS = { # DEAD_KEYS = ['grave', 'acute', 'circumflex', 'tilde', 'diaeresis', 'abovering'] -DEAD_DICTS = [{'A':192, 'E':200, 'I':204, 'O':210, 'U':217, 'a':224, 'e':232, - 'i':236, 'o':242, 'u':249}, - {'A':193, 'E':201, 'I':205, 'O':211, 'U':218, 'a':225, 'e':233, - 'i':237, 'o':243, 'u':250}, - {'A':194, 'E':202, 'I':206, 'O':212, 'U':219, 'a':226, 'e':234, - 'i':238, 'o':244, 'u':251}, - {'A':195, 'O':211, 'N':209, 'U':360, 'a':227, 'o':245, 'n':241, - 'u':361}, - {'A':196, 'E':203, 'I':207, 'O':211, 'U':218, 'a':228, 'e':235, - 'i':239, 'o':245, 'u':252}, - {'A':197, 'a':229}] +DEAD_DICTS = [{'A': 192, 'E': 200, 'I': 204, 'O': 210, 'U': 217, 'a': 224, + 'e': 232, 'i': 236, 'o': 242, 'u': 249}, + {'A': 193, 'E': 201, 'I': 205, 'O': 211, 'U': 218, 'a': 225, + 'e': 233, 'i': 237, 'o': 243, 'u': 250}, + {'A': 194, 'E': 202, 'I': 206, 'O': 212, 'U': 219, 'a': 226, + 'e': 234, 'i': 238, 'o': 244, 'u': 251}, + {'A': 195, 'O': 211, 'N': 209, 'U': 360, 'a': 227, 'o': 245, + 'n': 241, 'u': 361}, + {'A': 196, 'E': 203, 'I': 207, 'O': 211, 'U': 218, 'a': 228, + 'e': 235, 'i': 239, 'o': 245, 'u': 252}, + {'A': 197, 'a': 229}] NOISE_KEYS = ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', 'Pause', 'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift', 'KP_Divide', 'Escape', 'Return', 'KP_Page_Up', 'Up', 'Down', 'Menu', diff --git a/TurtleArt/taexporthtml.py b/TurtleArt/taexporthtml.py index 09042f8..2dac7e6 100644 --- a/TurtleArt/taexporthtml.py +++ b/TurtleArt/taexporthtml.py @@ -22,7 +22,7 @@ import pygtk pygtk.require('2.0') import gtk import os.path -from tautils import data_to_string, save_picture, image_to_base64 +from tautils import data_to_string, save_picture, image_to_base64, get_path from gettext import gettext as _ from cgi import escape @@ -90,7 +90,8 @@ def save_html(self, tw, embed_flag=True): tmp = imgdata else: pixbuf = gtk.gdk.pixbuf_new_from_file(p) - imgdata = image_to_base64(pixbuf, tw.activity) + imgdata = image_to_base64(pixbuf, + get_path(tw.activity, 'instance')) tmp = self.html_glue['img2'][0] tmp += imgdata tmp += self.html_glue['img2'][1] @@ -110,7 +111,7 @@ def save_html(self, tw, embed_flag=True): else: if self.embed_images == True: imgdata = image_to_base64(save_picture(self.tw.canvas), - tw.activity) + get_path(tw.activity, 'instance')) else: imgdata = os.path.join(self.tw.load_save_folder, 'image') self.tw.save_as_image(imgdata) diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index e3576fa..d3b214c 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -25,12 +25,8 @@ import gtk from time import time from math import sqrt -from numpy import append -from numpy.fft import rfft from random import uniform from operator import isNumberType -from fcntl import ioctl - import os.path from UserDict import UserDict @@ -40,32 +36,26 @@ try: except ImportError: pass -from taconstants import TAB_LAYER, BLACK, WHITE, \ - DEFAULT_SCALE, ICON_SIZE, BLOCK_NAMES, CONSTANTS, SENSOR_DC_NO_BIAS, \ - SENSOR_DC_BIAS, XO1, XO15 -from tagplay import play_audio_from_file, play_movie_from_file, stop_media, \ - media_playing -from tacamera import Camera -import v4l2 +from taconstants import TAB_LAYER, BLACK, WHITE, DEFAULT_SCALE, ICON_SIZE, \ + BLOCK_NAMES, CONSTANTS, PREFIX_DICTIONARY from tajail import myfunc, myfunc_import from tautils import get_pixbuf_from_journal, convert, data_from_file, \ text_media_type, round_int, chr_to_ord, strtype, get_path -from RtfParser import RtfTextOnly - -from ringbuffer import RingBuffer1d +from util.RtfParser import RtfTextOnly from gettext import gettext as _ VALUE_BLOCKS = ['box1', 'box2', 'color', 'shade', 'gray', 'scale', 'pensize', - 'heading', 'xcor', 'ycor', 'pop', 'see', 'keyboard', - 'sound', 'volume', 'pitch', 'resistance', 'voltage', - 'luminance'] + 'heading', 'xcor', 'ycor', 'pop', 'time', 'keyboard', 'see'] +MEDIA_BLOCKS_DICTIONARY = {} # new media blocks get added here +PLUGIN_DICTIONARY = {} # new block primitives get added here import logging _logger = logging.getLogger('turtleart-activity') +<<<<<<< HEAD def find_device(): """ Search for RFID devices. Return a device instance or None. """ device = None @@ -83,6 +73,8 @@ def find_device(): return device +======= +>>>>>>> 860754f7e871617df9d101a51dc64a69b742a0ba class noKeyError(UserDict): __missing__ = lambda x, y: 0 @@ -290,6 +282,7 @@ def _identity(x): return(x) +<<<<<<< HEAD def _avg(array, abs_value=False): """ Calc. the average value of an array """ if len(array) == 0: @@ -319,6 +312,8 @@ def stop_logo(tw): tw.active_turtle.show() +======= +>>>>>>> 860754f7e871617df9d101a51dc64a69b742a0ba def _just_stop(): """ yield False to stop stack """ yield False @@ -337,6 +332,7 @@ class LogoCode: self.tw = tw self.oblist = {} + # TODO: remove plugin blocks DEFPRIM = { '(': [1, lambda self, x: self._prim_opar(x)], 'and': [2, lambda self, x, y: _and(x, y)], @@ -386,7 +382,6 @@ class LogoCode: 'leftx': [0, lambda self: CONSTANTS['leftx']], 'lpos': [0, lambda self: CONSTANTS['leftpos']], 'less?': [2, lambda self, x, y: _less(x, y)], - 'luminance': [0, lambda self: self._read_camera(True)], 'mediawait': [0, self._media_wait, True], 'minus': [2, lambda self, x, y: _minus(x, y)], 'mod': [2, lambda self, x, y: _mod(x, y)], @@ -404,7 +399,6 @@ class LogoCode: 'pendown': [0, lambda self: self.tw.canvas.setpen(True)], 'pensize': [0, lambda self: self.tw.canvas.pensize], 'penup': [0, lambda self: self.tw.canvas.setpen(False)], - 'pitch': [0, lambda self: self._get_pitch()], 'plus': [2, lambda self, x, y: _plus(x, y)], 'polar': [0, lambda self: self.tw.set_polar(True)], 'pop': [0, lambda self: self._prim_pop()], @@ -414,12 +408,9 @@ class LogoCode: 'purple': [0, lambda self: CONSTANTS['purple']], 'push': [1, lambda self, x: self._prim_push(x)], 'random': [2, lambda self, x, y: _random(x, y)], - 'readcamera': [0, lambda self: self._read_camera()], 'readpixel': [0, lambda self: self._read_pixel()], 'red': [0, lambda self: CONSTANTS['red']], 'repeat': [2, self._prim_repeat, True], - 'resistance': [0, lambda self: self._get_resistance()], - 'rfid': [0, lambda self: self.tw.rfid_idn], 'right': [1, lambda self, x: self._prim_right(x)], 'rightx': [0, lambda self: CONSTANTS['rightx']], 'rpos': [0, lambda self: CONSTANTS['rightpos']], @@ -450,7 +441,6 @@ class LogoCode: 'showaligned': [1, lambda self, x: self._show(x, False)], 'showblocks': [0, lambda self: self.tw.showblocks()], 'skin': [1, lambda self, x: self._reskin(x)], - 'sound': [0, lambda self: self._get_sound()], 'sqrt': [1, lambda self, x: _sqrt(x)], 'stack1': [0, self._prim_stack1, True], 'stack': [1, self._prim_stack, True], @@ -470,6 +460,7 @@ class LogoCode: x, y, z, a, b)], 'textcolor': [0, lambda self: self.tw.canvas.textcolor], 'textsize': [0, lambda self: self.tw.textsize], + 'time': [0, lambda self: self._elapsed_time()], 'titlex': [0, lambda self: CONSTANTS['titlex']], 'titley': [0, lambda self: CONSTANTS['titley']], 'topy': [0, lambda self: CONSTANTS['topy']], @@ -480,8 +471,6 @@ class LogoCode: 'userdefined3': [3, lambda self, x, y, z: self._prim_myblock([x, y, z])], 'video': [1, lambda self, x: self._play_video(x)], - 'voltage': [0, lambda self: self._get_voltage()], - 'volume': [0, lambda self: self._get_volume()], 'vres': [0, lambda self: CONSTANTS['height']], 'wait': [1, self._prim_wait, True], 'white': [0, lambda self: WHITE], @@ -520,9 +509,9 @@ class LogoCode: self.trace = 0 self.update_values = False self.gplay = None - self.ag = None self.filepath = None self.dsobject = None + self._start_time = None # Scale factors for depreciated portfolio blocks self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale) @@ -531,6 +520,7 @@ class LogoCode: self.scale = DEFAULT_SCALE +<<<<<<< HEAD self.max_samples = 1500 self.input_step = 1 @@ -550,6 +540,18 @@ class LogoCode: else: self.imagepath = '/tmp/turtlepic.png' self.camera = Camera(self.imagepath) +======= + def stop_logo(self): + """ Stop logo is called from the Stop button on the toolbar """ + self.tw.step_time = 0 + self.step = _just_stop() + for plugin in self.tw._plugins: + plugin.stop() + if self.tw.gst_available: + from tagplay import stop_media + stop_media(self) + self.tw.active_turtle.show() +>>>>>>> 860754f7e871617df9d101a51dc64a69b742a0ba def _def_prim(self, name, args, fcn, rprim=False): """ Define the primitives associated with the blocks """ @@ -574,7 +576,6 @@ class LogoCode: self.tw.saving_svg = False self.find_value_blocks() - self._update_audio_mode() if self.trace > 0: self.update_values = True else: @@ -601,6 +602,7 @@ class LogoCode: code = self._blocks_to_code(blk) if run_flag: _logger.debug("running code: %s" % (code)) + self._start_time = time() self._setup_cmd(code) if not self.tw.hide: self.tw.display_coordinates() @@ -625,35 +627,22 @@ class LogoCode: code.append(float(blk.values[0])) except ValueError: code.append(float(ord(blk.values[0][0]))) - elif blk.name == 'string' or blk.name == 'title': + elif blk.name == 'string' or \ + blk.name == 'title': # depreciated block if type(blk.values[0]) == float or type(blk.values[0]) == int: if int(blk.values[0]) == blk.values[0]: blk.values[0] = int(blk.values[0]) code.append('#s' + str(blk.values[0])) else: code.append('#s' + blk.values[0]) - elif blk.name == 'journal': - if blk.values[0] is not None: - code.append('#smedia_' + str(blk.values[0])) - else: - code.append('#smedia_None') - elif blk.name == 'description': - if blk.values[0] is not None: - code.append('#sdescr_' + str(blk.values[0])) - else: - code.append('#sdescr_None') - elif blk.name == 'audio': + elif blk.name in PREFIX_DICTIONARY: if blk.values[0] is not None: - code.append('#saudio_' + str(blk.values[0])) + code.append(PREFIX_DICTIONARY[blk.name] + \ + str(blk.values[0])) else: - code.append('#saudio_None') - elif blk.name == 'video': - if blk.values[0] is not None: - code.append('#svideo_' + str(blk.values[0])) - else: - code.append('#svideo_None') - elif blk.name == 'camera': - code.append('#smedia_CAMERA') + code.append(PREFIX_DICTIONARY[blk.name] + 'None') + elif blk.name in MEDIA_BLOCKS_DICTIONARY: + code.append('#smedia_' + blk.name.upper()) else: return ['%nothing%'] else: @@ -900,12 +889,16 @@ class LogoCode: def prim_clear(self): """ Clear screen """ - stop_media(self) + if self.tw.gst_available: + from tagplay import stop_media + stop_media(self) self.tw.canvas.clearscreen() self.scale = DEFAULT_SCALE - self.tw.set_polar(False) - self.tw.set_cartesian(False) + # Note: users find this "feature" confusing + # self.tw.set_polar(False) + # self.tw.set_cartesian(False) self.hidden_turtle = None + self._start_time = time() for name in VALUE_BLOCKS: self.update_label_value(name) @@ -953,27 +946,27 @@ class LogoCode: y = myfunc(f, x) if str(y) == 'nan': _logger.debug("python function returned nan") - stop_logo(self.tw) + self.stop_logo() raise logoerror("#notanumber") else: return y except ZeroDivisionError: - stop_logo(self.tw) + self.stop_logo() raise logoerror("#zerodivide") except ValueError, e: - stop_logo(self.tw) + self.stop_logo() raise logoerror('#' + str(e)) except SyntaxError, e: - stop_logo(self.tw) + self.stop_logo() raise logoerror('#' + str(e)) except NameError, e: - stop_logo(self.tw) + self.stop_logo() raise logoerror('#' + str(e)) except OverflowError: - stop_logo(self.tw) + self.stop_logo() raise logoerror("#overflowerror") except TypeError: - stop_logo(self.tw) + self.stop_logo() raise logoerror("#notanumber") def _prim_forever(self, blklist): @@ -1106,7 +1099,8 @@ class LogoCode: if flag and (self.tw.hide or self.tw.step_time == 0): return if type(n) == str or type(n) == unicode: - if n[0:6] == 'media_' and n[6:] != 'CAMERA': + if n[0:6] == 'media_' and \ + n[6:].lower not in MEDIA_BLOCKS_DICTIONARY: try: if self.tw.running_sugar: try: @@ -1134,9 +1128,8 @@ class LogoCode: else: try: self.keyboard = {'Escape': 27, 'space': 32, ' ': 32, - 'Return': 13, \ - 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, \ - 'KP_Right': 3}[self.tw.keypress] + 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, + 'KP_Left': 1, 'KP_Right': 3}[self.tw.keypress] except KeyError: self.keyboard = 0 self.update_label_value('keyboard', self.keyboard) @@ -1149,19 +1142,6 @@ class LogoCode: self.value_blocks[name] = self.tw.block_list.get_similar_blocks( 'block', name) - def _update_audio_mode(self): - """ If there are sensor blocks, set the appropriate audio mode """ - for name in ['sound', 'volume', 'pitch']: - if len(self.value_blocks[name]) > 0: - self.tw.audiograb.set_sensor_type() - return - if len(self.value_blocks['resistance']) > 0: - self.tw.audiograb.set_sensor_type(SENSOR_DC_BIAS) - return - elif len(self.value_blocks['voltage']) > 0: - self.tw.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS) - return - def update_label_value(self, name, value=None): """ Update the label of value blocks to reflect current value """ if self.tw.hide or not self.tw.interactive_mode or \ @@ -1188,10 +1168,12 @@ class LogoCode: self.update_label_value(name, value) def _prim_right(self, value): + """ Turtle rotates clockwise """ self.tw.canvas.right(float(value)) self.update_label_value('heading', self.tw.canvas.heading) def _prim_move(self, cmd, value1, value2=None, pendown=True): + """ Turtle moves by method specified in value1 """ if value2 is None: cmd(value1) else: @@ -1204,6 +1186,7 @@ class LogoCode: self.see() def _prim_arc(self, cmd, value1, value2): + """ Turtle draws an arc of degree, radius """ cmd(float(value1), float(value2)) self.update_label_value('xcor', self.tw.canvas.xcor / self.tw.coord_scale) @@ -1336,11 +1319,9 @@ class LogoCode: elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: self.filepath = None self.dsobject = None - if string[6:] == 'CAMERA': - if self.tw.camera_available: - self.camera.save_camera_input_to_file() - self.camera.stop_camera_input() - self.filepath = self.imagepath + print string[6:], MEDIA_BLOCKS_DICTIONARY + if string[6:].lower() in MEDIA_BLOCKS_DICTIONARY: + MEDIA_BLOCKS_DICTIONARY[string[6:].lower()]() elif os.path.exists(string[6:]): # is it a path? self.filepath = string[6:] elif self.tw.running_sugar: # is it a datastore object? @@ -1377,8 +1358,7 @@ class LogoCode: if self.dsobject is not None: self.dsobject.destroy() else: # assume it is text to display - x = self._x() - y = self._y() + x, y = self._x(), self._y() if center: y -= self.tw.canvas.textsize self.tw.canvas.draw_text(string, x, y, @@ -1387,8 +1367,7 @@ class LogoCode: self.tw.canvas.width - x) elif type(string) == float or type(string) == int: string = round_int(string) - x = self._x() - y = self._y() + x, y = self._x(), self._y() if center: y -= self.tw.canvas.textsize self.tw.canvas.draw_text(string, x, y, @@ -1401,23 +1380,23 @@ class LogoCode: if filepath is not None: self.filepath = filepath pixbuf = None - w = self._w() - h = self._h() + w, h = self._w(), self._h() if w < 1 or h < 1: return - if self.filepath is not None and self.filepath != '': + if self.dsobject is not None: + try: + pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) + except: + _logger.debug("Couldn't open dsobject %s" % (self.dsobject)) + if pixbuf is None and \ + self.filepath is not None and \ + self.filepath != '': try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.filepath, w, h) except: self.tw.showlabel('nojournal', self.filepath) _logger.debug("Couldn't open filepath %s" % (self.filepath)) - elif self.dsobject is not None: - try: - pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) - except: - self.tw.showlabel('nojournal', self.dsobject) - _logger.debug("Couldn't open dsobject %s" % (self.dsobject)) if pixbuf is not None: if center: self.tw.canvas.draw_pixbuf(pixbuf, 0, 0, @@ -1448,8 +1427,12 @@ class LogoCode: f.close() except IOError: self.tw.showlabel('nojournal', self.filepath) +<<<<<<< HEAD _logger.debug("Couldn't open filepath %s" % \ (self.filepath)) +======= + _logger.debug("Couldn't open %s" % (self.filepath)) +>>>>>>> 860754f7e871617df9d101a51dc64a69b742a0ba else: if description is not None: text = str(description) @@ -1461,23 +1444,35 @@ class LogoCode: def _media_wait(self): """ Wait for media to stop playing """ - while(media_playing(self)): - yield True + if self.tw.gst_available: + from tagplay import media_playing + while(media_playing(self)): + yield True self._ireturn() yield True def _play_sound(self): """ Sound file from Journal """ - play_audio_from_file(self, self.filepath) + if self.tw.gst_available: + from tagplay import play_audio_from_file + play_audio_from_file(self, self.filepath) def _play_video(self): """ Movie file from Journal """ - w = self._w() - h = self._h() + w, h = self._w(), self._h() if w < 1 or h < 1: return - play_movie_from_file(self, self.filepath, self._x(), self._y(), - self._w(), self._h()) + if self.tw.gst_available: + from tagplay import play_movie_from_file + play_movie_from_file(self, self.filepath, self._x(), self._y(), + w, h) + + def _elapsed_time(self): + """ Number of seconds since program execution has started or + clean (prim_clear) block encountered """ + elapsed_time = int(time() - self._start_time) + self.update_label_value('time', elapsed_time) + return elapsed_time def see(self): """ Read r, g, b from the canvas and return a corresponding palette @@ -1493,6 +1488,7 @@ class LogoCode: self.heap.append(b) self.heap.append(g) self.heap.append(r) +<<<<<<< HEAD def _read_camera(self, luminance_only=False): """ Read average pixel from camera and push b, g, r to the stack """ @@ -1635,6 +1631,8 @@ class LogoCode: else: return 0 +======= +>>>>>>> 860754f7e871617df9d101a51dc64a69b742a0ba # Depreciated block methods def _show_template1x1(self, title, media): diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py index a7a3205..f309eef 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -19,7 +19,7 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -from taconstants import TURTLE_LAYER +from taconstants import TURTLE_LAYER, DEFAULT_TURTLE_COLORS from tasprite_factory import SVG, svg_str_to_pixbuf from tacanvas import wrap100, color_table from sprites import Sprite @@ -149,7 +149,7 @@ class Turtle: self.shapes = generate_turtle_pixbufs(self.colors) else: if turtles is not None: - self.colors = ['#008000', '#00A000'] + self.colors = DEFAULT_TURTLE_COLORS self.shapes = turtles.get_pixbufs() def set_shapes(self, shapes): diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index c66b322..521637e 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -285,18 +285,30 @@ def get_path(activity, subpath): "org.laptop.TurtleArtActivity", subpath)) -def image_to_base64(pixbuf, activity): - """ Convert an image to base64 """ - _file_name = os.path.join(get_path(activity, 'instance'), 'imagetmp.png') +def image_to_base64(pixbuf, path_name): + """ Convert an image to base64-encoded data """ + file_name = os.path.join(path_name, 'imagetmp.png') if pixbuf != None: - pixbuf.save(_file_name, "png") - _base64 = os.path.join(get_path(activity, 'instance'), 'base64tmp') - _cmd = "base64 <" + _file_name + " >" + _base64 - subprocess.check_call(_cmd, shell=True) - _file_handle = open(_base64, 'r') - _data = _file_handle.read() - _file_handle.close() - return _data + pixbuf.save(file_name, "png") + base64 = os.path.join(path_name, 'base64tmp') + cmd = "base64 <" + file_name + " >" + base64 + subprocess.check_call(cmd, shell=True) + file_handle = open(base64, 'r') + data = file_handle.read() + file_handle.close() + return data + + +def base64_to_image(data, path_name): + """ Convert base64-encoded data to an image """ + base64 = os.path.join(path_name, 'base64tmp') + file_handle = open(base64, 'w') + file_handle.write(data) + file_handle.close() + file_name = os.path.join(path_name, 'imagetmp.png') + cmd = "base64 -d <" + base64 + ">" + file_name + subprocess.check_call(cmd, shell=True) + return file_name def movie_media_type(name): @@ -318,7 +330,7 @@ def image_media_type(name): def text_media_type(name): """ Is it text media? """ - return name.lower().endswith(('.txt', '.py', '.lg', '.rtf', '.ta')) + return name.lower().endswith(('.txt', '.py', '.lg', '.rtf')) def round_int(num): diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index ad830d9..f12a753 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- #Copyright (c) 2007, Playful Invention Company -#Copyright (c) 2008-10, Walter Bender -#Copyright (c) 2009-10 Raúl Gutiérrez Segalés -#Copyright (C) 2010 Emiliano Pastorino +#Copyright (c) 2008-11, Walter Bender +#Copyright (c) 2009-11 Raúl Gutiérrez Segalés #Copyright (c) 2011 Collabora Ltd. #Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,7 +26,14 @@ import pygtk pygtk.require('2.0') import gtk import gobject -import gst + +try: + import gst + GST_AVAILABLE = True +except ImportError: + # Turtle Art should not fail if gst is not available + GST_AVAILABLE = False + import os import os.path import dbus @@ -60,7 +66,7 @@ from taconstants import HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, \ NUMBER_STYLE_PORCH, NUMBER_STYLE_BLOCK, \ NUMBER_STYLE_VAR_ARG, CONSTANTS, XO1, XO15, UNKNOWN, \ BASIC_STYLE_VAR_ARG -from talogo import LogoCode, stop_logo +from talogo import LogoCode from tacanvas import TurtleGraphics from tablock import Blocks, Block from taturtle import Turtles, Turtle @@ -76,18 +82,11 @@ from tautils import magnitude, get_load_name, get_save_name, data_from_file, \ dock_dx_dy, data_to_string, journal_check, chooser, \ get_hardware from tasprite_factory import SVG, svg_str_to_pixbuf, svg_from_file -from tagplay import stop_media from sprites import Sprites, Sprite -from audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15 -from rfidutils import strhex2bin, strbin2dec, find_device from dbus.mainloop.glib import DBusGMainLoop -HAL_SERVICE = 'org.freedesktop.Hal' -HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' -HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' -HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' -REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ - 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' +if GST_AVAILABLE: + from tagplay import stop_media import logging _logger = logging.getLogger('turtleart-activity') @@ -96,13 +95,17 @@ _logger = logging.getLogger('turtleart-activity') class TurtleArtWindow(): """ TurtleArt Window class abstraction """ timeout_tag = [0] + _INSTALL_PATH = '/usr/share/turtleart' + _ALTERNATE_INSTALL_PATH = '/usr/local/share/turtleart' + _PLUGIN_SUBPATH = 'plugins' def __init__(self, win, path, parent=None, mycolors=None, mynick=None): self._loaded_project = '' self.win = None self._sharing = False self.parent = parent - self.send_event = None # method to send events over the network + self.send_event = None # method to send events over the network + self.gst_available = GST_AVAILABLE if type(win) == gtk.DrawingArea: self.interactive_mode = True self.window = win @@ -147,7 +150,11 @@ class TurtleArtWindow(): self.mouse_x = 0 self.mouse_y = 0 - locale.setlocale(locale.LC_NUMERIC, '') + # if self.running_sugar: + try: + locale.setlocale(locale.LC_NUMERIC, '') + except locale.Error: + _logger.debug('unsupported locale') self.decimal_point = locale.localeconv()['decimal_point'] if self.decimal_point == '' or self.decimal_point is None: self.decimal_point = '.' @@ -256,95 +263,91 @@ class TurtleArtWindow(): self._setup_misc() self._show_toolbar_palette(0, False) - # setup sound/sensor grab - if self.hw in [XO1, XO15]: - PALETTES[PALETTE_NAMES.index('sensor')].append('resistance') - PALETTES[PALETTE_NAMES.index('sensor')].append('voltage') - self.audio_started = False - - self.camera_available = False - v4l2src = gst.element_factory_make('v4l2src') - if v4l2src.props.device_name is not None: - PALETTES[PALETTE_NAMES.index('sensor')].append('readcamera') - PALETTES[PALETTE_NAMES.index('sensor')].append('luminance') - PALETTES[PALETTE_NAMES.index('sensor')].append('camera') - self.camera_available = True + self._plugins = [] + self._init_plugins() self.lc = LogoCode(self) - self.saved_pictures = [] + self._setup_plugins() + self.saved_pictures = [] self.block_operation = '' - """ - The following code will initialize a USB RFID reader. Please note that - in order to make this initialization function work, it is necessary to - set the permission for the ttyUSB device to 0666. You can do this by - adding a rule to /etc/udev/rules.d - - As root (using sudo or su), copy the following text into a new file in - /etc/udev/rules.d/94-ttyUSB-rules - - KERNEL=="ttyUSB[0-9]",MODE="0666" - - You only have to do this once. - """ - - self.rfid_connected = False - self.rfid_device = find_device() - self.rfid_idn = '' - - if self.rfid_device is not None: - _logger.info("RFID device found") - self.rfid_connected = self.rfid_device.do_connect() - if self.rfid_connected: - self.rfid_device.connect("tag-read", self._tag_read_cb) - self.rfid_device.connect("disconnected", self._disconnected_cb) - - loop = DBusGMainLoop() - bus = dbus.SystemBus(mainloop=loop) - hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE, - HAL_MGR_PATH), HAL_MGR_IFACE) - - hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb) - - PALETTES[PALETTE_NAMES.index('sensor')].append('rfid') - - def _device_added_cb(self, path): - """ - Called from hal connection when a new device is plugged. - """ - if not self.rfid_connected: - self.rfid_device = find_device() - _logger.debug("DEVICE_ADDED: %s"%self.rfid_device) - if self.rfid_device is not None: - _logger.debug("DEVICE_ADDED: RFID device is not None!") - self.rfid_connected = self._device.do_connect() - if self.rfid_connected: - _logger.debug("DEVICE_ADDED: Connected!") - self.rfid_device.connect("tag-read", self._tag_read_cb) - self.rfid_device.connect("disconnected", self._disconnected_cb) - - def _disconnected_cb(self, device, text): - """ - Called when the device is disconnected. - """ - self.rfid_connected = False - self.rfid_device = None - - def _tag_read_cb(self, device, tagid): - """ - Callback for "tag-read" signal. Receives the read tag id. - """ - idbin = strhex2bin(tagid) - self.rfid_idn = strbin2dec(idbin[26:64]) - while self.rfid_idn.__len__() < 9: - self.rfid_idn = '0' + self.rfid_idn - print tagid, idbin, self.rfid_idn - - def new_buffer(self, buf): - """ Append a new buffer to the ringbuffer """ - self.lc.ringbuffer.append(buf) - return True + def _get_plugin_home(self): + """ Look in current directory first, then usual places """ + path = os.path.join(os.getcwd(), self._PLUGIN_SUBPATH) + if os.path.exists(path): + return path + path = os.path.expanduser(os.path.join('~', 'Activities', + 'TurtleBlocks.activity', + self._PLUGIN_SUBPATH)) + if os.path.exists(path): + return path + path = os.path.expanduser(os.path.join('~', 'Activities', + 'TurtleArt.activity', + self._PLUGIN_SUBPATH)) + if os.path.exists(path): + return path + path = os.path.join(self._INSTALL_PATH, self._PLUGIN_SUBPATH) + if os.path.exists(path): + return path + path = os.path.join(self._ALTERNATE_INSTALL_PATH, + self._PLUGIN_SUBPATH) + if os.path.exists(path): + return path + return None + + def _get_plugin_candidates(self, path): + """ Look for plugin files in plugin directory. """ + plugin_files = [] + if path is not None: + candidates = os.listdir(path) + for c in candidates: + if c[-10:] == '_plugin.py' and c[0] != '#' and c[0] != '.': + plugin_files.append(c.split('.')[0]) + return plugin_files + + def _init_plugins(self): + """ Try importing plugin files from the plugin directory. """ + for pluginfile in self._get_plugin_candidates(self._get_plugin_home()): + pluginclass = pluginfile.capitalize() + f = "def f(self): from plugins.%s import %s; return %s(self)" \ + % (pluginfile, pluginclass, pluginclass) + plugins = {} + try: + exec f in globals(), plugins + self._plugins.append(plugins.values()[0](self)) + except ImportError: + print 'failed to import %s' % (pluginclass) + + def _setup_plugins(self): + """ Initial setup -- call just once. """ + for plugin in self._plugins: + plugin.setup() + + def _start_plugins(self): + """ Start is called everytime we execute blocks. """ + for plugin in self._plugins: + plugin.start() + + def _stop_plugins(self): + """ Stop is called whenever we stop execution. """ + for plugin in self._plugins: + plugin.stop() + + def background_plugins(self): + """ Background is called when we are pushed to the background. """ + for plugin in self._plugins: + plugin.goto_background() + + def foreground_plugins(self): + """ Foreground is called when we are return from the background. """ + for plugin in self._plugins: + plugin.return_to_foreground() + + def _quit_plugins(self): + """ Quit is called upon program exit. """ + for plugin in self._plugins: + plugin.quit() def _setup_events(self): """ Register the events we listen to. """ @@ -420,29 +423,13 @@ class TurtleArtWindow(): self.lc.prim_clear() self.display_coordinates() - def _start_audiograb(self): - """ Start grabbing audio if there is an audio block in use """ - if len(self.block_list.get_similar_blocks('block', - ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0: - if self.audio_started: - self.audiograb.resume_grabbing() - else: - if self.hw == XO15: - self.audiograb = AudioGrab_XO15(self.new_buffer, self) - elif self.hw == XO1: - self.audiograb = AudioGrab_XO1(self.new_buffer, self) - else: - self.audiograb = AudioGrab_Unknown(self.new_buffer, self) - self.audiograb.start_grabbing() - self.audio_started = True - def run_button(self, time): """ Run turtle! """ if self.running_sugar: self.activity.recenter() if self.interactive_mode: - self._start_audiograb() + self._start_plugins() # Look for a 'start' block for blk in self.just_blocks(): @@ -462,9 +449,8 @@ class TurtleArtWindow(): def stop_button(self): """ Stop button """ - stop_logo(self) - if self.audio_started: - self.audiograb.pause_grabbing() + self.lc.stop_logo() + self._stop_plugins() def set_userdefined(self, blk=None): """ Change icon for user-defined blocks after loading Python code. """ @@ -1585,6 +1571,7 @@ class TurtleArtWindow(): blk.spr.labels[0] += CURSOR elif blk.name in BOX_STYLE_MEDIA and blk.name != 'camera': + # TODO: isolate reference to camera self._import_from_journal(self.selected_blk) if blk.name == 'journal' and self.running_sugar: self._load_description_block(blk) @@ -1628,7 +1615,7 @@ class TurtleArtWindow(): dy = 20 blk.expand_in_y(dy) else: - self._start_audiograb() + self._start_plugins() self._run_stack(blk) return @@ -1691,10 +1678,10 @@ class TurtleArtWindow(): elif blk.name in PYTHON_SKIN: self._import_py() else: - self._start_audiograb() + self._start_plugins() self._run_stack(blk) - elif blk.name in ['sandwichtop_no_arm_no_label', + elif blk.name in ['sandwichtop_no_arm_no_label', 'sandwichtop_no_arm']: restore_stack(blk) @@ -1706,7 +1693,7 @@ class TurtleArtWindow(): collapse_stack(top) else: - self._start_audiograb() + self._start_plugins() self._run_stack(blk) def _expand_boolean(self, blk, blk2, dy): @@ -1790,7 +1777,6 @@ class TurtleArtWindow(): """ Run a stack of blocks. """ if blk is None: return - self.lc.ag = None top = find_top_block(blk) self.lc.run_blocks(top, self.just_blocks(), True) if self.interactive_mode: @@ -1996,9 +1982,9 @@ class TurtleArtWindow(): if keyname == "p": self.hideshow_button() elif keyname == 'q': - if self.audio_started: - self.audiograb.stop_grabbing() - stop_media(self.lc) + self._plugins_quit() + if self.gst_available: + stop_media(self.lc) exit() elif keyname == 'g': self._align_to_grid() @@ -2356,7 +2342,7 @@ class TurtleArtWindow(): def new_project(self): """ Start a new project """ - stop_logo(self) + self.lc.stop_logo() self._loaded_project = "" # Put current project in the trash. while len(self.just_blocks()) > 0: @@ -2474,7 +2460,7 @@ class TurtleArtWindow(): if self.running_sugar: try: dsobject = datastore.get(value) - except: # Should be IOError, but dbus error is raised + except: # Should be IOError, but dbus error is raised dsobject = None _logger.debug("couldn't get dsobject %s" % value) if dsobject is not None: @@ -2515,6 +2501,7 @@ class TurtleArtWindow(): else: self._block_skin('pythonoff', blk) elif btype in BOX_STYLE_MEDIA and blk.spr is not None: + # TODO: isolate reference to camera if len(blk.values) == 0 or blk.values[0] == 'None' or \ blk.values[0] is None or btype == 'camera': self._block_skin(btype + 'off', blk) diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py index 98968a8..aa8d5bd 100644 --- a/TurtleArtActivity.py +++ b/TurtleArtActivity.py @@ -43,7 +43,8 @@ from gettext import gettext as _ import os.path import tarfile -from TurtleArt.taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS +from TurtleArt.taconstants import PALETTE_NAMES, OVERLAY_LAYER, HELP_STRINGS, \ + ICON_SIZE from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo from TurtleArt.tautils import data_to_file, data_to_string, data_from_string, \ @@ -164,7 +165,7 @@ class TurtleArtActivity(activity.Activity): def do_load_ta_project_cb(self, button): """ Load a project from the Journal """ - chooser(self, SERVICE, self._load_ta_project) + chooser(self, 'org.laptop.TurtleArtActivity', self._load_ta_project) def _load_ta_project(self, dsobject): """ Load a ta project from the datastore """ @@ -258,7 +259,7 @@ class TurtleArtActivity(activity.Activity): def do_hideshow_cb(self, button): """ Toggle visibility. """ self.tw.hideshow_button() - if self.tw.hide == True: # we just hid the blocks + if self.tw.hide == True: # we just hid the blocks self.blocks_button.set_icon("hideshowon") self.blocks_button.set_tooltip(_('Show blocks')) else: @@ -428,10 +429,10 @@ class TurtleArtActivity(activity.Activity): def __visibility_notify_cb(self, window, event): """ Callback method for when the activity's visibility changes. """ if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED: - self.tw.lc.ag = None + self.tw.background_plugins() elif event.state in \ [gtk.gdk.VISIBILITY_UNOBSCURED, gtk.gdk.VISIBILITY_PARTIAL]: - pass + self.tw.foreground_plugins() def update_title_cb(self, widget, event, toolbox): """ Update the title. """ @@ -531,14 +532,16 @@ class TurtleArtActivity(activity.Activity): self.save_as_logo = self._add_button('logo-saveoff', _("Save as Logo"), self.do_save_as_logo_cb, journal_toolbar_button) - self.save_as_image = self._add_button('image-saveoff', _("Save as image"), + self.save_as_image = self._add_button('image-saveoff', _( + "Save as image"), self.do_save_as_image_cb, journal_toolbar_button) self.load_ta_project = self._add_button('load-from-journal', _("Import project from the Journal"), self.do_load_ta_project_cb, journal_toolbar_button) self._add_separator(journal_toolbar) - self.load_python = self._add_button('pippy-openoff', _("Load Python block"), + self.load_python = self._add_button('pippy-openoff', _( + "Load Python block"), self.do_load_python_cb, journal_toolbar_button) self.samples_button = self._add_button("ta-open", _('Load example'), @@ -547,8 +550,8 @@ class TurtleArtActivity(activity.Activity): edit_toolbar_button, 'c') paste = self._add_button('edit-paste', _('Paste'), self._paste_cb, edit_toolbar_button, 'v') - fullscreen_button = self._add_button('view-fullscreen', _("Fullscreen"), - self.do_fullscreen_cb, + fullscreen_button = self._add_button('view-fullscreen', + _("Fullscreen"), self.do_fullscreen_cb, view_toolbar_button, 'Return') cartesian_button = self._add_button('view-Cartesian', _("Cartesian coordinates"), @@ -566,11 +569,11 @@ class TurtleArtActivity(activity.Activity): view_toolbar_button) self.resize_up_button = self._add_button('resize+', _("Grow blocks"), self.do_grow_blocks_cb, view_toolbar_button) - self.resize_down_button = self._add_button('resize-', _("Shrink blocks"), - self.do_shrink_blocks_cb, view_toolbar_button) + self.resize_down_button = self._add_button('resize-', + _("Shrink blocks"), self.do_shrink_blocks_cb, view_toolbar_button) self.hover_help_label = self._add_label( _("Move the cursor over the orange palette for help."), - help_toolbar) + help_toolbar, gtk.gdk.screen_width() - 2 * ICON_SIZE) # The palette toolbar is only used with 0.86+ if self.new_sugar_system: @@ -607,7 +610,8 @@ class TurtleArtActivity(activity.Activity): def _make_palette_buttons(self, toolbar, palette_button=False): """ Creates the palette and block buttons for both toolbar types""" if palette_button: # old-style toolbars need this button - self.palette_button = self._add_button("paletteoff", _('Hide palette'), + self.palette_button = self._add_button("paletteoff", _( + 'Hide palette'), self.do_palette_cb, toolbar, _('p')) self.blocks_button = self._add_button("hideshowoff", _('Hide blocks'), self.do_hideshow_cb, toolbar, _('b')) @@ -616,14 +620,14 @@ class TurtleArtActivity(activity.Activity): """ Creates the turtle buttons for both toolbar types""" self.eraser_button = self._add_button("eraseron", _('Clean'), self.do_eraser_cb, toolbar, _('e')) - self.run_button = self._add_button("run-fastoff", _('Run'), self.do_run_cb, - toolbar, _('r')) + self.run_button = self._add_button("run-fastoff", _('Run'), + self.do_run_cb, toolbar, _('r')) self.step_button = self._add_button("run-slowoff", _('Step'), self.do_step_cb, toolbar, _('w')) self.debug_button = self._add_button("debugoff", _('Debug'), self.do_debug_cb, toolbar, _('d')) - self.stop_turtle_button = self._add_button("stopitoff", _('Stop turtle'), - self.do_stop_cb, toolbar, _('s')) + self.stop_turtle_button = self._add_button("stopitoff", + _('Stop turtle'), self.do_stop_cb, toolbar, _('s')) def _setup_scrolled_window(self): """ Create a scrolled window to contain the turtle canvas. """ @@ -681,7 +685,7 @@ class TurtleArtActivity(activity.Activity): if self._jobject and self._jobject.file_path: self.read_file(self._jobject.file_path) - else: # if new, load a start brick onto the canvas + else: # if new, load a start brick onto the canvas self.tw.load_start() def _setup_sharing(self): @@ -717,7 +721,7 @@ class TurtleArtActivity(activity.Activity): # If run_it is True, we want to create a new project tar_fd.extractall(tmpdir) self.tw.load_files(os.path.join(tmpdir, 'ta_code.ta'), \ - run_it) # create a new project flag + run_it) # create a new project flag finally: shutil.rmtree(tmpdir) tar_fd.close() @@ -777,10 +781,12 @@ class TurtleArtActivity(activity.Activity): self.tw.paste_offset) self.tw.paste_offset += 20 - def _add_label(self, string, toolbar): + def _add_label(self, string, toolbar, width=None): """ add a label to a toolbar """ label = gtk.Label(string) label.set_line_wrap(True) + if width is not None: + label.set_size_request(width, -1) label.show() toolitem = gtk.ToolItem() toolitem.add(label) @@ -796,7 +802,8 @@ class TurtleArtActivity(activity.Activity): toolbar.insert(separator, -1) separator.show() - def _add_button(self, name, tooltip, callback, toolbar, accelerator=None, arg=None): + def _add_button(self, name, tooltip, callback, toolbar, accelerator=None, + arg=None): """ add a button to a toolbar """ button = ToolButton(name) button.set_tooltip(tooltip) @@ -810,7 +817,7 @@ class TurtleArtActivity(activity.Activity): except AttributeError: pass button.show() - if hasattr(toolbar, 'insert'): # the main toolbar + if hasattr(toolbar, 'insert'): # the main toolbar toolbar.insert(button, -1) else: # or a secondary toolbar toolbar.props.page.insert(button, -1) diff --git a/devices/__init__.py b/audio/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/audio/__init__.py diff --git a/TurtleArt/audiograb.py b/audio/audiograb.py index 3ecdc11..1240be7 100644 --- a/TurtleArt/audiograb.py +++ b/audio/audiograb.py @@ -21,6 +21,11 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +SENSOR_AC_NO_BIAS = 'external' +SENSOR_AC_BIAS = 'sound' +SENSOR_DC_NO_BIAS = 'voltage' +SENSOR_DC_BIAS = 'resistance' + import pygst import gst import gst.interfaces @@ -49,8 +54,7 @@ _logger = logging.getLogger('TurtleArt') _logger.setLevel(logging.DEBUG) logging.basicConfig() -from taconstants import SENSOR_AC_NO_BIAS, SENSOR_AC_BIAS, SENSOR_DC_NO_BIAS, \ - SENSOR_DC_BIAS, XO1 +from TurtleArt.taconstants import XO1 class AudioGrab: diff --git a/TurtleArt/ringbuffer.py b/audio/ringbuffer.py index 2afb5c9..2afb5c9 100644 --- a/TurtleArt/ringbuffer.py +++ b/audio/ringbuffer.py diff --git a/extra/__init__.py b/camera/__init__.py index e69de29..e69de29 100644 --- a/extra/__init__.py +++ b/camera/__init__.py diff --git a/TurtleArt/tacamera.py b/camera/tacamera.py index 2177288..2177288 100644 --- a/TurtleArt/tacamera.py +++ b/camera/tacamera.py diff --git a/TurtleArt/v4l2.py b/camera/v4l2.py index 9c052fd..9c052fd 100644 --- a/TurtleArt/v4l2.py +++ b/camera/v4l2.py diff --git a/collaboration/neighborhood.py b/collaboration/neighborhood.py index b587bc8..192b66f 100755 --- a/collaboration/neighborhood.py +++ b/collaboration/neighborhood.py @@ -1,8 +1,21 @@ #!/usr/bin/python +# Copyright (C) 2007, Red Hat, Inc. +# Copyright (C) 2010-11 Collabora Ltd. # -# neighborhood.py +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. # +# This library 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 +# Lesser General Public License for more details. # +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. from functools import partial from hashlib import sha1 diff --git a/collaboration/telepathyclient.py b/collaboration/telepathyclient.py index 5491530..f3e8578 100644 --- a/collaboration/telepathyclient.py +++ b/collaboration/telepathyclient.py @@ -18,11 +18,15 @@ import logging import dbus from dbus import PROPERTIES_IFACE -from telepathy.interfaces import CLIENT, \ - CLIENT_APPROVER, \ - CLIENT_HANDLER, \ - CLIENT_INTERFACE_REQUESTS -from telepathy.server import DBusProperties +try: + from telepathy.interfaces import CLIENT, \ + CLIENT_APPROVER, \ + CLIENT_HANDLER, \ + CLIENT_INTERFACE_REQUESTS + from telepathy.server import DBusProperties + TELEPATHY_AVAILABLE = True +except ImportError: + TELEPATHY_AVAILABLE = False import dispatch @@ -34,7 +38,10 @@ _instance = None class TelepathyClient(dbus.service.Object, DBusProperties): + def __init__(self): + if not TELEPATHY_AVAILABLE: + return None self._interfaces = set([CLIENT, CLIENT_HANDLER, CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE, CLIENT_APPROVER]) diff --git a/extra/plugin.py b/extra/plugin.py deleted file mode 100644 index fe95f95..0000000 --- a/extra/plugin.py +++ /dev/null @@ -1,10 +0,0 @@ - -import gobject - -class Plugin(gobject.GObject): - def __init__(self): - gobject.GObject.__init__(self) - - def get_menu(self): - raise RuntimeError("You need to define get_menu for your plugin.") - diff --git a/devices/__init__.py b/gnome_plugins/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/gnome_plugins/__init__.py diff --git a/extra/collaborationplugin.py b/gnome_plugins/collaboration_plugin.py index 56fcae5..54abdc8 100644 --- a/extra/collaborationplugin.py +++ b/gnome_plugins/collaboration_plugin.py @@ -1,3 +1,24 @@ +#!/usr/bin/env python +#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. import sys sys.path.append("..") @@ -6,23 +27,29 @@ import dbus from gettext import gettext as _ import gobject import gtk + from plugin import Plugin + from util.menubuilder import MenuBuilder from util.configfile import ConfigFile from util.configwizard import ConfigWizard + import telepathy from collaboration.neighborhood import get_neighborhood from collaboration.connectionmanager import get_connection_manager from collaboration.activity import Activity from collaboration import telepathyclient from collaboration.tubeconn import TubeConnection -import traceback + from TurtleArt.tacollaboration import Collaboration +import traceback + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ 'org.laptop.Telepathy.ActivityProperties' -class CollaborationPlugin(Plugin): + +class Collaboration_plugin(Plugin): __gsignals__ = { 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -31,56 +58,75 @@ class CollaborationPlugin(Plugin): ()), } - # Using activity here is kinda misleading cause the - # obj we are receiving is not a Sugar activity. - def __init__(self, activity, config_file_path): + def __init__(self, parent): Plugin.__init__(self) - self._activity = activity + self._parent = parent self._neighborhood = None - self._title = "My Turtle Art session" + self._title = _('My Turtle Art session') self._bundle_id = "org.laptop.TurtleArt" - self._activity_id = "1234567" # This could be hashed from the file path (if resuming) + # This could be hashed from the file path (if resuming) + self._activity_id = "1234567" self._nick = "" + self._setup_has_been_called = False + + def _setup_config_file(self, config_file_path): self._config_file_path = config_file_path self._collaboration_config_values = ConfigFile(self._config_file_path) - valid_config_values = { - "nick" : { "type" : "text"}, - "account_id" : { "type" : "text"}, - "password" : { "type" : "text"}, - "server" : { "type" : "text"}, - "port" : { "type" : "integer"}, - "register": { "type" : "boolean"}, - "turtle_color" : { "type" : "text"}, - "colors" : { "type" : "text"} + self._valid_config_values = { + 'nick': {'type': 'text'}, + 'account_id': {'type': 'text'}, + 'password': {'type': 'text'}, + 'server': {'type': 'text'}, + 'port': {'type': 'integer'}, + 'register': {'type': 'boolean'}, + 'colors': {'type': 'text'} } - self._collaboration_config_values.set_valid_keys(valid_config_values) - self._collaboration_config_values.connect("configuration-loaded", self._connect_to_neighborhood) - self._collaboration_config_values.connect("configuration-saved", self._connect_to_neighborhood) + + def _connect_cb(self, button): + """ Enable connection """ + self._collaboration_config_values.set_valid_keys( + self._valid_config_values) + self._collaboration_config_values.connect( + 'configuration-loaded', self._connect_to_neighborhood) + self._collaboration_config_values.connect( + 'configuration-saved', self._connect_to_neighborhood) self._collaboration_config_values.load() + self.setup() def setup(self): self._collaboration = Collaboration(self.tw, self) self._collaboration.setup() + # Do we know if we were successful? + self._setup_has_been_called = True + # TODO: + # use set_sensitive to enable Share and Configuration menuitems def set_tw(self, turtleart_window): self.tw = turtleart_window self.tw.nick = self._get_nick() + self._setup_config_file(self._parent.get_config_home()) def get_menu(self): menu = gtk.Menu() + MenuBuilder.make_menu_item(menu, _('Enable collaboration'), + self._connect_cb) + self._activities_submenu = gtk.Menu() - activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, _('Activities')) + activities_menu = MenuBuilder.make_sub_menu(self._activities_submenu, + _('Activities')) menu.append(activities_menu) self._buddies_submenu = gtk.Menu() - buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, _('Buddies')) + buddies_menu = MenuBuilder.make_sub_menu(self._buddies_submenu, + _('Buddies')) menu.append(buddies_menu) - + MenuBuilder.make_menu_item(menu, _('Share'), self._share_cb) - MenuBuilder.make_menu_item(menu, _('Configuration'), self._config_neighborhood_cb) - + MenuBuilder.make_menu_item(menu, _('Configuration'), + self._config_neighborhood_cb) + neighborhood_menu = MenuBuilder.make_sub_menu(menu, _('Neighborhood')) return neighborhood_menu @@ -99,40 +145,42 @@ class CollaborationPlugin(Plugin): def _get_title(self): return self._title - - def _get_turtle_color(self): - return self._turtle_color def _connect_to_neighborhood(self, config_file_obj): if self._neighborhood is not None: return - print "_connect_to_neighborhood has been called" - params = {} - params["nickname"] = self._collaboration_config_values.get("nick") - params["account_id"] = self._collaboration_config_values.get("account_id") - params["server"] = self._collaboration_config_values.get("server") - params["port"] = self._collaboration_config_values.get("port") - params["password"] = self._collaboration_config_values.get("password") - params["register"] = self._collaboration_config_values.get("register") - if params["server"] == "": - raise RuntimeError("Invalid server address") - - self._nick = self._collaboration_config_values.get("nick") - self._colors = self._collaboration_config_values.get("colors") - self._turtle_color = self._collaboration_config_values.get("turtle_color") + params['nickname'] = self._collaboration_config_values.get('nick') + params['account_id'] = self._collaboration_config_values.get( + 'account_id') + params['server'] = self._collaboration_config_values.get('server') + params['port'] = self._collaboration_config_values.get('port') + params['password'] = self._collaboration_config_values.get('password') + params['register'] = self._collaboration_config_values.get('register') + if params['server'] == '': + raise RuntimeError('Invalid server address') + + self._nick = self._collaboration_config_values.get('nick') + # Tell the parent activity that the nick may have changed + self._parent.nick_changed(self._nick) + + self._colors = self._collaboration_config_values.get('colors') + # Tell the parent activity that the colors may have changed + self._parent.color_changed(self._colors) self._activities = {} self._buddies = {} - print "connecting to the neighborhood" dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - + self._client_handler = telepathyclient.get_instance() + if self._client_handler == None: + raise RuntimeError('Telepathy client unavailable') self._neighborhood = get_neighborhood(params) self._neighborhood.connect('activity-added', self._activity_added_cb) - self._neighborhood.connect('activity-removed', self._activity_removed_cb) + self._neighborhood.connect('activity-removed', + self._activity_removed_cb) self._neighborhood.connect('buddy-added', self._buddy_added_cb) self._neighborhood.connect('buddy-removed', self._buddy_removed_cb) @@ -147,7 +195,7 @@ class CollaborationPlugin(Plugin): try: self._activities.pop(activity_model.props.name) except: - print "Failed to remove activity %s" % activity_model.props.name + print 'Failed to remove activity %s' % activity_model.props.name self._recreate_available_activities_menu() @@ -162,7 +210,7 @@ class CollaborationPlugin(Plugin): print "Couldn't remove buddy %s" % buddy.get_key() self._recreate_available_buddies_menu() - # TODO: we should have a list of available actions over + # TODO: we should have a list of available actions over # a given buddy. I.e.: a) chat with him b) make friend # c) invite to current activity # @@ -173,26 +221,28 @@ class CollaborationPlugin(Plugin): for buddy in self._buddies.values(): key = buddy.get_key() if key is None: - key = "" - n = buddy.get_nick() + "|" + key[0:15] - MenuBuilder.make_menu_item(self._buddies_submenu, n, self._buddy_actions_cb, buddy) + key = '' + n = buddy.get_nick() + '|' + key[0:15] + MenuBuilder.make_menu_item(self._buddies_submenu, n, + self._buddy_actions_cb, buddy) def _buddy_actions_cb(self, widget, buddy): - print "do something with %s" % buddy.get_nick() + print 'do something with %s' % buddy.get_nick() - # TODO - # - we need an extra menu branch with a) 'Join' button b) List of buddies + # TODO: + # we need an extra menu branch with a) 'Join' button b) List of buddies def _recreate_available_activities_menu(self): for child in self._activities_submenu.get_children(): self._activities_submenu.remove(child) for activity in self._activities.values(): n = activity.props.name - MenuBuilder.make_menu_item(self._activities_submenu, n, self._join_activity_cb, activity) + MenuBuilder.make_menu_item(self._activities_submenu, n, + self._join_activity_cb, activity) def _join_activity_cb(self, widget, activity): - print "Lets try to join..." - + print 'Lets try to join...' + connection_manager = get_connection_manager() account_path, connection = \ connection_manager.get_preferred_connection() @@ -201,17 +251,17 @@ class CollaborationPlugin(Plugin): return properties = {} - properties["id"] = activity.activity_id - properties["color"] = activity.get_color() - print "room handle according to activity %s" % activity.room_handle - properties["private"] = True + properties['id'] = activity.activity_id + properties['color'] = activity.get_color() + print 'room handle according to activity %s' % activity.room_handle + properties['private'] = True try: room_handle = connection.GetActivity(activity.activity_id, - dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) - print("room_handle = %s" % str(room_handle)) - self._joined_activity = Activity(account_path, connection, room_handle, - properties=properties) + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES) + print('room_handle = %s' % str(room_handle)) + self._joined_activity = Activity( + account_path, connection, room_handle, properties=properties) # FIXME: this should be unified, no need to keep 2 references self._shared_activity = self._joined_activity except: @@ -224,32 +274,42 @@ class CollaborationPlugin(Plugin): _join_id = self._joined_activity.connect('joined', self.__joined_cb) self._joined_activity.join() - def __joined_cb(self,activity, success, err): + def __joined_cb(self, activity, success, err): print "We've joined an activity" self.emit('joined') def _config_neighborhood_cb(self, widget): + if not self._setup_has_been_called: + return config_w = ConfigWizard(self._config_file_path) config_items = [ - {"item_label" : _("Nickname"), "item_type" : "text", "item_name" : "nick" }, - { "item_label" : _("Account ID"), "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : _("Server"), "item_type" : "text", "item_name" : "server" }, - { "item_label" : _("Port"), "item_type" : "text", "item_name" : "port" }, - { "item_label" : _("Password"), "item_type" : "text", "item_name" : "password" }, - { "item_label" : _("Register"), "item_type" : "boolean", "item_name" : "register" }, - { "item_label" : _("Colors"), "item_type" : "text", "item_name" : "colors" }, - { "item_label" : _("Turtle Color"), "item_type" : "text", "item_name" : "turtle_color" } + {'item_label': _('Nickname'), 'item_type': 'text', + 'item_name': 'nick'}, + {'item_label': _('Account ID'), 'item_type': 'text', + 'item_name': 'account_id'}, + {'item_label': _('Server'), 'item_type': 'text', + 'item_name': 'server'}, + {'item_label': _('Port'), 'item_type': 'text', + 'item_name': 'port'}, + {'item_label': _('Password'), 'item_type': 'text', + 'item_name': 'password'}, + {'item_label': _('Register'), 'item_type': 'boolean', + 'item_name': 'register'}, + {'item_label': _('Colors'), 'item_type': 'text', + 'item_name': 'colors'} ] config_w.set_config_items(config_items) config_w.set_config_file_obj(self._collaboration_config_values) config_w.show() def _share_cb(self, button): + if not self._setup_has_been_called: + return properties = {} properties['id'] = self._get_activity_id() properties['type'] = self._get_bundle_id() properties['name'] = self._get_title() - properties['color'] = self._get_turtle_color() + properties['color'] = self.get_colors() properties['private'] = False connection_manager = get_connection_manager() @@ -261,18 +321,19 @@ class CollaborationPlugin(Plugin): return try: - self._activity._shared_activity = Activity(account_path, connection, - properties=properties) + self._parent._shared_activity = Activity(account_path, + connection, + properties=properties) # FIXME: this should be unified, no need to keep 2 references - self._shared_activity = self._activity._shared_activity + self._shared_activity = self._parent._shared_activity except: traceback.print_exc(file=sys.stdout) - if self._activity._shared_activity.props.joined: + if self._parent._shared_parent.props.joined: raise RuntimeError('Activity %s is already shared.' % - self._activity._get_activity_id()) + self._parent._get_activity_id()) - self._activity._shared_activity.share(self.__share_activity_cb, + self._parent._shared_parent.share(self.__share_activity_cb, self.__share_activity_error_cb) def __share_activity_cb(self, activity): @@ -280,10 +341,8 @@ class CollaborationPlugin(Plugin): self.emit('shared') def __share_activity_error_cb(self, activity, error): - """Notify with GObject event of unsuccessful sharing of activity - """ - print "%s got error: %s" % (activity, error) - -if __name__ == "__main__": - print "testing collaboration" + """Notify with GObject event of unsuccessful sharing of activity""" + print '%s got error: %s' % (activity, error) +if __name__ == '__main__': + print 'testing collaboration' diff --git a/TurtleArt/tacamera.py b/gnome_plugins/plugin.py index 2177288..590c9bc 100644 --- a/TurtleArt/tacamera.py +++ b/gnome_plugins/plugin.py @@ -1,6 +1,6 @@ -# -*- coding: utf-8 -*- -#Copyright (c) 2010, Walter Bender -#Copyright (c) 2010, Tony Forster +#!/usr/bin/env python +#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 @@ -20,23 +20,15 @@ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN #THE SOFTWARE. -import gst, time +import gobject -GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] -class Camera(): - """ A class for representing the video camera """ +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) - def __init__(self, imagepath): - GST_PIPE.append('filesink location=%s' % imagepath) - self.pipe = gst.parse_launch('!'.join(GST_PIPE)) - self.bus = self.pipe.get_bus() - - def save_camera_input_to_file(self): - """ Grab a frame from the camera """ - self.pipe.set_state(gst.STATE_PLAYING) - self.bus.poll(gst.MESSAGE_EOS, -1) - - def stop_camera_input(self): - self.pipe.set_state(gst.STATE_NULL) + def get_menu(self): + raise RuntimeError("You need to define get__menu for your plugin.") + def set_tw(self, turtle_window=None): + raise RuntimeError("You need to define set_tw for your plugin.") diff --git a/extra/upload.py b/gnome_plugins/uploader_plugin.py index e39d099..06224fa 100644 --- a/extra/upload.py +++ b/gnome_plugins/uploader_plugin.py @@ -1,3 +1,26 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +#Copyright (c) 2010 Jamie Boisture +#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. + #!/usr/bin/python try: @@ -8,14 +31,21 @@ except ImportError, e: print "Import Error: %s. Project upload is disabled." % (e) _UPLOAD_AVAILABLE = False +import os import gtk + +from plugin import Plugin +from util.menubuilder import MenuBuilder + from gettext import gettext as _ -class Uploader(): + +class Uploader_plugin(Plugin): MAX_FILE_SIZE = 950000 UPLOAD_SERVER = 'http://turtleartsite.appspot.com' - def __init__(self, upload_server = None, max_file_size = 0): + def __init__(self, parent, upload_server=None, max_file_size=None): + self._parent = parent self.uploading = False if upload_server is None: @@ -23,14 +53,23 @@ class Uploader(): if max_file_size is None: self._max_file_size = self.MAX_FILE_SIZE + else: + self._max_file_size = max_file_size def set_tw(self, turtleart_window): self.tw = turtleart_window + def get_menu(self): + menu = gtk.Menu() + MenuBuilder.make_menu_item(menu, _('Upload to Web'), + self.do_upload_to_web) + upload_menu = MenuBuilder.make_sub_menu(menu, _('Upload')) + return upload_menu + def enabled(self): return _UPLOAD_AVAILABLE - def do_upload_to_web(self, widget = None): + def do_upload_to_web(self, widget=None): if self.uploading: return @@ -127,14 +166,14 @@ http://turtleartsite.sugarlabs.org to upload your project.')) tafile, imagefile = self.tw.save_for_upload(title) # Set a maximum file size for image to be uploaded. - if int(os.path.getsize(imagefile)) > self.max_file_size: + if int(os.path.getsize(imagefile)) > self._max_file_size: import Image while int(os.path.getsize(imagefile)) > self._max_file_size: big_file = Image.open(imagefile) smaller_file = big_file.resize(int(0.9 * big_file.size[0]), int(0.9 * big_file.size[1]), Image.ANTIALIAS) - smaller_file.save(imagefile, quality = 100) + smaller_file.save(imagefile, quality=100) c = pycurl.Curl() c.setopt(c.POST, 1) diff --git a/icons/blocksoff.svg b/icons/blocksoff.svg index f0e10b3..e7db290 100644 --- a/icons/blocksoff.svg +++ b/icons/blocksoff.svg @@ -2,13 +2,29 @@ + + + + image/svg+xml + + + + + + - - + + + + + + diff --git a/icons/blockson.svg b/icons/blockson.svg index df19fdb..4435cc2 100644 --- a/icons/blockson.svg +++ b/icons/blockson.svg @@ -2,89 +2,29 @@ + + + + image/svg+xml + + + + + - - - - - - - - - - - - + id="defs4" /> + id="g3788"> + d="m 15.719636,31.331478 0.114372,5.261133 11.437247,6.290486 L 27.5,29.387146" + id="path2463" + style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + d="m 27.728744,29.501519 0.05719,13.381578 11.723178,-7.548583 0,-4.689272 -5.947368,3.545548" + id="path2465" + style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#804000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + + diff --git a/icons/extrasoff.svg b/icons/extrasoff.svg index 47547fc..7975a9d 100644 --- a/icons/extrasoff.svg +++ b/icons/extrasoff.svg @@ -2,14 +2,29 @@ + + + + image/svg+xml + + + + + + id="defs8" /> + transform="matrix(0.64,0,0,0.63959066,9.90448,9.9400384)" + id="g3"> + + + + + + + + - - - + d="m 39.925,22.352 c 2.845,6.863 -0.412,14.728 -7.274,17.574 -6.867,2.85 -14.734,-0.418 -17.578,-7.281 -2.84,-6.862 0.418,-14.733 7.279,-17.572 6.862,-2.845 14.734,0.417 17.573,7.279 z" + id="path21" + style="fill:none;stroke:#ffffff;stroke-width:11.69439983" /> diff --git a/icons/extrason.svg b/icons/extrason.svg index 3d2cd85..7ee08bf 100644 --- a/icons/extrason.svg +++ b/icons/extrason.svg @@ -2,14 +2,104 @@ + + + + image/svg+xml + + + + + + id="defs5"> + + + + + + + + + + + + + id="g3799"> + + + + + + + + - - - + d="m 35.45648,24.236169 c 1.8208,4.389511 -0.26368,9.419891 -4.65536,11.240166 -4.39488,1.822833 -9.42976,-0.267349 -11.24992,-4.65686 -1.8176,-4.388871 0.26752,-9.423089 4.65856,-11.238887 4.39168,-1.819635 9.42976,0.26671 11.24672,4.655581 z" + id="path21" + style="fill:none;stroke:#ff0000;stroke-width:7.48202181;stroke-opacity:1" /> diff --git a/icons/mediaoff.svg b/icons/mediaoff.svg new file mode 100644 index 0000000..b2f460a --- /dev/null +++ b/icons/mediaoff.svg @@ -0,0 +1,46 @@ + + + + + +` + + + + + + + diff --git a/icons/mediaon.svg b/icons/mediaon.svg new file mode 100644 index 0000000..27dd516 --- /dev/null +++ b/icons/mediaon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/icons/portfoliooff.svg b/icons/portfoliooff.svg index b2f460a..d404bab 100644 --- a/icons/portfoliooff.svg +++ b/icons/portfoliooff.svg @@ -2,45 +2,71 @@ - + id="svg2"> + + + image/svg+xml + + + + + + + + + ` - + transform="matrix(0.8501594,0,0,0.8501594,4.1206165,2.9055499)" + id="g3041"> + + + - - - + id="line2460-2" + y2="52.313156" + y1="36.358479" + x2="33.749943" + x1="29.474899" + style="fill:none;stroke:#ffffff;stroke-width:1.78056884;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /> + + diff --git a/icons/portfolioon.svg b/icons/portfolioon.svg index fa4ddf6..e174ee2 100644 --- a/icons/portfolioon.svg +++ b/icons/portfolioon.svg @@ -2,6 +2,9 @@ + + + + image/svg+xml + + + + + - + transform="matrix(0.8501594,0,0,0.8501594,4.1206165,2.9055499)" + id="g3816" + style="fill:#f9f9f9;stroke:#0000ff;stroke-opacity:1"> + + + + + - - - + style="fill:#f9f9f9;stroke:#0000ff;stroke-width:2.66399026;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" + x1="9.0131855" + x2="45.986813" + y1="8.168005" + y2="8.168005" + id="line2460-0" /> diff --git a/devices/__init__.py b/plugins/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/plugins/__init__.py diff --git a/plugins/__init__.pyc b/plugins/__init__.pyc new file mode 100644 index 0000000..ce06608 --- /dev/null +++ b/plugins/__init__.pyc Binary files differ diff --git a/plugins/audio_sensors_plugin.py b/plugins/audio_sensors_plugin.py new file mode 100644 index 0000000..d8908c8 --- /dev/null +++ b/plugins/audio_sensors_plugin.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender + +#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. + +import gst +try: + from numpy import append + from numpy.fft import rfft + PITCH_AVAILABLE = True +except: + PITCH_AVAILABLE = False +import gtk + +from gettext import gettext as _ + +from plugin import Plugin + +from audio.audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15, \ + SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS + +from audio.ringbuffer import RingBuffer1d + +from TurtleArt.taconstants import PALETTES, PALETTE_NAMES, BOX_STYLE_MEDIA, \ + CONTENT_BLOCKS, BLOCK_NAMES, DEFAULTS, SPECIAL_NAMES, HELP_STRINGS, \ + BOX_STYLE, PRIMITIVES, XO1, XO15 +from TurtleArt.talogo import VALUE_BLOCKS, PLUGIN_DICTIONARY +from TurtleArt.tautils import get_path + +import logging +_logger = logging.getLogger('turtleart-activity audio sensors plugin') + + +def _avg(array, abs_value=False): + """ Calc. the average value of an array """ + if len(array) == 0: + return 0 + array_sum = 0 + if abs_value: + for a in array: + array_sum += abs(a) + else: + for a in array: + array_sum += a + return float(array_sum) / len(array) + + +class Audio_sensors_plugin(Plugin): + + def __init__(self, parent): + self._parent = parent + self.hw = self._parent.hw + self._status = True # TODO: test for audio device + + def setup(self): + # set up audio-sensor-specific blocks + if not self._status: + return + + self.max_samples = 1500 + self.input_step = 1 + + self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16') + if self.hw == XO1: + self.voltage_gain = 0.00002225 + self.voltage_bias = 1.140 + elif self.hw == XO15: + self.voltage_gain = -0.0001471 + self.voltage_bias = 1.695 + + PALETTES[PALETTE_NAMES.index('sensor')].append('sound') + BOX_STYLE.append('sound') + BLOCK_NAMES['sound'] = [_('sound')] + HELP_STRINGS['sound'] = _('raw microphone input signal') + VALUE_BLOCKS.append('sound') + PRIMITIVES['sound'] = 'sound' + PLUGIN_DICTIONARY['sound'] = self.prim_sound + self._parent.lc._def_prim('sound', 0, + lambda self: PLUGIN_DICTIONARY['sound']()) + PALETTES[PALETTE_NAMES.index('sensor')].append('volume') + BOX_STYLE.append('volume') + BLOCK_NAMES['volume'] = [_('volume')] + HELP_STRINGS['volume'] = _('microphone input volume') + VALUE_BLOCKS.append('volume') + PRIMITIVES['volume'] = 'volume' + PLUGIN_DICTIONARY['volume'] = self.prim_volume + self._parent.lc._def_prim('volume', 0, + lambda self: PLUGIN_DICTIONARY['volume']()) + PALETTES[PALETTE_NAMES.index('sensor')].append('pitch') + BOX_STYLE.append('pitch') + BLOCK_NAMES['pitch'] = [_('pitch')] + HELP_STRINGS['pitch'] = _('microphone input pitch') + VALUE_BLOCKS.append('pitch') + PRIMITIVES['pitch'] = 'pitch' + PLUGIN_DICTIONARY['pitch'] = self.prim_pitch + self._parent.lc._def_prim('pitch', 0, + lambda self: PLUGIN_DICTIONARY['pitch']()) + + if self.hw in [XO1, XO15]: + PALETTES[PALETTE_NAMES.index('sensor')].append('resistance') + BOX_STYLE.append('resistance') + BLOCK_NAMES['resistance'] = [_('resistance')] + HELP_STRINGS['resistance'] = _('sensor input resistance') + VALUE_BLOCKS.append('resistance') + PRIMITIVES['resistance'] = 'resistance' + PLUGIN_DICTIONARY['resistance'] = self.prim_resistance + self._parent.lc._def_prim('resistance', 0, + lambda self: PLUGIN_DICTIONARY['resistance']()) + + PALETTES[PALETTE_NAMES.index('sensor')].append('voltage') + BOX_STYLE.append('voltage') + BLOCK_NAMES['voltage'] = [_('voltage')] + HELP_STRINGS['voltage'] = _('sensor voltage') + VALUE_BLOCKS.append('voltage') + PRIMITIVES['voltage'] = 'voltage' + PLUGIN_DICTIONARY['voltage'] = self.prim_voltage + self._parent.lc._def_prim('voltage', 0, + lambda self: PLUGIN_DICTIONARY['voltage']()) + self.audio_started = False + + def start(self): + # This gets called by the start button + if not self._status: + return + """ Start grabbing audio if there is an audio block in use """ + if len(self._parent.block_list.get_similar_blocks('block', + ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0: + if self.audio_started: + self.audiograb.resume_grabbing() + else: + if self.hw == XO15: + self.audiograb = AudioGrab_XO15(self.new_buffer, self) + elif self.hw == XO1: + self.audiograb = AudioGrab_XO1(self.new_buffer, self) + else: + self.audiograb = AudioGrab_Unknown(self.new_buffer, self) + self.audiograb.start_grabbing() + self.audio_started = True + self._update_audio_mode() + + def new_buffer(self, buf): + """ Append a new buffer to the ringbuffer """ + self.ringbuffer.append(buf) + return True + + def _update_audio_mode(self): + """ If there are sensor blocks, set the appropriate audio mode """ + if not hasattr(self._parent.lc, 'value_blocks'): + return + for name in ['sound', 'volume', 'pitch']: + if name in self._parent.lc.value_blocks: + if len(self._parent.lc.value_blocks[name]) > 0: + self.audiograb.set_sensor_type() + return + if 'resistance' in self._parent.lc.value_blocks: + if len(self._parent.lc.value_blocks['resistance']) > 0: + self.audiograb.set_sensor_type(SENSOR_DC_BIAS) + return + if 'voltage' in self._parent.lc.value_blocks: + if len(self._parent.lc.value_blocks['voltage']) > 0: + self.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS) + return + + def stop(self): + # This gets called by the stop button + if self._status: + if self.audio_started: + self.audiograb.pause_grabbing() + + def goto_background(self): + # This gets called when your process is sent to the background + pass + + def return_to_foreground(self): + # This gets called when your process returns from the background + pass + + def quit(self): + # This gets called by the quit button + self.stop() + + def _status_report(self): + print 'Reporting audio sensor status: %s' % (str(self._status)) + return self._status + + # Block primitives used in talogo + + def prim_volume(self): + """ return mic in value """ + #TODO: Adjust gain for different HW + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + volume = float(_avg(buf, abs_value=True)) + self._parent.lc.update_label_value('volume', volume) + return volume + else: + return 0 + + def prim_sound(self): + """ return raw mic in value """ + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + sound = float(buf[0]) + self._parent.lc.update_label_value('sound', sound) + return sound + else: + return 0 + + def prim_pitch(self): + """ return index of max value in fft of mic in values """ + if not PITCH_AVAILABLE: + return 0 + buf = [] + for i in range(4): + buf = append(buf, self.ringbuffer.read(None, self.input_step)) + if len(buf) > 0: + r = [] + for j in rfft(buf): + r.append(abs(j)) + # Convert output to Hertz + pitch = r.index(max(r)) * 48000 / len(buf) + self._parent.lc.update_label_value('pitch', pitch) + return pitch + else: + return 0 + + def prim_resistance(self): + """ return resistance sensor value """ + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + # See + # TODO: test this calibration on XO 1.5 + if self.hw == XO1: + resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \ + 8.0531) + else: + avg_buf = float(_avg(buf)) + if avg_buf > 0: + resistance = (420000000 / avg_buf) - 13500 + else: + resistance = 420000000 + self._parent.lc.update_label_value('resistance', resistance) + return resistance + else: + return 0 + + def prim_voltage(self): + """ return voltage sensor value """ + buf = self.ringbuffer.read(None, self.input_step) + if len(buf) > 0: + # See + voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias + self._parent.lc.update_label_value('voltage', voltage) + return voltage + else: + return 0 diff --git a/plugins/camera_plugin.py b/plugins/camera_plugin.py new file mode 100644 index 0000000..3061d39 --- /dev/null +++ b/plugins/camera_plugin.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender + +#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. + +import gst +import gtk +from fcntl import ioctl + +from gettext import gettext as _ + +from camera.tacamera import Camera +from camera.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, VIDIOC_G_CTRL, \ + VIDIOC_S_CTRL + +from plugin import Plugin +from TurtleArt.taconstants import PALETTES, PALETTE_NAMES, BOX_STYLE_MEDIA, \ + CONTENT_BLOCKS, BLOCK_NAMES, DEFAULTS, SPECIAL_NAMES, HELP_STRINGS, \ + BOX_STYLE, PRIMITIVES +from TurtleArt.talogo import VALUE_BLOCKS, MEDIA_BLOCKS_DICTIONARY, \ + PLUGIN_DICTIONARY +from TurtleArt.tautils import get_path + +import logging +_logger = logging.getLogger('turtleart-activity camera plugin') + + +class Camera_plugin(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + v4l2src = gst.element_factory_make('v4l2src') + if v4l2src.props.device_name is not None: + + if self._parent.running_sugar: + self._imagepath = get_path(self._parent.activity, + 'data/turtlepic.png') + else: + self._imagepath = '/tmp/turtlepic.png' + self._camera = Camera(self._imagepath) + + self._status = True + + def setup(self): + # set up camera-specific blocks + if self._status: + PALETTES[PALETTE_NAMES.index('sensor')].append('luminance') + BOX_STYLE.append('luminance') + BLOCK_NAMES['luminance'] = [_('brightness')] + HELP_STRINGS['luminance'] = _("light level detected by camera") + VALUE_BLOCKS.append('luminance') + PRIMITIVES['luminance'] = 'luminance' + PLUGIN_DICTIONARY['luminance'] = self.prim_read_camera + self._parent.lc._def_prim('luminance', 0, + lambda self: PLUGIN_DICTIONARY['luminance'](True)) + + # Depreciated block + BOX_STYLE.append('readcamera') + BLOCK_NAMES['readcamera'] = [_('read camera')] + HELP_STRINGS['readcamera'] = \ + _("Average RGB color from camera is pushed to the stack") + VALUE_BLOCKS.append('readcamera') + PRIMITIVES['readcamera'] = 'readcamera' + PLUGIN_DICTIONARY['readcamera'] = self.prim_read_camera + self._parent.lc._def_prim('readcamera', 0, + lambda self: PLUGIN_DICTIONARY['readcamera'](True)) + + PALETTES[PALETTE_NAMES.index('sensor')].append('camera') + BOX_STYLE_MEDIA.append('camera') + CONTENT_BLOCKS.append('camera') + BLOCK_NAMES['camera'] = [' '] + DEFAULTS['camera'] = ['CAMERA'] + SPECIAL_NAMES['camera'] = _('camera') + HELP_STRINGS['camera'] = _('camera output') + MEDIA_BLOCKS_DICTIONARY['camera'] = self.prim_take_picture + + def start(self): + # This gets called by the start button + pass + + def stop(self): + # This gets called by the stop button + if self._status: + self._camera.stop_camera_input() + + def goto_background(self): + # This gets called when your process is sent to the background + pass + + def return_to_foreground(self): + # This gets called when your process returns from the background + pass + + def quit(self): + # This gets called by the quit button + pass + + def _status_report(self): + print 'Reporting camera status: %s' % (str(self._status)) + return self._status + + # Block primitives used in talogo + + def prim_take_picture(self): + if self._status: + ''' method called by media block ''' + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + self._parent.lc.filepath = self._imagepath + + def prim_read_camera(self, luminance_only=False): + """ Read average pixel from camera and push b, g, r to the stack """ + pixbuf = None + array = None + w, h = self._parent.lc._w(), self._parent.lc._h() + if w > 0 and h > 0 and self._status: + try: + self._video_capture_device = open('/dev/video0', 'rw') + except: + self._video_capture_device = None + _logger.debug('video capture device not available') + + if self._video_capture_device is not None: + self._ag_control = v4l2_control(V4L2_CID_AUTOGAIN) + try: + ioctl(self._video_capture_device, VIDIOC_G_CTRL, + self._ag_control) + self._ag_control.value = 0 # disable AUTOGAIN + ioctl(self._video_capture_device, VIDIOC_S_CTRL, + self._ag_control) + except: + _logger.debug('AUTOGAIN control not available') + pass + + if self._video_capture_device is not None: + self._video_capture_device.close() + + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self._imagepath, w, h) + try: + array = pixbuf.get_pixels() + except: + array = None + + if array is not None: + length = len(array) / 3 + r, g, b, i = 0, 0, 0, 0 + for j in range(length): + r += ord(array[i]) + i += 1 + g += ord(array[i]) + i += 1 + b += ord(array[i]) + i += 1 + if luminance_only: + lum = int((r * 0.3 + g * 0.6 + b * 0.1) / length) + self._parent.lc.update_label_value('luminance', lum) + return lum + else: + self._parent.lc.heap.append(int((b / length))) + self._parent.lc.heap.append(int((g / length))) + self._parent.lc.heap.append(int((r / length))) + else: + if luminance_only: + return -1 + else: + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + + diff --git a/plugins/camera_plugin.pyc b/plugins/camera_plugin.pyc new file mode 100644 index 0000000..a557a12 --- /dev/null +++ b/plugins/camera_plugin.pyc Binary files differ diff --git a/plugins/plugin.py b/plugins/plugin.py new file mode 100644 index 0000000..0fe836b --- /dev/null +++ b/plugins/plugin.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +#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. + +import gobject + + +class Plugin(gobject.GObject): + def __init__(self): + gobject.GObject.__init__(self) + + def setup(self): + """ Setup is called once, when the Turtle Window is created. """ + raise RuntimeError("You need to define setup for your plugin.") + + def start(self): + """ start is called when run button is pressed. """ + raise RuntimeError("You need to define start for your plugin.") + + def stop(self): + """ stop is called when stop button is pressed. """ + raise RuntimeError("You need to define stop for your plugin.") + + def goto_background(self): + """ goto_background is called when the activity is sent to the + background. """ + raise RuntimeError( + "You need to define goto_background for your plugin.") + + def return_to_foreground(self): + """ return_to_foreground is called when the activity returns to + the foreground. """ + raise RuntimeError( + "You need to define return_to_foreground for your plugin.") + + def quit(self): + """ cleanup is called when the activity is exiting. """ + raise RuntimeError("You need to define quit for your plugin.") diff --git a/plugins/plugin.pyc b/plugins/plugin.pyc new file mode 100644 index 0000000..cf6e6fd --- /dev/null +++ b/plugins/plugin.pyc Binary files differ diff --git a/plugins/rfid_plugin.py b/plugins/rfid_plugin.py new file mode 100644 index 0000000..e0cfafc --- /dev/null +++ b/plugins/rfid_plugin.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +#Copyright (C) 2010 Emiliano Pastorino +#Copyright (c) 2011 Walter Bender + +#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. + +import os + +from gettext import gettext as _ + +from rfid.rfidutils import strhex2bin, strbin2dec, find_device + +from plugin import Plugin +from TurtleArt.taconstants import PALETTES, PALETTE_NAMES, BLOCK_NAMES, \ + HELP_STRINGS, BOX_STYLE +from TurtleArt.talogo import VALUE_BLOCKS, PLUGIN_DICTIONARY + +import logging +_logger = logging.getLogger('turtleart-activity RFID plugin') + +HAL_SERVICE = 'org.freedesktop.Hal' +HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' +HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' +HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' +REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ + 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' + + +class Rfid_plugin(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + """ + The following code will initialize a USB RFID reader. Please note that + in order to make this initialization function work, it is necessary to + set the permission for the ttyUSB device to 0666. You can do this by + adding a rule to /etc/udev/rules.d + + As root (using sudo or su), copy the following text into a new file in + /etc/udev/rules.d/94-ttyUSB-rules + + KERNEL=="ttyUSB[0-9]",MODE="0666" + + You only have to do this once. + """ + + self.rfid_connected = False + self.rfid_device = find_device() + self.rfid_idn = '' + + if self.rfid_device is not None: + _logger.info("RFID device found") + self.rfid_connected = self.rfid_device.do_connect() + if self.rfid_connected: + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + loop = DBusGMainLoop() + bus = dbus.SystemBus(mainloop=loop) + hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE, + HAL_MGR_PATH), HAL_MGR_IFACE) + + hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb) + + self._status = True + + def setup(self): + # set up camera-specific blocks + if self._status: + PALETTES[PALETTE_NAMES.index('sensor')].append('rfid') + BOX_STYLE.append('rfid') + BLOCK_NAMES['rfid'] = [_('RFID')] + HELP_STRINGS['rfid'] = _("read value from RFID device") + PRIMITIVES['rfid'] = 'rfid' + VALUE_BLOCKS.append('rfid') + PLUGIN_DICTIONARY['rfid'] = self.prim_read_rfid + self._parent.lc._def_prim('rfid', 0, + lambda self: PLUGIN_DICTIONARY['rfid']()) + + + def start(self): + # This gets called by the start button + if self._status: + pass + + def stop(self): + # This gets called by the stop button + if self._status: + pass + + def goto_background(self): + # This gets called when your process is sent to the background + pass + + def return_to_foreground(self): + # This gets called when your process returns from the background + pass + + def quit(self): + # This gets called by the quit button + pass + + def _status_report(self): + print 'Reporting RFID status: %s' % (str(self._status)) + return self._status + + def _device_added_cb(self, path): + """ + Called from hal connection when a new device is plugged. + """ + if not self.rfid_connected: + self.rfid_device = find_device() + _logger.debug("DEVICE_ADDED: %s" % self.rfid_device) + if self.rfid_device is not None: + _logger.debug("DEVICE_ADDED: RFID device is not None!") + self.rfid_connected = self._device.do_connect() + if self.rfid_connected: + _logger.debug("DEVICE_ADDED: Connected!") + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + def _disconnected_cb(self, device, text): + """ + Called when the device is disconnected. + """ + self.rfid_connected = False + self.rfid_device = None + + def _tag_read_cb(self, device, tagid): + """ + Callback for "tag-read" signal. Receives the read tag id. + """ + idbin = strhex2bin(tagid) + self.rfid_idn = strbin2dec(idbin[26:64]) + while self.rfid_idn.__len__() < 9: + self.rfid_idn = '0' + self.rfid_idn + print tagid, idbin, self.rfid_idn + + # Block primitives used in talogo + + def prim_read_rfid(self): + if self._status: + return self.rfid_idn diff --git a/devices/__init__.py b/rfid/__init__.py index e69de29..e69de29 100644 --- a/devices/__init__.py +++ b/rfid/__init__.py diff --git a/devices/device.py b/rfid/device.py index 04a82b2..04a82b2 100644 --- a/devices/device.py +++ b/rfid/device.py diff --git a/devices/rfidrweusb.py b/rfid/rfidrweusb.py index bd12631..bd12631 100644 --- a/devices/rfidrweusb.py +++ b/rfid/rfidrweusb.py diff --git a/TurtleArt/rfidutils.py b/rfid/rfidutils.py index f2c74b4..be79dd5 100644 --- a/TurtleArt/rfidutils.py +++ b/rfid/rfidutils.py @@ -23,10 +23,10 @@ def find_device(): Return a device instance or None. """ device = None - for i in os.listdir(os.path.join('.', 'devices')): - if not os.path.isdir(os.path.join('.', 'devices', i)): + for i in os.listdir(os.path.join('.', 'rfid')): + if not os.path.isdir(os.path.join('.', 'rfid', i)): try: - _tempmod = __import__('devices.%s'%i.split('.')[0], globals(), + _tempmod = __import__('rfid.%s'%i.split('.')[0], globals(), locals(), ['RFIDReader'], -1) devtemp = _tempmod.RFIDReader() if devtemp.get_present() == True: diff --git a/devices/serial/__init__.py b/rfid/serial/__init__.py index 681ad5c..681ad5c 100644 --- a/devices/serial/__init__.py +++ b/rfid/serial/__init__.py diff --git a/devices/serial/serialposix.py b/rfid/serial/serialposix.py index 174e2f7..174e2f7 100644 --- a/devices/serial/serialposix.py +++ b/rfid/serial/serialposix.py diff --git a/devices/serial/serialutil.py b/rfid/serial/serialutil.py index fd466f2..fd466f2 100644 --- a/devices/serial/serialutil.py +++ b/rfid/serial/serialutil.py diff --git a/devices/tis2000.py b/rfid/tis2000.py index 91d1991..91d1991 100644 --- a/devices/tis2000.py +++ b/rfid/tis2000.py diff --git a/devices/utils.py b/rfid/utils.py index 94e5540..94e5540 100644 --- a/devices/utils.py +++ b/rfid/utils.py diff --git a/samples/love-speaks-volumes.ta b/samples/love-speaks-volumes.ta new file mode 100644 index 0000000..30e0cb3 --- /dev/null +++ b/samples/love-speaks-volumes.ta @@ -0,0 +1,109 @@ +[[0, ["start", 2.0], 760, 100, [null, 55]], +[1, "hat1", 780, 260, [null, 106]], +[2, "hat2", 120, 200, [null, 80]], +[3, "setcolor", 1164, 992, [51, 4, 16]], +[4, ["number", 0], 1242, 992, [3, null]], +[5, "stack1", 1164, 698, [17, 62]], +[6, "volume", 1233, 572, [23, null]], +[7, "forever", 1100, 344, [102, 29, 103]], +[8, "penup", 780, 378, [67, 101]], +[9, "pendown", 780, 462, [101, 70]], +[10, "penup", 780, 546, [70, 75]], +[11, "pendown", 780, 714, [78, 107]], +[12, "clean", 900, 344, [104, 13]], +[13, ["fillscreen", 0], 900, 386, [12, 15, 14, 35]], +[14, ["number", 80], 986, 428, [13, null]], +[15, "white", 986, 386, [13, null]], +[16, "stack1", 1164, 1034, [3, 42]], +[17, "setcolor", 1164, 656, [64, 18, 5]], +[18, "white", 1242, 656, [17, null]], +[19, ["storein", 0], 1164, 446, [29, 20, 27, 23]], +[20, ["string", "b"], 1233, 446, [19, null]], +[21, ["storein", 0], 900, 554, [35, 22, 41, 38]], +[22, ["string", "b"], 969, 554, [21, null]], +[23, ["storein", 0], 1164, 530, [19, 24, 6, 64]], +[24, ["string", "a"], 1233, 530, [23, null]], +[25, "box", 1283, 908, [63, 26, null]], +[26, ["string", "a"], 1338, 908, [25, null]], +[27, "box", 1233, 488, [19, 28, null]], +[28, ["string", "a"], 1288, 488, [27, null]], +[29, ["storein", 0], 1164, 362, [7, 30, 31, 19]], +[30, ["string", "c"], 1233, 362, [29, null]], +[31, "box", 1233, 404, [29, 32, null]], +[32, ["string", "b"], 1288, 404, [31, null]], +[33, "box", 1283, 614, [64, 34, null]], +[34, ["string", "c"], 1338, 614, [33, null]], +[35, ["storein", 0], 900, 470, [13, 36, 37, 21]], +[36, ["string", "a"], 969, 470, [35, null]], +[37, ["number", 0], 969, 512, [35, null]], +[38, ["storein", 0], 900, 638, [21, 39, 40, 105]], +[39, ["string", "c"], 969, 638, [38, null]], +[40, ["number", 0], 969, 680, [38, null]], +[41, ["number", 0], 969, 596, [21, null]], +[42, "wait", 1164, 1076, [16, 43, null]], +[43, ["number", 0.1], 1222, 1076, [42, null]], +[44, "setshade", 1164, 782, [62, 45, 46]], +[45, ["number", 75], 1250, 782, [44, null]], +[46, "setcolor", 1164, 824, [44, 47, 50]], +[47, ["number", 0], 1242, 824, [46, null]], +[48, "box", 1283, 740, [62, 49, null]], +[49, ["string", "b"], 1338, 740, [48, null]], +[50, "stack1", 1164, 866, [46, 63]], +[51, "setshade", 1164, 950, [63, 52, 3]], +[52, ["number", 50], 1250, 950, [51, null]], +[53, "hat", 900, 260, [null, 54, 104]], +[54, ["string", "setup"], 958, 268, [53, null]], +[55, "stack", 760, 142, [0, 56, 59]], +[56, ["string", "setup"], 818, 142, [55, null]], +[57, "hat", 1100, 260, [null, 58, 102]], +[58, ["string", "loop"], 1158, 268, [57, null]], +[59, "stack", 760, 184, [55, 60, null]], +[60, ["string", "loop"], 818, 184, [59, null]], +[61, "box1", 953, 336, [71, null]], +[62, "storeinbox1", 1164, 740, [5, 48, 44]], +[63, "storeinbox1", 1164, 908, [50, 25, 51]], +[64, "storeinbox1", 1164, 614, [23, 33, 17]], +[65, "forward", 120, 326, [80, 82, 99]], +[66, "box2", 178, 284, [80, null]], +[67, "storeinbox2", 780, 336, [106, 71, 8]], +[68, "box2", 246, 326, [82, null]], +[69, "box2", 906, 420, [73, null]], +[70, "stack2", 780, 504, [9, 10]], +[71, ["division2", 0], 899, 336, [67, 61, 72]], +[72, ["number", 2], 977, 378, [71, null]], +[73, ["division2", 0], 852, 420, [101, 69, 74]], +[74, ["number", 2], 930, 462, [73, null]], +[75, ["setxy2", 0], 780, 588, [10, 76, 77, 78]], +[76, ["number", 0], 838, 588, [75, null]], +[77, ["number", 0], 838, 630, [75, null]], +[78, "seth", 780, 672, [75, 79, 11]], +[79, ["number", 0], 882, 672, [78, null]], +[80, ["arc", 0], 120, 242, [2, 81, 66, 65]], +[81, ["number", 225], 178, 242, [80, null]], +[82, ["product2", 0], 192, 326, [65, 68, 85]], +[83, "sqrt", 300, 410, [85, 84]], +[84, ["number", 2], 354, 410, [83, null]], +[85, ["product2", 0], 246, 368, [82, 86, 83]], +[86, ["number", 1.7], 300, 368, [85, null]], +[87, ["arc", 0], 120, 616, [100, 88, 89, null]], +[88, ["number", 225], 178, 616, [87, null]], +[89, "box2", 178, 658, [87, null]], +[90, "right", 120, 450, [99, 91, 92]], +[91, ["number", 90], 178, 450, [90, null]], +[92, "forward", 120, 492, [90, 93, 100]], +[93, ["product2", 0], 192, 492, [92, 94, 95]], +[94, "box2", 246, 492, [93, null]], +[95, ["product2", 0], 246, 534, [93, 96, 97]], +[96, ["number", 1.7], 300, 534, [95, null]], +[97, "sqrt", 300, 576, [95, 98]], +[98, ["number", 2], 354, 576, [97, null]], +[99, ["vspace", 20], 120, 368, [65, 90]], +[100, ["vspace", 20], 120, 534, [92, 87]], +[101, "forward", 780, 420, [8, 73, 9]], +[102, "sandwichtop_no_arm_no_label", 1082, 310, [57, 7]], +[103, ["sandwichcollapsed", 1], 1100, 344, [7, null]], +[104, "sandwichtop_no_arm_no_label", 882, 310, [53, 12]], +[105, ["sandwichcollapsed", 1], 900, 344, [38, null]], +[106, "sandwichtop_no_arm_no_label", 762, 302, [1, 67]], +[107, ["sandwichcollapsed", 1], 780, 336, [11, null]], +[-1, ["turtle", "Yertle"], 0, 0, 0.0, 0.0, 50.0, 5]] \ No newline at end of file diff --git a/samples/spiralaterals.ta b/samples/spiralaterals.ta new file mode 100644 index 0000000..dcde280 --- /dev/null +++ b/samples/spiralaterals.ta @@ -0,0 +1,56 @@ +[[0, ["start", 2.0], 183, 0, [null, 6]], +[1, "hat1", 490, 0, [null, 18]], +[2, "stack1", 313, 706, [31, 32]], +[3, "forward", 719, 42, [9, 11, 12]], +[4, "right", 719, 126, [12, 5, null]], +[5, ["number", 90], 793, 126, [4, null]], +[6, "storeinbox1", 183, 42, [0, 7, 53]], +[7, ["number", 20], 317, 42, [6, null]], +[8, "box1", 853, 42, [11, null]], +[9, "hat2", 719, 0, [null, 3]], +[10, "pop", 853, 84, [11, null]], +[11, ["product2", 0], 799, 42, [3, 8, 10]], +[12, ["vspace", 0], 719, 84, [3, 4]], +[13, "stack2", 490, 168, [19, 22]], +[14, "stack2", 490, 84, [18, 19]], +[15, "stack2", 490, 252, [22, 21]], +[16, "stack2", 490, 336, [21, 20]], +[17, "stack2", 490, 420, [20, null]], +[18, "push", 490, 42, [1, 23, 14]], +[19, "push", 490, 126, [14, 24, 13]], +[20, "push", 490, 378, [16, 25, 17]], +[21, "push", 490, 294, [15, 26, 16]], +[22, "push", 490, 210, [13, 27, 15]], +[23, ["number", 1], 565, 42, [18, null]], +[24, ["number", 1], 565, 126, [19, null]], +[25, ["number", 2], 565, 378, [20, null]], +[26, ["number", 3], 565, 294, [21, null]], +[27, ["number", 1], 565, 210, [22, null]], +[28, "repeat", 248, 604, [30, 29, 31, null]], +[29, ["number", 4], 299, 604, [28, null]], +[30, ["vspace", 0], 248, 562, [43, 28]], +[31, "startfill", 313, 664, [28, 2]], +[32, "stopfill", 313, 748, [2, null]], +[33, "repeat", 183, 168, [53, 34, 48, null]], +[34, ["number", 400], 234, 168, [33, null]], +[35, ["vspace", 0], 248, 436, [36, 52]], +[36, ["setxy2", 20], 248, 312, [47, 37, 38, 35]], +[37, ["random", 0], 312, 312, [36, 39, 41, null]], +[38, ["random", 0], 312, 394, [36, 40, 42, null]], +[39, "leftpos", 372, 312, [37, null]], +[40, "bottompos", 372, 394, [38, null]], +[41, "rightpos", 372, 354, [37, null]], +[42, "toppos", 372, 436, [38, null]], +[43, "setcolor", 248, 520, [52, 44, 30]], +[44, ["random", 0], 333, 520, [43, 45, 46, null]], +[45, ["number", 0], 393, 520, [44, null]], +[46, ["number", 100], 393, 562, [44, null]], +[47, "penup", 248, 270, [48, 36]], +[48, "seth", 248, 228, [33, 49, 47]], +[49, ["random", 0], 345, 228, [48, 50, 51, null]], +[50, ["number", 0], 405, 228, [49, null]], +[51, ["number", 90], 405, 270, [49, null]], +[52, "pendown", 248, 478, [35, 43]], +[53, ["fillscreen", 0], 183, 84, [6, 55, 54, 33]], +[54, ["number", 80], 322, 126, [53, null]], +[55, "black", 322, 84, [53, null]]] diff --git a/turtleart.py b/turtleart.py index b8ffa08..d9a205b 100755 --- a/turtleart.py +++ b/turtleart.py @@ -1,6 +1,7 @@ #!/usr/bin/env python #Copyright (c) 2007-8, Playful Invention Company -#Copyright (c) 2008-10, Walter Bender +#Copyright (c) 2008-11, 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 @@ -42,17 +43,21 @@ except ImportError, e: argv = sys.argv[:] # Workaround for import behavior of gst in tagplay sys.argv[1:] = [] # Execution of import gst cannot see '--help' or '-h' -from gettext import gettext as _ +import gettext -from TurtleArt.taconstants import OVERLAY_LAYER +gettext.bindtextdomain('org.laptop.TurtleArtActivity', 'locale') +gettext.textdomain('org.laptop.TurtleArtActivity') +_ = gettext.gettext + +from TurtleArt.taconstants import OVERLAY_LAYER, DEFAULT_TURTLE_COLORS from TurtleArt.tautils import data_to_string, data_from_string, get_save_name from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexporthtml import save_html from TurtleArt.taexportlogo import save_logo -from extra.upload import Uploader -from extra.collaborationplugin import CollaborationPlugin + from util.menubuilder import MenuBuilder + class TurtleMain(): """ Launch Turtle Art from outside of Sugar """ @@ -64,11 +69,13 @@ class TurtleMain(): _INSTALL_PATH = '/usr/share/turtleart' _ALTERNATE_INSTALL_PATH = '/usr/local/share/turtleart' _ICON_SUBPATH = 'images/turtle.png' + _GNOME_PLUGIN_SUBPATH = 'gnome_plugins' def __init__(self): self._init_vars() self._parse_command_line() self._ensure_sugar_paths() + self._plugins = [] if self.output_png: pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, @@ -85,17 +92,60 @@ class TurtleMain(): self._run_plugins() self._start_gtk() + def get_config_home(self): + return CONFIG_HOME + + def _get_gnome_plugin_home(self): + """ Look in current directory first, then usual places """ + path = os.path.join(os.getcwd(), self._GNOME_PLUGIN_SUBPATH) + if os.path.exists(path): + return path + path = os.path.expanduser(os.path.join('~', 'Activities', + 'TurtleBlocks.activity', + self._GNOME_PLUGIN_SUBPATH)) + if os.path.exists(path): + return path + path = os.path.expanduser(os.path.join('~', 'Activities', + 'TurtleArt.activity', + self._GNOME_PLUGIN_SUBPATH)) + if os.path.exists(path): + return path + path = os.path.join(self._INSTALL_PATH, self._GNOME_PLUGIN_SUBPATH) + if os.path.exists(path): + return path + path = os.path.join(self._ALTERNATE_INSTALL_PATH, + self._GNOME_PLUGIN_SUBPATH) + if os.path.exists(path): + return path + return None + + def _get_plugin_candidates(self, path): + """ Look for plugin files in plugin directory. """ + plugin_files = [] + if path is not None: + candidates = os.listdir(path) + for c in candidates: + if c[-10:] == '_plugin.py' and c[0] != '#' and c[0] != '.': + plugin_files.append(c.split('.')[0]) + return plugin_files + def _init_plugins(self): - config_file_path = os.path.join(CONFIG_HOME, 'turtleartrc.collab') - self._collab_plugin = CollaborationPlugin(self, config_file_path) - self._uploader = Uploader() + for p in self._get_plugin_candidates(self._get_gnome_plugin_home()): + P = p.capitalize() + f = "def f(self): from gnome_plugins.%s import %s; return %s(self)" \ + % (p, P, P) + plugin = {} + try: + exec f in globals(), plugin + self._plugins.append(plugin.values()[0](self)) + except ImportError: + print 'failed to import %s' % (P) def _run_plugins(self): - self._uploader.set_tw(self.tw) - self._collab_plugin.set_tw(self.tw) - self._collab_plugin.setup() + for p in self._plugins: + p.set_tw(self.tw) - def _mkdir_p(path): + def _mkdir_p(self, path): '''Create a directory in a fashion similar to `mkdir -p`''' try: os.makedirs(path) @@ -136,15 +186,16 @@ class TurtleMain(): if os.path.exists(self._INSTALL_PATH): self.tw = TurtleArtWindow(self.canvas, self._INSTALL_PATH) elif os.path.exists(self._ALTERNATE_INSTALL_PATH): - self.tw = TurtleArtWindow(self.canvas, self._ALTERNATE_INSTALL_PATH) + self.tw = TurtleArtWindow(self.canvas, + self._ALTERNATE_INSTALL_PATH) else: self.tw = TurtleArtWindow(self.canvas, os.path.abspath('.')) self.tw.save_folder = os.path.expanduser('~') def _init_vars(self): - # If we are invoked to start a project from Gnome, we should make - # sure our current directory is TA's source dir. + """ If we are invoked to start a project from Gnome, we should make + sure our current directory is TA's source dir. """ os.chdir(os.path.dirname(__file__)) self.ta_file = None @@ -182,10 +233,8 @@ class TurtleMain(): if not os.path.exists(self.ta_file): assert False, ('%s: %s' % (self.ta_file, _('File not found'))) - """ - make sure Sugar paths are present - """ def _ensure_sugar_paths(self): + """ Make sure Sugar paths are present. """ tapath = os.path.join(os.environ['HOME'], '.sugar', 'default', 'org.laptop.TurtleArtActivity') map(self._makepath, (os.path.join(tapath, 'data/'), @@ -212,10 +261,16 @@ class TurtleMain(): data_file.write(str(800) + '\n') data_file.write(str(550) + '\n') data_file.seek(0) - self.x = int(data_file.readline()) - self.y = int(data_file.readline()) - self.width = int(data_file.readline()) - self.height = int(data_file.readline()) + try: + self.x = int(data_file.readline()) + self.y = int(data_file.readline()) + self.width = int(data_file.readline()) + self.height = int(data_file.readline()) + except ValueError: + self.x = 50 + self.y = 50 + self.width = 800 + self.height = 550 def _setup_gtk(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) @@ -223,7 +278,8 @@ class TurtleMain(): win.move(self.x, self.y) win.maximize() win.set_title(_('Turtle Art')) - if os.path.exists(os.path.join(self._INSTALL_PATH, self._ICON_SUBPATH)): + if os.path.exists(os.path.join(self._INSTALL_PATH, + self._ICON_SUBPATH)): win.set_icon_from_file(os.path.join(self._INSTALL_PATH, self._ICON_SUBPATH)) else: @@ -261,23 +317,25 @@ class TurtleMain(): MenuBuilder.make_menu_item(menu, _('New'), self._do_new_cb) MenuBuilder.make_menu_item(menu, _('Open'), self._do_open_cb) MenuBuilder.make_menu_item(menu, _('Save'), self._do_save_cb) - MenuBuilder.make_menu_item(menu, _('Save As'), self._do_save_as_cb) - MenuBuilder.make_menu_item(menu, _('Save as image'), self._do_save_picture_cb) - MenuBuilder.make_menu_item(menu, _('Save as HTML'), self._do_save_html_cb) - MenuBuilder.make_menu_item(menu, _('Save as Logo'), self._do_save_logo_cb) - if self._uploader.enabled(): - MenuBuilder.make_menu_item(menu, _('Upload to Web'), - self._uploader.do_upload_to_web) + MenuBuilder.make_menu_item(menu, _('Save as'), self._do_save_as_cb) + MenuBuilder.make_menu_item(menu, _('Save as image'), + self._do_save_picture_cb) + MenuBuilder.make_menu_item(menu, _('Save as HTML'), + self._do_save_html_cb) + MenuBuilder.make_menu_item(menu, _('Save as Logo'), + self._do_save_logo_cb) MenuBuilder.make_menu_item(menu, _('Quit'), self.destroy) activity_menu = MenuBuilder.make_sub_menu(menu, _('File')) menu = gtk.Menu() MenuBuilder.make_menu_item(menu, _('Cartesian coordinates'), self._do_cartesian_cb) - MenuBuilder.make_menu_item(menu, _('Polar coordinates'), self._do_polar_cb) + MenuBuilder.make_menu_item(menu, _('Polar coordinates'), + self._do_polar_cb) MenuBuilder.make_menu_item(menu, _('Rescale coordinates'), self._do_rescale_cb) - MenuBuilder.make_menu_item(menu, _('Grow blocks'), self._do_resize_cb, 1.5) + MenuBuilder.make_menu_item(menu, _('Grow blocks'), + self._do_resize_cb, 1.5) MenuBuilder.make_menu_item(menu, _('Shrink blocks'), self._do_resize_cb, 0.667) MenuBuilder.make_menu_item(menu, _('Reset block size'), @@ -290,9 +348,12 @@ class TurtleMain(): edit_menu = MenuBuilder.make_sub_menu(menu, _('Edit')) menu = gtk.Menu() - MenuBuilder.make_menu_item(menu, _('Show palette'), self._do_palette_cb) - MenuBuilder.make_menu_item(menu, _('Hide palette'), self._do_hide_palette_cb) - MenuBuilder.make_menu_item(menu, _('Show/hide blocks'), self._do_hideshow_cb) + MenuBuilder.make_menu_item(menu, _('Show palette'), + self._do_palette_cb) + MenuBuilder.make_menu_item(menu, _('Hide palette'), + self._do_hide_palette_cb) + MenuBuilder.make_menu_item(menu, _('Show/hide blocks'), + self._do_hideshow_cb) tool_menu = MenuBuilder.make_sub_menu(menu, _('Tools')) menu = gtk.Menu() @@ -303,15 +364,16 @@ class TurtleMain(): MenuBuilder.make_menu_item(menu, _('Stop'), self._do_stop_cb) turtle_menu = MenuBuilder.make_sub_menu(menu, _('Turtle')) - collaboration_menu = self._collab_plugin.get_menu() - menu_bar = gtk.MenuBar() menu_bar.append(activity_menu) menu_bar.append(edit_menu) menu_bar.append(view_menu) menu_bar.append(tool_menu) menu_bar.append(turtle_menu) - menu_bar.append(collaboration_menu) + + # Add menus for plugins + for p in self._plugins: + menu_bar.append(p.get_menu()) return menu_bar def _quit_ta(self, widget=None, e=None): @@ -329,8 +391,8 @@ class TurtleMain(): """ Dialog for save project """ dlg = gtk.MessageDialog(parent=None, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK_CANCEL, - message_format= \ - _('You have unsaved work. Would you like to save before quitting?')) + message_format=_( + 'You have unsaved work. Would you like to save before quitting?')) dlg.set_title(_('Save project?')) dlg.set_property('skip-taskbar-hint', False) @@ -529,5 +591,21 @@ class TurtleMain(): """ Callback for destroy event. """ gtk.main_quit() + def nick_changed(self, nick): + """ TODO: Rename default turtle in dictionary """ + pass + + def color_changed(self, colors): + """ Reskin turtle with collaboration colors """ + turtle = self.tw.turtles.get_turtle(self.tw.default_turtle_name) + try: + turtle.colors = colors.split(',') + except: + turtle.colors = DEFAULT_TURTLE_COLORS + turtle.custom_shapes = True # Force regeneration of shapes + turtle.reset_shapes() + turtle.show() + + if __name__ == "__main__": TurtleMain() diff --git a/TurtleArt/RtfParser.py b/util/RtfParser.py index 9a141a4..9a141a4 100644 --- a/TurtleArt/RtfParser.py +++ b/util/RtfParser.py diff --git a/util/configfile.py b/util/configfile.py index 5df8f59..adabb34 100644 --- a/util/configfile.py +++ b/util/configfile.py @@ -1,10 +1,24 @@ #!/usr/bin/env python # # Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. # +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. import gobject + class ConfigFile(gobject.GObject): """Load/save a simple (key = value) config file""" @@ -15,7 +29,7 @@ class ConfigFile(gobject.GObject): ()), } - def __init__(self, config_file_path, valid_keys = {}): + def __init__(self, config_file_path, valid_keys={}): gobject.GObject.__init__(self) self._config_file_path = config_file_path @@ -29,24 +43,24 @@ class ConfigFile(gobject.GObject): def is_loaded(self): return self._is_loaded - def get(self, key, empty_if_not_loaded = False): - if not self._valid_keys.has_key(key): + def get(self, key, empty_if_not_loaded=False): + if not key in self._valid_keys: raise RuntimeError("Unknown config value %s" % key) - if self._config_hash.has_key(key): + if key in self._config_hash: value = self._config_hash[key] else: if self._valid_keys[key]["type"] == "text": value = "" - elif self._valid_keys[key]["type"] == "boolean": + elif self._valid_keys[key]["type"] == "boolean": value = False - elif self._valid_keys[key]["type"] == "integer": - value = 0 + elif self._valid_keys[key]["type"] == "integer": + value = 0 return value def set(self, key, value): - if not self._valid_keys.has_key(key): + if not key in self._valid_keys: raise RuntimeError("Unknown config value %s" % key) self._config_hash[key] = value @@ -58,10 +72,10 @@ class ConfigFile(gobject.GObject): config_file.close() for line in lines: line = line.strip() - k,v = line.split('=') + k, v = line.split('=') k = k.strip(' ') v = v.strip(' ') - if not self._valid_keys.has_key(k): + if not k in self._valid_keys: raise RuntimeError("Unknown config value %s" % k) value_type = self._valid_keys[k]["type"] if value_type == "text": @@ -73,11 +87,11 @@ class ConfigFile(gobject.GObject): self._config_hash[k] = value self._is_loaded = True self.emit('configuration-loaded') - except Exception,e: + except Exception, e: print e return self._is_loaded - + def save(self): config_file = open(self._config_file_path, 'w') for k in self._config_hash.keys(): @@ -94,14 +108,15 @@ class ConfigFile(gobject.GObject): l = "%s = %s\n" % (k, v) print l + def test_save_load(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -113,28 +128,31 @@ def test_save_load(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.set_valid_keys(keys) c.load() c.dump_keys() + def _configuration_saved_cb(config_file_obj): print "_configuration_saved_cb called" config_file_obj.dump_keys() + def _configuration_loaded_cb(config_file_obj): print "_configuration_loaded_cb called" config_file_obj.dump_keys() + def test_signals(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.connect('configuration-saved', _configuration_saved_cb) @@ -147,12 +165,13 @@ def test_signals(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.connect('configuration-loaded', _configuration_loaded_cb) c.set_valid_keys(keys) c.load() + if __name__ == "__main__": test_save_load("/tmp/configfile.0001") test_signals("/tmp/configfile.0002") diff --git a/util/configwizard.py b/util/configwizard.py index 7716362..6c66bd1 100644 --- a/util/configwizard.py +++ b/util/configwizard.py @@ -1,25 +1,41 @@ #!/usr/bin/python +# Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. from configfile import ConfigFile -import gtk +import gtk + class ConfigWizard(): """Simple configuration wizard window.""" - + def __init__(self, config_file_path): self._config_items = [] self._config_entries = {} self._config_file_path = config_file_path - self._config_file_obj = None + self._config_file_obj = None """ - [ { item_label, item_type, item_name, item_with_value } , ... ] + [ {item_label, item_type, item_name, item_with_value} , ... ] """ def set_config_items(self, items): self._config_items = items keys = {} for i in self._config_items: - keys[i["item_name"]] = { "type" : i["item_type"] } + keys[i["item_name"]] = {"type": i["item_type"]} self._valid_keys = keys def set_config_file_obj(self, obj): @@ -28,7 +44,7 @@ class ConfigWizard(): def get_config_file_obj(self, obj): return self._config_file_obj - def show(self, read_from_disc = False): + def show(self, read_from_disc=False): if read_from_disc: self._config_file_obj = ConfigFile(self._config_file_path) @@ -47,8 +63,8 @@ class ConfigWizard(): row = 1 for i in self._config_items: hbox = self._create_param(i) - table.attach(hbox, 0, 1, row, row+1, xpadding=5, ypadding=2) - row = row +1 + table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2) + row = row + 1 hbox = gtk.HBox() save_button = gtk.Button('Save') @@ -59,7 +75,7 @@ class ConfigWizard(): cancel_button.set_size_request(50, 15) cancel_button.connect('pressed', self._close_config_cb) hbox.add(cancel_button) - table.attach(hbox, 0, 1, row, row+1, xpadding=5, ypadding=2) + table.attach(hbox, 0, 1, row, row + 1, xpadding=5, ypadding=2) self._config_popup.show_all() @@ -89,11 +105,12 @@ class ConfigWizard(): self._config_file_obj.save() """ - { item_label, item_type, item_name, item_with_value } + {item_label, item_type, item_name, item_with_value} """ def _create_param(self, opts): param_name = opts["item_name"] - with_value = opts["item_with_value"] if opts.has_key("item_with_value") else True + with_value = opts["item_with_value"] if "item_with_value" in opts \ + else True hbox = gtk.HBox() if opts["item_type"] == "text": entry = gtk.Entry() @@ -117,14 +134,15 @@ class ConfigWizard(): def _close_config_cb(self, widget, event=None): self._config_popup.hide() + def test_wizard_from_config_file_obj(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -136,32 +154,36 @@ def test_wizard_from_config_file_obj(test_config_file): c.set("register", True) c.save() - + c = ConfigFile(test_config_file) c.set_valid_keys(keys) c.load() config_w = ConfigWizard(test_config_file) config_items = [ - {"item_label" : "Nickname", "item_type" : "text", "item_name" : "nick" }, - { "item_label" : "Account ID", "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : "Server", "item_type" : "text", "item_name" : "server" }, - { "item_label" : "Port", "item_type" : "text", "item_name" : "port" }, - { "item_label" : "Password", "item_type" : "text", "item_name" : "password" }, - { "item_label" : "Register", "item_type" : "text", "item_name" : "register" } + {"item_label": "Nickname", "item_type": "text", "item_name": "nick"}, + {"item_label": "Account ID", "item_type": "text", + "item_name": "account_id"}, + {"item_label": "Server", "item_type": "text", "item_name": "server"}, + {"item_label": "Port", "item_type": "text", "item_name": "port"}, + {"item_label": "Password", "item_type": "text", + "item_name": "password"}, + {"item_label": "Register", "item_type": "text", + "item_name": "register"} ] config_w.set_config_items(config_items) config_w.set_config_file_obj(c) config_w.show() + def test_wizard_from_config_file_path(test_config_file): keys = {} - keys["nick"] = { "type" : "text" } - keys["account_id"] = { "type" : "text" } - keys["server"] = { "type" : "text" } - keys["port"] = { "type" : "text" } - keys["password"] = { "type" : "text" } - keys["register"] = { "type" : "text" } + keys["nick"] = {"type": "text"} + keys["account_id"] = {"type": "text"} + keys["server"] = {"type": "text"} + keys["port"] = {"type": "text"} + keys["password"] = {"type": "text"} + keys["register"] = {"type": "text"} c = ConfigFile(test_config_file) c.set_valid_keys(keys) @@ -173,19 +195,23 @@ def test_wizard_from_config_file_path(test_config_file): c.set("register", True) c.save() - + config_w = ConfigWizard(test_config_file) config_items = [ - {"item_label" : "Nickname", "item_type" : "text", "item_name" : "nick" }, - { "item_label" : "Account ID", "item_type" : "text", "item_name" : "account_id" }, - { "item_label" : "Server", "item_type" : "text", "item_name" : "server" }, - { "item_label" : "Port", "item_type" : "text", "item_name" : "port" }, - { "item_label" : "Password", "item_type" : "text", "item_name" : "password" }, - { "item_label" : "Register", "item_type" : "text", "item_name" : "register" } + {"item_label": "Nickname", "item_type": "text", "item_name": "nick"}, + {"item_label": "Account ID", "item_type": "text", + "item_name": "account_id"}, + {"item_label": "Server", "item_type": "text", "item_name": "server"}, + {"item_label": "Port", "item_type": "text", "item_name": "port"}, + {"item_label": "Password", "item_type": "text", + "item_name": "password"}, + {"item_label": "Register", "item_type": "text", + "item_name": "register"} ] config_w.set_config_items(config_items) config_w.show(True) + if __name__ == "__main__": #test_wizard_from_config_file_obj("/tmp/configwizard.test.0001") test_wizard_from_config_file_path("/tmp/configwizard.test.0002") diff --git a/util/menubuilder.py b/util/menubuilder.py index 302d510..4ee4550 100644 --- a/util/menubuilder.py +++ b/util/menubuilder.py @@ -1,7 +1,23 @@ #!/usr/bin/python +# Copyright (c) 2011 Collabora Ltd. +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. import gtk + class MenuBuilder(): @classmethod def make_sub_menu(cls, menu, name): -- cgit v0.9.1