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