procedures.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  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 Procedure blocks for Blockly.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Blocks.procedures');
  26. goog.require('Blockly.Blocks');
  27. Blockly.Blocks.procedures.HUE = 340;
  28. Blockly.Blocks['procedures_defnoreturn'] = {
  29. /**
  30. * Block for defining a procedure with no return value.
  31. * @this Blockly.Block
  32. */
  33. init: function() {
  34. var nameField = new Blockly.FieldTextInput(
  35. Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE,
  36. Blockly.Procedures.rename);
  37. nameField.setSpellcheck(false);
  38. this.setPreviousStatement(true);
  39. this.setNextStatement(true);
  40. this.appendDummyInput()
  41. .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE)
  42. .appendField(nameField, 'NAME')
  43. .appendField('', 'PARAMS');
  44. this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
  45. /*if ((this.workspace.options.comments ||
  46. (this.workspace.options.parentWorkspace &&
  47. this.workspace.options.parentWorkspace.options.comments)) &&
  48. Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) {
  49. this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);
  50. }*/
  51. this.setColour(Blockly.Blocks.procedures.HUE);
  52. this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
  53. this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
  54. this.arguments_ = [];
  55. this.setStatements_(true);
  56. this.statementConnection_ = null;
  57. },
  58. /**
  59. * Add or remove the statement block from this function definition.
  60. * @param {boolean} hasStatements True if a statement block is needed.
  61. * @this Blockly.Block
  62. */
  63. setStatements_: function(hasStatements) {
  64. if (this.hasStatements_ === hasStatements) {
  65. return;
  66. }
  67. if (hasStatements) {
  68. this.appendStatementInput('STACK')
  69. .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
  70. if (this.getInput('RETURN')) {
  71. this.moveInputBefore('STACK', 'RETURN');
  72. }
  73. } else {
  74. this.removeInput('STACK', true);
  75. }
  76. this.hasStatements_ = hasStatements;
  77. },
  78. /**
  79. * Update the display of parameters for this procedure definition block.
  80. * Display a warning if there are duplicately named parameters.
  81. * @private
  82. * @this Blockly.Block
  83. */
  84. updateParams_: function() {
  85. // Check for duplicated arguments.
  86. var badArg = false;
  87. var hash = {};
  88. for (var i = 0; i < this.arguments_.length; i++) {
  89. if (hash['arg_' + this.arguments_[i].toLowerCase()]) {
  90. badArg = true;
  91. break;
  92. }
  93. hash['arg_' + this.arguments_[i].toLowerCase()] = true;
  94. }
  95. if (badArg) {
  96. this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING);
  97. } else {
  98. this.setWarningText(null);
  99. }
  100. // Merge the arguments into a human-readable list.
  101. var paramString = '';
  102. if (this.arguments_.length) {
  103. paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS +
  104. ' ' + this.arguments_.join(', ');
  105. }
  106. // The params field is deterministic based on the mutation,
  107. // no need to fire a change event.
  108. Blockly.Events.disable();
  109. try {
  110. this.setFieldValue(paramString, 'PARAMS');
  111. } finally {
  112. Blockly.Events.enable();
  113. }
  114. },
  115. /**
  116. * Create XML to represent the argument inputs.
  117. * @param {=boolean} opt_paramIds If true include the IDs of the parameter
  118. * quarks. Used by Blockly.Procedures.mutateCallers for reconnection.
  119. * @return {!Element} XML storage element.
  120. * @this Blockly.Block
  121. */
  122. mutationToDom: function(opt_paramIds) {
  123. var container = document.createElement('mutation');
  124. if (opt_paramIds) {
  125. container.setAttribute('name', this.getFieldValue('NAME'));
  126. }
  127. for (var i = 0; i < this.arguments_.length; i++) {
  128. var parameter = document.createElement('arg');
  129. parameter.setAttribute('name', this.arguments_[i]);
  130. if (opt_paramIds && this.paramIds_) {
  131. parameter.setAttribute('paramId', this.paramIds_[i]);
  132. }
  133. container.appendChild(parameter);
  134. }
  135. // Save whether the statement input is visible.
  136. if (!this.hasStatements_) {
  137. container.setAttribute('statements', 'false');
  138. }
  139. return container;
  140. },
  141. /**
  142. * Parse XML to restore the argument inputs.
  143. * @param {!Element} xmlElement XML storage element.
  144. * @this Blockly.Block
  145. */
  146. domToMutation: function(xmlElement) {
  147. this.arguments_ = [];
  148. for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
  149. if (childNode.nodeName.toLowerCase() == 'arg') {
  150. this.arguments_.push(childNode.getAttribute('name'));
  151. }
  152. }
  153. this.updateParams_();
  154. Blockly.Procedures.mutateCallers(this);
  155. // Show or hide the statement input.
  156. this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
  157. },
  158. /**
  159. * Populate the mutator's dialog with this block's components.
  160. * @param {!Blockly.Workspace} workspace Mutator's workspace.
  161. * @return {!Blockly.Block} Root block in mutator.
  162. * @this Blockly.Block
  163. */
  164. decompose: function(workspace) {
  165. var containerBlock = workspace.newBlock('procedures_mutatorcontainer');
  166. containerBlock.initSvg();
  167. // Check/uncheck the allow statement box.
  168. if (this.getInput('RETURN')) {
  169. containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE',
  170. 'STATEMENTS');
  171. } else {
  172. containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
  173. }
  174. // Parameter list.
  175. var connection = containerBlock.getInput('STACK').connection;
  176. for (var i = 0; i < this.arguments_.length; i++) {
  177. var paramBlock = workspace.newBlock('procedures_mutatorarg');
  178. paramBlock.initSvg();
  179. paramBlock.setFieldValue(this.arguments_[i], 'NAME');
  180. // Store the old location.
  181. paramBlock.oldLocation = i;
  182. connection.connect(paramBlock.previousConnection);
  183. connection = paramBlock.nextConnection;
  184. }
  185. // Initialize procedure's callers with blank IDs.
  186. Blockly.Procedures.mutateCallers(this);
  187. return containerBlock;
  188. },
  189. /**
  190. * Reconfigure this block based on the mutator dialog's components.
  191. * @param {!Blockly.Block} containerBlock Root block in mutator.
  192. * @this Blockly.Block
  193. */
  194. compose: function(containerBlock) {
  195. // Parameter list.
  196. this.arguments_ = [];
  197. this.paramIds_ = [];
  198. var paramBlock = containerBlock.getInputTargetBlock('STACK');
  199. while (paramBlock) {
  200. this.arguments_.push(paramBlock.getFieldValue('NAME'));
  201. this.paramIds_.push(paramBlock.id);
  202. paramBlock = paramBlock.nextConnection &&
  203. paramBlock.nextConnection.targetBlock();
  204. }
  205. this.updateParams_();
  206. Blockly.Procedures.mutateCallers(this);
  207. // Show/hide the statement input.
  208. var hasStatements = containerBlock.getFieldValue('STATEMENTS');
  209. if (hasStatements !== null) {
  210. hasStatements = hasStatements == 'TRUE';
  211. if (this.hasStatements_ != hasStatements) {
  212. if (hasStatements) {
  213. this.setStatements_(true);
  214. // Restore the stack, if one was saved.
  215. Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
  216. this.statementConnection_ = null;
  217. } else {
  218. // Save the stack, then disconnect it.
  219. var stackConnection = this.getInput('STACK').connection;
  220. this.statementConnection_ = stackConnection.targetConnection;
  221. if (this.statementConnection_) {
  222. var stackBlock = stackConnection.targetBlock();
  223. stackBlock.unplug();
  224. stackBlock.bumpNeighbours_();
  225. }
  226. this.setStatements_(false);
  227. }
  228. }
  229. }
  230. },
  231. /**
  232. * Return the signature of this procedure definition.
  233. * @return {!Array} Tuple containing three elements:
  234. * - the name of the defined procedure,
  235. * - a list of all its arguments,
  236. * - that it DOES NOT have a return value.
  237. * @this Blockly.Block
  238. */
  239. getProcedureDef: function() {
  240. return [this.getFieldValue('NAME'), this.arguments_, false];
  241. },
  242. /**
  243. * Return all variables referenced by this block.
  244. * @return {!Array.<string>} List of variable names.
  245. * @this Blockly.Block
  246. */
  247. getVars: function() {
  248. return this.arguments_;
  249. },
  250. /**
  251. * Notification that a variable is renaming.
  252. * If the name matches one of this block's variables, rename it.
  253. * @param {string} oldName Previous name of variable.
  254. * @param {string} newName Renamed variable.
  255. * @this Blockly.Block
  256. */
  257. renameVar: function(oldName, newName) {
  258. var change = false;
  259. for (var i = 0; i < this.arguments_.length; i++) {
  260. if (Blockly.Names.equals(oldName, this.arguments_[i])) {
  261. this.arguments_[i] = newName;
  262. change = true;
  263. }
  264. }
  265. if (change) {
  266. this.updateParams_();
  267. // Update the mutator's variables if the mutator is open.
  268. if (this.mutator.isVisible()) {
  269. var blocks = this.mutator.workspace_.getAllBlocks();
  270. for (var i = 0, block; block = blocks[i]; i++) {
  271. if (block.type == 'procedures_mutatorarg' &&
  272. Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
  273. block.setFieldValue(newName, 'NAME');
  274. }
  275. }
  276. }
  277. }
  278. },
  279. /**
  280. * Add custom menu options to this block's context menu.
  281. * @param {!Array} options List of menu options to add to.
  282. * @this Blockly.Block
  283. */
  284. customContextMenu: function(options) {
  285. // Add option to create caller.
  286. var option = {enabled: true};
  287. var name = this.getFieldValue('NAME');
  288. option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name);
  289. var xmlMutation = goog.dom.createDom('mutation');
  290. xmlMutation.setAttribute('name', name);
  291. for (var i = 0; i < this.arguments_.length; i++) {
  292. var xmlArg = goog.dom.createDom('arg');
  293. xmlArg.setAttribute('name', this.arguments_[i]);
  294. xmlMutation.appendChild(xmlArg);
  295. }
  296. var xmlBlock = goog.dom.createDom('block', null, xmlMutation);
  297. xmlBlock.setAttribute('type', this.callType_);
  298. option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
  299. options.push(option);
  300. // Add options to create getters for each parameter.
  301. if (!this.isCollapsed()) {
  302. for (var i = 0; i < this.arguments_.length; i++) {
  303. var option = {enabled: true};
  304. var name = this.arguments_[i];
  305. option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
  306. var xmlField = goog.dom.createDom('field', null, name);
  307. xmlField.setAttribute('name', 'VAR');
  308. var xmlBlock = goog.dom.createDom('block', null, xmlField);
  309. xmlBlock.setAttribute('type', 'variables_get');
  310. option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
  311. options.push(option);
  312. }
  313. }
  314. },
  315. callType_: 'procedures_callnoreturn',
  316. /**
  317. * Called whenever anything on the workspace changes.
  318. * Add warning if this flow block is not nested inside a loop.
  319. * @this Blockly.Block
  320. */
  321. onchange: function() {
  322. if (!this.workspace) {
  323. // Block has been deleted.
  324. return;
  325. }
  326. // Does it have valid returns?
  327. var hasReturns = false;
  328. var descendants = this.getDescendants();
  329. for (var i = 0; i < descendants.length; i++) {
  330. if (descendants[i].type == 'procedures_return') {
  331. hasReturns = true;
  332. }
  333. }
  334. // Iterate through every block and check the name.
  335. var functionName = this.getFieldValue('NAME');
  336. var blocks = this.workspace.getAllBlocks();
  337. for (var i = 0; i < blocks.length; i++) {
  338. if (blocks[i].type == 'procedures_callreturn' ||
  339. blocks[i].type == 'procedures_callnoreturn') {
  340. var callName = blocks[i].getFieldValue('NAME');
  341. // Variable name may be null if the block is only half-built.
  342. if (callName && Blockly.Names.equals(functionName, callName)) {
  343. blocks[i].setReturn(hasReturns);
  344. }
  345. }
  346. }
  347. }
  348. };
  349. Blockly.Blocks['procedures_defreturn'] = {
  350. /**
  351. * Block for defining a procedure with a return value.
  352. * @this Blockly.Block
  353. */
  354. init: function() {
  355. var nameField = new Blockly.FieldTextInput(
  356. Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE,
  357. Blockly.Procedures.rename);
  358. nameField.setSpellcheck(false);
  359. this.appendDummyInput()
  360. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE)
  361. .appendField(nameField, 'NAME')
  362. .appendField('', 'PARAMS');
  363. this.appendValueInput('RETURN')
  364. .setAlign(Blockly.ALIGN_RIGHT)
  365. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  366. this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
  367. /*if ((this.workspace.options.comments ||
  368. (this.workspace.options.parentWorkspace &&
  369. this.workspace.options.parentWorkspace.options.comments)) &&
  370. Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT) {
  371. this.setCommentText(Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT);
  372. }*/
  373. this.setColour(Blockly.Blocks.procedures.HUE);
  374. this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
  375. this.setPreviousStatement(true);
  376. this.setNextStatement(true);
  377. this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
  378. this.arguments_ = [];
  379. this.setStatements_(true);
  380. this.statementConnection_ = null;
  381. },
  382. setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
  383. updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
  384. mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
  385. domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
  386. decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
  387. compose: Blockly.Blocks['procedures_defnoreturn'].compose,
  388. onchange: Blockly.Blocks['procedures_defnoreturn'].onchange,
  389. /**
  390. * Return the signature of this procedure definition.
  391. * @return {!Array} Tuple containing three elements:
  392. * - the name of the defined procedure,
  393. * - a list of all its arguments,
  394. * - that it DOES have a return value.
  395. * @this Blockly.Block
  396. */
  397. getProcedureDef: function() {
  398. return [this.getFieldValue('NAME'), this.arguments_, true];
  399. },
  400. getVars: Blockly.Blocks['procedures_defnoreturn'].getVars,
  401. renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar,
  402. customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu,
  403. callType_: 'procedures_callreturn'
  404. };
  405. Blockly.Blocks['procedures_mutatorcontainer'] = {
  406. /**
  407. * Mutator block for procedure container.
  408. * @this Blockly.Block
  409. */
  410. init: function() {
  411. this.appendDummyInput()
  412. .appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
  413. this.appendStatementInput('STACK');
  414. this.appendDummyInput('STATEMENT_INPUT')
  415. .appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS)
  416. .appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
  417. this.setColour(Blockly.Blocks.procedures.HUE);
  418. this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
  419. this.contextMenu = false;
  420. }
  421. };
  422. Blockly.Blocks['procedures_mutatorarg'] = {
  423. /**
  424. * Mutator block for procedure argument.
  425. * @this Blockly.Block
  426. */
  427. init: function() {
  428. var field = new Blockly.FieldTextInput('x', this.validator_);
  429. this.appendDummyInput()
  430. .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE)
  431. .appendField(field, 'NAME');
  432. this.setPreviousStatement(true);
  433. this.setNextStatement(true);
  434. this.setColour(Blockly.Blocks.procedures.HUE);
  435. this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);
  436. this.contextMenu = false;
  437. // Create the default variable when we drag the block in from the flyout.
  438. // Have to do this after installing the field on the block.
  439. field.onFinishEditing_ = this.createNewVar_;
  440. field.onFinishEditing_('x');
  441. },
  442. /**
  443. * Obtain a valid name for the procedure.
  444. * Merge runs of whitespace. Strip leading and trailing whitespace.
  445. * Beyond this, all names are legal.
  446. * @param {string} newVar User-supplied name.
  447. * @return {?string} Valid name, or null if a name was not specified.
  448. * @private
  449. * @this Blockly.Block
  450. */
  451. validator_: function(newVar) {
  452. newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
  453. return newVar || null;
  454. },
  455. /**
  456. * Called when focusing away from the text field.
  457. * Creates a new variable with this name.
  458. * @param {string} newText The new variable name.
  459. * @private
  460. * @this Blockly.FieldTextInput
  461. */
  462. createNewVar_: function(newText) {
  463. var source = this.sourceBlock_;
  464. if (source && source.workspace && source.workspace.options
  465. && source.workspace.options.parentWorkspace) {
  466. source.workspace.options.parentWorkspace.createVariable(newText);
  467. }
  468. }
  469. };
  470. Blockly.Blocks['procedures_callnoreturn'] = {
  471. /**
  472. * Block for calling a procedure with no return value.
  473. * @this Blockly.Block
  474. */
  475. init: function() {
  476. this.appendDummyInput('TOPROW')
  477. .appendField(this.id, 'NAME');
  478. this.setPreviousStatement(true);
  479. this.setNextStatement(true);
  480. this.setColour(Blockly.Blocks.procedures.HUE);
  481. // Tooltip is set in renameProcedure.
  482. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);
  483. this.arguments_ = [];
  484. this.quarkConnections_ = {};
  485. this.quarkIds_ = null;
  486. },
  487. /**
  488. * Returns the name of the procedure this block calls.
  489. * @return {string} Procedure name.
  490. * @this Blockly.Block
  491. */
  492. getProcedureCall: function() {
  493. // The NAME field is guaranteed to exist, null will never be returned.
  494. return /** @type {string} */ (this.getFieldValue('NAME'));
  495. },
  496. /**
  497. * Notification that a procedure is renaming.
  498. * If the name matches this block's procedure, rename it.
  499. * @param {string} oldName Previous name of procedure.
  500. * @param {string} newName Renamed procedure.
  501. * @this Blockly.Block
  502. */
  503. renameProcedure: function(oldName, newName) {
  504. if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
  505. this.setFieldValue(newName, 'NAME');
  506. this.setTooltip(
  507. (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
  508. Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
  509. .replace('%1', newName));
  510. }
  511. },
  512. /**
  513. * Notification that the procedure's return state has changed.
  514. * @param {boolean} returnState New return state
  515. * @this Blockly.Block
  516. */
  517. setReturn: function(returnState) {
  518. if (returnState) {
  519. this.setPreviousStatement(false);
  520. this.setNextStatement(false);
  521. this.setOutput(true);
  522. } else {
  523. this.setOutput(false);
  524. this.setPreviousStatement(true);
  525. this.setNextStatement(true);
  526. }
  527. if (this.rendered) {
  528. this.render();
  529. }
  530. },
  531. /**
  532. * Notification that the procedure's parameters have changed.
  533. * @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
  534. * @param {!Array.<string>} paramIds IDs of params (consistent for each
  535. * parameter through the life of a mutator, regardless of param renaming),
  536. * e.g. ['piua', 'f8b_', 'oi.o'].
  537. * @private
  538. * @this Blockly.Block
  539. */
  540. setProcedureParameters_: function(paramNames, paramIds) {
  541. // Data structures:
  542. // this.arguments = ['x', 'y']
  543. // Existing param names.
  544. // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
  545. // Look-up of paramIds to connections plugged into the call block.
  546. // this.quarkIds_ = ['piua', 'f8b_']
  547. // Existing param IDs.
  548. // Note that quarkConnections_ may include IDs that no longer exist, but
  549. // which might reappear if a param is reattached in the mutator.
  550. var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
  551. this.workspace);
  552. var mutatorOpen = defBlock && defBlock.mutator &&
  553. defBlock.mutator.isVisible();
  554. if (!mutatorOpen) {
  555. this.quarkConnections_ = {};
  556. this.quarkIds_ = null;
  557. }
  558. if (!paramIds) {
  559. // Reset the quarks (a mutator is about to open).
  560. return;
  561. }
  562. if (goog.array.equals(this.arguments_, paramNames)) {
  563. // No change.
  564. this.quarkIds_ = paramIds;
  565. return;
  566. }
  567. if (paramIds.length != paramNames.length) {
  568. throw 'Error: paramNames and paramIds must be the same length.';
  569. }
  570. this.setCollapsed(false);
  571. if (!this.quarkIds_) {
  572. // Initialize tracking for this block.
  573. this.quarkConnections_ = {};
  574. if (paramNames.join('\n') == this.arguments_.join('\n')) {
  575. // No change to the parameters, allow quarkConnections_ to be
  576. // populated with the existing connections.
  577. this.quarkIds_ = paramIds;
  578. } else {
  579. this.quarkIds_ = [];
  580. }
  581. }
  582. // Switch off rendering while the block is rebuilt.
  583. var savedRendered = this.rendered;
  584. this.rendered = false;
  585. // Update the quarkConnections_ with existing connections.
  586. for (var i = 0; i < this.arguments_.length; i++) {
  587. var input = this.getInput('ARG' + i);
  588. if (input) {
  589. var connection = input.connection.targetConnection;
  590. this.quarkConnections_[this.quarkIds_[i]] = connection;
  591. if (mutatorOpen && connection &&
  592. paramIds.indexOf(this.quarkIds_[i]) == -1) {
  593. // This connection should no longer be attached to this block.
  594. connection.disconnect();
  595. connection.getSourceBlock().bumpNeighbours_();
  596. }
  597. }
  598. }
  599. // Rebuild the block's arguments.
  600. this.arguments_ = [].concat(paramNames);
  601. this.updateShape_();
  602. this.quarkIds_ = paramIds;
  603. // Reconnect any child blocks.
  604. if (this.quarkIds_) {
  605. for (var i = 0; i < this.arguments_.length; i++) {
  606. var quarkId = this.quarkIds_[i];
  607. if (quarkId in this.quarkConnections_) {
  608. var connection = this.quarkConnections_[quarkId];
  609. if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
  610. // Block no longer exists or has been attached elsewhere.
  611. delete this.quarkConnections_[quarkId];
  612. }
  613. }
  614. }
  615. }
  616. // Restore rendering and show the changes.
  617. this.rendered = savedRendered;
  618. if (this.rendered) {
  619. this.render();
  620. }
  621. },
  622. /**
  623. * Modify this block to have the correct number of arguments.
  624. * @private
  625. * @this Blockly.Block
  626. */
  627. updateShape_: function() {
  628. for (var i = 0; i < this.arguments_.length; i++) {
  629. if (this.arguments_[i] != "") {
  630. var field = this.getField('ARGNAME' + i);
  631. if (field) {
  632. // Ensure argument name is up to date.
  633. // The argument name field is deterministic based on the mutation,
  634. // no need to fire a change event.
  635. Blockly.Events.disable();
  636. try {
  637. field.setValue(this.arguments_[i]);
  638. } finally {
  639. Blockly.Events.enable();
  640. }
  641. } else {
  642. // Add new input.
  643. field = new Blockly.FieldLabel(this.arguments_[i]);
  644. var input = this.appendValueInput('ARG' + i)
  645. .setAlign(Blockly.ALIGN_RIGHT)
  646. .appendField(field, 'ARGNAME' + i);
  647. input.init();
  648. }
  649. } else {
  650. if (!this.getInput('ARG' + i)) {
  651. var input = this.appendValueInput('ARG' + i)
  652. .setAlign(Blockly.ALIGN_RIGHT);
  653. //.appendField(field, 'ARGNAME' + i);
  654. input.init();
  655. }
  656. }
  657. }
  658. // Remove deleted inputs.
  659. while (this.getInput('ARG' + i)) {
  660. this.removeInput('ARG' + i);
  661. i++;
  662. }
  663. // Add 'with:' if there are parameters, remove otherwise.
  664. /*
  665. var topRow = this.getInput('TOPROW');
  666. if (topRow) {
  667. if (this.arguments_.length) {
  668. if (!this.getField('WITH')) {
  669. topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
  670. topRow.init();
  671. }
  672. } else {
  673. if (this.getField('WITH')) {
  674. topRow.removeField('WITH');
  675. }
  676. }
  677. }
  678. */
  679. },
  680. /**
  681. * Create XML to represent the (non-editable) name and arguments.
  682. * @return {!Element} XML storage element.
  683. * @this Blockly.Block
  684. */
  685. mutationToDom: function() {
  686. var container = document.createElement('mutation');
  687. container.setAttribute('name', this.getProcedureCall());
  688. for (var i = 0; i < this.arguments_.length; i++) {
  689. var parameter = document.createElement('arg');
  690. parameter.setAttribute('name', this.arguments_[i]);
  691. container.appendChild(parameter);
  692. }
  693. return container;
  694. },
  695. /**
  696. * Parse XML to restore the (non-editable) name and parameters.
  697. * @param {!Element} xmlElement XML storage element.
  698. * @this Blockly.Block
  699. */
  700. domToMutation: function(xmlElement) {
  701. var name = xmlElement.getAttribute('name');
  702. this.renameProcedure(this.getProcedureCall(), name);
  703. var args = [];
  704. var paramIds = [];
  705. for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
  706. if (childNode.nodeName.toLowerCase() == 'arg') {
  707. args.push(childNode.getAttribute('name'));
  708. paramIds.push(childNode.getAttribute('paramId'));
  709. }
  710. }
  711. this.setProcedureParameters_(args, paramIds);
  712. },
  713. /**
  714. * Notification that a variable is renaming.
  715. * If the name matches one of this block's variables, rename it.
  716. * @param {string} oldName Previous name of variable.
  717. * @param {string} newName Renamed variable.
  718. * @this Blockly.Block
  719. */
  720. renameVar: function(oldName, newName) {
  721. for (var i = 0; i < this.arguments_.length; i++) {
  722. if (Blockly.Names.equals(oldName, this.arguments_[i])) {
  723. this.arguments_[i] = newName;
  724. var argname = this.getField('ARGNAME' + i);
  725. if (argname) {
  726. argname.setValue(newName);
  727. }
  728. }
  729. }
  730. },
  731. /**
  732. * Procedure calls cannot exist without the corresponding procedure
  733. * definition. Enforce this link whenever an event is fired.
  734. * @this Blockly.Block
  735. */
  736. onchange: function(event) {
  737. if (!this.workspace || this.workspace.isFlyout) {
  738. // Block is deleted or is in a flyout.
  739. return;
  740. }
  741. /*
  742. if (event.type == Blockly.Events.CREATE &&
  743. event.ids.indexOf(this.id) != -1) {
  744. // Look for the case where a procedure call was created (usually through
  745. // paste) and there is no matching definition. In this case, create
  746. // an empty definition block with the correct signature.
  747. var name = this.getProcedureCall();
  748. var def = Blockly.Procedures.getDefinition(name, this.workspace);
  749. if (def && (def.type != this.defType_ ||
  750. JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
  751. // The signatures don't match.
  752. def = null;
  753. }
  754. if (!def) {
  755. Blockly.Events.setGroup(event.group);
  756. */ /**
  757. * Create matching definition block.
  758. * <xml>
  759. * <block type="procedures_defreturn" x="10" y="20">
  760. * <mutation name="test">
  761. * <arg name="x"></arg>
  762. * </mutation>
  763. * <field name="NAME">test</field>
  764. * </block>
  765. * </xml>
  766. */
  767. /* var xml = goog.dom.createDom('xml');
  768. var block = goog.dom.createDom('block');
  769. block.setAttribute('type', this.defType_);
  770. var xy = this.getRelativeToSurfaceXY();
  771. var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
  772. var y = xy.y + Blockly.SNAP_RADIUS * 2;
  773. block.setAttribute('x', x);
  774. block.setAttribute('y', y);
  775. var mutation = this.mutationToDom();
  776. block.appendChild(mutation);
  777. var field = goog.dom.createDom('field');
  778. field.setAttribute('name', 'NAME');
  779. field.appendChild(document.createTextNode(this.getProcedureCall()));
  780. block.appendChild(field);
  781. xml.appendChild(block);
  782. Blockly.Xml.domToWorkspace(xml, this.workspace);
  783. Blockly.Events.setGroup(false);
  784. }
  785. } else if (event.type == Blockly.Events.DELETE) {
  786. // Look for the case where a procedure definition has been deleted,
  787. // leaving this block (a procedure call) orphaned. In this case, delete
  788. // the orphan.
  789. var name = this.getProcedureCall();
  790. var def = Blockly.Procedures.getDefinition(name, this.workspace);
  791. if (!def) {
  792. Blockly.Events.setGroup(event.group);
  793. this.dispose(true, false);
  794. Blockly.Events.setGroup(false);
  795. }
  796. }*/
  797. },
  798. /**
  799. * Add menu option to find the definition block for this call.
  800. * @param {!Array} options List of menu options to add to.
  801. * @this Blockly.Block
  802. */
  803. customContextMenu: function(options) {
  804. var option = {enabled: true};
  805. option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
  806. var name = this.getProcedureCall();
  807. var workspace = this.workspace;
  808. option.callback = function() {
  809. var def = Blockly.Procedures.getDefinition(name, workspace);
  810. def && def.select();
  811. };
  812. options.push(option);
  813. },
  814. defType_: 'procedures_defnoreturn'
  815. };
  816. Blockly.Blocks['procedures_callreturn'] = {
  817. /**
  818. * Block for calling a procedure with a return value.
  819. * @this Blockly.Block
  820. */
  821. init: function() {
  822. this.appendDummyInput('TOPROW')
  823. .appendField('', 'NAME');
  824. this.setOutput(true);
  825. this.setColour(Blockly.Blocks.procedures.HUE);
  826. // Tooltip is set in domToMutation.
  827. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);
  828. this.arguments_ = [];
  829. this.quarkConnections_ = {};
  830. this.quarkIds_ = null;
  831. },
  832. getProcedureCall: Blockly.Blocks['procedures_callnoreturn'].getProcedureCall,
  833. renameProcedure: Blockly.Blocks['procedures_callnoreturn'].renameProcedure,
  834. setProcedureParameters_:
  835. Blockly.Blocks['procedures_callnoreturn'].setProcedureParameters_,
  836. updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
  837. mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
  838. domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
  839. renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
  840. onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
  841. setReturn: Blockly.Blocks['procedures_callnoreturn'].setReturn,
  842. customContextMenu:
  843. Blockly.Blocks['procedures_callnoreturn'].customContextMenu,
  844. defType_: 'procedures_defreturn'
  845. };
  846. Blockly.Blocks['procedures_ifreturn'] = {
  847. /**
  848. * Block for conditionally returning a value from a procedure.
  849. * @this Blockly.Block
  850. */
  851. init: function() {
  852. this.appendValueInput('CONDITION')
  853. .setCheck('Boolean')
  854. .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
  855. this.appendValueInput('VALUE')
  856. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  857. this.setInputsInline(true);
  858. this.setPreviousStatement(true);
  859. this.setNextStatement(true);
  860. this.setColour(Blockly.Blocks.procedures.HUE);
  861. this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
  862. this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);
  863. this.hasReturnValue_ = true;
  864. },
  865. /**
  866. * Create XML to represent whether this block has a return value.
  867. * @return {!Element} XML storage element.
  868. * @this Blockly.Block
  869. */
  870. mutationToDom: function() {
  871. var container = document.createElement('mutation');
  872. container.setAttribute('value', Number(this.hasReturnValue_));
  873. return container;
  874. },
  875. /**
  876. * Parse XML to restore whether this block has a return value.
  877. * @param {!Element} xmlElement XML storage element.
  878. * @this Blockly.Block
  879. */
  880. domToMutation: function(xmlElement) {
  881. var value = xmlElement.getAttribute('value');
  882. this.hasReturnValue_ = (value == 1);
  883. if (!this.hasReturnValue_) {
  884. this.removeInput('VALUE');
  885. this.appendDummyInput('VALUE')
  886. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  887. }
  888. },
  889. /**
  890. * Called whenever anything on the workspace changes.
  891. * Add warning if this flow block is not nested inside a loop.
  892. * @param {!Blockly.Events.Abstract} e Change event.
  893. * @this Blockly.Block
  894. */
  895. onchange: function(e) {
  896. if (this.workspace.isDragging()) {
  897. return; // Don't change state at the start of a drag.
  898. }
  899. var legal = false;
  900. // Is the block nested in a procedure?
  901. var block = this;
  902. do {
  903. if (this.FUNCTION_TYPES.indexOf(block.type) != -1) {
  904. legal = true;
  905. break;
  906. }
  907. block = block.getSurroundParent();
  908. } while (block);
  909. if (legal) {
  910. /*
  911. // If needed, toggle whether this block has a return value.
  912. if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) {
  913. this.removeInput('VALUE');
  914. this.appendDummyInput('VALUE')
  915. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  916. this.hasReturnValue_ = false;
  917. } else if (block.type == 'procedures_defreturn' &&
  918. !this.hasReturnValue_) {
  919. this.removeInput('VALUE');
  920. this.appendValueInput('VALUE')
  921. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  922. this.hasReturnValue_ = true;
  923. }
  924. */
  925. this.setWarningText(null);
  926. if (!this.isInFlyout) {
  927. this.setDisabled(false);
  928. }
  929. } else {
  930. this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
  931. if (!this.isInFlyout && !this.getInheritedDisabled()) {
  932. this.setDisabled(true);
  933. }
  934. }
  935. },
  936. /**
  937. * List of block types that are functions and thus do not need warnings.
  938. * To add a new function type add this to your code:
  939. * Blockly.Blocks['procedures_ifreturn'].FUNCTION_TYPES.push('custom_func');
  940. */
  941. FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn']
  942. };
  943. Blockly.Blocks['procedures_return'] = {
  944. /**
  945. * Block for returning a value from a procedure.
  946. * @this Blockly.Block
  947. */
  948. init: function() {
  949. this.setHelpUrl('http://c2.com/cgi/wiki?GuardClause');
  950. this.setColour(Blockly.Blocks.procedures.HUE);
  951. this.appendValueInput('VALUE')
  952. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  953. this.setInputsInline(false);
  954. this.setPreviousStatement(true);
  955. this.setNextStatement(true);
  956. this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
  957. this.hasReturnValue_ = true;
  958. },
  959. /**
  960. * Called whenever anything on the workspace changes.
  961. * Add warning if this flow block is not nested inside a loop.
  962. * @this Blockly.Block
  963. */
  964. onchange: function() {
  965. if (!this.workspace) {
  966. // Block has been deleted.
  967. return;
  968. }
  969. var legal = false;
  970. // Is the block nested in a procedure?
  971. var block = this;
  972. do {
  973. if (block.type == 'procedures_defnoreturn' ||
  974. block.type == 'procedures_defreturn') {
  975. legal = true;
  976. break;
  977. }
  978. block = block.getSurroundParent();
  979. } while (block);
  980. if (legal) {
  981. this.setWarningText(null);
  982. } else {
  983. this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
  984. }
  985. }
  986. };
  987. Blockly.Blocks['procedures_main'] = {
  988. /**
  989. * Block for returning a value from a procedure.
  990. * @this Blockly.Block
  991. */
  992. init: function() {
  993. this.setColour(Blockly.Blocks.procedures.HUE);
  994. this.setPreviousStatement(true);
  995. this.setNextStatement(true);
  996. this.appendDummyInput()
  997. .appendField(Blockly.Msg.PROCEDURES_MAINFUNCTION)
  998. this.setStatements_(true);
  999. this.statementConnection_ = null;
  1000. },
  1001. setStatements_: function(hasStatements) {
  1002. if (this.hasStatements_ === hasStatements) {
  1003. return;
  1004. }
  1005. if (hasStatements) {
  1006. this.appendStatementInput('STACK')
  1007. } else {
  1008. this.removeInput('STACK', true);
  1009. }
  1010. this.hasStatements_ = hasStatements;
  1011. },
  1012. };