variables.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  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 Utility functions for handling variables.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Variables');
  26. goog.require('Blockly.Blocks');
  27. goog.require('Blockly.Workspace');
  28. goog.require('goog.string');
  29. /**
  30. * Category to separate variable names from procedures and generated functions.
  31. */
  32. Blockly.Variables.NAME_TYPE = 'VARIABLE';
  33. /**
  34. * Find all user-created variables that are in use in the workspace.
  35. * For use by generators.
  36. * @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace.
  37. * @return {!Array.<string>} Array of variable names.
  38. */
  39. Blockly.Variables.allUsedVariables = function(root) {
  40. var blocks;
  41. if (root instanceof Blockly.Block) {
  42. // Root is Block.
  43. blocks = root.getDescendants();
  44. } else if (root.getAllBlocks) {
  45. // Root is Workspace.
  46. blocks = root.getAllBlocks();
  47. } else {
  48. throw 'Not Block or Workspace: ' + root;
  49. }
  50. var variableHash = Object.create(null);
  51. // Iterate through every block and add each variable to the hash.
  52. for (var x = 0; x < blocks.length; x++) {
  53. var blockVariables = blocks[x].getVars();
  54. if (blockVariables) {
  55. for (var y = 0; y < blockVariables.length; y++) {
  56. var varName = blockVariables[y];
  57. // Variable name may be null if the block is only half-built.
  58. if (varName) {
  59. variableHash[varName.toLowerCase()] = varName;
  60. }
  61. }
  62. }
  63. }
  64. // Flatten the hash into a list.
  65. var variableList = [];
  66. for (var name in variableHash) {
  67. variableList.push(variableHash[name]);
  68. }
  69. return variableList;
  70. };
  71. /**
  72. * Find all variables that the user has created through the workspace or
  73. * toolbox. For use by generators.
  74. * @param {!Blockly.Workspace} root The workspace to inspect.
  75. * @return {!Array.<string>} Array of variable names.
  76. */
  77. Blockly.Variables.allVariables = function(root) {
  78. if (root instanceof Blockly.Block) {
  79. // Root is Block.
  80. console.warn('Deprecated call to Blockly.Variables.allVariables ' +
  81. 'with a block instead of a workspace. You may want ' +
  82. 'Blockly.Variables.allUsedVariables');
  83. }
  84. return root.variableList;
  85. };
  86. /**
  87. * Construct the blocks required by the flyout for the variable category.
  88. * @param {!Blockly.Workspace} workspace The workspace contianing variables.
  89. * @return {!Array.<!Element>} Array of XML block elements.
  90. */
  91. Blockly.Variables.flyoutCategory = function(workspace) {
  92. var variableList = workspace.variableList;
  93. variableList.sort(goog.string.caseInsensitiveCompare);
  94. var xmlList = [];
  95. var button = goog.dom.createDom('button');
  96. button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
  97. button.setAttribute('callbackKey', 'CREATE_VARIABLE');
  98. Blockly.registerButtonCallback('CREATE_VARIABLE', function(button) {
  99. Blockly.Variables.createVariable(button.getTargetWorkspace());
  100. });
  101. xmlList.push(button);
  102. if (variableList.length > 0) {
  103. /*
  104. if (Blockly.Blocks['math_change']) {
  105. // <block type="math_change">
  106. // <value name="DELTA">
  107. // <shadow type="math_number">
  108. // <field name="NUM">1</field>
  109. // </shadow>
  110. // </value>
  111. // </block>
  112. var block = goog.dom.createDom('block');
  113. block.setAttribute('type', 'math_change');
  114. if (Blockly.Blocks['variables_get']) {
  115. block.setAttribute('gap', 20);
  116. }
  117. var value = goog.dom.createDom('value');
  118. value.setAttribute('name', 'DELTA');
  119. block.appendChild(value);
  120. var field = goog.dom.createDom('field', null, variableList[0]);
  121. field.setAttribute('name', 'VAR');
  122. block.appendChild(field);
  123. var shadowBlock = goog.dom.createDom('shadow');
  124. shadowBlock.setAttribute('type', 'math_number');
  125. value.appendChild(shadowBlock);
  126. var numberField = goog.dom.createDom('field', null, '1');
  127. numberField.setAttribute('name', 'NUM');
  128. shadowBlock.appendChild(numberField);
  129. xmlList.push(block);
  130. }*/
  131. for (var i = 0; i < variableList.length; i++) {
  132. if (Blockly.Blocks['variables_set']) {
  133. // <block type="variables_set" gap="20">
  134. // <field name="VAR">item</field>
  135. // </block>
  136. var block = goog.dom.createDom('block');
  137. block.setAttribute('type', 'variables_set');
  138. if (Blockly.Blocks['math_change']) {
  139. block.setAttribute('gap', 8);
  140. } else {
  141. //block.setAttribute('gap', 24);
  142. }
  143. var field = goog.dom.createDom('field', null, variableList[i]);
  144. field.setAttribute('name', 'VAR');
  145. block.appendChild(field);
  146. xmlList.push(block);
  147. }
  148. }
  149. for (var i = 0; i < variableList.length; i++) {
  150. if (Blockly.Blocks['variables_get']) {
  151. // <block type="variables_get" gap="8">
  152. // <field name="VAR">item</field>
  153. // </block>
  154. var block = goog.dom.createDom('block');
  155. block.setAttribute('type', 'variables_get');
  156. if (Blockly.Blocks['variables_set']) {
  157. block.setAttribute('gap', 8);
  158. }
  159. var field = goog.dom.createDom('field', null, variableList[i]);
  160. field.setAttribute('name', 'VAR');
  161. block.appendChild(field);
  162. xmlList.push(block);
  163. }
  164. }
  165. }
  166. if (Blockly.Blocks['variables_getself']) {
  167. var block = goog.dom.createDom('block');
  168. block.setAttribute('type', 'variables_getself');
  169. block.setAttribute('gap', 16);
  170. xmlList.push(block);
  171. }
  172. if (Blockly.Blocks['variables_setself']) {
  173. var block = goog.dom.createDom('block');
  174. block.setAttribute('type', 'variables_setself');
  175. block.setAttribute('gap', 16);
  176. xmlList.push(block);
  177. }
  178. return xmlList;
  179. };
  180. /**
  181. * Return a new variable name that is not yet being used. This will try to
  182. * generate single letter variable names in the range 'i' to 'z' to start with.
  183. * If no unique name is located it will try 'i' to 'z', 'a' to 'h',
  184. * then 'i2' to 'z2' etc. Skip 'l'.
  185. * @param {!Blockly.Workspace} workspace The workspace to be unique in.
  186. * @return {string} New variable name.
  187. */
  188. Blockly.Variables.generateUniqueName = function(workspace) {
  189. var variableList = workspace.variableList;
  190. var newName = '';
  191. if (variableList.length) {
  192. var nameSuffix = 1;
  193. var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'.
  194. var letterIndex = 0;
  195. var potName = letters.charAt(letterIndex);
  196. while (!newName) {
  197. var inUse = false;
  198. for (var i = 0; i < variableList.length; i++) {
  199. if (variableList[i].toLowerCase() == potName) {
  200. // This potential name is already used.
  201. inUse = true;
  202. break;
  203. }
  204. }
  205. if (inUse) {
  206. // Try the next potential name.
  207. letterIndex++;
  208. if (letterIndex == letters.length) {
  209. // Reached the end of the character sequence so back to 'i'.
  210. // a new suffix.
  211. letterIndex = 0;
  212. nameSuffix++;
  213. }
  214. potName = letters.charAt(letterIndex);
  215. if (nameSuffix > 1) {
  216. potName += nameSuffix;
  217. }
  218. } else {
  219. // We can use the current potential name.
  220. newName = potName;
  221. }
  222. }
  223. } else {
  224. newName = 'i';
  225. }
  226. return newName;
  227. };
  228. /**
  229. * Create a new variable on the given workspace.
  230. * @param {!Blockly.Workspace} workspace The workspace on which to create the
  231. * variable.
  232. * @param {function(null|undefined|string)=} opt_callback A callback. It will
  233. * return an acceptable new variable name, or null if change is to be
  234. * aborted (cancel button), or undefined if an existing variable was chosen.
  235. */
  236. Blockly.Variables.createVariable = function(workspace, opt_callback) {
  237. var promptAndCheckWithAlert = function(defaultName) {
  238. Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, defaultName,
  239. function(text) {
  240. if (text) {
  241. if (workspace.variableIndexOf(text) != -1) {
  242. Blockly.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
  243. text.toLowerCase()),
  244. function() {
  245. promptAndCheckWithAlert(text); // Recurse
  246. });
  247. } else {
  248. workspace.createVariable(text);
  249. if (opt_callback) {
  250. opt_callback(text);
  251. }
  252. }
  253. } else {
  254. // User canceled prompt without a value.
  255. if (opt_callback) {
  256. opt_callback(null);
  257. }
  258. }
  259. });
  260. };
  261. promptAndCheckWithAlert('');
  262. };
  263. /**
  264. * Prompt the user for a new variable name.
  265. * @param {string} promptText The string of the prompt.
  266. * @param {string} defaultText The default value to show in the prompt's field.
  267. * @param {function(?string)} callback A callback. It will return the new
  268. * variable name, or null if the user picked something illegal.
  269. */
  270. Blockly.Variables.promptName = function(promptText, defaultText, callback) {
  271. Blockly.prompt(promptText, defaultText, function(newVar) {
  272. // Merge runs of whitespace. Strip leading and trailing whitespace.
  273. // Beyond this, all names are legal.
  274. if (newVar) {
  275. newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
  276. if (newVar == Blockly.Msg.RENAME_VARIABLE ||
  277. newVar == Blockly.Msg.NEW_VARIABLE) {
  278. // Ok, not ALL names are legal...
  279. newVar = null;
  280. }
  281. }
  282. callback(newVar);
  283. });
  284. };