# -*- coding: utf-8 -*- #Copyright (c) 2007-8, Playful Invention Company. #Copyright (c) 2008-13, Walter Bender #Copyright (c) 2008-10, Raúl Gutiérrez Segalés #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 gtk import gobject from time import time, sleep from operator import isNumberType import os from os.path import exists as os_path_exists from UserDict import UserDict import urllib2 import tempfile try: from sugar.graphics import style GRID_CELL_SIZE = style.GRID_CELL_SIZE except ImportError: GRID_CELL_SIZE = 55 import traceback from tablock import (Block, Media, media_blocks_dictionary) from taconstants import (TAB_LAYER, DEFAULT_SCALE, ICON_SIZE) from tajail import (myfunc, myfunc_import) from tapalette import (block_names, value_blocks) from tatype import (TATypeError, TYPES_NUMERIC) from tautils import (get_pixbuf_from_journal, data_from_file, get_stack_name, text_media_type, round_int, debug_output, find_group, get_path, image_to_base64, data_to_string, data_to_file, get_load_name, chooser_dialog) try: from util.RtfParser import RtfTextOnly RTFPARSE = True except ImportError: RTFPARSE = False from gettext import gettext as _ primitive_dictionary = {} # new block primitives get added here class noKeyError(UserDict): __missing__ = lambda x, y: 0 class symbol: def __init__(self, name): self.name = name self.nargs = None self.fcn = None def __str__(self): return self.name def __repr__(self): return '#' + self.name class logoerror(Exception): def __init__(self, value): self.value = value def __str__(self): # return repr(self.value) return str(self.value) class NegativeRootError(BaseException): """ Similar to the ZeroDivisionError, this error is raised at runtime when trying to computer the square root of a negative number. """ DEFAULT_MESSAGE = 'square root of negative number' def __init__(self, neg_value=None, message=DEFAULT_MESSAGE): self.neg_value = neg_value self.message = message def __str__(self): return str(self.message) class HiddenBlock(Block): def __init__(self, name, value=None): self.name = name self.values = [] if value is not None: self.values.append(value) self.primitive = None else: self.primitive = name self.connections = [] self.docks = [] # Utility functions def _just_stop(): """ yield False to stop stack """ yield False def _millisecond(): """ Current time in milliseconds """ return time() * 1000 class LogoCode: """ A class for parsing Logo code """ def __init__(self, tw): self.tw = tw self.oblist = {} DEFPRIM = {'(': [1, lambda self, x: self._prim_opar(x)], 'define': [2, self._prim_define], 'nop': [0, lambda self: None]} for p in iter(DEFPRIM): if len(DEFPRIM[p]) == 2: self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1]) else: self.def_prim(p, DEFPRIM[p][0], DEFPRIM[p][1], DEFPRIM[p][2]) self.symtype = type(self._intern('print')) self.symnothing = self._intern('%nothing%') self.symopar = self._intern('(') self.iline = None self.cfun = None self.arglist = None self.ufun = None self.procstop = False self.running = False self.istack = [] self.stacks = {} self.boxes = {'box1': 0, 'box2': 0} self.heap = [] self.iresults = None self.step = None self.bindex = None self.hidden_turtle = None self.trace = 0 self.update_values = False self.gplay = None self.filepath = None self.pixbuf = None self.dsobject = None self.start_time = None self._disable_help = False self.body_height = int((self.tw.canvas.height / 40) * self.tw.scale) self.scale = DEFAULT_SCALE def stop_logo(self): """ Stop logo is called from the Stop button on the toolbar """ self.step = _just_stop() # Clear istack and iline of any code that was not executed due to Stop self.istack = [] self.iline = None self.tw.stop_plugins() if self.tw.gst_available: from tagplay import stop_media stop_media(self) self.tw.turtles.get_active_turtle().show() self.tw.running_blocks = False # If we disabled hover help, reenable it if self._disable_help: self.tw.no_help = False self._disable_help = False def def_prim(self, name, args, fcn, rprim=False): """ Define the primitives associated with the blocks """ sym = self._intern(name) sym.nargs, sym.fcn = args, fcn sym.rprim = rprim def _intern(self, string): """ Add any new objects to the symbol list. """ if string in self.oblist: return self.oblist[string] sym = symbol(string) self.oblist[string] = sym return sym def get_prim_callable(self, name): """ Return the callable primitive associated with the given name """ sym = self.oblist.get(name) if sym is not None: return sym.fcn else: return None def run_blocks(self, code): """Run code generated by generate_code(). """ self.start_time = time() self._setup_cmd(code) def generate_code(self, blk, blocks): """ Generate code to be passed to run_blocks() from a stack of blocks. """ for k in self.stacks.keys(): self.stacks[k] = None self.stacks['stack1'] = None self.stacks['stack2'] = None # Save state in case there is a hidden macro expansion self.save_blocks = None self.save_blk = blk self.save_while_blks = [] if self.trace > 0: self.update_values = True else: self.update_values = False self.clear_value_blocks() # Disabled hover help while program is running if not self.tw.no_help: self._disable_help = True self.tw.no_help = True for b in blocks: b.unhighlight() # Hidden macro expansions for b in blocks: if b.name in ['while', 'until']: action_blk, new_blocks = self._expand_forever(b, blk, blocks) blocks = new_blocks[:] if b == blk: blk = action_blk for b in blocks: if b.name in ['forever']: action_blk, new_blocks = self._expand_forever(b, blk, blocks) blocks = new_blocks[:] if b == blk: blk = action_blk for b in blocks: if b.name in ('hat', 'hat1', 'hat2'): stack_name = get_stack_name(b) if stack_name: stack_key = self._get_stack_key(stack_name) code = self._blocks_to_code(b) self.stacks[stack_key] = self._readline(code) else: self.tw.showlabel('#nostack') self.tw.showblocks() self.tw.running_blocks = False return None code = self._blocks_to_code(blk) if self.save_blocks is not None: # Undo any hidden macro expansion blocks = self.save_blocks[:] blk = self.save_blk for b in self.save_while_blks: if b[1] is not None: b[0].connections[0].connections[b[1]] = b[0] if b[2] is not None: b[0].connections[-1].connections[b[2]] = b[0] if b[3] is not None: b[0].connections[-2].connections[b[3]] = b[0] return code def _blocks_to_code(self, blk): """ Convert a stack of blocks to pseudocode. """ if blk is None: return ['%nothing%', '%nothing%'] code = [] dock = blk.docks[0] # There could be a '(', ')', '[' or ']'. if len(dock) > 4 and dock[4] in ('[', ']', ']['): code.append(dock[4]) if blk.primitive is not None: # make a tuple (prim, blk) if blk in self.tw.block_list.list: code.append((blk.primitive, self.tw.block_list.list.index(blk))) else: code.append(blk.primitive) # Hidden block elif blk.is_value_block(): # Extract the value from content blocks. value = blk.get_value() if value is None: return ['%nothing%'] else: code.append(value) else: return ['%nothing%'] if blk.connections is not None and len(blk.connections) > 0: for i in range(1, len(blk.connections)): b = blk.connections[i] dock = blk.docks[i] # There could be a '(', ')', '[' or ']'. if len(dock) > 4 and dock[4] in ('[', ']', ']['): for c in dock[4]: code.append(c) if b is not None: code.extend(self._blocks_to_code(b)) elif blk.docks[i][0] not in ['flow', 'unavailable']: code.append('%nothing%') return code def _setup_cmd(self, string): """ Execute the psuedocode. """ self.hidden_turtle = self.tw.turtles.get_active_turtle() self.hidden_turtle.hide() # Hide the turtle while we are running. self.procstop = False blklist = self._readline(string) self.step = self._start_eval(blklist) def _readline(self, line): """ Convert the pseudocode into a list of commands. The block associated with the command is stored as the second element in a tuple, e.g., (#forward, 16) """ # debug_output(line, self.tw.running_sugar) res = [] while line: token = line.pop(0) bindex = None if isinstance(token, tuple): (token, bindex) = token if isinstance(token, Media): res.append(token) elif isNumberType(token): res.append(token) elif token.isdigit(): res.append(float(token)) elif token[0] == '-' and token[1:].isdigit(): res.append(-float(token[1:])) elif token[0] == '"': res.append(token[1:]) elif token[0:2] == "#s": res.append(token[2:]) elif token == '[': res.append(self._readline(line)) elif token == ']': return res elif bindex is None or not isinstance(bindex, int): res.append(self._intern(token)) else: res.append((self._intern(token), bindex)) return res def _start_eval(self, blklist): """ Step through the list. """ if self.tw.running_sugar: self.tw.activity.stop_turtle_button.set_icon("stopiton") self.tw.activity.stop_turtle_button.set_tooltip( _('Stop turtle')) elif self.tw.interactive_mode: self.tw.toolbar_shapes['stopiton'].set_layer(TAB_LAYER) self.running = True self.icall(self.evline, blklist) yield True if self.tw.running_sugar: if self.tw.step_time == 0 and self.tw.selected_blk is None: self.tw.activity.stop_turtle_button.set_icon("hideshowon") self.tw.activity.stop_turtle_button.set_tooltip( _('Show blocks')) else: self.tw.activity.stop_turtle_button.set_icon("hideshowoff") self.tw.activity.stop_turtle_button.set_tooltip( _('Hide blocks')) elif self.tw.interactive_mode: self.tw.toolbar_shapes['stopiton'].hide() yield False self.running = False # If we disabled hover help, reenable it if self._disable_help: self.tw.no_help = False self._disable_help = False self.tw.display_coordinates() def icall(self, fcn, *args): """ Add a function and its arguments to the program stack. """ self.istack.append(self.step) self.step = fcn(*(args)) def evline(self, blklist, call_me=True): """ Evaluate a line of code from the list. """ oldiline = self.iline self.iline = blklist[:] self.arglist = None while self.iline: token = self.iline[0] self.bindex = None if isinstance(token, tuple): (token, self.bindex) = self.iline[0] # If the blocks are visible, highlight the current block. if not self.tw.hide and self.bindex is not None: self.tw.block_list.list[self.bindex].highlight() # In debugging modes, we pause between steps and show the turtle. if self.tw.step_time > 0: self.tw.turtles.get_active_turtle().show() endtime = _millisecond() + self.tw.step_time * 100. while _millisecond() < endtime: sleep(0.1) yield True self.tw.turtles.get_active_turtle().hide() # 'Stand-alone' booleans are handled here. if token == self.symopar: token = self.iline[1] if isinstance(token, tuple): (token, self.bindex) = self.iline[1] # Process the token and any arguments. self.icall(self._eval, call_me) yield True # Time to unhighlight the current block. if not self.tw.hide and self.bindex is not None: self.tw.block_list.list[self.bindex].unhighlight() if self.procstop: break if self.iresult is None: continue if self.bindex is not None: self.tw.block_list.list[self.bindex].highlight() self.tw.showblocks() self.tw.display_coordinates() raise logoerror(str(self.iresult)) self.iline = oldiline self.ireturn() if not self.tw.hide and self.tw.step_time > 0: self.tw.display_coordinates() yield True def _eval(self, call_me=True): """ Evaluate the next token on the line of code we are processing. """ token = self.iline.pop(0) bindex = None if isinstance(token, tuple): (token, bindex) = token # Either we are processing a symbol or a value. if isinstance(token, self.symtype): # We highlight blocks here in case an error occurs... if not self.tw.hide and bindex is not None: self.tw.block_list.list[bindex].highlight() self.icall(self._evalsym, token, call_me) yield True # and unhighlight if everything was OK. if not self.tw.hide and bindex is not None: self.tw.block_list.list[bindex].unhighlight() res = self.iresult else: res = token self.ireturn(res) yield True def _evalsym(self, token, call_me): """ Process primitive associated with symbol token """ self._undefined_check(token) oldcfun, oldarglist = self.cfun, self.arglist self.cfun, self.arglist = token, [] if token.nargs is None: self.tw.showblocks() self.tw.display_coordinates() raise logoerror("#noinput") is_Primitive = type(self.cfun.fcn).__name__ == 'Primitive' is_PrimitiveDisjunction = type(self.cfun.fcn).__name__ == \ 'PrimitiveDisjunction' call_args = not (is_Primitive or is_PrimitiveDisjunction) for i in range(token.nargs): self._no_args_check() self.icall(self._eval, call_args) yield True self.arglist.append(self.iresult) need_to_pop_istack = False if self.cfun.rprim: if isinstance(self.cfun.fcn, list): # debug_output('evalsym rprim list: %s' % (str(token)), # self.tw.running_sugar) self.icall(self._ufuncall, self.cfun.fcn, call_args) yield True need_to_pop_istack = True result = None else: if call_me: self.icall(self.cfun.fcn, *self.arglist) yield True need_to_pop_istack = True result = None else: result = (self.cfun.fcn, ) + tuple(self.arglist) else: need_to_pop_istack = True if call_me: result = self.cfun.fcn(self, *self.arglist) else: result = (self.cfun.fcn, self) + tuple(self.arglist) self.cfun, self.arglist = oldcfun, oldarglist if self.arglist is not None and result is None: self.tw.showblocks() raise logoerror("%s %s %s" % (oldcfun.name, _("did not output to"), self.cfun.name)) if need_to_pop_istack: self.ireturn(result) yield True else: self.iresult = result def _ufuncall(self, body, call_me): """ ufuncall """ self.ijmp(self.evline, body, call_me) yield True def doevalstep(self): """ evaluate one step """ starttime = _millisecond() try: while (_millisecond() - starttime) < 120: try: if self.step is None: return False if self.tw.running_turtleart: try: self.step.next() except ValueError: debug_output('generator already executing', self.tw.running_sugar) self.tw.running_blocks = False return False except TATypeError as tte: # TODO insert the correct block name # (self.cfun.name is only the name of the # outermost block in this statement/ line of code) # use logoerror("#notanumber") when possible if (tte.req_type in TYPES_NUMERIC and tte.bad_type not in TYPES_NUMERIC): raise logoerror("#notanumber") else: raise logoerror( "%s %s %s %s" % (self.cfun.name, _("doesn't like"), str(tte.bad_value), _("as input"))) except ZeroDivisionError: raise logoerror("#zerodivide") except NegativeRootError: raise logoerror("#negroot") except IndexError: raise logoerror("#emptyheap") else: try: self.step.next() except BaseException as error: if isinstance(error, (StopIteration, logoerror)): raise error else: traceback.print_exc() self.tw.showlabel( 'status', '%s: %s' % (type(error).__name__, str(error))) return False except StopIteration: if self.tw.running_turtleart: # self.tw.turtles.show_all() if self.hidden_turtle is not None: self.hidden_turtle.show() self.hidden_turtle = None else: self.tw.turtles.get_active_turtle().show() self.tw.running_blocks = False return False else: self.ireturn() except logoerror, e: if self.tw.running_turtleart: self.tw.showblocks() self.tw.display_coordinates() self.tw.showlabel('syntaxerror', str(e)) self.tw.turtles.show_all() self.tw.running_blocks = False else: traceback.print_exc() self.tw.showlabel('status', 'logoerror: ' + str(e)) return False return True def ireturn(self, res=None): """ return value """ self.step = self.istack.pop() self.iresult = res def ijmp(self, fcn, *args): """ ijmp """ self.step = fcn(*(args)) def _undefined_check(self, token): """ Make sure token has a definition """ if token.fcn is not None: return False if token.name == '%nothing%': errormsg = '' else: errormsg = "%s %s" % (_("I don't know how to"), _(token.name)) self.tw.showblocks() raise logoerror(errormsg) def _no_args_check(self): """ Missing argument ? """ if self.iline and self.iline[0] is not self.symnothing: return self.tw.showblocks() self.tw.display_coordinates() raise logoerror("#noinput") # # Primitives # def _prim_opar(self, val): self.iline.pop(0) return val def _prim_define(self, name, body): """ Define a primitive """ if not isinstance(name, self.symtype): name = self._intern(name) name.nargs, name.fcn = 0, body name.rprim = True def prim_start(self, *ignored_args): ''' Start block: recenter ''' if self.tw.running_sugar: self.tw.activity.recenter() def prim_clear(self): """ Clear screen """ self.tw.clear_plugins() self.stop_playing_media() self.reset_scale() self.reset_timer() self.clear_value_blocks() self.reset_internals() self.tw.canvas.clearscreen() self.tw.turtles.reset_turtles() def stop_playing_media(self): if self.tw.gst_available: from tagplay import stop_media stop_media(self) def reset_scale(self): self.scale = DEFAULT_SCALE def reset_timer(self): self.start_time = time() def get_start_time(self): return self.start_time def reset_internals(self): self.hidden_turtle = None if self.tw.running_turtleart: self.tw.activity.restore_state() def prim_loop(self, controller, blklist): """ Execute a loop controller -- iterator that yields True iff the loop should be run once more OR a callable that returns such an iterator blklist -- list of callables that form the loop body """ if not hasattr(controller, "next"): if callable(controller): controller = controller() else: raise TypeError("a loop controller must be either an iterator " "or a callable that returns an iterator") while next(controller): self.icall(self.evline, blklist[:]) yield True if self.procstop: break self.ireturn() yield True def prim_clamp(self, blklist): """ Run clamp blklist """ self.icall(self.evline, blklist[:]) yield True self.procstop = False self.ireturn() yield True def set_scale(self, scale): ''' Set scale for media blocks ''' self.scale = scale def get_scale(self): ''' Set scale for media blocks ''' return self.scale def prim_stop_stack(self): """ Stop execution of a stack """ self.procstop = True def active_turtle(self): ''' NOP used to add get_active_turtle to Python export ''' # turtle = self.tw.turtles.get_turtle() pass def prim_turtle(self, name): self.tw.turtles.set_turtle(name) def prim_wait(self, wait_time): """ Show the turtle while we wait """ self.tw.turtles.get_active_turtle().show() endtime = _millisecond() + wait_time * 1000. while _millisecond() < endtime: sleep(wait_time / 10.) yield True self.tw.turtles.get_active_turtle().hide() self.ireturn() yield True def prim_if(self, boolean, blklist): """ If bool, do list """ if boolean: self.icall(self.evline, blklist[:]) yield True self.ireturn() yield True def prim_ifelse(self, boolean, list1, list2): """ If bool, do list1, else do list2 """ if boolean: self.ijmp(self.evline, list1[:]) yield True else: self.ijmp(self.evline, list2[:]) yield True def prim_set_box(self, name, value): """ Store value in named box """ (key, is_native) = self._get_box_key(name) self.boxes[key] = value if is_native: if self.update_values: self.update_label_value(name, value) else: if self.update_values: self.update_label_value('box', value, label=name) def prim_get_box(self, name): """ Retrieve value from named box """ (key, is_native) = self._get_box_key(name) try: return self.boxes[key] except KeyError: # FIXME this looks like a syntax error in the GUI raise logoerror("#emptybox") def _get_box_key(self, name): """ Return the key used for this box in the boxes dictionary and a boolean indicating whether it is a 'native' box """ if name in ('box1', 'box2'): return (name, True) else: # make sure '5' and '5.0' point to the same box if isinstance(name, (basestring, int, long)): try: name = float(name) except ValueError: pass return ('box3_' + str(name), False) def prim_define_stack(self, name): """ Top of a named stack """ pass def prim_invoke_stack(self, name): """ Process a named stack """ key = self._get_stack_key(name) if self.stacks.get(key) is None: raise logoerror("#nostack") self.icall(self.evline, self.stacks[key][:]) yield True self.procstop = False self.ireturn() yield True def _get_stack_key(self, name): """ Return the key used for this stack in the stacks dictionary """ if name in ('stack1', 'stack2'): return name else: # make sure '5' and '5.0' point to the same action stack if isinstance(name, (int, long, float)): if int(name) == name: name = int(name) else: name = float(name) return 'stack3' + str(name) def load_heap(self, path): """ Load FILO from file """ if self.tw.running_sugar: # Choose a datastore object and push data to heap (Sugar only) chooser_dialog(self.tw.parent, path, self.push_file_data_to_heap) else: if not os.path.exists(path): path, self.tw.load_save_folder = get_load_name( '.*', self.tw.load_save_folder) if path is None: return data = data_from_file(path) if data is not None: for val in data: self.heap.append(val) def save_heap(self, path): """ save FILO to file """ if self.tw.running_sugar: from sugar import profile from sugar.datastore import datastore from sugar.activity import activity # Save JSON-encoded heap to temporary file heap_file = os.path.join(get_path(activity, 'instance'), str(path) + '.txt') data_to_file(self.heap, heap_file) # Create a datastore object dsobject = datastore.create() # Write any metadata (specifically set the title of the file # and specify that this is a plain text file). dsobject.metadata['title'] = str(path) dsobject.metadata['icon-color'] = profile.get_color().to_string() dsobject.metadata['mime_type'] = 'text/plain' dsobject.set_file_path(heap_file) datastore.write(dsobject) dsobject.destroy() else: heap_file = path data_to_file(self.heap, heap_file) def get_heap(self): return self.heap def reset_heap(self): """ Reset heap to an empty list """ # empty the list rather than setting it to a new empty list object, # so the object references are preserved while self.heap: self.heap.pop() def prim_myblock(self, *args): """ Run Python code imported from Journal """ if self.bindex is not None and self.bindex in self.tw.myblock: try: myfunc_import(self, self.tw.myblock[self.bindex], args) except: raise logoerror("#syntaxerror") def prim_myfunction(self, f, *args): """ Programmable block (Call tajail.myfunc and convert any errors to logoerrors) """ try: y = myfunc(f, args) if str(y) == 'nan': debug_output('Python function returned NAN', self.tw.running_sugar) self.stop_logo() raise logoerror("#notanumber") else: return y except ZeroDivisionError: self.stop_logo() raise logoerror("#zerodivide") except ValueError, e: self.stop_logo() raise logoerror('#' + str(e)) except SyntaxError, e: self.stop_logo() raise logoerror('#' + str(e)) except NameError, e: self.stop_logo() raise logoerror('#' + str(e)) except OverflowError: self.stop_logo() raise logoerror("#overflowerror") except TypeError: self.stop_logo() raise logoerror("#notanumber") def clear_value_blocks(self): if not hasattr(self, 'value_blocks_to_update'): return for name in value_blocks: self.update_label_value(name) def int(self, n): """ Raise an error if n doesn't convert to int. """ if isinstance(n, int): return n elif isinstance(n, float): return int(n) elif isinstance(n, str): return int(ord(n[0])) else: self.tw.showblocks() raise logoerror("%s %s %s %s" % (self.cfun.name, _("doesn't like"), str(n), _("as input"))) def find_value_blocks(self): """ Find any value blocks that may need label updates """ self.value_blocks_to_update = {} for name in value_blocks: self.value_blocks_to_update[name] = \ self.tw.block_list.get_similar_blocks('block', name) def update_label_value(self, name, value=None, label=None): """ Update the label of value blocks to reflect current value """ # If it is a named box, we need to match the label to the box if not self.tw.interactive_mode: return if self.tw.hide: return self.tw.display_coordinates() if value is None: for block in self.value_blocks_to_update[name]: block.spr.set_label(block_names[name][0]) if name == 'box': argblk = block.connections[-2] dx = block.dx block.resize() if argblk is not None: # Move connections over... dx = (block.dx - dx) * self.tw.block_scale drag_group = find_group(argblk) for blk in drag_group: blk.spr.move_relative((dx, 0)) else: block.resize() elif self.update_values: if isinstance(value, float): valstring = str(round_int(value)).replace( '.', self.tw.decimal_point) else: valstring = str(value) for block in self.value_blocks_to_update[name]: if label is None: block.spr.set_label( block_names[name][0] + ' = ' + valstring) block.resize() else: argblk = block.connections[-2] # Only update if label matches if argblk is not None and argblk.spr.labels[0] == label: block.spr.set_label( block_names[name][0] + ' = ' + valstring) dx = block.dx block.resize() # Move connections over... dx = (block.dx - dx) * self.tw.block_scale drag_group = find_group(argblk) for blk in drag_group: blk.spr.move_relative((dx, 0)) def reskin(self, obj): """ Reskin the turtle with an image from a file """ scale = int(ICON_SIZE * float(self.scale) / DEFAULT_SCALE) if scale < 1: return self.filepath = None self.dsobject = None if obj.value is not None and os_path_exists(obj.value): self.filepath = obj.value elif self.tw.running_sugar: # datastore object from sugar.datastore import datastore try: self.dsobject = datastore.get(obj.value) except: debug_output("Couldn't find dsobject %s" % (obj.value), self.tw.running_sugar) if self.dsobject is not None: self.filepath = self.dsobject.file_path if self.filepath is None: self.tw.showlabel('nojournal', self.filepath) return pixbuf = None try: pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( self.filepath, scale, scale) except: self.tw.showlabel('nojournal', self.filepath) debug_output("Couldn't open skin %s" % (self.filepath), self.tw.running_sugar) if pixbuf is not None: self.tw.turtles.get_active_turtle().set_shapes([pixbuf]) pen_state = self.tw.turtles.get_active_turtle().get_pen_state() if pen_state: self.tw.turtles.get_active_turtle().set_pen_state(False) self.tw.turtles.get_active_turtle().forward(0) if pen_state: self.tw.turtles.get_active_turtle().set_pen_state(True) if self.tw.sharing(): if self.tw.running_sugar: tmp_path = get_path(self.tw.activity, 'instance') else: tmp_path = '/tmp' tmp_file = os.path.join(get_path(self.tw.activity, 'instance'), 'tmpfile.png') pixbuf.save(tmp_file, 'png', {'quality': '100'}) data = image_to_base64(tmp_file, tmp_path) height = pixbuf.get_height() width = pixbuf.get_width() event = 'R|%s' % (data_to_string([self.tw.nick, [round_int(width), round_int(height), data]])) gobject.idle_add(self.tw.send_event, event) os.remove(tmp_file) def get_from_url(self, url): """ Get contents of URL as text or tempfile to image """ if "://" not in url: # no protocol url = "http://" + url # assume HTTP try: req = urllib2.urlopen(url) except urllib2.HTTPError, e: debug_output("Couldn't open %s: %s" % (url, e), self.tw.running_sugar) raise logoerror(url + ' [%d]' % (e.code)) except urllib2.URLError, e: if hasattr(e, 'code'): debug_output("Couldn't open %s: %s" % (url, e), self.tw.running_sugar) raise logoerror(url + ' [%d]' % (e.code)) else: # elif hasattr(e, 'reason'): debug_output("Couldn't reach server: %s" % (e), self.tw.running_sugar) raise logoerror('#noconnection') if req.info().getheader("Content-Type")[0:5] == "image": # it can't be deleted immediately, or else we won't ever access it tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(req.read()) # prepare for writing tmp.flush() # actually write it obj = Media('media', value=tmp.name) return obj else: return req.read() def showlist(self, objects): """ Display list of media objects """ x = (self.tw.turtles.get_active_turtle().get_xy()[0] / self.tw.coord_scale) y = (self.tw.turtles.get_active_turtle().get_xy()[1] / self.tw.coord_scale) for obj in objects: self.tw.turtles.get_active_turtle().set_xy(x, y, pendown=False) self.show(obj) y -= int(self.tw.canvas.textsize * self.tw.lead) def show(self, obj, center=False): """ Show is the general-purpose media-rendering block. """ # media if isinstance(obj, Media) and obj.value: self.filepath = None self.pixbuf = None # Camera writes directly to pixbuf self.dsobject = None # camera snapshot if obj.value.lower() in media_blocks_dictionary: media_blocks_dictionary[obj.value.lower()]() # file path elif os_path_exists(obj.value): self.filepath = obj.value # datastore object elif self.tw.running_sugar: from sugar.datastore import datastore try: self.dsobject = datastore.get(obj.value) except: debug_output("Couldn't find dsobject %s" % (obj.value), self.tw.running_sugar) if self.dsobject is not None: self.filepath = self.dsobject.file_path if self.pixbuf is not None: self.insert_image(center=center, pixbuf=True) elif self.filepath is None: if self.dsobject is not None: self.tw.showlabel( 'nojournal', self.dsobject.metadata['title']) else: self.tw.showlabel('nojournal', obj.value) debug_output("Couldn't open %s" % (obj.value), self.tw.running_sugar) elif obj.type == 'media': self.insert_image(center=center) elif obj.type == 'descr': mimetype = None if self.dsobject is not None and \ 'mime_type' in self.dsobject.metadata: mimetype = self.dsobject.metadata['mime_type'] description = None if self.dsobject is not None and \ 'description' in self.dsobject.metadata: description = self.dsobject.metadata[ 'description'] self.insert_desc(mimetype, description) elif obj.type == 'audio': self.play_sound() elif obj.type == 'video': self.play_video() if self.dsobject is not None: self.dsobject.destroy() # text or number elif isinstance(obj, (basestring, float, int)): if isinstance(obj, (float, int)): obj = round_int(obj) x, y = self.x2tx(), self.y2ty() if center: y -= self.tw.canvas.textsize self.tw.turtles.get_active_turtle().draw_text( obj, x, y, int(self.tw.canvas.textsize * self.scale / 100.), self.tw.canvas.width - x) def push_file_data_to_heap(self, dsobject): """ push contents of a data store object (assuming json encoding) """ data = data_from_file(dsobject.file_path) if data is not None: for val in data: self.heap.append(val) self.update_label_value('pop', self.heap[-1]) def x2tx(self): """ Convert screen coordinates to turtle coordinates """ return int(self.tw.canvas.width / 2) + \ int(self.tw.turtles.get_active_turtle().get_xy()[0]) def y2ty(self): """ Convert screen coordinates to turtle coordinates """ return int(self.tw.canvas.height / 2) - \ int(self.tw.turtles.get_active_turtle().get_xy()[1]) def wpercent(self): """ width as a percentage of screen coordinates """ return int((self.tw.canvas.width * self.scale) / 100.) def hpercent(self): """ height as a percentage of screen coordinates """ return int((self.tw.canvas.height * self.scale) / 100.) def insert_image(self, center=False, filepath=None, resize=True, offset=False, pixbuf=False): """ Image only (at current x, y) """ if filepath is not None: self.filepath = filepath if not pixbuf: self.pixbuf = None w, h = self.wpercent(), self.hpercent() if w < 1 or h < 1: return if pixbuf: # We may have to rescale the picture if w != self.pixbuf.get_width() or h != self.pixbuf.get_height(): self.pixbuf = self.pixbuf.scale_simple( w, h, gtk.gdk.INTERP_BILINEAR) elif self.dsobject is not None: try: self.pixbuf = get_pixbuf_from_journal(self.dsobject, w, h) except: debug_output("Couldn't open dsobject %s" % (self.dsobject), self.tw.running_sugar) if self.pixbuf is None and \ self.filepath is not None and \ self.filepath != '': try: if not resize: self.pixbuf = gtk.gdk.pixbuf_new_from_file(self.filepath) w = self.pixbuf.get_width() h = self.pixbuf.get_height() else: self.pixbuf = gtk.gdk.pixbuf_new_from_file_at_size( self.filepath, w, h) except: self.tw.showlabel('nojournal', self.filepath) debug_output("Couldn't open filepath %s" % (self.filepath), self.tw.running_sugar) if self.pixbuf is not None: w *= self.tw.coord_scale h *= self.tw.coord_scale if center: self.tw.turtles.get_active_turtle().draw_pixbuf( self.pixbuf, 0, 0, self.x2tx() - int(w / 2), self.y2ty() - int(h / 2), w, h, self.filepath) elif offset: self.tw.turtles.get_active_turtle().draw_pixbuf( self.pixbuf, 0, 0, self.x2tx(), self.y2ty() - h, w, h, self.filepath) else: self.tw.turtles.get_active_turtle().draw_pixbuf( self.pixbuf, 0, 0, self.x2tx(), self.y2ty(), w, h, self.filepath) def insert_desc(self, mimetype=None, description=None): """ Description text only (at current x, y) """ w = self.wpercent() if w < 1: return text = None if text_media_type(self.filepath): if RTFPARSE and ( mimetype == 'application/rtf' or self.filepath.endswith(('rtf'))): text_only = RtfTextOnly() for line in open(self.filepath, 'r'): text_only.feed(line) text = text_only.output else: try: f = open(self.filepath, 'r') text = f.read() f.close() except IOError: self.tw.showlabel('nojournal', self.filepath) debug_output("Couldn't open %s" % (self.filepath), self.tw.running_sugar) else: if description is not None: text = str(description) else: text = self.filepath if text is not None: self.tw.turtles.get_active_turtle().draw_text( text, self.x2tx(), self.y2ty(), self.body_height, w) def media_wait(self): """ Wait for media to stop playing """ if self.tw.gst_available: from tagplay import media_playing while(media_playing(self)): yield True self.ireturn() yield True def media_stop(self): """ Stop playing media""" if self.tw.gst_available: from tagplay import stop_media stop_media(self) self.ireturn() yield True def media_pause(self): """ Pause media""" if self.tw.gst_available: from tagplay import pause_media pause_media(self) self.ireturn() yield True def media_play(self): """ Play media""" if self.tw.gst_available: from tagplay import play_media play_media(self) self.ireturn() yield True def play_sound(self): """ Sound file from Journal """ 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, h = self.wpercent(), self.hpercent() if w < 1 or h < 1: return if self.tw.gst_available: from tagplay import play_movie_from_file # The video window is an overlay, so we need to know where # the canvas is relative to the window, e.g., which # toolbars, if any are open. yoffset = 0 if self.tw.running_sugar: if not self.tw.activity.is_fullscreen(): yoffset += GRID_CELL_SIZE if self.tw.activity.toolbars_expanded(): yoffset += GRID_CELL_SIZE play_movie_from_file(self, self.filepath, self.x2tx(), self.y2ty() + yoffset, w, h) def _expand_forever(self, b, blk, blocks): """ Expand a while or until block into: forever, ifelse, stopstack Expand a forever block to run in a separate stack Parameters: the loop block, the top block, all blocks. Return the start block of the expanded loop, and all blocks.""" # TODO: create a less brittle way of doing this; having to # manage the connections and flows locally means we may run # into trouble if any of these block types (forever, while, # until. ifelse, stopstack, or stack) is changed in tablock.py if b.name == 'while': while_blk = True else: while_blk = False if b.name == 'until': until_blk = True else: until_blk = False # We'll restore the original blocks when we are finished if self.save_blocks is None: self.save_blocks = blocks[:] # Create an action block that will jump to the new stack action_name = '_forever %d' % (len(self.save_while_blks) + 1) action_blk = HiddenBlock('stack') action_label_blk = HiddenBlock('string', value=action_name) # Create the blocks we'll put in the new stack forever_blk = HiddenBlock('forever') if while_blk or until_blk: ifelse_blk = HiddenBlock('ifelse') stopstack_blk = HiddenBlock('stopstack') inflow = None whileflow = None outflow = None boolflow = None if b.connections is not None: inflow = b.connections[0] if while_blk or until_blk: boolflow = b.connections[1] whileflow = b.connections[-2] outflow = b.connections[-1] # Create action block(s) to run the code inside the forever loop if until_blk and whileflow is not None: # run until flow at least once action_flow_name = '_flow %d' % (len(self.save_while_blks) + 1) action_first = HiddenBlock('stack') first_label_blk = HiddenBlock('string', value=action_flow_name) # Assign new connections and build the docks if inflow is not None and b in inflow.connections: i = inflow.connections.index(b) if until_blk and whileflow is not None: inflow.connections[i] = action_first else: inflow.connections[i] = action_blk else: i = None j = None if outflow is not None: if b in outflow.connections: j = outflow.connections.index(b) outflow.connections[j] = action_blk if until_blk and whileflow is not None: action_first.connections.append(inflow) action_first.docks.append(['flow', True, 0, 0]) action_first.connections.append(first_label_blk) action_first.docks.append(['number', False, 0, 0]) action_first.connections.append(action_blk) action_first.docks.append(['flow', False, 0, 0]) first_label_blk.connections.append(action_first) first_label_blk.docks.append(['number', True, 0, 0]) action_blk.connections.append(action_first) else: action_blk.connections.append(inflow) action_blk.docks.append(['flow', True, 0, 0]) action_blk.connections.append(action_label_blk) action_blk.docks.append(['number', False, 0, 0]) action_blk.connections.append(outflow) action_blk.docks.append(['flow', False, 0, 0]) action_label_blk.connections.append(action_blk) action_label_blk.docks.append(['number', True, 0, 0]) forever_blk.connections.append(None) forever_blk.docks.append(['flow', True, 0, 0]) if while_blk or until_blk: forever_blk.connections.append(ifelse_blk) else: forever_blk.connections.append(whileflow) forever_blk.docks.append(['flow', False, 0, 0, '[']) forever_blk.connections.append(outflow) forever_blk.docks.append(['flow', False, 0, 0, ']']) if while_blk or until_blk: ifelse_blk.connections.append(forever_blk) ifelse_blk.docks.append(['flow', True, 0, 0]) ifelse_blk.connections.append(boolflow) ifelse_blk.docks.append(['bool', False, 0, 0]) if while_blk: ifelse_blk.connections.append(whileflow) ifelse_blk.connections.append(stopstack_blk) else: # until ifelse_blk.connections.append(stopstack_blk) ifelse_blk.connections.append(whileflow) ifelse_blk.docks.append(['flow', False, 0, 0, '[']) ifelse_blk.docks.append(['flow', False, 0, 0, '][']) ifelse_blk.connections.append(None) ifelse_blk.docks.append(['flow', False, 0, 0, ']']) stopstack_blk.connections.append(ifelse_blk) stopstack_blk.docks.append(['flow', False, 0, 0]) if whileflow is not None: if while_blk or until_blk: whileflow.connections[0] = ifelse_blk else: whileflow.connections[0] = forever_blk # Create a separate stacks for the forever loop and the whileflow code = self._blocks_to_code(forever_blk) self.stacks[self._get_stack_key(action_name)] = self._readline(code) if until_blk and whileflow is not None: # Create a stack from the whileflow to be called from # action_first, but then reconnect it to the ifelse block c = whileflow.connections[0] whileflow.connections[0] = None code = self._blocks_to_code(whileflow) self.stacks[self._get_stack_key(action_flow_name)] = \ self._readline(code) whileflow.connections[0] = c # Save the connections so we can restore them later if whileflow is not None: self.save_while_blks.append([b, i, j, 0]) else: self.save_while_blks.append([b, i, j, None]) # Insert the new blocks into the stack i = blocks.index(b) if i == 0: blocks_left = [] else: blocks_left = blocks[0:i] if i == len(blocks) - 1: blocks_right = [] else: blocks_right = blocks[i + 1:] blocks = blocks_left[:] if until_blk and whileflow is not None: blocks.append(action_first) blocks.append(action_blk) blocks.append(forever_blk) if while_blk or until_blk: blocks.append(ifelse_blk) blocks.append(stopstack_blk) blocks.extend(blocks_right) if until_blk and whileflow is not None: return action_first, blocks else: return action_blk, blocks