Blockly.Blocks['procedures_defnoreturn'] = { /** * Block for defining a procedure with no return value. * @this Blockly.Block */ init: function() { var nameField = new Blockly.FieldTextInput( Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE, Blockly.Procedures.rename); nameField.setSpellcheck(false); this.setPreviousStatement(true); this.setNextStatement(true); this.updateShape_(); this.setColour(Blockly.Blocks.procedures.HUE); this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL); this.arguments_ = []; this.statementConnection_ = null; }, /** * Update the display of parameters for this procedure definition block. * Display a warning if there are duplicately named parameters. * @private * @this Blockly.Block */ updateParams_: function() { // Check for duplicated arguments. var badArg = false; var hash = {}; for (var i = 0; i < this.arguments_.length; i++) { if (hash['arg_' + this.arguments_[i].toLowerCase()]) { badArg = true; break; } hash['arg_' + this.arguments_[i].toLowerCase()] = true; } if (badArg) { this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING); } else { this.setWarningText(null); } }, updateShape_: function() { var that = this; function addField(field, block, e) { var rect = field.fieldGroup_.getBoundingClientRect(); var yPosition = e.clientY; if (yPosition < rect.top+rect.height/2) { this.arguments_ that.itemCount_ += 1; that.addParameter(that.itemCount_); } else { if (that.itemCount_ > 0) { that.removeInput('VALUE' + that.itemCount_); that.itemCount_ -= 1; } } } var clickablePlusMinus = new Blockly.FieldClickImage("images/plus_minus_v.png", 12, 24, '+', addField, '-2px'); this.appendDummyInput() .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE) .appendField(nameField, 'NAME'); this.appendDummyInput() .appendField("parameters") .appendField(clickablePlusMinus) .appendField('', 'PARAMS'); this.appendStatementInput('STACK') .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO); }, /** * Create XML to represent the argument inputs. * @param {=boolean} opt_paramIds If true include the IDs of the parameter * quarks. Used by Blockly.Procedures.mutateCallers for reconnection. * @return {!Element} XML storage element. * @this Blockly.Block */ mutationToDom: function(opt_paramIds) { var container = document.createElement('mutation'); if (opt_paramIds) { container.setAttribute('name', this.getFieldValue('NAME')); } for (var i = 0; i < this.arguments_.length; i++) { var parameter = document.createElement('arg'); parameter.setAttribute('name', this.arguments_[i]); if (opt_paramIds && this.paramIds_) { parameter.setAttribute('paramId', this.paramIds_[i]); } container.appendChild(parameter); } return container; }, /** * Parse XML to restore the argument inputs. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function(xmlElement) { this.arguments_ = []; for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { if (childNode.nodeName.toLowerCase() == 'arg') { this.arguments_.push(childNode.getAttribute('name')); } } this.updateShape_(); this.updateParams_(); Blockly.Procedures.mutateCallers(this); }, /** * Return the signature of this procedure definition. * @return {!Array} Tuple containing three elements: * - the name of the defined procedure, * - a list of all its arguments, * - that it DOES NOT have a return value. * @this Blockly.Block */ getProcedureDef: function() { return [this.getFieldValue('NAME'), this.arguments_, false]; }, /** * Return all variables referenced by this block. * @return {!Array.} List of variable names. * @this Blockly.Block */ getVars: function() { return this.arguments_; }, /** * Notification that a variable is renaming. * If the name matches one of this block's variables, rename it. * @param {string} oldName Previous name of variable. * @param {string} newName Renamed variable. * @this Blockly.Block */ renameVar: function(oldName, newName) { var change = false; for (var i = 0; i < this.arguments_.length; i++) { if (Blockly.Names.equals(oldName, this.arguments_[i])) { this.arguments_[i] = newName; change = true; } } if (change) { this.updateParams_(); // Update the mutator's variables if the mutator is open. if (this.mutator.isVisible()) { var blocks = this.mutator.workspace_.getAllBlocks(); for (var i = 0, block; block = blocks[i]; i++) { if (block.type == 'procedures_mutatorarg' && Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) { block.setFieldValue(newName, 'NAME'); } } } } }, /** * Add custom menu options to this block's context menu. * @param {!Array} options List of menu options to add to. * @this Blockly.Block */ customContextMenu: function(options) { // Add option to create caller. var option = {enabled: true}; var name = this.getFieldValue('NAME'); option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name); var xmlMutation = goog.dom.createDom('mutation'); xmlMutation.setAttribute('name', name); for (var i = 0; i < this.arguments_.length; i++) { var xmlArg = goog.dom.createDom('arg'); xmlArg.setAttribute('name', this.arguments_[i]); xmlMutation.appendChild(xmlArg); } var xmlBlock = goog.dom.createDom('block', null, xmlMutation); xmlBlock.setAttribute('type', this.callType_); option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); options.push(option); // Add options to create getters for each parameter. if (!this.isCollapsed()) { for (var i = 0; i < this.arguments_.length; i++) { var option = {enabled: true}; var name = this.arguments_[i]; option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name); var xmlField = goog.dom.createDom('field', null, name); xmlField.setAttribute('name', 'VAR'); var xmlBlock = goog.dom.createDom('block', null, xmlField); xmlBlock.setAttribute('type', 'variables_get'); option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); options.push(option); } } }, callType_: 'procedures_callnoreturn', /** * Called whenever anything on the workspace changes. * Add warning if this flow block is not nested inside a loop. * @this Blockly.Block */ onchange: function() { if (!this.workspace) { // Block has been deleted. return; } // Does it have valid returns? var hasReturns = false; var descendants = this.getDescendants(); for (var i = 0; i < descendants.length; i++) { if (descendants[i].type == 'procedures_return') { hasReturns = true; } } // Iterate through every block and fix the return state var functionName = this.getFieldValue('NAME'); var blocks = this.workspace.getAllBlocks(); for (var i = 0; i < blocks.length; i++) { if (blocks[i].type == 'procedures_callreturn' || blocks[i].type == 'procedures_callnoreturn') { var callName = blocks[i].getFieldValue('NAME'); // Variable name may be null if the block is only half-built. if (callName && Blockly.Names.equals(functionName, callName)) { blocks[i].setReturn(hasReturns); } } } } }; Blockly.Python['procedures_defreturn'] = function(block) { // Define a procedure with a return value. // Get the function's name var funcName = Blockly.Python.variableDB_.getName(block.getFieldValue('NAME'), Blockly.Procedures.NAME_TYPE); // Get the stack of code var branch = Blockly.Python.statementToCode(block, 'STACK'); // Handle prefixing if (Blockly.Python.STATEMENT_PREFIX) { branch = Blockly.Python.prefixLines( Blockly.Python.STATEMENT_PREFIX.replace(/%1/g, '\'' + block.id + '\''), Blockly.Python.INDENT) + branch; } // Handle infinite loop trapping if (Blockly.Python.INFINITE_LOOP_TRAP) { branch = Blockly.Python.INFINITE_LOOP_TRAP.replace(/%1/g, '"' + block.id + '"') + branch; } // Handle return value var returnValue = Blockly.Python.valueToCode(block, 'RETURN', Blockly.Python.ORDER_NONE) || ''; if (returnValue) { returnValue = ' return ' + returnValue + '\n'; } else if (!branch) { branch = Blockly.Python.PASS; } var args = []; for (var i = 0; i < block.arguments_.length; i++) { args[i] = Blockly.Python.variableDB_.getName(block.arguments_[i], Blockly.Variables.NAME_TYPE); } var code = 'def ' + funcName + '(' + args.join(', ') + '):\n' + branch + returnValue; return code; };