Ticket #1633: tawindow.py

File tawindow.py, 30.9 KB (added by walter, 14 years ago)

new version of tawindow.py (2010-01-07)

Line 
1# -*- coding: utf-8 -*-
2#Copyright (c) 2007, Playful Invention Company
3#Copyright (c) 2008-9, Walter Bender
4#Copyright (c) 2009, Raúl Gutiérrez Segalés
5
6#Permission is hereby granted, free of charge, to any person obtaining a copy
7#of this software and associated documentation files (the "Software"), to deal
8#in the Software without restriction, including without limitation the rights
9#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10#copies of the Software, and to permit persons to whom the Software is
11#furnished to do so, subject to the following conditions:
12
13#The above copyright notice and this permission notice shall be included in
14#all copies or substantial portions of the Software.
15
16#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22#THE SOFTWARE.
23
24import pygtk
25pygtk.require('2.0')
26import gtk
27import pango
28import gobject
29import os
30import os.path
31import time
32
33# Import from Journal for these blocks
34importblocks = ['audiooff', 'descriptionoff','journal']
35
36class taWindow: pass
37
38from math import atan2, pi
39DEGTOR = 2*pi/360
40
41from tasetup import *
42from tasprites import *
43from talogo import *
44from taturtle import *
45from taproject import *
46try:
47    from sugar.graphics.objectchooser import ObjectChooser
48except:
49    pass
50
51from tahoverhelp import *
52from gettext import gettext as _
53
54# dead key dictionaries
55dead_grave = {'A':192,'E':200,'I':204,'O':210,'U':217,'a':224,'e':232,'i':236,\
56              'o':242,'u':249}
57dead_acute = {'A':193,'E':201,'I':205,'O':211,'U':218,'a':225,'e':233,'i':237,\
58              'o':243,'u':250}
59dead_circumflex = {'A':194,'E':202,'I':206,'O':212,'U':219,'a':226,'e':234,\
60                   'i':238,'o':244,'u':251}
61dead_tilde = {'A':195,'O':211,'N':209,'U':360,'a':227,'o':245,'n':241,'u':361}
62dead_diaeresis = {'A':196,'E':203,'I':207,'O':211,'U':218,'a':228,'e':235,\
63                  'i':239,'o':245,'u':252}
64dead_abovering = {'A':197,'a':229}
65
66# Time out for triggering help
67timeout_tag = [0]
68
69
70#
71# Setup
72#
73
74def twNew(win, path, lang, parent=None):
75    tw = taWindow()
76    tw.window = win
77    tw.path = os.path.join(path,'images')
78    tw.path_lang = os.path.join(path,'images',lang)
79    tw.path_en = os.path.join(path,'images/en') # en as fallback
80    tw.load_save_folder = os.path.join(path,'samples')
81    tw.save_folder = None
82    tw.save_file_name = None
83    win.set_flags(gtk.CAN_FOCUS)
84    tw.width = gtk.gdk.screen_width()
85    tw.height = gtk.gdk.screen_height()
86    # starting from command line
87    if parent is None:
88        win.show_all()
89    # starting from Sugar
90    else:
91        parent.show_all()
92    win.add_events(gtk.gdk.BUTTON_PRESS_MASK)
93    win.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
94    win.add_events(gtk.gdk.POINTER_MOTION_MASK)
95    win.add_events(gtk.gdk.KEY_PRESS_MASK)
96    win.connect("expose-event", expose_cb, tw)
97    win.connect("button-press-event", buttonpress_cb, tw)
98    win.connect("button-release-event", buttonrelease_cb, tw)
99    win.connect("motion-notify-event", move_cb, tw)
100    win.connect("key_press_event", keypress_cb, tw)
101    tw.keypress = ""
102    tw.keyvalue = 0
103    tw.dead_key = ""
104    tw.area = win.window
105    tw.gc = tw.area.new_gc()
106    # on an OLPC-XO-1, there is a scaling factor
107    if os.path.exists('/etc/olpc-release') or \
108       os.path.exists('/sys/power/olpc-pm'):
109        tw.lead = 1.6
110        tw.scale = 1.0
111    else:
112        tw.lead = 1.0
113        tw.scale = 1.6
114    tw.cm = tw.gc.get_colormap()
115    tw.rgb = [255,0,0]
116    tw.bgcolor = tw.cm.alloc_color('#fff8de')
117    tw.msgcolor = tw.cm.alloc_color('black')
118    tw.fgcolor = tw.cm.alloc_color('red')
119    tw.textcolor = tw.cm.alloc_color('blue')
120    tw.textsize = 32
121    tw.sprites = []
122    tw.selected_block = None
123    tw.draggroup = None
124    prep_selectors(tw)
125    tw.myblock = None
126    tw.nop = 'nop'
127    tw.loaded = 0
128    for s in selectors:
129        setup_selectors(tw,s)
130    setup_misc(tw)
131    tw.step_time = 0
132    tw.hide = False
133    tw.palette = True
134    select_category(tw, tw.selbuttons[0])
135    tw.coord_scale = 1
136    tw.turtle = tNew(tw,tw.width,tw.height)
137    tw.lc = lcNew(tw)
138    tw.buddies = []
139    tw.dx = 0
140    tw.dy = 0
141    tw.cartesian = False
142    tw.polar = False
143    tw.spr = None # "currently selected spr"
144    return tw
145
146#
147# Button Press
148#
149
150def buttonpress_cb(win, event, tw):
151    win.grab_focus()
152    x, y = xy(event)
153    button_press(tw, event.get_state()&gtk.gdk.CONTROL_MASK, x, y)
154    # if sharing, send button press
155    if hasattr(tw, 'activity') and \
156        hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None:
157        # print "sending button pressed"
158        if event.get_state()&gtk.gdk.CONTROL_MASK is True:
159            tw.activity._send_event("p:"+str(x)+":"+str(y)+":"+'T')
160        else:
161            tw.activity._send_event("p:"+str(x)+":"+str(y)+":"+'F')
162    return True
163
164def button_press(tw, mask, x, y, verbose=False):
165    if verbose:
166        print "processing remote button press: " + str(x) + " " + str(y)
167    tw.block_operation = 'click'
168    if tw.selected_block!=None:
169        unselect(tw)
170    else:
171        setlayer(tw.status_spr,400)
172    spr = findsprite(tw,(x,y))
173    tw.x, tw.y = x,y
174    tw.dx = 0
175    tw.dy = 0
176    if spr is None:
177        return True
178    if spr.type == "canvas":
179        return True
180    elif spr.type == 'selbutton':
181        select_category(tw,spr)
182    elif spr.type == 'category':
183        block_selector_pressed(tw,x,y)
184    elif spr.type == 'block':
185        block_pressed(tw,mask,x,y,spr)
186    elif spr.type == 'turtle':
187        turtle_pressed(tw,x,y)
188    tw.spr = spr
189
190def block_selector_pressed(tw,x,y):
191    proto = get_proto_from_category(tw,x,y)
192    if proto==None:
193        return
194    if proto is not 'hide':
195        new_block_from_category(tw,proto,x,y)
196    else:
197        hideshow_palette(tw,False)
198
199def hideshow_palette(tw,state):
200    if state is False:
201        tw.palette == False
202        if hasattr(tw,'activity'):
203            # Use new toolbar design
204            tw.activity.do_hidepalette()
205        hide_palette(tw)
206    else:
207        tw.palette == True
208        if hasattr(tw,'activity'):
209            # Use new toolbar design
210            tw.activity.do_showpalette()
211        show_palette(tw)
212
213def show_palette(tw):
214    for i in tw.selbuttons: setlayer(i,800)
215    select_category(tw,tw.selbuttons[0])
216    tw.palette = True
217
218def hide_palette(tw):
219    for i in tw.selbuttons: hide(i)
220    setshape(tw.category_spr, tw.hidden_palette_icon)
221    tw.palette = False
222
223def get_proto_from_category(tw,x,y):
224    dx,dy = x-tw.category_spr.x, y-tw.category_spr.y,
225    pixel = getpixel(tw.current_category.mask,dx,dy)
226    index = ((pixel%256)>>3)-1
227    if index==0:
228        return 'hide'
229    index-=1
230    if index>len(tw.current_category.blockprotos):
231        return None
232    return tw.current_category.blockprotos[index]
233
234def select_category(tw, spr):
235    if hasattr(tw, 'current_category'):
236        setshape(tw.current_category, tw.current_category.offshape)
237    setshape(spr, spr.onshape)
238    tw.current_category = spr
239    setshape(tw.category_spr,spr.group)
240
241def new_block_from_category(tw,proto,x,y):
242    if proto is None:
243        return True
244    # load alternative image of nop block if python code is loaded
245    if proto.name == 'nop' and tw.nop == 'pythonloaded':
246        newspr = sprNew(tw,x-20,y-20,tw.media_shapes['pythonloaded'])
247    else:
248        newspr = sprNew(tw,x-20,y-20,proto.image)
249    setlayer(newspr,2000)
250    tw.dragpos = 20,20
251    newspr.type = 'block'
252    newspr.proto = proto
253    if tw.defdict.has_key(newspr.proto.name):
254        newspr.label=tw.defdict[newspr.proto.name]
255    newspr.connections = [None]*len(proto.docks)
256    for i in range(len(proto.defaults)):
257        dock = proto.docks[i+1]
258        argproto = tw.protodict[tw.valdict[dock[0]]]
259        argdock = argproto.docks[0]
260        nx,ny = newspr.x+dock[2]-argdock[2],newspr.y+dock[3]-argdock[3]
261        argspr = sprNew(tw,nx,ny,argproto.image)
262        argspr.type = 'block'
263        argspr.proto = argproto
264        argspr.label = str(proto.defaults[i])
265        setlayer(argspr,2000)
266        argspr.connections = [newspr,None]
267        newspr.connections[i+1] = argspr
268    tw.draggroup = findgroup(newspr)
269    tw.block_operation = 'new'
270
271def block_pressed(tw,mask,x,y,spr):
272    if spr is not None:
273        if hasattr(spr,'proto'):
274            print "block_pressed (%s)" % spr.proto.name
275        tw.draggroup = findgroup(spr)
276        for b in tw.draggroup: setlayer(b,2000)
277        if spr.connections[0] != None and spr.proto.name == 'lock':
278            b = find_top_block(spr)
279            tw.dragpos = x-b.x,y-b.y
280        else:
281            tw.dragpos = x-spr.x,y-spr.y
282            disconnect(spr)
283
284def turtle_pressed(tw,x,y):
285    dx,dy = x-tw.turtle.spr.x-30,y-tw.turtle.spr.y-30
286    if dx*dx+dy*dy > 200:
287        tw.dragpos = ('turn', \
288        tw.turtle.heading-atan2(dy,dx)/DEGTOR,0)
289    else:
290        tw.dragpos = ('move', x-tw.turtle.spr.x,y-tw.turtle.spr.y)
291    tw.draggroup = [tw.turtle.spr]
292
293#
294# Mouse move
295#
296
297def move_cb(win, event, tw):
298    x,y = xy(event)
299    mouse_move(tw, x, y)
300    return True
301
302def mouse_move(tw, x, y, verbose=False, mdx=0, mdy=0):
303    if verbose:
304        print "processing remote mouse move: " + str(x) + " " + str(y)
305    if tw.draggroup is None:
306        # popup help from RGS
307        spr = findsprite(tw,(x,y))
308        if spr and spr.type == 'category':
309            proto = get_proto_from_category(tw,x,y)
310            if proto and proto!='hide':
311                if timeout_tag[0] == 0:
312                    timeout_tag[0] = showPopup(proto.name,tw)
313                    tw.spr = spr
314                    return
315            else:
316                if timeout_tag[0] > 0:
317                    try:
318                        gobject.source_remove(timeout_tag[0])
319                        timeout_tag[0] = 0
320                    except:
321                        timeout_tag[0] = 0
322        elif spr and spr.type == 'selbutton':
323            if timeout_tag[0] == 0:
324                timeout_tag[0] = showPopup(spr.name,tw)
325                tw.spr = spr
326            else:
327                if timeout_tag[0] > 0:
328                    try:
329                        gobject.source_remove(timeout_tag[0])
330                        timeout_tag[0] = 0
331                    except:
332                        timeout_tag[0] = 0
333        elif spr and spr.type == 'block':
334            if timeout_tag[0] == 0:
335                timeout_tag[0] = showPopup(spr.proto.name,tw)
336                tw.spr = spr
337            else:
338                if timeout_tag[0] > 0:
339                    try:
340                        gobject.source_remove(timeout_tag[0])
341                        timeout_tag[0] = 0
342                    except:
343                        timeout_tag[0] = 0
344        else:
345            if timeout_tag[0] > 0:
346                try:
347                    gobject.source_remove(timeout_tag[0])
348                    timeout_tag[0] = 0
349                except:
350                    timeout_tag[0] = 0
351        return
352    tw.block_operation = 'move'
353    spr = tw.draggroup[0]
354    if spr.type == 'block':
355        tw.spr = spr
356        dragx, dragy = tw.dragpos
357        if mdx != 0 or mdy != 0:
358            dx,dy = mdx,mdy
359        else:
360            dx,dy = x-dragx-spr.x,y-dragy-spr.y
361        # skip if there was a move of 0,0
362        if dx == 0 and dy == 0:
363            return
364        # drag entire stack if moving lock block
365        if spr.proto.name == 'lock':
366            tw.draggroup = findgroup(find_top_block(spr))
367        else:
368            tw.draggroup = findgroup(spr)
369        # check to see if any block ends up with a negative x
370        for b in tw.draggroup:
371            if b.x+dx < 0:
372                dx += -(b.x+dx)
373        # move the stack
374        for b in tw.draggroup:
375            move(b,(b.x+dx, b.y+dy))
376    elif spr.type=='turtle':
377        type,dragx,dragy = tw.dragpos
378        if type == 'move':
379            if mdx != 0 or mdy != 0:
380                dx,dy = mdx,mdy
381            else:
382                dx,dy = x-dragx-spr.x,y-dragy-spr.y
383            move(spr, (spr.x+dx, spr.y+dy))
384        else:
385            if mdx != 0 or mdy != 0:
386                dx,dy = mdx,mdy
387            else:
388                dx,dy = x-spr.x-30,y-spr.y-30
389            seth(tw.turtle, int(dragx+atan2(dy,dx)/DEGTOR+5)/10*10)
390    if mdx != 0 or mdy != 0:
391        dx,dy = 0,0
392    else:
393        tw.dx += dx
394        tw.dy += dy
395
396#
397# Button release
398#
399
400def buttonrelease_cb(win, event, tw):
401    x,y = xy(event)
402    button_release(tw, x, y)
403    if hasattr(tw, 'activity') and \
404        hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None:
405        # print "sending release button"
406        tw.activity._send_event("r:"+str(x)+":"+str(y))
407    return True
408
409def button_release(tw, x, y, verbose=False):
410    if tw.dx != 0 or tw.dy != 0:
411        if hasattr(tw, 'activity') and \
412            hasattr(tw.activity, 'chattube') and \
413            tw.activity.chattube is not None:
414                if verbose:
415                    print "processing move: " + str(tw.dx) + " " + str(tw.dy)
416                tw.activity._send_event("m:"+str(tw.dx)+":"+str(tw.dy))
417                tw.dx = 0
418                tw.dy = 0
419    if verbose:
420        print "processing remote button release: " + str(x) + " " + str(y)
421    if tw.draggroup == None:
422        return
423    spr = tw.draggroup[0]
424    if hasattr(spr,'proto'):
425        print "processing %s block in button release" % (spr.proto.name)
426        print "block operation is %s" % (tw.block_operation)
427    if spr.type == 'turtle':
428        tw.turtle.xcor = tw.turtle.spr.x-tw.turtle.canvas.x- \
429            tw.turtle.canvas.width/2+30
430        tw.turtle.ycor = tw.turtle.canvas.height/2-tw.turtle.spr.y+ \
431            tw.turtle.canvas.y-30
432        move_turtle(tw.turtle)
433        display_coordinates(tw)
434        tw.draggroup = None
435        return
436    if tw.block_operation=='move' and hit(tw.category_spr, (x,y)):
437        for b in tw.draggroup: hide(b)
438        tw.draggroup = None
439        return
440    if tw.block_operation=='new':
441        for b in tw.draggroup:
442            move(b, (b.x+200, b.y))
443    snap_to_dock(tw)
444    for b in tw.draggroup: setlayer(b,650)
445    tw.draggroup = None
446    print "block operation: %s" % (tw.block_operation)
447    if tw.block_operation=='click':
448        print "block operation is click"
449        if tw.spr.proto.name=='number':
450            print "number block selected"
451            tw.selected_block = spr
452            move(tw.select_mask, (spr.x-5,spr.y-5))
453            setlayer(tw.select_mask, 660)
454            print "setting selection mask"
455            tw.firstkey = True
456        elif tw.defdict.has_key(spr.proto.name):
457            tw.selected_block = spr
458            if tw.spr.proto.name=='string':
459                print "string block selected"
460                move(tw.select_mask_string, (spr.x-5,spr.y-5))
461                setlayer(tw.select_mask_string, 660)
462                print "setting selection mask"
463                tw.firstkey = True
464            elif tw.spr.proto.name in importblocks:
465                import_from_journal(tw, spr)
466        elif tw.spr.proto.name=='nop' and tw.myblock==None:
467            tw.activity.import_py()
468        else: run_stack(tw, spr)
469
470def import_from_journal(tw, spr):
471    if hasattr(tw,"activity"):
472        chooser = ObjectChooser('Choose image', None,\
473                              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
474        try:
475            result = chooser.run()
476            if result == gtk.RESPONSE_ACCEPT:
477                dsobject = chooser.get_selected_object()
478                # change block graphic to indicate that object is "loaded"
479                if spr.proto.name == 'journal':
480                    load_image(tw, dsobject, spr)
481                elif spr.proto.name == 'audiooff':
482                    setimage(spr,tw.media_shapes['audioon'])
483                else:
484                    setimage(spr, tw.media_shapes['decson'])
485                spr.ds_id = dsobject.object_id
486                dsobject.destroy()
487        finally:
488            chooser.destroy()
489            del chooser
490    else:
491        print "Journal Object Chooser unavailable from outside of Sugar"
492
493# Replace Journal block graphic with preview image
494def load_image(tw, picture, spr):
495    from talogo import get_pixbuf_from_journal
496    pixbuf = get_pixbuf_from_journal(picture,spr.width,spr.height)
497    if pixbuf is not None:
498        setimage(spr, pixbuf)
499    else:
500        setimage(spr, tw.media_shapes['texton'])
501
502# change the icon for user-defined blocks after Python code is loaded
503def set_userdefined(tw):
504    list = tw.sprites[:]
505    for spr in list:
506        if hasattr(spr,'proto') and spr.proto.name == 'nop':
507            setimage(spr,tw.media_shapes['pythonloaded'])
508    tw.nop = 'pythonloaded'
509
510def snap_to_dock(tw):
511    d=200
512    me = tw.draggroup[0]
513    for mydockn in range(len(me.proto.docks)):
514        for you in blocks(tw):
515            if you in tw.draggroup:
516                continue
517            for yourdockn in range(len(you.proto.docks)):
518                thisxy = dock_dx_dy(you,yourdockn,me,mydockn)
519                if magnitude(thisxy)>d:
520                    continue
521                d=magnitude(thisxy)
522                bestxy=thisxy
523                bestyou=you
524                bestyourdockn=yourdockn
525                bestmydockn=mydockn
526    if d<200:
527        for b in tw.draggroup:
528            move(b,(b.x+bestxy[0],b.y+bestxy[1]))
529        blockindock=bestyou.connections[bestyourdockn]
530        if blockindock!=None:
531            for b in findgroup(blockindock):
532                hide(b)
533        bestyou.connections[bestyourdockn]=me
534        me.connections[bestmydockn]=bestyou
535
536def dock_dx_dy(block1,dock1n,block2,dock2n):
537    dock1 = block1.proto.docks[dock1n]
538    dock2 = block2.proto.docks[dock2n]
539    d1type,d1dir,d1x,d1y=dock1[0:4]
540    d2type,d2dir,d2x,d2y=dock2[0:4]
541    if (d2type!='num') or (dock2n!=0):
542        if block1.connections[dock1n] != None:
543            return (100,100)
544        if block2.connections[dock2n] != None:
545            return (100,100)
546    if block1==block2: return (100,100)
547    if d1type!=d2type:
548        # some blocks can take strings or nums
549        if block1.proto.name in ('write', 'plus2', 'equal', 'less', 'greater', \
550                                 'template1', 'template2', 'template3', \
551                                 'template4', 'template6', 'template7', 'nop', \
552                                 'print', 'stack'):
553            if block1.proto.name == 'write' and d1type == 'string':
554                if d2type == 'num' or d2type == 'string':
555                    pass
556            else:
557                if d2type == 'num' or d2type == 'string':
558                    pass
559        # some blocks can take strings, nums, or Journal
560        elif block1.proto.name in ('show', 'push', 'storein', 'storeinbox1', \
561                                   'storeinbox2'):
562            if d2type == 'num' or d2type == 'string' or d2type == 'journal':
563                pass
564        # some blocks can take media, audio, movies, of descriptions
565        elif block1.proto.name in ('containter'):
566            if d1type == 'audiooff' or d1type == 'journal':
567                pass
568        else:
569            return (100,100)
570    if d1dir==d2dir:
571        return (100,100)
572    return (block1.x+d1x)-(block2.x+d2x),(block1.y+d1y)-(block2.y+d2y)
573
574def magnitude(pos):
575    x,y = pos
576    return x*x+y*y
577
578#
579# Repaint
580#
581
582def expose_cb(win, event, tw):
583    redrawsprites(tw)
584    return True
585
586#
587# Keyboard
588#
589
590def keypress_cb(area, event, tw):
591    keyname = gtk.gdk.keyval_name(event.keyval)
592#    keyunicode = unichr(gtk.gdk.keyval_to_unicode(event.keyval)).replace("\x00","")
593    keyunicode = gtk.gdk.keyval_to_unicode(event.keyval)
594#    print keyname
595#    if keyunicode > 0:
596#        print unichr(keyunicode)
597
598    if event.get_state()&gtk.gdk.MOD1_MASK:
599        alt_mask = True
600    else:
601        alt_mask = False
602    results = key_press(tw, alt_mask, keyname, keyunicode)
603    if keyname is not None and hasattr(tw,"activity") and \
604        hasattr(tw.activity, 'chattube') and tw.activity.chattube is not None:
605        # print "key press"
606        if alt_mask:
607            tw.activity._send_event("k:"+'T'+":"+keyname+":"+str(keyunicode))
608        else:
609            tw.activity._send_event("k:"+'F'+":"+keyname+":"+str(keyunicode))
610    return keyname
611'''
612    if len(keyname)>1:
613        # print "(" + keyunicode.encode("utf-8") + ")"
614        return keyname
615    else:
616        # print "[" + keyunicode.encode("utf-8") + "]"
617        return keyunicode.encode("utf-8")
618'''
619def key_press(tw, alt_mask, keyname, keyunicode, verbose=False):
620    if keyname is None:
621        return False
622    print "processing key press: " + keyname
623    tw.keypress = keyname
624    if alt_mask is True and tw.selected_block==None:
625        if keyname=="i" and hasattr(tw, 'activity'):
626            tw.activity.waiting_for_blocks = True
627            tw.activity._send_event("i") # request sync for sharing
628        elif keyname=="p":
629            hideshow_button(tw)
630        elif keyname=='q':
631            exit()
632        return True
633    if tw.selected_block is not None and \
634       tw.selected_block.proto.name == 'number':
635        print "processing keyboard input for number block"
636        if keyname in ['minus', 'period']:
637            keyname = {'minus': '-', 'period': '.'}[keyname]
638        oldnum = tw.selected_block.label
639        selblock=tw.selected_block.proto
640        if keyname == 'BackSpace':
641            if len(oldnum) > 1:
642                newnum = oldnum[:len(oldnum)-1]
643            else:
644                newnum = ''
645            setlabel(tw.selected_block, selblock.check(newnum,oldnum))
646            if len(newnum) > 0:
647                tw.firstkey = False
648            else:
649                tw.firstkey = True
650        if len(keyname)>1:
651            return True
652    else: # gtk.keysyms.Left ...
653        if keyname in ['Escape', 'Return', 'KP_Page_Up',
654                       'Up', 'Down', 'Left', 'Right', 'KP_Home', 'KP_End',
655                       'KP_Up', 'KP_Down', 'KP_Left', 'KP_Right',
656                       'KP_Page_Down']:
657            # move blocks (except number and text blocks only with arrows)
658            # or click with Return
659            if keyname == 'KP_End':
660                run_button(tw, 0)
661            elif tw.spr is not None:
662                if tw.spr.type == 'turtle': # jog turtle with arrow keys
663                    if keyname == 'KP_Up' or keyname == 'Up':
664                        jog_turtle(tw,0,10)
665                    elif keyname == 'KP_Down' or keyname == 'Down':
666                        jog_turtle(tw,0,-10)
667                    elif keyname == 'KP_Left' or keyname == 'Left':
668                        jog_turtle(tw,-10,0)
669                    elif keyname == 'KP_Right' or keyname == 'Right':
670                        jog_turtle(tw,10,0)
671                    elif keyname == 'KP_Home':
672                        jog_turtle(tw,-1,-1)
673                elif tw.spr.type == 'block':
674                    if keyname == 'Return' or keyname == 'KP_Page_Up':
675                        click_block(tw)
676                    elif keyname == 'KP_Up' or keyname == 'Up':
677                        jog_block(tw,0,10)
678                    elif keyname == 'KP_Down' or keyname == 'Down':
679                        jog_block(tw,0,-10)
680                    elif keyname == 'KP_Left' or keyname == 'Left':
681                        jog_block(tw,-10,0)
682                    elif keyname == 'KP_Right' or keyname == 'Right':
683                        jog_block(tw,10,0)
684                    elif keyname == 'KP_Page_Down':
685                        if tw.draggroup == None:
686                            tw.draggroup = findgroup(tw.spr)
687                        for b in tw.draggroup: hide(b)
688                        tw.draggroup = None
689                elif tw.spr.type == 'selbutton':
690                    if keyname == 'Return' or keyname == 'KP_Page_Up':
691                        select_category(tw,tw.spr)
692                elif tw.spr.type == 'category':
693                    if keyname == 'Return' or keyname == 'KP_Page_Up':
694                        (x,y) = tw.window.get_pointer()
695                        block_selector_pressed(tw,x,y)
696                        for b in tw.draggroup:
697                           move(b, (b.x+200, b.y))
698                        tw.draggroup = None
699            return True
700    if tw.selected_block is None:
701        return False
702    if keyname in ['Shift_L', 'Shift_R', 'Control_L', 'Caps_Lock', \
703                   'Alt_L', 'Alt_R', 'KP_Enter', 'ISO_Level3_Shift']:
704        keyname = ''
705        keyunicode = 0
706    # Hack until I sort out input and unicode + dead keys
707    if keyname[0:5] == 'dead_':
708        tw.dead_key = keyname
709        keyname = ''
710        keyunicode = 0
711    if keyname == 'Tab':
712        keyunicode = 32 # substitute a space for a tab
713    oldnum = tw.selected_block.label
714    selblock=tw.selected_block.proto
715    if keyname == 'BackSpace':
716        if len(oldnum) > 1:
717            newnum = oldnum[:len(oldnum)-1]
718        else:
719            newnum = ''
720        setlabel(tw.selected_block, selblock.check(newnum,oldnum))
721        if len(newnum) > 0:
722            tw.firstkey = False
723        else:
724            tw.firstkey = True
725    elif keyname is not '':
726        # Hack until I sort out input and unicode + dead keys
727        if tw.dead_key == 'dead_grave':
728            keyunicode = dead_grave[keyname]
729        elif tw.dead_key == 'dead_acute':
730            keyunicode = dead_acute[keyname]
731        elif tw.dead_key == 'dead_circumflex':
732            keyunicode = dead_circumflex[keyname]
733        elif tw.dead_key == 'dead_tilde':
734            keyunicode = dead_tilde[keyname]
735        elif tw.dead_key == 'dead_diaeresis':
736            keyunicode = dead_diaeresis[keyname]
737        elif tw.dead_key == 'dead_abovering':
738            keyunicode = dead_abovering[keyname]
739        tw.dead_key = ""
740        if tw.firstkey:
741            newnum = selblock.check(unichr(keyunicode), \
742                                    tw.defdict[selblock.name])
743        elif keyunicode > 0:
744            if unichr(keyunicode) is not '\x00':
745                newnum = oldnum+unichr(keyunicode)
746            else:
747                newnum = oldnum
748        else:
749            newnum = ""
750        setlabel(tw.selected_block, selblock.check(newnum,oldnum))
751        tw.firstkey = False
752    return True
753
754def unselect(tw):
755    if tw.selected_block.label in ['-', '.', '-.']:
756        setlabel(tw.selected_block,'0')
757
758    # put an upper and lower bound on numbers to prevent OverflowError
759    if tw.selected_block.proto.name == 'number' and \
760       tw.selected_block.label is not None:
761        try:
762            i = float(tw.selected_block.label)
763            if i > 1000000:
764                setlabel(tw.selected_block,'1')
765                showlabel(tw.lc,"#overflowerror")
766            elif i < -1000000:
767                setlabel(tw.selected_block,'-1')
768                showlabel(tw.lc,"#overflowerror")
769        except ValueError:
770            pass
771
772    hide(tw.select_mask)
773    hide(tw.select_mask_string)
774    tw.selected_block = None
775
776def jog_turtle(tw,dx,dy):
777    if dx == -1 and dy == -1:
778        tw.turtle.xcor = 0
779        tw.turtle.ycor = 0
780    else:
781        tw.turtle.xcor += dx
782        tw.turtle.ycor += dy
783    move_turtle(tw.turtle)
784    display_coordinates(tw)
785    tw.draggroup = None
786
787def jog_block(tw,dx,dy):
788    # drag entire stack if moving lock block
789    if tw.spr.proto.name == 'lock':
790        tw.draggroup = findgroup(find_top_block(tw.spr))
791    else:
792        tw.draggroup = findgroup(tw.spr)
793    # check to see if any block ends up with a negative x
794    for b in tw.draggroup:
795        if b.x+dx < 0:
796            dx += -(b.x+dx)
797    # move the stack
798    for b in tw.draggroup:
799        move(b,(b.x+dx, b.y-dy))
800    snap_to_dock(tw)
801    tw.draggroup = None
802
803def click_block(tw):
804    if tw.spr.proto.name=='number':
805        tw.selected_block = tw.spr
806        move(tw.select_mask, (tw.spr.x-5,tw.spr.y-5))
807        setlayer(tw.select_mask, 660)
808        tw.firstkey = True
809    elif tw.defdict.has_key(tw.spr.proto.name):
810        tw.selected_block = tw.spr
811        if tw.spr.proto.name=='string':
812            move(tw.select_mask_string, (tw.spr.x-5,tw.spr.y-5))
813            setlayer(tw.select_mask_string, 660)
814            tw.firstkey = True
815        elif tw.spr.proto.name in importblocks:
816            import_from_journal(tw, tw.spr)
817    elif tw.spr.proto.name=='nop' and tw.myblock==None:
818        tw.activity.import_py()
819    else: run_stack(tw, tw.spr)
820
821#
822# Block utilities
823#
824
825def disconnect(b):
826    if b.connections[0]==None:
827        return
828    b2=b.connections[0]
829    b2.connections[b2.connections.index(b)] = None
830    b.connections[0] = None
831
832def run_stack(tw,spr):
833    tw.lc.ag = None
834    top = find_top_block(spr)
835    run_blocks(tw.lc, top, blocks(tw), True)
836    gobject.idle_add(doevalstep, tw.lc)
837
838def findgroup(b):
839    group=[b]
840    for b2 in b.connections[1:]:
841        if b2!=None: group.extend(findgroup(b2))
842    return group
843
844def find_top_block(spr):
845    b = spr
846    while b.connections[0]!=None:
847        b=b.connections[0]
848    return b
849
850def runtool(tw, spr, cmd, *args):
851    cmd(*(args))
852
853def eraser_button(tw):
854    # hide status block
855    setlayer(tw.status_spr,400)
856    clear(tw.lc)
857    display_coordinates(tw)
858
859def stop_button(tw):
860    stop_logo(tw)
861
862def run_button(tw, time):
863    print "you better run, turtle, run!!"
864    # look for the start block
865    for b in blocks(tw):
866        if find_start_stack(tw, b):
867            tw.step_time = time
868            if hasattr(tw,'activity'):
869                tw.activity.recenter()
870            run_stack(tw, b)
871            return
872    # no start block, so run a stack that isn't a hat
873    for b in blocks(tw):
874        if find_block_to_run(tw, b):
875            print "running " + b.proto.name
876            tw.step_time = time
877            run_stack(tw, b)
878    return
879
880def hideshow_button(tw):
881    if tw.hide is False:
882        for b in blocks(tw): setlayer(b,100)
883        hide_palette(tw)
884        hide(tw.select_mask)
885        hide(tw.select_mask_string)
886        tw.hide = True
887    else:
888        for b in blocks(tw): setlayer(b,650)
889        show_palette(tw)
890        tw.hide = False
891    inval(tw.turtle.canvas)
892
893# find start stack
894def find_start_stack(tw, spr):
895    top = find_top_block(spr)
896    if spr.proto.name == 'start':
897        return True
898    else:
899        return False
900
901# find a stack to run (any stack without a hat)
902def find_block_to_run(tw, spr):
903    top = find_top_block(spr)
904    if spr == top and spr.proto.name[0:3] != 'hat':
905        return True
906    else:
907        return False
908
909def blocks(tw):
910    return [spr for spr in tw.sprites if spr.type == 'block']
911
912def xy(event):
913    return map(int, event.get_coords())
914
915def showPopup(block_name,tw):
916    if blocks_dict.has_key(block_name):
917        block_name_s = _(blocks_dict[block_name])
918    else:
919        block_name_s = _(block_name)
920    if hover_dict.has_key(block_name):
921        label = block_name_s + ": " + hover_dict[block_name]
922    else:
923        label = block_name_s
924    if hasattr(tw, "activity"):
925        tw.activity.hover_help_label.set_text(label)
926        tw.activity.hover_help_label.show()
927    elif hasattr(tw, "win"):
928        tw.win.set_title(_("Turtle Art") + " — " + label)
929    return 0