/** * @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 Logic blocks for Blockly. * @author q.neutron@gmail.com (Quynh Neutron) */ 'use strict'; goog.provide('Blockly.Blocks.logic'); goog.require('Blockly.Blocks'); Blockly.Blocks['controls_if'] = { /** * Block for if/elseif/else condition. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.appendValueInput('IF0') .setCheck(['Boolean','Number']) .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF); this.appendStatementInput('DO0') .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN) .setCheck(['CSG','CAG']); this.setPreviousStatement(true); //this.setNextStatement(true); this.setMutator(new Blockly.Mutator(['controls_if_elseif', 'controls_if_else'])); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function() { if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) { return Blockly.Msg.CONTROLS_IF_TOOLTIP_1; } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) { return Blockly.Msg.CONTROLS_IF_TOOLTIP_2; } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) { return Blockly.Msg.CONTROLS_IF_TOOLTIP_3; } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) { return Blockly.Msg.CONTROLS_IF_TOOLTIP_4; } return ''; }); this.elseifCount_ = 0; this.elseCount_ = 0; }, /** * Create XML to represent the number of else-if and else inputs. * @return {Element} XML storage element. * @this Blockly.Block */ mutationToDom: function() { if (!this.elseifCount_ && !this.elseCount_) { return null; } var container = document.createElement('mutation'); if (this.elseifCount_) { container.setAttribute('elseif', this.elseifCount_); } if (this.elseCount_) { container.setAttribute('else', 1); } return container; }, /** * Parse XML to restore the else-if and else inputs. * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ domToMutation: function(xmlElement) { this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0; this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0; for (var i = 1; i <= this.elseifCount_; i++) { this.appendValueInput('IF' + i) .setCheck('Boolean') .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF); this.appendStatementInput('DO' + i) .setCheck(['CSG','CAG']) .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN); } if (this.elseCount_) { this.appendStatementInput('ELSE') .setCheck(['CSG','CAG']) .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE); } }, /** * 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 = Blockly.Block.obtain(workspace, 'controls_if_if'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var i = 1; i <= this.elseifCount_; i++) { var elseifBlock = Blockly.Block.obtain(workspace, 'controls_if_elseif'); elseifBlock.initSvg(); connection.connect(elseifBlock.previousConnection); connection = elseifBlock.nextConnection; } if (this.elseCount_) { var elseBlock = Blockly.Block.obtain(workspace, 'controls_if_else'); elseBlock.initSvg(); connection.connect(elseBlock.previousConnection); } 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) { // Disconnect the else input blocks and remove the inputs. if (this.elseCount_) { this.removeInput('ELSE'); } this.elseCount_ = 0; // Disconnect all the elseif input blocks and remove the inputs. for (var i = this.elseifCount_; i > 0; i--) { this.removeInput('IF' + i); this.removeInput('DO' + i); } this.elseifCount_ = 0; // Rebuild the block's optional inputs. var clauseBlock = containerBlock.getInputTargetBlock('STACK'); while (clauseBlock) { switch (clauseBlock.type) { case 'controls_if_elseif': this.elseifCount_++; var ifInput = this.appendValueInput('IF' + this.elseifCount_) .setCheck('Boolean') .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF); var doInput = this.appendStatementInput('DO' + this.elseifCount_) .setCheck(['CSG','CAG']); doInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN); // Reconnect any child blocks. if (clauseBlock.valueConnection_) { ifInput.connection.connect(clauseBlock.valueConnection_); } if (clauseBlock.statementConnection_) { doInput.connection.connect(clauseBlock.statementConnection_); } break; case 'controls_if_else': this.elseCount_++; var elseInput = this.appendStatementInput('ELSE') .setCheck(['CSG','CAG']); elseInput.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE); // Reconnect any child blocks. if (clauseBlock.statementConnection_) { elseInput.connection.connect(clauseBlock.statementConnection_); } break; default: throw 'Unknown block type.'; } clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock(); } }, /** * Store pointers to any connected child blocks. * @param {!Blockly.Block} containerBlock Root block in mutator. * @this Blockly.Block */ saveConnections: function(containerBlock) { var clauseBlock = containerBlock.getInputTargetBlock('STACK'); var i = 1; while (clauseBlock) { switch (clauseBlock.type) { case 'controls_if_elseif': var inputIf = this.getInput('IF' + i); var inputDo = this.getInput('DO' + i); clauseBlock.valueConnection_ = inputIf && inputIf.connection.targetConnection; clauseBlock.statementConnection_ = inputDo && inputDo.connection.targetConnection; i++; break; case 'controls_if_else': var inputDo = this.getInput('ELSE'); clauseBlock.statementConnection_ = inputDo && inputDo.connection.targetConnection; break; default: throw 'Unknown block type.'; } clauseBlock = clauseBlock.nextConnection && clauseBlock.nextConnection.targetBlock(); } } }; Blockly.Blocks['controls_if_if'] = { /** * Mutator block for if container. * @this Blockly.Block */ init: function() { this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.appendDummyInput() .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF); this.appendStatementInput('STACK'); this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP); this.contextMenu = false; } }; Blockly.Blocks['controls_if_elseif'] = { /** * Mutator bolck for else-if condition. * @this Blockly.Block */ init: function() { this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.appendDummyInput() .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF); this.setPreviousStatement(true); this.setNextStatement(true); this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP); this.contextMenu = false; } }; Blockly.Blocks['controls_if_else'] = { /** * Mutator block for else condition. * @this Blockly.Block */ init: function() { this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.appendDummyInput() .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE); this.setPreviousStatement(true); this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP); this.contextMenu = false; } }; Blockly.Blocks['logic_compare'] = { /** * Block for comparison operator. * @this Blockly.Block */ init: function() { var OPERATORS = this.RTL ? [ ['=', 'EQ'], ['\u2260', 'NEQ'], ['>', 'LT'], ['\u2265', 'LTE'], ['<', 'GT'], ['\u2264', 'GTE'] ] : [ ['=', 'EQ'], ['\u2260', 'NEQ'], ['<', 'LT'], ['\u2264', 'LTE'], ['>', 'GT'], ['\u2265', 'GTE'] ]; this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.setOutput(true, 'Boolean'); this.appendValueInput('A'); this.appendValueInput('B') .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); this.setInputsInline(true); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function() { var op = thisBlock.getFieldValue('OP'); var TOOLTIPS = { 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ, 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ, 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT, 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE, 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT, 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE }; return TOOLTIPS[op]; }); this.prevBlocks_ = [null, null]; }, /** * Called whenever anything on the workspace changes. * Prevent mismatched types from being compared. * @this Blockly.Block */ onchange: function() { var blockA = this.getInputTargetBlock('A'); var blockB = this.getInputTargetBlock('B'); // Disconnect blocks that existed prior to this change if they don't match. if (blockA && blockB && !blockA.outputConnection.checkType_(blockB.outputConnection)) { // Mismatch between two inputs. Disconnect previous and bump it away. for (var i = 0; i < this.prevBlocks_.length; i++) { var block = this.prevBlocks_[i]; if (block === blockA || block === blockB) { block.setParent(null); block.bumpNeighbours_(); } } } this.prevBlocks_[0] = blockA; this.prevBlocks_[1] = blockB; } }; Blockly.Blocks['logic_operation'] = { /** * Block for logical operations: 'and', 'or'. * @this Blockly.Block */ init: function() { var OPERATORS = [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'], [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']]; this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.setOutput(true, 'Boolean'); this.appendValueInput('A') .setCheck('Boolean'); this.appendValueInput('B') .setCheck('Boolean') .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); this.setInputsInline(true); // Assign 'this' to a variable for use in the tooltip closure below. var thisBlock = this; this.setTooltip(function() { var op = thisBlock.getFieldValue('OP'); var TOOLTIPS = { 'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND, 'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR }; return TOOLTIPS[op]; }); } }; Blockly.Blocks['logic_negate'] = { /** * Block for negation. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.LOGIC_NEGATE_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.setOutput(true, 'Boolean'); this.interpolateMsg(Blockly.Msg.LOGIC_NEGATE_TITLE, ['BOOL', 'Boolean', Blockly.ALIGN_RIGHT], Blockly.ALIGN_RIGHT); this.setTooltip(Blockly.Msg.LOGIC_NEGATE_TOOLTIP); } }; Blockly.Blocks['logic_boolean'] = { /** * Block for boolean data type: true and false. * @this Blockly.Block */ init: function() { var BOOLEANS = [[Blockly.Msg.LOGIC_BOOLEAN_TRUE, 'TRUE'], [Blockly.Msg.LOGIC_BOOLEAN_FALSE, 'FALSE']]; this.setHelpUrl(Blockly.Msg.LOGIC_BOOLEAN_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.setOutput(true, 'Boolean'); this.appendDummyInput() .appendField(new Blockly.FieldDropdown(BOOLEANS), 'BOOL'); this.setTooltip(Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP); } }; Blockly.Blocks['logic_null'] = { /** * Block for null data type. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.LOGIC_NULL_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.setOutput(true,'Boolean'); this.appendDummyInput() .appendField(Blockly.Msg.LOGIC_NULL); this.setTooltip(Blockly.Msg.LOGIC_NULL_TOOLTIP); } }; Blockly.Blocks['logic_ternary'] = { /** * Block for ternary operator. * @this Blockly.Block */ init: function() { this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL); this.setColour(Blockscad.Toolbox.HEX_LOGIC); this.appendValueInput('IF') .setCheck('Boolean') .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION); this.appendValueInput('THEN') // .setCheck('Number') .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE); this.appendValueInput('ELSE') // .setCheck('Number') .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE); this.setOutput(true,'Number'); this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP); } };