|
| 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 | + |
0 commit comments