logic.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2012 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Logic blocks for Blockly.
  22. * @author q.neutron@gmail.com (Quynh Neutron)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Blocks.logic');
  26. goog.require('Blockly.Blocks');
  27. /**
  28. * Common HSV hue for all blocks in this category.
  29. */
  30. Blockly.HSV_SATURATION = 0.7;
  31. Blockly.HSV_VALUE = 0.9;
  32. Blockly.Blocks.logic.HUE = 214;
  33. Blockly.Blocks['controls_if'] = {
  34. /**
  35. * Block for if/elseif/else condition.
  36. * @this Blockly.Block
  37. */
  38. init: function() {
  39. this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);
  40. this.setColour(Blockly.Blocks.logic.HUE);
  41. this.appendValueInput('IF0')
  42. .setCheck('Boolean')
  43. .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
  44. this.appendStatementInput('DO0')
  45. .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
  46. this.setPreviousStatement(true);
  47. this.setNextStatement(true);
  48. this.setMutator(new Blockly.Mutator(['controls_if_elseif',
  49. 'controls_if_else']));
  50. // Assign 'this' to a variable for use in the tooltip closure below.
  51. var thisBlock = this;
  52. this.setTooltip(function() {
  53. if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) {
  54. return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;
  55. } else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) {
  56. return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;
  57. } else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) {
  58. return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;
  59. } else if (thisBlock.elseifCount_ && thisBlock.elseCount_) {
  60. return Blockly.Msg.CONTROLS_IF_TOOLTIP_4;
  61. }
  62. return '';
  63. });
  64. this.elseifCount_ = 0;
  65. this.elseCount_ = 0;
  66. },
  67. /**
  68. * Create XML to represent the number of else-if and else inputs.
  69. * @return {Element} XML storage element.
  70. * @this Blockly.Block
  71. */
  72. mutationToDom: function() {
  73. if (!this.elseifCount_ && !this.elseCount_) {
  74. return null;
  75. }
  76. var container = document.createElement('mutation');
  77. if (this.elseifCount_) {
  78. container.setAttribute('elseif', this.elseifCount_);
  79. }
  80. if (this.elseCount_) {
  81. container.setAttribute('else', 1);
  82. }
  83. return container;
  84. },
  85. /**
  86. * Parse XML to restore the else-if and else inputs.
  87. * @param {!Element} xmlElement XML storage element.
  88. * @this Blockly.Block
  89. */
  90. domToMutation: function(xmlElement) {
  91. this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0;
  92. this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0;
  93. this.updateShape_();
  94. },
  95. /**
  96. * Populate the mutator's dialog with this block's components.
  97. * @param {!Blockly.Workspace} workspace Mutator's workspace.
  98. * @return {!Blockly.Block} Root block in mutator.
  99. * @this Blockly.Block
  100. */
  101. decompose: function(workspace) {
  102. var containerBlock = workspace.newBlock('controls_if_if');
  103. containerBlock.initSvg();
  104. var connection = containerBlock.nextConnection;
  105. for (var i = 1; i <= this.elseifCount_; i++) {
  106. var elseifBlock = workspace.newBlock('controls_if_elseif');
  107. elseifBlock.initSvg();
  108. connection.connect(elseifBlock.previousConnection);
  109. connection = elseifBlock.nextConnection;
  110. }
  111. if (this.elseCount_) {
  112. var elseBlock = workspace.newBlock('controls_if_else');
  113. elseBlock.initSvg();
  114. connection.connect(elseBlock.previousConnection);
  115. }
  116. return containerBlock;
  117. },
  118. /**
  119. * Reconfigure this block based on the mutator dialog's components.
  120. * @param {!Blockly.Block} containerBlock Root block in mutator.
  121. * @this Blockly.Block
  122. */
  123. compose: function(containerBlock) {
  124. var clauseBlock = containerBlock.nextConnection.targetBlock();
  125. // Count number of inputs.
  126. this.elseifCount_ = 0;
  127. this.elseCount_ = 0;
  128. var valueConnections = [null];
  129. var statementConnections = [null];
  130. var elseStatementConnection = null;
  131. while (clauseBlock) {
  132. switch (clauseBlock.type) {
  133. case 'controls_if_elseif':
  134. this.elseifCount_++;
  135. valueConnections.push(clauseBlock.valueConnection_);
  136. statementConnections.push(clauseBlock.statementConnection_);
  137. break;
  138. case 'controls_if_else':
  139. this.elseCount_++;
  140. elseStatementConnection = clauseBlock.statementConnection_;
  141. break;
  142. default:
  143. throw 'Unknown block type.';
  144. }
  145. clauseBlock = clauseBlock.nextConnection &&
  146. clauseBlock.nextConnection.targetBlock();
  147. }
  148. this.updateShape_();
  149. // Reconnect any child blocks.
  150. for (var i = 1; i <= this.elseifCount_; i++) {
  151. Blockly.Mutator.reconnect(valueConnections[i], this, 'IF' + i);
  152. Blockly.Mutator.reconnect(statementConnections[i], this, 'DO' + i);
  153. }
  154. Blockly.Mutator.reconnect(elseStatementConnection, this, 'ELSE');
  155. },
  156. /**
  157. * Store pointers to any connected child blocks.
  158. * @param {!Blockly.Block} containerBlock Root block in mutator.
  159. * @this Blockly.Block
  160. */
  161. saveConnections: function(containerBlock) {
  162. var clauseBlock = containerBlock.nextConnection.targetBlock();
  163. var i = 1;
  164. while (clauseBlock) {
  165. switch (clauseBlock.type) {
  166. case 'controls_if_elseif':
  167. var inputIf = this.getInput('IF' + i);
  168. var inputDo = this.getInput('DO' + i);
  169. clauseBlock.valueConnection_ =
  170. inputIf && inputIf.connection.targetConnection;
  171. clauseBlock.statementConnection_ =
  172. inputDo && inputDo.connection.targetConnection;
  173. i++;
  174. break;
  175. case 'controls_if_else':
  176. var inputDo = this.getInput('ELSE');
  177. clauseBlock.statementConnection_ =
  178. inputDo && inputDo.connection.targetConnection;
  179. break;
  180. default:
  181. throw 'Unknown block type.';
  182. }
  183. clauseBlock = clauseBlock.nextConnection &&
  184. clauseBlock.nextConnection.targetBlock();
  185. }
  186. },
  187. /**
  188. * Modify this block to have the correct number of inputs.
  189. * @private
  190. * @this Blockly.Block
  191. */
  192. updateShape_: function() {
  193. // Delete everything.
  194. if (this.getInput('ELSE')) {
  195. this.removeInput('ELSE');
  196. }
  197. var i = 1;
  198. while (this.getInput('IF' + i)) {
  199. this.removeInput('IF' + i);
  200. this.removeInput('DO' + i);
  201. i++;
  202. }
  203. // Rebuild block.
  204. for (var i = 1; i <= this.elseifCount_; i++) {
  205. this.appendValueInput('IF' + i)
  206. .setCheck('Boolean')
  207. .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
  208. this.appendStatementInput('DO' + i)
  209. .appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
  210. }
  211. if (this.elseCount_) {
  212. this.appendStatementInput('ELSE')
  213. .appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
  214. }
  215. }
  216. };
  217. Blockly.Blocks['controls_if_if'] = {
  218. /**
  219. * Mutator block for if container.
  220. * @this Blockly.Block
  221. */
  222. init: function() {
  223. this.setColour(Blockly.Blocks.logic.HUE);
  224. this.appendDummyInput()
  225. .appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);
  226. this.setNextStatement(true);
  227. this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);
  228. this.contextMenu = false;
  229. }
  230. };
  231. Blockly.Blocks['controls_if_elseif'] = {
  232. /**
  233. * Mutator bolck for else-if condition.
  234. * @this Blockly.Block
  235. */
  236. init: function() {
  237. this.setColour(Blockly.Blocks.logic.HUE);
  238. this.appendDummyInput()
  239. .appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF);
  240. this.setPreviousStatement(true);
  241. this.setNextStatement(true);
  242. this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);
  243. this.contextMenu = false;
  244. }
  245. };
  246. Blockly.Blocks['controls_if_else'] = {
  247. /**
  248. * Mutator block for else condition.
  249. * @this Blockly.Block
  250. */
  251. init: function() {
  252. this.setColour(Blockly.Blocks.logic.HUE);
  253. this.appendDummyInput()
  254. .appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE);
  255. this.setPreviousStatement(true);
  256. this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);
  257. this.contextMenu = false;
  258. }
  259. };
  260. Blockly.Blocks['logic_compare'] = {
  261. /**
  262. * Block for comparison operator.
  263. * @this Blockly.Block
  264. */
  265. init: function() {
  266. var rtlOperators = [
  267. ['==', 'EQ'],
  268. ['!=', 'NEQ'],
  269. ['>', 'LT'],
  270. ['>=', 'LTE'],
  271. ['<', 'GT'],
  272. ['<=', 'GTE']
  273. ];
  274. var ltrOperators = [
  275. ['==', 'EQ'],
  276. ['!=', 'NEQ'],
  277. ['<', 'LT'],
  278. ['<=', 'LTE'],
  279. ['>', 'GT'],
  280. ['>=', 'GTE']
  281. ];
  282. var OPERATORS = this.RTL ? rtlOperators : ltrOperators;
  283. this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);
  284. this.setColour(Blockly.Blocks.logic.HUE);
  285. this.setOutput(true, 'Boolean');
  286. this.appendValueInput('A');
  287. this.appendValueInput('B')
  288. .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
  289. this.setInputsInline(true);
  290. // Assign 'this' to a variable for use in the tooltip closure below.
  291. var thisBlock = this;
  292. this.setTooltip(function() {
  293. var op = thisBlock.getFieldValue('OP');
  294. var TOOLTIPS = {
  295. 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,
  296. 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,
  297. 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,
  298. 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,
  299. 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,
  300. 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE
  301. };
  302. return TOOLTIPS[op];
  303. });
  304. this.prevBlocks_ = [null, null];
  305. },
  306. /**
  307. * Called whenever anything on the workspace changes.
  308. * Prevent mismatched types from being compared.
  309. * @param {!Blockly.Events.Abstract} e Change event.
  310. * @this Blockly.Block
  311. */
  312. onchange: function(e) {
  313. var blockA = this.getInputTargetBlock('A');
  314. var blockB = this.getInputTargetBlock('B');
  315. // Disconnect blocks that existed prior to this change if they don't match.
  316. if (blockA && blockB &&
  317. !blockA.outputConnection.checkType_(blockB.outputConnection)) {
  318. // Mismatch between two inputs. Disconnect previous and bump it away.
  319. // Ensure that any disconnections are grouped with the causing event.
  320. Blockly.Events.setGroup(e.group);
  321. for (var i = 0; i < this.prevBlocks_.length; i++) {
  322. var block = this.prevBlocks_[i];
  323. if (block === blockA || block === blockB) {
  324. block.unplug();
  325. block.bumpNeighbours_();
  326. }
  327. }
  328. Blockly.Events.setGroup(false);
  329. }
  330. this.prevBlocks_[0] = blockA;
  331. this.prevBlocks_[1] = blockB;
  332. }
  333. };
  334. Blockly.Blocks['logic_operation'] = {
  335. /**
  336. * Block for logical operations: 'and', 'or'.
  337. * @this Blockly.Block
  338. */
  339. init: function() {
  340. var OPERATORS =
  341. [[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'],
  342. [Blockly.Msg.LOGIC_OPERATION_OR, 'OR']];
  343. this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL);
  344. this.setColour(Blockly.Blocks.logic.HUE);
  345. this.setOutput(true, 'Boolean');
  346. this.appendValueInput('A')
  347. .setCheck('Boolean');
  348. this.appendValueInput('B')
  349. .setCheck('Boolean')
  350. .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
  351. this.setInputsInline(true);
  352. // Assign 'this' to a variable for use in the tooltip closure below.
  353. var thisBlock = this;
  354. this.setTooltip(function() {
  355. var op = thisBlock.getFieldValue('OP');
  356. var TOOLTIPS = {
  357. 'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND,
  358. 'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR
  359. };
  360. return TOOLTIPS[op];
  361. });
  362. }
  363. };
  364. Blockly.Blocks['logic_negate'] = {
  365. /**
  366. * Block for negation.
  367. * @this Blockly.Block
  368. */
  369. init: function() {
  370. this.jsonInit({
  371. "message0": Blockly.Msg.LOGIC_NEGATE_TITLE,
  372. "args0": [
  373. {
  374. "type": "input_value",
  375. "name": "BOOL",
  376. "check": "Boolean"
  377. }
  378. ],
  379. "output": "Boolean",
  380. "colour": Blockly.Blocks.logic.HUE,
  381. "tooltip": Blockly.Msg.LOGIC_NEGATE_TOOLTIP,
  382. "helpUrl": Blockly.Msg.LOGIC_NEGATE_HELPURL
  383. });
  384. }
  385. };
  386. Blockly.Blocks['logic_boolean'] = {
  387. /**
  388. * Block for boolean data type: true and false.
  389. * @this Blockly.Block
  390. */
  391. init: function() {
  392. this.jsonInit({
  393. "message0": "%1",
  394. "args0": [
  395. {
  396. "type": "field_dropdown",
  397. "name": "BOOL",
  398. "options": [
  399. [Blockly.Msg.LOGIC_BOOLEAN_TRUE, "TRUE"],
  400. [Blockly.Msg.LOGIC_BOOLEAN_FALSE, "FALSE"]
  401. ]
  402. }
  403. ],
  404. "output": "Boolean",
  405. "colour": Blockly.Blocks.logic.HUE,
  406. "tooltip": Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP,
  407. "helpUrl": Blockly.Msg.LOGIC_BOOLEAN_HELPURL
  408. });
  409. }
  410. };
  411. Blockly.Blocks['logic_null'] = {
  412. /**
  413. * Block for null data type.
  414. * @this Blockly.Block
  415. */
  416. init: function() {
  417. this.jsonInit({
  418. "message0": Blockly.Msg.LOGIC_NULL,
  419. "output": null,
  420. "colour": Blockly.Blocks.logic.HUE,
  421. "tooltip": Blockly.Msg.LOGIC_NULL_TOOLTIP,
  422. "helpUrl": Blockly.Msg.LOGIC_NULL_HELPURL
  423. });
  424. }
  425. };
  426. Blockly.Blocks['logic_ternary'] = {
  427. /**
  428. * Block for ternary operator.
  429. * @this Blockly.Block
  430. */
  431. init: function() {
  432. this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);
  433. this.setColour(Blockly.Blocks.logic.HUE);
  434. this.appendValueInput('IF')
  435. .setCheck('Boolean')
  436. .appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);
  437. this.appendValueInput('THEN')
  438. .appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);
  439. this.appendValueInput('ELSE')
  440. .appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);
  441. this.setOutput(true);
  442. this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);
  443. this.prevParentConnection_ = null;
  444. },
  445. /**
  446. * Called whenever anything on the workspace changes.
  447. * Prevent mismatched types.
  448. * @param {!Blockly.Events.Abstract} e Change event.
  449. * @this Blockly.Block
  450. */
  451. onchange: function(e) {
  452. var blockA = this.getInputTargetBlock('THEN');
  453. var blockB = this.getInputTargetBlock('ELSE');
  454. var parentConnection = this.outputConnection.targetConnection;
  455. // Disconnect blocks that existed prior to this change if they don't match.
  456. if ((blockA || blockB) && parentConnection) {
  457. for (var i = 0; i < 2; i++) {
  458. var block = (i == 1) ? blockA : blockB;
  459. if (block && !block.outputConnection.checkType_(parentConnection)) {
  460. // Ensure that any disconnections are grouped with the causing event.
  461. Blockly.Events.setGroup(e.group);
  462. if (parentConnection === this.prevParentConnection_) {
  463. this.unplug();
  464. parentConnection.getSourceBlock().bumpNeighbours_();
  465. } else {
  466. block.unplug();
  467. block.bumpNeighbours_();
  468. }
  469. Blockly.Events.setGroup(false);
  470. }
  471. }
  472. }
  473. this.prevParentConnection_ = parentConnection;
  474. }
  475. };
  476. Blockly.Blocks['logic_isIn'] = {
  477. /**
  478. * Block for testing if something contains something.
  479. * @this Blockly.Block
  480. */
  481. init: function() {
  482. var OPERATORS =
  483. [["is in", 'IN'],
  484. ["is not in", 'NOTIN']];
  485. this.setColour(Blockly.Blocks.logic.HUE);
  486. this.setOutput(true, 'Boolean');
  487. this.appendValueInput('ITEM');
  488. this.appendValueInput('LIST')
  489. .setCheck(['Array', 'String'])
  490. .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
  491. this.setInputsInline(true);
  492. }
  493. };
  494. Blockly.Blocks['logic_none'] = {
  495. /**
  496. * Block for testing if something contains something.
  497. * @this Blockly.Block
  498. */
  499. init: function() {
  500. this.appendDummyInput()
  501. .appendField("None");
  502. this.setColour(Blockly.Blocks.logic.HUE);
  503. this.setOutput(true);
  504. }
  505. };