summaryrefslogtreecommitdiff
path: root/src/blockly/blocks/procedures.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/blockly/blocks/procedures.js')
-rw-r--r--src/blockly/blocks/procedures.js875
1 files changed, 875 insertions, 0 deletions
diff --git a/src/blockly/blocks/procedures.js b/src/blockly/blocks/procedures.js
new file mode 100644
index 0000000..19a5f6e
--- /dev/null
+++ b/src/blockly/blocks/procedures.js
@@ -0,0 +1,875 @@
+/**
+ * @license
+ * Visual Blocks Editor
+ *
+ * Copyright 2012 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Procedure blocks for Blockly.
+ * @author fraser@google.com (Neil Fraser)
+ */
+'use strict';
+
+goog.provide('Blockly.Blocks.procedures');
+
+goog.require('Blockly.Blocks');
+
+
+/**
+ * Common HSV hue for all blocks in this category.
+ */
+Blockly.Blocks.procedures.HUE = 290;
+
+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.appendDummyInput()
+ .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE)
+ .appendField(nameField, 'NAME')
+ .appendField('', 'PARAMS');
+ this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
+ if (Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) {
+ this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);
+ }
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
+ this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
+ this.arguments_ = [];
+ this.setStatements_(true);
+ this.statementConnection_ = null;
+ },
+ /**
+ * Add or remove the statement block from this function definition.
+ * @param {boolean} hasStatements True if a statement block is needed.
+ * @this Blockly.Block
+ */
+ setStatements_: function(hasStatements) {
+ if (this.hasStatements_ === hasStatements) {
+ return;
+ }
+ if (hasStatements) {
+ this.appendStatementInput('STACK')
+ .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
+ if (this.getInput('RETURN')) {
+ this.moveInputBefore('STACK', 'RETURN');
+ }
+ } else {
+ this.removeInput('STACK', true);
+ }
+ this.hasStatements_ = hasStatements;
+ },
+ /**
+ * 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);
+ }
+ // Merge the arguments into a human-readable list.
+ var paramString = '';
+ if (this.arguments_.length) {
+ paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS +
+ ' ' + this.arguments_.join(', ');
+ }
+ // The params field is deterministic based on the mutation,
+ // no need to fire a change event.
+ Blockly.Events.disable();
+ try {
+ this.setFieldValue(paramString, 'PARAMS');
+ } finally {
+ Blockly.Events.enable();
+ }
+ },
+ /**
+ * 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);
+ }
+
+ // Save whether the statement input is visible.
+ if (!this.hasStatements_) {
+ container.setAttribute('statements', 'false');
+ }
+ 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.updateParams_();
+ Blockly.Procedures.mutateCallers(this);
+
+ // Show or hide the statement input.
+ this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
+ },
+ /**
+ * Populate the mutator's dialog with this block's components.
+ * @param {!Blockly.Workspace} workspace Mutator's workspace.
+ * @return {!Blockly.Block} Root block in mutator.
+ * @this Blockly.Block
+ */
+ decompose: function(workspace) {
+ var containerBlock = workspace.newBlock('procedures_mutatorcontainer');
+ containerBlock.initSvg();
+
+ // Check/uncheck the allow statement box.
+ if (this.getInput('RETURN')) {
+ containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE',
+ 'STATEMENTS');
+ } else {
+ containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
+ }
+
+ // Parameter list.
+ var connection = containerBlock.getInput('STACK').connection;
+ for (var i = 0; i < this.arguments_.length; i++) {
+ var paramBlock = workspace.newBlock('procedures_mutatorarg');
+ paramBlock.initSvg();
+ paramBlock.setFieldValue(this.arguments_[i], 'NAME');
+ // Store the old location.
+ paramBlock.oldLocation = i;
+ connection.connect(paramBlock.previousConnection);
+ connection = paramBlock.nextConnection;
+ }
+ // Initialize procedure's callers with blank IDs.
+ Blockly.Procedures.mutateCallers(this);
+ return containerBlock;
+ },
+ /**
+ * Reconfigure this block based on the mutator dialog's components.
+ * @param {!Blockly.Block} containerBlock Root block in mutator.
+ * @this Blockly.Block
+ */
+ compose: function(containerBlock) {
+ // Parameter list.
+ this.arguments_ = [];
+ this.paramIds_ = [];
+ var paramBlock = containerBlock.getInputTargetBlock('STACK');
+ while (paramBlock) {
+ this.arguments_.push(paramBlock.getFieldValue('NAME'));
+ this.paramIds_.push(paramBlock.id);
+ paramBlock = paramBlock.nextConnection &&
+ paramBlock.nextConnection.targetBlock();
+ }
+ this.updateParams_();
+ Blockly.Procedures.mutateCallers(this);
+
+ // Show/hide the statement input.
+ var hasStatements = containerBlock.getFieldValue('STATEMENTS');
+ if (hasStatements !== null) {
+ hasStatements = hasStatements == 'TRUE';
+ if (this.hasStatements_ != hasStatements) {
+ if (hasStatements) {
+ this.setStatements_(true);
+ // Restore the stack, if one was saved.
+ Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
+ this.statementConnection_ = null;
+ } else {
+ // Save the stack, then disconnect it.
+ var stackConnection = this.getInput('STACK').connection;
+ this.statementConnection_ = stackConnection.targetConnection;
+ if (this.statementConnection_) {
+ var stackBlock = stackConnection.targetBlock();
+ stackBlock.unplug();
+ stackBlock.bumpNeighbours_();
+ }
+ this.setStatements_(false);
+ }
+ }
+ }
+ },
+ /**
+ * 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.<string>} 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'
+};
+
+Blockly.Blocks['procedures_defreturn'] = {
+ /**
+ * Block for defining a procedure with a return value.
+ * @this Blockly.Block
+ */
+ init: function() {
+ var nameField = new Blockly.FieldTextInput(
+ Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE,
+ Blockly.Procedures.rename);
+ nameField.setSpellcheck(false);
+ this.appendDummyInput()
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE)
+ .appendField(nameField, 'NAME')
+ .appendField('', 'PARAMS');
+ this.appendValueInput('RETURN')
+ .setAlign(Blockly.ALIGN_RIGHT)
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+ this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
+ if (Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT) {
+ this.setCommentText(Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT);
+ }
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
+ this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
+ this.arguments_ = [];
+ this.setStatements_(true);
+ this.statementConnection_ = null;
+ },
+ setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
+ updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
+ mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
+ domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
+ decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
+ compose: Blockly.Blocks['procedures_defnoreturn'].compose,
+ /**
+ * 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 have a return value.
+ * @this Blockly.Block
+ */
+ getProcedureDef: function() {
+ return [this.getFieldValue('NAME'), this.arguments_, true];
+ },
+ getVars: Blockly.Blocks['procedures_defnoreturn'].getVars,
+ renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar,
+ customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu,
+ callType_: 'procedures_callreturn'
+};
+
+Blockly.Blocks['procedures_mutatorcontainer'] = {
+ /**
+ * Mutator block for procedure container.
+ * @this Blockly.Block
+ */
+ init: function() {
+ this.appendDummyInput()
+ .appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
+ this.appendStatementInput('STACK');
+ this.appendDummyInput('STATEMENT_INPUT')
+ .appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS)
+ .appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
+ this.contextMenu = false;
+ }
+};
+
+Blockly.Blocks['procedures_mutatorarg'] = {
+ /**
+ * Mutator block for procedure argument.
+ * @this Blockly.Block
+ */
+ init: function() {
+ var field = new Blockly.FieldTextInput('x', this.validator_);
+ this.appendDummyInput()
+ .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE)
+ .appendField(field, 'NAME');
+ this.setPreviousStatement(true);
+ this.setNextStatement(true);
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);
+ this.contextMenu = false;
+
+ // Create the default variable when we drag the block in from the flyout.
+ // Have to do this after installing the field on the block.
+ field.onFinishEditing_ = this.createNewVar_;
+ field.onFinishEditing_('x');
+ },
+ /**
+ * Obtain a valid name for the procedure.
+ * Merge runs of whitespace. Strip leading and trailing whitespace.
+ * Beyond this, all names are legal.
+ * @param {string} newVar User-supplied name.
+ * @return {?string} Valid name, or null if a name was not specified.
+ * @private
+ * @this Blockly.Block
+ */
+ validator_: function(newVar) {
+ newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
+ return newVar || null;
+ },
+ /**
+ * Called when focusing away from the text field.
+ * Creates a new variable with this name.
+ * @param {string} newText The new variable name.
+ * @private
+ * @this Blockly.FieldTextInput
+ */
+ createNewVar_: function(newText) {
+ var source = this.sourceBlock_;
+ if (source && source.workspace && source.workspace.options
+ && source.workspace.options.parentWorkspace) {
+ source.workspace.options.parentWorkspace.createVariable(newText);
+ }
+ }
+};
+
+Blockly.Blocks['procedures_callnoreturn'] = {
+ /**
+ * Block for calling a procedure with no return value.
+ * @this Blockly.Block
+ */
+ init: function() {
+ this.appendDummyInput('TOPROW')
+ .appendField(this.id, 'NAME');
+ this.setPreviousStatement(true);
+ this.setNextStatement(true);
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ // Tooltip is set in renameProcedure.
+ this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);
+ this.arguments_ = [];
+ this.quarkConnections_ = {};
+ this.quarkIds_ = null;
+ },
+ /**
+ * Returns the name of the procedure this block calls.
+ * @return {string} Procedure name.
+ * @this Blockly.Block
+ */
+ getProcedureCall: function() {
+ // The NAME field is guaranteed to exist, null will never be returned.
+ return /** @type {string} */ (this.getFieldValue('NAME'));
+ },
+ /**
+ * Notification that a procedure is renaming.
+ * If the name matches this block's procedure, rename it.
+ * @param {string} oldName Previous name of procedure.
+ * @param {string} newName Renamed procedure.
+ * @this Blockly.Block
+ */
+ renameProcedure: function(oldName, newName) {
+ if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
+ this.setFieldValue(newName, 'NAME');
+ this.setTooltip(
+ (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
+ Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
+ .replace('%1', newName));
+ }
+ },
+ /**
+ * Notification that the procedure's parameters have changed.
+ * @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
+ * @param {!Array.<string>} paramIds IDs of params (consistent for each
+ * parameter through the life of a mutator, regardless of param renaming),
+ * e.g. ['piua', 'f8b_', 'oi.o'].
+ * @private
+ * @this Blockly.Block
+ */
+ setProcedureParameters_: function(paramNames, paramIds) {
+ // Data structures:
+ // this.arguments = ['x', 'y']
+ // Existing param names.
+ // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
+ // Look-up of paramIds to connections plugged into the call block.
+ // this.quarkIds_ = ['piua', 'f8b_']
+ // Existing param IDs.
+ // Note that quarkConnections_ may include IDs that no longer exist, but
+ // which might reappear if a param is reattached in the mutator.
+ var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
+ this.workspace);
+ var mutatorOpen = defBlock && defBlock.mutator &&
+ defBlock.mutator.isVisible();
+ if (!mutatorOpen) {
+ this.quarkConnections_ = {};
+ this.quarkIds_ = null;
+ }
+ if (!paramIds) {
+ // Reset the quarks (a mutator is about to open).
+ return;
+ }
+ if (goog.array.equals(this.arguments_, paramNames)) {
+ // No change.
+ this.quarkIds_ = paramIds;
+ return;
+ }
+ if (paramIds.length != paramNames.length) {
+ throw 'Error: paramNames and paramIds must be the same length.';
+ }
+ this.setCollapsed(false);
+ if (!this.quarkIds_) {
+ // Initialize tracking for this block.
+ this.quarkConnections_ = {};
+ if (paramNames.join('\n') == this.arguments_.join('\n')) {
+ // No change to the parameters, allow quarkConnections_ to be
+ // populated with the existing connections.
+ this.quarkIds_ = paramIds;
+ } else {
+ this.quarkIds_ = [];
+ }
+ }
+ // Switch off rendering while the block is rebuilt.
+ var savedRendered = this.rendered;
+ this.rendered = false;
+ // Update the quarkConnections_ with existing connections.
+ for (var i = 0; i < this.arguments_.length; i++) {
+ var input = this.getInput('ARG' + i);
+ if (input) {
+ var connection = input.connection.targetConnection;
+ this.quarkConnections_[this.quarkIds_[i]] = connection;
+ if (mutatorOpen && connection &&
+ paramIds.indexOf(this.quarkIds_[i]) == -1) {
+ // This connection should no longer be attached to this block.
+ connection.disconnect();
+ connection.getSourceBlock().bumpNeighbours_();
+ }
+ }
+ }
+ // Rebuild the block's arguments.
+ this.arguments_ = [].concat(paramNames);
+ this.updateShape_();
+ this.quarkIds_ = paramIds;
+ // Reconnect any child blocks.
+ if (this.quarkIds_) {
+ for (var i = 0; i < this.arguments_.length; i++) {
+ var quarkId = this.quarkIds_[i];
+ if (quarkId in this.quarkConnections_) {
+ var connection = this.quarkConnections_[quarkId];
+ if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
+ // Block no longer exists or has been attached elsewhere.
+ delete this.quarkConnections_[quarkId];
+ }
+ }
+ }
+ }
+ // Restore rendering and show the changes.
+ this.rendered = savedRendered;
+ if (this.rendered) {
+ this.render();
+ }
+ },
+ /**
+ * Modify this block to have the correct number of arguments.
+ * @private
+ * @this Blockly.Block
+ */
+ updateShape_: function() {
+ for (var i = 0; i < this.arguments_.length; i++) {
+ var field = this.getField('ARGNAME' + i);
+ if (field) {
+ // Ensure argument name is up to date.
+ // The argument name field is deterministic based on the mutation,
+ // no need to fire a change event.
+ Blockly.Events.disable();
+ try {
+ field.setValue(this.arguments_[i]);
+ } finally {
+ Blockly.Events.enable();
+ }
+ } else {
+ // Add new input.
+ field = new Blockly.FieldLabel(this.arguments_[i]);
+ var input = this.appendValueInput('ARG' + i)
+ .setAlign(Blockly.ALIGN_RIGHT)
+ .appendField(field, 'ARGNAME' + i);
+ input.init();
+ }
+ }
+ // Remove deleted inputs.
+ while (this.getInput('ARG' + i)) {
+ this.removeInput('ARG' + i);
+ i++;
+ }
+ // Add 'with:' if there are parameters, remove otherwise.
+ var topRow = this.getInput('TOPROW');
+ if (topRow) {
+ if (this.arguments_.length) {
+ if (!this.getField('WITH')) {
+ topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
+ topRow.init();
+ }
+ } else {
+ if (this.getField('WITH')) {
+ topRow.removeField('WITH');
+ }
+ }
+ }
+ },
+ /**
+ * Create XML to represent the (non-editable) name and arguments.
+ * @return {!Element} XML storage element.
+ * @this Blockly.Block
+ */
+ mutationToDom: function() {
+ var container = document.createElement('mutation');
+ container.setAttribute('name', this.getProcedureCall());
+ for (var i = 0; i < this.arguments_.length; i++) {
+ var parameter = document.createElement('arg');
+ parameter.setAttribute('name', this.arguments_[i]);
+ container.appendChild(parameter);
+ }
+ return container;
+ },
+ /**
+ * Parse XML to restore the (non-editable) name and parameters.
+ * @param {!Element} xmlElement XML storage element.
+ * @this Blockly.Block
+ */
+ domToMutation: function(xmlElement) {
+ var name = xmlElement.getAttribute('name');
+ this.renameProcedure(this.getProcedureCall(), name);
+ var args = [];
+ var paramIds = [];
+ for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
+ if (childNode.nodeName.toLowerCase() == 'arg') {
+ args.push(childNode.getAttribute('name'));
+ paramIds.push(childNode.getAttribute('paramId'));
+ }
+ }
+ this.setProcedureParameters_(args, paramIds);
+ },
+ /**
+ * 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) {
+ for (var i = 0; i < this.arguments_.length; i++) {
+ if (Blockly.Names.equals(oldName, this.arguments_[i])) {
+ this.arguments_[i] = newName;
+ this.getField('ARGNAME' + i).setValue(newName);
+ }
+ }
+ },
+ /**
+ * Procedure calls cannot exist without the corresponding procedure
+ * definition. Enforce this link whenever an event is fired.
+ * @this Blockly.Block
+ */
+ onchange: function(event) {
+ if (!this.workspace || this.workspace.isFlyout) {
+ // Block is deleted or is in a flyout.
+ return;
+ }
+ if (event.type == Blockly.Events.CREATE &&
+ event.ids.indexOf(this.id) != -1) {
+ // Look for the case where a procedure call was created (usually through
+ // paste) and there is no matching definition. In this case, create
+ // an empty definition block with the correct signature.
+ var name = this.getProcedureCall();
+ var def = Blockly.Procedures.getDefinition(name, this.workspace);
+ if (def && (def.type != this.defType_ ||
+ JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
+ // The signatures don't match.
+ def = null;
+ }
+ if (!def) {
+ Blockly.Events.setGroup(event.group);
+ /**
+ * Create matching definition block.
+ * <xml>
+ * <block type="procedures_defreturn" x="10" y="20">
+ * <mutation name="test">
+ * <arg name="x"></arg>
+ * </mutation>
+ * <field name="NAME">test</field>
+ * </block>
+ * </xml>
+ */
+ var xml = goog.dom.createDom('xml');
+ var block = goog.dom.createDom('block');
+ block.setAttribute('type', this.defType_);
+ var xy = this.getRelativeToSurfaceXY();
+ var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
+ var y = xy.y + Blockly.SNAP_RADIUS * 2;
+ block.setAttribute('x', x);
+ block.setAttribute('y', y);
+ var mutation = this.mutationToDom();
+ block.appendChild(mutation);
+ var field = goog.dom.createDom('field');
+ field.setAttribute('name', 'NAME');
+ field.appendChild(document.createTextNode(this.getProcedureCall()));
+ block.appendChild(field);
+ xml.appendChild(block);
+ Blockly.Xml.domToWorkspace(xml, this.workspace);
+ Blockly.Events.setGroup(false);
+ }
+ } else if (event.type == Blockly.Events.DELETE) {
+ // Look for the case where a procedure definition has been deleted,
+ // leaving this block (a procedure call) orphaned. In this case, delete
+ // the orphan.
+ var name = this.getProcedureCall();
+ var def = Blockly.Procedures.getDefinition(name, this.workspace);
+ if (!def) {
+ Blockly.Events.setGroup(event.group);
+ this.dispose(true, false);
+ Blockly.Events.setGroup(false);
+ }
+ }
+ },
+ /**
+ * Add menu option to find the definition block for this call.
+ * @param {!Array} options List of menu options to add to.
+ * @this Blockly.Block
+ */
+ customContextMenu: function(options) {
+ var option = {enabled: true};
+ option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
+ var name = this.getProcedureCall();
+ var workspace = this.workspace;
+ option.callback = function() {
+ var def = Blockly.Procedures.getDefinition(name, workspace);
+ def && def.select();
+ };
+ options.push(option);
+ },
+ defType_: 'procedures_defnoreturn'
+};
+
+Blockly.Blocks['procedures_callreturn'] = {
+ /**
+ * Block for calling a procedure with a return value.
+ * @this Blockly.Block
+ */
+ init: function() {
+ this.appendDummyInput('TOPROW')
+ .appendField('', 'NAME');
+ this.setOutput(true);
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ // Tooltip is set in domToMutation.
+ this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);
+ this.arguments_ = [];
+ this.quarkConnections_ = {};
+ this.quarkIds_ = null;
+ },
+ getProcedureCall: Blockly.Blocks['procedures_callnoreturn'].getProcedureCall,
+ renameProcedure: Blockly.Blocks['procedures_callnoreturn'].renameProcedure,
+ setProcedureParameters_:
+ Blockly.Blocks['procedures_callnoreturn'].setProcedureParameters_,
+ updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
+ mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
+ domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
+ renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
+ onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
+ customContextMenu:
+ Blockly.Blocks['procedures_callnoreturn'].customContextMenu,
+ defType_: 'procedures_defreturn'
+};
+
+Blockly.Blocks['procedures_ifreturn'] = {
+ /**
+ * Block for conditionally returning a value from a procedure.
+ * @this Blockly.Block
+ */
+ init: function() {
+ this.appendValueInput('CONDITION')
+ .setCheck('Boolean')
+ .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
+ this.appendValueInput('VALUE')
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+ this.setInputsInline(true);
+ this.setPreviousStatement(true);
+ this.setNextStatement(true);
+ this.setColour(Blockly.Blocks.procedures.HUE);
+ this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
+ this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);
+ this.hasReturnValue_ = true;
+ },
+ /**
+ * Create XML to represent whether this block has a return value.
+ * @return {!Element} XML storage element.
+ * @this Blockly.Block
+ */
+ mutationToDom: function() {
+ var container = document.createElement('mutation');
+ container.setAttribute('value', Number(this.hasReturnValue_));
+ return container;
+ },
+ /**
+ * Parse XML to restore whether this block has a return value.
+ * @param {!Element} xmlElement XML storage element.
+ * @this Blockly.Block
+ */
+ domToMutation: function(xmlElement) {
+ var value = xmlElement.getAttribute('value');
+ this.hasReturnValue_ = (value == 1);
+ if (!this.hasReturnValue_) {
+ this.removeInput('VALUE');
+ this.appendDummyInput('VALUE')
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+ }
+ },
+ /**
+ * Called whenever anything on the workspace changes.
+ * Add warning if this flow block is not nested inside a loop.
+ * @param {!Blockly.Events.Abstract} e Change event.
+ * @this Blockly.Block
+ */
+ onchange: function(e) {
+ var legal = false;
+ // Is the block nested in a procedure?
+ var block = this;
+ do {
+ if (this.FUNCTION_TYPES.indexOf(block.type) != -1) {
+ legal = true;
+ break;
+ }
+ block = block.getSurroundParent();
+ } while (block);
+ if (legal) {
+ // If needed, toggle whether this block has a return value.
+ if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) {
+ this.removeInput('VALUE');
+ this.appendDummyInput('VALUE')
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+ this.hasReturnValue_ = false;
+ } else if (block.type == 'procedures_defreturn' &&
+ !this.hasReturnValue_) {
+ this.removeInput('VALUE');
+ this.appendValueInput('VALUE')
+ .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
+ this.hasReturnValue_ = true;
+ }
+ this.setWarningText(null);
+ } else {
+ this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
+ }
+ },
+ /**
+ * List of block types that are functions and thus do not need warnings.
+ * To add a new function type add this to your code:
+ * Blockly.Blocks['procedures_ifreturn'].FUNCTION_TYPES.push('custom_func');
+ */
+ FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn']
+};