/** * @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 Utility functions for handling variables. * @author fraser@google.com (Neil Fraser) */ using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Bridge; using Bridge.Html5; using Bridge.Text.RegularExpressions; using WebMrbc; namespace WebMrbc { public class Variables { /// /// Category to separate variable names from procedures and generated functions. /// public string NAME_TYPE = "VARIABLE"; /// /// Common HSV hue for all blocks in this category. /// public int HUE = 330; /// /// Find all user-created variables that are in use in the workspace. /// For use by generators. /// /// Root block or workspace. /// Array of variable names. public string[] allUsedVariables(Union root) { Block[] blocks; if (Script.Write("root instanceof Blockly.Block")) { // Root is Block. blocks = ((Block)root).getDescendants(); } else if (Script.Write("root instanceof Blockly.Workspace")) { // Root is Workspace. blocks = ((Workspace)root).getAllBlocks(); } else { throw new Exception("Not Block or Workspace: " + root); } var variableHash = new Dictionary(); // Iterate through every block and add each variable to the hash. for (var x = 0; x < blocks.Length; x++) { var blockVariables = blocks[x].getVars(); if (blockVariables != null) { for (var y = 0; y < blockVariables.Length; y++) { var varName = blockVariables[y]; // Variable name may be null if the block is only half-built. if (varName != null) { variableHash[varName.ToLower()] = varName; } } } } // Flatten the hash into a list. var variableList = new string[0]; foreach (var name in variableHash.Keys) { variableList.Push(variableHash[name]); } return variableList; } /// /// Find all variables that the user has created through the workspace or /// toolbox. For use by generators. /// /// The workspace to inspect. /// Array of variable names. public string[] allVariables(Workspace root) { if (Script.Write("root instanceof Blockly.Block")) { // Root is Block. App.WriteLine("Deprecated call to Variables.allVariables " + "with a block instead of a workspace. You may want " + "Variables.allUsedVariables"); } return root.variableList; } /// /// Construct the blocks required by the flyout for the variable category. /// /// The workspace contianing variables. /// Array of XML block elements. public Element[] flyoutCategory(Workspace workspace) { var variableList = workspace.variableList; Array.Sort(variableList, goog.@string.caseInsensitiveCompare); var xmlList = new Element[0]; var button = goog.dom.createDom("button"); button.SetAttribute("text", Msg.NEW_VARIABLE); button.SetAttribute("callbackKey", "CREATE_VARIABLE"); Blockly.registerButtonCallback("CREATE_VARIABLE", (btn) => { createVariable(btn.getTargetWorkspace()); }); xmlList.Push(button); if (variableList.Length > 0) { if (Script.Get(Blockly.Blocks, "variables_set") != null) { // // item // var block = goog.dom.createDom("block"); block.SetAttribute("type", "variables_set"); if (Script.Get(Blockly.Blocks, "math_change") != null) { block.SetAttribute("gap", "8"); } else { block.SetAttribute("gap", "24"); } var field = goog.dom.createDom("field", null, variableList[0]); field.SetAttribute("name", "VAR"); block.AppendChild(field); xmlList.Push(block); } if (Script.Get(Blockly.Blocks, "math_change") != null) { // // // // 1 // // // var block = goog.dom.createDom("block"); block.SetAttribute("type", "math_change"); if (Script.Get(Blockly.Blocks, "variables_get") != null) { block.SetAttribute("gap", "20"); } var value = goog.dom.createDom("value"); value.SetAttribute("name", "DELTA"); block.AppendChild(value); var field = goog.dom.createDom("field", null, variableList[0]); field.SetAttribute("name", "VAR"); block.AppendChild(field); var shadowBlock = goog.dom.createDom("shadow"); shadowBlock.SetAttribute("type", "math_number"); value.AppendChild(shadowBlock); var numberField = goog.dom.createDom("field", null, "1"); numberField.SetAttribute("name", "NUM"); shadowBlock.AppendChild(numberField); xmlList.Push(block); } for (var i = 0; i < variableList.Length; i++) { if (Script.Get(Blockly.Blocks, "variables_get") != null) { // // item // var block = goog.dom.createDom("block"); block.SetAttribute("type", "variables_get"); if (Script.Get(Blockly.Blocks, "variables_set") != null) { block.SetAttribute("gap", "8"); } var field = goog.dom.createDom("field", null, variableList[i]); field.SetAttribute("name", "VAR"); block.AppendChild(field); xmlList.Push(block); } } } return xmlList; } /// /// Return a new variable name that is not yet being used. This will try to /// generate single letter variable names in the range 'i' to 'z' to start with. /// If no unique name is located it will try 'i' to 'z', 'a' to 'h', /// then 'i2' to 'z2' etc. Skip 'l'. /// /// The workspace to be unique in. /// New variable name. public string generateUniqueName(Workspace workspace) { var variableList = workspace.variableList; var newName = ""; if (variableList.Length != 0) { var nameSuffix = 1; var letters = "ijkmnopqrstuvwxyzabcdefgh"; // No 'l'. var letterIndex = 0; var potName = letters.CharAt(letterIndex); while (String.IsNullOrEmpty(newName)) { var inUse = false; for (var i = 0; i < variableList.Length; i++) { if (variableList[i].ToLower() == potName) { // This potential name is already used. inUse = true; break; } } if (inUse) { // Try the next potential name. letterIndex++; if (letterIndex == letters.Length) { // Reached the end of the character sequence so back to 'i'. // a new suffix. letterIndex = 0; nameSuffix++; } potName = letters.CharAt(letterIndex); if (nameSuffix > 1) { potName += nameSuffix; } } else { // We can use the current potential name. newName = potName; } } } else { newName = "i"; } return newName; } /// /// Create a new variable on the given workspace. /// /// The workspace on which to create the variable. /// A callback. It will /// return an acceptable new variable name, or null if change is to be /// aborted (cancel button), or undefined if an existing variable was chosen. public void createVariable(Workspace workspace, Func opt_callback = null) { Action promptAndCheckWithAlert = null; promptAndCheckWithAlert = new Action((defaultName) => { promptName(Msg.NEW_VARIABLE_TITLE, defaultName, (text) => { if (text != null) { if (workspace.variableIndexOf(text) != -1) { Blockly.alert(Msg.VARIABLE_ALREADY_EXISTS.Replace("%1", text.ToLower()), () => { promptAndCheckWithAlert(text); // Recurse }); } else { workspace.createVariable(text); if (opt_callback != null) { opt_callback(text); } } } else { // User canceled prompt without a value. if (opt_callback != null) { opt_callback(null); } } }); }); promptAndCheckWithAlert(""); } /// /// Prompt the user for a new variable name. /// /// The string of the prompt. /// The default value to show in the prompt's field. /// A callback. It will return the new /// variable name, or null if the user picked something illegal. public void promptName(string promptText, string defaultText, Action callback) { Blockly.prompt(promptText, defaultText, new Action((newVar) => { // Merge runs of whitespace. Strip leading and trailing whitespace. // Beyond this, all names are legal. if (newVar != null) { newVar = newVar.Replace(new Regex(@"[\s\xa0] +"), " ").Replace(new Regex("^ | $"), ""); if (newVar == Msg.RENAME_VARIABLE || newVar == Msg.NEW_VARIABLE) { // Ok, not ALL names are legal... newVar = null; } } callback(newVar); })); } } }