/** * @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'); Blockly.Blocks['procedures_defnoreturn'] = { /** * Block for defining a procedure with no return value. * @this Blockly.Block */ init: function() { this.category = 'PROCEDURE'; // for blocksCAD this.myType_ = ['CSG','CAG']; // for blocksCAD this.backlightBlocks = []; // for blocksCAD 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'])); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL); this.setColour(Blockscad.Toolbox.HEX_PROCEDURE); this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP); this.arguments_ = []; this.setStatements_(true, 'VariableSet'); this.statementConnection_ = null; }, /** * if this procedure has statements, use them to determine the * type of this procedure, then update types of any callers.. * @this Blockly.Block */ // setType: function(type,drawMe) { // for blocksCAD // if (!this.workspace) { // // Block has been deleted. // return; // } // console.log("starting proc ST with oldtype:" + this.myType_ + " and newtype:" + type); // if (this.myType_ == type) // return; // console.log("in modules's setType"); // var oldtype = this.myType_; // var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace); // var numBumped = []; // var notBumped = []; // // I need to find out what my caller stacks think their types are. // if (callers.length) { // for (var i = 0; i < callers.length; i++) { // var areaType = Blockscad.findBlockType(callers[i],callers); // //console.log("caller area type is",areaType); // //console.log("caller category is", callers[i].category); // // console.log("parent type is changing to",type); // if (!goog.isArray(type) && areaType != 'EITHER' && areaType != type) { // // call blocks are going to be kicked out. // // console.log("warning message! call block id", callers[i].id, "will be kicked out and backlit"); // numBumped.push(callers[i]); // // If the call block is in a collapsed stack, find the collapsed parent and expand them. // var topBlock = callers[i].collapsedParents(); // if (topBlock) // for (var j=0; j < topBlock.length; j++) // topBlock[j].setCollapsed(false); // } // else notBumped.push(callers[i]); // } // } // if (numBumped.length) { // var text = ''; // // text += numBumped.length + " "; // // took out the name so I wouldn't have to deal with renaming the proc. // //text += this.getFieldValue('NAME') + " "; // text += Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1", numBumped.length); // this.setWarningText(text); // } // this.myType_ = type; // // some of my callers don't need to be bumped. I'll set their category to "BLAH" // // temporarily (note this is NOT a valid category), // // reset the types of the blocks around them, then set them to their new type. // // this should prevent them getting bumped out incorrectly. // // if (notBumped.length) { // // for (var j = 0; j < notBumped.length; j++) { // // notBumped[j].category = 'BLAH'; // // notBumped[j].previousConnection.setCheck(['CSG','CAG']); // // } // // for (j = 0; j < notBumped.length; j++) { // // console.log("in proc set_type, calling assignBT with BLAH"); // // this.myType_ = type; // // Blockscad.assignBlockTypes([notBumped[j]]); // // } // // } // // this section actually re-sets the type that triggers the bumping. This needs to be done before backlighting. // // to make undo work, I need to set the group of the next events. // if (numBumped.length) { // // console.log("firing events now - something should have been bumped"); // // lets get the group event // var eventGroup = true; // if (Blockscad.workspace.undoStack_.length) // eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group; // // console.log("event group is: ", eventGroup); // Blockly.Events.setGroup(eventGroup); // } // if (callers.length > 0) { // for (var i = 0; i < callers.length; i++) { // callers[i].previousConnection.setCheck(type); // // if I'm bumping something, I need to put the typing event into the undoStack_ // // so that on undo the caller doesn't get immediately bumped out again! // // So first I set the check (which will prompt the bump) THEN fire the type event // // so that when it is run backwards I untype first then move back into place. // if (Blockly.Events.isEnabled() && numBumped.length) { // Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type)); // } // if (type == 'CSG') // callers[i].category = 'PRIMITIVE_CSG' // else if (type == 'CAG') // callers[i].category = 'PRIMITIVE_CAG'; // else callers[i].category = 'UNKNOWN'; // // if the top block isn't the procedure definition (recursion!), then assign their types // var topBlock = callers[i].getRootBlock(); // if (!(topBlock.category && topBlock.category == 'PROCEDURE')) { // console.log("calling assignBlockTypes from proc ST (this is prob the bad one"); // this.myType_ = type; // Blockscad.assignBlockTypes([callers[i]]); // } // } // } // // // the system will be done now with unplugging all the blocks that need it. // // set the backlighting and warning message here (with a delay) so that the events that occur during the // // bumping don't overwrite the backlighting of the caller blocks. // for (var k = 0; k < numBumped.length; k++) { // numBumped[k].backlight(); // this.backlightBlocks.push(numBumped[k].id); // } // if (numBumped.length) { // // Blockly.Events.Filter // Blockly.Events.setGroup(false); // } // // events aren't all getting the group setting. Walk back through the undoStack_ and make sure the group is set. // // for (var i = Blockscad.workspace.undoStack_.length - 1; i > 0; i--) { // // if // // } // this.myType_ = type; // }, // end for blocksCAD // // for BlocksCAD - check to see if my callers are still backlight? // onchange: function() { // var found_it; // // go through my backlight id list, see if I have any blocks on it that are not on // // the general backlight list (they must have been unhighlighted!) // for (var i=0; i < this.backlightBlocks.length; i++) { // found_it = 0; // for (var j=0; j} 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); } } // for BlocksCAD, var option = {enabled: true}; var name = this.getFieldValue('NAME'); option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name); var workspace = this.workspace; option.callback = function() { var def = Blockly.Procedures.getDefinition(name, workspace); if (def) { var callers = Blockly.Procedures.getCallers(name, workspace); workspace.clearBacklight(); Blockly.selected.unselect(); for (var i = 0; callers && i < callers.length; i++) { callers[i] && callers[i].backlight(); // if caller block is in a collapsed parent, highlight collapsed parent too var others = callers[i].collapsedParents(); if (others) for (var j=0; j < others.length; j++) others[j].backlight(); } } }; options.push(option); }, callType_: 'procedures_callnoreturn' }; Blockly.Blocks['procedures_defreturn'] = { /** * Block for a blockSCAD function: no statements, a return value. * * @this Blockly.Block */ init: function() { this.category = 'PROCEDURE'; // for blockscad this.myType_ = null; // for blocksCAD this.backlightBlocks = []; // for blocksCAD 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'])); this.setColour(Blockscad.Toolbox.HEX_PROCEDURE); this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP); this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL); this.arguments_ = []; this.setStatements_(false); // set false for blockscad - jayod this.statementConnection_ = null; }, /** * if this procedure has statements, use them to determine the * type of this procedure, then update types of any callers.. * @this Blockly.Block */ // setType: function(type,drawMe) { // for blocksCAD // if (!this.workspace) { // // Block has been deleted. // return; // } // var ret = this.getInput('RETURN'); // console.log("in setType for function. here is the input:",ret); // if (ret.connection.targetConnection) { // if (ret.connection.targetConnection.check_ == 'Number') // this.myType_ = ret.connection.check_ = 'Number'; // else if (ret.connection.targetConnection.check_ == 'Boolean') // this.myType_ = ret.connection.check_ = 'Boolean'; // } // else this.myType_ = ret.connection.check_ = null; // var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace); // var numBumped = []; // var conType = null; // // I need to find out what my caller stacks think their types are. // if (callers.length) { // for (var i = 0; i < callers.length; i++) { // // console.log("callers.length is:",callers.length); // // get caller's connection type here // if (callers[i].outputConnection.targetConnection) // conType = callers[i].outputConnection.targetConnection.check_; // if (!goog.isArray(conType)) conType = [conType]; // // conType is an array. // // console.log("caller type is",conType); // // console.log(this.myType_); // if (this.myType_ && conType && conType.indexOf(this.myType_) == -1) { // // call blocks are going to be kicked out. // console.log("warning message! call block id", callers[i].id, "will be kicked out"); // // there is a bug here - if we add to the numBumped stack, then we get an infinite loop. ??? // // if (numBumped[numBumped.length] != callers[i]) // // numBumped.push(callers[i]); // // If the call block is in a collapsed stack, find the collapsed parent and expand them. // var topBlock = callers[i].collapsedParents(); // if (topBlock) // for (var j=0; j < topBlock.length; j++) // topBlock[j].setCollapsed(false); // } // } // } // if (numBumped.length) { // // console.log("blah"); // var text = ''; // // text += numBumped.length + " "; // // text += this.getFieldValue('NAME') + " "; // text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length + " " + this.getFieldValue('NAME')); // this.setWarningText(text); // } // if (callers.length > 0) { // for (var i = 0; i < callers.length; i++) { // callers[i].outputConnection.setCheck(this.myType_); // if (this.myType_ == 'Number') // callers[i].category = 'NUMBER' // else if (this.myType_ == 'Boolean') // callers[i].category = 'BOOLEAN'; // else callers[i].category = 'UNKNOWN'; // // console.log("tried to set caller type to ",this.myType_, callers[i]); // } // } // // the system will be done now with unplugging all the blocks that need it. // // Time to fire a workspaceChanged() so our list of parentIDs will be current. // if (numBumped.length) // Blockscad.workspaceChanged(); // }, // end for blocksCAD // second stab at setType. setType: function(type, drawMe) { if (!this.workspace) { // Block has been deleted. return; } // // compare to see if type matches this.myType_ var oldtype = this.myType_; var ret = this.getInput('RETURN'); // console.log("in setType for function. here is the input:" + ret.connection); if (ret.connection.targetConnection) { if (ret.connection.targetConnection.check_ == 'Number') type = 'Number'; else if (ret.connection.targetConnection.check_ == 'Boolean') type = 'Boolean'; else if (ret.connection.targetConnection.check_ == 'String') type = 'String'; else type = null; } else type = null; // console.log("starting func ST with oldtype:" + this.myType_ + " and newtype:" + type); if (this.myType_ == type) { // console.log("in func ST. returning because types didn't change."); return; } // set the function def's type to what is now connected to its output this.myType_ = type; var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace); var numBumped = []; var conType = null; // type of a caller's output connection var parentAccepts; // start grouping events in case some blocks are bumped out, so that undo will work easily. var eventGroup = true; if (Blockscad.workspace.undoStack_.length) eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group; // console.log("event group is: ", eventGroup); Blockly.Events.setGroup(eventGroup); // now, set my caller block's types if (callers.length) { for (var i = 0; i < callers.length; i++) { if (!callers[i]) continue; // the caller block only gets bumped if it has a parent. var parent = callers[i].getParent(); // get caller's connection type here if (parent) { // console.log("found instance with parent: ", parent.type); parentAccepts = callers[i].outputConnection.targetConnection.check_; // console.log(parentAccepts); // make sure that parentAccepts is an array so that we can check for a match if (parentAccepts && !(goog.isArray(parentAccepts))) parentAccepts = [parentAccepts]; if (parentAccepts) { var found_match = 0; // check to see if my type matches any type accepted by the parent - if so, it will bump. for (var j = 0; j < parentAccepts.length; j++) { if (parentAccepts[j] == this.myType_) { found_match = 1; } } if (!found_match) { // I have a type mismatch with this variable. it is going to be bumped. // console.log("warning message! call block id", callers[i].id, "will be kicked out and backlit"); // console.log("parent accepts: " + parentAccepts + ", type is:", this.myType_); numBumped.push(callers[i]); // instances[i].backlight(); // this.backlightBlocks.push(instances[i].id); // if the instance is in a collapsed stack, find collapsed parent and expand var topBlock = callers[i].collapsedParents(); if (topBlock) for (var j = 0; j < topBlock.length; j++) topBlock[j].setCollapsed(false); } } } // end if (parent) // change caller's type - this is the command that actually prompts Blockly to bump blocks out // console.log("Set the caller's output check to ", this.myType_); if (callers[i]) { callers[i].outputConnection.setCheck(this.myType_); if (this.myType_ == 'Number') callers[i].category = 'NUMBER' else if (this.myType_ == 'Boolean') callers[i].category = 'BOOLEAN'; else if (this.myType_ == 'String') callers[i].category = 'STRING'; else { callers[i].category = 'UNKNOWN'; // console.log("function caller with type UNKNOWN"); } } // console.log("tried to set caller type to ",this.myType_, callers[i]); // if it was a bumping change, fire a typing event // if (Blockly.Events.isEnabled() && numBumped.length) { // Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type)); // } // if caller is inside of another setter block, that setter's type needs to be changed. Do so. // note that this can lead to an infinite loop if procedures are circularly defined - that is why // setType MUST exit immediately if it is called with the type not changing. // what if a parent is a variables_set of a different variable? // then I want to call Blockscad.assignVarTypes for that parent. var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defreturn"); if (!setterParent) setterParent = Blockscad.hasParentOfType(callers[i],"variables_set"); if (setterParent) { setTimeout(function() { // console.log("this caller function is inside a setter. Set its type to: ",type); setterParent.setType(type); }, 0); } else { // if the caller was inside a non setter, I still want to type that parent. var parent = false; if (callers[i]) parent = callers[i].getParent(); if (parent) { Blockscad.assignBlockTypes(parent) } } } } // end of going through all callers to set their types. // turn off event grouping Blockly.Events.setGroup(false); // handle backlighting and warning text - do this later so that // the bumping process itself (which now selects and deselects the blocks) doesn't // just immediately turn the backlighting off. for (var k = 0; k < numBumped.length; k++) { numBumped[k].backlight(); this.backlightBlocks.push(numBumped[k].id); // finally, set a warning message on the procedure definition that counts how many callers were bumped. var text = ''; text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length).replace("%2", parentAccepts).replace("%3",type); this.setWarningText(text); } }, 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.setColour(Blockscad.Toolbox.HEX_PROCEDURE); 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.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP); this.contextMenu = false; } }; Blockly.Blocks['procedures_mutatorarg'] = { /** * Mutator block for procedure argument. * @this Blockly.Block */ init: function() { this.setColour(Blockscad.Toolbox.HEX_PROCEDURE); this.appendDummyInput() .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE) .appendField(new Blockly.FieldTextInput('x', this.validator_), 'NAME'); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP); this.contextMenu = false; }, /** * 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; } }; Blockly.Blocks['procedures_callnoreturn'] = { /** * Block for calling a procedure with no return value. * @this Blockly.Block */ init: function() { this.category = 'UNKNOWN'; // for blocksCAD this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL); this.setColour(Blockscad.Toolbox.HEX_PROCEDURE); this.appendDummyInput('TOPROW') .appendField(this.id, 'NAME'); this.setPreviousStatement(true); //this.setNextStatement(true); // for Blockscad, we don't want this to have a next. Breaks difference. // Tooltip is set in domToMutation. this.arguments_ = []; this.quarkConnections_ = {}; this.quarkIds_ = null; this.setType(); }, /** * on being added, this will be called to set the parent procedure type for BlocksCAD */ setType: function() { // for blockscad - jayod var parent = Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace); // console.log("in caller's setType func. defined by: ",parent); if (parent) { var myType = parent.myType_; // console.log("found a parent procedure with type: ",parent.myType_); if (myType) { this.previousConnection.setCheck(myType); //this.nextConnection.setCheck(myType); // no more next connection if (myType == 'CSG' || myType == ['CSG']) this.category = 'PRIMITIVE_CSG' else if (myType == 'CAG' || myType == ['CAG']) this.category = 'PRIMITIVE_CAG'; else this.category = 'UNKNOWN'; } } }, /** * 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.} paramNames New param names, e.g. ['x', 'y', 'z']. * @param {!Array.} 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. * * * * * * test * * */ 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); workspace.clearBacklight(); // def && def.backlight(); def && def.select(); }; options.push(option); // for BlocksCAD, var option = {enabled: true}; var name = this.getProcedureCall(); option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name); var workspace = this.workspace; option.callback = function() { var def = Blockly.Procedures.getDefinition(name, workspace); if (def) { var callers = Blockly.Procedures.getCallers(name, workspace); workspace.clearBacklight(); Blockly.selected.unselect(); for (var i = 0; callers && i < callers.length; i++) { callers[i] && callers[i].backlight(); // if caller block is in a collapsed parent, highlight collapsed parent too var others = callers[i].collapsedParents(); if (others) for (var j=0; j < others.length; j++) others[j].backlight(); } } }; 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.category = 'UNKNOWN'; // for blockscad - jayod this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL); this.setColour(Blockscad.Toolbox.HEX_PROCEDURE); this.appendDummyInput('TOPROW') .appendField('', 'NAME'); this.setOutput(true); // Tooltip is set in domToMutation. this.arguments_ = []; this.quarkConnections_ = {}; this.quarkIds_ = null; // set the initial type. this.setType(); }, /** * on being added, this will be called to get the parent procedure type for BlocksCAD */ setType: function() { // for blockscad - jayod var parent = Blockly.Procedures.getDefinition(this.getFieldValue('NAME'),this.workspace); if (parent) { var myType = parent.myType_; if (myType) { // console.log("setting type for new function caller to:",myType); this.outputConnection.setCheck(myType); if (myType == 'Number') this.category = 'NUMBER' else if (myType == 'Boolean') this.category = 'BOOLEAN'; else this.category = 'UNKNOWN'; } } }, 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, /** * 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); workspace.clearBacklight(); def && def.backlight(); }; options.push(option); // for BlocksCAD, var name = this.getProcedureCall(); var option = {enabled: true}; option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",name); var workspace = this.workspace; option.callback = function() { var def = Blockly.Procedures.getDefinition(name, workspace); if (def) { var callers = Blockly.Procedures.getCallers(name, workspace); workspace.clearBacklight(); Blockly.selected.unselect(); for (var i = 0; callers && i < callers.length; i++) { callers[i] && callers[i].backlight(); // if caller block is in a collapsed parent, highlight collapsed parent too var others = callers[i].collapsedParents(); if (others) for (var j=0; j < others.length; j++) others[j].backlight(); } } }; options.push(option); }, defType_: 'procedures_defreturn' }; // Blockly.Blocks['procedures_ifreturn'] = { // commented out for Blockscad // /** // * Block for conditionally returning a value from a procedure. // * @this Blockly.Block // */ // init: function() { // this.setHelpUrl('http://c2.com/cgi/wiki?GuardClause'); // this.setColour(290); // 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.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP); // 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. // * @this Blockly.Block // */ // onchange: function() { // if (!this.workspace) { // // Block has been deleted. // return; // } // var legal = false; // // Is the block nested in a procedure? // var block = this; // do { // if (block.type == 'procedures_defnoreturn' || // block.type == 'procedures_defreturn') { // 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); // } // } // };