Import procedures from Blockly (#595)

* Import procedures from Blockly

* Give mutators the dragSurface

* Draw a box behind non-shadow text fields

* ifreturn -> report

* Non-reporting proc -> next; reporting -> statement. Remove mutator for statement.

* Remove align_right in call

* Lints

* procedures_defnoreturn outputShape

* Revert rect CSS for event blocks
This commit is contained in:
Tim Mickel 2016-09-16 18:29:15 -04:00 committed by GitHub
parent 687cee2221
commit b2c84cb802
6 changed files with 845 additions and 6 deletions

817
blocks_vertical/more.js Normal file
View file

@ -0,0 +1,817 @@
/**
* @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');
goog.require('Blockly.constants');
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']));
this.setColour(Blockly.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
this.arguments_ = [];
this.setNextStatement(true);
},
/**
* 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);
}
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);
},
/**
* 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();
// 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.
// Restore the stack, if one was saved.
Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
this.statementConnection_ = null;
},
/**
* 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.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
this.setColour(Blockly.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
this.arguments_ = [];
this.appendStatementInput('STACK')
.appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
this.appendValueInput('RETURN')
.setAlign(Blockly.ALIGN_RIGHT)
.appendField('report');
this.statementConnection_ = null;
},
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.setColour(Blockly.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
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.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
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.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
// 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)
.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.
* @param {!Blockly.Events.Abstract} event Change event.
* @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.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
this.setColour(Blockly.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
// 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_report'] = {
/**
* Block for conditionally returning a value from a procedure.
* @this Blockly.Block
*/
init: function() {
this.appendValueInput('VALUE')
.appendField('report');
this.setInputsInline(true);
this.setPreviousStatement(true);
this.setNextStatement(false);
this.setColour(Blockly.Colours.more.primary,
Blockly.Colours.more.secondary,
Blockly.Colours.more.tertiary);
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']
};

View file

@ -160,7 +160,7 @@ Blockly.Css.CONTENT = [
'background-color: $colour_workspace;',
'outline: none;',
'overflow: hidden;', /* IE overflows by default. */
'display: block;',
'display: block;',
'}',
/* Necessary to position the drag surface */
@ -403,7 +403,6 @@ Blockly.Css.CONTENT = [
'.blocklyNonEditableText>text {',
'pointer-events: none;',
'}',
'.blocklyNonEditableText>rect,',
'.blocklyEditableText>rect {',
'fill-opacity: 0;',

View file

@ -78,6 +78,28 @@ Blockly.FieldTextInput.prototype.CURSOR = 'text';
*/
Blockly.FieldTextInput.prototype.spellcheck_ = true;
/**
* Install this text field on a block.
*/
Blockly.FieldTextInput.prototype.init = function() {
Blockly.FieldTextInput.superClass_.init.call(this);
// If not in a shadow block, draw a box.
if (!this.sourceBlock_.isShadow()) {
this.box_ = Blockly.createSvgElement('rect', {
'rx': Blockly.BlockSvg.CORNER_RADIUS,
'ry': Blockly.BlockSvg.CORNER_RADIUS,
'x': 0,
'y': 0,
'width': this.size_.width,
'height': this.size_.height,
'fill': Blockly.Colours.textField,
'stroke': this.sourceBlock_.getColourTertiary()
});
this.fieldGroup_.insertBefore(this.box_, this.textElement_);
}
};
/**
* Close the input widget if this input is being deleted.
*/

View file

@ -134,7 +134,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
getMetrics: this.getFlyoutMetrics_.bind(this),
setMetrics: null
};
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions, this.block_.workspace.dragSurface);
this.workspace_.isMutator = true;
this.svgDialog_.appendChild(
this.workspace_.createDom('blocklyMutatorBackground'));

View file

@ -174,10 +174,10 @@ Blockly.Procedures.flyoutCategory = function(workspace) {
block.setAttribute('gap', 16);
xmlList.push(block);
}
if (Blockly.Blocks['procedures_ifreturn']) {
if (Blockly.Blocks['procedures_report']) {
// <block type="procedures_ifreturn" gap="16"></block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'procedures_ifreturn');
block.setAttribute('type', 'procedures_report');
block.setAttribute('gap', 16);
xmlList.push(block);
}

View file

@ -15,6 +15,7 @@
<script src="../blocks_vertical/event.js"></script>
<script src="../blocks_vertical/motion.js"></script>
<script src="../blocks_vertical/looks.js"></script>
<script src="../blocks_vertical/more.js"></script>
<script src="../blocks_vertical/operators.js"></script>
<script src="../blocks_vertical/pen.js"></script>
<script src="../blocks_vertical/sound.js"></script>
@ -1096,7 +1097,7 @@
</value>
</block>
</category>
<category name="More Blocks" colour="#FF6680"></category>
<category name="More Blocks" colour="#FF6680" custom="PROCEDURE"></category>
</xml>