Ensure that if a procedure call exists, so does its definition.

Specifically, if a call is copied, then the def is deleted, then the
call is pasted, the def should be created.
This commit is contained in:
Neil Fraser 2016-07-08 15:50:09 -07:00
parent 2b191e897e
commit 2dab19c015
5 changed files with 77 additions and 43 deletions

View file

@ -59,16 +59,6 @@ Blockly.Blocks['procedures_defnoreturn'] = {
this.setStatements_(true);
this.statementConnection_ = null;
},
/**
* Initialization of the block has completed, clean up anything that may be
* inconsistent as a result of the XML loading.
* @this Blockly.Block
*/
validate: function() {
var name = Blockly.Procedures.findLegalName(
this.getFieldValue('NAME'), this);
this.setFieldValue(name, 'NAME');
},
/**
* Add or remove the statement block from this function definition.
* @param {boolean} hasStatements True if a statement block is needed.
@ -244,16 +234,6 @@ Blockly.Blocks['procedures_defnoreturn'] = {
}
}
},
/**
* Dispose of any callers.
* @this Blockly.Block
*/
dispose: function() {
var name = this.getFieldValue('NAME');
Blockly.Procedures.disposeCallers(name, this.workspace);
// Call parent's destructor.
this.constructor.prototype.dispose.apply(this, arguments);
},
/**
* Return the signature of this procedure definition.
* @return {!Array} Tuple containing three elements:
@ -371,13 +351,11 @@ Blockly.Blocks['procedures_defreturn'] = {
this.statementConnection_ = null;
},
setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
validate: Blockly.Blocks['procedures_defnoreturn'].validate,
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,
dispose: Blockly.Blocks['procedures_defnoreturn'].dispose,
/**
* Return the signature of this procedure definition.
* @return {!Array} Tuple containing three elements:
@ -668,6 +646,72 @@ Blockly.Blocks['procedures_callnoreturn'] = {
}
}
},
/**
* 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.
@ -683,7 +727,8 @@ Blockly.Blocks['procedures_callnoreturn'] = {
def && def.select();
};
options.push(option);
}
},
defType_: 'procedures_defnoreturn'
};
Blockly.Blocks['procedures_callreturn'] = {
@ -710,7 +755,10 @@ Blockly.Blocks['procedures_callreturn'] = {
mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
customContextMenu: Blockly.Blocks['procedures_callnoreturn'].customContextMenu
onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
customContextMenu:
Blockly.Blocks['procedures_callnoreturn'].customContextMenu,
defType_: 'procedures_defreturn'
};
Blockly.Blocks['procedures_ifreturn'] = {

View file

@ -277,7 +277,9 @@ Blockly.onKeyDown_ = function(e) {
if (e.keyCode == 86) {
// 'v' for paste.
if (Blockly.clipboardXml_) {
Blockly.Events.setGroup(true);
Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
Blockly.Events.setGroup(false);
}
} else if (e.keyCode == 90) {
// 'z' for undo 'Z' is for redo.

View file

@ -234,19 +234,6 @@ Blockly.Procedures.getCallers = function(name, workspace) {
return callers;
};
/**
* When a procedure definition is disposed of, find and dispose of all its
* callers.
* @param {string} name Name of deleted procedure definition.
* @param {!Blockly.Workspace} workspace The workspace to delete callers from.
*/
Blockly.Procedures.disposeCallers = function(name, workspace) {
var callers = Blockly.Procedures.getCallers(name, workspace);
for (var i = 0; i < callers.length; i++) {
callers[i].dispose(true, false);
}
};
/**
* When a procedure definition changes its parameters, find and edit all its
* callers.
@ -282,7 +269,8 @@ Blockly.Procedures.mutateCallers = function(defBlock) {
* @return {Blockly.Block} The procedure definition block, or null not found.
*/
Blockly.Procedures.getDefinition = function(name, workspace) {
var blocks = workspace.getAllBlocks();
// Assume that a procedure definition is a top block.
var blocks = workspace.getTopBlocks(false);
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var tuple = blocks[i].getProcedureDef();

View file

@ -740,7 +740,7 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
* containing the blocks on the workspace.
*/
Blockly.WorkspaceSvg.prototype.getBlocksBoundingBox = function() {
var topBlocks = this.getTopBlocks();
var topBlocks = this.getTopBlocks(false);
// There are no blocks, return empty rectangle.
if (!topBlocks.length) {
return {x: 0, y: 0, width: 0, height: 0};

View file

@ -532,10 +532,6 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
}
block.setShadow(true);
}
// Give the block a chance to clean up any initial inputs.
if (block.validate) {
block.validate();
}
return block;
};