Skip to content

Commit 90e2b5a

Browse files
author
hartsantler
committed
GoogleBlockly: new custom "callback blocks" generator
1 parent 1ba276a commit 90e2b5a

7 files changed

Lines changed: 774 additions & 15 deletions

File tree

bindings/blockly.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Google Blockly wrapper for PythonJS
2+
# by Brett Hartshorn - copyright 2013
3+
# License: "New BSD"
4+
5+
BlocklyBlockGenerators = dict() ## Blocks share a single namespace in Blockly
6+
_blockly_selected_block = None ## for triggering on_click_callback
7+
8+
def initialize_blockly( blockly_id='blocklyDiv', toolbox_id='toolbox', on_changed_callback=None ):
9+
print 'initialize_blockly'
10+
if len( BlocklyBlockGenerators.keys() ):
11+
toolbox = document.getElementById( toolbox_id )
12+
cat = document.createElement('category')
13+
cat.setAttribute('name', 'Callbacks')
14+
toolbox.appendChild( cat )
15+
for block_name in BlocklyBlockGenerators.keys():
16+
e = document.createElement('block')
17+
e.setAttribute('type', block_name)
18+
cat.appendChild( e )
19+
20+
21+
with javascript:
22+
Blockly.inject(
23+
document.getElementById( blockly_id ),
24+
{path : './', toolbox : document.getElementById(toolbox_id)}
25+
)
26+
27+
def on_changed():
28+
global _blockly_selected_block
29+
if Blockly.selected != _blockly_selected_block:
30+
_blockly_selected_block = Blockly.selected
31+
if _blockly_selected_block and _blockly_selected_block.on_selected_callback:
32+
print 'BLOCKLY - new block selected:', _blockly_selected_block
33+
_blockly_selected_block.on_selected_callback()
34+
35+
if on_changed_callback: ## this gets triggered for any change, even moving a block in the workspace.
36+
on_changed_callback()
37+
38+
Blockly.addChangeListener( on_changed )
39+
40+
for block_name in BlocklyBlockGenerators.keys():
41+
b = BlocklyBlockGenerators[ block_name ]
42+
b.bind_block()
43+
b.bind_generator()
44+
45+
class BlocklyBlock:
46+
'''
47+
Instead of using this class directly, you should use StatementBlock or ValueBlock subclasses below.
48+
notes:
49+
. a block is not allowed to have previous or next statement notches and have an output.
50+
. a bare block can have no inputs or output, and no previous or next statement notches.
51+
52+
'''
53+
def __init__(self, name, title=None, color=None):
54+
self.setup( name, title=title, color=color )
55+
56+
def setup(self, name, title=None, color=None):
57+
if name in BlocklyBlockGenerators: raise TypeError
58+
BlocklyBlockGenerators[ name ] = self
59+
self.name = name
60+
self.title = title
61+
self.color = color
62+
self.input_values = []
63+
self.input_statements = []
64+
self.output = None
65+
self.stackable = False
66+
self.stack_input = False
67+
self.stack_output = False
68+
self.is_statement = False
69+
self.external_function = None
70+
self.on_click_callback = None
71+
72+
def set_on_click_callback(self, callback):
73+
self.on_click_callback = callback
74+
75+
def set_external_function(self, func_name):
76+
self.external_function = func_name
77+
if self.title:
78+
self.title = self.title + ': ' + func_name
79+
80+
def set_output(self, output):
81+
if self.stack_input: raise TypeError
82+
elif self.stack_output: raise TypeError
83+
elif output == 'Number': pass
84+
elif output == 'String': pass
85+
elif output == 'Array': pass
86+
elif output == '*': pass
87+
else: raise TypeError
88+
self.output = output
89+
90+
def make_statement(self, stack_input=None, stack_output=None):
91+
if self.output: raise TypeError
92+
elif stack_input: pass
93+
elif stack_output: pass
94+
else: raise TypeError
95+
self.stack_input = stack_input
96+
self.stack_output = stack_output
97+
self.is_statement = True
98+
99+
def add_input_value(self, name=None, type=None, title=None):
100+
if name is None: raise TypeError
101+
elif type == 'Number': pass
102+
elif type == 'String': pass
103+
elif type == 'Array': pass
104+
else: raise TypeError
105+
self.input_values.append(
106+
{'name':name, 'type':type, 'title':title}
107+
)
108+
109+
def add_input_statement(self, name=None, title=None):
110+
if name is None: raise TypeError
111+
self.input_statements.append(
112+
{'name':name, 'title':title}
113+
)
114+
115+
116+
def bind_block(self):
117+
block_name = self.name
118+
stack_input = self.stack_input
119+
stack_output = self.stack_output
120+
output = self.output
121+
if stack_input or stack_output:
122+
if output:
123+
raise TypeError
124+
125+
title = self.title
126+
input_values = self.input_values
127+
input_statements = self.input_statements
128+
color = self.color
129+
130+
with javascript:
131+
def init():
132+
if color:
133+
this.setColour( color )
134+
135+
if title:
136+
this.appendDummyInput().appendTitle(title)
137+
138+
if stack_input:
139+
this.setPreviousStatement( True )
140+
if stack_output:
141+
this.setNextStatement( True )
142+
143+
for input in input_values: # input[...]['name'] for a dict becomes: input.__dict__.js_object['name']
144+
this.appendValueInput( input['name'] ).setCheck( input['type'] ).appendTitle( input['title'] )
145+
146+
for input in input_statements:
147+
this.appendStatementInput( input['name'] ).appendTitle( input['title'] )
148+
149+
if output == '*':
150+
this.setOutput( True, null ) ## allows output of any type
151+
elif output:
152+
this.setOutput( True, output )
153+
154+
print 'binding block:', block_name
155+
Blockly.Blocks[ block_name ] = {'init':init} ## register the block type with Blockly
156+
157+
def bind_generator(self):
158+
block_name = self.name
159+
external_function = self.external_function
160+
input_values = self.input_values
161+
input_statements = self.input_statements
162+
is_statement = self.is_statement
163+
164+
with javascript:
165+
def generator(block):
166+
code = ''
167+
168+
args = []
169+
for input in input_values:
170+
a = Blockly.Python.valueToCode(block, input['name'], Blockly.Python.ORDER_NONE)
171+
args.push( a )
172+
173+
for input in input_statements:
174+
a = Blockly.Python.statementToCode(block, input['name'])
175+
args.push( a )
176+
177+
if external_function:
178+
code += external_function + '(' + ','.join(args) + ')'
179+
else: ## this should be a simple series of statements?
180+
for a in args:
181+
code += a + ';'
182+
183+
if is_statement:
184+
return code ## statements can directly return
185+
else:
186+
return [ code, Blockly.Python.ORDER_NONE ] ## return Array
187+
188+
print 'bindings block generator:', block_name
189+
Blockly.Python[ block_name ] = generator
190+
191+
192+
193+
class StatementBlock( BlocklyBlock ):
194+
'''
195+
A statement-block has a previous and/or next statement notch: stack_input and/or stack_output
196+
'''
197+
def __init__(self, name, title=None, stack_input=False, stack_output=False, color=150):
198+
self.setup( name, title=title, color=color )
199+
self.make_statement( stack_input=stack_input, stack_output=stack_output)
200+
201+
202+
class ValueBlock( BlocklyBlock ):
203+
def __init__(self, name, title=None, output=None, color=120):
204+
if output is None: raise TypeError
205+
self.setup( name, title=title, color=color )
206+
self.set_output( output )
207+
208+
209+
210+
211+

pythonjs/python_to_pythonjs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,10 @@ def visit_ClassDef(self, node):
426426
self._class_attributes[ name ].add( item_name ) ## should this come before self.visit(item) ??
427427
elif isinstance(item, Pass):
428428
pass
429+
elif isinstance(item, ast.Expr) and isinstance(item.value, Str): ## skip doc strings
430+
pass
429431
else:
430-
raise NotImplementedError
432+
raise NotImplementedError( item )
431433

432434
for prop_name in self._decorator_properties:
433435
getter = self._decorator_properties[prop_name]['get']

0 commit comments

Comments
 (0)